summaryrefslogtreecommitdiff
path: root/src/gui/contextmenu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/contextmenu.cpp')
-rw-r--r--src/gui/contextmenu.cpp437
1 files changed, 437 insertions, 0 deletions
diff --git a/src/gui/contextmenu.cpp b/src/gui/contextmenu.cpp
new file mode 100644
index 0000000..0775651
--- /dev/null
+++ b/src/gui/contextmenu.cpp
@@ -0,0 +1,437 @@
+#include "base.h"
+
+U8 gui_contextmenu_item_enabled( GUI_CONTEXTMENU_ITEM* item ) {
+ if( !item )
+ return 0;
+
+ if( item->enabled_cb )
+ return item->enabled_cb( item );
+
+ return item->enabled;
+}
+
+I32 gui_contextmenu_title_h( GUI_CONTEXTMENU* menu ) {
+ return ( menu && menu->title[0] ) ? 18 : 0;
+}
+
+I32 gui_contextmenu_row_h() {
+ return 20;
+}
+
+I32 gui_contextmenu_width( LIST<GUI_CONTEXTMENU_ITEM>* items, const char* title ) {
+ I32 width = 0;
+ if( title && title[0] ) {
+ I32 text_w = 0;
+ gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", title );
+ width = max( width, text_w + 18 );
+ }
+
+ if( !items )
+ return width;
+
+ for( I32 i = 0; i < (I32)items->size; ++i ) {
+ I32 text_w = 0;
+ gui_draw_get_str_bounds( &text_w, 0, FNT_JPN12, "%s", ( *items )[i].text );
+ width = max( width, text_w + 20 );
+ }
+
+ return width;
+}
+
+I32 gui_contextmenu_height( I32 rows, const char* title ) {
+ I32 height = 4 + gui_contextmenu_row_h() * rows;
+ if( title && title[0] )
+ height += 18 + 1;
+ return max( height, gui_contextmenu_row_h() + 4 );
+}
+
+static I32 gui_contextmenu_items_y( GUI_CONTEXTMENU* menu ) {
+ if( !menu )
+ return 0;
+ I32 y = gui_rely( menu ) + 2;
+ if( menu->title[0] )
+ y += gui_contextmenu_title_h( menu ) + 1;
+ return y;
+}
+
+I32 gui_contextmenu_hit_test( GUI_CONTEXTMENU* menu, I32 mx, I32 my ) {
+ if( !menu || !menu->items )
+ return -1;
+
+ I32 x = gui_relx( menu );
+ I32 y = gui_rely( menu );
+ if( mx < x || mx >= x + menu->w || my < y || my >= y + menu->h )
+ return -1;
+
+ I32 items_y = gui_contextmenu_items_y( menu );
+ if( my < items_y )
+ return -1;
+
+ I32 row = ( my - items_y ) / gui_contextmenu_row_h();
+ if( row < 0 || row >= menu->visible_rows )
+ return -1;
+
+ I32 idx = menu->first_visible + row;
+ if( idx < 0 || idx >= (I32)menu->items->size )
+ return -1;
+
+ return idx;
+}
+
+static I32 gui_contextmenu_draw_frame(
+ GUI_CONTEXTMENU* menu,
+ I32 x, I32 y,
+ CLR border
+) {
+ gui_draw_frect( x, y, menu->w, menu->h, border );
+ gui_draw_frect(
+ x + 1,
+ y + 1,
+ max( 1, menu->w - 2 ),
+ max( 1, menu->h - 2 ),
+ ui_clr.bg_sec
+ );
+
+ I32 item_y = y + 2;
+
+ if( menu->title[0] ) {
+ gui_draw_str(
+ x + 8, item_y + 1,
+ ALIGN_L, FNT_JPN12,
+ ui_clr.txt,
+ "%s", menu->title
+ );
+ item_y += gui_contextmenu_title_h( menu );
+ gui_draw_line(
+ x + 1, item_y,
+ x + menu->w - 2, item_y,
+ border
+ );
+ ++item_y;
+ }
+ return item_y;
+}
+
+static void gui_contextmenu_draw_rows(
+ GUI_CONTEXTMENU* menu,
+ I32 x, I32 y,
+ I32 hit,
+ U8 left_down
+) {
+ I32 item_count = (I32)menu->items->size;
+ I32 first = m_clamp(
+ menu->first_visible,
+ 0, item_count
+ );
+ I32 rows = min(
+ menu->visible_rows,
+ item_count - first
+ );
+
+ CLR hover = CLR::blend( ui_clr.border, ui_clr.bg, 0.7f );
+ CLR active = CLR::blend( ui_clr.bg_sec, ui_clr.border, 0.22f );
+
+ gui_draw_push_clip(
+ x + 1, y,
+ max( 0, menu->w - 2 ),
+ max( 0, rows * gui_contextmenu_row_h() )
+ );
+
+ for( I32 row = 0; row < rows; ++row ) {
+ I32 idx = first + row;
+ GUI_CONTEXTMENU_ITEM* item = &( *menu->items )[idx];
+ U8 enabled = gui_contextmenu_item_enabled( item );
+ U8 pressed = menu->held
+ && menu->held_idx == idx
+ && left_down
+ && enabled;
+ CLR fill = pressed
+ ? active
+ : ( hit == idx || menu->selected_idx == idx )
+ ? hover
+ : ui_clr.bg_sec;
+ I32 row_y = y + row * gui_contextmenu_row_h();
+
+ gui_draw_frect(
+ x + 1, row_y,
+ max( 1, menu->w - 2 ),
+ gui_contextmenu_row_h(),
+ fill
+ );
+ gui_draw_str(
+ x + 8, row_y + 2,
+ ALIGN_L, FNT_JPN12,
+ enabled ? ui_clr.txt : ui_clr.txt_inactive,
+ "%s", item->text
+ );
+ }
+ gui_draw_pop_clip();
+}
+
+void gui_contextmenu_draw_fn( void* ptr ) {
+ GUI_CONTEXTMENU* menu = (GUI_CONTEXTMENU*)ptr;
+ if( !menu || !menu->items || !menu->items->size )
+ return;
+
+ I32 x = gui_relx( menu );
+ I32 y = gui_rely( menu );
+ I32 mx = 0, my = 0;
+ gui_cursor_pos( &mx, &my );
+
+ CLR border = gui_is_fg_window( menu )
+ ? ui_clr.border
+ : ui_clr.border_inactive;
+
+ I32 item_y = gui_contextmenu_draw_frame(
+ menu,
+ x, y,
+ border
+ );
+
+ gui_contextmenu_draw_rows(
+ menu, x, item_y,
+ gui_contextmenu_hit_test( menu, mx, my ),
+ gui_mbutton_down( GUI_MBTNLEFT )
+ );
+}
+
+void gui_contextmenu_input_fn( void* ptr ) {
+ GUI_CONTEXTMENU* menu = (GUI_CONTEXTMENU*)ptr;
+ if( !menu || !menu->items || !menu->items->size )
+ return;
+
+ U8 m1 = gui_mbutton_down( GUI_MBTNLEFT );
+ U8 m2 = gui_mbutton_down( GUI_MBTNRIGHT );
+ if( menu->initheld ) {
+ if( m1 || m2 )
+ return;
+ menu->initheld = 0;
+ return;
+ }
+
+ I32 mx = 0, my = 0;
+ gui_cursor_pos( &mx, &my );
+ I32 hit = gui_contextmenu_hit_test( menu, mx, my );
+
+ if( m1 || m2 ) {
+ if( !menu->held ) {
+ menu->held = 1;
+ menu->held_idx = hit;
+ }
+ return;
+ }
+
+ if( !menu->held )
+ return;
+
+ if( hit == -1 ) {
+ gui_contextmenu_hide( menu );
+ return;
+ }
+
+ if( hit == menu->held_idx ) {
+ GUI_CONTEXTMENU_ITEM* item = &( *menu->items )[hit];
+ if( gui_contextmenu_item_enabled( item ) && item->cb ) {
+ menu->selected_idx = hit;
+
+ GUI_BASE* parent = menu->parent;
+ item->cb( item->data );
+
+ if( parent
+ && parent->children.idx_of( (GUI_BASE*)menu ) != -1
+ ) gui_contextmenu_hide( menu );
+ return;
+ }
+ }
+
+ menu->held = 0;
+ menu->held_idx = -1;
+}
+
+void gui_contextmenu_hide( GUI_CONTEXTMENU* menu ) {
+ if( !menu )
+ return;
+
+ if( menu->owner_ref && *menu->owner_ref == menu )
+ *menu->owner_ref = 0;
+
+ if( menu->parent ) {
+ I32 idx = menu->parent->children.idx_of( (GUI_BASE*)menu );
+ if( idx != -1 )
+ menu->parent->children.erase( idx );
+ }
+
+ gui_free( menu );
+}
+
+U8 gui_contextmenu_open( GUI_CONTEXTMENU *menu ) {
+ return !menu ? 0 : menu->enabled;
+}
+
+void gui_contextmenu_set_item(
+ GUI_CONTEXTMENU_ITEM* item,
+ const char* text,
+ GUI_CONTEXTMENU_CALLBACK cb,
+ void* data,
+ GUI_CONTEXTMENU_ENABLED_CALLBACK enabled_cb
+) {
+ if( !item )
+ return;
+
+ item->items.clear();
+ snprintf( item->text, sizeof( item->text ), "%s", text ? text : "" );
+ item->cb = cb;
+ item->data = data;
+ item->enabled = 1;
+ item->enabled_cb = enabled_cb;
+}
+
+GUI_CONTEXTMENU_ITEM* gui_contextmenu_selected( GUI_CONTEXTMENU* menu ) {
+ if( !menu || !menu->items )
+ return 0;
+
+ if( menu->selected_idx < 0 || menu->selected_idx >= (I32)menu->items->size )
+ return 0;
+
+ return &( *menu->items )[menu->selected_idx];
+}
+
+static I32 gui_contextmenu_next_enabled(
+ GUI_CONTEXTMENU* menu,
+ I32 delta,
+ U8 wrap
+) {
+ I32 item_count = (I32)menu->items->size;
+ I32 direction = delta < 0 ? -1 : 1;
+ I32 candidate = menu->selected_idx;
+ U8 has_candidate = candidate >= 0
+ && candidate < item_count;
+ for( I32 scanned = 0; scanned < item_count; ++scanned ) {
+ if( !has_candidate ) {
+ candidate = direction < 0
+ ? item_count - 1
+ : 0;
+ has_candidate = 1;
+ }
+ else {
+ candidate += direction;
+
+ if( candidate < 0 || candidate >= item_count ) {
+ if( !wrap )
+ return -1;
+
+ candidate = direction < 0
+ ? item_count - 1
+ : 0;
+ }
+ }
+
+ if( gui_contextmenu_item_enabled(
+ &( *menu->items )[candidate]
+ ) ) return candidate;
+ }
+ return -1;
+}
+
+void gui_contextmenu_reveal_selection(
+ GUI_CONTEXTMENU* menu
+) {
+ if( !menu || !menu->items || menu->visible_rows <= 0 )
+ return;
+
+ I32 selected = menu->selected_idx;
+
+ if( selected < 0 || selected >= (I32)menu->items->size )
+ return;
+
+ if( selected < menu->first_visible )
+ menu->first_visible = selected;
+ else if( selected >= menu->first_visible + menu->visible_rows )
+ menu->first_visible = selected - menu->visible_rows + 1;
+
+ I32 max_first = max( 0,
+ (I32)menu->items->size - menu->visible_rows
+ );
+
+ menu->first_visible = m_clamp(
+ menu->first_visible,
+ 0, max_first
+ );
+}
+
+void gui_contextmenu_move_selection(
+ GUI_CONTEXTMENU* menu,
+ I32 delta,
+ U8 wrap
+) {
+ if( !menu || !menu->items || !menu->items->size || !delta )
+ return;
+
+ I32 selected = gui_contextmenu_next_enabled(
+ menu,
+ delta,
+ wrap
+ );
+
+ if( selected < 0 )
+ return;
+
+ menu->selected_idx = selected;
+ gui_contextmenu_reveal_selection( menu );
+}
+
+GUI_CONTEXTMENU* gui_show_contextmenu(
+ I32 x, I32 y,
+ LIST<GUI_CONTEXTMENU_ITEM>* items,
+ GUI_BASE* parent,
+ const char* title,
+ I32 max_visible_rows,
+ GUI_CONTEXTMENU** owner_ref
+) {
+ if( !parent || !items || !items->size )
+ return 0;
+
+ GUI_WINDOW* owner_window = gui_get_parent_wnd( parent );
+ if( !owner_window )
+ return 0;
+
+ if( owner_ref && *owner_ref )
+ gui_contextmenu_hide( *owner_ref );
+
+ GUI_CONTEXTMENU* menu = new GUI_CONTEXTMENU;
+ menu->items = items;
+ menu->draw_fn = gui_contextmenu_draw_fn;
+ menu->input_fn = gui_contextmenu_input_fn;
+ menu->enabled = 1;
+ menu->initheld = 1;
+ menu->held = 0;
+ menu->held_idx = -1;
+ menu->selected_idx = -1;
+ menu->parent = owner_window;
+ menu->owner_ref = owner_ref;
+ I32 item_count = (I32)items->size;
+ menu->max_visible_rows = max_visible_rows > 0
+ ? min( max_visible_rows, item_count )
+ : item_count;
+ menu->visible_rows = menu->max_visible_rows;
+ menu->first_visible = max( 0,
+ item_count - menu->visible_rows
+ );
+
+ snprintf( menu->name, sizeof( menu->name ), "GUI_CONTEXTMENU" );
+ snprintf( menu->title, sizeof( menu->title ), "%s", title ? title : "" );
+
+ menu->w = gui_contextmenu_width( items, title );
+ menu->h = gui_contextmenu_height( menu->visible_rows, title );
+ menu->xbound = menu->w;
+ menu->ybound = menu->h;
+
+ menu->x = x - gui_relx( owner_window );
+ menu->y = y - gui_rely( owner_window );
+ owner_window->children.push( menu );
+
+ if( owner_ref )
+ *owner_ref = menu;
+
+ return menu;
+}