/* * Copyright 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Conor Curran * Mirco Müller * Andrea Cimitan * Lars Uebernickel */ #include "config.h" #include "idoplaybackmenuitem.h" #include #include typedef enum { STATE_PAUSED, STATE_PLAYING, STATE_LAUNCHING } State; typedef enum { BUTTON_NONE, BUTTON_PREVIOUS, BUTTON_PLAYPAUSE, BUTTON_NEXT } Button; typedef GtkMenuItemClass IdoPlaybackMenuItemClass; struct _IdoPlaybackMenuItem { GtkMenuItem parent; State current_state; Button cur_pushed_button; Button cur_hover_button; gboolean has_focus; gboolean keyboard_activated; /* TRUE if the current button was activated with a key */ GActionGroup *action_group; gchar *play_action; gchar *next_action; gchar *prev_action; }; G_DEFINE_TYPE (IdoPlaybackMenuItem, ido_playback_menu_item, GTK_TYPE_MENU_ITEM); static gboolean ido_playback_menu_item_draw (GtkWidget* button, cairo_t *cr); static void ido_playback_menu_item_dispose (GObject *object) { IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (object); if (item->action_group) { g_signal_handlers_disconnect_by_data (item->action_group, item); g_clear_object (&item->action_group); } G_OBJECT_CLASS (ido_playback_menu_item_parent_class)->dispose (object); } static void ido_playback_menu_item_finalize (GObject *object) { IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (object); g_free (item->play_action); g_free (item->next_action); g_free (item->prev_action); G_OBJECT_CLASS (ido_playback_menu_item_parent_class)->finalize (object); } static Button ido_playback_menu_item_get_button_at_pos (gint x, gint y) { /* 57 101 143 187 * 5 +------+ * 12 +-----+ +-----+ * |prev play next| * 40 +-----+ +-----+ * 47 +------+ */ if (x > 57 && x < 102 && y > 12 && y < 40) return BUTTON_PREVIOUS; if (x > 101 && x < 143 && y > 5 && y < 47) return BUTTON_PLAYPAUSE; if (x > 142 && x < 187 && y > 12 && y < 40) return BUTTON_NEXT; return BUTTON_NONE; } static gboolean ido_playback_menu_item_parent_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { IdoPlaybackMenuItem *self = user_data; /* only listen to events when the playback menu item is selected */ if (!self->has_focus) return FALSE; switch (event->keyval) { case GDK_KEY_Left: self->cur_pushed_button = BUTTON_PREVIOUS; if (self->action_group && self->prev_action) g_action_group_activate_action (self->action_group, self->prev_action, NULL); break; case GDK_KEY_Right: self->cur_pushed_button = BUTTON_NEXT; if (self->action_group && self->next_action) g_action_group_activate_action (self->action_group, self->next_action, NULL); break; case GDK_KEY_space: self->cur_pushed_button = BUTTON_PLAYPAUSE; if (self->action_group && self->play_action) g_action_group_activate_action (self->action_group, self->play_action, NULL); break; default: self->cur_pushed_button = BUTTON_NONE; } if (self->cur_pushed_button != BUTTON_NONE) { self->keyboard_activated = TRUE; gtk_widget_queue_draw (widget); return TRUE; } return FALSE; } static gboolean ido_playback_menu_item_parent_key_release_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { IdoPlaybackMenuItem *self = user_data; switch (event->keyval) { case GDK_KEY_Left: case GDK_KEY_Right: case GDK_KEY_space: self->cur_pushed_button = BUTTON_NONE; self->keyboard_activated = FALSE; gtk_widget_queue_draw (widget); break; } return FALSE; } static void ido_playback_menu_item_parent_set (GtkWidget *widget, GtkWidget *old_parent) { GtkWidget *parent; /* Menus don't pass key events to their children. This works around * that by listening to key events on the parent widget. */ if (old_parent) { g_signal_handlers_disconnect_by_func (old_parent, ido_playback_menu_item_parent_key_press_event, widget); g_signal_handlers_disconnect_by_func (old_parent, ido_playback_menu_item_parent_key_release_event, widget); } parent = gtk_widget_get_parent (widget); if (parent) { g_signal_connect (parent, "key-press-event", G_CALLBACK (ido_playback_menu_item_parent_key_press_event), widget); g_signal_connect (parent, "key-release-event", G_CALLBACK (ido_playback_menu_item_parent_key_release_event), widget); } } static void ido_playback_menu_item_select (GtkMenuItem *item) { IdoPlaybackMenuItem *self = IDO_PLAYBACK_MENU_ITEM (item); self->has_focus = TRUE; GTK_MENU_ITEM_CLASS (ido_playback_menu_item_parent_class)->select (item); } static void ido_playback_menu_item_deselect (GtkMenuItem *item) { IdoPlaybackMenuItem *self = IDO_PLAYBACK_MENU_ITEM (item); self->has_focus = FALSE; GTK_MENU_ITEM_CLASS (ido_playback_menu_item_parent_class)->deselect (item); } static gboolean ido_playback_menu_item_button_press_event (GtkWidget *menuitem, GdkEventButton *event) { IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); item->cur_pushed_button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); gtk_widget_queue_draw (menuitem); return TRUE; } static gboolean ido_playback_menu_item_button_release_event (GtkWidget *menuitem, GdkEventButton *event) { IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); Button button; button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); if (button != item->cur_pushed_button) button = BUTTON_NONE; switch (button) { case BUTTON_NONE: break; case BUTTON_PREVIOUS: if (item->action_group && item->prev_action) g_action_group_activate_action (item->action_group, item->prev_action, NULL); break; case BUTTON_NEXT: if (item->action_group && item->next_action) g_action_group_activate_action (item->action_group, item->next_action, NULL); break; case BUTTON_PLAYPAUSE: if (item->action_group && item->play_action) g_action_group_activate_action (item->action_group, item->play_action, NULL); break; } item->cur_pushed_button = BUTTON_NONE; gtk_widget_queue_draw (menuitem); return TRUE; } static gboolean ido_playback_menu_item_motion_notify_event (GtkWidget *menuitem, GdkEventMotion *event) { IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); item->cur_hover_button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); gtk_widget_queue_draw (menuitem); return TRUE; } static gboolean ido_playback_menu_item_leave_notify_event (GtkWidget *menuitem, GdkEventCrossing *event) { IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); item->cur_pushed_button = BUTTON_NONE; item->cur_hover_button = BUTTON_NONE; gtk_widget_queue_draw (GTK_WIDGET(menuitem)); return TRUE; } static void ido_playback_menu_item_class_init (IdoPlaybackMenuItemClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkMenuItemClass *menuitem_class = GTK_MENU_ITEM_CLASS (klass); gobject_class->dispose = ido_playback_menu_item_dispose; gobject_class->finalize = ido_playback_menu_item_finalize; widget_class->button_press_event = ido_playback_menu_item_button_press_event; widget_class->button_release_event = ido_playback_menu_item_button_release_event; widget_class->motion_notify_event = ido_playback_menu_item_motion_notify_event; widget_class->leave_notify_event = ido_playback_menu_item_leave_notify_event; widget_class->parent_set = ido_playback_menu_item_parent_set; widget_class->draw = ido_playback_menu_item_draw; menuitem_class->select = ido_playback_menu_item_select; menuitem_class->deselect = ido_playback_menu_item_deselect; } static void ido_playback_menu_item_init (IdoPlaybackMenuItem *self) { gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), GTK_STYLE_CLASS_SPINNER); gtk_widget_set_size_request (GTK_WIDGET (self), 200, 43); } static void ido_playback_menu_item_set_state (IdoPlaybackMenuItem *self, State state) { self->current_state = state; if (self->current_state == STATE_LAUNCHING) gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE, FALSE); else gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); gtk_widget_queue_draw (GTK_WIDGET (self)); } static void ido_playback_menu_item_set_state_from_string (IdoPlaybackMenuItem *self, const gchar *state) { g_return_if_fail (state != NULL); if (g_str_equal (state, "Playing")) ido_playback_menu_item_set_state (self, STATE_PLAYING); else if (g_str_equal (state, "Launching")) ido_playback_menu_item_set_state (self, STATE_LAUNCHING); else /* "Paused" and fallback */ ido_playback_menu_item_set_state (self, STATE_PAUSED); } static void ido_playback_menu_item_action_added (GActionGroup *action_group, const gchar *action_name, gpointer user_data) { IdoPlaybackMenuItem *self = user_data; if (self->play_action && g_str_equal (action_name, self->play_action)) { GVariant *state; state = g_action_group_get_action_state (action_group, self->play_action); if (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)) ido_playback_menu_item_set_state_from_string (self, g_variant_get_string (state, NULL)); g_variant_unref (state); } } static void ido_playback_menu_item_action_removed (GActionGroup *action_group, const gchar *action_name, gpointer user_data) { IdoPlaybackMenuItem *self = user_data; if (self->play_action && g_str_equal (action_name, self->play_action)) ido_playback_menu_item_set_state (self, STATE_PAUSED); } static void ido_playback_menu_item_action_state_changed (GActionGroup *action_group, const gchar *action_name, GVariant *value, gpointer user_data) { IdoPlaybackMenuItem *self = user_data; g_return_if_fail (action_name != NULL); if (self->play_action && g_str_equal (action_name, self->play_action)) { if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) ido_playback_menu_item_set_state_from_string (self, g_variant_get_string (value, NULL)); } } GtkMenuItem * ido_playback_menu_item_new_from_model (GMenuItem *item, GActionGroup *actions) { IdoPlaybackMenuItem *widget; widget = g_object_new (IDO_TYPE_PLAYBACK_MENU_ITEM, NULL); widget->action_group = g_object_ref (actions); g_signal_connect (actions, "action-state-changed", G_CALLBACK (ido_playback_menu_item_action_state_changed), widget); g_signal_connect (actions, "action-added", G_CALLBACK (ido_playback_menu_item_action_added), widget); g_signal_connect (actions, "action-removed", G_CALLBACK (ido_playback_menu_item_action_removed), widget); g_menu_item_get_attribute (item, "x-canonical-play-action", "s", &widget->play_action); g_menu_item_get_attribute (item, "x-canonical-next-action", "s", &widget->next_action); g_menu_item_get_attribute (item, "x-canonical-previous-action", "s", &widget->prev_action); if (widget->play_action && g_action_group_has_action (actions, widget->play_action)) ido_playback_menu_item_action_added (actions, widget->play_action, widget); return GTK_MENU_ITEM (widget); } /* * Drawing */ #define RECT_WIDTH 130.0f #define Y 7.0f #define X 70.0f #define INNER_RADIUS 12.5 #define MIDDLE_RADIUS 13.0f #define OUTER_RADIUS 14.5f #define CIRCLE_RADIUS 21.0f #define PREV_WIDTH 25.0f #define PREV_HEIGHT 17.0f #define NEXT_WIDTH 25.0f //PREV_WIDTH #define NEXT_HEIGHT 17.0f //PREV_HEIGHT #define TRI_WIDTH 11.0f #define TRI_HEIGHT 13.0f #define TRI_OFFSET 6.0f #define PREV_X 68.0f #define PREV_Y 13.0f #define NEXT_X 146.0f #define NEXT_Y 13.0f //prev_y #define PAUSE_WIDTH 21.0f #define PAUSE_HEIGHT 27.0f #define BAR_WIDTH 4.5f #define BAR_HEIGHT 24.0f #define BAR_OFFSET 10.0f #define PAUSE_X 111.0f #define PAUSE_Y 7.0f #define PLAY_WIDTH 28.0f #define PLAY_HEIGHT 29.0f #define PLAY_PADDING 5.0f #define INNER_START_SHADE 0.98 #define INNER_END_SHADE 0.98 #define MIDDLE_START_SHADE 1.0 #define MIDDLE_END_SHADE 1.0 #define OUTER_START_SHADE 0.75 #define OUTER_END_SHADE 1.3 #define SHADOW_BUTTON_SHADE 0.8 #define OUTER_PLAY_START_SHADE 0.7 #define OUTER_PLAY_END_SHADE 1.38 #define BUTTON_START_SHADE 1.1 #define BUTTON_END_SHADE 0.9 #define BUTTON_SHADOW_SHADE 0.8 #define INNER_COMPRESSED_START_SHADE 1.0 #define INNER_COMPRESSED_END_SHADE 1.0 typedef struct { double r; double g; double b; } CairoColorRGB; static void draw_gradient (cairo_t* cr, double x, double y, double w, double r, double* rgba_start, double* rgba_end) { cairo_pattern_t* pattern = NULL; cairo_move_to (cr, x, y); cairo_line_to (cr, x + w - 2.0f * r, y); cairo_arc (cr, x + w - 2.0f * r, y + r, r, -90.0f * G_PI / 180.0f, 90.0f * G_PI / 180.0f); cairo_line_to (cr, x, y + 2.0f * r); cairo_arc (cr, x, y + r, r, 90.0f * G_PI / 180.0f, 270.0f * G_PI / 180.0f); cairo_close_path (cr); pattern = cairo_pattern_create_linear (x, y, x, y + 2.0f * r); cairo_pattern_add_color_stop_rgba (pattern, 0.0f, rgba_start[0], rgba_start[1], rgba_start[2], rgba_start[3]); cairo_pattern_add_color_stop_rgba (pattern, 1.0f, rgba_end[0], rgba_end[1], rgba_end[2], rgba_end[3]); cairo_set_source (cr, pattern); cairo_fill (cr); cairo_pattern_destroy (pattern); } static void draw_circle (cairo_t* cr, double x, double y, double r, double* rgba_start, double* rgba_end) { cairo_pattern_t* pattern = NULL; cairo_move_to (cr, x, y); cairo_arc (cr, x + r, y + r, r, 0.0f * G_PI / 180.0f, 360.0f * G_PI / 180.0f); pattern = cairo_pattern_create_linear (x, y, x, y + 2.0f * r); cairo_pattern_add_color_stop_rgba (pattern, 0.0f, rgba_start[0], rgba_start[1], rgba_start[2], rgba_start[3]); cairo_pattern_add_color_stop_rgba (pattern, 1.0f, rgba_end[0], rgba_end[1], rgba_end[2], rgba_end[3]); cairo_set_source (cr, pattern); cairo_fill (cr); cairo_pattern_destroy (pattern); } static void _setup (cairo_t** cr, cairo_surface_t** surf, gint width, gint height) { if (!cr || !surf) return; *surf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); *cr = cairo_create (*surf); cairo_scale (*cr, 1.0f, 1.0f); cairo_set_operator (*cr, CAIRO_OPERATOR_CLEAR); cairo_paint (*cr); cairo_set_operator (*cr, CAIRO_OPERATOR_OVER); } static void _mask_prev (cairo_t* cr, double x, double y, double tri_width, double tri_height, double tri_offset) { if (!cr) return; cairo_move_to (cr, x, y + tri_height / 2.0f); cairo_line_to (cr, x + tri_width, y); cairo_line_to (cr, x + tri_width, y + tri_height); x += tri_offset; cairo_move_to (cr, x, y + tri_height / 2.0f); cairo_line_to (cr, x + tri_width, y); cairo_line_to (cr, x + tri_width, y + tri_height); x -= tri_offset; cairo_rectangle (cr, x, y, 2.5f, tri_height); cairo_close_path (cr); } static void _mask_next (cairo_t* cr, double x, double y, double tri_width, double tri_height, double tri_offset) { if (!cr) return; cairo_move_to (cr, x, y); cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); cairo_line_to (cr, x, y + tri_height); x += tri_offset; cairo_move_to (cr, x, y); cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); cairo_line_to (cr, x, y + tri_height); x -= tri_offset; x += 2.0f * tri_width - tri_offset - 1.0f; cairo_rectangle (cr, x, y, 2.5f, tri_height); cairo_close_path (cr); } static void _mask_pause (cairo_t* cr, double x, double y, double bar_width, double bar_height, double bar_offset) { if (!cr) return; cairo_set_line_width (cr, bar_width); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); x += bar_width; y += bar_width; cairo_move_to (cr, x, y); cairo_line_to (cr, x, y + bar_height); cairo_move_to (cr, x + bar_offset, y); cairo_line_to (cr, x + bar_offset, y + bar_height); } static void _mask_play (cairo_t* cr, double x, double y, double tri_width, double tri_height) { if (!cr) return; cairo_move_to (cr, x, y); cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); cairo_line_to (cr, x, y + tri_height); cairo_close_path (cr); } static void _fill (cairo_t* cr, double x_start, double y_start, double x_end, double y_end, double* rgba_start, double* rgba_end, gboolean stroke) { cairo_pattern_t* pattern = NULL; if (!cr || !rgba_start || !rgba_end) return; pattern = cairo_pattern_create_linear (x_start, y_start, x_end, y_end); cairo_pattern_add_color_stop_rgba (pattern, 0.0f, rgba_start[0], rgba_start[1], rgba_start[2], rgba_start[3]); cairo_pattern_add_color_stop_rgba (pattern, 1.0f, rgba_end[0], rgba_end[1], rgba_end[2], rgba_end[3]); cairo_set_source (cr, pattern); if (stroke) cairo_stroke (cr); else cairo_fill (cr); cairo_pattern_destroy (pattern); } static void _finalize (cairo_t* cr, cairo_t** cr_surf, cairo_surface_t** surf, double x, double y) { if (!cr || !cr_surf || !surf) return; cairo_set_source_surface (cr, *surf, x, y); cairo_paint (cr); cairo_surface_destroy (*surf); cairo_destroy (*cr_surf); } static void _finalize_repaint (cairo_t* cr, cairo_t** cr_surf, cairo_surface_t** surf, double x, double y, int repaints) { if (!cr || !cr_surf || !surf) return; while (repaints > 0) { cairo_set_source_surface (cr, *surf, x, y); cairo_paint (cr); repaints--; } cairo_surface_destroy (*surf); cairo_destroy (*cr_surf); } static void _color_rgb_to_hls (gdouble *r, gdouble *g, gdouble *b) { gdouble min; gdouble max; gdouble red; gdouble green; gdouble blue; gdouble h = 0; gdouble l; gdouble s; gdouble delta; red = *r; green = *g; blue = *b; if (red > green) { if (red > blue) max = red; else max = blue; if (green < blue) min = green; else min = blue; } else { if (green > blue) max = green; else max = blue; if (red < blue) min = red; else min = blue; } l = (max+min)/2; if (fabs (max-min) < 0.0001) { h = 0; s = 0; } else { if (l <= 0.5) s = (max-min)/(max+min); else s = (max-min)/(2-max-min); delta = (max -min) != 0 ? (max -min) : 1; if(delta == 0) delta = 1; if (red == max) h = (green-blue)/delta; else if (green == max) h = 2+(blue-red)/delta; else if (blue == max) h = 4+(red-green)/delta; h *= 60; if (h < 0.0) h += 360; } *r = h; *g = l; *b = s; } static void _color_hls_to_rgb (gdouble *h, gdouble *l, gdouble *s) { gdouble hue; gdouble lightness; gdouble saturation; gdouble m1, m2; gdouble r, g, b; lightness = *l; saturation = *s; if (lightness <= 0.5) m2 = lightness*(1+saturation); else m2 = lightness+saturation-lightness*saturation; m1 = 2*lightness-m2; if (saturation == 0) { *h = lightness; *l = lightness; *s = lightness; } else { hue = *h+120; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) r = m1+(m2-m1)*hue/60; else if (hue < 180) r = m2; else if (hue < 240) r = m1+(m2-m1)*(240-hue)/60; else r = m1; hue = *h; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) g = m1+(m2-m1)*hue/60; else if (hue < 180) g = m2; else if (hue < 240) g = m1+(m2-m1)*(240-hue)/60; else g = m1; hue = *h-120; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) b = m1+(m2-m1)*hue/60; else if (hue < 180) b = m2; else if (hue < 240) b = m1+(m2-m1)*(240-hue)/60; else b = m1; *h = r; *l = g; *s = b; } } static void _color_shade (const CairoColorRGB *a, float k, CairoColorRGB *b) { double red; double green; double blue; red = a->r; green = a->g; blue = a->b; if (k == 1.0) { b->r = red; b->g = green; b->b = blue; return; } _color_rgb_to_hls (&red, &green, &blue); green *= k; if (green > 1.0) green = 1.0; else if (green < 0.0) green = 0.0; blue *= k; if (blue > 1.0) blue = 1.0; else if (blue < 0.0) blue = 0.0; _color_hls_to_rgb (&red, &green, &blue); b->r = red; b->g = green; b->b = blue; } static inline void _blurinner (guchar* pixel, gint* zR, gint* zG, gint* zB, gint* zA, gint alpha, gint aprec, gint zprec) { gint R; gint G; gint B; guchar A; R = *pixel; G = *(pixel + 1); B = *(pixel + 2); A = *(pixel + 3); *zR += (alpha * ((R << zprec) - *zR)) >> aprec; *zG += (alpha * ((G << zprec) - *zG)) >> aprec; *zB += (alpha * ((B << zprec) - *zB)) >> aprec; *zA += (alpha * ((A << zprec) - *zA)) >> aprec; *pixel = *zR >> zprec; *(pixel + 1) = *zG >> zprec; *(pixel + 2) = *zB >> zprec; *(pixel + 3) = *zA >> zprec; } static inline void _blurrow (guchar* pixels, gint width, gint height, gint channels, gint line, gint alpha, gint aprec, gint zprec) { gint zR; gint zG; gint zB; gint zA; gint index; guchar* scanline; scanline = &(pixels[line * width * channels]); zR = *scanline << zprec; zG = *(scanline + 1) << zprec; zB = *(scanline + 2) << zprec; zA = *(scanline + 3) << zprec; for (index = 0; index < width; index ++) _blurinner (&scanline[index * channels], &zR, &zG, &zB, &zA, alpha, aprec, zprec); for (index = width - 2; index >= 0; index--) _blurinner (&scanline[index * channels], &zR, &zG, &zB, &zA, alpha, aprec, zprec); } static inline void _blurcol (guchar* pixels, gint width, gint height, gint channels, gint x, gint alpha, gint aprec, gint zprec) { gint zR; gint zG; gint zB; gint zA; gint index; guchar* ptr; ptr = pixels; ptr += x * channels; zR = *((guchar*) ptr ) << zprec; zG = *((guchar*) ptr + 1) << zprec; zB = *((guchar*) ptr + 2) << zprec; zA = *((guchar*) ptr + 3) << zprec; for (index = width; index < (height - 1) * width; index += width) _blurinner ((guchar*) &ptr[index * channels], &zR, &zG, &zB, &zA, alpha, aprec, zprec); for (index = (height - 2) * width; index >= 0; index -= width) _blurinner ((guchar*) &ptr[index * channels], &zR, &zG, &zB, &zA, alpha, aprec, zprec); } static void _expblur (guchar* pixels, gint width, gint height, gint channels, gint radius, gint aprec, gint zprec) { gint alpha; gint row = 0; gint col = 0; if (radius < 1) return; // calculate the alpha such that 90% of // the kernel is within the radius. // (Kernel extends to infinity) alpha = (gint) ((1 << aprec) * (1.0f - expf (-2.3f / (radius + 1.f)))); for (; row < height; row++) _blurrow (pixels, width, height, channels, row, alpha, aprec, zprec); for(; col < width; col++) _blurcol (pixels, width, height, channels, col, alpha, aprec, zprec); return; } static void _surface_blur (cairo_surface_t* surface, guint radius) { guchar* pixels; guint width; guint height; cairo_format_t format; // before we mess with the surface execute any pending drawing cairo_surface_flush (surface); pixels = cairo_image_surface_get_data (surface); width = cairo_image_surface_get_width (surface); height = cairo_image_surface_get_height (surface); format = cairo_image_surface_get_format (surface); switch (format) { case CAIRO_FORMAT_ARGB32: _expblur (pixels, width, height, 4, radius, 16, 7); break; case CAIRO_FORMAT_RGB24: _expblur (pixels, width, height, 3, radius, 16, 7); break; case CAIRO_FORMAT_A8: _expblur (pixels, width, height, 1, radius, 16, 7); break; default : // do nothing break; } // inform cairo we altered the surfaces contents cairo_surface_mark_dirty (surface); } static gboolean ido_playback_menu_item_draw (GtkWidget* button, cairo_t *cr) { IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (button); g_return_val_if_fail(IDO_IS_PLAYBACK_MENU_ITEM (button), FALSE); g_return_val_if_fail(cr != NULL, FALSE); cairo_surface_t* surf = NULL; cairo_t* cr_surf = NULL; GtkStyle *style; CairoColorRGB bg_color, fg_color, bg_selected, bg_prelight; CairoColorRGB color_middle[2], color_middle_prelight[2], color_outer[2], color_outer_prelight[2], color_play_outer[2], color_play_outer_prelight[2], color_button[4], color_button_shadow, color_inner[2], color_inner_compressed[2]; /* Use the menu's style instead of that of the menuitem ('button' is a * menuitem that is packed in a menu directly). The menuitem's style * can't be used due to a change in light-themes (lp #1130183). * Menuitems now have a transparent background, which confuses * GtkStyle. * * This is a workaround until this code gets refactored to use * GtkStyleContext. */ style = gtk_widget_get_style (gtk_widget_get_parent (button)); bg_color.r = style->bg[0].red/65535.0; bg_color.g = style->bg[0].green/65535.0; bg_color.b = style->bg[0].blue/65535.0; bg_prelight.r = style->bg[GTK_STATE_PRELIGHT].red/65535.0; bg_prelight.g = style->bg[GTK_STATE_PRELIGHT].green/65535.0; bg_prelight.b = style->bg[GTK_STATE_PRELIGHT].blue/65535.0; bg_selected.r = style->bg[GTK_STATE_SELECTED].red/65535.0; bg_selected.g = style->bg[GTK_STATE_SELECTED].green/65535.0; bg_selected.b = style->bg[GTK_STATE_SELECTED].blue/65535.0; fg_color.r = style->fg[0].red/65535.0; fg_color.g = style->fg[0].green/65535.0; fg_color.b = style->fg[0].blue/65535.0; _color_shade (&bg_color, MIDDLE_START_SHADE, &color_middle[0]); _color_shade (&bg_color, MIDDLE_END_SHADE, &color_middle[1]); _color_shade (&bg_prelight, MIDDLE_START_SHADE, &color_middle_prelight[0]); _color_shade (&bg_prelight, MIDDLE_END_SHADE, &color_middle_prelight[1]); _color_shade (&bg_color, OUTER_START_SHADE, &color_outer[0]); _color_shade (&bg_color, OUTER_END_SHADE, &color_outer[1]); _color_shade (&bg_prelight, OUTER_START_SHADE, &color_outer_prelight[0]); _color_shade (&bg_prelight, OUTER_END_SHADE, &color_outer_prelight[1]); _color_shade (&bg_color, OUTER_PLAY_START_SHADE, &color_play_outer[0]); _color_shade (&bg_color, OUTER_PLAY_END_SHADE, &color_play_outer[1]); _color_shade (&bg_prelight, OUTER_PLAY_START_SHADE, &color_play_outer_prelight[0]); _color_shade (&bg_prelight, OUTER_PLAY_END_SHADE, &color_play_outer_prelight[1]); _color_shade (&bg_color, INNER_START_SHADE, &color_inner[0]); _color_shade (&bg_color, INNER_END_SHADE, &color_inner[1]); _color_shade (&fg_color, BUTTON_START_SHADE, &color_button[0]); _color_shade (&fg_color, BUTTON_END_SHADE, &color_button[1]); _color_shade (&bg_color, BUTTON_SHADOW_SHADE, &color_button[2]); _color_shade (&bg_color, SHADOW_BUTTON_SHADE, &color_button_shadow); _color_shade (&bg_selected, 1.0, &color_button[3]); _color_shade (&bg_color, INNER_COMPRESSED_START_SHADE, &color_inner_compressed[0]); _color_shade (&bg_color, INNER_COMPRESSED_END_SHADE, &color_inner_compressed[1]); double MIDDLE_END[] = {color_middle[0].r, color_middle[0].g, color_middle[0].b, 1.0f}; double MIDDLE_START[] = {color_middle[1].r, color_middle[1].g, color_middle[1].b, 1.0f}; double MIDDLE_END_PRELIGHT[] = {color_middle_prelight[0].r, color_middle_prelight[0].g, color_middle_prelight[0].b, 1.0f}; double MIDDLE_START_PRELIGHT[] = {color_middle_prelight[1].r, color_middle_prelight[1].g, color_middle_prelight[1].b, 1.0f}; double OUTER_END[] = {color_outer[0].r, color_outer[0].g, color_outer[0].b, 1.0f}; double OUTER_START[] = {color_outer[1].r, color_outer[1].g, color_outer[1].b, 1.0f}; double OUTER_END_PRELIGHT[] = {color_outer_prelight[0].r, color_outer_prelight[0].g, color_outer_prelight[0].b, 1.0f}; double OUTER_START_PRELIGHT[] = {color_outer_prelight[1].r, color_outer_prelight[1].g, color_outer_prelight[1].b, 1.0f}; double SHADOW_BUTTON[] = {color_button_shadow.r, color_button_shadow.g, color_button_shadow.b, 0.3f}; double OUTER_PLAY_END[] = {color_play_outer[0].r, color_play_outer[0].g, color_play_outer[0].b, 1.0f}; double OUTER_PLAY_START[] = {color_play_outer[1].r, color_play_outer[1].g, color_play_outer[1].b, 1.0f}; double OUTER_PLAY_END_PRELIGHT[] = {color_play_outer_prelight[0].r, color_play_outer_prelight[0].g, color_play_outer_prelight[0].b, 1.0f}; double OUTER_PLAY_START_PRELIGHT[] = {color_play_outer_prelight[1].r, color_play_outer_prelight[1].g, color_play_outer_prelight[1].b, 1.0f}; double BUTTON_END[] = {color_button[0].r, color_button[0].g, color_button[0].b, 1.0f}; double BUTTON_START[] = {color_button[1].r, color_button[1].g, color_button[1].b, 1.0f}; double BUTTON_SHADOW[] = {color_button[2].r, color_button[2].g, color_button[2].b, 0.75f}; double BUTTON_SHADOW_FOCUS[] = {color_button[3].r, color_button[3].g, color_button[3].b, 1.0f}; double INNER_COMPRESSED_END[] = {color_inner_compressed[1].r, color_inner_compressed[1].g, color_inner_compressed[1].b, 1.0f}; double INNER_COMPRESSED_START[] = {color_inner_compressed[0].r, color_inner_compressed[0].g, color_inner_compressed[0].b, 1.0f}; draw_gradient (cr, X, Y, RECT_WIDTH, OUTER_RADIUS, OUTER_START, OUTER_END); draw_gradient (cr, X, Y + 1, RECT_WIDTH - 2, MIDDLE_RADIUS, MIDDLE_START, MIDDLE_END); draw_gradient (cr, X, Y + 2, RECT_WIDTH - 4, MIDDLE_RADIUS, MIDDLE_START, MIDDLE_END); if(item->cur_pushed_button == BUTTON_PREVIOUS) { draw_gradient (cr, X, Y, RECT_WIDTH/2, OUTER_RADIUS, OUTER_END, OUTER_START); draw_gradient (cr, X, Y + 1, RECT_WIDTH/2, MIDDLE_RADIUS, INNER_COMPRESSED_START, INNER_COMPRESSED_END); draw_gradient (cr, X, Y + 2, RECT_WIDTH/2, MIDDLE_RADIUS, INNER_COMPRESSED_START, INNER_COMPRESSED_END); } else if(item->cur_pushed_button == BUTTON_NEXT) { draw_gradient (cr, RECT_WIDTH / 2 + X, Y, RECT_WIDTH/2, OUTER_RADIUS, OUTER_END, OUTER_START); draw_gradient (cr, RECT_WIDTH / 2 + X, Y + 1, (RECT_WIDTH - 4.5)/2, MIDDLE_RADIUS, INNER_COMPRESSED_START, INNER_COMPRESSED_END); draw_gradient (cr, RECT_WIDTH / 2 + X, Y + 2, (RECT_WIDTH - 7)/2, MIDDLE_RADIUS, INNER_COMPRESSED_START, INNER_COMPRESSED_END); } else if (item->cur_hover_button == BUTTON_PREVIOUS) { draw_gradient (cr, X, Y, RECT_WIDTH/2, OUTER_RADIUS, OUTER_START_PRELIGHT, OUTER_END_PRELIGHT); draw_gradient (cr, X, Y + 1, RECT_WIDTH/2, MIDDLE_RADIUS, MIDDLE_START_PRELIGHT, MIDDLE_END_PRELIGHT); draw_gradient (cr, X, Y + 2, RECT_WIDTH/2, MIDDLE_RADIUS, MIDDLE_START_PRELIGHT, MIDDLE_END_PRELIGHT); } else if (item->cur_hover_button == BUTTON_NEXT) { draw_gradient (cr, RECT_WIDTH / 2 + X, Y, RECT_WIDTH/2, OUTER_RADIUS, OUTER_START_PRELIGHT, OUTER_END_PRELIGHT); draw_gradient (cr, RECT_WIDTH / 2 + X, Y + 1, (RECT_WIDTH - 4.5)/2, MIDDLE_RADIUS, MIDDLE_START_PRELIGHT, MIDDLE_END_PRELIGHT); draw_gradient (cr, RECT_WIDTH / 2 + X, Y + 2, (RECT_WIDTH - 7)/2, MIDDLE_RADIUS, MIDDLE_START_PRELIGHT, MIDDLE_END_PRELIGHT); } // play/pause shadow if(item->cur_pushed_button != BUTTON_PLAYPAUSE) { cairo_save (cr); cairo_rectangle (cr, X, Y, RECT_WIDTH, MIDDLE_RADIUS*2); cairo_clip (cr); draw_circle (cr, X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f - 1.0f, Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) - 1.0f, CIRCLE_RADIUS + 1.0f, SHADOW_BUTTON, SHADOW_BUTTON); cairo_restore (cr); } // play/pause button if(item->cur_pushed_button == BUTTON_PLAYPAUSE) { draw_circle (cr, X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f, Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) , CIRCLE_RADIUS, OUTER_PLAY_END, OUTER_PLAY_START); draw_circle (cr, X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, CIRCLE_RADIUS - 1.25, INNER_COMPRESSED_START, INNER_COMPRESSED_END); } else if (item->cur_hover_button == BUTTON_PLAYPAUSE) { /* this subtle offset is to fix alpha borders, should be removed once this draw routine will be refactored */ draw_circle (cr, X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 0.1, Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 0.1, CIRCLE_RADIUS - 0.1, OUTER_PLAY_START_PRELIGHT, OUTER_PLAY_END_PRELIGHT); draw_circle (cr, X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, CIRCLE_RADIUS - 1.25, MIDDLE_START_PRELIGHT, MIDDLE_END_PRELIGHT); } else { draw_circle (cr, X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f, Y - ((CIRCLE_RADIUS - OUTER_RADIUS)), CIRCLE_RADIUS, OUTER_PLAY_START, OUTER_PLAY_END); draw_circle (cr, X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, CIRCLE_RADIUS - 1.25, MIDDLE_START, MIDDLE_END); } // draw previous-button drop-shadow if (item->cur_pushed_button == BUTTON_PREVIOUS && item->keyboard_activated ) { _setup (&cr_surf, &surf, PREV_WIDTH+6, PREV_HEIGHT+6); _mask_prev (cr_surf, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, TRI_WIDTH, TRI_HEIGHT, TRI_OFFSET); _fill (cr_surf, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (double) TRI_HEIGHT, BUTTON_SHADOW_FOCUS, BUTTON_SHADOW_FOCUS, FALSE); _surface_blur (surf, 3); _finalize_repaint (cr, &cr_surf, &surf, PREV_X, PREV_Y + 0.5f, 3); } else { _setup (&cr_surf, &surf, PREV_WIDTH, PREV_HEIGHT); _mask_prev (cr_surf, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, TRI_WIDTH, TRI_HEIGHT, TRI_OFFSET); _fill (cr_surf, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (double) TRI_HEIGHT, BUTTON_SHADOW, BUTTON_SHADOW, FALSE); _surface_blur (surf, 1); _finalize (cr, &cr_surf, &surf, PREV_X, PREV_Y + 1.0f); } // draw previous-button _setup (&cr_surf, &surf, PREV_WIDTH, PREV_HEIGHT); _mask_prev (cr_surf, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, TRI_WIDTH, TRI_HEIGHT, TRI_OFFSET); _fill (cr_surf, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (double) TRI_HEIGHT, BUTTON_START, BUTTON_END, FALSE); _finalize (cr, &cr_surf, &surf, PREV_X, PREV_Y); // draw next-button drop-shadow if (item->cur_pushed_button == BUTTON_NEXT && item->keyboard_activated) { _setup (&cr_surf, &surf, NEXT_WIDTH+6, NEXT_HEIGHT+6); _mask_next (cr_surf, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, TRI_WIDTH, TRI_HEIGHT, TRI_OFFSET); _fill (cr_surf, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (double) TRI_HEIGHT, BUTTON_SHADOW_FOCUS, BUTTON_SHADOW_FOCUS, FALSE); _surface_blur (surf, 3); _finalize_repaint (cr, &cr_surf, &surf, NEXT_X, NEXT_Y + 0.5f, 3); } else { _setup (&cr_surf, &surf, NEXT_WIDTH, NEXT_HEIGHT); _mask_next (cr_surf, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, TRI_WIDTH, TRI_HEIGHT, TRI_OFFSET); _fill (cr_surf, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (double) TRI_HEIGHT, BUTTON_SHADOW, BUTTON_SHADOW, FALSE); _surface_blur (surf, 1); _finalize (cr, &cr_surf, &surf, NEXT_X, NEXT_Y + 1.0f); } // draw next-button _setup (&cr_surf, &surf, NEXT_WIDTH, NEXT_HEIGHT); _mask_next (cr_surf, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, TRI_WIDTH, TRI_HEIGHT, TRI_OFFSET); _fill (cr_surf, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, (double) TRI_HEIGHT, BUTTON_START, BUTTON_END, FALSE); _finalize (cr, &cr_surf, &surf, NEXT_X, NEXT_Y); // draw pause-button drop-shadow if (item->current_state == STATE_PLAYING) { if (item->has_focus && (item->cur_pushed_button == BUTTON_NONE || item->cur_pushed_button == BUTTON_PLAYPAUSE)) { _setup (&cr_surf, &surf, PAUSE_WIDTH+6, PAUSE_HEIGHT+6); _mask_pause (cr_surf, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, BAR_WIDTH, BAR_HEIGHT - 2.0f * BAR_WIDTH, BAR_OFFSET); _fill (cr_surf, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (double) BAR_HEIGHT, BUTTON_SHADOW_FOCUS, BUTTON_SHADOW_FOCUS, TRUE); _surface_blur (surf, 3); _finalize_repaint (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y + 0.5f, 3); } else { _setup (&cr_surf, &surf, PAUSE_WIDTH, PAUSE_HEIGHT); _mask_pause (cr_surf, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, BAR_WIDTH, BAR_HEIGHT - 2.0f * BAR_WIDTH, BAR_OFFSET); _fill (cr_surf, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (double) BAR_HEIGHT, BUTTON_SHADOW, BUTTON_SHADOW, TRUE); _surface_blur (surf, 1); _finalize (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y + 1.0f); } // draw pause-button _setup (&cr_surf, &surf, PAUSE_WIDTH, PAUSE_HEIGHT); _mask_pause (cr_surf, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, BAR_WIDTH, BAR_HEIGHT - 2.0f * BAR_WIDTH, BAR_OFFSET); _fill (cr_surf, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, (double) BAR_HEIGHT, BUTTON_START, BUTTON_END, TRUE); _finalize (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y); } else if (item->current_state == STATE_PAUSED) { if (item->has_focus && (item->cur_pushed_button == BUTTON_NONE || item->cur_pushed_button == BUTTON_PLAYPAUSE)) { _setup (&cr_surf, &surf, PLAY_WIDTH+6, PLAY_HEIGHT+6); _mask_play (cr_surf, PLAY_PADDING, PLAY_PADDING, PLAY_WIDTH - (2*PLAY_PADDING), PLAY_HEIGHT - (2*PLAY_PADDING)); _fill (cr_surf, PLAY_PADDING, PLAY_PADDING, PLAY_WIDTH - (2*PLAY_PADDING), PLAY_HEIGHT - (2*PLAY_PADDING), BUTTON_SHADOW_FOCUS, BUTTON_SHADOW_FOCUS, FALSE); _surface_blur (surf, 3); _finalize_repaint (cr, &cr_surf, &surf, PAUSE_X-0.5f, PAUSE_Y + 0.5f, 3); } else { _setup (&cr_surf, &surf, PLAY_WIDTH, PLAY_HEIGHT); _mask_play (cr_surf, PLAY_PADDING, PLAY_PADDING, PLAY_WIDTH - (2*PLAY_PADDING), PLAY_HEIGHT - (2*PLAY_PADDING)); _fill (cr_surf, PLAY_PADDING, PLAY_PADDING, PLAY_WIDTH - (2*PLAY_PADDING), PLAY_HEIGHT - (2*PLAY_PADDING), BUTTON_SHADOW, BUTTON_SHADOW, FALSE); _surface_blur (surf, 1); _finalize (cr, &cr_surf, &surf, PAUSE_X-0.75f, PAUSE_Y + 1.0f); } // draw play-button _setup (&cr_surf, &surf, PLAY_WIDTH, PLAY_HEIGHT); cairo_set_line_width (cr, 10.5); cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); _mask_play (cr_surf, PLAY_PADDING, PLAY_PADDING, PLAY_WIDTH - (2*PLAY_PADDING), PLAY_HEIGHT - (2*PLAY_PADDING)); _fill (cr_surf, PLAY_PADDING, PLAY_PADDING, PLAY_WIDTH - (2*PLAY_PADDING), PLAY_HEIGHT - (2*PLAY_PADDING), BUTTON_START, BUTTON_END, FALSE); _finalize (cr, &cr_surf, &surf, PAUSE_X-0.5f, PAUSE_Y); } else if (item->current_state == STATE_LAUNCHING) { // the spinner is not aligned, why? because the play button has odd width/height numbers gtk_render_activity (gtk_widget_get_style_context (button), cr, 106, 6, 30, 30); } return FALSE; }