diff --git a/configure.ac b/configure.ac index e66346da4..6320855f8 100644 --- a/configure.ac +++ b/configure.ac @@ -18,6 +18,7 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 metacity-plugins gjs-gi-1.0) PKG_CHECK_MODULES(TIDY, clutter-0.8) +PKG_CHECK_MODULES(BIG, clutter-cairo-0.8 gtk+-2.0 librsvg-2.0) PKG_CHECK_MODULES(TRAY, gtk+-2.0) # Sets GLIB_GENMARSHAL and GLIB_MKENUMS diff --git a/src/Makefile-big.am b/src/Makefile-big.am new file mode 100644 index 000000000..7b5ea6e1a --- /dev/null +++ b/src/Makefile-big.am @@ -0,0 +1,67 @@ +big_cflags = \ + -I$(top_srcdir)/src \ + -DPREFIX=\""$(prefix)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DG_DISABLE_DEPRECATED \ + -DG_LOG_DOMAIN=\"Big\" \ + $(BIG_CFLAGS) \ + $(NULL) + +big_built_sources = \ + big-enum-types.h \ + big-enum-types.c \ + $(NULL) + +BUILT_SOURCES += $(big_built_sources) + +BIG_STAMP_FILES = stamp-big-marshal.h stamp-big-enum-types.h + +# please, keep this sorted alphabetically +big_source_h = \ + big/box.h \ + big/rectangle.h \ + big/theme-image.h \ + $(NULL) + +# please, keep this sorted alphabetically +big_source_c = \ + big/box.c \ + big/rectangle.c \ + big/theme-image.c \ + $(NULL) + +big-enum-types.h: stamp-big-enum-types.h Makefile + @true +stamp-big-enum-types.h: $(big_source_h) big/big-enum-types.h.in + ( cd $(srcdir) && \ + $(GLIB_MKENUMS) \ + --template $(srcdir)/big/big-enum-types.h.in \ + $(big_source_h) ) >> xgen-teth && \ + (cmp xgen-teth big-enum-types.h || cp xgen-teth big-enum-types.h) && \ + rm -f xgen-teth && \ + echo timestamp > $(@F) + +big-enum-types.c: stamp-big-enum-types.h big/big-enum-types.c.in + ( cd $(srcdir) && \ + $(GLIB_MKENUMS) \ + --template $(srcdir)/big/big-enum-types.c.in \ + $(big_source_h) ) >> xgen-tetc && \ + cp xgen-tetc big-enum-types.c && \ + rm -f xgen-tetc + +noinst_LTLIBRARIES += libbig-1.0.la + +libbig_1_0_la_LIBADD = $(BIG_LIBS) +libbig_1_0_la_SOURCES = \ + $(big_source_c) \ + $(big_source_h) \ + $(big_built_sources) \ + $(NULL) +libbig_1_0_la_CPPFLAGS = $(big_cflags) +libbig_1_0_la_LDFLAGS = $(LDADD) + +CLEANFILES += $(BIG_STAMP_FILES) $(BUILT_SOURCES) + +EXTRA_DIST += \ + big/big-enum-types.h.in \ + big/big-enum-types.c.in diff --git a/src/Makefile-tidy.am b/src/Makefile-tidy.am index 327a9e198..0c7f1b1c7 100644 --- a/src/Makefile-tidy.am +++ b/src/Makefile-tidy.am @@ -1,5 +1,3 @@ -NULL = - tidy_cflags = \ -I$(top_srcdir)/src \ -DPREFIX=\""$(prefix)"\" \ @@ -85,7 +83,7 @@ tidy-enum-types.c: stamp-tidy-enum-types.h tidy/tidy-enum-types.c.in cp xgen-tetc tidy-enum-types.c && \ rm -f xgen-tetc -noinst_LTLIBRARIES = libtidy-1.0.la +noinst_LTLIBRARIES += libtidy-1.0.la libtidy_1_0_la_LIBADD = $(TIDY_LIBS) libtidy_1_0_la_SOURCES = \ @@ -99,7 +97,7 @@ libtidy_1_0_la_LDFLAGS = $(LDADD) CLEANFILES += $(TIDY_STAMP_FILES) $(BUILT_SOURCES) -EXTRA_DIST = \ +EXTRA_DIST += \ tidy/tidy-enum-types.h.in \ tidy/tidy-enum-types.c.in \ tidy/tidy-private.h \ diff --git a/src/Makefile.am b/src/Makefile.am index 01b563199..57ebbfb10 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,11 @@ +NULL = BUILT_SOURCES = CLEANFILES = +EXTRA_DIST = +noinst_LTLIBRARIES = include Makefile-tidy.am +include Makefile-big.am include Makefile-tray.am gnome_shell_cflags = \ @@ -60,12 +64,13 @@ shell-marshal.c: Makefile shell-marshal.list libgnome_shell_la_LDFLAGS = -avoid-version -module libgnome_shell_la_LIBADD = \ $(MUTTER_PLUGIN_LIBS) \ + libbig-1.0.la \ libtidy-1.0.la \ libtray.la libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) typelibdir = $(pkglibdir) -typelib_DATA = Shell-0.1.typelib Tidy-1.0.typelib +typelib_DATA = Shell-0.1.typelib Tidy-1.0.typelib Big-1.0.typelib Shell-0.1.gir: $(metacity) $(G_IR_SCANNER) libgnome-shell.la Makefile $(G_IR_SCANNER) \ @@ -103,3 +108,20 @@ CLEANFILES += Tidy-1.0.gir Tidy-1.0.typelib: libtidy-1.0.la Tidy-1.0.gir LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler Tidy-1.0.gir -o $@ CLEANFILES += Tidy-1.0.typelib + +Big-1.0.gir: $(metacity) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la Makefile + $(G_IR_SCANNER) \ + --namespace=Big \ + --nsversion=1.0 \ + --include=ClutterCairo-0.8 \ + --program=metacity \ + --program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la \ + $(big_source_h) \ + $(big_source_c) \ + $(big_cflags) \ + -o $@ +CLEANFILES += Big-1.0.gir + +Big-1.0.typelib: libbig-1.0.la Big-1.0.gir + LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler Big-1.0.gir -o $@ +CLEANFILES += Big-1.0.typelib diff --git a/src/big/big-enum-types.c.in b/src/big/big-enum-types.c.in new file mode 100644 index 000000000..80417e0c5 --- /dev/null +++ b/src/big/big-enum-types.c.in @@ -0,0 +1,30 @@ +/*** BEGIN file-header ***/ +#include "big-enum-types.h" +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +#include "@filename@" +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type(void) { + static GType enum_type_id = 0; + if (G_UNLIKELY (!enum_type_id)) + { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + enum_type_id = g_@type@_register_static("@EnumName@", values); + } + return enum_type_id; +} +/*** END value-tail ***/ diff --git a/src/big/big-enum-types.h.in b/src/big/big-enum-types.h.in new file mode 100644 index 000000000..30851b661 --- /dev/null +++ b/src/big/big-enum-types.h.in @@ -0,0 +1,25 @@ +/*** BEGIN file-header ***/ +#ifndef __BIG_ENUM_TYPES_H__ +#define __BIG_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* !__BIG_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define BIG_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) + +/*** END value-header ***/ diff --git a/src/big/box.c b/src/big/box.c new file mode 100644 index 000000000..8587a3614 --- /dev/null +++ b/src/big/box.c @@ -0,0 +1,3050 @@ +/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* big-box.c: Box container. + + Copyright (C) 2006-2008 Red Hat, Inc. + Copyright (C) 2008 litl, LLC. + + The libbigwidgets-lgpl is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The libbigwidgets-lgpl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the libbigwidgets-lgpl; see the file COPYING.LIB. + If not, write to the Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include +#include +#include +#include + +#include + +#include "big-enum-types.h" + +#include "box.h" +#include "theme-image.h" +#include "rectangle.h" + +static void clutter_container_iface_init (ClutterContainerIface *iface); + +G_DEFINE_TYPE_WITH_CODE (BigBox, + big_box, + CLUTTER_TYPE_ACTOR, + G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, + clutter_container_iface_init)); + +#define BIG_BOX_GET_PRIVATE(obj) \ +(G_TYPE_INSTANCE_GET_PRIVATE ((obj), BIG_TYPE_BOX, BigBoxPrivate)) + +#define BOX_CHILD_IS_VISIBLE(c) \ +(CLUTTER_ACTOR_IS_VISIBLE (c->actor)) + +#define BOX_CHILD_IN_LAYOUT(c) \ +(!c->fixed && (BOX_CHILD_IS_VISIBLE (c) || c->if_hidden)) + +enum +{ + PROP_0, + + PROP_ORIENTATION, + PROP_SPACING, + PROP_X_ALIGN, + PROP_Y_ALIGN, + PROP_PADDING, + PROP_PADDING_TOP, + PROP_PADDING_BOTTOM, + PROP_PADDING_LEFT, + PROP_PADDING_RIGHT, + PROP_BORDER, + PROP_BORDER_TOP, + PROP_BORDER_BOTTOM, + PROP_BORDER_LEFT, + PROP_BORDER_RIGHT, + PROP_CORNER_RADIUS, + PROP_BACKGROUND_BORDER_TOP, + PROP_BACKGROUND_BORDER_BOTTOM, + PROP_BACKGROUND_BORDER_LEFT, + PROP_BACKGROUND_BORDER_RIGHT, + PROP_BACKGROUND_COLOR, + PROP_BACKGROUND_FILENAME, + PROP_BACKGROUND_PIXBUF, + PROP_BACKGROUND_TEXTURE, + PROP_BACKGROUND_RECTANGLE, + PROP_BACKGROUND_REPEAT, + PROP_BACKGROUND_X_ALIGN, + PROP_BACKGROUND_Y_ALIGN, + PROP_BORDER_COLOR, + PROP_DEBUG +}; + +struct _BigBoxPrivate +{ + GList *children; + BigBoxOrientation orientation; + BigBoxAlignment x_align; + BigBoxAlignment y_align; + ClutterUnit spacing; + ClutterUnit padding_top; + ClutterUnit padding_bottom; + ClutterUnit padding_left; + ClutterUnit padding_right; + ClutterUnit border_top; + ClutterUnit border_bottom; + ClutterUnit border_left; + ClutterUnit border_right; + ClutterUnit corner_radius; + ClutterColor border_color; + guint background_border_top; + guint background_border_bottom; + guint background_border_left; + guint background_border_right; + ClutterColor background_color; + ClutterActor *background_texture; + BigBoxBackgroundRepeat background_repeat; + BigBoxAlignment background_x_align; + BigBoxAlignment background_y_align; + ClutterActor *background_rectangle; + + guint draw_rounded_corner : 1; + guint debug : 1; +}; + +typedef struct +{ + ClutterActor *actor; + + guint expand : 1; + guint end : 1; + guint if_fits : 1; + guint fixed : 1; + guint if_hidden : 1; + + /* BigBoxAlignment applies if fixed=true */ + guint fixed_x_align : 3; + guint fixed_y_align : 3; +} BigBoxChild; + +typedef struct +{ + ClutterUnit minimum; + ClutterUnit natural; + ClutterUnit adjustment; + guint does_not_fit : 1; +} BigBoxAdjustInfo; + +static gboolean +box_child_set_flags (BigBoxChild *c, + BigBoxPackFlags flags) +{ + BigBoxPackFlags old; + + old = 0; + + if (c->end) + old |= BIG_BOX_PACK_END; + if (c->expand) + old |= BIG_BOX_PACK_EXPAND; + if (c->if_fits) + old |= BIG_BOX_PACK_IF_FITS; + if (c->fixed) + old |= BIG_BOX_PACK_FIXED; + if (c->if_hidden) + old |= BIG_BOX_PACK_ALLOCATE_WHEN_HIDDEN; + + if (old == flags) + return FALSE; /* no change */ + + c->expand = (flags & BIG_BOX_PACK_EXPAND) != 0; + c->end = (flags & BIG_BOX_PACK_END) != 0; + c->if_fits = (flags & BIG_BOX_PACK_IF_FITS) != 0; + c->fixed = (flags & BIG_BOX_PACK_FIXED) != 0; + c->if_hidden = (flags & BIG_BOX_PACK_ALLOCATE_WHEN_HIDDEN) != 0; + + return TRUE; +} + +static gboolean +box_child_set_align (BigBoxChild *c, + BigBoxAlignment fixed_x_align, + BigBoxAlignment fixed_y_align) +{ + if (fixed_x_align == c->fixed_x_align && + fixed_y_align == c->fixed_y_align) + return FALSE; + + c->fixed_x_align = fixed_x_align; + c->fixed_y_align = fixed_y_align; + + return TRUE; +} + +static BigBoxChild * +box_child_find (BigBox *box, + ClutterActor *actor) +{ + GList *c; + + for (c = box->priv->children; c != NULL; c = c->next) + { + BigBoxChild *child = c->data; + + if (child->actor == actor) + return (BigBoxChild *) child; + } + + return NULL; +} + +static BigBoxChild * +box_child_new_from_actor (ClutterActor *child, + BigBoxPackFlags flags) +{ + BigBoxChild *c; + + c = g_new0 (BigBoxChild, 1); + + g_object_ref (child); + c->actor = child; + + box_child_set_flags (c, flags); + + return c; +} + +static void +box_child_free (BigBoxChild *c) +{ + g_object_unref (c->actor); + + g_free (c); +} + +static void +box_child_remove (BigBox *box, BigBoxChild *child) +{ + BigBoxPrivate *priv = box->priv; + + priv->children = g_list_remove (priv->children, child); + + clutter_actor_unparent (child->actor); + + /* at this point, the actor passed to the "actor-removed" signal + * handlers is not parented anymore to the container but since we + * are holding a reference on it, it's still valid + */ + g_signal_emit_by_name (box, "actor-removed", child->actor); + + box_child_free (child); +} + +static void +big_box_real_add (ClutterContainer *container, + ClutterActor *child) +{ + big_box_append (BIG_BOX (container), child, BIG_BOX_PACK_FIXED); +} + +static void +big_box_real_remove (ClutterContainer *container, + ClutterActor *child) +{ + BigBox *box = BIG_BOX (container); + BigBoxChild *c; + + g_object_ref (child); + + c = box_child_find (box, child); + + if (c != NULL) + { + box_child_remove (box, c); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + } + + g_object_unref (child); +} + +static void +big_box_real_foreach (ClutterContainer *container, + ClutterCallback callback, + gpointer user_data) +{ + BigBox *group = BIG_BOX (container); + BigBoxPrivate *priv = group->priv; + GList *l; + + for (l = priv->children; l; l = l->next) + { + BigBoxChild *c = (BigBoxChild *) l->data; + + (* callback) (c->actor, user_data); + } +} + +static void +big_box_real_raise (ClutterContainer *container, + ClutterActor *child, + ClutterActor *sibling) +{ + BigBox *box = BIG_BOX (container); + BigBoxPrivate *priv = box->priv; + BigBoxChild *c; + + c = box_child_find (box, child); + + /* Child not found */ + if (c == NULL) + return; + + /* We only do raise for children not in the layout */ + if (!c->fixed) + return; + + priv->children = g_list_remove (priv->children, c); + + /* Raise at the top */ + if (!sibling) + { + GList *last_item; + + last_item = g_list_last (priv->children); + + if (last_item) + { + BigBoxChild *sibling_child = last_item->data; + + sibling = sibling_child->actor; + } + + priv->children = g_list_append (priv->children, c); + } + else + { + BigBoxChild *sibling_child; + gint pos; + + sibling_child = box_child_find (box, sibling); + + pos = g_list_index (priv->children, sibling_child) + 1; + + priv->children = g_list_insert (priv->children, c, pos); + } + + if (sibling && + clutter_actor_get_depth (sibling) != clutter_actor_get_depth (child)) + { + clutter_actor_set_depth (child, clutter_actor_get_depth (sibling)); + } +} + +static void +big_box_real_lower (ClutterContainer *container, + ClutterActor *child, + ClutterActor *sibling) +{ + BigBox *box = BIG_BOX (container); + BigBoxPrivate *priv = box->priv; + BigBoxChild *c; + + c = box_child_find (box, child); + + /* Child not found */ + if (c == NULL) + return; + + /* We only do lower for children not in the layout */ + if (!c->fixed) + return; + + priv->children = g_list_remove (priv->children, c); + + /* Push to bottom */ + if (!sibling) + { + GList *first_item; + + first_item = g_list_first (priv->children); + + if (first_item) + { + BigBoxChild *sibling_child = first_item->data; + + sibling = sibling_child->actor; + } + + priv->children = g_list_prepend (priv->children, c); + } + else + { + BigBoxChild *sibling_child; + gint pos; + + sibling_child = box_child_find (box, sibling); + + pos = g_list_index (priv->children, sibling_child); + + priv->children = g_list_insert (priv->children, c, pos); + } + + if (sibling && + clutter_actor_get_depth (sibling) != clutter_actor_get_depth (child)) + { + clutter_actor_set_depth (child, clutter_actor_get_depth (sibling)); + } +} + +static int +sort_z_order (gconstpointer a, + gconstpointer b) +{ + const BigBoxChild *child_a = a; + const BigBoxChild *child_b = b; + int depth_a, depth_b; + + /* Depth of non-fixed children is ignored in stacking/painting order and they + * are considered to be at depth=0. Though when the children are painting + * themselves the depth translation is applied. + */ + + depth_a = child_a->fixed ? clutter_actor_get_depth (child_a->actor) : 0; + depth_b = child_b->fixed ? clutter_actor_get_depth (child_b->actor) : 0; + + return depth_a - depth_b; +} + +static void +big_box_real_sort_depth_order (ClutterContainer *container) +{ + BigBox *box = BIG_BOX (container); + BigBoxPrivate *priv = box->priv; + + priv->children = g_list_sort (priv->children, sort_z_order); + + if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (box))) + clutter_actor_queue_redraw (CLUTTER_ACTOR (box)); +} + +static void +clutter_container_iface_init (ClutterContainerIface *iface) +{ + iface->add = big_box_real_add; + iface->remove = big_box_real_remove; + iface->foreach = big_box_real_foreach; + iface->raise = big_box_real_raise; + iface->lower = big_box_real_lower; + iface->sort_depth_order = big_box_real_sort_depth_order; +} + +static void +big_box_get_bg_texture_allocation (ClutterActor *self, + ClutterUnit allocated_width, + ClutterUnit allocated_height, + ClutterActorBox *bg_box) +{ + BigBoxPrivate *priv; + ClutterUnit bg_width, bg_height; + ClutterUnit min_x1, min_y1, max_x2, max_y2; + + g_return_if_fail (BIG_IS_BOX (self)); + + priv = BIG_BOX (self)->priv; + + clutter_actor_get_preferred_width (priv->background_texture, + -1, + NULL, + &bg_width); + clutter_actor_get_preferred_height (priv->background_texture, + bg_width, + NULL, + &bg_height); + + min_x1 = priv->border_left; + max_x2 = allocated_width - priv->border_right; + + switch (priv->background_x_align) + { + case BIG_BOX_ALIGNMENT_FIXED: + g_warning("Must specify a real alignment for background, not FIXED"); + break; + case BIG_BOX_ALIGNMENT_FILL: + bg_box->x1 = min_x1; + bg_box->x2 = max_x2; + break; + + case BIG_BOX_ALIGNMENT_START: + bg_box->x1 = min_x1; + bg_box->x2 = MIN (bg_box->x1 + bg_width, max_x2); + break; + + case BIG_BOX_ALIGNMENT_END: + bg_box->x1 = MAX (min_x1, max_x2 - bg_width); + bg_box->x2 = max_x2; + break; + + case BIG_BOX_ALIGNMENT_CENTER: + bg_box->x1 = MAX (min_x1, priv->border_left + (allocated_width - bg_width) / 2); + bg_box->x2 = MIN (bg_box->x1 + bg_width, max_x2); + break; + } + + min_y1 = priv->border_top; + max_y2 = allocated_height - priv->border_bottom; + + switch (priv->background_y_align) + { + case BIG_BOX_ALIGNMENT_FIXED: + g_warning("Must specify a real alignment for background, not FIXED"); + break; + case BIG_BOX_ALIGNMENT_FILL: + bg_box->y1 = min_y1; + bg_box->y2 = max_y2; + break; + + case BIG_BOX_ALIGNMENT_START: + bg_box->y1 = min_y1; + bg_box->y2 = MIN (bg_box->y1 + bg_height, max_y2); + break; + + case BIG_BOX_ALIGNMENT_END: + bg_box->y1 = MAX (min_y1, max_y2 - bg_height); + bg_box->y2 = max_y2; + break; + + case BIG_BOX_ALIGNMENT_CENTER: + bg_box->y1 = MAX (min_y1, priv->border_top + (allocated_height - bg_height) / 2); + bg_box->y2 = MIN (bg_box->y1 + bg_height, max_y2); + break; + } +} + +static void +big_box_update_background_border (BigBox *box) +{ + BigBoxPrivate *priv; + + g_return_if_fail (BIG_IS_BOX (box)); + + priv = box->priv; + + if (priv->background_texture) + { + guint border_top = 0, border_bottom = 0, border_left = 0, border_right = 0; + + if ((priv->background_x_align == BIG_BOX_ALIGNMENT_FILL) && + /* No repeat on the horizontal axis */ + ((priv->background_repeat == BIG_BOX_BACKGROUND_REPEAT_NONE) || + (priv->background_repeat == BIG_BOX_BACKGROUND_REPEAT_Y))) + { + border_left = priv->background_border_left; + border_right = priv->background_border_right; + } + + if ((priv->background_y_align == BIG_BOX_ALIGNMENT_FILL) && + /* No repeat on the vertical axis */ + ((priv->background_repeat == BIG_BOX_BACKGROUND_REPEAT_NONE) || + (priv->background_repeat == BIG_BOX_BACKGROUND_REPEAT_X))) + { + border_top = priv->background_border_top; + border_bottom = priv->background_border_bottom; + } + + g_object_set (priv->background_texture, + "border-left", border_left, + "border-right", border_right, + "border-top", border_top, + "border-bottom", border_bottom, + NULL); + } +} + +/* Update whether or not we draw rounded corners. + * If different sizes are set for different border segments, + * we ignore the radius. + */ +static void +big_box_update_draw_rounded_corner (BigBox *box) +{ + BigBoxPrivate *priv; + + priv = box->priv; + + priv->draw_rounded_corner = (priv->border_top == priv->border_left) && + (priv->border_top == priv->border_right) && + (priv->border_top == priv->border_bottom) && + (priv->corner_radius != 0); + + if (!priv->draw_rounded_corner && + priv->background_rectangle) + { + clutter_actor_unparent(priv->background_rectangle); + priv->background_rectangle = NULL; + } + + if (priv->draw_rounded_corner && + !priv->background_rectangle) + { + priv->background_rectangle = g_object_new (BIG_TYPE_RECTANGLE, NULL); + clutter_actor_set_parent (priv->background_rectangle, CLUTTER_ACTOR (box)); + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + } + + if (priv->draw_rounded_corner) + { + g_object_set (priv->background_rectangle, + "color", &priv->background_color, + "border-color", &priv->border_color, + "border-width", CLUTTER_UNITS_TO_DEVICE (priv->border_top), + "corner-radius", CLUTTER_UNITS_TO_DEVICE (priv->corner_radius), + NULL); + } +} + +static void +big_box_set_background_repeat (BigBox *box, + BigBoxBackgroundRepeat background_repeat) +{ + BigBoxPrivate *priv; + + priv = box->priv; + + priv->background_repeat = background_repeat; + + if (priv->background_texture) + { + gboolean repeat_x = FALSE, repeat_y = FALSE; + + switch (priv->background_repeat) + { + case BIG_BOX_BACKGROUND_REPEAT_NONE: + break; + + case BIG_BOX_BACKGROUND_REPEAT_X: + repeat_x = TRUE; + break; + + case BIG_BOX_BACKGROUND_REPEAT_Y: + repeat_y = TRUE; + break; + + case BIG_BOX_BACKGROUND_REPEAT_BOTH: + repeat_x = TRUE; + repeat_y = TRUE; + break; + } + + g_object_set (G_OBJECT (priv->background_texture), + "repeat-x", repeat_x, + "repeat-y", repeat_y, + NULL); + } +} + +static void +big_box_set_background_pixbuf (BigBox *box, GdkPixbuf *pixbuf) +{ + BigBoxPrivate *priv; + ClutterActor *background_texture; + + g_return_if_fail (BIG_IS_BOX (box)); + + priv = box->priv; + + if (priv->background_texture) + { + clutter_actor_unparent (priv->background_texture); + priv->background_texture = NULL; + } + + /* This means we just want to remove current background texture */ + if (pixbuf == NULL) + return; + + /* The border values are set depending on the background-repeat + * and background alignment settings in + * big_box_update_background_border */ + background_texture = big_theme_image_new_from_pixbuf (pixbuf, + 0, 0, 0, 0); + + if (background_texture) + { + clutter_actor_set_parent (background_texture, CLUTTER_ACTOR (box)); + priv->background_texture = background_texture; + big_box_set_background_repeat (box, priv->background_repeat); + } +} + +static void +big_box_set_background_filename (BigBox *box, const char *filename) +{ + BigBoxPrivate *priv; + ClutterActor *background_texture; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (filename != NULL); + + priv = box->priv; + + if (priv->background_texture) + { + clutter_actor_unparent (priv->background_texture); + priv->background_texture = NULL; + } + + /* This means we just want to remove current background texture */ + if (filename == NULL) + return; + + /* The border values are set depending on the background-repeat + * and background alignment settings in + * big_box_update_background_border */ + background_texture = big_theme_image_new_from_file (filename, + 0, 0, 0, 0); + + if (background_texture) + { + clutter_actor_set_parent (background_texture, CLUTTER_ACTOR (box)); + priv->background_texture = background_texture; + big_box_set_background_repeat (box, priv->background_repeat); + } +} + +static void +big_box_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BigBoxPrivate *priv = BIG_BOX (gobject)->priv; + gboolean need_repaint = TRUE; + gboolean need_resize = TRUE; + + switch (prop_id) + { + case PROP_ORIENTATION: + priv->orientation = g_value_get_enum (value); + break; + + case PROP_SPACING: + priv->spacing = CLUTTER_UNITS_FROM_DEVICE (g_value_get_int (value)); + break; + + case PROP_X_ALIGN: + priv->x_align = g_value_get_enum (value); + break; + + case PROP_Y_ALIGN: + priv->y_align = g_value_get_enum (value); + break; + + case PROP_PADDING: + big_box_set_padding(BIG_BOX(gobject), + g_value_get_int(value)); + break; + + case PROP_PADDING_TOP: + priv->padding_top = CLUTTER_UNITS_FROM_DEVICE (g_value_get_int (value)); + break; + + case PROP_PADDING_BOTTOM: + priv->padding_bottom = CLUTTER_UNITS_FROM_DEVICE (g_value_get_int (value)); + break; + + case PROP_PADDING_LEFT: + priv->padding_left = CLUTTER_UNITS_FROM_DEVICE(g_value_get_int (value)); + break; + + case PROP_PADDING_RIGHT: + priv->padding_right = CLUTTER_UNITS_FROM_DEVICE(g_value_get_int (value)); + break; + + case PROP_BORDER: + big_box_set_border_width(BIG_BOX(gobject), + g_value_get_int(value)); + break; + + case PROP_BORDER_TOP: + priv->border_top = CLUTTER_UNITS_FROM_DEVICE(g_value_get_int (value)); + big_box_update_draw_rounded_corner (BIG_BOX (gobject)); + break; + + case PROP_BORDER_BOTTOM: + priv->border_bottom = CLUTTER_UNITS_FROM_DEVICE(g_value_get_int (value)); + big_box_update_draw_rounded_corner (BIG_BOX (gobject)); + break; + + case PROP_BORDER_LEFT: + priv->border_left = CLUTTER_UNITS_FROM_DEVICE(g_value_get_int (value)); + big_box_update_draw_rounded_corner (BIG_BOX (gobject)); + break; + + case PROP_BORDER_RIGHT: + priv->border_right = CLUTTER_UNITS_FROM_DEVICE(g_value_get_int (value)); + big_box_update_draw_rounded_corner (BIG_BOX (gobject)); + break; + + case PROP_CORNER_RADIUS: + priv->corner_radius = CLUTTER_UNITS_FROM_DEVICE(g_value_get_uint (value)); + big_box_update_draw_rounded_corner (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_BORDER_TOP: + priv->background_border_top = g_value_get_uint (value); + need_resize = FALSE; + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_BORDER_BOTTOM: + priv->background_border_bottom = g_value_get_uint (value); + need_resize = FALSE; + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_BORDER_LEFT: + priv->background_border_left = g_value_get_uint (value); + need_resize = FALSE; + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_BORDER_RIGHT: + priv->background_border_right = g_value_get_uint (value); + need_resize = FALSE; + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_COLOR: + { + ClutterColor *color; + color = g_value_get_boxed (value); + if (color) { + priv->background_color = *color; + } else { + /* null = default (black and transparent) */ + priv->background_color.red = 0; + priv->background_color.green = 0; + priv->background_color.blue = 0; + priv->background_color.alpha = 0; + } + + if (priv->background_rectangle) + { + g_object_set (priv->background_rectangle, + "color", &priv->background_color, + NULL); + } + + need_resize = FALSE; + } + break; + + case PROP_BACKGROUND_FILENAME: + big_box_set_background_filename (BIG_BOX (gobject), + g_value_get_string (value)); + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_PIXBUF: + big_box_set_background_pixbuf (BIG_BOX (gobject), + g_value_get_object (value)); + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_REPEAT: + big_box_set_background_repeat (BIG_BOX (gobject), + g_value_get_enum (value)); + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_X_ALIGN: + priv->background_x_align = g_value_get_enum (value); + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BACKGROUND_Y_ALIGN: + priv->background_y_align = g_value_get_enum (value); + big_box_update_background_border (BIG_BOX (gobject)); + break; + + case PROP_BORDER_COLOR: + { + ClutterColor *color; + color = g_value_get_boxed (value); + if (color) { + priv->border_color = *color; + } else { + /* null = default (black and transparent) */ + priv->border_color.red = 0; + priv->border_color.green = 0; + priv->border_color.blue = 0; + priv->border_color.alpha = 0; + } + + if (priv->background_rectangle) + { + g_object_set (priv->background_rectangle, + "border-color", &priv->border_color, + NULL); + } + + need_resize = FALSE; + } + break; + + case PROP_DEBUG: + priv->debug = g_value_get_boolean (value); + + need_repaint = FALSE; + need_resize = FALSE; + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } + + if (need_resize) + clutter_actor_queue_relayout (CLUTTER_ACTOR (gobject)); + else if (need_repaint) + clutter_actor_queue_redraw (CLUTTER_ACTOR (gobject)); +} + +static void +big_box_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BigBoxPrivate *priv = BIG_BOX (gobject)->priv; + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, priv->orientation); + break; + + case PROP_SPACING: + g_value_set_int (value, CLUTTER_UNITS_TO_DEVICE(priv->spacing)); + break; + + case PROP_X_ALIGN: + g_value_set_enum (value, priv->x_align); + break; + + case PROP_Y_ALIGN: + g_value_set_enum (value, priv->y_align); + break; + + case PROP_PADDING_TOP: + g_value_set_int (value, CLUTTER_UNITS_TO_DEVICE(priv->padding_top)); + break; + + case PROP_PADDING_BOTTOM: + g_value_set_int (value, CLUTTER_UNITS_TO_DEVICE(priv->padding_bottom)); + break; + + case PROP_PADDING_LEFT: + g_value_set_int (value, CLUTTER_UNITS_TO_DEVICE(priv->padding_left)); + break; + + case PROP_PADDING_RIGHT: + g_value_set_int (value, CLUTTER_UNITS_TO_DEVICE(priv->padding_right)); + break; + + case PROP_BORDER_TOP: + g_value_set_int (value, CLUTTER_UNITS_TO_DEVICE(priv->border_top)); + break; + + case PROP_BORDER_BOTTOM: + g_value_set_int (value, CLUTTER_UNITS_TO_DEVICE(priv->border_bottom)); + break; + + case PROP_BORDER_LEFT: + g_value_set_int (value, CLUTTER_UNITS_TO_DEVICE(priv->border_left)); + break; + + case PROP_BORDER_RIGHT: + g_value_set_int (value,CLUTTER_UNITS_TO_DEVICE(priv->border_right)); + break; + + case PROP_CORNER_RADIUS: + g_value_set_uint (value,CLUTTER_UNITS_TO_DEVICE (priv->corner_radius)); + break; + + case PROP_BACKGROUND_TEXTURE: + g_value_set_object (value, priv->background_texture); + break; + + case PROP_BACKGROUND_RECTANGLE: + g_value_set_object (value, priv->background_rectangle); + break; + + case PROP_BACKGROUND_BORDER_TOP: + g_value_set_uint (value, priv->background_border_top); + break; + + case PROP_BACKGROUND_BORDER_BOTTOM: + g_value_set_uint (value, priv->background_border_bottom); + break; + + case PROP_BACKGROUND_BORDER_LEFT: + g_value_set_uint (value, priv->background_border_left); + break; + + case PROP_BACKGROUND_BORDER_RIGHT: + g_value_set_uint (value, priv->background_border_right); + break; + + case PROP_BACKGROUND_COLOR: + g_value_set_boxed (value, &priv->background_color); + break; + + case PROP_BACKGROUND_REPEAT: + g_value_set_enum (value, priv->background_repeat); + break; + + case PROP_BACKGROUND_X_ALIGN: + g_value_set_enum (value, priv->background_x_align); + break; + + case PROP_BACKGROUND_Y_ALIGN: + g_value_set_enum (value, priv->background_y_align); + break; + + case PROP_BORDER_COLOR: + g_value_set_boxed (value, &priv->border_color); + break; + + case PROP_DEBUG: + g_value_set_boolean (value, priv->debug); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +big_box_finalize (GObject *gobject) +{ + G_OBJECT_CLASS (big_box_parent_class)->finalize (gobject); +} + +static void +big_box_dispose (GObject *gobject) +{ + BigBox *self = BIG_BOX (gobject); + BigBoxPrivate *priv = self->priv; + + if (priv->background_texture) + { + clutter_actor_unparent (priv->background_texture); + priv->background_texture = NULL; + } + + if (priv->background_rectangle) + { + clutter_actor_unparent (priv->background_rectangle); + priv->background_rectangle = NULL; + } + + while (priv->children) + { + clutter_actor_destroy(((BigBoxChild*)priv->children->data)->actor); + } + + G_OBJECT_CLASS (big_box_parent_class)->dispose (gobject); +} + +static void +big_box_get_content_width_request (ClutterActor *self, + ClutterUnit *min_width_p, + ClutterUnit *natural_width_p) +{ + BigBoxPrivate *priv; + ClutterUnit total_min; + ClutterUnit total_natural; + gint n_children_in_min; + gint n_children_in_natural; + GList *c; + + priv = BIG_BOX (self)->priv; + + total_min = 0; + total_natural = 0; + n_children_in_min = 0; + n_children_in_natural = 0; + + for (c = priv->children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + ClutterUnit min_width; + ClutterUnit natural_width; + + if (!BOX_CHILD_IN_LAYOUT (child)) + continue; + + /* PACK_IF_FITS children are do not contribute to the min size + * of the whole box, but do contribute to natural size, and + * will be hidden entirely if their width request does not + * fit. + */ + clutter_actor_get_preferred_width (child->actor, + -1, + &min_width, + &natural_width); + + if (priv->debug) + g_debug ("Child %p min width %d natural %d", + child->actor, + CLUTTER_UNITS_TO_DEVICE (min_width), + CLUTTER_UNITS_TO_DEVICE (natural_width)); + + n_children_in_natural += 1; + + /* children with if fits flag won't appear at our min width if + * we are horizontal. If we're vertical, always request enough + * width for all if_fits children. Children with 0 min size won't + * themselves appear but they will get spacing around them, so + * they count in n_children_in_min. + */ + if (priv->orientation == BIG_BOX_ORIENTATION_VERTICAL) + { + total_min = MAX (total_min, min_width); + n_children_in_min += 1; + + total_natural = MAX (total_natural, natural_width); + } + else + { + if (!child->if_fits) + { + total_min += min_width; + n_children_in_min += 1; + } + + total_natural += natural_width; + } + } + + if (priv->orientation == BIG_BOX_ORIENTATION_HORIZONTAL && + n_children_in_min > 1) + { + total_min += priv->spacing * (n_children_in_min - 1); + } + + if (priv->orientation == BIG_BOX_ORIENTATION_HORIZONTAL && + n_children_in_natural > 1) + { + total_natural += priv->spacing * (n_children_in_natural - 1); + } + + if (min_width_p) + *min_width_p = total_min; + if (natural_width_p) + *natural_width_p = total_natural; +} + +static void +big_box_get_preferred_width (ClutterActor *self, + ClutterUnit for_height, + ClutterUnit *min_width_p, + ClutterUnit *natural_width_p) +{ + BigBoxPrivate *priv; + ClutterUnit content_min_width; + ClutterUnit content_natural_width; + ClutterUnit outside; + + priv = BIG_BOX (self)->priv; + + big_box_get_content_width_request (self, + &content_min_width, + &content_natural_width); + + outside = priv->padding_left + priv->padding_right + + priv->border_left + priv->border_right; + + if (min_width_p) + *min_width_p = content_min_width + outside; + if (natural_width_p) + *natural_width_p = content_natural_width + outside; + + if (priv->debug) + { + if (min_width_p) + g_debug ("Computed minimum width as %d", CLUTTER_UNITS_TO_DEVICE (*min_width_p)); + if (natural_width_p) + g_debug ("Computed natural width as %d", CLUTTER_UNITS_TO_DEVICE (*natural_width_p)); + } +} + +static void +big_box_get_content_area_horizontal (ClutterActor *self, + ClutterUnit requested_content_width, + ClutterUnit natural_content_width, + ClutterUnit allocated_box_width, + ClutterUnit *x_p, + ClutterUnit *width_p) +{ + BigBoxPrivate *priv; + ClutterUnit left; + ClutterUnit right; + ClutterUnit unpadded_box_width; + ClutterUnit content_width; + + priv = BIG_BOX (self)->priv; + + left = priv->border_left + priv->padding_left; + right = priv->border_right + priv->padding_right; + + g_return_if_fail (requested_content_width >= 0); + + if (natural_content_width < allocated_box_width) + content_width = natural_content_width; + else + content_width = MAX (requested_content_width, allocated_box_width); + + unpadded_box_width = allocated_box_width - left - right; + + switch (priv->x_align) + { + case BIG_BOX_ALIGNMENT_FIXED: + g_warning("Must specify a real alignment for content, not FIXED"); + break; + case BIG_BOX_ALIGNMENT_FILL: + if (x_p) + *x_p = left; + if (width_p) + *width_p = unpadded_box_width; + break; + case BIG_BOX_ALIGNMENT_START: + if (x_p) + *x_p = left; + if (width_p) + *width_p = content_width; + break; + case BIG_BOX_ALIGNMENT_END: + if (x_p) + *x_p = allocated_box_width - right - content_width; + if (width_p) + *width_p = content_width; + break; + case BIG_BOX_ALIGNMENT_CENTER: + if (x_p) + *x_p = left + (unpadded_box_width - content_width) / 2; + if (width_p) + *width_p = content_width; + break; + } +} + +static void +big_box_get_content_area_vertical (ClutterActor *self, + ClutterUnit requested_content_height, + ClutterUnit natural_content_height, + ClutterUnit allocated_box_height, + ClutterUnit *y_p, + ClutterUnit *height_p) +{ + BigBoxPrivate *priv; + ClutterUnit top; + ClutterUnit bottom; + ClutterUnit unpadded_box_height; + ClutterUnit content_height; + + priv = BIG_BOX (self)->priv; + + top = priv->border_top + priv->padding_top; + bottom = priv->border_bottom + priv->padding_bottom; + + g_return_if_fail (requested_content_height >= 0); + + if (natural_content_height < allocated_box_height) + content_height = natural_content_height; + else + content_height = MAX (requested_content_height, allocated_box_height); + + unpadded_box_height = allocated_box_height - top - bottom; + + switch (priv->y_align) + { + case BIG_BOX_ALIGNMENT_FIXED: + g_warning("Must specify a real alignment for content, not FIXED"); + break; + case BIG_BOX_ALIGNMENT_FILL: + if (y_p) + *y_p = top; + if (height_p) + *height_p = unpadded_box_height; + break; + case BIG_BOX_ALIGNMENT_START: + if (y_p) + *y_p = top; + if (height_p) + *height_p = content_height; + break; + case BIG_BOX_ALIGNMENT_END: + if (y_p) + *y_p = allocated_box_height - bottom - content_height; + if (height_p) + *height_p = content_height; + break; + case BIG_BOX_ALIGNMENT_CENTER: + if (y_p) + *y_p = top + (unpadded_box_height - content_height) / 2; + if (height_p) + *height_p = content_height; + break; + } +} +static BigBoxAdjustInfo * +big_box_adjust_infos_new (BigBox *box, + ClutterUnit for_content_width) +{ + BigBoxPrivate *priv = box->priv; + BigBoxAdjustInfo *adjusts = g_new0 (BigBoxAdjustInfo, g_list_length (priv->children)); + GList *c; + gint i = 0; + + for (c = priv->children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + + if (!BOX_CHILD_IN_LAYOUT (child)) + { + adjusts[i].minimum = adjusts[i].natural = 0; + } + else if (priv->orientation == BIG_BOX_ORIENTATION_VERTICAL) + { + clutter_actor_get_preferred_height (child->actor, for_content_width, + &adjusts[i].minimum, &adjusts[i].natural); + } + else + { + clutter_actor_get_preferred_width (child->actor, -1, &adjusts[i].minimum, + &adjusts[i].natural); + } + + i++; + } + + return adjusts; +} + +static void +big_box_adjust_if_fits_as_not_fitting (GList *children, + BigBoxAdjustInfo *adjusts) +{ + GList *c; + gint i; + + i = 0; + + for (c = children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + + if (child->if_fits) + { + adjusts[i].adjustment -= adjusts[i].minimum; + adjusts[i].does_not_fit = TRUE; + } + + ++i; + } +} + +static gboolean +big_box_adjust_up_to_natural_size (GList *children, + ClutterUnit *remaining_extra_space_p, + BigBoxAdjustInfo *adjusts, + gboolean if_fits) +{ + ClutterUnit smallest_increase; + ClutterUnit space_to_distribute; + GList *c; + gint n_needing_increase; + gint i; + + g_assert (*remaining_extra_space_p >= 0); + + if (*remaining_extra_space_p == 0) + return FALSE; + + smallest_increase = CLUTTER_MAXUNIT; + n_needing_increase = 0; + + i = 0; + + for (c = children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + + if (BOX_CHILD_IN_LAYOUT (child) && + ((!child->if_fits && !if_fits) || + (child->if_fits && if_fits && !adjusts[i].does_not_fit))) + { + ClutterUnit needed_increase; + + g_assert (adjusts[i].adjustment >= 0); + + /* Guaranteed to be >= 0 */ + needed_increase = adjusts[i].natural - adjusts[i].minimum; + + g_assert (needed_increase >= 0); + + needed_increase -= adjusts[i].adjustment; /* see how much we've already increased */ + + if (needed_increase > 0) + { + n_needing_increase += 1; + smallest_increase = MIN (smallest_increase, needed_increase); + } + } + + ++i; + } + + if (n_needing_increase == 0) + return FALSE; + + g_assert (smallest_increase < G_MAXINT); + + space_to_distribute = MIN (*remaining_extra_space_p, + smallest_increase * n_needing_increase); + + g_assert(space_to_distribute >= 0); + g_assert(space_to_distribute <= *remaining_extra_space_p); + + *remaining_extra_space_p -= space_to_distribute; + + i = 0; + + for (c = children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + + if (BOX_CHILD_IN_LAYOUT (child) && + ((!child->if_fits && !if_fits) || + (child->if_fits && if_fits && !adjusts[i].does_not_fit))) + { + ClutterUnit needed_increase; + + g_assert (adjusts[i].adjustment >= 0); + + /* Guaranteed to be >= 0 */ + needed_increase = adjusts[i].natural - adjusts[i].minimum; + + g_assert(needed_increase >= 0); + + needed_increase -= adjusts[i].adjustment; /* see how much we've already increased */ + + if (needed_increase > 0) + { + ClutterUnit extra; + + extra = (space_to_distribute / n_needing_increase); + + n_needing_increase -= 1; + space_to_distribute -= extra; + adjusts[i].adjustment += extra; + } + } + + ++i; + } + + g_assert (n_needing_increase == 0); + g_assert (space_to_distribute == 0); + + return TRUE; +} + +static gboolean +big_box_adjust_one_if_fits (GList *children, + ClutterUnit spacing, + ClutterUnit *remaining_extra_space_p, + BigBoxAdjustInfo *adjusts) +{ + GList *c; + ClutterUnit spacing_delta; + gint i; + gboolean visible_children = FALSE; + + if (*remaining_extra_space_p == 0) + return FALSE; + + /* if there are no currently visible children, then adding a child won't + * add another spacing + */ + i = 0; + + for (c = children; c != NULL; c = c->next) { + BigBoxChild *child = (BigBoxChild *) c->data; + + if (BOX_CHILD_IN_LAYOUT (child) && + (!child->if_fits || !adjusts[i].does_not_fit)) + { + visible_children = TRUE; + break; + } + + i++; + } + + spacing_delta = visible_children ? spacing : 0; + + i = 0; + + for (c = children; c != NULL; c = c->next) + { + if (adjusts[i].does_not_fit) + { + /* This child was adjusted downward, see if we can pop it visible + * (picking the smallest instead of first if-fits child on each pass + * might be nice, but for now it's the first that fits) + */ + if ((adjusts[i].minimum + spacing_delta) <= *remaining_extra_space_p) + { + adjusts[i].adjustment += adjusts[i].minimum; + + g_assert (adjusts[i].adjustment >= 0); + + adjusts[i].does_not_fit = FALSE; + *remaining_extra_space_p -= (adjusts[i].minimum + spacing_delta); + + g_assert (*remaining_extra_space_p >= 0); + + return TRUE; + } + } + + ++i; + } + + return FALSE; +} + +static gboolean +box_child_is_expandable (BigBoxChild *child, BigBoxAdjustInfo *adjust) +{ + return (BOX_CHILD_IS_VISIBLE (child) || child->if_hidden) && child->expand && + (!child->if_fits || (adjust && !(adjust->does_not_fit))); +} + +static int +big_box_count_expandable_children (GList *children, + BigBoxAdjustInfo *adjusts) +{ + GList *c; + gint count; + gint i; + + count = 0; + i = 0; + + for (c = children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + + /* We assume here that we've prevented via g_warning + * any floats/fixed from having expand=TRUE + */ + if (box_child_is_expandable (child, adjusts ? &(adjusts[i]) : NULL)) + ++count; + + ++i; + } + + return count; +} + +static void +big_box_adjust_for_expandable (GList *children, + ClutterUnit *remaining_extra_space_p, + BigBoxAdjustInfo *adjusts) +{ + GList *c; + ClutterUnit expand_space; + gint expand_count; + gint i; + + if (*remaining_extra_space_p == 0) + return; + + expand_space = *remaining_extra_space_p; + expand_count = big_box_count_expandable_children (children, adjusts); + + if (expand_count == 0) + return; + + i = 0; + + for (c = children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + + if (box_child_is_expandable (child, &(adjusts[i])) && + !adjusts[i].does_not_fit) + { + ClutterUnit extra; + + extra = (expand_space / expand_count); + + expand_count -= 1; + expand_space -= extra; + adjusts[i].adjustment += extra; + } + + ++i; + } + + /* if we had anything to expand, then we will have used up all space */ + g_assert (expand_space == 0); + g_assert (expand_count == 0); + + *remaining_extra_space_p = 0; +} +static void +big_box_compute_adjusts (GList *children, + BigBoxAdjustInfo *adjusts, + ClutterUnit spacing, + ClutterUnit alloc_request_delta) +{ + ClutterUnit remaining_extra_space; + + if (children == NULL) + return; + + /* Go ahead and cram all PACK_IF_FITS children to zero width, + * we'll expand them again if we can. + */ + big_box_adjust_if_fits_as_not_fitting (children, adjusts); + + /* Make no adjustments if we got too little or just right space. + * (FIXME handle too little space better) + */ + if (alloc_request_delta <= 0) { + return; + } + + remaining_extra_space = alloc_request_delta; + + /* Adjust non-PACK_IF_FITS up to natural size */ + while (big_box_adjust_up_to_natural_size (children, + &remaining_extra_space, adjusts, + FALSE)) + ; + + /* See if any PACK_IF_FITS can get their minimum size */ + while (big_box_adjust_one_if_fits (children, + spacing, &remaining_extra_space, adjusts)) + ; + + /* If so, then see if they can also get a natural size */ + while (big_box_adjust_up_to_natural_size(children, + &remaining_extra_space, adjusts, + TRUE)) + ; + + /* And finally we can expand to fill empty space */ + big_box_adjust_for_expandable (children, &remaining_extra_space, adjusts); + + /* remaining_extra_space need not be 0, if we had no expandable children */ +} + +static ClutterUnit +big_box_get_adjusted_size (BigBoxAdjustInfo *adjust) +{ + return adjust->minimum + adjust->adjustment; +} + +static void +big_box_get_hbox_height_request (ClutterActor *self, + ClutterUnit for_width, + ClutterUnit *min_height_p, + ClutterUnit *natural_height_p) +{ + BigBoxPrivate *priv; + ClutterUnit total_min; + ClutterUnit total_natural; + ClutterUnit requested_content_width; + ClutterUnit natural_content_width; + ClutterUnit allocated_content_width; + BigBoxAdjustInfo *width_adjusts; + GList *c; + gint i; + + priv = BIG_BOX (self)->priv; + + total_min = 0; + total_natural = 0; + + big_box_get_content_width_request (self, &requested_content_width, + &natural_content_width); + + big_box_get_content_area_horizontal (self, + requested_content_width, + natural_content_width, + for_width, NULL, + &allocated_content_width); + + width_adjusts = big_box_adjust_infos_new (BIG_BOX (self), for_width); + + big_box_compute_adjusts (priv->children, + width_adjusts, + priv->spacing, + allocated_content_width - requested_content_width); + + i = 0; + + for (c = priv->children; c != NULL; c = c->next) + { + BigBoxChild *child = c->data; + ClutterUnit min_height, natural_height; + ClutterUnit req = 0; + + if (!BOX_CHILD_IN_LAYOUT (child)) + { + ++i; + continue; + } + + req = big_box_get_adjusted_size (&width_adjusts[i]); + + clutter_actor_get_preferred_height (child->actor, req, + &min_height, &natural_height); + + if (priv->debug) + g_debug ("H - Child %p min height %d natural %d", + child->actor, + CLUTTER_UNITS_TO_DEVICE (min_height), + CLUTTER_UNITS_TO_DEVICE (natural_height)); + + total_min = MAX (total_min, min_height); + total_natural = MAX (total_natural, natural_height); + + ++i; + } + + g_free (width_adjusts); + + if (min_height_p) + *min_height_p = total_min; + if (natural_height_p) + *natural_height_p = total_natural; +} + +static void +big_box_get_vbox_height_request (ClutterActor *self, + ClutterUnit for_width, + ClutterUnit *min_height_p, + ClutterUnit *natural_height_p) +{ + BigBoxPrivate *priv; + ClutterUnit total_min; + ClutterUnit total_natural; + gint n_children_in_min; + gint n_children_in_natural; + GList *c; + + priv = BIG_BOX (self)->priv; + + total_min = 0; + total_natural = 0; + n_children_in_min = 0; + n_children_in_natural = 0; + + for (c = priv->children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + ClutterUnit min_height; + ClutterUnit natural_height; + + if (!BOX_CHILD_IN_LAYOUT (child)) + continue; + + clutter_actor_get_preferred_height (child->actor, for_width, + &min_height, &natural_height); + + if (priv->debug) + g_debug ("V - Child %p min height %d natural %d", + child->actor, + CLUTTER_UNITS_TO_DEVICE (min_height), + CLUTTER_UNITS_TO_DEVICE (natural_height)); + + n_children_in_natural += 1; + total_natural += natural_height; + + if (!child->if_fits) + { + n_children_in_min += 1; + total_min += min_height; + } + } + + if (n_children_in_min > 1) + total_min += priv->spacing * (n_children_in_min - 1); + if (n_children_in_natural > 1) + total_natural += priv->spacing * (n_children_in_natural - 1); + + if (min_height_p) + *min_height_p = total_min; + if (natural_height_p) + *natural_height_p = total_natural; +} + +static void +big_box_get_content_height_request (ClutterActor *self, + ClutterUnit for_width, + ClutterUnit *min_height_p, + ClutterUnit *natural_height_p) +{ + BigBoxPrivate *priv; + + priv = BIG_BOX (self)->priv; + + if (priv->orientation == BIG_BOX_ORIENTATION_VERTICAL) + big_box_get_vbox_height_request (self, for_width, + min_height_p, natural_height_p); + else + big_box_get_hbox_height_request (self, for_width, + min_height_p, natural_height_p); +} + +static void +big_box_get_preferred_height (ClutterActor *self, + ClutterUnit for_width, + ClutterUnit *min_height_p, + ClutterUnit *natural_height_p) +{ + BigBoxPrivate *priv; + ClutterUnit content_min_height, content_natural_height; + ClutterUnit content_for_width; + ClutterUnit outside; + + priv = BIG_BOX (self)->priv; + + content_for_width = for_width + - priv->padding_left - priv->padding_right + - priv->border_left - priv->border_right; + + /* We need to call this even if just returning the box-height prop, + * so that children can rely on getting the full request, allocate + * cycle in order every time, and so we compute the cached requests. + */ + big_box_get_content_height_request (self, + content_for_width, + &content_min_height, + &content_natural_height); + + outside = priv->padding_top + priv->padding_bottom + + priv->border_top + priv->border_bottom; + + if (min_height_p) + *min_height_p = content_min_height + outside; + if (natural_height_p) + *natural_height_p = content_natural_height + outside; + + + if (priv->debug) + { + if (min_height_p) + g_debug ("Computed minimum height for width=%d as %d", + CLUTTER_UNITS_TO_DEVICE (for_width), CLUTTER_UNITS_TO_DEVICE (*min_height_p)); + if (natural_height_p) + g_debug ("Computed natural height for width=%d as %d", + CLUTTER_UNITS_TO_DEVICE (for_width), CLUTTER_UNITS_TO_DEVICE (*natural_height_p)); + } +} + +static void +big_box_layout (ClutterActor *self, + ClutterUnit content_x, + ClutterUnit content_y, + ClutterUnit allocated_content_width, + ClutterUnit allocated_content_height, + ClutterUnit requested_content_width, + ClutterUnit requested_content_height, + gboolean absolute_origin_changed) +{ + BigBoxPrivate *priv; + BigBoxAdjustInfo *adjusts; + ClutterActorBox child_box; + ClutterUnit allocated_size, requested_size; + ClutterUnit start; + ClutterUnit end; + GList *c; + gint i; + + priv = BIG_BOX (self)->priv; + + if (priv->orientation == BIG_BOX_ORIENTATION_VERTICAL) + { + allocated_size = allocated_content_height; + requested_size = requested_content_height; + start = content_y; + } + else + { + allocated_size = allocated_content_width; + requested_size = requested_content_width; + start = content_x; + } + + end = start + allocated_size; + + adjusts = big_box_adjust_infos_new (BIG_BOX (self), allocated_content_width); + + big_box_compute_adjusts (priv->children, + adjusts, + priv->spacing, + allocated_size - requested_size); + + i = 0; + + for (c = priv->children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + ClutterUnit req; + + if (!BOX_CHILD_IN_LAYOUT (child)) + { + ++i; + continue; + } + + if (priv->orientation == BIG_BOX_ORIENTATION_VERTICAL) + { + req = big_box_get_adjusted_size (&adjusts[i]); + + child_box.x1 = content_x; + child_box.y1 = child->end ? end - req : start; + child_box.x2 = child_box.x1 + allocated_content_width; + child_box.y2 = child_box.y1 + req; + + if (priv->debug) + g_debug ("V - Child %p %s being allocated: %d, %d, %d, %d w: %d h: %d", + child->actor, + g_type_name_from_instance((GTypeInstance*) child->actor), + CLUTTER_UNITS_TO_DEVICE (child_box.x1), + CLUTTER_UNITS_TO_DEVICE (child_box.y1), + CLUTTER_UNITS_TO_DEVICE (child_box.x2), + CLUTTER_UNITS_TO_DEVICE (child_box.y2), + CLUTTER_UNITS_TO_DEVICE (child_box.x2 - child_box.x1), + CLUTTER_UNITS_TO_DEVICE (child_box.y2 - child_box.y1)); + + clutter_actor_allocate (child->actor, &child_box, + absolute_origin_changed); + } + else + { + req = big_box_get_adjusted_size (&adjusts[i]); + + child_box.x1 = child->end ? end - req : start; + child_box.y1 = content_y; + child_box.x2 = child_box.x1 + req; + child_box.y2 = child_box.y1 + allocated_content_height; + + if (priv->debug) + g_debug ("H - Child %p %s being allocated: %d, %d, %d, %d w: %d h: %d", + child->actor, + g_type_name_from_instance((GTypeInstance*) child->actor), + CLUTTER_UNITS_TO_DEVICE (child_box.x1), + CLUTTER_UNITS_TO_DEVICE (child_box.y1), + CLUTTER_UNITS_TO_DEVICE (child_box.x2), + CLUTTER_UNITS_TO_DEVICE (child_box.y2), + CLUTTER_UNITS_TO_DEVICE (child_box.x2 - child_box.x1), + CLUTTER_UNITS_TO_DEVICE (child_box.y2 - child_box.y1)); + + clutter_actor_allocate (child->actor, &child_box, + absolute_origin_changed); + } + + if (req <= 0) + { + /* Child was adjusted out of existence, act like it's + * !visible + */ + child_box.x1 = 0; + child_box.y1 = 0; + child_box.x2 = 0; + child_box.y2 = 0; + + clutter_actor_allocate (child->actor, &child_box, + absolute_origin_changed); + } + + /* Children with req == 0 still get spacing unless they are IF_FITS. + * The handling of spacing could use improvement (spaces should probably + * act like items with min width 0 and natural width of spacing) but + * it's pretty hard to get right without rearranging the code a lot. + */ + if (!adjusts[i].does_not_fit) + { + if (child->end) + end -= (req + priv->spacing); + else + start += (req + priv->spacing); + } + + ++i; + } + + g_free (adjusts); +} + +static void +big_box_allocate (ClutterActor *self, + const ClutterActorBox *box, + gboolean absolute_origin_changed) +{ + BigBoxPrivate *priv; + ClutterUnit requested_content_width; + ClutterUnit requested_content_height; + ClutterUnit natural_content_width; + ClutterUnit natural_content_height; + ClutterUnit allocated_content_width; + ClutterUnit allocated_content_height = 0; + ClutterUnit content_x, content_y = 0; + GList *c; + + priv = BIG_BOX (self)->priv; + + if (priv->debug) + g_debug ("Entire box %p being allocated: %d, %d, %d, %d", + self, + CLUTTER_UNITS_TO_DEVICE (box->x1), + CLUTTER_UNITS_TO_DEVICE (box->y1), + CLUTTER_UNITS_TO_DEVICE (box->x2), + CLUTTER_UNITS_TO_DEVICE (box->y2)); + + CLUTTER_ACTOR_CLASS (big_box_parent_class)->allocate (self, box, absolute_origin_changed); + + big_box_get_content_width_request (self, + &requested_content_width, + &natural_content_width); + + big_box_get_content_area_horizontal (self, requested_content_width, + natural_content_width, box->x2 - box->x1, + &content_x, &allocated_content_width); + + big_box_get_content_height_request (self, + allocated_content_width, + &requested_content_height, + &natural_content_height); + + big_box_get_content_area_vertical (self, requested_content_height, + natural_content_height, box->y2 - box->y1, + &content_y, &allocated_content_height); + + if (priv->debug) + { + if (allocated_content_height < requested_content_height) + g_debug ("Box %p allocated height %d but requested %d", + self, + CLUTTER_UNITS_TO_DEVICE (allocated_content_height), + CLUTTER_UNITS_TO_DEVICE (requested_content_height)); + if (allocated_content_width < requested_content_width) + g_debug ("Box %p allocated width %d but requested %d", + self, + CLUTTER_UNITS_TO_DEVICE (allocated_content_width), + CLUTTER_UNITS_TO_DEVICE (requested_content_width)); + } + + if (priv->background_texture) + { + ClutterActorBox bg_box; + + big_box_get_bg_texture_allocation (self, + box->x2 - box->x1, + box->y2 - box->y1, + &bg_box); + + if (priv->debug) + { + g_debug ("Box %p texture allocated width %d and height %d", + self, + CLUTTER_UNITS_TO_DEVICE (bg_box.x2 - bg_box.x1), + CLUTTER_UNITS_TO_DEVICE (bg_box.y2 - bg_box.y1)); + } + + clutter_actor_allocate (priv->background_texture, &bg_box, + absolute_origin_changed); + } + + if (priv->background_rectangle) + { + ClutterActorBox rectangle_box; + + rectangle_box.x1 = 0; + rectangle_box.y1 = 0; + rectangle_box.x2 = box->x2 - box->x1; + rectangle_box.y2 = box->y2 - box->y1; + + clutter_actor_allocate (priv->background_rectangle, + &rectangle_box, + absolute_origin_changed); + } + + for (c = priv->children; c != NULL; c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + ClutterActorBox child_box; + + if (!(BOX_CHILD_IS_VISIBLE (child) || child->if_hidden)) + { + child_box.x1 = 0; + child_box.y1 = 0; + child_box.x2 = 0; + child_box.y2 = 0; + + clutter_actor_allocate (child->actor, &child_box, FALSE); + } + else if (child->fixed) + { + ClutterUnit x, y, width, height; + + clutter_actor_get_positionu (child->actor, &x, &y); + clutter_actor_get_preferred_width(child->actor, -1, NULL, &width); + clutter_actor_get_preferred_height(child->actor, width, NULL, &height); + + switch (child->fixed_x_align) + { + case BIG_BOX_ALIGNMENT_FIXED: + /* honor child forced x,y instead of aligning automatically */ + child_box.x1 = x; + child_box.x2 = x + width; + break; + case BIG_BOX_ALIGNMENT_START: + child_box.x1 = content_x; + child_box.x2 = child_box.x1 + width; + break; + case BIG_BOX_ALIGNMENT_END: + child_box.x2 = content_x + allocated_content_width; + child_box.x1 = child_box.x2 - width; + break; + case BIG_BOX_ALIGNMENT_CENTER: + child_box.x1 = content_x + (allocated_content_width - width) / 2; + child_box.x2 = child_box.x1 + width; + break; + case BIG_BOX_ALIGNMENT_FILL: + child_box.x1 = content_x; + child_box.x2 = content_x + allocated_content_width; + break; + } + + switch (child->fixed_y_align) + { + case BIG_BOX_ALIGNMENT_FIXED: + /* honor child forced x,y instead of aligning automatically */ + child_box.y1 = y; + child_box.y2 = y + height; + break; + case BIG_BOX_ALIGNMENT_START: + child_box.y1 = content_y; + child_box.y2 = child_box.y1 + height; + break; + case BIG_BOX_ALIGNMENT_END: + child_box.y2 = content_y + allocated_content_height; + child_box.y1 = child_box.y2 - height; + break; + case BIG_BOX_ALIGNMENT_CENTER: + child_box.y1 = content_y + (allocated_content_height - height) / 2; + child_box.y2 = child_box.y1 + height; + break; + case BIG_BOX_ALIGNMENT_FILL: + child_box.y1 = content_y; + child_box.y2 = content_y + allocated_content_height; + break; + } + + if (priv->debug) + g_debug ("Fixed Child being allocated: %d, %d, %d, %d", + CLUTTER_UNITS_TO_DEVICE (child_box.x1), + CLUTTER_UNITS_TO_DEVICE (child_box.y1), + CLUTTER_UNITS_TO_DEVICE (child_box.x2), + CLUTTER_UNITS_TO_DEVICE (child_box.y2)); + + clutter_actor_allocate(child->actor, &child_box, + absolute_origin_changed); + } + } + + big_box_layout (self, content_x, content_y, + allocated_content_width, allocated_content_height, + requested_content_width, requested_content_height, + absolute_origin_changed); +} + +static void +big_box_paint (ClutterActor *actor) +{ + BigBox *self = BIG_BOX (actor); + GList *c; + ClutterColor color; + guint8 actor_opacity; + ClutterGeometry allocation; + ClutterGeometry bg_rect; + int border_top, border_bottom, border_left, border_right; + int padding_top, padding_bottom, padding_left, padding_right; + + /* First, convert everything to pixels */ + border_top = CLUTTER_UNITS_TO_DEVICE (self->priv->border_top); + border_bottom = CLUTTER_UNITS_TO_DEVICE (self->priv->border_bottom); + border_left = CLUTTER_UNITS_TO_DEVICE (self->priv->border_left); + border_right = CLUTTER_UNITS_TO_DEVICE (self->priv->border_right); + padding_top = CLUTTER_UNITS_TO_DEVICE (self->priv->padding_top); + padding_bottom = CLUTTER_UNITS_TO_DEVICE (self->priv->padding_bottom); + padding_left = CLUTTER_UNITS_TO_DEVICE (self->priv->padding_left); + padding_right = CLUTTER_UNITS_TO_DEVICE (self->priv->padding_right); + + actor_opacity = clutter_actor_get_paint_opacity (actor); + + /* Remember allocation.x and .y don't matter since we are + * theoretically already translated to 0,0 + */ + clutter_actor_get_allocation_geometry (actor, &allocation); + + cogl_enable(0); /* defeat cogl GL cache in case GL used directly */ + + cogl_push_matrix (); + + /* Background */ + + color = self->priv->background_color; + color.alpha = (color.alpha * actor_opacity) / 0xff; + + bg_rect.x = border_left; + bg_rect.y = border_top; + bg_rect.width = allocation.width - border_left - border_right; + bg_rect.height = allocation.height - border_top - border_bottom; + + /* Border */ + + if (self->priv->draw_rounded_corner) + { + /* we delegate drawing of the background to BigRectangle. + * Ideally we would always do that but currently BigRectangle + * does not support border segments with different size + */ + clutter_actor_paint (self->priv->background_rectangle); + } + else + { + if (color.alpha != 0) + { + cogl_color (&color); + cogl_rectangle (bg_rect.x, bg_rect.y, bg_rect.width, bg_rect.height); + } + + color = self->priv->border_color; + color.alpha = (color.alpha * actor_opacity) / 0xff; + + if (color.alpha != 0) + { + cogl_color (&color); + + /* top */ + cogl_rectangle (0, 0, + allocation.width, + border_top); + + /* left */ + cogl_rectangle (0, border_top, + border_left, + allocation.height - border_top - border_bottom); + + /* right */ + cogl_rectangle (allocation.width - border_right, + border_top, + border_right, + allocation.height - border_top - border_bottom); + + /* bottom */ + cogl_rectangle (0, allocation.height - border_bottom, + allocation.width, + border_bottom); + } + } + + cogl_enable(0); /* defeat cogl GL cache in case GL used directly */ + + if (self->priv->background_texture && + CLUTTER_ACTOR_IS_VISIBLE(self->priv->background_texture)) { + clutter_actor_paint (CLUTTER_ACTOR (self->priv->background_texture)); + cogl_enable(0); /* defeat cogl GL cache in case GL used directly */ + } + + /* Children */ + + for (c = self->priv->children; + c != NULL; + c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + + g_assert (child != NULL); + + if (BOX_CHILD_IS_VISIBLE (child)) { + clutter_actor_paint (child->actor); + cogl_enable(0); /* defeat cogl GL cache in case GL used directly */ + } + } + + cogl_pop_matrix(); +} + +static void +big_box_pick (ClutterActor *actor, + const ClutterColor *color) +{ + BigBox *self = BIG_BOX (actor); + GList *c; + + CLUTTER_ACTOR_CLASS (big_box_parent_class)->pick (actor, color); + + for (c = self->priv->children; + c != NULL; + c = c->next) + { + BigBoxChild *child = (BigBoxChild *) c->data; + + g_assert (child != NULL); + + if (BOX_CHILD_IS_VISIBLE (child)) + clutter_actor_paint (child->actor); + } +} + +static void +big_box_class_init (BigBoxClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, sizeof (BigBoxPrivate)); + + gobject_class->set_property = big_box_set_property; + gobject_class->get_property = big_box_get_property; + gobject_class->dispose = big_box_dispose; + gobject_class->finalize = big_box_finalize; + + actor_class->get_preferred_width = big_box_get_preferred_width; + actor_class->get_preferred_height = big_box_get_preferred_height; + actor_class->allocate = big_box_allocate; + actor_class->paint = big_box_paint; + actor_class->pick = big_box_pick; + + g_object_class_install_property + (gobject_class, + PROP_ORIENTATION, + g_param_spec_enum ("orientation", + "Orientation", + "Orientation of the box", + BIG_TYPE_BOX_ORIENTATION, + BIG_BOX_ORIENTATION_VERTICAL, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_SPACING, + g_param_spec_int ("spacing", + "Spacing", + "Spacing between items of the box", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_X_ALIGN, + g_param_spec_enum ("x-align", + "X alignment", + "X alignment", + BIG_TYPE_BOX_ALIGNMENT, + BIG_BOX_ALIGNMENT_START, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_Y_ALIGN, + g_param_spec_enum ("y-align", + "Y alignment", + "Y alignment", + BIG_TYPE_BOX_ALIGNMENT, + BIG_BOX_ALIGNMENT_START, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_PADDING, + g_param_spec_int ("padding", + "Padding", + "Padding (set all paddings at once)", + G_MININT, + G_MAXINT, + 0, + G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_PADDING_TOP, + g_param_spec_int ("padding-top", + "Padding on the top", + "Padding on the top", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_PADDING_BOTTOM, + g_param_spec_int ("padding-bottom", + "Padding on the bottom", + "Padding on the bottom", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_PADDING_LEFT, + g_param_spec_int ("padding-left", + "Padding on the left", + "Padding on the left", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_PADDING_RIGHT, + g_param_spec_int ("padding-right", + "Padding on the right", + "Padding on the right", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_BORDER, + g_param_spec_int ("border", + "Border", + "Border (set all borders at once)", + G_MININT, + G_MAXINT, + 0, + G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_BORDER_TOP, + g_param_spec_int ("border-top", + "Border on the top", + "Border on the top", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_BORDER_BOTTOM, + g_param_spec_int ("border-bottom", + "Border on the bottom", + "Border on the bottom", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_BORDER_LEFT, + g_param_spec_int ("border-left", + "Border on the left", + "Border on the left", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_BORDER_RIGHT, + g_param_spec_int ("border-right", + "Border on the right", + "Border on the right", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_CORNER_RADIUS, + g_param_spec_uint ("corner-radius", + "Corner radius", + "Radius of the rounded corner " + "(ignored with border segments of " + "different sizes)", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + /** + * BigBox:background-border-top: + * + * Specifies a border on the top of the image which should not be + * stretched when the image is set to fill the box vertically + * (background-y-align set to FILL, background-repeat set to + * not repeat vertically. Useful for images with rounded corners. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_BORDER_TOP, + g_param_spec_uint ("background-border-top", + "Background border on the top", + "Background border on the top", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + /** + * BigBox:background-border-bottom: + * + * Specifies a border on the bottom of the image which should not be + * stretched when the image is set to fill the box vertically + * (background-y-align set to FILL, background-repeat set to + * not repeat vertically. Useful for images with rounded corners. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_BORDER_BOTTOM, + g_param_spec_uint ("background-border-bottom", + "Background border on the bottom", + "Background border on the bottom", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + /** + * BigBox:background-border-left: + * + * Specifies a border on the left of the image which should not be + * stretched when the image is set to fill the box horizontally + * (background-x-align set to FILL, background-repeat set to + * not repeat horizontally. Useful for images with rounded corners. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_BORDER_LEFT, + g_param_spec_uint ("background-border-left", + "Background border on the left", + "Background border on the left", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + /** + * BigBox:background-border-right: + * + * Specifies a border on the right of the image which should not be + * stretched when the image is set to fill the box horizontally + * (background-x-align set to FILL, background-repeat set to + * not repeat horizontally. Useful for images with rounded corners. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_BORDER_RIGHT, + g_param_spec_uint ("background-border-right", + "Background border on the right", + "Background border on the right", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + /** + * BigBox:background-color: + * + * Background color, covers padding but not border. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_COLOR, + g_param_spec_boxed ("background-color", + "Background Color", + "The color of the background", + CLUTTER_TYPE_COLOR, + G_PARAM_READWRITE)); + /** + * BigBox:background-filename: + * + * Background filename, covers padding but not border. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_FILENAME, + g_param_spec_string ("background-filename", + "Background Filename", + "The image filename of the background", + NULL, + G_PARAM_WRITABLE)); + /** + * BigBox:background-pixbuf: + * + * Background pixbuf, covers padding but not border. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_PIXBUF, + g_param_spec_object ("background-pixbuf", + "Background Pixbuf", + "The image pixbuf of the background", + GDK_TYPE_PIXBUF, + G_PARAM_WRITABLE)); + /** + * BigBox:background-texture: + * + * Background texture, covers padding but not border. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_TEXTURE, + g_param_spec_object ("background-texture", + "Background Texture", + "The texture of the background", + CLUTTER_TYPE_TEXTURE, + G_PARAM_READABLE)); + + /** + * BigBox:background-rectangle: + * + * Background rectangle, covers the complete box allocation. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_RECTANGLE, + g_param_spec_object ("background-rectangle", + "Background Rectangle", + "The rectangle forming the box border and " + "background color", + BIG_TYPE_RECTANGLE, + G_PARAM_READABLE)); + /** + * BigBox:background-repeat: + * + * Sets if/how a background image will be repeated. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_REPEAT, + g_param_spec_enum ("background-repeat", + "Background Repeat", + "Background Repeat", + BIG_TYPE_BOX_BACKGROUND_REPEAT, + BIG_BOX_BACKGROUND_REPEAT_NONE, + G_PARAM_READWRITE)); + /** + * BigBox:background-x-align: + * + * Sets the horizontal alignment of the background texture. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_X_ALIGN, + g_param_spec_enum ("background-x-align", + "Background X alignment", + "Background X alignment", + BIG_TYPE_BOX_ALIGNMENT, + BIG_BOX_ALIGNMENT_FILL, + G_PARAM_READWRITE)); + /** + * BigBox:background-y-align: + * + * Sets the vertical alignment of the background texture. + */ + g_object_class_install_property + (gobject_class, + PROP_BACKGROUND_Y_ALIGN, + g_param_spec_enum ("background-y-align", + "Background Y alignment", + "Background Y alignment", + BIG_TYPE_BOX_ALIGNMENT, + BIG_BOX_ALIGNMENT_FILL, + G_PARAM_READWRITE)); + /** + * BigBox:border-color: + * + * Border color, color of border if any. Make the border transparent + * to use the border purely for spacing with no visible border. + * + */ + g_object_class_install_property + (gobject_class, + PROP_BORDER_COLOR, + g_param_spec_boxed ("border-color", + "Border Color", + "The color of the border of the rectangle", + CLUTTER_TYPE_COLOR, + G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, + PROP_DEBUG, + g_param_spec_boolean ("debug", + "Debug", + "Whether debug is activated or not", + FALSE, + G_PARAM_READWRITE)); +} + +static void +big_box_init (BigBox *box) +{ + box->priv = BIG_BOX_GET_PRIVATE (box); + + box->priv->orientation = BIG_BOX_ORIENTATION_VERTICAL; + box->priv->x_align = BIG_BOX_ALIGNMENT_FILL; + box->priv->y_align = BIG_BOX_ALIGNMENT_FILL; + box->priv->spacing = 0; + box->priv->padding_top = 0; + box->priv->padding_bottom = 0; + box->priv->padding_left = 0; + box->priv->padding_right = 0; + box->priv->border_top = 0; + box->priv->border_bottom = 0; + box->priv->border_left = 0; + box->priv->border_right = 0; + + box->priv->background_texture = NULL; + box->priv->background_repeat = BIG_BOX_BACKGROUND_REPEAT_NONE; + box->priv->background_x_align = BIG_BOX_ALIGNMENT_FILL; + box->priv->background_y_align = BIG_BOX_ALIGNMENT_FILL; + + box->priv->background_rectangle = NULL; + box->priv->draw_rounded_corner = FALSE; + + /* both bg and border colors default to black and transparent (all bits 0) */ +} + +ClutterActor * +big_box_new (BigBoxOrientation orientation) +{ + return g_object_new (BIG_TYPE_BOX, + "orientation", orientation, + NULL); +} + +void +big_box_prepend (BigBox *box, + ClutterActor *child, + BigBoxPackFlags flags) +{ + BigBoxPrivate *priv; + BigBoxChild *c; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_ACTOR (child)); + + priv = box->priv; + + g_object_ref (child); + + c = box_child_new_from_actor (child, flags); + + priv->children = g_list_prepend (priv->children, c); + + clutter_actor_set_parent (child, CLUTTER_ACTOR (box)); + + g_signal_emit_by_name (box, "actor-added", child); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + + g_object_unref (child); +} + +void +big_box_append (BigBox *box, + ClutterActor *child, + BigBoxPackFlags flags) +{ + BigBoxPrivate *priv; + BigBoxChild *c; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_ACTOR (child)); + + priv = box->priv; + + g_object_ref (child); + + c = box_child_new_from_actor (child, flags); + + priv->children = g_list_append (priv->children, c); + + clutter_actor_set_parent (child, CLUTTER_ACTOR (box)); + + g_signal_emit_by_name (box, "actor-added", child); + + big_box_real_sort_depth_order (CLUTTER_CONTAINER (box)); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + + g_object_unref (child); +} + +gboolean +big_box_is_empty (BigBox *box) +{ + g_return_val_if_fail (BIG_IS_BOX (box), TRUE); + + return (box->priv->children == NULL); +} + +void +big_box_remove_all (BigBox *box) +{ + BigBoxPrivate *priv; + + g_return_if_fail (BIG_IS_BOX (box)); + + priv = box->priv; + + while (priv->children != NULL) { + BigBoxChild *child = priv->children->data; + + box_child_remove (box, child); + } + + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); +} + +void +big_box_insert_after (BigBox *box, + ClutterActor *child, + ClutterActor *ref_child, + BigBoxPackFlags flags) +{ + BigBoxPrivate *priv; + BigBoxChild *c, *ref_c; + gint position; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_ACTOR (child)); + g_return_if_fail (CLUTTER_IS_ACTOR (ref_child)); + + priv = box->priv; + + g_object_ref (child); + + ref_c = box_child_find (box, ref_child); + + if (ref_c != NULL) + { + c = box_child_new_from_actor (child, flags); + + position = g_list_index (priv->children, ref_c); + priv->children = g_list_insert (priv->children, c, ++position); + + clutter_actor_set_parent (child, CLUTTER_ACTOR (box)); + + g_signal_emit_by_name (box, "actor-added", child); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + } + + g_object_unref (child); +} + +void +big_box_insert_before (BigBox *box, + ClutterActor *child, + ClutterActor *ref_child, + BigBoxPackFlags flags) +{ + BigBoxPrivate *priv; + BigBoxChild *c, *ref_c; + gint position; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_ACTOR (child)); + + priv = box->priv; + + g_object_ref (child); + + ref_c = box_child_find (box, ref_child); + + if (ref_c != NULL) + { + c = box_child_new_from_actor (child, flags); + + position = g_list_index (priv->children, ref_c); + priv->children = g_list_insert (priv->children, c, position); + + clutter_actor_set_parent (child, CLUTTER_ACTOR (box)); + + g_signal_emit_by_name (box, "actor-added", child); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + } + + g_object_unref (child); +} + +void +big_box_set_child_packing (BigBox *box, + ClutterActor *child, + BigBoxPackFlags flags) +{ + BigBoxChild *c; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_ACTOR (child)); + + g_object_ref (child); + + c = box_child_find (box, child); + + if (c != NULL && box_child_set_flags (c, flags)) + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + + g_object_unref (child); +} + +void +big_box_set_child_align(BigBox *box, + ClutterActor *child, + BigBoxAlignment fixed_x_align, + BigBoxAlignment fixed_y_align) +{ + BigBoxChild *c; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (CLUTTER_IS_ACTOR (child)); + + g_object_ref (child); + + c = box_child_find (box, child); + + if (c != NULL && box_child_set_align (c, fixed_x_align, fixed_y_align)) + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + + g_object_unref (child); +} + +void +big_box_set_padding (BigBox *box, int padding) +{ + BigBoxPrivate *priv; + ClutterUnit padding_in_units; + gboolean padding_changed; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (padding >= 0); + + priv = box->priv; + + padding_in_units = CLUTTER_UNITS_FROM_DEVICE (padding); + + padding_changed = (priv->padding_top != padding_in_units || + priv->padding_bottom != padding_in_units || + priv->padding_left != padding_in_units || + priv->padding_right != padding_in_units); + + if (padding_changed) + { + g_object_freeze_notify (G_OBJECT (box)); + + if (box->priv->padding_top != padding_in_units) + g_object_notify (G_OBJECT (box), "padding-top"); + box->priv->padding_top = padding_in_units; + + if (box->priv->padding_bottom != padding_in_units) + g_object_notify (G_OBJECT (box), "padding-bottom"); + box->priv->padding_bottom = padding_in_units; + + if (box->priv->padding_left != padding_in_units) + g_object_notify (G_OBJECT (box), "padding-left"); + box->priv->padding_left = padding_in_units; + + if (box->priv->padding_right != padding_in_units) + g_object_notify (G_OBJECT (box), "padding-right"); + box->priv->padding_right = padding_in_units; + + g_object_thaw_notify (G_OBJECT (box)); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + } +} + +void +big_box_set_border_width (BigBox *box, int border_width) +{ + BigBoxPrivate *priv; + ClutterUnit border_in_units; + gboolean border_changed; + + g_return_if_fail (BIG_IS_BOX (box)); + g_return_if_fail (border_width >= 0); + + priv = box->priv; + + border_in_units = CLUTTER_UNITS_FROM_DEVICE (border_width); + + border_changed = (priv->border_top != border_in_units || + priv->border_bottom != border_in_units || + priv->border_left != border_in_units || + priv->border_right != border_in_units); + + if (border_changed) + { + g_object_freeze_notify (G_OBJECT (box)); + + if (box->priv->border_top != border_in_units) + g_object_notify (G_OBJECT (box), "border-top"); + box->priv->border_top = border_in_units; + + if (box->priv->border_bottom != border_in_units) + g_object_notify (G_OBJECT (box), "border-bottom"); + box->priv->border_bottom = border_in_units; + + if (box->priv->border_left != border_in_units) + g_object_notify (G_OBJECT (box), "border-left"); + box->priv->border_left = border_in_units; + + if (box->priv->border_right != border_in_units) + g_object_notify (G_OBJECT (box), "border-right"); + box->priv->border_right = border_in_units; + + g_object_thaw_notify (G_OBJECT (box)); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (box)); + + big_box_update_draw_rounded_corner (box); + } +} diff --git a/src/big/box.h b/src/big/box.h new file mode 100644 index 000000000..1d4878f8b --- /dev/null +++ b/src/big/box.h @@ -0,0 +1,129 @@ +/* -*- mode: C; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* big-box.h: Box container. + + Copyright (C) 2006-2008 Red Hat, Inc. + Copyright (C) 2008 litl, LLC. + + The libbigwidgets-lgpl is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The libbigwidgets-lgpl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the libbigwidgets-lgpl; see the file COPYING.LIB. + If not, write to the Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef __BIG_BOX_H__ +#define __BIG_BOX_H__ + +#include + +G_BEGIN_DECLS + +#define BIG_TYPE_BOX (big_box_get_type ()) +#define BIG_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BIG_TYPE_BOX, BigBox)) +#define BIG_IS_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BIG_TYPE_BOX)) +#define BIG_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BIG_TYPE_BOX, BigBoxClass)) +#define BIG_IS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BIG_TYPE_BOX)) +#define BIG_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BIG_TYPE_BOX, BigBoxClass)) + +typedef struct _BigBox BigBox; +typedef struct _BigBoxPrivate BigBoxPrivate; +typedef struct _BigBoxClass BigBoxClass; + +typedef enum +{ + BIG_BOX_PACK_NONE = 0, + BIG_BOX_PACK_EXPAND = 1 << 0, + BIG_BOX_PACK_END = 1 << 1, + BIG_BOX_PACK_IF_FITS = 1 << 2, + BIG_BOX_PACK_FIXED = 1 << 3, + BIG_BOX_PACK_ALLOCATE_WHEN_HIDDEN = 1 << 4 +} BigBoxPackFlags; + +typedef enum +{ + BIG_BOX_ALIGNMENT_FIXED = 0, + BIG_BOX_ALIGNMENT_FILL = 1, + BIG_BOX_ALIGNMENT_START = 2, + BIG_BOX_ALIGNMENT_END = 3, + BIG_BOX_ALIGNMENT_CENTER = 4 +} BigBoxAlignment; + +typedef enum +{ + BIG_BOX_ORIENTATION_VERTICAL = 1, + BIG_BOX_ORIENTATION_HORIZONTAL = 2 +} BigBoxOrientation; + +typedef enum +{ + BIG_BOX_BACKGROUND_REPEAT_NONE = 0, + BIG_BOX_BACKGROUND_REPEAT_X = 1, + BIG_BOX_BACKGROUND_REPEAT_Y = 2, + BIG_BOX_BACKGROUND_REPEAT_BOTH = 3, +} BigBoxBackgroundRepeat; + +struct _BigBox +{ + ClutterActor parent_instance; + + BigBoxPrivate *priv; +}; + +struct _BigBoxClass +{ + ClutterActorClass parent_class; +}; + +GType big_box_get_type (void) G_GNUC_CONST; + +ClutterActor *big_box_new (BigBoxOrientation orientation); + +void big_box_prepend (BigBox *box, + ClutterActor *child, + BigBoxPackFlags flags); + +void big_box_append (BigBox *box, + ClutterActor *child, + BigBoxPackFlags flags); + +gboolean big_box_is_empty (BigBox *box); + +void big_box_remove_all (BigBox *box); + +void big_box_insert_after (BigBox *box, + ClutterActor *child, + ClutterActor *ref_child, + BigBoxPackFlags flags); + +void big_box_insert_before (BigBox *box, + ClutterActor *child, + ClutterActor *ref_child, + BigBoxPackFlags flags); + +void big_box_set_child_packing (BigBox *box, + ClutterActor *child, + BigBoxPackFlags flags); + +void big_box_set_child_align (BigBox *box, + ClutterActor *child, + BigBoxAlignment fixed_x_align, + BigBoxAlignment fixed_y_align); + +void big_box_set_padding (BigBox *box, + int padding); + +void big_box_set_border_width (BigBox *box, + int border_width); + +G_END_DECLS + +#endif /* __BIG_BOX_H__ */ diff --git a/src/big/rectangle.c b/src/big/rectangle.c new file mode 100644 index 000000000..db848dece --- /dev/null +++ b/src/big/rectangle.c @@ -0,0 +1,537 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* rectangle.c: Rounded rectangle. + + Copyright (C) 2008 litl, LLC. + + The libbigwidgets-lgpl is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The libbigwidgets-lgpl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the libbigwidgets-lgpl; see the file COPYING.LIB. + If not, write to the Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include + +#include +#include +#include +#include + +#include "rectangle.h" + +typedef struct { + gint ref_count; + + ClutterColor color; + ClutterColor border_color; + int radius; + int border_width; + + CoglHandle texture; + guint8 *data; +} Corner; + +struct BigRectangle { + ClutterRectangle parent_instance; + ClutterUnit radius; + Corner *corner; + gboolean corners_dirty; +}; + +/* map of { radius, border_width, border_color, color } to Corner textures */ +static GHashTable *all_corners = NULL; + +struct BigRectangleClass { + ClutterRectangleClass parent_class; +}; + +G_DEFINE_TYPE(BigRectangle, big_rectangle, CLUTTER_TYPE_RECTANGLE) + +enum +{ + PROP_0, + + PROP_CORNER_RADIUS +}; + +static gboolean +corner_equal(gconstpointer a, + gconstpointer b) +{ + const Corner *corner_a; + const Corner *corner_b; + + corner_a = a; + corner_b = b; + + return *((guint32 *)&corner_a->color) == *((guint32 *)&corner_b->color) && + *((guint32 *)&corner_a->border_color) == *((guint32 *)&corner_b->border_color) && + corner_a->border_width == corner_b->border_width && + corner_a->radius == corner_b->radius; +} + +static guint +corner_hash(gconstpointer key) +{ + const Corner *corner; + guint hashed[4]; + + corner = key; + + hashed[0] = *((guint *)&(corner->color)); + hashed[1] = *((guint *)&(corner->border_color)); + hashed[2] = *((guint *)&(corner->border_width)); + hashed[3] = *((guint *)&(corner->radius)); + + return hashed[0] ^ hashed[1] ^ hashed[2] ^ hashed[3]; +} + +static Corner * +create_corner_texture(Corner *src) +{ + Corner *corner; + CoglHandle texture; + cairo_t *cr; + cairo_surface_t *surface; + guint x, y; + guint rowstride; + guint8 *data; + guint32 *src_p; + guint8 *dst_p; + guint size; + + corner = g_memdup(src, sizeof(Corner)); + + size = 2 * MAX(corner->border_width, corner->radius); + rowstride = size * 4; + data = g_new0(guint8, size * rowstride); + + surface = cairo_image_surface_create_for_data(data, + CAIRO_FORMAT_ARGB32, + size, size, + rowstride); + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_scale(cr, size, size); + + if (corner->border_width < corner->radius) { + double internal_radius = 0.5 * (1.0 - (double) corner->border_width / corner->radius); + + if (corner->border_width != 0) { + cairo_set_source_rgba(cr, + (double)corner->border_color.red / G_MAXUINT8, + (double)corner->border_color.green / G_MAXUINT8, + (double)corner->border_color.blue / G_MAXUINT8, + (double)corner->border_color.alpha / G_MAXUINT8); + + cairo_arc(cr, 0.5, 0.5, 0.5, 0, 2 * M_PI); + cairo_fill(cr); + } + + cairo_set_source_rgba(cr, + (double)corner->color.red / G_MAXUINT8, + (double)corner->color.green / G_MAXUINT8, + (double)corner->color.blue / G_MAXUINT8, + (double)corner->color.alpha / G_MAXUINT8); + cairo_arc(cr, 0.5, 0.5, internal_radius, 0, 2 * M_PI); + cairo_fill(cr); + + } else { + double radius; + + radius = (gdouble)corner->radius / corner->border_width; + + cairo_set_source_rgba(cr, + (double)corner->border_color.red / G_MAXUINT8, + (double)corner->border_color.green / G_MAXUINT8, + (double)corner->border_color.blue / G_MAXUINT8, + (double)corner->border_color.alpha / G_MAXUINT8); + + cairo_arc(cr, radius, radius, radius, M_PI, 3 * M_PI / 2); + cairo_line_to(cr, 1.0 - radius, 0.0); + cairo_arc(cr, 1.0 - radius, radius, radius, 3 * M_PI / 2, 2*M_PI); + cairo_line_to(cr, 1.0, 1.0 - radius); + cairo_arc(cr, 1.0 - radius, 1.0 - radius, radius, 0, M_PI / 2); + cairo_line_to(cr, radius, 1.0); + cairo_arc(cr, radius, 1.0 - radius, radius, M_PI / 2, M_PI); + cairo_fill(cr); + } + cairo_destroy(cr); + + cairo_surface_destroy(surface); + + corner->data = g_new0(guint8, size * rowstride); + + /* cogl doesn't seem to support the conversion, do it manually */ + /* borrowed from clutter-cairo, conversion from ARGB pre-multiplied + * to RGBA */ + for (y = 0; y < size; y++) { + src_p = (guint32 *) (data + y * rowstride); + dst_p = corner->data + y * rowstride; + + for (x = 0; x < size; x++) { + guint8 alpha = (*src_p >> 24) & 0xff; + + if (alpha == 0) { + dst_p[0] = dst_p[1] = dst_p[2] = dst_p[3] = alpha; + } else { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + dst_p[0] = (((*src_p >> 16) & 0xff) * 255 ) / alpha; + dst_p[1] = (((*src_p >> 8) & 0xff) * 255 ) / alpha; + dst_p[2] = (((*src_p >> 0) & 0xff) * 255 ) / alpha; + dst_p[3] = alpha; +#elif G_BYTE_ORDER == G_BIG_ENDIAN + dst_p[0] = alpha; + dst_p[1] = (((*src_p >> 0) & 0xff) * 255 ) / alpha; + dst_p[2] = (((*src_p >> 8) & 0xff) * 255 ) / alpha; + dst_p[3] = (((*src_p >> 16) & 0xff) * 255 ) / alpha; +#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ +#error unknown ENDIAN type +#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ + } + dst_p += 4; + src_p++; + } + } + + g_free(data); + + texture = cogl_texture_new_from_data(size, size, + 0, + FALSE, + COGL_PIXEL_FORMAT_RGBA_8888, + COGL_PIXEL_FORMAT_ANY, + rowstride, + corner->data); + g_assert(texture != COGL_INVALID_HANDLE); + + corner->ref_count = 1; + corner->texture = texture; + + g_hash_table_insert(all_corners, corner, corner); + + return corner; +} + +static void +corner_unref(Corner *corner) +{ + corner->ref_count --; + + if (corner->ref_count == 0) { + g_hash_table_remove(all_corners, corner); + + cogl_texture_unref(corner->texture); + g_free(corner->data); + g_free(corner); + } +} + +static Corner * +corner_get(guint radius, + ClutterColor *color, + guint border_width, + ClutterColor *border_color) +{ + Corner key; + Corner *corner; + + if (all_corners == NULL) { + all_corners = g_hash_table_new(corner_hash, corner_equal); + } + + key.radius = radius; + key.color = *color; + key.border_color = *border_color; + key.border_width = border_width; + + corner = g_hash_table_lookup(all_corners, &key); + + if (!corner) { + corner = create_corner_texture(&key); + } else { + corner->ref_count ++; + } + + return corner; +} + +static void +big_rectangle_update_corners(BigRectangle *rectangle) +{ + Corner *corner; + + corner = NULL; + + if (rectangle->radius != 0) { + ClutterColor *color; + ClutterColor *border_color; + guint border_width; + + g_object_get(rectangle, + "border-color", &border_color, + "border-width", &border_width, + "color", &color, + NULL); + + corner = corner_get(CLUTTER_UNITS_TO_DEVICE(rectangle->radius), + color, + border_width, + border_color); + + clutter_color_free(border_color); + clutter_color_free(color); + } + + if (rectangle->corner) { + corner_unref(rectangle->corner); + } + + rectangle->corner = corner; + + rectangle->corners_dirty = FALSE; +} + +static void +big_rectangle_paint(ClutterActor *actor) +{ + BigRectangle *rectangle; + ClutterColor *color; + ClutterColor *border_color; + guint border_width; + ClutterActorBox box; + ClutterFixed radiusx; + ClutterFixed widthx; + ClutterFixed heightx; + ClutterFixed border_widthx; + ClutterFixed max; + ClutterColor tmp_col; + + rectangle = BIG_RECTANGLE(actor); + + if (rectangle->radius == 0) { + /* In that case we are no different than our parent class, + * so don't bother */ + CLUTTER_ACTOR_CLASS(big_rectangle_parent_class)->paint(actor); + return; + } + + if (rectangle->corners_dirty) + big_rectangle_update_corners(rectangle); + + g_object_get(rectangle, + "border-color", &border_color, + "border-width", &border_width, + "color", &color, + NULL); + + clutter_actor_get_allocation_box(actor, &box); + + /* translation was already done */ + box.x2 -= box.x1; + box.y2 -= box.y1; + + widthx = CLUTTER_UNITS_TO_FIXED(box.x2); + heightx = CLUTTER_UNITS_TO_FIXED(box.y2); + + radiusx = CLUTTER_UNITS_TO_FIXED(rectangle->radius); + + border_widthx = CLUTTER_INT_TO_FIXED(border_width); + max = MAX(border_widthx, radiusx); + + if (radiusx != 0) { + tmp_col.red = 0xFF; + tmp_col.blue = 0xFF; + tmp_col.green = 0xFF; + tmp_col.alpha = clutter_actor_get_paint_opacity(actor); + + cogl_color(&tmp_col); + + /* NW */ + cogl_texture_rectangle(rectangle->corner->texture, + 0, 0, + max, max, + 0, 0, + CFX_HALF, CFX_HALF); + + /* NE */ + cogl_texture_rectangle(rectangle->corner->texture, + widthx - max, 0, + widthx, max, + CFX_HALF, 0, + CFX_ONE, CFX_HALF); + + /* SW */ + cogl_texture_rectangle(rectangle->corner->texture, + 0, heightx - max, + max, heightx, + 0, CFX_HALF, + CFX_HALF, CFX_ONE); + + /* SE */ + cogl_texture_rectangle(rectangle->corner->texture, + widthx - max, heightx - max, + widthx, heightx, + CFX_HALF, CFX_HALF, + CFX_ONE, CFX_ONE); + + } + + if (border_width != 0) { + tmp_col = *border_color; + + tmp_col.alpha = clutter_actor_get_paint_opacity(actor) * tmp_col.alpha / 255; + + cogl_color(&tmp_col); + + /* NORTH */ + cogl_rectanglex(max, 0, + widthx - 2 * max, border_widthx); + + /* EAST */ + cogl_rectanglex(widthx - border_widthx, max, + border_widthx, heightx - 2 * max); + + /* SOUTH */ + cogl_rectanglex(max, heightx - border_widthx, + widthx - 2 * max, border_widthx); + + /* WEST */ + cogl_rectanglex(0, max, + border_widthx, heightx - 2 * max); + + } + + tmp_col = *color; + tmp_col.alpha = clutter_actor_get_paint_opacity(actor) * tmp_col.alpha / 255; + + cogl_color(&tmp_col); + + if (radiusx > border_widthx) { + cogl_rectanglex(radiusx, border_widthx, + widthx - radiusx - radiusx, radiusx - border_widthx); + cogl_rectanglex(radiusx, heightx - radiusx, + widthx - 2 * radiusx, radiusx - border_widthx); + } + + cogl_rectanglex(border_widthx, MAX(radiusx, border_widthx), + widthx - 2 * border_widthx, heightx - 2 * MAX(radiusx, border_widthx)); + + clutter_color_free(border_color); + clutter_color_free(color); +} + +static void +big_rectangle_notify(GObject *object, + GParamSpec *pspec) +{ + BigRectangle *rectangle; + + rectangle = BIG_RECTANGLE(object); + + if (g_str_equal(pspec->name, "border-width") || + g_str_equal(pspec->name, "color") || + g_str_equal(pspec->name, "border-color")) { + rectangle->corners_dirty = TRUE; + } +} + +static void +big_rectangle_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BigRectangle *rectangle; + + rectangle = BIG_RECTANGLE(object); + + switch (prop_id) { + case PROP_CORNER_RADIUS: + rectangle->radius = CLUTTER_UNITS_FROM_DEVICE(g_value_get_uint(value)); + rectangle->corners_dirty = TRUE; + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + + } +} + +static void +big_rectangle_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BigRectangle *rectangle; + + rectangle = BIG_RECTANGLE(object); + + switch (prop_id) { + case PROP_CORNER_RADIUS: + g_value_set_uint(value, CLUTTER_UNITS_TO_DEVICE(rectangle->radius)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +big_rectangle_dispose(GObject *object) +{ + BigRectangle *rectangle; + + rectangle = BIG_RECTANGLE(object); + + rectangle->radius = 0; + big_rectangle_update_corners(rectangle); + + if (G_OBJECT_CLASS(big_rectangle_parent_class)->dispose) + G_OBJECT_CLASS(big_rectangle_parent_class)->dispose(object); + +} + + +static void +big_rectangle_class_init(BigRectangleClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + gobject_class->dispose = big_rectangle_dispose; + gobject_class->set_property = big_rectangle_set_property; + gobject_class->get_property = big_rectangle_get_property; + gobject_class->notify = big_rectangle_notify; + + actor_class->paint = big_rectangle_paint; + + g_object_class_install_property + (gobject_class, + PROP_CORNER_RADIUS, + g_param_spec_uint("corner-radius", + "Corner radius", + "Radius of the rectangle rounded corner", + 0, G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + +} + +static void +big_rectangle_init(BigRectangle *rectangle) +{ +} diff --git a/src/big/rectangle.h b/src/big/rectangle.h new file mode 100644 index 000000000..c22024dd0 --- /dev/null +++ b/src/big/rectangle.h @@ -0,0 +1,42 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + Copyright (C) 2008 litl, LLC. + + The libbigwidgets-lgpl is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The libbigwidgets-lgpl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the libbigwidgets-lgpl; see the file COPYING.LIB. + If not, write to the Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +*/ +#ifndef __BIG_RECTANGLE_H__ +#define __BIG_RECTANGLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define BIG_TYPE_RECTANGLE (big_rectangle_get_type ()) +#define BIG_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BIG_TYPE_RECTANGLE, BigRectangle)) +#define BIG_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BIG_TYPE_RECTANGLE, BigRectangleClass)) +#define BIG_IS_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BIG_TYPE_RECTANGLE)) +#define BIG_IS_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BIG_TYPE_RECTANGLE)) +#define BIG_RECTANGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BIG_TYPE_RECTANGLE, BigRectangleClass)) + +typedef struct BigRectangle BigRectangle; +typedef struct BigRectangleClass BigRectangleClass; + +GType big_rectangle_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __BIG_RECTANGLE_H__ */ diff --git a/src/big/theme-image.c b/src/big/theme-image.c new file mode 100644 index 000000000..01eab0922 --- /dev/null +++ b/src/big/theme-image.c @@ -0,0 +1,795 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* theme-image.c: Stretched image. + + Copyright (C) 2005-2008 Red Hat, Inc. + Copyright (C) 2008 litl, LLC. + + The libbigwidgets-lgpl is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The libbigwidgets-lgpl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the libbigwidgets-lgpl; see the file COPYING.LIB. + If not, write to the Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include +#include +#include + +#include + +#include +#include + +#include "theme-image.h" + +typedef enum { + BIG_THEME_IMAGE_UNSET, + BIG_THEME_IMAGE_SVG, + BIG_THEME_IMAGE_SURFACE + +} BigThemeImageType; + +struct BigThemeImage { + ClutterCairo parent_instance; + + guint border_top; + guint border_bottom; + guint border_left; + guint border_right; + + BigThemeImageType type; + + union { + RsvgHandle *svg_handle; + cairo_surface_t *surface; + } u; + + guint render_idle; + guint needs_render : 1; +}; + +struct BigThemeImageClass { + ClutterCairoClass parent_class; +}; + +G_DEFINE_TYPE(BigThemeImage, big_theme_image, CLUTTER_TYPE_CAIRO) + +enum +{ + PROP_0, + + PROP_BORDER_TOP, + PROP_BORDER_BOTTOM, + PROP_BORDER_LEFT, + PROP_BORDER_RIGHT, + + PROP_FILENAME, + PROP_PIXBUF +}; + +static void +big_theme_image_render(BigThemeImage *image) +{ + int source_width = 0; + int source_height = 0; + int source_x1 = 0, source_x2 = 0, source_y1 = 0, source_y2 = 0; + int dest_x1 = 0, dest_x2 = 0, dest_y1 = 0, dest_y2 = 0; + int i, j; + int dest_width; + int dest_height; + ClutterGeometry geometry; + cairo_t *cr; + + image->needs_render = FALSE; + + if (image->render_idle) { + g_source_remove(image->render_idle); + image->render_idle = 0; + } + + + /* To draw a theme image, we divide the source and destination into 9 + * pieces and draw each piece separately. (Some pieces may not exist + * if we have 0-width borders, in which case they'll be skipped) + * + * i=0 i=1 i=2 + * border_left border_right + * +------------+--------------------+--------------+ + * j=0: border_top | | | | + * +------------+--------------------+--------------+ + * j=1 | | | | + * +------------+--------------------+--------------+ + * j=2: border_bottom | | | | + * +------------+--------------------+--------------+ + * + */ + + switch (image->type) { + case BIG_THEME_IMAGE_SURFACE: + if (!image->u.surface) + return; + + source_width = cairo_image_surface_get_width(image->u.surface); + source_height = cairo_image_surface_get_height(image->u.surface); + break; + case BIG_THEME_IMAGE_SVG: + { + RsvgDimensionData dimensions; + + if (!image->u.svg_handle) + return; + + rsvg_handle_get_dimensions(image->u.svg_handle, &dimensions); + source_width = dimensions.width; + source_height = dimensions.height; + break; + } + default: + return; + } + + clutter_actor_get_allocation_geometry(CLUTTER_ACTOR(image), &geometry); + + dest_width = geometry.width; + dest_height = geometry.height; + + cr = clutter_cairo_create(CLUTTER_CAIRO(image)); + + for (j = 0; j < 3; j++) { + switch (j) { + case 0: + source_y1 = 0; + source_y2 = image->border_top; + dest_y1 = 0; + dest_y2 = image->border_top; + break; + case 1: + source_y1 = image->border_top; + source_y2 = source_height - image->border_bottom; + dest_y1 = image->border_top; + dest_y2 = dest_height - image->border_bottom; + break; + case 2: + source_y1 = source_height - image->border_bottom; + source_y2 = source_height; + dest_y1 = dest_height - image->border_bottom; + dest_y2 = dest_height; + break; + } + + if (dest_y2 <= dest_y1) + continue; + + /* pixbuf-theme-engine has a nice interpretation of source_y2 == source_y1, + * dest_y2 != dest_y1, which is to linearly interpolate between the surrounding + * areas. We could do that for the surface case by setting + * + * source_y1 == y - 0.5 + * source_y2 == y + 0.5 + * + * but it's hard for the SVG case. source_y2 < source_y1 is pathological ... someone + * specified borders that sum up larger than the image. + */ + if (source_y2 <= source_y1) + continue; + + for (i = 0; i < 3; i++) { + switch (i) { + case 0: + source_x1 = 0; + source_x2 = image->border_left; + dest_x1 = 0; + dest_x2 = image->border_left; + break; + case 1: + source_x1 = image->border_left; + source_x2 = source_width - image->border_right; + dest_x1 = image->border_left; + dest_x2 = dest_width - image->border_right; + break; + case 2: + source_x1 = source_width - image->border_right; + source_x2 = source_width; + dest_x1 = dest_width - image->border_right; + dest_x2 = dest_width; + break; + } + + if (dest_x2 <= dest_x1) + continue; + + if (source_x2 <= source_x1) + continue; + + cairo_save(cr); + + cairo_rectangle(cr, dest_x1, dest_y1, dest_x2 - dest_x1, dest_y2 - dest_y1); + cairo_clip(cr); + + cairo_translate(cr, dest_x1, dest_y1); + cairo_scale(cr, + (double)(dest_x2 - dest_x1) / (source_x2 - source_x1), + (double)(dest_y2 - dest_y1) / (source_y2 - source_y1)); + + switch (image->type) { + case BIG_THEME_IMAGE_SURFACE: + cairo_set_source_surface(cr, image->u.surface, - source_x1, - source_y1); + cairo_paint(cr); + break; + case BIG_THEME_IMAGE_SVG: + cairo_translate(cr, - source_x1, - source_y1); + rsvg_handle_render_cairo(image->u.svg_handle, cr); + break; + default: + break; + } + + cairo_restore(cr); + } + } + + /* This will cause the surface content to be uploaded as + * new texture content */ + cairo_destroy(cr); +} + +static gboolean +big_theme_image_render_idle(gpointer data) +{ + BigThemeImage *image; + + image = data; + big_theme_image_render(image); + + return FALSE; +} + +static void +big_theme_image_queue_render(BigThemeImage *image) +{ + image->needs_render = TRUE; + if (!image->render_idle) + image->render_idle = g_idle_add(big_theme_image_render_idle, + image); +} + +static void +big_theme_image_paint(ClutterActor *actor) +{ + BigThemeImage *image; + + image = BIG_THEME_IMAGE(actor); + + if (image->needs_render) + big_theme_image_render(image); + + if (CLUTTER_ACTOR_CLASS(big_theme_image_parent_class)->paint) + CLUTTER_ACTOR_CLASS(big_theme_image_parent_class)->paint(actor); +} + +static void +big_theme_image_allocate(ClutterActor *actor, + const ClutterActorBox *box, + gboolean absolute_origin_changed) +{ + BigThemeImage *image; + guint old_width; + guint old_height; + guint width; + guint height; + + image = BIG_THEME_IMAGE(actor); + + width = ABS(CLUTTER_UNITS_TO_DEVICE(box->x2 - box->x1)); + height = ABS(CLUTTER_UNITS_TO_DEVICE(box->y2 - box->y1)); + + g_object_get(actor, + "surface-width", &old_width, + "surface-height", &old_height, + NULL); + + if (width != old_width || height != old_height) { + + clutter_cairo_surface_resize(CLUTTER_CAIRO(actor), width, height); + + big_theme_image_queue_render(image); + } + + if (CLUTTER_ACTOR_CLASS(big_theme_image_parent_class)) + CLUTTER_ACTOR_CLASS(big_theme_image_parent_class)->allocate(actor, + box, + absolute_origin_changed); +} + +static void +big_theme_image_get_preferred_height(ClutterActor *actor, + ClutterUnit for_width, + ClutterUnit *min_height_p, + ClutterUnit *natural_height_p) +{ + BigThemeImage *image; + + image = BIG_THEME_IMAGE(actor); + + if (min_height_p) + *min_height_p = 0; + + if (!natural_height_p) + return; + + *natural_height_p = 0; + + switch (image->type) { + case BIG_THEME_IMAGE_SURFACE: + if (!image->u.surface) + break; + + *natural_height_p = CLUTTER_UNITS_FROM_DEVICE( + cairo_image_surface_get_height(image->u.surface)); + break; + case BIG_THEME_IMAGE_SVG: + { + RsvgDimensionData dimensions; + + if (!image->u.svg_handle) + return; + + rsvg_handle_get_dimensions(image->u.svg_handle, &dimensions); + *natural_height_p = + CLUTTER_UNITS_FROM_DEVICE(dimensions.height); + break; + } + default: + break; + } +} + +static void +big_theme_image_get_preferred_width(ClutterActor *actor, + ClutterUnit for_height, + ClutterUnit *min_width_p, + ClutterUnit *natural_width_p) +{ + BigThemeImage *image; + + image = BIG_THEME_IMAGE(actor); + + if (min_width_p) + *min_width_p = 0; + + if (!natural_width_p) + return; + + *natural_width_p = 0; + + switch (image->type) { + case BIG_THEME_IMAGE_SURFACE: + if (!image->u.surface) + break; + + *natural_width_p = CLUTTER_UNITS_FROM_DEVICE(cairo_image_surface_get_width(image->u.surface)); + break; + case BIG_THEME_IMAGE_SVG: + { + RsvgDimensionData dimensions; + + if (!image->u.svg_handle) + return; + + rsvg_handle_get_dimensions(image->u.svg_handle, &dimensions); + *natural_width_p = CLUTTER_UNITS_FROM_DEVICE(dimensions.width); + break; + } + default: + break; + } + +} + +static void +big_theme_image_set_border_value(BigThemeImage *image, guint *old_value, const GValue *new_value) +{ + guint border_value; + + border_value = g_value_get_uint(new_value); + + if (*old_value != border_value) { + *old_value = border_value; + + big_theme_image_queue_render(image); + } +} + +static void +big_theme_image_set_filename(BigThemeImage *image, const char *filename) +{ + if (!filename) + return; + + if (g_str_has_suffix(filename, ".png") || + g_str_has_suffix(filename, ".PNG")) { + + image->type = BIG_THEME_IMAGE_SURFACE; + + image->u.surface = cairo_image_surface_create_from_png(filename); + + if (image->u.surface == NULL) { + g_warning("Error when loading PNG from file %s", filename); + } + } else if (g_str_has_suffix(filename, ".svg") || + g_str_has_suffix(filename, ".SVG")) { + + GError *error; + + error = NULL; + image->u.svg_handle = rsvg_handle_new_from_file(filename, &error); + + if (image->u.svg_handle == NULL) { + g_warning("Error when loading SVG from file %s: %s", filename, + error?error->message:"Error not set by RSVG"); + if (error) + g_error_free(error); + + return; + } + + image->type = BIG_THEME_IMAGE_SVG; + } else { + g_warning("%s: Unsupported file type", filename); + return; + } + + big_theme_image_queue_render(image); +} + +static cairo_surface_t * +create_surface_from_pixbuf(const GdkPixbuf *pixbuf) +{ + gint width = gdk_pixbuf_get_width (pixbuf); + gint height = gdk_pixbuf_get_height (pixbuf); + guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf); + int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf); + int n_channels = gdk_pixbuf_get_n_channels (pixbuf); + + guchar *cairo_pixels; + cairo_format_t format; + cairo_surface_t *surface; + static const cairo_user_data_key_t key; + + int j; + + if (n_channels == 3) + format = CAIRO_FORMAT_RGB24; + else + format = CAIRO_FORMAT_ARGB32; + + cairo_pixels = g_malloc (4 * width * height); + + surface = cairo_image_surface_create_for_data((unsigned char *)cairo_pixels, + format, + width, height, 4 * width); + + cairo_surface_set_user_data(surface, &key, + cairo_pixels, (cairo_destroy_func_t)g_free); + + for (j = height; j; j--) { + guchar *p = gdk_pixels; + guchar *q = cairo_pixels; + + if (n_channels == 3) { + guchar *end = p + 3 * width; + + while (p < end) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + q[0] = p[2]; + q[1] = p[1]; + q[2] = p[0]; +#else + q[1] = p[0]; + q[2] = p[1]; + q[3] = p[2]; +#endif + p += 3; + q += 4; + } + } else { + guchar *end = p + 4 * width; + guint t1,t2,t3; + +#define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END + + while (p < end) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + MULT(q[0], p[2], p[3], t1); + MULT(q[1], p[1], p[3], t2); + MULT(q[2], p[0], p[3], t3); + q[3] = p[3]; +#else + q[0] = p[3]; + MULT(q[1], p[0], p[3], t1); + MULT(q[2], p[1], p[3], t2); + MULT(q[3], p[2], p[3], t3); +#endif + + p += 4; + q += 4; + } +#undef MULT + } + + gdk_pixels += gdk_rowstride; + cairo_pixels += 4 * width; + } + + return surface; +} + +static void +big_theme_image_set_pixbuf(BigThemeImage *image, GdkPixbuf *pixbuf) +{ + if (!pixbuf) + return; + + image->type = BIG_THEME_IMAGE_SURFACE; + + image->u.surface = create_surface_from_pixbuf(pixbuf); + + g_assert(image->u.surface != NULL); + + big_theme_image_queue_render(image); +} + +static void +big_theme_image_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BigThemeImage *image; + + image = BIG_THEME_IMAGE(object); + + switch (prop_id) { + case PROP_BORDER_TOP: + big_theme_image_set_border_value(image, &image->border_top, value); + break; + + case PROP_BORDER_BOTTOM: + big_theme_image_set_border_value(image, &image->border_bottom, value); + break; + + case PROP_BORDER_LEFT: + big_theme_image_set_border_value(image, &image->border_left, value); + break; + + case PROP_BORDER_RIGHT: + big_theme_image_set_border_value(image, &image->border_right, value); + break; + + case PROP_FILENAME: + big_theme_image_set_filename(image, g_value_get_string(value)); + break; + + case PROP_PIXBUF: + big_theme_image_set_pixbuf(image, g_value_get_object(value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + + } +} + +static void +big_theme_image_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BigThemeImage *image; + + image = BIG_THEME_IMAGE(object); + + switch (prop_id) { + case PROP_BORDER_TOP: + g_value_set_uint(value, image->border_top); + break; + + case PROP_BORDER_BOTTOM: + g_value_set_uint(value, image->border_bottom); + break; + + case PROP_BORDER_LEFT: + g_value_set_uint(value, image->border_left); + break; + + case PROP_BORDER_RIGHT: + g_value_set_uint(value, image->border_right); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +big_theme_image_dispose(GObject *object) +{ + BigThemeImage *image; + + image = BIG_THEME_IMAGE(object); + + if (image->render_idle) { + g_source_remove(image->render_idle); + image->render_idle = 0; + } + + switch (image->type) { + case BIG_THEME_IMAGE_SVG: + if (image->u.svg_handle) { + g_object_unref(image->u.svg_handle); + image->u.svg_handle = NULL; + } + break; + case BIG_THEME_IMAGE_SURFACE: + if (image->u.surface) { + cairo_surface_destroy(image->u.surface); + image->u.surface = NULL; + } + break; + default: + break; + } + + if (G_OBJECT_CLASS(big_theme_image_parent_class)->dispose) + G_OBJECT_CLASS(big_theme_image_parent_class)->dispose(object); + +} + + +static void +big_theme_image_class_init(BigThemeImageClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + gobject_class->dispose = big_theme_image_dispose; + gobject_class->set_property = big_theme_image_set_property; + gobject_class->get_property = big_theme_image_get_property; + + actor_class->allocate = big_theme_image_allocate; + actor_class->get_preferred_width = big_theme_image_get_preferred_width; + actor_class->get_preferred_height = big_theme_image_get_preferred_height; + actor_class->paint = big_theme_image_paint; + + g_object_class_install_property + (gobject_class, + PROP_BORDER_TOP, + g_param_spec_uint("border-top", + "Border top", + "Top dimension of the image border " + "(none-scaled part)", + 0, G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_BORDER_BOTTOM, + g_param_spec_uint("border-bottom", + "Border bottom", + "Bottom dimension of the image border " + "(none-scaled part)", + 0, G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_BORDER_LEFT, + g_param_spec_uint("border-left", + "Border left", + "Left dimension of the image border " + "(none-scaled part)", + 0, G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_BORDER_RIGHT, + g_param_spec_uint("border-right", + "Border right", + "Right dimension of the image border " + "(none-scaled part)", + 0, G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_FILENAME, + g_param_spec_string("filename", + "Filename", + "Name of the file", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_PIXBUF, + g_param_spec_object("pixbuf", + "Pixbuf", + "Pixbuf of the image", + GDK_TYPE_PIXBUF, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); +} + +static void +big_theme_image_init(BigThemeImage *image) +{ +} + +ClutterActor * +big_theme_image_new_from_file(const char *filename, + guint border_top, + guint border_bottom, + guint border_left, + guint border_right) +{ + ClutterActor *actor; + + actor = g_object_new(BIG_TYPE_THEME_IMAGE, + /* FIXME ClutterCairo requires creating a bogus + * surface with nonzero size + */ + "surface-width", 1, + "surface-height", 1, + "filename", filename, + "border-top", border_top, + "border-bottom", border_bottom, + "border-left", border_left, + "border-right", border_right, + NULL); + + return actor; +} + +ClutterActor * +big_theme_image_new_from_pixbuf(GdkPixbuf *pixbuf, + guint border_top, + guint border_bottom, + guint border_left, + guint border_right) +{ + ClutterActor *actor; + + actor = g_object_new(BIG_TYPE_THEME_IMAGE, + /* FIXME ClutterCairo requires creating a bogus + * surface with nonzero size + */ + "surface-width", 1, + "surface-height", 1, + "pixbuf", pixbuf, + "border-top", border_top, + "border-bottom", border_bottom, + "border-left", border_left, + "border-right", border_right, + NULL); + + return actor; +} + diff --git a/src/big/theme-image.h b/src/big/theme-image.h new file mode 100644 index 000000000..e124edc6d --- /dev/null +++ b/src/big/theme-image.h @@ -0,0 +1,39 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* Copyright 2008 litl, LLC. All Rights Reserved. */ + +#ifndef __BIG_THEME_IMAGE_H__ +#define __BIG_THEME_IMAGE_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define BIG_TYPE_THEME_IMAGE (big_theme_image_get_type ()) +#define BIG_THEME_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BIG_TYPE_THEME_IMAGE, BigThemeImage)) +#define BIG_THEME_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BIG_TYPE_THEME_IMAGE, BigThemeImageClass)) +#define BIG_IS_THEME_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BIG_TYPE_THEME_IMAGE)) +#define BIG_IS_THEME_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BIG_TYPE_THEME_IMAGE)) +#define BIG_THEME_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BIG_TYPE_THEME_IMAGE, BigThemeImageClass)) + +typedef struct BigThemeImage BigThemeImage; +typedef struct BigThemeImageClass BigThemeImageClass; + +GType big_theme_image_get_type (void) G_GNUC_CONST; + +ClutterActor * big_theme_image_new_from_file (const gchar *filename, + guint border_top, + guint border_bottom, + guint border_left, + guint border_right); + +ClutterActor * big_theme_image_new_from_pixbuf (GdkPixbuf *pixbuf, + guint border_top, + guint border_bottom, + guint border_left, + guint border_right); + +G_END_DECLS + +#endif /* __BIG_THEME_IMAGE_H__ */