~rycwo/forge

ref: HEAD forge/src/gui_window.c -rw-r--r-- 9.1 KiB
d7ee94d6Ryan Chan Fix missing cd in .build.yml 8 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// This file is part of Forge, the foundation library for Forge tools.
//
// Copyright (C) 2021 Ryan Chan <rycwo@posteo.net>
// SPDX-License-Identifier: GPL-3.0-only
//
// This Source Code Form is subject to the terms of the GNU General Public
// License v3.0 only. You should have received a copy of the license along with
// this program. If not, see <https://www.gnu.org/licenses/>.

#include "forge/gui_window.h"

#include <assert.h>
#include <string.h>

#include "forge/gui_basic.h"
#include "forge/gui_draw.h"
#include "forge/gui_input.h"
#include "forge/gui_layout.h"
#include "forge/hash_table.h"
#include "forge/input.h"
#include "forge/memory_pool.h"
#include "forge/stb_sprintf.h"

static inline struct fg_window_tile*
get_window_tile(struct fg_window_manager const* wm, struct fg_handle handle) {
	return (struct fg_window_tile*)(
			fg_access_memory_pool_block(&wm->nodes, handle));
}

static void
resize_window(
		struct fg_window_manager* wm,
		struct fg_handle node,
		fg_vec2 const scale) {
	struct fg_window_tile* tile = get_window_tile(wm, node);
	if (tile->leaf)
		return;
	tile->split.size *= scale[tile->split.axis];
	resize_window(wm, tile->lhs, scale);
	resize_window(wm, tile->rhs, scale);
}

static void
draw_gui_window(
		struct fg_gui_context* context,
		struct fg_window_manager* wm,
		struct fg_handle node,
		struct fg_gui_style const* style,
		struct fg_gui_window_draw_callback const* cb,
		void* user_data,
		fg_real spacing,
		uint64_t seed) {
	// FIXME(rycwo): Although the recursion depth will be probably never be
	// enough to cause a stack overflow, an iterative version of this algorithm
	// would be preferred.

	struct fg_window_tile* tile = get_window_tile(wm, node);

	fg_vec4 container;
	fg_gui_top_layout_container(context, container, false);

	if (tile->leaf) {
		// FIXME(rycwo): Spacing to system window edges and in-between
		// windows is consistent.
		fg_real const half_space = spacing * 0.5;
		fg_set_next_gui_position(
				context,
				(fg_vec2){half_space, half_space},
				false);
		fg_begin_gui_window(
				context,
				tile->window.id,
				style,
				(fg_vec2){
					container[2] - spacing,
					container[3] - spacing
				});
		// TODO(rycwo): Draw window decorations (title bar, close button, etc.)
		cb->draw(context, &tile->window, user_data);
		fg_end_gui_window(context);
		return;
	}

	// FIXME(rycwo)
	static char name[32];
	stbsp_snprintf(&name[0], 32, u8"split_%d", node.id);

	int const i = tile->split.axis;
	int const j = i ^ 1;

	fg_real split_size = tile->split.size;
	fg_gui_window_split(
			context,
			fg_hash_string(name, seed),
			tile->split.axis,
			container[2 + j],
			&split_size);

	// Compensate right child split if on the same axis.
	// This correctly retains the size of subsequent splits.
	struct fg_window_tile* child = get_window_tile(wm, tile->rhs);
	if (!child->leaf && child->split.axis == tile->split.axis)
		child->split.size -= split_size - tile->split.size;
	tile->split.size = split_size;

	// Depth-first traversal of window binary tree
	{
		fg_vec4 rect;
		rect[0] = 0.0;
		rect[1] = 0.0;
		rect[2 + i] = tile->split.size;
		rect[2 + j] = container[2 + j];
		fg_push_gui_layout_container(context, rect, false);
		draw_gui_window(
				context, wm, tile->lhs, style, cb, user_data, spacing, seed);
		fg_pop_gui_layout_container(context, (fg_vec4){0}, false);
	}
	{
		fg_vec4 rect;
		rect[i] = tile->split.size;
		rect[j] = 0.0;
		rect[2 + i] = container[2 + i] - tile->split.size;
		rect[2 + j] = container[2 + j];
		fg_push_gui_layout_container(context, rect, false);
		draw_gui_window(
				context, wm, tile->rhs, style, cb, user_data, spacing, seed);
		fg_pop_gui_layout_container(context, (fg_vec4){0}, false);
	}
}

void
fg_init_window_manager(
		struct fg_window_manager* wm,
		struct fg_allocator const* alloc,
		int max_window_tree_size) {
	memset(wm, 0, sizeof(struct fg_window_manager));
	fg_alloc_memory_pool(
			&wm->nodes,
			alloc,
			max_window_tree_size,
			sizeof(struct fg_window_tile));
}

void
fg_terminate_window_manager(struct fg_window_manager* wm) {
	fg_free_memory_pool(&wm->nodes);
}

struct fg_handle
fg_insert_root_window(struct fg_window_manager* wm, struct fg_window window) {
	assert(!fg_handle_valid(&wm->nodes, wm->root));
	struct fg_handle const root_hdl = fg_alloc_memory_pool_block(&wm->nodes);
	struct fg_window_tile* root = get_window_tile(wm, root_hdl);
	memset(root, 0, sizeof(struct fg_window_tile));
	root->window = window;
	root->leaf = true;
	return root_hdl;
}

