Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
canvas-item-guideline.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/*
7 * Authors:
8 * Tavmjong Bah - Rewrite of SPGuideLine
9 * Rafael Siejakowski - Tweaks to handle appearance
10 *
11 * Copyright (C) 2020-2022 the Authors.
12 *
13 *
14 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15 */
16
17#include <2geom/line.h>
18
20#include "canvas-item-ctrl.h"
21
22#include "desktop.h" // Canvas orientation so label is orientated correctly.
24#include "ui/widget/canvas.h"
25
26namespace Inkscape {
27
32 Geom::Point const &origin, Geom::Point const &normal)
33 : CanvasItem(group)
34 , _origin(origin)
35 , _normal(normal)
36 , _label(std::move(label))
37{
38 _name = "CanvasItemGuideLine:" + _label;
39 _pickable = true; // For now, everybody gets events from this class!
40
41 // Control to move guide line.
42 _origin_ctrl = make_canvasitem<CanvasItemGuideHandle>(group, _origin, this);
43 _origin_ctrl->set_name("CanvasItemGuideLine:Ctrl:" + _label);
44 _origin_ctrl->set_size_default();
45 _origin_ctrl->set_pickable(true); // The handle will also react to dragging
46 set_locked(false); // Init _origin_ctrl shape and stroke.
47}
48
53{
54 if (_origin != origin) {
56 _origin_ctrl->set_position(_origin);
58 }
59}
60
65{
66 if (_normal != normal) {
67 _normal = normal;
69 }
70}
71
76{
77 if (_inverted != inverted) {
78 _inverted = inverted;
80 }
81}
82
87{
88 // Maybe store guide as a Geom::Line?
90 guide *= affine();
91 return Geom::distance(p, guide);
92}
93
97bool CanvasItemGuideLine::contains(Geom::Point const &p, double tolerance)
98{
99 if (tolerance == 0) {
100 tolerance = 1; // Can't pick of zero!
101 }
102
103 return closest_distance_to(p) < tolerance;
104}
105
113
118{
119 // Required when rotating canvas
121
122 // Queue redraw of new area (and old too).
124}
125
130{
131 // Document to canvas
132 Geom::Point const normal = _normal * affine().withoutTranslation(); // Direction only
133 Geom::Point const origin = _origin * affine();
134
135 /* Need to use floor()+0.5 such that Cairo will draw us lines with a width of a single pixel,
136 * without any aliasing. For this we need to position the lines at exactly half pixels, see
137 * https://www.cairographics.org/FAQ/#sharp_lines
138 * Must be consistent with the pixel alignment of the grid lines, see CanvasXYGrid::Render(),
139 * and the drawing of the rulers.
140 * Lastly, the origin control is also pixel-aligned and we want to visually cut through its
141 * exact center.
142 */
143 // this rendering operates in logical pixels, so only align lines to pixel grid if device scale is odd
144 const auto linefit = buf.device_scale & 1 ? Geom::Point(0.5, 0.5) : Geom::Point(0, 0);
145 Geom::Point const aligned_origin = origin.floor() + linefit;
146
147 // Set up the Cairo rendering context
148 auto ctx = buf.cr;
149 ctx->save();
150 ctx->translate(-buf.rect.left(), -buf.rect.top()); // Canvas to screen
151 ctx->set_source_rgba(SP_RGBA32_R_F(_stroke), SP_RGBA32_G_F(_stroke),
153 ctx->set_line_width(1);
154
155 if (_inverted) {
156 // operator not available in cairo C++ bindings
157 cairo_set_operator(ctx->cobj(), CAIRO_OPERATOR_DIFFERENCE);
158 }
159
160 if (!_label.empty()) { // Render text label
161 ctx->save();
162 ctx->translate(aligned_origin.x(), aligned_origin.y());
163
164 auto desktop = get_canvas()->get_desktop();
165 ctx->rotate(atan2(normal.cw()) + M_PI * (desktop && desktop->is_yaxisdown() ? 1 : 0));
166 ctx->translate(0, -(_origin_ctrl->radius() + LABEL_SEP)); // Offset by dot radius + 2
167 ctx->move_to(0, 0);
168 ctx->show_text(_label);
169 ctx->restore();
170 }
171
172 // Draw guide.
173 // Special case: horizontal and vertical lines (easier calculations)
174
175 // Don't use isHorizontal()/isVertical() as they test only exact matches.
176 if (Geom::are_near(normal.y(), 0.0)) {
177 // Vertical
178 double const position = aligned_origin.x();
179 ctx->move_to(position, buf.rect.top() + 0.5);
180 ctx->line_to(position, buf.rect.bottom() - 0.5);
181 } else if (Geom::are_near(normal.x(), 0.0)) {
182 // Horizontal
183 double position = aligned_origin.y();
184 ctx->move_to(buf.rect.left() + 0.5, position);
185 ctx->line_to(buf.rect.right() - 0.5, position);
186 } else {
187 // Angled
188 Geom::Line line = Geom::Line::from_origin_and_vector(aligned_origin, Geom::rot90(normal));
189
190 // Find intersections of the line with buf rectangle. There should be zero or two.
191 std::vector<Geom::Point> intersections;
192 for (unsigned i = 0; i < 4; ++i) {
193 Geom::LineSegment side(buf.rect.corner(i), buf.rect.corner((i+1)%4));
194 try {
195 Geom::OptCrossing oc = Geom::intersection(line, side);
196 if (oc) {
197 intersections.push_back(line.pointAt(oc->ta));
198 }
199 } catch (Geom::InfiniteSolutions const &) {
200 // Shouldn't happen as we have already taken care of horizontal/vertical guides.
201 std::cerr << "CanvasItemGuideLine::render: Error: Infinite intersections." << std::endl;
202 }
203 }
204
205 if (intersections.size() == 2) {
206 double const x0 = intersections[0].x();
207 double const x1 = intersections[1].x();
208 double const y0 = intersections[0].y();
209 double const y1 = intersections[1].y();
210 ctx->move_to(x0, y0);
211 ctx->line_to(x1, y1);
212 }
213 }
214 ctx->stroke();
215
216 ctx->restore();
217}
218
224
226{
227 // Make sure the fill of the control is the same as the stroke
228 // of the guide-line:
229 _origin_ctrl->set_fill(color);
231}
232
234{
235 defer([=, this, label = std::move(label)] () mutable {
236 if (_label == label) return;
237 _label = std::move(label);
239 });
240}
241
243{
244 defer([=, this] {
245 if (_locked == locked) return;
246 _locked = locked;
247 if (_locked) {
250 _origin_ctrl->set_fill(0x00000000); // no fill
251 } else {
253 _origin_ctrl->set_stroke(0x00000000); // no stroke
254 _origin_ctrl->set_fill(_stroke);
255 }
256 });
257}
258
259//===============================================================================================
260
268 Geom::Point const &pos,
271 , _my_line(line) // Save a pointer to our guide line
272{
274}
275
280{
281 auto width = std::round(get_width());
282 return 0.5 * static_cast<double>(width); // radius is half the width
283}
284
285} // namespace Inkscape
286
287/*
288 Local Variables:
289 mode:c++
290 c-file-style:"stroustrup"
291 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
292 indent-tabs-mode:nil
293 fill-column:99
294 End:
295*/
296// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Point origin
Definition aa.cpp:227
Enums for CanvasItems.
Geom::IntRect visible
Definition canvas.cpp:154
Inkscape canvas widget.
Affine withoutTranslation() const
Definition affine.h:169
Infinite line on a plane.
Definition line.h:53
static Line from_origin_and_vector(Point const &o, Point const &v)
Create a line from origin and unit vector.
Definition line.h:114
Point pointAt(Coord t) const
Definition line.h:231
Two-dimensional point that doubles as a vector.
Definition point.h:66
IntPoint floor() const
Round coordinates downwards.
Definition point.h:206
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
Axis aligned, non-empty rectangle.
Definition rect.h:92
void set_shape(CanvasItemCtrlShape shape)
double radius() const
Return the radius of the handle dot.
CanvasItemGuideHandle(CanvasItemGroup *group, Geom::Point const &pos, CanvasItemGuideLine *line)
Create a handle ("dot") along a guide line.
CanvasItemPtr< CanvasItemGuideHandle > _origin_ctrl
bool contains(Geom::Point const &p, double tolerance=0) override
Returns true if point p (in canvas units) is within tolerance (canvas units) distance of guideLine (o...
CanvasItemGuideHandle * dot() const
Returns the pointer to the origin control (the "dot")
void set_origin(Geom::Point const &origin)
Sets origin of guide line (place where handle is located).
CanvasItemGuideLine(CanvasItemGroup *group, Glib::ustring label, Geom::Point const &origin, Geom::Point const &normal)
Create a control guide line.
static constexpr double LABEL_SEP
void set_visible(bool visible) override
void set_stroke(uint32_t color) override
static constexpr uint32_t CONTROL_LOCKED_COLOR
void set_inverted(bool inverted)
Sets the inverted nature of the line.
void set_label(Glib::ustring &&label)
void set_normal(Geom::Point const &normal)
Sets orientation of guide line.
double closest_distance_to(Geom::Point const &p)
Returns distance between point in canvas units and nearest point on guideLine.
void _update(bool propagate) override
Update and redraw control guideLine.
void _render(Inkscape::CanvasItemBuffer &buf) const override
Render guideLine to screen via Cairo.
virtual void set_visible(bool visible)
Geom::OptRect _bounds
Geom::Affine const & affine() const
virtual void set_stroke(uint32_t rgba)
UI::Widget::Canvas * get_canvas() const
Definition canvas-item.h:61
SPDesktop * get_desktop() const
Definition canvas.h:73
bool is_yaxisdown() const
Definition desktop.h:427
constexpr double SP_RGBA32_G_F(uint32_t v)
Definition utils.h:47
constexpr double SP_RGBA32_R_F(uint32_t v)
Definition utils.h:43
constexpr double SP_RGBA32_A_F(uint32_t v)
Definition utils.h:55
constexpr double SP_RGBA32_B_F(uint32_t v)
Definition utils.h:51
Editable view implementation.
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
Infinite straight line.
Glib::ustring label
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
std::optional< Crossing > OptCrossing
Definition crossing.h:64
OptCrossing intersection(Ray const &r1, Line const &l2)
Definition line.h:545
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
Helper class to stream background task notifications as a series of messages.
@ CANVAS_ITEM_CTRL_SHAPE_CROSS
@ CANVAS_ITEM_CTRL_SHAPE_CIRCLE
@ CANVAS_ITEM_CTRL_TYPE_GUIDE_HANDLE
STL namespace.
int buf
Class used when rendering canvas items.
SPDesktop * desktop
double width