/*
 * 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-util.h>
#include <linux/input.h>

#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_border_sizes(struct frame *frame, int32_t *top, int32_t *bottom,
		   int32_t *left, int32_t *right)
{
	struct theme *t = frame->theme;

	/* Top may have a titlebar */
	if (frame->title || !wl_list_empty(&frame->buttons))
		*top = t->titlebar_height;
	else
		*top = t->width;

	/* All other sides have the basic frame thickness */
	*bottom = t->width;
	*right = t->width;
	*left = t->width;
}
void
frame_decoration_sizes(struct frame *frame, int32_t *top, int32_t *bottom,
		       int32_t *left, int32_t *right)
{
	struct theme *t = frame->theme;

	frame_border_sizes(frame, top, bottom, left, right);

	if (frame->flags & FRAME_FLAG_MAXIMIZED)
		return;

	/* Not maximized, add shadows */
	*top += t->margin;
	*bottom += t->margin;
	*left += t->margin;
	*right += t->margin;
}

void
frame_resize_inside(struct frame *frame, int32_t width, int32_t height)
{
	int32_t top, bottom, left, right;

	frame_decoration_sizes(frame, &top, &bottom, &left, &right);
	frame_resize(frame, width + left + right,
		     height + top + bottom);
}

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);
	}
}

enum theme_location
frame_tablet_tool_motion(struct frame *frame, void *data, int x, int y)
{
	struct frame_pointer *tool_pointer = frame_pointer_get(frame, data);
	struct frame_button *button,
			    *prev_button = tool_pointer->hover_button;
	enum theme_location location;

	location = theme_get_location(frame->theme, tool_pointer->x,
				      tool_pointer->y, frame->width,
				      frame->height,
				      frame->flags & FRAME_FLAG_MAXIMIZED ?
				      THEME_FRAME_MAXIMIZED : 0);

	if (!tool_pointer)
		return location;

	tool_pointer->x = x;
	tool_pointer->y = y;

	button = frame_find_button(frame, x, y);

	if (prev_button) {
		if (prev_button == button)
			/* The button hasn't changed so we're done here */
			return location;
		else
			frame_button_leave(prev_button, tool_pointer);
	}

	if (button)
		frame_button_enter(button);

	tool_pointer->hover_button = button;

	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;

	if (frame->flags & FRAME_FLAG_NO_SHADOW)
		flags |= THEME_FRAME_NO_SHADOW;

	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);
}
