summaryrefslogtreecommitdiffstats
path: root/src/alignment-snapper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/alignment-snapper.cpp')
-rw-r--r--src/alignment-snapper.cpp347
1 files changed, 347 insertions, 0 deletions
diff --git a/src/alignment-snapper.cpp b/src/alignment-snapper.cpp
new file mode 100644
index 0000000..0b51ce3
--- /dev/null
+++ b/src/alignment-snapper.cpp
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Snapping things to on-canvas alignment guides.
+ *
+ * Authors:
+ * Parth Pant <parthpant4@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/circle.h>
+#include <2geom/line.h>
+#include <2geom/path-intersection.h>
+#include <2geom/path-sink.h>
+#include <memory>
+
+#include "desktop.h"
+#include "display/curve.h"
+#include "document.h"
+#include "inkscape.h"
+#include "live_effects/effect-enum.h"
+#include "object/sp-clippath.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-image.h"
+#include "object/sp-item-group.h"
+#include "object/sp-mask.h"
+#include "object/sp-namedview.h"
+#include "object/sp-page.h"
+#include "object/sp-path.h"
+#include "object/sp-root.h"
+#include "object/sp-shape.h"
+#include "object/sp-text.h"
+#include "object/sp-use.h"
+#include "path/path-util.h" // curve_for_item
+#include "preferences.h"
+#include "snap-enums.h"
+#include "style.h"
+#include "svg/svg.h"
+#include "text-editing.h"
+#include "page-manager.h"
+
+Inkscape::AlignmentSnapper::AlignmentSnapper(SnapManager *sm, Geom::Coord const d)
+ : Snapper(sm, d)
+{
+ _points_to_snap_to = std::make_unique<std::vector<Inkscape::SnapCandidatePoint>>();
+}
+
+Inkscape::AlignmentSnapper::~AlignmentSnapper()
+{
+ _points_to_snap_to->clear();
+}
+
+void Inkscape::AlignmentSnapper::_collectBBoxPoints(bool const &first_point) const
+{
+ if (!first_point)
+ return;
+
+ _points_to_snap_to->clear();
+ SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX;
+
+ Preferences *prefs = Preferences::get();
+ bool prefs_bbox = prefs->getBool("/tools/bounding_box");
+ bbox_type = !prefs_bbox ?
+ SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX;
+
+ // collect page corners and center
+ if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_CORNER)) {
+ if (auto document = _snapmanager->getDocument()) {
+ auto ignore_page = _snapmanager->getPageToIgnore();
+ for (auto page : document->getPageManager().getPages()) {
+ if (ignore_page == page)
+ continue;
+ getBBoxPoints(page->getDesktopRect(), _points_to_snap_to.get(), true,
+ SNAPSOURCE_ALIGNMENT_PAGE_CORNER, SNAPTARGET_ALIGNMENT_PAGE_CORNER,
+ SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED, // No edges
+ SNAPSOURCE_ALIGNMENT_PAGE_CENTER, SNAPTARGET_ALIGNMENT_PAGE_CENTER);
+ }
+ getBBoxPoints(document->preferredBounds(), _points_to_snap_to.get(), true,
+ SNAPSOURCE_ALIGNMENT_PAGE_CORNER, SNAPTARGET_ALIGNMENT_PAGE_CORNER,
+ SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED, // No edges
+ SNAPSOURCE_ALIGNMENT_PAGE_CENTER, SNAPTARGET_ALIGNMENT_PAGE_CENTER);
+ }
+ }
+
+
+
+ // collect bounding boxes of other objects
+ for (const auto & candidate : *(_snapmanager->_align_snapper_candidates)) {
+ SPItem *root_item = candidate.item;
+
+ // get the root item in case we have a duplicate at hand
+ SPUse *use = dynamic_cast<SPUse *>(candidate.item);
+ if (use) {
+ root_item = use->root();
+ }
+ g_return_if_fail(root_item);
+
+ // if candidate is not a clip or a mask object then extract its BBox points
+ if (!candidate.clip_or_mask) {
+ Geom::OptRect b = root_item->desktopBounds(bbox_type);
+ getBBoxPoints(b, _points_to_snap_to.get(), true,
+ SNAPSOURCE_ALIGNMENT_BBOX_CORNER, SNAPTARGET_ALIGNMENT_BBOX_CORNER,
+ SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED, // No edges
+ SNAPSOURCE_ALIGNMENT_BBOX_MIDPOINT, SNAPTARGET_ALIGNMENT_BBOX_MIDPOINT);
+ }
+ }
+
+ // Debug log
+ //std::cout<<"----------"<<std::endl;
+ //for (auto point : *_points_to_snap_to)
+ //std::cout<<point.getPoint().x()<<","<<point.getPoint().y()<<std::endl;
+}
+
+void Inkscape::AlignmentSnapper::_snapBBoxPoints(IntermSnapResults &isr,
+ SnapCandidatePoint const &p,
+ std::vector<SnapCandidatePoint> *unselected_nodes,
+ SnapConstraint const &c,
+ Geom::Point const &p_proj_on_constraint) const
+{
+
+ _collectBBoxPoints(p.getSourceNum() <= 0);
+
+ if (unselected_nodes != nullptr &&
+ unselected_nodes->size() > 0 &&
+ _snapmanager->snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_ALIGNMENT_HANDLE)) {
+ g_assert(_points_to_snap_to != nullptr);
+ _points_to_snap_to->insert(_points_to_snap_to->end(), unselected_nodes->begin(), unselected_nodes->end());
+ }
+
+ SnappedPoint sx;
+ SnappedPoint sy;
+ SnappedPoint si;
+
+ bool consider_x = true;
+ bool consider_y = true;
+ bool success_x = false;
+ bool success_y = false;
+ bool intersection = false;
+ bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping();
+
+ for (const auto & k : *_points_to_snap_to) {
+ if (_allowSourceToSnapToTarget(p.getSourceType(), k.getTargetType(), strict_snapping)) {
+ Geom::Point target_pt = k.getPoint();
+ // (unconstrained) distance from HORIZONTAL guide
+ Geom::Point point_on_x(p.getPoint().x(), target_pt.y());
+ Geom::Coord distX = Geom::L2(point_on_x - p.getPoint());
+
+ // (unconstrained) distance from VERTICAL guide
+ Geom::Point point_on_y(target_pt.x(), p.getPoint().y());
+ Geom::Coord distY = Geom::L2(point_on_y - p.getPoint());
+
+ if (!c.isUndefined() && c.isLinear()) {
+ if (c.getDirection().x() == 0)
+ consider_y = false; // consider vertical snapping if moving vertically
+ else
+ consider_x = false; // consider horizontal snapping if moving horizontally
+ }
+
+ bool is_target_node = k.getTargetType() & SNAPTARGET_NODE_CATEGORY;
+ if (consider_x && distX < getSnapperTolerance() && Geom::L2(target_pt - point_on_x) < sx.getDistanceToAlignTarget()) {
+ sx = SnappedPoint(point_on_x,
+ k.getPoint(),
+ source2alignment(p.getSourceType()),
+ p.getSourceNum(),
+ is_target_node ? SNAPTARGET_ALIGNMENT_HANDLE : k.getTargetType(),
+ distX,
+ getSnapperTolerance(),
+ getSnapperAlwaysSnap(),
+ false,
+ true,
+ k.getTargetBBox());
+ success_x = true;
+ }
+
+ if (consider_y && distY < getSnapperTolerance() && Geom::L2(target_pt - point_on_y) < sy.getDistanceToAlignTarget()) {
+ sy = SnappedPoint(point_on_y,
+ k.getPoint(),
+ source2alignment(p.getSourceType()),
+ p.getSourceNum(),
+ is_target_node ? SNAPTARGET_ALIGNMENT_HANDLE : k.getTargetType(),
+ distY,
+ getSnapperTolerance(),
+ getSnapperAlwaysSnap(),
+ false,
+ true,
+ k.getTargetBBox());
+ success_y = true;
+ }
+
+ if (consider_x && consider_y && success_x && success_y) {
+ Geom::Point intersection_p = Geom::Point(sy.getPoint().x(), sx.getPoint().y());
+ Geom::Coord d = Geom::L2(intersection_p - p.getPoint());
+
+ if (d < sqrt(2)*getSnapperTolerance()) {
+ si = SnappedPoint(intersection_p,
+ *sx.getAlignmentTarget(),
+ *sy.getAlignmentTarget(),
+ source2alignment(p.getSourceType()),
+ p.getSourceNum(),
+ SNAPTARGET_ALIGNMENT_INTERSECTION,
+ d,
+ getSnapperTolerance(),
+ getSnapperAlwaysSnap(),
+ false,
+ true,
+ k.getTargetBBox());
+ intersection = true;
+ }
+ }
+ }
+ }
+
+ if (intersection) {
+ isr.points.push_back(si);
+ return;
+ }
+
+ if (success_x || success_y) {
+ if (sx.getSnapDistance() < sy.getSnapDistance()) {
+ isr.points.push_back(sx);
+ } else {
+ isr.points.push_back(sy);
+ }
+ }
+
+}
+
+bool Inkscape::AlignmentSnapper::_allowSourceToSnapToTarget(SnapSourceType source, SnapTargetType target, bool strict_snapping) const
+{
+ if (strict_snapping && (source == SNAPSOURCE_PAGE_CENTER || source == SNAPSOURCE_PAGE_CORNER)) {
+ // Restrict page alignment snapping to just other pages (no objects please!)
+ return target == SNAPTARGET_PAGE_CENTER || target == SNAPTARGET_PAGE_CORNER
+ || target == SNAPTARGET_ALIGNMENT_PAGE_CENTER || target == SNAPTARGET_ALIGNMENT_PAGE_CORNER;
+ }
+ return true;
+}
+
+
+void Inkscape::AlignmentSnapper::freeSnap(IntermSnapResults &isr,
+ Inkscape::SnapCandidatePoint const &p,
+ Geom::OptRect const &bbox_to_snap,
+ std::vector<SPObject const *> const *it,
+ std::vector<SnapCandidatePoint> *unselected_nodes) const
+{
+ // toggle checks
+ if (!_snap_enabled || !_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_ALIGNMENT_CATEGORY))
+ return;
+
+ bool p_is_bbox = p.getSourceType() & SNAPSOURCE_BBOX_CATEGORY;
+ bool p_is_node = p.getSourceType() & SNAPSOURCE_NODE_HANDLE;
+
+ if (p.getSourceNum() <= 0) {
+ Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p.getPoint(), p.getPoint());
+ _snapmanager->_findCandidates(_snapmanager->getDocument()->getRoot(), it, local_bbox_to_snap, false, Geom::identity());
+ }
+
+ unsigned n = (unselected_nodes == nullptr) ? 0 : unselected_nodes->size();
+
+ // n > 0 : node tool is active
+ if (!(p_is_bbox || (n > 0 && p_is_node) || (p.considerForAlignment() && p_is_node)))
+ return;
+
+ _snapBBoxPoints(isr, p, unselected_nodes);
+}
+
+void Inkscape::AlignmentSnapper::constrainedSnap(IntermSnapResults &isr,
+ Inkscape::SnapCandidatePoint const &p,
+ Geom::OptRect const &bbox_to_snap,
+ SnapConstraint const &c,
+ std::vector<SPObject const *> const *it,
+ std::vector<SnapCandidatePoint> *unselected_nodes) const
+{
+ // project the mouse pointer onto the constraint. Only the projected point will be considered for snapping
+ Geom::Point pp = c.projection(p.getPoint());
+
+ // toggle checks
+ if (!_snap_enabled || !_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_ALIGNMENT_CATEGORY))
+ return;
+
+ bool p_is_bbox = p.getSourceType() & SNAPSOURCE_BBOX_CATEGORY;
+ bool p_is_node = p.getSourceType() & SNAPSOURCE_NODE_HANDLE;
+
+ if (p.getSourceNum() <= 0) {
+ Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p.getPoint(), p.getPoint());
+ _snapmanager->_findCandidates(_snapmanager->getDocument()->getRoot(), it, local_bbox_to_snap, false, Geom::identity());
+ }
+
+ unsigned n = (unselected_nodes == nullptr) ? 0 : unselected_nodes->size();
+
+ // n > 0 : node tool is active
+ if (!(p_is_bbox || (n > 0 && p_is_node) || (p.considerForAlignment() && p_is_node)))
+ return;
+
+ _snapBBoxPoints(isr, p, unselected_nodes, c, pp);
+}
+
+bool Inkscape::AlignmentSnapper::ThisSnapperMightSnap() const
+{
+ return true;
+}
+
+bool Inkscape::AlignmentSnapper::getSnapperAlwaysSnap() const
+{
+ return _snapmanager->snapprefs.getAlignmentTolerance() == 10000; //TODO: Replace this threshold of 10000 by a constant; see also tolerance-slider.cpp
+}
+
+Geom::Coord Inkscape::AlignmentSnapper::getSnapperTolerance() const
+{
+ SPDesktop const *dt = _snapmanager->getDesktop();
+ double const zoom = dt ? dt->current_zoom() : 1;
+ return _snapmanager->snapprefs.getAlignmentTolerance() / zoom;
+}
+
+Inkscape::SnapSourceType Inkscape::AlignmentSnapper::source2alignment(SnapSourceType s) const
+{
+ switch (s) {
+ case SNAPSOURCE_BBOX_CATEGORY:
+ return SNAPSOURCE_ALIGNMENT_CATEGORY;
+ case SNAPSOURCE_BBOX_CORNER:
+ return SNAPSOURCE_ALIGNMENT_BBOX_CORNER;
+ case SNAPSOURCE_BBOX_MIDPOINT:
+ return SNAPSOURCE_ALIGNMENT_BBOX_MIDPOINT;
+ case SNAPSOURCE_BBOX_EDGE_MIDPOINT:
+ return SNAPSOURCE_ALIGNMENT_BBOX_EDGE_MIDPOINT;
+ case SNAPSOURCE_NODE_CATEGORY:
+ case SNAPSOURCE_OTHER_HANDLE:
+ return SNAPSOURCE_ALIGNMENT_HANDLE;
+ default:
+ return SNAPSOURCE_UNDEFINED;
+ }
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+
+
+