ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

menu.c (55328B)


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