From a4294e4a94189dffb1fdf99c9a60d87d77272926 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 13 Jul 2024 10:52:24 -0700 Subject: Restructure project. --- src/render.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 src/render.c (limited to 'src/render.c') 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 @@ +#include + +#include "uiLibrary.h" +#include "widget/table.h" +#include "widget/widget.h" + +#include +#include + +static const uiPixel uiBlack = {40, 40, 40, 255}; +static const uiPixel uiWhite = {255, 255, 255, 255}; +static const uiPixel uiPink = {128, 0, 128, 255}; + +/// Render state. +/// +/// Render functions are allowed to manipulate the state internally (e.g., the +/// subsurface), but must leave the state intact before returning, except, of +/// course, for the rendered pixels. +/// +/// We store a subsurface separate from the surface so that we can always check +/// whether a given coordinate is within the bounds of the physical surface. +typedef struct RenderState { + uiSurface surface; /// Surface of pixels on which the UI is rendered. + uiRect subsurface; /// Subregion where the current UI widget is rendered. + uiPoint pen; /// Current pen position relative to subsurface. +} RenderState; + +static void RenderWidget(RenderState* state, const uiWidget* widget); + +/// Push a new subsurface onto which subsequent UI widgets are rendered. +void PushSubsurface( + RenderState* state, int width, int height, uiRect* original_subsurface, + uiPoint* original_pen) { + assert(state); + assert(original_subsurface); + assert(original_pen); + + *original_subsurface = state->subsurface; + *original_pen = state->pen; + + state->subsurface.x = state->subsurface.x + state->pen.x; + state->subsurface.width = width; + state->subsurface.height = height; + state->pen.x = 0; +} + +/// Restore the previous subsurface. +void PopSubsurface( + RenderState* state, const uiRect* original_subsurface, + const uiPoint* original_pen) { + assert(state); + assert(original_subsurface); + assert(original_pen); + + state->subsurface = *original_subsurface; + state->pen = *original_pen; +} + +/// Check whether pen + (w,h) is within the surface and subsurface. +static bool PenInSurface(const RenderState* state, int w, int h) { + assert(state); + + // Surface. + const bool in_surface = + ((state->subsurface.x + state->pen.x + w) < state->surface.width) && + ((state->subsurface.y + state->pen.y + h) < state->surface.height); + + // Subsurface. + const bool in_subsurface = ((state->pen.x + w) < state->subsurface.width) && + ((state->pen.y + h) < state->subsurface.height); + + return in_surface && in_subsurface; +} + +/// Get the pixel at (x,y). +static uiPixel* SurfaceXy(uiSurface* surface, int x, int y) { + assert(surface); + assert(x >= 0); + assert(y >= 0); + assert(x < surface->width); + assert(y < surface->height); + return surface->pixels + (surface->width * y) + x; +} + +/// Get the pixel at pen + (x,y). +static uiPixel* PixelXy(RenderState* state, int x, int y) { + assert(state); + return SurfaceXy( + &state->surface, state->subsurface.x + state->pen.x + x, + state->subsurface.y + state->pen.y + y); +} + +/// Fill a rectangle with a constant colour. +static void FillRect(const uiRect* rect, uiPixel colour, RenderState* state) { + assert(rect); + assert(state); + assert(rect->width <= state->subsurface.width); + assert(rect->height <= state->subsurface.height); + + for (int y = rect->y; y < rect->y + rect->height; ++y) { + uiPixel* pixel = PixelXy(state, rect->x, y); + for (int x = rect->x; x < rect->x + rect->width; ++x) { + *pixel++ = colour; + } + } +} + +/// Render a glyph. +/// The glyph is clamped to the surface's bounds. +static void RenderGlyph( + const FontAtlas* atlas, unsigned char c, RenderState* state) { + assert(atlas); + assert(state); + assert(atlas->header.glyph_width <= state->subsurface.width); + assert(atlas->header.glyph_height <= state->subsurface.height); + + const int glyph_width = atlas->header.glyph_width; + const int glyph_height = atlas->header.glyph_height; + + const unsigned char* glyph = FontGetGlyph(atlas, c); + + for (int y = 0; (y < atlas->header.glyph_height) && + PenInSurface(state, glyph_width - 1, glyph_height - 1); + ++y) { + for (int x = 0; (x < atlas->header.glyph_width) && + PenInSurface(state, glyph_width - 1, glyph_height - 1); + ++x, ++glyph) { + uiPixel* pixel = PixelXy(state, x, y); + if (*glyph > 0) { + pixel->r = *glyph; + pixel->g = *glyph; + pixel->b = *glyph; + pixel->a = 255; + } + } + } +} + +/// Render text. +static void RenderText(const char* text, size_t length, RenderState* state) { + assert(text); + assert(state); + + const FontAtlas* atlas = g_ui.font; + + const int glyph_width = atlas->header.glyph_width; + const int glyph_height = atlas->header.glyph_height; + + // Save the x-pen so that we can restore it after rendering the text. + const int x0 = state->pen.x; + + // Truncate the text rendering if it exceeds the subsurface's width or height. + const char* c = text; + for (size_t i = 0; + (i < length) && PenInSurface(state, glyph_width - 1, glyph_height - 1); + ++i, ++c, state->pen.x += glyph_width) { + RenderGlyph(atlas, *c, state); + } + + state->pen.x = x0; +} + +/// Render a frame. +static void RenderFrame(const uiFrame* frame, RenderState* state) { + assert(frame); + + FillRect(&frame->widget.rect, uiBlack, state); +} + +/// Render a label. +static void RenderLabel(const uiLabel* label, RenderState* state) { + assert(label); + assert(state); + + RenderText(string_data(label->text), string_length(label->text), state); +} + +/// Render a table. +static void RenderTable(const uiTable* table, RenderState* state) { + assert(table); + assert(state); + + const int x0 = state->pen.x; + const int y0 = state->pen.y; + + uiRect original_subsurface = {0}; + uiPoint original_pen = {0}; + + // Render header. + if (table->header) { + for (int col = 0; col < table->cols; ++col) { + // Crop the column contents to the column width so that one column does + // not spill into the next. + PushSubsurface( + state, table->widths[col], state->subsurface.height, + &original_subsurface, &original_pen); + + const uiCell* cell = &table->header[col]; + RenderWidget(state, cell->child); + + // Reset the original subsurface and pen for subsequent columns. + PopSubsurface(state, &original_subsurface, &original_pen); + + // Next column. + state->pen.x += table->widths[col]; + } + } + state->pen.x = x0; + state->pen.y += g_ui.font->header.glyph_height; + + // Render rows. + for (int row = table->offset; + (row < table->rows) && PenInSurface(state, 0, 0); ++row) { + for (int col = 0; (col < table->cols) && PenInSurface(state, 0, 0); ++col) { + // Crop the column contents to the column width so that one column does + // not spill into the next. + PushSubsurface( + state, table->widths[col], state->subsurface.height, + &original_subsurface, &original_pen); + + state->subsurface.x = state->subsurface.x + state->pen.x; + state->subsurface.width = table->widths[col]; + state->pen.x = 0; + + const uiCell* cell = GetCell(table, row, col); + RenderWidget(state, cell->child); + + // Reset the original subsurface and pen for subsequent columns. + PopSubsurface(state, &original_subsurface, &original_pen); + + // Next column. + state->pen.x += table->widths[col]; + } + state->pen.x = x0; + state->pen.y += g_ui.font->header.glyph_height; + } + state->pen.y = y0; +} + +/// Render a widget. +static void RenderWidget(RenderState* state, const uiWidget* widget) { + assert(state); + assert(widget); + + // Render this widget. + switch (widget->type) { + case uiTypeButton: + break; + case uiTypeFrame: + RenderFrame((const uiFrame*)widget, state); + break; + case uiTypeLabel: + RenderLabel((const uiLabel*)widget, state); + break; + case uiTypeTable: + RenderTable((const uiTable*)widget, state); + break; + case uiTypeMax: + TRAP(); + break; + } + + // Render children. + list_foreach(widget->children, child, { RenderWidget(state, child); }); +} + +void uiRender(const uiFrame* frame, uiSurface* surface) { + assert(frame); + assert(surface); + + RenderWidget( + &(RenderState){ + .surface = *surface, + .subsurface = + (uiRect){ + .x = 0, + .y = 0, + .width = surface->width, + .height = surface->height}, + .pen = {.x = 0, .y = 0}, + }, + (const uiWidget*)frame); +} -- cgit v1.2.3