mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
New symbol part list control that shows previews and has a built in editor
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@529 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
+118
-70
@@ -18,13 +18,17 @@ 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"));
|
||||
return format_string(action, parts.size() == 1 ? (*parts.begin())->name : _TYPE_("shapes"));
|
||||
}
|
||||
|
||||
SymbolPartsAction::SymbolPartsAction(const set<SymbolPartP>& parts)
|
||||
: parts(parts)
|
||||
{}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Moving symbol parts
|
||||
|
||||
SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts, const Vector2D& delta)
|
||||
: parts(parts)
|
||||
: SymbolPartsAction(parts)
|
||||
, delta(delta), moved(-delta)
|
||||
, min_pos(Vector2D::infinity()), max_pos(-Vector2D::infinity())
|
||||
, constrain(false)
|
||||
@@ -32,10 +36,8 @@ SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts, const
|
||||
{
|
||||
// Determine min/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);
|
||||
}
|
||||
min_pos = piecewise_min(min_pos, p->min_pos);
|
||||
max_pos = piecewise_max(max_pos, p->max_pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,20 +48,28 @@ String SymbolPartMoveAction::getName(bool to_undo) const {
|
||||
void SymbolPartMoveAction::perform(bool to_undo) {
|
||||
// move the points back
|
||||
FOR_EACH(p, parts) {
|
||||
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"));
|
||||
}
|
||||
movePart(*p);
|
||||
}
|
||||
moved = -moved;
|
||||
}
|
||||
void SymbolPartMoveAction::movePart(SymbolPart& part) {
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
s->min_pos -= moved;
|
||||
s->max_pos -= moved;
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos -= moved;
|
||||
}
|
||||
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
s->center -= moved;
|
||||
} else if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
movePart(*p);
|
||||
}
|
||||
g->calculateBoundsNonRec();
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolPartMoveAction::move(const Vector2D& deltaDelta) {
|
||||
delta += deltaDelta;
|
||||
@@ -75,26 +85,34 @@ void SymbolPartMoveAction::move(const Vector2D& deltaDelta) {
|
||||
// ----------------------------------------------------------------------------- : Rotating symbol parts
|
||||
|
||||
SymbolPartMatrixAction::SymbolPartMatrixAction(const set<SymbolPartP>& parts, const Vector2D& center)
|
||||
: parts(parts)
|
||||
: SymbolPartsAction(parts)
|
||||
, center(center)
|
||||
{}
|
||||
|
||||
void SymbolPartMatrixAction::transform(const Vector2D& mx, const Vector2D& my) {
|
||||
// Transform each point
|
||||
void SymbolPartMatrixAction::transform(const Matrix2D& m) {
|
||||
// Transform each part
|
||||
FOR_EACH(p, parts) {
|
||||
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"));
|
||||
transform(*p, m);
|
||||
}
|
||||
}
|
||||
void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) {
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos = ((pnt->pos - center) * m) + center;
|
||||
pnt->delta_before = pnt->delta_before * m;
|
||||
pnt->delta_after = pnt->delta_after * m;
|
||||
}
|
||||
// bounds change after transforming
|
||||
s->calculateBounds();
|
||||
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
s->handle = s->handle * m;
|
||||
} else if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
transform(*p, m);
|
||||
}
|
||||
g->calculateBoundsNonRec();
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +148,8 @@ void SymbolPartRotateAction::rotateTo(double newAngle) {
|
||||
void SymbolPartRotateAction::rotateBy(double deltaAngle) {
|
||||
// Rotation 'matrix'
|
||||
transform(
|
||||
Vector2D(cos(deltaAngle), -sin(deltaAngle)),
|
||||
Vector2D(sin(deltaAngle), cos(deltaAngle))
|
||||
Matrix2D(cos(deltaAngle), -sin(deltaAngle)
|
||||
,sin(deltaAngle), cos(deltaAngle))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -170,8 +188,8 @@ void SymbolPartShearAction::move(const Vector2D& deltaShear) {
|
||||
void SymbolPartShearAction::shearBy(const Vector2D& shear) {
|
||||
// Shear 'matrix'
|
||||
transform(
|
||||
Vector2D(1, shear.x),
|
||||
Vector2D(shear.y, 1)
|
||||
Matrix2D(1, shear.x
|
||||
,shear.y, 1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,7 +198,7 @@ void SymbolPartShearAction::shearBy(const Vector2D& shear) {
|
||||
|
||||
|
||||
SymbolPartScaleAction::SymbolPartScaleAction(const set<SymbolPartP>& parts, int scaleX, int scaleY)
|
||||
: parts(parts)
|
||||
: SymbolPartsAction(parts)
|
||||
, scaleX(scaleX), scaleY(scaleY)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
@@ -189,10 +207,8 @@ SymbolPartScaleAction::SymbolPartScaleAction(const set<SymbolPartP>& parts, int
|
||||
old_min = Vector2D( 1e6, 1e6);
|
||||
Vector2D old_max (-1e6,-1e6);
|
||||
FOR_EACH(p, parts) {
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
old_min = piecewise_min(old_min, s->min_pos);
|
||||
old_max = piecewise_max(old_max, s->max_pos);
|
||||
}
|
||||
old_min = piecewise_min(old_min, p->min_pos);
|
||||
old_max = piecewise_max(old_max, p->max_pos);
|
||||
}
|
||||
// new == old
|
||||
new_min = new_real_min = old_min;
|
||||
@@ -244,26 +260,34 @@ void SymbolPartScaleAction::update() {
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::transformAll() {
|
||||
Vector2D scale = new_size.div(old_size);
|
||||
FOR_EACH(p, parts) {
|
||||
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"));
|
||||
transformPart(*p);
|
||||
}
|
||||
}
|
||||
void SymbolPartScaleAction::transformPart(SymbolPart& part) {
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
Vector2D scale = new_size.div(old_size);
|
||||
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 = part.isSymbolSymmetry()) {
|
||||
throw "TODO";
|
||||
} else if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
transformPart(*p);
|
||||
}
|
||||
g->calculateBoundsNonRec();
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,11 +298,18 @@ Vector2D SymbolPartScaleAction::transform(const Vector2D& v) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change combine mode
|
||||
|
||||
CombiningModeAction::CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode) {
|
||||
CombiningModeAction::CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode)
|
||||
: SymbolPartsAction(parts)
|
||||
{
|
||||
FOR_EACH(p, parts) {
|
||||
if (p->isSymbolShape()) {
|
||||
this->parts.push_back(make_pair(static_pointer_cast<SymbolShape>(p),mode));
|
||||
}
|
||||
add(p,mode);
|
||||
}
|
||||
}
|
||||
void CombiningModeAction::add(const SymbolPartP& part, SymbolShapeCombine mode) {
|
||||
if (part->isSymbolShape()) {
|
||||
this->parts.push_back(make_pair(static_pointer_cast<SymbolShape>(part),mode));
|
||||
} else if (SymbolGroup* g = part->isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) add(p, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,16 +325,28 @@ void CombiningModeAction::perform(bool to_undo) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change name
|
||||
|
||||
SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name)
|
||||
SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name, size_t old_cursor, size_t new_cursor)
|
||||
: part(part), part_name(name)
|
||||
, new_cursor(old_cursor), old_cursor(new_cursor) // will be swapped
|
||||
{}
|
||||
|
||||
String SymbolPartNameAction::getName(bool to_undo) const {
|
||||
return _ACTION_("change shape name");
|
||||
}
|
||||
bool SymbolPartNameAction::merge(const Action& action) {
|
||||
TYPE_CASE(action, SymbolPartNameAction) {
|
||||
if (action.part == part) {
|
||||
// adjacent actions on the same part, discard the other one,
|
||||
// because it only keeps an intermediate value
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SymbolPartNameAction::perform(bool to_undo) {
|
||||
swap(part->name, part_name);
|
||||
swap(old_cursor, new_cursor);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add symbol part
|
||||
@@ -313,7 +356,7 @@ AddSymbolPartAction::AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part
|
||||
{}
|
||||
|
||||
String AddSymbolPartAction::getName(bool to_undo) const {
|
||||
return format_string(_ACTION_("add"), part->name);
|
||||
return format_string(_ACTION_("add part"), part->name);
|
||||
}
|
||||
|
||||
void AddSymbolPartAction::perform(bool to_undo) {
|
||||
@@ -407,8 +450,8 @@ void DuplicateSymbolPartsAction::getParts(set<SymbolPartP>& parts) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reorder symbol parts
|
||||
|
||||
ReorderSymbolPartsAction::ReorderSymbolPartsAction(Symbol& symbol, size_t part_id1, size_t part_id2)
|
||||
: symbol(symbol), part_id1(part_id1), part_id2(part_id2)
|
||||
ReorderSymbolPartsAction::ReorderSymbolPartsAction(Symbol& symbol, size_t old_position, size_t new_position)
|
||||
: symbol(symbol), old_position(old_position), new_position(new_position)
|
||||
{}
|
||||
|
||||
String ReorderSymbolPartsAction::getName(bool to_undo) const {
|
||||
@@ -416,9 +459,12 @@ String ReorderSymbolPartsAction::getName(bool to_undo) const {
|
||||
}
|
||||
|
||||
void ReorderSymbolPartsAction::perform(bool to_undo) {
|
||||
assert(part_id1 < symbol.parts.size());
|
||||
assert(part_id2 < symbol.parts.size());
|
||||
swap(symbol.parts[part_id1], symbol.parts[part_id2]);
|
||||
assert(old_position < symbol.parts.size());
|
||||
assert(new_position < symbol.parts.size());
|
||||
SymbolPartP part = symbol.parts.at(old_position);
|
||||
symbol.parts.erase( symbol.parts.begin() + old_position);
|
||||
symbol.parts.insert(symbol.parts.begin() + new_position, part);
|
||||
swap(old_position, new_position);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Group symbol parts
|
||||
@@ -439,6 +485,7 @@ GroupSymbolPartsAction::GroupSymbolPartsAction(Symbol& symbol, const set<SymbolP
|
||||
group->name = _("Group");
|
||||
FOR_EACH(p, symbol.parts) {
|
||||
if (parts.find(p) != parts.end()) {
|
||||
// add to group instead
|
||||
group->parts.push_back(p);
|
||||
if (!done) {
|
||||
done = true;
|
||||
@@ -449,6 +496,7 @@ GroupSymbolPartsAction::GroupSymbolPartsAction(Symbol& symbol, const set<SymbolP
|
||||
old_part_list.push_back(p);
|
||||
}
|
||||
}
|
||||
group->calculateBounds();
|
||||
}
|
||||
String GroupSymbolPartsAction::getName(bool to_undo) const {
|
||||
return _ACTION_("group parts");
|
||||
|
||||
+30
-17
@@ -20,16 +20,24 @@
|
||||
|
||||
// ----------------------------------------------------------------------------- : Transform symbol part
|
||||
|
||||
/// Anything that changes a part
|
||||
/// Anything that changes one or more parts
|
||||
class SymbolPartAction : public Action {};
|
||||
|
||||
/// Anything that changes a set of parts
|
||||
class SymbolPartsAction : public SymbolPartAction {
|
||||
public:
|
||||
SymbolPartsAction(const set<SymbolPartP>& parts);
|
||||
|
||||
const set<SymbolPartP> parts; ///< Affected parts
|
||||
};
|
||||
|
||||
/// Anything that changes a part as displayed in the part list
|
||||
class SymbolPartListAction : public SymbolPartAction {};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Moving symbol parts
|
||||
|
||||
/// Move some symbol parts
|
||||
class SymbolPartMoveAction : public SymbolPartAction {
|
||||
class SymbolPartMoveAction : public SymbolPartsAction {
|
||||
public:
|
||||
SymbolPartMoveAction(const set<SymbolPartP>& parts, const Vector2D& delta = Vector2D());
|
||||
|
||||
@@ -40,10 +48,11 @@ class SymbolPartMoveAction : public SymbolPartAction {
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
private:
|
||||
set<SymbolPartP> parts; ///< Parts to move
|
||||
Vector2D delta; ///< How much to move
|
||||
Vector2D moved; ///< How much has been moved
|
||||
Vector2D min_pos, max_pos; ///< Bounding box of the thing we are moving
|
||||
|
||||
void movePart(SymbolPart& part); ///< Move a single part
|
||||
public:
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
@@ -52,7 +61,7 @@ class SymbolPartMoveAction : public SymbolPartAction {
|
||||
// ----------------------------------------------------------------------------- : Rotating symbol parts
|
||||
|
||||
/// Transforming symbol parts using a matrix
|
||||
class SymbolPartMatrixAction : public SymbolPartAction {
|
||||
class SymbolPartMatrixAction : public SymbolPartsAction {
|
||||
public:
|
||||
SymbolPartMatrixAction(const set<SymbolPartP>& parts, const Vector2D& center);
|
||||
|
||||
@@ -61,10 +70,10 @@ class SymbolPartMatrixAction : public SymbolPartAction {
|
||||
|
||||
protected:
|
||||
/// Perform the transformation using the given matrix
|
||||
void transform(const Vector2D& mx, const Vector2D& my);
|
||||
void transform(const Matrix2D& m);
|
||||
void transform(SymbolPart& part, const Matrix2D& m);
|
||||
|
||||
set<SymbolPartP> parts; ///< Parts to transform
|
||||
Vector2D center; ///< Center to transform around
|
||||
Vector2D center; ///< Center to transform around
|
||||
};
|
||||
|
||||
/// Rotate some symbol parts
|
||||
@@ -114,7 +123,7 @@ class SymbolPartShearAction : public SymbolPartMatrixAction {
|
||||
// ----------------------------------------------------------------------------- : Scaling symbol parts
|
||||
|
||||
/// Scale some symbol parts
|
||||
class SymbolPartScaleAction : public SymbolPartAction {
|
||||
class SymbolPartScaleAction : public SymbolPartsAction {
|
||||
public:
|
||||
SymbolPartScaleAction(const set<SymbolPartP>& parts, int scaleX, int scaleY);
|
||||
|
||||
@@ -127,13 +136,13 @@ class SymbolPartScaleAction : public SymbolPartAction {
|
||||
void update();
|
||||
|
||||
private:
|
||||
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();
|
||||
void transformPart(SymbolPart&);
|
||||
/// Transform a single vector
|
||||
inline Vector2D transform(const Vector2D& v);
|
||||
public:
|
||||
@@ -144,7 +153,7 @@ class SymbolPartScaleAction : public SymbolPartAction {
|
||||
// ----------------------------------------------------------------------------- : Change combine mode
|
||||
|
||||
/// Change the name of a symbol part
|
||||
class CombiningModeAction : public SymbolPartListAction {
|
||||
class CombiningModeAction : public SymbolPartsAction {
|
||||
public:
|
||||
// All parts must be SymbolParts
|
||||
CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode);
|
||||
@@ -153,22 +162,26 @@ class CombiningModeAction : public SymbolPartListAction {
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
void add(const SymbolPartP&, SymbolShapeCombine mode);
|
||||
vector<pair<SymbolShapeP,SymbolShapeCombine> > parts; ///< Affected parts with new combining modes
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change name
|
||||
|
||||
/// Change the name of a symbol part
|
||||
class SymbolPartNameAction : public SymbolPartListAction {
|
||||
class SymbolPartNameAction : public SymbolPartAction {
|
||||
public:
|
||||
SymbolPartNameAction(const SymbolPartP& part, const String& name);
|
||||
SymbolPartNameAction(const SymbolPartP& part, const String& name, size_t old_cursor, size_t new_cursor);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
virtual bool merge(const Action& action);
|
||||
|
||||
private:
|
||||
public:
|
||||
SymbolPartP part; ///< Affected part
|
||||
String part_name; ///< New name
|
||||
size_t old_cursor; ///< Cursor position
|
||||
size_t new_cursor; ///< Cursor position
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add symbol part
|
||||
@@ -224,18 +237,18 @@ class DuplicateSymbolPartsAction : public SymbolPartListAction {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reorder symbol parts
|
||||
|
||||
/// Change the position of a part in a symbol, by swapping two parts.
|
||||
/// Change the position of a part in a symbol, by moving a part.
|
||||
class ReorderSymbolPartsAction : public SymbolPartListAction {
|
||||
public:
|
||||
ReorderSymbolPartsAction(Symbol& symbol, size_t part_id1, size_t part_id2);
|
||||
ReorderSymbolPartsAction(Symbol& symbol, size_t old_position, size_t new_position);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
Symbol& symbol; ///< Symbol to swap the parts in
|
||||
Symbol& symbol; ///< Symbol to swap the parts in
|
||||
public:
|
||||
size_t part_id1, part_id2; ///< Indices of parts to swap
|
||||
size_t old_position, new_position; ///< Positions to move from and to
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -349,8 +349,8 @@ SinglePointRemoveAction::SinglePointRemoveAction(const SymbolShapeP& shape, UInt
|
||||
Vector2D p = c.pointAt(t);
|
||||
Vector2D distP = point->pos - p;
|
||||
// adjust handle sizes
|
||||
point1.other.delta_after *= ssqrt(distP.dot(point1.other.delta_after) /point1.other.delta_after.lengthSqr()) + 1;
|
||||
point2.other.delta_before *= ssqrt(distP.dot(point2.other.delta_before)/point2.other.delta_before.lengthSqr()) + 1;
|
||||
point1.other.delta_after *= ssqrt(dot(distP, point1.other.delta_after) /point1.other.delta_after.lengthSqr()) + 1;
|
||||
point2.other.delta_before *= ssqrt(dot(distP, point2.other.delta_before)/point2.other.delta_before.lengthSqr()) + 1;
|
||||
|
||||
// unlock if needed
|
||||
if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR;
|
||||
|
||||
@@ -245,7 +245,7 @@ void mark_corners(SymbolShape& shape) {
|
||||
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) {
|
||||
if (dot(before,after) >= -0.25f) {
|
||||
// corner
|
||||
current.lock = LOCK_FREE;
|
||||
} else {
|
||||
@@ -285,8 +285,8 @@ void merge_corners(SymbolShape& shape) {
|
||||
for (int j = 0 ; j < 4 ; ++j) {
|
||||
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);
|
||||
double a_dot = dot(a_, ab);
|
||||
double b_dot = -dot(b_, ab);
|
||||
if (a_dot < min_a_dot) {
|
||||
min_a_dot = a_dot;
|
||||
a = a_;
|
||||
@@ -300,8 +300,8 @@ void merge_corners(SymbolShape& shape) {
|
||||
// t a + ab = u b, solve for t,u
|
||||
// Gives us:
|
||||
// t = ab cross b / b cross a
|
||||
double tden = max(0.00000001, b.cross(a));
|
||||
double t = ab.cross(b) / tden;
|
||||
double tden = max(0.00000001, cross(b,a));
|
||||
double t = cross(ab,b) / tden;
|
||||
// do these tangent lines intersect, and not too far away?
|
||||
// if so, then the intersection point is the merged point
|
||||
if (t >= 0 && t < 20.0) {
|
||||
@@ -358,7 +358,7 @@ void straighten(SymbolShape& shape) {
|
||||
Vector2D bb = next.delta_before.normalized();
|
||||
// if the area beneath the polygon formed by the handles is small
|
||||
// then it is a straight line
|
||||
double cpDot = abs(aa.cross(ab)) + abs(bb.cross(ab));
|
||||
double cpDot = abs(cross(aa,ab)) + abs(cross(bb,ab));
|
||||
if (cpDot < treshold) {
|
||||
cur.segment_after = next.segment_before = SEGMENT_LINE;
|
||||
cur.delta_after = next.delta_before = Vector2D();
|
||||
|
||||
@@ -94,6 +94,11 @@ Vector2D& ControlPoint::getOther(WhichHandle wh) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolPart
|
||||
|
||||
void SymbolPart::calculateBounds() {
|
||||
min_pos = Vector2D::infinity();
|
||||
max_pos = -Vector2D::infinity();
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolPart) {
|
||||
REFLECT_IF_NOT_READING {
|
||||
String type = typeName();
|
||||
@@ -235,6 +240,19 @@ SymbolPartP SymbolGroup::clone() const {
|
||||
return part;
|
||||
}
|
||||
|
||||
void SymbolGroup::calculateBounds() {
|
||||
FOR_EACH(p, parts) p->calculateBounds();
|
||||
calculateBoundsNonRec();
|
||||
}
|
||||
void SymbolGroup::calculateBoundsNonRec() {
|
||||
min_pos = Vector2D::infinity();
|
||||
max_pos = -Vector2D::infinity();
|
||||
FOR_EACH(p, parts) {
|
||||
min_pos = piecewise_min(min_pos, p->min_pos);
|
||||
max_pos = piecewise_max(max_pos, p->max_pos);
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolGroup) {
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(parts);
|
||||
|
||||
+13
-8
@@ -112,6 +112,9 @@ class SymbolPart : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
/// Name/label for this part
|
||||
String name;
|
||||
/// Position and size of the part.
|
||||
/** this is the smallest axis aligned bounding box that fits around the part */
|
||||
Vector2D min_pos, max_pos;
|
||||
|
||||
/// Type of this part
|
||||
virtual String typeName() const = 0;
|
||||
@@ -130,6 +133,9 @@ class SymbolPart : public IntrusivePtrVirtualBase {
|
||||
virtual SymbolGroup* isSymbolGroup() { return nullptr; }
|
||||
virtual const SymbolGroup* isSymbolGroup() const { return nullptr; }
|
||||
|
||||
/// Calculate the position and size of the part (min_pos and max_pos)
|
||||
virtual void calculateBounds();
|
||||
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
};
|
||||
|
||||
@@ -162,9 +168,6 @@ class SymbolShape : public SymbolPart {
|
||||
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;
|
||||
|
||||
SymbolShape();
|
||||
|
||||
@@ -183,7 +186,7 @@ class SymbolShape : public SymbolPart {
|
||||
void enforceConstraints();
|
||||
|
||||
/// Calculate the position and size of the part
|
||||
void calculateBounds();
|
||||
virtual void calculateBounds();
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
@@ -221,7 +224,7 @@ class SymbolSymmetry : public SymbolPart {
|
||||
/// A group of symbol parts
|
||||
class SymbolGroup : public SymbolPart {
|
||||
public:
|
||||
vector<SymbolPartP> parts; ///< The parts in this group
|
||||
vector<SymbolPartP> parts; ///< The parts in this group, first item is on top
|
||||
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
@@ -229,16 +232,18 @@ class SymbolGroup : public SymbolPart {
|
||||
virtual SymbolGroup* isSymbolGroup() { return this; }
|
||||
virtual const SymbolGroup* isSymbolGroup() const { return this; }
|
||||
|
||||
virtual void calculateBounds();
|
||||
/// re-calculate the bounds, but not of the contained parts
|
||||
void calculateBoundsNonRec();
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Symbol
|
||||
|
||||
/// An editable symbol, consists of any number of SymbolParts
|
||||
class Symbol : public IntrusivePtrBase<Symbol> {
|
||||
class Symbol : public SymbolGroup {
|
||||
public:
|
||||
/// The parts of this symbol, first item is on top
|
||||
vector<SymbolPartP> parts;
|
||||
/// Actions performed on this symbol and the parts in it
|
||||
ActionStack actions;
|
||||
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ bool pos_on_line(const Vector2D& pos, double range, const Vector2D& p1, const Ve
|
||||
Vector2D p21 = p2 - p1;
|
||||
double p21len = p21.lengthSqr();
|
||||
if (p21len < 0.00001) return false; // line is too short
|
||||
t = p21.dot(pos - p1) / p21len; // 'time' on line p1->p2
|
||||
t = dot(p21, pos - p1) / p21len; // 'time' on line p1->p2
|
||||
if (t < 0 || t > 1) return false; // outside segment
|
||||
pOut = p1 + p21 * t; // point on line
|
||||
Vector2D dist = pOut - pos; // distance to line
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include <gui/symbol/basic_shape_editor.hpp>
|
||||
#include <gui/util.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <data/action/symbol.hpp>
|
||||
#include <data/action/symbol_part.hpp>
|
||||
#include <wx/spinctrl.h>
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolBasicShapeEditor
|
||||
@@ -108,7 +110,7 @@ void SymbolBasicShapeEditor::onMouseDrag (const Vector2D& from, const Vector2D&
|
||||
// Resize the object
|
||||
if (drawing) {
|
||||
end = to;
|
||||
makeShape(start, end, ev.ControlDown(), ev.ShiftDown());
|
||||
makeShape(start, end, ev.ControlDown(), settings.symbol_grid_snap, ev.ShiftDown());
|
||||
control.Refresh(false);
|
||||
}
|
||||
}
|
||||
@@ -119,7 +121,7 @@ void SymbolBasicShapeEditor::onKeyChange(wxKeyEvent& ev) {
|
||||
if (drawing) {
|
||||
if (ev.GetKeyCode() == WXK_CONTROL || ev.GetKeyCode() == WXK_SHIFT) {
|
||||
// changed constrains
|
||||
makeShape(start, end, ev.ControlDown(), ev.ShiftDown());
|
||||
makeShape(start, end, ev.ControlDown(), settings.symbol_grid_snap, ev.ShiftDown());
|
||||
control.Refresh(false);
|
||||
} else if (ev.GetKeyCode() == WXK_ESCAPE) {
|
||||
// cancel drawing
|
||||
@@ -156,7 +158,12 @@ inline double sgn(double d) {
|
||||
return d < 0 ? - 1 : 1;
|
||||
}
|
||||
|
||||
void SymbolBasicShapeEditor::makeShape(const Vector2D& a, const Vector2D& b, bool constrained, bool centered) {
|
||||
void SymbolBasicShapeEditor::makeShape(Vector2D a, Vector2D b, bool constrained, bool snap, bool centered) {
|
||||
// snap
|
||||
if (snap) {
|
||||
a = snap_vector(a, settings.symbol_grid_size);
|
||||
b = snap_vector(b, settings.symbol_grid_size);
|
||||
}
|
||||
// constrain
|
||||
Vector2D size = b - a;
|
||||
if (constrained) {
|
||||
|
||||
@@ -63,7 +63,7 @@ class SymbolBasicShapeEditor : public SymbolEditorBase {
|
||||
/** when centered: a = center, b-a = radius
|
||||
* otherwise: a = top left, b = bottom right
|
||||
*/
|
||||
void makeShape(const Vector2D& a, const Vector2D& b, bool constrained, bool centered);
|
||||
void makeShape(Vector2D a, Vector2D b, bool constrained, bool snap, bool centered);
|
||||
|
||||
/// Make the shape, centered in c, with radius r
|
||||
void makeCenteredShape(const Vector2D& c, Vector2D r, bool constrained);
|
||||
|
||||
@@ -162,20 +162,20 @@ void SymbolControl::draw(DC& dc) {
|
||||
SymbolViewer::draw(dc);
|
||||
// draw grid
|
||||
if (settings.symbol_grid) {
|
||||
wxSize s = dc.GetSize();
|
||||
double lines = settings.symbol_grid_size;
|
||||
double end = rotation.trS(1);
|
||||
for (int i = 0 ; i <= lines ; ++i) {
|
||||
int x = (int) floor(rotation.trS(i/lines-0.0001));
|
||||
//dc.SetPen(Color(0, i%5 == 0 ? 64 : 31, 0));
|
||||
//dc.SetPen(Color(i%5 == 0 ? 64 : 31, 0, 0));
|
||||
dc.SetLogicalFunction(wxAND);
|
||||
dc.SetPen(i%5 == 0 ? Color(191,255,191) : Color(191, 255, 191));
|
||||
dc.DrawLine(x, 0, x, s.y);
|
||||
dc.DrawLine(0, x, s.x, x);
|
||||
dc.DrawLine(x, 0, x, end);
|
||||
dc.DrawLine(0, x, end, x);
|
||||
dc.SetLogicalFunction(wxOR);
|
||||
dc.SetPen(i%5 == 0 ? Color(0,63,0) : Color(0, 31, 0));
|
||||
dc.DrawLine(x, 0, x, s.y);
|
||||
dc.DrawLine(0, x, s.x, x);
|
||||
dc.DrawLine(x, 0, x, end);
|
||||
dc.DrawLine(0, x, end, x);
|
||||
}
|
||||
dc.SetLogicalFunction(wxCOPY);
|
||||
}
|
||||
@@ -251,7 +251,7 @@ void SymbolControl::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
ev.Check(editor->modeToolId() == ev.GetId());
|
||||
if (ev.GetId() == ID_MODE_POINTS) {
|
||||
// can only edit points when a single part is selected <TODO?>
|
||||
ev.Enable(selected_parts.size() == 1);
|
||||
ev.Enable(selected_parts.size() == 1 && (*selected_parts.begin())->isSymbolShape());
|
||||
}
|
||||
break;
|
||||
case ID_MODE_PAINT:
|
||||
|
||||
@@ -72,7 +72,8 @@ class SymbolControl : public wxControl, public SymbolViewer {
|
||||
public:
|
||||
/// What parts are selected?
|
||||
set<SymbolPartP> selected_parts;
|
||||
SymbolShapeP selected_shape; // if there is a single selection
|
||||
SymbolPartP highlight_part; ///< part the mouse cursor is over
|
||||
SymbolShapeP selected_shape; ///< if there is a single selection
|
||||
|
||||
/// Parent window
|
||||
SymbolWindow* parent;
|
||||
|
||||
+419
-111
@@ -9,147 +9,455 @@
|
||||
#include <gui/symbol/part_list.hpp>
|
||||
#include <gui/util.hpp>
|
||||
#include <data/action/symbol.hpp>
|
||||
#include <wx/imaglist.h>
|
||||
#include <gfx/gfx.hpp>
|
||||
#include <render/symbol/filter.hpp>
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/caret.h>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Constructor
|
||||
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||
|
||||
SymbolPartList::SymbolPartList(Window* parent, int id, SymbolP symbol)
|
||||
: wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize,
|
||||
wxLC_REPORT | wxLC_NO_HEADER | wxLC_VIRTUAL | wxLC_EDIT_LABELS)
|
||||
// ----------------------------------------------------------------------------- : Events
|
||||
|
||||
DEFINE_EVENT_TYPE(EVENT_PART_SELECT);
|
||||
DEFINE_EVENT_TYPE(EVENT_PART_ACTIVATE);
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolPartList
|
||||
|
||||
|
||||
SymbolPartList::SymbolPartList(Window* parent, int id, set<SymbolPartP>& selection, SymbolP symbol)
|
||||
: wxScrolledWindow(parent, id, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER | wxVSCROLL)
|
||||
, selection(selection)
|
||||
, state_icons(9,8)
|
||||
{
|
||||
// Create image list
|
||||
wxImageList* images = new wxImageList(16,16);
|
||||
// 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"));
|
||||
state_icons.Add(load_resource_image(_("icon_combine_merge")));
|
||||
state_icons.Add(load_resource_image(_("icon_combine_subtract")));
|
||||
state_icons.Add(load_resource_image(_("icon_combine_intersection")));
|
||||
state_icons.Add(load_resource_image(_("icon_combine_difference")));
|
||||
state_icons.Add(load_resource_image(_("icon_combine_overlap")));
|
||||
state_icons.Add(load_resource_image(_("icon_combine_border")));
|
||||
state_icons.Add(load_resource_image(_("icon_symmetry_rotation")));
|
||||
state_icons.Add(load_resource_image(_("icon_symmetry_reflection")));
|
||||
state_icons.Add(load_resource_image(_("icon_symbol_group")));
|
||||
// view symbol
|
||||
setSymbol(symbol);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : View events
|
||||
|
||||
void SymbolPartList::onChangeSymbol() {
|
||||
typing_in = SymbolPartP();
|
||||
cursor = 0;
|
||||
update();
|
||||
}
|
||||
|
||||
void SymbolPartList::onAction(const Action& action, bool undone) {
|
||||
TYPE_CASE(action, ReorderSymbolPartsAction) {
|
||||
if (selected == (long) action.part_id1) {
|
||||
selectItem((long) action.part_id2);
|
||||
} else if (selected == (long) action.part_id2) {
|
||||
selectItem((long) action.part_id1);
|
||||
TYPE_CASE(action, SymbolPartNameAction) {
|
||||
if (typing_in == action.part) {
|
||||
cursor = action.new_cursor;
|
||||
}
|
||||
Refresh(false);
|
||||
if (true) return;
|
||||
}
|
||||
TYPE_CASE_(action, SymbolPartListAction) {
|
||||
update();
|
||||
return;
|
||||
}
|
||||
TYPE_CASE(action, SymbolPartsAction) {
|
||||
symbol_preview.up_to_date = false;
|
||||
updateParts(action.parts);
|
||||
if (true) return;
|
||||
}
|
||||
TYPE_CASE_(action, SymbolPartAction) {
|
||||
symbol_preview.up_to_date = false;
|
||||
// some part changed, but we don't know which one, assume it is the selection
|
||||
updateParts(selection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Other
|
||||
|
||||
String SymbolPartList::OnGetItemText(long item, long col) const {
|
||||
assert(col == 0);
|
||||
return getPart(item)->name;
|
||||
}
|
||||
int SymbolPartList::OnGetItemImage(long item) const {
|
||||
return getPart(item)->icon();
|
||||
}
|
||||
|
||||
SymbolPartP SymbolPartList::getPart(long item) const {
|
||||
return symbol->parts.at(item);
|
||||
}
|
||||
void SymbolPartList::selectItem(long item) {
|
||||
selected = (long)item;
|
||||
long count = GetItemCount();
|
||||
for (long i = 0 ; i < count ; ++i) {
|
||||
SetItemState(i, i == selected ? wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED : 0,
|
||||
wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolPartList::selectParts(const set<SymbolPartP>& sel) {
|
||||
long count = GetItemCount();
|
||||
for (long i = 0 ; i < count ; ++ i) {
|
||||
// is that part selected?
|
||||
bool selected = sel.find(symbol->parts.at(i)) != sel.end();
|
||||
SetItemState(i, selected ? wxLIST_STATE_SELECTED : 0,
|
||||
wxLIST_STATE_SELECTED);
|
||||
}
|
||||
wxSize SymbolPartList::DoGetBestSize() const {
|
||||
wxSize ws = GetSize(), cs = GetClientSize();
|
||||
const int w = 110;
|
||||
const int h = ITEM_HEIGHT;
|
||||
return wxSize(w, h) + ws - cs;
|
||||
}
|
||||
|
||||
void SymbolPartList::update() {
|
||||
if (symbol->parts.empty()) {
|
||||
// deleting all items requires a full refresh on win32
|
||||
SetItemCount(0);
|
||||
Refresh(true);
|
||||
} else {
|
||||
SetItemCount((long) symbol->parts.size() );
|
||||
Refresh(false);
|
||||
// count items
|
||||
number_of_items = childCount(symbol);
|
||||
SetVirtualSize(110, number_of_items * (ITEM_HEIGHT+1));
|
||||
// invalidate previews
|
||||
symbol_preview.up_to_date = false;
|
||||
for (size_t i = 0 ; i < part_previews.size() ; ++i) {
|
||||
part_previews[i].up_to_date = false;
|
||||
}
|
||||
// refresh
|
||||
Refresh(false);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Event handling
|
||||
|
||||
void SymbolPartList::onSelect(wxListEvent& ev) {
|
||||
selected = ev.GetIndex();
|
||||
ev.Skip();
|
||||
void SymbolPartList::updateParts(const set<SymbolPartP>& parts) {
|
||||
symbol_preview.up_to_date = false;
|
||||
int i = 0;
|
||||
FOR_EACH(p, symbol->parts) {
|
||||
updatePart(parts, i, false, p);
|
||||
}
|
||||
// refresh
|
||||
Refresh(false);
|
||||
}
|
||||
void SymbolPartList::onDeselect(wxListEvent& ev) {
|
||||
selected = -1;
|
||||
ev.Skip();
|
||||
}
|
||||
|
||||
void SymbolPartList::onLabelEdit(wxListEvent& ev){
|
||||
symbol->actions.add(
|
||||
new SymbolPartNameAction(getPart(ev.GetIndex()), ev.GetLabel())
|
||||
);
|
||||
}
|
||||
|
||||
void SymbolPartList::onSize(wxSizeEvent& ev) {
|
||||
wxSize s = GetClientSize();
|
||||
SetColumnWidth(0, s.GetWidth() - 2);
|
||||
}
|
||||
|
||||
void SymbolPartList::onDrag(wxMouseEvent& ev) {
|
||||
if (!ev.Dragging() || selected == -1) return;
|
||||
// reorder the list of parts
|
||||
int flags;
|
||||
long item = HitTest(ev.GetPosition(), flags);
|
||||
if (flags & wxLIST_HITTEST_ONITEM) {
|
||||
if (item > 0) EnsureVisible(item - 1);
|
||||
if (item < GetItemCount() - 1) EnsureVisible(item + 1);
|
||||
if (item != selected) {
|
||||
// swap the two items
|
||||
symbol->actions.add(new ReorderSymbolPartsAction(*symbol, item, selected));
|
||||
selectItem(item); // deselect all other items, to prevent 'lassoing' them
|
||||
void SymbolPartList::updatePart(const set<SymbolPartP>& parts, int& i, bool parent_updated, const SymbolPartP& part) {
|
||||
bool update = parent_updated || parts.find(part) != parts.end();
|
||||
if (update) {
|
||||
part_previews[i].up_to_date = false;
|
||||
}
|
||||
++i;
|
||||
if (SymbolGroup* g = part->isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
updatePart(parts, i, update, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Events
|
||||
|
||||
void SymbolPartList::onLeftDown(wxMouseEvent& ev) {
|
||||
// find item under cursor
|
||||
if (ev.GetX() < 0 || ev.GetX() >= GetClientSize().x) return;
|
||||
SymbolPartP part = findItem(ev.GetY() / (ITEM_HEIGHT + 1));
|
||||
if (part) {
|
||||
// toggle/select
|
||||
if (!ev.ShiftDown()) {
|
||||
selection.clear();
|
||||
}
|
||||
if (selection.find(part) != selection.end()) {
|
||||
selection.erase(part);
|
||||
} else {
|
||||
selection.insert(part);
|
||||
}
|
||||
if (!ev.ShiftDown()) {
|
||||
// drag item
|
||||
mouse_down_on = part;
|
||||
drop_position = -1;
|
||||
CaptureMouse();
|
||||
}
|
||||
sendEvent(EVENT_PART_SELECT);
|
||||
Refresh(false);
|
||||
}
|
||||
ev.Skip(); // for focus
|
||||
}
|
||||
void SymbolPartList::onLeftUp(wxMouseEvent& ev) {
|
||||
if (HasCapture()) ReleaseMouse();
|
||||
if (mouse_down_on && drop_position != -1) {
|
||||
// move part
|
||||
// find old position
|
||||
vector<SymbolPartP>::const_iterator it = find(symbol->parts.begin(), symbol->parts.end(), mouse_down_on);
|
||||
mouse_down_on = SymbolPartP();
|
||||
if (it == symbol->parts.end()) {
|
||||
Refresh(false);
|
||||
return;
|
||||
}
|
||||
size_t old_position = it - symbol->parts.begin();
|
||||
// find new position
|
||||
size_t new_position;
|
||||
SymbolPartP drop_before = findItem(drop_position);
|
||||
it = find(symbol->parts.begin(), symbol->parts.end(), drop_before);
|
||||
if (it == symbol->parts.end()) {
|
||||
new_position = number_of_items - 1;
|
||||
} else {
|
||||
new_position = it - symbol->parts.begin();
|
||||
if (old_position < new_position) new_position -= 1;
|
||||
}
|
||||
// move part
|
||||
if (old_position != new_position) {
|
||||
symbol->actions.add(new ReorderSymbolPartsAction(*symbol, old_position, new_position));
|
||||
} else {
|
||||
Refresh(false);
|
||||
}
|
||||
} else {
|
||||
mouse_down_on = SymbolPartP();
|
||||
}
|
||||
}
|
||||
void SymbolPartList::onMotion(wxMouseEvent& ev) {
|
||||
if (mouse_down_on) {
|
||||
int new_drop_position = (ev.GetY() + ITEM_HEIGHT/2) / (ITEM_HEIGHT + 1);
|
||||
if (new_drop_position < 0 || new_drop_position > number_of_items) new_drop_position = -1;
|
||||
// TODO: make sure it is not in a group
|
||||
if (drop_position != new_drop_position) {
|
||||
drop_position = new_drop_position;
|
||||
Refresh(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolPartList::onLeftDClick(wxMouseEvent& ev) {
|
||||
// double click = activate
|
||||
sendEvent(EVENT_PART_ACTIVATE);
|
||||
}
|
||||
|
||||
void SymbolPartList::onChar(wxKeyEvent& ev) {
|
||||
if (typing_in) {
|
||||
// Typing in name
|
||||
String new_name = typing_in->name;
|
||||
switch (ev.GetKeyCode()) {
|
||||
case WXK_HOME:
|
||||
if (cursor != 0) {
|
||||
cursor = 0; Refresh(false); // TODO: without refresh
|
||||
}
|
||||
break;
|
||||
case WXK_END:
|
||||
if (cursor != typing_in->name.size()) {
|
||||
cursor = typing_in->name.size(); Refresh(false);
|
||||
}
|
||||
break;
|
||||
case WXK_LEFT:
|
||||
if (cursor > 0) {
|
||||
--cursor; Refresh(false);
|
||||
}
|
||||
break;
|
||||
case WXK_RIGHT:
|
||||
if (cursor < typing_in->name.size()) {
|
||||
++cursor; Refresh(false);
|
||||
}
|
||||
break;
|
||||
case WXK_BACK:
|
||||
if (cursor > 0 && cursor <= typing_in->name.size()) {
|
||||
String new_name = typing_in->name;
|
||||
new_name.erase(cursor - 1, 1);
|
||||
symbol->actions.add(new SymbolPartNameAction(typing_in, new_name, cursor, cursor - 1));
|
||||
}
|
||||
break;
|
||||
case WXK_DELETE:
|
||||
if (cursor < typing_in->name.size()) {
|
||||
String new_name = typing_in->name;
|
||||
new_name.erase(cursor, 1);
|
||||
symbol->actions.add(new SymbolPartNameAction(typing_in, new_name, cursor, cursor));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// See gui/value/text.cpp
|
||||
#ifdef __WXMSW__
|
||||
if (ev.GetKeyCode() >= _(' ') && ev.GetKeyCode() == (int)ev.GetRawKeyCode()) {
|
||||
#else
|
||||
if (ev.GetKeyCode() >= _(' ') /*&& ev.GetKeyCode() == (int)ev.GetRawKeyCode()*/) {
|
||||
#endif
|
||||
#ifdef UNICODE
|
||||
Char key = ev.GetUnicodeKey();
|
||||
#else
|
||||
Char key = (Char)ev.GetKeyCode();
|
||||
#endif
|
||||
String new_name = typing_in->name;
|
||||
new_name.insert(cursor, 1, key);
|
||||
symbol->actions.add(new SymbolPartNameAction(typing_in, new_name, cursor, cursor + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move up/down?
|
||||
}
|
||||
|
||||
void SymbolPartList::onSize(wxSizeEvent&) {
|
||||
Refresh(false);
|
||||
}
|
||||
|
||||
void SymbolPartList::sendEvent(int type) {
|
||||
wxCommandEvent ev(type, GetId());
|
||||
ProcessEvent(ev);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Items
|
||||
|
||||
SymbolPartP SymbolPartList::findItem(int i) const {
|
||||
FOR_EACH(p, symbol->parts) {
|
||||
SymbolPartP f = findItem(i, p);
|
||||
if (f) return f;
|
||||
}
|
||||
return SymbolPartP();
|
||||
}
|
||||
SymbolPartP SymbolPartList::findItem(int& i, const SymbolPartP& part) {
|
||||
if (i < 0 ) return SymbolPartP();
|
||||
if (i == 0) return part;
|
||||
i -= 1;
|
||||
// sub item?
|
||||
if (SymbolGroup* g = part->isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
if (findItem(i, p)) return part;
|
||||
}
|
||||
}
|
||||
return SymbolPartP();
|
||||
}
|
||||
|
||||
int SymbolPartList::childCount(const SymbolPartP& part) {
|
||||
if (SymbolGroup* g = part->isSymbolGroup()) {
|
||||
int count = 0;
|
||||
FOR_EACH(p, g->parts) count += 1 + childCount(p);
|
||||
return count;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Text editor
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Drawing
|
||||
|
||||
|
||||
void SymbolPartList::onPaint(wxPaintEvent&) {
|
||||
wxBufferedPaintDC dc(this);
|
||||
DoPrepareDC(dc);
|
||||
OnDraw(dc);
|
||||
}
|
||||
void SymbolPartList::OnDraw(DC& dc) {
|
||||
// init
|
||||
dc.SetFont(*wxNORMAL_FONT);
|
||||
// clear background
|
||||
wxSize size = GetClientSize();
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
dc.DrawRectangle(0,0,size.x,size.y);
|
||||
// items
|
||||
int i = 0;
|
||||
FOR_EACH(p, symbol->parts) {
|
||||
drawItem(dc, 0, i, false, p);
|
||||
}
|
||||
// drag/drop indicator
|
||||
if (mouse_down_on && drop_position != -1) {
|
||||
dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), 3));
|
||||
int y = drop_position * (ITEM_HEIGHT + 1) - 1;
|
||||
dc.DrawLine(0,y,size.x,y);
|
||||
dc.DrawLine(0,y-3,0,y+3);
|
||||
dc.DrawLine(size.x-1,y-3,size.x-1,y+3);
|
||||
}
|
||||
// hide caret
|
||||
if (selection.size() != 1) {
|
||||
typing_in = SymbolPartP();
|
||||
wxCaret* caret = GetCaret();
|
||||
if (caret) caret->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolPartList::drawItem(DC& dc, int x, int& i, bool parent_active, const SymbolPartP& part) {
|
||||
wxSize size = GetClientSize();
|
||||
int w = size.x - x;
|
||||
int y = i * (ITEM_HEIGHT + 1);
|
||||
// draw item : highlight
|
||||
Color background;
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
bool active = selection.find(part) != selection.end();
|
||||
if (active) {
|
||||
background = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
|
||||
dc.SetBrush(background);
|
||||
dc.DrawRectangle(x,y,w,ITEM_HEIGHT);
|
||||
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
|
||||
} else if (parent_active) {
|
||||
background = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
|
||||
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
|
||||
} else {
|
||||
background = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
|
||||
}
|
||||
// draw item : name
|
||||
int h = dc.GetCharHeight();
|
||||
dc.DrawText(part->name, ITEM_HEIGHT + x + 3, y + (ITEM_HEIGHT - h) / 2);
|
||||
// draw item : icon
|
||||
dc.SetBrush(lerp(background,wxColour(0,128,0),0.7));
|
||||
dc.DrawRectangle(x,y,ITEM_HEIGHT,ITEM_HEIGHT);
|
||||
dc.DrawBitmap(symbolPreview(), x, y);
|
||||
dc.DrawBitmap(itemPreview(i,part), x, y);
|
||||
// draw item : border
|
||||
wxPen line_pen = lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW),
|
||||
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),0.5);
|
||||
dc.SetPen(line_pen);
|
||||
dc.DrawLine(x+ITEM_HEIGHT, y, x+ITEM_HEIGHT, y + ITEM_HEIGHT + 1); // line after image
|
||||
dc.DrawLine(x, y + ITEM_HEIGHT, size.x, y + ITEM_HEIGHT); // line below
|
||||
// update caret
|
||||
if (selection.size() == 1 && active) {
|
||||
updateCaret(dc, x + ITEM_HEIGHT + 3, y + (ITEM_HEIGHT - h) / 2, h, part);
|
||||
}
|
||||
// move down
|
||||
i += 1;
|
||||
// draw more?
|
||||
if (SymbolShape* s = part->isSymbolShape()) {
|
||||
// combine state
|
||||
state_icons.Draw(s->combine, dc, size.x - 10, y + 1);
|
||||
} else if (SymbolSymmetry* s = part->isSymbolSymmetry()) {
|
||||
// kind of symmetry
|
||||
state_icons.Draw(s->kind, dc, size.x - 10, y + 1);
|
||||
// TODO: show clip mode?
|
||||
} else if (SymbolGroup* g = part->isSymbolGroup()) {
|
||||
state_icons.Draw(SYMMETRY_REFLECTION + 1, dc, size.x - 10, y + 1);
|
||||
FOR_EACH(p, g->parts) drawItem(dc, x + 5, i, active || parent_active, p);
|
||||
// draw bar on the left
|
||||
int new_y = i * (ITEM_HEIGHT + 1);
|
||||
y += ITEM_HEIGHT+1;
|
||||
if (y != new_y) {
|
||||
dc.SetPen(line_pen);
|
||||
dc.SetBrush(background);
|
||||
dc.DrawRectangle(x-1,y-1,5+1,new_y-y+1);
|
||||
}
|
||||
} else {
|
||||
throw "Unknown symbol part type";
|
||||
}
|
||||
}
|
||||
|
||||
const Image& SymbolPartList::itemPreview(int i, const SymbolPartP& part) {
|
||||
if ((size_t)i >= part_previews.size()) part_previews.resize(i + 1);
|
||||
Preview& p = part_previews[i];
|
||||
if (!p.up_to_date) {
|
||||
SolidFillSymbolFilter filter(*wxBLACK, Color(255,128,128));
|
||||
// temporary symbol
|
||||
SymbolP sym(new Symbol); sym->parts.push_back(part);
|
||||
Image img;
|
||||
if (SymbolShape* s = part->isSymbolShape()) {
|
||||
if (s->combine == SYMBOL_COMBINE_SUBTRACT) {
|
||||
// temporarily render using subtract instead, otherwise we don't see anything
|
||||
s->combine = SYMBOL_COMBINE_BORDER;
|
||||
img = render_symbol(sym, filter, 0.08, ITEM_HEIGHT * 4);
|
||||
s->combine = SYMBOL_COMBINE_SUBTRACT;
|
||||
} else {
|
||||
img = render_symbol(sym, filter, 0.08, ITEM_HEIGHT * 4);
|
||||
}
|
||||
} else {
|
||||
img = render_symbol(sym, filter, 0.08, ITEM_HEIGHT * 4);
|
||||
}
|
||||
resample(img, p.image);
|
||||
p.up_to_date = true;
|
||||
}
|
||||
return p.image;
|
||||
}
|
||||
const Image& SymbolPartList::symbolPreview() {
|
||||
if (!symbol_preview.up_to_date) {
|
||||
SolidFillSymbolFilter filter(AColor(0,0,0,40), AColor(255,255,255,40));
|
||||
Image img = render_symbol(symbol, filter, 0.06, ITEM_HEIGHT * 4);
|
||||
resample(img, symbol_preview.image);
|
||||
symbol_preview.up_to_date = true;
|
||||
}
|
||||
return symbol_preview.image;
|
||||
}
|
||||
|
||||
void SymbolPartList::updateCaret(DC& dc, int x, int y, int h, const SymbolPartP& part) {
|
||||
// make caret
|
||||
wxCaret* caret = GetCaret();
|
||||
if (!caret) {
|
||||
caret = new wxCaret(this, 1, h);
|
||||
SetCaret(caret);
|
||||
}
|
||||
// move caret
|
||||
if (typing_in != part) {
|
||||
typing_in = part;
|
||||
cursor = typing_in->name.size();
|
||||
}
|
||||
cursor = min(cursor, typing_in->name.size());
|
||||
int w;
|
||||
dc.GetTextExtent(typing_in->name.substr(0,cursor), &w, nullptr);
|
||||
caret->Move(x+w,y);
|
||||
if (!caret->IsVisible()) caret->Show();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Event table
|
||||
|
||||
BEGIN_EVENT_TABLE(SymbolPartList, wxListCtrl)
|
||||
EVT_LIST_ITEM_SELECTED (wxID_ANY, SymbolPartList::onSelect)
|
||||
EVT_LIST_ITEM_DESELECTED (wxID_ANY, SymbolPartList::onDeselect)
|
||||
EVT_LIST_END_LABEL_EDIT (wxID_ANY, SymbolPartList::onLabelEdit)
|
||||
EVT_SIZE ( SymbolPartList::onSize)
|
||||
EVT_MOTION ( SymbolPartList::onDrag)
|
||||
BEGIN_EVENT_TABLE(SymbolPartList, wxScrolledWindow)
|
||||
EVT_LEFT_DOWN (SymbolPartList::onLeftDown)
|
||||
EVT_LEFT_DCLICK (SymbolPartList::onLeftDClick)
|
||||
EVT_LEFT_UP (SymbolPartList::onLeftUp)
|
||||
EVT_MOTION (SymbolPartList::onMotion)
|
||||
EVT_CHAR (SymbolPartList::onChar)
|
||||
EVT_PAINT (SymbolPartList::onPaint)
|
||||
EVT_SIZE (SymbolPartList::onSize)
|
||||
END_EVENT_TABLE ()
|
||||
|
||||
@@ -11,61 +11,80 @@
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <data/symbol.hpp>
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Events
|
||||
|
||||
DECLARE_EVENT_TYPE(EVENT_PART_SELECT, <not used>)
|
||||
DECLARE_EVENT_TYPE(EVENT_PART_ACTIVATE, <not used>)
|
||||
|
||||
/// Handle EVENT_PART_SELECT events
|
||||
#define EVT_PART_SELECT( id, handler) EVT_COMMAND(id, EVENT_PART_SELECT, handler)
|
||||
/// Handle EVENT_PART_ACTIVATE events
|
||||
#define EVT_PART_ACTIVATE(id, handler) EVT_COMMAND(id, EVENT_PART_ACTIVATE, handler)
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolPartList
|
||||
|
||||
// A list view of parts of a symbol
|
||||
class SymbolPartList : public wxListCtrl, public SymbolView {
|
||||
class SymbolPartList : public wxScrolledWindow, public SymbolView {
|
||||
public:
|
||||
SymbolPartList(Window* parent, int id, SymbolP symbol = SymbolP());
|
||||
|
||||
/// Update the list
|
||||
void update();
|
||||
|
||||
/// Is there a selection?
|
||||
inline bool hasSelection() const { return selected != -1; }
|
||||
/// Return the last part that was selected
|
||||
/** @pre hasSelection()
|
||||
*/
|
||||
inline SymbolPartP getSelection() const { return getPart(selected); }
|
||||
|
||||
/// Get a set of selected parts
|
||||
void getSelectedParts(set<SymbolPartP>& sel);
|
||||
/// Select the specified parts, and nothing else
|
||||
void selectParts(const set<SymbolPartP>& sel);
|
||||
SymbolPartList(Window* parent, int id, set<SymbolPartP>& selection, SymbolP symbol = SymbolP());
|
||||
|
||||
/// Another symbol is being viewed
|
||||
void onChangeSymbol();
|
||||
|
||||
virtual void onChangeSymbol();
|
||||
/// Event handler for changes to the symbol
|
||||
virtual void onAction(const Action& a, bool undone);
|
||||
virtual void onAction(const Action&, bool);
|
||||
|
||||
/// Update the control
|
||||
void update();
|
||||
/// Update only a subset of the parts
|
||||
void updateParts(const set<SymbolPartP>& parts);
|
||||
|
||||
protected:
|
||||
/// Get the text of an item
|
||||
virtual String OnGetItemText(long item, long col) const;
|
||||
/// Get the icon of an item
|
||||
virtual int OnGetItemImage(long item) const;
|
||||
|
||||
virtual wxSize DoGetBestSize() const;
|
||||
private:
|
||||
/// The selected item, or -1 if there is no selection
|
||||
long selected;
|
||||
set<SymbolPartP>& selection; ///< Store selection here
|
||||
|
||||
/// Get a part from the symbol
|
||||
SymbolPartP getPart(long item) const;
|
||||
SymbolPartP mouse_down_on;
|
||||
int drop_position;
|
||||
int number_of_items;
|
||||
|
||||
/// Select an item, also in the list control
|
||||
/// Deselects all other items
|
||||
void selectItem(long item);
|
||||
SymbolPartP typing_in;
|
||||
size_t cursor;
|
||||
|
||||
wxImageList state_icons;
|
||||
struct Preview {
|
||||
Preview() : up_to_date(false), image(25,25) {}
|
||||
bool up_to_date;
|
||||
Image image;
|
||||
};
|
||||
Preview symbol_preview; ///< Preview of the whole symbol
|
||||
vector<Preview> part_previews;
|
||||
|
||||
static const int ITEM_HEIGHT = 25;
|
||||
// --------------------------------------------------- : Event handling
|
||||
DECLARE_EVENT_TABLE();
|
||||
|
||||
void onSelect (wxListEvent& ev);
|
||||
void onDeselect (wxListEvent& ev);
|
||||
void onLabelEdit(wxListEvent& ev);
|
||||
void onSize (wxSizeEvent& ev);
|
||||
void onDrag (wxMouseEvent& ev);
|
||||
void onLeftDown (wxMouseEvent& ev);
|
||||
void onLeftDClick(wxMouseEvent& ev);
|
||||
void onLeftUp (wxMouseEvent& ev);
|
||||
void onMotion (wxMouseEvent& ev);
|
||||
void onChar(wxKeyEvent& ev);
|
||||
void onPaint(wxPaintEvent&);
|
||||
void onSize(wxSizeEvent&);
|
||||
void OnDraw(DC& dc);
|
||||
|
||||
void sendEvent(int type);
|
||||
|
||||
void drawItem(DC& dc, int x, int& i, bool parent_active, const SymbolPartP& part);
|
||||
const Image& itemPreview(int i, const SymbolPartP& part);
|
||||
const Image& symbolPreview();
|
||||
void updatePart(const set<SymbolPartP>& parts, int& i, bool parent_updated, const SymbolPartP& part);
|
||||
|
||||
SymbolPartP findItem(int i) const;
|
||||
static SymbolPartP findItem(int& i, const SymbolPartP& part);
|
||||
|
||||
static int childCount(const SymbolPartP& part);
|
||||
|
||||
void updateCaret(DC& dc, int x, int y, int h, const SymbolPartP& part);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
@@ -37,11 +37,7 @@ SymbolSelectEditor::SymbolSelectEditor(SymbolControl* control, bool rotate)
|
||||
handleShearY = wxBitmap(rotate_image(shear,90));
|
||||
handleCenter = wxBitmap(load_resource_image(_("handle_center")));
|
||||
// Make sure all parts have updated bounds
|
||||
FOR_EACH(p, getSymbol()->parts) {
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
s->calculateBounds();
|
||||
}
|
||||
}
|
||||
getSymbol()->calculateBounds();
|
||||
resetActions();
|
||||
}
|
||||
|
||||
@@ -132,24 +128,32 @@ void SymbolSelectEditor::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
|
||||
void SymbolSelectEditor::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
if (ev.GetId() >= ID_SYMBOL_COMBINE && ev.GetId() < ID_SYMBOL_COMBINE_MAX) {
|
||||
if (control.selected_parts.empty()) {
|
||||
ev.Check(false);
|
||||
ev.Enable(false);
|
||||
} else {
|
||||
ev.Enable(true);
|
||||
bool check = true;
|
||||
FOR_EACH(p, control.selected_parts) {
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
if (s->combine != ev.GetId() - ID_SYMBOL_COMBINE) {
|
||||
check = false;
|
||||
break;
|
||||
}
|
||||
} // disable when symmetries are selected?
|
||||
}
|
||||
ev.Check(check);
|
||||
bool enable = false;
|
||||
bool check = true;
|
||||
FOR_EACH(p, control.selected_parts) {
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
enable = true;
|
||||
if (s->combine != ev.GetId() - ID_SYMBOL_COMBINE) {
|
||||
check = false;
|
||||
break;
|
||||
}
|
||||
} // disable when symmetries are selected?
|
||||
}
|
||||
ev.Enable(enable);
|
||||
ev.Check(enable && check);
|
||||
} else if (ev.GetId() == ID_EDIT_DUPLICATE) {
|
||||
ev.Enable(!control.selected_parts.empty());
|
||||
} else if (ev.GetId() == ID_EDIT_GROUP) {
|
||||
ev.Enable(control.selected_parts.size() >= 2);
|
||||
} else if (ev.GetId() == ID_EDIT_UNGROUP) {
|
||||
// is a group selected
|
||||
FOR_EACH(p, control.selected_parts) {
|
||||
if (p->isSymbolGroup()) {
|
||||
ev.Enable(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ev.Enable(false);
|
||||
} else {
|
||||
ev.Enable(false); // we don't know about this item
|
||||
}
|
||||
@@ -165,10 +169,15 @@ void SymbolSelectEditor::onCommand(int id) {
|
||||
control.Refresh(false);
|
||||
} else if (id == ID_EDIT_DUPLICATE && !isEditing()) {
|
||||
// duplicate selection, not when dragging
|
||||
DuplicateSymbolPartsAction* action = new DuplicateSymbolPartsAction(
|
||||
*getSymbol(), control.selected_parts
|
||||
);
|
||||
getSymbol()->actions.add(action);
|
||||
getSymbol()->actions.add(new DuplicateSymbolPartsAction(*getSymbol(), control.selected_parts));
|
||||
control.Refresh(false);
|
||||
} else if (id == ID_EDIT_GROUP && !isEditing()) {
|
||||
// group selection, not when dragging
|
||||
getSymbol()->actions.add(new GroupSymbolPartsAction(*getSymbol(), control.selected_parts));
|
||||
control.Refresh(false);
|
||||
} else if (id == ID_EDIT_UNGROUP && !isEditing()) {
|
||||
// ungroup selection, not when dragging
|
||||
getSymbol()->actions.add(new UngroupSymbolPartsAction(*getSymbol(), control.selected_parts));
|
||||
control.Refresh(false);
|
||||
}
|
||||
}
|
||||
@@ -383,6 +392,7 @@ void SymbolSelectEditor::onChar(wxKeyEvent& ev) {
|
||||
if (ev.GetKeyCode() == WXK_DELETE) {
|
||||
// delete selected parts
|
||||
getSymbol()->actions.add(new RemoveSymbolPartsAction(*getSymbol(), control.selected_parts));
|
||||
if (control.selected_parts.find(highlightPart) != control.selected_parts.end()) highlightPart = SymbolPartP(); // deleted it
|
||||
control.selected_parts.clear();
|
||||
resetActions();
|
||||
control.Refresh(false);
|
||||
@@ -441,15 +451,25 @@ double SymbolSelectEditor::angleTo(const Vector2D& pos) {
|
||||
}
|
||||
|
||||
|
||||
SymbolPartP find_part(const SymbolPartP& part, const Vector2D& pos) {
|
||||
if (SymbolShape* s = part->isSymbolShape()) {
|
||||
if (point_in_shape(pos, *s)) return part;
|
||||
} else if (SymbolSymmetry* s = part->isSymbolSymmetry()) {
|
||||
// TODO
|
||||
} else if (SymbolGroup* g = part->isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
if (find_part(p,pos)) return part;
|
||||
}
|
||||
} else {
|
||||
throw InternalError(_("Invalid symbol part type"));
|
||||
}
|
||||
return SymbolPartP();
|
||||
}
|
||||
|
||||
SymbolPartP SymbolSelectEditor::findPart(const Vector2D& pos) {
|
||||
FOR_EACH(p, getSymbol()->parts) {
|
||||
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"));
|
||||
}
|
||||
SymbolPartP found = find_part(p, pos);
|
||||
if (found) return found;
|
||||
}
|
||||
return SymbolPartP();
|
||||
}
|
||||
@@ -459,10 +479,8 @@ void SymbolSelectEditor::updateBoundingBox() {
|
||||
minV = Vector2D::infinity();
|
||||
maxV = -Vector2D::infinity();
|
||||
FOR_EACH(p, control.selected_parts) {
|
||||
if (SymbolShape* s = p->isSymbolShape()) {
|
||||
minV = piecewise_min(minV, s->min_pos);
|
||||
maxV = piecewise_max(maxV, s->max_pos);
|
||||
}
|
||||
minV = piecewise_min(minV, p->min_pos);
|
||||
maxV = piecewise_max(maxV, p->max_pos);
|
||||
}
|
||||
/* // Find rotation center
|
||||
center = Vector2D(0,0);
|
||||
|
||||
+13
-10
@@ -74,6 +74,9 @@ void SymbolWindow::init(Window* parent, SymbolP symbol) {
|
||||
menuEdit->Append(ID_EDIT_UNDO, _("undo"), _MENU_1_("undo",wxEmptyString), _HELP_("undo"));
|
||||
menuEdit->Append(ID_EDIT_REDO, _("redo"), _MENU_1_("redo",wxEmptyString), _HELP_("redo"));
|
||||
menuEdit->AppendSeparator();
|
||||
menuEdit->Append(ID_EDIT_GROUP, _("group"), _MENU_("group"), _HELP_("group"));
|
||||
menuEdit->Append(ID_EDIT_UNGROUP, _("ungroup"), _MENU_("ungroup"), _HELP_("ungroup"));
|
||||
menuEdit->AppendSeparator();
|
||||
menuEdit->Append(ID_EDIT_DUPLICATE, _("duplicate"), _MENU_("duplicate"), _HELP_("duplicate"));
|
||||
menuBar->Append(menuEdit, _MENU_("edit"));
|
||||
|
||||
@@ -82,6 +85,7 @@ void SymbolWindow::init(Window* parent, SymbolP symbol) {
|
||||
menuTool->Append(ID_MODE_ROTATE, _("mode_rotate"), _MENU_("rotate"), _HELP_("rotate"), wxITEM_CHECK);
|
||||
menuTool->Append(ID_MODE_POINTS, _("mode_curve"), _MENU_("points"), _HELP_("points"), wxITEM_CHECK);
|
||||
menuTool->Append(ID_MODE_SHAPES, _("circle"), _MENU_("basic shapes"), _HELP_("basic shapes"), wxITEM_CHECK);
|
||||
menuTool->Append(ID_MODE_SYMMETRY, _("mode_symmetry"), _MENU_("symmetry"), _HELP_("symmetry"), wxITEM_CHECK);
|
||||
menuTool->Append(ID_MODE_PAINT, _("mode_paint"), _MENU_("paint"), _HELP_("paint"), wxITEM_CHECK);
|
||||
menuBar->Append(menuTool, _MENU_("tool"));
|
||||
|
||||
@@ -118,11 +122,11 @@ void SymbolWindow::init(Window* parent, SymbolP symbol) {
|
||||
|
||||
// Controls
|
||||
control = new SymbolControl (this, ID_CONTROL, symbol);
|
||||
parts = new SymbolPartList(this, ID_PART_LIST, symbol);
|
||||
parts = new SymbolPartList(this, ID_PART_LIST, control->selected_parts, symbol);
|
||||
|
||||
// Lay out
|
||||
wxSizer* es = new wxBoxSizer(wxHORIZONTAL);
|
||||
es->Add(em, 0, wxEXPAND | wxTOP | wxBOTTOM | wxALIGN_CENTER, 1);
|
||||
es->Add(em, 1, wxEXPAND | wxTOP | wxBOTTOM | wxALIGN_CENTER, 1);
|
||||
emp->SetSizer(es);
|
||||
|
||||
wxSizer* s = new wxBoxSizer(wxHORIZONTAL);
|
||||
@@ -246,21 +250,21 @@ void SymbolWindow::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
}
|
||||
|
||||
|
||||
void SymbolWindow::onSelectFromList(wxListEvent& ev) {
|
||||
void SymbolWindow::onSelectFromList(wxCommandEvent& ev) {
|
||||
if (inSelectionEvent) return ;
|
||||
inSelectionEvent = true;
|
||||
parts->getSelectedParts(control->selected_parts);
|
||||
control->onUpdateSelection();
|
||||
inSelectionEvent = false;
|
||||
}
|
||||
void SymbolWindow::onActivateFromList(wxListEvent& ev) {
|
||||
control->activatePart(control->getSymbol()->parts.at(ev.GetIndex()));
|
||||
void SymbolWindow::onActivateFromList(wxCommandEvent& ev) {
|
||||
//% control->activatePart(control->getSymbol()->parts.at(ev.GetIndex()));
|
||||
// TODO
|
||||
}
|
||||
|
||||
void SymbolWindow::onSelectFromControl() {
|
||||
if (inSelectionEvent) return ;
|
||||
inSelectionEvent = true;
|
||||
parts->selectParts(control->selected_parts);
|
||||
parts->Refresh(false);
|
||||
inSelectionEvent = false;
|
||||
}
|
||||
|
||||
@@ -280,7 +284,6 @@ BEGIN_EVENT_TABLE(SymbolWindow, wxFrame)
|
||||
EVT_TOOL_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, SymbolWindow::onExtraTool)
|
||||
EVT_UPDATE_UI (wxID_ANY, SymbolWindow::onUpdateUI)
|
||||
|
||||
EVT_LIST_ITEM_SELECTED (ID_PART_LIST, SymbolWindow::onSelectFromList)
|
||||
EVT_LIST_ITEM_DESELECTED (ID_PART_LIST, SymbolWindow::onSelectFromList)
|
||||
EVT_LIST_ITEM_ACTIVATED (ID_PART_LIST, SymbolWindow::onActivateFromList)
|
||||
EVT_PART_SELECT (ID_PART_LIST, SymbolWindow::onSelectFromList)
|
||||
EVT_PART_ACTIVATE (ID_PART_LIST, SymbolWindow::onActivateFromList)
|
||||
END_EVENT_TABLE ()
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
class SymbolControl;
|
||||
//%%class SymbolPartList;
|
||||
class SymbolPartList;
|
||||
DECLARE_POINTER_TYPE(SymbolValue);
|
||||
DECLARE_POINTER_TYPE(Set);
|
||||
@@ -62,9 +63,9 @@ class SymbolWindow : public Frame {
|
||||
void onUpdateUI(wxUpdateUIEvent&);
|
||||
|
||||
/// Changing selected parts in the list
|
||||
void onSelectFromList(wxListEvent& ev);
|
||||
void onSelectFromList(wxCommandEvent& ev);
|
||||
/// Activating a part: open the point editor
|
||||
void onActivateFromList(wxListEvent& ev);
|
||||
void onActivateFromList(wxCommandEvent& ev);
|
||||
|
||||
bool inSelectionEvent; ///< Prevent recursion in onSelect...
|
||||
|
||||
|
||||
@@ -96,6 +96,10 @@ bool MSE::OnInit() {
|
||||
// Installer; install it
|
||||
Installer::installFrom(argv[1], true);
|
||||
return false;
|
||||
} else if (arg == _("--symbol-editor")) {
|
||||
Window* wnd = new SymbolWindow(nullptr);
|
||||
wnd->Show();
|
||||
return true;
|
||||
} else if (arg == _("--create-installer")) {
|
||||
// create an installer
|
||||
Installer inst;
|
||||
|
||||
@@ -12,6 +12,29 @@
|
||||
#include <util/error.hpp>
|
||||
#include <script/value.hpp> // for some strange reason the profile build needs this :(
|
||||
|
||||
// ----------------------------------------------------------------------------- : Color
|
||||
|
||||
template <> void GetDefaultMember::handle(const AColor& col) {
|
||||
handle((const Color&)col);
|
||||
}
|
||||
template <> void Reader::handle(AColor& col) {
|
||||
UInt r,g,b,a;
|
||||
if (wxSscanf(getValue().c_str(),_("rgb(%u,%u,%u)"),&r,&g,&b)) {
|
||||
col.Set(r,g,b);
|
||||
col.alpha = 255;
|
||||
} else if (wxSscanf(getValue().c_str(),_("rgba(%u,%u,%u,%u)"),&r,&g,&b,&a)) {
|
||||
col.Set(r,g,b);
|
||||
col.alpha = a;
|
||||
}
|
||||
}
|
||||
template <> void Writer::handle(const AColor& col) {
|
||||
if (col.alpha == 255) {
|
||||
handle(String::Format(_("rgb(%u,%u,%u)"), col.Red(), col.Green(), col.Blue()));
|
||||
} else {
|
||||
handle(String::Format(_("rgba(%u,%u,%u,%u)"), col.Red(), col.Green(), col.Blue(), col.alpha));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Symbol filtering
|
||||
|
||||
void filter_symbol(Image& symbol, const SymbolFilter& filter) {
|
||||
|
||||
@@ -20,9 +20,10 @@ class SymbolFilter;
|
||||
/// Color with alpha channel
|
||||
class AColor : public Color {
|
||||
public:
|
||||
int alpha; ///< The alpha value, in the range [0..255]
|
||||
inline AColor(int r, int g, int b, int a = 255) : Color(r,g,b), alpha(a) {}
|
||||
inline AColor(const Color& color, int a = 255) : Color(color), alpha(a) {}
|
||||
Byte alpha; ///< The alpha value, in the range [0..255]
|
||||
inline AColor() : alpha(0) {}
|
||||
inline AColor(Byte r, Byte g, Byte b, Byte a = 255) : Color(r,g,b), alpha(a) {}
|
||||
inline AColor(const Color& color, Byte a = 255) : Color(color), alpha(a) {}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Symbol filtering
|
||||
@@ -69,14 +70,14 @@ intrusive_ptr<SymbolFilter> read_new<SymbolFilter>(Reader& reader);
|
||||
class SolidFillSymbolFilter : public SymbolFilter {
|
||||
public:
|
||||
inline SolidFillSymbolFilter() {}
|
||||
inline SolidFillSymbolFilter(Color fill_color, Color border_color)
|
||||
inline SolidFillSymbolFilter(const AColor& fill_color, const AColor& border_color)
|
||||
: fill_color(fill_color), border_color(border_color)
|
||||
{}
|
||||
virtual AColor color(double x, double y, SymbolSet point) const;
|
||||
virtual String fillType() const;
|
||||
virtual bool operator == (const SymbolFilter& that) const;
|
||||
private:
|
||||
Color fill_color, border_color;
|
||||
AColor fill_color, border_color;
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
|
||||
@@ -72,10 +72,8 @@ void SymbolViewer::draw(DC& dc) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Draw all parts, in reverse order (bottom to top)
|
||||
FOR_EACH_REVERSE(p, symbol->parts) {
|
||||
combineSymbolPart(dc, *p, paintedSomething, buffersFilled, borderDC, interiorDC);
|
||||
}
|
||||
// Draw all parts
|
||||
combineSymbolPart(dc, *symbol, paintedSomething, buffersFilled, borderDC, interiorDC);
|
||||
|
||||
// Output the final parts from the buffer
|
||||
if (buffersFilled) {
|
||||
@@ -118,7 +116,8 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint
|
||||
} else if (const SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
// symmetry, already handled above
|
||||
} else if (const SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH_CONST(p, g->parts) {
|
||||
// Draw all parts, in reverse order (bottom to top)
|
||||
FOR_EACH_CONST_REVERSE(p, g->parts) {
|
||||
combineSymbolPart(dc, *p, paintedSomething, buffersFilled, borderDC, interiorDC);
|
||||
}
|
||||
}
|
||||
@@ -147,6 +146,10 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolShape& shape, HighlightStyl
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.SetPen (wxPen(Color(255,0,0), 2));
|
||||
dc.DrawPolygon((int)points.size(), &points[0]);
|
||||
} else if (style == HIGHLIGHT_BORDER_DOT) {
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.SetPen (wxPen(Color(255,0,0), 1, wxDOT));
|
||||
dc.DrawPolygon((int)points.size(), &points[0]);
|
||||
} else {
|
||||
dc.SetLogicalFunction(wxOR);
|
||||
dc.SetBrush(Color(0,0,64));
|
||||
@@ -164,8 +167,13 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolSymmetry& sym) {
|
||||
// TODO
|
||||
}
|
||||
void SymbolViewer::highlightPart(DC& dc, const SymbolGroup& group, HighlightStyle style) {
|
||||
if (style == HIGHLIGHT_BORDER) {
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.SetPen (wxPen(Color(255,0,0), 2));
|
||||
dc.DrawRectangle(rotation.tr(RealRect(group.min_pos, RealSize(group.max_pos - group.min_pos))));
|
||||
}
|
||||
FOR_EACH_CONST(part, group.parts) {
|
||||
highlightPart(dc, *part, style);
|
||||
highlightPart(dc, *part, (HighlightStyle)(style | HIGHLIGHT_LESS));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ Image render_symbol(const SymbolP& symbol, double border_radius = 0.05, int size
|
||||
|
||||
// ----------------------------------------------------------------------------- : Symbol Viewer
|
||||
|
||||
enum HighlightStyle {
|
||||
HIGHLIGHT_BORDER,
|
||||
HIGHLIGHT_INTERIOR
|
||||
enum HighlightStyle
|
||||
{ HIGHLIGHT_BORDER = 0x01
|
||||
, HIGHLIGHT_INTERIOR = 0x02
|
||||
, HIGHLIGHT_LESS = 0x10
|
||||
, HIGHLIGHT_BORDER_DOT = HIGHLIGHT_BORDER | HIGHLIGHT_LESS
|
||||
};
|
||||
|
||||
/// Class that knows how to draw a symbol
|
||||
|
||||
@@ -87,10 +87,13 @@ tool/mode_select IMAGE "tool/mode_select.png"
|
||||
tool/mode_rotate IMAGE "tool/mode_rotate.png"
|
||||
tool/mode_curve IMAGE "tool/mode_curve.png"
|
||||
tool/mode_paint IMAGE "tool/mode_paint.png"
|
||||
tool/mode_symmetry IMAGE "tool/mode_symmetry.png"
|
||||
tool/apply IMAGE "tool/apply.png"
|
||||
tool/duplicate IMAGE "tool/duplicate.png"
|
||||
tool/grid IMAGE "tool/grid.png"
|
||||
tool/grid_snap IMAGE "tool/grid_snap.png"
|
||||
tool/group IMAGE "tool/group.png"
|
||||
tool/ungroup IMAGE "tool/ungroup.png"
|
||||
|
||||
combine_or IMAGE "../common/combine_or.png"
|
||||
combine_sub IMAGE "../common/combine_sub.png"
|
||||
@@ -102,6 +105,17 @@ 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"
|
||||
symbol_group IMAGE "../common/symbol_group.png"
|
||||
|
||||
icon_combine_merge IMAGE "../common/icon_combine_merge.png"
|
||||
icon_combine_subtract IMAGE "../common/icon_combine_subtract.png"
|
||||
icon_combine_intersection IMAGE "../common/icon_combine_intersection.png"
|
||||
icon_combine_difference IMAGE "../common/icon_combine_difference.png"
|
||||
icon_combine_overlap IMAGE "../common/icon_combine_overlap.png"
|
||||
icon_combine_border IMAGE "../common/icon_combine_border.png"
|
||||
icon_symmetry_rotation IMAGE "../common/icon_symmetry_rotation.png"
|
||||
icon_symmetry_reflection IMAGE "../common/icon_symmetry_reflection.png"
|
||||
icon_symbol_group IMAGE "../common/icon_symbol_group.png"
|
||||
|
||||
handle_rotate IMAGE "../common/handle_rotate.png"
|
||||
handle_shear_x IMAGE "../common/handle_shear_x.png"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 149 B |
Binary file not shown.
|
After Width: | Height: | Size: 146 B |
Binary file not shown.
|
After Width: | Height: | Size: 148 B |
@@ -37,7 +37,14 @@ class RealSize {
|
||||
: width(w), height(h)
|
||||
{}
|
||||
inline RealSize(wxSize s)
|
||||
: width(s.GetWidth()), height(s.GetHeight())
|
||||
: width(s.x), height(s.y)
|
||||
{}
|
||||
inline explicit RealSize(const Vector2D& v)
|
||||
: width(v.x), height(v.y)
|
||||
{}
|
||||
/// size of an image
|
||||
inline explicit RealSize(const wxImage& img)
|
||||
: width(img.GetWidth()), height(img.GetHeight())
|
||||
{}
|
||||
|
||||
/// Negation of a size, negates both components
|
||||
|
||||
+28
-16
@@ -74,15 +74,6 @@ class Vector2D {
|
||||
x /= r; y /= r;
|
||||
}
|
||||
|
||||
/// Inner product with another vector
|
||||
inline double dot(Vector2D p2) const {
|
||||
return (x * p2.x) + (y * p2.y);
|
||||
}
|
||||
/// Outer product with another vector
|
||||
inline double cross(Vector2D p2) const {
|
||||
return (x * p2.y) - (y * p2.x);
|
||||
}
|
||||
|
||||
/// Piecewise multiplication
|
||||
inline Vector2D mul(Vector2D p2) {
|
||||
return Vector2D(x * p2.x, y * p2.y);
|
||||
@@ -91,12 +82,7 @@ class Vector2D {
|
||||
inline Vector2D div(Vector2D p2) {
|
||||
return Vector2D(x / p2.x, y / p2.y);
|
||||
}
|
||||
|
||||
/// Apply a 'matrix' to this vector
|
||||
inline Vector2D mul(Vector2D mx, Vector2D my) {
|
||||
return Vector2D(dot(mx), dot(my));
|
||||
}
|
||||
|
||||
|
||||
/// Returns the square of the length of this vector
|
||||
inline double lengthSqr() const {
|
||||
return x*x + y*y;
|
||||
@@ -106,7 +92,7 @@ class Vector2D {
|
||||
return sqrt(lengthSqr());
|
||||
}
|
||||
/// Returns a normalized version of this vector
|
||||
/// i.e. length() == 1
|
||||
/** i.e. length() == 1 */
|
||||
inline Vector2D normalized() const {
|
||||
return *this / length();
|
||||
}
|
||||
@@ -122,6 +108,15 @@ class Vector2D {
|
||||
}
|
||||
};
|
||||
|
||||
/// Inner product of two vectors
|
||||
inline double dot(const Vector2D& a, const Vector2D& b) {
|
||||
return (a.x * b.x) + (a.y * b.y);
|
||||
}
|
||||
/// Length of the outer product of two vectors
|
||||
inline double cross(const Vector2D& a, const Vector2D& b) {
|
||||
return (a.x * b.y) - (a.y * b.x);
|
||||
}
|
||||
|
||||
/// Piecewise minimum
|
||||
inline Vector2D piecewise_min(const Vector2D& a, const Vector2D& b) {
|
||||
return Vector2D(
|
||||
@@ -140,6 +135,23 @@ inline Vector2D piecewise_max(const Vector2D& a, const Vector2D& b) {
|
||||
|
||||
inline Vector2D operator * (double a, const Vector2D& b) { return b * a; }
|
||||
|
||||
// ----------------------------------------------------------------------------- : Matrix2D
|
||||
|
||||
/// A two dimensional transformation matrix, simply two vectors
|
||||
class Matrix2D {
|
||||
public:
|
||||
Vector2D mx, my;
|
||||
|
||||
inline Matrix2D() {}
|
||||
inline Matrix2D(const Vector2D& mx, const Vector2D& my) : mx(mx), my(my) {}
|
||||
inline Matrix2D(double a, double b, double c, double d) : mx(a,b), my(c,d) {}
|
||||
};
|
||||
|
||||
/// vector-matrix product
|
||||
inline Vector2D operator * (const Vector2D& a, const Matrix2D& m) {
|
||||
return Vector2D(dot(a,m.mx), dot(a,m.my));
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
#endif
|
||||
|
||||
@@ -76,6 +76,7 @@ enum MenuID {
|
||||
, ID_MODE_ROTATE
|
||||
, ID_MODE_POINTS
|
||||
, ID_MODE_SHAPES
|
||||
, ID_MODE_SYMMETRY
|
||||
, ID_MODE_PAINT
|
||||
, ID_MODE_MAX
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user