expo: Allow rendering the background of any object
[pandora-u-boot.git] / boot / scene.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Implementation of a scene, a collection of text/image/menu items in an expo
4  *
5  * Copyright 2022 Google LLC
6  * Written by Simon Glass <sjg@chromium.org>
7  */
8
9 #define LOG_CATEGORY    LOGC_EXPO
10
11 #include <common.h>
12 #include <dm.h>
13 #include <expo.h>
14 #include <malloc.h>
15 #include <mapmem.h>
16 #include <menu.h>
17 #include <video.h>
18 #include <video_console.h>
19 #include <linux/input.h>
20 #include "scene_internal.h"
21
22 int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
23 {
24         struct scene *scn;
25
26         scn = calloc(1, sizeof(struct scene));
27         if (!scn)
28                 return log_msg_ret("expo", -ENOMEM);
29         scn->name = strdup(name);
30         if (!scn->name) {
31                 free(scn);
32                 return log_msg_ret("name", -ENOMEM);
33         }
34
35         INIT_LIST_HEAD(&scn->obj_head);
36         scn->id = resolve_id(exp, id);
37         scn->expo = exp;
38         list_add_tail(&scn->sibling, &exp->scene_head);
39
40         *scnp = scn;
41
42         return scn->id;
43 }
44
45 void scene_obj_destroy(struct scene_obj *obj)
46 {
47         if (obj->type == SCENEOBJT_MENU)
48                 scene_menu_destroy((struct scene_obj_menu *)obj);
49         free(obj->name);
50         free(obj);
51 }
52
53 void scene_destroy(struct scene *scn)
54 {
55         struct scene_obj *obj, *next;
56
57         list_for_each_entry_safe(obj, next, &scn->obj_head, sibling)
58                 scene_obj_destroy(obj);
59
60         free(scn->name);
61         free(scn);
62 }
63
64 int scene_title_set(struct scene *scn, uint id)
65 {
66         scn->title_id = id;
67
68         return 0;
69 }
70
71 int scene_obj_count(struct scene *scn)
72 {
73         struct scene_obj *obj;
74         int count = 0;
75
76         list_for_each_entry(obj, &scn->obj_head, sibling)
77                 count++;
78
79         return count;
80 }
81
82 void *scene_obj_find(const struct scene *scn, uint id, enum scene_obj_t type)
83 {
84         struct scene_obj *obj;
85
86         list_for_each_entry(obj, &scn->obj_head, sibling) {
87                 if (obj->id == id &&
88                     (type == SCENEOBJT_NONE || obj->type == type))
89                         return obj;
90         }
91
92         return NULL;
93 }
94
95 void *scene_obj_find_by_name(struct scene *scn, const char *name)
96 {
97         struct scene_obj *obj;
98
99         list_for_each_entry(obj, &scn->obj_head, sibling) {
100                 if (!strcmp(name, obj->name))
101                         return obj;
102         }
103
104         return NULL;
105 }
106
107 int scene_obj_add(struct scene *scn, const char *name, uint id,
108                   enum scene_obj_t type, uint size, struct scene_obj **objp)
109 {
110         struct scene_obj *obj;
111
112         obj = calloc(1, size);
113         if (!obj)
114                 return log_msg_ret("obj", -ENOMEM);
115         obj->name = strdup(name);
116         if (!obj->name) {
117                 free(obj);
118                 return log_msg_ret("name", -ENOMEM);
119         }
120
121         obj->id = resolve_id(scn->expo, id);
122         obj->scene = scn;
123         obj->type = type;
124         list_add_tail(&obj->sibling, &scn->obj_head);
125         *objp = obj;
126
127         return obj->id;
128 }
129
130 int scene_img(struct scene *scn, const char *name, uint id, char *data,
131               struct scene_obj_img **imgp)
132 {
133         struct scene_obj_img *img;
134         int ret;
135
136         ret = scene_obj_add(scn, name, id, SCENEOBJT_IMAGE,
137                             sizeof(struct scene_obj_img),
138                             (struct scene_obj **)&img);
139         if (ret < 0)
140                 return log_msg_ret("obj", ret);
141
142         img->data = data;
143
144         if (imgp)
145                 *imgp = img;
146
147         return img->obj.id;
148 }
149
150 int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
151               struct scene_obj_txt **txtp)
152 {
153         struct scene_obj_txt *txt;
154         int ret;
155
156         ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
157                             sizeof(struct scene_obj_txt),
158                             (struct scene_obj **)&txt);
159         if (ret < 0)
160                 return log_msg_ret("obj", ret);
161
162         txt->str_id = str_id;
163
164         if (txtp)
165                 *txtp = txt;
166
167         return txt->obj.id;
168 }
169
170 int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
171                   const char *str, struct scene_obj_txt **txtp)
172 {
173         struct scene_obj_txt *txt;
174         int ret;
175
176         ret = expo_str(scn->expo, name, str_id, str);
177         if (ret < 0)
178                 return log_msg_ret("str", ret);
179         if (str_id && ret != str_id)
180                 return log_msg_ret("id", -EEXIST);
181         str_id = ret;
182
183         ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
184                             sizeof(struct scene_obj_txt),
185                             (struct scene_obj **)&txt);
186         if (ret < 0)
187                 return log_msg_ret("obj", ret);
188
189         txt->str_id = str_id;
190
191         if (txtp)
192                 *txtp = txt;
193
194         return txt->obj.id;
195 }
196
197 int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
198                        uint font_size)
199 {
200         struct scene_obj_txt *txt;
201
202         txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
203         if (!txt)
204                 return log_msg_ret("find", -ENOENT);
205         txt->font_name = font_name;
206         txt->font_size = font_size;
207
208         return 0;
209 }
210
211 int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)
212 {
213         struct scene_obj *obj;
214
215         obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
216         if (!obj)
217                 return log_msg_ret("find", -ENOENT);
218         obj->dim.x = x;
219         obj->dim.y = y;
220
221         return 0;
222 }
223
224 int scene_obj_set_size(struct scene *scn, uint id, int w, int h)
225 {
226         struct scene_obj *obj;
227
228         obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
229         if (!obj)
230                 return log_msg_ret("find", -ENOENT);
231         obj->dim.w = w;
232         obj->dim.h = h;
233
234         return 0;
235 }
236
237 int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
238 {
239         int ret;
240
241         ret = scene_obj_flag_clrset(scn, id, SCENEOF_HIDE,
242                                     hide ? SCENEOF_HIDE : 0);
243         if (ret)
244                 return log_msg_ret("flg", ret);
245
246         return 0;
247 }
248
249 int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set)
250 {
251         struct scene_obj *obj;
252
253         obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
254         if (!obj)
255                 return log_msg_ret("find", -ENOENT);
256         obj->flags &= ~clr;
257         obj->flags |= set;
258
259         return 0;
260 }
261
262 int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)
263 {
264         struct scene_obj *obj;
265
266         obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
267         if (!obj)
268                 return log_msg_ret("find", -ENOENT);
269
270         switch (obj->type) {
271         case SCENEOBJT_NONE:
272         case SCENEOBJT_MENU:
273                 break;
274         case SCENEOBJT_IMAGE: {
275                 struct scene_obj_img *img = (struct scene_obj_img *)obj;
276                 ulong width, height;
277                 uint bpix;
278
279                 video_bmp_get_info(img->data, &width, &height, &bpix);
280                 if (widthp)
281                         *widthp = width;
282                 return height;
283         }
284         case SCENEOBJT_TEXT: {
285                 struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
286                 struct expo *exp = scn->expo;
287                 struct vidconsole_bbox bbox;
288                 const char *str;
289                 int len, ret;
290
291                 str = expo_get_str(exp, txt->str_id);
292                 if (!str)
293                         return log_msg_ret("str", -ENOENT);
294                 len = strlen(str);
295
296                 /* if there is no console, make it up */
297                 if (!exp->cons) {
298                         if (widthp)
299                                 *widthp = 8 * len;
300                         return 16;
301                 }
302
303                 ret = vidconsole_measure(scn->expo->cons, txt->font_name,
304                                          txt->font_size, str, &bbox);
305                 if (ret)
306                         return log_msg_ret("mea", ret);
307                 if (widthp)
308                         *widthp = bbox.x1;
309
310                 return bbox.y1;
311         }
312         }
313
314         return 0;
315 }
316
317 /**
318  * scene_render_background() - Render the background for an object
319  *
320  * @obj: Object to render
321  */
322 static void scene_render_background(struct scene_obj *obj)
323 {
324         struct expo *exp = obj->scene->expo;
325         const struct expo_theme *theme = &exp->theme;
326         struct vidconsole_bbox bbox, label_bbox;
327         struct udevice *dev = exp->display;
328         struct video_priv *vid_priv;
329         struct udevice *cons = exp->cons;
330         struct vidconsole_colour old;
331         enum colour_idx fore, back;
332         uint inset = theme->menu_inset;
333
334         /* draw a background for the object */
335         if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
336                 fore = VID_BLACK;
337                 back = VID_WHITE;
338         } else {
339                 fore = VID_LIGHT_GRAY;
340                 back = VID_BLACK;
341         }
342
343         /* see if this object wants to render a background */
344         if (scene_obj_calc_bbox(obj, &bbox, &label_bbox))
345                 return;
346
347         vidconsole_push_colour(cons, fore, back, &old);
348         vid_priv = dev_get_uclass_priv(dev);
349         video_fill_part(dev, label_bbox.x0 - inset, label_bbox.y0 - inset,
350                         label_bbox.x1 + inset, label_bbox.y1 + inset,
351                         vid_priv->colour_fg);
352         vidconsole_pop_colour(cons, &old);
353 }
354
355 /**
356  * scene_obj_render() - Render an object
357  *
358  */
359 static int scene_obj_render(struct scene_obj *obj, bool text_mode)
360 {
361         struct scene *scn = obj->scene;
362         struct expo *exp = scn->expo;
363         const struct expo_theme *theme = &exp->theme;
364         struct udevice *dev = exp->display;
365         struct udevice *cons = text_mode ? NULL : exp->cons;
366         int x, y, ret;
367
368         x = obj->dim.x;
369         y = obj->dim.y;
370
371         switch (obj->type) {
372         case SCENEOBJT_NONE:
373                 break;
374         case SCENEOBJT_IMAGE: {
375                 struct scene_obj_img *img = (struct scene_obj_img *)obj;
376
377                 if (!cons)
378                         return -ENOTSUPP;
379                 ret = video_bmp_display(dev, map_to_sysmem(img->data), x, y,
380                                         true);
381                 if (ret < 0)
382                         return log_msg_ret("img", ret);
383                 break;
384         }
385         case SCENEOBJT_TEXT: {
386                 struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
387                 const char *str;
388
389                 if (!cons)
390                         return -ENOTSUPP;
391
392                 if (txt->font_name || txt->font_size) {
393                         ret = vidconsole_select_font(cons,
394                                                      txt->font_name,
395                                                      txt->font_size);
396                 } else {
397                         ret = vidconsole_select_font(cons, NULL, 0);
398                 }
399                 if (ret && ret != -ENOSYS)
400                         return log_msg_ret("font", ret);
401                 str = expo_get_str(exp, txt->str_id);
402                 if (str) {
403                         struct video_priv *vid_priv;
404                         struct vidconsole_colour old;
405                         enum colour_idx fore, back;
406
407                         if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
408                                 fore = VID_BLACK;
409                                 back = VID_WHITE;
410                         } else {
411                                 fore = VID_LIGHT_GRAY;
412                                 back = VID_BLACK;
413                         }
414
415                         vid_priv = dev_get_uclass_priv(dev);
416                         if (obj->flags & SCENEOF_POINT) {
417                                 vidconsole_push_colour(cons, fore, back, &old);
418                                 video_fill_part(dev, x - theme->menu_inset, y,
419                                                 x + obj->dim.w,
420                                                 y + obj->dim.h,
421                                                 vid_priv->colour_bg);
422                         }
423                         vidconsole_set_cursor_pos(cons, x, y);
424                         vidconsole_put_string(cons, str);
425                         if (obj->flags & SCENEOF_POINT)
426                                 vidconsole_pop_colour(cons, &old);
427                 }
428                 break;
429         }
430         case SCENEOBJT_MENU: {
431                 struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
432
433                 if (exp->popup && (obj->flags & SCENEOF_OPEN)) {
434                         if (!cons)
435                                 return -ENOTSUPP;
436
437                         /* draw a background behind the menu items */
438                         scene_render_background(obj);
439                 }
440                 /*
441                  * With a vidconsole, the text and item pointer are rendered as
442                  * normal objects so we don't need to do anything here. The menu
443                  * simply controls where they are positioned.
444                  */
445                 if (cons)
446                         return -ENOTSUPP;
447
448                 ret = scene_menu_display(menu);
449                 if (ret < 0)
450                         return log_msg_ret("img", ret);
451
452                 break;
453         }
454         }
455
456         return 0;
457 }
458
459 int scene_arrange(struct scene *scn)
460 {
461         struct scene_obj *obj;
462         int ret;
463
464         list_for_each_entry(obj, &scn->obj_head, sibling) {
465                 switch (obj->type) {
466                 case SCENEOBJT_NONE:
467                 case SCENEOBJT_IMAGE:
468                 case SCENEOBJT_TEXT:
469                         break;
470                 case SCENEOBJT_MENU: {
471                         struct scene_obj_menu *menu;
472
473                         menu = (struct scene_obj_menu *)obj,
474                         ret = scene_menu_arrange(scn, menu);
475                         if (ret)
476                                 return log_msg_ret("arr", ret);
477                         break;
478                 }
479                 }
480         }
481
482         return 0;
483 }
484
485 int scene_render_deps(struct scene *scn, uint id)
486 {
487         struct scene_obj *obj;
488         int ret;
489
490         if (!id)
491                 return 0;
492         obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
493         if (!obj)
494                 return log_msg_ret("obj", -ENOENT);
495
496         if (!(obj->flags & SCENEOF_HIDE)) {
497                 ret = scene_obj_render(obj, false);
498                 if (ret && ret != -ENOTSUPP)
499                         return log_msg_ret("ren", ret);
500
501                 switch (obj->type) {
502                 case SCENEOBJT_NONE:
503                 case SCENEOBJT_IMAGE:
504                 case SCENEOBJT_TEXT:
505                         break;
506                 case SCENEOBJT_MENU:
507                         scene_menu_render_deps(scn,
508                                                (struct scene_obj_menu *)obj);
509                         break;
510                 }
511         }
512
513         return 0;
514 }
515
516 int scene_render(struct scene *scn)
517 {
518         struct expo *exp = scn->expo;
519         struct scene_obj *obj;
520         int ret;
521
522         list_for_each_entry(obj, &scn->obj_head, sibling) {
523                 if (!(obj->flags & SCENEOF_HIDE)) {
524                         ret = scene_obj_render(obj, exp->text_mode);
525                         if (ret && ret != -ENOTSUPP)
526                                 return log_msg_ret("ren", ret);
527                 }
528         }
529
530         /* render any highlighted object on top of the others */
531         if (scn->highlight_id && !exp->text_mode) {
532                 ret = scene_render_deps(scn, scn->highlight_id);
533                 if (ret && ret != -ENOTSUPP)
534                         return log_msg_ret("dep", ret);
535         }
536
537         return 0;
538 }
539
540 /**
541  * send_key_obj() - Handle a keypress for moving between objects
542  *
543  * @scn: Scene to receive the key
544  * @key: Key to send (KEYCODE_UP)
545  * @event: Returns resulting event from this keypress
546  * Returns: 0 if OK, -ve on error
547  */
548 static void send_key_obj(struct scene *scn, struct scene_obj *obj, int key,
549                          struct expo_action *event)
550 {
551         switch (key) {
552         case BKEY_UP:
553                 while (obj != list_first_entry(&scn->obj_head, struct scene_obj,
554                                                sibling)) {
555                         obj = list_entry(obj->sibling.prev,
556                                          struct scene_obj, sibling);
557                         if (scene_obj_can_highlight(obj)) {
558                                 event->type = EXPOACT_POINT_OBJ;
559                                 event->select.id = obj->id;
560                                 log_debug("up to obj %d\n", event->select.id);
561                                 break;
562                         }
563                 }
564                 break;
565         case BKEY_DOWN:
566                 while (!list_is_last(&obj->sibling, &scn->obj_head)) {
567                         obj = list_entry(obj->sibling.next, struct scene_obj,
568                                          sibling);
569                         if (scene_obj_can_highlight(obj)) {
570                                 event->type = EXPOACT_POINT_OBJ;
571                                 event->select.id = obj->id;
572                                 log_debug("down to obj %d\n", event->select.id);
573                                 break;
574                         }
575                 }
576                 break;
577         case BKEY_SELECT:
578                 if (scene_obj_can_highlight(obj)) {
579                         event->type = EXPOACT_OPEN;
580                         event->select.id = obj->id;
581                         log_debug("open obj %d\n", event->select.id);
582                 }
583                 break;
584         case BKEY_QUIT:
585                 event->type = EXPOACT_QUIT;
586                 log_debug("obj quit\n");
587                 break;
588         }
589 }
590
591 int scene_send_key(struct scene *scn, int key, struct expo_action *event)
592 {
593         struct scene_obj *obj;
594         int ret;
595
596         event->type = EXPOACT_NONE;
597
598         /*
599          * In 'popup' mode, arrow keys move betwen objects, unless a menu is
600          * opened
601          */
602         if (scn->expo->popup) {
603                 obj = NULL;
604                 if (scn->highlight_id) {
605                         obj = scene_obj_find(scn, scn->highlight_id,
606                                              SCENEOBJT_NONE);
607                 }
608                 if (!obj)
609                         return 0;
610
611                 if (!(obj->flags & SCENEOF_OPEN)) {
612                         send_key_obj(scn, obj, key, event);
613                         return 0;
614                 }
615
616                 switch (obj->type) {
617                 case SCENEOBJT_NONE:
618                 case SCENEOBJT_IMAGE:
619                 case SCENEOBJT_TEXT:
620                         break;
621                 case SCENEOBJT_MENU: {
622                         struct scene_obj_menu *menu;
623
624                         menu = (struct scene_obj_menu *)obj,
625                         ret = scene_menu_send_key(scn, menu, key, event);
626                         if (ret)
627                                 return log_msg_ret("key", ret);
628                         break;
629                 }
630                 }
631                 return 0;
632         }
633
634         list_for_each_entry(obj, &scn->obj_head, sibling) {
635                 if (obj->type == SCENEOBJT_MENU) {
636                         struct scene_obj_menu *menu;
637
638                         menu = (struct scene_obj_menu *)obj,
639                         ret = scene_menu_send_key(scn, menu, key, event);
640                         if (ret)
641                                 return log_msg_ret("key", ret);
642                         break;
643                 }
644         }
645
646         return 0;
647 }
648
649 int scene_obj_calc_bbox(struct scene_obj *obj, struct vidconsole_bbox *bbox,
650                         struct vidconsole_bbox *label_bbox)
651 {
652         switch (obj->type) {
653         case SCENEOBJT_NONE:
654         case SCENEOBJT_IMAGE:
655         case SCENEOBJT_TEXT:
656                 return -ENOSYS;
657         case SCENEOBJT_MENU: {
658                 struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
659
660                 scene_menu_calc_bbox(menu, bbox, label_bbox);
661                 break;
662         }
663         }
664
665         return 0;
666 }
667
668 int scene_calc_dims(struct scene *scn, bool do_menus)
669 {
670         struct scene_obj *obj;
671         int ret;
672
673         list_for_each_entry(obj, &scn->obj_head, sibling) {
674                 switch (obj->type) {
675                 case SCENEOBJT_NONE:
676                 case SCENEOBJT_TEXT:
677                 case SCENEOBJT_IMAGE: {
678                         int width;
679
680                         if (!do_menus) {
681                                 ret = scene_obj_get_hw(scn, obj->id, &width);
682                                 if (ret < 0)
683                                         return log_msg_ret("get", ret);
684                                 obj->dim.w = width;
685                                 obj->dim.h = ret;
686                         }
687                         break;
688                 }
689                 case SCENEOBJT_MENU: {
690                         struct scene_obj_menu *menu;
691
692                         if (do_menus) {
693                                 menu = (struct scene_obj_menu *)obj;
694
695                                 ret = scene_menu_calc_dims(menu);
696                                 if (ret)
697                                         return log_msg_ret("men", ret);
698                         }
699                         break;
700                 }
701                 }
702         }
703
704         return 0;
705 }
706
707 int scene_apply_theme(struct scene *scn, struct expo_theme *theme)
708 {
709         struct scene_obj *obj;
710         int ret;
711
712         /* Avoid error-checking optional items */
713         scene_txt_set_font(scn, scn->title_id, NULL, theme->font_size);
714
715         list_for_each_entry(obj, &scn->obj_head, sibling) {
716                 switch (obj->type) {
717                 case SCENEOBJT_NONE:
718                 case SCENEOBJT_IMAGE:
719                 case SCENEOBJT_MENU:
720                         break;
721                 case SCENEOBJT_TEXT:
722                         scene_txt_set_font(scn, obj->id, NULL,
723                                            theme->font_size);
724                         break;
725                 }
726         }
727
728         ret = scene_arrange(scn);
729         if (ret)
730                 return log_msg_ret("arr", ret);
731
732         return 0;
733 }
734
735 void scene_set_highlight_id(struct scene *scn, uint id)
736 {
737         scn->highlight_id = id;
738 }
739
740 void scene_highlight_first(struct scene *scn)
741 {
742         struct scene_obj *obj;
743
744         list_for_each_entry(obj, &scn->obj_head, sibling) {
745                 if (scene_obj_can_highlight(obj)) {
746                         scene_set_highlight_id(scn, obj->id);
747                         return;
748                 }
749         }
750 }
751
752 int scene_set_open(struct scene *scn, uint id, bool open)
753 {
754         int ret;
755
756         ret = scene_obj_flag_clrset(scn, id, SCENEOF_OPEN,
757                                     open ? SCENEOF_OPEN : 0);
758         if (ret)
759                 return log_msg_ret("flg", ret);
760
761         return 0;
762 }
763
764 int scene_iter_objs(struct scene *scn, expo_scene_obj_iterator iter,
765                     void *priv)
766 {
767         struct scene_obj *obj;
768
769         list_for_each_entry(obj, &scn->obj_head, sibling) {
770                 int ret;
771
772                 ret = iter(obj, priv);
773                 if (ret)
774                         return log_msg_ret("itr", ret);
775         }
776
777         return 0;
778 }
779
780 int scene_bbox_union(struct scene *scn, uint id, int inset,
781                      struct vidconsole_bbox *bbox)
782 {
783         struct scene_obj *obj;
784
785         if (!id)
786                 return 0;
787         obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
788         if (!obj)
789                 return log_msg_ret("obj", -ENOENT);
790         if (bbox->valid) {
791                 bbox->x0 = min(bbox->x0, obj->dim.x - inset);
792                 bbox->y0 = min(bbox->y0, obj->dim.y);
793                 bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w + inset);
794                 bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
795         } else {
796                 bbox->x0 = obj->dim.x - inset;
797                 bbox->y0 = obj->dim.y;
798                 bbox->x1 = obj->dim.x + obj->dim.w + inset;
799                 bbox->y1 = obj->dim.y + obj->dim.h;
800                 bbox->valid = true;
801         }
802
803         return 0;
804 }