Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
ink-ruler.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Ruler widget. Indicates horizontal or vertical position of a cursor in a specified widget.
4 *
5 * Copyright (C) 2019, 2023 Tavmjong Bah
6 * 2022 Martin Owens
7 *
8 * Rewrite of the 'C' ruler code which came originally from Gimp.
9 *
10 * The contents of this file may be used under the GNU General Public License Version 2 or later.
11 *
12 */
13
14#include <cmath>
15#include <sigc++/functors/mem_fun.h>
16#include <cairomm/context.h>
17#include <glibmm/ustring.h>
18#include <giomm/menu.h>
19#include <giomm/menuitem.h>
20#include <gdkmm/general.h>
21#include <gtkmm/binlayout.h>
22#include <gtkmm/drawingarea.h>
23#include <gtkmm/eventcontrollermotion.h>
24#include <gtkmm/gestureclick.h>
25#include <gtkmm/popovermenu.h>
26#include <gtkmm/snapshot.h>
27
28#include "ink-ruler.h"
29#include "inkscape.h"
30#include "ui/containerize.h"
31#include "ui/controller.h"
32#include "ui/popup-menu.h"
33#include "ui/themes.h"
34#include "ui/util.h"
35#include "util/units.h"
36
37namespace Inkscape::UI::Widget {
38
39// Half width of pointer triangle.
40constexpr double half_width = 5.0;
41
42Ruler::Ruler(Gtk::Orientation orientation)
43 : Glib::ObjectBase{"InkRuler"}
44 , _orientation{orientation}
45 , _popover{create_context_menu()}
46{
47 set_name("InkRuler");
48 add_css_class(_orientation == Gtk::Orientation::HORIZONTAL ? "horz" : "vert");
49 containerize(*this);
50 set_layout_manager(Gtk::BinLayout::create());
51
52 auto const motion = Gtk::EventControllerMotion::create();
53 motion->signal_motion().connect([this, &motion = *motion](auto &&...args) { return on_motion(motion, args...); });
54 add_controller(motion);
55
56 auto const click = Gtk::GestureClick::create();
57 click->set_button(3); // right
58 click->signal_pressed().connect(Controller::use_state([this](auto &, auto &&...args) { return on_click_pressed(args...); }, *click));
59 add_controller(click);
60
61 auto prefs = Inkscape::Preferences::get();
62 _watch_prefs = prefs->createObserver("/options/ruler/show_bbox", sigc::mem_fun(*this, &Ruler::on_prefs_changed));
64
65 INKSCAPE.themecontext->getChangeThemeSignal().connect(sigc::track_object([this] { css_changed(nullptr); }, *this));
66}
67
68Ruler::~Ruler() = default;
69
71{
72 auto prefs = Inkscape::Preferences::get();
73 _sel_visible = prefs->getBool("/options/ruler/show_bbox", true);
74
76}
77
78// Set display unit for ruler.
80{
81 if (_unit != unit) {
82 _unit = unit;
84 _scale_tile_node.reset();
85 }
86}
87
88// Set range for ruler, update ticks.
89void Ruler::set_range(double lower, double upper)
90{
91 if (_lower != lower || _upper != upper) {
92 _lower = lower;
93 _upper = upper;
95 if (_max_size == 0) {
96 _max_size = 1;
97 }
99 }
100}
101
105void Ruler::set_page(double lower, double upper)
106{
107 if (_page_lower != lower || _page_upper != upper) {
108 _page_lower = lower;
109 _page_upper = upper;
110 redraw_ruler();
111 }
112}
113
117void Ruler::set_selection(double lower, double upper)
118{
119 if (_sel_lower != lower || _sel_upper != upper) {
120 _sel_lower = lower;
121 _sel_upper = upper;
122 redraw_ruler();
123 }
124}
125
126// Add a widget (i.e. canvas) to monitor.
127void Ruler::set_track_widget(Gtk::Widget &widget)
128{
130 _track_widget_controller = Gtk::EventControllerMotion::create();
131 _track_widget_controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
132 _track_widget_controller->signal_motion().connect([this] (auto &&...args) { return on_motion(*_track_widget_controller, args...); }, false); // before
133 widget.add_controller(_track_widget_controller);
134}
135
137{
139 _track_widget_controller->get_widget()->remove_controller(_track_widget_controller);
141}
142
143// Draws marker in response to motion events from canvas. Position is defined in ruler pixel
144// coordinates. The routine assumes that the ruler is the same width (height) as the canvas. If
145// not, one could use Gtk::Widget::translate_coordinates() to convert the coordinates.
146void Ruler::on_motion(Gtk::EventControllerMotion &motion, double x, double y)
147{
148 // This may come from a widget other than `this`, so translate to accommodate border, etc.
149 auto const widget = motion.get_widget();
150 double drawing_x{}, drawing_y{};
151 widget->translate_coordinates(*this, std::lround(x), std::lround(y), drawing_x, drawing_y);
152
153 double const position = _orientation == Gtk::Orientation::HORIZONTAL ? drawing_x : drawing_y;
154 if (position == _position) {
155 return;
156 }
157
158 _position = position;
159 queue_draw();
160}
161
162Gtk::EventSequenceState Ruler::on_click_pressed(int, double x, double y)
163{
164 UI::popup_at(*_popover, *this, x, y);
165 return Gtk::EventSequenceState::CLAIMED;
166}
167
168static double safe_frac(double x)
169{
170 return x - std::floor(x);
171}
172
173void Ruler::draw_ruler(Glib::RefPtr<Gtk::Snapshot> const &snapshot)
174{
175 auto const dims = Geom::IntPoint{get_width(), get_height()};
176
177 // aparallel is the longer dimension of the ruler; aperp shorter.
178 auto const [aparallel, aperp] = _orientation == Gtk::Orientation::HORIZONTAL
179 ? std::pair{dims.x(), dims.y()}
180 : std::pair{dims.y(), dims.x()};
181
182 // Color in page indication box
183 if (auto const interval = Geom::IntInterval(std::round(_page_lower), std::round(_page_upper)) & Geom::IntInterval{0, aparallel}) {
184 Geom::IntRect rect;
185 if (_orientation == Gtk::Orientation::HORIZONTAL) {
186 rect = {interval->min(), 0, interval->max(), aperp};
187 } else {
188 rect = {0, interval->min(), aperp, interval->max()};
189 }
190 snapshot->append_color(_page_fill, geom_to_gtk(rect));
191 }
192
193 // Draw bottom/right line of ruler
194 Geom::IntRect edge_rect;
195 if (_orientation == Gtk::Orientation::HORIZONTAL) {
196 edge_rect = {0, aperp - 1, aparallel, aperp};
197 } else {
198 edge_rect = {aperp - 1, 0, aperp, aparallel};
199 }
200 snapshot->append_color(_foreground, geom_to_gtk(edge_rect));
201
202 double const abs_size = std::abs(_max_size);
203 int const sign = _max_size >= 0 ? 1 : -1;
204
205 // Figure out scale. Largest ticks must be far enough apart to fit largest text in vertical ruler.
206 // We actually require twice the distance.
207 int scale = std::ceil(abs_size); // Largest number
208 Glib::ustring const scale_text = std::to_string(scale);
209 int const digits = scale_text.length() + 1; // Add one for negative sign.
210 int const minimum = digits * _font_size * 2;
211
212 double const pixels_per_unit = aparallel / abs_size;
213
214 auto ruler_metric = _unit->getUnitMetric();
215 if (!ruler_metric) {
216 // User warning already done in Unit code.
217 return;
218 }
219
220 unsigned scale_index;
221 for (scale_index = 0; scale_index < ruler_metric->ruler_scale.size() - 1; ++scale_index) {
222 if (ruler_metric->ruler_scale[scale_index] * pixels_per_unit > minimum) {
223 break;
224 }
225 }
226
227 // Now we find out what is the subdivide index for the closest ticks we can draw
228 unsigned divide_index;
229 for (divide_index = 0; divide_index < ruler_metric->subdivide.size() - 1; ++divide_index) {
230 if (ruler_metric->ruler_scale[scale_index] * pixels_per_unit < 5 * ruler_metric->subdivide[divide_index + 1]) {
231 break;
232 }
233 }
234
235 int const subdivisions = ruler_metric->subdivide[divide_index];
236 double const units_per_major = ruler_metric->ruler_scale[scale_index];
237 double const pixels_per_major = pixels_per_unit * units_per_major;
238 double const pixels_per_tick = pixels_per_major / subdivisions;
239
240 // Figure out which cached render nodes to invalidate.
241 if (!_params) {
243 .aparallel = aparallel,
244 .aperp = aperp,
245 .divide_index = divide_index,
246 .pixels_per_tick = pixels_per_tick,
247 .pixels_per_major = pixels_per_major
248 };
249 } else {
250 auto update = [] (auto src, auto &dst, auto&... to_reset) {
251 if (src != dst) {
252 dst = src;
253 (to_reset.reset(), ...);
254 }
255 };
256 auto update_approx = [] (auto src, auto &dst, auto&... to_reset) {
257 if (!Geom::are_near(src, dst)) {
258 dst = src;
259 (to_reset.reset(), ...);
260 }
261 };
262 update(aparallel, _params->aparallel, _shadow_node, _scale_node);
263 update(aperp, _params->aperp, _scale_tile_node);
264 update(divide_index, _params->divide_index, _scale_tile_node);
265 update_approx(pixels_per_tick, _params->pixels_per_tick, _scale_tile_node);
266 update_approx(pixels_per_major, _params->pixels_per_major, _scale_node);
267 }
268 if (!_scale_tile_node) {
269 _scale_node.reset(); // _scale_node contains _scale_tile_node
270 }
271
272 // Draw a shadow which overlaps any previously painted object.
273 if (!_shadow_node) {
274 Geom::IntRect shadow_rect;
275 Geom::IntPoint end_point;
276 static constexpr int gradient_size = 4;
277 if (_orientation == Gtk::Orientation::HORIZONTAL) {
278 shadow_rect = {0, 0, aparallel, gradient_size};
279 end_point = {0, gradient_size};
280 } else {
281 shadow_rect = {0, 0, gradient_size, aparallel};
282 end_point = {gradient_size, 0};
283 }
284 auto const stops = create_cubic_gradient(_shadow, change_alpha(_shadow, 0.0), Geom::Point(0, 0.5), Geom::Point(0.5, 1));
285 auto shadow_snapshot = gtk_snapshot_new();
286 gtk_snapshot_append_linear_gradient(
287 shadow_snapshot,
288 geom_to_gtk(shadow_rect).gobj(),
289 geom_to_gtk(Geom::IntPoint{}).gobj(),
290 geom_to_gtk(end_point).gobj(),
291 stops.data(),
292 stops.size()
293 );
294 _shadow_node = RenderNodePtr{gtk_snapshot_free_to_node(shadow_snapshot)};
295 }
296 gtk_snapshot_append_node(snapshot->gobj(), _shadow_node.get());
297
298 snapshot->push_clip(geom_to_gtk(Geom::IntRect{{}, dims}));
299
300 // Build a single scale tile, i.e. one major tick.
301 if (!_scale_tile_node) {
302 auto scale_tile = gtk_snapshot_new();
303
304 for (int i = 0; i < subdivisions; i++) {
305 // Position of tick
306 double position = std::round(i * pixels_per_tick);
307
308 // Height of tick
309 int size = aperp - 7;
310 for (int j = divide_index; j > 0; --j) {
311 if (i % ruler_metric->subdivide[j] == 0) {
312 break;
313 }
314 size = size / 2 + 1;
315 }
316
317 // Draw ticks
318 Geom::Rect rect;
319 if (_orientation == Gtk::Orientation::HORIZONTAL) {
320 rect = Geom::Rect(position, aperp - size, position + 1, aperp - 1);
321 } else {
322 rect = Geom::Rect(aperp - size, position, aperp - 1, position + 1);
323 }
324 gtk_snapshot_append_color(scale_tile, _foreground.gobj(), geom_to_gtk(rect).gobj());
325 }
326
327 _scale_tile_node = RenderNodePtr{gtk_snapshot_free_to_node(scale_tile)};
328 }
329
330 // Glue scale tiles together.
331 // Note: We can't use a repeat node for this, because then the ticks will either be blurry or inaccurate.
332 if (!_scale_node) {
333 auto scale_tiles = gtk_snapshot_new();
334
335 for (int i = 0; ; i++) {
336 if (i > 0) {
337 int const pos = std::round(i * pixels_per_major);
338 if (pos >= aparallel + pixels_per_major) {
339 break;
340 }
341 int const lastpos = std::round((i - 1) * pixels_per_major);
342 int const shift = pos - lastpos;
343 auto const translate = _orientation == Gtk::Orientation::HORIZONTAL
345 : Geom::IntPoint(0, shift);
346 gtk_snapshot_translate(scale_tiles, geom_to_gtk(translate).gobj());
347 }
348 gtk_snapshot_append_node(scale_tiles, _scale_tile_node.get());
349 }
350
351 _scale_node = RenderNodePtr{gtk_snapshot_free_to_node(scale_tiles)};
352 }
353
354 // Render the scale with a shift.
355 int const shift = -std::round(safe_frac(_lower * sign / units_per_major) * pixels_per_major);
356 auto const translate = _orientation == Gtk::Orientation::HORIZONTAL
357 ? Geom::Point(shift, 0)
358 : Geom::Point(0, shift);
359 snapshot->save();
360 snapshot->translate(geom_to_gtk(translate));
361 gtk_snapshot_append_node(snapshot->gobj(), _scale_node.get());
362
363 // Find first and last major ticks
364 int const start = std::floor(_lower * sign / units_per_major);
365 int const end = std::floor(_upper * sign / units_per_major);
366
367 // Draw text for major ticks.
368 for (int i = start; i <= end; ++i) {
369 int const label_value = std::round(i * units_per_major * sign);
370 double const position = std::round((i - start) * pixels_per_major);
371 bool const rotate = _orientation != Gtk::Orientation::HORIZONTAL;
372 auto const layout = create_pango_layout(std::to_string(label_value));
373
374 int text_width{};
375 int text_height{};
376 layout->get_pixel_size(text_width, text_height);
377 if (rotate) {
378 std::swap(text_width, text_height);
379 }
380
381 // Align text to pixel
382 int x = position + 3;
383 int y = 3;
384 if (rotate) {
385 std::swap(x, y);
386 }
387
388 // Create label text or retrieve from cache. (Note: This cache is never pruned.)
389 auto &label_node = _label_nodes[label_value];
390 if (!label_node) {
391 auto label = gtk_snapshot_new();
392 gtk_snapshot_append_layout(label, layout->gobj(), _foreground.gobj());
393 label_node = RenderNodePtr{gtk_snapshot_free_to_node(label)};
394 }
395
396 snapshot->save();
397 snapshot->translate(Gdk::Graphene::Point(x, y));
398 if (rotate) {
399 snapshot->translate(Gdk::Graphene::Point(0.0, text_height));
400 snapshot->rotate(-90);
401 }
402 gtk_snapshot_append_node(snapshot->gobj(), label_node.get());
403 snapshot->restore();
404 }
405
406 snapshot->restore();
407
408 // Draw a selection bar
410 constexpr auto radius = 3.0;
411 constexpr auto line_width = 2.0;
412 auto const delta = _sel_upper - _sel_lower;
413 auto const dxy = delta > 0 ? radius : -radius;
414 double sy0 = _sel_lower;
415 double sy1 = _sel_upper;
416 double sx0 = std::floor(aperp * 0.7);
417 double sx1 = sx0;
418
419 if (_orientation == Gtk::Orientation::HORIZONTAL) {
420 std::swap(sy0, sx0);
421 std::swap(sy1, sx1);
422 }
423
424 if (std::abs(delta) > 2 * radius) {
425 Geom::Rect rect;
426 if (_orientation == Gtk::Orientation::HORIZONTAL) {
427 auto const y = std::round(sy0 - line_width / 2);
428 rect = Geom::Rect(sx0 + dxy, y, sx1 - dxy, y + line_width);
429 } else {
430 auto const x = std::round(sx0 - line_width / 2);
431 rect = Geom::Rect(x, sy0 + dxy, x + line_width, sy1 - dxy);
432 }
433 snapshot->append_color(_select_stroke, geom_to_gtk(rect).gobj());
434 }
435
436 static auto const path = [] {
437 auto builder = gsk_path_builder_new();
438 gsk_path_builder_add_circle(builder, Gdk::Graphene::Point{0.0, 0.0}.gobj(), radius);
439 return gsk_path_builder_free_to_path(builder);
440 }();
441
442 static auto const stroke = [] {
443 return gsk_stroke_new(line_width);
444 }();
445
446 // Markers
447 for (auto pt : {Geom::Point(sx0, sy0), Geom::Point(sx1, sy1)}) {
448 snapshot->save();
449 snapshot->translate(geom_to_gtk(pt));
450 gtk_snapshot_append_fill(snapshot->gobj(), path, GSK_FILL_RULE_WINDING, _select_fill.gobj());
451 gtk_snapshot_append_stroke(snapshot->gobj(), path, stroke, _select_stroke.gobj());
452 snapshot->restore();
453 }
454 }
455
456 snapshot->pop();
457}
458
459// Draw position marker, we use doubles here.
460void Ruler::draw_marker(Glib::RefPtr<Gtk::Snapshot> const &snapshot)
461{
462 static auto const path = [] {
463 auto builder = gsk_path_builder_new();
464 gsk_path_builder_move_to(builder, 0, 0);
465 gsk_path_builder_line_to(builder, -half_width, -half_width);
466 gsk_path_builder_line_to(builder, half_width, -half_width);
467 gsk_path_builder_close(builder);
468 return gsk_path_builder_free_to_path(builder);
469 }();
470
471 auto const pos = _orientation == Gtk::Orientation::HORIZONTAL
472 ? Geom::Point(_position, get_height())
473 : Geom::Point(get_width(), _position);
474 snapshot->save();
475 snapshot->translate(geom_to_gtk(pos));
476 if (_orientation != Gtk::Orientation::HORIZONTAL) {
477 snapshot->rotate(-90);
478 }
479 gtk_snapshot_append_fill(snapshot->gobj(), path, GSK_FILL_RULE_WINDING, _foreground.gobj());
480 snapshot->restore();
481}
482
483void Ruler::snapshot_vfunc(Glib::RefPtr<Gtk::Snapshot> const &snapshot)
484{
485 if (!_ruler_node) {
486 auto ruler = gtk_snapshot_new();
487 draw_ruler(Glib::wrap_gtk_snapshot(ruler, true));
488 _ruler_node = RenderNodePtr{gtk_snapshot_free_to_node(ruler)};
489 }
490 gtk_snapshot_append_node(snapshot->gobj(), _ruler_node.get());
491 draw_marker(snapshot);
492}
493
494// Update ruler on style change (font-size, etc.)
495void Ruler::css_changed(GtkCssStyleChange *change)
496{
497 // Cache all our colors to speed up rendering.
498 _foreground = get_color();
499 _font_size = get_font_size(*this);
500
501 _shadow = get_color_with_class(*this, "shadow");
502 _page_fill = get_color_with_class(*this, "page");
503
504 add_css_class("selection");
505 _select_fill = get_color_with_class(*this, "background");
506 _select_stroke = get_color_with_class(*this, "border");
507 remove_css_class("selection");
508
509 redraw_ruler();
510 _shadow_node.reset();
511 _label_nodes.clear();
512}
513
517std::unique_ptr<Gtk::Popover> Ruler::create_context_menu()
518{
519 auto unit_menu = Gio::Menu::create();
520
522 auto unit = pair.second.abbr;
523 Glib::ustring action_name = "doc.set-display-unit('" + unit + "')";
524 auto item = Gio::MenuItem::create(unit, action_name);
525 unit_menu->append_item(item);
526 }
527
528 auto popover = std::make_unique<Gtk::PopoverMenu>(unit_menu);
529 popover->set_parent(*this);
530 popover->set_autohide(true);
531 return popover;
532}
533
534} // namespace Inkscape::UI::Widget
535
536/*
537 Local Variables:
538 mode:c++
539 c-file-style:"stroustrup"
540 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
541 indent-tabs-mode:nil
542 fill-column:99
543 End:
544*/
545// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
const char * action_name
A range of numbers which is never empty.
Axis aligned, non-empty, generic rectangle.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
Two-dimensional point with integer coordinates.
Definition int-point.h:57
constexpr IntCoord x() const noexcept
Definition int-point.h:77
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
static Preferences * get()
Access the singleton Preferences object.
std::optional< LastRenderParams > _params
Definition ink-ruler.h:117
std::map< int, RenderNodePtr > _label_nodes
Definition ink-ruler.h:101
void css_changed(GtkCssStyleChange *) override
Called after gtk_widget_css_changed(): when a CSS widget node is validated & style changed.
void draw_marker(Glib::RefPtr< Gtk::Snapshot > const &snapshot)
void set_selection(double lower, double upper)
Set the location of the currently selected page.
Ruler(Gtk::Orientation orientation)
Definition ink-ruler.cpp:42
RenderNodePtr _scale_tile_node
Definition ink-ruler.h:99
void snapshot_vfunc(Glib::RefPtr< Gtk::Snapshot > const &snapshot) override
void set_track_widget(Gtk::Widget &widget)
void set_range(double lower, double upper)
Definition ink-ruler.cpp:89
Gtk::EventSequenceState on_click_pressed(int n_press, double x, double y)
Gtk::Orientation const _orientation
Definition ink-ruler.h:71
std::unique_ptr< Gtk::Popover > create_context_menu()
Return a contextmenu for the ruler.
std::unique_ptr< Gtk::Popover > _popover
Definition ink-ruler.h:70
void set_unit(Inkscape::Util::Unit const *unit)
Definition ink-ruler.cpp:79
Inkscape::PrefObserver _watch_prefs
Definition ink-ruler.h:69
Glib::RefPtr< Gtk::EventControllerMotion > _track_widget_controller
Definition ink-ruler.h:87
void on_motion(Gtk::EventControllerMotion &motion, double x, double y)
void draw_ruler(Glib::RefPtr< Gtk::Snapshot > const &snapshot)
void set_page(double lower, double upper)
Set the location of the currently selected page.
Inkscape::Util::Unit const * _unit
Definition ink-ruler.h:72
RenderNodePtr _shadow_node
Definition ink-ruler.h:98
UnitMap units(UnitType type) const
Provides an iterable list of items in the given unit table.
Definition units.cpp:406
static UnitTable & get()
Definition units.cpp:441
UnitMetric const * getUnitMetric() const
Get the ways this unit is subdivided in rulers.
Definition units.cpp:253
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Colors::Color stroke
SPItem * item
void shift(T &a, T &b, T const &c)
Glib::ustring label
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
auto use_state(Slot &&slot)
Definition controller.h:43
Custom widgets.
Definition desktop.h:126
constexpr double half_width
Definition ink-ruler.cpp:40
static double safe_frac(double x)
std::unique_ptr< GskRenderNode, Util::Deleter< gsk_render_node_unref > > RenderNodePtr
Definition ink-ruler.h:38
void containerize(Gtk::Widget &widget)
Make a custom widget implement sensible memory management for its children.
static void popup_at(Gtk::Popover &popover, Gtk::Widget &widget, double const x_offset, double const y_offset, int width, int height)
int get_font_size(Gtk::Widget &widget)
Get the relative font size as determined by a widgetʼs style/Pango contexts.
Definition util.cpp:239
@ UNIT_TYPE_LINEAR
Definition units.h:32
Helpers to connect signals to events that popup a menu in both GTK3 and GTK4.
static double sign(double const x)
Returns -1 or 1 according to the sign of x.
std::vector< int > subdivide
Definition units.h:47
int delta
int minimum
Gtk <themes> helper code.
Glib::RefPtr< Gtk::Builder > builder
std::vector< GskColorStop > create_cubic_gradient(const Gdk::RGBA &from, const Gdk::RGBA &to, Geom::Point ctrl1, Geom::Point ctrl2, Geom::Point p0, Geom::Point p1, int steps)
Definition util.cpp:378
Gdk::RGBA get_color_with_class(Gtk::Widget &widget, Glib::ustring const &css_class)
Definition util.cpp:303
Gdk::RGBA change_alpha(const Gdk::RGBA &color, double new_alpha)
Definition util.cpp:417
Gdk::Graphene::Rect geom_to_gtk(Geom::GenericRect< T > const &rect)
Definition util.h:215