Files
MagicSetEditor2/src/gui/symbol/point_editor.cpp
T
twanvl a2e709d86b It is 2007 by now
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@275 0fc631ac-6414-0410-93d0-97cfa31319b6
2007-04-21 17:06:29 +00:00

544 lines
18 KiB
C++

//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2007 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <gui/symbol/point_editor.hpp>
#include <gui/symbol/window.hpp>
#include <gui/util.hpp>
#include <gfx/bezier.hpp>
#include <data/action/symbol_part.hpp>
#include <data/settings.hpp>
#include <util/window_id.hpp>
#include <util/error.hpp>
DECLARE_TYPEOF_COLLECTION(ControlPointP);
// ----------------------------------------------------------------------------- : SymbolPointEditor
SymbolPointEditor::SymbolPointEditor(SymbolControl* control, const SymbolPartP& part)
: SymbolEditorBase(control)
, part(part)
, selection(SELECTED_NONE)
, hovering(SELECTED_NONE)
// Load gui stock
, pointSelect(load_resource_cursor(_("point")))
, pointAdd (load_resource_cursor(_("point_add")))
, pointCurve (load_resource_cursor(_("curve")))
, pointMove (load_resource_cursor(_("point_move")))
{
resetActions();
// // fix pen joins
// penHandleHover.join = wxJOIN_MITER;
// penMainHover.join = wxJOIN_MITER;
}
// ----------------------------------------------------------------------------- : Drawing
void SymbolPointEditor::draw(DC& dc) {
// highlight the part
control.highlightPart(dc, *part, HIGHLIGHT_BORDER);
// handles etc.
if (hovering == SELECTED_LINE) {
drawHoveredLine(dc);
}
drawHandles(dc);
if (hovering == SELECTED_NEW_POINT) {
drawNewPoint(dc);
}
}
void SymbolPointEditor::drawHoveredLine(DC& dc) {
BezierCurve c(*hover_line_1, *hover_line_2);
wxPoint prevPoint = control.rotation.tr(hover_line_1->pos);
for(int i = 1 ; i <= 100 ; ++i) {
// Draw 100 segments of the curve
double t = double(i)/100.0f;
wxPoint curPoint = control.rotation.tr(c.pointAt(t));
double selectPercent = 1.0 - 1.2 * sqrt(fabs(hover_line_t-t)); // amount to color
if (selectPercent > 0) {
// gradient color
Color color(
col(300 - int(300 * selectPercent)),
col(300 * int(selectPercent)),
col(0)
);
dc.SetPen(wxPen(color, 3));
dc.DrawLine(prevPoint, curPoint);
}
prevPoint = curPoint;
}
}
void SymbolPointEditor::drawHandles(DC& dc) {
dc.SetPen(Color(0,0,128));
dc.SetBrush(Color(128,128,255));
for (int i = 0 ; (size_t)i < part->points.size() ; ++i) {
// determine which handles to draw
bool selected = pointSelected(*part->getPoint(i));
bool selBefore = selected || pointSelected(*part->getPoint(i-1));
bool selAfter = selected || pointSelected(*part->getPoint(i+1));
// and draw them
drawControlPoint(dc, *part->getPoint(i), selBefore, selAfter);
}
}
void SymbolPointEditor::drawNewPoint(DC& dc) {
dc.SetPen(*wxGREEN_PEN);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
wxPoint p = control.rotation.tr(new_point);
drawHandleBox(dc, p.x, p.y, true);
}
void SymbolPointEditor::drawControlPoint(DC& dc, const ControlPoint& p, bool drawHandleBefore, bool drawHandleAfter) {
// Position
wxPoint p0 = control.rotation.tr(p.pos);
// Sub handles
if (drawHandleBefore || drawHandleAfter) {
dc.SetBrush(*wxTRANSPARENT_BRUSH);
// Before handle
if (drawHandleBefore && p.segment_before == SEGMENT_CURVE) {
wxPoint p1 = control.rotation.tr(p.pos + p.delta_before);
dc.SetPen(handlePen(PEN_LINE, p.lock));
dc.DrawLine(p0.x, p0.y, p1.x, p1.y);
dc.SetPen(handlePen(handleHovered(p, HANDLE_BEFORE) ? PEN_HOVER : PEN_NORMAL, p.lock));
drawHandleCircle(dc, p1.x, p1.y);
}
// After handle
if (drawHandleAfter && p.segment_after == SEGMENT_CURVE) {
wxPoint p1 = control.rotation.tr(p.pos + p.delta_after);
dc.SetPen(handlePen(PEN_LINE, p.lock));
dc.DrawLine(p0.x, p0.y, p1.x, p1.y);
dc.SetPen(handlePen(handleHovered(p, HANDLE_AFTER) ? PEN_HOVER : PEN_NORMAL, p.lock));
drawHandleCircle(dc, p1.x, p1.y);
}
}
// Main handle
// last, so it draws over lines to handles
bool selected = pointSelected(p);
wxPen hp(*wxBLACK, pointHovered(p) ? 2 : 1);
hp.SetJoin(wxJOIN_MITER);
dc.SetPen(hp);
dc.SetBrush(selected ? *wxGREEN_BRUSH : *wxTRANSPARENT_BRUSH);
drawHandleBox(dc, p0.x, p0.y, selected);
}
void SymbolPointEditor::drawHandleBox(DC& dc, UInt px, UInt py, bool active) {
dc.DrawRectangle(px - 3, py - 3, 7, 7);
if (!active) {
dc.SetPen(*wxWHITE_PEN);
dc.DrawRectangle(px - 2, py - 2, 5, 5);
}
}
void SymbolPointEditor::drawHandleCircle(DC& dc, UInt px, UInt py) {
dc.DrawCircle(px, py, 4);
}
wxPen SymbolPointEditor::handlePen(WhichPen p, LockMode lock) {
Color col;
if (lock == LOCK_FREE) col = Color(100, 100, 255);
if (lock == LOCK_DIR) col = Color(153, 0, 204);
if (lock == LOCK_SIZE) col = Color(204, 50, 50);
switch(p) {
case PEN_NORMAL: return wxPen(col);
case PEN_HOVER: return wxPen(col, 2);
case PEN_LINE: return wxPen(col, 1, wxDOT);
default: throw InternalError(_("SymbolPointEditor::handlePen"));
}
}
// ----------------------------------------------------------------------------- : UI
void SymbolPointEditor::initUI(wxToolBar* tb, wxMenuBar* mb) {
// Initialize toolbar
tb->AddSeparator();
tb->AddTool(ID_SEGMENT_LINE, _TOOL_("line segment"), load_resource_tool_image(_("line")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("line segment"), _HELP_("line segment"));
tb->AddTool(ID_SEGMENT_CURVE, _TOOL_("curve segment"), load_resource_tool_image(_("curve")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("curve segment"), _HELP_("curve segment"));
tb->AddSeparator();
tb->AddTool(ID_LOCK_FREE, _TOOL_("free point"), load_resource_tool_image(_("lock_free")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("free point"), _HELP_("free point"));
tb->AddTool(ID_LOCK_DIR, _TOOL_("smooth point"), load_resource_tool_image(_("lock_dir")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("smooth point"), _HELP_("smooth point"));
tb->AddTool(ID_LOCK_SIZE, _TOOL_("symmetric point"), load_resource_tool_image(_("lock_size")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("symmetric point"), _HELP_("symmetric point"));
tb->Realize();
// TODO : menu bar
//mb->Insert(2, curveMenu, _("&Curve"))
}
void SymbolPointEditor::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
tb->DeleteTool(ID_SEGMENT_LINE);
tb->DeleteTool(ID_SEGMENT_CURVE);
tb->DeleteTool(ID_LOCK_FREE);
tb->DeleteTool(ID_LOCK_DIR);
tb->DeleteTool(ID_LOCK_SIZE);
// HACK: hardcoded size of rest of toolbar
tb->DeleteToolByPos(7); // delete separator
tb->DeleteToolByPos(7); // delete separator
// TODO : menu bar
//mb->Remove(2)
}
void SymbolPointEditor::onUpdateUI(wxUpdateUIEvent& ev) {
// enable
bool enabled = false, checked = false;
switch (ev.GetId()) {
case ID_SEGMENT_LINE: case ID_SEGMENT_CURVE:
enabled = selection == SELECTED_LINE;
break;
case ID_LOCK_FREE: case ID_LOCK_DIR: case ID_LOCK_SIZE:
enabled = selection == SELECTED_POINTS &&
selected_points.size() == 1 &&
(*selected_points.begin())->segment_before == SEGMENT_CURVE &&
(*selected_points.begin())->segment_after == SEGMENT_CURVE;
break;
default:
ev.Enable(false); // we don't know this item
return;
}
// check
if (enabled) {
switch (ev.GetId()) {
case ID_SEGMENT_LINE: case ID_SEGMENT_CURVE:
checked = selected_line1->segment_after == ev.GetId() - ID_SEGMENT;
break;
case ID_LOCK_FREE: case ID_LOCK_DIR: case ID_LOCK_SIZE:
checked = (*selected_points.begin())->lock == ev.GetId() - ID_LOCK;
break;
}
}
ev.Enable(enabled);
ev.Check(checked);
}
void SymbolPointEditor::onCommand(int id) {
switch (id) {
case ID_SEGMENT_LINE: case ID_SEGMENT_CURVE:
onChangeSegment( static_cast<SegmentMode>(id - ID_SEGMENT) );
break;
case ID_LOCK_FREE: case ID_LOCK_DIR: case ID_LOCK_SIZE:
onChangeLock( static_cast<LockMode>(id - ID_LOCK) );
break;
}
}
int SymbolPointEditor::modeToolId() { return ID_MODE_POINTS; }
// ----------------------------------------------------------------------------- : Mouse events
void SymbolPointEditor::onLeftDown(const Vector2D& pos, wxMouseEvent& ev) {
SelectedHandle handle = findHandle(pos);
if (handle.handle) {
selectHandle(handle, ev);
} else if (hovering == SELECTED_LINE) {
selectLine(ev);
} else if (hovering == SELECTED_NEW_POINT) {
selectLine(ev);
} else {
selectNothing();
}
// update window
control.Refresh(false);
}
void SymbolPointEditor::onLeftUp(const Vector2D& pos, wxMouseEvent& ev) {
// Left up => finalize all actions, new events start new actions
resetActions();
}
void SymbolPointEditor::onLeftDClick(const Vector2D& pos, wxMouseEvent& ev) {
findHoveredItem(pos, false);
if (hovering == SELECTED_NEW_POINT) {
// Add point
ControlPointAddAction* act = new ControlPointAddAction(part, hover_line_1_idx, hover_line_t);
getSymbol()->actions.add(act);
// select the new point
selectPoint(act->getNewPoint(), false);
selection = SELECTED_POINTS;
} else if (hovering == SELECTED_HANDLE && hover_handle.handle == HANDLE_MAIN) { //%%%%%%% ||/&&
// Delete point
selected_points.clear();
selectPoint(hover_handle.point, false);
getSymbol()->actions.add(controlPointRemoveAction(part, selected_points));
selected_points.clear();
selection = SELECTED_NONE;
}
// refresh
findHoveredItem(pos, false);
control.Refresh(false);
}
void SymbolPointEditor::onMouseMove(const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) {
// Moving the mouse without dragging => select a point/handle
findHoveredItem(to, ev.AltDown());
control.Refresh(false);
}
template <typename Event> int snap(Event& ev) {
return settings.symbol_grid_snap != ev.ShiftDown() ? settings.symbol_grid_size : 0; // shift toggles snap
}
void SymbolPointEditor::onMouseDrag(const Vector2D& from, const Vector2D& to, wxMouseEvent& ev) {
Vector2D delta = to - from;
if (selection == SELECTED_LINE && ev.AltDown()) {
// Drag the curve
if (controlPointMoveAction) controlPointMoveAction = 0;
if (!curveDragAction) {
curveDragAction = new CurveDragAction(selected_line1, selected_line2);
getSymbol()->actions.add(curveDragAction);
}
curveDragAction->move(delta, selected_line_t);
control.Refresh(false);
} else if (selection == SELECTED_POINTS || selection == SELECTED_LINE) {
// Move all selected points
if (curveDragAction) curveDragAction = 0;
if (!controlPointMoveAction) {
// create action we can add this movement to
controlPointMoveAction = new ControlPointMoveAction(selected_points);
getSymbol()->actions.add(controlPointMoveAction);
}
controlPointMoveAction->constrain = ev.ControlDown(); // ctrl constrains
controlPointMoveAction->snap = snap(ev);
controlPointMoveAction->move(delta);
new_point += delta;
control.Refresh(false);
} else if (selection == SELECTED_HANDLE) {
// Move the selected handle
if (!handleMoveAction) {
handleMoveAction = new HandleMoveAction(selected_handle);
getSymbol()->actions.add(handleMoveAction);
}
handleMoveAction->constrain = ev.ControlDown(); // ctrl constrains
handleMoveAction->snap = snap(ev);
handleMoveAction->move(delta);
control.Refresh(false);
}
}
// ----------------------------------------------------------------------------- : Other events
void SymbolPointEditor::onKeyChange(wxKeyEvent& ev) {
if (ev.GetKeyCode() == WXK_ALT && (hovering == SELECTED_LINE || hovering == SELECTED_NEW_POINT)) {
if (ev.AltDown()) {
hovering = SELECTED_LINE;
control.SetCursor(pointCurve);
SetStatusText(_HELP_("drag to move curve"));
} else {
hovering = SELECTED_NEW_POINT;
control.SetCursor(pointAdd);
SetStatusText(_HELP_("drag to move line"));
}
control.Refresh(false);
} else if (ev.GetKeyCode() == WXK_CONTROL || ev.GetKeyCode() == WXK_SHIFT) {
// constrain/snap changed
if (controlPointMoveAction) {
controlPointMoveAction->constrain = ev.ControlDown();
controlPointMoveAction->snap = snap(ev);
controlPointMoveAction->move(Vector2D()); //refresh action
control.Refresh(false);
} else if (handleMoveAction) {
handleMoveAction->constrain = ev.ControlDown();
handleMoveAction->snap = snap(ev);
handleMoveAction->move(Vector2D()); //refresh action
control.Refresh(false);
}
}
}
void SymbolPointEditor::onChar(wxKeyEvent& ev) {
if (ev.GetKeyCode() == WXK_DELETE) {
deleteSelection();
} else {
ev.Skip();
}
}
bool SymbolPointEditor::isEditing() {
return handleMoveAction || controlPointMoveAction || curveDragAction;
}
// ----------------------------------------------------------------------------- : Selection
void SymbolPointEditor::selectNothing() {
selection = SELECTED_NONE;
selected_points.clear();
}
void SymbolPointEditor::selectPoint(const ControlPointP& point, bool toggle) {
set<ControlPointP>::iterator inSet = selected_points.find(point);
if (toggle) {
if (inSet == selected_points.end()) {
selected_points.insert(point);
} else {
selected_points.erase(inSet);
}
} else {
if (inSet == selected_points.end()) {
selected_points.clear();
selected_points.insert(point);
}
}
}
void SymbolPointEditor::selectHandle(const SelectedHandle& h, const wxMouseEvent& keystate) {
if (h.handle == HANDLE_MAIN) {
selection = SELECTED_POINTS;
selectPoint(h.point, keystate.ShiftDown());
} else {
selection = SELECTED_HANDLE;
selected_handle = h;
}
}
void SymbolPointEditor::selectLine(const wxMouseEvent& keystate) {
selection = SELECTED_LINE;
selected_line1 = hover_line_1;
selected_line2 = hover_line_2;
selected_line_t = hover_line_t;
if (!keystate.ShiftDown()) selected_points.clear();
selectPoint(selected_line1, true);
selectPoint(selected_line2, true);
}
bool SymbolPointEditor::pointSelected(const ControlPointP& pnt) {
return selected_points.find(pnt) != selected_points.end();
}
bool SymbolPointEditor::pointSelected(const ControlPoint& pnt) {
FOR_EACH(s, selected_points) {
if (s.get() == &pnt) return true;
}
return false;
}
bool SymbolPointEditor::pointHovered(const ControlPointP& pnt) {
return handleHovered(pnt, HANDLE_MAIN);
}
bool SymbolPointEditor::pointHovered(const ControlPoint& pnt) {
return handleHovered(pnt, HANDLE_MAIN);
}
bool SymbolPointEditor::handleHovered(const ControlPointP& pnt, WhichHandle wh) {
return hovering == SELECTED_HANDLE && hover_handle.point == pnt && hover_handle.handle == wh;
}
bool SymbolPointEditor::handleHovered(const ControlPoint& pnt, WhichHandle wh) {
return hovering == SELECTED_HANDLE && hover_handle.point && hover_handle.point.get() == &pnt && hover_handle.handle == wh;
}
// ----------------------------------------------------------------------------- : Actions
void SymbolPointEditor::resetActions() {
handleMoveAction = nullptr;
controlPointMoveAction = nullptr;
curveDragAction = nullptr;
}
void SymbolPointEditor::deleteSelection() {
if (!selected_points.empty()) {
getSymbol()->actions.add(controlPointRemoveAction(part, selected_points));
selected_points.clear();
resetActions();
control.Refresh(false);
}
}
void SymbolPointEditor::onChangeSegment(SegmentMode mode) {
assert(selected_line1);
assert(selected_line2);
if (selected_line1->segment_after == mode) return;
getSymbol()->actions.add(new SegmentModeAction(selected_line1, selected_line2, mode));
control.Refresh(false);
}
void SymbolPointEditor::onChangeLock(LockMode mode) {
getSymbol()->actions.add(new LockModeAction(*selected_points.begin(), mode));
control.Refresh(false);
}
// ----------------------------------------------------------------------------- : Finding items
void SymbolPointEditor::findHoveredItem(const Vector2D& pos, bool altDown) {
// is there a point currently under the cursor?
hover_handle = findHandle(pos);
// change cursor and statusbar if point is under it
if (hover_handle.handle) {
hovering = SELECTED_HANDLE;
control.SetCursor(pointMove);
SetStatusText(_HELP_("drag to move point"));
} else {
// Not on a point or handle, maybe the cursor is on a curve
if (checkPosOnCurve(pos)) {
if (altDown) {
hovering = SELECTED_LINE;
control.SetCursor(pointCurve);
SetStatusText(_HELP_("drag to move curve"));
} else {
hovering = SELECTED_NEW_POINT;
control.SetCursor(pointAdd);
SetStatusText(_HELP_("drag to move line"));
}
} else {
hovering = SELECTED_NONE;
control.SetCursor(*wxSTANDARD_CURSOR);
SetStatusText(_(""));
}
}
}
bool SymbolPointEditor::checkPosOnCurve(const Vector2D& pos) {
double range = control.rotation.trInvS(3); // less then 3 pixels away is still a hit
size_t size = part->points.size();
for(int i = 0 ; (size_t)i < size ; ++i) {
// Curve between these lines
hover_line_1 = part->getPoint(i);
hover_line_2 = part->getPoint(i + 1);
if (pos_on_segment(pos, range, *hover_line_1, *hover_line_2, new_point, hover_line_t)) {
// mouse is on this line
hover_line_1_idx = i;
return true;
}
}
return false;
}
SelectedHandle SymbolPointEditor::findHandle(const Vector2D& pos) {
double range = control.rotation.trInvS(3); // less then 3 pixels away is still a hit
// Is there a main handle there?
FOR_EACH(p, part->points) {
if (inRange(p->pos, pos, range)) {
// point is at pos
return SelectedHandle(p, HANDLE_MAIN);
}
}
// Is there a sub handle there?
// only check visible handles
for (int i = 0 ; (size_t)i < part->points.size() ; ++i) {
ControlPointP p = part->getPoint(i);
bool sel = pointSelected(p);
bool before = sel || pointSelected(part->getPoint(i-1)); // are the handles visible?
bool after = sel || pointSelected(part->getPoint(i+1));
if (before && p->segment_before == SEGMENT_CURVE) {
if (inRange(p->pos + p->delta_before, pos, range)) {
return SelectedHandle(p, HANDLE_BEFORE);
}
}
if (after && p->segment_after == SEGMENT_CURVE) {
if (inRange(p->pos + p->delta_after, pos, range)) {
return SelectedHandle(p, HANDLE_AFTER);
}
}
}
// Nothing found
return HANDLE_NONE;
}
bool SymbolPointEditor::inRange(const Vector2D& a, const Vector2D& b, double range) {
return fabs(a.x - b.x) <= range &&
fabs(a.y - b.y) <= range;
}