// SPDX-License-Identifier: GPL-2.0-or-later /** @file * Affine transform handles component */ /* Authors: * Krzysztof Kosiński * Jon A. Cruz * * Copyright (C) 2009 Authors * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include #include #include <2geom/transforms.h> #include "control-point.h" #include "desktop.h" #include "pure-transform.h" #include "seltrans.h" #include "snap.h" #include "display/control/canvas-item-rect.h" #include "object/sp-namedview.h" #include "ui/tool/commit-events.h" #include "ui/tool/control-point-selection.h" #include "ui/tool/event-utils.h" #include "ui/tool/node.h" #include "ui/tool/selectable-control-point.h" #include "ui/tool/transform-handle-set.h" #include "ui/tools/node-tool.h" GType sp_select_context_get_type(); namespace Inkscape { namespace UI { namespace { SPAnchorType corner_to_anchor(unsigned c) { switch (c % 4) { case 0: return SP_ANCHOR_NE; case 1: return SP_ANCHOR_NW; case 2: return SP_ANCHOR_SW; default: return SP_ANCHOR_SE; } } SPAnchorType side_to_anchor(unsigned s) { switch (s % 4) { case 0: return SP_ANCHOR_N; case 1: return SP_ANCHOR_W; case 2: return SP_ANCHOR_S; default: return SP_ANCHOR_E; } } // TODO move those two functions into a common place double snap_angle(double a) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000); double unit_angle = M_PI / snaps; return CLAMP(unit_angle * round(a / unit_angle), -M_PI, M_PI); } double snap_increment_degrees() { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000); return 180.0 / snaps; } } // anonymous namespace ControlPoint::ColorSet TransformHandle::thandle_cset = { {0x000000ff, 0x000000ff}, // normal fill, stroke {0x00ff66ff, 0x000000ff}, // mouseover fill, stroke {0x00ff66ff, 0x000000ff}, // clicked fill, stroke // {0x000000ff, 0x000000ff}, // normal fill, stroke when selected {0x00ff66ff, 0x000000ff}, // mouseover fill, stroke when selected {0x00ff66ff, 0x000000ff} // clicked fill, stroke when selected }; TransformHandle::TransformHandle(TransformHandleSet &th, SPAnchorType anchor, Inkscape::CanvasItemCtrlType type) : ControlPoint(th._desktop, Geom::Point(), anchor, type, thandle_cset, th._transform_handle_group) , _th(th) { _canvas_item_ctrl->set_name("CanvasItemCtrl:TransformHandle"); setVisible(false); } // TODO: This code is duplicated in seltrans.cpp; fix this! void TransformHandle::getNextClosestPoint(bool reverse) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/options/snapclosestonly/value", false)) { if (!_all_snap_sources_sorted.empty()) { if (reverse) { // Shift-tab will find a closer point if (_all_snap_sources_iter == _all_snap_sources_sorted.begin()) { _all_snap_sources_iter = _all_snap_sources_sorted.end(); } --_all_snap_sources_iter; } else { // Tab will find a point further away ++_all_snap_sources_iter; if (_all_snap_sources_iter == _all_snap_sources_sorted.end()) { _all_snap_sources_iter = _all_snap_sources_sorted.begin(); } } _snap_points.clear(); _snap_points.push_back(*_all_snap_sources_iter); // Show the updated snap source now; otherwise it won't be shown until the selection is being moved again SnapManager &m = _desktop->namedview->snap_manager; m.setup(_desktop); m.displaySnapsource(*_all_snap_sources_iter); m.unSetup(); } } } bool TransformHandle::grabbed(GdkEventMotion *) { _origin = position(); _last_transform.setIdentity(); startTransform(); _th._setActiveHandle(this); _setLurking(true); _setState(_state); // Collect the snap-candidates, one for each selected node. These will be stored in the _snap_points vector. Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(_th._desktop->event_context); //ControlPointSelection *selection = nt->_selected_nodes.get(); ControlPointSelection* selection = nt->_selected_nodes; selection->setOriginalPoints(); selection->getOriginalPoints(_snap_points); selection->getUnselectedPoints(_unselected_points); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/options/snapclosestonly/value", false)) { // Find the closest snap source candidate _all_snap_sources_sorted = _snap_points; // Calculate and store the distance to the reference point for each snap candidate point for(auto & i : _all_snap_sources_sorted) { i.setDistance(Geom::L2(i.getPoint() - _origin)); } // Sort them ascending, using the distance calculated above as the single criteria std::sort(_all_snap_sources_sorted.begin(), _all_snap_sources_sorted.end()); // Now get the closest snap source _snap_points.clear(); if (!_all_snap_sources_sorted.empty()) { _all_snap_sources_iter = _all_snap_sources_sorted.begin(); _snap_points.push_back(_all_snap_sources_sorted.front()); } } return false; } void TransformHandle::dragged(Geom::Point &new_pos, GdkEventMotion *event) { Geom::Affine t = computeTransform(new_pos, event); // protect against degeneracies if (t.isSingular()) return; Geom::Affine incr = _last_transform.inverse() * t; if (incr.isSingular()) return; _th.signal_transform.emit(incr); _last_transform = t; } void TransformHandle::ungrabbed(GdkEventButton *) { _snap_points.clear(); _th._clearActiveHandle(); _setLurking(false); _setState(_state); endTransform(); _th.signal_commit.emit(getCommitEvent()); //updates the positions of the nodes Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(_th._desktop->event_context); ControlPointSelection* selection = nt->_selected_nodes; selection->setOriginalPoints(); } class ScaleHandle : public TransformHandle { public: ScaleHandle(TransformHandleSet &th, SPAnchorType anchor, Inkscape::CanvasItemCtrlType type) : TransformHandle(th, anchor, type) {} protected: Glib::ustring _getTip(unsigned state) const override { if (state_held_control(state)) { if (state_held_shift(state)) { return C_("Transform handle tip", "Shift+Ctrl: scale uniformly about the rotation center"); } return C_("Transform handle tip", "Ctrl: scale uniformly"); } if (state_held_shift(state)) { if (state_held_alt(state)) { return C_("Transform handle tip", "Shift+Alt: scale using an integer ratio about the rotation center"); } return C_("Transform handle tip", "Shift: scale from the rotation center"); } if (state_held_alt(state)) { return C_("Transform handle tip", "Alt: scale using an integer ratio"); } return C_("Transform handle tip", "Scale handle: drag to scale the selection"); } Glib::ustring _getDragTip(GdkEventMotion */*event*/) const override { return format_tip(C_("Transform handle tip", "Scale by %.2f%% x %.2f%%"), _last_scale_x * 100, _last_scale_y * 100); } bool _hasDragTips() const override { return true; } static double _last_scale_x, _last_scale_y; }; double ScaleHandle::_last_scale_x = 1.0; double ScaleHandle::_last_scale_y = 1.0; /** * Corner scaling handle for node transforms. */ class ScaleCornerHandle : public ScaleHandle { public: ScaleCornerHandle(TransformHandleSet &th, unsigned corner, unsigned d_corner) : ScaleHandle(th, corner_to_anchor(d_corner), Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE), _corner(corner) {} protected: void startTransform() override { _sc_center = _th.rotationCenter(); _sc_opposite = _th.bounds().corner(_corner + 2); _last_scale_x = _last_scale_y = 1.0; } Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) override { Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite; Geom::Point vold = _origin - scc, vnew = new_pos - scc; // avoid exploding the selection if (Geom::are_near(vold[Geom::X], 0) || Geom::are_near(vold[Geom::Y], 0)) return Geom::identity(); Geom::Scale scale = Geom::Scale(vnew[Geom::X] / vold[Geom::X], vnew[Geom::Y] / vold[Geom::Y]); if (held_alt(*event)) { for (unsigned i = 0; i < 2; ++i) { if (fabs(scale[i]) >= 1.0) { scale[i] = round(scale[i]); } else { scale[i] = 1.0 / round(1.0 / MIN(scale[i],10)); } } } else { SnapManager &m = _th._desktop->namedview->snap_manager; m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); Inkscape::PureScale *ptr; if (held_control(*event)) { scale[0] = scale[1] = std::min(scale[0], scale[1]); ptr = new Inkscape::PureScaleConstrained(Geom::Scale(scale[0], scale[1]), scc); } else { ptr = new Inkscape::PureScale(Geom::Scale(scale[0], scale[1]), scc, false); } m.snapTransformed(_snap_points, _origin, (*ptr)); m.unSetup(); if (ptr->best_snapped_point.getSnapped()) { scale = ptr->getScaleSnapped(); } delete ptr; } _last_scale_x = scale[0]; _last_scale_y = scale[1]; Geom::Affine t = Geom::Translate(-scc) * Geom::Scale(scale[0], scale[1]) * Geom::Translate(scc); return t; } CommitEvent getCommitEvent() override { return _last_transform.isUniformScale() ? COMMIT_MOUSE_SCALE_UNIFORM : COMMIT_MOUSE_SCALE; } private: Geom::Point _sc_center; Geom::Point _sc_opposite; unsigned _corner; }; /** * Side scaling handle for node transforms. */ class ScaleSideHandle : public ScaleHandle { public: ScaleSideHandle(TransformHandleSet &th, unsigned side, unsigned d_side) : ScaleHandle(th, side_to_anchor(d_side), Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE) , _side(side) {} protected: void startTransform() override { _sc_center = _th.rotationCenter(); Geom::Rect b = _th.bounds(); _sc_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3)); _last_scale_x = _last_scale_y = 1.0; } Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) override { Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite; Geom::Point vs; Geom::Dim2 d1 = static_cast((_side + 1) % 2); Geom::Dim2 d2 = static_cast(_side % 2); // avoid exploding the selection if (Geom::are_near(scc[d1], _origin[d1])) return Geom::identity(); vs[d1] = (new_pos - scc)[d1] / (_origin - scc)[d1]; if (held_alt(*event)) { if (fabs(vs[d1]) >= 1.0) { vs[d1] = round(vs[d1]); } else { vs[d1] = 1.0 / round(1.0 / MIN(vs[d1],10)); } vs[d2] = 1.0; } else { SnapManager &m = _th._desktop->namedview->snap_manager; m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); bool uniform = held_control(*event); Inkscape::PureStretchConstrained psc = Inkscape::PureStretchConstrained(vs[d1], scc, d1, uniform); m.snapTransformed(_snap_points, _origin, psc); m.unSetup(); if (psc.best_snapped_point.getSnapped()) { Geom::Point result = psc.getStretchSnapped().vector(); //best_snapped_point.getTransformation(); vs[d1] = result[d1]; vs[d2] = result[d2]; } else { // on ctrl, apply uniform scaling instead of stretching // Preserve aspect ratio, but never flip in the dimension not being edited (by using fabs()) vs[d2] = uniform ? fabs(vs[d1]) : 1.0; } } _last_scale_x = vs[Geom::X]; _last_scale_y = vs[Geom::Y]; Geom::Affine t = Geom::Translate(-scc) * Geom::Scale(vs) * Geom::Translate(scc); return t; } CommitEvent getCommitEvent() override { return _last_transform.isUniformScale() ? COMMIT_MOUSE_SCALE_UNIFORM : COMMIT_MOUSE_SCALE; } private: Geom::Point _sc_center; Geom::Point _sc_opposite; unsigned _side; }; /** * Rotation handle for node transforms. */ class RotateHandle : public TransformHandle { public: RotateHandle(TransformHandleSet &th, unsigned corner, unsigned d_corner) : TransformHandle(th, corner_to_anchor(d_corner), Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE) , _corner(corner) {} protected: void startTransform() override { _rot_center = _th.rotationCenter(); _rot_opposite = _th.bounds().corner(_corner + 2); _last_angle = 0; } Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) override { Geom::Point rotc = held_shift(*event) ? _rot_opposite : _rot_center; double angle = Geom::angle_between(_origin - rotc, new_pos - rotc); if (held_control(*event)) { angle = snap_angle(angle); } else { SnapManager &m = _th._desktop->namedview->snap_manager; m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); Inkscape::PureRotateConstrained prc = Inkscape::PureRotateConstrained(angle, rotc); m.snapTransformed(_snap_points, _origin, prc); m.unSetup(); if (prc.best_snapped_point.getSnapped()) { angle = prc.getAngleSnapped(); //best_snapped_point.getTransformation()[0]; } } _last_angle = angle; Geom::Affine t = Geom::Translate(-rotc) * Geom::Rotate(angle) * Geom::Translate(rotc); return t; } CommitEvent getCommitEvent() override { return COMMIT_MOUSE_ROTATE; } Glib::ustring _getTip(unsigned state) const override { if (state_held_shift(state)) { if (state_held_control(state)) { return format_tip(C_("Transform handle tip", "Shift+Ctrl: rotate around the opposite corner and snap " "angle to %f° increments"), snap_increment_degrees()); } return C_("Transform handle tip", "Shift: rotate around the opposite corner"); } if (state_held_control(state)) { return format_tip(C_("Transform handle tip", "Ctrl: snap angle to %f° increments"), snap_increment_degrees()); } return C_("Transform handle tip", "Rotation handle: drag to rotate " "the selection around the rotation center"); } Glib::ustring _getDragTip(GdkEventMotion */*event*/) const override { return format_tip(C_("Transform handle tip", "Rotate by %.2f°"), _last_angle * 180.0 / M_PI); } bool _hasDragTips() const override { return true; } private: Geom::Point _rot_center; Geom::Point _rot_opposite; unsigned _corner; static double _last_angle; }; double RotateHandle::_last_angle = 0; class SkewHandle : public TransformHandle { public: SkewHandle(TransformHandleSet &th, unsigned side, unsigned d_side) : TransformHandle(th, side_to_anchor(d_side), Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW) , _side(side) {} protected: void startTransform() override { _skew_center = _th.rotationCenter(); Geom::Rect b = _th.bounds(); _skew_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3)); _last_angle = 0; _last_horizontal = _side % 2; } Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) override { Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite; Geom::Dim2 d1 = static_cast((_side + 1) % 2); Geom::Dim2 d2 = static_cast(_side % 2); Geom::Point const initial_delta = _origin - scc; if (fabs(initial_delta[d1]) < 1e-15) { return Geom::Affine(); } // Calculate the scale factors, which can be either visual or geometric // depending on which type of bbox is currently being used (see preferences -> selector tool) Geom::Scale scale = calcScaleFactors(_origin, new_pos, scc, false); Geom::Scale skew = calcScaleFactors(_origin, new_pos, scc, true); scale[d2] = 1; skew[d2] = 1; // Skew handles allow scaling up to integer multiples of the original size // in the second direction; prevent explosions if (fabs(scale[d1]) < 1) { // Prevent shrinking of the selected object, while allowing mirroring scale[d1] = copysign(1.0, scale[d1]); } else { // Allow expanding of the selected object by integer multiples scale[d1] = floor(scale[d1] + 0.5); } double angle = atan(skew[d1] / scale[d1]); if (held_control(*event)) { angle = snap_angle(angle); skew[d1] = tan(angle) * scale[d1]; } else { SnapManager &m = _th._desktop->namedview->snap_manager; m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); Inkscape::PureSkewConstrained psc = Inkscape::PureSkewConstrained(skew[d1], scale[d1], scc, d2); m.snapTransformed(_snap_points, _origin, psc); m.unSetup(); if (psc.best_snapped_point.getSnapped()) { skew[d1] = psc.getSkewSnapped(); //best_snapped_point.getTransformation()[0]; } } _last_angle = angle; // Update the handle position Geom::Point new_new_pos; new_new_pos[d2] = initial_delta[d1] * skew[d1] + _origin[d2]; new_new_pos[d1] = initial_delta[d1] * scale[d1] + scc[d1]; // Calculate the relative affine Geom::Affine relative_affine = Geom::identity(); relative_affine[2*d1 + d1] = (new_new_pos[d1] - scc[d1]) / initial_delta[d1]; relative_affine[2*d1 + (d2)] = (new_new_pos[d2] - _origin[d2]) / initial_delta[d1]; relative_affine[2*(d2) + (d1)] = 0; relative_affine[2*(d2) + (d2)] = 1; for (int i = 0; i < 2; i++) { if (fabs(relative_affine[3*i]) < 1e-15) { relative_affine[3*i] = 1e-15; } } Geom::Affine t = Geom::Translate(-scc) * relative_affine * Geom::Translate(scc); return t; } CommitEvent getCommitEvent() override { return (_side % 2) ? COMMIT_MOUSE_SKEW_Y : COMMIT_MOUSE_SKEW_X; } Glib::ustring _getTip(unsigned state) const override { if (state_held_shift(state)) { if (state_held_control(state)) { return format_tip(C_("Transform handle tip", "Shift+Ctrl: skew about the rotation center with snapping " "to %f° increments"), snap_increment_degrees()); } return C_("Transform handle tip", "Shift: skew about the rotation center"); } if (state_held_control(state)) { return format_tip(C_("Transform handle tip", "Ctrl: snap skew angle to %f° increments"), snap_increment_degrees()); } return C_("Transform handle tip", "Skew handle: drag to skew (shear) selection about " "the opposite handle"); } Glib::ustring _getDragTip(GdkEventMotion */*event*/) const override { if (_last_horizontal) { return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"), _last_angle * 360.0); } else { return format_tip(C_("Transform handle tip", "Skew vertically by %.2f°"), _last_angle * 360.0); } } bool _hasDragTips() const override { return true; } private: Geom::Point _skew_center; Geom::Point _skew_opposite; unsigned _side; static bool _last_horizontal; static double _last_angle; }; bool SkewHandle::_last_horizontal = false; double SkewHandle::_last_angle = 0; class RotationCenter : public ControlPoint { public: RotationCenter(TransformHandleSet &th) : ControlPoint(th._desktop, Geom::Point(), SP_ANCHOR_CENTER, Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER, _center_cset, th._transform_handle_group), _th(th) { setVisible(false); } protected: void dragged(Geom::Point &new_pos, GdkEventMotion *event) override { SnapManager &sm = _th._desktop->namedview->snap_manager; sm.setup(_th._desktop); bool snap = !held_shift(*event) && sm.someSnapperMightSnap(); if (held_control(*event)) { // constrain to axes Geom::Point origin = _last_drag_origin(); std::vector constraints; constraints.emplace_back(origin, Geom::Point(1, 0)); constraints.emplace_back(origin, Geom::Point(0, 1)); new_pos = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos, SNAPSOURCE_ROTATION_CENTER), constraints, held_shift(*event)).getPoint(); } else if (snap) { sm.freeSnapReturnByRef(new_pos, SNAPSOURCE_ROTATION_CENTER); } sm.unSetup(); } Glib::ustring _getTip(unsigned /*state*/) const override { return C_("Transform handle tip", "Rotation center: drag to change the origin of transforms"); } private: static ColorSet _center_cset; TransformHandleSet &_th; }; ControlPoint::ColorSet RotationCenter::_center_cset = { {0x000000ff, 0x000000ff}, // normal fill, stroke {0x00ff66ff, 0x000000ff}, // mouseover fill, stroke {0x00ff66ff, 0x000000ff}, // clicked fill, stroke // {0x000000ff, 0x000000ff}, // normal fill, stroke when selected {0x00ff66ff, 0x000000ff}, // mouseover fill, stroke when selected {0x00ff66ff, 0x000000ff} // clicked fill, stroke when selected }; TransformHandleSet::TransformHandleSet(SPDesktop *d, Inkscape::CanvasItemGroup *th_group) : Manipulator(d) , _active(nullptr) , _transform_handle_group(th_group) , _mode(MODE_SCALE) , _in_transform(false) , _visible(true) { _trans_outline = new Inkscape::CanvasItemRect(_desktop->getCanvasControls()); _trans_outline->set_name("CanvasItemRect:Transform"); _trans_outline->hide(); _trans_outline->set_dashed(true); bool y_inverted = !d->is_yaxisdown(); for (unsigned i = 0; i < 4; ++i) { unsigned d_c = y_inverted ? i : 3 - i; unsigned d_s = y_inverted ? i : 6 - i; _scale_corners[i] = new ScaleCornerHandle(*this, i, d_c); _scale_sides[i] = new ScaleSideHandle(*this, i, d_s); _rot_corners[i] = new RotateHandle(*this, i, d_c); _skew_sides[i] = new SkewHandle(*this, i, d_s); } _center = new RotationCenter(*this); // when transforming, update rotation center position signal_transform.connect(sigc::mem_fun(*_center, &RotationCenter::transform)); } TransformHandleSet::~TransformHandleSet() { for (auto & _handle : _handles) { delete _handle; } } void TransformHandleSet::setMode(Mode m) { _mode = m; _updateVisibility(_visible); } Geom::Rect TransformHandleSet::bounds() const { return Geom::Rect(*_scale_corners[0], *_scale_corners[2]); } ControlPoint const &TransformHandleSet::rotationCenter() const { return *_center; } ControlPoint &TransformHandleSet::rotationCenter() { return *_center; } void TransformHandleSet::setVisible(bool v) { if (_visible != v) { _visible = v; _updateVisibility(_visible); } } void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center) { if (_in_transform) { _trans_outline->set_rect(r); } else { for (unsigned i = 0; i < 4; ++i) { _scale_corners[i]->move(r.corner(i)); _scale_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1))); _rot_corners[i]->move(r.corner(i)); _skew_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1))); } if (!preserve_center) _center->move(r.midpoint()); if (_visible) _updateVisibility(true); } } bool TransformHandleSet::event(Inkscape::UI::Tools::ToolBase *, GdkEvent*) { return false; } void TransformHandleSet::_emitTransform(Geom::Affine const &t) { signal_transform.emit(t); _center->transform(t); } void TransformHandleSet::_setActiveHandle(ControlPoint *th) { _active = th; if (_in_transform) throw std::logic_error("Transform initiated when another transform in progress"); _in_transform = true; // hide all handles except the active one _updateVisibility(false); _trans_outline->show(); } void TransformHandleSet::_clearActiveHandle() { // This can only be called from handles, so they had to be visible before _setActiveHandle _trans_outline->hide(); _active = nullptr; _in_transform = false; _updateVisibility(_visible); } void TransformHandleSet::_updateVisibility(bool v) { if (v) { Geom::Rect b = bounds(); // Roughly estimate handle size. Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int handle_index = prefs->getIntLimited("/options/grabsize/value", 3, 1, 15); int handle_size = handle_index * 2 + 1; // Handle pixmaps are actually larger but that's to allow space when handle is rotated. Geom::Point bp = b.dimensions(); // do not scale when the bounding rectangle has zero width or height bool show_scale = (_mode == MODE_SCALE) && !Geom::are_near(b.minExtent(), 0); // do not rotate if the bounding rectangle is degenerate bool show_rotate = (_mode == MODE_ROTATE_SKEW) && !Geom::are_near(b.maxExtent(), 0); bool show_scale_side[2], show_skew[2]; // show sides if: // a) there is enough space between corner handles, or // b) corner handles are not shown, but side handles make sense // this affects horizontal and vertical scale handles; skew handles never // make sense if rotate handles are not shown for (unsigned i = 0; i < 2; ++i) { Geom::Dim2 d = static_cast(i); Geom::Dim2 otherd = static_cast((i+1)%2); show_scale_side[i] = (_mode == MODE_SCALE); show_scale_side[i] &= (show_scale ? bp[d] >= handle_size : !Geom::are_near(bp[otherd], 0)); show_skew[i] = (show_rotate && bp[d] >= handle_size && !Geom::are_near(bp[otherd], 0)); } for (unsigned i = 0; i < 4; ++i) { _scale_corners[i]->setVisible(show_scale); _rot_corners[i]->setVisible(show_rotate); _scale_sides[i]->setVisible(show_scale_side[i%2]); _skew_sides[i]->setVisible(show_skew[i%2]); } // show rotation center _center->setVisible(show_rotate); } else { for (auto & _handle : _handles) { if (_handle != _active) _handle->setVisible(false); } } } } // namespace UI } // namespace Inkscape /* 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 :