diff --git a/shared/Makefile.am b/shared/Makefile.am index 2fcff7bb..31fab5f6 100644 --- a/shared/Makefile.am +++ b/shared/Makefile.am @@ -10,6 +10,7 @@ libshared_la_SOURCES = \ os-compatibility.h libshared_cairo_la_CFLAGS = \ + -DDATADIR='"$(datadir)"' \ $(GCC_CFLAGS) \ $(COMPOSITOR_CFLAGS) \ $(PIXMAN_CFLAGS) \ @@ -29,4 +30,5 @@ libshared_cairo_la_SOURCES = \ image-loader.c \ image-loader.h \ cairo-util.c \ + frame.c \ cairo-util.h diff --git a/shared/cairo-util.h b/shared/cairo-util.h index 7b403944..da1cef95 100644 --- a/shared/cairo-util.h +++ b/shared/cairo-util.h @@ -23,6 +23,7 @@ #ifndef _CAIRO_UTIL_H #define _CAIRO_UTIL_H +#include #include void @@ -86,4 +87,114 @@ enum theme_location { enum theme_location theme_get_location(struct theme *t, int x, int y, int width, int height, int flags); +struct frame; + +enum frame_status { + FRAME_STATUS_NONE = 0, + FRAME_STATUS_REPAINT = 0x1, + FRAME_STATUS_MINIMIZE = 0x2, + FRAME_STATUS_MAXIMIZE = 0x4, + FRAME_STATUS_CLOSE = 0x8, + FRAME_STATUS_MENU = 0x10, + FRAME_STATUS_RESIZE = 0x20, + FRAME_STATUS_MOVE = 0x40, + FRAME_STATUS_ALL = 0x7f +}; + +enum frame_flag { + FRAME_FLAG_ACTIVE = 0x1, + FRAME_FLAG_MAXIMIZED = 0x2 +}; + +enum { + FRAME_BUTTON_NONE = 0, + FRAME_BUTTON_CLOSE = 0x1, + FRAME_BUTTON_MAXIMIZE = 0x2, + FRAME_BUTTON_MINIMIZE = 0x4, + FRAME_BUTTON_ALL = 0x7 +}; + +enum frame_button_state { + FRAME_BUTTON_RELEASED = 0, + FRAME_BUTTON_PRESSED = 1 +}; + +struct frame * +frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, + const char *title); + +void +frame_destroy(struct frame *frame); + +/* May set FRAME_STATUS_REPAINT */ +int +frame_set_title(struct frame *frame, const char *title); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_set_flag(struct frame *frame, enum frame_flag flag); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_unset_flag(struct frame *frame, enum frame_flag flag); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_resize(struct frame *frame, int32_t width, int32_t height); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_resize_inside(struct frame *frame, int32_t width, int32_t height); + +int32_t +frame_width(struct frame *frame); + +int32_t +frame_height(struct frame *frame); + +void +frame_interior(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height); +void +frame_input_rect(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height); +void +frame_opaque_rect(struct frame *frame, int32_t *x, int32_t *y, + int32_t *width, int32_t *height); + +uint32_t +frame_status(struct frame *frame); + +void +frame_status_clear(struct frame *frame, enum frame_status status); + +/* May set FRAME_STATUS_REPAINT */ +enum theme_location +frame_pointer_enter(struct frame *frame, void *pointer, int x, int y); + +/* May set FRAME_STATUS_REPAINT */ +enum theme_location +frame_pointer_motion(struct frame *frame, void *pointer, int x, int y); + +/* May set FRAME_STATUS_REPAINT */ +void +frame_pointer_leave(struct frame *frame, void *pointer); + +/* May set: + * FRAME_STATUS_MINIMIZE + * FRAME_STATUS_MAXIMIZE + * FRAME_STATUS_CLOSE + * FRAME_STATUS_MENU + * FRAME_STATUS_RESIZE + * FRAME_STATUS_MOVE + */ +enum theme_location +frame_pointer_button(struct frame *frame, void *pointer, + uint32_t button, enum frame_button_state state); + +/* TODO: Add Touch */ + +void +frame_repaint(struct frame *frame, cairo_t *cr); + #endif 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 +#include +#include +#include + +#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); +}