super basic UI layouting
[fishladder.git] / vg / vg_ui.h
1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
2 /*
3
4 Concurrent UI buffer system
5 Coordinate space:
6
7 0,0 --- +(res:x)
8 |
9 |
10 |
11 +(res:y)
12
13 */
14
15 #define UI_AUTO_FILL 0
16
17 // Types
18 // ================================================================
19 typedef i16 ui_px;
20 typedef u32 ui_colour;
21 typedef ui_px ui_rect[4];
22 typedef struct ui_ctx ui_ctx;
23
24 struct ui_ctx
25 {
26 ui_px padding;
27
28 struct ui_qnode
29 {
30 ui_rect rect;
31 ui_colour colour;
32 int mouse_over;
33 int capture_id;
34 }
35 stack[ 32 ];
36
37 ui_rect cursor;
38 u32 stack_count;
39 u32 capture_mouse_id;
40
41 // User input
42 ui_px mouse[2];
43 int click_state; // 0: released, 1: on down, 2: pressed, 3: on release
44 };
45
46 // Rect controls
47 // ==========================================================
48
49 static void ui_rect_copy( ui_rect src, ui_rect dst )
50 {
51 dst[0] = src[0];
52 dst[1] = src[1];
53 dst[2] = src[2];
54 dst[3] = src[3];
55 }
56
57 static void ui_rect_pad( ui_ctx *ctx, ui_rect rect )
58 {
59 rect[0] += ctx->padding;
60 rect[1] += ctx->padding;
61 rect[2] -= ctx->padding*2;
62 rect[3] -= ctx->padding*2;
63 }
64
65 static void ui_vis_rect( ui_rect rect, u32 colour )
66 {
67 v2f p0;
68 v2f p1;
69
70 p0[0] = rect[0];
71 p0[1] = rect[1];
72 p1[0] = rect[0]+rect[2];
73 p1[1] = rect[1]+rect[3];
74
75 vg_line( p0, (v2f){p1[0],p0[1]}, colour );
76 vg_line( (v2f){p1[0],p0[1]}, p1, colour );
77 vg_line( p1, (v2f){p0[0],p1[1]}, colour );
78 vg_line( (v2f){p0[0],p1[1]}, p0, colour );
79 }
80
81 static void ui_new_node( ui_ctx *ctx )
82 {
83 if( ctx->stack_count == vg_list_size( ctx->stack ) )
84 vg_exiterr( "[UI] Stack overflow while creating box!" );
85
86 struct ui_qnode *parent = &ctx->stack[ ctx->stack_count-1 ];
87 struct ui_qnode *node = &ctx->stack[ ctx->stack_count++ ];
88 ui_rect_copy( ctx->cursor, node->rect );
89
90 if( parent->mouse_over )
91 {
92 if( ctx->mouse[0] >= node->rect[0] && ctx->mouse[0] <= node->rect[0]+node->rect[2] &&
93 ctx->mouse[1] >= node->rect[1] && ctx->mouse[1] <= node->rect[1]+node->rect[3] )
94 node->mouse_over = 1;
95 else
96 node->mouse_over = 0;
97 }
98 }
99
100 static int ui_hasmouse( ui_ctx *ctx )
101 {
102 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
103 return (node->mouse_over && (node->capture_id == ctx->capture_mouse_id));
104 }
105
106 static void ui_end( ui_ctx *ctx )
107 {
108 struct ui_qnode *node = &ctx->stack[ --ctx->stack_count ];
109
110 ui_rect_copy( node->rect, ctx->cursor );
111 ui_vis_rect( ctx->cursor, (node->mouse_over && (node->capture_id == ctx->capture_mouse_id))? 0xffff0000: 0xff0000ff );
112 }
113
114 static void ui_end_down( ui_ctx *ctx )
115 {
116 ui_px height = ctx->stack[ ctx->stack_count ].rect[3];
117 ui_end( ctx );
118 ctx->cursor[1] += height;
119 }
120
121 static void ui_end_right( ui_ctx *ctx )
122 {
123 ui_px width = ctx->stack[ ctx->stack_count ].rect[2];
124 ui_end( ctx );
125 ctx->cursor[0] += width;
126 }
127
128 static void ui_capture_mouse( ui_ctx *ctx, int id )
129 {
130 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
131 node->capture_id = id;
132
133 if( node->mouse_over )
134 {
135 ctx->capture_mouse_id = id;
136 }
137 }
138
139 // API control
140 // ====================================================================
141
142 static void ui_begin( ui_ctx *ctx, ui_px res_x, ui_px res_y )
143 {
144 ctx->cursor[0] = 0;
145 ctx->cursor[1] = 0;
146 ctx->cursor[2] = res_x;
147 ctx->cursor[3] = res_y;
148
149 ui_rect_copy( ctx->cursor, ctx->stack[0].rect );
150 ctx->stack[0].mouse_over = 1;
151
152 ctx->stack_count = 1;
153 }
154
155 static void ui_resolve( ui_ctx *ctx )
156 {
157 if( ctx->stack_count-1 )
158 vg_exiterr( "[UI] Mismatched node create/drestroy!" );
159 }
160
161 // User Input piping
162 // ====================================================================
163
164 static void ui_set_mouse( ui_ctx *ctx, int x, int y, int click_state )
165 {
166 ctx->mouse[0] = x;
167 ctx->mouse[1] = y;
168
169 ctx->click_state = click_state;
170 }
171
172 static void ui_test(void)
173 {
174 /*
175 +------------------------------------------------------+
176 | Central Market [x]|
177 +------+--------------+-+------------------------------+
178 | Buy | Balance |#| [filters] [favorites] |
179 | <>_ | () 2,356 |#|----------------------------+-+
180 |------|--------------|#| [] potion of madness 4 |#|
181 | Sell | \ Main sword |#|----------------------------|#|
182 | _*^ |--------------|#| [] Balance of time 23 | |
183 |------| * Side arm |#|----------------------------| |
184 | 235 |--------------| | [] Strength 5,300 | |
185 | | () Sheild | |----------------------------| |
186 | |--------------| | [] Bewilder 2,126 | |
187 | [ & Spells ] |----------------------------| |
188 | |--------------| | [] Eternal flames 6 | |
189 +------+--------------+-+----------------------------+-+
190 */
191
192 ui_ctx ctx = { .padding = 8 };
193
194 ui_begin( &ctx, vg_window_x, vg_window_y );
195
196 // TODO: Find a more elegent form for this
197 int mouse_state = 0;
198 if( vg_get_button( "primary" ) ) mouse_state = 2;
199 if( vg_get_button_down( "primary" ) ) mouse_state = 1;
200 if( vg_get_button_up( "primary" ) ) mouse_state = 3;
201
202 ui_set_mouse( &ctx, vg_mouse[0], vg_mouse[1], mouse_state );
203
204 static ui_px window_x = 20;
205 static ui_px window_y = 20;
206 static int window_drag = 0;
207 static ui_px drag_offset[2];
208
209 if( window_drag )
210 {
211 window_x = ctx.mouse[0]+drag_offset[0];
212 window_y = ctx.mouse[1]+drag_offset[1];
213
214 if( ctx.click_state == 0 )
215 {
216 window_drag = 0;
217 }
218 }
219
220 ctx.cursor[0] = window_x;
221 ctx.cursor[1] = window_y;
222 ctx.cursor[2] = 500;
223 ctx.cursor[3] = 350;
224
225 ui_new_node( &ctx );
226 {
227 ctx.cursor[0] += 20;
228 ctx.cursor[1] += 20;
229 ctx.cursor[2] = 150;
230 ctx.cursor[3] = 25;
231
232 ui_capture_mouse( &ctx, 1 );
233
234 ui_new_node( &ctx );
235 {
236 ui_capture_mouse( &ctx, 2 );
237
238 if( ui_hasmouse( &ctx ) )
239 {
240 if( ctx.click_state == 1 ) // start drag
241 {
242 window_drag = 1;
243 drag_offset[0] = window_x-ctx.mouse[0];
244 drag_offset[1] = window_y-ctx.mouse[1];
245 }
246 }
247 }
248 ui_end( &ctx );
249 }
250 ui_end( &ctx );
251
252 ui_resolve( &ctx );
253
254 m3x3f view = M3X3_IDENTITY;
255 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
256 m3x3_scale( view, (v3f){ 1.0f/((float)vg_window_x*0.5f), -1.0f/((float)vg_window_y*0.5f), 1.0f } );
257 vg_lines_drawall( (float*)view );
258 }