1 /* This file is part of Ganv.
2 * Copyright 2007-2015 David Robillard <http://drobilla.net>
3 *
4 * Ganv is free software: you can redistribute it and/or modify it under the
5 * terms of the GNU General Public License as published by the Free Software
6 * Foundation, either version 3 of the License, or any later version.
7 *
8 * Ganv is distributed in the hope that it will be useful, but WITHOUT ANY
9 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
11 *
12 * You should have received a copy of the GNU General Public License along
13 * with Ganv. If not, see <http://www.gnu.org/licenses/>.
14 */
15
16 #include "boilerplate.h"
17 #include "color.h"
18 #include "ganv-private.h"
19 #include "gettext.h"
20
21 #include "ganv/box.h"
22 #include "ganv/item.h"
23 #include "ganv/node.h"
24 #include "ganv/types.h"
25
26 #include <cairo.h>
27 #include <glib-object.h>
28 #include <glib.h>
29 #include <gtk/gtk.h>
30
31 #include <math.h>
32 #include <string.h>
33
34 static const double STACKED_OFFSET = 4.0;
35
36 G_DEFINE_TYPE_WITH_CODE(GanvBox, ganv_box, GANV_TYPE_NODE,
37 G_ADD_PRIVATE(GanvBox))
38
39 static GanvNodeClass* parent_class;
40
41 enum {
42 PROP_0,
43 PROP_X1,
44 PROP_Y1,
45 PROP_X2,
46 PROP_Y2,
47 PROP_RADIUS_TL,
48 PROP_RADIUS_TR,
49 PROP_RADIUS_BR,
50 PROP_RADIUS_BL,
51 PROP_STACKED,
52 PROP_BEVELED
53 };
54
55 static void
ganv_box_init(GanvBox * box)56 ganv_box_init(GanvBox* box)
57 {
58 box->impl = (GanvBoxPrivate*)ganv_box_get_instance_private(box);
59
60 memset(&box->impl->coords, '\0', sizeof(GanvBoxCoords));
61
62 box->impl->coords.border_width = GANV_NODE(box)->impl->border_width;
63 box->impl->old_coords = box->impl->coords;
64 box->impl->radius_tl = 0.0;
65 box->impl->radius_tr = 0.0;
66 box->impl->radius_br = 0.0;
67 box->impl->radius_bl = 0.0;
68 box->impl->beveled = FALSE;
69 }
70
71 static void
ganv_box_destroy(GtkObject * object)72 ganv_box_destroy(GtkObject* object)
73 {
74 g_return_if_fail(object != NULL);
75 g_return_if_fail(GANV_IS_BOX(object));
76
77 if (GTK_OBJECT_CLASS(parent_class)->destroy) {
78 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
79 }
80 }
81
82 static void
ganv_box_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)83 ganv_box_set_property(GObject* object,
84 guint prop_id,
85 const GValue* value,
86 GParamSpec* pspec)
87 {
88 g_return_if_fail(object != NULL);
89 g_return_if_fail(GANV_IS_BOX(object));
90
91 GanvBox* box = GANV_BOX(object);
92 GanvBoxPrivate* impl = box->impl;
93 GanvBoxCoords* coords = &impl->coords;
94
95 switch (prop_id) {
96 SET_CASE(X1, double, coords->x1)
97 SET_CASE(Y1, double, coords->y1)
98 SET_CASE(X2, double, coords->x2)
99 SET_CASE(Y2, double, coords->y2)
100 SET_CASE(RADIUS_TL, double, impl->radius_tl)
101 SET_CASE(RADIUS_TR, double, impl->radius_tr)
102 SET_CASE(RADIUS_BR, double, impl->radius_br)
103 SET_CASE(RADIUS_BL, double, impl->radius_bl)
104 SET_CASE(STACKED, boolean, coords->stacked)
105 SET_CASE(BEVELED, boolean, impl->beveled)
106 default:
107 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
108 break;
109 }
110 }
111
112 static void
ganv_box_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)113 ganv_box_get_property(GObject* object,
114 guint prop_id,
115 GValue* value,
116 GParamSpec* pspec)
117 {
118 g_return_if_fail(object != NULL);
119 g_return_if_fail(GANV_IS_BOX(object));
120
121 GanvBox* box = GANV_BOX(object);
122 GanvBoxPrivate* impl = box->impl;
123 GanvBoxCoords* coords = &impl->coords;
124
125 switch (prop_id) {
126 GET_CASE(X1, double, coords->x1)
127 GET_CASE(X2, double, coords->x2)
128 GET_CASE(Y1, double, coords->y1)
129 GET_CASE(Y2, double, coords->y2)
130 GET_CASE(RADIUS_TL, double, impl->radius_tl)
131 GET_CASE(RADIUS_TR, double, impl->radius_tr)
132 GET_CASE(RADIUS_BR, double, impl->radius_br)
133 GET_CASE(RADIUS_BL, double, impl->radius_bl)
134 GET_CASE(STACKED, boolean, impl->coords.stacked)
135 GET_CASE(BEVELED, boolean, impl->beveled)
136 default:
137 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
138 break;
139 }
140 }
141
142 static void
ganv_box_bounds_item(const GanvBoxCoords * coords,double * x1,double * y1,double * x2,double * y2)143 ganv_box_bounds_item(const GanvBoxCoords* coords,
144 double* x1, double* y1,
145 double* x2, double* y2)
146 {
147 *x1 = coords->x1 - coords->border_width;
148 *y1 = coords->y1 - coords->border_width;
149 *x2 = coords->x2 + coords->border_width + (coords->stacked * STACKED_OFFSET);
150 *y2 = coords->y2 + coords->border_width + (coords->stacked * STACKED_OFFSET);
151 }
152
153 void
ganv_box_request_redraw(GanvItem * item,const GanvBoxCoords * coords,gboolean world)154 ganv_box_request_redraw(GanvItem* item,
155 const GanvBoxCoords* coords,
156 gboolean world)
157 {
158 double x1 = 0.0;
159 double y1 = 0.0;
160 double x2 = 0.0;
161 double y2 = 0.0;
162 ganv_box_bounds_item(coords, &x1, &y1, &x2, &y2);
163
164 if (!world) {
165 // Convert from item-relative coordinates to world coordinates
166 ganv_item_i2w_pair(item, &x1, &y1, &x2, &y2);
167 }
168
169 ganv_canvas_request_redraw_w(item->impl->canvas, x1, y1, x2, y2);
170 }
171
172 static void
coords_i2w(GanvItem * item,GanvBoxCoords * coords)173 coords_i2w(GanvItem* item, GanvBoxCoords* coords)
174 {
175 ganv_item_i2w_pair(item, &coords->x1, &coords->y1, &coords->x2, &coords->y2);
176 }
177
178 static void
ganv_box_bounds(GanvItem * item,double * x1,double * y1,double * x2,double * y2)179 ganv_box_bounds(GanvItem* item,
180 double* x1, double* y1,
181 double* x2, double* y2)
182 {
183 // Note this will not be correct if children are outside the box bounds
184 GanvBox* box = (GanvBox*)item;
185 ganv_box_bounds_item(&box->impl->coords, x1, y1, x2, y2);
186 }
187
188 static void
ganv_box_update(GanvItem * item,int flags)189 ganv_box_update(GanvItem* item, int flags)
190 {
191 GanvBox* box = GANV_BOX(item);
192 GanvBoxPrivate* impl = box->impl;
193 impl->coords.border_width = box->node.impl->border_width;
194
195 // Request redraw of old location
196 ganv_box_request_redraw(item, &impl->old_coords, TRUE);
197
198 // Store old coordinates in world relative coordinates in case the
199 // group we are in moves between now and the next update
200 impl->old_coords = impl->coords;
201 coords_i2w(item, &impl->old_coords);
202
203 // Call parent class update method, resizing if necessary
204 GANV_ITEM_CLASS(parent_class)->update(item, flags);
205 ganv_box_normalize(box);
206
207 // Update world-relative bounding box
208 ganv_box_bounds(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2);
209 ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2);
210
211 // Request redraw of new location
212 ganv_box_request_redraw(item, &impl->coords, FALSE);
213 }
214
215 void
ganv_box_path(GanvBox * box,cairo_t * cr,double x1,double y1,double x2,double y2,double dr)216 ganv_box_path(GanvBox* box,
217 cairo_t* cr, double x1, double y1, double x2, double y2,
218 double dr)
219 {
220 static const double degrees = G_PI / 180.0;
221
222 GanvBoxPrivate* impl = box->impl;
223
224 if (impl->radius_tl == 0.0 && impl->radius_tr == 0.0
225 && impl->radius_br == 0.0 && impl->radius_bl == 0.0) {
226 // Simple rectangle
227 cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
228 } else if (impl->beveled) {
229 // Beveled rectangle
230 cairo_new_sub_path(cr);
231 cairo_move_to(cr, x1 + impl->radius_tl, y1);
232 cairo_line_to(cr, x2 - impl->radius_tr, y1);
233 cairo_line_to(cr, x2, y1 + impl->radius_tr);
234 cairo_line_to(cr, x2, y2 - impl->radius_br);
235 cairo_line_to(cr, x2 - impl->radius_br, y2);
236 cairo_line_to(cr, x1 + impl->radius_bl, y2);
237 cairo_line_to(cr, x1, y2 - impl->radius_bl);
238 cairo_line_to(cr, x1, y2 - impl->radius_bl);
239 cairo_line_to(cr, x1, y1 + impl->radius_tl);
240 cairo_close_path(cr);
241 } else {
242 // Rounded rectangle
243 cairo_new_sub_path(cr);
244 cairo_arc(cr,
245 x2 - impl->radius_tr - dr,
246 y1 + impl->radius_tr + dr,
247 impl->radius_tr + dr, -90 * degrees, 0 * degrees);
248 cairo_arc(cr,
249 x2 - impl->radius_br - dr, y2 - impl->radius_br - dr,
250 impl->radius_br + dr, 0 * degrees, 90 * degrees);
251 cairo_arc(cr,
252 x1 + impl->radius_bl + dr, y2 - impl->radius_bl - dr,
253 impl->radius_bl + dr, 90 * degrees, 180 * degrees);
254 cairo_arc(cr,
255 x1 + impl->radius_tl + dr, y1 + impl->radius_tl + dr,
256 impl->radius_tl + dr, 180 * degrees, 270 * degrees);
257 cairo_close_path(cr);
258 }
259 }
260
261 static void
ganv_box_draw(GanvItem * item,cairo_t * cr,double cx,double cy,double cw,double ch)262 ganv_box_draw(GanvItem* item,
263 cairo_t* cr, double cx, double cy, double cw, double ch)
264 {
265 GanvBox* box = GANV_BOX(item);
266 GanvBoxPrivate* impl = box->impl;
267
268 double x1 = impl->coords.x1;
269 double y1 = impl->coords.y1;
270 double x2 = impl->coords.x2;
271 double y2 = impl->coords.y2;
272 ganv_item_i2w_pair(item, &x1, &y1, &x2, &y2);
273
274 double dash_length = 0.0;
275 double border_color = 0.0;
276 double fill_color = 0.0;
277 ganv_node_get_draw_properties(
278 &box->node, &dash_length, &border_color, &fill_color);
279
280 double r = 0.0;
281 double g = 0.0;
282 double b = 0.0;
283 double a = 0.0;
284
285 for (int i = (impl->coords.stacked ? 1 : 0); i >= 0; --i) {
286 const double x = 0.0 - (STACKED_OFFSET * i);
287 const double y = 0.0 - (STACKED_OFFSET * i);
288
289 // Trace basic box path
290 ganv_box_path(box, cr, x1 - x, y1 - y, x2 - x, y2 - y, 0.0);
291
292 // Fill
293 color_to_rgba(fill_color, &r, &g, &b, &a);
294 cairo_set_source_rgba(cr, r, g, b, a);
295
296 // Border
297 if (impl->coords.border_width > 0.0) {
298 cairo_fill_preserve(cr);
299 color_to_rgba(border_color, &r, &g, &b, &a);
300 cairo_set_source_rgba(cr, r, g, b, a);
301 cairo_set_line_width(cr, impl->coords.border_width);
302 if (dash_length > 0) {
303 cairo_set_dash(cr, &dash_length, 1, box->node.impl->dash_offset);
304 } else {
305 cairo_set_dash(cr, &dash_length, 0, 0);
306 }
307 cairo_stroke(cr);
308 } else {
309 cairo_fill(cr);
310 }
311 }
312
313 GanvItemClass* item_class = GANV_ITEM_CLASS(parent_class);
314 item_class->draw(item, cr, cx, cy, cw, ch);
315 }
316
317 static double
ganv_box_point(GanvItem * item,double x,double y,GanvItem ** actual_item)318 ganv_box_point(GanvItem* item, double x, double y, GanvItem** actual_item)
319 {
320 GanvBox* box = GANV_BOX(item);
321 GanvBoxPrivate* impl = box->impl;
322
323 *actual_item = NULL;
324
325 double x1 = 0.0;
326 double y1 = 0.0;
327 double x2 = 0.0;
328 double y2 = 0.0;
329 ganv_box_bounds_item(&impl->coords, &x1, &y1, &x2, &y2);
330
331 // Point is inside the box (distance 0)
332 if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) {
333 *actual_item = item;
334 return 0.0;
335 }
336
337 // Point is outside the box
338 double dx = 0.0;
339 double dy = 0.0;
340
341 // Find horizontal distance to nearest edge
342 if (x < x1) {
343 dx = x1 - x;
344 } else if (x > x2) {
345 dx = x - x2;
346 }
347
348 // Find vertical distance to nearest edge
349 if (y < y1) {
350 dy = y1 - y;
351 } else if (y > y2) {
352 dy = y - y2;
353 }
354
355 return sqrt((dx * dx) + (dy * dy));
356 }
357
358 static gboolean
ganv_box_is_within(const GanvNode * self,double x1,double y1,double x2,double y2)359 ganv_box_is_within(const GanvNode* self,
360 double x1,
361 double y1,
362 double x2,
363 double y2)
364 {
365 double bx1 = 0.0;
366 double by1 = 0.0;
367 double bx2 = 0.0;
368 double by2 = 0.0;
369 g_object_get(G_OBJECT(self),
370 "x1", &bx1,
371 "y1", &by1,
372 "x2", &bx2,
373 "y2", &by2,
374 NULL);
375
376 ganv_item_i2w_pair(GANV_ITEM(self), &bx1, &by1, &bx2, &by2);
377
378 return ( bx1 >= x1
379 && by2 >= y1
380 && bx2 <= x2
381 && by2 <= y2);
382 }
383
384 static void
ganv_box_default_set_width(GanvBox * box,double width)385 ganv_box_default_set_width(GanvBox* box, double width)
386 {
387 box->impl->coords.x2 = ganv_box_get_x1(box) + width;
388 ganv_item_request_update(GANV_ITEM(box));
389 }
390
391 static void
ganv_box_default_set_height(GanvBox * box,double height)392 ganv_box_default_set_height(GanvBox* box, double height)
393 {
394 box->impl->coords.y2 = ganv_box_get_y1(box) + height;
395 ganv_item_request_update(GANV_ITEM(box));
396 }
397
398 static void
ganv_box_class_init(GanvBoxClass * klass)399 ganv_box_class_init(GanvBoxClass* klass)
400 {
401 GObjectClass* gobject_class = (GObjectClass*)klass;
402 GtkObjectClass* object_class = (GtkObjectClass*)klass;
403 GanvItemClass* item_class = (GanvItemClass*)klass;
404 GanvNodeClass* node_class = (GanvNodeClass*)klass;
405
406 parent_class = GANV_NODE_CLASS(g_type_class_peek_parent(klass));
407
408 gobject_class->set_property = ganv_box_set_property;
409 gobject_class->get_property = ganv_box_get_property;
410
411 g_object_class_install_property(
412 gobject_class, PROP_X1, g_param_spec_double(
413 "x1",
414 _("x1"),
415 _("Top left x coordinate."),
416 -G_MAXDOUBLE, G_MAXDOUBLE,
417 0.0,
418 G_PARAM_READWRITE));
419
420 g_object_class_install_property(
421 gobject_class, PROP_Y1, g_param_spec_double(
422 "y1",
423 _("y1"),
424 _("Top left y coordinate."),
425 -G_MAXDOUBLE, G_MAXDOUBLE,
426 0.0,
427 G_PARAM_READWRITE));
428
429 g_object_class_install_property(
430 gobject_class, PROP_X2, g_param_spec_double(
431 "x2",
432 _("x2"),
433 _("Bottom right x coordinate."),
434 -G_MAXDOUBLE, G_MAXDOUBLE,
435 0.0,
436 G_PARAM_READWRITE));
437
438 g_object_class_install_property(
439 gobject_class, PROP_Y2, g_param_spec_double(
440 "y2",
441 _("y2"),
442 _("Bottom right y coordinate."),
443 -G_MAXDOUBLE, G_MAXDOUBLE,
444 0.0,
445 G_PARAM_READWRITE));
446
447 g_object_class_install_property(
448 gobject_class, PROP_RADIUS_TL, g_param_spec_double(
449 "radius-tl",
450 _("Top left radius"),
451 _("The radius of the top left corner."),
452 0.0, G_MAXDOUBLE,
453 0.0,
454 G_PARAM_READWRITE));
455
456 g_object_class_install_property(
457 gobject_class, PROP_RADIUS_TR, g_param_spec_double(
458 "radius-tr",
459 _("Top right radius"),
460 _("The radius of the top right corner."),
461 0.0, G_MAXDOUBLE,
462 0.0,
463 G_PARAM_READWRITE));
464
465 g_object_class_install_property(
466 gobject_class, PROP_RADIUS_BR, g_param_spec_double(
467 "radius-br",
468 _("Bottom right radius"),
469 _("The radius of the bottom right corner."),
470 0.0, G_MAXDOUBLE,
471 0.0,
472 G_PARAM_READWRITE));
473
474 g_object_class_install_property(
475 gobject_class, PROP_RADIUS_BL, g_param_spec_double(
476 "radius-bl",
477 _("Bottom left radius"),
478 _("The radius of the bottom left corner."),
479 0.0, G_MAXDOUBLE,
480 0.0,
481 G_PARAM_READWRITE));
482
483 g_object_class_install_property(
484 gobject_class, PROP_STACKED, g_param_spec_boolean(
485 "stacked",
486 _("Stacked"),
487 _("Show box with a stacked appearance."),
488 FALSE,
489 G_PARAM_READWRITE));
490
491 g_object_class_install_property(
492 gobject_class, PROP_BEVELED, g_param_spec_boolean(
493 "beveled",
494 _("Beveled"),
495 _("Show radiused corners with a sharp bevel."),
496 FALSE,
497 G_PARAM_READWRITE));
498
499 object_class->destroy = ganv_box_destroy;
500
501 item_class->update = ganv_box_update;
502 item_class->bounds = ganv_box_bounds;
503 item_class->point = ganv_box_point;
504 item_class->draw = ganv_box_draw;
505
506 node_class->is_within = ganv_box_is_within;
507
508 klass->set_width = ganv_box_default_set_width;
509 klass->set_height = ganv_box_default_set_height;
510 }
511
512 void
ganv_box_normalize(GanvBox * box)513 ganv_box_normalize(GanvBox* box)
514 {
515 if (box->impl->coords.x2 < box->impl->coords.x1) {
516 const double tmp = box->impl->coords.x1;
517 box->impl->coords.x1 = box->impl->coords.x2;
518 box->impl->coords.x2 = tmp;
519 }
520 if (box->impl->coords.y2 < box->impl->coords.y1) {
521 const double tmp = box->impl->coords.y1;
522 box->impl->coords.y1 = box->impl->coords.y2;
523 box->impl->coords.y2 = tmp;
524 }
525 }
526
527 double
ganv_box_get_x1(const GanvBox * box)528 ganv_box_get_x1(const GanvBox* box)
529 {
530 return box->impl->coords.x1;
531 }
532
533 double
ganv_box_get_y1(const GanvBox * box)534 ganv_box_get_y1(const GanvBox* box)
535 {
536 return box->impl->coords.y1;
537 }
538
539 double
ganv_box_get_x2(const GanvBox * box)540 ganv_box_get_x2(const GanvBox* box)
541 {
542 return box->impl->coords.x2;
543 }
544
545 double
ganv_box_get_y2(const GanvBox * box)546 ganv_box_get_y2(const GanvBox* box)
547 {
548 return box->impl->coords.y2;
549 }
550
551 double
ganv_box_get_width(const GanvBox * box)552 ganv_box_get_width(const GanvBox* box)
553 {
554 return box->impl->coords.x2 - box->impl->coords.x1;
555 }
556
557 void
ganv_box_set_width(GanvBox * box,double width)558 ganv_box_set_width(GanvBox* box,
559 double width)
560 {
561 GANV_BOX_GET_CLASS(box)->set_width(box, width);
562 }
563
564 double
ganv_box_get_height(const GanvBox * box)565 ganv_box_get_height(const GanvBox* box)
566 {
567 return box->impl->coords.y2 - box->impl->coords.y1;
568 }
569
570 void
ganv_box_set_height(GanvBox * box,double height)571 ganv_box_set_height(GanvBox* box,
572 double height)
573 {
574 GANV_BOX_GET_CLASS(box)->set_height(box, height);
575 }
576
577 double
ganv_box_get_border_width(const GanvBox * box)578 ganv_box_get_border_width(const GanvBox* box)
579 {
580 return box->impl->coords.border_width;
581 }
582