#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* 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* 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; }