static void mex_tile_init (MexTile *self) { MexTilePrivate *priv = self->priv = TILE_PRIVATE (self); const ClutterColor opaque = { 0x00, 0x00, 0x00, 0x00 }; ClutterEffect *fade; /* create a template material for the header background from which cheap * copies can be made for each instance */ if (G_UNLIKELY (!template_material)) template_material = cogl_material_new (); priv->material = cogl_material_copy (template_material); /* layout for primary and secondary labels */ priv->box_layout = mx_box_layout_new (); mx_box_layout_set_spacing (MX_BOX_LAYOUT (priv->box_layout), 12); /* add fade effect to the box layout */ fade = (ClutterEffect*) mx_fade_effect_new (); mx_fade_effect_set_border (MX_FADE_EFFECT (fade), 0, 50, 0, 0); mx_fade_effect_set_color (MX_FADE_EFFECT (fade), &opaque); clutter_actor_add_effect_with_name (priv->box_layout, "fade", fade); clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (fade), TRUE); clutter_actor_push_internal (CLUTTER_ACTOR (self)); clutter_actor_set_parent (priv->box_layout, CLUTTER_ACTOR (self)); clutter_actor_pop_internal (CLUTTER_ACTOR (self)); priv->label = clutter_text_new (); priv->secondary_label = clutter_text_new (); clutter_actor_set_opacity (priv->secondary_label, 128); clutter_container_add (CLUTTER_CONTAINER (priv->box_layout), priv->label, priv->secondary_label, NULL); priv->header_visible = TRUE; priv->timeline = clutter_timeline_new (DURATION); priv->important_alpha = clutter_alpha_new_full (priv->timeline, CLUTTER_EASE_OUT_QUAD); g_signal_connect_object (priv->timeline, "new-frame", G_CALLBACK (mex_tile_important_new_frame_cb), self, 0); g_signal_connect_object (priv->timeline, "completed", G_CALLBACK (mex_tile_timeline_completed_cb), self, 0); g_signal_connect (self, "style-changed", G_CALLBACK (mex_tile_style_changed_cb), NULL); g_signal_connect (self, "actor-added", G_CALLBACK (mex_tile_actor_added), NULL); g_signal_connect (self, "actor-removed", G_CALLBACK (mex_tile_actor_removed), NULL); }
static void mx_fade_effect_post_paint (ClutterEffect *effect) { MxFadeEffectPrivate *priv = MX_FADE_EFFECT (effect)->priv; if (!priv->freeze_update) CLUTTER_EFFECT_CLASS (mx_fade_effect_parent_class)->post_paint (effect); else { CoglMatrix modelview; ClutterActor *actor, *stage; actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); stage = clutter_actor_get_stage (actor); /* Set up the draw matrix so we draw the offscreen texture at the * absolute coordinates of the actor-box. We need to do this to * avoid transforming by the actor matrix twice, as when it's drawn * into the offscreen surface, it'll already be transformed. */ cogl_push_matrix (); cogl_matrix_init_identity (&modelview); CLUTTER_ACTOR_CLASS (G_OBJECT_GET_CLASS (stage))-> apply_transform (stage, &modelview); cogl_matrix_translate (&modelview, priv->x_offset, priv->y_offset, 0.f); cogl_set_modelview_matrix (&modelview); clutter_offscreen_effect_paint_target (CLUTTER_OFFSCREEN_EFFECT (effect)); cogl_pop_matrix (); } }
static gboolean mx_fade_effect_pre_paint (ClutterEffect *effect) { MxFadeEffectPrivate *priv = MX_FADE_EFFECT (effect)->priv; if (!priv->freeze_update) { return CLUTTER_EFFECT_CLASS (mx_fade_effect_parent_class)-> pre_paint (effect); } else { ClutterActorBox box; ClutterActor *actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); /* Store the stage coordinates of the actor for when we post-paint */ clutter_actor_get_paint_box (actor, &box); clutter_actor_box_get_origin (&box, &priv->x_offset, &priv->y_offset); /* Connect to the paint signal so we can block it */ priv->blocked_id = g_signal_connect (actor, "paint", G_CALLBACK (mx_fade_effect_paint_cb), effect); return TRUE; } }
static void mx_label_label_changed_cb (MxLabel *label) { MxLabelPrivate *priv = label->priv; /* Enable updating of the off-screen texture */ _mx_fade_effect_set_freeze_update (MX_FADE_EFFECT (priv->fade_effect), FALSE); }
static void mx_fade_effect_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { MxFadeEffect *effect = MX_FADE_EFFECT (object); MxFadeEffectPrivate *priv = effect->priv; switch (property_id) { case PROP_BOUNDS_X: priv->x = g_value_get_int (value); break; case PROP_BOUNDS_Y: priv->y = g_value_get_int (value); break; case PROP_BOUNDS_WIDTH: priv->bounds_width = g_value_get_uint (value); break; case PROP_BOUNDS_HEIGHT: priv->bounds_height = g_value_get_uint (value); break; case PROP_BORDER_TOP: priv->border[0] = g_value_get_uint (value); break; case PROP_BORDER_RIGHT: priv->border[1] = g_value_get_uint (value); break; case PROP_BORDER_BOTTOM: priv->border[2] = g_value_get_uint (value); break; case PROP_BORDER_LEFT: priv->border[3] = g_value_get_uint (value); break; case PROP_COLOR: priv->color = *(clutter_value_get_color (value)); break; case PROP_FREEZE_UPDATE: priv->freeze_update = g_value_get_boolean (value); return; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); return; } priv->update_vbo = TRUE; }
static void mx_fade_effect_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { MxFadeEffectPrivate *priv = MX_FADE_EFFECT (object)->priv; switch (property_id) { case PROP_BOUNDS_X: g_value_set_int (value, priv->x); break; case PROP_BOUNDS_Y: g_value_set_int (value, priv->y); break; case PROP_BOUNDS_WIDTH: g_value_set_uint (value, priv->bounds_width); break; case PROP_BOUNDS_HEIGHT: g_value_set_uint (value, priv->bounds_height); break; case PROP_BORDER_TOP: g_value_set_uint (value, priv->border[0]); break; case PROP_BORDER_RIGHT: g_value_set_uint (value, priv->border[1]); break; case PROP_BORDER_BOTTOM: g_value_set_uint (value, priv->border[2]); break; case PROP_BORDER_LEFT: g_value_set_uint (value, priv->border[3]); break; case PROP_COLOR: clutter_value_set_color (value, &priv->color); break; case PROP_FREEZE_UPDATE: g_value_set_boolean (value, priv->freeze_update); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } }
static void mx_label_paint (ClutterActor *actor) { MxLabelPrivate *priv = MX_LABEL (actor)->priv; ClutterActorClass *parent_class; parent_class = CLUTTER_ACTOR_CLASS (mx_label_parent_class); parent_class->paint (actor); clutter_actor_paint (priv->label); _mx_fade_effect_set_freeze_update (MX_FADE_EFFECT (priv->fade_effect), TRUE); }
static void mx_fade_effect_paint_target (ClutterOffscreenEffect *effect) { guint8 opacity; CoglColor color; ClutterActor *actor; CoglMaterial *material = clutter_offscreen_effect_get_target (effect); MxFadeEffect *self = MX_FADE_EFFECT (effect); MxFadeEffectPrivate *priv = self->priv; if (priv->update_vbo) mx_fade_effect_update_vbo (self); if (!priv->vbo || !priv->indices || !material) return; /* Set the blend string if the material has changed so we can blend with * the paint opacity. */ if (material != priv->old_material) { GError *error = NULL; priv->old_material = material; if (!cogl_material_set_layer_combine (material, 1, "RGBA = MODULATE(PREVIOUS,CONSTANT)", &error)) { g_warning (G_STRLOC ": Error setting layer combine blend string: %s", error->message); g_error_free (error); } } /* Set the layer-combine constant so the texture is blended with the paint * opacity when painted. */ actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); opacity = clutter_actor_get_paint_opacity (actor); cogl_color_init_from_4ub (&color, opacity, opacity, opacity, opacity); cogl_material_set_layer_combine_constant (material, 1, &color); /* Draw the texture */ cogl_set_source (material); cogl_vertex_buffer_draw_elements (priv->vbo, COGL_VERTICES_MODE_TRIANGLES, priv->indices, 0, (priv->n_quads * 4) - 1, 0, priv->n_quads * 6); }
static CoglHandle mx_fade_effect_create_texture (ClutterOffscreenEffect *effect, gfloat width, gfloat height) { MxFadeEffectPrivate *priv = MX_FADE_EFFECT (effect)->priv; priv->width = width; priv->height = height; priv->update_vbo = TRUE; return CLUTTER_OFFSCREEN_EFFECT_CLASS (mx_fade_effect_parent_class)-> create_texture (effect, width, height); }
static void notify_vertical_changed_cb (MxAdjustment *adjustment, MexMediaControls *controls) { MexMediaControlsPrivate *priv = controls->priv; gint bottom; gdouble value, max, page_size; max = mx_adjustment_get_upper (adjustment); value = mx_adjustment_get_value (adjustment); page_size = mx_adjustment_get_page_size (adjustment); bottom = MIN (50, (max - value - page_size)); mx_fade_effect_set_border (MX_FADE_EFFECT (priv->vertical_effect), 0, 0, bottom, 0); }
static void notify_horizontal_changed_cb (MxAdjustment *adjustment, MexMediaControls *controls) { MexMediaControlsPrivate *priv = controls->priv; gint left, right; gdouble value, min, max, page_size; min = mx_adjustment_get_lower (adjustment); max = mx_adjustment_get_upper (adjustment); value = mx_adjustment_get_value (adjustment); page_size = mx_adjustment_get_page_size (adjustment); left = MIN (136, (value - min)); right = MIN (136, (max - value - page_size)); mx_fade_effect_set_border (MX_FADE_EFFECT (priv->horizontal_effect), 0, right, 0, left); }
static void mx_label_fade_new_frame_cb (ClutterTimeline *timeline, gint msecs, MxLabel *self) { guint8 a; ClutterColor color; MxLabelPrivate *priv = self->priv; a = (1.0 - clutter_alpha_get_alpha (priv->fade_alpha)) * 255; color.red = a; color.green = a; color.blue = a; color.alpha = a; mx_fade_effect_set_color (MX_FADE_EFFECT (priv->fade_effect), &color); clutter_actor_queue_redraw (CLUTTER_ACTOR (self)); }
static void mx_fade_effect_dispose (GObject *object) { MxFadeEffectPrivate *priv = MX_FADE_EFFECT (object)->priv; if (priv->vbo) { cogl_handle_unref (priv->vbo); priv->vbo = NULL; } if (priv->blocked_id) { ClutterActor *actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (object)); g_signal_handler_disconnect (actor, priv->blocked_id); priv->blocked_id = 0; } G_OBJECT_CLASS (mx_fade_effect_parent_class)->dispose (object); }
static void mx_label_init (MxLabel *label) { MxLabelPrivate *priv; const ClutterColor opaque = { 0xff, 0xff, 0xff, 0xff }; label->priv = priv = MX_LABEL_GET_PRIVATE (label); priv->label = g_object_new (CLUTTER_TYPE_TEXT, "ellipsize", PANGO_ELLIPSIZE_END, NULL); clutter_actor_set_parent (priv->label, CLUTTER_ACTOR (label)); priv->fade_effect = mx_fade_effect_new (); mx_fade_effect_set_color (MX_FADE_EFFECT (priv->fade_effect), &opaque); clutter_actor_add_effect (priv->label, priv->fade_effect); clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (priv->fade_effect), FALSE); g_signal_connect (label, "style-changed", G_CALLBACK (mx_label_style_changed), NULL); g_signal_connect (priv->label, "notify::single-line-mode", G_CALLBACK (mx_label_single_line_mode_cb), label); g_signal_connect_swapped (priv->label, "queue-redraw", G_CALLBACK (mx_label_label_changed_cb), label); priv->fade_timeline = clutter_timeline_new (250); priv->fade_alpha = clutter_alpha_new_full (priv->fade_timeline, CLUTTER_EASE_OUT_QUAD); g_signal_connect (priv->fade_timeline, "new-frame", G_CALLBACK (mx_label_fade_new_frame_cb), label); g_signal_connect (priv->fade_timeline, "started", G_CALLBACK (mx_label_fade_started_cb), label); g_signal_connect (priv->fade_timeline, "completed", G_CALLBACK (mx_label_fade_completed_cb), label); }
static void mx_label_font_description_cb (ClutterText *text, GParamSpec *pspec, MxLabel *self) { PangoFontDescription *font; MxLabelPrivate *priv = self->priv; /* Find out the em-width - code pretty much copied from Clutter, * clutter-backend.c, get_units_per_em () */ font = clutter_text_get_font_description (text); if (font) { gint i_dpi; gdouble dpi; gdouble font_size = 0; gint pango_size = pango_font_description_get_size (font); ClutterSettings *settings = clutter_settings_get_default (); g_object_get (G_OBJECT (settings), "font-dpi", &i_dpi, NULL); dpi = i_dpi / 1024.0; if (pango_font_description_get_size_is_absolute (font)) font_size = pango_size / PANGO_SCALE; else font_size = pango_size / PANGO_SCALE * dpi / 96.f; priv->em_width = (1.2f * font_size) * dpi / 96.f; mx_fade_effect_set_border (MX_FADE_EFFECT (priv->fade_effect), 0, priv->em_width * 5, 0, 0); } }
static void mex_tile_allocate (ClutterActor *actor, const ClutterActorBox *box, ClutterAllocationFlags flags) { MxPadding padding; ClutterActorBox child_box; gfloat available_width, available_height; ClutterEffect *fade; MexTilePrivate *priv = MEX_TILE (actor)->priv; CLUTTER_ACTOR_CLASS (mex_tile_parent_class)->allocate (actor, box, flags); mx_widget_get_padding (MX_WIDGET (actor), &padding); available_width = box->x2 - box->x1 - padding.left - padding.right; available_height = box->y2 - box->y1 - padding.top - padding.bottom; if (priv->child) { gfloat child_width, full_width, full_height; clutter_actor_get_preferred_size (priv->child, NULL, NULL, &full_width, &full_height); child_box.y1 = padding.top; if (clutter_alpha_get_alpha (priv->important_alpha) < 0.5) { child_width = full_width * (available_height / full_height); if (child_width > available_width) child_width = available_width; child_box.y2 = child_box.y1 + available_height; /* When we're in unimportant state, make sure the label * doesn't overlap the image. */ if (available_height < full_height) available_width -= child_width * ((0.5 - clutter_alpha_get_alpha (priv->important_alpha)) * 2); } else { child_width = available_width; clutter_actor_set_clip_to_allocation ( actor, (full_height > available_height)); child_box.y2 = child_box.y1 + full_height; } child_box.x2 = box->x2 - box->x1 - padding.right; child_box.x1 = child_box.x2 - child_width; mx_allocate_align_fill (priv->child, &child_box, MX_ALIGN_MIDDLE, MX_ALIGN_MIDDLE, FALSE, FALSE); clutter_actor_allocate (priv->child, &child_box, flags); } /* Allocate Header */ if (priv->header_visible) { gfloat icon1_w, icon1_h, icon2_w, icon2_h, label_h, label_w, header_h; gfloat middle_w; if (priv->header_padding) { padding.top += priv->header_padding->top; padding.right += priv->header_padding->right; padding.bottom += priv->header_padding->bottom; padding.left += priv->header_padding->left; } clutter_actor_get_preferred_size (priv->box_layout, NULL, NULL, &label_w, &label_h); if (priv->icon1) clutter_actor_get_preferred_size (priv->icon1, NULL, NULL, &icon1_w, &icon1_h); else icon1_h = icon1_w = 0; if (priv->icon2) clutter_actor_get_preferred_size (priv->icon2, NULL, NULL, &icon2_w, &icon2_h); else icon2_h = icon2_w = 0; header_h = MAX (icon1_h, MAX (icon2_h, label_h)); /* primary icon */ if (priv->icon1) { child_box.y1 = padding.top + (header_h / 2.0) - (icon1_h / 2.0); child_box.x1 = padding.left; child_box.y2 = child_box.y1 + icon1_h; child_box.x2 = child_box.x1 + icon1_w; clutter_actor_allocate (priv->icon1, &child_box, flags); child_box.x1 += icon1_w + 8; } else child_box.x1 = padding.left; /* label */ child_box.x2 = child_box.x1 + label_w; child_box.y1 = (int) (padding.top + (header_h / 2.0) - (label_h / 2.0)); child_box.y2 = child_box.y1 + label_h; fade = clutter_actor_get_effect (priv->box_layout, "fade"); middle_w = available_width - icon1_w - icon2_w; if (priv->header_padding) middle_w -= priv->header_padding->left + priv->header_padding->right; clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (fade), !(middle_w > label_w)); mx_fade_effect_set_bounds (MX_FADE_EFFECT (fade), 0, 0, middle_w, 0); clutter_actor_allocate (priv->box_layout, &child_box, flags); /* secondary icon */ if (priv->icon2) { child_box.x2 = (box->x2 - box->x1) - padding.right; child_box.x1 = child_box.x2 - icon2_w; child_box.y1 = padding.top + (header_h / 2.0) - (icon2_h / 2.0); child_box.y2 = child_box.y1 + icon2_h; clutter_actor_allocate (priv->icon2, &child_box, flags); } priv->header_height = header_h; if (priv->header_padding) priv->header_height += priv->header_padding->top + priv->header_padding->bottom; } }
static void mx_label_allocate (ClutterActor *actor, const ClutterActorBox *box, ClutterAllocationFlags flags) { MxLabelPrivate *priv = MX_LABEL (actor)->priv; gboolean label_did_fade = priv->label_should_fade; ClutterActorClass *parent_class; ClutterActorBox child_box; gboolean x_fill, y_fill; gfloat avail_width; parent_class = CLUTTER_ACTOR_CLASS (mx_label_parent_class); parent_class->allocate (actor, box, flags); mx_widget_get_available_area (MX_WIDGET (actor), box, &child_box); avail_width = child_box.x2 - child_box.x1; /* The default behaviour of ClutterText is to align to the * top-left when it gets more space than is needed. Because * of this behaviour, if we're aligning to the left, we can * assign all our horizontal space to the label without * measuring it (i.e. x-fill), and the same applies for * aligning to the top and vertical space. */ x_fill = (priv->x_align == MX_ALIGN_START) ? TRUE : FALSE; y_fill = (priv->y_align == MX_ALIGN_START) ? TRUE : FALSE; mx_allocate_align_fill (priv->label, &child_box, priv->x_align, priv->y_align, x_fill, y_fill); priv->label_should_fade = FALSE; if (priv->fade_out) { /* If we're fading out, make sure the label has its full width * allocated. This ensures that the offscreen effect has the full * label inside its texture. */ gfloat label_width; clutter_actor_get_preferred_width (priv->label, -1, NULL, &label_width); if (label_width > avail_width) { priv->label_should_fade = TRUE; child_box.x2 = child_box.x1 + label_width; } mx_fade_effect_set_bounds (MX_FADE_EFFECT (priv->fade_effect), 0, 0, MIN (label_width, avail_width), 0); } /* Allocate the label */ clutter_actor_allocate (priv->label, &child_box, flags); if (priv->show_tooltip) { PangoLayout *layout; const gchar *text; layout = clutter_text_get_layout (CLUTTER_TEXT (priv->label)); if (pango_layout_is_ellipsized (layout)) text = clutter_text_get_text (CLUTTER_TEXT (priv->label)); else text = NULL; mx_widget_set_tooltip_text (MX_WIDGET (actor), text); } /* Animate in/out the faded end of the label */ if (label_did_fade != priv->label_should_fade) { /* Begin/reverse the fading timeline when necessary */ if (priv->label_should_fade) clutter_timeline_set_direction (priv->fade_timeline, CLUTTER_TIMELINE_FORWARD); else clutter_timeline_set_direction (priv->fade_timeline, CLUTTER_TIMELINE_BACKWARD); if (!clutter_timeline_is_playing (priv->fade_timeline)) clutter_timeline_rewind (priv->fade_timeline); clutter_timeline_start (priv->fade_timeline); } }