diff options
Diffstat (limited to 'src/alignment-snapper.cpp')
-rw-r--r-- | src/alignment-snapper.cpp | 347 |
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 : + + + |