~rycwo/forge

ref: HEAD forge/src/gui_graph.c -rw-r--r-- 7.1 KiB
d7ee94d6Ryan Chan Fix missing cd in .build.yml 10 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
// 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_graph.h"

#include <assert.h>
#include <math.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/input.h"
#include "forge/math.h"
#include "forge/matrix.h"
#include "forge/vector.h"

static void
draw_grid(
		struct fg_gui_context* context,
		fg_vec2 const size,
		fg_vec2 const translate,
		fg_real scale) {
	static fg_real const spacing_ = 50.0;
	static fg_real const radius_ = 2.0;

	// TODO: At a certain density make the dots more sparse

	fg_real const spacing = spacing_ * scale;
	fg_real const radius = radius_ * scale;

	// We take care to extend the size by the dot diameter or we may get
	// popping as we are using the dot centers.
	int const dots_x = (int)ceil((size[0] + radius * 2.0) / spacing) + 1;
	int const dots_y = (int)ceil((size[1] + radius * 2.0) / spacing) + 1;

	fg_vec2 center;
	fg_vec2_scale(center, size, 0.5);

	fg_vec2 const offset = {
		fmodf(scale * (translate[0] - center[0]) + center[0], spacing),
		fmodf(scale * (translate[1] - center[1]) + center[1], spacing),
	};

	for (int i = 0; i < dots_x; ++i) {
		for (int j = 0; j < dots_y; ++j) {
			fg_vec2 pos = {(fg_real)(i * spacing), (fg_real)(j * spacing)};
			fg_vec2_add(pos, pos, offset);

			fg_draw_gui_circle(
					context,
					&(struct fg_gui_style){.color = {0.6, 0.6, 0.6, 1.0}},
					pos,
					radius);
		}
	}
}

void
fg_gui_nodes(
		struct fg_gui_context* context,
		struct fg_gui_node* nodes,
		int count,
		struct fg_gui_node_draw_callback* cb,
		void* user_data) {
	int bring_to_front = -1;
	for (int i = 0; i < count; ++i) {
		if (cb->draw(context, &nodes[i], user_data))
			bring_to_front = i;
	}
	int const last = count - 1;
	if (bring_to_front != -1 && bring_to_front != last) {
		// Although O(n) for the number of nodes, this is only done
		// at most once per-frame so we should be fine.
		struct fg_gui_node const tmp = nodes[bring_to_front];
		int const next = bring_to_front + 1;
		memmove(
				&nodes[bring_to_front],
				&nodes[next],
				sizeof(struct fg_gui_node) * (count - next));
		nodes[last] = tmp;
	}
}

bool
fg_begin_gui_node(
		struct fg_gui_context* context,
		uint64_t id,
		struct fg_gui_style const* style,
		fg_vec2 const size,
		fg_vec2 pos) {
	assert(id != 0);

	// Important. Process events and apply input changes since last frame
	// (if necessary) before drawing. Otherwise a frame delay is introduced
	// and may cause inconsistencies.

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

	fg_vec4 rect;
	fg_vec4_set(
			rect,
			container[0] + pos[0],
			container[1] + pos[1],
			size[0],
			size[1]);

	bool hovered;
	bool const pressed = fg_gui_item_pressed(
			context,
			id,
			rect,
			FG_MOUSE_BUTTON_LEFT,
			&hovered);
	if (pressed) {
		// Apply scale to the mouse position delta in the space of
		// the nodes (according to the active transform).
		fg_vec2 const scale = {
			1.0 / fg_mat4_get(context->transform, 0, 0),
			1.0 / fg_mat4_get(context->transform, 1, 1),
		};
		fg_vec2 delta;
		fg_vec2_mul(delta, context->input.cursor_pos_delta, scale);
		fg_vec2_add(pos, pos, delta);
	}

	fg_set_next_gui_position(context, pos, false);
	fg_begin_gui_container(context, style, size);
	return context->active == id;
}

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

