diff options
author | 3gg <3gg@shellblade.net> | 2024-07-13 10:52:24 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2024-07-13 10:52:24 -0700 |
commit | a4294e4a94189dffb1fdf99c9a60d87d77272926 (patch) | |
tree | 2e92f7c95116861bc39f4dae1d0ab5d388550000 /src/render.c | |
parent | cf9579d7546c04dbc708bd8719e3f935a28088bd (diff) |
Restructure project.
Diffstat (limited to 'src/render.c')
-rw-r--r-- | src/render.c | 283 |
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 | |||
10 | static const uiPixel uiBlack = {40, 40, 40, 255}; | ||
11 | static const uiPixel uiWhite = {255, 255, 255, 255}; | ||
12 | static 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. | ||
22 | typedef 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 | |||
28 | static void RenderWidget(RenderState* state, const uiWidget* widget); | ||
29 | |||
30 | /// Push a new subsurface onto which subsequent UI widgets are rendered. | ||
31 | void 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. | ||
48 | void 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. | ||
60 | static 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). | ||
76 | static 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). | ||
86 | static 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. | ||
94 | static 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. | ||
110 | static 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. | ||
140 | static 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. | ||
164 | static void RenderFrame(const uiFrame* frame, RenderState* state) { | ||
165 | assert(frame); | ||
166 | |||
167 | FillRect(&frame->widget.rect, uiBlack, state); | ||
168 | } | ||
169 | |||
170 | /// Render a label. | ||
171 | static 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. | ||
179 | static 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. | ||
241 | static 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 | |||
267 | void 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 | } | ||