From 7a28a7a0ebd5a9720d60989266723a078771c02f Mon Sep 17 00:00:00 2001 From: dvlierop2 Date: Sun, 15 Mar 2009 21:08:02 +0000 Subject: [PATCH] Implement constrained snapping when dragging the position and size handles of a rectangle in the node tool, with the ctrl-key being pressed. --- src/context-fns.cpp | 6 +-- src/draw-context.cpp | 2 +- src/knot-holder-entity.cpp | 4 +- src/knot-holder-entity.h | 2 +- src/nodepath.cpp | 4 +- src/object-edit.cpp | 95 +++++++++++++++++++++++++------------- src/object-snapper.cpp | 5 +- src/snap.cpp | 12 +++-- src/snap.h | 2 + src/snapper.h | 6 +++ 10 files changed, 88 insertions(+), 50 deletions(-) diff --git a/src/context-fns.cpp b/src/context-fns.cpp index 50942b80a..8e4b6384c 100644 --- a/src/context-fns.cpp +++ b/src/context-fns.cpp @@ -132,11 +132,11 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item /* Try to snap p[0] (the opposite corner) along the constraint vector */ s[0] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[0]), Inkscape::SNAPSOURCE_HANDLE, - Inkscape::Snapper::ConstraintLine(p[0] - p[1])); + Inkscape::Snapper::ConstraintLine(p[0] - p[1]), false); /* Try to snap p[1] (the dragged corner) along the constraint vector */ s[1] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]), Inkscape::SNAPSOURCE_HANDLE, - Inkscape::Snapper::ConstraintLine(p[1] - p[0])); + Inkscape::Snapper::ConstraintLine(p[1] - p[0]), false); /* Choose the best snap and update points accordingly */ if (s[0].getSnapDistance() < s[1].getSnapDistance()) { @@ -157,7 +157,7 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item /* Our origin is the opposite corner. Snap the drag point along the constraint vector */ p[0] = center; snappoint = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]), Inkscape::SNAPSOURCE_HANDLE, - Inkscape::Snapper::ConstraintLine(p[1] - p[0])); + Inkscape::Snapper::ConstraintLine(p[1] - p[0]), false); if (snappoint.getSnapped()) { p[1] = snappoint.getPoint(); } diff --git a/src/draw-context.cpp b/src/draw-context.cpp index 3891ce331..955aeeae2 100644 --- a/src/draw-context.cpp +++ b/src/draw-context.cpp @@ -512,7 +512,7 @@ void spdc_endpoint_snap_rotation(SPEventContext const *const ec, Geom::Point &p, SnapManager &m = SP_EVENT_CONTEXT_DESKTOP(ec)->namedview->snap_manager; m.setup(SP_EVENT_CONTEXT_DESKTOP(ec)); Geom::Point pt2g = to_2geom(p); - m.constrainedSnapReturnByRef( Inkscape::SnapPreferences::SNAPPOINT_NODE, pt2g, Inkscape::SNAPSOURCE_HANDLE, Inkscape::Snapper::ConstraintLine(best)); + m.constrainedSnapReturnByRef( Inkscape::SnapPreferences::SNAPPOINT_NODE, pt2g, Inkscape::SNAPSOURCE_HANDLE, Inkscape::Snapper::ConstraintLine(best), false); p = from_2geom(pt2g); } } diff --git a/src/knot-holder-entity.cpp b/src/knot-holder-entity.cpp index 4225dd9e3..6e4ecae83 100644 --- a/src/knot-holder-entity.cpp +++ b/src/knot-holder-entity.cpp @@ -98,14 +98,14 @@ KnotHolderEntity::snap_knot_position(Geom::Point const &p) } Geom::Point -KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::ConstraintLine const &constraint) +KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::ConstraintLine const &constraint, bool const snap_projection) { Geom::Matrix const i2d (sp_item_i2d_affine(item)); Geom::Point s = p * i2d; Inkscape::Snapper::ConstraintLine transformed_constraint = Inkscape::Snapper::ConstraintLine(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d); SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, true, item); - m.constrainedSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, s, Inkscape::SNAPSOURCE_HANDLE, transformed_constraint); + m.constrainedSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, s, Inkscape::SNAPSOURCE_HANDLE, transformed_constraint, snap_projection); return s * i2d.inverse(); } diff --git a/src/knot-holder-entity.h b/src/knot-holder-entity.h index c8fd29ddf..7414f3d45 100644 --- a/src/knot-holder-entity.h +++ b/src/knot-holder-entity.h @@ -58,7 +58,7 @@ public: //private: Geom::Point snap_knot_position(Geom::Point const &p); - Geom::Point snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::ConstraintLine const &constraint); + Geom::Point snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::ConstraintLine const &constraint, bool const snap_projection); SPKnot *knot; SPItem *item; diff --git a/src/nodepath.cpp b/src/nodepath.cpp index 51d4b9bef..1899f7978 100644 --- a/src/nodepath.cpp +++ b/src/nodepath.cpp @@ -1394,7 +1394,7 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, if (constrained) { Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint; dedicated_constraint.setPoint(n->pos); - s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type, dedicated_constraint); + s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type, dedicated_constraint, false); } else { s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type); } @@ -3962,7 +3962,7 @@ static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, g p = n->pos + (scal / linelen) * ndelta; } if ((state & GDK_SHIFT_MASK) == 0) { - s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type, Inkscape::Snapper::ConstraintLine(p, ndelta)); + s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type, Inkscape::Snapper::ConstraintLine(p, ndelta), false); } } else { if ((state & GDK_SHIFT_MASK) == 0) { diff --git a/src/object-edit.cpp b/src/object-edit.cpp index 0719a59d3..e88550a0a 100644 --- a/src/object-edit.cpp +++ b/src/object-edit.cpp @@ -141,7 +141,7 @@ RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*orig //In general we cannot just snap this radius to an arbitrary point, as we have only a single //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap //the radius then we should have a constrained snap. snap_knot_position() is unconstrained - Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0))); + Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)), false); if (state & GDK_CONTROL_MASK) { gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0; @@ -191,7 +191,7 @@ RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*orig //In general we cannot just snap this radius to an arbitrary point, as we have only a single //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap //the radius then we should have a constrained snap. snap_knot_position() is unconstrained - Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1))); + Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)), false); if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry, // resulting in a perfect circle (and not an ellipse) @@ -259,7 +259,8 @@ void RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state) { SPRect *rect = SP_RECT(item); - Geom::Point const s = snap_knot_position(p); + + Geom::Point s = p; if (state & GDK_CONTROL_MASK) { // original width/height when drag started @@ -267,41 +268,56 @@ RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &or gdouble const h_orig = (origin[Geom::Y] - rect->y.computed); //original ratio - gdouble const ratio = (w_orig / h_orig); + gdouble ratio = (w_orig / h_orig); // mouse displacement since drag started - gdouble const minx = s[Geom::X] - origin[Geom::X]; - gdouble const miny = s[Geom::Y] - origin[Geom::Y]; + gdouble minx = p[Geom::X] - origin[Geom::X]; + gdouble miny = p[Geom::Y] - origin[Geom::Y]; - if (fabs(minx) > fabs(miny)) { + Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed); - // snap to horizontal or diagonal - rect->width.computed = MAX(w_orig + minx, 0); + if (fabs(minx) > fabs(miny)) { + // snap to horizontal or diagonal if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) { // closer to the diagonal and in same-sign quarters, change both using ratio - rect->height.computed = MAX(h_orig + minx / ratio, 0); + s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)), true); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->height.computed = MAX(h_orig + minx / ratio, 0); } else { // closer to the horizontal, change only width, height is h_orig - rect->height.computed = MAX(h_orig, 0); + s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0)), true); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->height.computed = MAX(h_orig, 0); } + rect->width.computed = MAX(w_orig + minx, 0); } else { // snap to vertical or diagonal - rect->height.computed = MAX(h_orig + miny, 0); if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) { // closer to the diagonal and in same-sign quarters, change both using ratio - rect->width.computed = MAX(w_orig + miny * ratio, 0); + s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)), true); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->width.computed = MAX(w_orig + miny * ratio, 0); } else { // closer to the vertical, change only height, width is w_orig - rect->width.computed = MAX(w_orig, 0); + s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1)), true); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->width.computed = MAX(w_orig, 0); } + rect->height.computed = MAX(h_orig + miny, 0); + } rect->width._set = rect->height._set = true; } else { // move freely - rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0); + s = snap_knot_position(p); + rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0); rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0); rect->width._set = rect->height._set = true; } @@ -339,53 +355,66 @@ RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin gdouble w_orig = opposite_x - origin[Geom::X]; gdouble h_orig = opposite_y - origin[Geom::Y]; - Geom::Point const s = snap_knot_position(p); + Geom::Point s = p; + Geom::Point p_handle(rect->x.computed, rect->y.computed); // mouse displacement since drag started - gdouble minx = s[Geom::X] - origin[Geom::X]; - gdouble miny = s[Geom::Y] - origin[Geom::Y]; + gdouble minx = p[Geom::X] - origin[Geom::X]; + gdouble miny = p[Geom::Y] - origin[Geom::Y]; if (state & GDK_CONTROL_MASK) { //original ratio gdouble ratio = (w_orig / h_orig); if (fabs(minx) > fabs(miny)) { - - // snap to horizontal or diagonal - rect->x.computed = MIN(s[Geom::X], opposite_x); - rect->width.computed = MAX(w_orig - minx, 0); + // snap to horizontal or diagonal if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) { // closer to the diagonal and in same-sign quarters, change both using ratio - rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y); + s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)), true); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y); rect->height.computed = MAX(h_orig - minx / ratio, 0); } else { // closer to the horizontal, change only width, height is h_orig - rect->y.computed = MIN(origin[Geom::Y], opposite_y); + s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0)), true); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->y.computed = MIN(origin[Geom::Y], opposite_y); rect->height.computed = MAX(h_orig, 0); } - + rect->x.computed = MIN(s[Geom::X], opposite_x); + rect->width.computed = MAX(w_orig - minx, 0); } else { - // snap to vertical or diagonal - rect->y.computed = MIN(s[Geom::Y], opposite_y); - rect->height.computed = MAX(h_orig - miny, 0); - if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) { + if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) { // closer to the diagonal and in same-sign quarters, change both using ratio - rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x); + s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)), true); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x); rect->width.computed = MAX(w_orig - miny * ratio, 0); } else { // closer to the vertical, change only height, width is w_orig - rect->x.computed = MIN(origin[Geom::X], opposite_x); + s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1)), true); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->x.computed = MIN(origin[Geom::X], opposite_x); rect->width.computed = MAX(w_orig, 0); } - + rect->y.computed = MIN(s[Geom::Y], opposite_y); + rect->height.computed = MAX(h_orig - miny, 0); } rect->width._set = rect->height._set = rect->x._set = rect->y._set = true; } else { // move freely - rect->x.computed = MIN(s[Geom::X], opposite_x); + s = snap_knot_position(p); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + + rect->x.computed = MIN(s[Geom::X], opposite_x); rect->width.computed = MAX(w_orig - minx, 0); rect->y.computed = MIN(s[Geom::Y], opposite_y); rect->height.computed = MAX(h_orig - miny, 0); diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index b02b9786a..e0506c8e2 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -548,13 +548,10 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, direction_vector = Geom::unit_vector(direction_vector); } - Geom::Point const p1_on_cl = c.hasPoint() ? c.getPoint() : p; - Geom::Point const p2_on_cl = p1_on_cl + direction_vector; - // The intersection point of the constraint line with any path, // must lie within two points on the constraintline: p_min_on_cl and p_max_on_cl // The distance between those points is twice the snapping tolerance - Geom::Point const p_proj_on_cl = Geom::projection(p, Geom::Line(p1_on_cl, p2_on_cl)); + Geom::Point const p_proj_on_cl = c.projection(p); Geom::Point const p_min_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_cl - getSnapperTolerance() * direction_vector); Geom::Point const p_max_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_cl + getSnapperTolerance() * direction_vector); diff --git a/src/snap.cpp b/src/snap.cpp index a22f25ab0..6cd89cb0b 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -264,10 +264,11 @@ void SnapManager::constrainedSnapReturnByRef(Inkscape::SnapPreferences::PointTyp Geom::Point &p, Inkscape::SnapSourceType const source_type, Inkscape::Snapper::ConstraintLine const &constraint, + bool const snap_projection, bool first_point, Geom::OptRect const &bbox_to_snap) const { - Inkscape::SnappedPoint const s = constrainedSnap(point_type, p, source_type, constraint, first_point, bbox_to_snap); + Inkscape::SnappedPoint const s = constrainedSnap(point_type, p, source_type, constraint, snap_projection, first_point, bbox_to_snap); s.getPoint(p); } @@ -288,6 +289,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P Geom::Point const &p, Inkscape::SnapSourceType const &source_type, Inkscape::Snapper::ConstraintLine const &constraint, + bool snap_projection, bool first_point, Geom::OptRect const &bbox_to_snap) const { @@ -310,10 +312,12 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P items_to_ignore = _items_to_ignore; } + Geom::Point pp = constraint.projection(p); + SnappedConstraints sc; SnapperList const snappers = getSnappers(); for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->constrainedSnap(sc, point_type, p, source_type, first_point, bbox_to_snap, constraint, items_to_ignore); + (*i)->constrainedSnap(sc, point_type, pp, source_type, first_point, bbox_to_snap, constraint, items_to_ignore); } if (_item_to_ignore) { @@ -456,7 +460,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( if (transformation_type == SCALE && !uniform) { g_warning("Non-uniform constrained scaling is not supported!"); } - snapped_point = constrainedSnap(type, (*j).first, static_cast((*j).second), dedicated_constraint, i == points.begin(), bbox); + snapped_point = constrainedSnap(type, (*j).first, static_cast((*j).second), dedicated_constraint, false, i == points.begin(), bbox); } else { bool const c1 = fabs(b[Geom::X]) < 1e-6; bool const c2 = fabs(b[Geom::Y]) < 1e-6; @@ -465,7 +469,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( // move in that specific direction; therefore it should only snap in that direction, otherwise // we will get snapped points with an invalid transformation dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, component_vectors[c1]); - snapped_point = constrainedSnap(type, (*j).first, static_cast((*j).second), dedicated_constraint, i == points.begin(), bbox); + snapped_point = constrainedSnap(type, (*j).first, static_cast((*j).second), dedicated_constraint, false, i == points.begin(), bbox); } else { snapped_point = freeSnap(type, (*j).first, static_cast((*j).second), i == points.begin(), bbox); } diff --git a/src/snap.h b/src/snap.h index ac314c66d..5290ef081 100644 --- a/src/snap.h +++ b/src/snap.h @@ -86,6 +86,7 @@ public: Geom::Point &p, Inkscape::SnapSourceType const source_type, Inkscape::Snapper::ConstraintLine const &constraint, + bool snap_projection, //try snapping the projection of p onto the constraint line, not p itself bool first_point = true, Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; @@ -93,6 +94,7 @@ public: Geom::Point const &p, Inkscape::SnapSourceType const &source_type, Inkscape::Snapper::ConstraintLine const &constraint, + bool const snap_projection, bool first_point = true, Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; diff --git a/src/snapper.h b/src/snapper.h index 0110a9154..cbd52f052 100644 --- a/src/snapper.h +++ b/src/snapper.h @@ -86,6 +86,12 @@ public: _has_point = true; } + Geom::Point projection(Geom::Point const &p) const { // returns the projection of p on this constraintline + Geom::Point const p1_on_cl = _has_point ? _point : p; + Geom::Point const p2_on_cl = p1_on_cl + _direction; + return Geom::projection(p, Geom::Line(p1_on_cl, p2_on_cl)); + } + private: bool _has_point; -- 2.30.2