From 00ba93717141ce149e1708594bdb24b12a7653c3 Mon Sep 17 00:00:00 2001 From: Adel Gadllah Date: Thu, 20 Jan 2011 20:25:21 +0100 Subject: [PATCH 01/30] StScrollView: Implement real fade effect Implement an edge fade effect (top/bottom) using a ClutterOffscreenEffect subclass, replacing the former shadow hack. https://bugzilla.gnome.org/show_bug.cgi?id=639460 --- js/ui/appDisplay.js | 2 +- js/ui/messageTray.js | 2 +- js/ui/searchDisplay.js | 2 +- src/Makefile-st.am | 8 +- src/Makefile.am | 2 +- src/st/st-scroll-view-fade.c | 348 ++++++++++++++++++++++++ src/st/st-scroll-view-fade.h | 40 +++ src/st/st-scroll-view.c | 179 +++--------- src/st/st-scroll-view.h | 4 +- tests/interactive/scroll-view-sizing.js | 18 +- 10 files changed, 446 insertions(+), 159 deletions(-) create mode 100644 src/st/st-scroll-view-fade.c create mode 100644 src/st/st-scroll-view-fade.h diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 4b460fcc0..f1d44ebe7 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -40,7 +40,7 @@ AlphabeticalView.prototype = { this.actor = new St.ScrollView({ x_fill: true, y_fill: false, y_align: St.Align.START, - vshadows: true }); + vfade: true }); this.actor.add_actor(box); this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); }, diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index c158c037a..073b87acc 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -386,7 +386,7 @@ Notification.prototype = { this._scrollArea = new St.ScrollView({ name: 'notification-scrollview', vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, hscrollbar_policy: Gtk.PolicyType.NEVER, - vshadows: true }); + vfade: true }); this.actor.add(this._scrollArea, { row: 1, col: 1 }); this._contentArea = new St.BoxLayout({ name: 'notification-body', diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js index fe6c76072..f85eb6714 100644 --- a/js/ui/searchDisplay.js +++ b/js/ui/searchDisplay.js @@ -166,7 +166,7 @@ SearchResults.prototype = { let scrollView = new St.ScrollView({ x_fill: true, y_fill: false, - vshadows: true }); + vfade: true }); scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); scrollView.add_actor(this._content); diff --git a/src/Makefile-st.am b/src/Makefile-st.am index 225ff5bc2..cb54ef4ba 100644 --- a/src/Makefile-st.am +++ b/src/Makefile-st.am @@ -159,11 +159,17 @@ st_source_c = \ st/st-widget.c \ $(NULL) +st_non_gir_sources = \ + st/st-scroll-view-fade.c \ + st/st-scroll-view-fade.h \ + $(NULL) + noinst_LTLIBRARIES += libst-1.0.la libst_1_0_la_LIBADD = -lm $(ST_LIBS) libst_1_0_la_SOURCES = \ - $(st_source_c) \ + $(st_source_c) \ + $(st_non_gir_sources) \ $(st_source_private_h) \ $(st_source_private_c) \ $(st_source_h) \ diff --git a/src/Makefile.am b/src/Makefile.am index f252e5bc1..5bfa8c764 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -262,7 +262,7 @@ St-1.0.gir: $(mutter) $(G_IR_SCANNER) libst-1.0.la Makefile --libtool="$(LIBTOOL)" \ --library=libst-1.0.la \ -DST_COMPILATION \ - $(filter-out %-private.h, $(addprefix $(srcdir)/,$(st_source_h))) \ + $(filter-out %-private.h $(st_non_gir_sources), $(addprefix $(srcdir)/,$(st_source_h))) \ $(addprefix $(srcdir)/,$(st_source_c)) \ $(srcdir)/st-enum-types.h \ $(st_cflags) \ diff --git a/src/st/st-scroll-view-fade.c b/src/st/st-scroll-view-fade.c new file mode 100644 index 000000000..050cea5a7 --- /dev/null +++ b/src/st/st-scroll-view-fade.c @@ -0,0 +1,348 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-scroll-view-fade.h: Edge fade effect for StScrollView + * + * Copyright 2010 Intel Corporation. + * Copyright 2011 Adel Gadllah + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + +#define ST_SCROLL_VIEW_FADE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_SCROLL_VIEW_FADE, StScrollViewFadeClass)) +#define ST_IS_SCROLL_VIEW_FADE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_SCROLL_VIEW_FADE)) +#define ST_SCROLL_VIEW_FADE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_SCROLL_VIEW_FADE, StScrollViewFadeClass)) + +#include "st-scroll-view-fade.h" +#include "st-scroll-view.h" +#include "st-scroll-bar.h" +#include "st-scrollable.h" + +#include +#include + +typedef struct _StScrollViewFadeClass StScrollViewFadeClass; + +#define FADE_OFFSET 68.0f + +static const gchar *fade_glsl_shader = +"uniform sampler2D tex;\n" +"uniform float height;\n" +"uniform float width;\n" +"uniform float scrollbar_width;\n" +"uniform float offset_bottom;\n" +"uniform float offset_top;\n" +"\n" +"void main ()\n" +"{\n" +" vec4 color = cogl_color_in * texture2D (tex, vec2 (cogl_tex_coord_in[0].xy));\n" +" float y = height * cogl_tex_coord_in[0].y;\n" +" float x = width * cogl_tex_coord_in[0].x;\n" +" float ratio = 0.0;\n" +" float fade_bottom_start = height - offset_bottom;\n" +" \n" +" if (offset_top != 0.0 && y < offset_top && x < (width - scrollbar_width)) {\n" +" ratio = y / offset_top;\n" +" cogl_color_out = color * ratio;\n" +" }\n" +" else if (offset_bottom != 0.0 && y > fade_bottom_start && x < (width - scrollbar_width)) {\n" +" ratio = (height - y)/(height - fade_bottom_start);\n" +" cogl_color_out = color * ratio;\n" +" }\n" +" else { \n" +" cogl_color_out = color;\n" +" }\n" +"}\n"; + +struct _StScrollViewFade +{ + ClutterOffscreenEffect parent_instance; + + /* a back pointer to our actor, so that we can query it */ + ClutterActor *actor; + + CoglHandle shader; + CoglHandle program; + + gint tex_uniform; + gint height_uniform; + gint width_uniform; + gint scrollbar_width_uniform; + gint offset_top_uniform; + gint offset_bottom_uniform; + + StAdjustment *vadjustment; + + guint is_attached : 1; +}; + +struct _StScrollViewFadeClass +{ + ClutterOffscreenEffectClass parent_class; +}; + +G_DEFINE_TYPE (StScrollViewFade, + st_scroll_view_fade, + CLUTTER_TYPE_OFFSCREEN_EFFECT); + +static gboolean +st_scroll_view_fade_pre_paint (ClutterEffect *effect) +{ + StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect); + ClutterEffectClass *parent_class; + + if (self->shader == COGL_INVALID_HANDLE) + return FALSE; + + if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect))) + return FALSE; + + if (self->actor == NULL) + return FALSE; + + if (self->program == COGL_INVALID_HANDLE) + self->program = cogl_create_program (); + + if (!self->is_attached) + { + g_assert (self->shader != COGL_INVALID_HANDLE); + g_assert (self->program != COGL_INVALID_HANDLE); + + cogl_program_attach_shader (self->program, self->shader); + cogl_program_link (self->program); + + cogl_handle_unref (self->shader); + + self->is_attached = TRUE; + + self->tex_uniform = + cogl_program_get_uniform_location (self->program, "tex"); + self->height_uniform = + cogl_program_get_uniform_location (self->program, "height"); + self->width_uniform = + cogl_program_get_uniform_location (self->program, "width"); + self->scrollbar_width_uniform = + cogl_program_get_uniform_location (self->program, "scrollbar_width"); + self->offset_top_uniform = + cogl_program_get_uniform_location (self->program, "offset_top"); + self->offset_bottom_uniform = + cogl_program_get_uniform_location (self->program, "offset_bottom"); + } + + parent_class = CLUTTER_EFFECT_CLASS (st_scroll_view_fade_parent_class); + return parent_class->pre_paint (effect); +} + +static CoglHandle +st_scroll_view_fade_create_texture (ClutterOffscreenEffect *effect, + gfloat min_width, + gfloat min_height) +{ + return cogl_texture_new_with_size (min_width, + min_height, + COGL_TEXTURE_NO_SLICING, + COGL_PIXEL_FORMAT_RGBA_8888_PRE); +} + +static void +st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect) +{ + StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect); + ClutterOffscreenEffectClass *parent; + CoglHandle material; + + gdouble value, lower, upper, page_size; + ClutterActor *vscroll = st_scroll_view_get_vscroll_bar (ST_SCROLL_VIEW (self->actor)); + + if (self->program == COGL_INVALID_HANDLE) + goto out; + + st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size); + + if (self->offset_top_uniform > -1) { + if (value > lower + 0.1) + cogl_program_set_uniform_1f (self->program, self->offset_top_uniform, FADE_OFFSET); + else + cogl_program_set_uniform_1f (self->program, self->offset_top_uniform, 0.0f); + } + + if (self->offset_bottom_uniform > -1) { + if (value < upper - page_size - 0.1) + cogl_program_set_uniform_1f (self->program, self->offset_bottom_uniform, FADE_OFFSET); + else + cogl_program_set_uniform_1f (self->program, self->offset_bottom_uniform, 0.0f); + } + + if (self->tex_uniform > -1) + cogl_program_set_uniform_1i (self->program, self->tex_uniform, 0); + if (self->height_uniform > -1) + cogl_program_set_uniform_1f (self->program, self->height_uniform, clutter_actor_get_height (self->actor)); + if (self->width_uniform > -1) + cogl_program_set_uniform_1f (self->program, self->width_uniform, clutter_actor_get_width (self->actor)); + if (self->scrollbar_width_uniform > -1) + cogl_program_set_uniform_1f (self->program, self->scrollbar_width_uniform, clutter_actor_get_width (vscroll)); + + material = clutter_offscreen_effect_get_target (effect); + cogl_material_set_user_program (material, self->program); + +out: + parent = CLUTTER_OFFSCREEN_EFFECT_CLASS (st_scroll_view_fade_parent_class); + parent->paint_target (effect); +} + +static void +on_vadjustment_changed (StAdjustment *adjustment, + ClutterEffect *effect) +{ + gdouble value, lower, upper, page_size; + gboolean needs_fade; + + st_adjustment_get_values (adjustment, &value, &lower, &upper, NULL, NULL, &page_size); + needs_fade = (value > lower + 0.1) || (value < upper - page_size - 0.1); + + clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (effect), needs_fade); +} + +static void +st_scroll_view_fade_set_actor (ClutterActorMeta *meta, + ClutterActor *actor) +{ + StScrollViewFade *self = ST_SCROLL_VIEW_FADE (meta); + ClutterActorMetaClass *parent; + + g_return_if_fail (actor == NULL || ST_IS_SCROLL_VIEW (actor)); + + if (self->shader == COGL_INVALID_HANDLE) + { + clutter_actor_meta_set_enabled (meta, FALSE); + return; + } + + if (self->vadjustment) + { + g_signal_handlers_disconnect_by_func (self->vadjustment, + (gpointer)on_vadjustment_changed, + self); + self->vadjustment = NULL; + } + + if (actor) + { + StScrollView *scroll_view = ST_SCROLL_VIEW (actor); + StScrollBar *vscroll = ST_SCROLL_BAR (st_scroll_view_get_vscroll_bar (scroll_view)); + self->vadjustment = ST_ADJUSTMENT (st_scroll_bar_get_adjustment (vscroll)); + + g_signal_connect (self->vadjustment, "changed", + G_CALLBACK (on_vadjustment_changed), + self); + + on_vadjustment_changed (self->vadjustment, CLUTTER_EFFECT (self)); + } + + parent = CLUTTER_ACTOR_META_CLASS (st_scroll_view_fade_parent_class); + parent->set_actor (meta, actor); + + /* we keep a back pointer here, to avoid going through the ActorMeta */ + self->actor = clutter_actor_meta_get_actor (meta); +} + +static void +st_scroll_view_fade_dispose (GObject *gobject) +{ + StScrollViewFade *self = ST_SCROLL_VIEW_FADE (gobject); + + if (self->program != COGL_INVALID_HANDLE) + { + cogl_handle_unref (self->program); + + self->program = COGL_INVALID_HANDLE; + self->shader = COGL_INVALID_HANDLE; + } + + if (self->vadjustment) + { + g_signal_handlers_disconnect_by_func (self->vadjustment, + (gpointer)on_vadjustment_changed, + self); + self->vadjustment = NULL; + } + + self->actor = NULL; + + G_OBJECT_CLASS (st_scroll_view_fade_parent_class)->dispose (gobject); +} + +static void +st_scroll_view_fade_class_init (StScrollViewFadeClass *klass) +{ + ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterOffscreenEffectClass *offscreen_class; + ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); + + gobject_class->dispose = st_scroll_view_fade_dispose; + + meta_class->set_actor = st_scroll_view_fade_set_actor; + + effect_class->pre_paint = st_scroll_view_fade_pre_paint; + + offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass); + offscreen_class->create_texture = st_scroll_view_fade_create_texture; + offscreen_class->paint_target = st_scroll_view_fade_paint_target; +} + + +static void +st_scroll_view_fade_init (StScrollViewFade *self) +{ + static CoglHandle shader = COGL_INVALID_HANDLE; + + if (shader == COGL_INVALID_HANDLE) + { + if (clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL)) + { + shader = cogl_create_shader (COGL_SHADER_TYPE_FRAGMENT); + cogl_shader_source (shader, fade_glsl_shader); + cogl_shader_compile (shader); + if (!cogl_shader_is_compiled (shader)) + { + gchar *log_buf = cogl_shader_get_info_log (shader); + + g_warning (G_STRLOC ": Unable to compile the fade shader: %s", + log_buf); + g_free (log_buf); + + cogl_handle_unref (shader); + shader = COGL_INVALID_HANDLE; + } + } + } + + self->shader = shader; + self->is_attached = FALSE; + self->tex_uniform = -1; + self->height_uniform = -1; + self->width_uniform = -1; + self->scrollbar_width_uniform = -1; + self->offset_top_uniform = -1; + self->offset_bottom_uniform = -1; + + if (shader != COGL_INVALID_HANDLE) + cogl_handle_ref (self->shader); +} + +ClutterEffect * +st_scroll_view_fade_new (void) +{ + return g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL); +} diff --git a/src/st/st-scroll-view-fade.h b/src/st/st-scroll-view-fade.h new file mode 100644 index 000000000..f8ec2d5a2 --- /dev/null +++ b/src/st/st-scroll-view-fade.h @@ -0,0 +1,40 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-scroll-view-fade.h: Edge fade effect for StScrollView + * + * Copyright 2010 Intel Corporation. + * Copyright 2011 Adel Gadllah + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef __ST_SCROLL_VIEW_FADE_H__ +#define __ST_SCROLL_VIEW_FADE_H__ + +#include + +G_BEGIN_DECLS + +#define ST_TYPE_SCROLL_VIEW_FADE (st_scroll_view_fade_get_type ()) +#define ST_SCROLL_VIEW_FADE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_SCROLL_VIEW_FADE, StScrollViewFade)) +#define ST_IS_SCROLL_VIEW_FADE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_SCROLL_VIEW_FADE)) + +typedef struct _StScrollViewFade StScrollViewFade; + +GType st_scroll_view_fade_get_type (void) G_GNUC_CONST; + +ClutterEffect *st_scroll_view_fade_new (void); + +G_END_DECLS + +#endif /* __ST_SCROLL_VIEW_FADE_H__ */ diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c index 14df6005b..c56182e11 100644 --- a/src/st/st-scroll-view.c +++ b/src/st/st-scroll-view.c @@ -62,6 +62,7 @@ #include "st-marshal.h" #include "st-scroll-bar.h" #include "st-scrollable.h" +#include "st-scroll-view-fade.h" #include #include @@ -93,20 +94,17 @@ struct _StScrollViewPrivate GtkPolicyType hscrollbar_policy; GtkPolicyType vscrollbar_policy; - ClutterActor *top_shadow; - ClutterActor *bottom_shadow; - gfloat row_size; gfloat column_size; - gboolean vshadows; + gboolean vfade; + StScrollViewFade *vfade_effect; + gboolean row_size_set : 1; gboolean column_size_set : 1; guint mouse_scroll : 1; guint hscrollbar_visible : 1; guint vscrollbar_visible : 1; - guint top_shadow_visible : 1; - guint bottom_shadow_visible : 1; }; enum { @@ -117,7 +115,7 @@ enum { PROP_HSCROLLBAR_POLICY, PROP_VSCROLLBAR_POLICY, PROP_MOUSE_SCROLL, - PROP_VSHADOWS + PROP_VFADE }; static void @@ -145,80 +143,50 @@ st_scroll_view_get_property (GObject *object, case PROP_MOUSE_SCROLL: g_value_set_boolean (value, priv->mouse_scroll); break; - case PROP_VSHADOWS: - g_value_set_boolean (value, priv->vshadows); + case PROP_VFADE: + g_value_set_boolean (value, priv->vfade); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } -static void -update_shadow_visibility (StScrollView *scroll) -{ - StScrollViewPrivate *priv = scroll->priv; - - if (priv->vshadows) - { - gdouble value, lower, upper, page_size; - - st_adjustment_get_values (priv->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size); - - priv->top_shadow_visible = value > lower + 0.1; - priv->bottom_shadow_visible = value < upper - page_size - 0.1; - } - else - { - priv->top_shadow_visible = FALSE; - priv->bottom_shadow_visible = FALSE; - } -} - /** - * st_scroll_view_set_vshadows: + * st_scroll_view_set_vfade: * @self: a #StScrollView - * @vshadows: Whether to enable vertical shadows + * @vfade: Whether to enable the vertical fade effect * - * Sets whether to show shadows at the top and bottom of the area. Shadows - * are omitted when fully scrolled to that edge. + * Sets whether to fade the content at the top and bottom of the area when not + * fully scrolled to that edge. */ void -st_scroll_view_set_vshadows (StScrollView *self, - gboolean vshadows) +st_scroll_view_set_vfade (StScrollView *self, + gboolean vfade) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv; - vshadows = vshadows != FALSE; - if (priv->vshadows == vshadows) + vfade = vfade != FALSE; + if (priv->vfade == vfade) return; - priv->vshadows = vshadows; + priv->vfade = vfade; - if (vshadows) + if (vfade) { - if (priv->top_shadow) - { - clutter_actor_show (priv->top_shadow); - clutter_actor_show (priv->bottom_shadow); - } - else - { - priv->top_shadow = g_object_new (ST_TYPE_BIN, "style-class", "top-shadow", NULL); - priv->bottom_shadow = g_object_new (ST_TYPE_BIN, "style-class", "bottom-shadow", NULL); + if (priv->vfade_effect == NULL) + priv->vfade_effect = g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL); - clutter_actor_set_parent (priv->bottom_shadow, CLUTTER_ACTOR (self)); - clutter_actor_set_parent (priv->top_shadow, CLUTTER_ACTOR (self)); - } + clutter_actor_add_effect (CLUTTER_ACTOR (self), CLUTTER_EFFECT (priv->vfade_effect)); } - else + else { - clutter_actor_hide (priv->top_shadow); - clutter_actor_hide (priv->bottom_shadow); + clutter_actor_remove_effect (CLUTTER_ACTOR (self), CLUTTER_EFFECT (priv->vfade_effect)); + priv->vfade_effect = NULL; } - update_shadow_visibility (self); + clutter_actor_queue_redraw (CLUTTER_ACTOR (self)); - g_object_notify (G_OBJECT (self), "vshadows"); + g_object_notify (G_OBJECT (self), "vfade"); } static void @@ -232,8 +200,8 @@ st_scroll_view_set_property (GObject *object, switch (property_id) { - case PROP_VSHADOWS: - st_scroll_view_set_vshadows (self, g_value_get_boolean (value)); + case PROP_VFADE: + st_scroll_view_set_vfade (self, g_value_get_boolean (value)); break; case PROP_MOUSE_SCROLL: st_scroll_view_set_mouse_scrolling (self, @@ -259,6 +227,12 @@ st_scroll_view_dispose (GObject *object) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv; + if (priv->vfade_effect) + { + clutter_actor_remove_effect (CLUTTER_ACTOR (object), CLUTTER_EFFECT (priv->vfade_effect)); + priv->vfade_effect = NULL; + } + if (priv->vscroll) clutter_actor_destroy (priv->vscroll); @@ -284,20 +258,6 @@ st_scroll_view_dispose (GObject *object) priv->vadjustment = NULL; } - /* since it's impossible to get a handle to these actors, we can - * just directly unparent them and not go through destroy/remove */ - if (priv->top_shadow) - { - clutter_actor_unparent (priv->top_shadow); - priv->top_shadow = NULL; - } - - if (priv->bottom_shadow) - { - clutter_actor_unparent (priv->bottom_shadow); - priv->bottom_shadow = NULL; - } - G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object); } @@ -314,11 +274,6 @@ st_scroll_view_paint (ClutterActor *actor) clutter_actor_paint (priv->hscroll); if (priv->vscrollbar_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) clutter_actor_paint (priv->vscroll); - - if (priv->top_shadow_visible) - clutter_actor_paint (priv->top_shadow); - if (priv->bottom_shadow_visible) - clutter_actor_paint (priv->bottom_shadow); } static void @@ -532,18 +487,6 @@ st_scroll_view_get_preferred_height (ClutterActor *actor, st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p); } -static gfloat -get_shadow_height (ClutterActor *shadow) -{ - gfloat natural_height; - - /* The shadows are empty StBin and have no height-for-width behavior */ - - clutter_actor_get_preferred_height (shadow, -1, NULL, &natural_height); - - return natural_height; -} - static void st_scroll_view_allocate (ClutterActor *actor, const ClutterActorBox *box, @@ -688,25 +631,6 @@ st_scroll_view_allocate (ClutterActor *actor, if (priv->child) clutter_actor_allocate (priv->child, &child_box, flags); - /* Shadows */ - if (priv->top_shadow && CLUTTER_ACTOR_IS_VISIBLE (priv->top_shadow)) - { - child_box.x1 = content_box.x1; - child_box.y1 = content_box.y1; - child_box.x2 = MAX (child_box.x1, content_box.x2 - sb_width); - child_box.y2 = content_box.y1 + get_shadow_height (priv->top_shadow); - clutter_actor_allocate (priv->top_shadow, &child_box, flags); - } - - if (priv->bottom_shadow && CLUTTER_ACTOR_IS_VISIBLE (priv->bottom_shadow)) - { - child_box.x1 = content_box.x1; - child_box.y1 = content_box.y2 - sb_height - get_shadow_height (priv->bottom_shadow); - child_box.x2 = MAX (content_box.x1, content_box.x2 - sb_width); - child_box.y2 = content_box.y2 - sb_height; - clutter_actor_allocate (priv->bottom_shadow, &child_box, flags); - } - priv->hscrollbar_visible = hscrollbar_visible; priv->vscrollbar_visible = vscrollbar_visible; } @@ -719,12 +643,6 @@ st_scroll_view_style_changed (StWidget *widget) st_widget_style_changed (ST_WIDGET (priv->hscroll)); st_widget_style_changed (ST_WIDGET (priv->vscroll)); - if (priv->top_shadow) - { - st_widget_style_changed (ST_WIDGET (priv->top_shadow)); - st_widget_style_changed (ST_WIDGET (priv->bottom_shadow)); - } - ST_WIDGET_CLASS (st_scroll_view_parent_class)->style_changed (widget); } @@ -857,31 +775,16 @@ st_scroll_view_class_init (StScrollViewClass *klass) PROP_MOUSE_SCROLL, pspec); - pspec = g_param_spec_boolean ("vshadows", + pspec = g_param_spec_boolean ("vfade", "Vertical Shadows", - "Show shadows at the top and and bottom of the area unless fully scrolled to that edge", + "Fade the content at the top and and bottom of the area unless fully scrolled to that edge", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, - PROP_VSHADOWS, + PROP_VFADE, pspec); } -static void -child_adjustment_changed_cb (StAdjustment *adjustment, - StScrollView *scroll) -{ - update_shadow_visibility (scroll); -} - -static void -child_adjustment_notify_value (GObject *gobject, - GParamSpec *pspec, - StScrollView *scroll) -{ - update_shadow_visibility (scroll); -} - static void st_scroll_view_init (StScrollView *self) { @@ -897,10 +800,6 @@ st_scroll_view_init (StScrollView *self) NULL); priv->vadjustment = g_object_new (ST_TYPE_ADJUSTMENT, NULL); - g_signal_connect (priv->vadjustment, "changed", - G_CALLBACK (child_adjustment_changed_cb), self); - g_signal_connect (priv->vadjustment, "notify::value", - G_CALLBACK (child_adjustment_notify_value), self); priv->vscroll = g_object_new (ST_TYPE_SCROLL_BAR, "adjustment", priv->vadjustment, "vertical", TRUE, @@ -988,12 +887,6 @@ st_scroll_view_foreach_with_internals (ClutterContainer *container, if (priv->vscroll != NULL) callback (priv->vscroll, user_data); - - if (priv->top_shadow) - { - callback (priv->top_shadow, user_data); - callback (priv->bottom_shadow, user_data); - } } static void diff --git a/src/st/st-scroll-view.h b/src/st/st-scroll-view.h index ddb1ebb7d..f0a03fe9d 100644 --- a/src/st/st-scroll-view.h +++ b/src/st/st-scroll-view.h @@ -84,8 +84,8 @@ void st_scroll_view_set_policy (StScrollView *scroll, GtkPolicyType hscroll, GtkPolicyType vscroll); -void st_scroll_view_set_vshadows (StScrollView *self, - gboolean vshadows); +void st_scroll_view_set_vfade (StScrollView *self, + gboolean vfade); G_END_DECLS diff --git a/tests/interactive/scroll-view-sizing.js b/tests/interactive/scroll-view-sizing.js index c7c8c9541..4ea5b8a58 100644 --- a/tests/interactive/scroll-view-sizing.js +++ b/tests/interactive/scroll-view-sizing.js @@ -319,17 +319,17 @@ function togglePolicy(button) { hpolicy.connect('clicked', function() { togglePolicy(hpolicy); }); vpolicy.connect('clicked', function() { togglePolicy(vpolicy); }); -let shadowsBox = new St.BoxLayout({ vertical: false }); -mainBox.add(shadowsBox); +let fadeBox = new St.BoxLayout({ vertical: false }); +mainBox.add(fadeBox); spacer = new St.Bin(); -shadowsBox.add(spacer, { expand: true }); +fadeBox.add(spacer, { expand: true }); -shadowsBox.add(new St.Label({ text: 'Vertical Shadows: '})); -let vshadows = new St.Button({ label: 'No', style: 'text-decoration: underline; color: #4444ff;' }); -shadowsBox.add(vshadows); +fadeBox.add(new St.Label({ text: 'Vertical Fade: '})); +let vfade = new St.Button({ label: 'No', style: 'text-decoration: underline; color: #4444ff;' }); +fadeBox.add(vfade); -function toggleShadows(button) { +function toggleFade(button) { switch(button.label) { case 'No': button.label = 'Yes'; @@ -338,10 +338,10 @@ function toggleShadows(button) { button.label = 'No'; break; } - scrollView.set_vshadows(vshadows.label == 'Yes'); + scrollView.set_vfade(vfade.label == 'Yes'); } -vshadows.connect('clicked', function() { toggleShadows(vshadows); }); +vfade.connect('clicked', function() { toggleFade(vfade); }); stage.show(); Clutter.main(); From 289d577bc15b6406930adcfd2ab970c0d6330796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 20 Jan 2011 21:15:05 +0100 Subject: [PATCH 02/30] st: Report correct paint volumes during transitions StWidget reports a paint volume large enough to paint the current theme node. As CSS transitions also paint the previous theme node, the reported paint volume may be incorrect, resulting in screen artifacts when painting outside the reported volume. Add st_theme_node_transition_get_paint_box() to calculate an allocation large enough to paint both theme nodes, and use it to report the correct paint volume during transitions. https://bugzilla.gnome.org/show_bug.cgi?id=640085 --- src/st/st-theme-node-transition.c | 28 ++++++++++++++++++++-------- src/st/st-theme-node-transition.h | 4 ++++ src/st/st-widget.c | 10 +++++++++- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/st/st-theme-node-transition.c b/src/st/st-theme-node-transition.c index ea1ba1c9e..3da45ae05 100644 --- a/src/st/st-theme-node-transition.c +++ b/src/st/st-theme-node-transition.c @@ -182,6 +182,22 @@ st_theme_node_transition_update (StThemeNodeTransition *transition, static void calculate_offscreen_box (StThemeNodeTransition *transition, const ClutterActorBox *allocation) +{ + ClutterActorBox paint_box; + + st_theme_node_transition_get_paint_box (transition, + allocation, + &paint_box); + transition->priv->offscreen_box.x1 = paint_box.x1 - allocation->x1; + transition->priv->offscreen_box.y1 = paint_box.y1 - allocation->y1; + transition->priv->offscreen_box.x2 = paint_box.x2 - allocation->x1; + transition->priv->offscreen_box.y2 = paint_box.y2 - allocation->y1; +} + +void +st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition, + const ClutterActorBox *allocation, + ClutterActorBox *paint_box) { StThemeNodeTransitionPrivate *priv = transition->priv; ClutterActorBox old_node_box, new_node_box; @@ -194,14 +210,10 @@ calculate_offscreen_box (StThemeNodeTransition *transition, allocation, &new_node_box); - priv->offscreen_box.x1 = MIN (old_node_box.x1, new_node_box.x1) - - allocation->x1; - priv->offscreen_box.y1 = MIN (old_node_box.y1, new_node_box.y1) - - allocation->y1; - priv->offscreen_box.x2 = MAX (old_node_box.x2, new_node_box.x2) - - allocation->x1; - priv->offscreen_box.y2 = MAX (old_node_box.y2, new_node_box.y2) - - allocation->y1; + paint_box->x1 = MIN (old_node_box.x1, new_node_box.x1); + paint_box->y1 = MIN (old_node_box.y1, new_node_box.y1); + paint_box->x2 = MAX (old_node_box.x2, new_node_box.x2); + paint_box->y2 = MAX (old_node_box.y2, new_node_box.y2); } static gboolean diff --git a/src/st/st-theme-node-transition.h b/src/st/st-theme-node-transition.h index 3bcef9beb..d6fd9358a 100644 --- a/src/st/st-theme-node-transition.h +++ b/src/st/st-theme-node-transition.h @@ -65,6 +65,10 @@ void st_theme_node_transition_paint (StThemeNodeTransition *transition, ClutterActorBox *allocation, guint8 paint_opacity); +void st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition, + const ClutterActorBox *allocation, + ClutterActorBox *paint_box); + G_END_DECLS #endif diff --git a/src/st/st-widget.c b/src/st/st-widget.c index 8ebe1dd27..71c1518f8 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -686,15 +686,23 @@ st_widget_get_paint_volume (ClutterActor *self, ClutterPaintVolume *volume) { ClutterActorBox paint_box, alloc_box; StThemeNode *theme_node; + StWidgetPrivate *priv; ClutterVertex origin; /* Setting the paint volume does not make sense when we don't have any allocation */ if (!clutter_actor_has_allocation (self)) return FALSE; + priv = ST_WIDGET(self)->priv; + theme_node = st_widget_get_theme_node (ST_WIDGET(self)); clutter_actor_get_allocation_box (self, &alloc_box); - st_theme_node_get_paint_box (theme_node, &alloc_box, &paint_box); + + if (priv->transition_animation) + st_theme_node_transition_get_paint_box (priv->transition_animation, + &alloc_box, &paint_box); + else + st_theme_node_get_paint_box (theme_node, &alloc_box, &paint_box); origin.x = paint_box.x1 - alloc_box.x1; origin.y = paint_box.y1 - alloc_box.y1; From 3b333c37fd7183105037ec415c033147849cd2ce Mon Sep 17 00:00:00 2001 From: Luca Ferretti Date: Fri, 21 Jan 2011 01:18:36 +0100 Subject: [PATCH 03/30] Updated Italian translation --- po/it.po | 102 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/po/it.po b/po/it.po index 4b6f085ed..fca96959f 100644 --- a/po/it.po +++ b/po/it.po @@ -8,14 +8,14 @@ msgid "" msgstr "" "Project-Id-Version: gnome-shell\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-15 01:10+0100\n" +"POT-Creation-Date: 2011-01-21 01:16+0100\n" "PO-Revision-Date: 2011-01-15 01:19+0100\n" "Last-Translator: Luca Ferretti \n" "Language-Team: Italian \n" +"Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../data/gnome-shell.desktop.in.in.h:1 @@ -161,6 +161,10 @@ msgid "Whether to collect stats about applications usage" msgstr "" "Indica se raccogliere statistiche riguardo l'utilizzo delle applicazioni" +#: ../data/org.gnome.shell.gschema.xml.in.h:22 +msgid "disabled OpenSearch providers" +msgstr "" + #: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:1 msgid "Clip the crosshairs at the center" msgstr "" @@ -296,7 +300,7 @@ msgstr "Impossibile analizzare il comando:" msgid "No such application" msgstr "Applicazione inesistente" -#: ../js/misc/util.js:143 ../js/ui/runDialog.js:364 +#: ../js/misc/util.js:143 ../js/ui/runDialog.js:351 #, c-format msgid "Execution of '%s' failed:" msgstr "Esecuzione di «%s» non riuscita:" @@ -344,7 +348,7 @@ msgstr "Rimuovi" #: ../js/ui/docDisplay.js:18 msgid "RECENT ITEMS" -msgstr "Elementi recenti" +msgstr "ELEMENTI RECENTI" #: ../js/ui/endSessionDialog.js:63 #, c-format @@ -358,7 +362,9 @@ msgstr "Termina sessione" # oddio... abbandonare il sistema sembra la nave che affonda... (LF) #: ../js/ui/endSessionDialog.js:65 msgid "Click Log Out to quit these applications and log out of the system." -msgstr "Fare clic su «Termina sessione» per chiudere queste applicazioni e abbandonare il sistema." +msgstr "" +"Fare clic su «Termina sessione» per chiudere queste applicazioni e " +"abbandonare il sistema." #: ../js/ui/endSessionDialog.js:66 #, c-format @@ -381,7 +387,9 @@ msgstr "Arresta" # usato un termine diverso, magari si capisce meglio (LF) #: ../js/ui/endSessionDialog.js:75 msgid "Click Shut Down to quit these applications and shut down the system." -msgstr "Fare clic su «Arresta» per chiudere queste applicazioni e spegnere il sistema." +msgstr "" +"Fare clic su «Arresta» per chiudere queste applicazioni e spegnere il " +"sistema." #: ../js/ui/endSessionDialog.js:76 #, c-format @@ -398,7 +406,9 @@ msgstr "Riavvia" #: ../js/ui/endSessionDialog.js:85 msgid "Click Restart to quit these applications and restart the system." -msgstr "Fare clic su «Riavvia» per chiudere queste applicazioni e riavviare il sistema." +msgstr "" +"Fare clic su «Riavvia» per chiudere queste applicazioni e riavviare il " +"sistema." #: ../js/ui/endSessionDialog.js:86 #, c-format @@ -413,7 +423,7 @@ msgstr "Riavvio del sistema." msgid "Confirm" msgstr "Conferma" -#: ../js/ui/endSessionDialog.js:400 ../js/ui/status/bluetooth.js:469 +#: ../js/ui/endSessionDialog.js:400 ../js/ui/status/bluetooth.js:470 msgid "Cancel" msgstr "Annulla" @@ -449,7 +459,7 @@ msgstr "Visualizza sorgente" msgid "Web Page" msgstr "Pagina web" -#: ../js/ui/messageTray.js:1748 +#: ../js/ui/messageTray.js:1765 msgid "System Information" msgstr "Informazione di sistema" @@ -519,22 +529,22 @@ msgid "Activities" msgstr "Attività" # (ndt) libera, ma unmount non si può proprio vedere... -#: ../js/ui/placeDisplay.js:112 +#: ../js/ui/placeDisplay.js:106 #, c-format msgid "Failed to unmount '%s'" msgstr "Impossibile scollegare «%s»" -#: ../js/ui/placeDisplay.js:115 +#: ../js/ui/placeDisplay.js:109 msgid "Retry" msgstr "Riprova" -#: ../js/ui/placeDisplay.js:160 +#: ../js/ui/placeDisplay.js:150 msgid "Connect to..." msgstr "Connetti a..." -#: ../js/ui/placeDisplay.js:559 +#: ../js/ui/placeDisplay.js:386 msgid "PLACES & DEVICES" -msgstr "Risorse e dispositivi" +msgstr "RISORSE E DISPOSITIVI" #. Translators: this MUST be either "toggle-switch-us" #. (for toggle switches containing the English words @@ -545,7 +555,7 @@ msgstr "Risorse e dispositivi" msgid "toggle-switch-us" msgstr "toggle-switch-intl" -#: ../js/ui/runDialog.js:222 +#: ../js/ui/runDialog.js:209 msgid "Please enter a command:" msgstr "Inserire un comando:" @@ -629,7 +639,7 @@ msgstr "Contrasto elevato" msgid "Large Text" msgstr "Caratteri grandi" -#: ../js/ui/status/bluetooth.js:42 ../js/ui/status/bluetooth.js:240 +#: ../js/ui/status/bluetooth.js:42 ../js/ui/status/bluetooth.js:241 msgid "Bluetooth" msgstr "Bluetooth" @@ -646,105 +656,105 @@ msgstr "Invia file al dispositivo..." msgid "Setup a New Device..." msgstr "Imposta un nuovo dispositivo..." -#: ../js/ui/status/bluetooth.js:94 +#: ../js/ui/status/bluetooth.js:95 msgid "Bluetooth Settings" msgstr "Impostazioni Bluetooth" # indica lo stato del device BT, per esempio gli auricolari # credo sia meglio l'aggettivo che il sostantivo -#: ../js/ui/status/bluetooth.js:191 +#: ../js/ui/status/bluetooth.js:192 msgid "Connection" msgstr "Collegato" -#: ../js/ui/status/bluetooth.js:227 +#: ../js/ui/status/bluetooth.js:228 msgid "Send Files..." msgstr "Invia file..." -#: ../js/ui/status/bluetooth.js:232 +#: ../js/ui/status/bluetooth.js:233 msgid "Browse Files..." msgstr "Esplora file..." -#: ../js/ui/status/bluetooth.js:241 +#: ../js/ui/status/bluetooth.js:242 msgid "Error browsing device" msgstr "Errore nell'esplorare il dispositivo" -#: ../js/ui/status/bluetooth.js:242 +#: ../js/ui/status/bluetooth.js:243 #, c-format msgid "The requested device cannot be browsed, error is '%s'" msgstr "Non è possibile esplorare il dispositivo richiesto, l'errore è «%s»" -#: ../js/ui/status/bluetooth.js:250 ../js/ui/status/keyboard.js:78 +#: ../js/ui/status/bluetooth.js:251 ../js/ui/status/keyboard.js:78 msgid "Keyboard Settings" msgstr "Impostazioni tastiera" -#: ../js/ui/status/bluetooth.js:255 +#: ../js/ui/status/bluetooth.js:256 msgid "Mouse Settings" msgstr "Impostazioni mouse" -#: ../js/ui/status/bluetooth.js:262 ../js/ui/status/volume.js:63 +#: ../js/ui/status/bluetooth.js:263 ../js/ui/status/volume.js:63 msgid "Sound Settings" msgstr "Impostazioni audio" -#: ../js/ui/status/bluetooth.js:336 ../js/ui/status/bluetooth.js:370 -#: ../js/ui/status/bluetooth.js:410 ../js/ui/status/bluetooth.js:443 +#: ../js/ui/status/bluetooth.js:337 ../js/ui/status/bluetooth.js:371 +#: ../js/ui/status/bluetooth.js:411 ../js/ui/status/bluetooth.js:444 msgid "Bluetooth Agent" msgstr "" -#: ../js/ui/status/bluetooth.js:371 +#: ../js/ui/status/bluetooth.js:372 #, c-format msgid "Authorization request from %s" msgstr "Richesta autorizzazione da %s" -#: ../js/ui/status/bluetooth.js:377 +#: ../js/ui/status/bluetooth.js:378 #, c-format msgid "Device %s wants access to the service '%s'" msgstr "Il dispositivo %s vuole accedere al servizio «%s»" -#: ../js/ui/status/bluetooth.js:379 +#: ../js/ui/status/bluetooth.js:380 msgid "Always grant access" msgstr "Consenti sempre accesso" -#: ../js/ui/status/bluetooth.js:380 +#: ../js/ui/status/bluetooth.js:381 msgid "Grant this time only" msgstr "Consenti solo stavolta" -#: ../js/ui/status/bluetooth.js:381 +#: ../js/ui/status/bluetooth.js:382 msgid "Reject" msgstr "Rifiuta" -#: ../js/ui/status/bluetooth.js:411 +#: ../js/ui/status/bluetooth.js:412 #, c-format msgid "Pairing confirmation for %s" msgstr "Conferma associazione per %s" -#: ../js/ui/status/bluetooth.js:417 ../js/ui/status/bluetooth.js:451 +#: ../js/ui/status/bluetooth.js:418 ../js/ui/status/bluetooth.js:452 #, c-format msgid "Device %s wants to pair with this computer" msgstr "Il dispositivo %s vuole associarsi con questo computer" -#: ../js/ui/status/bluetooth.js:418 +#: ../js/ui/status/bluetooth.js:419 #, c-format msgid "Please confirm whether the PIN '%s' matches the one on the device." msgstr "Confermare la corrispondenza del PIN «%s» con quello sul dispositivo." -#: ../js/ui/status/bluetooth.js:420 +#: ../js/ui/status/bluetooth.js:421 msgid "Matches" msgstr "Corrisponde" -#: ../js/ui/status/bluetooth.js:421 +#: ../js/ui/status/bluetooth.js:422 msgid "Does not match" msgstr "Non corrisponde" -#: ../js/ui/status/bluetooth.js:444 +#: ../js/ui/status/bluetooth.js:445 #, c-format msgid "Pairing request for %s" msgstr "Richiesta associazione per %s" -#: ../js/ui/status/bluetooth.js:452 +#: ../js/ui/status/bluetooth.js:453 msgid "Please enter the PIN mentioned on the device." msgstr "Inserire il PIN indicato sul dispositivo." -#: ../js/ui/status/bluetooth.js:468 +#: ../js/ui/status/bluetooth.js:469 msgid "OK" msgstr "OK" @@ -867,7 +877,7 @@ msgstr "%s non è disponibile." #: ../js/ui/telepathyClient.js:666 #, no-c-format msgid "Sent at %X on %A" -msgstr "Inviato alle %-H.%M.%S di %A" +msgstr "Inviato alle %-H.%M di %A" # FIXME ma ha senso in inglese??? #: ../js/ui/viewSelector.js:26 @@ -919,32 +929,32 @@ msgstr[1] "%u ingressi" msgid "System Sounds" msgstr "Audio di sistema" -#: ../src/shell-global.c:1233 +#: ../src/shell-global.c:1366 msgid "Less than a minute ago" msgstr "Meno di un minuto fa" -#: ../src/shell-global.c:1237 +#: ../src/shell-global.c:1370 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "%d minuto fa" msgstr[1] "%d minuti fa" -#: ../src/shell-global.c:1242 +#: ../src/shell-global.c:1375 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "%d ora fa" msgstr[1] "%d ore fa" -#: ../src/shell-global.c:1247 +#: ../src/shell-global.c:1380 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d giorno fa" msgstr[1] "%d giorni fa" -#: ../src/shell-global.c:1252 +#: ../src/shell-global.c:1385 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" From d6a6e6220a6cfc635634094f54c30bcc3f12bc5e Mon Sep 17 00:00:00 2001 From: Adel Gadllah Date: Fri, 21 Jan 2011 18:55:00 +0100 Subject: [PATCH 04/30] recorder: Force full stage redraws during recording Partial redraws can result into artifacts in the recording, so disable them while recording. https://bugzilla.gnome.org/show_bug.cgi?id=640206 --- src/shell-recorder.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/shell-recorder.c b/src/shell-recorder.c index a129f7d7b..57377bd82 100644 --- a/src/shell-recorder.c +++ b/src/shell-recorder.c @@ -83,6 +83,7 @@ struct _ShellRecorder { guint redraw_idle; guint update_memory_used_timeout; guint update_pointer_timeout; + guint repaint_hook_id; }; struct _RecorderPipeline @@ -239,6 +240,22 @@ get_memory_target (void) return DEFAULT_MEMORY_TARGET; } +/* + * Used to force full stage redraws during recording to avoid artifacts + * + * Note: That this will cause the stage to be repainted on + * every animation frame even if the frame wouldn't normally cause any new + * drawing + */ +static gboolean +recorder_repaint_hook (gpointer data) +{ + ClutterActor *stage = data; + clutter_actor_queue_redraw (stage); + + return TRUE; +} + static void shell_recorder_init (ShellRecorder *recorder) { @@ -1682,6 +1699,9 @@ shell_recorder_record (ShellRecorder *recorder) recorder->state = RECORDER_STATE_RECORDING; recorder_add_update_pointer_timeout (recorder); + /* Set up repaint hook */ + recorder->repaint_hook_id = clutter_threads_add_repaint_func(recorder_repaint_hook, recorder->stage, NULL); + /* Record an initial frame and also redraw with the indicator */ clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage)); @@ -1723,6 +1743,12 @@ shell_recorder_pause (ShellRecorder *recorder) /* Queue a redraw to remove the recording indicator */ clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage)); + + if (recorder->repaint_hook_id != 0) + { + clutter_threads_remove_repaint_func (recorder->repaint_hook_id); + recorder->repaint_hook_id = 0; + } } /** From 20f49e8c8917bff0d6e17ba6218fcbaec3b016e9 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Wed, 12 Jan 2011 16:00:54 +0100 Subject: [PATCH 05/30] KeyboardStatus: handle modifier key indicators Introduce a generic framework for on/off indicators that are shown in the panel, next to the system status area, and use it for showing the status of modifier keys. https://bugzilla.gnome.org/show_bug.cgi?id=600771 --- js/ui/panel.js | 28 ++++++++++++++------ js/ui/status/keyboard.js | 55 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/js/ui/panel.js b/js/ui/panel.js index 97789d802..c1baf2c8a 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -43,6 +43,10 @@ const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = { if (Config.HAVE_BLUETOOTH) STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['bluetooth'] = imports.ui.status.bluetooth.Indicator; +const STANDARD_TRAY_INDICATOR_FACTORIES = [ + imports.ui.status.keyboard.ModifierIndicatorFactory +]; + // in org.gnome.desktop.interface const CLOCK_FORMAT_KEY = 'clock-format'; @@ -809,17 +813,18 @@ Panel.prototype = { /* right */ - // System status applets live in statusBox, while legacy tray icons + // On-off indicators (for keyboard leds and accessx) are in indicatorBox + // System status applets live in statusBox, and legacy tray icons // live in trayBox // The trayBox is hidden when there are no tray icons. - let statusBox = new St.BoxLayout({ name: 'statusTray' }); - let trayBox = new St.BoxLayout({ name: 'legacyTray' }); - this._trayBox = trayBox; - this._statusBox = statusBox; + this._indicatorBox = new St.BoxLayout({ name: 'indicatorBox' }); + this._trayBox = new St.BoxLayout({ name: 'legacyTray' }); + this._statusBox = new St.BoxLayout({ name: 'statusTray' }); - trayBox.hide(); - this._rightBox.add(trayBox); - this._rightBox.add(statusBox); + this._trayBox.hide(); + this._rightBox.add(this._indicatorBox); + this._rightBox.add(this._trayBox); + this._rightBox.add(this._statusBox); Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded)); Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); @@ -869,6 +874,13 @@ Panel.prototype = { }, startStatusArea: function() { + for (let i = 0; i < STANDARD_TRAY_INDICATOR_FACTORIES.length; i++) { + let factory = new STANDARD_TRAY_INDICATOR_FACTORIES[i]; + let indicators = factory.getIndicators(); + for (let j = 0; j < indicators.length; j++) + this._indicatorBox.add(indicators[j]); + } + for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) { let role = STANDARD_TRAY_ICON_ORDER[i]; let constructor = STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]; diff --git a/js/ui/status/keyboard.js b/js/ui/status/keyboard.js index 404a766cc..3813c62de 100644 --- a/js/ui/status/keyboard.js +++ b/js/ui/status/keyboard.js @@ -42,7 +42,7 @@ LayoutMenuItem.prototype = { }; function XKBIndicator() { - this._init.apply(this, arguments); + this._init.call(this); } XKBIndicator.prototype = { @@ -75,7 +75,7 @@ XKBIndicator.prototype = { this._sync_config(); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this.menu.addAction(_("Keyboard Settings"), function() { + this.menu.addAction(_("Localization Settings"), function() { GLib.spawn_command_line_async('gnome-control-center region'); }); }, @@ -203,4 +203,53 @@ XKBIndicator.prototype = { for (let i = 0; i < this._labelActors.length; i++) this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags); } -}; \ No newline at end of file +}; + +function ModifierIndicatorFactory() { + this._init.call(this); +} + +ModifierIndicatorFactory.prototype = { + _init: function() { + this._settings = new Gio.Settings({ schema: INDICATOR_SCHEMA }); + this._settings.connect('changed::show-keyboard-leds-indicator', Lang.bind(this, this._changed)); + + this._config = Gkbd.Configuration.get(); + this._config.connect('indicators-changed', Lang.bind(this, this._changed)); + + this._scrollLock = new St.Icon({ icon_name: 'kbdled-scroll-lock', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); + this._numLock = new St.Icon({ icon_name: 'kbdled-num-lock', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); + this._capsLock = new St.Icon({ icon_name: 'kbdled-caps-lock', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); + + this._changed(); + }, + + getIndicators: function() { + return [this._scrollLock, this._numLock, this._capsLock]; + }, + + _changed: function() { + let enable = this._settings.get_boolean('show-keyboard-leds-indicator'); + + if (enable) { + if (this._config.get_scroll_lock_state()) + this._scrollLock.show(); + else + this._scrollLock.hide(); + + if (this._config.get_num_lock_state()) + this._numLock.show(); + else + this._numLock.hide(); + + if (this._config.get_caps_lock_state()) + this._capsLock.show(); + else + this._capsLock.hide(); + } else { + this._scrollLock.hide(); + this._numLock.hide(); + this._capsLock.hide(); + } + } +}; From c6baee26229972984abcf709f6e8516f504c51c5 Mon Sep 17 00:00:00 2001 From: Adel Gadllah Date: Fri, 21 Jan 2011 19:33:11 +0100 Subject: [PATCH 06/30] recorder: Switch to webm The vp8 codec provides better performance in pretty much all cases compared to theora while still being free (as in not patent encumbered). Also add a %T placeholder to the pipeline string which will be replaced with the a thread count based on the target system. https://bugzilla.gnome.org/show_bug.cgi?id=632595 --- data/org.gnome.shell.gschema.xml.in | 6 ++-- src/shell-recorder.c | 47 ++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in index c19463099..d45cba319 100644 --- a/data/org.gnome.shell.gschema.xml.in +++ b/data/org.gnome.shell.gschema.xml.in @@ -101,11 +101,13 @@ take care of its own output - this might be used to send the output to an icecast server via shout2send or similar. When unset or set to an empty value, the default pipeline will be used. This is currently - 'videorate ! theoraenc ! oggmux' and records to Ogg Theora. + 'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' + and records to WEBM using the VP8 codec. %T is used as a placeholder + for a guess at the optimal thread count on the system. - 'ogv' + 'webm' <_summary>File extension used for storing the screencast <_description> The filename for recorded screencasts will be a unique filename diff --git a/src/shell-recorder.c b/src/shell-recorder.c index 57377bd82..a1c626b41 100644 --- a/src/shell-recorder.c +++ b/src/shell-recorder.c @@ -147,11 +147,11 @@ G_DEFINE_TYPE(ShellRecorder, shell_recorder, G_TYPE_OBJECT); * (Theora does have some support for frames at non-uniform times, but * things seem to break down if there are large gaps.) */ -#define DEFAULT_PIPELINE "videorate ! theoraenc ! oggmux" +#define DEFAULT_PIPELINE "videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux" -/* The default filename pattern. Example shell-20090311b-2.ogg +/* The default filename pattern. Example shell-20090311b-2.webm */ -#define DEFAULT_FILENAME "shell-%d%u-%c.ogg" +#define DEFAULT_FILENAME "shell-%d%u-%c.webm" /* If we can find the amount of memory on the machine, we use half * of that for memory_target, otherwise, we use this value, in kB. @@ -1493,11 +1493,47 @@ recorder_pipeline_closed (RecorderPipeline *pipeline) recorder_pipeline_free (pipeline); } +/* + * Replaces '%T' in the passed pipeline with the thread count, + * the maximum possible value is 64 (limit of what vp8enc supports) + * + * It is assumes that %T occurs only once. + */ +static char* +substitute_thread_count (const char *pipeline) +{ + char *tmp; + int n_threads; + GString *result; + + tmp = strstr (pipeline, "%T"); + + if (!tmp) + return g_strdup (pipeline); + +#ifdef _SC_NPROCESSORS_ONLN + { + int n_processors = sysconf (_SC_NPROCESSORS_ONLN); /* includes hyper-threading */ + n_threads = MIN (MAX (1, n_processors - 1), 64); + } +#else + n_threads = 3; +#endif + + result = g_string_new (NULL); + g_string_append_len (result, pipeline, tmp - pipeline); + g_string_append_printf (result, "%d", n_threads); + g_string_append (result, tmp + 2); + + return g_string_free (result, FALSE);; +} + static gboolean recorder_open_pipeline (ShellRecorder *recorder) { RecorderPipeline *pipeline; const char *pipeline_description; + char *parsed_pipeline; GError *error = NULL; GstBus *bus; @@ -1509,9 +1545,12 @@ recorder_open_pipeline (ShellRecorder *recorder) if (!pipeline_description) pipeline_description = DEFAULT_PIPELINE; - pipeline->pipeline = gst_parse_launch_full (pipeline_description, NULL, + parsed_pipeline = substitute_thread_count (pipeline_description); + + pipeline->pipeline = gst_parse_launch_full (parsed_pipeline, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &error); + g_free (parsed_pipeline); if (pipeline->pipeline == NULL) { From 39f7aa84572fd09e4e3706bc564517392a8baf2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Sat, 22 Jan 2011 16:41:46 +0100 Subject: [PATCH 07/30] Updated Spanish translation --- po/es.po | 120 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/po/es.po b/po/es.po index 0fb03cdb4..c45ed73a8 100644 --- a/po/es.po +++ b/po/es.po @@ -1,17 +1,17 @@ # Spanish translation of gnome-shell. # Copyright (C) 2009 gnome-shell's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-shell package. -# Jorge González , 2009, 2010. # Daniel Mustieles , 2010, 2011. +# Jorge González , 2009, 2010, 2011. # msgid "" msgstr "" "Project-Id-Version: gnome-shell.master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2011-01-17 21:45+0000\n" -"PO-Revision-Date: 2011-01-19 16:17+0100\n" -"Last-Translator: Daniel Mustieles \n" +"POT-Creation-Date: 2011-01-21 18:46+0000\n" +"PO-Revision-Date: 2011-01-22 16:34+0100\n" +"Last-Translator: Jorge González \n" "Language-Team: Español \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -76,7 +76,18 @@ msgstr "Si es cierta muestra la fecha de semana ISO en el calendario." msgid "List of desktop file IDs for favorite applications" msgstr "Lista de ID de archivos de escritorio para las aplicaciones favoritas" -#: ../data/org.gnome.shell.gschema.xml.in.h:11 +#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#, no-c-format +#| msgid "" +#| "Sets the GStreamer pipeline used to encode recordings. It follows the " +#| "syntax used for gst-launch. The pipeline should have an unconnected sink " +#| "pad where the recorded video is recorded. It will normally have a " +#| "unconnected source pad; output from that pad will be written into the " +#| "output file. However the pipeline can also take care of its own output - " +#| "this might be used to send the output to an icecast server via shout2send " +#| "or similar. When unset or set to an empty value, the default pipeline " +#| "will be used. This is currently 'videorate ! theoraenc ! oggmux' and " +#| "records to Ogg Theora." msgid "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -85,32 +96,35 @@ msgid "" "pipeline can also take care of its own output - this might be used to send " "the output to an icecast server via shout2send or similar. When unset or set " "to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and " +"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess " +"at the optimal thread count on the system." msgstr "" "Establece la tubería GStreamer usada para codificar grabaciones. Sigue la " -"sintaxis usada para gst-launch. La tubería debería tener un sumidero («sink») " -"de ensamblaje/sesensamblaje donde el vídeo que se está grabando se graba. " -"Generalmente tendrá un origen de ensamblado/desensamblado; la salida de ese " -"punto se escibirá en el archivo de salida. No obstante la tubería también " -"puede tomar parte en su propia salida; esto se puede usar para enviar la " -"salida a un servidor «icecast» a través de shout2send o similar. Cuando no " -"está establecido o lo está a un valor vacío, se usará la tubería " -"predeterminada. Actualmente es «videorate ! theoraenc ! oggmux» y greba en " -"Ogg Theora." +"sintaxis usada para gst-launch. La tubería debería tener un sumidero " +"(«sink») de ensamblaje/sesensamblaje donde el vídeo que se está grabando se " +"graba. Generalmente tendrá un origen de ensamblado/desensamblado; la salida " +"de ese punto se escibirá en el archivo de salida. No obstante la tubería " +"también puede tomar parte en su propia salida; esto se puede usar para " +"enviar la salida a un servidor «icecast» a través de shout2send o similar. " +"Cuando no está establecido o lo está a un valor vacío, se usará la tubería " +"predeterminada. Actualmente es «videorate ! vp8enc quality=10 speed=2 " +"threads=%T ! queue ! webmmux» y greba en WEBM usando el códec VP8. Se usa %T " +"como suposición para el número de hilos óptimos en el sistema." -#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#: ../data/org.gnome.shell.gschema.xml.in.h:13 msgid "Show date in clock" msgstr "Mostrar la fecha en el reloj" -#: ../data/org.gnome.shell.gschema.xml.in.h:13 +#: ../data/org.gnome.shell.gschema.xml.in.h:14 msgid "Show the week date in the calendar" msgstr "Mostrar la fecha de la semana en el calendario" -#: ../data/org.gnome.shell.gschema.xml.in.h:14 +#: ../data/org.gnome.shell.gschema.xml.in.h:15 msgid "Show time with seconds" msgstr "Mostrar la hora con segundos" -#: ../data/org.gnome.shell.gschema.xml.in.h:15 +#: ../data/org.gnome.shell.gschema.xml.in.h:16 msgid "" "The applications corresponding to these identifiers will be displayed in the " "favorites area." @@ -118,7 +132,7 @@ msgstr "" "Las aplicaciones correspondientes con esos identificadores se mostrarán en " "el área de favoritos." -#: ../data/org.gnome.shell.gschema.xml.in.h:16 +#: ../data/org.gnome.shell.gschema.xml.in.h:17 msgid "" "The filename for recorded screencasts will be a unique filename based on the " "current date, and use this extension. It should be changed when recording to " @@ -128,7 +142,7 @@ msgstr "" "basado en la fecha actual y usará esta extensión. Se debería cambiar al " "grabar en otro formato contenedor diferente." -#: ../data/org.gnome.shell.gschema.xml.in.h:17 +#: ../data/org.gnome.shell.gschema.xml.in.h:18 msgid "" "The framerate of the resulting screencast recordered by GNOME Shell's " "screencast recorder in frames-per-second." @@ -136,11 +150,11 @@ msgstr "" "La tasa de fotogramas de la grabación resultante grabada por el grabador de " "«screencast» de GNOME Shell, en fotogramas por segundo." -#: ../data/org.gnome.shell.gschema.xml.in.h:18 +#: ../data/org.gnome.shell.gschema.xml.in.h:19 msgid "The gstreamer pipeline used to encode the screencast" msgstr "La tubería de gstreamer usada para codificar el «screencast»" -#: ../data/org.gnome.shell.gschema.xml.in.h:19 +#: ../data/org.gnome.shell.gschema.xml.in.h:20 msgid "" "The shell normally monitors active applications in order to present the most " "used ones (e.g. in launchers). While this data will be kept private, you may " @@ -152,16 +166,16 @@ msgstr "" "mantienen de forma privada, puede querer desactivarlo por razones de " "privacidad. Note que haciéndolo no eliminará los datos ya guardados." -#: ../data/org.gnome.shell.gschema.xml.in.h:20 +#: ../data/org.gnome.shell.gschema.xml.in.h:21 msgid "Uuids of extensions to disable" msgstr "Uuid de las extensiones que desactivar" -#: ../data/org.gnome.shell.gschema.xml.in.h:21 +#: ../data/org.gnome.shell.gschema.xml.in.h:22 msgid "Whether to collect stats about applications usage" msgstr "" "Indica si se deben recolectar estadísticas acerca del uso de las aplicaciones" -#: ../data/org.gnome.shell.gschema.xml.in.h:22 +#: ../data/org.gnome.shell.gschema.xml.in.h:23 msgid "disabled OpenSearch providers" msgstr "proveedores OpenSearch desactivados" @@ -482,7 +496,7 @@ msgstr "Ver fuente" msgid "Web Page" msgstr "Página web" -#: ../js/ui/messageTray.js:1748 +#: ../js/ui/messageTray.js:1765 msgid "System Information" msgstr "Información del sistema" @@ -499,54 +513,54 @@ msgid "Applications" msgstr "Aplicaciones" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:479 +#: ../js/ui/panel.js:483 #, c-format msgid "Quit %s" msgstr "Salir de %s" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:564 +#: ../js/ui/panel.js:568 msgid "%a %b %e, %R:%S" msgstr "%a %e de %b, %R:%S" -#: ../js/ui/panel.js:565 +#: ../js/ui/panel.js:569 msgid "%a %b %e, %R" msgstr "%a %e de %b, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:569 +#: ../js/ui/panel.js:573 msgid "%a %R:%S" msgstr "%a %R:%S" -#: ../js/ui/panel.js:570 +#: ../js/ui/panel.js:574 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:581 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e de %b, %H:%M:%S" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:582 msgid "%a %b %e, %l:%M %p" msgstr "%a %e de %b, %H:%M" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:582 +#: ../js/ui/panel.js:586 msgid "%a %l:%M:%S %p" msgstr "%a %H:%M:%S" -#: ../js/ui/panel.js:583 +#: ../js/ui/panel.js:587 msgid "%a %l:%M %p" msgstr "%a %H:%M" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:728 +#: ../js/ui/panel.js:732 msgid "Activities" msgstr "Actividades" @@ -701,7 +715,7 @@ msgstr "Error al examinar el dispositivo" msgid "The requested device cannot be browsed, error is '%s'" msgstr "No se puede examinar el dispositivo solicitado, el error es «%s»" -#: ../js/ui/status/bluetooth.js:251 ../js/ui/status/keyboard.js:78 +#: ../js/ui/status/bluetooth.js:251 msgid "Keyboard Settings" msgstr "Configuración del teclado" @@ -776,6 +790,11 @@ msgstr "Introduzca el PIN mencionado en el dispositivo." msgid "OK" msgstr "Aceptar" +#: ../js/ui/status/keyboard.js:78 +#| msgid "Sound Settings" +msgid "Localization Settings" +msgstr "Configuración regional" + #: ../js/ui/status/power.js:85 msgid "Power Settings" msgstr "Configuración de energía" @@ -1015,8 +1034,8 @@ msgstr "%1$s: %2$s" #~ "If true and format is either \"12-hour\" or \"24-hour\", display seconds " #~ "in time." #~ msgstr "" -#~ "Si es cierta y el formato es «12-horas» o «24-horas», muestra los segundos " -#~ "en la hora." +#~ "Si es cierta y el formato es «12-horas» o «24-horas», muestra los " +#~ "segundos en la hora." #~ msgid "" #~ "This key specifies the format used by the panel clock when the format key " @@ -1033,18 +1052,19 @@ msgstr "%1$s: %2$s" #~ msgid "" #~ "This key specifies the hour format used by the panel clock. Possible " #~ "values are \"12-hour\", \"24-hour\", \"unix\" and \"custom\". If set to " -#~ "\"unix\", the clock will display time in seconds since Epoch, i.e. 1970-" -#~ "01-01. If set to \"custom\", the clock will display time according to the " -#~ "format specified in the custom_format key. Note that if set to either " -#~ "\"unix\" or \"custom\", the show_date and show_seconds keys are ignored." +#~ "\"unix\", the clock will display time in seconds since Epoch, i.e. " +#~ "1970-01-01. If set to \"custom\", the clock will display time according " +#~ "to the format specified in the custom_format key. Note that if set to " +#~ "either \"unix\" or \"custom\", the show_date and show_seconds keys are " +#~ "ignored." #~ msgstr "" #~ "Esta clave especifica el formato de la hora especificado por el reloj del " -#~ "panel. Los valores posibles son «12-hour» (12 horas), «24-hour» (24 horas), " -#~ "«unix» y «custom» (personalizado).Si se establece a «unix» el reloj mostrará " -#~ "la hora en segundos desde la época (1 de enero de 1970). Si se establece " -#~ "a «custom» el reloj mostrará la hora según el formato especificado en la " -#~ "clave «custom_format». Note que si se establece a «unix» o «custom» se " -#~ "ignoran las claves «show_date» y «show_seconds»." +#~ "panel. Los valores posibles son «12-hour» (12 horas), «24-hour» (24 " +#~ "horas), «unix» y «custom» (personalizado).Si se establece a «unix» el " +#~ "reloj mostrará la hora en segundos desde la época (1 de enero de 1970). " +#~ "Si se establece a «custom» el reloj mostrará la hora según el formato " +#~ "especificado en la clave «custom_format». Note que si se establece a " +#~ "«unix» o «custom» se ignoran las claves «show_date» y «show_seconds»." #~ msgid "Clock Format" #~ msgstr "Formato del reloj" From 5b71788a84de90046a65d286c2d04372d5443e0a Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Sun, 23 Jan 2011 02:43:13 +0200 Subject: [PATCH 08/30] Updated Hebrew translation. --- po/he.po | 87 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/po/he.po b/po/he.po index fda1af7c1..e87ad6e7a 100644 --- a/po/he.po +++ b/po/he.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-18 13:02+0200\n" -"PO-Revision-Date: 2011-01-18 13:05+0200\n" +"POT-Creation-Date: 2011-01-23 02:42+0200\n" +"PO-Revision-Date: 2011-01-23 02:43+0200\n" "Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew \n" "MIME-Version: 1.0\n" @@ -73,7 +73,8 @@ msgstr "If true, display the ISO week date in the calendar." msgid "List of desktop file IDs for favorite applications" msgstr "List of desktop file IDs for favorite applications" -#: ../data/org.gnome.shell.gschema.xml.in.h:11 +#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#, no-c-format msgid "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -82,7 +83,9 @@ msgid "" "pipeline can also take care of its own output - this might be used to send " "the output to an icecast server via shout2send or similar. When unset or set " "to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and " +"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess " +"at the optimal thread count on the system." msgstr "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -91,51 +94,53 @@ msgstr "" "pipeline can also take care of its own output - this might be used to send " "the output to an icecast server via shout2send or similar. When unset or set " "to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and " +"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess " +"at the optimal thread count on the system." -#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#: ../data/org.gnome.shell.gschema.xml.in.h:13 msgid "Show date in clock" msgstr "Show date in clock" -#: ../data/org.gnome.shell.gschema.xml.in.h:13 +#: ../data/org.gnome.shell.gschema.xml.in.h:14 msgid "Show the week date in the calendar" msgstr "Show the week date in the calendar" -#: ../data/org.gnome.shell.gschema.xml.in.h:14 +#: ../data/org.gnome.shell.gschema.xml.in.h:15 msgid "Show time with seconds" msgstr "Show time with seconds" -#: ../data/org.gnome.shell.gschema.xml.in.h:15 -msgid "" -"The applications corresponding to these identifiers will be displayed in the " -"favorites area." -msgstr "" -"The applications corresponding to these identifiers will be displayed in the " -"favorites area." - #: ../data/org.gnome.shell.gschema.xml.in.h:16 msgid "" -"The filename for recorded screencasts will be a unique filename based on the " -"current date, and use this extension. It should be changed when recording to " -"a different container format." +"The applications corresponding to these identifiers will be displayed in the " +"favorites area." msgstr "" -"The filename for recorded screencasts will be a unique filename based on the " -"current date, and use this extension. It should be changed when recording to " -"a different container format." +"The applications corresponding to these identifiers will be displayed in the " +"favorites area." #: ../data/org.gnome.shell.gschema.xml.in.h:17 msgid "" +"The filename for recorded screencasts will be a unique filename based on the " +"current date, and use this extension. It should be changed when recording to " +"a different container format." +msgstr "" +"The filename for recorded screencasts will be a unique filename based on the " +"current date, and use this extension. It should be changed when recording to " +"a different container format." + +#: ../data/org.gnome.shell.gschema.xml.in.h:18 +msgid "" "The framerate of the resulting screencast recordered by GNOME Shell's " "screencast recorder in frames-per-second." msgstr "" "The framerate of the resulting screencast recordered by GNOME Shell's " "screencast recorder in frames-per-second." -#: ../data/org.gnome.shell.gschema.xml.in.h:18 +#: ../data/org.gnome.shell.gschema.xml.in.h:19 msgid "The gstreamer pipeline used to encode the screencast" msgstr "The gstreamer pipeline used to encode the screencast" -#: ../data/org.gnome.shell.gschema.xml.in.h:19 +#: ../data/org.gnome.shell.gschema.xml.in.h:20 msgid "" "The shell normally monitors active applications in order to present the most " "used ones (e.g. in launchers). While this data will be kept private, you may " @@ -147,15 +152,15 @@ msgstr "" "want to disable this for privacy reasons. Please note that doing so won't " "remove already saved data." -#: ../data/org.gnome.shell.gschema.xml.in.h:20 +#: ../data/org.gnome.shell.gschema.xml.in.h:21 msgid "Uuids of extensions to disable" msgstr "Uuids of extensions to disable" -#: ../data/org.gnome.shell.gschema.xml.in.h:21 +#: ../data/org.gnome.shell.gschema.xml.in.h:22 msgid "Whether to collect stats about applications usage" msgstr "Whether to collect stats about applications usage" -#: ../data/org.gnome.shell.gschema.xml.in.h:22 +#: ../data/org.gnome.shell.gschema.xml.in.h:23 msgid "disabled OpenSearch providers" msgstr "disabled OpenSearch providers" @@ -471,7 +476,7 @@ msgstr "צפייה במקור" msgid "Web Page" msgstr "דף אינטרנט" -#: ../js/ui/messageTray.js:1748 +#: ../js/ui/messageTray.js:1765 msgid "System Information" msgstr "פרטי המערכת" @@ -488,54 +493,54 @@ msgid "Applications" msgstr "יישומים" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:479 +#: ../js/ui/panel.js:483 #, c-format msgid "Quit %s" msgstr "יציאה מ־%s" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:564 +#: ../js/ui/panel.js:568 msgid "%a %b %e, %R:%S" msgstr "%a %b %e, %R:%S" -#: ../js/ui/panel.js:565 +#: ../js/ui/panel.js:569 msgid "%a %b %e, %R" msgstr "%a %b %e, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:569 +#: ../js/ui/panel.js:573 msgid "%a %R:%S" msgstr "%a %R:%S" -#: ../js/ui/panel.js:570 +#: ../js/ui/panel.js:574 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:581 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %b %e, %l:%M:%S %p" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:582 msgid "%a %b %e, %l:%M %p" msgstr "%a %b %e, %l:%M %p" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:582 +#: ../js/ui/panel.js:586 msgid "%a %l:%M:%S %p" msgstr "%a %l:%M:%S %p" -#: ../js/ui/panel.js:583 +#: ../js/ui/panel.js:587 msgid "%a %l:%M %p" msgstr "%a %l:%M %p" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:728 +#: ../js/ui/panel.js:732 msgid "Activities" msgstr "פעילויות" @@ -690,7 +695,7 @@ msgstr "שגיאה בעיון בהתקן" msgid "The requested device cannot be browsed, error is '%s'" msgstr "לא ניתן לעיין בהתקן הנבחר, השגיאה היא '%s'" -#: ../js/ui/status/bluetooth.js:251 ../js/ui/status/keyboard.js:78 +#: ../js/ui/status/bluetooth.js:251 msgid "Keyboard Settings" msgstr "הגדרות מקלדת" @@ -765,6 +770,10 @@ msgstr "נא להזין את קוד ה־PIN המוזכר בהתקן." msgid "OK" msgstr "אישור" +#: ../js/ui/status/keyboard.js:78 +msgid "Localization Settings" +msgstr "הגדרות אוזריות" + #: ../js/ui/status/power.js:85 msgid "Power Settings" msgstr "הגדרות צריכת החשמל" From 56cf7a6628d7490efd9162af86ef90cc68527a12 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sun, 23 Jan 2011 13:29:55 +0200 Subject: [PATCH 09/30] Updated Arabic translation --- po/ar.po | 63 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/po/ar.po b/po/ar.po index f6f6902b9..9c7b69342 100644 --- a/po/ar.po +++ b/po/ar.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: HEAD\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-19 22:47+0200\n" -"PO-Revision-Date: 2011-01-19 22:47+0300\n" +"POT-Creation-Date: 2011-01-23 13:29+0200\n" +"PO-Revision-Date: 2011-01-23 13:29+0300\n" "Last-Translator: Khaled Hosny \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" @@ -70,7 +70,8 @@ msgstr "" msgid "List of desktop file IDs for favorite applications" msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:11 +#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#, no-c-format msgid "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -79,45 +80,47 @@ msgid "" "pipeline can also take care of its own output - this might be used to send " "the output to an icecast server via shout2send or similar. When unset or set " "to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." -msgstr "" - -#: ../data/org.gnome.shell.gschema.xml.in.h:12 -msgid "Show date in clock" +"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and " +"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess " +"at the optimal thread count on the system." msgstr "" #: ../data/org.gnome.shell.gschema.xml.in.h:13 -msgid "Show the week date in the calendar" +msgid "Show date in clock" msgstr "" #: ../data/org.gnome.shell.gschema.xml.in.h:14 -msgid "Show time with seconds" +msgid "Show the week date in the calendar" msgstr "" #: ../data/org.gnome.shell.gschema.xml.in.h:15 +msgid "Show time with seconds" +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:16 msgid "" "The applications corresponding to these identifiers will be displayed in the " "favorites area." msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:16 +#: ../data/org.gnome.shell.gschema.xml.in.h:17 msgid "" "The filename for recorded screencasts will be a unique filename based on the " "current date, and use this extension. It should be changed when recording to " "a different container format." msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:17 +#: ../data/org.gnome.shell.gschema.xml.in.h:18 msgid "" "The framerate of the resulting screencast recordered by GNOME Shell's " "screencast recorder in frames-per-second." msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:18 +#: ../data/org.gnome.shell.gschema.xml.in.h:19 msgid "The gstreamer pipeline used to encode the screencast" msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:19 +#: ../data/org.gnome.shell.gschema.xml.in.h:20 msgid "" "The shell normally monitors active applications in order to present the most " "used ones (e.g. in launchers). While this data will be kept private, you may " @@ -125,15 +128,15 @@ msgid "" "remove already saved data." msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:20 +#: ../data/org.gnome.shell.gschema.xml.in.h:21 msgid "Uuids of extensions to disable" msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:21 +#: ../data/org.gnome.shell.gschema.xml.in.h:22 msgid "Whether to collect stats about applications usage" msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:22 +#: ../data/org.gnome.shell.gschema.xml.in.h:23 msgid "disabled OpenSearch providers" msgstr "" @@ -436,54 +439,54 @@ msgid "Applications" msgstr "التطبيقات" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:479 +#: ../js/ui/panel.js:483 #, c-format msgid "Quit %s" msgstr "أغلق %s" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:564 +#: ../js/ui/panel.js:568 msgid "%a %b %e, %R:%S" msgstr "%A %e %B، %R:%S" -#: ../js/ui/panel.js:565 +#: ../js/ui/panel.js:569 msgid "%a %b %e, %R" msgstr "%A %e %B، %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:569 +#: ../js/ui/panel.js:573 msgid "%a %R:%S" msgstr "%A %R:%S" -#: ../js/ui/panel.js:570 +#: ../js/ui/panel.js:574 msgid "%a %R" msgstr "%A %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:581 msgid "%a %b %e, %l:%M:%S %p" msgstr "%A %e %B، %l:%M:%S %p" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:582 msgid "%a %b %e, %l:%M %p" msgstr "%A %e %B، %l:%M %p" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:582 +#: ../js/ui/panel.js:586 msgid "%a %l:%M:%S %p" msgstr "%A %l:%M:%S %p" -#: ../js/ui/panel.js:583 +#: ../js/ui/panel.js:587 msgid "%a %l:%M %p" msgstr "%A %Ol:%OM %p" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:728 +#: ../js/ui/panel.js:732 msgid "Activities" msgstr "الأنشطة" @@ -638,7 +641,7 @@ msgstr "عطل أثناء تصفّح الجهاز" msgid "The requested device cannot be browsed, error is '%s'" msgstr "تعذّر تصفح الجهاز، رسالة العطل '%s'" -#: ../js/ui/status/bluetooth.js:251 ../js/ui/status/keyboard.js:78 +#: ../js/ui/status/bluetooth.js:251 msgid "Keyboard Settings" msgstr "إعدادات لوحة المفاتيح" @@ -713,6 +716,10 @@ msgstr "من فضلك أدخل الرقم المذكور على الجهاز." msgid "OK" msgstr "حسنا" +#: ../js/ui/status/keyboard.js:78 +msgid "Localization Settings" +msgstr "إعدادات اللغة" + #: ../js/ui/status/power.js:85 msgid "Power Settings" msgstr "إعدادات الطاقة" From a6da22fa70e1307359af203c3140edf61739df1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 21 Jan 2011 14:08:42 +0100 Subject: [PATCH 10/30] overview: Add a facility for swipe-scrolling The workspaces view allows to drag the active workspace to swipe-scroll to the next or previous workspace. While this behavior can come in handy in general, there are good reasons to move the functionality to the overview: - Finding a spot on a workspace to start a drag can be hard, especially when the workspace contains a single window - With the new layout, workspaces have no visible border, making it hard to predict where a drag can be initiated - The same behavior is equally useful for other elements So add setScrollAdjustment() to the overview, which allows setting an adjustment controlled with swipe-scrolling (either horizontally or vertically); only a single adjustment can be controlled at a time. A swipe-scroll can be initiated on any part of the background that is not occupied by a reactive actor. For cases where further control is needed, 'swipe-scroll-start' and 'swipe-scroll-end' signals are emitted. https://bugzilla.gnome.org/show_bug.cgi?id=635034 --- js/ui/overview.js | 179 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 4 deletions(-) diff --git a/js/ui/overview.js b/js/ui/overview.js index 35c1b05c4..8f4e830fa 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -1,6 +1,7 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; +const Gtk = imports.gi.Gtk; const Meta = imports.gi.Meta; const Mainloop = imports.mainloop; const Signals = imports.signals; @@ -32,6 +33,18 @@ const DASH_SPLIT_FRACTION = 0.1; const DND_WINDOW_SWITCH_TIMEOUT = 1250; +const SwipeScrollDirection = { + NONE: 0, + HORIZONTAL: 1, + VERTICAL: 2 +}; + +const SwipeScrollResult = { + CANCEL: 0, + SWIPE: 1, + CLICK: 2 +}; + function ShellInfo() { this._init(); } @@ -98,7 +111,8 @@ Overview.prototype = { this._spacing = 0; - this._group = new St.Group({ name: 'overview' }); + this._group = new St.Group({ name: 'overview', + reactive: true }); this._group._delegate = this; this._group.connect('style-changed', Lang.bind(this, function() { @@ -110,6 +124,11 @@ Overview.prototype = { } })); + this._scrollDirection = SwipeScrollDirection.NONE; + this._scrollAdjustment = null; + this._capturedEventId = 0; + this._buttonPressId = 0; + this.shellInfo = new ShellInfo(); this._workspacesDisplay = null; @@ -154,7 +173,7 @@ Overview.prototype = { this._dash.actor.add_constraint(this.viewSelector.constrainY); this._dash.actor.add_constraint(this.viewSelector.constrainHeight); - this._coverPane.lower_bottom(); + this._coverPane.hide(); // XDND this._dragMonitor = { @@ -233,6 +252,149 @@ Overview.prototype = { return DND.DragMotionResult.CONTINUE; }, + setScrollAdjustment: function(adjustment, direction) { + this._scrollAdjustment = adjustment; + if (this._scrollAdjustment == null) + this._scrollDirection = SwipeScrollDirection.NONE; + else + this._scrollDirection = direction; + }, + + _onButtonPress: function(actor, event) { + if (this._scrollDirection == SwipeScrollDirection.NONE) + return; + + let [stageX, stageY] = event.get_coords(); + this._dragStartX = this._dragX = stageX; + this._dragStartY = this._dragY = stageY; + this._dragStartValue = this._scrollAdjustment.value; + this._lastMotionTime = -1; // used to track "stopping" while swipe-scrolling + this._capturedEventId = global.stage.connect('captured-event', + Lang.bind(this, this._onCapturedEvent)); + this.emit('swipe-scroll-begin'); + }, + + _onCapturedEvent: function(actor, event) { + let stageX, stageY; + switch(event.type()) { + case Clutter.EventType.BUTTON_RELEASE: + [stageX, stageY] = event.get_coords(); + + // default to snapping back to the original value + let newValue = this._dragStartValue; + + let minValue = this._scrollAdjustment.lower; + let maxValue = this._scrollAdjustment.upper - this._scrollAdjustment.page_size; + + let direction; + if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) { + direction = stageX > this._dragStartX ? -1 : 1; + if (St.Widget.get_default_direction() == St.TextDirection.RTL) + direction *= -1; + } else { + direction = stageY > this._dragStartY ? -1 : 1; + } + + // We default to scroll a full page size; both the first + // and the last page may be smaller though, so we need to + // adjust difference in those cases. + let difference = direction * this._scrollAdjustment.page_size; + if (this._dragStartValue + difference > maxValue) + difference = maxValue - this._dragStartValue; + else if (this._dragStartValue + difference < minValue) + difference = minValue - this._dragStartValue; + + // If the user has moved more than half the scroll + // difference, we want to "settle" to the new value + // even if the user stops dragging rather "throws" by + // releasing during the drag. + let distance = this._dragStartValue - this._scrollAdjustment.value; + let noStop = Math.abs(distance / difference) > 0.5; + + // We detect if the user is stopped by comparing the + // timestamp of the button release with the timestamp of + // the last motion. Experimentally, a difference of 0 or 1 + // millisecond indicates that the mouse is in motion, a + // larger difference indicates that the mouse is stopped. + if ((this._lastMotionTime > 0 && + this._lastMotionTime > event.get_time() - 2) || + noStop) { + if (this._dragStartValue + difference >= minValue && + this._dragStartValue + difference <= maxValue) + newValue += difference; + } + + // See if the user has moved the mouse enough to trigger + // a drag + let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold; + if (Math.abs(stageX - this._dragStartX) < threshold && + Math.abs(stageY - this._dragStartY) < threshold) { + // no motion? It's a click! + this.emit('swipe-scroll-end', SwipeScrollResult.CLICK); + } else { + let result; + + if (newValue == this._dragStartValue) + result = SwipeScrollResult.CANCEL; + else + result = SwipeScrollResult.SWIPE; + + // The event capture handler is disconnected + // while scrolling to the final position, so + // to avoid undesired prelights we raise + // the cover pane. + this._coverPane.raise_top(); + this._coverPane.show(); + + Tweener.addTween(this._scrollAdjustment, + { value: newValue, + time: ANIMATION_TIME, + transition: 'easeOutQuad', + onCompleteScope: this, + onComplete: function() { + this._coverPane.hide(); + this.emit('swipe-scroll-end', + result); + } + }); + } + + global.stage.disconnect(this._capturedEventId); + this._capturedEventId = 0; + + return true; + + case Clutter.EventType.MOTION: + [stageX, stageY] = event.get_coords(); + let dx = this._dragX - stageX; + let dy = this._dragY - stageY; + let primary = global.get_primary_monitor(); + + if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) { + if (St.Widget.get_default_direction() == St.TextDirection.RTL) + this._scrollAdjustment.value -= (dx / primary.width) * this._scrollAdjustment.page_size; + else + this._scrollAdjustment.value += (dx / primary.width) * this._scrollAdjustment.page_size; + } else { + this._scrollAdjustment.value += (dy / primary.height) * this._scrollAdjustment.page_size; + } + + this._dragX = stageX; + this._dragY = stageY; + this._lastMotionTime = event.get_time(); + + return true; + + // Block enter/leave events to avoid prelights + // during swipe-scroll + case Clutter.EventType.ENTER: + case Clutter.EventType.LEAVE: + return true; + } + + return false; + }, + _getDesktopClone: function() { let windows = global.get_window_actors().filter(function(w) { return w.meta_window.get_window_type() == Meta.WindowType.DESKTOP; @@ -335,6 +497,9 @@ Overview.prototype = { this._modal = true; this._animateVisible(); this._shown = true; + + this._buttonPressId = this._group.connect('button-press-event', + Lang.bind(this, this._onButtonPress)); }, _animateVisible: function() { @@ -402,6 +567,7 @@ Overview.prototype = { }); this._coverPane.raise_top(); + this._coverPane.show(); this.emit('showing'); }, @@ -432,6 +598,10 @@ Overview.prototype = { this._shown = false; this._syncInputMode(); + + if (this._buttonPressId > 0) + this._group.disconnect(this._buttonPressId); + this._buttonPressId = 0; }, // hideTemporarily: @@ -541,13 +711,14 @@ Overview.prototype = { }); this._coverPane.raise_top(); + this._coverPane.show(); this.emit('hiding'); }, _showDone: function() { this.animationInProgress = false; this._desktopFade.hide(); - this._coverPane.lower_bottom(); + this._coverPane.hide(); this.emit('shown'); // Handle any calls to hide* while we were showing @@ -575,7 +746,7 @@ Overview.prototype = { this.animationInProgress = false; this._hideInProgress = false; - this._coverPane.lower_bottom(); + this._coverPane.hide(); this.emit('hidden'); // Handle any calls to show* while we were hiding From d64d491f635389e79195f796caea5c9445f15312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 21 Jan 2011 19:47:54 +0100 Subject: [PATCH 11/30] workspaces-view: Use overview swipe-scrolling Remove the swipe-scrolling implementation in WorkspacesView and use the new overview facility. https://bugzilla.gnome.org/show_bug.cgi?id=635034 --- js/ui/workspace.js | 16 ++-- js/ui/workspacesView.js | 183 +++++++++++----------------------------- 2 files changed, 56 insertions(+), 143 deletions(-) diff --git a/js/ui/workspace.js b/js/ui/workspace.js index 600249294..e44f5584d 100644 --- a/js/ui/workspace.js +++ b/js/ui/workspace.js @@ -517,20 +517,10 @@ Workspace.prototype = { // Without this the drop area will be overlapped. this._windowOverlaysGroup.set_size(0, 0); - this.actor = new Clutter.Group({ reactive: true }); + this.actor = new Clutter.Group(); this.actor._delegate = this; this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); - this.actor.connect('button-release-event', Lang.bind(this, - function(actor, event) { - // Only switch to the workspace when there's no application - // windows open. The problem is that it's too easy to miss - // an app window and get the wrong one focused. - if (this._windows.length == 0) { - this.metaWorkspace.activate(event.get_time()); - Main.overview.hide(); - } - })); // Items in _windowOverlaysGroup should not be scaled, so we don't // add them to this.actor, but to its parent whenever it changes @@ -605,6 +595,10 @@ Workspace.prototype = { return this._lookupIndex(metaWindow) >= 0; }, + isEmpty: function() { + return this._windows.length == 0; + }, + setShowOnlyWindows: function(showOnlyWindows, reposition) { this._showOnlyWindows = showOnlyWindows; this._resetCloneVisibility(); diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js index 747800391..40b7320d9 100644 --- a/js/ui/workspacesView.js +++ b/js/ui/workspacesView.js @@ -45,6 +45,8 @@ WorkspacesView.prototype = { this._spacing = node.get_length('spacing'); this._computeWorkspacePositions(); })); + this.actor.connect('notify::mapped', + Lang.bind(this, this._onMappedChanged)); this._width = width; this._height = height; @@ -55,10 +57,9 @@ WorkspacesView.prototype = { this._activeWorkspaceY = 0; // y offset of active ws while dragging this._lostWorkspaces = []; this._animating = false; // tweening - this._scrolling = false; // dragging desktop + this._scrolling = false; // swipe-scrolling this._animatingScroll = false; // programatically updating the adjustment this._inDrag = false; // dragging a window - this._lastMotionTime = -1; // used to track "stopping" while dragging workspaces let activeWorkspaceIndex = global.screen.get_active_workspace_index(); this._workspaces = workspaces; @@ -89,10 +90,6 @@ WorkspacesView.prototype = { this._scrollAdjustment.connect('notify::value', Lang.bind(this, this._onScroll)); - this._dragIndex = -1; - - this._buttonPressId = 0; - this._capturedEventId = 0; this._timeoutId = 0; this._windowSelectionAppId = null; @@ -113,6 +110,8 @@ WorkspacesView.prototype = { Lang.bind(this, this._dragBegin)); this._windowDragEndId = Main.overview.connect('window-drag-end', Lang.bind(this, this._dragEnd)); + this._swipeScrollBeginId = 0; + this._swipeScrollEndId = 0; }, _lookupWorkspaceForMetaWindow: function (metaWindow) { @@ -290,8 +289,6 @@ WorkspacesView.prototype = { if (this._inDrag) scale *= WORKSPACE_DRAGGING_SCALE; - this._setWorkspaceDraggable(active, true); - let _width = this._workspaces[0].actor.width * scale; let _height = this._workspaces[0].actor.height * scale; @@ -323,128 +320,6 @@ WorkspacesView.prototype = { this._updateScrollAdjustment(active, showAnimation); }, - // _setWorkspaceDraggable: - // @index: workspace index - // @draggable: whether workspace @index should be draggable - // - // If @draggable is %true, set up workspace @index to allow switching - // workspaces by dragging the desktop - if a draggable workspace has - // been set up before, it will be reset before the new one is made - // draggable. - // If @draggable is %false, workspace @index is reset to no longer allow - // dragging. - _setWorkspaceDraggable: function(index, draggable) { - if (index < 0 || index >= global.n_workspaces) - return; - - let dragActor = this._workspaces[index].actor; - - if (draggable) { - this._workspaces[index].actor.reactive = true; - - // reset old draggable workspace - if (this._dragIndex > -1) - this._setWorkspaceDraggable(this._dragIndex, false); - - this._dragIndex = index; - this._buttonPressId = dragActor.connect('button-press-event', - Lang.bind(this, this._onButtonPress)); - } else { - this._dragIndex = -1; - - if (this._buttonPressId > 0) { - if (dragActor.get_stage()) - dragActor.disconnect(this._buttonPressId); - this._buttonPressId = 0; - } - - if (this._capturedEventId > 0) { - global.stage.disconnect(this._capturedEventId); - this._capturedEventId = 0; - } - } - }, - - // start dragging the active workspace - _onButtonPress: function(actor, event) { - if (actor != event.get_source()) - return; - - if (this._dragIndex == -1) - return; - - let [stageX, stageY] = event.get_coords(); - this._dragStartX = this._dragX = stageX; - this._scrolling = true; - this._capturedEventId = global.stage.connect('captured-event', - Lang.bind(this, this._onCapturedEvent)); - }, - - // handle captured events while dragging a workspace - _onCapturedEvent: function(actor, event) { - let active = global.screen.get_active_workspace_index(); - let stageX, stageY; - - switch (event.type()) { - case Clutter.EventType.BUTTON_RELEASE: - this._scrolling = false; - - [stageX, stageY] = event.get_coords(); - - // default to snapping back to the original workspace - let activate = this._dragIndex; - let last = global.screen.n_workspaces - 1; - - // If the user has moved more than half a workspace, we want to "settle" - // to the new workspace even if the user stops dragging rather "throws" - // by releasing during the drag. - let noStop = Math.abs(activate - this._scrollAdjustment.value) > 0.5; - - let difference = stageX > this._dragStartX ? -1 : 1; - if (St.Widget.get_default_direction() == St.TextDirection.RTL) - difference *= -1; - - // We detect if the user is stopped by comparing the timestamp of the button - // release with the timestamp of the last motion. Experimentally, a difference - // of 0 or 1 millisecond indicates that the mouse is in motion, a larger - // difference indicates that the mouse is stopped. - if ((this._lastMotionTime > 0 && this._lastMotionTime > event.get_time() - 2) || noStop) { - if (activate + difference >= 0 && - activate + difference <= last) - activate += difference; - } - - if (activate != active) { - let workspace = this._workspaces[activate].metaWorkspace; - workspace.activate(global.get_current_time()); - } else { - this._scrollToActive(true); - } - - if (stageX == this._dragStartX) - // no motion? It's a click! - return false; - - return true; - - case Clutter.EventType.MOTION: - [stageX, stageY] = event.get_coords(); - let dx = this._dragX - stageX; - let primary = global.get_primary_monitor(); - - if (St.Widget.get_default_direction() == St.TextDirection.RTL) - this._scrollAdjustment.value -= (dx / primary.width); - else - this._scrollAdjustment.value += (dx / primary.width); - this._dragX = stageX; - this._lastMotionTime = event.get_time(); - - return true; - } - - return false; - }, - // Update workspace actors parameters to the values calculated in // _computeWorkspacePositions() // @showAnimation: iff %true, transition between states @@ -454,7 +329,6 @@ WorkspacesView.prototype = { let targetWorkspaceCurrentX = this._workspaces[active].x; let dx = targetWorkspaceNewX - targetWorkspaceCurrentX; - this._setWorkspaceDraggable(active, true); this._animating = showAnimation; for (let w = 0; w < this._workspaces.length; w++) { @@ -606,7 +480,6 @@ WorkspacesView.prototype = { global.window_manager.disconnect(this._switchWorkspaceNotifyId); global.screen.disconnect(this._restackedNotifyId); - this._setWorkspaceDraggable(this._dragIndex, false); if (this._timeoutId) { Mainloop.source_remove(this._timeoutId); this._timeoutId = 0; @@ -629,6 +502,21 @@ WorkspacesView.prototype = { } }, + _onMappedChanged: function() { + if (this.actor.mapped) { + let direction = Overview.SwipeScrollDirection.HORIZONTAL; + Main.overview.setScrollAdjustment(this._scrollAdjustment, + direction); + this._swipeScrollBeginId = Main.overview.connect('swipe-scroll-begin', + Lang.bind(this, this._swipeScrollBegin)); + this._swipeScrollEndId = Main.overview.connect('swipe-scroll-end', + Lang.bind(this, this._swipeScrollEnd)); + } else { + Main.overview.disconnect(this._swipeScrollBeginId); + Main.overview.disconnect(this._swipeScrollEndId); + } + }, + _dragBegin: function() { if (this._scrolling) return; @@ -736,6 +624,37 @@ WorkspacesView.prototype = { this._workspaces[i].setReservedSlot(null); }, + _swipeScrollBegin: function() { + this._scrolling = true; + }, + + _swipeScrollEnd: function(overview, result) { + this._scrolling = false; + + if (result == Overview.SwipeScrollResult.CLICK) { + let [x, y, mod] = global.get_pointer(); + let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, + x, y); + + // Only switch to the workspace when there's no application + // windows open. The problem is that it's too easy to miss + // an app window and get the wrong one focused. + let active = global.screen.get_active_workspace_index(); + if (this._workspaces[active].isEmpty() && + this.actor.contains(actor)) + Main.overview.hide(); + } + + if (result == Overview.SwipeScrollResult.SWIPE) + // The active workspace has changed; while swipe-scrolling + // has already taken care of the positioning, the cached + // positions need to be updated + this._computeWorkspacePositions(); + + // Make sure title captions etc are shown as necessary + this._updateVisibility(); + }, + // sync the workspaces' positions to the value of the scroll adjustment // and change the active workspace if appropriate _onScroll: function(adj) { From e96a90b161eb81ce55edf51dcc8c87ca9d8d70a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 21 Jan 2011 19:50:25 +0100 Subject: [PATCH 12/30] app-display: Enable swipe-scrolling in the app view With general support for swipe-scrolling in the overview, there is no reason to limit the behavior to workspaces. It is equally useful for scrolling through the grid of available applications, so enable swipe-scrolling for the app view. https://bugzilla.gnome.org/show_bug.cgi?id=635034 --- js/ui/appDisplay.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index f1d44ebe7..6cefd6496 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -43,6 +43,15 @@ AlphabeticalView.prototype = { vfade: true }); this.actor.add_actor(box); this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + this.actor.connect('notify::mapped', Lang.bind(this, + function() { + if (!this.actor.mapped) + return; + + let adjustment = this.actor.vscroll.adjustment; + let direction = Overview.SwipeScrollDirection.VERTICAL; + Main.overview.setScrollAdjustment(adjustment, direction); + })); }, _removeAll: function() { From 1b383c72859da0f30379f2279e852ffa898711c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 21 Jan 2011 23:49:03 +0100 Subject: [PATCH 13/30] search-display: Enable swipe scrolling If a search gives many results from various providers, the result area will be scrollable, so enable swipe-scrolling here as well. https://bugzilla.gnome.org/show_bug.cgi?id=635034 --- js/ui/searchDisplay.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js index f85eb6714..45fccd6ea 100644 --- a/js/ui/searchDisplay.js +++ b/js/ui/searchDisplay.js @@ -10,6 +10,7 @@ const St = imports.gi.St; const DND = imports.ui.dnd; const IconGrid = imports.ui.iconGrid; const Main = imports.ui.main; +const Overview = imports.ui.overview; const Search = imports.ui.search; const MAX_SEARCH_RESULTS_ROWS = 2; @@ -175,6 +176,15 @@ SearchResults.prototype = { expand: true, x_align: St.Align.START, y_align: St.Align.START }); + this.actor.connect('notify::mapped', Lang.bind(this, + function() { + if (!this.actor.mapped) + return; + + let adjustment = scrollView.vscroll.adjustment; + let direction = Overview.SwipeScrollDirection.VERTICAL; + Main.overview.setScrollAdjustment(adjustment, direction); + })); this._statusText = new St.Label({ style_class: 'search-statustext' }); this._content.add(this._statusText); From 8dcd70edbee11259c671ef19ec51054225ff4842 Mon Sep 17 00:00:00 2001 From: Adel Gadllah Date: Sun, 23 Jan 2011 15:20:43 +0100 Subject: [PATCH 14/30] St: Use clutter_actor_get_request_mode Use clutter_actor_get_request_mode rather than g_object_get since we have it in the required clutter version. https://bugzilla.gnome.org/show_bug.cgi?id=640415 --- src/st/st-private.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/st/st-private.c b/src/st/st-private.c index f4707691e..290cdf833 100644 --- a/src/st/st-private.c +++ b/src/st/st-private.c @@ -49,7 +49,7 @@ _st_actor_get_preferred_width (ClutterActor *actor, ClutterRequestMode mode; gfloat natural_height; - g_object_get (G_OBJECT (actor), "request-mode", &mode, NULL); + mode = clutter_actor_get_request_mode (actor); if (mode == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT) { clutter_actor_get_preferred_height (actor, -1, NULL, &natural_height); @@ -86,7 +86,7 @@ _st_actor_get_preferred_height (ClutterActor *actor, ClutterRequestMode mode; gfloat natural_width; - g_object_get (G_OBJECT (actor), "request-mode", &mode, NULL); + mode = clutter_actor_get_request_mode (actor); if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) { clutter_actor_get_preferred_width (actor, -1, NULL, &natural_width); @@ -173,8 +173,7 @@ _st_allocate_fill (StWidget *parent, * modified to cope with the fact that the available size may be * less than the preferred size. */ - request = CLUTTER_REQUEST_HEIGHT_FOR_WIDTH; - g_object_get (G_OBJECT (child), "request-mode", &request, NULL); + request = clutter_actor_get_request_mode (child); if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) { From 412c50b9398eddec248bd8c0a01cc329544e809d Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 5 Jan 2011 10:08:50 -0500 Subject: [PATCH 15/30] tests: showcase borders with non-solid backgrounds This commit adds a few more examples to borders.js that render borders with various combinations of gradients, background images, shadows, and border-images. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- tests/Makefile.am | 1 + tests/interactive/borders.js | 59 ++++++++++++++++++++++++++++++++ tests/testcommon/face-plain.png | Bin 0 -> 4298 bytes tests/testcommon/test.css | 17 +++++++++ 4 files changed, 77 insertions(+) create mode 100644 tests/testcommon/face-plain.png diff --git a/tests/Makefile.am b/tests/Makefile.am index 9fb38a294..5958ffb11 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,6 +15,7 @@ TEST_JS = \ interactive/scroll-view-sizing.js \ interactive/table.js \ testcommon/border-image.png \ + testcommon/face-plain.png \ testcommon/ui.js \ unit/format.js EXTRA_DIST += $(TEST_JS) diff --git a/tests/interactive/borders.js b/tests/interactive/borders.js index 7c5cda13a..5c97ea105 100644 --- a/tests/interactive/borders.js +++ b/tests/interactive/borders.js @@ -71,6 +71,65 @@ box.add(new St.Label({ text: "Border Image", style_class: "border-image", style: "padding: 10px;" })); +box.add(new St.Label({ text: "Border Image with Gradient", + style_class: 'border-image-with-background-gradient', + style: "padding: 10px;" + + 'background-gradient-direction: vertical;' })); + +box.add(new St.Label({ text: "Rounded, framed, shadowed gradients" })); + +let framedGradients = new St.BoxLayout({ vertical: false, + style: 'padding: 10px; spacing: 12px;' }); +box.add(framedGradients); + +function addGradientCase(direction, borderWidth, borderRadius, extra) { + let gradientBox = new St.BoxLayout({ style_class: 'background-gradient', + style: 'border: ' + borderWidth + 'px solid #8b8b8b;' + + 'border-radius: ' + borderRadius + 'px;' + + 'background-gradient-direction: ' + direction + ';' + + 'width: 32px;' + + 'height: 32px;' + + extra }); + framedGradients.add(gradientBox, { x_fill: false, y_fill: false } ); +} + +addGradientCase ('horizontal', 0, 5, '-st-shadow: 0px 0px 0px 0px rgba(0,0,0,0.5);'); +addGradientCase ('horizontal', 2, 5, '-st-shadow: 0px 2px 0px 0px rgba(0,255,0,0.5);'); +addGradientCase ('horizontal', 5, 2, '-st-shadow: 2px 0px 0px 0px rgba(0,0,255,0.5);'); +addGradientCase ('horizontal', 5, 20, '-st-shadow: 0px 0px 4px 0px rgba(255,0,0,0.5);'); +addGradientCase ('vertical', 0, 5, '-st-shadow: 0px 0px 0px 4px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 2, 5, '-st-shadow: 0px 0px 4px 4px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 5, 2, '-st-shadow: -2px -2px 6px 0px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 5, 20, '-st-shadow: -2px -2px 0px 6px rgba(0,0,0,0.5);'); + +box.add(new St.Label({ text: "Rounded, framed, shadowed images" })); + +let framedImages = new St.BoxLayout({ vertical: false, + style: 'padding: 10px; spacing: 6px;' }); +box.add(framedImages); + +function addBackgroundImageCase(borderWidth, borderRadius, width, height, extra) { + let imageBox = new St.BoxLayout({ style_class: 'background-image', + style: 'border: ' + borderWidth + 'px solid #8b8b8b;' + + 'border-radius: ' + borderRadius + 'px;' + + 'width: ' + width + 'px;' + + 'height: ' + height + 'px;' + + extra }); + framedImages.add(imageBox, { x_fill: false, y_fill: false } ); +} + +addBackgroundImageCase (0, 0, 32, 32, 'background-position: 2px 5px'); +addBackgroundImageCase (0, 0, 16, 16, '-st-shadow: 1px 1px 4px 0px rgba(0,0,0,0.5); background-color: rgba(0,0,0,0)'); +addBackgroundImageCase (0, 5, 32, 32, '-st-shadow: 0px 0px 0px 0px rgba(0,0,0,0.5);'); +addBackgroundImageCase (2, 5, 32, 32, '-st-shadow: 0px 2px 0px 0px rgba(0,255,0,0.5);'); +addBackgroundImageCase (5, 2, 32, 32, '-st-shadow: 2px 0px 0px 0px rgba(0,0,255,0.5);'); +addBackgroundImageCase (5, 20, 32, 32, '-st-shadow: 0px 0px 4px 0px rgba(255,0,0,0.5);'); +addBackgroundImageCase (0, 5, 48, 48, '-st-shadow: 0px 0px 0px 4px rgba(0,0,0,0.5);'); +addBackgroundImageCase (5, 5, 48, 48, '-st-shadow: 0px 0px 4px 4px rgba(0,0,0,0.5);'); +addBackgroundImageCase (0, 5, 64, 64, '-st-shadow: -2px -2px 6px 0px rgba(0,0,0,0.5);'); +addBackgroundImageCase (5, 5, 64, 64, '-st-shadow: -2px -2px 0px 6px rgba(0,0,0,0.5);'); +addBackgroundImageCase (0, 5, 32, 32, 'background-position: 2px 5px'); + stage.show(); Clutter.main(); stage.destroy(); diff --git a/tests/testcommon/face-plain.png b/tests/testcommon/face-plain.png new file mode 100644 index 0000000000000000000000000000000000000000..962d70f1bae824b12c0db60e099bdf99be9ec378 GIT binary patch literal 4298 zcmV;*5H;_KP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2ipMz z7CIeRE0xIr01!$^L_t(&-o=`GtYvpq$3N@$`|Z8YIrlO5&P=D%cWFxjDJ?XWP;H1w zG5BagfY=aGDg+f$5{N+zgonW>BK~L8Km-({F=&kiYy^s02nEBVBZWezwb1E2+S_^D zd(UI>qnI1dPhLW-D9u~=fwHdbw7g2Q z#o`#7Ze!&sOOp+B^qA<@G0Ia%jtx(r{Ie_GaL;?65&&O#_p|n`7V!;x*4JLw-+TVK z+4=?ISI#%Td@eHBE7I#&4+97}_#BZ8aSm`;8!TW7q!xfS5G$-1W78p8ZDQpq)6wIk z;R%_ZK0#MIBtRLJLRu2%C_aVJC zAsdK<0(@v)#e+ByXW$e>f!K;P7!6PZ4QPdF1*sKFHI^DNje;f6oh+H<=;5CWPQp0bY*4miej+3DtXUbmSG}-)u0nBjUa7fx&^k4*$}f4W+PyP z$rxmeF$NiR?{8za4Z4k`Ei7(faR@p_tOkcj$Z&bj^8Vne)wQ*M`uf|}p8rEx;I7xX zbN8h%! z-olzqtlq@xZLA()%>-*op((MZ!dbH~2sgg!>gLn$|3WC|;fi zS75b?)o7fE)?4WGBsSe>r8$YEEmX&?Vl)T|6T@V7R1oV#K_W(jS=Cx3ZMN5$ZeZ00 zR-J5trHNou(Au)VW$SB8w}0ukSUOwZu=B;EZ*%_GFuZi7pS?cMd||0FQHM4YaUM)6 zkOFmmB&+%{YcyMEJ;Le=rZrku zSS-a-ZKRq!xJ&l!U-=*mU-6_CIC`3=^)vqmy#Gvtq~vj$Y=TI+rNyFaFQ^aVrhalBQ$Qe zX*QN%wU{*~+T=yA_u_B7DSzd6wZNg*$&xw$#-)CKDb|=s!VnQ{TG~{QBw!A+NI!>< zedaL#a_3RT)&Fq*#TRhxb;}S6%n~xs(9(jkR#BXC=V$KWumA3D_O1l_w#_eIzn9@K z0d1{7>sBnb-xU+H1lZcja{qm|-Bf?{<)5J0Wq~@$(|eiw?JV;~s%d>;Rw)c@b_2yM z5pw0DcYKTe=ROS|miYI(zRvb=h<8n!=PhE^VAga>HMpRhc;pygx#t1)?K?=Nt9$*s%%EYnm2hzT-haTbK#g}qCh zdVIu*jS2VOdoPckIL24+d63a~8>~XC#H>O(u}&azSLy9+FKeFx4!q?j?g>?lm^F&MQysHq0;#R6!&4bc(4$N?=A) zH)u12xZTMRbObg8+XCGJwn+7e)HIm3WTX^{P1P1LO_*s%)DycLL)yd~>#)=>ZUzX! zNuG?GL&(rriOb~dZ!Jzb3utWEGYA9^W{P2~u6TlupgseZVH98i5kWMeO#@RHr6OV=dH3KRCIeO%@3wAliKpL%NhG z)&ek1#3pk7-i-ZgJvKHKoa4$%m+8w0rWr;KmSYky5zqvwfl1=PO60vYz#>N6BbGWlFY3Dv>9k3RJXyk_H z3>c?A<7wh&uUTc;O^BsnE{7Drg*Ka+LTb<|a_KpVYpz`8>kq8sEb;71d+g~OTW4bD zo?Q(w+iCvAxwW+>r6^gRzaS`F<-8}wTH4OY+5&3KFDAAVgn}xQ8g9PvTuzK@ycceG z-Wr7~No7Pr3qYLf%7B3;>e$c=#`RaP@a>}wYkl~+>vASlg{1`A7CUr7pzS=LA0cUU zA%tfKfv4jRHUo_Fl;*uk^}#I|-=>?A3cX8+%cb!6GhBAxOriAGdt zREP+nHYpnuYJ*0h(3zsG(zA<*3YJ_usHj2`#A#b^MCK{4O}n}azzwhlU|+Q@jJkL|3+~n;?MLgGh;D>w;-M-kednk((I_2&nY$=B z0pGUMCfc?avb*1!XAHBp{gy>XGOwf^0G%lqJ4@`a3UOhHR%T$@OjIjO6>7YdjXfNy z#C?;PSjvMq>W zwG`7tO3F0BRE0=LlP294@?dw5%7(RKAZ0A+C+H>cGv=Lua|wBXzqUb1kqZ z17ID1&TV$FpVB;#7t#f9YTc$%s1h_Gvtc#p;v$jLvtQhaiz&?vcEJ{wl7whAZ~qdTU5;9m)5AFWSa9l~_H&rem}jVbc*- z4YBexR&8O`xZOlFZH8I|w9+J_Oi(ITCTM&Sw$XLk*z9nkhCpgK?y5UJXSs@!&F?^Ed=?g0loEBB?EcVsx=!i{iE;K2mIz zA15diRAe$qHhp}v`M?~&&p$)TJHEjmZw71x+gSo6*@rCt5dEN!ww`Iw9^?XOeTpFo1^MA@4P+U^}{@3(*Hij(Y=Fobfw1?p~%mbtmn-IBos(C z5X%N==%c=e`2v#+!vh}a!gWWo>~0lM3nW)iyMo#csKPQ+zs%GRsH9+G-p1-F)yufn zBd4~@Ti$hh^XVVu8CksV1m7{$@9gR8xkctKbXgHh0_Jm*tPlB;SiX#A%V@rg<|}Bv zgl4Uzutdt1N!c4L&w}tAd{;7cjx$Zd^`CESGig4o<2a8MAR#)@>(yHqfJ_w&h3HK8#QQ?wYPO=B(fbYkUr+nwGTeeJHM;=FI0Kr<{QODS5@)Ha3bnI%IRriM~73p se`7lxdg!rq=>Es_;j@;XKhod-0uWR{`Okk+XaE2J07*qoM6N<$g4j@AN&o-= literal 0 HcmV?d00001 diff --git a/tests/testcommon/test.css b/tests/testcommon/test.css index d1005eeb3..34611118b 100644 --- a/tests/testcommon/test.css +++ b/tests/testcommon/test.css @@ -37,6 +37,23 @@ stage { border-image: url('border-image.png') 16; } +.background-gradient { + background-gradient-start: #88ff88; + background-gradient-end: #8888ff; +} + +.border-image-with-background-gradient { + border: 15px black solid; + border-image: url('border-image.png') 16; + background-gradient-start: #88ff88; + background-gradient-end: #8888ff; +} + +.background-image { + background-image: url('face-plain.png'); + background-color: white; +} + .push-button { background: #eeddbb; border: 1px solid black; From e727c184ef09a8f5662960e1c12fba630737af39 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 13 Jan 2011 18:27:24 -0500 Subject: [PATCH 16/30] StThemeNode: Split -st-shadow into three attributes Currently, "-st-shadow" can mean one of three very different things: 1) shadow based on alpha of the background image 2) shadow the "border box" of the node 3) shadow applied to the content of a StIcon It isn't well defined which of the above 3 cases -st-shadow will mean for any given node, however. This commit splits the property into three different properties, "box-shadow", "-st-background-image-shadow", and "icon-shadow" to make it all very explicit. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- data/theme/gnome-shell.css | 8 +- src/st/st-icon.c | 25 ++- src/st/st-theme-node-drawing.c | 39 ++-- src/st/st-theme-node-private.h | 8 +- src/st/st-theme-node.c | 332 ++++++++++++++++++++++++--------- src/st/st-theme-node.h | 10 +- tests/interactive/borders.js | 34 ++-- 7 files changed, 322 insertions(+), 134 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 6104ac95a..65aaaf238 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -313,7 +313,7 @@ StTooltip StLabel { background-image: url("close-window.svg"); height: 24px; width: 24px; - -st-shadow: -2px 2px 6px rgba(0,0,0,0.5); + -st-background-image-shadow: -2px 2px 6px rgba(0,0,0,0.5); -shell-close-overlap: 16px; } @@ -374,7 +374,7 @@ StTooltip StLabel { background-gradient-direction: vertical; color: rgb(64, 64, 64); font-weight: bold; - -st-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9); + box-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9); transition-duration: 0; } @@ -506,7 +506,7 @@ StTooltip StLabel { .remove-favorite-icon:hover { color: white; - -st-shadow: black 0px 2px 2px; + icon-shadow: black 0px 2px 2px; } .app-well-app > .overview-icon, @@ -851,7 +851,7 @@ StTooltip StLabel { color: #545454; background-color: #e8e8e8; caret-color: #545454; - -st-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9); + box-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9); } /* The spacing and padding on the summary is tricky; we want to keep diff --git a/src/st/st-icon.c b/src/st/st-icon.c index 78519c483..d1210f156 100644 --- a/src/st/st-icon.c +++ b/src/st/st-icon.c @@ -62,6 +62,7 @@ struct _StIconPrivate CoglHandle shadow_material; float shadow_width; float shadow_height; + StShadow *shadow_spec; }; static void st_icon_update (StIcon *icon); @@ -164,6 +165,12 @@ st_icon_dispose (GObject *gobject) priv->shadow_material = COGL_INVALID_HANDLE; } + if (priv->shadow_spec) + { + st_shadow_unref (priv->shadow_spec); + priv->shadow_spec = NULL; + } + G_OBJECT_CLASS (st_icon_parent_class)->dispose (gobject); } @@ -249,8 +256,6 @@ st_icon_paint (ClutterActor *actor) { if (priv->shadow_material) { - StThemeNode *node = st_widget_get_theme_node (ST_WIDGET (actor)); - StShadow *shadow_spec = st_theme_node_get_shadow (node); ClutterActorBox allocation; float width, height; @@ -262,7 +267,7 @@ st_icon_paint (ClutterActor *actor) allocation.x2 = allocation.x1 + priv->shadow_width; allocation.y2 = allocation.y1 + priv->shadow_height; - _st_paint_shadow_with_opacity (shadow_spec, + _st_paint_shadow_with_opacity (priv->shadow_spec, priv->shadow_material, &allocation, clutter_actor_get_paint_opacity (priv->icon_texture)); @@ -279,6 +284,13 @@ st_icon_style_changed (StWidget *widget) StThemeNode *theme_node = st_widget_get_theme_node (widget); StIconPrivate *priv = self->priv; + if (priv->shadow_spec) + { + st_shadow_unref (priv->shadow_spec); + priv->shadow_spec = NULL; + } + priv->shadow_spec = st_theme_node_get_shadow (theme_node, "icon-shadow"); + priv->theme_icon_size = (int)(0.5 + st_theme_node_get_length (theme_node, "icon-size")); st_icon_update_icon_size (self); st_icon_update (self); @@ -353,8 +365,6 @@ static void st_icon_update_shadow_material (StIcon *icon) { StIconPrivate *priv = icon->priv; - StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (icon)); - StShadow *shadow_spec = st_theme_node_get_shadow (theme_node); if (priv->shadow_material) { @@ -362,14 +372,15 @@ st_icon_update_shadow_material (StIcon *icon) priv->shadow_material = COGL_INVALID_HANDLE; } - if (shadow_spec) + if (priv->shadow_spec) { CoglHandle material; gint width, height; clutter_texture_get_base_size (CLUTTER_TEXTURE (priv->icon_texture), &width, &height); - material = _st_create_shadow_material_from_actor (shadow_spec, + + material = _st_create_shadow_material_from_actor (priv->shadow_spec, priv->icon_texture); priv->shadow_material = material; priv->shadow_width = width; diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index 6e71c9881..8eb2b15ab 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -637,8 +637,8 @@ _st_theme_node_free_drawing_state (StThemeNode *node) cogl_handle_unref (node->border_texture); if (node->border_material != COGL_INVALID_HANDLE) cogl_handle_unref (node->border_material); - if (node->border_shadow_material != COGL_INVALID_HANDLE) - cogl_handle_unref (node->border_shadow_material); + if (node->box_shadow_material != COGL_INVALID_HANDLE) + cogl_handle_unref (node->box_shadow_material); for (corner_id = 0; corner_id < 4; corner_id++) if (node->corner_material[corner_id] != COGL_INVALID_HANDLE) @@ -655,7 +655,7 @@ _st_theme_node_init_drawing_state (StThemeNode *node) node->background_texture = COGL_INVALID_HANDLE; node->background_material = COGL_INVALID_HANDLE; node->background_shadow_material = COGL_INVALID_HANDLE; - node->border_shadow_material = COGL_INVALID_HANDLE; + node->box_shadow_material = COGL_INVALID_HANDLE; node->border_texture = COGL_INVALID_HANDLE; node->border_material = COGL_INVALID_HANDLE; @@ -674,7 +674,8 @@ st_theme_node_render_resources (StThemeNode *node, { StTextureCache *texture_cache; StBorderImage *border_image; - StShadow *shadow_spec; + StShadow *box_shadow_spec; + StShadow *background_image_shadow_spec; const char *background_image; texture_cache = st_texture_cache_get_default (); @@ -691,7 +692,7 @@ st_theme_node_render_resources (StThemeNode *node, _st_theme_node_ensure_background (node); _st_theme_node_ensure_geometry (node); - shadow_spec = st_theme_node_get_shadow (node); + box_shadow_spec = st_theme_node_get_box_shadow (node); /* Load referenced images from disk and draw anything we need with cairo now */ @@ -714,11 +715,11 @@ st_theme_node_render_resources (StThemeNode *node, else node->border_material = COGL_INVALID_HANDLE; - if (shadow_spec) + if (box_shadow_spec) { if (node->border_texture != COGL_INVALID_HANDLE) - node->border_shadow_material = _st_create_shadow_material (shadow_spec, - node->border_texture); + node->box_shadow_material = _st_create_shadow_material (box_shadow_spec, + node->border_texture); else if (node->background_color.alpha > 0 || node->border_width[ST_SIDE_TOP] > 0 || node->border_width[ST_SIDE_LEFT] > 0 || @@ -743,23 +744,25 @@ st_theme_node_render_resources (StThemeNode *node, cogl_pop_framebuffer (); cogl_handle_unref (offscreen); - node->border_shadow_material = _st_create_shadow_material (shadow_spec, - buffer); + node->box_shadow_material = _st_create_shadow_material (box_shadow_spec, + buffer); } cogl_handle_unref (buffer); } } background_image = st_theme_node_get_background_image (node); + background_image_shadow_spec = st_theme_node_get_background_image_shadow (node); + if (background_image != NULL) { node->background_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, background_image); node->background_material = _st_create_texture_material (node->background_texture); - if (shadow_spec) + if (background_image_shadow_spec) { - node->background_shadow_material = _st_create_shadow_material (shadow_spec, + node->background_shadow_material = _st_create_shadow_material (background_image_shadow_spec, node->background_texture); } } @@ -1240,9 +1243,9 @@ st_theme_node_paint (StThemeNode *node, * border_image and a single background image above it. */ - if (node->border_shadow_material) - _st_paint_shadow_with_opacity (node->shadow, - node->border_shadow_material, + if (node->box_shadow_material) + _st_paint_shadow_with_opacity (node->box_shadow, + node->box_shadow_material, &allocation, paint_opacity); @@ -1278,7 +1281,7 @@ st_theme_node_paint (StThemeNode *node, * like boder and background color into account). */ if (node->background_shadow_material != COGL_INVALID_HANDLE) - _st_paint_shadow_with_opacity (node->shadow, + _st_paint_shadow_with_opacity (node->background_image_shadow, node->background_shadow_material, &background_box, paint_opacity); @@ -1316,8 +1319,8 @@ st_theme_node_copy_cached_paint_state (StThemeNode *node, if (other->background_shadow_material) node->background_shadow_material = cogl_handle_ref (other->background_shadow_material); - if (other->border_shadow_material) - node->border_shadow_material = cogl_handle_ref (other->border_shadow_material); + if (other->box_shadow_material) + node->box_shadow_material = cogl_handle_ref (other->box_shadow_material); if (other->background_texture) node->background_texture = cogl_handle_ref (other->background_texture); if (other->background_material) diff --git a/src/st/st-theme-node-private.h b/src/st/st-theme-node-private.h index 7673755e9..ef4839eb3 100644 --- a/src/st/st-theme-node-private.h +++ b/src/st/st-theme-node-private.h @@ -65,7 +65,8 @@ struct _StThemeNode { char *background_image; StBorderImage *border_image; - StShadow *shadow; + StShadow *box_shadow; + StShadow *background_image_shadow; StShadow *text_shadow; StIconColors *icon_colors; @@ -86,7 +87,8 @@ struct _StThemeNode { guint background_computed : 1; guint foreground_computed : 1; guint border_image_computed : 1; - guint shadow_computed : 1; + guint box_shadow_computed : 1; + guint background_image_shadow_computed : 1; guint text_shadow_computed : 1; guint link_type : 2; @@ -95,7 +97,7 @@ struct _StThemeNode { float alloc_height; CoglHandle background_shadow_material; - CoglHandle border_shadow_material; + CoglHandle box_shadow_material; CoglHandle background_texture; CoglHandle background_material; CoglHandle border_texture; diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c index 4a6a1218d..b57afb654 100644 --- a/src/st/st-theme-node.c +++ b/src/st/st-theme-node.c @@ -122,10 +122,16 @@ st_theme_node_finalize (GObject *object) node->font_desc = NULL; } - if (node->shadow) + if (node->box_shadow) { - st_shadow_unref (node->shadow); - node->shadow = NULL; + st_shadow_unref (node->box_shadow); + node->box_shadow = NULL; + } + + if (node->background_image_shadow) + { + st_shadow_unref (node->background_image_shadow); + node->background_image_shadow = NULL; } if (node->text_shadow) @@ -2606,16 +2612,17 @@ st_theme_node_get_vertical_padding (StThemeNode *node) return padding; } -static void -parse_shadow_property (StThemeNode *node, - CRDeclaration *decl, - ClutterColor *color, - gdouble *xoffset, - gdouble *yoffset, - gdouble *blur, - gdouble *spread) +static GetFromTermResult +parse_shadow_property (StThemeNode *node, + CRDeclaration *decl, + ClutterColor *color, + gdouble *xoffset, + gdouble *yoffset, + gdouble *blur, + gdouble *spread) { /* Set value for width/color/blur in any order */ + GetFromTermResult result; CRTerm *term; int n_offsets = 0; @@ -2628,8 +2635,6 @@ parse_shadow_property (StThemeNode *node, for (term = decl->value; term; term = term->next) { - GetFromTermResult result; - if (term->type == TERM_NUMBER) { gdouble value; @@ -2637,7 +2642,16 @@ parse_shadow_property (StThemeNode *node, multiplier = (term->unary_op == MINUS_UOP) ? -1. : 1.; result = get_length_from_term (node, term, FALSE, &value); - if (result != VALUE_NOT_FOUND) + + if (result == VALUE_INHERIT) + { + /* we only allow inherit on the line by itself */ + if (n_offsets > 0) + return VALUE_NOT_FOUND; + else + return VALUE_INHERIT; + } + else if (result == VALUE_FOUND) { switch (n_offsets++) { @@ -2665,57 +2679,200 @@ parse_shadow_property (StThemeNode *node, } result = get_color_from_term (node, term, color); - if (result != VALUE_NOT_FOUND) + + if (result == VALUE_INHERIT) + { + if (n_offsets > 0) + return VALUE_NOT_FOUND; + else + return VALUE_INHERIT; + } + else if (result == VALUE_FOUND) { continue; } } + + /* The only required terms are the x and y offsets + */ + if (n_offsets >= 2) + return VALUE_FOUND; + else + return VALUE_NOT_FOUND; } /** - * st_theme_node_get_shadow: + * st_theme_node_lookup_shadow: * @node: a #StThemeNode + * @property_name: The name of the shadow property + * @inherit: if %TRUE, if a value is not found for the property on the + * node, then it will be looked up on the parent node, and then on the + * parent's parent, and so forth. Note that if the property has a + * value of 'inherit' it will be inherited even if %FALSE is passed + * in for @inherit; this only affects the default behavior for inheritance. + * @shadow: (out): location to store the shadow * - * Gets the value for the -st-shadow style property + * If the property is not found, the value in the shadow variable will not + * be changed. * - * Return value: (transfer none): the node's shadow, or %NULL - * if node has no shadow + * Generically looks up a property containing a set of shadow values. When + * specific getters (like st_theme_node_get_box_shadow ()) exist, they + * should be used instead. They are cached, so more efficient, and have + * handling for shortcut properties and other details of CSS. + * + * See also st_theme_node_get_shadow(), which provides a simpler API. + * + * Return value: %TRUE if the property was found in the properties for this + * theme node (or in the properties of parent nodes when inheriting.) */ -StShadow * -st_theme_node_get_shadow (StThemeNode *node) +gboolean +st_theme_node_lookup_shadow (StThemeNode *node, + const char *property_name, + gboolean inherit, + StShadow **shadow) { + ClutterColor color = { 0., }; + gdouble xoffset = 0.; + gdouble yoffset = 0.; + gdouble blur = 0.; + gdouble spread = 0.; + int i; - if (node->shadow_computed) - return node->shadow; - - node->shadow = NULL; - node->shadow_computed = TRUE; - ensure_properties (node); for (i = node->n_properties - 1; i >= 0; i--) { CRDeclaration *decl = node->properties[i]; - if (strcmp (decl->property->stryng->str, "-st-shadow") == 0) + if (strcmp (decl->property->stryng->str, property_name) == 0) { - ClutterColor color; - gdouble xoffset; - gdouble yoffset; - gdouble blur; - gdouble spread; - - parse_shadow_property (node, decl, - &color, &xoffset, &yoffset, &blur, &spread); - - node->shadow = st_shadow_new (&color, - xoffset, yoffset, - blur, spread); - - return node->shadow; + GetFromTermResult result = parse_shadow_property (node, + decl, + &color, + &xoffset, + &yoffset, + &blur, + &spread); + if (result == VALUE_FOUND) + { + *shadow = st_shadow_new (&color, xoffset, yoffset, blur, spread); + return TRUE; + } + else if (result == VALUE_INHERIT) + { + if (node->parent_node) + return st_theme_node_lookup_shadow (node->parent_node, + property_name, + inherit, + shadow); + else + break; + } } } + + if (inherit && node->parent_node) + return st_theme_node_lookup_shadow (node->parent_node, + property_name, + inherit, + shadow); + + return FALSE; +} + +/** + * st_theme_node_get_shadow: + * @node: a #StThemeNode + * @property_name: The name of the shadow property + * + * Generically looks up a property containing a set of shadow values. When + * specific getters (like st_theme_node_get_box_shadow()) exist, they + * should be used instead. They are cached, so more efficient, and have + * handling for shortcut properties and other details of CSS. + * + * Like st_theme_get_length(), this does not print a warning if the property is + * not found; it just returns %NULL + * + * See also st_theme_node_lookup_shadow (), which provides more options. + * + * Return value: (transfer full): the shadow, or %NULL if the property was not found. + */ +StShadow * +st_theme_node_get_shadow (StThemeNode *node, + const char *property_name) +{ + StShadow *shadow; + + if (st_theme_node_lookup_shadow (node, property_name, FALSE, &shadow)) + return shadow; + else + return NULL; +} + +/** + * st_theme_node_get_box_shadow: + * @node: a #StThemeNode + * + * Gets the value for the box-shadow style property + * + * Return value: (transfer none): the node's shadow, or %NULL + * if node has no shadow + */ +StShadow * +st_theme_node_get_box_shadow (StThemeNode *node) +{ + StShadow *shadow; + + if (node->box_shadow_computed) + return node->box_shadow; + + node->box_shadow = NULL; + node->box_shadow_computed = TRUE; + + if (st_theme_node_lookup_shadow (node, + "box-shadow", + FALSE, + &shadow)) + { + node->box_shadow = shadow; + + return node->box_shadow; + } + + return NULL; +} + +/** + * st_theme_node_get_background_image_shadow: + * @node: a #StThemeNode + * + * Gets the value for the -st-background-image-shadow style property + * + * Return value: (transfer none): the node's background image shadow, or %NULL + * if node has no such shadow + */ +StShadow * +st_theme_node_get_background_image_shadow (StThemeNode *node) +{ + StShadow *shadow; + + if (node->background_image_shadow_computed) + return node->background_image_shadow; + + node->background_image_shadow = NULL; + node->background_image_shadow_computed = TRUE; + + if (st_theme_node_lookup_shadow (node, + "-st-background-image-shadow", + FALSE, + &shadow)) + { + node->background_image_shadow = shadow; + + return node->background_image_shadow; + } + return NULL; } @@ -2732,43 +2889,25 @@ StShadow * st_theme_node_get_text_shadow (StThemeNode *node) { StShadow *result = NULL; - int i; if (node->text_shadow_computed) return node->text_shadow; ensure_properties (node); - for (i = node->n_properties - 1; i >= 0; i--) + if (!st_theme_node_lookup_shadow (node, + "text-shadow", + FALSE, + &result)) { - CRDeclaration *decl = node->properties[i]; - - if (strcmp (decl->property->stryng->str, "text-shadow") == 0) + if (node->parent_node) { - ClutterColor color; - gdouble xoffset; - gdouble yoffset; - gdouble blur; - gdouble spread; - - parse_shadow_property (node, decl, - &color, &xoffset, &yoffset, &blur, &spread); - - result = st_shadow_new (&color, - xoffset, yoffset, - blur, spread); - - break; + result = st_theme_node_get_text_shadow (node->parent_node); + if (result) + st_shadow_ref (result); } } - if (!result && node->parent_node) - { - result = st_theme_node_get_text_shadow (node->parent_node); - if (result) - st_shadow_ref (result); - } - node->text_shadow = result; node->text_shadow_computed = TRUE; @@ -3102,7 +3241,8 @@ st_theme_node_get_paint_box (StThemeNode *node, const ClutterActorBox *actor_box, ClutterActorBox *paint_box) { - StShadow *shadow; + StShadow *box_shadow; + StShadow *background_image_shadow; ClutterActorBox shadow_box; int outline_width; @@ -3110,26 +3250,41 @@ st_theme_node_get_paint_box (StThemeNode *node, g_return_if_fail (actor_box != NULL); g_return_if_fail (paint_box != NULL); - shadow = st_theme_node_get_shadow (node); + box_shadow = st_theme_node_get_box_shadow (node); + background_image_shadow = st_theme_node_get_background_image_shadow (node); outline_width = st_theme_node_get_outline_width (node); - if (!shadow && !outline_width) + + *paint_box = *actor_box; + + if (!box_shadow && !background_image_shadow && !outline_width) + return; + + paint_box->x1 -= outline_width; + paint_box->x2 += outline_width; + paint_box->y1 -= outline_width; + paint_box->y2 += outline_width; + + if (box_shadow) { - *paint_box = *actor_box; - return; + st_shadow_get_box (box_shadow, actor_box, &shadow_box); + + paint_box->x1 = MIN (paint_box->x1, shadow_box.x1); + paint_box->x2 = MAX (paint_box->x2, shadow_box.x2); + paint_box->y1 = MIN (paint_box->y1, shadow_box.y1); + paint_box->y2 = MAX (paint_box->y2, shadow_box.y2); } - if (shadow) - st_shadow_get_box (shadow, actor_box, &shadow_box); - else - shadow_box = *actor_box; + if (background_image_shadow) + { + st_shadow_get_box (background_image_shadow, actor_box, &shadow_box); - paint_box->x1 = MIN (actor_box->x1 - outline_width, shadow_box.x1); - paint_box->x2 = MAX (actor_box->x2 + outline_width, shadow_box.x2); - paint_box->y1 = MIN (actor_box->y1 - outline_width, shadow_box.y1); - paint_box->y2 = MAX (actor_box->y2 + outline_width, shadow_box.y2); + paint_box->x1 = MIN (paint_box->x1, shadow_box.x1); + paint_box->x2 = MAX (paint_box->x2, shadow_box.x2); + paint_box->y1 = MIN (paint_box->y1, shadow_box.y1); + paint_box->y2 = MAX (paint_box->y2, shadow_box.y2); + } } - /** * st_theme_node_geometry_equal: * @node: a #StThemeNode @@ -3240,8 +3395,17 @@ st_theme_node_paint_equal (StThemeNode *node, if (border_image != NULL && !st_border_image_equal (border_image, other_border_image)) return FALSE; - shadow = st_theme_node_get_shadow (node); - other_shadow = st_theme_node_get_shadow (other); + shadow = st_theme_node_get_box_shadow (node); + other_shadow = st_theme_node_get_box_shadow (other); + + if ((shadow == NULL) != (other_shadow == NULL)) + return FALSE; + + if (shadow != NULL && !st_shadow_equal (shadow, other_shadow)) + return FALSE; + + shadow = st_theme_node_get_background_image_shadow (node); + other_shadow = st_theme_node_get_background_image_shadow (other); if ((shadow == NULL) != (other_shadow == NULL)) return FALSE; diff --git a/src/st/st-theme-node.h b/src/st/st-theme-node.h index 13e0bf63c..ad9e4e097 100644 --- a/src/st/st-theme-node.h +++ b/src/st/st-theme-node.h @@ -133,6 +133,10 @@ gboolean st_theme_node_lookup_length (StThemeNode *node, const char *property_name, gboolean inherit, gdouble *length); +gboolean st_theme_node_lookup_shadow (StThemeNode *node, + const char *property_name, + gboolean inherit, + StShadow **shadow); /* Easier-to-use variants of the above, for application-level use */ void st_theme_node_get_color (StThemeNode *node, @@ -142,6 +146,8 @@ gdouble st_theme_node_get_double (StThemeNode *node, const char *property_name); gdouble st_theme_node_get_length (StThemeNode *node, const char *property_name); +StShadow *st_theme_node_get_shadow (StThemeNode *node, + const char *property_name); /* Specific getters for particular properties: cached */ @@ -195,9 +201,11 @@ StTextAlign st_theme_node_get_text_align (StThemeNode *node); const PangoFontDescription *st_theme_node_get_font (StThemeNode *node); StBorderImage *st_theme_node_get_border_image (StThemeNode *node); -StShadow *st_theme_node_get_shadow (StThemeNode *node); +StShadow *st_theme_node_get_box_shadow (StThemeNode *node); StShadow *st_theme_node_get_text_shadow (StThemeNode *node); +StShadow *st_theme_node_get_background_image_shadow (StThemeNode *node); + StIconColors *st_theme_node_get_icon_colors (StThemeNode *node); /* Helpers for get_preferred_width()/get_preferred_height() ClutterActor vfuncs */ diff --git a/tests/interactive/borders.js b/tests/interactive/borders.js index 5c97ea105..ba8d1e0ac 100644 --- a/tests/interactive/borders.js +++ b/tests/interactive/borders.js @@ -93,14 +93,14 @@ function addGradientCase(direction, borderWidth, borderRadius, extra) { framedGradients.add(gradientBox, { x_fill: false, y_fill: false } ); } -addGradientCase ('horizontal', 0, 5, '-st-shadow: 0px 0px 0px 0px rgba(0,0,0,0.5);'); -addGradientCase ('horizontal', 2, 5, '-st-shadow: 0px 2px 0px 0px rgba(0,255,0,0.5);'); -addGradientCase ('horizontal', 5, 2, '-st-shadow: 2px 0px 0px 0px rgba(0,0,255,0.5);'); -addGradientCase ('horizontal', 5, 20, '-st-shadow: 0px 0px 4px 0px rgba(255,0,0,0.5);'); -addGradientCase ('vertical', 0, 5, '-st-shadow: 0px 0px 0px 4px rgba(0,0,0,0.5);'); -addGradientCase ('vertical', 2, 5, '-st-shadow: 0px 0px 4px 4px rgba(0,0,0,0.5);'); -addGradientCase ('vertical', 5, 2, '-st-shadow: -2px -2px 6px 0px rgba(0,0,0,0.5);'); -addGradientCase ('vertical', 5, 20, '-st-shadow: -2px -2px 0px 6px rgba(0,0,0,0.5);'); +addGradientCase ('horizontal', 0, 5, 'box-shadow: 0px 0px 0px 0px rgba(0,0,0,0.5);'); +addGradientCase ('horizontal', 2, 5, 'box-shadow: 0px 2px 0px 0px rgba(0,255,0,0.5);'); +addGradientCase ('horizontal', 5, 2, 'box-shadow: 2px 0px 0px 0px rgba(0,0,255,0.5);'); +addGradientCase ('horizontal', 5, 20, 'box-shadow: 0px 0px 4px 0px rgba(255,0,0,0.5);'); +addGradientCase ('vertical', 0, 5, 'box-shadow: 0px 0px 0px 4px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 2, 5, 'box-shadow: 0px 0px 4px 4px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 5, 2, 'box-shadow: -2px -2px 6px 0px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 5, 20, 'box-shadow: -2px -2px 0px 6px rgba(0,0,0,0.5);'); box.add(new St.Label({ text: "Rounded, framed, shadowed images" })); @@ -119,15 +119,15 @@ function addBackgroundImageCase(borderWidth, borderRadius, width, height, extra) } addBackgroundImageCase (0, 0, 32, 32, 'background-position: 2px 5px'); -addBackgroundImageCase (0, 0, 16, 16, '-st-shadow: 1px 1px 4px 0px rgba(0,0,0,0.5); background-color: rgba(0,0,0,0)'); -addBackgroundImageCase (0, 5, 32, 32, '-st-shadow: 0px 0px 0px 0px rgba(0,0,0,0.5);'); -addBackgroundImageCase (2, 5, 32, 32, '-st-shadow: 0px 2px 0px 0px rgba(0,255,0,0.5);'); -addBackgroundImageCase (5, 2, 32, 32, '-st-shadow: 2px 0px 0px 0px rgba(0,0,255,0.5);'); -addBackgroundImageCase (5, 20, 32, 32, '-st-shadow: 0px 0px 4px 0px rgba(255,0,0,0.5);'); -addBackgroundImageCase (0, 5, 48, 48, '-st-shadow: 0px 0px 0px 4px rgba(0,0,0,0.5);'); -addBackgroundImageCase (5, 5, 48, 48, '-st-shadow: 0px 0px 4px 4px rgba(0,0,0,0.5);'); -addBackgroundImageCase (0, 5, 64, 64, '-st-shadow: -2px -2px 6px 0px rgba(0,0,0,0.5);'); -addBackgroundImageCase (5, 5, 64, 64, '-st-shadow: -2px -2px 0px 6px rgba(0,0,0,0.5);'); +addBackgroundImageCase (0, 0, 16, 16, '-st-background-image-shadow: 1px 1px 4px 0px rgba(0,0,0,0.5); background-color: rgba(0,0,0,0)'); +addBackgroundImageCase (0, 5, 32, 32, '-st-background-image-shadow: 0px 0px 0px 0px rgba(0,0,0,0.5);'); +addBackgroundImageCase (2, 5, 32, 32, '-st-background-image-shadow: 0px 2px 0px 0px rgba(0,255,0,0.5);'); +addBackgroundImageCase (5, 2, 32, 32, '-st-background-image-shadow: 2px 0px 0px 0px rgba(0,0,255,0.5);'); +addBackgroundImageCase (5, 20, 32, 32, '-st-background-image-shadow: 0px 0px 4px 0px rgba(255,0,0,0.5);'); +addBackgroundImageCase (0, 5, 48, 48, '-st-background-image-shadow: 0px 0px 0px 4px rgba(0,0,0,0.5);'); +addBackgroundImageCase (5, 5, 48, 48, '-st-background-image-shadow: 0px 0px 4px 4px rgba(0,0,0,0.5);'); +addBackgroundImageCase (0, 5, 64, 64, '-st-background-image-shadow: -2px -2px 6px 0px rgba(0,0,0,0.5);'); +addBackgroundImageCase (5, 5, 64, 64, '-st-background-image-shadow: -2px -2px 0px 6px rgba(0,0,0,0.5);'); addBackgroundImageCase (0, 5, 32, 32, 'background-position: 2px 5px'); stage.show(); From 6dc4adfc137c1c7d2bd16a2095d943e9b0674c66 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 12 Jan 2011 17:07:19 -0500 Subject: [PATCH 17/30] StThemeNode: split border_texture into two vars The border_texture (and border_material) variable is being overloaded for two purposes: it's used as a source to 9-slice the border from, and it's used as place to prerender the background and border together for gradients. While we only do one or the other for any given node, the two cases are distinct, and should use distinct variables for readability. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-theme-node-drawing.c | 71 ++++++++++++++++++++-------------- src/st/st-theme-node-private.h | 6 ++- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index 8eb2b15ab..2720239ff 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -633,10 +633,14 @@ _st_theme_node_free_drawing_state (StThemeNode *node) cogl_handle_unref (node->background_material); if (node->background_shadow_material != COGL_INVALID_HANDLE) cogl_handle_unref (node->background_shadow_material); - if (node->border_texture != COGL_INVALID_HANDLE) - cogl_handle_unref (node->border_texture); - if (node->border_material != COGL_INVALID_HANDLE) - cogl_handle_unref (node->border_material); + if (node->border_slices_texture != COGL_INVALID_HANDLE) + cogl_handle_unref (node->border_slices_texture); + if (node->border_slices_material != COGL_INVALID_HANDLE) + cogl_handle_unref (node->border_slices_material); + if (node->prerendered_texture != COGL_INVALID_HANDLE) + cogl_handle_unref (node->prerendered_texture); + if (node->prerendered_material != COGL_INVALID_HANDLE) + cogl_handle_unref (node->prerendered_material); if (node->box_shadow_material != COGL_INVALID_HANDLE) cogl_handle_unref (node->box_shadow_material); @@ -656,8 +660,10 @@ _st_theme_node_init_drawing_state (StThemeNode *node) node->background_material = COGL_INVALID_HANDLE; node->background_shadow_material = COGL_INVALID_HANDLE; node->box_shadow_material = COGL_INVALID_HANDLE; - node->border_texture = COGL_INVALID_HANDLE; - node->border_material = COGL_INVALID_HANDLE; + node->border_slices_texture = COGL_INVALID_HANDLE; + node->border_slices_material = COGL_INVALID_HANDLE; + node->prerendered_texture = COGL_INVALID_HANDLE; + node->prerendered_material = COGL_INVALID_HANDLE; for (corner_id = 0; corner_id < 4; corner_id++) node->corner_material[corner_id] = COGL_INVALID_HANDLE; @@ -703,23 +709,31 @@ st_theme_node_render_resources (StThemeNode *node, filename = st_border_image_get_filename (border_image); - node->border_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename); + node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename); } else if (node->background_gradient_type != ST_GRADIENT_NONE) { - node->border_texture = st_theme_node_render_gradient (node); + node->prerendered_texture = st_theme_node_render_gradient (node); } - if (node->border_texture) - node->border_material = _st_create_texture_material (node->border_texture); + if (node->border_slices_texture) + node->border_slices_material = _st_create_texture_material (node->border_slices_texture); else - node->border_material = COGL_INVALID_HANDLE; + node->border_slices_material = COGL_INVALID_HANDLE; + + if (node->prerendered_texture) + node->prerendered_material = _st_create_texture_material (node->prerendered_texture); + else + node->prerendered_material = COGL_INVALID_HANDLE; if (box_shadow_spec) { - if (node->border_texture != COGL_INVALID_HANDLE) + if (node->border_slices_texture != COGL_INVALID_HANDLE) node->box_shadow_material = _st_create_shadow_material (box_shadow_spec, - node->border_texture); + node->border_slices_texture); + else if (node->prerendered_texture != COGL_INVALID_HANDLE) + node->box_shadow_material = _st_create_shadow_material (box_shadow_spec, + node->prerendered_texture); else if (node->background_color.alpha > 0 || node->border_width[ST_SIDE_TOP] > 0 || node->border_width[ST_SIDE_LEFT] > 0 || @@ -1072,8 +1086,8 @@ st_theme_node_paint_sliced_border_image (StThemeNode *node, st_border_image_get_borders (border_image, &border_left, &border_right, &border_top, &border_bottom); - img_width = cogl_texture_get_width (node->border_texture); - img_height = cogl_texture_get_height (node->border_texture); + img_width = cogl_texture_get_width (node->border_slices_texture); + img_height = cogl_texture_get_height (node->border_slices_texture); tx1 = border_left / img_width; tx2 = (img_width - border_right) / img_width; @@ -1088,7 +1102,7 @@ st_theme_node_paint_sliced_border_image (StThemeNode *node, if (ey < 0) ey = border_bottom; /* FIXME ? */ - material = node->border_material; + material = node->border_slices_material; cogl_material_set_color4ub (material, paint_opacity, paint_opacity, paint_opacity, paint_opacity); @@ -1249,14 +1263,11 @@ st_theme_node_paint (StThemeNode *node, &allocation, paint_opacity); - if (node->border_material != COGL_INVALID_HANDLE) - { - /* Gradients and border images are mutually exclusive at this time */ - if (node->background_gradient_type != ST_GRADIENT_NONE) - paint_material_with_opacity (node->border_material, &allocation, paint_opacity); - else - st_theme_node_paint_sliced_border_image (node, &allocation, paint_opacity); - } + /* Gradients and border images are mutually exclusive at this time */ + if (node->prerendered_material != COGL_INVALID_HANDLE) + paint_material_with_opacity (node->prerendered_material, &allocation, paint_opacity); + else if (node->border_slices_material != COGL_INVALID_HANDLE) + st_theme_node_paint_sliced_border_image (node, &allocation, paint_opacity); else st_theme_node_paint_borders (node, box, paint_opacity); @@ -1325,10 +1336,14 @@ st_theme_node_copy_cached_paint_state (StThemeNode *node, node->background_texture = cogl_handle_ref (other->background_texture); if (other->background_material) node->background_material = cogl_handle_ref (other->background_material); - if (other->border_texture) - node->border_texture = cogl_handle_ref (other->border_texture); - if (other->border_material) - node->border_material = cogl_handle_ref (other->border_material); + if (other->border_slices_texture) + node->border_slices_texture = cogl_handle_ref (other->border_slices_texture); + if (other->border_slices_material) + node->border_slices_material = cogl_handle_ref (other->border_slices_material); + if (other->prerendered_texture) + node->prerendered_texture = cogl_handle_ref (other->prerendered_texture); + if (other->prerendered_material) + node->prerendered_material = cogl_handle_ref (other->prerendered_material); for (corner_id = 0; corner_id < 4; corner_id++) if (other->corner_material[corner_id]) node->corner_material[corner_id] = cogl_handle_ref (other->corner_material[corner_id]); diff --git a/src/st/st-theme-node-private.h b/src/st/st-theme-node-private.h index ef4839eb3..f42cf4025 100644 --- a/src/st/st-theme-node-private.h +++ b/src/st/st-theme-node-private.h @@ -100,8 +100,10 @@ struct _StThemeNode { CoglHandle box_shadow_material; CoglHandle background_texture; CoglHandle background_material; - CoglHandle border_texture; - CoglHandle border_material; + CoglHandle border_slices_texture; + CoglHandle border_slices_material; + CoglHandle prerendered_texture; + CoglHandle prerendered_material; CoglHandle corner_material[4]; }; From 260ca64e947792e2942146c9945b8edae4438571 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Fri, 14 Jan 2011 14:03:40 -0500 Subject: [PATCH 18/30] StThemeNode: Don't make border images and gradients mutually exclusive Since we no longer use the same material for both purposes, we can now remove the restriction that they are mutually exclusive. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-theme-node-drawing.c | 47 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index 2720239ff..710bc12ba 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -430,6 +430,7 @@ get_arbitrary_border_color (StThemeNode *node, static CoglHandle st_theme_node_render_gradient (StThemeNode *node) { + StBorderImage *border_image; CoglHandle texture; int radius[4], i; cairo_t *cr; @@ -440,6 +441,8 @@ st_theme_node_render_gradient (StThemeNode *node) guint rowstride; guchar *data; + border_image = st_theme_node_get_border_image (node); + rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, node->alloc_width); data = g_new0 (guchar, node->alloc_height * rowstride); surface = cairo_image_surface_create_for_data (data, @@ -512,15 +515,16 @@ st_theme_node_render_gradient (StThemeNode *node) cairo_close_path (cr); - /* If we have a border, we fill the outline with the border + /* If we have a solid border, we fill the outline with the border * color and create the inline shape for the background gradient; * otherwise the outline shape is filled with the background * gradient directly */ - if (border_width[ST_SIDE_TOP] > 0 || - border_width[ST_SIDE_RIGHT] > 0 || - border_width[ST_SIDE_BOTTOM] > 0 || - border_width[ST_SIDE_LEFT] > 0) + if (border_image == NULL && + (border_width[ST_SIDE_TOP] > 0 || + border_width[ST_SIDE_RIGHT] > 0 || + border_width[ST_SIDE_BOTTOM] > 0 || + border_width[ST_SIDE_LEFT] > 0)) { cairo_set_source_rgba (cr, border_color.red / 255., @@ -711,10 +715,9 @@ st_theme_node_render_resources (StThemeNode *node, node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename); } - else if (node->background_gradient_type != ST_GRADIENT_NONE) - { - node->prerendered_texture = st_theme_node_render_gradient (node); - } + + if (node->background_gradient_type != ST_GRADIENT_NONE) + node->prerendered_texture = st_theme_node_render_gradient (node); if (node->border_slices_texture) node->border_slices_material = _st_create_texture_material (node->border_slices_texture); @@ -1247,14 +1250,8 @@ st_theme_node_paint (StThemeNode *node, * - The combination of border image and a non-zero border radius is * not supported; the background color will be drawn with square * corners. - * - The combination of border image and a background gradient is not - * supported; the background will be drawn as a solid color - * - The background image is drawn above the border color or image, - * not below it. + * - The background image is drawn above the border color, not below it. * - We don't clip the background image to the (rounded) border area. - * - * The first three allow us to always draw with no more than a single - * border_image and a single background image above it. */ if (node->box_shadow_material) @@ -1263,13 +1260,19 @@ st_theme_node_paint (StThemeNode *node, &allocation, paint_opacity); - /* Gradients and border images are mutually exclusive at this time */ - if (node->prerendered_material != COGL_INVALID_HANDLE) - paint_material_with_opacity (node->prerendered_material, &allocation, paint_opacity); - else if (node->border_slices_material != COGL_INVALID_HANDLE) - st_theme_node_paint_sliced_border_image (node, &allocation, paint_opacity); + if (node->prerendered_material != COGL_INVALID_HANDLE || + node->border_slices_material != COGL_INVALID_HANDLE) + { + if (node->prerendered_material != COGL_INVALID_HANDLE) + paint_material_with_opacity (node->prerendered_material, &allocation, paint_opacity); + + if (node->border_slices_material != COGL_INVALID_HANDLE) + st_theme_node_paint_sliced_border_image (node, &allocation, paint_opacity); + } else - st_theme_node_paint_borders (node, box, paint_opacity); + { + st_theme_node_paint_borders (node, box, paint_opacity); + } st_theme_node_paint_outline (node, box, paint_opacity); From 06cea89daeadaa7d2ec406865aa15336122822a7 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 13 Jan 2011 17:46:59 -0500 Subject: [PATCH 19/30] StThemeNodeDrawing: clip background image to node allocation When drawing the background image, we need to make sure we don't draw outside the bounding rectangle of the node. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-theme-node-drawing.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index 710bc12ba..feea5fc09 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -773,8 +773,35 @@ st_theme_node_render_resources (StThemeNode *node, if (background_image != NULL) { + CoglHandle texture; + + texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, background_image); + + /* If no background position is specified, then we will automatically scale + * the background to fit within the node allocation. But, if a background + * position is specified, we won't scale the background, and it could + * potentially leak out of bounds. To prevent that, we subtexture from the + * in bounds area when necessary. + */ + if (node->background_position_set && + (cogl_texture_get_width (texture) > width || + cogl_texture_get_height (texture) > height)) + { + CoglHandle subtexture; + + subtexture = cogl_texture_new_from_sub_texture (texture, + 0, 0, + width - node->background_position_x, + height - node->background_position_y); + cogl_handle_unref (texture); + + node->background_texture = subtexture; + } + else + { + node->background_texture = texture; + } - node->background_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, background_image); node->background_material = _st_create_texture_material (node->background_texture); if (background_image_shadow_spec) From 2b6d4ff27923ded5ba468bbdaaed740a47ea0c9e Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Fri, 21 Jan 2011 21:52:18 -0500 Subject: [PATCH 20/30] StThemeNodeDrawing: clip background image shadow to outline When drawing the background image shadow, we need to clip it to the node's background color, gradient, or borders if present. If the background color is transparent, and there aren't any borders, then we don't clip the shadow since there is nothing to confine it. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-theme-node-drawing.c | 42 ++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index feea5fc09..9ffb7bb9c 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -427,6 +427,30 @@ get_arbitrary_border_color (StThemeNode *node, st_theme_node_get_border_color (node, ST_SIDE_TOP, color); } +static gboolean +st_theme_node_has_visible_outline (StThemeNode *node) +{ + if (node->background_color.alpha > 0) + return TRUE; + + if (node->background_gradient_end.alpha > 0) + return TRUE; + + if (node->border_radius[ST_CORNER_TOPLEFT] > 0 || + node->border_radius[ST_CORNER_TOPRIGHT] > 0 || + node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 || + node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0) + return TRUE; + + if (node->border_width[ST_SIDE_TOP] > 0 || + node->border_width[ST_SIDE_LEFT] > 0 || + node->border_width[ST_SIDE_RIGHT] > 0 || + node->border_width[ST_SIDE_BOTTOM] > 0) + return TRUE; + + return FALSE; +} + static CoglHandle st_theme_node_render_gradient (StThemeNode *node) { @@ -1308,6 +1332,16 @@ st_theme_node_paint (StThemeNode *node, ClutterActorBox background_box; get_background_position (node, &allocation, &background_box); + gboolean has_visible_outline; + + /* If the background doesn't have a border or opaque background, + * then we let its background image shadows leak out, but other + * wise we clip it. + */ + has_visible_outline = st_theme_node_has_visible_outline (node); + + if (has_visible_outline) + cogl_clip_push_rectangle (allocation.x1, allocation.y1, allocation.x2, allocation.y2); /* CSS based drop shadows * @@ -1318,8 +1352,9 @@ st_theme_node_paint (StThemeNode *node, * multiple shadows and allow for a more liberal placement of the color * parameter - its interpretation defers significantly in that the shadow's * shape is not determined by the bounding box, but by the CSS background - * image (we could exend this in the future to take other CSS properties - * like boder and background color into account). + * image. The drop shadows are allowed to escape the nodes allocation if + * there is nothing (like a border, or the edge of the background color) + * to logically confine it. */ if (node->background_shadow_material != COGL_INVALID_HANDLE) _st_paint_shadow_with_opacity (node->background_image_shadow, @@ -1328,6 +1363,9 @@ st_theme_node_paint (StThemeNode *node, paint_opacity); paint_material_with_opacity (node->background_material, &background_box, paint_opacity); + + if (has_visible_outline) + cogl_clip_pop (); } } From a7fa7d748dd285023d3fcdc770f185c7ea8d51d4 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sun, 12 Dec 2010 15:41:41 -0500 Subject: [PATCH 21/30] StThemeNodeDrawing: generalize render_gradient to render_background_with_border A lot of the border drawing logic in st_theme_node_render_gradient is applicable to other non-solid background types than gradients. This commit refactors that code so that support for other non-solid background types can be more easily integrated later. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-theme-node-drawing.c | 97 +++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index 9ffb7bb9c..59b3f84c0 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -451,15 +451,54 @@ st_theme_node_has_visible_outline (StThemeNode *node) return FALSE; } +static cairo_pattern_t * +create_cairo_pattern_of_background_gradient (StThemeNode *node) +{ + cairo_pattern_t *pattern; + + g_return_val_if_fail (node->background_gradient_type != ST_GRADIENT_NONE, + NULL); + + if (node->background_gradient_type == ST_GRADIENT_VERTICAL) + pattern = cairo_pattern_create_linear (0, 0, 0, node->alloc_height); + else if (node->background_gradient_type == ST_GRADIENT_HORIZONTAL) + pattern = cairo_pattern_create_linear (0, 0, node->alloc_width, 0); + else + { + gdouble cx, cy; + + cx = node->alloc_width / 2.; + cy = node->alloc_height / 2.; + pattern = cairo_pattern_create_radial (cx, cy, 0, cx, cy, MIN (cx, cy)); + } + + cairo_pattern_add_color_stop_rgba (pattern, 0, + node->background_color.red / 255., + node->background_color.green / 255., + node->background_color.blue / 255., + node->background_color.alpha / 255.); + cairo_pattern_add_color_stop_rgba (pattern, 1, + node->background_gradient_end.red / 255., + node->background_gradient_end.green / 255., + node->background_gradient_end.blue / 255., + node->background_gradient_end.alpha / 255.); + return pattern; +} + +/* In order for borders to be smoothly blended with non-solid backgrounds, + * we need to use cairo. This function is a slow fallback path for those + * cases. It currently only supports gradients, however. + */ static CoglHandle -st_theme_node_render_gradient (StThemeNode *node) +st_theme_node_render_background_with_border (StThemeNode *node) { StBorderImage *border_image; CoglHandle texture; int radius[4], i; cairo_t *cr; cairo_surface_t *surface; - cairo_pattern_t *pattern; + cairo_pattern_t *pattern = NULL; + gboolean draw_solid_background; ClutterColor border_color; int border_width[4]; guint rowstride; @@ -486,30 +525,17 @@ st_theme_node_render_gradient (StThemeNode *node) radius[i] = st_theme_node_get_border_radius (node, i); } - if (node->background_gradient_type == ST_GRADIENT_VERTICAL) - pattern = cairo_pattern_create_linear (0, 0, 0, node->alloc_height); - else if (node->background_gradient_type == ST_GRADIENT_HORIZONTAL) - pattern = cairo_pattern_create_linear (0, 0, node->alloc_width, 0); + if (node->background_gradient_type != ST_GRADIENT_NONE) + { + pattern = create_cairo_pattern_of_background_gradient (node); + draw_solid_background = FALSE; + } else { - gdouble cx, cy; - - cx = node->alloc_width / 2.; - cy = node->alloc_height / 2.; - pattern = cairo_pattern_create_radial (cx, cy, 0, cx, cy, MIN (cx, cy)); + g_warning ("st_theme_node_render_background_with_border called with non-gradient background (which isn't yet supported). Falling back to solid background color."); + draw_solid_background = TRUE; } - cairo_pattern_add_color_stop_rgba (pattern, 0, - node->background_color.red / 255., - node->background_color.green / 255., - node->background_color.blue / 255., - node->background_color.alpha / 255.); - cairo_pattern_add_color_stop_rgba (pattern, 1, - node->background_gradient_end.red / 255., - node->background_gradient_end.green / 255., - node->background_gradient_end.blue / 255., - node->background_gradient_end.alpha / 255.); - /* Create a path for the background's outline first */ if (radius[ST_CORNER_TOPLEFT] > 0) cairo_arc (cr, @@ -538,11 +564,10 @@ st_theme_node_render_gradient (StThemeNode *node) radius[ST_CORNER_BOTTOMLEFT], M_PI / 2, M_PI); cairo_close_path (cr); - - /* If we have a solid border, we fill the outline with the border - * color and create the inline shape for the background gradient; + /* If we have a solid border, we fill the outline shape with the border + * color and create the inline shape for the background; * otherwise the outline shape is filled with the background - * gradient directly + * directly */ if (border_image == NULL && (border_width[ST_SIDE_TOP] > 0 || @@ -624,10 +649,22 @@ st_theme_node_render_gradient (StThemeNode *node) cairo_close_path (cr); } - cairo_set_source (cr, pattern); - cairo_fill (cr); + if (draw_solid_background) + { + cairo_set_source_rgba (cr, + node->background_color.red / 255., + node->background_color.green / 255., + node->background_color.blue / 255., + node->background_color.alpha / 255.); + cairo_fill_preserve (cr); + } - cairo_pattern_destroy (pattern); + if (pattern != NULL) + { + cairo_set_source (cr, pattern); + cairo_fill (cr); + cairo_pattern_destroy (pattern); + } texture = cogl_texture_new_from_data (node->alloc_width, node->alloc_height, COGL_TEXTURE_NONE, @@ -741,7 +778,7 @@ st_theme_node_render_resources (StThemeNode *node, } if (node->background_gradient_type != ST_GRADIENT_NONE) - node->prerendered_texture = st_theme_node_render_gradient (node); + node->prerendered_texture = st_theme_node_render_background_with_border (node); if (node->border_slices_texture) node->border_slices_material = _st_create_texture_material (node->border_slices_texture); From f7ab90b93b88b7e4ad08e56614cd0aaf25160697 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Fri, 10 Dec 2010 17:15:39 -0500 Subject: [PATCH 22/30] StTextureCache: add api to load image to cairo surface Loading a pixbuf in a way that cairo can use it is a pretty involved process that involves a lot of code, and pixel fiddling. This commit adds the mechanism to StTextureCache so we can reuse the existing pixbuf handling code there, and also get caching. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-texture-cache.c | 98 +++++++++++++++++++++++++++++++++++++++ src/st/st-texture-cache.h | 3 ++ 2 files changed, 101 insertions(+) diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c index c7906ab9c..52ce02ed0 100644 --- a/src/st/st-texture-cache.c +++ b/src/st/st-texture-cache.c @@ -30,6 +30,7 @@ #define CACHE_PREFIX_GICON "gicon:" #define CACHE_PREFIX_URI "uri:" +#define CACHE_PREFIX_URI_FOR_CAIRO "uri-for-cairo:" #define CACHE_PREFIX_THUMBNAIL_URI "thumbnail-uri:" #define CACHE_PREFIX_RAW_CHECKSUM "raw-checksum:" #define CACHE_PREFIX_COMPRESSED_CHECKSUM "compressed-checksum:" @@ -798,6 +799,27 @@ pixbuf_to_cogl_handle (GdkPixbuf *pixbuf) gdk_pixbuf_get_pixels (pixbuf)); } +static cairo_surface_t * +pixbuf_to_cairo_surface (GdkPixbuf *pixbuf) +{ + cairo_surface_t *dummy_surface; + cairo_pattern_t *pattern; + cairo_surface_t *surface; + cairo_t *cr; + + dummy_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1); + + cr = cairo_create (dummy_surface); + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + pattern = cairo_get_source (cr); + cairo_pattern_get_surface (pattern, &surface); + cairo_surface_reference (surface); + cairo_destroy (cr); + cairo_surface_destroy (dummy_surface); + + return surface; +} + static GdkPixbuf * load_pixbuf_fallback(AsyncTextureLoadData *data) { @@ -1504,6 +1526,45 @@ out: return texdata; } +static cairo_surface_t * +st_texture_cache_load_uri_sync_to_cairo_surface (StTextureCache *cache, + StTextureCachePolicy policy, + const gchar *uri, + int available_width, + int available_height, + GError **error) +{ + cairo_surface_t *surface; + GdkPixbuf *pixbuf; + char *key; + + key = g_strconcat (CACHE_PREFIX_URI_FOR_CAIRO, uri, NULL); + + surface = g_hash_table_lookup (cache->priv->keyed_cache, key); + + if (surface == NULL) + { + pixbuf = impl_load_pixbuf_file (uri, available_width, available_height, error); + if (!pixbuf) + goto out; + + surface = pixbuf_to_cairo_surface (pixbuf); + g_object_unref (pixbuf); + + if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER) + { + cairo_surface_reference (surface); + g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), surface); + } + } + else + cairo_surface_reference (surface); + +out: + g_free (key); + return surface; +} + /** * st_texture_cache_load_uri_sync: * @@ -1582,6 +1643,43 @@ st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache, return texture; } +/** + * st_texture_cache_load_file_to_cairo_surface: + * @cache: A #StTextureCache + * @file_path: Path to a file in supported image format + * + * This function synchronously loads the given file path + * into a cairo surface. On error, a warning is emitted + * and %NULL is returned. + * + * Returns: (transfer full): a new #cairo_surface_t * + */ +cairo_surface_t * +st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache, + const gchar *file_path) +{ + cairo_surface_t *surface; + GFile *file; + char *uri; + GError *error = NULL; + + file = g_file_new_for_path (file_path); + uri = g_file_get_uri (file); + + surface = st_texture_cache_load_uri_sync_to_cairo_surface (cache, ST_TEXTURE_CACHE_POLICY_FOREVER, + uri, -1, -1, &error); + g_object_unref (file); + g_free (uri); + + if (surface == NULL) + { + g_warning ("Failed to load %s: %s", file_path, error->message); + g_clear_error (&error); + return NULL; + } + return surface; +} + /** * st_texture_cache_load_file_simple: * @cache: A #StTextureCache diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h index 4f1a4820a..5f1e70193 100644 --- a/src/st/st-texture-cache.h +++ b/src/st/st-texture-cache.h @@ -119,6 +119,9 @@ ClutterActor *st_texture_cache_load_uri_sync (StTextureCache *cache, CoglHandle st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache, const gchar *file_path); +cairo_surface_t *st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache, + const gchar *file_path); + ClutterActor *st_texture_cache_load_file_simple (StTextureCache *cache, const gchar *file_path); From 1faee65a68bfcd0ebc7d5a9443b61f45601fcc22 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 12 Jan 2011 19:43:36 -0500 Subject: [PATCH 23/30] st-private: split pixel blurring code out The guts are somewhat complicated, and are potentially reusable for future cairo fallback code. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-private.c | 105 ++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/src/st/st-private.c b/src/st/st-private.c index 290cdf833..757f1e425 100644 --- a/src/st/st-private.c +++ b/src/st/st-private.c @@ -422,44 +422,31 @@ calculate_gaussian_kernel (gdouble sigma, return ret; } -CoglHandle -_st_create_shadow_material (StShadow *shadow_spec, - CoglHandle src_texture) +static guchar * +blur_pixels (guchar *pixels_in, + gint width_in, + gint height_in, + gint rowstride_in, + gdouble blur, + gint *width_out, + gint *height_out, + gint *rowstride_out) { - static CoglHandle shadow_material_template = COGL_INVALID_HANDLE; - - CoglHandle material; - CoglHandle texture; - guchar *pixels_in, *pixels_out; - gint width_in, height_in, rowstride_in; - gint width_out, height_out, rowstride_out; - float sigma; - - g_return_val_if_fail (shadow_spec != NULL, COGL_INVALID_HANDLE); - g_return_val_if_fail (src_texture != COGL_INVALID_HANDLE, - COGL_INVALID_HANDLE); + guchar *pixels_out; + float sigma; /* we use an approximation of the sigma - blur radius relationship used in Firefox for doing SVG blurs; see http://mxr.mozilla.org/mozilla-central/source/gfx/thebes/src/gfxBlur.cpp#280 */ - sigma = shadow_spec->blur / 1.9; + sigma = blur / 1.9; - width_in = cogl_texture_get_width (src_texture); - height_in = cogl_texture_get_height (src_texture); - rowstride_in = (width_in + 3) & ~3; - - pixels_in = g_malloc0 (rowstride_in * height_in); - - cogl_texture_get_data (src_texture, COGL_PIXEL_FORMAT_A_8, - rowstride_in, pixels_in); - - if ((guint) shadow_spec->blur == 0) + if ((guint) blur == 0) { - width_out = width_in; - height_out = height_in; - rowstride_out = rowstride_in; - pixels_out = g_memdup (pixels_in, rowstride_out * height_out); + *width_out = width_in; + *height_out = height_in; + *rowstride_out = rowstride_in; + pixels_out = g_memdup (pixels_in, *rowstride_out * *height_out); } else { @@ -471,18 +458,18 @@ _st_create_shadow_material (StShadow *shadow_spec, n_values = (gint) 5 * sigma; half = n_values / 2; - width_out = width_in + 2 * half; - height_out = height_in + 2 * half; - rowstride_out = (width_out + 3) & ~3; + *width_out = width_in + 2 * half; + *height_out = height_in + 2 * half; + *rowstride_out = (*width_out + 3) & ~3; - pixels_out = g_malloc0 (rowstride_out * height_out); - line = g_malloc0 (rowstride_out); + pixels_out = g_malloc0 (*rowstride_out * *height_out); + line = g_malloc0 (*rowstride_out); kernel = calculate_gaussian_kernel (sigma, n_values); /* vertical blur */ for (x_in = 0; x_in < width_in; x_in++) - for (y_out = 0; y_out < height_out; y_out++) + for (y_out = 0; y_out < *height_out; y_out++) { guchar *pixel_in, *pixel_out; gint i0, i1; @@ -496,7 +483,7 @@ _st_create_shadow_material (StShadow *shadow_spec, i1 = MIN (height_in + half - y_in, n_values); pixel_in = pixels_in + (y_in + i0 - half) * rowstride_in + x_in; - pixel_out = pixels_out + y_out * rowstride_out + (x_in + half); + pixel_out = pixels_out + y_out * *rowstride_out + (x_in + half); for (i = i0; i < i1; i++) { @@ -506,11 +493,11 @@ _st_create_shadow_material (StShadow *shadow_spec, } /* horizontal blur */ - for (y_out = 0; y_out < height_out; y_out++) + for (y_out = 0; y_out < *height_out; y_out++) { - memcpy (line, pixels_out + y_out * rowstride_out, rowstride_out); + memcpy (line, pixels_out + y_out * *rowstride_out, *rowstride_out); - for (x_out = 0; x_out < width_out; x_out++) + for (x_out = 0; x_out < *width_out; x_out++) { gint i0, i1; guchar *pixel_out, *pixel_in; @@ -519,10 +506,10 @@ _st_create_shadow_material (StShadow *shadow_spec, * full i range [0, n_values) so that x is in [0, width_out). */ i0 = MAX (half - x_out, 0); - i1 = MIN (width_out + half - x_out, n_values); + i1 = MIN (*width_out + half - x_out, n_values); pixel_in = line + x_out + i0 - half; - pixel_out = pixels_out + rowstride_out * y_out + x_out; + pixel_out = pixels_out + *rowstride_out * y_out + x_out; *pixel_out = 0; for (i = i0; i < i1; i++) @@ -536,6 +523,39 @@ _st_create_shadow_material (StShadow *shadow_spec, g_free (line); } + return pixels_out; +} + +CoglHandle +_st_create_shadow_material (StShadow *shadow_spec, + CoglHandle src_texture) +{ + static CoglHandle shadow_material_template = COGL_INVALID_HANDLE; + + CoglHandle material; + CoglHandle texture; + guchar *pixels_in, *pixels_out; + gint width_in, height_in, rowstride_in; + gint width_out, height_out, rowstride_out; + + g_return_val_if_fail (shadow_spec != NULL, COGL_INVALID_HANDLE); + g_return_val_if_fail (src_texture != COGL_INVALID_HANDLE, + COGL_INVALID_HANDLE); + + width_in = cogl_texture_get_width (src_texture); + height_in = cogl_texture_get_height (src_texture); + rowstride_in = (width_in + 3) & ~3; + + pixels_in = g_malloc0 (rowstride_in * height_in); + + cogl_texture_get_data (src_texture, COGL_PIXEL_FORMAT_A_8, + rowstride_in, pixels_in); + + pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in, + shadow_spec->blur, + &width_out, &height_out, &rowstride_out); + g_free (pixels_in); + texture = cogl_texture_new_from_data (width_out, height_out, COGL_TEXTURE_NONE, @@ -544,7 +564,6 @@ _st_create_shadow_material (StShadow *shadow_spec, rowstride_out, pixels_out); - g_free (pixels_in); g_free (pixels_out); if (G_UNLIKELY (shadow_material_template == COGL_INVALID_HANDLE)) From 03757c1fcb9b9f27d44cc4cc8665aa59fcc3d409 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 12 Jan 2011 19:45:28 -0500 Subject: [PATCH 24/30] st-private: add cairo code for drawing shadow This does the same thing as the cogl equivalent code, but for handling fallbacks. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-private.c | 97 +++++++++++++++++++++++++++++++++++++++++++++ src/st/st-private.h | 4 ++ 2 files changed, 101 insertions(+) diff --git a/src/st/st-private.c b/src/st/st-private.c index 757f1e425..54d9a4b5f 100644 --- a/src/st/st-private.c +++ b/src/st/st-private.c @@ -636,6 +636,103 @@ _st_create_shadow_material_from_actor (StShadow *shadow_spec, return shadow_material; } +cairo_pattern_t * +_st_create_shadow_cairo_pattern (StShadow *shadow_spec, + cairo_pattern_t *src_pattern) +{ + cairo_t *cr; + cairo_surface_t *src_surface; + cairo_surface_t *surface_in; + cairo_surface_t *surface_out; + cairo_pattern_t *dst_pattern; + guchar *pixels_in, *pixels_out; + gint width_in, height_in, rowstride_in; + gint width_out, height_out, rowstride_out; + cairo_matrix_t shadow_matrix; + + g_return_val_if_fail (shadow_spec != NULL, NULL); + g_return_val_if_fail (src_pattern != NULL, NULL); + + cairo_pattern_get_surface (src_pattern, &src_surface); + + width_in = cairo_image_surface_get_width (src_surface); + height_in = cairo_image_surface_get_height (src_surface); + + /* We want the output to be a color agnostic alpha mask, + * so we need to strip the color channels from the input + */ + if (cairo_image_surface_get_format (src_surface) != CAIRO_FORMAT_A8) + { + surface_in = cairo_image_surface_create (CAIRO_FORMAT_A8, + width_in, height_in); + + cr = cairo_create (surface_in); + cairo_set_source_surface (cr, src_surface, 0, 0); + cairo_paint (cr); + cairo_destroy (cr); + } + else + { + surface_in = cairo_surface_reference (src_surface); + } + + pixels_in = cairo_image_surface_get_data (surface_in); + rowstride_in = cairo_image_surface_get_stride (surface_in); + + pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in, + shadow_spec->blur, + &width_out, &height_out, &rowstride_out); + cairo_surface_destroy (surface_in); + + surface_out = cairo_image_surface_create_for_data (pixels_out, + CAIRO_FORMAT_A8, + width_out, + height_out, + rowstride_out); + + dst_pattern = cairo_pattern_create_for_surface (surface_out); + cairo_surface_destroy (surface_out); + + cairo_pattern_get_matrix (src_pattern, &shadow_matrix); + + /* Read all the code from the cairo_pattern_set_matrix call + * at the end of this function to here from bottom to top, + * because each new affine transformation is applied in + * front of all the previous ones */ + + /* 6. Invert the matrix back */ + cairo_matrix_invert (&shadow_matrix); + + /* 5. Adjust based on specified offsets */ + cairo_matrix_translate (&shadow_matrix, + shadow_spec->xoffset, + shadow_spec->yoffset); + + /* 4. Recenter the newly scaled image */ + cairo_matrix_translate (&shadow_matrix, + - shadow_spec->spread, + - shadow_spec->spread); + + /* 3. Scale up the blurred image to fill the spread */ + cairo_matrix_scale (&shadow_matrix, + (width_in + 2.0 * shadow_spec->spread) / width_in, + (height_in + 2.0 * shadow_spec->spread) / height_in); + + /* 2. Shift the blurred image left, so that it aligns centered + * under the unblurred one */ + cairo_matrix_translate (&shadow_matrix, + - (width_out - width_in) / 2.0, + - (height_out - height_in) / 2.0); + + /* 1. Invert the matrix so we can work with it in pattern space + */ + cairo_matrix_invert (&shadow_matrix); + + cairo_pattern_set_matrix (dst_pattern, &shadow_matrix); + + return dst_pattern; +} + void _st_paint_shadow_with_opacity (StShadow *shadow_spec, CoglHandle shadow_material, diff --git a/src/st/st-private.h b/src/st/st-private.h index f958842d3..495475742 100644 --- a/src/st/st-private.h +++ b/src/st/st-private.h @@ -24,6 +24,7 @@ #define __ST_PRIVATE_H__ #include +#include #include "st-widget.h" #include "st-bin.h" #include "st-shadow.h" @@ -79,6 +80,9 @@ CoglHandle _st_create_shadow_material (StShadow *shadow_spec, CoglHandle src_texture); CoglHandle _st_create_shadow_material_from_actor (StShadow *shadow_spec, ClutterActor *actor); +cairo_pattern_t *_st_create_shadow_cairo_pattern (StShadow *shadow_spec, + cairo_pattern_t *src_pattern); + void _st_paint_shadow_with_opacity (StShadow *shadow_spec, CoglHandle shadow_material, ClutterActorBox *box, From 779d5462f272f809344d46baf2b152bf7ed7af47 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Fri, 10 Dec 2010 17:15:39 -0500 Subject: [PATCH 25/30] StThemeDrawing: clip background to border Previously, trying to use a background image and border on the same node resulted in the background drawing over the border. This commit adds support for background images to st_theme_node_render_background_with_border and changes the code to call that function when appropriate. https://bugzilla.gnome.org/show_bug.cgi?id=636976 --- src/st/st-theme-node-drawing.c | 460 ++++++++++++++++++++++++++++----- 1 file changed, 402 insertions(+), 58 deletions(-) diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index 59b3f84c0..52c737ef4 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -485,9 +485,233 @@ create_cairo_pattern_of_background_gradient (StThemeNode *node) return pattern; } +static cairo_pattern_t * +create_cairo_pattern_of_background_image (StThemeNode *node, + gboolean *needs_background_fill) +{ + cairo_surface_t *surface; + cairo_pattern_t *pattern; + 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; + + file = st_theme_node_get_background_image (node); + + texture_cache = st_texture_cache_get_default (); + + surface = st_texture_cache_load_file_to_cairo_surface (texture_cache, file); + + if (surface == NULL) + return NULL; + + g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE); + + 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; + + *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; + + /* center vertically */ + + scale_factor = width_ratio; + scaled_height = file_height / scale_factor; + + x_offset = 0.; + y_offset = - (node->alloc_height / 2. - scaled_height / 2.); + } + else + { + double scaled_width; + + /* 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_translate (&matrix, x_offset, y_offset); + cairo_matrix_scale (&matrix, scale_factor, scale_factor); + + 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); + } + + return pattern; +} + +static void +paint_background_image_shadow_to_cairo_context (StThemeNode *node, + StShadow *shadow_spec, + cairo_pattern_t *pattern, + cairo_t *cr, + cairo_path_t *interior_path, + cairo_path_t *outline_path, + int x, + int y, + int width, + int height) +{ + cairo_pattern_t *shadow_pattern; + + g_assert (shadow_spec != NULL); + g_assert (pattern != NULL); + + if (outline_path != NULL) + { + cairo_surface_t *clipped_surface; + cairo_pattern_t *clipped_pattern; + cairo_t *temp_cr; + + /* Prerender the pattern to a temporary surface, + * so it's properly clipped before we create a shadow from it + */ + clipped_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + temp_cr = cairo_create (clipped_surface); + + cairo_set_operator (temp_cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (temp_cr); + cairo_set_operator (temp_cr, CAIRO_OPERATOR_SOURCE); + + if (interior_path != NULL) + { + cairo_append_path (temp_cr, interior_path); + cairo_clip (temp_cr); + } + + cairo_append_path (temp_cr, outline_path); + cairo_translate (temp_cr, x, y); + cairo_set_source (temp_cr, pattern); + cairo_clip (temp_cr); + cairo_paint (temp_cr); + cairo_destroy (temp_cr); + + clipped_pattern = cairo_pattern_create_for_surface (clipped_surface); + cairo_surface_destroy (clipped_surface); + + shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, + clipped_pattern); + cairo_pattern_destroy (clipped_pattern); + } + else + { + shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, + pattern); + } + + /* Stamp the shadow pattern out in the appropriate color + * in a new layer + */ + cairo_push_group (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba (cr, + shadow_spec->color.red / 255.0, + shadow_spec->color.green / 255.0, + shadow_spec->color.blue / 255.0, + shadow_spec->color.alpha / 255.0); + cairo_paint (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_DEST_IN); + + cairo_set_source (cr, shadow_pattern); + cairo_paint (cr); + cairo_pattern_destroy (shadow_pattern); + + cairo_pop_group_to_source (cr); + + /* mask and merge the shadow + */ + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_save (cr); + if (interior_path != NULL) + { + /* If there are borders, clip the shadow to the interior + * of the borders + */ + cairo_append_path (cr, interior_path); + cairo_clip (cr); + } + else if (outline_path != NULL) + { + /* If there is a visible outline, clip the shadow to + * that outline + */ + cairo_append_path (cr, outline_path); + cairo_clip (cr); + } + + cairo_paint (cr); + cairo_restore (cr); +} + /* In order for borders to be smoothly blended with non-solid backgrounds, * we need to use cairo. This function is a slow fallback path for those - * cases. It currently only supports gradients, however. + * cases (gradients, background images, etc). */ static CoglHandle st_theme_node_render_background_with_border (StThemeNode *node) @@ -497,21 +721,54 @@ st_theme_node_render_background_with_border (StThemeNode *node) int radius[4], i; cairo_t *cr; cairo_surface_t *surface; + StShadow *shadow_spec; cairo_pattern_t *pattern = NULL; - gboolean draw_solid_background; + cairo_path_t *outline_path = NULL; + gboolean draw_solid_background = TRUE; + gboolean draw_background_image_shadow = FALSE; + gboolean has_visible_outline; ClutterColor border_color; int border_width[4]; guint rowstride; guchar *data; + ClutterActorBox actor_box; + ClutterActorBox paint_box; + cairo_path_t *interior_path = NULL; border_image = st_theme_node_get_border_image (node); - rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, node->alloc_width); - data = g_new0 (guchar, node->alloc_height * rowstride); + shadow_spec = st_theme_node_get_background_image_shadow (node); + + actor_box.x1 = 0; + actor_box.x2 = node->alloc_width; + actor_box.y1 = 0; + actor_box.y2 = node->alloc_height; + + /* If there's a background image shadow, we + * may need to create an image bigger than the nodes + * allocation + */ + st_theme_node_get_paint_box (node, &actor_box, &paint_box); + + /* translate the boxes so the paint box is at 0,0 + */ + actor_box.x1 += - paint_box.x1; + actor_box.x2 += - paint_box.x1; + actor_box.y1 += - paint_box.y1; + actor_box.y2 += - paint_box.y1; + + paint_box.x2 += - paint_box.x1; + paint_box.x1 += - paint_box.x1; + paint_box.y2 += - paint_box.y1; + paint_box.y1 += - paint_box.y1; + + rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, + paint_box.x2 - paint_box.x1); + data = g_new0 (guchar, (paint_box.y2 - paint_box.y1) * rowstride); surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, - node->alloc_width, - node->alloc_height, + paint_box.x2 - paint_box.x1, + paint_box.y2 - paint_box.y1, rowstride); cr = cairo_create (surface); @@ -525,6 +782,9 @@ st_theme_node_render_background_with_border (StThemeNode *node) radius[i] = st_theme_node_get_border_radius (node, i); } + /* Note we don't support translucent background images on top + * of gradients. It's strictly either/or. + */ if (node->background_gradient_type != ST_GRADIENT_NONE) { pattern = create_cairo_pattern_of_background_gradient (node); @@ -532,38 +792,54 @@ st_theme_node_render_background_with_border (StThemeNode *node) } else { - g_warning ("st_theme_node_render_background_with_border called with non-gradient background (which isn't yet supported). Falling back to solid background color."); - draw_solid_background = TRUE; + const char *background_image; + + background_image = st_theme_node_get_background_image (node); + + if (background_image != NULL) + { + pattern = create_cairo_pattern_of_background_image (node, + &draw_solid_background); + if (shadow_spec && pattern != NULL) + draw_background_image_shadow = TRUE; + } } + if (pattern == NULL) + draw_solid_background = TRUE; + + has_visible_outline = st_theme_node_has_visible_outline (node); + /* Create a path for the background's outline first */ if (radius[ST_CORNER_TOPLEFT] > 0) cairo_arc (cr, - radius[ST_CORNER_TOPLEFT], - radius[ST_CORNER_TOPLEFT], + actor_box.x1 + radius[ST_CORNER_TOPLEFT], + actor_box.y1 + radius[ST_CORNER_TOPLEFT], radius[ST_CORNER_TOPLEFT], M_PI, 3 * M_PI / 2); else - cairo_move_to (cr, 0, 0); - cairo_line_to (cr, node->alloc_width - radius[ST_CORNER_TOPRIGHT], 0); + cairo_move_to (cr, actor_box.x1, actor_box.y1); + cairo_line_to (cr, actor_box.x2 - radius[ST_CORNER_TOPRIGHT], actor_box.x1); if (radius[ST_CORNER_TOPRIGHT] > 0) cairo_arc (cr, - node->alloc_width - radius[ST_CORNER_TOPRIGHT], - radius[ST_CORNER_TOPRIGHT], + actor_box.x2 - radius[ST_CORNER_TOPRIGHT], + actor_box.x1 + radius[ST_CORNER_TOPRIGHT], radius[ST_CORNER_TOPRIGHT], 3 * M_PI / 2, 2 * M_PI); - cairo_line_to (cr, node->alloc_width, node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT]); + cairo_line_to (cr, actor_box.x2, actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT]); if (radius[ST_CORNER_BOTTOMRIGHT] > 0) cairo_arc (cr, - node->alloc_width - radius[ST_CORNER_BOTTOMRIGHT], - node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT], + actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT], + actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT], radius[ST_CORNER_BOTTOMRIGHT], 0, M_PI / 2); - cairo_line_to (cr, radius[ST_CORNER_BOTTOMLEFT], node->alloc_height); + cairo_line_to (cr, actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], actor_box.y2); if (radius[ST_CORNER_BOTTOMLEFT] > 0) cairo_arc (cr, - radius[ST_CORNER_BOTTOMLEFT], - node->alloc_height - radius[ST_CORNER_BOTTOMLEFT], + actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], + actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT], radius[ST_CORNER_BOTTOMLEFT], M_PI / 2, M_PI); cairo_close_path (cr); + outline_path = cairo_copy_path (cr); + /* If we have a solid border, we fill the outline shape with the border * color and create the inline shape for the background; * otherwise the outline shape is filled with the background @@ -585,68 +861,80 @@ st_theme_node_render_background_with_border (StThemeNode *node) if (radius[ST_CORNER_TOPLEFT] > MAX(border_width[ST_SIDE_TOP], border_width[ST_SIDE_LEFT])) elliptical_arc (cr, - radius[ST_CORNER_TOPLEFT], - radius[ST_CORNER_TOPLEFT], + actor_box.x1 + radius[ST_CORNER_TOPLEFT], + actor_box.y1 + radius[ST_CORNER_TOPLEFT], radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_LEFT], radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_TOP], M_PI, 3 * M_PI / 2); else cairo_move_to (cr, - border_width[ST_SIDE_LEFT], - border_width[ST_SIDE_TOP]); + actor_box.x1 + border_width[ST_SIDE_LEFT], + actor_box.y1 + border_width[ST_SIDE_TOP]); cairo_line_to (cr, - node->alloc_width - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]), - border_width[ST_SIDE_TOP]); + actor_box.x2 - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]), + actor_box.y1 + border_width[ST_SIDE_TOP]); if (radius[ST_CORNER_TOPRIGHT] > MAX(border_width[ST_SIDE_TOP], border_width[ST_SIDE_RIGHT])) elliptical_arc (cr, - node->alloc_width - radius[ST_CORNER_TOPRIGHT], - radius[ST_CORNER_TOPRIGHT], + actor_box.x2 - radius[ST_CORNER_TOPRIGHT], + actor_box.y1 + radius[ST_CORNER_TOPRIGHT], radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_RIGHT], radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_TOP], 3 * M_PI / 2, 2 * M_PI); else cairo_line_to (cr, - node->alloc_width - border_width[ST_SIDE_RIGHT], - border_width[ST_SIDE_TOP]); + actor_box.x2 - border_width[ST_SIDE_RIGHT], + actor_box.y1 + border_width[ST_SIDE_TOP]); cairo_line_to (cr, - node->alloc_width - border_width[ST_SIDE_RIGHT], - node->alloc_height - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM])); + actor_box.x2 - border_width[ST_SIDE_RIGHT], + actor_box.y2 - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM])); if (radius[ST_CORNER_BOTTOMRIGHT] > MAX(border_width[ST_SIDE_BOTTOM], border_width[ST_SIDE_RIGHT])) elliptical_arc (cr, - node->alloc_width - radius[ST_CORNER_BOTTOMRIGHT], - node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT], + actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT], + actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT], radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_RIGHT], radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_BOTTOM], 0, M_PI / 2); else cairo_line_to (cr, - node->alloc_width - border_width[ST_SIDE_RIGHT], - node->alloc_height - border_width[ST_SIDE_BOTTOM]); + actor_box.x2 - border_width[ST_SIDE_RIGHT], + actor_box.y2 - border_width[ST_SIDE_BOTTOM]); cairo_line_to (cr, MAX(radius[ST_CORNER_BOTTOMLEFT], border_width[ST_SIDE_LEFT]), - node->alloc_height - border_width[ST_SIDE_BOTTOM]); + actor_box.y2 - border_width[ST_SIDE_BOTTOM]); if (radius[ST_CORNER_BOTTOMLEFT] > MAX(border_width[ST_SIDE_BOTTOM], border_width[ST_SIDE_LEFT])) elliptical_arc (cr, - radius[ST_CORNER_BOTTOMLEFT], - node->alloc_height - radius[ST_CORNER_BOTTOMLEFT], + actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], + actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT], radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_LEFT], radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_BOTTOM], M_PI / 2, M_PI); else cairo_line_to (cr, - border_width[ST_SIDE_LEFT], - node->alloc_height - border_width[ST_SIDE_BOTTOM]); + actor_box.x1 + border_width[ST_SIDE_LEFT], + actor_box.y2 - border_width[ST_SIDE_BOTTOM]); cairo_close_path (cr); + + interior_path = cairo_copy_path (cr); + + /* clip drawing to the region inside of the borders + */ + cairo_clip (cr); + + /* But fill the pattern as if it started at the edge of outline, + * behind the borders. This is similar to + * background-clip: border-box; semantics. + */ + cairo_append_path (cr, outline_path); } if (draw_solid_background) @@ -659,6 +947,23 @@ st_theme_node_render_background_with_border (StThemeNode *node) cairo_fill_preserve (cr); } + if (draw_background_image_shadow) + { + paint_background_image_shadow_to_cairo_context (node, + shadow_spec, + pattern, + cr, + interior_path, + has_visible_outline? outline_path : NULL, + actor_box.x1, + actor_box.y1, + paint_box.x2 - paint_box.x1, + paint_box.y2 - paint_box.y1); + cairo_append_path (cr, outline_path); + } + + cairo_translate (cr, actor_box.x1, actor_box.y1); + if (pattern != NULL) { cairo_set_source (cr, pattern); @@ -666,7 +971,14 @@ st_theme_node_render_background_with_border (StThemeNode *node) cairo_pattern_destroy (pattern); } - texture = cogl_texture_new_from_data (node->alloc_width, node->alloc_height, + if (outline_path != NULL) + cairo_path_destroy (outline_path); + + if (interior_path != NULL) + cairo_path_destroy (interior_path); + + texture = cogl_texture_new_from_data (paint_box.x2 - paint_box.x1, + paint_box.y2 - paint_box.y1, COGL_TEXTURE_NONE, #if G_BYTE_ORDER == G_LITTLE_ENDIAN COGL_PIXEL_FORMAT_BGRA_8888_PRE, @@ -745,6 +1057,8 @@ st_theme_node_render_resources (StThemeNode *node, { StTextureCache *texture_cache; StBorderImage *border_image; + gboolean has_border; + gboolean has_border_radius; StShadow *box_shadow_spec; StShadow *background_image_shadow_spec; const char *background_image; @@ -765,9 +1079,26 @@ st_theme_node_render_resources (StThemeNode *node, box_shadow_spec = st_theme_node_get_box_shadow (node); - /* Load referenced images from disk and draw anything we need with cairo now */ + if (node->border_width[ST_SIDE_TOP] > 0 || + node->border_width[ST_SIDE_LEFT] > 0 || + node->border_width[ST_SIDE_RIGHT] > 0 || + node->border_width[ST_SIDE_BOTTOM] > 0) + has_border = TRUE; + else + has_border = FALSE; + if (node->border_radius[ST_CORNER_TOPLEFT] > 0 || + node->border_radius[ST_CORNER_TOPRIGHT] > 0 || + node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 || + node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0) + has_border_radius = TRUE; + else + has_border_radius = FALSE; + + /* Load referenced images from disk and draw anything we need with cairo now */ + background_image = st_theme_node_get_background_image (node); border_image = st_theme_node_get_border_image (node); + if (border_image) { const char *filename; @@ -777,14 +1108,23 @@ st_theme_node_render_resources (StThemeNode *node, node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename); } - if (node->background_gradient_type != ST_GRADIENT_NONE) - node->prerendered_texture = st_theme_node_render_background_with_border (node); - if (node->border_slices_texture) node->border_slices_material = _st_create_texture_material (node->border_slices_texture); else node->border_slices_material = COGL_INVALID_HANDLE; + /* Use cairo to prerender the node if there is a gradient, or + * background image with borders and/or rounded corners, + * since we can't do those things easily with cogl. + * + * FIXME: if we could figure out ahead of time that a + * background image won't overlap with the node borders, + * then we could use cogl for that case. + */ + if ((node->background_gradient_type != ST_GRADIENT_NONE) + || (background_image && (has_border || has_border_radius))) + node->prerendered_texture = st_theme_node_render_background_with_border (node); + if (node->prerendered_texture) node->prerendered_material = _st_create_texture_material (node->prerendered_texture); else @@ -798,11 +1138,7 @@ st_theme_node_render_resources (StThemeNode *node, else if (node->prerendered_texture != COGL_INVALID_HANDLE) node->box_shadow_material = _st_create_shadow_material (box_shadow_spec, node->prerendered_texture); - else if (node->background_color.alpha > 0 || - node->border_width[ST_SIDE_TOP] > 0 || - node->border_width[ST_SIDE_LEFT] > 0 || - node->border_width[ST_SIDE_RIGHT] > 0 || - node->border_width[ST_SIDE_BOTTOM] > 0) + else if (node->background_color.alpha > 0 || has_border) { CoglHandle buffer, offscreen; @@ -829,10 +1165,8 @@ st_theme_node_render_resources (StThemeNode *node, } } - background_image = st_theme_node_get_background_image (node); background_image_shadow_spec = st_theme_node_get_background_image_shadow (node); - - if (background_image != NULL) + if (background_image != NULL && !has_border && !has_border_radius) { CoglHandle texture; @@ -1339,7 +1673,9 @@ st_theme_node_paint (StThemeNode *node, * not supported; the background color will be drawn with square * corners. * - The background image is drawn above the border color, not below it. - * - We don't clip the background image to the (rounded) border area. + * - We clip the background image to the inside edges of the border + * instead of the outside edges of the border (but position the image + * such that it's aligned to the outside edges) */ if (node->box_shadow_material) @@ -1352,7 +1688,15 @@ st_theme_node_paint (StThemeNode *node, node->border_slices_material != COGL_INVALID_HANDLE) { if (node->prerendered_material != COGL_INVALID_HANDLE) - paint_material_with_opacity (node->prerendered_material, &allocation, paint_opacity); + { + ClutterActorBox paint_box; + + st_theme_node_get_paint_box (node, &allocation, &paint_box); + + paint_material_with_opacity (node->prerendered_material, + &paint_box, + paint_opacity); + } if (node->border_slices_material != COGL_INVALID_HANDLE) st_theme_node_paint_sliced_border_image (node, &allocation, paint_opacity); @@ -1367,8 +1711,6 @@ st_theme_node_paint (StThemeNode *node, if (node->background_texture != COGL_INVALID_HANDLE) { ClutterActorBox background_box; - - get_background_position (node, &allocation, &background_box); gboolean has_visible_outline; /* If the background doesn't have a border or opaque background, @@ -1377,6 +1719,8 @@ st_theme_node_paint (StThemeNode *node, */ has_visible_outline = st_theme_node_has_visible_outline (node); + get_background_position (node, &allocation, &background_box); + if (has_visible_outline) cogl_clip_push_rectangle (allocation.x1, allocation.y1, allocation.x2, allocation.y2); From 75d1230dd16cf576bdfd00093b7ee3e9f95765cd Mon Sep 17 00:00:00 2001 From: Adel Gadllah Date: Thu, 6 Jan 2011 20:21:27 +0100 Subject: [PATCH 26/30] AppSwitcher: Delay activating appIcons when the thumbnail list is open When aiming for the thumbnails with the mouse one might cross an icon by accident which causes the thumbnail list to be closed, which is frustrating. Fix this by delaying the icon activation when the thumbnail list is open. https://bugzilla.gnome.org/show_bug.cgi?id=636650 --- js/ui/altTab.js | 62 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/js/ui/altTab.js b/js/ui/altTab.js index aa0e7e246..a8d3e79ea 100644 --- a/js/ui/altTab.js +++ b/js/ui/altTab.js @@ -15,6 +15,8 @@ const POPUP_APPICON_SIZE = 96; const POPUP_SCROLL_TIME = 0.10; // seconds const POPUP_FADE_TIME = 0.1; // seconds +const APP_ICON_HOVER_TIMEOUT = 750; // milliseconds + const DISABLE_HOVER_TIMEOUT = 500; // milliseconds const THUMBNAIL_DEFAULT_SIZE = 256; @@ -50,6 +52,8 @@ AltTabPopup.prototype = { this._thumbnailTimeoutId = 0; this._motionTimeoutId = 0; + this.thumbnailsVisible = false; + // Initially disable hover so we ignore the enter-event if // the switcher appears underneath the current pointer location this._disableHover(); @@ -133,7 +137,7 @@ AltTabPopup.prototype = { this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside)); this.actor.connect('scroll-event', Lang.bind(this, this._onScroll)); - this._appSwitcher = new AppSwitcher(apps); + this._appSwitcher = new AppSwitcher(apps, this); this.actor.add_actor(this._appSwitcher.actor); this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated)); this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered)); @@ -457,11 +461,15 @@ AltTabPopup.prototype = { }, _destroyThumbnails : function() { - Tweener.addTween(this._thumbnails.actor, + let thumbnailsActor = this._thumbnails.actor; + Tweener.addTween(thumbnailsActor, { opacity: 0, time: THUMBNAIL_FADE_TIME, transition: 'easeOutQuad', - onComplete: function() { this.destroy(); } + onComplete: Lang.bind(this, function() { + thumbnailsActor.destroy(); + this.thumbnailsVisible = false; + }) }); this._thumbnails = null; }, @@ -477,7 +485,8 @@ AltTabPopup.prototype = { Tweener.addTween(this._thumbnails.actor, { opacity: 255, time: THUMBNAIL_FADE_TIME, - transition: 'easeOutQuad' + transition: 'easeOutQuad', + onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; }) }); } }; @@ -591,16 +600,20 @@ SwitcherList.prototype = { this._list.add_actor(bbox); let n = this._items.length; - bbox.connect('clicked', Lang.bind(this, function () { - this._itemActivated(n); - })); - bbox.connect('enter-event', Lang.bind(this, function () { - this._itemEntered(n); - })); + bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); })); + bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); })); this._items.push(bbox); }, + _onItemClicked: function (index) { + this._itemActivated(index); + }, + + _onItemEnter: function (index) { + this._itemEntered(index); + }, + addSeparator: function () { let box = new St.Bin({ style_class: 'separator' }); this._separator = box; @@ -819,14 +832,14 @@ AppIcon.prototype = { } }; -function AppSwitcher(apps) { - this._init(apps); +function AppSwitcher(apps, altTabPopup) { + this._init(apps, altTabPopup); } AppSwitcher.prototype = { __proto__ : SwitcherList.prototype, - _init : function(apps) { + _init : function(apps, altTabPopup) { SwitcherList.prototype._init.call(this, true); // Construct the AppIcons, sort by time, add to the popup @@ -858,6 +871,8 @@ AppSwitcher.prototype = { this._curApp = -1; this._iconSize = 0; + this._altTabPopup = altTabPopup; + this._mouseTimeOutId = 0; }, _getPreferredHeight: function (actor, forWidth, alloc) { @@ -922,6 +937,27 @@ AppSwitcher.prototype = { } }, + // We override SwitcherList's _onItemEnter method to delay + // activation when the thumbnail list is open + _onItemEnter: function (index) { + if (this._mouseTimeOutId != 0) + Mainloop.source_remove(this._mouseTimeOutId); + if (this._altTabPopup.thumbnailsVisible) { + this._mouseTimeOutId = Mainloop.timeout_add(APP_ICON_HOVER_TIMEOUT, + Lang.bind(this, function () { + this._enterItem(index); + })); + } else + this._itemEntered(index); + }, + + _enterItem: function(index) { + let [x, y, mask] = global.get_pointer(); + let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y); + if (this._items[index].contains(pickedActor)) + this._itemEntered(index); + }, + // We override SwitcherList's highlight() method to also deal with // the AppSwitcher->ThumbnailList arrows. Apps with only 1 window // will hide their arrows by default, but show them when their From 13edecde6ce0af6a67fde98a9d9a06aa9341f018 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Mon, 24 Jan 2011 12:41:23 -0500 Subject: [PATCH 27/30] Remove set-but-unused variables, to appease gcc 4.6 https://bugzilla.gnome.org/show_bug.cgi?id=640447 --- src/gdmuser/gdm-user-manager.c | 13 ++----------- src/gdmuser/gdm-user.c | 4 ---- src/gvc/gvc-mixer-event-role.c | 6 ------ src/gvc/gvc-mixer-sink-input.c | 9 --------- src/gvc/gvc-mixer-sink.c | 7 ------- src/gvc/gvc-mixer-source-output.c | 3 --- src/gvc/gvc-mixer-source.c | 7 ------- src/shell-app-system.c | 2 -- src/shell-global.c | 3 +-- src/shell-window-tracker.c | 3 --- src/st/st-overflow-box.c | 3 +-- src/st/st-scroll-bar.c | 2 -- src/st/st-table.c | 9 +++------ src/st/st-widget.c | 3 --- 14 files changed, 7 insertions(+), 67 deletions(-) diff --git a/src/gdmuser/gdm-user-manager.c b/src/gdmuser/gdm-user-manager.c index a39bea1ca..eba9da11e 100644 --- a/src/gdmuser/gdm-user-manager.c +++ b/src/gdmuser/gdm-user-manager.c @@ -1020,13 +1020,10 @@ on_get_unix_user_finished (DBusGProxy *proxy, DBusGProxyCall *call, GdmUserManagerNewSession *new_session) { - GdmUserManager *manager; GError *error; guint uid; gboolean res; - manager = new_session->manager; - g_assert (new_session->get_unix_user_call == call); error = NULL; @@ -1550,11 +1547,9 @@ static void get_accounts_proxy (GdmUserManager *manager) { DBusGProxy *proxy; - GError *error; g_assert (manager->priv->accounts_proxy == NULL); - error = NULL; proxy = dbus_g_proxy_new_for_name (manager->priv->connection, ACCOUNTS_NAME, ACCOUNTS_PATH, @@ -2554,8 +2549,6 @@ reload_shells (GdmUserManager *manager) static void load_users_manually (GdmUserManager *manager) { - gboolean res; - manager->priv->shells = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, @@ -2564,7 +2557,7 @@ load_users_manually (GdmUserManager *manager) load_sessions (manager); - res = load_ck_history (manager); + load_ck_history (manager); schedule_reload_passwd (manager); } @@ -2605,10 +2598,8 @@ load_seat_incrementally (GdmUserManager *manager) } if (manager->priv->seat.state == GDM_USER_MANAGER_SEAT_STATE_LOADED) { - gboolean res; - load_sessions (manager); - res = load_ck_history (manager); + load_ck_history (manager); } maybe_set_is_loaded (manager); diff --git a/src/gdmuser/gdm-user.c b/src/gdmuser/gdm-user.c index 710510a44..735c2cb74 100644 --- a/src/gdmuser/gdm-user.c +++ b/src/gdmuser/gdm-user.c @@ -151,10 +151,6 @@ gdm_user_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - GdmUser *user; - - user = GDM_USER (object); - switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); diff --git a/src/gvc/gvc-mixer-event-role.c b/src/gvc/gvc-mixer-event-role.c index 7eb3d00d7..071722e48 100644 --- a/src/gvc/gvc-mixer-event-role.c +++ b/src/gvc/gvc-mixer-event-role.c @@ -59,13 +59,10 @@ update_settings (GvcMixerEventRole *role, gpointer *op) { pa_operation *o; - guint index; const GvcChannelMap *map; pa_context *context; pa_ext_stream_restore_info info; - index = gvc_mixer_stream_get_index (GVC_MIXER_STREAM (role)); - map = gvc_mixer_stream_get_channel_map (GVC_MIXER_STREAM(role)); info.volume = *gvc_channel_map_get_cvolume(map); @@ -165,12 +162,9 @@ gvc_mixer_event_role_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerEventRole *self; object = G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_EVENT_ROLE (object); - return object; } diff --git a/src/gvc/gvc-mixer-sink-input.c b/src/gvc/gvc-mixer-sink-input.c index 9429eca0a..5cd665c9c 100644 --- a/src/gvc/gvc-mixer-sink-input.c +++ b/src/gvc/gvc-mixer-sink-input.c @@ -55,12 +55,10 @@ gvc_mixer_sink_input_push_volume (GvcMixerStream *stream, gpointer *op) const GvcChannelMap *map; pa_context *context; const pa_cvolume *cv; - guint num_channels; index = gvc_mixer_stream_get_index (stream); map = gvc_mixer_stream_get_channel_map (stream); - num_channels = gvc_channel_map_get_num_channels (map); cv = gvc_channel_map_get_cvolume(map); @@ -115,12 +113,9 @@ gvc_mixer_sink_input_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerSinkInput *self; object = G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_SINK_INPUT (object); - return object; } @@ -149,13 +144,9 @@ gvc_mixer_sink_input_init (GvcMixerSinkInput *sink_input) static void gvc_mixer_sink_input_dispose (GObject *object) { - GvcMixerSinkInput *mixer_sink_input; - g_return_if_fail (object != NULL); g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object)); - mixer_sink_input = GVC_MIXER_SINK_INPUT (object); - G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->dispose (object); } diff --git a/src/gvc/gvc-mixer-sink.c b/src/gvc/gvc-mixer-sink.c index 30fceac4d..5b74a5ec6 100644 --- a/src/gvc/gvc-mixer-sink.c +++ b/src/gvc/gvc-mixer-sink.c @@ -145,12 +145,9 @@ gvc_mixer_sink_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerSink *self; object = G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_SINK (object); - return object; } @@ -180,13 +177,9 @@ gvc_mixer_sink_init (GvcMixerSink *sink) static void gvc_mixer_sink_dispose (GObject *object) { - GvcMixerSink *mixer_sink; - g_return_if_fail (object != NULL); g_return_if_fail (GVC_IS_MIXER_SINK (object)); - mixer_sink = GVC_MIXER_SINK (object); - G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->dispose (object); } diff --git a/src/gvc/gvc-mixer-source-output.c b/src/gvc/gvc-mixer-source-output.c index 636fc2ea7..8d76763bb 100644 --- a/src/gvc/gvc-mixer-source-output.c +++ b/src/gvc/gvc-mixer-source-output.c @@ -66,12 +66,9 @@ gvc_mixer_source_output_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerSourceOutput *self; object = G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_SOURCE_OUTPUT (object); - return object; } diff --git a/src/gvc/gvc-mixer-source.c b/src/gvc/gvc-mixer-source.c index 46d640380..6fed25e7b 100644 --- a/src/gvc/gvc-mixer-source.c +++ b/src/gvc/gvc-mixer-source.c @@ -145,12 +145,9 @@ gvc_mixer_source_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerSource *self; object = G_OBJECT_CLASS (gvc_mixer_source_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_SOURCE (object); - return object; } @@ -180,13 +177,9 @@ gvc_mixer_source_init (GvcMixerSource *source) static void gvc_mixer_source_dispose (GObject *object) { - GvcMixerSource *mixer_source; - g_return_if_fail (object != NULL); g_return_if_fail (GVC_IS_MIXER_SOURCE (object)); - mixer_source = GVC_MIXER_SOURCE (object); - G_OBJECT_CLASS (gvc_mixer_source_parent_class)->dispose (object); } diff --git a/src/shell-app-system.c b/src/shell-app-system.c index daf0f14cc..0cf0bbb8b 100644 --- a/src/shell-app-system.c +++ b/src/shell-app-system.c @@ -1317,7 +1317,6 @@ shell_app_info_launch_full (ShellAppInfo *info, gboolean ret; ShellGlobal *global; MetaScreen *screen; - MetaDisplay *display; if (startup_id) *startup_id = NULL; @@ -1353,7 +1352,6 @@ shell_app_info_launch_full (ShellAppInfo *info, global = shell_global_get (); screen = shell_global_get_screen (global); - display = meta_screen_get_display (screen); if (timestamp == 0) timestamp = clutter_get_current_event_time (); diff --git a/src/shell-global.c b/src/shell-global.c index 8e0463ddd..68ee6190c 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -885,7 +885,6 @@ shell_global_add_extension_importer (ShellGlobal *global, GError **error) { jsval target_object; - JSObject *importer; JSContext *context = gjs_context_get_native_context (global->js_context); char *search_path[2] = { 0, 0 }; @@ -920,7 +919,7 @@ shell_global_add_extension_importer (ShellGlobal *global, } search_path[0] = (char*)directory; - importer = gjs_define_importer (context, JSVAL_TO_OBJECT (target_object), target_property, (const char **)search_path, FALSE); + gjs_define_importer (context, JSVAL_TO_OBJECT (target_object), target_property, (const char **)search_path, FALSE); JS_EndRequest (context); return TRUE; out_error: diff --git a/src/shell-window-tracker.c b/src/shell-window-tracker.c index 1e61b7c6d..c2ff93281 100644 --- a/src/shell-window-tracker.c +++ b/src/shell-window-tracker.c @@ -825,12 +825,9 @@ on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellWindowTracker *tracker) { - MetaScreen *screen; MetaWindow *new_focus_win; ShellApp *new_focus_app; - screen = shell_global_get_screen (shell_global_get ()); - new_focus_win = meta_display_get_focus_window (display); new_focus_app = new_focus_win ? g_hash_table_lookup (tracker->window_to_app, new_focus_win) : NULL; diff --git a/src/st/st-overflow-box.c b/src/st/st-overflow-box.c index 9996b2a18..643a0e968 100644 --- a/src/st/st-overflow-box.c +++ b/src/st/st-overflow-box.c @@ -250,7 +250,7 @@ st_overflow_box_allocate (ClutterActor *actor, StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); ClutterActorBox content_box; gfloat position; - float avail_width, avail_height; + float avail_width; GList *l, *children; int i; gboolean done_non_fixed; @@ -265,7 +265,6 @@ st_overflow_box_allocate (ClutterActor *actor, st_theme_node_get_content_box (theme_node, box, &content_box); avail_width = content_box.x2 - content_box.x1; - avail_height = content_box.y2 - content_box.y1; position = content_box.y1; priv->n_visible = 0; diff --git a/src/st/st-scroll-bar.c b/src/st/st-scroll-bar.c index 61acdf06e..46c974d9c 100644 --- a/src/st/st-scroll-bar.c +++ b/src/st/st-scroll-bar.c @@ -643,13 +643,11 @@ st_scroll_bar_constructor (GType type, GObjectClass *gobject_class; GObject *obj; StScrollBar *bar; - StScrollBarPrivate *priv; gobject_class = G_OBJECT_CLASS (st_scroll_bar_parent_class); obj = gobject_class->constructor (type, n_properties, properties); bar = ST_SCROLL_BAR (obj); - priv = ST_SCROLL_BAR_GET_PRIVATE (bar); g_signal_connect (bar, "notify::reactive", G_CALLBACK (bar_reactive_notify_cb), NULL); diff --git a/src/st/st-table.c b/src/st/st-table.c index 26fb42ed0..b736a5426 100644 --- a/src/st/st-table.c +++ b/src/st/st-table.c @@ -315,12 +315,12 @@ st_table_calculate_col_widths (StTable *table, for (list = children; list; list = list->next) { - gint row, col; + gint col; gfloat w_min, w_pref; gboolean x_expand; StTableChild *meta; ClutterActor *child; - gint col_span, row_span; + gint col_span; child = CLUTTER_ACTOR (list->data); @@ -331,10 +331,8 @@ st_table_calculate_col_widths (StTable *table, /* get child properties */ col = meta->col; - row = meta->row; x_expand = meta->x_expand; col_span = meta->col_span; - row_span = meta->row_span; if (x_expand) is_expand_col[col] = TRUE; @@ -428,7 +426,7 @@ st_table_calculate_row_heights (StTable *table, { gint row, col, cell_width; gfloat h_min, h_pref; - gboolean x_expand, y_expand; + gboolean y_expand; StTableChild *meta; ClutterActor *child; gint col_span, row_span; @@ -443,7 +441,6 @@ st_table_calculate_row_heights (StTable *table, /* get child properties */ col = meta->col; row = meta->row; - x_expand = meta->x_expand; y_expand = meta->y_expand; col_span = meta->col_span; row_span = meta->row_span; diff --git a/src/st/st-widget.c b/src/st/st-widget.c index 71c1518f8..09b282413 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -357,13 +357,10 @@ st_widget_allocate (ClutterActor *actor, { StWidget *self = ST_WIDGET (actor); StWidgetPrivate *priv = self->priv; - StThemeNode *theme_node; ClutterActorClass *klass; ClutterGeometry area; ClutterVertex in_v, out_v; - theme_node = st_widget_get_theme_node (self); - klass = CLUTTER_ACTOR_CLASS (st_widget_parent_class); klass->allocate (actor, box, flags); From 6f070319a012199a411c24bb2bf612f23d87bdd8 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Mon, 24 Jan 2011 12:30:34 -0500 Subject: [PATCH 28/30] shell-doc-system: fix %-escaping code in shell_doc_system_open() It was escaping app_exec into app_exec_quoted, but then forgot about it and went back to using app_exec. https://bugzilla.gnome.org/show_bug.cgi?id=640447 --- src/shell-doc-system.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shell-doc-system.c b/src/shell-doc-system.c index dab64e9a0..3ee51e6ba 100644 --- a/src/shell-doc-system.c +++ b/src/shell-doc-system.c @@ -285,7 +285,8 @@ shell_doc_system_open (ShellDocSystem *system, app_exec_quoted = g_regex_replace (regex, app_exec, -1, 0, "%%", 0, NULL); g_regex_unref (regex); - app_info = g_app_info_create_from_commandline (app_exec, NULL, 0, NULL); + app_info = g_app_info_create_from_commandline (app_exec_quoted, NULL, 0, NULL); + g_free (app_exec_quoted); /* The point of passing an app launch context to launch() is mostly to get startup notification and From 4cb967f3acee4752b06ac655c0e15cfe7638273c Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Mon, 24 Jan 2011 13:25:09 -0500 Subject: [PATCH 29/30] shell-app-usage: fix tracking code We were checking if the app was running, but then marking it as recently-seen either way. Fix it to only update it when the app is running. https://bugzilla.gnome.org/show_bug.cgi?id=640447 --- src/shell-app-usage.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shell-app-usage.c b/src/shell-app-usage.c index 6126dc096..e8fe3ae8a 100644 --- a/src/shell-app-usage.c +++ b/src/shell-app-usage.c @@ -324,7 +324,8 @@ on_app_state_changed (ShellWindowTracker *tracker, running = shell_app_get_state (app) == SHELL_APP_STATE_RUNNING; - usage->last_seen = get_time (); + if (running) + usage->last_seen = get_time (); } static void From d107b84be4d90ecef34e5c60f63ca3dc07d044d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 8 Dec 2010 01:47:41 +0100 Subject: [PATCH 30/30] view-selector: Prelight view titles on hover The titles are clickable, indicate this to the user by prelighting them on hover. --- data/theme/gnome-shell.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 65aaaf238..cf6f1eb78 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -391,6 +391,10 @@ StTooltip StLabel { height: 24px; } +.view-tab-title:hover { + color: #bbb; +} + .view-tab-title:selected { color: #000000; background-color: #c2c7cd;