module ugui; import std::ascii; // command type enum CmdType { CMD_RECT, CMD_UPDATE_ATLAS, CMD_SPRITE, CMD_SCISSOR, } // command to draw a rect struct CmdRect { Rect rect; ushort radius; Color color; } struct CmdUpdateAtlas { Id id; char* raw_buffer; short width, height, bpp; } // TODO: // 1. Add atlases as a data type // 2. Each atlas has an id struct CmdSprite { Rect rect; Rect texture_rect; Id texture_id; } // if rect is zero Rect{0} then reset the scissor struct CmdScissor { Rect rect; } // command structure struct Cmd { CmdType type; union { CmdRect rect; CmdUpdateAtlas update_atlas; CmdSprite sprite; CmdScissor scissor; } } // FIXME: is this really the best solution? // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) { // FIXME: this should be culled higher up, maybe if (rect.w <= 0 || rect.h <= 0) { return; } Rect border = ctx.style.border; Rect padding = ctx.style.padding; ushort radius = ctx.style.radius; Color border_color = ctx.style.brcolor; if (do_border) { Cmd cmd = { .type = CMD_RECT, .rect.rect = rect, .rect.color = border_color, .rect.radius = do_radius ? radius : 0, }; ctx.cmd_queue.enqueue(&cmd)!; } Cmd cmd = { .type = CMD_RECT, .rect.rect = { .x = rect.x + (do_border ? border.x : 0) + (do_padding ? padding.x : 0), .y = rect.y + (do_border ? border.y : 0) + (do_padding ? padding.y : 0), .w = rect.w - (do_border ? border.x+border.w : 0) - (do_padding ? padding.x+padding.w : 0), .h = rect.h - (do_border ? border.y+border.h : 0) - (do_padding ? padding.y+padding.h : 0), }, .rect.color = color, .rect.radius = do_radius ? radius : 0, }; ctx.cmd_queue.enqueue(&cmd)!; } // TODO: add texture id fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id) { Cmd cmd = { .type = CMD_SPRITE, .sprite.rect = bounds, .sprite.texture_rect = texture, .sprite.texture_id = texture_id, }; ctx.cmd_queue.enqueue(&cmd)!; } fn void! Ctx.push_string(&ctx, Rect bounds, String text) { if (text.len == 0) { return; } ctx.push_scissor(bounds)!; short baseline = (short)ctx.font.ascender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short line_gap = (short)ctx.font.linegap; Id texture_id = ctx.font.id; // or ctx.font.atlas.id Point orig = { .x = bounds.x, .y = bounds.y, }; short line_len; Codepoint cp; usz off, x; while ((cp = str_to_codepoint(text[off..], &x)) != 0) { off += x; Glyph* gp; if (!ascii::is_cntrl((char)cp)) { gp = ctx.font.get_glyph(cp)!; Rect gb = { .x = orig.x + line_len + gp.ox, .y = orig.y + gp.oy + baseline, .w = gp.w, .h = gp.h, }; Rect gt = { .x = gp.u, .y = gp.v, .w = gp.w, .h = gp.h, }; // push the sprite only if it collides with the bounds if (gb.collides(bounds)) { ctx.push_sprite(gb, gt, texture_id)!; } line_len += gp.adv; } else if (cp == '\n'){ orig.y += line_height + line_gap; line_len = 0; } else { continue; } } // FIXME: we never get here if an error was thrown before ctx.push_scissor(Rect{})!; } fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas) { Cmd up = { .type = CMD_UPDATE_ATLAS, .update_atlas = { .id = atlas.id, .raw_buffer = atlas.buffer, .width = atlas.width, .height = atlas.height, .bpp = (ushort)atlas.type.bpp(), }, }; ctx.cmd_queue.enqueue(&up)!; } fn void! Ctx.push_scissor(&ctx, Rect rect) { Cmd sc = { .type = CMD_SCISSOR, .scissor.rect = rect.intersection(ctx.div_scissor), }; ctx.cmd_queue.enqueue(&sc)!; }