From 25948f214ea0dbe08e99124b6c408b184905e021 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Fri, 23 Dec 2011 18:59:20 +0100 Subject: [PATCH] St: Implement background-size CSS property Implement the background-size CSS property, specified by the CSS Backgrounds and Borders Module Level 3, including the keywords "contain", "cover", and fixed-size backgrounds. https://bugzilla.gnome.org/show_bug.cgi?id=633462 --- data/theme/gnome-shell.css | 18 ++ src/st/st-theme-node-drawing.c | 258 +++++++++++++-------------- src/st/st-theme-node-private.h | 5 + src/st/st-theme-node.c | 41 +++++ src/st/st-types.h | 7 + tests/interactive/background-size.js | 89 +++++++++ tests/testcommon/100-200.svg | 21 +++ tests/testcommon/200-100.svg | 21 +++ tests/testcommon/200-200.svg | 21 +++ tests/testcommon/test.css | 12 ++ 10 files changed, 360 insertions(+), 133 deletions(-) create mode 100644 tests/interactive/background-size.js create mode 100644 tests/testcommon/100-200.svg create mode 100644 tests/testcommon/200-100.svg create mode 100644 tests/testcommon/200-200.svg diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 4294e5ec1..afd4d6ead 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -60,6 +60,7 @@ StScrollBar StBin#trough { StScrollBar StButton#vhandle { background-image: url("scroll-vhandle.svg"); + background-size: contain; background-color: #252525; border: 1px solid #080808; border-radius: 8px; @@ -68,6 +69,7 @@ StScrollBar StButton#vhandle StScrollBar StButton#hhandle { background-image: url("scroll-hhandle.svg"); + background-size: contain; background-color: #252525; border: 1px solid #080808; border-radius: 8px; @@ -224,16 +226,20 @@ StTooltip StLabel { .toggle-switch-us { background-image: url("toggle-off-us.svg"); + background-size: contain; } .toggle-switch-us:checked { background-image: url("toggle-on-us.svg"); + background-size: contain; } .toggle-switch-intl { background-image: url("toggle-off-intl.svg"); + background-size: contain; } .toggle-switch-intl:checked { background-image: url("toggle-on-intl.svg"); + background-size: contain; } .nm-menu-item-icons { @@ -356,6 +362,7 @@ StTooltip StLabel { .panel-button:focus { border-image: url("panel-button-border.svg") 10 10 0 2; background-image: url("panel-button-highlight-wide.svg"); + background-size: contain; color: white; text-shadow: black 0px 2px 2px; } @@ -364,6 +371,7 @@ StTooltip StLabel { .panel-status-button:checked, .panel-status-button:focus { background-image: url("panel-button-highlight-narrow.svg"); + background-size: contain; } .panel-button:active > .system-status-icon, @@ -477,6 +485,7 @@ StTooltip StLabel { .window-close { background-image: url("close-window.svg"); + background-size: 34px; height: 34px; width: 34px; -shell-close-overlap: 20px; @@ -511,6 +520,7 @@ StTooltip StLabel { .placeholder { background-image: url("dash-placeholder.svg"); + background-size: contain; height: 24px; } @@ -697,11 +707,13 @@ StTooltip StLabel { .app-filter:selected { color: #ffffff; background-image: url("filter-selected-ltr.svg"); + background-size: contain; background-position: 190px 10px; } .app-filter:selected:rtl { background-image: url("filter-selected-rtl.svg"); + background-size: contain; background-position: 10px 10px; } @@ -782,6 +794,7 @@ StTooltip StLabel { .app-well-app.running > .overview-icon { text-shadow: black 0px 2px 2px; background-image: url("running-indicator.svg"); + background-size: contain; } .contact:selected, @@ -1443,6 +1456,7 @@ StTooltip StLabel { .summary-source-button:selected .summary-source { background-image: url("panel-button-highlight-narrow.svg"); + background-size: contain; border-image: url("source-button-border.svg") 10 10 0 1; } @@ -1453,6 +1467,7 @@ StTooltip StLabel { .summary-source-button:expanded:selected { background-image: url("panel-button-highlight-wide.svg"); + background-size: contain; border-image: url("source-button-border.svg") 10 10 0 1; } @@ -1566,6 +1581,7 @@ StTooltip StLabel { width: 52px; height: 52px; background-image: url("corner-ripple-ltr.png"); + background-size: contain; } .ripple-box:rtl { @@ -1607,6 +1623,7 @@ StTooltip StLabel { border: 0px; background: rgba(255,255,255,0.5); background-image: url("ws-switch-arrow-up.svg"); + background-size: contain; border-radius: 8px; } @@ -1615,6 +1632,7 @@ StTooltip StLabel { border: 0px; background: rgba(255,255,255,0.5); background-image: url("ws-switch-arrow-down.svg"); + background-size: contain; border-radius: 8px; } diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index 440ca138a..853310765 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -5,6 +5,7 @@ * Copyright 2009, 2010 Red Hat, Inc. * Copyright 2009, 2010 Florian Müllner * Copyright 2010 Intel Corporation. + * Copyright 2011 Quentin "Sardem FF7" Glidic * * Contains code derived from: * rectangle.c: Rounded rectangle. @@ -390,69 +391,110 @@ st_theme_node_lookup_corner (StThemeNode *node, return material; } +static void +get_background_scale (StThemeNode *node, + gdouble painting_area_width, + gdouble painting_area_height, + gdouble background_image_width, + gdouble background_image_height, + gdouble *scale_w, + gdouble *scale_h) +{ + *scale_w = -1.0; + *scale_h = -1.0; + + switch (node->background_size) + { + case ST_BACKGROUND_SIZE_AUTO: + *scale_w = 1.0; + break; + case ST_BACKGROUND_SIZE_CONTAIN: + if (background_image_width > background_image_height) + *scale_w = painting_area_width / background_image_width; + else + *scale_w = painting_area_height / background_image_height; + break; + case ST_BACKGROUND_SIZE_COVER: + if (background_image_width < background_image_height) + *scale_w = painting_area_width / background_image_width; + else + *scale_w = painting_area_height / background_image_height; + break; + case ST_BACKGROUND_SIZE_FIXED: + if (node->background_size_w > -1) + { + *scale_w = node->background_size_w / background_image_width; + if (node->background_size_h > -1) + *scale_h = node->background_size_h / background_image_height; + } + else if (node->background_size_h > -1) + *scale_w = node->background_size_h / background_image_height; + break; + } + if (*scale_h < 0.0) + *scale_h = *scale_w; +} + +static void +get_background_coordinates (StThemeNode *node, + gdouble painting_area_width, + gdouble painting_area_height, + gdouble background_image_width, + gdouble background_image_height, + gdouble *x, + gdouble *y) +{ + /* honor the specified position if any */ + if (node->background_position_set) + { + *x = node->background_position_x; + *y = node->background_position_y; + } + else + { + /* center the background on the widget */ + *x = (painting_area_width / 2.0) - (background_image_width / 2.0); + *y = (painting_area_height / 2.0) - (background_image_height / 2.0); + } +} + static void get_background_position (StThemeNode *self, const ClutterActorBox *allocation, ClutterActorBox *result) { - gfloat w, h; + gdouble painting_area_width, painting_area_height; + gdouble background_image_width, background_image_height; + gdouble x, y; + gdouble scale_w, scale_h; - result->x1 = result->y1 = 0; - result->x2 = allocation->x2 - allocation->x1; - result->y2 = allocation->y2 - allocation->y1; + /* get the background image size */ + background_image_width = allocation->x2 - allocation->x1; + background_image_height = allocation->y2 - allocation->y1; - w = cogl_texture_get_width (self->background_texture); - h = cogl_texture_get_height (self->background_texture); + /* get the painting area size */ + painting_area_width = cogl_texture_get_width (self->background_texture); + painting_area_height = cogl_texture_get_height (self->background_texture); - /* scale the background into the allocated bounds, when not being absolutely positioned */ - if ((w > result->x2 || h > result->y2) && !self->background_position_set) - { - gint new_h, new_w, offset; - gint box_w, box_h; + /* scale if requested */ + get_background_scale (self, + painting_area_width, painting_area_height, + background_image_width, background_image_height, + &scale_w, &scale_h); + background_image_width *= scale_w; + background_image_height *= scale_h; - box_w = (int) result->x2; - box_h = (int) result->y2; + /* get coordinates */ + get_background_coordinates (self, + painting_area_width, painting_area_height, + background_image_width, background_image_height, + &x, &y); - /* scale to fit */ - new_h = (int)((h / w) * ((gfloat) box_w)); - new_w = (int)((w / h) * ((gfloat) box_h)); - - if (new_h > box_h) - { - /* center for new width */ - offset = ((box_w) - new_w) * 0.5; - result->x1 = offset; - result->x2 = offset + new_w; - - result->y2 = box_h; - } - else - { - /* center for new height */ - offset = ((box_h) - new_h) * 0.5; - result->y1 = offset; - result->y2 = offset + new_h; - - result->x2 = box_w; - } - } - else - { - /* honor the specified position if any */ - if (self->background_position_set) - { - result->x1 = self->background_position_x; - result->y1 = self->background_position_y; - } - else - { - /* center the background on the widget */ - result->x1 = (int)(((allocation->x2 - allocation->x1) / 2) - (w / 2)); - result->y1 = (int)(((allocation->y2 - allocation->y1) / 2) - (h / 2)); - } - result->x2 = result->x1 + w; - result->y2 = result->y1 + h; - } + /* place the background image */ + result->x1 = x; + result->y1 = y; + result->x2 = result->x1 + background_image_width; + result->y2 = result->y1 + background_image_height; } /* Use of this function marks code which doesn't support @@ -533,12 +575,13 @@ create_cairo_pattern_of_background_image (StThemeNode *node, cairo_content_t content; cairo_matrix_t matrix; const char *file; - double height_ratio, width_ratio; - int file_width; - int file_height; StTextureCache *texture_cache; + gdouble background_image_width, background_image_height; + gdouble x, y; + gdouble scale_w, scale_h; + file = st_theme_node_get_background_image (node); texture_cache = st_texture_cache_get_default (); @@ -553,90 +596,39 @@ create_cairo_pattern_of_background_image (StThemeNode *node, content = cairo_surface_get_content (surface); pattern = cairo_pattern_create_for_surface (surface); - file_width = cairo_image_surface_get_width (surface); - file_height = cairo_image_surface_get_height (surface); - - height_ratio = file_height / node->alloc_height; - width_ratio = file_width / node->alloc_width; + background_image_width = cairo_image_surface_get_width (surface); + background_image_height = cairo_image_surface_get_height (surface); *needs_background_fill = TRUE; - if ((file_width > node->alloc_width || file_height > node->alloc_height) - && !node->background_position_set) - { - double scale_factor; - double x_offset, y_offset; - if (width_ratio > height_ratio) - { - double scaled_height; + cairo_matrix_init_identity (&matrix); - /* center vertically */ + get_background_scale (node, + node->alloc_width, node->alloc_height, + background_image_width, background_image_height, + &scale_w, &scale_h); + if ((scale_w != 1) || (scale_h != 1)) + cairo_matrix_scale (&matrix, 1.0/scale_w, 1.0/scale_h); + background_image_width *= scale_w; + background_image_height *= scale_h; - scale_factor = width_ratio; - scaled_height = file_height / scale_factor; + get_background_coordinates (node, + node->alloc_width, node->alloc_height, + background_image_width, background_image_height, + &x, &y); + cairo_matrix_translate (&matrix, -x, -y); - x_offset = 0.; - y_offset = - (node->alloc_height / 2. - scaled_height / 2.); - } - else - { - double scaled_width; + /* If it's opaque, fills up the entire allocated + * area, then don't bother doing a background fill first + */ + if (content != CAIRO_CONTENT_COLOR_ALPHA + && x >= 0 + && -x + background_image_width >= node->alloc_width + && y >= 0 + && -y + background_image_height >= node->alloc_height) + *needs_background_fill = FALSE; - /* center horizontally */ - - scale_factor = height_ratio; - scaled_width = file_width / scale_factor; - - y_offset = 0.; - x_offset = - (node->alloc_width / 2. - scaled_width / 2.); - } - - cairo_matrix_init_scale (&matrix, scale_factor, scale_factor); - cairo_matrix_translate (&matrix, x_offset, y_offset); - - cairo_pattern_set_matrix (pattern, &matrix); - - /* If it's opaque, and when scaled, fills up the entire allocated - * area, then don't bother doing a background fill first - */ - if (content != CAIRO_CONTENT_COLOR_ALPHA && width_ratio == height_ratio) - *needs_background_fill = FALSE; - } - else - { - double x_offset, y_offset; - - if (node->background_position_set) - { - x_offset = -node->background_position_x; - y_offset = -node->background_position_y; - } - else - { - if (node->alloc_width > file_width) - x_offset = - (node->alloc_width / 2.0 - file_width / 2.0); - else - x_offset = - (file_width / 2.0 - node->alloc_width / 2.0); - - if (node->alloc_height > file_height) - y_offset = - (node->alloc_height / 2.0 - file_height / 2.0); - else - y_offset = - (file_height / 2.0 - node->alloc_height / 2.0); - } - - /* If it's opaque, and when translated, fills up the entire allocated - * area, then don't bother doing a background fill first - */ - if (content != CAIRO_CONTENT_COLOR_ALPHA - && -x_offset <= 0 - && -x_offset + file_width >= node->alloc_width - && -y_offset <= 0 - && -y_offset + file_height >= node->alloc_height) - *needs_background_fill = FALSE; - - cairo_matrix_init_translate (&matrix, x_offset, y_offset); - cairo_pattern_set_matrix (pattern, &matrix); - } + cairo_pattern_set_matrix (pattern, &matrix); return pattern; } diff --git a/src/st/st-theme-node-private.h b/src/st/st-theme-node-private.h index f42cf4025..b6cd2a4dd 100644 --- a/src/st/st-theme-node-private.h +++ b/src/st/st-theme-node-private.h @@ -3,6 +3,7 @@ * st-theme-node-private.h: private structures and functions for StThemeNode * * Copyright 2009, 2010 Red Hat, Inc. + * Copyright 2011 Quentin "Sardem FF7" Glidic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -24,6 +25,7 @@ #include #include "st-theme-node.h" +#include "st-types.h" G_BEGIN_DECLS @@ -44,6 +46,9 @@ struct _StThemeNode { int background_position_x; int background_position_y; gboolean background_position_set : 1; + StBackgroundSize background_size; + gint background_size_w; + gint background_size_h; ClutterColor foreground_color; ClutterColor border_color[4]; diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c index cc51498c6..47b117c2f 100644 --- a/src/st/st-theme-node.c +++ b/src/st/st-theme-node.c @@ -7,6 +7,7 @@ * Copyright 2009, 2010 Florian Müllner * Copyright 2010 Adel Gadllah * Copyright 2010 Giovanni Campagna + * Copyright 2011 Quentin "Sardem FF7" Glidic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -1579,6 +1580,7 @@ _st_theme_node_ensure_background (StThemeNode *node) node->background_color = TRANSPARENT_COLOR; node->background_gradient_type = ST_GRADIENT_NONE; node->background_position_set = FALSE; + node->background_size = ST_BACKGROUND_SIZE_AUTO; ensure_properties (node); @@ -1606,6 +1608,7 @@ _st_theme_node_ensure_background (StThemeNode *node) g_free (node->background_image); node->background_image = NULL; node->background_position_set = FALSE; + node->background_size = ST_BACKGROUND_SIZE_AUTO; for (term = decl->value; term; term = term->next) { @@ -1662,6 +1665,44 @@ _st_theme_node_ensure_background (StThemeNode *node) else node->background_position_set = TRUE; } + else if (strcmp (property_name, "-size") == 0) + { + if (decl->value->type == TERM_IDENT) + { + if (strcmp (decl->value->content.str->stryng->str, "contain") == 0) + node->background_size = ST_BACKGROUND_SIZE_CONTAIN; + else if (strcmp (decl->value->content.str->stryng->str, "cover") == 0) + node->background_size = ST_BACKGROUND_SIZE_COVER; + else if ((strcmp (decl->value->content.str->stryng->str, "auto") == 0) && (decl->value->next) && (decl->value->next->type == TERM_NUMBER)) + { + GetFromTermResult result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h); + + node->background_size_w = -1; + node->background_size = (result == VALUE_FOUND) ? ST_BACKGROUND_SIZE_FIXED : ST_BACKGROUND_SIZE_AUTO; + } + else + node->background_size = ST_BACKGROUND_SIZE_AUTO; + } + else if (decl->value->type == TERM_NUMBER) + { + GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_size_w); + if (result == VALUE_NOT_FOUND) + continue; + + node->background_size = ST_BACKGROUND_SIZE_FIXED; + + if ((decl->value->next) && (decl->value->next->type == TERM_NUMBER)) + { + result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h); + + if (result == VALUE_FOUND) + continue; + } + node->background_size_h = -1; + } + else + node->background_size = ST_BACKGROUND_SIZE_AUTO; + } else if (strcmp (property_name, "-color") == 0) { GetFromTermResult result; diff --git a/src/st/st-types.h b/src/st/st-types.h index 335c2a094..edcbbfde3 100644 --- a/src/st/st-types.h +++ b/src/st/st-types.h @@ -49,6 +49,13 @@ typedef enum { ST_ICON_DOCUMENT } StIconType; +typedef enum { + ST_BACKGROUND_SIZE_AUTO, + ST_BACKGROUND_SIZE_CONTAIN, + ST_BACKGROUND_SIZE_COVER, + ST_BACKGROUND_SIZE_FIXED +} StBackgroundSize; + G_END_DECLS #endif /* __ST_TYPES_H__ */ diff --git a/tests/interactive/background-size.js b/tests/interactive/background-size.js new file mode 100644 index 000000000..00f967dde --- /dev/null +++ b/tests/interactive/background-size.js @@ -0,0 +1,89 @@ +// -*- mode: js; js-indent-level: 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 = 1024; +stage.height = 768; + +let vbox = new St.BoxLayout({ width: stage.width, + height: stage.height, + style: 'background: #ffee88;' }); +stage.add_actor(vbox); + +let scroll = new St.ScrollView(); +vbox.add(scroll, { expand: true }); + +let vbox = new St.BoxLayout({ vertical: true, + style: 'padding: 10px;' + + 'spacing: 20px;' }); +scroll.add_actor(vbox); + +let tbox = null; + +function addTestCase(image, size, backgroundSize) { + let obin = new St.Bin({ style: 'border: 3px solid green;' }); + tbox.add(obin); + + let bin = new St.Bin({ style_class: 'background-image-' + image, + width: size.width, + height: size.height, + style: 'border: 1px solid transparent;' + + 'background-size: ' + backgroundSize + ';', + x_fill: true, + y_fill: true + }); + obin.set_child(bin); + + bin.set_child(new St.Label({ text: backgroundSize, + style: 'font-size: 15px;' + + 'text-align: center;' + })); +} + +function addTestLine(image, size, backgroundSizes) { + vbox.add(new St.Label({ text: image + '.svg / ' + size.width + '×' + size.height, + style: 'font-size: 15px;' + + 'text-align: center;' + })); + + tbox = new St.BoxLayout({ style: 'spacing: 20px;' }); + vbox.add(tbox); + + if (backgroundSizes.length == 2) + addTestCase(image, size, "auto"); + for each (let s in backgroundSizes) + addTestCase(image, size, s); +} + +let size1 = { width: 200, height: 200 } +let size2 = { width: 250, height: 250 } +let size3 = { width: 100, height: 100 } + +// fixed size +addTestLine('200-200', size1, ["200px 200px", "100px 100px", "100px 200px"]); + +// same size +addTestLine('200-200', size1, ["contain", "cover"]); +// smaller +addTestLine('200-200', size2, ["contain", "cover"]); +// larger +addTestLine('200-200', size3, ["contain", "cover"]); + + +addTestLine('200-100', size1, ["contain", "cover"]); +addTestLine('200-100', size2, ["contain", "cover"]); +addTestLine('200-100', size3, ["contain", "cover"]); + + +addTestLine('100-200', size1, ["contain", "cover"]); +addTestLine('100-200', size2, ["contain", "cover"]); +addTestLine('100-200', size3, ["contain", "cover"]); + +stage.show(); +Clutter.main(); +stage.destroy(); diff --git a/tests/testcommon/100-200.svg b/tests/testcommon/100-200.svg new file mode 100644 index 000000000..59a5307be --- /dev/null +++ b/tests/testcommon/100-200.svg @@ -0,0 +1,21 @@ + + + + + + diff --git a/tests/testcommon/200-100.svg b/tests/testcommon/200-100.svg new file mode 100644 index 000000000..e149b5ffe --- /dev/null +++ b/tests/testcommon/200-100.svg @@ -0,0 +1,21 @@ + + + + + + diff --git a/tests/testcommon/200-200.svg b/tests/testcommon/200-200.svg new file mode 100644 index 000000000..9965a2afd --- /dev/null +++ b/tests/testcommon/200-200.svg @@ -0,0 +1,21 @@ + + + + + + diff --git a/tests/testcommon/test.css b/tests/testcommon/test.css index 5e60254c3..49f5b65dd 100644 --- a/tests/testcommon/test.css +++ b/tests/testcommon/test.css @@ -37,6 +37,18 @@ stage { border-image: url('border-image.png') 16; } +.background-image-200-200 { + background-image: url('200-200.svg'); +} + +.background-image-100-200 { + background-image: url('100-200.svg'); +} + +.background-image-200-100 { + background-image: url('200-100.svg'); +} + .background-gradient { background-gradient-start: rgba(127, 255, 127, .6); background-gradient-end: rgba(127, 127, 255, .6);