/* * Copyright © 2008 Kristian Høgsberg * Copyright © 2012-2013 Collabora, Ltd. * Copyright © 2013 Jason Ekstrand * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include "cairo-util.h" #include "shared/file-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_button { struct wl_list link; uint32_t button; enum theme_location press_location; struct frame_button *frame_button; }; struct frame_pointer { struct wl_list link; void *data; int x, y; struct frame_button *hover_button; struct wl_list down_buttons; }; struct frame_touch { struct wl_list link; void *data; int x, y; struct frame_button *button; }; 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; cairo_rectangle_int_t title_rect; uint32_t status; struct wl_list buttons; struct wl_list pointers; struct wl_list touches; }; static struct frame_button * frame_button_create_from_surface(struct frame *frame, cairo_surface_t *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 = icon; button->frame = frame; button->flags = flags; button->status_effect = status_effect; wl_list_insert(frame->buttons.prev, &button->link); return button; } static struct frame_button * frame_button_create(struct frame *frame, const char *icon_name, enum frame_status status_effect, enum frame_button_flags flags) { struct frame_button *button; cairo_surface_t *icon; icon = cairo_image_surface_create_from_png(icon_name); if (cairo_surface_status(icon) != CAIRO_STATUS_SUCCESS) { goto error; } button = frame_button_create_from_surface(frame, icon, status_effect, flags); if (!button) { goto error; } return button; error: cairo_surface_destroy(icon); return NULL; } 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; } } 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) { return; } button->frame->status |= FRAME_STATUS_REPAINT; if (!(button->flags & FRAME_BUTTON_CLICK_DOWN)) { button->frame->status |= button->status_effect; } } static void frame_button_cancel(struct frame_button *button) { button->press_count--; if (!button->press_count) { button->frame->status |= FRAME_STATUS_REPAINT; } } 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_init(&pointer->down_buttons); 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); } static struct frame_touch * frame_touch_get(struct frame *frame, void *data) { struct frame_touch *touch; wl_list_for_each(touch, &frame->touches, link) if (touch->data == data) { return touch; } touch = calloc(1, sizeof *touch); if (!touch) { return NULL; } touch->data = data; wl_list_insert(&frame->touches, &touch->link); return touch; } static void frame_touch_destroy(struct frame_touch *touch) { wl_list_remove(&touch->link); free(touch); } void frame_destroy(struct frame *frame) { struct frame_button *button, *next; struct frame_touch *touch, *next_touch; struct frame_pointer *pointer, *next_pointer; wl_list_for_each_safe(button, next, &frame->buttons, link) frame_button_destroy(button); wl_list_for_each_safe(touch, next_touch, &frame->touches, link) frame_touch_destroy(touch); wl_list_for_each_safe(pointer, next_pointer, &frame->pointers, link) frame_pointer_destroy(pointer); free(frame->title); free(frame); } struct frame * frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, const char *title, cairo_surface_t *icon) { 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; wl_list_init(&frame->buttons); wl_list_init(&frame->pointers); wl_list_init(&frame->touches); if (title) { frame->title = strdup(title); if (!frame->title) { goto free_frame; } } if (title) { if (icon) { button = frame_button_create_from_surface(frame, icon, FRAME_STATUS_MENU, FRAME_BUTTON_CLICK_DOWN); } else { char *name = file_name_with_datadir("icon_window.png"); if (!name) { goto free_frame; } button = frame_button_create(frame, name, FRAME_STATUS_MENU, FRAME_BUTTON_CLICK_DOWN); free(name); } if (!button) { goto free_frame; } } if (buttons & FRAME_BUTTON_CLOSE) { char *name = file_name_with_datadir("sign_close.png"); if (!name) { goto free_frame; } button = frame_button_create(frame, name, FRAME_STATUS_CLOSE, FRAME_BUTTON_ALIGN_RIGHT | FRAME_BUTTON_DECORATED); free(name); if (!button) { goto free_frame; } } if (buttons & FRAME_BUTTON_MAXIMIZE) { char *name = file_name_with_datadir("sign_maximize.png"); if (!name) { goto free_frame; } button = frame_button_create(frame, name, FRAME_STATUS_MAXIMIZE, FRAME_BUTTON_ALIGN_RIGHT | FRAME_BUTTON_DECORATED); free(name); if (!button) { goto free_frame; } } if (buttons & FRAME_BUTTON_MINIMIZE) { char *name = file_name_with_datadir("sign_minimize.png"); if (!name) { goto free_frame; } button = frame_button_create(frame, name, FRAME_STATUS_MINIMIZE, FRAME_BUTTON_ALIGN_RIGHT | FRAME_BUTTON_DECORATED); free(name); if (!button) { goto free_frame; } } return frame; free_frame: frame_destroy(frame); return NULL; } 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->geometry_dirty = 1; frame->status |= FRAME_STATUS_REPAINT; return 0; } void frame_set_icon(struct frame *frame, cairo_surface_t *icon) { struct frame_button *button; wl_list_for_each(button, &frame->buttons, link) { if (button->status_effect != FRAME_STATUS_MENU) { continue; } if (button->icon) { cairo_surface_destroy(button->icon); } button->icon = icon; frame->status |= FRAME_STATUS_REPAINT; } } 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, titlebar_height; if (frame->title || !wl_list_empty(&frame->buttons)) { titlebar_height = t->titlebar_height; } else { titlebar_height = t->width; } if (frame->flags & FRAME_FLAG_MAXIMIZED) { decoration_width = t->width * 2; decoration_height = t->width + titlebar_height; } else { decoration_width = (t->width + t->margin) * 2; decoration_height = t->width + 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, titlebar_height; int32_t decoration_width, decoration_height; if (!frame->geometry_dirty) { return; } if (frame->title || !wl_list_empty(&frame->buttons)) { titlebar_height = t->titlebar_height; } else { titlebar_height = t->width; } if (frame->flags & FRAME_FLAG_MAXIMIZED) { decoration_width = t->width * 2; decoration_height = t->width + titlebar_height; frame->interior.x = t->width; frame->interior.y = 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 + titlebar_height + t->margin * 2; frame->interior.x = t->width + t->margin; frame->interior.y = 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->title_rect.x = x_l; frame->title_rect.y = y; frame->title_rect.width = x_r - x_l; frame->title_rect.height = titlebar_height; 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; } } int frame_get_shadow_margin(struct frame *frame) { frame_refresh_geometry(frame); return frame->shadow_margin; } 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); } pointer->hover_button = button; if (pointer->hover_button) { frame_button_enter(pointer->hover_button); } return location; } static void frame_pointer_button_destroy(struct frame_pointer_button *button) { wl_list_remove(&button->link); free(button); } static void frame_pointer_button_press(struct frame *frame, struct frame_pointer *pointer, struct frame_pointer_button *button) { if (button->button == BTN_RIGHT) { if (button->press_location == THEME_LOCATION_TITLEBAR) { frame->status |= FRAME_STATUS_MENU; } frame_pointer_button_destroy(button); } else if (button->button == BTN_LEFT) { if (pointer->hover_button) { frame_button_press(pointer->hover_button); } else { switch (button->press_location) { case THEME_LOCATION_TITLEBAR: frame->status |= FRAME_STATUS_MOVE; frame_pointer_button_destroy(button); 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; frame_pointer_button_destroy(button); break; default: break; } } } } static void frame_pointer_button_release(struct frame *frame, struct frame_pointer *pointer, struct frame_pointer_button *button) { if ((button->button == BTN_LEFT) && button->frame_button) { if (button->frame_button == pointer->hover_button) { frame_button_release(button->frame_button); } else { frame_button_cancel(button->frame_button); } } } static void frame_pointer_button_cancel(struct frame *frame, struct frame_pointer *pointer, struct frame_pointer_button *button) { if (button->frame_button) { frame_button_cancel(button->frame_button); } } void frame_pointer_leave(struct frame *frame, void *data) { struct frame_pointer *pointer = frame_pointer_get(frame, data); struct frame_pointer_button *button, *next; if (!pointer) { return; } if (pointer->hover_button) { frame_button_leave(pointer->hover_button, pointer); } wl_list_for_each_safe(button, next, &pointer->down_buttons, link) { frame_pointer_button_cancel(frame, pointer, button); frame_pointer_button_destroy(button); } frame_pointer_destroy(pointer); } enum theme_location frame_pointer_button(struct frame *frame, void *data, uint32_t btn, enum wl_pointer_button_state state) { struct frame_pointer *pointer = frame_pointer_get(frame, data); struct frame_pointer_button *button; enum theme_location location = THEME_LOCATION_EXTERIOR; if (!pointer) { return 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 (state == WL_POINTER_BUTTON_STATE_PRESSED) { button = malloc(sizeof *button); if (!button) { return location; } button->button = btn; button->press_location = location; button->frame_button = pointer->hover_button; wl_list_insert(&pointer->down_buttons, &button->link); frame_pointer_button_press(frame, pointer, button); } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { button = NULL; wl_list_for_each(button, &pointer->down_buttons, link) if (button->button == btn) { break; } /* Make sure we didn't hit the end */ if (&button->link == &pointer->down_buttons) { return location; } location = button->press_location; frame_pointer_button_release(frame, pointer, button); frame_pointer_button_destroy(button); } return location; } enum theme_location frame_touch_down(struct frame *frame, void *data, int32_t id, int x, int y) { struct frame_touch *touch = frame_touch_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 (id > 0) { return location; } if (touch && button) { touch->button = button; frame_button_press(touch->button); return location; } 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; } return location; } void frame_touch_up(struct frame *frame, void *data, int32_t id) { struct frame_touch *touch = frame_touch_get(frame, data); if (id > 0) { return; } if (touch && touch->button) { frame_button_release(touch->button); frame_touch_destroy(touch); } } enum theme_location frame_double_click(struct frame *frame, void *data, uint32_t btn, enum wl_pointer_button_state state) { struct frame_pointer *pointer = frame_pointer_get(frame, data); struct frame_button *button; enum theme_location location = THEME_LOCATION_EXTERIOR; location = theme_get_location(frame->theme, pointer->x, pointer->y, frame->width, frame->height, frame->flags & FRAME_FLAG_MAXIMIZED ? THEME_FRAME_MAXIMIZED : 0); button = frame_find_button(frame, pointer->x, pointer->y); if ((location != THEME_LOCATION_TITLEBAR) || (btn != BTN_LEFT)) { return location; } if (state == WL_POINTER_BUTTON_STATE_PRESSED) { if (button) { frame_button_press(button); } else { frame->status |= FRAME_STATUS_MAXIMIZE; } } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { if (button) { frame_button_release(button); } } return location; } void frame_double_touch_down(struct frame *frame, void *data, int32_t id, int x, int y) { struct frame_touch *touch = frame_touch_get(frame, data); struct frame_button *button = frame_find_button(frame, x, y); enum theme_location location; if (touch && button) { touch->button = button; frame_button_press(touch->button); return; } location = theme_get_location(frame->theme, x, y, frame->width, frame->height, frame->flags & FRAME_FLAG_MAXIMIZED ? THEME_FRAME_MAXIMIZED : 0); switch (location) { case THEME_LOCATION_TITLEBAR: frame->status |= FRAME_STATUS_MAXIMIZE; 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; } } void frame_double_touch_up(struct frame *frame, void *data, int32_t id) { struct frame_touch *touch = frame_touch_get(frame, data); if (touch && touch->button) { frame_button_release(touch->button); frame_touch_destroy(touch); } } 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, &frame->title_rect, &frame->buttons, flags); cairo_restore(cr); wl_list_for_each(button, &frame->buttons, link) frame_button_repaint(button, cr); frame_status_clear(frame, FRAME_STATUS_REPAINT); }