void
fg_begin_gui_canvas(
		struct fg_gui_context* context,
		uint64_t id,
		struct fg_gui_canvas_transform* transform) {
	fg_vec4 container;
	fg_gui_top_layout_container(context, container, true);
	fg_begin_gui_clip(context, container);

	fg_vec2 const center = {
		container[0] + (container[2] / 2.0),
		container[1] + (container[3] / 2.0),
	};

	// Shift the translation according to the amount the scale pivot
	// is being shifted if the container is re-sized.
	fg_vec2 offset;
	fg_vec2_sub(offset, center, transform->scale_pivot);
	fg_vec2_scale(
			offset,
			offset,
			(1.0 / transform->scale) * (transform->scale - 1.0));
	fg_vec2_add(transform->translate, transform->translate, offset);

	fg_vec2_copy(transform->scale_pivot, center);

	bool hovered;
	bool const pressed = fg_gui_item_pressed(
			context,
			id,
			container,
			FG_MOUSE_BUTTON_LEFT | FG_MOUSE_BUTTON_RIGHT,
			&hovered);

	if (pressed) {
		if (context->input.mouse & FG_MOUSE_BUTTON_LEFT) {
			fg_vec2 offset;
			// Express translation in unscaled space
			fg_vec2_scale(
					offset,
					context->input.cursor_pos_delta,
					1.0 / transform->scale);
			fg_vec2_add(transform->translate, transform->translate, offset);
		}
		else { // MOUSE_BUTTON_RIGHT
			static fg_real const scale_min = 0.1;
			static fg_real const scale_max = 2.5;
			static fg_real const scale_rate = 0.001;

			// We capture the mouse offset from the canvas center to use
			// as the target as we are zooming.
			//
			// Note that mouse_press only registers the frame at which the
			// state is first triggered.
			//
			if (context->input.mouse_press & FG_MOUSE_BUTTON_RIGHT) {
				fg_vec2_sub(
						transform->scale_offset,
						center,
						context->input.cursor_pos);

				// Unscale and compensate length. To avoid massive
				// scale_offsets we simply enforce a hard limit.
				fg_real const norm = 1.0
					/ (transform->scale
							* fmax(scale_max - transform->scale, 0.5));
				fg_vec2_scale(
						transform->scale_offset,
						transform->scale_offset,
						norm);
			}

			fg_real const delta = context->input.cursor_pos_delta[0]
					* scale_rate;
			fg_real const scale = transform->scale;
			transform->scale = fg_clamp(scale + delta, scale_min, scale_max);

			fg_vec2 offset;
			fg_vec2_scale(
					offset,
					transform->scale_offset,
					transform->scale - scale);
			fg_vec2_add(transform->translate, transform->translate, offset);
		}
	}

	// Note we draw the grid before begin_gui_transform()
	draw_grid(context, &container[2], transform->translate, transform->scale);

	// To act like a 2d camera, the following transformation is composed:
	//
	// Sp * S * Sp^-1 * T * p
	//
	// Where Sp is the scale pivot, S is scale, T is translation, and p is the
	// position to be transformed. Note that unlike a regular transformation,
	// we intentionally translate *then* scale.

	fg_mat4 T;
	fg_mat4_identity(T);

	fg_mat4_set(T, 0, 0, transform->scale);
	fg_mat4_set(T, 1, 1, transform->scale);

	fg_real const tx = transform->scale
		* (transform->translate[0] - transform->scale_pivot[0])
		+ transform->scale_pivot[0];
	fg_real const ty = transform->scale
		* (transform->translate[1] - transform->scale_pivot[1])
		+ transform->scale_pivot[1];
	fg_mat4_set(T, 0, 3, tx);
	fg_mat4_set(T, 1, 3, ty);

	fg_set_gui_transform(context, T);
}

void
fg_end_gui_canvas(struct fg_gui_context* context) {
	fg_set_gui_transform_identity(context);
	fg_end_gui_clip(context);
}