Added the necessery classes to handle symmetry objects/mirrors in symbols; What used to be SymbolPart is now SymbolShape, SymbolPart is a base class.
This should also pave the way for grouping. git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@526 0fc631ac-6414-0410-93d0-97cfa31319b6
@@ -413,6 +413,10 @@ label:
|
||||
|
||||
############################################################## Buttons/checkboxes/choices in the GUI
|
||||
button:
|
||||
# Editor
|
||||
edit symbol: Edit
|
||||
symbol gallery: Gallery
|
||||
|
||||
# Style panel
|
||||
use for all cards: Use for &all cards
|
||||
use custom styling options: Options &specific to this card
|
||||
@@ -491,10 +495,35 @@ title:
|
||||
|
||||
############################################################## Action (undo/redo) names
|
||||
action:
|
||||
typing: Typing
|
||||
backspace: Backspace
|
||||
delete: Delete
|
||||
change: Change %s
|
||||
# Text editor
|
||||
typing: Typing
|
||||
backspace: Backspace
|
||||
delete: Delete
|
||||
# Choice/color editors
|
||||
change: Change %s
|
||||
|
||||
# Symbol Actions
|
||||
move: Move %s
|
||||
rotate: Rotate %s
|
||||
shear: Shear %s
|
||||
scale: Resize %s
|
||||
add part: Add %s
|
||||
remove parts: Remove %s
|
||||
duplicate: Duplicate %s
|
||||
reorder parts: Reorder
|
||||
change combine mode:Change combine mode
|
||||
change shape name: Change shape name
|
||||
|
||||
# Symbol Part Actions
|
||||
convert to line: Convert to line
|
||||
convert to curve: Convert to curve
|
||||
lock point: Lock point
|
||||
move handle: Move handle
|
||||
move curve: Move curve
|
||||
add control point: Add control point
|
||||
delete point: Delete point
|
||||
delete points: Delete points
|
||||
|
||||
|
||||
############################################################## Error messages
|
||||
error:
|
||||
@@ -579,6 +608,10 @@ type:
|
||||
polygon: polygon
|
||||
star: star
|
||||
|
||||
point: point
|
||||
points: points
|
||||
handle: handle
|
||||
|
||||
|
||||
############################################################## Magic
|
||||
game:
|
||||
|
||||
@@ -8,12 +8,19 @@
|
||||
|
||||
#include <data/action/symbol.hpp>
|
||||
#include <data/action/symbol_part.hpp>
|
||||
#include <util/error.hpp>
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA SymbolPartCombine>);
|
||||
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA size_t >);
|
||||
DECLARE_TYPEOF_COLLECTION(pair<SymbolShapeP COMMA SymbolShapeCombine>);
|
||||
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA size_t >);
|
||||
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||
DECLARE_TYPEOF_COLLECTION(ControlPointP);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Utility
|
||||
|
||||
String action_name_for(const set<SymbolPartP>& parts, const String& action) {
|
||||
return format_string(action, parts.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Moving symbol parts
|
||||
|
||||
SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts)
|
||||
@@ -23,23 +30,31 @@ SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts)
|
||||
, snap(0)
|
||||
{
|
||||
// Determine min/max_pos
|
||||
FOR_EACH(p,parts) {
|
||||
min_pos = piecewise_min(min_pos, p->min_pos);
|
||||
max_pos = piecewise_max(max_pos, p->max_pos);
|
||||
FOR_EACH(p, parts) {
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
min_pos = piecewise_min(min_pos, s->min_pos);
|
||||
max_pos = piecewise_max(max_pos, s->max_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String SymbolPartMoveAction::getName(bool to_undo) const {
|
||||
return parts.size() == 1 ? _("Move shape") : _("Move shapes");
|
||||
return action_name_for(parts, _ACTION_("move"));
|
||||
}
|
||||
|
||||
void SymbolPartMoveAction::perform(bool to_undo) {
|
||||
// move the points back
|
||||
FOR_EACH(p, parts) {
|
||||
p->min_pos -= moved;
|
||||
p->max_pos -= moved;
|
||||
FOR_EACH(pnt, p->points) {
|
||||
pnt->pos -= moved;
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
s->min_pos -= moved;
|
||||
s->max_pos -= moved;
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos -= moved;
|
||||
}
|
||||
} else if (SymbolSymmetry* s = p->isSymbolSymmetry()) {
|
||||
s->center -= moved;
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
}
|
||||
moved = -moved;
|
||||
@@ -49,15 +64,10 @@ void SymbolPartMoveAction::move(const Vector2D& deltaDelta) {
|
||||
delta += deltaDelta;
|
||||
// Determine actual delta, possibly constrained and snapped
|
||||
Vector2D d = constrain_snap_vector_offset(min_pos, max_pos, delta, constrain, snap);
|
||||
Vector2D dd = d - moved;
|
||||
Vector2D dd = d - moved; // move this much more
|
||||
// Move each point by d
|
||||
FOR_EACH(p, parts) {
|
||||
p->min_pos += dd;
|
||||
p->max_pos += dd;
|
||||
FOR_EACH(pnt, p->points) {
|
||||
pnt->pos += dd;
|
||||
}
|
||||
}
|
||||
moved = -dd;
|
||||
perform(false); // (ab)use perform to move by +dd
|
||||
moved = d;
|
||||
}
|
||||
|
||||
@@ -71,13 +81,19 @@ SymbolPartMatrixAction::SymbolPartMatrixAction(const set<SymbolPartP>& parts, co
|
||||
void SymbolPartMatrixAction::transform(const Vector2D& mx, const Vector2D& my) {
|
||||
// Transform each point
|
||||
FOR_EACH(p, parts) {
|
||||
FOR_EACH(pnt, p->points) {
|
||||
pnt->pos = (pnt->pos - center).mul(mx,my) + center;
|
||||
pnt->delta_before = pnt->delta_before.mul(mx,my);
|
||||
pnt->delta_after = pnt->delta_after .mul(mx,my);
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos = (pnt->pos - center).mul(mx,my) + center;
|
||||
pnt->delta_before = pnt->delta_before.mul(mx,my);
|
||||
pnt->delta_after = pnt->delta_after .mul(mx,my);
|
||||
}
|
||||
// bounds change after transforming
|
||||
s->calculateBounds();
|
||||
} else if (SymbolSymmetry* s = p->isSymbolSymmetry()) {
|
||||
s->handle = s->handle.mul(mx,my);
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
// bounds change after transforming
|
||||
p->calculateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +105,7 @@ SymbolPartRotateAction::SymbolPartRotateAction(const set<SymbolPartP>& parts, co
|
||||
{}
|
||||
|
||||
String SymbolPartRotateAction::getName(bool to_undo) const {
|
||||
return parts.size() == 1 ? _("Rotate shape") : _("Rotate shapes");
|
||||
return action_name_for(parts, _ACTION_("rotate"));
|
||||
}
|
||||
|
||||
void SymbolPartRotateAction::perform(bool to_undo) {
|
||||
@@ -128,7 +144,7 @@ SymbolPartShearAction::SymbolPartShearAction(const set<SymbolPartP>& parts, cons
|
||||
{}
|
||||
|
||||
String SymbolPartShearAction::getName(bool to_undo) const {
|
||||
return parts.size() == 1 ? _("Shear shape") : _("Shear shapes");
|
||||
return action_name_for(parts, _ACTION_("shear"));
|
||||
}
|
||||
|
||||
void SymbolPartShearAction::perform(bool to_undo) {
|
||||
@@ -169,89 +185,104 @@ SymbolPartScaleAction::SymbolPartScaleAction(const set<SymbolPartP>& parts, int
|
||||
, snap(0)
|
||||
{
|
||||
// Find min and max coordinates
|
||||
oldMin = Vector2D::infinity();
|
||||
Vector2D oldMax = -Vector2D::infinity();
|
||||
old_min = Vector2D( 1e6, 1e6);
|
||||
Vector2D old_max (-1e6,-1e6);
|
||||
FOR_EACH(p, parts) {
|
||||
oldMin = piecewise_min(oldMin, p->min_pos);
|
||||
oldMax = piecewise_max(oldMax, p->max_pos);
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
old_min = piecewise_min(old_min, s->min_pos);
|
||||
old_max = piecewise_max(old_max, s->max_pos);
|
||||
}
|
||||
}
|
||||
// new == old
|
||||
newMin = newRealMin = oldMin;
|
||||
newSize = newRealSize = oldSize = oldMax - oldMin;
|
||||
new_min = new_real_min = old_min;
|
||||
new_size = new_real_size = old_size = old_max - old_min;
|
||||
}
|
||||
|
||||
String SymbolPartScaleAction::getName(bool to_undo) const {
|
||||
return parts.size() == 1 ? _("Scale shape") : _("Scale shapes");
|
||||
return action_name_for(parts, _ACTION_("scale"));
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::perform(bool to_undo) {
|
||||
swap(oldMin, newMin);
|
||||
swap(oldSize, newSize);
|
||||
swap(old_min, new_min);
|
||||
swap(old_size, new_size);
|
||||
transformAll();
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::move(const Vector2D& deltaMin, const Vector2D& deltaMax) {
|
||||
newRealMin += deltaMin;
|
||||
newRealSize += deltaMax - deltaMin;
|
||||
void SymbolPartScaleAction::move(const Vector2D& delta_min, const Vector2D& delta_max) {
|
||||
new_real_min += delta_min;
|
||||
new_real_size += delta_max - delta_min;
|
||||
update();
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::update() {
|
||||
// Move each point so the range <oldMin...oldMax> maps to <newMin...newMax>
|
||||
// we have already moved to the current <newMin...newMax>
|
||||
Vector2D tmpMin = oldMin, tmpSize = oldSize; // the size before any scaling
|
||||
oldMin = newMin; oldSize = newSize; // the size before this move
|
||||
// Move each point so the range [old_min...old_max] maps to [new_min...new_max]
|
||||
// we have already moved to the current [new_min...new_max]
|
||||
Vector2D tmp_min = old_min, tmp_size = old_size; // the size before any scaling
|
||||
old_min = new_min; old_size = new_size; // the size before this move
|
||||
// the size after the move
|
||||
newMin = newRealMin; newSize = newRealSize;
|
||||
new_min = new_real_min; new_size = new_real_size;
|
||||
if (constrain && scaleX != 0 && scaleY != 0) {
|
||||
Vector2D scale = newSize.div(tmpSize);
|
||||
Vector2D scale = new_size.div(tmp_size);
|
||||
scale = constrain_vector(scale, true, true);
|
||||
newSize = tmpSize.mul(scale);
|
||||
newMin += (newRealSize - newSize).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0));
|
||||
new_size = tmp_size.mul(scale);
|
||||
new_min += (new_real_size - new_size).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0));
|
||||
// TODO : snapping
|
||||
} else if (snap >= 0) {
|
||||
if (scaleX + scaleY < 0) {
|
||||
newMin = snap_vector(newMin, snap);
|
||||
newSize += newRealMin - newMin;
|
||||
new_min = snap_vector(new_min, snap);
|
||||
new_size += new_real_min - new_min;
|
||||
} else {
|
||||
Vector2D newMax = snap_vector(newMin + newSize, snap);
|
||||
newSize = newMax - newMin;
|
||||
Vector2D new_max = snap_vector(new_min + new_size, snap);
|
||||
new_size = new_max - new_min;
|
||||
}
|
||||
}
|
||||
// now move all points
|
||||
transformAll();
|
||||
// restore oldMin/Size
|
||||
oldMin = tmpMin; oldSize = tmpSize;
|
||||
// restore old_min/size
|
||||
old_min = tmp_min; old_size = tmp_size;
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::transformAll() {
|
||||
Vector2D scale = newSize.div(oldSize);
|
||||
Vector2D scale = new_size.div(old_size);
|
||||
FOR_EACH(p, parts) {
|
||||
p->min_pos = transform(p->min_pos);
|
||||
p->max_pos = transform(p->max_pos);
|
||||
// make sure that max >= min
|
||||
if (p->min_pos.x > p->max_pos.x) swap(p->min_pos.x, p->max_pos.x);
|
||||
if (p->min_pos.y > p->max_pos.y) swap(p->min_pos.y, p->max_pos.y);
|
||||
// scale all points
|
||||
FOR_EACH(pnt, p->points) {
|
||||
pnt->pos = transform(pnt->pos);
|
||||
// also scale handles
|
||||
pnt->delta_before = pnt->delta_before.mul(scale);
|
||||
pnt->delta_after = pnt->delta_after .mul(scale);
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
s->min_pos = transform(s->min_pos);
|
||||
s->max_pos = transform(s->max_pos);
|
||||
// make sure that max >= min
|
||||
if (s->min_pos.x > s->max_pos.x) swap(s->min_pos.x, s->max_pos.x);
|
||||
if (s->min_pos.y > s->max_pos.y) swap(s->min_pos.y, s->max_pos.y);
|
||||
// scale all points
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos = transform(pnt->pos);
|
||||
// also scale handles
|
||||
pnt->delta_before = pnt->delta_before.mul(scale);
|
||||
pnt->delta_after = pnt->delta_after .mul(scale);
|
||||
}
|
||||
} else if (SymbolSymmetry* s = p->isSymbolSymmetry()) {
|
||||
throw "TODO";
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2D SymbolPartScaleAction::transform(const Vector2D& v) {
|
||||
// TODO: prevent div by 0
|
||||
return (v - old_min).div(old_size).mul(new_size) + new_min;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change combine mode
|
||||
|
||||
CombiningModeAction::CombiningModeAction(const set<SymbolPartP>& parts, SymbolPartCombine mode) {
|
||||
CombiningModeAction::CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode) {
|
||||
FOR_EACH(p, parts) {
|
||||
this->parts.push_back(make_pair(p,mode));
|
||||
if (p->isSymbolShape()) {
|
||||
this->parts.push_back(make_pair(static_pointer_cast<SymbolShape>(p),mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String CombiningModeAction::getName(bool to_undo) const {
|
||||
return _("Change combine mode");
|
||||
return _ACTION_("change combine mode");
|
||||
}
|
||||
|
||||
void CombiningModeAction::perform(bool to_undo) {
|
||||
@@ -263,15 +294,15 @@ void CombiningModeAction::perform(bool to_undo) {
|
||||
// ----------------------------------------------------------------------------- : Change name
|
||||
|
||||
SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name)
|
||||
: part(part), partName(name)
|
||||
: part(part), part_name(name)
|
||||
{}
|
||||
|
||||
String SymbolPartNameAction::getName(bool to_undo) const {
|
||||
return _("Change shape name");
|
||||
return _ACTION_("change shape name");
|
||||
}
|
||||
|
||||
void SymbolPartNameAction::perform(bool to_undo) {
|
||||
swap(part->name, partName);
|
||||
swap(part->name, part_name);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add symbol part
|
||||
@@ -281,7 +312,7 @@ AddSymbolPartAction::AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part
|
||||
{}
|
||||
|
||||
String AddSymbolPartAction::getName(bool to_undo) const {
|
||||
return _("Add ") + part->name;
|
||||
return format_string(_ACTION_("add"), part->name);
|
||||
}
|
||||
|
||||
void AddSymbolPartAction::perform(bool to_undo) {
|
||||
@@ -308,7 +339,7 @@ RemoveSymbolPartsAction::RemoveSymbolPartsAction(Symbol& symbol, const set<Symbo
|
||||
}
|
||||
|
||||
String RemoveSymbolPartsAction::getName(bool to_undo) const {
|
||||
return removals.size() == 1 ? _("Remove shape") : _("Remove shapes");
|
||||
return format_string(_ACTION_("remove parts"), removals.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
|
||||
}
|
||||
|
||||
void RemoveSymbolPartsAction::perform(bool to_undo) {
|
||||
@@ -346,7 +377,7 @@ DuplicateSymbolPartsAction::DuplicateSymbolPartsAction(Symbol& symbol, const set
|
||||
}
|
||||
|
||||
String DuplicateSymbolPartsAction::getName(bool to_undo) const {
|
||||
return duplications.size() == 1 ? _("Duplicate shape") : _("Duplicate shapes");
|
||||
return format_string(_ACTION_("duplicate"), duplications.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
|
||||
}
|
||||
|
||||
void DuplicateSymbolPartsAction::perform(bool to_undo) {
|
||||
@@ -380,7 +411,7 @@ ReorderSymbolPartsAction::ReorderSymbolPartsAction(Symbol& symbol, size_t part_i
|
||||
{}
|
||||
|
||||
String ReorderSymbolPartsAction::getName(bool to_undo) const {
|
||||
return _("Reorder");
|
||||
return _ACTION_("reorder parts");
|
||||
}
|
||||
|
||||
void ReorderSymbolPartsAction::perform(bool to_undo) {
|
||||
|
||||
@@ -63,7 +63,7 @@ class SymbolPartMatrixAction : public SymbolPartAction {
|
||||
/// Perform the transformation using the given matrix
|
||||
void transform(const Vector2D& mx, const Vector2D& my);
|
||||
|
||||
set<SymbolPartP> parts; ///< Parts to transform
|
||||
set<SymbolPartP> parts; ///< Parts to transform
|
||||
Vector2D center; ///< Center to transform around
|
||||
};
|
||||
|
||||
@@ -122,22 +122,20 @@ class SymbolPartScaleAction : public SymbolPartAction {
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Change min and max coordinates
|
||||
void move(const Vector2D& deltaMin, const Vector2D& deltaMax);
|
||||
void move(const Vector2D& delta_min, const Vector2D& delta_max);
|
||||
/// Update the action's effect
|
||||
void update();
|
||||
|
||||
private:
|
||||
set<SymbolPartP> parts; ///< Parts to scale
|
||||
Vector2D oldMin, oldSize; ///< the original pos/size
|
||||
Vector2D newRealMin, newRealSize; ///< the target pos/sizevoid shearBy(const Vector2D& shear)
|
||||
Vector2D newMin, newSize; ///< the target pos/size after applying constrains
|
||||
int scaleX, scaleY; ///< to what corner are we attached?
|
||||
set<SymbolPartP> parts; ///< Parts to scale
|
||||
Vector2D old_min, old_size; ///< the original pos/size
|
||||
Vector2D new_real_min, new_real_size; ///< the target pos/sizevoid shearBy(const Vector2D& shear)
|
||||
Vector2D new_min, new_size; ///< the target pos/size after applying constrains
|
||||
int scaleX, scaleY; ///< to what corner are we attached?
|
||||
/// Transform everything in the parts
|
||||
void transformAll();
|
||||
/// Transform a single vector
|
||||
inline Vector2D transform(const Vector2D& v) {
|
||||
return (v - oldMin).div(oldSize).mul(newSize) + newMin;
|
||||
}
|
||||
inline Vector2D transform(const Vector2D& v);
|
||||
public:
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
@@ -148,13 +146,14 @@ class SymbolPartScaleAction : public SymbolPartAction {
|
||||
/// Change the name of a symbol part
|
||||
class CombiningModeAction : public SymbolPartListAction {
|
||||
public:
|
||||
CombiningModeAction(const set<SymbolPartP>& parts, SymbolPartCombine mode);
|
||||
// All parts must be SymbolParts
|
||||
CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
vector<pair<SymbolPartP,SymbolPartCombine> > parts; ///< Affected parts with new combining modes
|
||||
vector<pair<SymbolShapeP,SymbolShapeCombine> > parts; ///< Affected parts with new combining modes
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change name
|
||||
@@ -168,8 +167,8 @@ class SymbolPartNameAction : public SymbolPartListAction {
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
SymbolPartP part; ///< Affected part
|
||||
String partName; ///< New name
|
||||
SymbolPartP part; ///< Affected part
|
||||
String part_name; ///< New name
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add symbol part
|
||||
@@ -183,8 +182,8 @@ class AddSymbolPartAction : public SymbolPartListAction {
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
Symbol& symbol; ///< Symbol to add the part to
|
||||
SymbolPartP part; ///< Part to add
|
||||
Symbol& symbol; ///< Symbol to add the part to
|
||||
SymbolPartP part; ///< Part to add
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Remove symbol part
|
||||
|
||||
@@ -80,6 +80,10 @@ Vector2D constrain_snap_vector_offset(const Vector2D& off1, const Vector2D& off2
|
||||
return dd;
|
||||
}
|
||||
|
||||
String action_name_for(const set<ControlPointP>& points, const String& action) {
|
||||
return format_string(action, points.size() == 1 ? _TYPE_("point") : _TYPE_("points"));
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Move control point
|
||||
|
||||
@@ -95,7 +99,7 @@ ControlPointMoveAction::ControlPointMoveAction(const set<ControlPointP>& points)
|
||||
}
|
||||
|
||||
String ControlPointMoveAction::getName(bool to_undo) const {
|
||||
return points.size() == 1 ? _("Move point") : _("Move points");
|
||||
return action_name_for(points, _ACTION_("move"));
|
||||
}
|
||||
|
||||
void ControlPointMoveAction::perform(bool to_undo) {
|
||||
@@ -126,7 +130,7 @@ HandleMoveAction::HandleMoveAction(const SelectedHandle& handle)
|
||||
{}
|
||||
|
||||
String HandleMoveAction::getName(bool to_undo) const {
|
||||
return _("Move handle");
|
||||
return _ACTION_("move handle");
|
||||
}
|
||||
|
||||
void HandleMoveAction::perform(bool to_undo) {
|
||||
@@ -171,8 +175,8 @@ SegmentModeAction::SegmentModeAction(const ControlPointP& p1, const ControlPoint
|
||||
}
|
||||
String SegmentModeAction::getName(bool to_undo) const {
|
||||
SegmentMode mode = to_undo ? point1.point->segment_after : point1.other.segment_after;
|
||||
if (mode == SEGMENT_LINE) return _("Convert to line");
|
||||
else return _("Convert to curve");
|
||||
if (mode == SEGMENT_LINE) return _ACTION_("convert to line");
|
||||
else return _ACTION_("convert to curve");
|
||||
}
|
||||
|
||||
void SegmentModeAction::perform(bool to_undo) {
|
||||
@@ -191,7 +195,7 @@ LockModeAction::LockModeAction(const ControlPointP& p, LockMode lock)
|
||||
}
|
||||
|
||||
String LockModeAction::getName(bool to_undo) const {
|
||||
return _("Lock point");
|
||||
return _ACTION_("lock point");
|
||||
}
|
||||
|
||||
void LockModeAction::perform(bool to_undo) {
|
||||
@@ -206,7 +210,7 @@ CurveDragAction::CurveDragAction(const ControlPointP& point1, const ControlPoint
|
||||
{}
|
||||
|
||||
String CurveDragAction::getName(bool to_undo) const {
|
||||
return _("Move curve");
|
||||
return _ACTION_("move curve");
|
||||
}
|
||||
|
||||
void CurveDragAction::perform(bool to_undo) {
|
||||
@@ -239,12 +243,12 @@ void CurveDragAction::move(const Vector2D& delta, double t) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add control point
|
||||
|
||||
ControlPointAddAction::ControlPointAddAction(const SymbolPartP& part, UInt insert_after, double t)
|
||||
: part(part)
|
||||
ControlPointAddAction::ControlPointAddAction(const SymbolShapeP& shape, UInt insert_after, double t)
|
||||
: shape(shape)
|
||||
, new_point(new ControlPoint())
|
||||
, insert_after(insert_after)
|
||||
, point1(part->getPoint(insert_after))
|
||||
, point2(part->getPoint(insert_after + 1))
|
||||
, point1(shape->getPoint(insert_after))
|
||||
, point2(shape->getPoint(insert_after + 1))
|
||||
{
|
||||
// calculate new point
|
||||
if (point1.other.segment_after == SEGMENT_CURVE) {
|
||||
@@ -265,14 +269,14 @@ ControlPointAddAction::ControlPointAddAction(const SymbolPartP& part, UInt inser
|
||||
}
|
||||
|
||||
String ControlPointAddAction::getName(bool to_undo) const {
|
||||
return _("Add control point");
|
||||
return _ACTION_("add control point");
|
||||
}
|
||||
|
||||
void ControlPointAddAction::perform(bool to_undo) {
|
||||
if (to_undo) { // remove the point
|
||||
part->points.erase( part->points.begin() + insert_after + 1);
|
||||
shape->points.erase( shape->points.begin() + insert_after + 1);
|
||||
} else {
|
||||
part->points.insert(part->points.begin() + insert_after + 1, new_point);
|
||||
shape->points.insert(shape->points.begin() + insert_after + 1, new_point);
|
||||
}
|
||||
// update points before/after
|
||||
point1.perform();
|
||||
@@ -291,24 +295,24 @@ double ssqrt(double x) {
|
||||
// Remove a single control point
|
||||
class SinglePointRemoveAction : public Action, public IntrusivePtrBase<SinglePointRemoveAction> {
|
||||
public:
|
||||
SinglePointRemoveAction(const SymbolPartP& part, UInt position);
|
||||
SinglePointRemoveAction(const SymbolShapeP& shape, UInt position);
|
||||
|
||||
virtual String getName(bool to_undo) const { return _("Delete point"); }
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
SymbolPartP part;
|
||||
SymbolShapeP shape;
|
||||
UInt position;
|
||||
ControlPointP point; ///< Removed point
|
||||
ControlPointUpdate point1, point2; ///< Points before/after
|
||||
};
|
||||
|
||||
SinglePointRemoveAction::SinglePointRemoveAction(const SymbolPartP& part, UInt position)
|
||||
: part(part)
|
||||
SinglePointRemoveAction::SinglePointRemoveAction(const SymbolShapeP& shape, UInt position)
|
||||
: shape(shape)
|
||||
, position(position)
|
||||
, point (part->getPoint(position))
|
||||
, point1(part->getPoint(position - 1))
|
||||
, point2(part->getPoint(position + 1))
|
||||
, point (shape->getPoint(position))
|
||||
, point1(shape->getPoint(position - 1))
|
||||
, point2(shape->getPoint(position + 1))
|
||||
{
|
||||
if (point1.other.segment_after == SEGMENT_CURVE || point2.other.segment_before == SEGMENT_CURVE) {
|
||||
// try to preserve curve
|
||||
@@ -359,10 +363,10 @@ SinglePointRemoveAction::SinglePointRemoveAction(const SymbolPartP& part, UInt p
|
||||
void SinglePointRemoveAction::perform(bool to_undo) {
|
||||
if (to_undo) {
|
||||
// reinsert the point
|
||||
part->points.insert(part->points.begin() + position, point);
|
||||
shape->points.insert(shape->points.begin() + position, point);
|
||||
} else {
|
||||
// remove the point
|
||||
part->points.erase( part->points.begin() + position);
|
||||
shape->points.erase( shape->points.begin() + position);
|
||||
}
|
||||
// update points around removed point
|
||||
point1.perform();
|
||||
@@ -373,12 +377,12 @@ DECLARE_POINTER_TYPE(SinglePointRemoveAction);
|
||||
DECLARE_TYPEOF_COLLECTION(SinglePointRemoveActionP);
|
||||
|
||||
|
||||
// Remove a set of points from a symbol part.
|
||||
// Remove a set of points from a symbol shape.
|
||||
// Internally represented as a list of Single Point Remove Actions.
|
||||
// Not all points mat be removed, at least two points must remain.
|
||||
class ControlPointRemoveAction : public Action {
|
||||
public:
|
||||
ControlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete);
|
||||
ControlPointRemoveAction(const SymbolShapeP& shape, const set<ControlPointP>& to_delete);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
@@ -387,20 +391,20 @@ class ControlPointRemoveAction : public Action {
|
||||
vector<SinglePointRemoveActionP> removals;
|
||||
};
|
||||
|
||||
ControlPointRemoveAction::ControlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete) {
|
||||
ControlPointRemoveAction::ControlPointRemoveAction(const SymbolShapeP& shape, const set<ControlPointP>& to_delete) {
|
||||
int index = 0;
|
||||
// find points to remove, in reverse order
|
||||
FOR_EACH(point, part->points) {
|
||||
if (toDelete.find(point) != toDelete.end()) {
|
||||
FOR_EACH(point, shape->points) {
|
||||
if (to_delete.find(point) != to_delete.end()) {
|
||||
// remove this point
|
||||
removals.push_back(new_intrusive2<SinglePointRemoveAction>(part, index));
|
||||
removals.push_back(new_intrusive2<SinglePointRemoveAction>(shape, index));
|
||||
}
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
String ControlPointRemoveAction::getName(bool to_undo) const {
|
||||
return removals.size() == 1 ? _("Delete point") : _("Delete points");
|
||||
return removals.size() == 1 ? _ACTION_("delete point") : _ACTION_("delete points");
|
||||
}
|
||||
|
||||
void ControlPointRemoveAction::perform(bool to_undo) {
|
||||
@@ -414,12 +418,12 @@ void ControlPointRemoveAction::perform(bool to_undo) {
|
||||
}
|
||||
|
||||
|
||||
Action* controlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete) {
|
||||
if (part->points.size() - toDelete.size() < 2) {
|
||||
Action* control_point_remove_action(const SymbolShapeP& shape, const set<ControlPointP>& to_delete) {
|
||||
if (shape->points.size() - to_delete.size() < 2) {
|
||||
// TODO : remove part?
|
||||
//new_intrusive<ControlPointRemoveAllAction>(part);
|
||||
return 0; // no action
|
||||
} else {
|
||||
return new ControlPointRemoveAction(part, toDelete);
|
||||
return new ControlPointRemoveAction(shape, to_delete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
/** @file data/action/symbol_part.hpp
|
||||
*
|
||||
* Actions operating on the insides of SymbolParts (ControlPoints and the like).
|
||||
* Actions operating on the insides of SymbolParts/SymbolShapes (ControlPoints and the like).
|
||||
*/
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
@@ -147,11 +147,11 @@ class CurveDragAction : public SegmentModeAction {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add control point
|
||||
|
||||
/// Insert a new point in a symbol part
|
||||
/// Insert a new point in a symbol shape
|
||||
class ControlPointAddAction : public Action {
|
||||
public:
|
||||
/// Insert a new point in part, after position insertAfter_, at the time t on the segment
|
||||
ControlPointAddAction(const SymbolPartP& part, UInt insert_after, double t);
|
||||
/// Insert a new point in shape, after position insertAfter_, at the time t on the segment
|
||||
ControlPointAddAction(const SymbolShapeP& shape, UInt insert_after, double t);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
@@ -159,17 +159,17 @@ class ControlPointAddAction : public Action {
|
||||
inline ControlPointP getNewPoint() const { return new_point; }
|
||||
|
||||
private:
|
||||
SymbolPartP part; ///< SymbolPart we are in
|
||||
ControlPointP new_point; ///< The point to insert
|
||||
SymbolShapeP shape; ///< SymbolShape we are in
|
||||
ControlPointP new_point; ///< The point to insert
|
||||
UInt insert_after; ///< Insert after index .. in the array
|
||||
ControlPointUpdate point1, point2; ///< Update the points around the new point
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Remove control point
|
||||
|
||||
/// Action that removes any number of points from a symbol part
|
||||
/// TODO: If less then 3 points are left removes the entire part?
|
||||
Action* controlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete);
|
||||
/// Action that removes any number of points from a symbol shape
|
||||
/// TODO: If less then 3 points are left removes the entire shape?
|
||||
Action* control_point_remove_action(const SymbolShapeP& shape, const set<ControlPointP>& to_delete);
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
@@ -121,7 +121,7 @@ struct ImageData {
|
||||
}
|
||||
};
|
||||
|
||||
bool find_symbol_part_start(const ImageData& data, int& x_out, int& y_out) {
|
||||
bool find_symbol_shape_start(const ImageData& data, int& x_out, int& y_out) {
|
||||
for (int x = 0 ; x < data.width ; ++x) {
|
||||
for (int y = 0 ; y < data.height ; ++y) {
|
||||
if (data(x, y) == FULL && data(x, y-1) == EMPTY) {
|
||||
@@ -136,13 +136,13 @@ bool find_symbol_part_start(const ImageData& data, int& x_out, int& y_out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SymbolPartP read_symbol_part(const ImageData& data) {
|
||||
SymbolShapeP read_symbol_shape(const ImageData& data) {
|
||||
// find start point
|
||||
int xs, ys;
|
||||
if (!find_symbol_part_start(data, xs, ys)) return SymbolPartP();
|
||||
if (!find_symbol_shape_start(data, xs, ys)) return SymbolShapeP();
|
||||
data(xs, ys) |= MARKED;
|
||||
|
||||
SymbolPartP part(new SymbolPart);
|
||||
SymbolShapeP shape(new SymbolShape);
|
||||
|
||||
// walk around, clockwise
|
||||
xs += 1; // start right of the found point, otherwise last_move might think we came from above
|
||||
@@ -178,12 +178,12 @@ SymbolPartP read_symbol_part(const ImageData& data) {
|
||||
throw InternalError(_("in the ground/air"));
|
||||
}
|
||||
|
||||
// add to part and place a mark
|
||||
part->points.push_back(new_intrusive2<ControlPoint>(
|
||||
// add to shape and place a mark
|
||||
shape->points.push_back(new_intrusive2<ControlPoint>(
|
||||
double(x) / data.width,
|
||||
double(y) / data.height
|
||||
));
|
||||
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the part
|
||||
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the shape
|
||||
last_move = (x + y) - (old_x + old_y);
|
||||
old_x = x;
|
||||
old_y = y;
|
||||
@@ -191,11 +191,11 @@ SymbolPartP read_symbol_part(const ImageData& data) {
|
||||
|
||||
// are we on the inside or the outside?
|
||||
if (data(x-2,y-1) & FULL) {
|
||||
part->combine = PART_SUBTRACT;
|
||||
shape->combine = SYMBOL_COMBINE_SUBTRACT;
|
||||
} else {
|
||||
part->combine = PART_MERGE;
|
||||
shape->combine = SYMBOL_COMBINE_MERGE;
|
||||
}
|
||||
return part;
|
||||
return shape;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,13 +204,13 @@ SymbolP image_to_symbol(Image& img) {
|
||||
// 1. threshold the image
|
||||
greyscale(img);
|
||||
threshold(img.GetData(), w, h);
|
||||
// 2. read as many symbol parts as we can
|
||||
// 2. read as many symbol shapes as we can
|
||||
ImageData data = {w,h,img.GetData()};
|
||||
SymbolP symbol(new Symbol);
|
||||
while (true) {
|
||||
SymbolPartP part = read_symbol_part(data);
|
||||
if (!part) break;
|
||||
symbol->parts.push_back(part);
|
||||
SymbolShapeP shape = read_symbol_shape(data);
|
||||
if (!shape) break;
|
||||
symbol->parts.push_back(shape);
|
||||
}
|
||||
reverse(symbol->parts.begin(), symbol->parts.end());
|
||||
return symbol;
|
||||
@@ -238,11 +238,11 @@ SymbolP import_symbol(Image& img) {
|
||||
/// Finds corners, marks corners as LOCK_FREE, non-corners as LOCK_DIR
|
||||
/** A corner is a point that has an angle between tangent greater then a treshold
|
||||
*/
|
||||
void mark_corners(SymbolPart& part) {
|
||||
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||
ControlPoint& current = *part.getPoint(i);
|
||||
Vector2D before = .6 * part.getPoint(i-1)->pos + .2 * part.getPoint(i-2)->pos + .1 * part.getPoint(i-3)->pos + .1 * part.getPoint(i-4)->pos;
|
||||
Vector2D after = .6 * part.getPoint(i+1)->pos + .2 * part.getPoint(i+2)->pos + .1 * part.getPoint(i+3)->pos + .1 * part.getPoint(i+4)->pos;
|
||||
void mark_corners(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& current = *shape.getPoint(i);
|
||||
Vector2D before = .6 * shape.getPoint(i-1)->pos + .2 * shape.getPoint(i-2)->pos + .1 * shape.getPoint(i-3)->pos + .1 * shape.getPoint(i-4)->pos;
|
||||
Vector2D after = .6 * shape.getPoint(i+1)->pos + .2 * shape.getPoint(i+2)->pos + .1 * shape.getPoint(i+3)->pos + .1 * shape.getPoint(i+4)->pos;
|
||||
before = (before - current.pos).normalized();
|
||||
after = (after - current.pos).normalized();
|
||||
if (before.dot(after) >= -0.25f) {
|
||||
@@ -271,10 +271,10 @@ void mark_corners(SymbolPart& part) {
|
||||
* Where these two lines (one for each corner) intersect,
|
||||
* is the merged corner. If it is too far away, don't merge
|
||||
*/
|
||||
void merge_corners(SymbolPart& part) {
|
||||
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||
ControlPoint& cur = *part.getPoint(i);
|
||||
ControlPoint& prev = *part.getPoint(i - 1);
|
||||
void merge_corners(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i - 1);
|
||||
if (prev.lock != LOCK_FREE || cur.lock != LOCK_FREE) continue;
|
||||
// step 1. find tangent lines: try tangent lines to the first point, the second, etc.
|
||||
// and take the one that has the largest angle with ab, i.e. the smallest dot,
|
||||
@@ -283,8 +283,8 @@ void merge_corners(SymbolPart& part) {
|
||||
double min_a_dot = 1e100, min_b_dot = 1e100;
|
||||
Vector2D a, b;
|
||||
for (int j = 0 ; j < 4 ; ++j) {
|
||||
Vector2D a_ = (part.getPoint(i-j-1)->pos - prev.pos).normalized();
|
||||
Vector2D b_ = (part.getPoint(i+j)->pos - cur.pos).normalized();
|
||||
Vector2D a_ = (shape.getPoint(i-j-1)->pos - prev.pos).normalized();
|
||||
Vector2D b_ = (shape.getPoint(i+j)->pos - cur.pos).normalized();
|
||||
double a_dot = a_.dot(ab);
|
||||
double b_dot = -b_.dot(ab);
|
||||
if (a_dot < min_a_dot) {
|
||||
@@ -306,22 +306,22 @@ void merge_corners(SymbolPart& part) {
|
||||
// if so, then the intersection point is the merged point
|
||||
if (t >= 0 && t < 20.0) {
|
||||
prev.pos += a * -t;
|
||||
part.points.erase(part.points.begin() + i);
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Avarage/'blur' a symbol part
|
||||
void avarage(SymbolPart& part) {
|
||||
/// Avarage/'blur' a symbol shape
|
||||
void avarage(SymbolShape& shape) {
|
||||
// create a copy of the points
|
||||
vector<Vector2D> old_points;
|
||||
FOR_EACH(p, part.points) {
|
||||
FOR_EACH(p, shape.points) {
|
||||
old_points.push_back(p->pos);
|
||||
}
|
||||
// avarage points
|
||||
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||
ControlPoint& p = *part.getPoint(i);
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& p = *shape.getPoint(i);
|
||||
if (p.lock == LOCK_DIR) {
|
||||
p.pos = .25 * old_points[mod(i-1, old_points.size())]
|
||||
+ .50 * p.pos
|
||||
@@ -330,29 +330,29 @@ void avarage(SymbolPart& part) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a symbol part to curves
|
||||
void convert_to_curves(SymbolPart& part) {
|
||||
/// Convert a symbol shape to curves
|
||||
void convert_to_curves(SymbolShape& shape) {
|
||||
// mark all segments as curves
|
||||
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||
ControlPoint& cur = *part.getPoint(i);
|
||||
ControlPoint& next = *part.getPoint(i + 1);
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
cur.segment_after = SEGMENT_CURVE;
|
||||
cur.segment_before = SEGMENT_CURVE;
|
||||
cur.delta_after = (next.pos - cur.pos) / 3.0;
|
||||
next.delta_before = (cur.pos - next.pos) / 3.0;
|
||||
}
|
||||
// make the curves smooth by enforcing direction constraints
|
||||
FOR_EACH(p, part.points) {
|
||||
FOR_EACH(p, shape.points) {
|
||||
p->onUpdateLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert almost straight curves in a symbol part to lines
|
||||
void straighten(SymbolPart& part) {
|
||||
/// Convert almost straight curves in a symbol shape to lines
|
||||
void straighten(SymbolShape& shape) {
|
||||
const double treshold = 0.2;
|
||||
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||
ControlPoint& cur = *part.getPoint(i);
|
||||
ControlPoint& next = *part.getPoint(i + 1);
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
Vector2D ab = (next.pos - cur.pos).normalized();
|
||||
Vector2D aa = cur.delta_after.normalized();
|
||||
Vector2D bb = next.delta_before.normalized();
|
||||
@@ -368,37 +368,37 @@ void straighten(SymbolPart& part) {
|
||||
}
|
||||
|
||||
/// Remove unneeded points between straight lines
|
||||
void merge_lines(SymbolPart& part) {
|
||||
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||
ControlPoint& cur = *part.getPoint(i);
|
||||
void merge_lines(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
if (cur.segment_before != cur.segment_after) continue;
|
||||
Vector2D a = part.getPoint(i-1)->pos, b = cur.pos, c = part.getPoint(i+1)->pos;
|
||||
Vector2D a = shape.getPoint(i-1)->pos, b = cur.pos, c = shape.getPoint(i+1)->pos;
|
||||
Vector2D ab = (a-b).normalized();
|
||||
Vector2D bc = (b-c).normalized();
|
||||
double angle_len = abs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr();
|
||||
bool keep = angle_len >= .0001;
|
||||
if (!keep) {
|
||||
part.points.erase(part.points.begin() + i);
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double cost_of_point_removal(SymbolPart& part, int i);
|
||||
void remove_point(SymbolPart& part, int i);
|
||||
double cost_of_point_removal(SymbolShape& shape, int i);
|
||||
void remove_point(SymbolShape& shape, int i);
|
||||
|
||||
/// Simplify a symbol part by removing points
|
||||
/// Simplify a symbol shape by removing points
|
||||
/** Always remove the point with the lowest cost,
|
||||
* stop when the cost becomes too high
|
||||
*/
|
||||
void remove_points(SymbolPart& part) {
|
||||
void remove_points(SymbolShape& shape) {
|
||||
const double treshold = 0.0002; // maximum cost
|
||||
while (true) {
|
||||
// Find the point with the lowest cost of removal
|
||||
int best = -1;
|
||||
double best_cost = 1e100;
|
||||
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
|
||||
double cost = cost_of_point_removal(part, i);
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
double cost = cost_of_point_removal(shape, i);
|
||||
if (cost < best_cost) {
|
||||
best_cost = cost;
|
||||
best = i;
|
||||
@@ -406,14 +406,14 @@ void remove_points(SymbolPart& part) {
|
||||
}
|
||||
if (best_cost > treshold) break;
|
||||
// ... and remove it
|
||||
remove_point(part, best);
|
||||
remove_point(shape, best);
|
||||
}
|
||||
}
|
||||
/// Cost of removing point i from a symbol part
|
||||
double cost_of_point_removal(SymbolPart& part, int i) {
|
||||
ControlPoint& cur = *part.getPoint(i);
|
||||
ControlPoint& prev = *part.getPoint(i-1);
|
||||
ControlPoint& next = *part.getPoint(i+1);
|
||||
/// Cost of removing point i from a symbol shape
|
||||
double cost_of_point_removal(SymbolShape& shape, int i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
if (cur.lock != LOCK_DIR) return 1e100; // don't remove corners
|
||||
|
||||
Vector2D before = cur.delta_before;
|
||||
@@ -430,15 +430,15 @@ double cost_of_point_removal(SymbolPart& part, int i) {
|
||||
BezierCurve c(prev.pos, prev.pos + after0, next.pos + before2, next.pos);
|
||||
double t = bl/totl;
|
||||
Vector2D np = cur.pos - c.pointAt(t);
|
||||
// cost is distance to new point * length of line ~= area added/removed from part
|
||||
// cost is distance to new point * length of line ~= area added/removed from shape
|
||||
return np.length() * ac.length();
|
||||
}
|
||||
/// Remove a point from a bezier curve
|
||||
/** See SinglePointRemoveAction for algorithm */
|
||||
void remove_point(SymbolPart& part, int i) {
|
||||
ControlPoint& cur = *part.getPoint(i);
|
||||
ControlPoint& prev = *part.getPoint(i-1);
|
||||
ControlPoint& next = *part.getPoint(i+1);
|
||||
void remove_point(SymbolShape& shape, int i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
// Based on SinglePointRemoveAction
|
||||
@@ -449,24 +449,26 @@ void remove_point(SymbolPart& part, int i) {
|
||||
prev.delta_after *= totl / bl;
|
||||
next.delta_before *= totl / al;
|
||||
// remove
|
||||
part.points.erase(part.points.begin() + i);
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
}
|
||||
|
||||
|
||||
void simplify_symbol_part(SymbolPart& part) {
|
||||
mark_corners(part);
|
||||
merge_corners(part);
|
||||
void simplify_symbol_shape(SymbolShape& shape) {
|
||||
mark_corners(shape);
|
||||
merge_corners(shape);
|
||||
for (int i = 0 ; i < 3 ; ++i) {
|
||||
avarage(part);
|
||||
avarage(shape);
|
||||
}
|
||||
convert_to_curves(part);
|
||||
remove_points(part);
|
||||
straighten(part);
|
||||
merge_lines(part);
|
||||
convert_to_curves(shape);
|
||||
remove_points(shape);
|
||||
straighten(shape);
|
||||
merge_lines(shape);
|
||||
}
|
||||
|
||||
void simplify_symbol(Symbol& symbol) {
|
||||
FOR_EACH(p, symbol.parts) {
|
||||
simplify_symbol_part(*p);
|
||||
FOR_EACH(pb, symbol.parts) {
|
||||
if (SymbolShape* p = pb->isSymbolShape()) {
|
||||
simplify_symbol_shape(*p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ SymbolP image_to_symbol(Image& img);
|
||||
void simplify_symbol(Symbol&);
|
||||
|
||||
/// Simplify a symbol parts, i.e. use bezier curves instead of lots of lines
|
||||
void simplify_symbol_part(SymbolPart&);
|
||||
void simplify_symbol_shape(SymbolShape&);
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
#endif
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <script/to_value.hpp>
|
||||
#include <gfx/bezier.hpp>
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||
DECLARE_TYPEOF_COLLECTION(ControlPointP);
|
||||
|
||||
// ----------------------------------------------------------------------------- : ControlPoint
|
||||
@@ -94,17 +93,39 @@ Vector2D& ControlPoint::getOther(WhichHandle wh) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolPart
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(SymbolPartCombine) {
|
||||
VALUE_N("overlap", PART_OVERLAP);
|
||||
VALUE_N("merge", PART_MERGE);
|
||||
VALUE_N("subtract", PART_SUBTRACT);
|
||||
VALUE_N("intersection", PART_INTERSECTION);
|
||||
VALUE_N("difference", PART_DIFFERENCE);
|
||||
VALUE_N("border", PART_BORDER);
|
||||
IMPLEMENT_REFLECTION(SymbolPart) {
|
||||
REFLECT_IF_NOT_READING {
|
||||
String type = typeName();
|
||||
REFLECT(type);
|
||||
}
|
||||
REFLECT(name);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolPart) {
|
||||
REFLECT(name);
|
||||
template <>
|
||||
SymbolPartP read_new<SymbolPart>(Reader& reader) {
|
||||
// there must be a type specified
|
||||
String type;
|
||||
reader.handle(_("type"), type);
|
||||
if (type == _("shape") || type.empty()) return new_intrusive<SymbolShape>();
|
||||
else if (type == _("symmetry")) return new_intrusive<SymbolSymmetry>();
|
||||
else {
|
||||
throw ParseError(_("Unsupported symbol part type: '") + type + _("'"));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolShape
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(SymbolShapeCombine) {
|
||||
VALUE_N("overlap", SYMBOL_COMBINE_OVERLAP);
|
||||
VALUE_N("merge", SYMBOL_COMBINE_MERGE);
|
||||
VALUE_N("subtract", SYMBOL_COMBINE_SUBTRACT);
|
||||
VALUE_N("intersection", SYMBOL_COMBINE_INTERSECTION);
|
||||
VALUE_N("difference", SYMBOL_COMBINE_DIFFERENCE);
|
||||
VALUE_N("border", SYMBOL_COMBINE_BORDER);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolShape) {
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(combine);
|
||||
REFLECT(points);
|
||||
// Fixes after reading
|
||||
@@ -126,12 +147,16 @@ IMPLEMENT_REFLECTION(SymbolPart) {
|
||||
}
|
||||
}
|
||||
|
||||
SymbolPart::SymbolPart()
|
||||
: combine(PART_OVERLAP), rotation_center(.5, .5)
|
||||
SymbolShape::SymbolShape()
|
||||
: combine(SYMBOL_COMBINE_OVERLAP), rotation_center(.5, .5)
|
||||
{}
|
||||
|
||||
SymbolPartP SymbolPart::clone() const {
|
||||
SymbolPartP part = new_intrusive1<SymbolPart>(*this);
|
||||
String SymbolShape::typeName() const {
|
||||
return _("shape");
|
||||
}
|
||||
|
||||
SymbolPartP SymbolShape::clone() const {
|
||||
SymbolShapeP part(new SymbolShape(*this));
|
||||
// also clone the control points
|
||||
FOR_EACH(p, part->points) {
|
||||
p = new_intrusive1<ControlPoint>(*p);
|
||||
@@ -139,7 +164,7 @@ SymbolPartP SymbolPart::clone() const {
|
||||
return part;
|
||||
}
|
||||
|
||||
void SymbolPart::enforceConstraints() {
|
||||
void SymbolShape::enforceConstraints() {
|
||||
for (int i = 0 ; i < (int)points.size() ; ++i) {
|
||||
ControlPointP p1 = getPoint(i);
|
||||
ControlPointP p2 = getPoint(i + 1);
|
||||
@@ -148,7 +173,7 @@ void SymbolPart::enforceConstraints() {
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolPart::calculateBounds() {
|
||||
void SymbolShape::calculateBounds() {
|
||||
min_pos = Vector2D::infinity();
|
||||
max_pos = -Vector2D::infinity();
|
||||
for (int i = 0 ; i < (int)points.size() ; ++i) {
|
||||
@@ -156,6 +181,43 @@ void SymbolPart::calculateBounds() {
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolSymmetry
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(SymbolSymmetryType) {
|
||||
VALUE_N("rotation", SYMMETRY_ROTATION);
|
||||
VALUE_N("reflection", SYMMETRY_REFLECTION);
|
||||
}
|
||||
|
||||
SymbolSymmetry::SymbolSymmetry()
|
||||
: kind(SYMMETRY_ROTATION), copies(2)
|
||||
{}
|
||||
|
||||
String SymbolSymmetry::typeName() const {
|
||||
return _("symmetry");
|
||||
}
|
||||
|
||||
SymbolPartP SymbolSymmetry::clone() const {
|
||||
return new_intrusive1<SymbolSymmetry>(*this);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolSymmetry) {
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(kind);
|
||||
REFLECT(copies);
|
||||
REFLECT(center);
|
||||
REFLECT(handle);
|
||||
// Fixes after reading
|
||||
REFLECT_IF_READING {
|
||||
if (name.empty()) {
|
||||
if (kind == SYMMETRY_REFLECTION) {
|
||||
name = _("Mirror");
|
||||
} else {
|
||||
name = _("Symmetry");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Symbol
|
||||
|
||||
IMPLEMENT_REFLECTION(Symbol) {
|
||||
@@ -165,8 +227,8 @@ IMPLEMENT_REFLECTION(Symbol) {
|
||||
// ----------------------------------------------------------------------------- : Default symbol
|
||||
|
||||
// A default symbol part, a square, moved by d
|
||||
SymbolPartP default_symbol_part(double d) {
|
||||
SymbolPartP part = new_intrusive<SymbolPart>();
|
||||
SymbolShapeP default_symbol_part(double d) {
|
||||
SymbolShapeP part = new_intrusive<SymbolShape>();
|
||||
part->points.push_back(new_intrusive2<ControlPoint>(d + .2, d + .2));
|
||||
part->points.push_back(new_intrusive2<ControlPoint>(d + .2, d + .8));
|
||||
part->points.push_back(new_intrusive2<ControlPoint>(d + .8, d + .8));
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
DECLARE_POINTER_TYPE(ControlPoint);
|
||||
DECLARE_POINTER_TYPE(SymbolPart);
|
||||
DECLARE_POINTER_TYPE(SymbolShape);
|
||||
DECLARE_POINTER_TYPE(SymbolSymmetry);
|
||||
DECLARE_POINTER_TYPE(Symbol);
|
||||
|
||||
// ----------------------------------------------------------------------------- : ControlPoint
|
||||
@@ -43,7 +45,7 @@ enum WhichHandle
|
||||
, HANDLE_AFTER
|
||||
};
|
||||
|
||||
/// A control point (corner) of a SymbolPart (polygon/bezier-gon)
|
||||
/// A control point (corner) of a SymbolShape (polygon/bezier-gon)
|
||||
class ControlPoint : public IntrusivePtrBase<ControlPoint> {
|
||||
public:
|
||||
Vector2D pos; ///< position of the control point itself
|
||||
@@ -104,14 +106,41 @@ class SelectedHandle {
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolPart
|
||||
|
||||
/// A part of a symbol, not necesserly a shape
|
||||
class SymbolPart : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
/// Name/label for this part
|
||||
String name;
|
||||
|
||||
/// Type of this part
|
||||
virtual String typeName() const = 0;
|
||||
/// Create a clone of this symbol part
|
||||
virtual SymbolPartP clone() const = 0;
|
||||
/// Icon for this part
|
||||
virtual int icon() const = 0;
|
||||
|
||||
/// Convert tot SymbolShape?
|
||||
virtual SymbolShape* isSymbolShape() { return nullptr; }
|
||||
virtual const SymbolShape* isSymbolShape() const { return nullptr; }
|
||||
/// Convert tot SymbolSymmetry?
|
||||
virtual SymbolSymmetry* isSymbolSymmetry() { return nullptr; }
|
||||
virtual const SymbolSymmetry* isSymbolSymmetry() const { return nullptr; }
|
||||
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
};
|
||||
|
||||
template <> SymbolPartP read_new<SymbolPart>(Reader& reader);
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolShape
|
||||
|
||||
/// How are symbol parts combined with parts below it?
|
||||
enum SymbolPartCombine
|
||||
{ PART_MERGE
|
||||
, PART_SUBTRACT
|
||||
, PART_INTERSECTION
|
||||
, PART_DIFFERENCE
|
||||
, PART_OVERLAP
|
||||
, PART_BORDER
|
||||
enum SymbolShapeCombine
|
||||
{ SYMBOL_COMBINE_MERGE
|
||||
, SYMBOL_COMBINE_SUBTRACT
|
||||
, SYMBOL_COMBINE_INTERSECTION
|
||||
, SYMBOL_COMBINE_DIFFERENCE
|
||||
, SYMBOL_COMBINE_OVERLAP
|
||||
, SYMBOL_COMBINE_BORDER
|
||||
};
|
||||
|
||||
/// A sane mod function, always returns a result in the range [0..size)
|
||||
@@ -120,25 +149,26 @@ inline size_t mod(int a, size_t size) {
|
||||
return m >= 0 ? m : m + size;
|
||||
}
|
||||
|
||||
/// A single part (polygon/bezier-gon) in a Symbol
|
||||
class SymbolPart : public IntrusivePtrBase<SymbolPart> {
|
||||
/// A single shape (polygon/bezier-gon) in a Symbol
|
||||
class SymbolShape : public SymbolPart {
|
||||
public:
|
||||
/// The points of this polygon
|
||||
vector<ControlPointP> points;
|
||||
/// Name/label for this part
|
||||
String name;
|
||||
/// How is this part combined with parts below it?
|
||||
SymbolPartCombine combine;
|
||||
SymbolShapeCombine combine;
|
||||
// Center of rotation, relative to the part, when the part is scaled to [0..1]
|
||||
Vector2D rotation_center;
|
||||
/// Position and size of the part
|
||||
/// this is the smallest axis aligned bounding box that fits around the part
|
||||
Vector2D min_pos, max_pos;
|
||||
|
||||
SymbolPart();
|
||||
SymbolShape();
|
||||
|
||||
/// Create a clone of this symbol part
|
||||
SymbolPartP clone() const;
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
virtual int icon() const { return combine; }
|
||||
virtual SymbolShape* isSymbolShape() { return this; }
|
||||
virtual const SymbolShape* isSymbolShape() const { return this; }
|
||||
|
||||
/// Get a control point, wraps around
|
||||
inline ControlPointP getPoint(int id) const {
|
||||
@@ -155,6 +185,34 @@ class SymbolPart : public IntrusivePtrBase<SymbolPart> {
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolSymmetry
|
||||
|
||||
enum SymbolSymmetryType
|
||||
{ SYMMETRY_ROTATION = SYMBOL_COMBINE_BORDER + 1 // for icons
|
||||
, SYMMETRY_REFLECTION
|
||||
};
|
||||
|
||||
/// A mirror, reflecting part of the symbol
|
||||
/** Can handle rotation symmetry with any number of reflections */
|
||||
class SymbolSymmetry : public SymbolPart {
|
||||
public:
|
||||
SymbolSymmetryType kind; ///< What kind of symmetry
|
||||
int copies; ///< How many times is the orignal reflected (including the original itself)
|
||||
bool clip; ///< Clip the orignal so it doesn't intersect the mirror(s)
|
||||
Vector2D center; ///< Center point of the mirror
|
||||
Vector2D handle; ///< A handle pointing in the direction of the original, relative to the center
|
||||
|
||||
SymbolSymmetry();
|
||||
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
virtual int icon() const { return kind; }
|
||||
virtual SymbolSymmetry* isSymbolSymmetry() { return this; }
|
||||
virtual const SymbolSymmetry* isSymbolSymmetry() const { return this; }
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Symbol
|
||||
|
||||
/// An editable symbol, consists of any number of SymbolParts
|
||||
|
||||
@@ -141,17 +141,17 @@ bool point_in_bounds(const Vector2D& p, const Vector2D& min, const Vector2D& max
|
||||
|
||||
// ----------------------------------------------------------------------------- : Point tests
|
||||
|
||||
// As a point inside a symbol part?
|
||||
bool point_in_part(const Vector2D& pos, const SymbolPart& part) {
|
||||
// Is a point inside a symbol shape?
|
||||
bool point_in_shape(const Vector2D& pos, const SymbolShape& shape) {
|
||||
// Step 1. compare bounding box of the part
|
||||
if (!point_in_bounds(pos, part.min_pos, part.max_pos)) return false;
|
||||
if (!point_in_bounds(pos, shape.min_pos, shape.max_pos)) return false;
|
||||
|
||||
// Step 2. trace ray outward, count intersections
|
||||
int count = 0;
|
||||
size_t size = part.points.size();
|
||||
size_t size = shape.points.size();
|
||||
for(size_t i = 0 ; i < size ; ++i) {
|
||||
ControlPointP p1 = part.getPoint((int) i);
|
||||
ControlPointP p2 = part.getPoint((int) i + 1);
|
||||
ControlPointP p1 = shape.getPoint((int) i);
|
||||
ControlPointP p2 = shape.getPoint((int) i + 1);
|
||||
if (p1->segment_after == SEGMENT_LINE) {
|
||||
count += intersect_line_ray (p1->pos, p2->pos, pos);
|
||||
} else {
|
||||
|
||||
@@ -98,8 +98,8 @@ void point_bounds(const Vector2D& p, Vector2D& min, Vector2D& max);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Point tests
|
||||
|
||||
/// Is a point inside the given symbol part?
|
||||
bool point_in_part(const Vector2D& p, const SymbolPart& part);
|
||||
/// Is a point inside the given shape?
|
||||
bool point_in_shape(const Vector2D& p, const SymbolShape& part);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Finding points
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ bool SymbolBasicShapeEditor::isEditing() { return drawing; }
|
||||
// ----------------------------------------------------------------------------- : Generating shapes
|
||||
|
||||
void SymbolBasicShapeEditor::stopActions() {
|
||||
shape = SymbolPartP();
|
||||
shape = SymbolShapeP();
|
||||
drawing = false;
|
||||
switch (mode) {
|
||||
case ID_SHAPE_CIRCLE:
|
||||
@@ -176,7 +176,7 @@ void SymbolBasicShapeEditor::makeShape(const Vector2D& a, const Vector2D& b, boo
|
||||
|
||||
// TODO : Move out of this class
|
||||
void SymbolBasicShapeEditor::makeCenteredShape(const Vector2D& c, Vector2D r, bool constrained) {
|
||||
shape = new_intrusive<SymbolPart>();
|
||||
shape = new_intrusive<SymbolShape>();
|
||||
// What shape to make?
|
||||
switch (mode) {
|
||||
case ID_SHAPE_CIRCLE: {
|
||||
|
||||
@@ -48,7 +48,7 @@ class SymbolBasicShapeEditor : public SymbolEditorBase {
|
||||
// --------------------------------------------------- : Data
|
||||
private:
|
||||
int mode;
|
||||
SymbolPartP shape;
|
||||
SymbolShapeP shape;
|
||||
Vector2D start;
|
||||
Vector2D end;
|
||||
bool drawing;
|
||||
|
||||
@@ -51,8 +51,10 @@ void SymbolControl::onModeChange(wxCommandEvent& ev) {
|
||||
break;
|
||||
case ID_MODE_POINTS:
|
||||
if (selected_parts.size() == 1) {
|
||||
single_selection = *selected_parts.begin();
|
||||
switchEditor(new_intrusive2<SymbolPointEditor>(this, single_selection));
|
||||
selected_shape = dynamic_pointer_cast<SymbolShape>(*selected_parts.begin());
|
||||
if (selected_shape) {
|
||||
switchEditor(new_intrusive2<SymbolPointEditor>(this, selected_shape));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ID_MODE_SHAPES:
|
||||
@@ -88,25 +90,34 @@ void SymbolControl::onAction(const Action& action, bool undone) {
|
||||
|
||||
void SymbolControl::onUpdateSelection() {
|
||||
switch(editor->modeToolId()) {
|
||||
case ID_MODE_POINTS:
|
||||
case ID_MODE_POINTS: {
|
||||
// can only select a single part!
|
||||
if (selected_parts.size() > 1) {
|
||||
// TODO: find a part that is a shape
|
||||
SymbolPartP part = *selected_parts.begin();
|
||||
selected_parts.clear();
|
||||
selected_parts.insert(part);
|
||||
signalSelectionChange();
|
||||
} else if (selected_parts.empty()) {
|
||||
selected_parts.insert(single_selection);
|
||||
selected_parts.insert(selected_shape);
|
||||
signalSelectionChange();
|
||||
break;
|
||||
}
|
||||
if (single_selection != *selected_parts.begin()) {
|
||||
SymbolShapeP shape = dynamic_pointer_cast<SymbolShape>(*selected_parts.begin());
|
||||
if (!shape) {
|
||||
selected_parts.clear();
|
||||
selected_parts.insert(selected_shape);
|
||||
signalSelectionChange();
|
||||
break;
|
||||
}
|
||||
if (shape != selected_shape) {
|
||||
// begin editing another part
|
||||
single_selection = *selected_parts.begin();
|
||||
editor = new_intrusive2<SymbolPointEditor>(this, single_selection);
|
||||
selected_shape = shape;
|
||||
editor = new_intrusive2<SymbolPointEditor>(this, selected_shape);
|
||||
Refresh(false);
|
||||
}
|
||||
break;
|
||||
case ID_MODE_SHAPES:
|
||||
} case ID_MODE_SHAPES:
|
||||
if (!selected_parts.empty()) {
|
||||
// there can't be a selection
|
||||
selected_parts.clear();
|
||||
@@ -127,9 +138,11 @@ void SymbolControl::selectPart(const SymbolPartP& part) {
|
||||
}
|
||||
|
||||
void SymbolControl::activatePart(const SymbolPartP& part) {
|
||||
selected_parts.clear();
|
||||
selected_parts.insert(part);
|
||||
switchEditor(new_intrusive2<SymbolPointEditor>(this, part));
|
||||
if (part->isSymbolShape()) {
|
||||
selected_parts.clear();
|
||||
selected_parts.insert(part);
|
||||
switchEditor(new_intrusive2<SymbolPointEditor>(this, static_pointer_cast<SymbolShape>(part)));
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolControl::signalSelectionChange() {
|
||||
|
||||
@@ -44,7 +44,7 @@ class SymbolControl : public wxControl, public SymbolViewer {
|
||||
/// The selection has changed, tell the part list
|
||||
void signalSelectionChange();
|
||||
|
||||
/// Activate a part, open it in the point editor
|
||||
/// Activate a part, open it in the point editor, if it is a shape
|
||||
void activatePart(const SymbolPartP& part);
|
||||
|
||||
/// Select a specific part from the symbol
|
||||
@@ -70,9 +70,9 @@ class SymbolControl : public wxControl, public SymbolViewer {
|
||||
// --------------------------------------------------- : Data
|
||||
|
||||
public:
|
||||
/// What parts are selected
|
||||
/// What parts are selected?
|
||||
set<SymbolPartP> selected_parts;
|
||||
SymbolPartP single_selection;
|
||||
SymbolShapeP selected_shape; // if there is a single selection
|
||||
|
||||
/// Parent window
|
||||
SymbolWindow* parent;
|
||||
|
||||
@@ -19,13 +19,15 @@ SymbolPartList::SymbolPartList(Window* parent, int id, SymbolP symbol)
|
||||
{
|
||||
// Create image list
|
||||
wxImageList* images = new wxImageList(16,16);
|
||||
// NOTE: this is based on the order of the SymbolPartCombine enum!
|
||||
// NOTE: this is based on the order of the SymbolShapeCombine and SymbolSymmetryType enums!
|
||||
images->Add(load_resource_image(_("combine_or")));
|
||||
images->Add(load_resource_image(_("combine_sub")));
|
||||
images->Add(load_resource_image(_("combine_and")));
|
||||
images->Add(load_resource_image(_("combine_xor")));
|
||||
images->Add(load_resource_image(_("combine_over")));
|
||||
images->Add(load_resource_image(_("combine_border")));
|
||||
images->Add(load_resource_image(_("symmetry_rotation")));
|
||||
images->Add(load_resource_image(_("symmetry_reflection")));
|
||||
AssignImageList(images, wxIMAGE_LIST_SMALL);
|
||||
// create columns
|
||||
InsertColumn(0, _("Name"));
|
||||
@@ -59,7 +61,7 @@ String SymbolPartList::OnGetItemText(long item, long col) const {
|
||||
return getPart(item)->name;
|
||||
}
|
||||
int SymbolPartList::OnGetItemImage(long item) const {
|
||||
return getPart(item)->combine;
|
||||
return getPart(item)->icon();
|
||||
}
|
||||
|
||||
SymbolPartP SymbolPartList::getPart(long item) const {
|
||||
@@ -74,14 +76,12 @@ void SymbolPartList::selectItem(long item) {
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolPartList::getselected_parts(set<SymbolPartP>& sel) {
|
||||
void SymbolPartList::getSelectedParts(set<SymbolPartP>& sel) {
|
||||
sel.clear();
|
||||
long count = GetItemCount();
|
||||
for (long i = 0 ; i < count ; ++ i) {
|
||||
bool selected = GetItemState(i, wxLIST_STATE_SELECTED);
|
||||
if (selected) {
|
||||
sel.insert(symbol->parts.at(i));
|
||||
}
|
||||
if (selected) sel.insert(symbol->parts.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ void SymbolPartList::selectParts(const set<SymbolPartP>& sel) {
|
||||
wxLIST_STATE_SELECTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SymbolPartList::update() {
|
||||
if (symbol->parts.empty()) {
|
||||
// deleting all items requires a full refresh on win32
|
||||
|
||||
@@ -31,7 +31,7 @@ class SymbolPartList : public wxListCtrl, public SymbolView {
|
||||
inline SymbolPartP getSelection() const { return getPart(selected); }
|
||||
|
||||
/// Get a set of selected parts
|
||||
void getselected_parts(set<SymbolPartP>& sel);
|
||||
void getSelectedParts(set<SymbolPartP>& sel);
|
||||
/// Select the specified parts, and nothing else
|
||||
void selectParts(const set<SymbolPartP>& sel);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ DECLARE_TYPEOF_COLLECTION(ControlPointP);
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolPointEditor
|
||||
|
||||
SymbolPointEditor::SymbolPointEditor(SymbolControl* control, const SymbolPartP& part)
|
||||
SymbolPointEditor::SymbolPointEditor(SymbolControl* control, const SymbolShapeP& part)
|
||||
: SymbolEditorBase(control)
|
||||
, part(part)
|
||||
, selection(SELECTED_NONE)
|
||||
@@ -261,7 +261,7 @@ void SymbolPointEditor::onLeftDClick(const Vector2D& pos, wxMouseEvent& ev) {
|
||||
// Delete point
|
||||
selected_points.clear();
|
||||
selectPoint(hover_handle.point, false);
|
||||
getSymbol()->actions.add(controlPointRemoveAction(part, selected_points));
|
||||
getSymbol()->actions.add(control_point_remove_action(part, selected_points));
|
||||
selected_points.clear();
|
||||
selection = SELECTED_NONE;
|
||||
}
|
||||
@@ -439,7 +439,7 @@ void SymbolPointEditor::resetActions() {
|
||||
|
||||
void SymbolPointEditor::deleteSelection() {
|
||||
if (!selected_points.empty()) {
|
||||
getSymbol()->actions.add(controlPointRemoveAction(part, selected_points));
|
||||
getSymbol()->actions.add(control_point_remove_action(part, selected_points));
|
||||
selected_points.clear();
|
||||
resetActions();
|
||||
control.Refresh(false);
|
||||
|
||||
@@ -22,7 +22,7 @@ class CurveDragAction;
|
||||
// Symbol editor for editing control points and handles
|
||||
class SymbolPointEditor : public SymbolEditorBase {
|
||||
public:
|
||||
SymbolPointEditor(SymbolControl* control, const SymbolPartP& part);
|
||||
SymbolPointEditor(SymbolControl* control, const SymbolShapeP& part);
|
||||
|
||||
// --------------------------------------------------- : Drawing
|
||||
|
||||
@@ -81,7 +81,7 @@ class SymbolPointEditor : public SymbolEditorBase {
|
||||
// --------------------------------------------------- : Data
|
||||
|
||||
// The symbol part we are editing
|
||||
SymbolPartP part;
|
||||
SymbolShapeP part;
|
||||
|
||||
// Actions in progress
|
||||
// All are owned by the action stack, or they are 0
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <util/window_id.hpp>
|
||||
#include <data/action/symbol.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <util/error.hpp>
|
||||
#include <gfx/gfx.hpp>
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||
@@ -37,7 +38,9 @@ SymbolSelectEditor::SymbolSelectEditor(SymbolControl* control, bool rotate)
|
||||
handleCenter = wxBitmap(load_resource_image(_("handle_center")));
|
||||
// Make sure all parts have updated bounds
|
||||
FOR_EACH(p, getSymbol()->parts) {
|
||||
p->calculateBounds();
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
s->calculateBounds();
|
||||
}
|
||||
}
|
||||
resetActions();
|
||||
}
|
||||
@@ -108,27 +111,27 @@ void SymbolSelectEditor::drawRotationCenter(DC& dc, const Vector2D& pos) {
|
||||
|
||||
void SymbolSelectEditor::initUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
tb->AddSeparator();
|
||||
tb->AddTool(ID_PART_MERGE, _TOOL_("merge"), load_resource_image(_("combine_or")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("merge"), _HELP_("merge"));
|
||||
tb->AddTool(ID_PART_SUBTRACT, _TOOL_("subtract"), load_resource_image(_("combine_sub_dark")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("subtract"), _HELP_("subtract"));
|
||||
tb->AddTool(ID_PART_INTERSECTION, _TOOL_("intersect"), load_resource_image(_("combine_and_dark")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("intersect"), _HELP_("intersect"));
|
||||
tb->AddTool(ID_PART_DIFFERENCE, _TOOL_("difference"),load_resource_image(_("combine_xor")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("difference"), _HELP_("difference"));
|
||||
tb->AddTool(ID_PART_OVERLAP, _TOOL_("overlap"), load_resource_image(_("combine_over")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("overlap"), _HELP_("overlap"));
|
||||
tb->AddTool(ID_PART_BORDER, _TOOL_("border"), load_resource_image(_("combine_border")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("border"), _HELP_("border"));
|
||||
tb->AddTool(ID_SYMBOL_COMBINE_MERGE, _TOOL_("merge"), load_resource_image(_("combine_or")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("merge"), _HELP_("merge"));
|
||||
tb->AddTool(ID_SYMBOL_COMBINE_SUBTRACT, _TOOL_("subtract"), load_resource_image(_("combine_sub_dark")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("subtract"), _HELP_("subtract"));
|
||||
tb->AddTool(ID_SYMBOL_COMBINE_INTERSECTION, _TOOL_("intersect"), load_resource_image(_("combine_and_dark")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("intersect"), _HELP_("intersect"));
|
||||
tb->AddTool(ID_SYMBOL_COMBINE_DIFFERENCE, _TOOL_("difference"), load_resource_image(_("combine_xor")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("difference"), _HELP_("difference"));
|
||||
tb->AddTool(ID_SYMBOL_COMBINE_OVERLAP, _TOOL_("overlap"), load_resource_image(_("combine_over")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("overlap"), _HELP_("overlap"));
|
||||
tb->AddTool(ID_SYMBOL_COMBINE_BORDER, _TOOL_("border"), load_resource_image(_("combine_border")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("border"), _HELP_("border"));
|
||||
tb->Realize();
|
||||
}
|
||||
void SymbolSelectEditor::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
tb->DeleteTool(ID_PART_MERGE);
|
||||
tb->DeleteTool(ID_PART_SUBTRACT);
|
||||
tb->DeleteTool(ID_PART_INTERSECTION);
|
||||
tb->DeleteTool(ID_PART_DIFFERENCE);
|
||||
tb->DeleteTool(ID_PART_OVERLAP);
|
||||
tb->DeleteTool(ID_PART_BORDER);
|
||||
tb->DeleteTool(ID_SYMBOL_COMBINE_MERGE);
|
||||
tb->DeleteTool(ID_SYMBOL_COMBINE_SUBTRACT);
|
||||
tb->DeleteTool(ID_SYMBOL_COMBINE_INTERSECTION);
|
||||
tb->DeleteTool(ID_SYMBOL_COMBINE_DIFFERENCE);
|
||||
tb->DeleteTool(ID_SYMBOL_COMBINE_OVERLAP);
|
||||
tb->DeleteTool(ID_SYMBOL_COMBINE_BORDER);
|
||||
// HACK: hardcoded size of rest of toolbar
|
||||
tb->DeleteToolByPos(7); // delete separator
|
||||
}
|
||||
|
||||
void SymbolSelectEditor::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
if (ev.GetId() >= ID_PART && ev.GetId() < ID_PART_MAX) {
|
||||
if (ev.GetId() >= ID_SYMBOL_COMBINE && ev.GetId() < ID_SYMBOL_COMBINE_MAX) {
|
||||
if (control.selected_parts.empty()) {
|
||||
ev.Check(false);
|
||||
ev.Enable(false);
|
||||
@@ -136,10 +139,12 @@ void SymbolSelectEditor::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
ev.Enable(true);
|
||||
bool check = true;
|
||||
FOR_EACH(p, control.selected_parts) {
|
||||
if (p->combine != ev.GetId() - ID_PART) {
|
||||
check = false;
|
||||
break;
|
||||
}
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
if (s->combine != ev.GetId() - ID_SYMBOL_COMBINE) {
|
||||
check = false;
|
||||
break;
|
||||
}
|
||||
} // disable when symmetries are selected?
|
||||
}
|
||||
ev.Check(check);
|
||||
}
|
||||
@@ -151,11 +156,11 @@ void SymbolSelectEditor::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
}
|
||||
|
||||
void SymbolSelectEditor::onCommand(int id) {
|
||||
if (id >= ID_PART && id < ID_PART_MAX) {
|
||||
if (id >= ID_SYMBOL_COMBINE && id < ID_SYMBOL_COMBINE_MAX) {
|
||||
// change combine mode
|
||||
getSymbol()->actions.add(new CombiningModeAction(
|
||||
control.selected_parts,
|
||||
static_cast<SymbolPartCombine>(id - ID_PART)
|
||||
static_cast<SymbolShapeCombine>(id - ID_SYMBOL_COMBINE)
|
||||
));
|
||||
control.Refresh(false);
|
||||
} else if (id == ID_EDIT_DUPLICATE && !isEditing()) {
|
||||
@@ -427,7 +432,13 @@ double SymbolSelectEditor::angleTo(const Vector2D& pos) {
|
||||
|
||||
SymbolPartP SymbolSelectEditor::findPart(const Vector2D& pos) {
|
||||
FOR_EACH(p, getSymbol()->parts) {
|
||||
if (point_in_part(pos, *p)) return p;
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
if (point_in_shape(pos, *s)) return p;
|
||||
} else if (SymbolSymmetry* s = p->isSymbolSymmetry()) {
|
||||
// TODO
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
}
|
||||
return SymbolPartP();
|
||||
}
|
||||
@@ -437,8 +448,10 @@ void SymbolSelectEditor::updateBoundingBox() {
|
||||
minV = Vector2D::infinity();
|
||||
maxV = -Vector2D::infinity();
|
||||
FOR_EACH(p, control.selected_parts) {
|
||||
minV = piecewise_min(minV, p->min_pos);
|
||||
maxV = piecewise_max(maxV, p->max_pos);
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
minV = piecewise_min(minV, s->min_pos);
|
||||
maxV = piecewise_max(maxV, s->max_pos);
|
||||
}
|
||||
}
|
||||
/* // Find rotation center
|
||||
center = Vector2D(0,0);
|
||||
|
||||
@@ -249,7 +249,7 @@ void SymbolWindow::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
void SymbolWindow::onSelectFromList(wxListEvent& ev) {
|
||||
if (inSelectionEvent) return ;
|
||||
inSelectionEvent = true;
|
||||
parts->getselected_parts(control->selected_parts);
|
||||
parts->getSelectedParts(control->selected_parts);
|
||||
control->onUpdateSelection();
|
||||
inSelectionEvent = false;
|
||||
}
|
||||
|
||||
@@ -36,31 +36,32 @@ void SymbolValueEditor::draw(RotatedDC& dc) {
|
||||
}
|
||||
void SymbolValueEditor::drawButton(RotatedDC& dc, int button, const String& text) {
|
||||
bool down = button == button_down;
|
||||
double size = style().height;
|
||||
double x = style().right - size - (size + 1) * button;
|
||||
double height = style().height;
|
||||
double width = style().height + 2;
|
||||
double x = style().right - width - (width + 1) * button;
|
||||
double y = style().top;
|
||||
// draw button
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
|
||||
dc.DrawRectangle(RealRect(x, y, size, size));
|
||||
dc.DrawRectangle(RealRect(x, y, width, height));
|
||||
dc.SetPen(wxSystemSettings::GetColour(down ? wxSYS_COLOUR_BTNSHADOW : wxSYS_COLOUR_BTNHIGHLIGHT));
|
||||
dc.DrawLine(RealPoint(x,y),RealPoint(x+size,y));
|
||||
dc.DrawLine(RealPoint(x,y),RealPoint(x,y+size));
|
||||
dc.DrawLine(RealPoint(x,y),RealPoint(x+width,y));
|
||||
dc.DrawLine(RealPoint(x,y),RealPoint(x,y+height));
|
||||
dc.SetPen(wxSystemSettings::GetColour(down ? wxSYS_COLOUR_BTNHIGHLIGHT : wxSYS_COLOUR_BTNSHADOW));
|
||||
dc.DrawLine(RealPoint(x+size-1,y),RealPoint(x+size-1,y+size));
|
||||
dc.DrawLine(RealPoint(x,y+size-1),RealPoint(x+size,y+size-1));
|
||||
dc.DrawLine(RealPoint(x+width-1,y),RealPoint(x+width-1,y+height));
|
||||
dc.DrawLine(RealPoint(x,y+height-1),RealPoint(x+width,y+height-1));
|
||||
// draw text
|
||||
RealSize text_size = dc.GetTextExtent(text);
|
||||
dc.DrawText(text, align_in_rect((Alignment)(ALIGN_BOTTOM | ALIGN_CENTER), text_size, RealRect(x, y, size,size*0.9)));
|
||||
dc.DrawText(text, align_in_rect((Alignment)(ALIGN_BOTTOM | ALIGN_CENTER), text_size, RealRect(x, y, width,height*0.9)));
|
||||
// draw image
|
||||
const Bitmap& bmp = button_images[button];
|
||||
RealSize image_size(bmp.GetWidth(), bmp.GetHeight());
|
||||
dc.DrawBitmap(bmp, align_in_rect(ALIGN_MIDDLE_CENTER, image_size, RealRect(x,y,size,size * 0.8)));
|
||||
dc.DrawBitmap(bmp, align_in_rect(ALIGN_MIDDLE_CENTER, image_size, RealRect(x,y,width,height * 0.8)));
|
||||
}
|
||||
|
||||
int SymbolValueEditor::findButton(const RealPoint& pos) {
|
||||
if (pos.y < style().top || pos.y >= style().bottom) return -1;
|
||||
int button = (int)floor( (style().right - pos.x) / (style().height + 1) );
|
||||
int button = (int)floor( (style().right - pos.x) / (style().height + 3) );
|
||||
if (button >= 0 && button <= 1) return button;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <render/symbol/viewer.hpp>
|
||||
#include <util/error.hpp> // clearDC_black
|
||||
#include <gui/util.hpp> // clearDC_black
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||
@@ -64,41 +65,49 @@ void SymbolViewer::draw(DC& dc) {
|
||||
// Check if we can paint directly to the dc
|
||||
// This will fail if there are parts with combine == intersection
|
||||
FOR_EACH(p, symbol->parts) {
|
||||
if (p->combine == PART_INTERSECTION) {
|
||||
paintedSomething = true;
|
||||
break;
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
if (s->combine == SYMBOL_COMBINE_INTERSECTION) {
|
||||
paintedSomething = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Draw all parts, in reverse order (bottom to top)
|
||||
FOR_EACH_REVERSE(p, symbol->parts) {
|
||||
const SymbolPart& part = *p;
|
||||
if (part.combine == PART_OVERLAP && buffersFilled) {
|
||||
// We will be overlapping some previous parts, write them to the screen
|
||||
combineBuffers(dc, borderDC.get(), interiorDC.get());
|
||||
// Clear the buffers
|
||||
buffersFilled = false;
|
||||
paintedSomething = true;
|
||||
wxSize s = dc.GetSize();
|
||||
if (borderDC) {
|
||||
borderDC->SetBrush(*wxBLACK_BRUSH);
|
||||
borderDC->SetPen( *wxTRANSPARENT_PEN);
|
||||
borderDC->DrawRectangle(0, 0, s.GetWidth(), s.GetHeight());
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
if (s->combine == SYMBOL_COMBINE_OVERLAP && buffersFilled) {
|
||||
// We will be overlapping some previous parts, write them to the screen
|
||||
combineBuffers(dc, borderDC.get(), interiorDC.get());
|
||||
// Clear the buffers
|
||||
buffersFilled = false;
|
||||
paintedSomething = true;
|
||||
wxSize s = dc.GetSize();
|
||||
if (borderDC) {
|
||||
borderDC->SetBrush(*wxBLACK_BRUSH);
|
||||
borderDC->SetPen( *wxTRANSPARENT_PEN);
|
||||
borderDC->DrawRectangle(0, 0, s.GetWidth(), s.GetHeight());
|
||||
}
|
||||
interiorDC->SetBrush(*wxBLACK_BRUSH);
|
||||
interiorDC->DrawRectangle(0, 0, s.GetWidth(), s.GetHeight());
|
||||
}
|
||||
interiorDC->SetBrush(*wxBLACK_BRUSH);
|
||||
interiorDC->DrawRectangle(0, 0, s.GetWidth(), s.GetHeight());
|
||||
}
|
||||
|
||||
if (!paintedSomething) {
|
||||
// No need to buffer
|
||||
if (!interiorDC) interiorDC = getTempDC(dc);
|
||||
combineSymbolPart(part, dc, *interiorDC, true, false);
|
||||
buffersFilled = true;
|
||||
|
||||
// Paint the part itself
|
||||
if (!paintedSomething) {
|
||||
// No need to buffer
|
||||
if (!interiorDC) interiorDC = getTempDC(dc);
|
||||
combineSymbolShape(*s, dc, *interiorDC, true, false);
|
||||
buffersFilled = true;
|
||||
} else {
|
||||
if (!borderDC) borderDC = getTempDC(dc);
|
||||
if (!interiorDC) interiorDC = getTempDC(dc);
|
||||
// Draw this shape to the buffer
|
||||
combineSymbolShape(*s, *borderDC, *interiorDC, false, false);
|
||||
buffersFilled = true;
|
||||
}
|
||||
// Paint symmetric versions of this part
|
||||
// TODO
|
||||
} else {
|
||||
if (!borderDC) borderDC = getTempDC(dc);
|
||||
if (!interiorDC) interiorDC = getTempDC(dc);
|
||||
// Draw this part to the buffer
|
||||
combineSymbolPart(part, *borderDC, *interiorDC, false, false);
|
||||
buffersFilled = true;
|
||||
// symmetry, already handled above
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,11 +118,20 @@ void SymbolViewer::draw(DC& dc) {
|
||||
}
|
||||
|
||||
void SymbolViewer::highlightPart(DC& dc, const SymbolPart& part, HighlightStyle style) {
|
||||
if (const SymbolShape* s = part.isSymbolShape()) {
|
||||
highlightPart(dc, *s, style);
|
||||
} else if (const SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
highlightPart(dc, *s);
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
}
|
||||
void SymbolViewer::highlightPart(DC& dc, const SymbolShape& shape, HighlightStyle style) {
|
||||
// create point list
|
||||
vector<wxPoint> points;
|
||||
size_t size = part.points.size();
|
||||
size_t size = shape.points.size();
|
||||
for(size_t i = 0 ; i < size ; ++i) {
|
||||
segment_subdivide(*part.getPoint((int)i), *part.getPoint((int)i+1), rotation, points);
|
||||
segment_subdivide(*shape.getPoint((int)i), *shape.getPoint((int)i+1), rotation, points);
|
||||
}
|
||||
// draw
|
||||
if (style == HIGHLIGHT_BORDER) {
|
||||
@@ -125,7 +143,7 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolPart& part, HighlightStyle
|
||||
dc.SetBrush(Color(0,0,64));
|
||||
dc.SetPen (*wxTRANSPARENT_PEN);
|
||||
dc.DrawPolygon((int)points.size(), &points[0]);
|
||||
if (part.combine == PART_SUBTRACT || part.combine == PART_BORDER) {
|
||||
if (shape.combine == SYMBOL_COMBINE_SUBTRACT || shape.combine == SYMBOL_COMBINE_BORDER) {
|
||||
dc.SetLogicalFunction(wxAND);
|
||||
dc.SetBrush(Color(191,191,255));
|
||||
dc.DrawPolygon((int)points.size(), &points[0]);
|
||||
@@ -133,51 +151,54 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolPart& part, HighlightStyle
|
||||
dc.SetLogicalFunction(wxCOPY);
|
||||
}
|
||||
}
|
||||
void SymbolViewer::highlightPart(DC& dc, const SymbolSymmetry& sym) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
void SymbolViewer::combineSymbolPart(const SymbolPart& part, DC& border, DC& interior, bool directB, bool directI) {
|
||||
void SymbolViewer::combineSymbolShape(const SymbolShape& shape, DC& border, DC& interior, bool directB, bool directI) {
|
||||
// what color should the interior be?
|
||||
// use black when drawing to the screen
|
||||
Byte interiorCol = directI ? 0 : 255;
|
||||
// how to draw depends on combining mode
|
||||
switch(part.combine) {
|
||||
case PART_OVERLAP:
|
||||
case PART_MERGE: {
|
||||
drawSymbolPart(part, &border, &interior, 255, interiorCol, directB, false);
|
||||
switch(shape.combine) {
|
||||
case SYMBOL_COMBINE_OVERLAP:
|
||||
case SYMBOL_COMBINE_MERGE: {
|
||||
drawSymbolShape(shape, &border, &interior, 255, interiorCol, directB, false);
|
||||
break;
|
||||
} case PART_SUBTRACT: {
|
||||
} case SYMBOL_COMBINE_SUBTRACT: {
|
||||
border.SetLogicalFunction(wxAND);
|
||||
drawSymbolPart(part, &border, &interior, 0, ~interiorCol, directB, false);
|
||||
drawSymbolShape(shape, &border, &interior, 0, ~interiorCol, directB, false);
|
||||
border.SetLogicalFunction(wxCOPY);
|
||||
break;
|
||||
} case PART_INTERSECTION: {
|
||||
} case SYMBOL_COMBINE_INTERSECTION: {
|
||||
MemoryDCP keepBorder = getTempDC(border);
|
||||
MemoryDCP keepInterior = getTempDC(interior);
|
||||
drawSymbolPart(part, keepBorder.get(), keepInterior.get(), 255, 255, false, false);
|
||||
drawSymbolShape(shape, keepBorder.get(), keepInterior.get(), 255, 255, false, false);
|
||||
// combine the temporary dcs with the result using the AND operator
|
||||
wxSize s = border.GetSize();
|
||||
border .Blit(0, 0, s.GetWidth(), s.GetHeight(), &*keepBorder , 0, 0, wxAND);
|
||||
interior.Blit(0, 0, s.GetWidth(), s.GetHeight(), &*keepInterior, 0, 0, wxAND);
|
||||
break;
|
||||
} case PART_DIFFERENCE: {
|
||||
} case SYMBOL_COMBINE_DIFFERENCE: {
|
||||
interior.SetLogicalFunction(wxXOR);
|
||||
drawSymbolPart(part, &border, &interior, 0, ~interiorCol, directB, true);
|
||||
drawSymbolShape(shape, &border, &interior, 0, ~interiorCol, directB, true);
|
||||
interior.SetLogicalFunction(wxCOPY);
|
||||
break;
|
||||
} case PART_BORDER: {
|
||||
} case SYMBOL_COMBINE_BORDER: {
|
||||
// draw border as interior
|
||||
drawSymbolPart(part, nullptr, &border, 0, 255, false, false);
|
||||
drawSymbolShape(shape, nullptr, &border, 0, 255, false, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolViewer::drawSymbolPart(const SymbolPart& part, DC* border, DC* interior, Byte borderCol, Byte interiorCol, bool directB, bool clear) {
|
||||
void SymbolViewer::drawSymbolShape(const SymbolShape& shape, DC* border, DC* interior, Byte borderCol, Byte interiorCol, bool directB, bool clear) {
|
||||
// create point list
|
||||
vector<wxPoint> points;
|
||||
size_t size = part.points.size();
|
||||
size_t size = shape.points.size();
|
||||
for(size_t i = 0 ; i < size ; ++i) {
|
||||
segment_subdivide(*part.getPoint((int)i), *part.getPoint((int)i+1), rotation, points);
|
||||
segment_subdivide(*shape.getPoint((int)i), *shape.getPoint((int)i+1), rotation, points);
|
||||
}
|
||||
// draw border
|
||||
if (border && border_radius > 0) {
|
||||
|
||||
@@ -45,6 +45,8 @@ class SymbolViewer : public SymbolView {
|
||||
void draw(DC& dc);
|
||||
|
||||
void highlightPart(DC& dc, const SymbolPart& part, HighlightStyle style);
|
||||
void highlightPart(DC& dc, const SymbolShape& shap, HighlightStyle style);
|
||||
void highlightPart(DC& dc, const SymbolSymmetry& sym);
|
||||
|
||||
void onAction(const Action&, bool) {}
|
||||
|
||||
@@ -53,14 +55,14 @@ class SymbolViewer : public SymbolView {
|
||||
/** directB/directI are true if the border/interior is the screen dc, false if it
|
||||
* is a temporary 1 bit one
|
||||
*/
|
||||
void combineSymbolPart(const SymbolPart& part, DC& border, DC& interior, bool directB, bool directI);
|
||||
void combineSymbolShape(const SymbolShape& part, DC& border, DC& interior, bool directB, bool directI);
|
||||
|
||||
/// Draw a symbol part, draws the border and the interior to separate DCs
|
||||
/** The DCs may be null. directB should be true when drawing the border directly to the screen.
|
||||
* The **Col parameters give the color to use for the (interior of) the border and the interior
|
||||
* default should be white (255) border and black (0) interior.
|
||||
*/
|
||||
void drawSymbolPart(const SymbolPart& part, DC* border, DC* interior, unsigned char borderCol, unsigned char interiorCol, bool directB, bool oppB);
|
||||
void drawSymbolShape(const SymbolShape& shape, DC* border, DC* interior, unsigned char borderCol, unsigned char interiorCol, bool directB, bool oppB);
|
||||
/*
|
||||
// ------------------- Bezier curve calculation
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 257 B |
|
After Width: | Height: | Size: 252 B |
@@ -100,6 +100,8 @@ combine_and_dark IMAGE "../common/combine_and_dark.png"
|
||||
combine_xor IMAGE "../common/combine_xor.png"
|
||||
combine_over IMAGE "../common/combine_over.png"
|
||||
combine_border IMAGE "../common/combine_border.png"
|
||||
symmetry_rotation IMAGE "../common/symmetry_rotation.png"
|
||||
symmetry_reflection IMAGE "../common/symmetry_reflection.png"
|
||||
|
||||
handle_rotate IMAGE "../common/handle_rotate.png"
|
||||
handle_shear_x IMAGE "../common/handle_shear_x.png"
|
||||
|
||||
|
Before Width: | Height: | Size: 657 B After Width: | Height: | Size: 551 B |
|
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 568 B After Width: | Height: | Size: 380 B |
@@ -117,14 +117,14 @@ enum ChildMenuID {
|
||||
, ID_INSERT_SYMBOL
|
||||
|
||||
// SymbolSelectEditor toolbar/menu
|
||||
, ID_PART = 2001
|
||||
, ID_PART_MERGE = ID_PART + 0//PART_MERGE
|
||||
, ID_PART_SUBTRACT = ID_PART + 1//PART_SUBTRACT
|
||||
, ID_PART_INTERSECTION = ID_PART + 2//PART_INTERSECTION
|
||||
, ID_PART_DIFFERENCE = ID_PART + 3//PART_DIFFERENCE
|
||||
, ID_PART_OVERLAP = ID_PART + 4//PART_OVERLAP
|
||||
, ID_PART_BORDER = ID_PART + 5//PART_BORDER
|
||||
, ID_PART_MAX
|
||||
, ID_SYMBOL_COMBINE = 2001
|
||||
, ID_SYMBOL_COMBINE_MERGE = ID_SYMBOL_COMBINE + 0 //SYMBOL_COMBINE_MERGE
|
||||
, ID_SYMBOL_COMBINE_SUBTRACT = ID_SYMBOL_COMBINE + 1 //SYMBOL_COMBINE_SUBTRACT
|
||||
, ID_SYMBOL_COMBINE_INTERSECTION = ID_SYMBOL_COMBINE + 2 //SYMBOL_COMBINE_INTERSECTION
|
||||
, ID_SYMBOL_COMBINE_DIFFERENCE = ID_SYMBOL_COMBINE + 3 //SYMBOL_COMBINE_DIFFERENCE
|
||||
, ID_SYMBOL_COMBINE_OVERLAP = ID_SYMBOL_COMBINE + 4 //SYMBOL_COMBINE_OVERLAP
|
||||
, ID_SYMBOL_COMBINE_BORDER = ID_SYMBOL_COMBINE + 5 //SYMBOL_COMBINE_BORDER
|
||||
, ID_SYMBOL_COMBINE_MAX
|
||||
, ID_EDIT_DUPLICATE // duplicating symbol parts
|
||||
, ID_VIEW_GRID
|
||||
, ID_VIEW_GRID_SNAP
|
||||
|
||||