9758d9840c4add9c0fd524b4b3a056730a0aa36a
[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 int capture_lock;
41 u32 id_base;
42
43 // User input
44 ui_px mouse[2];
45 int click_state; // 0: released, 1: on down, 2: pressed, 3: on release
46 };
47
48 // Rect controls
49 // ==========================================================
50
51 static void ui_rect_copy( ui_rect src, ui_rect dst )
52 {
53 dst[0] = src[0];
54 dst[1] = src[1];
55 dst[2] = src[2];
56 dst[3] = src[3];
57 }
58
59 static void ui_rect_pad( ui_ctx *ctx, ui_rect rect, ui_px pad )
60 {
61 rect[0] += pad;
62 rect[1] += pad;
63 rect[2] -= pad*2;
64 rect[3] -= pad*2;
65 }
66
67 static void ui_vis_rect( ui_rect rect, u32 colour )
68 {
69 v2f p0;
70 v2f p1;
71
72 p0[0] = rect[0];
73 p0[1] = rect[1];
74 p1[0] = rect[0]+rect[2];
75 p1[1] = rect[1]+rect[3];
76
77 vg_line( p0, (v2f){p1[0],p0[1]}, colour );
78 vg_line( (v2f){p1[0],p0[1]}, p1, colour );
79 vg_line( p1, (v2f){p0[0],p1[1]}, colour );
80 vg_line( (v2f){p0[0],p1[1]}, p0, colour );
81 }
82
83 static void ui_new_node( ui_ctx *ctx )
84 {
85 if( ctx->stack_count == vg_list_size( ctx->stack ) )
86 vg_exiterr( "[UI] Stack overflow while creating box!" );
87
88 struct ui_qnode *parent = &ctx->stack[ ctx->stack_count-1 ];
89 struct ui_qnode *node = &ctx->stack[ ctx->stack_count++ ];
90 ui_rect_copy( ctx->cursor, node->rect );
91
92 if( parent->mouse_over )
93 {
94 if( ctx->mouse[0] >= node->rect[0] && ctx->mouse[0] <= node->rect[0]+node->rect[2] &&
95 ctx->mouse[1] >= node->rect[1] && ctx->mouse[1] <= node->rect[1]+node->rect[3] )
96 node->mouse_over = 1;
97 else
98 node->mouse_over = 0;
99 }
100 else
101 {
102 node->mouse_over = 0;
103 }
104 }
105
106 static int ui_hasmouse( ui_ctx *ctx )
107 {
108 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
109 return (node->mouse_over && (node->capture_id == ctx->capture_mouse_id));
110 }
111
112 static void ui_end( ui_ctx *ctx )
113 {
114 struct ui_qnode *node = &ctx->stack[ --ctx->stack_count ];
115
116 ui_rect_copy( node->rect, ctx->cursor );
117 ui_vis_rect( ctx->cursor, (node->mouse_over && (node->capture_id == ctx->capture_mouse_id))? 0xffff0000: 0xff0000ff );
118 }
119
120 static void ui_end_down( ui_ctx *ctx )
121 {
122 ui_px height = ctx->stack[ ctx->stack_count ].rect[3];
123 ui_end( ctx );
124 ctx->cursor[1] += height;
125 }
126
127 static void ui_end_right( ui_ctx *ctx )
128 {
129 ui_px width = ctx->stack[ ctx->stack_count ].rect[2];
130 ui_end( ctx );
131 ctx->cursor[0] += width;
132 }
133
134 static void ui_align_right( ui_ctx *ctx )
135 {
136 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
137 ctx->cursor[0] = node->rect[0] + node->rect[2] - ctx->cursor[2];
138 }
139
140 static void ui_clamp_rect( ui_rect parent, ui_rect dest )
141 {
142 dest[0] = vg_min( parent[0] + parent[2] - dest[2], dest[0] );
143 dest[1] = vg_min( parent[1] + parent[3] - dest[3], dest[1] );
144 dest[0] = vg_max( parent[0], dest[0] );
145 dest[1] = vg_max( parent[1], dest[1] );
146 }
147
148 static u32 ui_group_id( ui_ctx *ctx, u32 lesser_unique )
149 {
150 return ctx->id_base | lesser_unique;
151 }
152
153 static void ui_capture_mouse( ui_ctx *ctx, u32 id )
154 {
155 u32 group_uid = ui_group_id(ctx,id);
156
157 struct ui_qnode *node = &ctx->stack[ ctx->stack_count-1 ];
158 node->capture_id = group_uid;
159
160 if( !ctx->capture_lock && node->mouse_over )
161 {
162 ctx->capture_mouse_id = group_uid;
163 }
164 }
165
166 // API control
167 // ====================================================================
168
169 static void ui_begin( ui_ctx *ctx, ui_px res_x, ui_px res_y )
170 {
171 ctx->cursor[0] = 0;
172 ctx->cursor[1] = 0;
173 ctx->cursor[2] = res_x;
174 ctx->cursor[3] = res_y;
175
176 ui_rect_copy( ctx->cursor, ctx->stack[0].rect );
177 ctx->stack[0].mouse_over = 1;
178
179 ctx->stack_count = 1;
180 }
181
182 static void ui_resolve( ui_ctx *ctx )
183 {
184 if( ctx->stack_count-1 )
185 vg_exiterr( "[UI] Mismatched node create/drestroy!" );
186
187 if( ctx->click_state == 3 || ctx->click_state == 0 )
188 {
189 ctx->capture_lock = 0;
190 }
191 }
192
193 // User Input piping
194 // ====================================================================
195
196 static void ui_set_mouse( ui_ctx *ctx, int x, int y, int click_state )
197 {
198 ctx->mouse[0] = x;
199 ctx->mouse[1] = y;
200
201 ctx->click_state = click_state;
202 }
203
204 // High level controls
205 // ====================================================================
206
207 struct ui_window
208 {
209 const char *title;
210 ui_rect transform;
211
212 int drag;
213 ui_px drag_offset[2];
214 };
215
216 static int ui_button( ui_ctx *ctx, u32 id )
217 {
218 ui_new_node( ctx );
219 {
220 ui_capture_mouse( ctx, id );
221
222 if( ui_hasmouse(ctx) )
223 {
224 if( ctx->click_state == 1 )
225 ctx->capture_lock = 1;
226 else if( ctx->capture_lock && ctx->click_state == 3 )
227 {
228 return 1;
229 }
230 }
231 }
232
233 return 0;
234 }
235
236 static int ui_window( ui_ctx *ctx, struct ui_window *window, u32 control_group )
237 {
238 ctx->id_base = control_group << 16;
239
240 if( window->drag )
241 {
242 window->transform[0] = ctx->mouse[0]+window->drag_offset[0];
243 window->transform[1] = ctx->mouse[1]+window->drag_offset[1];
244
245 ui_clamp_rect( ctx->stack[0].rect, window->transform );
246
247 if( ctx->click_state == 0 || ctx->click_state == 3 )
248 {
249 window->drag = 0;
250 }
251 }
252
253 ui_rect_copy( window->transform, ctx->cursor );
254
255 ui_new_node( ctx );
256 {
257 ui_capture_mouse( ctx, __COUNTER__ );
258
259 // Drag bar
260 ctx->cursor[3] = 25;
261 ui_new_node( ctx );
262 {
263 ui_capture_mouse( ctx, __COUNTER__ );
264
265 // title..
266
267 // Close button
268 ctx->cursor[2] = 25;
269 ui_align_right( ctx );
270 ui_rect_pad( ctx, ctx->cursor, 4 );
271
272 if( ui_button( ctx, __COUNTER__ ) )
273 {
274 vg_info( "Click clacked\n" );
275 }
276 ui_end( ctx );
277
278 if( ui_hasmouse( ctx ) )
279 {
280 // start drag
281 if( ctx->click_state == 1 )
282 {
283 window->drag = 1;
284 window->drag_offset[0] = window->transform[0]-ctx->mouse[0];
285 window->drag_offset[1] = window->transform[1]-ctx->mouse[1];
286 }
287 }
288 }
289 ui_end( ctx );
290 }
291
292 return 1;
293 }
294
295 static void ui_test(void)
296 {
297 /*
298 +------------------------------------------------------+
299 | Central Market [x]|
300 +------+--------------+-+------------------------------+
301 | Buy | Balance |#| [filters] [favorites] |
302 | <>_ | () 2,356 |#|----------------------------+-+
303 |------|--------------|#| [] potion of madness 4 |#|
304 | Sell | \ Main sword |#|----------------------------|#|
305 | _*^ |--------------|#| [] Balance of time 23 | |
306 |------| * Side arm |#|----------------------------| |
307 | 235 |--------------| | [] Strength 5,300 | |
308 | | () Sheild | |----------------------------| |
309 | |--------------| | [] Bewilder 2,126 | |
310 | [ & Spells ] |----------------------------| |
311 | |--------------| | [] Eternal flames 6 | |
312 +------+--------------+-+----------------------------+-+
313 */
314
315 static ui_ctx ctx = { .padding = 8 };
316
317 ui_begin( &ctx, vg_window_x, vg_window_y );
318
319 // TODO: Find a more elegent form for this
320 int mouse_state = 0;
321 if( vg_get_button( "primary" ) ) mouse_state = 2;
322 if( vg_get_button_down( "primary" ) ) mouse_state = 1;
323 if( vg_get_button_up( "primary" ) ) mouse_state = 3;
324
325 ui_set_mouse( &ctx, vg_mouse[0], vg_mouse[1], mouse_state );
326
327 static struct ui_window window =
328 {
329 .transform = { 20, 20, 500, 350 },
330 .title = "Central Market"
331 };
332
333 if( ui_window( &ctx, &window, __COUNTER__ ) )
334 {
335 // Contents
336 }
337 ui_end( &ctx );
338
339 ui_resolve( &ctx );
340
341 m3x3f view = M3X3_IDENTITY;
342 m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } );
343 m3x3_scale( view, (v3f){ 1.0f/((float)vg_window_x*0.5f), -1.0f/((float)vg_window_y*0.5f), 1.0f } );
344 vg_lines_drawall( (float*)view );
345 }