URI:
       menu.c - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       menu.c (58875B)
       ---
            1 /*
            2  * Copyright (c) 2022-2026 lumidify <nobody@lumidify.org>
            3  *
            4  * Permission to use, copy, modify, and/or distribute this software for any
            5  * purpose with or without fee is hereby granted, provided that the above
            6  * copyright notice and this permission notice appear in all copies.
            7  *
            8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
            9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
           10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
           11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
           12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
           13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
           14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
           15  */
           16 
           17 /* NOTE: The implementation of menus and menu entries is a collection of ugly hacks. */
           18 
           19 /* FIXME: parent is pressed when scroll arrows pressed */
           20 /* -> this is because the pressed handling checks if the widget is activatable, then goes to the parent,
           21    but the child isn't geometrically in the parent here, so that's weird */
           22 
           23 #include <stdint.h>
           24 #include <string.h>
           25 #include <math.h>
           26 #include <limits.h>
           27 
           28 #include "event.h"
           29 #include "config.h"
           30 #include "memory.h"
           31 #include "color.h"
           32 #include "rect.h"
           33 #include "widget.h"
           34 #include "ltk.h"
           35 #include "util.h"
           36 #include "text.h"
           37 #include "menu.h"
           38 #include "graphics.h"
           39 
           40 #define MAX_MENU_BORDER_WIDTH 10000
           41 #define MAX_MENU_PAD 50000
           42 #define MAX_MENU_ARROW_SIZE 10000
           43 
           44 #define MAX(a, b) ((a) > (b) ? (a) : (b))
           45 
           46 static struct theme {
           47         ltk_color *border;
           48         ltk_color *background;
           49         ltk_color *scroll_background;
           50         ltk_color *scroll_arrow_color;
           51 
           52         ltk_size pad;
           53         ltk_size arrow_pad;
           54         ltk_size arrow_size;
           55         ltk_size border_width;
           56         int compress_borders;
           57 } menu_theme, submenu_theme;
           58 
           59 static struct entry_theme {
           60         int compress_borders;
           61         /* FIXME: should border_sides actually factor into
           62            size calculation? - probably useless and would
           63            just make it more complicated */
           64         /* FIXME: allow different values for different states? */
           65         ltk_border_sides border_sides;
           66 
           67         ltk_color *text;
           68         ltk_color *border;
           69         ltk_color *fill;
           70 
           71         ltk_color *text_pressed;
           72         ltk_color *border_pressed;
           73         ltk_color *fill_pressed;
           74 
           75         ltk_color *text_active;
           76         ltk_color *border_active;
           77         ltk_color *fill_active;
           78 
           79         ltk_color *text_disabled;
           80         ltk_color *border_disabled;
           81         ltk_color *fill_disabled;
           82 
           83         char *font;
           84         ltk_size text_pad;
           85         ltk_size arrow_pad;
           86         ltk_size arrow_size;
           87         ltk_size border_width;
           88         ltk_size font_size;
           89 } menu_entry_theme, submenu_entry_theme;
           90 
           91 static void ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r);
           92 static void ltk_menu_resize(ltk_widget *self);
           93 static void ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
           94 static void ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret);
           95 static void ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step);
           96 static void ltk_menu_scroll_callback(ltk_callback_arg data);
           97 static void stop_scrolling(ltk_menu *menu);
           98 static ltk_widget_id ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y);
           99 static int set_scroll_timer(ltk_menu *menu, int x, int y);
          100 static int ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event);
          101 static void ltk_menu_hide(ltk_widget *self);
          102 static void popup_active_menu(ltk_menuentry *e);
          103 static void unpopup_active_entry(ltk_menuentry *e);
          104 static int ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event);
          105 static int ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event);
          106 static int ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event);
          107 static void ltk_menu_destroy(ltk_widget *self, int shallow);
          108 
          109 static ltk_widget_id ltk_menu_create_base(ltk_widget_id windowid, int is_submenu);
          110 
          111 static int ltk_menu_remove_child(ltk_widget *self, ltk_widget_id childid);
          112 static int ltk_menuentry_remove_child(ltk_widget *self, ltk_widget_id childid);
          113 
          114 static ltk_widget_id ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect);
          115 static ltk_widget_id ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget_id childid);
          116 static ltk_widget_id ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget_id childid);
          117 static ltk_widget_id ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget_id childid);
          118 static ltk_widget_id ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget_id childid);
          119 
          120 static void ltk_menuentry_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
          121 static void ltk_menuentry_destroy(ltk_widget *self, int shallow);
          122 static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state);
          123 static int ltk_menuentry_release(ltk_widget *self);
          124 
          125 static ltk_widget_id ltk_menu_prev_child(ltk_widget *self, ltk_widget_id childid);
          126 static ltk_widget_id ltk_menu_next_child(ltk_widget *self, ltk_widget_id childid);
          127 static ltk_widget_id ltk_menu_first_child(ltk_widget *self);
          128 static ltk_widget_id ltk_menu_last_child(ltk_widget *self);
          129 static ltk_widget_id ltk_menuentry_get_child(ltk_widget *self);
          130 
          131 /* FIXME: these functions are named really badly */
          132 static void recalc_ideal_menu_size_with_notification(ltk_widget *self, ltk_widget_id childid);
          133 static void recalc_ideal_menu_size(ltk_menu *menu);
          134 static void ltk_menuentry_set_font(ltk_menuentry *entry);
          135 static void ltk_menu_recalc_ideal_size(ltk_widget *self);
          136 static void ltk_menuentry_recalc_ideal_size(ltk_widget *self);
          137 static void ltk_menuentry_recalc_ideal_size_with_notification(ltk_menuentry *entry);
          138 
          139 #define IS_SUBMENU(parent) (parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENU && (LTK_CAST_MENU(parent))->is_submenu)
          140 
          141 static struct ltk_widget_vtable vtable = {
          142         .key_press = NULL,
          143         .key_release = NULL,
          144         .mouse_press = NULL,
          145         .mouse_scroll = &ltk_menu_mouse_scroll,
          146         .motion_notify = &ltk_menu_motion_notify,
          147         .mouse_release = NULL,
          148         .mouse_enter = &ltk_menu_mouse_enter,
          149         .mouse_leave = &ltk_menu_mouse_leave,
          150         .get_child_at_pos = &ltk_menu_get_child_at_pos,
          151         .resize = &ltk_menu_resize,
          152         .change_state = NULL,
          153         .hide = &ltk_menu_hide,
          154         .draw = &ltk_menu_draw,
          155         .destroy = &ltk_menu_destroy,
          156         .child_size_change = &recalc_ideal_menu_size_with_notification,
          157         .remove_child = &ltk_menu_remove_child,
          158         .prev_child = &ltk_menu_prev_child,
          159         .next_child = &ltk_menu_next_child,
          160         .first_child = &ltk_menu_first_child,
          161         .last_child = &ltk_menu_last_child,
          162         .nearest_child = &ltk_menu_nearest_child,
          163         .nearest_child_left = &ltk_menu_nearest_child_left,
          164         .nearest_child_right = &ltk_menu_nearest_child_right,
          165         .nearest_child_above = &ltk_menu_nearest_child_above,
          166         .nearest_child_below = &ltk_menu_nearest_child_below,
          167         .ensure_rect_shown = &ltk_menu_ensure_rect_shown,
          168         .recalc_ideal_size = &ltk_menu_recalc_ideal_size,
          169         .type = LTK_WIDGET_MENU,
          170         .flags = LTK_NEEDS_REDRAW,
          171         .invalid_signal = LTK_MENU_SIGNAL_INVALID,
          172 };
          173 
          174 static struct ltk_widget_vtable entry_vtable = {
          175         .key_press = NULL,
          176         .key_release = NULL,
          177         .mouse_press = NULL,
          178         .motion_notify = NULL,
          179         .mouse_release = NULL,
          180         .release = &ltk_menuentry_release,
          181         .mouse_enter = NULL,
          182         .mouse_leave = NULL,
          183         .get_child_at_pos = NULL,
          184         .resize = NULL,
          185         .change_state = &ltk_menuentry_change_state,
          186         .hide = NULL,
          187         .draw = &ltk_menuentry_draw,
          188         .destroy = &ltk_menuentry_destroy,
          189         .child_size_change = NULL,
          190         .remove_child = &ltk_menuentry_remove_child,
          191         .first_child = &ltk_menuentry_get_child,
          192         .last_child = &ltk_menuentry_get_child,
          193         .recalc_ideal_size = &ltk_menuentry_recalc_ideal_size,
          194         .type = LTK_WIDGET_MENUENTRY,
          195         .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_HOVER_IS_ACTIVE,
          196         .invalid_signal = LTK_MENUENTRY_SIGNAL_INVALID,
          197 };
          198 
          199 /* FIXME: standardize menuentry vs. menu_entry */
          200 
          201 static ltk_theme_parseinfo menu_parseinfo[] = {
          202         {"pad", THEME_SIZE, {.size = &menu_theme.pad}, {.size = {.val = 0, .unit = LTK_UNIT_PX}}, 0, MAX_MENU_PAD, 0},
          203         {"arrow-pad", THEME_SIZE, {.size = &menu_theme.arrow_pad}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_PAD, 0},
          204         {"arrow-size", THEME_SIZE, {.size = &menu_theme.arrow_size}, {.size = {.val = 200, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_ARROW_SIZE, 0},
          205         {"border-width", THEME_SIZE, {.size = &menu_theme.border_width}, {.size = {.val = 0, .unit = LTK_UNIT_PX}}, 0, MAX_MENU_BORDER_WIDTH, 0},
          206         {"compress-borders", THEME_BOOL, {.b = &menu_theme.compress_borders}, {.b = 1}, 0, 0, 0},
          207         {"border", THEME_COLOR, {.color = &menu_theme.border}, {.color = "#000000"}, 0, 0, 0},
          208         {"background", THEME_COLOR, {.color = &menu_theme.background}, {.color = "#000000"}, 0, 0, 0},
          209         {"scroll-background", THEME_COLOR, {.color = &menu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0},
          210         {"scroll-arrow-color", THEME_COLOR, {.color = &menu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0},
          211 };
          212 
          213 static ltk_theme_parseinfo menu_entry_parseinfo[] = {
          214         {"text-pad", THEME_SIZE, {.size = &menu_entry_theme.text_pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_PAD, 0},
          215         {"arrow-pad", THEME_SIZE, {.size = &menu_entry_theme.arrow_pad}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_PAD, 0},
          216         {"arrow-size", THEME_SIZE, {.size = &menu_entry_theme.arrow_size}, {.size = {.val = 200, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_ARROW_SIZE, 0},
          217         {"border-width", THEME_SIZE, {.size = &menu_entry_theme.border_width}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_BORDER_WIDTH, 0},
          218         {"border-sides", THEME_BORDERSIDES, {.border = &menu_entry_theme.border_sides}, {.border = LTK_BORDER_ALL}, 0, 0, 0},
          219         {"compress-borders", THEME_BOOL, {.b = &menu_entry_theme.compress_borders}, {.b = 1}, 0, 0, 0},
          220         {"text", THEME_COLOR, {.color = &menu_entry_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0},
          221         {"border", THEME_COLOR, {.color = &menu_entry_theme.border}, {.color = "#339999"}, 0, 0, 0},
          222         {"fill", THEME_COLOR, {.color = &menu_entry_theme.fill}, {.color = "#113355"}, 0, 0, 0},
          223         {"text-pressed", THEME_COLOR, {.color = &menu_entry_theme.text_pressed}, {.color = "#000000"}, 0, 0, 0},
          224         {"border-pressed", THEME_COLOR, {.color = &menu_entry_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
          225         {"fill-pressed", THEME_COLOR, {.color = &menu_entry_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
          226         {"text-active", THEME_COLOR, {.color = &menu_entry_theme.text_active}, {.color = "#000000"}, 0, 0, 0},
          227         {"border-active", THEME_COLOR, {.color = &menu_entry_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
          228         {"fill-active", THEME_COLOR, {.color = &menu_entry_theme.fill_active}, {.color = "#738194"}, 0, 0, 0},
          229         {"text-disabled", THEME_COLOR, {.color = &menu_entry_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
          230         {"border-disabled", THEME_COLOR, {.color = &menu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
          231         {"fill-disabled", THEME_COLOR, {.color = &menu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
          232         {"font-size", THEME_SIZE, {.size = &menu_entry_theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
          233         {"font", THEME_STRING, {.str = &menu_entry_theme.font}, {.str = "Monospace"}, 0, 0, 0},
          234 };
          235 
          236 static ltk_theme_parseinfo submenu_parseinfo[] = {
          237         {"pad", THEME_SIZE, {.size = &submenu_theme.pad}, {.size = {.val = 0, .unit = LTK_UNIT_PX}}, 0, MAX_MENU_PAD, 0},
          238         {"arrow-pad", THEME_SIZE, {.size = &submenu_theme.arrow_pad}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_PAD, 0},
          239         {"arrow-size", THEME_SIZE, {.size = &submenu_theme.arrow_size}, {.size = {.val = 200, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_ARROW_SIZE, 0},
          240         {"border-width", THEME_SIZE, {.size = &submenu_theme.border_width}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_BORDER_WIDTH, 0},
          241         {"compress-borders", THEME_BOOL, {.b = &submenu_theme.compress_borders}, {.b = 1}, 0, 0, 0},
          242         {"border", THEME_COLOR, {.color = &submenu_theme.border}, {.color = "#FFFFFF"}, 0, 0, 0},
          243         {"background", THEME_COLOR, {.color = &submenu_theme.background}, {.color = "#000000"}, 0, 0, 0},
          244         {"scroll-background", THEME_COLOR, {.color = &submenu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0},
          245         {"scroll-arrow-color", THEME_COLOR, {.color = &submenu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0},
          246 };
          247 
          248 static ltk_theme_parseinfo submenu_entry_parseinfo[] = {
          249         {"text-pad", THEME_SIZE, {.size = &submenu_entry_theme.text_pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_PAD, 0},
          250         {"arrow-pad", THEME_SIZE, {.size = &submenu_entry_theme.arrow_pad}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_PAD, 0},
          251         {"arrow-size", THEME_SIZE, {.size = &submenu_entry_theme.arrow_size}, {.size = {.val = 200, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_ARROW_SIZE, 0},
          252         {"border-width", THEME_SIZE, {.size = &submenu_entry_theme.border_width}, {.size = {.val = 0, .unit = LTK_UNIT_PX}}, 0, MAX_MENU_BORDER_WIDTH, 0},
          253         {"border-sides", THEME_BORDERSIDES, {.border = &submenu_entry_theme.border_sides}, {.border = LTK_BORDER_NONE}, 0, 0, 0},
          254         {"compress-borders", THEME_BOOL, {.b = &submenu_entry_theme.compress_borders}, {.b = 0}, 0, 0, 0},
          255         {"text", THEME_COLOR, {.color = &submenu_entry_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0},
          256         {"border", THEME_COLOR, {.color = &submenu_entry_theme.border}, {.color = "#FFFFFF"}, 0, 0, 0},
          257         {"fill", THEME_COLOR, {.color = &submenu_entry_theme.fill}, {.color = "#113355"}, 0, 0, 0},
          258         {"text-pressed", THEME_COLOR, {.color = &submenu_entry_theme.text_pressed}, {.color = "#000000"}, 0, 0, 0},
          259         {"border-pressed", THEME_COLOR, {.color = &submenu_entry_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
          260         {"fill-pressed", THEME_COLOR, {.color = &submenu_entry_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
          261         {"text-active", THEME_COLOR, {.color = &submenu_entry_theme.text_active}, {.color = "#000000"}, 0, 0, 0},
          262         {"border-active", THEME_COLOR, {.color = &submenu_entry_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
          263         {"fill-active", THEME_COLOR, {.color = &submenu_entry_theme.fill_active}, {.color = "#738194"}, 0, 0, 0},
          264         {"text-disabled", THEME_COLOR, {.color = &submenu_entry_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
          265         {"border-disabled", THEME_COLOR, {.color = &submenu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
          266         {"fill-disabled", THEME_COLOR, {.color = &submenu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
          267         {"font-size", THEME_SIZE, {.size = &submenu_entry_theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
          268         {"font", THEME_STRING, {.str = &submenu_entry_theme.font}, {.str = "Monospace"}, 0, 0, 0},
          269 };
          270 void
          271 ltk_menu_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
          272         *p = menu_parseinfo;
          273         *len = LENGTH(menu_parseinfo);
          274 }
          275 
          276 void
          277 ltk_submenu_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
          278         *p = submenu_parseinfo;
          279         *len = LENGTH(submenu_parseinfo);
          280 }
          281 
          282 void
          283 ltk_menuentry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
          284         *p = menu_entry_parseinfo;
          285         *len = LENGTH(menu_entry_parseinfo);
          286 }
          287 
          288 void
          289 ltk_submenuentry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
          290         *p = submenu_entry_parseinfo;
          291         *len = LENGTH(submenu_entry_parseinfo);
          292 }
          293 
          294 static void
          295 ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) {
          296         ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
          297         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
          298         ltk_menu *parentm = parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENU ? LTK_CAST_MENU(parent) : NULL;
          299         ltk_widget *submenu = ltk_get_widget_or_null_from_id(e->submenu);
          300         int in_submenu = IS_SUBMENU(parent);
          301         int submenus_opened = parentm && parentm->popup_submenus;
          302         if (!(self->state & (LTK_ACTIVE | LTK_PRESSED))) {
          303                 /* Note: This only has to take care of the submenu that is the direct child
          304                    of e because ltk_window_set_active_widget already calls change_state for
          305                    the whole hierarchy */
          306                 unpopup_active_entry(e);
          307         } else if ((self->state & LTK_PRESSED) && !(old_state & LTK_PRESSED) && submenus_opened) {
          308                 parentm->popup_submenus = 0;
          309         } else if (((self->state & LTK_PRESSED) ||
          310                    ((self->state & LTK_ACTIVE) && (in_submenu || submenus_opened))) &&
          311                    submenu && submenu->hidden) {
          312                 popup_active_menu(e);
          313                 if (parentm)
          314                         parentm->popup_submenus = 1;
          315         }
          316 }
          317 
          318 static ltk_widget_id
          319 ltk_menuentry_get_child(ltk_widget *self) {
          320         ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
          321         ltk_widget *submenu = ltk_get_widget_or_null_from_id(e->submenu);
          322         return submenu && !submenu->hidden ? e->submenu : LTK_WIDGET_ID_NONE;
          323 }
          324 
          325 const char *
          326 ltk_menuentry_get_text(ltk_widget_id entryid) {
          327         ltk_widget *entryw = ltk_get_widget_from_id(entryid);
          328         ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw);
          329         return ltk_text_line_get_text(entry->text_line);
          330 }
          331 
          332 static void
          333 ltk_menuentry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
          334         /* FIXME: figure out how hidden should work */
          335         if (self->hidden)
          336                 return;
          337         ltk_menuentry *entry = LTK_CAST_MENUENTRY(self);
          338         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
          339         int in_submenu = IS_SUBMENU(parent);
          340         struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme;
          341         int bw = ltk_size_to_pixel(t->border_width, self->last_dpi);
          342         int text_pad = ltk_size_to_pixel(t->text_pad, self->last_dpi);
          343         int arrow_pad = ltk_size_to_pixel(t->arrow_pad, self->last_dpi);
          344         int arrow_size = ltk_size_to_pixel(t->arrow_size, self->last_dpi);
          345         ltk_color *text, *border, *fill;
          346         if (self->state & LTK_DISABLED) {
          347                 text = t->text_disabled;
          348                 border = t->border_disabled;
          349                 fill = t->fill_disabled;
          350         } else if (self->state & LTK_PRESSED) {
          351                 text = t->text_pressed;
          352                 border = t->border_pressed;
          353                 fill = t->fill_pressed;
          354         } else if (self->state & LTK_HOVERACTIVE) {
          355                 text = t->text_active;
          356                 border = t->border_active;
          357                 fill = t->fill_active;
          358         } else {
          359                 text = t->text;
          360                 border = t->border;
          361                 fill = t->fill;
          362         }
          363         ltk_rect lrect = self->lrect;
          364         ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
          365         if (clip_final.w <= 0 || clip_final.h <= 0)
          366                 return;
          367         ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
          368         ltk_surface_fill_rect(draw_surf, fill, surf_clip);
          369 
          370         int text_w, text_h;
          371         ltk_text_line_get_size(entry->text_line, &text_w, &text_h);
          372         int text_x = x + text_pad + bw;
          373         int text_y = y + text_pad + bw;
          374         ltk_rect text_clip = ltk_rect_intersect(surf_clip, (ltk_rect){text_x, text_y, text_w, text_h});
          375         ltk_text_line_draw_clipped(entry->text_line, draw_surf, text, text_x, text_y, text_clip);
          376 
          377         if (in_submenu && !LTK_WIDGET_ID_IS_NONE(entry->submenu)) {
          378                 ltk_point arrow_points[] = {
          379                     {x + lrect.w - arrow_pad - bw, y + lrect.h / 2},
          380                     {x + lrect.w - arrow_pad - bw - arrow_size, y + lrect.h / 2 - arrow_size / 2},
          381                     {x + lrect.w - arrow_pad - bw - arrow_size, y + lrect.h / 2 + arrow_size / 2}
          382                 };
          383                 ltk_surface_fill_polygon_clipped(draw_surf, text, arrow_points, LENGTH(arrow_points), surf_clip);
          384         }
          385         ltk_surface_draw_border_clipped(draw_surf, border, (ltk_rect){x, y, lrect.w, lrect.h}, bw, t->border_sides, surf_clip);
          386         self->dirty = 0;
          387 }
          388 
          389 static void
          390 ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
          391         if (self->hidden)
          392                 return;
          393         ltk_menu *menu = LTK_CAST_MENU(self);
          394         ltk_rect lrect = self->lrect;
          395         ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
          396         if (clip_final.w <= 0 || clip_final.h <= 0)
          397                 return;
          398 
          399         struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme;
          400         int bw = ltk_size_to_pixel(t->border_width, self->last_dpi);
          401         int arrow_pad = ltk_size_to_pixel(t->arrow_pad, self->last_dpi);
          402         int arrow_size = ltk_size_to_pixel(t->arrow_size, self->last_dpi);
          403 
          404         ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
          405         ltk_surface_fill_rect(s, t->background, surf_clip);
          406         for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
          407                 /* FIXME: I guess it could be improved *slightly* by making the clip rect
          408                    smaller when scrollarrows are shown */
          409                 /* draw active entry after others so it isn't hidden with compress_borders */
          410                 ltk_widget *ptr = ltk_get_widget_from_id(ltk_array_get(menu->entries, i));
          411                 if ((ptr->state & (LTK_ACTIVE | LTK_PRESSED | LTK_HOVER)) && i < ltk_array_len(menu->entries) - 1) {
          412                         ltk_widget *ptr2 = ltk_get_widget_from_id(ltk_array_get(menu->entries, i + 1));
          413                         ltk_menuentry_draw(ptr2, s, x + ptr2->lrect.x, y + ptr2->lrect.y, ltk_rect_relative(ptr2->lrect, clip_final));
          414                         ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final));
          415                         i++;
          416                 } else {
          417                         ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final));
          418                 }
          419         }
          420 
          421         /* FIXME: active, pressed states */
          422         int sz = arrow_size + arrow_pad * 2;
          423         int ww = self->lrect.w;
          424         int wh = self->lrect.h;
          425         int wx = x, wy = y;
          426         int mbw = bw;
          427         /* FIXME: handle pathological case where rect is so small that this still draws outside */
          428         /* -> this is currently a mess because some parts handle clipping properly, but the scroll arrow drawing doesn't */
          429         if (lrect.w < (int)self->ideal_w) {
          430                 ltk_surface_fill_rect(s, t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, sz, wh - mbw * 2});
          431                 ltk_surface_fill_rect(s, t->scroll_background, (ltk_rect){wx + ww - sz - mbw, wy + mbw, sz, wh - mbw * 2});
          432                 ltk_point arrow_points[3] = {
          433                     {wx + arrow_pad + mbw, wy + wh / 2},
          434                     {wx + arrow_pad + mbw + arrow_size, wy + wh / 2 - arrow_size / 2},
          435                     {wx + arrow_pad + mbw + arrow_size, wy + wh / 2 + arrow_size / 2}
          436                 };
          437                 ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3);
          438                 arrow_points[0] = (ltk_point){wx + ww - arrow_pad - mbw, wy + wh / 2};
          439                 arrow_points[1] = (ltk_point){wx + ww - arrow_pad - mbw - arrow_size, wy + wh / 2 - arrow_size / 2};
          440                 arrow_points[2] = (ltk_point){wx + ww - arrow_pad - mbw - arrow_size, wy + wh / 2 + arrow_size / 2};
          441                 ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3);
          442         }
          443         if (lrect.h < (int)self->ideal_h) {
          444                 ltk_widget *windoww = ltk_get_widget_from_id(self->window);
          445                 ltk_window *window = LTK_CAST_WINDOW(windoww);
          446                 ltk_surface_fill_rect(window->surface, t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, ww - mbw * 2, sz});
          447                 ltk_surface_fill_rect(window->surface, t->scroll_background, (ltk_rect){wx + mbw, wy + wh - sz - mbw, ww - mbw * 2, sz});
          448                 ltk_point arrow_points[3] = {
          449                     {wx + ww / 2, wy + arrow_pad + mbw},
          450                     {wx + ww / 2 - arrow_size / 2, wy + arrow_pad + mbw + arrow_size},
          451                     {wx + ww / 2 + arrow_size / 2, wy + arrow_pad + mbw + arrow_size}
          452                 };
          453                 ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3);
          454                 arrow_points[0] = (ltk_point){wx + ww / 2, wy + wh - arrow_pad - mbw};
          455                 arrow_points[1] = (ltk_point){wx + ww / 2 - arrow_size / 2, wy + wh - arrow_pad - mbw - arrow_size};
          456                 arrow_points[2] = (ltk_point){wx + ww / 2 + arrow_size / 2, wy + wh - arrow_pad - mbw - arrow_size};
          457                 ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3);
          458         }
          459         ltk_surface_draw_border_clipped(s, t->border, (ltk_rect){x, y, lrect.w, lrect.h}, mbw, LTK_BORDER_ALL, surf_clip);
          460 
          461         self->dirty = 0;
          462 }
          463 
          464 
          465 static void
          466 ltk_menu_resize(ltk_widget *self) {
          467         ltk_menu *menu = LTK_CAST_MENU(self);
          468         int max_x, max_y;
          469         ltk_menu_get_max_scroll_offset(menu, &max_x, &max_y);
          470         if (menu->x_scroll_offset > max_x)
          471                 menu->x_scroll_offset = max_x;
          472         if (menu->y_scroll_offset > max_y)
          473                 menu->y_scroll_offset = max_y;
          474 
          475         ltk_rect lrect = self->lrect;
          476         struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme;
          477         struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme;
          478 
          479         int bw = ltk_size_to_pixel(t->border_width, self->last_dpi);
          480         int pad = ltk_size_to_pixel(t->pad, self->last_dpi);
          481         int arrow_pad = ltk_size_to_pixel(t->arrow_pad, self->last_dpi);
          482         int raw_arrow_size = ltk_size_to_pixel(t->arrow_size, self->last_dpi);
          483         int entry_bw = ltk_size_to_pixel(et->border_width, self->last_dpi);
          484 
          485         int ideal_w = self->ideal_w, ideal_h = self->ideal_h;
          486         int arrow_size = arrow_pad * 2 + raw_arrow_size;
          487         int start_x = lrect.w < ideal_w ? arrow_size : 0;
          488         int start_y = lrect.h < ideal_h ? arrow_size : 0;
          489         start_x += bw;
          490         start_y += bw;
          491 
          492         int mbw = bw;
          493         int cur_abs_x = -(int)menu->x_scroll_offset + start_x + pad;
          494         int cur_abs_y = -(int)menu->y_scroll_offset + start_y + pad;
          495 
          496         for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
          497                 ltk_widget *widget = ltk_get_widget_from_id(ltk_array_get(menu->entries, i));
          498                 widget->lrect.x = cur_abs_x;
          499                 widget->lrect.y = cur_abs_y;
          500                 if (menu->is_submenu) {
          501                         widget->lrect.w = ideal_w - 2 * pad - 2 * mbw;
          502                         widget->lrect.h = widget->ideal_h;
          503                         cur_abs_y += widget->ideal_h + pad;
          504                         if (et->compress_borders)
          505                                 cur_abs_y -= entry_bw;
          506                 } else {
          507                         widget->lrect.w = widget->ideal_w;
          508                         widget->lrect.h = ideal_h - 2 * pad - 2 * mbw;
          509                         cur_abs_x += widget->ideal_w + pad;
          510                         if (et->compress_borders)
          511                                 cur_abs_x -= entry_bw;
          512                 }
          513                 widget->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, widget->lrect);
          514         }
          515         self->dirty = 1;
          516         ltk_window_invalidate_widget_rect(self->window, self->id);
          517 }
          518 
          519 static void
          520 ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r) {
          521         ltk_menu *menu = LTK_CAST_MENU(self);
          522         struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme;
          523         int bw = ltk_size_to_pixel(theme->border_width, self->last_dpi);
          524         int arrow_pad = ltk_size_to_pixel(theme->arrow_pad, self->last_dpi);
          525         int arrow_size = ltk_size_to_pixel(theme->arrow_size, self->last_dpi);
          526         int extra_size = arrow_size + arrow_pad * 2 + bw;
          527         int delta = 0;
          528         if (self->lrect.w < (int)self->ideal_w && !menu->is_submenu) {
          529                 if (r.x + r.w > self->lrect.w - extra_size && r.w <= self->lrect.w - 2 * extra_size)
          530                         delta = r.x - (self->lrect.w - extra_size - r.w);
          531                 else if (r.x < extra_size || r.w > self->lrect.w - 2 * extra_size)
          532                         delta = r.x - extra_size;
          533                 if (delta)
          534                         ltk_menu_scroll(menu, 0, 0, 0, 1, delta);
          535         } else if (self->lrect.h < (int)self->ideal_h && menu->is_submenu) {
          536                 if (r.y + r.h > self->lrect.h - extra_size && r.h <= self->lrect.h - 2 * extra_size)
          537                         delta = r.y - (self->lrect.h - extra_size - r.h);
          538                 else if (r.y < extra_size || r.h > self->lrect.h - 2 * extra_size)
          539                         delta = r.y - extra_size;
          540                 if (delta)
          541                         ltk_menu_scroll(menu, 0, 1, 0, 0, delta);
          542         }
          543 }
          544 
          545 static void
          546 ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret) {
          547         ltk_widget *self = LTK_CAST_WIDGET(menu);
          548         struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme;
          549         int arrow_pad = ltk_size_to_pixel(theme->arrow_pad, self->last_dpi);
          550         int arrow_size = ltk_size_to_pixel(theme->arrow_size, self->last_dpi);
          551         int extra_size = arrow_size * 2 + arrow_pad * 4;
          552         *x_ret = 0;
          553         *y_ret = 0;
          554         if (self->lrect.w < (int)self->ideal_w) {
          555                 *x_ret = self->ideal_w - (self->lrect.w - extra_size);
          556         }
          557         if (self->lrect.h < (int)self->ideal_h) {
          558                 *y_ret = self->ideal_h - (self->lrect.h - extra_size);
          559         }
          560 }
          561 
          562 static void
          563 ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step) {
          564         int max_scroll_x, max_scroll_y;
          565         ltk_menu_get_max_scroll_offset(menu, &max_scroll_x, &max_scroll_y);
          566         double y_old = menu->y_scroll_offset;
          567         double x_old = menu->x_scroll_offset;
          568         if (t)
          569                 menu->y_scroll_offset -= step;
          570         else if (b)
          571                 menu->y_scroll_offset += step;
          572         else if (l)
          573                 menu->x_scroll_offset -= step;
          574         else if (r)
          575                 menu->x_scroll_offset += step;
          576         if (menu->x_scroll_offset < 0)
          577                 menu->x_scroll_offset = 0;
          578         if (menu->y_scroll_offset < 0)
          579                 menu->y_scroll_offset = 0;
          580         if (menu->x_scroll_offset > max_scroll_x)
          581                 menu->x_scroll_offset = max_scroll_x;
          582         if (menu->y_scroll_offset > max_scroll_y)
          583                 menu->y_scroll_offset = max_scroll_y;
          584         /* FIXME: sensible epsilon? */
          585         if (fabs(x_old - menu->x_scroll_offset) > 0.01 ||
          586             fabs(y_old - menu->y_scroll_offset) > 0.01) {
          587                 ltk_widget *self = LTK_CAST_WIDGET(menu);
          588                 ltk_menu_resize(self);
          589                 menu->widget.dirty = 1;
          590                 ltk_window_invalidate_widget_rect(self->window, self->id);
          591         }
          592 }
          593 
          594 /* FIXME: show scroll arrow disabled when nothing further */
          595 static void
          596 ltk_menu_scroll_callback(ltk_callback_arg data) {
          597         ltk_widget_id id = LTK_CAST_ARG_WIDGET_ID(data);
          598         ltk_widget *self = ltk_get_widget_from_id(id);
          599         ltk_menu *menu = LTK_CAST_MENU(self);
          600         ltk_menu_scroll(
          601             menu,
          602             menu->scroll_top_hover, menu->scroll_bottom_hover,
          603             menu->scroll_left_hover, menu->scroll_right_hover, 2
          604         );
          605 }
          606 
          607 static void
          608 stop_scrolling(ltk_menu *menu) {
          609         menu->scroll_top_hover = 0;
          610         menu->scroll_bottom_hover = 0;
          611         menu->scroll_left_hover = 0;
          612         menu->scroll_right_hover = 0;
          613         if (menu->scroll_timer_id >= 0)
          614                 ltk_unregister_timer(menu->scroll_timer_id);
          615 }
          616 
          617 /* FIXME: should ideal_w, ideal_h just be int? */
          618 static ltk_widget_id
          619 ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) {
          620         ltk_menu *menu = LTK_CAST_MENU(self);
          621         struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme;
          622         int mbw = ltk_size_to_pixel(t->border_width, self->last_dpi);
          623         int arrow_pad = ltk_size_to_pixel(t->arrow_pad, self->last_dpi);
          624         int raw_arrow_size = ltk_size_to_pixel(t->arrow_size, self->last_dpi);
          625         int arrow_size = raw_arrow_size + arrow_pad * 2;
          626         int start_x = mbw, end_x = self->lrect.w - mbw;
          627         int start_y = mbw, end_y = self->lrect.h - mbw;
          628         if (self->lrect.w < (int)self->ideal_w) {
          629                 start_x += arrow_size;
          630                 end_x -= arrow_size;
          631         }
          632         if (self->lrect.h < (int)self->ideal_h) {
          633                 start_y += arrow_size;
          634                 end_y -= arrow_size;
          635         }
          636         /* FIXME: use crect for this */
          637         if (!ltk_collide_rect((ltk_rect){start_x, start_y, end_x - start_x, end_y - start_y}, x, y))
          638                 return LTK_WIDGET_ID_NONE;
          639 
          640         for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
          641                 ltk_widget_id childid = ltk_array_get(menu->entries, i);
          642                 ltk_widget *child = ltk_get_widget_from_id(childid);
          643                 if (ltk_collide_rect(child->crect, x, y))
          644                         return childid;
          645         }
          646         return LTK_WIDGET_ID_NONE;
          647 }
          648 
          649 /* FIXME: make sure timers are always destroyed when widget is destroyed */
          650 static int
          651 set_scroll_timer(ltk_menu *menu, int x, int y) {
          652         ltk_widget *self = LTK_CAST_WIDGET(menu);
          653         /* this check probably isn't necessary, but whatever */
          654         if (x < 0 || y < 0 || x >= self->lrect.w || y >= self->lrect.h)
          655                 return 0;
          656         int t = 0, b = 0, l = 0,r = 0;
          657         struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme;
          658         int arrow_pad = ltk_size_to_pixel(theme->arrow_pad, self->last_dpi);
          659         int raw_arrow_size = ltk_size_to_pixel(theme->arrow_size, self->last_dpi);
          660         int arrow_size = raw_arrow_size + arrow_pad * 2;
          661         if (self->lrect.w < (int)self->ideal_w) {
          662                 if (x < arrow_size)
          663                         l = 1;
          664                 else if (x > self->lrect.w - arrow_size)
          665                         r = 1;
          666         }
          667         if (self->lrect.h < (int)self->ideal_h) {
          668                 if (y < arrow_size)
          669                         t = 1;
          670                 else if (y > self->lrect.h - arrow_size)
          671                         b = 1;
          672         }
          673         if (t == menu->scroll_top_hover &&
          674             b == menu->scroll_bottom_hover &&
          675             l == menu->scroll_left_hover &&
          676             r == menu->scroll_right_hover)
          677                 return 0;
          678         stop_scrolling(menu);
          679         menu->scroll_top_hover = t;
          680         menu->scroll_bottom_hover = b;
          681         menu->scroll_left_hover = l;
          682         menu->scroll_right_hover = r;
          683         ltk_menu_scroll_callback(LTK_MAKE_ARG_WIDGET(self));
          684         menu->scroll_timer_id = ltk_register_timer(0, 300, &ltk_menu_scroll_callback, LTK_MAKE_ARG_WIDGET_ID(self->id));
          685         return 1;
          686 }
          687 
          688 /* FIXME: The mouse release handler checks if the mouse collides with the rect of the widget
          689    before calling this, but that doesn't work with menuentries because part of their rect may
          690    be hidden when scrolling in a menu. Maybe widgets also need a "visible rect"? */
          691 static int
          692 ltk_menuentry_release(ltk_widget *self) {
          693         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
          694         int in_submenu = IS_SUBMENU(parent);
          695         int keep_popup = parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENU && (LTK_CAST_MENU(parent))->popup_submenus;
          696         if (in_submenu || !keep_popup) {
          697                 ltk_window_unregister_all_popups(self->window);
          698         }
          699         ltk_widget_emit_signal(self->id, LTK_MENUENTRY_SIGNAL_PRESSED, LTK_EMPTY_ARGLIST);
          700         return 1;
          701 }
          702 
          703 static int
          704 ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) {
          705         ltk_menu *menu = LTK_CAST_MENU(self);
          706         ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y);
          707         /* FIXME: configure scroll step */
          708         /* FIXME: fix the interface for ltk_menu_scroll */
          709         if (event->dx > 0)
          710                 ltk_menu_scroll(menu, 0, 0, 0, 1, event->dx * 10);
          711         else if (event->dx < 0)
          712                 ltk_menu_scroll(menu, 0, 0, 1, 0, -event->dx * 10);
          713         if (event->dy > 0)
          714                 ltk_menu_scroll(menu, 1, 0, 0, 0, event->dy * 10);
          715         else if (event->dy < 0)
          716                 ltk_menu_scroll(menu, 0, 1, 0, 0, -event->dy * 10);
          717         ltk_window_fake_motion_event(self->window, glob.x, glob.y);
          718         return 1;
          719 }
          720 
          721 /* FIXME: make sure timers are always removed when popups are removed */
          722 static void
          723 ltk_menu_hide(ltk_widget *self) {
          724         ltk_menu *menu = LTK_CAST_MENU(self);
          725         if (menu->scroll_timer_id >= 0)
          726                 ltk_unregister_timer(menu->scroll_timer_id);
          727         menu->scroll_bottom_hover = menu->scroll_top_hover = 0;
          728         menu->scroll_left_hover = menu->scroll_right_hover = 0;
          729         ltk_window_unregister_popup(self->window, self->id);
          730         ltk_window_invalidate_widget_rect(self->window, self->id);
          731         /* FIXME: this is really ugly/hacky */
          732         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
          733         ltk_widget *pparent = parent ? ltk_get_widget_or_null_from_id(parent->parent) : NULL;
          734         if (menu->unpopup_submenus_on_hide && parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY &&
          735             pparent && LTK_WIDGET_TYPE(pparent) == LTK_WIDGET_MENU) {
          736                 (LTK_CAST_MENU(pparent))->popup_submenus = 0;
          737         }
          738         menu->unpopup_submenus_on_hide = 1;
          739 }
          740 
          741 /* FIXME: hacky because entries need to know about their parents to be able to properly position the popup */
          742 static void
          743 popup_active_menu(ltk_menuentry *e) {
          744         ltk_widget *self = LTK_CAST_WIDGET(e);
          745         ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu);
          746         if (!submenuw)
          747                 return;
          748         ltk_menu *submenu = LTK_CAST_MENU(submenuw);
          749         int in_submenu = 0, was_opened_left = 0;
          750         ltk_rect menu_rect = self->lrect;
          751         ltk_point entry_global = ltk_widget_pos_to_global(self, 0, 0);
          752         ltk_point menu_global;
          753         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
          754         if (parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENU) {
          755                 ltk_menu *menu = LTK_CAST_MENU(parent);
          756                 in_submenu = menu->is_submenu;
          757                 was_opened_left = menu->was_opened_left;
          758                 menu_rect = LTK_CAST_WIDGET(menu)->lrect;
          759                 menu_global = ltk_widget_pos_to_global(parent, 0, 0);
          760         } else {
          761                 menu_global = ltk_widget_pos_to_global(self, 0, 0);
          762         }
          763         ltk_widget *windoww = ltk_get_widget_from_id(self->window);
          764         ltk_window *window = LTK_CAST_WINDOW(windoww);
          765         int win_w = window->rect.w;
          766         int win_h = window->rect.h;
          767         ltk_widget_recalc_ideal_size(submenuw);
          768         int ideal_w = submenuw->ideal_w;
          769         int ideal_h = submenuw->ideal_h;
          770         int x_final = 0, y_final = 0, w_final = ideal_w, h_final = ideal_h;
          771         int submenu_bw = ltk_size_to_pixel(submenu_theme.border_width, self->last_dpi);
          772         int submenu_pad = ltk_size_to_pixel(submenu_theme.pad, self->last_dpi);
          773         int menu_bw = ltk_size_to_pixel(menu_theme.border_width, self->last_dpi);
          774         if (in_submenu) {
          775                 int space_left = menu_global.x;
          776                 int space_right = win_w - (menu_global.x + menu_rect.w);
          777                 int x_right = menu_global.x + menu_rect.w;
          778                 int x_left = menu_global.x - ideal_w;
          779                 if (submenu_theme.compress_borders) {
          780                         x_right -= submenu_bw;
          781                         x_left += submenu_bw;
          782                 }
          783                 if (was_opened_left) {
          784                         if (x_left >= 0) {
          785                                 x_final = x_left;
          786                                 submenu->was_opened_left = 1;
          787                         } else if (space_right >= ideal_w) {
          788                                 x_final = x_right;
          789                                 submenu->was_opened_left = 0;
          790                         } else {
          791                                 x_final = 0;
          792                                 if (win_w < ideal_w)
          793                                         w_final = win_w;
          794                                 submenu->was_opened_left = 1;
          795                         }
          796                 } else {
          797                         if (space_right >= ideal_w) {
          798                                 x_final = x_right;
          799                                 submenu->was_opened_left = 0;
          800                         } else if (space_left >= ideal_w) {
          801                                 x_final = x_left;
          802                                 submenu->was_opened_left = 1;
          803                         } else {
          804                                 x_final = win_w - ideal_w;
          805                                 if (x_final < 0) {
          806                                         x_final = 0;
          807                                         w_final = win_w;
          808                                 }
          809                                 submenu->was_opened_left = 0;
          810                         }
          811                 }
          812                 /* subtract padding and border width so the actual entries are at the right position */
          813                 y_final = entry_global.y - submenu_pad - submenu_bw;
          814                 if (y_final + ideal_h > win_h)
          815                         y_final = win_h - ideal_h;
          816                 if (y_final < 0) {
          817                         y_final = 0;
          818                         h_final = win_h;
          819                 }
          820         } else {
          821                 int space_top = menu_global.y;
          822                 int space_bottom = win_h - (menu_global.y + menu_rect.h);
          823                 int y_top = menu_global.y - ideal_h;
          824                 int y_bottom = menu_global.y + menu_rect.h;
          825                 if (menu_theme.compress_borders) {
          826                         y_top += menu_bw;
          827                         y_bottom -= menu_bw;
          828                 }
          829                 if (space_top > space_bottom) {
          830                         y_final = y_top;
          831                         if (y_final < 0) {
          832                                 y_final = 0;
          833                                 h_final = menu_rect.y;
          834                         }
          835                         submenu->was_opened_above = 1;
          836                 } else {
          837                         y_final = y_bottom;
          838                         if (space_bottom < ideal_h)
          839                                 h_final = space_bottom;
          840                         submenu->was_opened_above = 0;
          841                 }
          842                 /* FIXME: maybe threshold so there's always at least a part of
          843                    the menu contents shown (instead of maybe just a few pixels) */
          844                 /* pathological case where window is way too small */
          845                 if (h_final <= 0) {
          846                         y_final = 0;
          847                         h_final = win_h;
          848                 }
          849                 x_final = entry_global.x;
          850                 if (x_final + ideal_w > win_w)
          851                         x_final = win_w - ideal_w;
          852                 if (x_final < 0) {
          853                         x_final = 0;
          854                         w_final = win_w;
          855                 }
          856         }
          857         /* reset everything just in case */
          858         submenu->x_scroll_offset = submenu->y_scroll_offset = 0;
          859         submenu->scroll_top_hover = submenu->scroll_bottom_hover = 0;
          860         submenu->scroll_left_hover = submenu->scroll_right_hover = 0;
          861         submenu->widget.lrect.x = x_final;
          862         submenu->widget.lrect.y = y_final;
          863         submenu->widget.lrect.w = w_final;
          864         submenu->widget.lrect.h = h_final;
          865         submenu->widget.crect = submenuw->lrect;
          866         submenu->widget.dirty = 1;
          867         submenu->widget.hidden = 0;
          868         submenu->popup_submenus = 0;
          869         submenu->unpopup_submenus_on_hide = 1;
          870         ltk_widget_resize(submenuw);
          871         ltk_window_register_popup(self->window, e->submenu);
          872         ltk_window_invalidate_widget_rect(submenuw->window, submenuw->id);
          873 }
          874 
          875 static void
          876 unpopup_active_entry(ltk_menuentry *e) {
          877         ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu);
          878         if (submenuw && !submenuw->hidden) {
          879                 ltk_menu *submenu = LTK_CAST_MENU(submenuw);
          880                 submenu->unpopup_submenus_on_hide = 0;
          881                 ltk_widget_hide(submenuw);
          882         }
          883 }
          884 
          885 static int
          886 ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event) {
          887         set_scroll_timer(LTK_CAST_MENU(self), event->x, event->y);
          888         return 1;
          889 }
          890 
          891 static int
          892 ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event) {
          893         set_scroll_timer(LTK_CAST_MENU(self), event->x, event->y);
          894         return 1;
          895 }
          896 
          897 static int
          898 ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event) {
          899         (void)event;
          900         stop_scrolling(LTK_CAST_MENU(self));
          901         return 1;
          902 }
          903 
          904 static void
          905 ltk_menu_recalc_ideal_size(ltk_widget *self) {
          906         ltk_menu *menu = LTK_CAST_MENU(self);
          907         recalc_ideal_menu_size(menu);
          908 }
          909 
          910 static ltk_widget_id
          911 ltk_menu_create_base(ltk_widget_id windowid, int is_submenu) {
          912         ltk_menu *menu = ltk_malloc(sizeof(ltk_menu));
          913         ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(menu), windowid, &vtable, 0, 0);
          914         ltk_widget *self = LTK_CAST_WIDGET(menu);
          915 
          916         int menu_pad = ltk_size_to_pixel(menu_theme.pad, self->last_dpi);
          917         self->ideal_w = menu_pad;
          918         self->ideal_h = menu_pad;
          919         self->dirty = 1;
          920 
          921         menu->entries = ltk_array_create(widget_id, 1);
          922         menu->x_scroll_offset = menu->y_scroll_offset = 0;
          923         menu->is_submenu = is_submenu;
          924         menu->was_opened_left = 0;
          925         menu->was_opened_above = 0;
          926         menu->scroll_timer_id = -1;
          927         menu->scroll_top_hover = menu->scroll_bottom_hover = 0;
          928         menu->scroll_left_hover = menu->scroll_right_hover = 0;
          929         menu->popup_submenus = 0;
          930         menu->unpopup_submenus_on_hide = 1;
          931         /* FIXME: hide widget by default so recalc doesn't cause
          932            unnecessary redrawing */
          933         recalc_ideal_menu_size(menu);
          934 
          935         return id;
          936 }
          937 
          938 ltk_widget_id
          939 ltk_menu_create(ltk_widget_id windowid) {
          940         return ltk_menu_create_base(windowid, 0);
          941 }
          942 
          943 ltk_widget_id
          944 ltk_submenu_create(ltk_widget_id windowid) {
          945         return ltk_menu_create_base(windowid, 1);
          946 }
          947 
          948 static int
          949 insert_entry(ltk_widget_id menuid, ltk_widget_id menuentryid, size_t idx) {
          950         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
          951         ltk_menu *menu = LTK_CAST_MENU(menuw);
          952         if (!menu->entries)
          953                 menu->entries = ltk_array_create(widget_id, 1);
          954         if (idx > ltk_array_len(menu->entries))
          955                 return 1;
          956         ltk_array_insert(widget_id, menu->entries, idx, menuentryid);
          957         return 0;
          958 }
          959 
          960 static void
          961 recalc_ideal_menu_size(ltk_menu *menu) {
          962         struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme;
          963         struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme;
          964 
          965         ltk_widget *self = LTK_CAST_WIDGET(menu);
          966         int bw = ltk_size_to_pixel(t->border_width, self->last_dpi);
          967         int pad = ltk_size_to_pixel(t->pad, self->last_dpi);
          968         int entry_bw = ltk_size_to_pixel(et->border_width, self->last_dpi);
          969 
          970         self->ideal_w = self->ideal_h = pad + bw * 2;
          971         for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
          972                 ltk_widget_id id = ltk_array_get(menu->entries, i);
          973                 ltk_widget *ew = ltk_get_widget_from_id(id);
          974                 ltk_widget_recalc_ideal_size(ew);
          975                 if (menu->is_submenu) {
          976                         self->ideal_w = MAX((pad + bw) * 2 + (int)ew->ideal_w, (int)self->ideal_w);
          977                         self->ideal_h += ew->ideal_h + pad;
          978                         if (et->compress_borders && i != 0)
          979                                 self->ideal_h -= entry_bw;
          980                 } else {
          981                         self->ideal_w += ew->ideal_w + pad;
          982                         if (et->compress_borders && i != 0)
          983                                 self->ideal_w -= entry_bw;
          984                         self->ideal_h = MAX((pad + bw) * 2 + (int)ew->ideal_h, (int)self->ideal_h);
          985                 }
          986         }
          987 }
          988 
          989 /* FIXME: rename these functions */
          990 static void
          991 recalc_ideal_menu_size_with_notification(ltk_widget *self, ltk_widget_id childid) {
          992         ltk_menu *menu = LTK_CAST_MENU(self);
          993         ltk_widget *child = ltk_get_widget_from_id(childid);
          994         /* If widget with size change is submenu, it doesn't affect this menu */
          995         if (child && LTK_WIDGET_TYPE(child) == LTK_WIDGET_MENU) {
          996                 ltk_widget_resize(child);
          997                 return;
          998         }
          999         unsigned int old_w = self->ideal_w, old_h = self->ideal_h;
         1000         recalc_ideal_menu_size(menu);
         1001         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1002         if ((old_w != self->ideal_w || old_h != self->ideal_h) &&
         1003             parent && parent->vtable->child_size_change) {
         1004                 parent->vtable->child_size_change(parent, self->id);
         1005         } else {
         1006                 ltk_menu_resize(self);
         1007         }
         1008         self->dirty = 1;
         1009         if (!self->hidden)
         1010                 ltk_window_invalidate_widget_rect(self->window, self->id);
         1011 }
         1012 
         1013 static void
         1014 recalc_ideal_menuentry_size(ltk_menuentry *entry) {
         1015         ltk_widget *self = LTK_CAST_WIDGET(entry);
         1016         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1017         int in_submenu = IS_SUBMENU(parent);
         1018         struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme;
         1019         int bw = ltk_size_to_pixel(t->border_width, self->last_dpi);
         1020         int text_pad = ltk_size_to_pixel(t->text_pad, self->last_dpi);
         1021         int arrow_pad = ltk_size_to_pixel(t->arrow_pad, self->last_dpi);
         1022         int arrow_size = ltk_size_to_pixel(t->arrow_size, self->last_dpi);
         1023         int extra_size = (in_submenu && !LTK_WIDGET_ID_IS_NONE(entry->submenu)) ? arrow_pad * 2 + arrow_size : 0;
         1024 
         1025         int text_w, text_h;
         1026         ltk_text_line_get_size(entry->text_line, &text_w, &text_h);
         1027         self->ideal_w = text_w + extra_size + (text_pad + bw) * 2;
         1028         self->ideal_h = MAX(text_h + text_pad * 2, extra_size) + bw * 2;
         1029 }
         1030 
         1031 static void
         1032 ltk_menuentry_recalc_ideal_size(ltk_widget *self) {
         1033         ltk_menuentry *entry = LTK_CAST_MENUENTRY(self);
         1034         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1035         struct entry_theme *t = IS_SUBMENU(parent) ? &submenu_entry_theme : &menu_entry_theme;
         1036         int font_size = ltk_size_to_pixel(t->font_size, self->last_dpi);
         1037         ltk_text_line_set_font_size(entry->text_line, font_size);
         1038         recalc_ideal_menuentry_size(entry);
         1039 }
         1040 
         1041 static void
         1042 ltk_menuentry_set_font(ltk_menuentry *entry) {
         1043         ltk_widget *self = LTK_CAST_WIDGET(entry);
         1044         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1045         struct entry_theme *t = IS_SUBMENU(parent) ? &submenu_entry_theme : &menu_entry_theme;
         1046         int font_size = ltk_size_to_pixel(t->font_size, self->last_dpi);
         1047         ltk_text_line_set_font(entry->text_line, t->font, font_size);
         1048 }
         1049 
         1050 static void
         1051 ltk_menuentry_recalc_ideal_size_with_notification(ltk_menuentry *entry) {
         1052         recalc_ideal_menuentry_size(entry);
         1053         /* FIXME: only call if something changed */
         1054         ltk_widget *self = LTK_CAST_WIDGET(entry);
         1055         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1056         if (parent && parent->vtable->child_size_change) {
         1057                 parent->vtable->child_size_change(parent, self->id);
         1058         }
         1059 }
         1060 
         1061 ltk_widget_id
         1062 ltk_menuentry_create(ltk_widget_id windowid, const char *text) {
         1063         ltk_menuentry *e = ltk_malloc(sizeof(ltk_menuentry));
         1064         ltk_widget *self = LTK_CAST_WIDGET(e);
         1065         ltk_widget_id id = ltk_initialize_widget(self, windowid, &entry_vtable, 0, 0);
         1066         e->text_line = ltk_text_line_create_const_text_default(
         1067                 menu_entry_theme.font,
         1068                 ltk_size_to_pixel(menu_entry_theme.font_size, self->last_dpi),
         1069                 text, -1
         1070         );
         1071         e->submenu = LTK_WIDGET_ID_NONE;
         1072         /* Note: This is only set as a dummy value! The actual ideal size can't
         1073            be set until it is part of a menu because it needs to know which
         1074            theme it should use */
         1075         recalc_ideal_menuentry_size(e);
         1076         self->dirty = 1;
         1077         return id;
         1078 }
         1079 
         1080 static int
         1081 ltk_menuentry_remove_child(ltk_widget *self, ltk_widget_id widgetid) {
         1082         ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
         1083         if (LTK_WIDGET_ID_IS_NONE(widgetid) || !LTK_WIDGET_ID_EQUAL(widgetid, e->submenu))
         1084                 return 1;
         1085         ltk_widget *widget = ltk_get_widget_from_id(widgetid);
         1086         widget->parent = LTK_WIDGET_ID_NONE;
         1087         e->submenu = LTK_WIDGET_ID_NONE;
         1088         ltk_menuentry_recalc_ideal_size_with_notification(e);
         1089         return 0;
         1090 }
         1091 
         1092 static void
         1093 ltk_menuentry_destroy(ltk_widget *self, int shallow) {
         1094         ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
         1095         ltk_text_line_destroy(e->text_line);
         1096         /* FIXME: function to call when parent is destroyed */
         1097         /* also function to call when parent added */
         1098         ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu);
         1099         if (submenuw) {
         1100                 submenuw->parent = LTK_WIDGET_ID_NONE;
         1101                 if (!shallow)
         1102                         ltk_widget_destroy(submenuw, shallow);
         1103         }
         1104         ltk_free(e);
         1105 }
         1106 
         1107 int
         1108 ltk_menu_insert_entry(ltk_widget_id menuid, ltk_widget_id entryid, size_t idx) {
         1109         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
         1110         ltk_widget *entryw = ltk_get_widget_from_id(entryid);
         1111         if (!LTK_WIDGET_ID_IS_NONE(entryw->parent))
         1112                 return 1; /* already child of some widget */
         1113         if (insert_entry(menuid, entryid, idx))
         1114                 return 2; /* invalid index */
         1115         entryw->parent = menuid;
         1116         /* the theme may have changed if the entry switched between menu and submenu */
         1117         ltk_menuentry_set_font(LTK_CAST_MENUENTRY(entryw));
         1118         ltk_menuentry_recalc_ideal_size_with_notification(LTK_CAST_MENUENTRY(entryw));
         1119         recalc_ideal_menu_size_with_notification(menuw, entryid);
         1120         menuw->dirty = 1;
         1121         return 0;
         1122 }
         1123 
         1124 int
         1125 ltk_menu_add_entry(ltk_widget_id menuid, ltk_widget_id entryid) {
         1126         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
         1127         ltk_menu *menu = LTK_CAST_MENU(menuw);
         1128         return ltk_menu_insert_entry(menuid, entryid, ltk_array_len(menu->entries));
         1129 }
         1130 
         1131 /* FIXME: maybe allow any menu and just change is_submenu (also need to recalculate size then) */
         1132 int
         1133 ltk_menuentry_attach_submenu(ltk_widget_id entryid, ltk_widget_id submenuid) {
         1134         ltk_widget *entryw = ltk_get_widget_from_id(entryid);
         1135         ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw);
         1136         ltk_widget *submenuw = ltk_get_widget_from_id(submenuid);
         1137         ltk_menu *submenu = LTK_CAST_MENU(submenuw);
         1138         if (!submenu->is_submenu)
         1139                 return 1; /* menu is not submenu */
         1140         else if (!LTK_WIDGET_ID_IS_NONE(entry->submenu))
         1141                 return 2; /* entry already contains submenu */
         1142         entry->submenu = submenuid;
         1143         ltk_menuentry_recalc_ideal_size_with_notification(entry);
         1144         entryw->dirty = 1;
         1145         submenuw->hidden = 1;
         1146         submenuw->parent = entryid;
         1147         if (!entryw->hidden)
         1148                 ltk_window_invalidate_widget_rect(entryw->window, entryw->id);
         1149         return 0;
         1150 }
         1151 
         1152 /* FIXME: hide all entries when menu hidden? */
         1153 
         1154 ltk_widget_id
         1155 ltk_menu_remove_entry_index(ltk_widget_id menuid, size_t idx) {
         1156         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
         1157         ltk_menu *menu = LTK_CAST_MENU(menuw);
         1158         if (idx >= ltk_array_len(menu->entries))
         1159                 return LTK_WIDGET_ID_NONE; /* invalid index */
         1160         ltk_widget_id entryid = ltk_array_get(menu->entries, idx);
         1161         ltk_widget *entry = ltk_get_widget_from_id(entryid);
         1162         entry->parent = LTK_WIDGET_ID_NONE;
         1163         /* I don't think this is needed because the entry isn't shown
         1164            anywhere. Its size will be recalculated once it is added
         1165            to a menu again. */
         1166         /* ltk_menuentry_recalc_ideal_size_with_notification(menu->entries[idx]); */
         1167         ltk_array_delete(widget_id, menu->entries, idx, 1);
         1168         recalc_ideal_menu_size_with_notification(menuw, LTK_WIDGET_ID_NONE);
         1169         return entryid;
         1170 }
         1171 
         1172 size_t
         1173 ltk_menu_get_entry_index(ltk_widget_id menuid, ltk_widget_id entryid) {
         1174         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
         1175         ltk_menu *menu = LTK_CAST_MENU(menuw);
         1176         for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
         1177                 ltk_widget_id id = ltk_array_get(menu->entries, i);
         1178                 if (LTK_WIDGET_ID_EQUAL(id, entryid))
         1179                         return i;
         1180         }
         1181         return SIZE_MAX;
         1182 }
         1183 
         1184 size_t
         1185 ltk_menu_get_num_entries(ltk_widget_id menuid) {
         1186         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
         1187         ltk_menu *menu = LTK_CAST_MENU(menuw);
         1188         return ltk_array_len(menu->entries);
         1189 }
         1190 
         1191 ltk_widget_id
         1192 ltk_menu_get_entry(ltk_widget_id menuid, size_t idx) {
         1193         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
         1194         ltk_menu *menu = LTK_CAST_MENU(menuw);
         1195         if (idx >= ltk_array_len(menu->entries))
         1196                 return LTK_WIDGET_ID_NONE;
         1197         return ltk_array_get(menu->entries, idx);
         1198 }
         1199 
         1200 int
         1201 ltk_menu_remove_entry(ltk_widget_id menuid, ltk_widget_id entryid) {
         1202         size_t idx = ltk_menu_get_entry_index(menuid, entryid);
         1203         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
         1204         ltk_menu *menu = LTK_CAST_MENU(menuw);
         1205         if (idx >= ltk_array_len(menu->entries))
         1206                 return 1;
         1207         ltk_widget_id ret = ltk_menu_remove_entry_index(menuid, idx);
         1208         if (LTK_WIDGET_ID_IS_NONE(ret)) /* shouldn't be possible */
         1209                 return 1;
         1210         return 0;
         1211 }
         1212 
         1213 static int
         1214 ltk_menu_remove_child(ltk_widget *self, ltk_widget_id childid) {
         1215         /* FIXME: this is kind of ugly - it's just there to avoid
         1216            aborting if a different widget type is given */
         1217         ltk_widget *child = ltk_get_widget_from_id(childid);
         1218         if (LTK_WIDGET_TYPE(child) != LTK_WIDGET_MENUENTRY)
         1219                 return 1;
         1220         return ltk_menu_remove_entry(self->id, childid);
         1221 }
         1222 
         1223 /* TODO: add function to also destroy the entries when removing them */
         1224 /* -> note: ltk_menu_remove_all_entries is used by ltk_menu_destroy, so
         1225       that has to be updated if this changes */
         1226 void
         1227 ltk_menu_remove_all_entries(ltk_widget_id menuid) {
         1228         ltk_widget *menuw = ltk_get_widget_from_id(menuid);
         1229         ltk_menu *menu = LTK_CAST_MENU(menuw);
         1230         for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
         1231                 ltk_widget *child = ltk_get_widget_from_id(ltk_array_get(menu->entries, i));
         1232                 child->parent = LTK_WIDGET_ID_NONE;
         1233                 /* I don't think this is needed because the entry isn't shown
         1234                    anywhere. Its size will be recalculated once it is added
         1235                    to a menu again. */
         1236                 /* ltk_menuentry_recalc_ideal_size_with_notification(menu->entries[i]); */
         1237         }
         1238         ltk_array_reset(widget_id, menu->entries);
         1239         recalc_ideal_menu_size_with_notification(menuw, LTK_WIDGET_ID_NONE);
         1240 }
         1241 
         1242 static ltk_widget_id
         1243 ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect) {
         1244         ltk_menu *menu = LTK_CAST_MENU(self);
         1245         ltk_widget_id minw = LTK_WIDGET_ID_NONE;
         1246         int min_dist = INT_MAX;
         1247         for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
         1248                 ltk_widget *child = ltk_get_widget_from_id(ltk_array_get(menu->entries, i));
         1249                 ltk_rect r = child->lrect;
         1250                 /* FIXME: maybe simplify this since all entries are
         1251                    laid out horizontal/vertical anyways */
         1252                 int dist = ltk_rect_fakedist(rect, r);
         1253                 if (dist < min_dist) {
         1254                         min_dist = dist;
         1255                         minw = ltk_array_get(menu->entries, i);
         1256                 }
         1257         }
         1258         return minw;
         1259 }
         1260 
         1261 /* FIXME: These need to be updated if all menus are allowed to be horizontal or vertical! */
         1262 /* FIXME: this doesn't work properly when parent and child are both on the same side,
         1263    but I guess there's no good way to fix that */
         1264 /* FIXME: behavior is a bit weird when e.g. moving down when every active menu entry in hierarchy
         1265    is already at bottom of respective menu - the top-level menu will give the first submenu in
         1266    the current active hierarchy as child widget again, and nearest_child on that submenu will
         1267    (probably) give the bottom widget again, so nothing changes except that all submenus except
         1268    for the first and second one disappear */
         1269 static ltk_widget_id
         1270 ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget_id childid) {
         1271         ltk_menu *menu = LTK_CAST_MENU(self);
         1272         ltk_widget *entryw = ltk_get_widget_from_id(childid);
         1273         ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw);
         1274         ltk_widget *submenuw = ltk_get_widget_or_null_from_id(entry->submenu);
         1275         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1276         ltk_widget_id left = LTK_WIDGET_ID_NONE;
         1277         if (!menu->is_submenu) {
         1278                 left = ltk_menu_prev_child(self, childid);
         1279         } else if (submenuw && !submenuw->hidden && LTK_CAST_MENU(submenuw)->was_opened_left) {
         1280                 left =  submenuw->id;
         1281         } else if (parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY) {
         1282                 ltk_widget *grandparent = ltk_get_widget_or_null_from_id(parent->parent);
         1283                 if (!menu->was_opened_left && IS_SUBMENU(grandparent)) {
         1284                         left = self->parent;
         1285                 } else if (!IS_SUBMENU(grandparent) && LTK_WIDGET_TYPE(grandparent) == LTK_WIDGET_MENU) {
         1286                         left = ltk_menu_prev_child(grandparent, self->parent);
         1287                 }
         1288         }
         1289         return left;
         1290 }
         1291 
         1292 static ltk_widget_id
         1293 ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget_id childid) {
         1294         ltk_menu *menu = LTK_CAST_MENU(self);
         1295         ltk_widget *entryw = ltk_get_widget_from_id(childid);
         1296         ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw);
         1297         ltk_widget *submenuw = ltk_get_widget_or_null_from_id(entry->submenu);
         1298         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1299         ltk_widget_id right = LTK_WIDGET_ID_NONE;
         1300         if (!menu->is_submenu) {
         1301                 right = ltk_menu_next_child(self, childid);
         1302         } else if (submenuw && !submenuw->hidden && !LTK_CAST_MENU(submenuw)->was_opened_left) {
         1303                 right =  entry->submenu;
         1304         } else if (parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY) {
         1305                 ltk_widget *grandparent = ltk_get_widget_or_null_from_id(parent->parent);
         1306                 if (menu->was_opened_left && IS_SUBMENU(grandparent)) {
         1307                         right = self->parent;
         1308                 } else if (!IS_SUBMENU(grandparent) && LTK_WIDGET_TYPE(grandparent) == LTK_WIDGET_MENU) {
         1309                         right = ltk_menu_next_child(grandparent, self->parent);
         1310                 }
         1311         }
         1312         return right;
         1313 }
         1314 
         1315 static ltk_widget_id
         1316 ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget_id childid) {
         1317         ltk_menu *menu = LTK_CAST_MENU(self);
         1318         ltk_widget *childw = ltk_get_widget_from_id(childid);
         1319         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1320         ltk_widget_id above = LTK_WIDGET_ID_NONE;
         1321         if (menu->is_submenu) {
         1322                 above = ltk_menu_prev_child(self, childid);
         1323                 if (LTK_WIDGET_ID_IS_NONE(above) && parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY) {
         1324                         ltk_widget *grandparent = ltk_get_widget_or_null_from_id(parent->parent);
         1325                         if (grandparent && LTK_WIDGET_TYPE(grandparent) == LTK_WIDGET_MENU) {
         1326                                 ltk_menu *gpmenu = LTK_CAST_MENU(grandparent);
         1327                                 if (!menu->was_opened_above && !gpmenu->is_submenu) {
         1328                                         above = self->parent;
         1329                                 } else if (gpmenu->is_submenu) {
         1330                                         above = ltk_menu_prev_child(grandparent, self->parent);
         1331                                 }
         1332                         }
         1333                 }
         1334         } else if (LTK_WIDGET_TYPE(childw) == LTK_WIDGET_MENUENTRY) {
         1335                 ltk_menuentry *e = LTK_CAST_MENUENTRY(childw);
         1336                 ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu);
         1337                 if (submenuw && !submenuw->hidden && LTK_CAST_MENU(submenuw)->was_opened_above) {
         1338                         above =  e->submenu;
         1339                 }
         1340         }
         1341         return above;
         1342 }
         1343 
         1344 static ltk_widget_id
         1345 ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget_id childid) {
         1346         ltk_menu *menu = LTK_CAST_MENU(self);
         1347         ltk_widget *childw = ltk_get_widget_from_id(childid);
         1348         ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
         1349         ltk_widget_id below = LTK_WIDGET_ID_NONE;
         1350         if (menu->is_submenu) {
         1351                 below = ltk_menu_next_child(self, childid);
         1352                 if (LTK_WIDGET_ID_IS_NONE(below) && parent && LTK_WIDGET_TYPE(parent) == LTK_WIDGET_MENUENTRY) {
         1353                         ltk_widget *grandparent = ltk_get_widget_or_null_from_id(parent->parent);
         1354                         if (grandparent && LTK_WIDGET_TYPE(grandparent) == LTK_WIDGET_MENU) {
         1355                                 ltk_menu *gpmenu = LTK_CAST_MENU(grandparent);
         1356                                 if (menu->was_opened_above && !gpmenu->is_submenu) {
         1357                                         below = self->parent;
         1358                                 } else if (gpmenu->is_submenu) {
         1359                                         below = ltk_menu_next_child(grandparent, self->parent);
         1360                                 }
         1361                         }
         1362                 }
         1363         } else if (LTK_WIDGET_TYPE(childw) == LTK_WIDGET_MENUENTRY) {
         1364                 ltk_menuentry *e = LTK_CAST_MENUENTRY(childw);
         1365                 ltk_widget *submenuw = ltk_get_widget_or_null_from_id(e->submenu);
         1366                 if (submenuw && !submenuw->hidden && !LTK_CAST_MENU(submenuw)->was_opened_above) {
         1367                         below = e->submenu;
         1368                 }
         1369         }
         1370         return below;
         1371 }
         1372 
         1373 static ltk_widget_id
         1374 ltk_menu_prev_child(ltk_widget *self, ltk_widget_id childid) {
         1375         ltk_menu *menu = LTK_CAST_MENU(self);
         1376         for (size_t i = ltk_array_len(menu->entries); i-- > 0;) {
         1377                 ltk_widget_id id = ltk_array_get(menu->entries, i);
         1378                 if (LTK_WIDGET_ID_EQUAL(id, childid))
         1379                         return i > 0 ? ltk_array_get(menu->entries, i - 1) : LTK_WIDGET_ID_NONE;
         1380         }
         1381         return LTK_WIDGET_ID_NONE;
         1382 }
         1383 
         1384 static ltk_widget_id
         1385 ltk_menu_next_child(ltk_widget *self, ltk_widget_id childid) {
         1386         ltk_menu *menu = LTK_CAST_MENU(self);
         1387         for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
         1388                 ltk_widget_id id = ltk_array_get(menu->entries, i);
         1389                 if (LTK_WIDGET_ID_EQUAL(id, childid))
         1390                         return i < ltk_array_len(menu->entries) - 1 ? ltk_array_get(menu->entries, i + 1) : LTK_WIDGET_ID_NONE;
         1391         }
         1392         return LTK_WIDGET_ID_NONE;
         1393 }
         1394 
         1395 static ltk_widget_id
         1396 ltk_menu_first_child(ltk_widget *self) {
         1397         ltk_menu *menu = LTK_CAST_MENU(self);
         1398         return ltk_array_len(menu->entries) > 0 ? ltk_array_get(menu->entries, 0) : LTK_WIDGET_ID_NONE;
         1399 }
         1400 
         1401 static ltk_widget_id
         1402 ltk_menu_last_child(ltk_widget *self) {
         1403         ltk_menu *menu = LTK_CAST_MENU(self);
         1404         return ltk_array_len(menu->entries) > 0 ? ltk_array_get(menu->entries, ltk_array_len(menu->entries) - 1) : LTK_WIDGET_ID_NONE;
         1405 }
         1406 
         1407 /* FIXME: unregister from window popups? */
         1408 int
         1409 ltk_menuentry_detach_submenu(ltk_widget_id entryid) {
         1410         ltk_widget *entryw = ltk_get_widget_from_id(entryid);
         1411         ltk_menuentry *entry = LTK_CAST_MENUENTRY(entryw);
         1412         ltk_widget *submenuw = ltk_get_widget_or_null_from_id(entry->submenu);
         1413         if (!submenuw)
         1414                 return 1;
         1415         submenuw->parent = LTK_WIDGET_ID_NONE;
         1416         entry->submenu = LTK_WIDGET_ID_NONE;
         1417         ltk_menuentry_recalc_ideal_size_with_notification(entry);
         1418         return 0;
         1419 }
         1420 
         1421 static void
         1422 ltk_menu_destroy(ltk_widget *self, int shallow) {
         1423         ltk_menu *menu = LTK_CAST_MENU(self);
         1424         if (menu->scroll_timer_id >= 0)
         1425                 ltk_unregister_timer(menu->scroll_timer_id);
         1426         ltk_window_unregister_popup(self->window, self->id);
         1427         if (!shallow) {
         1428                 for (size_t i = 0; i < ltk_array_len(menu->entries); i++) {
         1429                         /* for efficiency - to avoid ltk_widget_destroy calling
         1430                            ltk_menu_remove_child for each of the entries */
         1431                         ltk_widget *child = ltk_get_widget_from_id(ltk_array_get(menu->entries, i));
         1432                         child->parent = LTK_WIDGET_ID_NONE;
         1433                         ltk_widget_destroy(child, shallow);
         1434                 }
         1435                 ltk_array_destroy(widget_id, menu->entries);
         1436         } else {
         1437                 ltk_menu_remove_all_entries(self->id);
         1438         }
         1439         ltk_free(menu);
         1440 }