403 lines
11 KiB
C
403 lines
11 KiB
C
/* 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
|