blob: 956e104e9a808ef6e3aca4fe3c0c0cddd55fc462 [file] [log] [blame]
Jason Ekstrand01c9ec32013-10-13 19:08:39 -05001/*
2 * Copyright © 2008 Kristian Høgsberg
3 * Copyright © 2012-2013 Collabora, Ltd.
4 * Copyright © 2013 Jason Ekstrand
5 *
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that copyright
9 * notice and this permission notice appear in supporting documentation, and
10 * that the name of the copyright holders not be used in advertising or
11 * publicity pertaining to distribution of the software without specific,
12 * written prior permission. The copyright holders make no representations
13 * about the suitability of this software for any purpose. It is provided "as
14 * is" without express or implied warranty.
15 *
16 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
20 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
21 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
22 * OF THIS SOFTWARE.
23 */
24
25#include "config.h"
26
27#include <stdlib.h>
28#include <string.h>
29#include <wayland-util.h>
30#include <linux/input.h>
31
32#include "cairo-util.h"
33
34enum frame_button_flags {
35 FRAME_BUTTON_ALIGN_RIGHT = 0x1,
36 FRAME_BUTTON_DECORATED = 0x2,
37 FRAME_BUTTON_CLICK_DOWN = 0x4,
38};
39
40struct frame_button {
41 struct frame *frame;
42 struct wl_list link; /* buttons_list */
43
44 cairo_surface_t *icon;
45 enum frame_button_flags flags;
46 int hover_count;
47 int press_count;
48
49 struct {
50 int x, y;
51 int width, height;
52 } allocation;
53
54 enum frame_status status_effect;
55};
56
57struct frame_pointer {
58 struct wl_list link;
59 void *data;
60
61 int x, y;
62
63 struct frame_button *hover_button;
64 int active;
65};
66
Jason Ekstrand3f66cf92013-10-13 19:08:40 -050067struct frame_touch {
68 struct wl_list link;
69 void *data;
70
71 int x, y;
72
73 struct frame_button *button;
74};
75
Jason Ekstrand01c9ec32013-10-13 19:08:39 -050076struct frame {
77 int32_t width, height;
78 char *title;
79 uint32_t flags;
80 struct theme *theme;
81
82 struct {
83 int32_t x, y;
84 int32_t width, height;
85 } interior;
86 int shadow_margin;
87 int opaque_margin;
88 int geometry_dirty;
89
90 uint32_t status;
91
92 struct wl_list buttons;
93 struct wl_list pointers;
Jason Ekstrand3f66cf92013-10-13 19:08:40 -050094 struct wl_list touches;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -050095};
96
97static struct frame_button *
98frame_button_create(struct frame *frame, const char *icon,
99 enum frame_status status_effect,
100 enum frame_button_flags flags)
101{
102 struct frame_button *button;
103
104 button = calloc(1, sizeof *button);
105 if (!button)
106 return NULL;
107
108 button->icon = cairo_image_surface_create_from_png(icon);
109 if (!button->icon) {
110 free(button);
111 return NULL;
112 }
113
114 button->frame = frame;
115 button->flags = flags;
116 button->status_effect = status_effect;
117
118 wl_list_insert(frame->buttons.prev, &button->link);
119
120 return button;
121}
122
123static void
124frame_button_destroy(struct frame_button *button)
125{
126 cairo_surface_destroy(button->icon);
127 free(button);
128}
129
130static void
131frame_button_enter(struct frame_button *button)
132{
133 if (!button->hover_count)
134 button->frame->status |= FRAME_STATUS_REPAINT;
135 button->hover_count++;
136}
137
138static void
139frame_button_leave(struct frame_button *button, struct frame_pointer *pointer)
140{
141 button->hover_count--;
142 if (!button->hover_count)
143 button->frame->status |= FRAME_STATUS_REPAINT;
144
145 /* In this case, we won't get a release */
146 if (pointer->active)
147 button->press_count--;
148}
149
150static void
151frame_button_press(struct frame_button *button)
152{
153 if (!button->press_count)
154 button->frame->status |= FRAME_STATUS_REPAINT;
155 button->press_count++;
156
157 if (button->flags & FRAME_BUTTON_CLICK_DOWN)
158 button->frame->status |= button->status_effect;
159}
160
161static void
162frame_button_release(struct frame_button *button)
163{
164 button->press_count--;
165 if (!button->press_count)
166 button->frame->status |= FRAME_STATUS_REPAINT;
167
168 if (!(button->flags & FRAME_BUTTON_CLICK_DOWN))
169 button->frame->status |= button->status_effect;
170}
171
172static void
173frame_button_repaint(struct frame_button *button, cairo_t *cr)
174{
175 int x, y;
176
177 if (!button->allocation.width)
178 return;
179 if (!button->allocation.height)
180 return;
181
182 x = button->allocation.x;
183 y = button->allocation.y;
184
185 cairo_save(cr);
186
187 if (button->flags & FRAME_BUTTON_DECORATED) {
188 cairo_set_line_width(cr, 1);
189
190 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
191 cairo_rectangle(cr, x, y, 25, 16);
192
193 cairo_stroke_preserve(cr);
194
195 if (button->press_count) {
196 cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
197 } else if (button->hover_count) {
198 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
199 } else {
200 cairo_set_source_rgb(cr, 0.88, 0.88, 0.88);
201 }
202
203 cairo_fill (cr);
204
205 x += 4;
206 }
207
208 cairo_set_source_surface(cr, button->icon, x, y);
209 cairo_paint(cr);
210
211 cairo_restore(cr);
212}
213
214static struct frame_pointer *
215frame_pointer_get(struct frame *frame, void *data)
216{
217 struct frame_pointer *pointer;
218
219 wl_list_for_each(pointer, &frame->pointers, link)
220 if (pointer->data == data)
221 return pointer;
222
223 pointer = calloc(1, sizeof *pointer);
224 if (!pointer)
225 return NULL;
226
227 pointer->data = data;
228 wl_list_insert(&frame->pointers, &pointer->link);
229
230 return pointer;
231}
232
233static void
234frame_pointer_destroy(struct frame_pointer *pointer)
235{
236 wl_list_remove(&pointer->link);
237 free(pointer);
238}
239
Jason Ekstrand3f66cf92013-10-13 19:08:40 -0500240static struct frame_touch *
241frame_touch_get(struct frame *frame, void *data)
242{
243 struct frame_touch *touch;
244
245 wl_list_for_each(touch, &frame->touches, link)
246 if (touch->data == data)
247 return touch;
248
249 touch = calloc(1, sizeof *touch);
250 if (!touch)
251 return NULL;
252
253 touch->data = data;
254 wl_list_insert(&frame->touches, &touch->link);
255
256 return touch;
257}
258
259static void
260frame_touch_destroy(struct frame_touch *touch)
261{
262 wl_list_remove(&touch->link);
263 free(touch);
264}
265
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500266struct frame *
267frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons,
268 const char *title)
269{
270 struct frame *frame;
271 struct frame_button *button;
272
273 frame = calloc(1, sizeof *frame);
274 if (!frame)
275 return NULL;
276
277 frame->width = width;
278 frame->height = height;
279 frame->flags = 0;
280 frame->theme = t;
281 frame->status = FRAME_STATUS_REPAINT;
282 frame->geometry_dirty = 1;
283
284 if (title) {
285 frame->title = strdup(title);
286 if (!frame->title)
287 goto free_frame;
288 }
289
290 wl_list_init(&frame->buttons);
291 wl_list_init(&frame->pointers);
Jason Ekstrand3f66cf92013-10-13 19:08:40 -0500292 wl_list_init(&frame->touches);
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500293
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700294 if (title) {
295 button = frame_button_create(frame,
296 DATADIR "/weston/icon_window.png",
297 FRAME_STATUS_MENU,
298 FRAME_BUTTON_CLICK_DOWN);
299 if (!button)
300 goto free_frame;
301 }
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500302
303 if (buttons & FRAME_BUTTON_CLOSE) {
304 button = frame_button_create(frame,
305 DATADIR "/weston/sign_close.png",
306 FRAME_STATUS_CLOSE,
307 FRAME_BUTTON_ALIGN_RIGHT |
308 FRAME_BUTTON_DECORATED);
309 if (!button)
310 goto free_frame;
311 }
312
313 if (buttons & FRAME_BUTTON_MAXIMIZE) {
314 button = frame_button_create(frame,
315 DATADIR "/weston/sign_maximize.png",
316 FRAME_STATUS_MAXIMIZE,
317 FRAME_BUTTON_ALIGN_RIGHT |
318 FRAME_BUTTON_DECORATED);
319 if (!button)
320 goto free_frame;
321 }
322
323 if (buttons & FRAME_BUTTON_MINIMIZE) {
324 button = frame_button_create(frame,
325 DATADIR "/weston/sign_minimize.png",
326 FRAME_STATUS_MINIMIZE,
327 FRAME_BUTTON_ALIGN_RIGHT |
328 FRAME_BUTTON_DECORATED);
329 if (!button)
330 goto free_frame;
331 }
332
333 return frame;
334
335free_frame:
336 free(frame->title);
337 free(frame);
338 return NULL;
339}
340
341void
342frame_destroy(struct frame *frame)
343{
344 struct frame_button *button, *next;
345
346 wl_list_for_each_safe(button, next, &frame->buttons, link)
347 frame_button_destroy(button);
348
349 free(frame->title);
350 free(frame);
351}
352
353int
354frame_set_title(struct frame *frame, const char *title)
355{
356 char *dup = NULL;
357
358 if (title) {
359 dup = strdup(title);
360 if (!dup)
361 return -1;
362 }
363
364 free(frame->title);
365 frame->title = dup;
366
367 frame->status |= FRAME_STATUS_REPAINT;
368
369 return 0;
370}
371
372void
373frame_set_flag(struct frame *frame, enum frame_flag flag)
374{
375 if (flag & FRAME_FLAG_MAXIMIZED && !(frame->flags & FRAME_FLAG_MAXIMIZED))
376 frame->geometry_dirty = 1;
377
378 frame->flags |= flag;
379 frame->status |= FRAME_STATUS_REPAINT;
380}
381
382void
383frame_unset_flag(struct frame *frame, enum frame_flag flag)
384{
385 if (flag & FRAME_FLAG_MAXIMIZED && frame->flags & FRAME_FLAG_MAXIMIZED)
386 frame->geometry_dirty = 1;
387
388 frame->flags &= ~flag;
389 frame->status |= FRAME_STATUS_REPAINT;
390}
391
392void
393frame_resize(struct frame *frame, int32_t width, int32_t height)
394{
395 frame->width = width;
396 frame->height = height;
397
398 frame->geometry_dirty = 1;
399 frame->status |= FRAME_STATUS_REPAINT;
400}
401
402void
403frame_resize_inside(struct frame *frame, int32_t width, int32_t height)
404{
405 struct theme *t = frame->theme;
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700406 int decoration_width, decoration_height, titlebar_height;
407
408 if (frame->title)
409 titlebar_height = t->titlebar_height;
410 else
411 titlebar_height = t->width;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500412
413 if (frame->flags & FRAME_FLAG_MAXIMIZED) {
414 decoration_width = t->width * 2;
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700415 decoration_height = t->width + titlebar_height;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500416 } else {
417 decoration_width = (t->width + t->margin) * 2;
418 decoration_height = t->width +
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700419 titlebar_height + t->margin * 2;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500420 }
421
422 frame_resize(frame, width + decoration_width,
423 height + decoration_height);
424}
425
426int32_t
427frame_width(struct frame *frame)
428{
429 return frame->width;
430}
431
432int32_t
433frame_height(struct frame *frame)
434{
435 return frame->height;
436}
437
438static void
439frame_refresh_geometry(struct frame *frame)
440{
441 struct frame_button *button;
442 struct theme *t = frame->theme;
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700443 int x_l, x_r, y, w, h, titlebar_height;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500444 int32_t decoration_width, decoration_height;
445
446 if (!frame->geometry_dirty)
447 return;
448
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700449 if (frame->title)
450 titlebar_height = t->titlebar_height;
451 else
452 titlebar_height = t->width;
453
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500454 if (frame->flags & FRAME_FLAG_MAXIMIZED) {
455 decoration_width = t->width * 2;
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700456 decoration_height = t->width + titlebar_height;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500457
458 frame->interior.x = t->width;
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700459 frame->interior.y = titlebar_height;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500460 frame->interior.width = frame->width - decoration_width;
461 frame->interior.height = frame->height - decoration_height;
462
463 frame->opaque_margin = 0;
464 frame->shadow_margin = 0;
465 } else {
466 decoration_width = (t->width + t->margin) * 2;
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700467 decoration_height = t->width + titlebar_height + t->margin * 2;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500468
469 frame->interior.x = t->width + t->margin;
Kristian Høgsberg89f4bc42013-10-23 22:12:13 -0700470 frame->interior.y = titlebar_height + t->margin;
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500471 frame->interior.width = frame->width - decoration_width;
472 frame->interior.height = frame->height - decoration_height;
473
474 frame->opaque_margin = t->margin + t->frame_radius;
475 frame->shadow_margin = t->margin;
476 }
477
478 x_r = frame->width - t->width - frame->shadow_margin;
479 x_l = t->width + frame->shadow_margin;
480 y = t->width + frame->shadow_margin;
481 wl_list_for_each(button, &frame->buttons, link) {
482 const int button_padding = 4;
483 w = cairo_image_surface_get_width(button->icon);
484 h = cairo_image_surface_get_height(button->icon);
485
486 if (button->flags & FRAME_BUTTON_DECORATED)
487 w += 10;
488
489 if (button->flags & FRAME_BUTTON_ALIGN_RIGHT) {
490 x_r -= w;
491
492 button->allocation.x = x_r;
493 button->allocation.y = y;
494 button->allocation.width = w + 1;
495 button->allocation.height = h + 1;
496
497 x_r -= button_padding;
498 } else {
499 button->allocation.x = x_l;
500 button->allocation.y = y;
501 button->allocation.width = w + 1;
502 button->allocation.height = h + 1;
503
504 x_l += w;
505 x_l += button_padding;
506 }
507 }
508
509 frame->geometry_dirty = 0;
510}
511
512void
513frame_interior(struct frame *frame, int32_t *x, int32_t *y,
514 int32_t *width, int32_t *height)
515{
516 frame_refresh_geometry(frame);
517
518 if (x)
519 *x = frame->interior.x;
520 if (y)
521 *y = frame->interior.y;
522 if (width)
523 *width = frame->interior.width;
524 if (height)
525 *height = frame->interior.height;
526}
527
528void
529frame_input_rect(struct frame *frame, int32_t *x, int32_t *y,
530 int32_t *width, int32_t *height)
531{
532 frame_refresh_geometry(frame);
533
534 if (x)
535 *x = frame->shadow_margin;
536 if (y)
537 *y = frame->shadow_margin;
538 if (width)
539 *width = frame->width - frame->shadow_margin * 2;
540 if (height)
541 *height = frame->height - frame->shadow_margin * 2;
542}
543
544void
545frame_opaque_rect(struct frame *frame, int32_t *x, int32_t *y,
546 int32_t *width, int32_t *height)
547{
548 frame_refresh_geometry(frame);
549
550 if (x)
551 *x = frame->opaque_margin;
552 if (y)
553 *y = frame->opaque_margin;
554 if (width)
555 *width = frame->width - frame->opaque_margin * 2;
556 if (height)
557 *height = frame->height - frame->opaque_margin * 2;
558}
559
560uint32_t
561frame_status(struct frame *frame)
562{
563 return frame->status;
564}
565
566void
567frame_status_clear(struct frame *frame, enum frame_status status)
568{
569 frame->status &= ~status;
570}
571
572static struct frame_button *
573frame_find_button(struct frame *frame, int x, int y)
574{
575 struct frame_button *button;
576 int rel_x, rel_y;
577
578 wl_list_for_each(button, &frame->buttons, link) {
579 rel_x = x - button->allocation.x;
580 rel_y = y - button->allocation.y;
581
582 if (0 <= rel_x && rel_x < button->allocation.width &&
583 0 <= rel_y && rel_y < button->allocation.height)
584 return button;
585 }
586
587 return NULL;
588}
589
590enum theme_location
591frame_pointer_enter(struct frame *frame, void *data, int x, int y)
592{
593 return frame_pointer_motion(frame, data, x, y);
594}
595
596enum theme_location
597frame_pointer_motion(struct frame *frame, void *data, int x, int y)
598{
599 struct frame_pointer *pointer = frame_pointer_get(frame, data);
600 struct frame_button *button = frame_find_button(frame, x, y);
601 enum theme_location location;
602
603 location = theme_get_location(frame->theme, x, y,
604 frame->width, frame->height,
605 frame->flags & FRAME_FLAG_MAXIMIZED ?
606 THEME_FRAME_MAXIMIZED : 0);
607 if (!pointer)
608 return location;
609
610 pointer->x = x;
611 pointer->y = y;
612
613 if (pointer->hover_button == button)
614 return location;
615
616 if (pointer->hover_button)
617 frame_button_leave(pointer->hover_button, pointer);
618
619 /* No drags */
620 pointer->active = 0;
621 pointer->hover_button = button;
622
623 if (pointer->hover_button)
624 frame_button_enter(pointer->hover_button);
625
626 return location;
627}
628
629void
630frame_pointer_leave(struct frame *frame, void *data)
631{
632 struct frame_pointer *pointer = frame_pointer_get(frame, data);
633 if (!pointer)
634 return;
635
636 if (pointer->hover_button)
637 frame_button_leave(pointer->hover_button, pointer);
638
639 frame_pointer_destroy(pointer);
640}
641
642enum theme_location
643frame_pointer_button(struct frame *frame, void *data,
644 uint32_t button, enum frame_button_state state)
645{
646 struct frame_pointer *pointer = frame_pointer_get(frame, data);
647 enum theme_location location;
648
649 location = theme_get_location(frame->theme, pointer->x, pointer->y,
650 frame->width, frame->height,
651 frame->flags & FRAME_FLAG_MAXIMIZED ?
652 THEME_FRAME_MAXIMIZED : 0);
653
654 if (!pointer)
655 return location;
656
657 if (button == BTN_RIGHT) {
658 if (state == FRAME_BUTTON_PRESSED &&
659 location == THEME_LOCATION_TITLEBAR)
660 frame->status |= FRAME_STATUS_MENU;
661
662 } else if (button == BTN_LEFT && state == FRAME_BUTTON_PRESSED) {
663 if (pointer->hover_button) {
664 pointer->active = 1;
665 frame_button_press(pointer->hover_button);
666 return location;
667 } else {
668 switch (location) {
669 case THEME_LOCATION_TITLEBAR:
670 frame->status |= FRAME_STATUS_MOVE;
671 break;
672 case THEME_LOCATION_RESIZING_TOP:
673 case THEME_LOCATION_RESIZING_BOTTOM:
674 case THEME_LOCATION_RESIZING_LEFT:
675 case THEME_LOCATION_RESIZING_RIGHT:
676 case THEME_LOCATION_RESIZING_TOP_LEFT:
677 case THEME_LOCATION_RESIZING_TOP_RIGHT:
678 case THEME_LOCATION_RESIZING_BOTTOM_LEFT:
679 case THEME_LOCATION_RESIZING_BOTTOM_RIGHT:
680 frame->status |= FRAME_STATUS_RESIZE;
681 break;
682 default:
683 break;
684 }
685 }
686 } else if (button == BTN_LEFT && state == FRAME_BUTTON_RELEASED) {
687 if (pointer->hover_button && pointer->active)
688 frame_button_release(pointer->hover_button);
689
690 pointer->active = 0;
691 }
692
693 return location;
694}
695
696void
Jason Ekstrand3f66cf92013-10-13 19:08:40 -0500697frame_touch_down(struct frame *frame, void *data, int32_t id, int x, int y)
698{
699 struct frame_touch *touch = frame_touch_get(frame, data);
700 struct frame_button *button = frame_find_button(frame, x, y);
701 enum theme_location location;
702
703 if (id > 0)
704 return;
705
706 if (button) {
707 touch->button = button;
708 frame_button_press(touch->button);
709 return;
710 }
711
712 location = theme_get_location(frame->theme, x, y,
713 frame->width, frame->height,
714 frame->flags & FRAME_FLAG_MAXIMIZED ?
715 THEME_FRAME_MAXIMIZED : 0);
716
717 switch (location) {
718 case THEME_LOCATION_TITLEBAR:
719 frame->status |= FRAME_STATUS_MOVE;
720 break;
721 case THEME_LOCATION_RESIZING_TOP:
722 case THEME_LOCATION_RESIZING_BOTTOM:
723 case THEME_LOCATION_RESIZING_LEFT:
724 case THEME_LOCATION_RESIZING_RIGHT:
725 case THEME_LOCATION_RESIZING_TOP_LEFT:
726 case THEME_LOCATION_RESIZING_TOP_RIGHT:
727 case THEME_LOCATION_RESIZING_BOTTOM_LEFT:
728 case THEME_LOCATION_RESIZING_BOTTOM_RIGHT:
729 frame->status |= FRAME_STATUS_RESIZE;
730 break;
731 default:
732 break;
733 }
734}
735
736void
737frame_touch_up(struct frame *frame, void *data, int32_t id)
738{
739 struct frame_touch *touch = frame_touch_get(frame, data);
740
741 if (id > 0)
742 return;
743
744 if (touch->button) {
745 frame_button_release(touch->button);
746 frame_touch_destroy(touch);
747 return;
748 }
749}
750
751void
Jason Ekstrand01c9ec32013-10-13 19:08:39 -0500752frame_repaint(struct frame *frame, cairo_t *cr)
753{
754 struct frame_button *button;
755 uint32_t flags = 0;
756
757 frame_refresh_geometry(frame);
758
759 if (frame->flags & FRAME_FLAG_MAXIMIZED)
760 flags |= THEME_FRAME_MAXIMIZED;
761
762 if (frame->flags & FRAME_FLAG_ACTIVE)
763 flags |= THEME_FRAME_ACTIVE;
764
765 cairo_save(cr);
766 theme_render_frame(frame->theme, cr, frame->width, frame->height,
767 frame->title, flags);
768 cairo_restore(cr);
769
770 wl_list_for_each(button, &frame->buttons, link)
771 frame_button_repaint(button, cr);
772
773 frame_status_clear(frame, FRAME_STATUS_REPAINT);
774}