diff --git a/src/st/st-button.c b/src/st/st-button.c index 19493fb19..4c8b7f20b 100644 --- a/src/st/st-button.c +++ b/src/st/st-button.c @@ -425,13 +425,11 @@ st_button_unmap (ClutterActor *self) } static void -st_button_draw_background (StWidget *widget, - ClutterActor *background, - const ClutterColor *color) +st_button_draw_background (StWidget *widget) { StButtonPrivate *priv; - ST_WIDGET_CLASS (st_button_parent_class)->draw_background (widget, background, color); + ST_WIDGET_CLASS (st_button_parent_class)->draw_background (widget); priv = ST_BUTTON (widget)->priv; diff --git a/src/st/st-widget.c b/src/st/st-widget.c index 6e9f6a53f..bc2e0c944 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -43,6 +43,8 @@ #include "st-theme-context.h" #include "st-tooltip.h" +#include + /* * Forward declaration for sake of StWidgetChild */ @@ -61,6 +63,7 @@ struct _StWidgetPrivate gboolean is_stylable : 1; gboolean has_tooltip : 1; gboolean is_style_dirty : 1; + gboolean draw_bg_color : 1; StTooltip *tooltip; }; @@ -351,18 +354,18 @@ st_widget_allocate (ClutterActor *actor, } static void -st_widget_real_draw_background (StWidget *self, - ClutterActor *background, - const ClutterColor *color) +st_widget_real_draw_background (StWidget *self) { + StWidgetPrivate *priv = self->priv; + /* Default implementation just draws the background * colour and the image on top */ - if (color && color->alpha != 0) + if (priv->draw_bg_color) { ClutterActor *actor = CLUTTER_ACTOR (self); ClutterActorBox allocation = { 0, }; - ClutterColor bg_color = *color; + ClutterColor bg_color = priv->bg_color; gfloat w, h; bg_color.alpha = clutter_actor_get_paint_opacity (actor) @@ -381,8 +384,8 @@ st_widget_real_draw_background (StWidget *self, cogl_rectangle (0, 0, w, h); } - if (background) - clutter_actor_paint (background); + if (priv->border_image) + clutter_actor_paint (priv->border_image); } static void @@ -391,9 +394,7 @@ st_widget_paint (ClutterActor *self) StWidgetPrivate *priv = ST_WIDGET (self)->priv; StWidgetClass *klass = ST_WIDGET_GET_CLASS (self); - klass->draw_background (ST_WIDGET (self), - priv->border_image, - &priv->bg_color); + klass->draw_background (ST_WIDGET (self)); if (priv->background_image != NULL) clutter_actor_paint (priv->background_image); @@ -486,6 +487,11 @@ st_widget_real_style_changed (StWidget *self) gboolean relayout_needed = FALSE; gboolean has_changed = FALSE; ClutterColor color; + guint border_width = 0; + guint border_radius = 0; + ClutterColor border_color = { 0, }; + StSide side; + StCorner corner; /* application has request this widget is not stylable */ if (!priv->is_stylable) @@ -497,6 +503,7 @@ st_widget_real_style_changed (StWidget *self) if (!clutter_color_equal (&color, &priv->bg_color)) { priv->bg_color = color; + priv->draw_bg_color = color.alpha != 0; has_changed = TRUE; } @@ -514,6 +521,62 @@ st_widget_real_style_changed (StWidget *self) texture_cache = st_texture_cache_get_default (); + /* StThemeNode supports different widths and colors for different sides + * of the border, and different radii for the different corners. We take + * the different border widths into account when positioning, but our current + * drawing code (using BigRectangle) can only handle a single width, color, + * and radius, so we arbitrarily pick the first non-zero width and radius, + * and use that. + */ + for (side = ST_SIDE_TOP; side <= ST_SIDE_LEFT; side++) + { + double width = st_theme_node_get_border_width (theme_node, side); + if (width > 0.5) + { + border_width = round (width); + st_theme_node_get_border_color (theme_node, side, &border_color); + break; + } + } + + for (corner = ST_CORNER_TOPLEFT; corner <= ST_CORNER_BOTTOMLEFT; corner++) + { + double radius = st_theme_node_get_border_radius (theme_node, corner); + if (radius > 0.5) + { + border_radius = round (radius); + break; + } + } + + /* Rough notes about the relationship of borders and backgrounds in CSS3; + * see http://www.w3.org/TR/css3-background/ for more accurate details. + * + * - Things are drawn in 4 layers, from the bottom: + * Background color + * Background image + * Border color or border image + * Content + * - The background color and image extend to and are clipped by the + * edge of the border area, so will be rounded if the border is rounded. + * (CSS3 background-clip property modifies this) + * - The border image replaces what would normally be drawn by the border + * - The border image is not clipped by a rounded border-radius + * - The border radius rounds the background even if the border is + * zero width or a border image is being used. + * + * Deviations from the above as implemented here: + * - The combination of border image and a non-zero border radius is + * not supported; the background color will be drawn with square + * corners. + * - The background image is drawn above the border color or image, + * not below it. + * - We don't clip the background image to the (rounded) border area. + * + * The first two allow us always draw with no more than single border_image + * and a single background image above it. + */ + theme_image = st_theme_node_get_background_theme_image (theme_node); if (theme_image) { @@ -542,6 +605,22 @@ st_widget_real_style_changed (StWidget *self) border_left); clutter_actor_set_parent (priv->border_image, CLUTTER_ACTOR (self)); + has_changed = TRUE; + relayout_needed = TRUE; + } + else if ((border_width > 0 && border_color.alpha != 0) || + (border_radius > 0 && priv->bg_color.alpha != 0)) + { + priv->draw_bg_color = FALSE; + priv->border_image = g_object_new (BIG_TYPE_RECTANGLE, + "color", &priv->bg_color, + "border-width", border_width, + "border-color", &border_color, + "corner-radius", border_radius, + NULL); + + clutter_actor_set_parent (priv->border_image, CLUTTER_ACTOR (self)); + has_changed = TRUE; relayout_needed = TRUE; } @@ -1299,7 +1378,5 @@ st_widget_draw_background (StWidget *self) priv = self->priv; klass = ST_WIDGET_GET_CLASS (self); - klass->draw_background (ST_WIDGET (self), - priv->border_image, - &priv->bg_color); + klass->draw_background (ST_WIDGET (self)); } diff --git a/src/st/st-widget.h b/src/st/st-widget.h index 027aa87c4..66f7b0698 100644 --- a/src/st/st-widget.h +++ b/src/st/st-widget.h @@ -72,10 +72,8 @@ struct _StWidgetClass ClutterActorClass parent_class; /* vfuncs */ - void (* draw_background) (StWidget *self, - ClutterActor *background, - const ClutterColor *color); - void (* style_changed) (StWidget *self); + void (* draw_background) (StWidget *self); + void (* style_changed) (StWidget *self); }; GType st_widget_get_type (void) G_GNUC_CONST; diff --git a/tests/interactive/borders.js b/tests/interactive/borders.js new file mode 100644 index 000000000..258a621a4 --- /dev/null +++ b/tests/interactive/borders.js @@ -0,0 +1,57 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const St = imports.gi.St; + +const UI = imports.testcommon.ui; + +UI.init(); +let stage = Clutter.Stage.get_default(); +stage.width = 600; +stage.height = 600; + +let vbox = new St.BoxLayout({ vertical: true, + width: stage.width, + height: stage.height, + spacing: 20, + style: 'padding: 10px; background: #ffee88' }); +stage.add_actor(vbox); + +vbox.add(new St.Label({ text: "Hello World", + style: 'border: 1px solid black; ' + + 'padding: 5px;' })); + +vbox.add(new St.Label({ text: "Hello Round World", + style: 'border: 3px solid green; ' + + 'border-radius: 8px; ' + + 'padding: 5px;' })); + +vbox.add(new St.Label({ text: "Hello Background", + style: 'border: 3px solid green; ' + + 'border-radius: 8px; ' + + 'background: white; ' + + 'padding: 5px;' })); + +vbox.add(new St.Label({ text: "Border, Padding, Content: 20px" })); + +let b1 = new St.BoxLayout({ vertical: true, + style: 'border: 20px solid black; ' + + 'background: white; ' + + 'padding: 20px;' }); +vbox.add(b1); + +b1.add(new St.BoxLayout({ width: 20, height: 20, + style: 'background: black' })); + +vbox.add(new St.Label({ text: "Translucent blue border", + style: 'border: 20px solid rgba(0, 0, 255, 0.2); ' + + 'background: white; ' + + 'padding: 10px;' })); + +vbox.add(new St.Label({ text: "Border Image", + style_class: "border-image", + style: "padding: 10px;" })); + +stage.show(); +Clutter.main(); +stage.destroy(); diff --git a/tests/interactive/inline-style.js b/tests/interactive/inline-style.js index 2232c0d25..3cac25cea 100644 --- a/tests/interactive/inline-style.js +++ b/tests/interactive/inline-style.js @@ -25,16 +25,16 @@ function update_size() { } update_size(); -let button = new St.Button ({ label: 'Smaller', - style: 'padding: 4px; background: #eeddcc' }); +let button; + +button = new St.Button ({ label: 'Smaller' }); hbox.add (button); button.connect('clicked', function() { size /= 1.2; update_size (); }); -let button = new St.Button ({ label: 'Bigger', - style: 'padding: 4px; background: #eeddcc' }); +button = new St.Button ({ label: 'Bigger' }); hbox.add (button); button.connect('clicked', function() { size *= 1.2; diff --git a/tests/testcommon/border-image.png b/tests/testcommon/border-image.png new file mode 100644 index 000000000..e680020ab Binary files /dev/null and b/tests/testcommon/border-image.png differ diff --git a/tests/testcommon/test.css b/tests/testcommon/test.css index 6ce3d5dc0..e0e7b05e1 100644 --- a/tests/testcommon/test.css +++ b/tests/testcommon/test.css @@ -31,3 +31,23 @@ stage { .monospace { font-family: monospace; } + +.border-image { + border: 15px; + -st-background-image: url('border-image.png') 16px; +} + +StButton { + background: #eeddbb; + border: 1px solid black; + border-radius: 8px; + padding: 5px; +} + +StButton:hover { + background: #ffeecc; +} + +StButton:active { + background: #ccbb99; +}