struct fg_handle
fg_insert_window(
		struct fg_window_manager* wm,
		struct fg_handle node,
		struct fg_window window,
		struct fg_window_tile_split split,
		int side) {
	assert(fg_handle_valid(&wm->nodes, node));

	struct fg_window_tile* other = get_window_tile(wm, node);
	assert(other->leaf);

	struct fg_handle const branch_hdl = fg_alloc_memory_pool_block(&wm->nodes);
	struct fg_window_tile* branch = get_window_tile(wm, branch_hdl);

	branch->split = split;
	branch->leaf = false;

	// Parent the branch under the parent. If there isn't a valid
	// parent then we assume we are splitting the root tile.
	struct fg_handle const parent_hdl = other->parent;
	if (fg_handle_valid(&wm->nodes, parent_hdl)) {
		struct fg_window_tile* parent = get_window_tile(wm, parent_hdl);
		if (parent->lhs.id == node.id)
			parent->lhs = branch_hdl;
		else
			parent->rhs = branch_hdl;
		branch->parent = parent_hdl;
	}
	else
		wm->root = branch_hdl;

	// Parent the other and new windows under the branch
	struct fg_handle const new_hdl = fg_alloc_memory_pool_block(&wm->nodes);
	struct fg_window_tile* new = get_window_tile(wm, new_hdl);

	new->window = window;
	new->parent = branch_hdl;
	new->leaf = true;

	other->parent = branch_hdl;

	if (side == (int)FG_WINDOW_TILE_LU) {
		branch->lhs = new_hdl;
		branch->rhs = node;
	}
	else {
		branch->lhs = node;
		branch->rhs = new_hdl;
	}
	return new_hdl;
}

void
fg_delete_window(struct fg_window_manager* wm, struct fg_handle node) {
	struct fg_window_tile* window = get_window_tile(wm, node);
	assert(window->leaf);

	if (node.id == wm->root.id) {
		fg_free_memory_pool_block(&wm->nodes, node);
		memset(&wm->root, 0, sizeof(struct fg_handle));
		return;
	}

	struct fg_handle const branch_hdl = window->parent;
	struct fg_window_tile* branch = get_window_tile(wm, branch_hdl);

	struct fg_handle const other_hdl = (branch->lhs.id == node.id)
		?  branch->rhs : branch->lhs;
	struct fg_window_tile* other = get_window_tile(wm, other_hdl);

	// Parent the remaining window under the parent. If there isn't
	// a valid parent then we assume the window is the new root.
	struct fg_handle const parent_hdl = branch->parent;
	if (fg_handle_valid(&wm->nodes, parent_hdl)) {
		struct fg_window_tile* parent = get_window_tile(wm, parent_hdl);
		if (parent->lhs.id == branch_hdl.id)
			parent->lhs = other_hdl;
		else
			parent->rhs = other_hdl;
		other->parent = parent_hdl;
	}
	else
		wm->root = other_hdl;

	fg_free_memory_pool_block(&wm->nodes, node);
	fg_free_memory_pool_block(&wm->nodes, branch_hdl);
}

void
fg_resize_window_viewport(struct fg_window_manager* wm, fg_vec2 const scale) {
	if (!fg_handle_valid(&wm->nodes, wm->root))
		return;
	resize_window(wm, wm->root, scale);
}

void
fg_gui_windows(
		struct fg_gui_context* context,
		struct fg_window_manager* wm,
		struct fg_gui_style const* style,
		struct fg_gui_window_draw_callback const* cb,
		void* user_data,
		fg_real spacing,
		uint64_t seed) {
	if (!fg_handle_valid(&wm->nodes, wm->root))
		return;

	fg_vec4 container;
	fg_gui_top_layout_container(context, container, false);

	fg_real const half_space = spacing * 0.5;
	fg_vec4 const rect = {
		half_space,
		half_space,
		container[2] - spacing,
		container[3] - spacing
	};
	fg_push_gui_layout_container(context, rect, false);
	draw_gui_window(context, wm, wm->root, style, cb, user_data, spacing, seed);
	fg_pop_gui_layout_container(context, (fg_vec4){0}, false);
}

void
fg_gui_window_split(
		struct fg_gui_context* context,
		uint64_t id,
		int axis,
		fg_real length,
		fg_real* pos) {
	static fg_real const width = 2.0;
	fg_set_gui_layer(context, FG_GUI_LAYER_OVERLAY);

	int const i = axis;
	int const j = axis ^ 1;

	fg_vec4 container;
	fg_gui_top_layout_container(context, container, true);

	fg_vec4 rect;
	rect[i] = container[0] + *pos - 10.0;
	rect[j] = container[1];
	rect[2 + i] = 20.0;
	rect[2 + j] = length;

	bool hovered;
	bool const pressed = fg_gui_item_pressed(
			context,
			id,
			rect,
			FG_MOUSE_BUTTON_LEFT,
			&hovered);
	if (pressed) {
		fg_real const delta = context->input.cursor_pos_delta[i];
		*pos += delta;
	}

	if ((hovered && !context->active) || pressed) {
		fg_vec4 disp_rect;
		disp_rect[i] = container[0] + *pos - width * 0.5;
		disp_rect[j] = container[1];
		disp_rect[2 + i] = width;
		disp_rect[2 + j] = length;
		fg_draw_gui_rect(
				context,
				&(struct fg_gui_style){.color = {1.0, 0.4, 0.4, 0.8}},
				disp_rect);
	}
	fg_set_gui_layer(context, FG_GUI_LAYER_BASE);
}

void
fg_begin_gui_window(
		struct fg_gui_context* context,
		uint64_t id,
		struct fg_gui_style const* style,
		fg_vec2 const size) {
	assert(id != 0);
	// TODO(rycwo): Interaction
	fg_begin_gui_container(context, style, size);
}

void
fg_end_gui_window(struct fg_gui_context* context) {
	fg_end_gui_container(context);
}