From 55dd1351535fdbc7d4087cef62b2c3f59de8726e Mon Sep 17 00:00:00 2001 From: dvlierop2 Date: Sun, 23 Nov 2008 13:55:26 +0000 Subject: [PATCH] 1) Improve the way the distance to the pointer is taken into account when finding the best snap 2) Use this distance also when snapping nodes in the path editor 3) Add a slider in the preferences dialog to control the weighing of this distance --- src/context-fns.cpp | 4 +- src/nodepath.cpp | 29 +++++++---- src/nodepath.h | 4 ++ src/object-snapper.cpp | 4 +- src/preferences-skeleton.h | 1 + src/seltrans.cpp | 3 +- src/snap.cpp | 35 +++++++------ src/snapped-curve.cpp | 20 ++++---- src/snapped-line.cpp | 44 ++++++++--------- src/snapped-point.cpp | 68 +++++++++++++++++++------- src/snapped-point.h | 10 ++-- src/snapper.cpp | 4 +- src/ui/dialog/inkscape-preferences.cpp | 7 ++- src/ui/dialog/inkscape-preferences.h | 2 +- 14 files changed, 143 insertions(+), 92 deletions(-) diff --git a/src/context-fns.cpp b/src/context-fns.cpp index c80def787..c394e38ca 100644 --- a/src/context-fns.cpp +++ b/src/context-fns.cpp @@ -140,7 +140,7 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item Inkscape::Snapper::ConstraintLine(p[1] - p[0])); /* Choose the best snap and update points accordingly */ - if (s[0].getDistance() < s[1].getDistance()) { + if (s[0].getSnapDistance() < s[1].getSnapDistance()) { if (s[0].getSnapped()) { p[0] = s[0].getPoint(); p[1] = 2 * center - s[0].getPoint(); @@ -178,7 +178,7 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item s[0] = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[0])); s[1] = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1])); - if (s[0].getDistance() < s[1].getDistance()) { + if (s[0].getSnapDistance() < s[1].getSnapDistance()) { if (s[0].getSnapped()) { p[0] = s[0].getPoint(); p[1] = 2 * center - s[0].getPoint(); diff --git a/src/nodepath.cpp b/src/nodepath.cpp index 34c850d08..234aba4cc 100644 --- a/src/nodepath.cpp +++ b/src/nodepath.cpp @@ -314,6 +314,8 @@ Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, } else { np->item = SP_ITEM(object); } + + np->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE); // we need to update item's transform from the repr here, // because they may be out of sync when we respond @@ -1335,10 +1337,9 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, bool const snap, bool constrained = false, Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point()) { - Geom::Coord best = NR_HUGE; Geom::Point delta(dx, dy); Geom::Point best_pt = delta; - Inkscape::SnappedPoint best_abs; + Inkscape::SnappedPoint best; if (snap) { /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and @@ -1363,6 +1364,7 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; m.setup(nodepath->desktop, false, SP_PATH(n->subpath->nodepath->item), &unselected_nodes); Inkscape::SnappedPoint s; + if (constrained) { Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint; dedicated_constraint.setPoint(n->pos); @@ -1370,15 +1372,18 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, } else { s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta)); } - if (s.getSnapped() && (s.getDistance() < best)) { - best = s.getDistance(); - best_abs = s; - best_pt = from_2geom(s.getPoint()) - n->pos; + + if (s.getSnapped()) { + s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin)); + if (!s.isOtherOneBetter(best, true)) { + best = s; + best_pt = from_2geom(s.getPoint()) - n->pos; + } } } - if (best_abs.getSnapped()) { - nodepath->desktop->snapindicator->set_new_snappoint(best_abs); + if (best.getSnapped()) { + nodepath->desktop->snapindicator->set_new_snappoint(best); } else { nodepath->desktop->snapindicator->remove_snappoint(); } @@ -3546,7 +3551,7 @@ static void node_clicked(SPKnot */*knot*/, guint state, gpointer data) /** * Mouse grabbed node callback. */ -static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data) +static void node_grabbed(SPKnot *knot, guint state, gpointer data) { Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; @@ -3555,6 +3560,9 @@ static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data) } n->is_dragging = true; + // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping) + n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin; + sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5); sp_nodepath_remember_origins (n->subpath->nodepath); @@ -3569,6 +3577,7 @@ static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data) n->dragging_out = NULL; n->is_dragging = false; + n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE); sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas); sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes")); @@ -4579,7 +4588,7 @@ sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node * n->pos = *pos; n->p.pos = *ppos; n->n.pos = *npos; - + n->dragging_out = NULL; Inkscape::NodePath::Node *prev; diff --git a/src/nodepath.h b/src/nodepath.h index 262b93d04..8c3d27442 100644 --- a/src/nodepath.h +++ b/src/nodepath.h @@ -276,6 +276,10 @@ class Path { /// there isn't any); we also consider the node mouseovered if it is covered /// by one of its handles and the latter is mouseovered static Node *active_node; + + /// Location of mouse pointer when we started dragging, needed for snapping + Geom::Point drag_origin_mouse; + }; } // namespace NodePath diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index 1fd566c82..06f24c47f 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -247,7 +247,7 @@ void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc, for (std::vector::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) { Geom::Coord dist = Geom::L2(*k - p); - if (dist < getSnapperTolerance() && dist < s.getDistance()) { + if (dist < getSnapperTolerance() && dist < s.getSnapDistance()) { s = SnappedPoint(*k, SNAPTARGET_NODE, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true); success = true; } @@ -276,7 +276,7 @@ void Inkscape::ObjectSnapper::_snapTranslatingGuideToNodes(SnappedConstraints &s Geom::Point p_proj = project_on_linesegment(*k, p, p + Geom::rot90(guide_normal)); Geom::Coord dist = Geom::L2(*k - p_proj); // distance from node to the guide Geom::Coord dist2 = Geom::L2(p - p_proj); // distance from projection of node on the guide, to the mouse location - if ((dist < tol && dist2 < tol) || (getSnapperAlwaysSnap() && dist < s.getDistance())) { + if ((dist < tol && dist2 < tol) || (getSnapperAlwaysSnap() && dist < s.getSnapDistance())) { s = SnappedPoint(*k, SNAPTARGET_NODE, dist, tol, getSnapperAlwaysSnap(), true); success = true; } diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 0b8601748..1868b9c88 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -249,6 +249,7 @@ static char const preferences_skeleton[] = " \n" " \n" " \n" +" \n" " \n" " \n" " \n" diff --git a/src/seltrans.cpp b/src/seltrans.cpp index 614ce7584..34ea17a64 100644 --- a/src/seltrans.cpp +++ b/src/seltrans.cpp @@ -1413,10 +1413,9 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) /* Pick one */ Inkscape::SnappedPoint best_snapped_point; - g_assert(best_snapped_point.getDistance() == NR_HUGE); for (std::list::const_iterator i = s.begin(); i != s.end(); i++) { if (i->getSnapped()) { - if (i->getDistance() < best_snapped_point.getDistance()) { + if (best_snapped_point.isOtherOneBetter(*i, true)) { best_snapped_point = *i; dxy = i->getTransformation(); } diff --git a/src/snap.cpp b/src/snap.cpp index ebce87c98..f6504efe3 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -209,10 +209,12 @@ Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t) const snapper->freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_NODE, t_offset, TRUE, Geom::OptRect(), NULL, NULL); // Find the best snap for this grid, including intersections of the grid-lines Inkscape::SnappedPoint s = findBestSnap(t_offset, sc, false); - if (s.getSnapped() && (s.getDistance() < nearest_distance)) { - success = true; + if (s.getSnapped() && (s.getSnapDistance() < nearest_distance)) { + // use getSnapDistance() instead of getWeightedDistance() here because the pointer's position + // doesn't tell us anything about which node to snap + success = true; nearest_multiple = s.getPoint() - to_2geom(grid->origin); - nearest_distance = s.getDistance(); + nearest_distance = s.getSnapDistance(); } } } @@ -448,8 +450,9 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( } else { snapped_point = freeSnap(type, *j, i == points.begin(), bbox); } - snapped_point.setPointerDistance(Geom::L2(pointer - *i)); } + // std::cout << "dist = " << snapped_point.getSnapDistance() << std::endl; + snapped_point.setPointerDistance(Geom::L2(pointer - *i)); Geom::Point result; Geom::Point scale_metric(NR_HUGE, NR_HUGE); @@ -509,15 +512,15 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( } } // Store the metric for this transformation as a virtual distance - snapped_point.setDistance(std::abs(result[dim] - transformation[dim])); - snapped_point.setSecondDistance(NR_HUGE); + snapped_point.setSnapDistance(std::abs(result[dim] - transformation[dim])); + snapped_point.setSecondSnapDistance(NR_HUGE); break; case SKEW: result[0] = (snapped_point.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]); // skew factor result[1] = transformation[1]; // scale factor // Store the metric for this transformation as a virtual distance - snapped_point.setDistance(std::abs(result[0] - transformation[0])); - snapped_point.setSecondDistance(NR_HUGE); + snapped_point.setSnapDistance(std::abs(result[0] - transformation[0])); + snapped_point.setSecondSnapDistance(NR_HUGE); break; default: g_assert_not_reached(); @@ -548,10 +551,10 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( } } } else { // For all transformations other than scaling - if (best_snapped_point.isOtherOneBetter(snapped_point)) { - best_transformation = result; + if (best_snapped_point.isOtherOneBetter(snapped_point, true)) { + best_transformation = result; best_snapped_point = snapped_point; - } + } } } @@ -572,13 +575,13 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( } best_metric = std::min(best_scale_metric[0], best_scale_metric[1]); } else { // For all transformations other than scaling - best_metric = best_snapped_point.getDistance(); + best_metric = best_snapped_point.getSnapDistance(); } best_snapped_point.setTransformation(best_transformation); // Using " < 1e6" instead of " < NR_HUGE" for catching some rounding errors // These rounding errors might be caused by NRRects, see bug #1584301 - best_snapped_point.setDistance(best_metric < 1e6 ? best_metric : NR_HUGE); + best_snapped_point.setSnapDistance(best_metric < 1e6 ? best_metric : NR_HUGE); return best_snapped_point; } @@ -810,9 +813,9 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, SnappedCo for (std::list::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) { // first find out if this snapped point is within snapping range // std::cout << "sp = " << from_2geom((*i).getPoint()); - if ((*i).getDistance() <= (*i).getTolerance()) { + if ((*i).getSnapDistance() <= (*i).getTolerance()) { // if it's the first point, or if it is closer than the best snapped point so far - if (i == sp_list.begin() || bestSnappedPoint.isOtherOneBetter(*i)) { + if (i == sp_list.begin() || bestSnappedPoint.isOtherOneBetter(*i, false)) { // then prefer this point over the previous one bestSnappedPoint = *i; } @@ -829,7 +832,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, SnappedCo } } - // std::cout << "findBestSnap = " << bestSnappedPoint.getPoint() << std::endl; + // std::cout << "findBestSnap = " << bestSnappedPoint.getPoint() << " | dist = " << bestSnappedPoint.getSnapDistance() << std::endl; return bestSnappedPoint; } diff --git a/src/snapped-curve.cpp b/src/snapped-curve.cpp index 3a6512e5e..327de90bd 100644 --- a/src/snapped-curve.cpp +++ b/src/snapped-curve.cpp @@ -21,11 +21,11 @@ Inkscape::SnappedCurve::SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, bool const &fully_constrained, Geom::Curve const *curve) { _distance = snapped_distance; - _tolerance = snapped_tolerance; + _tolerance = std::max(snapped_tolerance, 1.0); _always_snap = always_snap; _curve = curve; _second_distance = NR_HUGE; - _second_tolerance = 0; + _second_tolerance = 1; _second_always_snap = false; _point = snapped_point; _at_intersection = false; @@ -35,11 +35,11 @@ Inkscape::SnappedCurve::SnappedCurve(Geom::Point const &snapped_point, Geom::Coo Inkscape::SnappedCurve::SnappedCurve() { _distance = NR_HUGE; - _tolerance = 0; + _tolerance = 1; _always_snap = false; _curve = NULL; _second_distance = NR_HUGE; - _second_tolerance = 0; + _second_tolerance = 1; _second_always_snap = false; _point = Geom::Point(0,0); _at_intersection = false; @@ -73,7 +73,7 @@ Inkscape::SnappedPoint Inkscape::SnappedCurve::intersect(SnappedCurve const &cur } // Now we've found the closests intersection, return it as a SnappedPoint - bool const use_this_as_primary = _distance < curve.getDistance(); + bool const use_this_as_primary = _distance < curve.getSnapDistance(); Inkscape::SnappedCurve const *primaryC = use_this_as_primary ? this : &curve; Inkscape::SnappedCurve const *secondaryC = use_this_as_primary ? &curve : this; // The intersection should in fact be returned in desktop coordinates, but for this @@ -83,8 +83,8 @@ Inkscape::SnappedPoint Inkscape::SnappedCurve::intersect(SnappedCurve const &cur // TODO: Investigate whether it is possible to use document coordinates everywhere // in the snapper code. Only the mouse position should be in desktop coordinates, I guess. // All paths are already in document coords and we are certainly not going to change THAT. - return SnappedPoint(from_2geom(best_p), Inkscape::SNAPTARGET_PATH_INTERSECTION, primaryC->getDistance(), primaryC->getTolerance(), primaryC->getAlwaysSnap(), true, true, - secondaryC->getDistance(), secondaryC->getTolerance(), secondaryC->getAlwaysSnap()); + return SnappedPoint(from_2geom(best_p), Inkscape::SNAPTARGET_PATH_INTERSECTION, primaryC->getSnapDistance(), primaryC->getTolerance(), primaryC->getAlwaysSnap(), true, true, + secondaryC->getSnapDistance(), secondaryC->getTolerance(), secondaryC->getAlwaysSnap()); } // No intersection @@ -97,7 +97,7 @@ bool getClosestCurve(std::list const &list, Inkscape::Sn bool success = false; for (std::list::const_iterator i = list.begin(); i != list.end(); i++) { - if ((i == list.begin()) || (*i).getDistance() < result.getDistance()) { + if ((i == list.begin()) || (*i).getSnapDistance() < result.getSnapDistance()) { result = *i; success = true; } @@ -120,10 +120,10 @@ bool getClosestIntersectionCS(std::list const &list, Geo // if it's the first point bool const c1 = !success; // or, if it's closer - bool const c2 = sp.getDistance() < result.getDistance(); + bool const c2 = sp.getSnapDistance() < result.getSnapDistance(); // or, if it's just then look at the other distance // (only relevant for snapped points which are at an intersection - bool const c3 = (sp.getDistance() == result.getDistance()) && (sp.getSecondDistance() < result.getSecondDistance()); + bool const c3 = (sp.getSnapDistance() == result.getSnapDistance()) && (sp.getSecondSnapDistance() < result.getSecondSnapDistance()); // then prefer this point over the previous one if (c1 || c2 || c3) { result = sp; diff --git a/src/snapped-line.cpp b/src/snapped-line.cpp index fc2d059dc..48fc82051 100644 --- a/src/snapped-line.cpp +++ b/src/snapped-line.cpp @@ -17,11 +17,11 @@ Inkscape::SnappedLineSegment::SnappedLineSegment(Geom::Point const &snapped_poin { _point = snapped_point; _distance = snapped_distance; - _tolerance = snapped_tolerance; + _tolerance = std::max(snapped_tolerance, 1.0); _always_snap = always_snap; _at_intersection = false; _second_distance = NR_HUGE; - _second_tolerance = 0; + _second_tolerance = 1; _second_always_snap = false; } @@ -31,11 +31,11 @@ Inkscape::SnappedLineSegment::SnappedLineSegment() _end_point_of_line = Geom::Point(0,0); _point = Geom::Point(0,0); _distance = NR_HUGE; - _tolerance = 0; + _tolerance = 1; _always_snap = false; _at_intersection = false; _second_distance = NR_HUGE; - _second_tolerance = 0; + _second_tolerance = 1; _second_always_snap = false; } @@ -63,12 +63,12 @@ Inkscape::SnappedPoint Inkscape::SnappedLineSegment::intersect(SnappedLineSegmen * line, not the distance to the intersection. * See the comment in Inkscape::SnappedLine::intersect */ - bool const c2 = _distance < line.getDistance(); + bool const c2 = _distance < line.getSnapDistance(); bool const use_this_as_primary = c1 || c2; Inkscape::SnappedLineSegment const *primarySLS = use_this_as_primary ? this : &line; Inkscape::SnappedLineSegment const *secondarySLS = use_this_as_primary ? &line : this; - return SnappedPoint(intersection, SNAPTARGET_PATH_INTERSECTION, primarySLS->getDistance(), primarySLS->getTolerance(), primarySLS->getAlwaysSnap(), true, true, - secondarySLS->getDistance(), secondarySLS->getTolerance(), secondarySLS->getAlwaysSnap()); + return SnappedPoint(intersection, SNAPTARGET_PATH_INTERSECTION, primarySLS->getSnapDistance(), primarySLS->getTolerance(), primarySLS->getAlwaysSnap(), true, true, + secondarySLS->getSnapDistance(), secondarySLS->getTolerance(), secondarySLS->getAlwaysSnap()); } // No intersection @@ -81,10 +81,10 @@ Inkscape::SnappedLine::SnappedLine(Geom::Point const &snapped_point, Geom::Coord : _normal_to_line(normal_to_line), _point_on_line(point_on_line) { _distance = snapped_distance; - _tolerance = snapped_tolerance; + _tolerance = std::max(snapped_tolerance, 1.0); _always_snap = always_snap; _second_distance = NR_HUGE; - _second_tolerance = 0; + _second_tolerance = 1; _second_always_snap = false; _point = snapped_point; _at_intersection = false; @@ -95,10 +95,10 @@ Inkscape::SnappedLine::SnappedLine() _normal_to_line = Geom::Point(0,0); _point_on_line = Geom::Point(0,0); _distance = NR_HUGE; - _tolerance = 0; + _tolerance = 1; _always_snap = false; _second_distance = NR_HUGE; - _second_tolerance = 0; + _second_tolerance = 1; _second_always_snap = false; _point = Geom::Point(0,0); _at_intersection = false; @@ -138,12 +138,12 @@ Inkscape::SnappedPoint Inkscape::SnappedLine::intersect(SnappedLine const &line) * than it, as that would rule the intersection out when comparing it with regular snappoint, * as the latter will always be closer */ - bool const c2 = _distance < line.getDistance(); + bool const c2 = _distance < line.getSnapDistance(); bool const use_this_as_primary = c1 || c2; Inkscape::SnappedLine const *primarySL = use_this_as_primary ? this : &line; Inkscape::SnappedLine const *secondarySL = use_this_as_primary ? &line : this; - return SnappedPoint(intersection, Inkscape::SNAPTARGET_UNDEFINED, primarySL->getDistance(), primarySL->getTolerance(), primarySL->getAlwaysSnap(), true, true, - secondarySL->getDistance(), secondarySL->getTolerance(), secondarySL->getAlwaysSnap()); + return SnappedPoint(intersection, Inkscape::SNAPTARGET_UNDEFINED, primarySL->getSnapDistance(), primarySL->getTolerance(), primarySL->getAlwaysSnap(), true, true, + secondarySL->getSnapDistance(), secondarySL->getTolerance(), secondarySL->getAlwaysSnap()); // The type of the snap target is yet undefined, as we cannot tell whether // we're snapping to grid or the guide lines; must be set by on a higher level } @@ -158,7 +158,7 @@ bool getClosestSLS(std::list const &list, Inkscape bool success = false; for (std::list::const_iterator i = list.begin(); i != list.end(); i++) { - if ((i == list.begin()) || (*i).getDistance() < result.getDistance()) { + if ((i == list.begin()) || (*i).getSnapDistance() < result.getSnapDistance()) { result = *i; success = true; } @@ -181,10 +181,10 @@ bool getClosestIntersectionSLS(std::list const &li // if it's the first point bool const c1 = !success; // or, if it's closer - bool const c2 = sp.getDistance() < result.getDistance(); + bool const c2 = sp.getSnapDistance() < result.getSnapDistance(); // or, if it's just then look at the other distance // (only relevant for snapped points which are at an intersection - bool const c3 = (sp.getDistance() == result.getDistance()) && (sp.getSecondDistance() < result.getSecondDistance()); + bool const c3 = (sp.getSnapDistance() == result.getSnapDistance()) && (sp.getSecondSnapDistance() < result.getSecondSnapDistance()); // then prefer this point over the previous one if (c1 || c2 || c3) { result = sp; @@ -203,7 +203,7 @@ bool getClosestSL(std::list const &list, Inkscape::Snappe bool success = false; for (std::list::const_iterator i = list.begin(); i != list.end(); i++) { - if ((i == list.begin()) || (*i).getDistance() < result.getDistance()) { + if ((i == list.begin()) || (*i).getSnapDistance() < result.getSnapDistance()) { result = *i; success = true; } @@ -226,10 +226,10 @@ bool getClosestIntersectionSL(std::list const &list, Inks // if it's the first point bool const c1 = !success; // or, if it's closer - bool const c2 = sp.getDistance() < result.getDistance(); + bool const c2 = sp.getSnapDistance() < result.getSnapDistance(); // or, if it's just then look at the other distance // (only relevant for snapped points which are at an intersection - bool const c3 = (sp.getDistance() == result.getDistance()) && (sp.getSecondDistance() < result.getSecondDistance()); + bool const c3 = (sp.getSnapDistance() == result.getSnapDistance()) && (sp.getSecondSnapDistance() < result.getSecondSnapDistance()); // then prefer this point over the previous one if (c1 || c2 || c3) { result = sp; @@ -254,10 +254,10 @@ bool getClosestIntersectionSL(std::list const &list1, std // if it's the first point bool const c1 = !success; // or, if it's closer - bool const c2 = sp.getDistance() < result.getDistance(); + bool const c2 = sp.getSnapDistance() < result.getSnapDistance(); // or, if it's just then look at the other distance // (only relevant for snapped points which are at an intersection - bool const c3 = (sp.getDistance() == result.getDistance()) && (sp.getSecondDistance() < result.getSecondDistance()); + bool const c3 = (sp.getSnapDistance() == result.getSnapDistance()) && (sp.getSecondSnapDistance() < result.getSecondSnapDistance()); // then prefer this point over the previous one if (c1 || c2 || c3) { result = sp; diff --git a/src/snapped-point.cpp b/src/snapped-point.cpp index 1177e5f14..d03968a94 100644 --- a/src/snapped-point.cpp +++ b/src/snapped-point.cpp @@ -9,27 +9,31 @@ * Released under GNU GPL, read the file 'COPYING' for more information. */ +#include #include "snapped-point.h" +#include "preferences.h" // overloaded constructor Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained) - : _point(p), _target(target), _distance(d), _tolerance(t), _always_snap(a) + : _point(p), _target(target), _distance(d), _tolerance(std::max(t,1.0)), _always_snap(a) { - _at_intersection = false; + // tolerance should never be smaller than 1 px, as it is used for normalization in isOtherOneBetter. We don't want a division by zero. _fully_constrained = fully_constrained; _second_distance = NR_HUGE; - _second_tolerance = 0; + _second_tolerance = 1; _second_always_snap = false; _transformation = Geom::Point(1,1); - _pointer_distance = 0; + _pointer_distance = NR_HUGE; } Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, bool const &fully_constrained, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2) - : _point(p), _target(target), _at_intersection(at_intersection), _fully_constrained(fully_constrained), _distance(d), _tolerance(t), _always_snap(a), - _second_distance(d2), _second_tolerance(t2), _second_always_snap(a2) + : _point(p), _target(target), _at_intersection(at_intersection), _fully_constrained(fully_constrained), _distance(d), _tolerance(std::max(t,1.0)), _always_snap(a), + _second_distance(d2), _second_tolerance(std::max(t2,1.0)), _second_always_snap(a2) { + // tolerance should never be smaller than 1 px, as it is used for normalization in + // isOtherOneBetter. We don't want a division by zero. _transformation = Geom::Point(1,1); - _pointer_distance = 0; + _pointer_distance = NR_HUGE; } Inkscape::SnappedPoint::SnappedPoint() @@ -37,14 +41,14 @@ Inkscape::SnappedPoint::SnappedPoint() _point = Geom::Point(0,0); _target = SNAPTARGET_UNDEFINED, _distance = NR_HUGE; - _tolerance = 0; + _tolerance = 1; _always_snap = false; _at_intersection = false; _second_distance = NR_HUGE; - _second_tolerance = 0; + _second_tolerance = 1; _second_always_snap = false; _transformation = Geom::Point(1,1); - _pointer_distance = 0; + _pointer_distance = NR_HUGE; } Inkscape::SnappedPoint::~SnappedPoint() @@ -66,7 +70,7 @@ bool getClosestSP(std::list &list, Inkscape::SnappedPoin bool success = false; for (std::list::const_iterator i = list.begin(); i != list.end(); i++) { - if ((i == list.begin()) || (*i).getDistance() < result.getDistance()) { + if ((i == list.begin()) || (*i).getSnapDistance() < result.getSnapDistance()) { result = *i; success = true; } @@ -75,13 +79,39 @@ bool getClosestSP(std::list &list, Inkscape::SnappedPoin return success; } -bool Inkscape::SnappedPoint::isOtherOneBetter(Inkscape::SnappedPoint const &other_one) const +bool Inkscape::SnappedPoint::isOtherOneBetter(Inkscape::SnappedPoint const &other_one, bool weighted) const { - double const w = 0.25; // weigth factor: controls which node should be preferrerd for snapping, which is either - // the node with the closest snap (w = 0), or the node closest to the mousepointer (w = 1) - // If it's closer - bool c1 = (w * other_one.getPointerDistance() + (1-w) * other_one.getDistance()) < (w * getPointerDistance() + (1-w) * getDistance()); + double dist_other = other_one.getSnapDistance(); + double dist_this = getSnapDistance(); + + // The distance to the pointer should only be taken into account when finding the best snapped source node (when + // there's more than one). It is not useful when trying to find the best snapped target point. + // (both the snap distance and the pointer distance are measured in document pixels, not in screen pixels) + if (weighted) { + // weigth factor: controls which node should be preferrerd for snapping, which is either + // the node with the closest snap (w = 0), or the node closest to the mousepointer (w = 1) + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double const w = prefs->getDoubleLimited("/options/snapweight/value", 0.5, 0, 1); + if (w > 0) { + // When accounting for the distance to the mouse pointer, then at least one of the snapped points should + // have that distance set. If not, then this is a bug. Either "weighted" must be set to false, or the + // mouse pointer distance must be set. + g_assert(getPointerDistance() != NR_HUGE || other_one.getPointerDistance() != NR_HUGE); + // The snap distance will always be smaller than the tolerance set for the snapper. The pointer distance can + // however be very large. To compare these in a fair way, we will have to normalize these metrics first + // The closest pointer distance will be normalized to 1.0; the other one will be > 1.0 + // The snap distance will be normalized to 1.0 if it's equal to the snapper tolerance + double const norm_p = std::min(getPointerDistance(), other_one.getPointerDistance()); + double const norm_t_other = std::min(50.0, other_one.getTolerance()); + double const norm_t_this = std::min(50.0, getTolerance()); + dist_other = w * other_one.getPointerDistance() / norm_p + (1-w) * dist_other / norm_t_other; + dist_this = w * getPointerDistance() / norm_p + (1-w) * dist_this / norm_t_this; + } + } + + // If it's closer + bool c1 = dist_other < dist_this; // or, if it's for a snapper with "always snap" turned on, and the previous wasn't bool c2 = other_one.getAlwaysSnap() && !getAlwaysSnap(); // But in no case fall back from a snapper with "always snap" on to one with "always snap" off @@ -92,10 +122,10 @@ bool Inkscape::SnappedPoint::isOtherOneBetter(Inkscape::SnappedPoint const &othe bool c3n = !other_one.getFullyConstrained() && getFullyConstrained(); // or, if it's just as close then consider the second distance // (which is only relevant for points at an intersection) - bool c4a = (other_one.getDistance() == getDistance()); - bool c4b = other_one.getSecondDistance() < getSecondDistance(); + bool c4a = (dist_other == dist_this); + bool c4b = other_one.getSecondSnapDistance() < getSecondSnapDistance(); - // std::cout << "c1 = " << c1 << " | c2 = " << c2 << " | c2n = " << c2n << " | c3 = " << c3 << " | c3n = " << c3n << " | c4a = " << c4a << " | c4b = " << c4b; + // std::cout << "c1 = " << c1 << " | c2 = " << c2 << " | c2n = " << c2n << " | c3 = " << c3 << " | c3n = " << c3n << " | c4a = " << c4a << " | c4b = " << c4b << std::endl; return (c1 || c2 || c3 || (c4a && c4b)) && !c2n && (!c3n || c2); } diff --git a/src/snapped-point.h b/src/snapped-point.h index 254e46421..bc5b2d39e 100644 --- a/src/snapped-point.h +++ b/src/snapped-point.h @@ -46,12 +46,12 @@ public: SnappedPoint(Geom::Point const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained); ~SnappedPoint(); - Geom::Coord getDistance() const {return _distance;} - void setDistance(Geom::Coord const d) {_distance = d;} + Geom::Coord getSnapDistance() const {return _distance;} + void setSnapDistance(Geom::Coord const d) {_distance = d;} Geom::Coord getTolerance() const {return _tolerance;} bool getAlwaysSnap() const {return _always_snap;} - Geom::Coord getSecondDistance() const {return _second_distance;} - void setSecondDistance(Geom::Coord const d) {_second_distance = d;} + Geom::Coord getSecondSnapDistance() const {return _second_distance;} + void setSecondSnapDistance(Geom::Coord const d) {_second_distance = d;} Geom::Coord getSecondTolerance() const {return _second_tolerance;} bool getSecondAlwaysSnap() const {return _second_always_snap;} Geom::Coord getPointerDistance() const {return _pointer_distance;} @@ -79,7 +79,7 @@ public: void setTarget(SnapTargetType const target) {_target = target;} SnapTargetType getTarget() {return _target;} - bool isOtherOneBetter(SnappedPoint const &other_one) const; + bool isOtherOneBetter(SnappedPoint const &other_one, bool weighted) const; protected: Geom::Point _point; // Location of the snapped point diff --git a/src/snapper.cpp b/src/snapper.cpp index 7e4568160..79f30fa3c 100644 --- a/src/snapper.cpp +++ b/src/snapper.cpp @@ -22,7 +22,7 @@ Inkscape::Snapper::Snapper(SnapManager const *sm, Geom::Coord const t) : _snapmanager(sm), _snap_enabled(true), - _snapper_tolerance(t) + _snapper_tolerance(std::max(t, 1.0)) { g_assert(_snapmanager != NULL); } @@ -33,7 +33,7 @@ Inkscape::Snapper::Snapper(SnapManager const *sm, Geom::Coord const t) : */ void Inkscape::Snapper::setSnapperTolerance(Geom::Coord const d) { - _snapper_tolerance = d; + _snapper_tolerance = std::max(d, 1.0); } /** diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index a436ce867..e78d51af4 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -208,7 +208,12 @@ void InkscapePreferences::initPageSnapping() _snap_delay.init("/options/snapdelay/value", 0, 1000, 50, 100, 300, 0); _page_snapping.add_line( false, _("Delay (in msec):"), _snap_delay, "", - _("Postpone snapping as long as the mouse is moving, and then wait an additional fraction of a second. This additional delay is specified here. When set to zero or to a very small number, snapping will be immediate"), true); + _("Postpone snapping as long as the mouse is moving, and then wait an additional fraction of a second. This additional delay is specified here. When set to zero or to a very small number, snapping will be immediate"), true); + + _snap_weight.init("/options/snapweight/value", 0, 1, 0.1, 0.2, 0.5, 1); + _page_snapping.add_line( false, _("Weight factor:"), _snap_weight, "", + _("When multiple snap solutions are found, then Inkscape can either prefer the closest transformation (when set to 0), or prefer the node that was initially the closest to the pointer (when set to 1)"), true); + this->AddPage(_page_snapping, _("Snapping"), PREFS_PAGE_SNAPPING); } diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index a957ce657..c62919c45 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -176,7 +176,7 @@ protected: PrefSpinButton _importexport_export, _misc_recent, _misc_simpl; ZoomCorrRulerSlider _ui_zoom_correction; - PrefSlider _snap_delay; + PrefSlider _snap_delay, _snap_weight; PrefSpinButton _misc_latency_skew; PrefCheckButton _misc_comment, _misc_forkvectors, _misc_scripts, _misc_namedicon_delay; PrefCombo _misc_small_toolbar; -- 2.30.2