+// Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
+/*
+
+ Concurrent UI buffer system
+ Coordinate space:
+
+ 0,0 --- +(res:x)
+ |
+ |
+ |
++(res:y)
+
+*/
+
+#define UI_AUTO_FILL 0
+
+// Types
+// ================================================================
+typedef i16 ui_px;
+typedef u32 ui_colour;
+typedef ui_px ui_rect[4];
+typedef struct ui_ctx ui_ctx;
+
+struct ui_ctx
+{
+ ui_px padding;
+
+ struct ui_qnode
+ {
+ ui_rect rect;
+ ui_colour colour;
+ int mouse_over;
+ int capture_id;
+ }
+ stack[ 32 ];
+
+ ui_rect cursor;
+ u32 stack_count;
+ u32 capture_mouse_id;
+
+ // User input
+ ui_px mouse[2];
+ int click_state; // 0: released, 1: on down, 2: pressed, 3: on release
+};
+
+// Rect controls
+// ==========================================================
+
+static void ui_rect_copy( ui_rect src, ui_rect dst )
+{
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+}
+
+static void ui_rect_pad( ui_ctx *ctx, ui_rect rect )
+{
+ rect[0] += ctx->padding;
+ rect[1] += ctx->padding;
+ rect[2] -= ctx->padding*2;
+ rect[3] -= ctx->padding*2;
+}
+
+static void ui_vis_rect( ui_rect rect, u32 colour )
+{
+ v2f p0;
+ v2f p1;
+
+ p0[0] = rect[0];
+ p0[1] = rect[1];
+ p1[0] = rect[0]+rect[2];
+ p1[1] = rect[1]+rect[3];
+
+ vg_line( p0, (v2f){p1[0],p0[1]}, colour );
+ vg_line( (v2f){p1[0],p0[1]}, p1, colour );
+ vg_line( p1, (v2f){p0[0],p1[1]}, colour );
+ vg_line( (v2f){p0[0],p1[1]}, p0, colour );
+}
+
+static void ui_new_node( ui_ctx *ctx )
+{
+ if( ctx->stack_count == vg_list_size( ctx->stack ) )
+ vg_exiterr( "[UI] Stack overflow while creating box!" );
+
+ struct ui_qnode *parent = &ctx->stack[ ctx->stack_count-1 ];
+ struct ui_qnode *node = &ctx->stack[ ctx->stack_count++ ];
+ ui_rect_copy( ctx->cursor, node->rect );
+
+ if( parent->mouse_over )
+ {
+ if( ctx->mouse[0] >= node->rect[0] && ctx->mouse[0] <= node->rect[0]+node->rect[2] &&
+ ctx->mouse[1] >= node->rect[1] && ctx->mouse[1] <= node->rect[1]+node->rect[3] )
+ node->mouse_over = 1;
+ else
+ node->mouse_over = 0;
+ }
+}
+
+static int ui_hasmouse( ui_ctx *ctx )
+{
+ struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
+ return (node->mouse_over && (node->capture_id == ctx->capture_mouse_id));
+}
+
+static void ui_end( ui_ctx *ctx )
+{
+ struct ui_qnode *node = &ctx->stack[ --ctx->stack_count ];
+
+ ui_rect_copy( node->rect, ctx->cursor );
+ ui_vis_rect( ctx->cursor, (node->mouse_over && (node->capture_id == ctx->capture_mouse_id))? 0xffff0000: 0xff0000ff );
+}
+
+static void ui_end_down( ui_ctx *ctx )
+{
+ ui_px height = ctx->stack[ ctx->stack_count ].rect[3];
+ ui_end( ctx );
+ ctx->cursor[1] += height;
+}
+
+static void ui_end_right( ui_ctx *ctx )
+{
+ ui_px width = ctx->stack[ ctx->stack_count ].rect[2];
+ ui_end( ctx );
+ ctx->cursor[0] += width;
+}
+
+static void ui_capture_mouse( ui_ctx *ctx, int id )
+{
+ struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
+ node->capture_id = id;
+
+ if( node->mouse_over )
+ {
+ ctx->capture_mouse_id = id;
+ }
+}
+
+// API control
+// ====================================================================
+
+static void ui_begin( ui_ctx *ctx, ui_px res_x, ui_px res_y )
+{
+ ctx->cursor[0] = 0;
+ ctx->cursor[1] = 0;
+ ctx->cursor[2] = res_x;
+ ctx->cursor[3] = res_y;
+
+ ui_rect_copy( ctx->cursor, ctx->stack[0].rect );
+ ctx->stack[0].mouse_over = 1;
+
+ ctx->stack_count = 1;
+}
+
+static void ui_resolve( ui_ctx *ctx )
+{
+ if( ctx->stack_count-1 )
+ vg_exiterr( "[UI] Mismatched node create/drestroy!" );
+}
+
+// User Input piping
+// ====================================================================
+
+static void ui_set_mouse( ui_ctx *ctx, int x, int y, int click_state )
+{
+ ctx->mouse[0] = x;
+ ctx->mouse[1] = y;
+
+ ctx->click_state = click_state;
+}
+
+static void ui_test(void)
+{
+ /*
+ +------------------------------------------------------+
+ | Central Market [x]|
+ +------+--------------+-+------------------------------+
+ | Buy | Balance |#| [filters] [favorites] |
+ | <>_ | () 2,356 |#|----------------------------+-+
+ |------|--------------|#| [] potion of madness 4 |#|
+ | Sell | \ Main sword |#|----------------------------|#|
+ | _*^ |--------------|#| [] Balance of time 23 | |
+ |------| * Side arm |#|----------------------------| |
+ | 235 |--------------| | [] Strength 5,300 | |
+ | | () Sheild | |----------------------------| |
+ | |--------------| | [] Bewilder 2,126 | |
+ | [ & Spells ] |----------------------------| |
+ | |--------------| | [] Eternal flames 6 | |
+ +------+--------------+-+----------------------------+-+
+ */
+
+ ui_ctx ctx = { .padding = 8 };
+
+ ui_begin( &ctx, vg_window_x, vg_window_y );
+
+ // TODO: Find a more elegent form for this
+ int mouse_state = 0;
+ if( vg_get_button( "primary" ) ) mouse_state = 2;
+ if( vg_get_button_down( "primary" ) ) mouse_state = 1;
+ if( vg_get_button_up( "primary" ) ) mouse_state = 3;
+
+ ui_set_mouse( &ctx, vg_mouse[0], vg_mouse[1], mouse_state );
+
+ static ui_px window_x = 20;
+ static ui_px window_y = 20;
+ static int window_drag = 0;
+ static ui_px drag_offset[2];
+
+ if( window_drag )
+ {
+ window_x = ctx.mouse[0]+drag_offset[0];
+ window_y = ctx.mouse[1]+drag_offset[1];
+
+ if( ctx.click_state == 0 )
+ {
+ window_drag = 0;
+ }
+ }
+
+ ctx.cursor[0] = window_x;
+ ctx.cursor[1] = window_y;
+ ctx.cursor[2] = 500;
+ ctx.cursor[3] = 350;
+
+ ui_new_node( &ctx );
+ {
+ ctx.cursor[0] += 20;
+ ctx.cursor[1] += 20;
+ ctx.cursor[2] = 150;
+ ctx.cursor[3] = 25;
+
+ ui_capture_mouse( &ctx, 1 );
+
+ ui_new_node( &ctx );
+ {
+ ui_capture_mouse( &ctx, 2 );
+
+ if( ui_hasmouse( &ctx ) )
+ {
+ if( ctx.click_state == 1 ) // start drag
+ {
+ window_drag = 1;
+ drag_offset[0] = window_x-ctx.mouse[0];
+ drag_offset[1] = window_y-ctx.mouse[1];
+ }
+ }
+ }
+ ui_end( &ctx );
+ }
+ ui_end( &ctx );
+
+ ui_resolve( &ctx );
+
+ m3x3f view = M3X3_IDENTITY;
+ m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
+ m3x3_scale( view, (v3f){ 1.0f/((float)vg_window_x*0.5f), -1.0f/((float)vg_window_y*0.5f), 1.0f } );
+ vg_lines_drawall( (float*)view );
+}