/* date = March 25th 2024 10:14 pm */ #ifndef UI_CORE_H #define UI_CORE_H //////////////////////////////// //~ Keys typedef enum UI_Color { UI_Color_Null, UI_Color_PlainBackground, UI_Color_PlainText, UI_Color_PlainBorder, UI_Color_PlainOverlay, UI_Color_COUNT } UI_Color; //////////////////////////////// //~ Keys // The UI Key is used to hash a "widget" so we can get events from it. typedef struct UI_Key UI_Key; struct UI_Key { U64 u64[1]; }; //////////////////////////////// //~ Mouse Button Kinds typedef enum UI_MouseButtonKind { UI_MouseButtonKind_Left, UI_MouseButtonKind_Middle, UI_MouseButtonKind_Right, UI_MouseButtonKind_COUNT } UI_MouseButtonKind; //////////////////////////////// //~ Focus Types (for hot, active widget states) typedef enum UI_FocusKind { UI_FocusKind_Null, UI_FocusKind_Off, UI_FocusKind_On, UI_FocusKind_Root, UI_FocusKind_COUNT } UI_FocusKind; //////////////////////////////// //~ Semantic sizes // The size kind specifies how the size of a box should be computed typedef enum UI_SizeKind { UI_SizeKind_Pixels, UI_SizeKind_TextDim, UI_SizeKind_Percent, UI_SizeKind_SizeByChildren, UI_SizeKind_COUNT } UI_SizeKind; typedef struct UI_Size UI_Size; struct UI_Size { UI_SizeKind kind; F32 value; F32 strictness; }; //////////////////////////////// //~ Main UI hierarchy typedef enum UI_TextAlignment { UI_TextAlignment_Left, UI_TextAlignment_COUNT, } UI_TextAlignment; typedef U32 UI_BoxFlags; enum { // Interaction UI_BoxFlag_Disabled = (1<<0), UI_BoxFlag_MouseClickable = (1<<1), UI_BoxFlag_FocusHot = (1<<3), UI_BoxFlag_FocusActive = (1<<4), // Layout UI_BoxFlag_FloatingX = (1<<5), UI_BoxFlag_FloatingY = (1<<6), UI_BoxFlag_FixedWidth = (1<<7), UI_BoxFlag_FixedHeight = (1<<8), UI_BoxFlag_OverflowX = (1<<9), UI_BoxFlag_OverflowY = (1<<10), // Appearance UI_BoxFlag_Clip = (1<<11), UI_BoxFlag_DrawText = (1<<12), UI_BoxFlag_DrawBackground = (1<<13), UI_BoxFlag_DrawBorder = (1<<14), UI_BoxFlag_DrawHotEffects = (1<<15), UI_BoxFlag_DrawActiveEffects = (1<<16), UI_BoxFlag_DisableTextTruncate = (1<<17), UI_BoxFlag_DrawDropShadow = (1<<18), // Helpers, when either X or Y is active etc. UI_BoxFlag_Floating = UI_BoxFlag_FloatingX | UI_BoxFlag_FloatingY, UI_BoxFlag_Clickable = UI_BoxFlag_MouseClickable, //| UI_BoxFlag_KeyboardClickable, }; // UI Box is the big struct that handles all of the information of a "node" in the ui hierarchy. // The box is a part of the composition that can be described as a "widget". // It has both the tree information adn the state information in it. typedef struct UI_Box UI_Box; struct UI_Box { // Hash links, persistent across frames UI_Box *hash_next; UI_Box *hash_prev; // Tree link data, updates every frame. // This enables us to encode an n-ary tree to describe the "box" hierarchy that defines the UI. UI_Box *first; UI_Box *last; UI_Box *next; UI_Box *prev; UI_Box *parent; U64 child_count; // Key and generation info UI_Key key; U64 last_frame_touched_index; // Per-build parameters UI_BoxFlags flags; String8 string; Vec2_F32 fixed_position; Vec2_F32 fixed_size; UI_Size pref_size[Axis2_COUNT]; Axis2 child_layout_axis; Vec4_F32 background_color; Vec4_F32 text_color; Vec4_F32 border_color; Vec4_F32 overlay_color; F32 corner_radii[Corner_COUNT]; // Post size determination Vec2_F32 calc_size; Vec2_F32 calc_rel_pos; // Post-layout data Rng2_F32 rel_rect; Rng2_F32 rect; // State that is persistent across frames F32 hot_t; F32 active_t; F32 disabled_t; F32 focus_hot_t; F32 focus_active_t; U64 first_gen_touched; U64 last_gen_touched; Vec2_F32 view_offset; Vec2_F32 target_view_offset; }; typedef struct UI_BoxRec UI_BoxRec; struct UI_BoxRec { UI_Box *next; S32 push_count; S32 pop_count; }; //~ Signal typedef U32 UI_SignalFlags; enum { // mouse press -> box was pressed while hovering UI_SignalFlag_LeftPressed = (1<<0), UI_SignalFlag_MiddlePressed = (1<<1), UI_SignalFlag_RightPressed = (1<<2), // released -> box was previously pressed & user released, in or out of bounds UI_SignalFlag_LeftReleased = (1<<12), UI_SignalFlag_MiddleReleased = (1<<13), UI_SignalFlag_RightReleased = (1<<14), // clicked -> box was previously pressed & user released, in bounds UI_SignalFlag_LeftClicked = (1<<15), UI_SignalFlag_MiddleClicked = (1<<16), UI_SignalFlag_RightClicked = (1<<17), UI_SignalFlag_Hovering = (1<<25), // hovering specifically this box UI_SignalFlag_MouseOver = (1<<26), // mouse is over, but may be occluded UI_SignalFlag_Pressed = UI_SignalFlag_LeftPressed | UI_SignalFlag_MiddlePressed | UI_SignalFlag_RightPressed, UI_SignalFlag_Clicked = UI_SignalFlag_LeftClicked | UI_SignalFlag_MiddleClicked | UI_SignalFlag_RightClicked }; // The UI Signal is the struct which carries the information about user interaction with a box typedef struct UI_Signal UI_Signal; struct UI_Signal { UI_Box *box; UI_SignalFlags flag; }; #define UI_hovering(s) !!((s).flag & UI_SignalFlag_Hovering) #define UI_pressed(s) !!((s).flag & UI_SignalFlag_Pressed) ///////////////////////////////// //~ Generated/meta // This is "metaprogramming" code that eventually will be generated externally. // Right now I am doing some thing by hand, but it basically contains macros that define // different stack members for the UI state. #include "ui_meta.h" //////////////////////////////// //~ UI State // This is a slot in the hash table that helps caching of UI boxes between frames. typedef struct UI_BoxSlot UI_BoxSlot; struct UI_BoxSlot { UI_Box *first; UI_Box *last; }; typedef struct UI_State UI_State; struct UI_State { // Permanent state U64 build_gen; Arena *arena; // Frame arenas. We have two since we want to use information from the previous frame to // compute things in the current frame. U32 last_frame_arena_index; U32 current_frame_arena_index; Arena *frame_arenas[2]; //- Persistent box state UI_Box *first_free_box; U64 free_box_list_count; UI_BoxSlot *box_table; U64 box_table_size; //- Per UI build parameters OS_Handle window; OS_EventList *events; UI_Box *root; UI_Box *ctx_menu_root; B32 ctx_menu_touched_this_frame; Vec2_F32 mouse; //- User interaction state UI_Key hot_box_key; UI_Key active_box_key[UI_MouseButtonKind_COUNT]; //- Color, rendering properties Vec4_F32 colors[UI_Color_COUNT]; //- Context menu state UI_Key ctx_menu_anchor_key; UI_Key next_ctx_menu_anchor_key; Vec2_F32 ctx_menu_anchor_box_last_pos; Vec2_F32 ctx_menu_anchor_offset; B32 ctx_menu_open; B32 next_ctx_menu_open; UI_Key ctx_menu_key; B32 ctx_menu_changed; // TODO(anton): tooltip root // Stack state. Here Ryan uses generated code from metadesk. I will do it by hand to start with. UI_declare_stack_nils; UI_declare_stacks; }; //////////////////////////////// //~ Globals // Nil structs global UI_Box ui_g_nil_box; //////////////////////////////// //~ Basic type functions //- Boxes root_function B32 UI_box_is_nil(UI_Box *box); #define UI_box_set_nil(b) ((b) = &ui_g_nil_box) root_function UI_BoxRec UI_box_recurse_depth_first(UI_Box *box, UI_Box *stopper, MemberOffset sib, MemberOffset child); #define UI_box_recurse_depth_first_post(box, stopper) UI_box_recurse_depth_first((box), (stopper), MemberOff(UI_Box, prev), MemberOff(UI_Box, last)); //- Sizes root_function UI_Size UI_size_make(UI_SizeKind kind, F32 value, F32 strictness); #define UI_pixels(v, strictness) UI_size_make(UI_SizeKind_Pixels, (v), (strictness)) #define UI_size_by_children(v, strictness) UI_size_make(UI_SizeKind_SizeByChildren, (v), (strictness)) #define UI_pct(v, strictness) UI_size_make(UI_SizeKind_Percent, (v), (strictness)) //- ID strings root_function String8 UI_hash_part_from_box_string(String8 string); //- Keys root_function UI_Key UI_key_zero(void); root_function UI_Key UI_key_from_string(UI_Key seed, String8 string); root_function B32 UI_key_match(UI_Key a, UI_Key b); //- Signal root_function UI_Signal UI_signal_from_box(UI_Box *box); //////////////////////////////// //~ UI State functions root_function UI_State *UI_state_alloc(void); root_function void UI_state_set(UI_State *ui); //////////////////////////////// //~ Build phase root_function void UI_build_begin(OS_Handle window, OS_EventList *events); root_function void UI_build_end(void); //////////////////////////////// //~ Context menu root_function void UI_ctx_menu_open(UI_Key key, UI_Key anchor_key, Vec2_F32 anchor_offset); root_function void UI_ctx_menu_close(void); root_function B32 UI_begin_ctx_menu(UI_Key key); root_function void UI_end_ctx_menu(void); //////////////////////////////// //~ UI Frame root_function Arena *UI_frame_arena(void); root_function void UI_frame_begin(F32 delta_time); root_function void UI_frame_end(void); //////////////////////////////// //~ Box hierarchy construction root_function UI_Box *UI_box_from_key(UI_Key key); root_function UI_Box *UI_box_make_from_key(UI_BoxFlags flags, UI_Key key); root_function UI_Box *UI_box_make(UI_BoxFlags flags, String8 string); //////////////////////////////// //~ layout root_function void UI_solve_independent_sizes(UI_Box *root, Axis2 axis); root_function void UI_solve_upward_dependent_sizes(UI_Box *root, Axis2 axis); root_function void UI_solve_downward_dependent_sizes(UI_Box *root, Axis2 axis); root_function void UI_solve_size_violations(UI_Box *root, Axis2 axis); root_function void UI_layout_root(UI_Box *root, Axis2 axis); root_function void UI_layout(void); //////////////////////////////// //~ Compositions root_function void UI_push_pref_size(Axis2 axis, UI_Size v); root_function void UI_pop_pref_size(Axis2 axis); root_function void UI_set_next_pref_size(Axis2 axis, UI_Size v); root_function void UI_push_fixed_pos(Vec2_F32 v); root_function void UI_pop_fixed_pos(); root_function void UI_set_next_fixed_pos(Vec2_F32 v); root_function void UI_push_fixed_rect(Rng2_F32 rect); root_function void UI_pop_fixed_rect(); root_function void UI_set_next_fixed_rect(Rng2_F32 rect); //////////////////////////////// //~ Drawing and text root_function Vec2_F32 UI_text_pos_from_box(UI_Box *box); root_function String8 UI_display_string_from_box(UI_Box *box); //////////////////////////////// //~ Defer helpers //- base #define UI_parent(v) DeferLoop(UI_push_parent(v), UI_pop_parent()) #define UI_pref_width(v) DeferLoop(UI_push_pref_width(v), UI_pop_pref_width()) #define UI_pref_height(v) DeferLoop(UI_push_pref_height(v), UI_pop_pref_height()) //- pane #define UI_pane(r, s) DeferLoop(UI_pane_begin((r), (s)), UI_pane_end()) //- compositions #define UI_width_fill UI_pref_width(UI_pct(1, 0)) #define UI_height_fill UI_pref_height(UI_pct(1, 0)) #define UI_fixed_x(v) DeferLoop(UI_push_fixed_x((v)), UI_pop_fixed_x()) #define UI_fixed_y(v) DeferLoop(UI_push_fixed_y((v)), UI_pop_fixed_y()) #define UI_fixed_pos(v) DeferLoop(UI_push_fixed_pos(v), UI_pop_fixed_pos()) #define UI_ctx_menu(key) DeferLoopChecked(UI_begin_ctx_menu(key), UI_end_ctx_menu()) #endif //UI_CORE_H