summaryrefslogtreecommitdiff
path: root/src/render.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2024-07-13 10:52:24 -0700
committer3gg <3gg@shellblade.net>2024-07-13 10:52:24 -0700
commita4294e4a94189dffb1fdf99c9a60d87d77272926 (patch)
tree2e92f7c95116861bc39f4dae1d0ab5d388550000 /src/render.c
parentcf9579d7546c04dbc708bd8719e3f935a28088bd (diff)
Restructure project.
Diffstat (limited to 'src/render.c')
-rw-r--r--src/render.c283
1 files changed, 283 insertions, 0 deletions
diff --git a/src/render.c b/src/render.c
new file mode 100644
index 0000000..24490c0
--- /dev/null
+++ b/src/render.c
@@ -0,0 +1,283 @@
1#include <ui.h>
2
3#include "uiLibrary.h"
4#include "widget/table.h"
5#include "widget/widget.h"
6
7#include <cassert.h>
8#include <cstring.h>
9
10static const uiPixel uiBlack = {40, 40, 40, 255};
11static const uiPixel uiWhite = {255, 255, 255, 255};
12static const uiPixel uiPink = {128, 0, 128, 255};
13
14/// Render state.
15///
16/// Render functions are allowed to manipulate the state internally (e.g., the
17/// subsurface), but must leave the state intact before returning, except, of
18/// course, for the rendered pixels.
19///
20/// We store a subsurface separate from the surface so that we can always check
21/// whether a given coordinate is within the bounds of the physical surface.
22typedef struct RenderState {
23 uiSurface surface; /// Surface of pixels on which the UI is rendered.
24 uiRect subsurface; /// Subregion where the current UI widget is rendered.
25 uiPoint pen; /// Current pen position relative to subsurface.
26} RenderState;
27
28static void RenderWidget(RenderState* state, const uiWidget* widget);
29
30/// Push a new subsurface onto which subsequent UI widgets are rendered.
31void PushSubsurface(
32 RenderState* state, int width, int height, uiRect* original_subsurface,
33 uiPoint* original_pen) {
34 assert(state);
35 assert(original_subsurface);
36 assert(original_pen);
37
38 *original_subsurface = state->subsurface;
39 *original_pen = state->pen;
40
41 state->subsurface.x = state->subsurface.x + state->pen.x;
42 state->subsurface.width = width;
43 state->subsurface.height = height;
44 state->pen.x = 0;
45}
46
47/// Restore the previous subsurface.
48void PopSubsurface(
49 RenderState* state, const uiRect* original_subsurface,
50 const uiPoint* original_pen) {
51 assert(state);
52 assert(original_subsurface);
53 assert(original_pen);
54
55 state->subsurface = *original_subsurface;
56 state->pen = *original_pen;
57}
58
59/// Check whether pen + (w,h) is within the surface and subsurface.
60static bool PenInSurface(const RenderState* state, int w, int h) {
61 assert(state);
62
63 // Surface.
64 const bool in_surface =
65 ((state->subsurface.x + state->pen.x + w) < state->surface.width) &&
66 ((state->subsurface.y + state->pen.y + h) < state->surface.height);
67
68 // Subsurface.
69 const bool in_subsurface = ((state->pen.x + w) < state->subsurface.width) &&
70 ((state->pen.y + h) < state->subsurface.height);
71
72 return in_surface && in_subsurface;
73}
74
75/// Get the pixel at (x,y).
76static uiPixel* SurfaceXy(uiSurface* surface, int x, int y) {
77 assert(surface);
78 assert(x >= 0);
79 assert(y >= 0);
80 assert(x < surface->width);
81 assert(y < surface->height);
82 return surface->pixels + (surface->width * y) + x;
83}
84
85/// Get the pixel at pen + (x,y).
86static uiPixel* PixelXy(RenderState* state, int x, int y) {
87 assert(state);
88 return SurfaceXy(
89 &state->surface, state->subsurface.x + state->pen.x + x,
90 state->subsurface.y + state->pen.y + y);
91}
92
93/// Fill a rectangle with a constant colour.
94static void FillRect(const uiRect* rect, uiPixel colour, RenderState* state) {
95 assert(rect);
96 assert(state);
97 assert(rect->width <= state->subsurface.width);
98 assert(rect->height <= state->subsurface.height);
99
100 for (int y = rect->y; y < rect->y + rect->height; ++y) {
101 uiPixel* pixel = PixelXy(state, rect->x, y);
102 for (int x = rect->x; x < rect->x + rect->width; ++x) {
103 *pixel++ = colour;
104 }
105 }
106}
107
108/// Render a glyph.
109/// The glyph is clamped to the surface's bounds.
110static void RenderGlyph(
111 const FontAtlas* atlas, unsigned char c, RenderState* state) {
112 assert(atlas);
113 assert(state);
114 assert(atlas->header.glyph_width <= state->subsurface.width);
115 assert(atlas->header.glyph_height <= state->subsurface.height);
116
117 const int glyph_width = atlas->header.glyph_width;
118 const int glyph_height = atlas->header.glyph_height;
119
120 const unsigned char* glyph = FontGetGlyph(atlas, c);
121
122 for (int y = 0; (y < atlas->header.glyph_height) &&
123 PenInSurface(state, glyph_width - 1, glyph_height - 1);
124 ++y) {
125 for (int x = 0; (x < atlas->header.glyph_width) &&
126 PenInSurface(state, glyph_width - 1, glyph_height - 1);
127 ++x, ++glyph) {
128 uiPixel* pixel = PixelXy(state, x, y);
129 if (*glyph > 0) {
130 pixel->r = *glyph;
131 pixel->g = *glyph;
132 pixel->b = *glyph;
133 pixel->a = 255;
134 }
135 }
136 }
137}
138
139/// Render text.
140static void RenderText(const char* text, size_t length, RenderState* state) {
141 assert(text);
142 assert(state);
143
144 const FontAtlas* atlas = g_ui.font;
145
146 const int glyph_width = atlas->header.glyph_width;
147 const int glyph_height = atlas->header.glyph_height;
148
149 // Save the x-pen so that we can restore it after rendering the text.
150 const int x0 = state->pen.x;
151
152 // Truncate the text rendering if it exceeds the subsurface's width or height.
153 const char* c = text;
154 for (size_t i = 0;
155 (i < length) && PenInSurface(state, glyph_width - 1, glyph_height - 1);
156 ++i, ++c, state->pen.x += glyph_width) {
157 RenderGlyph(atlas, *c, state);
158 }
159
160 state->pen.x = x0;
161}
162
163/// Render a frame.
164static void RenderFrame(const uiFrame* frame, RenderState* state) {
165 assert(frame);
166
167 FillRect(&frame->widget.rect, uiBlack, state);
168}
169
170/// Render a label.
171static void RenderLabel(const uiLabel* label, RenderState* state) {
172 assert(label);
173 assert(state);
174
175 RenderText(string_data(label->text), string_length(label->text), state);
176}
177
178/// Render a table.
179static void RenderTable(const uiTable* table, RenderState* state) {
180 assert(table);
181 assert(state);
182
183 const int x0 = state->pen.x;
184 const int y0 = state->pen.y;
185
186 uiRect original_subsurface = {0};
187 uiPoint original_pen = {0};
188
189 // Render header.
190 if (table->header) {
191 for (int col = 0; col < table->cols; ++col) {
192 // Crop the column contents to the column width so that one column does
193 // not spill into the next.
194 PushSubsurface(
195 state, table->widths[col], state->subsurface.height,
196 &original_subsurface, &original_pen);
197
198 const uiCell* cell = &table->header[col];
199 RenderWidget(state, cell->child);
200
201 // Reset the original subsurface and pen for subsequent columns.
202 PopSubsurface(state, &original_subsurface, &original_pen);
203
204 // Next column.
205 state->pen.x += table->widths[col];
206 }
207 }
208 state->pen.x = x0;
209 state->pen.y += g_ui.font->header.glyph_height;
210
211 // Render rows.
212 for (int row = table->offset;
213 (row < table->rows) && PenInSurface(state, 0, 0); ++row) {
214 for (int col = 0; (col < table->cols) && PenInSurface(state, 0, 0); ++col) {
215 // Crop the column contents to the column width so that one column does
216 // not spill into the next.
217 PushSubsurface(
218 state, table->widths[col], state->subsurface.height,
219 &original_subsurface, &original_pen);
220
221 state->subsurface.x = state->subsurface.x + state->pen.x;
222 state->subsurface.width = table->widths[col];
223 state->pen.x = 0;
224
225 const uiCell* cell = GetCell(table, row, col);
226 RenderWidget(state, cell->child);
227
228 // Reset the original subsurface and pen for subsequent columns.
229 PopSubsurface(state, &original_subsurface, &original_pen);
230
231 // Next column.
232 state->pen.x += table->widths[col];
233 }
234 state->pen.x = x0;
235 state->pen.y += g_ui.font->header.glyph_height;
236 }
237 state->pen.y = y0;
238}
239
240/// Render a widget.
241static void RenderWidget(RenderState* state, const uiWidget* widget) {
242 assert(state);
243 assert(widget);
244
245 // Render this widget.
246 switch (widget->type) {
247 case uiTypeButton:
248 break;
249 case uiTypeFrame:
250 RenderFrame((const uiFrame*)widget, state);
251 break;
252 case uiTypeLabel:
253 RenderLabel((const uiLabel*)widget, state);
254 break;
255 case uiTypeTable:
256 RenderTable((const uiTable*)widget, state);
257 break;
258 case uiTypeMax:
259 TRAP();
260 break;
261 }
262
263 // Render children.
264 list_foreach(widget->children, child, { RenderWidget(state, child); });
265}
266
267void uiRender(const uiFrame* frame, uiSurface* surface) {
268 assert(frame);
269 assert(surface);
270
271 RenderWidget(
272 &(RenderState){
273 .surface = *surface,
274 .subsurface =
275 (uiRect){
276 .x = 0,
277 .y = 0,
278 .width = surface->width,
279 .height = surface->height},
280 .pen = {.x = 0, .y = 0},
281 },
282 (const uiWidget*)frame);
283}