summaryrefslogtreecommitdiff
path: root/shared/frame.c
diff options
context:
space:
mode:
authorJason Ekstrand <jason@jlekstrand.net>2013-10-13 19:08:39 -0500
committerKristian Høgsberg <krh@bitplanet.net>2013-10-13 22:12:16 -0700
commit01c9ec3477c92509b8d53ae3e518a6d9b5b50aad (patch)
treed14ecf553f058ae497c9b8f5497dc8c7a10b768c /shared/frame.c
parentee7fefcffc73fbf2e5fbe2bcb08a3918f202144b (diff)
Add decoration frame support to cairo-util
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
Diffstat (limited to 'shared/frame.c')
-rw-r--r--shared/frame.c670
1 files changed, 670 insertions, 0 deletions
diff --git a/shared/frame.c b/shared/frame.c
new file mode 100644
index 00000000..508870c4
--- /dev/null
+++ b/shared/frame.c
@@ -0,0 +1,670 @@
+/*
+ * Copyright © 2008 Kristian Høgsberg
+ * Copyright © 2012-2013 Collabora, Ltd.
+ * Copyright © 2013 Jason Ekstrand
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-util.h>
+#include <linux/input.h>
+
+#include "cairo-util.h"
+
+enum frame_button_flags {
+ FRAME_BUTTON_ALIGN_RIGHT = 0x1,
+ FRAME_BUTTON_DECORATED = 0x2,
+ FRAME_BUTTON_CLICK_DOWN = 0x4,
+};
+
+struct frame_button {
+ struct frame *frame;
+ struct wl_list link; /* buttons_list */
+
+ cairo_surface_t *icon;
+ enum frame_button_flags flags;
+ int hover_count;
+ int press_count;
+
+ struct {
+ int x, y;
+ int width, height;
+ } allocation;
+
+ enum frame_status status_effect;
+};
+
+struct frame_pointer {
+ struct wl_list link;
+ void *data;
+
+ int x, y;
+
+ struct frame_button *hover_button;
+ int active;
+};
+
+struct frame {
+ int32_t width, height;
+ char *title;
+ uint32_t flags;
+ struct theme *theme;
+
+ struct {
+ int32_t x, y;
+ int32_t width, height;
+ } interior;
+ int shadow_margin;
+ int opaque_margin;
+ int geometry_dirty;
+
+ uint32_t status;
+
+ struct wl_list buttons;
+ struct wl_list pointers;
+};
+
+static struct frame_button *
+frame_button_create(struct frame *frame, const char *icon,
+ enum frame_status status_effect,
+ enum frame_button_flags flags)
+{
+ struct frame_button *button;
+
+ button = calloc(1, sizeof *button);
+ if (!button)
+ return NULL;
+
+ button->icon = cairo_image_surface_create_from_png(icon);
+ if (!button->icon) {
+ free(button);
+ return NULL;
+ }
+
+ button->frame = frame;
+ button->flags = flags;
+ button->status_effect = status_effect;
+
+ wl_list_insert(frame->buttons.prev, &button->link);
+
+ return button;
+}
+
+static void
+frame_button_destroy(struct frame_button *button)
+{
+ cairo_surface_destroy(button->icon);
+ free(button);
+}
+
+static void
+frame_button_enter(struct frame_button *button)
+{
+ if (!button->hover_count)
+ button->frame->status |= FRAME_STATUS_REPAINT;
+ button->hover_count++;
+}
+
+static void
+frame_button_leave(struct frame_button *button, struct frame_pointer *pointer)
+{
+ button->hover_count--;
+ if (!button->hover_count)
+ button->frame->status |= FRAME_STATUS_REPAINT;
+
+ /* In this case, we won't get a release */
+ if (pointer->active)
+ button->press_count--;
+}
+
+static void
+frame_button_press(struct frame_button *button)
+{
+ if (!button->press_count)
+ button->frame->status |= FRAME_STATUS_REPAINT;
+ button->press_count++;
+
+ if (button->flags & FRAME_BUTTON_CLICK_DOWN)
+ button->frame->status |= button->status_effect;
+}
+
+static void
+frame_button_release(struct frame_button *button)
+{
+ button->press_count--;
+ if (!button->press_count)
+ button->frame->status |= FRAME_STATUS_REPAINT;
+
+ if (!(button->flags & FRAME_BUTTON_CLICK_DOWN))
+ button->frame->status |= button->status_effect;
+}
+
+static void
+frame_button_repaint(struct frame_button *button, cairo_t *cr)
+{
+ int x, y;
+
+ if (!button->allocation.width)
+ return;
+ if (!button->allocation.height)
+ return;
+
+ x = button->allocation.x;
+ y = button->allocation.y;
+
+ cairo_save(cr);
+
+ if (button->flags & FRAME_BUTTON_DECORATED) {
+ cairo_set_line_width(cr, 1);
+
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
+ cairo_rectangle(cr, x, y, 25, 16);
+
+ cairo_stroke_preserve(cr);
+
+ if (button->press_count) {
+ cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
+ } else if (button->hover_count) {
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ } else {
+ cairo_set_source_rgb(cr, 0.88, 0.88, 0.88);
+ }
+
+ cairo_fill (cr);
+
+ x += 4;
+ }
+
+ cairo_set_source_surface(cr, button->icon, x, y);
+ cairo_paint(cr);
+
+ cairo_restore(cr);
+}
+
+static struct frame_pointer *
+frame_pointer_get(struct frame *frame, void *data)
+{
+ struct frame_pointer *pointer;
+
+ wl_list_for_each(pointer, &frame->pointers, link)
+ if (pointer->data == data)
+ return pointer;
+
+ pointer = calloc(1, sizeof *pointer);
+ if (!pointer)
+ return NULL;
+
+ pointer->data = data;
+ wl_list_insert(&frame->pointers, &pointer->link);
+
+ return pointer;
+}
+
+static void
+frame_pointer_destroy(struct frame_pointer *pointer)
+{
+ wl_list_remove(&pointer->link);
+ free(pointer);
+}
+
+struct frame *
+frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons,
+ const char *title)
+{
+ struct frame *frame;
+ struct frame_button *button;
+
+ frame = calloc(1, sizeof *frame);
+ if (!frame)
+ return NULL;
+
+ frame->width = width;
+ frame->height = height;
+ frame->flags = 0;
+ frame->theme = t;
+ frame->status = FRAME_STATUS_REPAINT;
+ frame->geometry_dirty = 1;
+
+ if (title) {
+ frame->title = strdup(title);
+ if (!frame->title)
+ goto free_frame;
+ }
+
+ wl_list_init(&frame->buttons);
+ wl_list_init(&frame->pointers);
+
+ button = frame_button_create(frame, DATADIR "/weston/icon_window.png",
+ FRAME_STATUS_MENU,
+ FRAME_BUTTON_CLICK_DOWN);
+ if (!button)
+ goto free_frame;
+
+ if (buttons & FRAME_BUTTON_CLOSE) {
+ button = frame_button_create(frame,
+ DATADIR "/weston/sign_close.png",
+ FRAME_STATUS_CLOSE,
+ FRAME_BUTTON_ALIGN_RIGHT |
+ FRAME_BUTTON_DECORATED);
+ if (!button)
+ goto free_frame;
+ }
+
+ if (buttons & FRAME_BUTTON_MAXIMIZE) {
+ button = frame_button_create(frame,
+ DATADIR "/weston/sign_maximize.png",
+ FRAME_STATUS_MAXIMIZE,
+ FRAME_BUTTON_ALIGN_RIGHT |
+ FRAME_BUTTON_DECORATED);
+ if (!button)
+ goto free_frame;
+ }
+
+ if (buttons & FRAME_BUTTON_MINIMIZE) {
+ button = frame_button_create(frame,
+ DATADIR "/weston/sign_minimize.png",
+ FRAME_STATUS_MINIMIZE,
+ FRAME_BUTTON_ALIGN_RIGHT |
+ FRAME_BUTTON_DECORATED);
+ if (!button)
+ goto free_frame;
+ }
+
+ return frame;
+
+free_frame:
+ free(frame->title);
+ free(frame);
+ return NULL;
+}
+
+void
+frame_destroy(struct frame *frame)
+{
+ struct frame_button *button, *next;
+
+ wl_list_for_each_safe(button, next, &frame->buttons, link)
+ frame_button_destroy(button);
+
+ free(frame->title);
+ free(frame);
+}
+
+int
+frame_set_title(struct frame *frame, const char *title)
+{
+ char *dup = NULL;
+
+ if (title) {
+ dup = strdup(title);
+ if (!dup)
+ return -1;
+ }
+
+ free(frame->title);
+ frame->title = dup;
+
+ frame->status |= FRAME_STATUS_REPAINT;
+
+ return 0;
+}
+
+void
+frame_set_flag(struct frame *frame, enum frame_flag flag)
+{
+ if (flag & FRAME_FLAG_MAXIMIZED && !(frame->flags & FRAME_FLAG_MAXIMIZED))
+ frame->geometry_dirty = 1;
+
+ frame->flags |= flag;
+ frame->status |= FRAME_STATUS_REPAINT;
+}
+
+void
+frame_unset_flag(struct frame *frame, enum frame_flag flag)
+{
+ if (flag & FRAME_FLAG_MAXIMIZED && frame->flags & FRAME_FLAG_MAXIMIZED)
+ frame->geometry_dirty = 1;
+
+ frame->flags &= ~flag;
+ frame->status |= FRAME_STATUS_REPAINT;
+}
+
+void
+frame_resize(struct frame *frame, int32_t width, int32_t height)
+{
+ frame->width = width;
+ frame->height = height;
+
+ frame->geometry_dirty = 1;
+ frame->status |= FRAME_STATUS_REPAINT;
+}
+
+void
+frame_resize_inside(struct frame *frame, int32_t width, int32_t height)
+{
+ struct theme *t = frame->theme;
+ int decoration_width, decoration_height;
+
+ if (frame->flags & FRAME_FLAG_MAXIMIZED) {
+ decoration_width = t->width * 2;
+ decoration_height = t->width + t->titlebar_height;
+ } else {
+ decoration_width = (t->width + t->margin) * 2;
+ decoration_height = t->width +
+ t->titlebar_height + t->margin * 2;
+ }
+
+ frame_resize(frame, width + decoration_width,
+ height + decoration_height);
+}
+
+int32_t
+frame_width(struct frame *frame)
+{
+ return frame->width;
+}
+
+int32_t
+frame_height(struct frame *frame)
+{
+ return frame->height;
+}
+
+static void
+frame_refresh_geometry(struct frame *frame)
+{
+ struct frame_button *button;
+ struct theme *t = frame->theme;
+ int x_l, x_r, y, w, h;
+ int32_t decoration_width, decoration_height;
+
+ if (!frame->geometry_dirty)
+ return;
+
+ if (frame->flags & FRAME_FLAG_MAXIMIZED) {
+ decoration_width = t->width * 2;
+ decoration_height = t->width + t->titlebar_height;
+
+ frame->interior.x = t->width;
+ frame->interior.y = t->titlebar_height;
+ frame->interior.width = frame->width - decoration_width;
+ frame->interior.height = frame->height - decoration_height;
+
+ frame->opaque_margin = 0;
+ frame->shadow_margin = 0;
+ } else {
+ decoration_width = (t->width + t->margin) * 2;
+ decoration_height = t->width +
+ t->titlebar_height + t->margin * 2;
+
+ frame->interior.x = t->width + t->margin;
+ frame->interior.y = t->titlebar_height + t->margin;
+ frame->interior.width = frame->width - decoration_width;
+ frame->interior.height = frame->height - decoration_height;
+
+ frame->opaque_margin = t->margin + t->frame_radius;
+ frame->shadow_margin = t->margin;
+ }
+
+ x_r = frame->width - t->width - frame->shadow_margin;
+ x_l = t->width + frame->shadow_margin;
+ y = t->width + frame->shadow_margin;
+ wl_list_for_each(button, &frame->buttons, link) {
+ const int button_padding = 4;
+ w = cairo_image_surface_get_width(button->icon);
+ h = cairo_image_surface_get_height(button->icon);
+
+ if (button->flags & FRAME_BUTTON_DECORATED)
+ w += 10;
+
+ if (button->flags & FRAME_BUTTON_ALIGN_RIGHT) {
+ x_r -= w;
+
+ button->allocation.x = x_r;
+ button->allocation.y = y;
+ button->allocation.width = w + 1;
+ button->allocation.height = h + 1;
+
+ x_r -= button_padding;
+ } else {
+ button->allocation.x = x_l;
+ button->allocation.y = y;
+ button->allocation.width = w + 1;
+ button->allocation.height = h + 1;
+
+ x_l += w;
+ x_l += button_padding;
+ }
+ }
+
+ frame->geometry_dirty = 0;
+}
+
+void
+frame_interior(struct frame *frame, int32_t *x, int32_t *y,
+ int32_t *width, int32_t *height)
+{
+ frame_refresh_geometry(frame);
+
+ if (x)
+ *x = frame->interior.x;
+ if (y)
+ *y = frame->interior.y;
+ if (width)
+ *width = frame->interior.width;
+ if (height)
+ *height = frame->interior.height;
+}
+
+void
+frame_input_rect(struct frame *frame, int32_t *x, int32_t *y,
+ int32_t *width, int32_t *height)
+{
+ frame_refresh_geometry(frame);
+
+ if (x)
+ *x = frame->shadow_margin;
+ if (y)
+ *y = frame->shadow_margin;
+ if (width)
+ *width = frame->width - frame->shadow_margin * 2;
+ if (height)
+ *height = frame->height - frame->shadow_margin * 2;
+}
+
+void
+frame_opaque_rect(struct frame *frame, int32_t *x, int32_t *y,
+ int32_t *width, int32_t *height)
+{
+ frame_refresh_geometry(frame);
+
+ if (x)
+ *x = frame->opaque_margin;
+ if (y)
+ *y = frame->opaque_margin;
+ if (width)
+ *width = frame->width - frame->opaque_margin * 2;
+ if (height)
+ *height = frame->height - frame->opaque_margin * 2;
+}
+
+uint32_t
+frame_status(struct frame *frame)
+{
+ return frame->status;
+}
+
+void
+frame_status_clear(struct frame *frame, enum frame_status status)
+{
+ frame->status &= ~status;
+}
+
+static struct frame_button *
+frame_find_button(struct frame *frame, int x, int y)
+{
+ struct frame_button *button;
+ int rel_x, rel_y;
+
+ wl_list_for_each(button, &frame->buttons, link) {
+ rel_x = x - button->allocation.x;
+ rel_y = y - button->allocation.y;
+
+ if (0 <= rel_x && rel_x < button->allocation.width &&
+ 0 <= rel_y && rel_y < button->allocation.height)
+ return button;
+ }
+
+ return NULL;
+}
+
+enum theme_location
+frame_pointer_enter(struct frame *frame, void *data, int x, int y)
+{
+ return frame_pointer_motion(frame, data, x, y);
+}
+
+enum theme_location
+frame_pointer_motion(struct frame *frame, void *data, int x, int y)
+{
+ struct frame_pointer *pointer = frame_pointer_get(frame, data);
+ struct frame_button *button = frame_find_button(frame, x, y);
+ enum theme_location location;
+
+ location = theme_get_location(frame->theme, x, y,
+ frame->width, frame->height,
+ frame->flags & FRAME_FLAG_MAXIMIZED ?
+ THEME_FRAME_MAXIMIZED : 0);
+ if (!pointer)
+ return location;
+
+ pointer->x = x;
+ pointer->y = y;
+
+ if (pointer->hover_button == button)
+ return location;
+
+ if (pointer->hover_button)
+ frame_button_leave(pointer->hover_button, pointer);
+
+ /* No drags */
+ pointer->active = 0;
+ pointer->hover_button = button;
+
+ if (pointer->hover_button)
+ frame_button_enter(pointer->hover_button);
+
+ return location;
+}
+
+void
+frame_pointer_leave(struct frame *frame, void *data)
+{
+ struct frame_pointer *pointer = frame_pointer_get(frame, data);
+ if (!pointer)
+ return;
+
+ if (pointer->hover_button)
+ frame_button_leave(pointer->hover_button, pointer);
+
+ frame_pointer_destroy(pointer);
+}
+
+enum theme_location
+frame_pointer_button(struct frame *frame, void *data,
+ uint32_t button, enum frame_button_state state)
+{
+ struct frame_pointer *pointer = frame_pointer_get(frame, data);
+ enum theme_location location;
+
+ location = theme_get_location(frame->theme, pointer->x, pointer->y,
+ frame->width, frame->height,
+ frame->flags & FRAME_FLAG_MAXIMIZED ?
+ THEME_FRAME_MAXIMIZED : 0);
+
+ if (!pointer)
+ return location;
+
+ if (button == BTN_RIGHT) {
+ if (state == FRAME_BUTTON_PRESSED &&
+ location == THEME_LOCATION_TITLEBAR)
+ frame->status |= FRAME_STATUS_MENU;
+
+ } else if (button == BTN_LEFT && state == FRAME_BUTTON_PRESSED) {
+ if (pointer->hover_button) {
+ pointer->active = 1;
+ frame_button_press(pointer->hover_button);
+ return location;
+ } else {
+ switch (location) {
+ case THEME_LOCATION_TITLEBAR:
+ frame->status |= FRAME_STATUS_MOVE;
+ break;
+ case THEME_LOCATION_RESIZING_TOP:
+ case THEME_LOCATION_RESIZING_BOTTOM:
+ case THEME_LOCATION_RESIZING_LEFT:
+ case THEME_LOCATION_RESIZING_RIGHT:
+ case THEME_LOCATION_RESIZING_TOP_LEFT:
+ case THEME_LOCATION_RESIZING_TOP_RIGHT:
+ case THEME_LOCATION_RESIZING_BOTTOM_LEFT:
+ case THEME_LOCATION_RESIZING_BOTTOM_RIGHT:
+ frame->status |= FRAME_STATUS_RESIZE;
+ break;
+ default:
+ break;
+ }
+ }
+ } else if (button == BTN_LEFT && state == FRAME_BUTTON_RELEASED) {
+ if (pointer->hover_button && pointer->active)
+ frame_button_release(pointer->hover_button);
+
+ pointer->active = 0;
+ }
+
+ return location;
+}
+
+void
+frame_repaint(struct frame *frame, cairo_t *cr)
+{
+ struct frame_button *button;
+ uint32_t flags = 0;
+
+ frame_refresh_geometry(frame);
+
+ if (frame->flags & FRAME_FLAG_MAXIMIZED)
+ flags |= THEME_FRAME_MAXIMIZED;
+
+ if (frame->flags & FRAME_FLAG_ACTIVE)
+ flags |= THEME_FRAME_ACTIVE;
+
+ cairo_save(cr);
+ theme_render_frame(frame->theme, cr, frame->width, frame->height,
+ frame->title, flags);
+ cairo_restore(cr);
+
+ wl_list_for_each(button, &frame->buttons, link)
+ frame_button_repaint(button, cr);
+
+ frame_status_clear(frame, FRAME_STATUS_REPAINT);
+}