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