1 /*
2 * Copyright © 2015 Red Hat Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20 #include "config.h"
21
22 #include "gtkcssgadgetprivate.h"
23
24 #include <math.h>
25
26 #include "gtkcssnumbervalueprivate.h"
27 #include "gtkcssshadowsvalueprivate.h"
28 #include "gtkcssstyleprivate.h"
29 #include "gtkcssstylepropertyprivate.h"
30 #include "gtkcsswidgetnodeprivate.h"
31 #include "gtkrenderbackgroundprivate.h"
32 #include "gtkrenderborderprivate.h"
33 #include "gtkdebug.h"
34 #include "gtkprivate.h"
35
36 /*
37 * Gadgets are 'next-generation widgets' - they combine a CSS node
38 * for style matching with geometry management and drawing. Each gadget
39 * corresponds to 'CSS box'. Compared to traditional widgets, they are more
40 * like building blocks - a typical GTK+ widget will have multiple gadgets,
41 * for example a check button has its main gadget, and sub-gadgets for
42 * the checkmark and the text.
43 *
44 * Gadgets are not themselves hierarchically organized, but it is common
45 * to have a 'main' gadget, which gets used by the widgets size_allocate,
46 * get_preferred_width, etc. and draw callbacks, and which in turn calls out
47 * to the sub-gadgets. This call tree might extend further if there are
48 * sub-sub-gadgets that a allocated relative to sub-gadgets. In typical
49 * situations, the callback chain will reflect the tree structure of the
50 * gadgets CSS nodes.
51 *
52 * Geometry management - Gadgets implement much of the CSS box model for you:
53 * margins, border, padding, shadows, min-width/height are all applied automatically.
54 *
55 * Drawing - Gadgets implement standardized CSS drawing for you: background,
56 * shadows and border are drawn before any custom drawing, and the focus outline
57 * is (optionally) drawn afterwards.
58 *
59 * Invalidation - Gadgets sit 'between' widgets and CSS nodes, and connect
60 * to the nodes ::style-changed signal and trigger appropriate invalidations
61 * on the widget side.
62 */
63
64 typedef struct _GtkCssGadgetPrivate GtkCssGadgetPrivate;
65 struct _GtkCssGadgetPrivate {
66 GtkCssNode *node;
67 GtkWidget *owner;
68 GtkAllocation allocated_size;
69 gint allocated_baseline;
70 };
71
72 enum {
73 PROP_0,
74 PROP_NODE,
75 PROP_OWNER,
76 /* add more */
77 NUM_PROPERTIES
78 };
79
80 static GParamSpec *properties[NUM_PROPERTIES];
81
G_DEFINE_ABSTRACT_TYPE_WITH_CODE(GtkCssGadget,gtk_css_gadget,G_TYPE_OBJECT,G_ADD_PRIVATE (GtkCssGadget))82 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkCssGadget, gtk_css_gadget, G_TYPE_OBJECT,
83 G_ADD_PRIVATE (GtkCssGadget))
84
85 static void
86 gtk_css_gadget_real_get_preferred_size (GtkCssGadget *gadget,
87 GtkOrientation orientation,
88 gint for_size,
89 gint *minimum,
90 gint *natural,
91 gint *minimum_baseline,
92 gint *natural_baseline)
93 {
94 *minimum = 0;
95 *natural = 0;
96
97 if (minimum_baseline)
98 *minimum_baseline = 0;
99 if (natural_baseline)
100 *natural_baseline = 0;
101 }
102
103 static void
gtk_css_gadget_real_allocate(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip)104 gtk_css_gadget_real_allocate (GtkCssGadget *gadget,
105 const GtkAllocation *allocation,
106 int baseline,
107 GtkAllocation *out_clip)
108 {
109 *out_clip = *allocation;
110 }
111
112 static gboolean
gtk_css_gadget_real_draw(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height)113 gtk_css_gadget_real_draw (GtkCssGadget *gadget,
114 cairo_t *cr,
115 int x,
116 int y,
117 int width,
118 int height)
119 {
120 return FALSE;
121 }
122
123 static void
gtk_css_gadget_real_style_changed(GtkCssGadget * gadget,GtkCssStyleChange * change)124 gtk_css_gadget_real_style_changed (GtkCssGadget *gadget,
125 GtkCssStyleChange *change)
126 {
127 if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_SIZE))
128 gtk_css_gadget_queue_resize (gadget);
129 else if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_CLIP))
130 gtk_css_gadget_queue_allocate (gadget);
131 else if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_REDRAW))
132 gtk_css_gadget_queue_draw (gadget);
133 }
134
135 static void
gtk_css_gadget_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)136 gtk_css_gadget_get_property (GObject *object,
137 guint property_id,
138 GValue *value,
139 GParamSpec *pspec)
140 {
141 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (GTK_CSS_GADGET (object));
142
143 switch (property_id)
144 {
145 case PROP_NODE:
146 g_value_set_object (value, priv->node);
147 break;
148
149 case PROP_OWNER:
150 g_value_set_object (value, priv->owner);
151 break;
152
153 default:
154 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
155 break;
156 }
157 }
158
159 static void
gtk_css_gadget_node_style_changed_cb(GtkCssNode * node,GtkCssStyleChange * change,GtkCssGadget * gadget)160 gtk_css_gadget_node_style_changed_cb (GtkCssNode *node,
161 GtkCssStyleChange *change,
162 GtkCssGadget *gadget)
163 {
164 GtkCssGadgetClass *klass = GTK_CSS_GADGET_GET_CLASS (gadget);
165
166 klass->style_changed (gadget, change);
167 }
168
169 static gboolean
gtk_css_gadget_should_connect_style_changed(GtkCssNode * node)170 gtk_css_gadget_should_connect_style_changed (GtkCssNode *node)
171 {
172 /* Delegate to WidgetClass->style_changed */
173 if (GTK_IS_CSS_WIDGET_NODE (node))
174 return FALSE;
175
176 return TRUE;
177 }
178
179 static void
gtk_css_gadget_unset_node(GtkCssGadget * gadget)180 gtk_css_gadget_unset_node (GtkCssGadget *gadget)
181 {
182 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
183
184 if (priv->node)
185 {
186 if (gtk_css_gadget_should_connect_style_changed (priv->node))
187 {
188 if (g_signal_handlers_disconnect_by_func (priv->node, gtk_css_gadget_node_style_changed_cb, gadget) != 1)
189 {
190 g_assert_not_reached ();
191 }
192 }
193 g_object_unref (priv->node);
194 priv->node = NULL;
195 }
196 }
197
198 void
gtk_css_gadget_set_node(GtkCssGadget * gadget,GtkCssNode * node)199 gtk_css_gadget_set_node (GtkCssGadget *gadget,
200 GtkCssNode *node)
201 {
202 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
203
204 gtk_css_gadget_unset_node (gadget);
205
206 if (node != NULL)
207 priv->node = g_object_ref (node);
208 else
209 priv->node = gtk_css_node_new ();
210
211 if (gtk_css_gadget_should_connect_style_changed (priv->node))
212 {
213 g_signal_connect_after (priv->node,
214 "style-changed",
215 G_CALLBACK (gtk_css_gadget_node_style_changed_cb),
216 gadget);
217 }
218 }
219
220 static void
gtk_css_gadget_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)221 gtk_css_gadget_set_property (GObject *object,
222 guint property_id,
223 const GValue *value,
224 GParamSpec *pspec)
225 {
226 GtkCssGadget *gadget = GTK_CSS_GADGET (object);
227 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
228
229 switch (property_id)
230 {
231 case PROP_NODE:
232 gtk_css_gadget_set_node (gadget, g_value_get_object (value));
233 break;
234
235 case PROP_OWNER:
236 priv->owner = g_value_get_object (value);
237 break;
238
239 default:
240 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
241 break;
242 }
243 }
244
245
246 static void
gtk_css_gadget_finalize(GObject * object)247 gtk_css_gadget_finalize (GObject *object)
248 {
249 GtkCssGadget *gadget = GTK_CSS_GADGET (object);
250
251 gtk_css_gadget_unset_node (gadget);
252
253 G_OBJECT_CLASS (gtk_css_gadget_parent_class)->finalize (object);
254 }
255
256 static void
gtk_css_gadget_class_init(GtkCssGadgetClass * klass)257 gtk_css_gadget_class_init (GtkCssGadgetClass *klass)
258 {
259 GObjectClass *object_class = G_OBJECT_CLASS (klass);
260
261 object_class->get_property = gtk_css_gadget_get_property;
262 object_class->set_property = gtk_css_gadget_set_property;
263 object_class->finalize = gtk_css_gadget_finalize;
264
265 klass->get_preferred_size = gtk_css_gadget_real_get_preferred_size;
266 klass->allocate = gtk_css_gadget_real_allocate;
267 klass->draw = gtk_css_gadget_real_draw;
268 klass->style_changed = gtk_css_gadget_real_style_changed;
269
270 properties[PROP_NODE] = g_param_spec_object ("node", "Node",
271 "CSS node",
272 GTK_TYPE_CSS_NODE,
273 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
274 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
275 properties[PROP_OWNER] = g_param_spec_object ("owner", "Owner",
276 "Widget that created and owns this gadget",
277 GTK_TYPE_WIDGET,
278 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
279 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
280
281
282 g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
283 }
284
285 static void
gtk_css_gadget_init(GtkCssGadget * gadget)286 gtk_css_gadget_init (GtkCssGadget *gadget)
287 {
288 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
289
290 priv->allocated_size.width = -1;
291 priv->allocated_size.height = -1;
292 priv->allocated_baseline = -1;
293 }
294
295 /**
296 * gtk_css_gadget_get_node:
297 * @gadget: a #GtkCssGadget
298 *
299 * Get the CSS node for this gadget.
300 *
301 * Returns: (transfer none): the CSS node
302 */
303 GtkCssNode *
gtk_css_gadget_get_node(GtkCssGadget * gadget)304 gtk_css_gadget_get_node (GtkCssGadget *gadget)
305 {
306 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
307
308 return priv->node;
309 }
310
311 /**
312 * gtk_css_gadget_get_style:
313 * @gadget: a #GtkCssGadget
314 *
315 * Get the CSS style for this gadget.
316 *
317 * Returns: (transfer none): the CSS style
318 */
319 GtkCssStyle *
gtk_css_gadget_get_style(GtkCssGadget * gadget)320 gtk_css_gadget_get_style (GtkCssGadget *gadget)
321 {
322 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
323
324 return gtk_css_node_get_style (priv->node);
325 }
326
327 /**
328 * gtk_css_gadget_get_owner:
329 * @gadget: a #GtkCssGadget
330 *
331 * Get the widget to which this gadget belongs.
332 *
333 * Returns: (transfer none): the widget to which @gadget belongs
334 */
335 GtkWidget *
gtk_css_gadget_get_owner(GtkCssGadget * gadget)336 gtk_css_gadget_get_owner (GtkCssGadget *gadget)
337 {
338 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
339
340 return priv->owner;
341 }
342
343 void
gtk_css_gadget_set_visible(GtkCssGadget * gadget,gboolean visible)344 gtk_css_gadget_set_visible (GtkCssGadget *gadget,
345 gboolean visible)
346 {
347 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
348
349 gtk_css_node_set_visible (priv->node, visible);
350 }
351
352 gboolean
gtk_css_gadget_get_visible(GtkCssGadget * gadget)353 gtk_css_gadget_get_visible (GtkCssGadget *gadget)
354 {
355 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
356
357 return gtk_css_node_get_visible (priv->node);
358 }
359
360 /**
361 * gtk_css_gadget_add_class:
362 * @gadget: a #GtkCssGadget
363 * @name: class name to use in CSS matching
364 *
365 * Adds a style class to the gadgets CSS node.
366 */
367 void
gtk_css_gadget_add_class(GtkCssGadget * gadget,const char * name)368 gtk_css_gadget_add_class (GtkCssGadget *gadget,
369 const char *name)
370 {
371 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
372 GQuark quark;
373
374 quark = g_quark_from_string (name);
375
376 gtk_css_node_add_class (priv->node, quark);
377 }
378
379 /**
380 * gtk_css_gadget_remove_class:
381 * @gadget: a #GtkCssGadget
382 * @name: class name
383 *
384 * Removes a style class from the gadgets CSS node.
385 */
386 void
gtk_css_gadget_remove_class(GtkCssGadget * gadget,const char * name)387 gtk_css_gadget_remove_class (GtkCssGadget *gadget,
388 const char *name)
389 {
390 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
391 GQuark quark;
392
393 quark = g_quark_try_string (name);
394 if (quark == 0)
395 return;
396
397 gtk_css_node_remove_class (priv->node, quark);
398 }
399
400 /**
401 * gtk_css_gadget_set_state:
402 * @gadget: a #GtkCssGadget
403 * @state: The new state
404 *
405 * Sets the state of the gadget's CSS node.
406 */
407 void
gtk_css_gadget_set_state(GtkCssGadget * gadget,GtkStateFlags state)408 gtk_css_gadget_set_state (GtkCssGadget *gadget,
409 GtkStateFlags state)
410 {
411 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
412
413 gtk_css_node_set_state (priv->node, state);
414 }
415
416 /**
417 * gtk_css_gadget_add_state:
418 * @gadget: a #GtkCssGadget
419 * @state: The state to add
420 *
421 * Adds the given states to the states of gadget's CSS node. Other states
422 * will be kept as they are.
423 */
424 void
gtk_css_gadget_add_state(GtkCssGadget * gadget,GtkStateFlags state)425 gtk_css_gadget_add_state (GtkCssGadget *gadget,
426 GtkStateFlags state)
427 {
428 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
429
430 gtk_css_node_set_state (priv->node, gtk_css_node_get_state (priv->node) | state);
431 }
432
433 /**
434 * gtk_css_gadget_remove_state:
435 * @gadget: a #GtkCssGadget
436 * @state: The state to remove
437 *
438 * Adds the given states to the states of gadget's CSS node. Other states
439 * will be kept as they are.
440 */
441 void
gtk_css_gadget_remove_state(GtkCssGadget * gadget,GtkStateFlags state)442 gtk_css_gadget_remove_state (GtkCssGadget *gadget,
443 GtkStateFlags state)
444 {
445 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
446
447 gtk_css_node_set_state (priv->node, gtk_css_node_get_state (priv->node) & ~state);
448 }
449
450 static gint
get_number(GtkCssStyle * style,guint property)451 get_number (GtkCssStyle *style,
452 guint property)
453 {
454 double d = _gtk_css_number_value_get (gtk_css_style_get_value (style, property), 100);
455
456 if (d < 1)
457 return ceil (d);
458 else
459 return floor (d);
460 }
461
462 /* Special-case min-width|height to round upwards, to avoid underalloc by 1px */
463 static int
get_number_ceil(GtkCssStyle * style,guint property)464 get_number_ceil (GtkCssStyle *style,
465 guint property)
466 {
467 return ceil (_gtk_css_number_value_get (gtk_css_style_get_value (style, property), 100));
468 }
469
470 static void
get_box_margin(GtkCssStyle * style,GtkBorder * margin)471 get_box_margin (GtkCssStyle *style,
472 GtkBorder *margin)
473 {
474 margin->top = get_number (style, GTK_CSS_PROPERTY_MARGIN_TOP);
475 margin->left = get_number (style, GTK_CSS_PROPERTY_MARGIN_LEFT);
476 margin->bottom = get_number (style, GTK_CSS_PROPERTY_MARGIN_BOTTOM);
477 margin->right = get_number (style, GTK_CSS_PROPERTY_MARGIN_RIGHT);
478 }
479
480 static void
get_box_border(GtkCssStyle * style,GtkBorder * border)481 get_box_border (GtkCssStyle *style,
482 GtkBorder *border)
483 {
484 border->top = get_number (style, GTK_CSS_PROPERTY_BORDER_TOP_WIDTH);
485 border->left = get_number (style, GTK_CSS_PROPERTY_BORDER_LEFT_WIDTH);
486 border->bottom = get_number (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_WIDTH);
487 border->right = get_number (style, GTK_CSS_PROPERTY_BORDER_RIGHT_WIDTH);
488 }
489
490 static void
get_box_padding(GtkCssStyle * style,GtkBorder * border)491 get_box_padding (GtkCssStyle *style,
492 GtkBorder *border)
493 {
494 border->top = get_number (style, GTK_CSS_PROPERTY_PADDING_TOP);
495 border->left = get_number (style, GTK_CSS_PROPERTY_PADDING_LEFT);
496 border->bottom = get_number (style, GTK_CSS_PROPERTY_PADDING_BOTTOM);
497 border->right = get_number (style, GTK_CSS_PROPERTY_PADDING_RIGHT);
498 }
499
500 static gboolean
allocation_contains_point(GtkAllocation * allocation,gint x,gint y)501 allocation_contains_point (GtkAllocation *allocation,
502 gint x,
503 gint y)
504 {
505 return (x >= allocation->x) &&
506 (x < allocation->x + allocation->width) &&
507 (y >= allocation->y) &&
508 (y < allocation->y + allocation->height);
509 }
510
511 static void
shift_allocation(GtkCssGadget * gadget,GtkAllocation * allocation)512 shift_allocation (GtkCssGadget *gadget,
513 GtkAllocation *allocation)
514 {
515 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
516
517 if (priv->owner && !gtk_widget_get_has_window (priv->owner))
518 {
519 GtkAllocation widget_alloc;
520 gtk_widget_get_allocation (priv->owner, &widget_alloc);
521 allocation->x -= widget_alloc.x;
522 allocation->y -= widget_alloc.y;
523 }
524 }
525
526 /**
527 * gtk_css_gadget_margin_box_contains_point:
528 * @gadget: the #GtkCssGadget being tested
529 * @x: X coordinate of the testing point
530 * @y: Y coordinate of the testing point
531 *
532 * Checks whether the point at the provided coordinates is contained within the
533 * margin box of the gadget. The (X, Y) are relative to the gadget
534 * origin.
535 *
536 * Returns: %TRUE if the point at (X, Y) is contained within the margin
537 * box of the @gadget.
538 */
539 gboolean
gtk_css_gadget_margin_box_contains_point(GtkCssGadget * gadget,int x,int y)540 gtk_css_gadget_margin_box_contains_point (GtkCssGadget *gadget,
541 int x,
542 int y)
543 {
544 GtkAllocation margin_box = { 0, };
545 gtk_css_gadget_get_margin_box (gadget, &margin_box);
546 return allocation_contains_point (&margin_box, x, y);
547 }
548
549 /**
550 * gtk_css_gadget_border_box_contains_point:
551 * @gadget: the #GtkCssGadget being tested
552 * @x: X coordinate of the testing point
553 * @y: Y coordinate of the testing point
554 *
555 * Checks whether the point at the provided coordinates is contained within the
556 * border box of the gadget. The (X, Y) are relative to the gadget
557 * origin.
558 *
559 * Returns: %TRUE if the point at (X, Y) is contained within the border
560 * box of the @gadget.
561 */
562 gboolean
gtk_css_gadget_border_box_contains_point(GtkCssGadget * gadget,int x,int y)563 gtk_css_gadget_border_box_contains_point (GtkCssGadget *gadget,
564 int x,
565 int y)
566 {
567 GtkAllocation border_box;
568 gtk_css_gadget_get_border_box (gadget, &border_box);
569 return allocation_contains_point (&border_box, x, y);
570 }
571
572 /**
573 * gtk_css_gadget_content_box_contains_point:
574 * @gadget: the #GtkCssGadget being tested
575 * @x: X coordinate of the testing point
576 * @y: Y coordinate of the testing point
577 *
578 * Checks whether the point at the provided coordinates is contained within the
579 * content box of the gadget. The (X, Y) are relative to the gadget
580 * origin.
581 *
582 * Returns: %TRUE if the point at (X, Y) is contained within the content
583 * box of the @gadget.
584 */
585 gboolean
gtk_css_gadget_content_box_contains_point(GtkCssGadget * gadget,int x,int y)586 gtk_css_gadget_content_box_contains_point (GtkCssGadget *gadget,
587 int x,
588 int y)
589 {
590 GtkAllocation content_box;
591 gtk_css_gadget_get_content_box (gadget, &content_box);
592 return allocation_contains_point (&content_box, x, y);
593 }
594
595 /**
596 * gtk_css_gadget_get_preferred_size:
597 * @gadget: the #GtkCssGadget whose size is requested
598 * @orientation: whether a width (ie horizontal) or height (ie vertical) size is requested
599 * @for_size: the available size in the opposite direction, or -1
600 * @minimum: (nullable): return location for the minimum size
601 * @natural: (nullable): return location for the natural size
602 * @minimum_baseline: (nullable): return location for the baseline at minimum size
603 * @natural_baseline: (nullable): return location for the baseline at natural size
604 *
605 * Gets the gadgets minimum and natural size (and, optionally, baseline)
606 * in the given orientation for the specified size in the opposite direction.
607 *
608 * The returned values include CSS padding, border and margin in addition to the
609 * gadgets content size, and respect the CSS min-with or min-height properties.
610 *
611 * The @for_size is assumed to include CSS padding, border and margins as well.
612 */
613 void
gtk_css_gadget_get_preferred_size(GtkCssGadget * gadget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline)614 gtk_css_gadget_get_preferred_size (GtkCssGadget *gadget,
615 GtkOrientation orientation,
616 gint for_size,
617 gint *minimum,
618 gint *natural,
619 gint *minimum_baseline,
620 gint *natural_baseline)
621 {
622 GtkCssStyle *style;
623 GtkBorder margin, border, padding;
624 int min_size, extra_size, extra_opposite, extra_baseline;
625 int unused_minimum, unused_natural;
626 int forced_minimum, forced_natural;
627 int min_for_size;
628
629 if (minimum == NULL)
630 minimum = &unused_minimum;
631 if (natural == NULL)
632 natural = &unused_natural;
633
634 if (!gtk_css_gadget_get_visible (gadget))
635 {
636 *minimum = 0;
637 *natural = 0;
638 if (minimum_baseline)
639 *minimum_baseline = -1;
640 if (natural_baseline)
641 *natural_baseline = -1;
642 return;
643 }
644
645 style = gtk_css_gadget_get_style (gadget);
646 get_box_margin (style, &margin);
647 get_box_border (style, &border);
648 get_box_padding (style, &padding);
649 if (orientation == GTK_ORIENTATION_HORIZONTAL)
650 {
651 extra_size = margin.left + margin.right + border.left + border.right + padding.left + padding.right;
652 extra_opposite = margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom;
653 extra_baseline = margin.left + border.left + padding.left;
654 min_size = get_number_ceil (style, GTK_CSS_PROPERTY_MIN_WIDTH);
655 min_for_size = get_number_ceil (style, GTK_CSS_PROPERTY_MIN_HEIGHT);
656 }
657 else
658 {
659 extra_size = margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom;
660 extra_opposite = margin.left + margin.right + border.left + border.right + padding.left + padding.right;
661 extra_baseline = margin.top + border.top + padding.top;
662 min_size = get_number_ceil (style, GTK_CSS_PROPERTY_MIN_HEIGHT);
663 min_for_size = get_number_ceil (style, GTK_CSS_PROPERTY_MIN_WIDTH);
664 }
665
666 if (for_size > -1)
667 {
668 if (for_size < min_for_size)
669 g_warning ("for_size smaller than min-size (%d < %d) "
670 "while measuring gadget (node %s, owner %s)",
671 for_size, min_for_size,
672 gtk_css_node_get_name (gtk_css_gadget_get_node (gadget)),
673 G_OBJECT_TYPE_NAME (gtk_css_gadget_get_owner (gadget)));
674
675 for_size = MAX (0, for_size - extra_opposite);
676 }
677
678 if (minimum_baseline)
679 *minimum_baseline = -1;
680 if (natural_baseline)
681 *natural_baseline = -1;
682
683 GTK_CSS_GADGET_GET_CLASS (gadget)->get_preferred_size (gadget,
684 orientation,
685 for_size,
686 minimum, natural,
687 minimum_baseline, natural_baseline);
688
689 g_warn_if_fail (*minimum <= *natural);
690
691 forced_minimum = MAX (*minimum, min_size);
692 forced_natural = MAX (*natural, min_size);
693
694 if (minimum_baseline && *minimum_baseline > -1)
695 {
696 *minimum_baseline += 0.5 * (forced_minimum - *minimum);
697 *minimum_baseline = MAX (0, *minimum_baseline + extra_baseline);
698 }
699 if (natural_baseline && *natural_baseline > -1)
700 {
701 *natural_baseline += 0.5 * (forced_natural - *natural);
702 *natural_baseline = MAX (0, *natural_baseline + extra_baseline);
703 }
704
705 *minimum = MAX (0, forced_minimum + extra_size);
706 *natural = MAX (0, forced_natural + extra_size);
707
708 }
709
710 /**
711 * gtk_css_gadget_allocate:
712 * @gadget: the #GtkCssGadget to allocate
713 * @allocation: the allocation
714 * @baseline: the baseline for the allocation
715 * @out_clip: (out): return location for the gadgets clip region
716 *
717 * Allocates the gadget.
718 *
719 * The @allocation is assumed to include CSS padding, border and margin.
720 * The gadget content will be allocated a smaller area that excludes these.
721 * The @out_clip includes the shadow extents of the gadget in addition to
722 * any content clip.
723 */
724 void
gtk_css_gadget_allocate(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip)725 gtk_css_gadget_allocate (GtkCssGadget *gadget,
726 const GtkAllocation *allocation,
727 int baseline,
728 GtkAllocation *out_clip)
729 {
730 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
731 GtkAllocation content_allocation;
732 GtkAllocation tmp_clip;
733 GtkAllocation content_clip = { 0, 0, 0, 0 };
734 GtkBorder margin, border, padding, shadow, extents;
735 GtkCssStyle *style;
736
737 g_return_if_fail (out_clip != NULL);
738
739 if (!gtk_css_gadget_get_visible (gadget))
740 {
741 out_clip->x = 0;
742 out_clip->y = 0;
743 out_clip->width = 0;
744 out_clip->height = 0;
745 return;
746 }
747
748 priv->allocated_size = *allocation;
749 priv->allocated_baseline = baseline;
750
751 style = gtk_css_gadget_get_style (gadget);
752 get_box_margin (style, &margin);
753 get_box_border (style, &border);
754 get_box_padding (style, &padding);
755 extents.top = margin.top + border.top + padding.top;
756 extents.right = margin.right + border.right + padding.right;
757 extents.bottom = margin.bottom + border.bottom + padding.bottom;
758 extents.left = margin.left + border.left + padding.left;
759
760 content_allocation.x = allocation->x + extents.left;
761 content_allocation.y = allocation->y + extents.top;
762 content_allocation.width = allocation->width - extents.left - extents.right;
763 content_allocation.height = allocation->height - extents.top - extents.bottom;
764
765 if (baseline >= 0)
766 baseline -= extents.top;
767
768 if (content_allocation.width < 0)
769 {
770 g_warning ("Negative content width %d (allocation %d, extents %dx%d) "
771 "while allocating gadget (node %s, owner %s)",
772 content_allocation.width, allocation->width,
773 extents.left, extents.right,
774 gtk_css_node_get_name (gtk_css_gadget_get_node (gadget)),
775 G_OBJECT_TYPE_NAME (gtk_css_gadget_get_owner (gadget)));
776 content_allocation.width = 0;
777 }
778
779 if (content_allocation.height < 0)
780 {
781 g_warning ("Negative content height %d (allocation %d, extents %dx%d) "
782 "while allocating gadget (node %s, owner %s)",
783 content_allocation.height, allocation->height,
784 extents.top, extents.bottom,
785 gtk_css_node_get_name (gtk_css_gadget_get_node (gadget)),
786 G_OBJECT_TYPE_NAME (gtk_css_gadget_get_owner (gadget)));
787 content_allocation.height = 0;
788 }
789
790 GTK_CSS_GADGET_GET_CLASS (gadget)->allocate (gadget, &content_allocation, baseline, &content_clip);
791
792 _gtk_css_shadows_value_get_extents (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BOX_SHADOW), &shadow);
793
794 out_clip->x = allocation->x + margin.left - shadow.left;
795 out_clip->y = allocation->y + margin.top - shadow.top;
796 out_clip->width = MAX (0, allocation->width - margin.left - margin.right + shadow.left + shadow.right);
797 out_clip->height = MAX (0, allocation->height - margin.top - margin.bottom + shadow.top + shadow.bottom);
798
799 if (content_clip.width > 0 && content_clip.height > 0)
800 gdk_rectangle_union (&content_clip, out_clip, out_clip);
801
802 if (gtk_css_style_render_outline_get_clip (style,
803 allocation->x + margin.left,
804 allocation->y + margin.top,
805 allocation->width - margin.left - margin.right,
806 allocation->height - margin.top - margin.bottom,
807 &tmp_clip))
808 gdk_rectangle_union (&tmp_clip, out_clip, out_clip);
809 }
810
811 /**
812 * gtk_css_gadget_draw:
813 * @gadget: The gadget to draw
814 * @cr: The cairo context to draw to
815 *
816 * Will draw the gadget at the position allocated via
817 * gtk_css_gadget_allocate(). It is your responsibility to make
818 * sure that those 2 coordinate systems match.
819 *
820 * The drawing virtual function will be passed an untransformed @cr.
821 * This is important because functions like
822 * gtk_container_propagate_draw() depend on that.
823 */
824 void
gtk_css_gadget_draw(GtkCssGadget * gadget,cairo_t * cr)825 gtk_css_gadget_draw (GtkCssGadget *gadget,
826 cairo_t *cr)
827 {
828 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
829 GtkBorder margin, border, padding;
830 gboolean draw_focus = FALSE;
831 GtkCssStyle *style;
832 int x, y, width, height;
833 int contents_x, contents_y, contents_width, contents_height;
834 GtkAllocation margin_box;
835
836 if (!gtk_css_gadget_get_visible (gadget))
837 return;
838
839 gtk_css_gadget_get_margin_box (gadget, &margin_box);
840
841 x = margin_box.x;
842 y = margin_box.y;
843 width = margin_box.width;
844 height = margin_box.height;
845
846 if (width < 0 || height < 0)
847 {
848 g_warning ("Drawing a gadget with negative dimensions. "
849 "Did you forget to allocate a size? (node %s owner %s)",
850 gtk_css_node_get_name (gtk_css_gadget_get_node (gadget)),
851 G_OBJECT_TYPE_NAME (gtk_css_gadget_get_owner (gadget)));
852 x = 0;
853 y = 0;
854 width = gtk_widget_get_allocated_width (priv->owner);
855 height = gtk_widget_get_allocated_height (priv->owner);
856 }
857
858 style = gtk_css_gadget_get_style (gadget);
859 get_box_margin (style, &margin);
860 get_box_border (style, &border);
861 get_box_padding (style, &padding);
862
863 gtk_css_style_render_background (style,
864 cr,
865 x + margin.left,
866 y + margin.top,
867 width - margin.left - margin.right,
868 height - margin.top - margin.bottom,
869 gtk_css_node_get_junction_sides (priv->node));
870 gtk_css_style_render_border (style,
871 cr,
872 x + margin.left,
873 y + margin.top,
874 width - margin.left - margin.right,
875 height - margin.top - margin.bottom,
876 0,
877 gtk_css_node_get_junction_sides (priv->node));
878
879 contents_x = x + margin.left + border.left + padding.left;
880 contents_y = y + margin.top + border.top + padding.top;
881 contents_width = width - margin.left - margin.right - border.left - border.right - padding.left - padding.right;
882 contents_height = height - margin.top - margin.bottom - border.top - border.bottom - padding.top - padding.bottom;
883
884 if (contents_width > 0 && contents_height > 0)
885 draw_focus = GTK_CSS_GADGET_GET_CLASS (gadget)->draw (gadget,
886 cr,
887 contents_x, contents_y,
888 contents_width, contents_height);
889
890 if (draw_focus)
891 gtk_css_style_render_outline (style,
892 cr,
893 x + margin.left,
894 y + margin.top,
895 width - margin.left - margin.right,
896 height - margin.top - margin.bottom);
897
898 #ifdef G_ENABLE_DEBUG
899 {
900 GdkDisplay *display = gtk_widget_get_display (gtk_css_gadget_get_owner (gadget));
901 GtkDebugFlag flags = gtk_get_display_debug_flags (display);
902 if G_UNLIKELY (flags & GTK_DEBUG_LAYOUT)
903 {
904 cairo_save (cr);
905 cairo_new_path (cr);
906 cairo_rectangle (cr,
907 x + margin.left,
908 y + margin.top,
909 width - margin.left - margin.right,
910 height - margin.top - margin.bottom);
911 cairo_set_line_width (cr, 1.0);
912 cairo_set_source_rgba (cr, 0, 0, 1.0, 0.33);
913 cairo_stroke (cr);
914 cairo_rectangle (cr,
915 contents_x,
916 contents_y,
917 contents_width,
918 contents_height);
919 cairo_set_line_width (cr, 1.0);
920 cairo_set_source_rgba (cr, 1.0, 0, 1.0, 0.33);
921 cairo_stroke (cr);
922 cairo_restore (cr);
923 }
924 if G_UNLIKELY (flags & GTK_DEBUG_BASELINES)
925 {
926 int baseline = priv->allocated_baseline;
927
928 if (baseline != -1)
929 {
930 if (priv->owner && !gtk_widget_get_has_window (priv->owner))
931 {
932 GtkAllocation widget_alloc;
933 gtk_widget_get_allocation (priv->owner, &widget_alloc);
934 baseline -= widget_alloc.y;
935 }
936 cairo_save (cr);
937 cairo_new_path (cr);
938 cairo_move_to (cr, x + margin.left, baseline + 0.5);
939 cairo_rel_line_to (cr, width - margin.left - margin.right, 0);
940 cairo_set_line_width (cr, 1.0);
941 cairo_set_source_rgba (cr, 1.0, 0, 0.25, 0.25);
942 cairo_stroke (cr);
943 cairo_restore (cr);
944 }
945 }
946 }
947 #endif
948 }
949
950 void
gtk_css_gadget_queue_resize(GtkCssGadget * gadget)951 gtk_css_gadget_queue_resize (GtkCssGadget *gadget)
952 {
953 g_return_if_fail (GTK_IS_CSS_GADGET (gadget));
954
955 gtk_widget_queue_resize (gtk_css_gadget_get_owner (gadget));
956 }
957
958 void
gtk_css_gadget_queue_allocate(GtkCssGadget * gadget)959 gtk_css_gadget_queue_allocate (GtkCssGadget *gadget)
960 {
961 g_return_if_fail (GTK_IS_CSS_GADGET (gadget));
962
963 gtk_widget_queue_allocate (gtk_css_gadget_get_owner (gadget));
964 }
965
966 void
gtk_css_gadget_queue_draw(GtkCssGadget * gadget)967 gtk_css_gadget_queue_draw (GtkCssGadget *gadget)
968 {
969 g_return_if_fail (GTK_IS_CSS_GADGET (gadget));
970
971 /* XXX: Only invalidate clip here */
972 gtk_widget_queue_draw (gtk_css_gadget_get_owner (gadget));
973 }
974
975 /**
976 * gtk_css_gadget_get_margin_box:
977 * @gadget: a #GtkCssGadget
978 * @box: (out): Return location for gadget's the margin box
979 *
980 * Returns the margin box of the gadget. The box coordinates are relative to
981 * the gadget origin. Compare with gtk_css_gadget_get_margin_allocation(),
982 * which returns the margin box in the widget allocation coordinates.
983 */
984 void
gtk_css_gadget_get_margin_box(GtkCssGadget * gadget,GtkAllocation * box)985 gtk_css_gadget_get_margin_box (GtkCssGadget *gadget,
986 GtkAllocation *box)
987 {
988 gtk_css_gadget_get_margin_allocation (gadget, box, NULL);
989 shift_allocation (gadget, box);
990 }
991
992 /**
993 * gtk_css_gadget_get_border_box:
994 * @gadget: a #GtkCssGadget
995 * @box: (out): Return location for gadget's the border box
996 *
997 * Returns the border box of the gadget. The box coordinates are relative to
998 * the gadget origin. Compare with gtk_css_gadget_get_border_allocation(),
999 * which returns the border box in the widget allocation coordinates.
1000 */
1001 void
gtk_css_gadget_get_border_box(GtkCssGadget * gadget,GtkAllocation * box)1002 gtk_css_gadget_get_border_box (GtkCssGadget *gadget,
1003 GtkAllocation *box)
1004 {
1005 gtk_css_gadget_get_border_allocation (gadget, box, NULL);
1006 shift_allocation (gadget, box);
1007 }
1008
1009 /**
1010 * gtk_css_gadget_get_content_box:
1011 * @gadget: a #GtkCssGadget
1012 * @box: (out): Return location for gadget's the content box
1013 *
1014 * Returns the content box of the gadget. The box coordinates are relative to
1015 * the gadget origin. Compare with gtk_css_gadget_get_content_allocation(),
1016 * which returns the content box in the widget allocation coordinates.
1017 */
1018 void
gtk_css_gadget_get_content_box(GtkCssGadget * gadget,GtkAllocation * box)1019 gtk_css_gadget_get_content_box (GtkCssGadget *gadget,
1020 GtkAllocation *box)
1021 {
1022 gtk_css_gadget_get_content_allocation (gadget, box, NULL);
1023 shift_allocation (gadget, box);
1024 }
1025
1026 void
gtk_css_gadget_get_margin_allocation(GtkCssGadget * gadget,GtkAllocation * allocation,int * baseline)1027 gtk_css_gadget_get_margin_allocation (GtkCssGadget *gadget,
1028 GtkAllocation *allocation,
1029 int *baseline)
1030 {
1031 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
1032
1033 g_return_if_fail (GTK_IS_CSS_GADGET (gadget));
1034
1035 if (!gtk_css_gadget_get_visible (gadget))
1036 {
1037 if (allocation)
1038 allocation->x = allocation->y = allocation->width = allocation->height = 0;
1039 if (baseline)
1040 *baseline = -1;
1041 return;
1042 }
1043
1044 if (allocation)
1045 *allocation = priv->allocated_size;
1046 if (baseline)
1047 *baseline = priv->allocated_baseline;
1048 }
1049
1050 void
gtk_css_gadget_get_border_allocation(GtkCssGadget * gadget,GtkAllocation * allocation,int * baseline)1051 gtk_css_gadget_get_border_allocation (GtkCssGadget *gadget,
1052 GtkAllocation *allocation,
1053 int *baseline)
1054 {
1055 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
1056 GtkBorder margin;
1057
1058 g_return_if_fail (GTK_IS_CSS_GADGET (gadget));
1059
1060 if (!gtk_css_gadget_get_visible (gadget))
1061 {
1062 if (allocation)
1063 allocation->x = allocation->y = allocation->width = allocation->height = 0;
1064 if (baseline)
1065 *baseline = -1;
1066 return;
1067 }
1068
1069 get_box_margin (gtk_css_gadget_get_style (gadget), &margin);
1070
1071 if (allocation)
1072 {
1073 allocation->x = priv->allocated_size.x + margin.left;
1074 allocation->y = priv->allocated_size.y + margin.top;
1075 allocation->width = MAX (0, priv->allocated_size.width - margin.left - margin.right);
1076 allocation->height = MAX (0, priv->allocated_size.height - margin.top - margin.bottom);
1077 }
1078 if (baseline)
1079 {
1080 if (priv->allocated_baseline >= 0)
1081 *baseline = priv->allocated_baseline - margin.top;
1082 else
1083 *baseline = -1;
1084 }
1085 }
1086
1087 void
gtk_css_gadget_get_content_allocation(GtkCssGadget * gadget,GtkAllocation * allocation,int * baseline)1088 gtk_css_gadget_get_content_allocation (GtkCssGadget *gadget,
1089 GtkAllocation *allocation,
1090 int *baseline)
1091 {
1092 GtkCssGadgetPrivate *priv = gtk_css_gadget_get_instance_private (gadget);
1093 GtkBorder margin, border, padding, extents;
1094 GtkCssStyle *style;
1095
1096 g_return_if_fail (GTK_IS_CSS_GADGET (gadget));
1097
1098 if (!gtk_css_gadget_get_visible (gadget))
1099 {
1100 if (allocation)
1101 allocation->x = allocation->y = allocation->width = allocation->height = 0;
1102 if (baseline)
1103 *baseline = -1;
1104 return;
1105 }
1106
1107 style = gtk_css_gadget_get_style (gadget);
1108 get_box_margin (style, &margin);
1109 get_box_border (style, &border);
1110 get_box_padding (style, &padding);
1111 extents.top = margin.top + border.top + padding.top;
1112 extents.right = margin.right + border.right + padding.right;
1113 extents.bottom = margin.bottom + border.bottom + padding.bottom;
1114 extents.left = margin.left + border.left + padding.left;
1115
1116 if (allocation)
1117 {
1118 allocation->x = priv->allocated_size.x + extents.left;
1119 allocation->y = priv->allocated_size.y + extents.top;
1120 allocation->width = MAX (0, priv->allocated_size.width - extents.left - extents.right);
1121 allocation->height = MAX (0, priv->allocated_size.height - extents.top - extents.bottom);
1122 }
1123
1124 if (baseline)
1125 {
1126 if (priv->allocated_baseline >= 0)
1127 *baseline = priv->allocated_baseline - extents.top;
1128 else
1129 *baseline = -1;
1130 }
1131 }
1132