diff options
Diffstat (limited to 'src/snap.cpp')
| -rw-r--r-- | src/snap.cpp | 207 |
1 files changed, 165 insertions, 42 deletions
diff --git a/src/snap.cpp b/src/snap.cpp index 718700103..40dd4ca31 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -174,7 +174,6 @@ void SnapManager::freeSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, Geom::OptRect const &bbox_to_snap) const { - //TODO: SnapCandidatePoint and point_type are somewhat redundant; can't we get rid of the point_type parameter? Inkscape::SnappedPoint const s = freeSnap(Inkscape::SnapCandidatePoint(p, source_type), bbox_to_snap); s.getPoint(p); } @@ -335,7 +334,7 @@ Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t, Geom::Point c void SnapManager::constrainedSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap) const { Inkscape::SnappedPoint const s = constrainedSnap(Inkscape::SnapCandidatePoint(p, source_type, 0), constraint, bbox_to_snap); @@ -359,13 +358,13 @@ void SnapManager::constrainedSnapReturnByRef(Geom::Point &p, */ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapCandidatePoint const &p, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap) const { // First project the mouse pointer onto the constraint Geom::Point pp = constraint.projection(p.getPoint()); - Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(pp, p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, Geom::L2(pp - p.getPoint()), 0, false, true, false); + Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(pp, p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, NR_HUGE, 0, false, true, false); if (!someSnapperMightSnap()) { // Always return point on constraint @@ -390,6 +389,68 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapCandidatePoint return no_snap; } +/* See the documentation for constrainedSnap() directly above for more details. + * The difference is that multipleConstrainedSnaps() will take a list of constraints instead of a single one, + * and will try to snap the SnapCandidatePoint to all of the provided constraints and see which one fits best + * \param p Source point to be snapped + * \param constraints List of directions or lines along which snapping must occur + * \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation + */ + + +Inkscape::SnappedPoint SnapManager::multipleConstrainedSnaps(Inkscape::SnapCandidatePoint const &p, + std::vector<Inkscape::Snapper::SnapConstraint> const &constraints, + Geom::OptRect const &bbox_to_snap) const +{ + + Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(p.getPoint(), p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, NR_HUGE, 0, false, true, false); + if (constraints.size() == 0) { + return no_snap; + } + + SnappedConstraints sc; + SnapperList const snappers = getSnappers(); + std::vector<Geom::Point> projections; + bool snapping_is_futile = !someSnapperMightSnap(); + + // Iterate over the constraints + for (std::vector<Inkscape::Snapper::SnapConstraint>::const_iterator c = constraints.begin(); c != constraints.end(); c++) { + // Project the mouse pointer onto the constraint; In case we don't snap then we will + // return the projection onto the constraint, such that the constraint is always enforced + Geom::Point pp = (*c).projection(p.getPoint()); + projections.push_back(pp); + // Try to snap to the constraint + if (!snapping_is_futile) { + for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { + (*i)->constrainedSnap(sc, p, bbox_to_snap, *c, &_items_to_ignore); + } + } + } + + Inkscape::SnappedPoint result = findBestSnap(p, sc, true); + + if (result.getSnapped()) { + // only change the snap indicator if we really snapped to something + if (_snapindicator) { + _desktop->snapindicator->set_new_snaptarget(result); + } + return result; + } + + // So we didn't snap, but we still need to return a point on one of the constraints + // Find out which of the constraints yielded the closest projection of point p + no_snap.setPoint(projections.front()); + for (std::vector<Geom::Point>::iterator pp = projections.begin(); pp != projections.end(); pp++) { + if (pp != projections.begin()) { + if (Geom::L2(*pp - p.getPoint()) < Geom::L2(no_snap.getPoint() - p.getPoint())) { + no_snap.setPoint(*pp); + } + } + } + + return no_snap; +} + /** * \brief Try to snap a point of a guide to another guide or to a node * @@ -408,7 +469,7 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, return; } - if (!(object.GuidesMightSnap() || snapprefs.getSnapToGuides())) { + if (!(object.ThisSnapperMightSnap() || snapprefs.getSnapToGuides())) { return; } @@ -419,7 +480,7 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, // Snap to nodes SnappedConstraints sc; - if (object.GuidesMightSnap()) { + if (object.ThisSnapperMightSnap()) { object.guideFreeSnap(sc, p, guide_normal); } @@ -430,8 +491,7 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, (*i)->freeSnap(sc, candidate, Geom::OptRect(), NULL, NULL); } - // Snap to intersections of curves, but not to the curves themselves! (see _snapTranslatingGuideToNodes in object-snapper.cpp) - Inkscape::SnappedPoint const s = findBestSnap(candidate, sc, false, true); + Inkscape::SnappedPoint const s = findBestSnap(candidate, sc, false, false); s.getPoint(p); } @@ -464,7 +524,7 @@ void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) // Snap to nodes or paths SnappedConstraints sc; - Inkscape::Snapper::ConstraintLine cl(guideline.point_on_line, Geom::rot90(guideline.normal_to_line)); + Inkscape::Snapper::SnapConstraint cl(guideline.point_on_line, Geom::rot90(guideline.normal_to_line)); if (object.ThisSnapperMightSnap()) { object.constrainedSnap(sc, candidate, Geom::OptRect(), cl, NULL); } @@ -509,7 +569,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( std::vector<Inkscape::SnapCandidatePoint> const &points, Geom::Point const &pointer, bool constrained, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Transformation transformation_type, Geom::Point const &transformation, Geom::Point const &origin, @@ -559,8 +619,17 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point g_assert(best_snapped_point.getAtIntersection() == false); - std::vector<Inkscape::SnapCandidatePoint>::iterator j = transformed_points.begin(); + // Warnings for the devs + if (constrained && transformation_type == SCALE && !uniform) { + g_warning("Non-uniform constrained scaling is not supported!"); + } + if (!constrained && transformation_type == ROTATE) { + // We do not yet allow for simultaneous rotation and scaling + g_warning("Unconstrained rotation is not supported!"); + } + + std::vector<Inkscape::SnapCandidatePoint>::iterator j = transformed_points.begin(); // std::cout << std::endl; bool first_free_snap = true; @@ -568,27 +637,38 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( /* Snap it */ Inkscape::SnappedPoint snapped_point; - Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint; - Geom::Point const b = ((*i).getPoint() - origin); // vector to original point + Inkscape::Snapper::SnapConstraint dedicated_constraint = constraint; + Geom::Point const b = ((*i).getPoint() - origin); // vector to original point (not the transformed point! required for rotations!) if (constrained) { - if ((transformation_type == SCALE || transformation_type == STRETCH) && uniform) { + if (((transformation_type == SCALE || transformation_type == STRETCH) && uniform)) { // When uniformly scaling, each point will have its own unique constraint line, // running from the scaling origin to the original untransformed point. We will // calculate that line here - dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, b); + dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, b); + } else if (transformation_type == ROTATE) { + Geom::Coord r = Geom::L2(b); // the radius of the circular constraint + if (r < 1e-9) { // points too close to the rotation center will not move. Don't try to snap these + // as they will always yield a perfect snap result if they're already snapped beforehand (e.g. + // when the transformation center has been snapped to a grid intersection in the selector tool) + continue; // skip this SnapCandidate and continue with the next one + // PS1: Apparently we don't have to do this for skewing, but why? + // PS2: We cannot easily filter these points upstream, e.g. in the grab() method (seltrans.cpp) + // because the rotation center will change when pressing shift, and grab() won't be recalled. + // Filtering could be done in handleRequest() (again in seltrans.cpp), by iterating through + // the snap candidates. But hey, we're iterating here anyway. + } + dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, b, r); } else if (transformation_type == STRETCH) { // when non-uniform stretching { - dedicated_constraint = Inkscape::Snapper::ConstraintLine((*i).getPoint(), component_vectors[dim]); - } else if (transformation_type == TRANSLATION) { + dedicated_constraint = Inkscape::Snapper::SnapConstraint((*i).getPoint(), component_vectors[dim]); + } else if (transformation_type == TRANSLATE) { // When doing a constrained translation, all points will move in the same direction, i.e. // either horizontally or vertically. The lines along which they move are therefore all - // parallel, but might not be colinear. Therefore we will have to set the point through - // which the constraint-line runs here, for each point individually. - dedicated_constraint.setPoint((*i).getPoint()); + // parallel, but might not be colinear. Therefore we will have to specify the point through + // which the constraint-line runs here, for each point individually. (we could also have done this + // earlier on, e.g. in seltrans.cpp but we're being lazy there and don't want to add an iteration loop) + dedicated_constraint = Inkscape::Snapper::SnapConstraint((*i).getPoint(), constraint.getDirection()); } // else: leave the original constraint, e.g. for skewing - if (transformation_type == SCALE && !uniform) { - g_warning("Non-uniform constrained scaling is not supported!"); - } snapped_point = constrainedSnap(*j, dedicated_constraint, bbox); } else { bool const c1 = fabs(b[Geom::X]) < 1e-6; @@ -597,7 +677,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( // When scaling, a point aligned either horizontally or vertically with the origin can only // 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]); + dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, component_vectors[c1]); snapped_point = constrainedSnap(*j, dedicated_constraint, bbox); } else { // If we have a collection of SnapCandidatePoints, with mixed constrained snapping and free snapping @@ -623,11 +703,11 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( /* We snapped. Find the transformation that describes where the snapped point has ** ended up, and also the metric for this transformation. */ - Geom::Point const a = (snapped_point.getPoint() - origin); // vector to snapped point + Geom::Point const a = snapped_point.getPoint() - origin; // vector to snapped point //Geom::Point const b = (*i - origin); // vector to original point switch (transformation_type) { - case TRANSLATION: + case TRANSLATE: result = snapped_point.getPoint() - (*i).getPoint(); /* Consider the case in which a box is almost aligned with a grid in both * horizontal and vertical directions. The distance to the intersection of @@ -687,12 +767,20 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( snapped_point.setSecondSnapDistance(Geom::infinity()); break; case SKEW: - result[0] = (snapped_point.getPoint()[dim] - ((*i).getPoint())[dim]) / (((*i).getPoint())[1 - dim] - origin[1 - dim]); // skew factor + result[0] = (snapped_point.getPoint()[dim] - ((*i).getPoint())[dim]) / b[1 - dim]; // skew factor result[1] = transformation[1]; // scale factor // Store the metric for this transformation as a virtual distance snapped_point.setSnapDistance(std::abs(result[0] - transformation[0])); snapped_point.setSecondSnapDistance(Geom::infinity()); break; + case ROTATE: + // a is vector to snapped point; b is vector to original point; now lets calculate angle between a and b + result[0] = atan2(Geom::dot(Geom::rot90(b), a), Geom::dot(b, a)); + result[1] = result[1]; // how else should we store an angle in a point ;-) + // Store the metric for this transformation as a virtual distance (we're storing an angle) + snapped_point.setSnapDistance(std::abs(result[0] - transformation[0])); + snapped_point.setSecondSnapDistance(NR_HUGE); + break; default: g_assert_not_reached(); } @@ -738,22 +826,23 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( * \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics. */ -Inkscape::SnappedPoint SnapManager::freeSnapTranslation(std::vector<Inkscape::SnapCandidatePoint> const &p, +Inkscape::SnappedPoint SnapManager::freeSnapTranslate(std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Point const &tr) const { if (p.size() == 1) { - Geom::Point pt = _transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + Geom::Point pt = _transformPoint(p.at(0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false); _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } - return _snapTransformed(p, pointer, false, Geom::Point(0,0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + + + return _snapTransformed(p, pointer, false, Geom::Point(0,0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false); } /** * \brief Apply a translation to a set of points and try to snap along a constraint * - * \param point_type Category of points to which the source point belongs: node or bounding box. * \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source. * \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed). * \param constraint The direction or line along which snapping must occur. @@ -761,24 +850,23 @@ Inkscape::SnappedPoint SnapManager::freeSnapTranslation(std::vector<Inkscape::Sn * \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics. */ -Inkscape::SnappedPoint SnapManager::constrainedSnapTranslation(std::vector<Inkscape::SnapCandidatePoint> const &p, +Inkscape::SnappedPoint SnapManager::constrainedSnapTranslate(std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::Point const &tr) const { if (p.size() == 1) { - Geom::Point pt = _transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + Geom::Point pt = _transformPoint(p.at(0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false); _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } - return _snapTransformed(p, pointer, true, constraint, TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + return _snapTransformed(p, pointer, true, constraint, TRANSLATE, tr, Geom::Point(0,0), Geom::X, false); } /** * \brief Apply a scaling to a set of points and try to snap freely in 2 degrees-of-freedom * - * \param point_type Category of points to which the source point belongs: node or bounding box. * \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source. * \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed). * \param s Proposed scaling; the final scaling can only be calculated after snapping has occurred @@ -803,7 +891,6 @@ Inkscape::SnappedPoint SnapManager::freeSnapScale(std::vector<Inkscape::SnapCand /** * \brief Apply a scaling to a set of points and snap such that the aspect ratio of the selection is preserved * - * \param point_type Category of points to which the source point belongs: node or bounding box. * \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source. * \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed). * \param s Proposed scaling; the final scaling can only be calculated after snapping has occurred @@ -828,7 +915,6 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapScale(std::vector<Inkscape::S /** * \brief Apply a stretch to a set of points and snap such that the direction of the stretch is preserved * - * \param point_type Category of points to which the source point belongs: node or bounding box. * \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source. * \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed). * \param s Proposed stretch; the final stretch can only be calculated after snapping has occurred @@ -856,7 +942,6 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(std::vector<Inkscape: /** * \brief Apply a skew to a set of points and snap such that the direction of the skew is preserved * - * \param point_type Category of points to which the source point belongs: node or bounding box. * \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source. * \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed). * \param constraint The direction or line along which snapping must occur. @@ -868,7 +953,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(std::vector<Inkscape: Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, - Inkscape::Snapper::ConstraintLine const &constraint, + Inkscape::Snapper::SnapConstraint const &constraint, Geom::Point const &s, Geom::Point const &o, Geom::Dim2 d) const @@ -893,6 +978,36 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(std::vector<Inkscape::Sn } /** + * \brief Apply a rotation to a set of points and snap, without scaling + * + * \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source. + * \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed). + * \param angle Proposed rotation (in radians); the final rotation can only be calculated after snapping has occurred + * \param o Origin of the rotation + * \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics. + */ + +Inkscape::SnappedPoint SnapManager::constrainedSnapRotate(std::vector<Inkscape::SnapCandidatePoint> const &p, + Geom::Point const &pointer, + Geom::Coord const &angle, + Geom::Point const &o) const +{ + // Snapping the nodes of the bounding box of a selection that is being transformed, will only work if + // the transformation of the bounding box is equal to the transformation of the individual nodes. This is + // NOT the case for example when rotating or skewing. The bounding box itself cannot possibly rotate or skew, + // so it's corners have a different transformation. The snappers cannot handle this, therefore snapping + // of bounding boxes is not allowed here. + + if (p.size() == 1) { + Geom::Point pt = _transformPoint(p.at(0), ROTATE, Geom::Point(angle, angle), o, Geom::X, false); + _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); + } + + return _snapTransformed(p, pointer, true, Geom::Point(0,0), ROTATE, Geom::Point(angle, angle), o, Geom::X, false); + +} + +/** * \brief Given a set of possible snap targets, find the best target (which is not necessarily * also the nearest target), and show the snap indicator if requested * @@ -1038,6 +1153,7 @@ void SnapManager::setup(SPDesktop const *desktop, _snapindicator = snapindicator; _unselected_nodes = unselected_nodes; _guide_to_ignore = guide_to_ignore; + _rotation_center_source_item = NULL; } /** @@ -1068,6 +1184,7 @@ void SnapManager::setup(SPDesktop const *desktop, _snapindicator = snapindicator; _unselected_nodes = unselected_nodes; _guide_to_ignore = guide_to_ignore; + _rotation_center_source_item = NULL; } /// Setup, taking the list of items to ignore from the desktop's selection. @@ -1080,6 +1197,7 @@ void SnapManager::setupIgnoreSelection(SPDesktop const *desktop, _snapindicator = snapindicator; _unselected_nodes = unselected_nodes; _guide_to_ignore = guide_to_ignore; + _rotation_center_source_item = NULL; _items_to_ignore.clear(); Inkscape::Selection *sel = _desktop->selection; @@ -1116,7 +1234,7 @@ Geom::Point SnapManager::_transformPoint(Inkscape::SnapCandidatePoint const &p, /* Work out the transformed version of this point */ Geom::Point transformed; switch (transformation_type) { - case TRANSLATION: + case TRANSLATE: transformed = p.getPoint() + transformation; break; case SCALE: @@ -1141,6 +1259,10 @@ Geom::Point SnapManager::_transformPoint(Inkscape::SnapCandidatePoint const &p, // Apply that scale factor here transformed[1-dim] = (p.getPoint() - origin)[1 - dim] * transformation[1] + origin[1 - dim]; break; + case ROTATE: + // for rotations: transformation[0] stores the angle in radians + transformed = (p.getPoint() - origin) * Geom::Rotate(transformation[0]) + origin; + break; default: g_assert_not_reached(); } @@ -1161,8 +1283,9 @@ void SnapManager::_displaySnapsource(Inkscape::SnapCandidatePoint const &p) cons if (prefs->getBool("/options/snapclosestonly/value")) { bool p_is_a_node = p.getSourceType() & Inkscape::SNAPSOURCE_NODE_CATEGORY; bool p_is_a_bbox = p.getSourceType() & Inkscape::SNAPSOURCE_BBOX_CATEGORY; + bool p_is_other = p.getSourceType() & Inkscape::SNAPSOURCE_OTHER_CATEGORY; - if (snapprefs.getSnapEnabledGlobally() && ((p_is_a_node && snapprefs.getSnapModeNode()) || (p_is_a_bbox && snapprefs.getSnapModeBBox()))) { + if (snapprefs.getSnapEnabledGlobally() && (p_is_other || (p_is_a_node && snapprefs.getSnapModeNode()) || (p_is_a_bbox && snapprefs.getSnapModeBBox()))) { _desktop->snapindicator->set_new_snapsource(p); } else { _desktop->snapindicator->remove_snapsource(); |
