2 * Copyright (c) 2010 Mike Qin <mikeandmore@gmail.com>
4 * The contents of this file are subject to the terms of either the GNU Lesser
5 * General Public License Version 2.1 only ("LGPL") or the Common Development and
6 * Distribution License ("CDDL")(collectively, the "License"). You may not use this
7 * file except in compliance with the License. You can obtain a copy of the CDDL at
8 * http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
9 * http://www.opensource.org/licenses/lgpl-license.php. See the License for the
10 * specific language governing permissions and limitations under the License. When
11 * distributing the software, include this License Header Notice in each file and
12 * include the full text of the License in the License file as well as the
15 * NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
17 * For Covered Software in this distribution, this License shall be governed by the
18 * laws of the State of California (excluding conflict-of-law provisions).
19 * Any litigation relating to this License shall be subject to the jurisdiction of
20 * the Federal Courts of the Northern District of California and the state courts
21 * of the State of California, with venue lying in Santa Clara County, California.
25 * If you wish your version of this file to be governed by only the CDDL or only
26 * the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
27 * include this software in this distribution under the [CDDL or LGPL Version 2.1]
28 * license." If you don't indicate a single choice of license, a recipient has the
29 * option to distribute your version of this file under either the CDDL or the LGPL
30 * Version 2.1, or to extend the choice of license to its licensees as provided
31 * above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
32 * Version 2 license, then the option applies only if the new code is made subject
33 * to such option by the copyright holder.
40 struct _skin_window_priv_t
45 /* current expose environment */
46 skin_button_t* highlight_btn;
47 skin_button_t* pressdown_btn;
54 void* release_cb_data;
57 /* drag to move positions */
58 gboolean enable_drag_to_move;
64 struct _skin_button_priv_t
67 skin_window_t* parent;
73 void* release_cb_data;
76 struct _skin_label_priv_t
83 zoom_and_composite(GdkPixbuf* src,
94 if (width <= 0 || height <= 0 || dst_width <= 0 || dst_height <= 0)
96 GdkPixbuf* sub = gdk_pixbuf_new_subpixbuf(src, x, y, width, height);
97 double x_scale = 1.0 * dst_width / width;
98 double y_scale = 1.0 * dst_height / height;
100 gdk_pixbuf_scale(sub, dst, dst_x, dst_y, dst_width, dst_height,
101 dst_x, dst_y, x_scale, y_scale, GDK_INTERP_BILINEAR);
106 cairo_paint_pixbuf(cairo_t* cr,
111 gdk_cairo_set_source_pixbuf(cr, buf, off_x, off_y);
116 paint_background_with_mask(GtkWidget* wid,
119 GdkPixbuf* bg = wind->background_image;
121 int bg_width, bg_height;
122 int top, left, bottom, right;
124 width = wid->allocation.width;
125 height = wid->allocation.height;
126 bg_width = gdk_pixbuf_get_width(bg);
127 bg_height = gdk_pixbuf_get_height(bg);
129 top = wind->margin_top;
130 left = wind->margin_left;
131 right = wind->margin_right;
132 bottom = wind->margin_bottom;
134 GdkPixbuf* newbg = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
138 gdk_pixbuf_copy_area(bg, 0, 0, left, top, newbg, 0, 0);
139 gdk_pixbuf_copy_area(bg, bg_width - right, 0, right, top,
140 newbg, width - right, 0);
141 gdk_pixbuf_copy_area(bg, 0, bg_height - bottom, left, bottom,
142 newbg, 0, height - bottom);
143 gdk_pixbuf_copy_area(bg, bg_width - right, bg_height - bottom,
145 newbg, width - right, height - bottom);
147 // 4 margins and the center area
148 int content_width = bg_width - left - right;
149 int content_height = bg_height - top - bottom;
150 int new_content_width = width - left - right;
151 int new_content_height = height - top - bottom;
154 zoom_and_composite(bg, newbg, left, 0, content_width, top,
155 left, 0, new_content_width, top);
157 zoom_and_composite(bg, newbg,
158 left, bg_height - bottom, content_width, bottom,
159 left, height - bottom, new_content_width, bottom);
161 zoom_and_composite(bg, newbg,
162 0, top, left, content_height,
163 0, top, left, new_content_height);
165 zoom_and_composite(bg, newbg,
166 bg_width - right, top, right, content_height,
167 width - right, top, right, new_content_height);
169 zoom_and_composite(bg, newbg,
170 left, top, content_width, content_height,
171 left, top, new_content_width, new_content_height);
173 gdk_window_clear(wid->window);
175 cairo_t* cr = gdk_cairo_create(wid->window);
176 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
177 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
179 cairo_paint_pixbuf(cr, newbg, 0, 0);
183 gdk_pixbuf_render_pixmap_and_mask(newbg, NULL, &mask,
184 wind->alpha_mask_threshold);
185 gdk_window_shape_combine_mask(wid->window, mask, 0, 0);
187 g_object_unref(newbg);
191 paint_buttons(GtkWidget* wid,
194 cairo_t* cr = gdk_cairo_create(wid->window);
195 skin_button_t* btn = wind->priv->btns;
196 for (; btn != NULL; btn = btn->priv->next) {
197 if (btn == wind->priv->pressdown_btn) {
198 cairo_paint_pixbuf(cr, btn->pressdown_image, btn->x, btn->y);
199 } else if (btn == wind->priv->highlight_btn) {
200 cairo_paint_pixbuf(cr, btn->highlight_image, btn->x, btn->y);
202 cairo_paint_pixbuf(cr, btn->normal_image, btn->x, btn->y);
209 paint_labels(GtkWidget* wid,
212 cairo_t* cr = gdk_cairo_create(wid->window);
213 skin_label_t* label = wind->priv->labels;
214 for (; label != NULL; label = label->priv->next) {
215 cairo_set_source_rgba(cr, label->color_r, label->color_g,
216 label->color_b, label->color_a);
217 if (label->layout == NULL) {
218 label->layout = pango_cairo_create_layout(cr);
219 pango_layout_set_font_description(
220 label->layout, pango_font_description_from_string(label->font));
222 pango_layout_set_text(label->layout, label->text, -1);
223 cairo_move_to(cr, label->x, label->y);
224 pango_cairo_layout_path(cr, label->layout);
231 window_on_expose(GtkWidget* wid,
235 paint_background_with_mask(wid, wind);
236 paint_labels(wid, wind);
237 paint_buttons(wid, wind);
242 window_on_configure(GtkWidget* wid,
243 GdkEventConfigure* evt,
246 gtk_widget_queue_draw(wid);
250 static skin_button_t*
251 find_button(skin_window_t* wind, int pos_x, int pos_y)
253 skin_button_t* btn = wind->priv->btns;
254 for (; btn != NULL; btn = btn->priv->next) {
255 int x = btn->x, y = btn->y, width = btn->width, height = btn->height;
256 if (pos_x > x && pos_y > y && pos_x < x + width && pos_y < y + height) {
264 set_expose_env(skin_window_t* wind,
265 skin_button_t* highlight,
268 gboolean ret = FALSE;
269 if (wind->priv->highlight_btn != highlight) {
271 wind->priv->highlight_btn = highlight;
273 if (wind->priv->pressdown_btn != down) {
275 wind->priv->pressdown_btn = down;
280 typedef gboolean (*motion_cb_t) (GtkWidget*, GdkEventMotion*, void*);
281 typedef gboolean (*mouse_cb_t) (GtkWidget*, GdkEventButton*, void*);
284 window_on_motion(GtkWidget* wid,
288 skin_button_t* btn = find_button(wind, evt->x, evt->y);
289 gboolean need_redraw = FALSE;
291 if ((evt->state & GDK_BUTTON1_MASK) != 0
292 || (evt->state & GDK_BUTTON2_MASK) != 0
293 || (evt->state & GDK_BUTTON3_MASK) != 0) {
294 need_redraw = set_expose_env(wind, NULL, btn);
296 need_redraw = set_expose_env(wind, btn, NULL);
299 /* move it if drag */
300 if (wind->priv->enable_drag_to_move && wind->priv->drag_begin) {
301 gtk_window_move(GTK_WINDOW(wind->widget),
302 evt->x_root - wind->priv->drag_init_x,
303 evt->y_root - wind->priv->drag_init_y);
305 need_redraw = set_expose_env(wind, NULL, NULL);
306 motion_cb_t cb = (motion_cb_t) wind->priv->motion_cb;
308 cb(wid, evt, wind->priv->motion_cb_data);
312 gtk_widget_queue_draw(wid);
318 window_on_press_or_release(GtkWidget* wid,
323 skin_button_t* btn = find_button(wind, evt->x, evt->y);
324 gboolean need_redraw = FALSE;
325 skin_button_t* highlight_btn = NULL;
326 skin_button_t* pressdown_btn = NULL;
327 mouse_cb_t btncb = NULL;
328 mouse_cb_t wincb = NULL;
331 wincb = (mouse_cb_t) wind->priv->press_cb;
334 /* update drag init positions */
335 wind->priv->drag_init_x = evt->x;
336 wind->priv->drag_init_y = evt->y;
337 wind->priv->drag_begin = wind->priv->enable_drag_to_move;
339 btncb = (mouse_cb_t) btn->priv->press_cb;
342 wincb = (mouse_cb_t) wind->priv->release_cb;
345 wind->priv->drag_begin = FALSE;
347 btncb = (mouse_cb_t) btn->priv->release_cb;
352 need_redraw = set_expose_env(wind, highlight_btn, pressdown_btn);
354 btncb(wid, evt, btn->priv->press_cb_data);
358 wincb(wid, evt, wind->priv->press_cb_data);
362 gtk_widget_queue_draw(wid);
368 window_on_press(GtkWidget* wid,
372 return window_on_press_or_release(wid, evt, wind, TRUE);
376 window_on_release(GtkWidget* wid,
380 return window_on_press_or_release(wid, evt, wind, FALSE);
384 skin_window_new(GtkWindow* widget,
385 GdkPixbuf* background_image,
390 int alpha_mask_threshold)
392 skin_window_t* wind = malloc(sizeof(skin_window_t));
393 wind->widget = GTK_WIDGET(widget);
394 wind->background_image = background_image;
395 wind->margin_top = margin_top;
396 wind->margin_left = margin_left;
397 wind->margin_bottom = margin_bottom;
398 wind->margin_right = margin_right;
399 wind->alpha_mask_threshold = alpha_mask_threshold;
400 wind->priv = malloc(sizeof(skin_window_priv_t));
401 memset(wind->priv, 0, sizeof(skin_window_priv_t));
402 gtk_window_set_default_size(widget,
403 gdk_pixbuf_get_width(background_image),
404 gdk_pixbuf_get_height(background_image));
407 GdkScreen* screen = gdk_screen_get_default();
408 GdkColormap* cmap = gdk_screen_get_rgba_colormap(screen);
410 gtk_widget_set_colormap(wind->widget, cmap);
412 fprintf(stderr, "Cannot set rgba colormap!\n");
416 gtk_widget_set_events(wind->widget,
417 GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK
418 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
419 gtk_widget_realize(wind->widget);
421 g_signal_connect(wind->widget, "expose-event",
422 G_CALLBACK(window_on_expose), wind);
423 g_signal_connect(wind->widget, "configure-event",
424 G_CALLBACK(window_on_configure), wind);
425 g_signal_connect(wind->widget, "motion-notify-event",
426 G_CALLBACK(window_on_motion), wind);
427 g_signal_connect(wind->widget, "button-press-event",
428 G_CALLBACK(window_on_press), wind);
429 g_signal_connect(wind->widget, "button-release-event",
430 G_CALLBACK(window_on_release), wind);
435 skin_window_destroy(skin_window_t* wind)
437 gtk_widget_destroy(wind->widget);
443 skin_window_add_button(skin_window_t* wind,
448 /* append to the buttons list */
449 btn->priv->next = wind->priv->btns;
450 wind->priv->btns = btn;
452 btn->priv->parent = wind;
458 skin_window_set_drag_to_move(skin_window_t* wind,
459 gboolean drag_to_move)
461 wind->priv->enable_drag_to_move = drag_to_move;
465 skin_button_new(GdkPixbuf* normal_image,
466 GdkPixbuf* highlight_image,
467 GdkPixbuf* pressdown_image)
469 skin_button_t* btn = malloc(sizeof(skin_button_t));
470 btn->normal_image = normal_image;
471 btn->highlight_image = highlight_image;
472 btn->pressdown_image = pressdown_image;
473 btn->width = gdk_pixbuf_get_width(normal_image);
474 btn->height = gdk_pixbuf_get_height(normal_image);
475 btn->priv = malloc(sizeof(skin_button_priv_t));
476 memset(btn->priv, 0, sizeof(skin_button_priv_t));
481 skin_button_destroy(skin_button_t* btn)
488 skin_button_set_image(skin_button_t* btn,
489 GdkPixbuf* normal_image,
490 GdkPixbuf* highlight_image,
491 GdkPixbuf* pressdown_image)
493 /* since we're redraw it, we'd better check if we really changed
494 the pixbuf. This can save a lot of time.
496 gboolean need_set = (btn->normal_image != normal_image) ||
497 (btn->highlight_image != highlight_image) ||
498 (btn->pressdown_image != pressdown_image);
503 btn->normal_image = normal_image;
504 btn->highlight_image = highlight_image;
505 btn->pressdown_image = pressdown_image;
506 if (btn->priv->parent) {
507 gtk_widget_queue_draw(btn->priv->parent->widget);
512 skin_label_new(char* font,
519 skin_label_t* label = malloc(sizeof(skin_label_t));
520 label->color_r = color_r;
521 label->color_g = color_g;
522 label->color_b = color_b;
523 label->color_a = color_a;
524 label->font = strdup(font);
526 label->text = strdup(text);
530 label->layout = NULL;
531 label->priv = malloc(sizeof(skin_label_priv_t));
532 memset(label->priv, 0, sizeof(skin_label_priv_t));
538 skin_label_destroy(skin_label_t* label)
544 g_object_unref(label->layout);
550 skin_window_add_label(skin_window_t* wind,
557 label->priv->wind = wind;
558 label->priv->next = wind->priv->labels;
559 wind->priv->labels = label;
561 cairo_t* cr = gdk_cairo_create(wind->widget->window);
562 label->layout = pango_cairo_create_layout(cr);
563 pango_layout_set_font_description(
564 label->layout, pango_font_description_from_string(label->font));
569 skin_label_set_text(skin_label_t* label, const char* text)
573 label->text = strdup(text);
574 pango_layout_set_text(label->layout, label->text, -1);
578 if (label->layout && label->priv->wind) {
579 gtk_widget_queue_draw(label->priv->wind->widget);
583 #define SET_CB_IMPL(name, event) \
584 void skin_##name##_set_##event##_cb(skin_##name##_t* wid, \
587 { wid->priv->event##_cb = cb; wid->priv->event##_cb_data = data; } \
590 SET_CB_IMPL(window, press);
591 SET_CB_IMPL(window, release);
592 SET_CB_IMPL(window, motion);
593 SET_CB_IMPL(button, press);
594 SET_CB_IMPL(button, release);