#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}; int col_widths_sum = 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); // Keep track of the sum of column widths to later render the scroll bar. col_widths_sum += table->widths[col]; // 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 scrollbar. if (table->flags.vertical_overflow) { state->pen.x = col_widths_sum; const int y_start = (int)((double)table->offset / (double)table->rows * (double)table->height); const int height = (int)((double)table->num_visible_rows / (double)table->rows * (double)table->height); FillRect( &(uiRect){.y = y_start, .width = ScrollBarWidth, .height = height}, uiPink, state); state->pen.x = x0; } } /// 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); }