diff options
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 | } | ||
