From 9f8e4aff59523b2aa3246394b5679171a2f4824d Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 13 Sep 2025 15:51:05 +1000 Subject: [PATCH] add text line widget, add text wrapping --- src/dlib | 2 +- src/editor/buffer.d | 34 ++++-- src/editor/editor.d | 230 ++++++++++++++------------------------ src/editor/ui.d | 259 +++++++++++++++++++++++++++++++++---------- src/editor/widgets.d | 40 +++++-- 5 files changed, 342 insertions(+), 223 deletions(-) diff --git a/src/dlib b/src/dlib index f30edc3..29a98de 160000 --- a/src/dlib +++ b/src/dlib @@ -1 +1 @@ -Subproject commit f30edc3bbe9fdfda5eb429a214fdf050a0012af0 +Subproject commit 29a98de0e05cb04001b42a55b1ae094ab42f2ab1 diff --git a/src/editor/buffer.d b/src/editor/buffer.d index 8cad8fa..0c753b9 100644 --- a/src/editor/buffer.d +++ b/src/editor/buffer.d @@ -4,6 +4,7 @@ import dlib.alloc; import dlib.util; import core.stdc.stdio : EOF; import parsing; +import std.format : sformat; struct FlatBuffer { @@ -25,7 +26,6 @@ struct LineBuffers { Arena arena; u8[][] lines; - u32[] lengths; } FlatBuffer @@ -108,15 +108,15 @@ Insert(FlatBuffer* buffer, u8[] insert, u64 length, Range pos) // TODO: handle case for when lines are longer than line buffer void -GetLines(FlatBuffer* buffer, LineBuffers* linebufs, u64 start_line, u64 length) +GetLines(FlatBuffer* buffer, LineBuffers* linebufs, u64 start_line, u64 length, bool add_hash = false) { - assert(start_line < buffer.line_count, "GetLines failure: start is not less than line_count"); assert(linebufs != null, "GetLines failure: linebufs is null"); + length = length > buffer.line_count ? buffer.line_count : length; Reset(&linebufs.arena); linebufs.lines = AllocArray!(u8[])(&linebufs.arena, length); - linebufs.lengths = AllocArray!(u32)(&linebufs.arena, length); + u64 extra_buffer = add_hash ? 10 : 0; i64 start = -1; u64 line = 0; u64 current_line = 0; @@ -136,9 +136,8 @@ GetLines(FlatBuffer* buffer, LineBuffers* linebufs, u64 start_line, u64 length) if (start < 0 && new_line) { - linebufs.lines[current_line] = AllocArray!(u8)(&linebufs.arena, 1); + linebufs.lines[current_line] = AllocArray!(u8)(&linebufs.arena, 1 + extra_buffer); linebufs.lines[current_line][0] = '\n'; - linebufs.lengths[current_line] = 1; current_line += 1; continue; } @@ -151,9 +150,8 @@ GetLines(FlatBuffer* buffer, LineBuffers* linebufs, u64 start_line, u64 length) if (new_line) { - linebufs.lines[current_line] = AllocArray!(u8)(&linebufs.arena, i-start); - linebufs.lines[current_line][0 .. $] = buffer.data[start .. i]; - linebufs.lengths[current_line] = cast(u32)(i-start); + linebufs.lines[current_line] = AllocArray!(u8)(&linebufs.arena, (i-start) + extra_buffer); + linebufs.lines[current_line][0 .. i-start] = buffer.data[start .. i]; current_line += 1; start = -1; continue; @@ -161,9 +159,21 @@ GetLines(FlatBuffer* buffer, LineBuffers* linebufs, u64 start_line, u64 length) if (i == buffer.length-1) { - linebufs.lines[current_line] = AllocArray!(u8)(&linebufs.arena, buffer.length-start); - linebufs.lines[current_line][0 .. $] = buffer.data[start .. buffer.length]; - linebufs.lengths[current_line] = cast(u32)(buffer.length-start); + linebufs.lines[current_line] = AllocArray!(u8)(&linebufs.arena, (buffer.length-start) + extra_buffer); + linebufs.lines[current_line][0 .. buffer.length-start] = buffer.data[start .. buffer.length]; + } + } + + if (add_hash) + { + for (u64 i = 0; i < linebufs.lines.length; i += 1) + { + if (linebufs.lines[i].length > 0) + { + u8[10] buf = 0; + (cast(char[])buf).sformat("##%08s", i); + linebufs.lines[i][linebufs.lines[i].length - extra_buffer .. $] = buf[0 .. $]; + } } } } diff --git a/src/editor/editor.d b/src/editor/editor.d index 95e02fb..f68faff 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -29,16 +29,16 @@ struct Editor Arena temp_arena; PlatformWindow* window; - Renderer rd; - ImageView font_atlas; - Pipeline pipeline; - DescSetLayout desc_set_layout; - DescSet desc_set; - PipelineLayout pipeline_layout; - PushConst pc; + Renderer rd; + ImageView font_atlas; + Pipeline pipeline; + DescSetLayout desc_set_layout; + DescSet desc_set; + PipelineLayout pipeline_layout; + PushConst pc; - FlatBuffer[] buffers; - Tokenizer[] tokenizers; + FlatBuffer[] buffers; + Tokenizer[] tokenizers; u8[][] buffer_names; u32 buffer_count; FlatBuffer* active_buffer; @@ -48,7 +48,7 @@ struct Editor FontFace font; FontAtlasBuf atlas_buf; - UVec2 res; + Vec2 res; } struct PushConst @@ -70,6 +70,82 @@ struct Vertex u32 texture; } +void +Cycle(Editor* ed, Inputs* inputs) +{ + Reset(&ed.temp_arena); + ResetScratch(MB(4)); + + BeginBuild(inputs); + + static UIPanel panel = { + id: CastStr!(u8)("##main_panel"), + pct: 1.0, + axis: A2D.X, + color: Vec4(0.2, 0.4, 0.8, 1.0), + }; + + static UIPanel panel_l = { + id: CastStr!(u8)("##panel_l"), + pct: 0.5, + axis: A2D.Y, + color: Vec4(0.2, 0.4, 0.8, 1.0), + }; + + static UIPanel panel_r = { + id: CastStr!(u8)("##panel_r"), + pct: 0.5, + axis: A2D.Y, + color: Vec4(0.4, 0.3, 0.7, 1.0), + }; + + BeginFrame(&ed.rd); + + Vec2 ext = GetExtent(&ed.rd); + if (ext != ed.res) + { + ed.res = ext; + Ortho(&ed.pc.projection, 0.0, 0.0, ext.x, ext.y, 1000.0, 0.1); + } + + u32 rows = cast(u32)(ext.y / ed.atlas_buf.atlas.size) + 5; + Panel(&panel); + { + Panel(&panel_l); + { + if (ed.active_buffer != null) + { + GetLines(&ed.buffers[0], &ed.linebufs, 0, rows); + + for(u64 i = 0; i < ed.linebufs.lines.length; i += 1) + { + TextLine(ed.linebufs.lines[i], ed.atlas_buf.atlas.size, i); + } + } + } + EndPanel(); + + Panel(&panel_r); + { + + } + EndPanel(); + } + EndPanel(); + + BeginRendering(&ed.rd); + + PushConstants(&ed.rd, ed.pipeline, &ed.pc); + + Bind(&ed.rd, ed.pipeline, ed.desc_set); + + EndBuild(); + + FinishRendering(&ed.rd); + + SubmitAndPresent(&ed.rd); +} + Editor CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name) { @@ -175,140 +251,6 @@ CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name) return editor; } -void -Cycle(Editor* ed, Inputs* inputs) -{ - Reset(&ed.temp_arena); - - BeginBuild(inputs); - - static UIPanel[11] panels = [ - { - id: CastStr!(u8)("##panel_1"), - pct: 0.3, - axis: A2D.X, - color: Vec4(0.2, 0.5, 0.8, 1.0), - }, - { - id: CastStr!(u8)("##sub_panel_1"), - pct: 0.125, - axis: A2D.X, - color: Vec4(0.2, 0.4, 0.5, 1.0), - }, - { - id: CastStr!(u8)("##sub_panel_2"), - pct: 0.675, - axis: A2D.X, - color: Vec4(0.9, 0.6, 0.5, 1.0), - }, - { - id: CastStr!(u8)("##sub_panel_3"), - pct: 0.2, - axis: A2D.X, - color: Vec4(1.0, 0.4, 0.5, 1.0), - }, - { - id: CastStr!(u8)("##panel_2"), - pct: 0.3, - axis: A2D.X, - color: Vec4(0.5, 0.2, 0.45, 1.0), - }, - { - id: CastStr!(u8)("##panel_3"), - pct: 0.4, - axis: A2D.X, - color: Vec4(0.3, 0.7, 0.6, 1.0), - }, - { - id: CastStr!(u8)("##sub_panel_4"), - pct: 0.25, - axis: A2D.Y, - color: Vec4(0.33, 0.4, 0.8, 1.0), - }, - { - id: CastStr!(u8)("##sub_sub_panel_1"), - pct: 0.4, - axis: A2D.X, - color: Vec4(1.0, 0.0, 0.0, 1.0), - }, - { - id: CastStr!(u8)("##sub_sub_panel_2"), - pct: 0.6, - axis: A2D.X, - color: Vec4(1.0, 1.0, 0.0, 1.0), - }, - { - id: CastStr!(u8)("##sub_panel_5"), - pct: 0.55, - axis: A2D.X, - color: Vec4(0.9, 0.2, 0.3, 1.0), - }, - { - id: CastStr!(u8)("##sub_panel_6"), - pct: 0.2, - axis: A2D.X, - color: Vec4(0.2, 0.76, 0.5, 1.0), - }, - ]; - - Panel(&panels[0]); - { - Panel(&panels[1]); - EndPanel(); - - Panel(&panels[2]); - EndPanel(); - - Panel(&panels[3]); - EndPanel(); - } - EndPanel(); - - Panel(&panels[4]); - EndPanel(); - - Panel(&panels[5]); - { - Panel(&panels[6]); - { - Panel(&panels[7]); - EndPanel(); - - Panel(&panels[8]); - EndPanel(); - } - EndPanel(); - - Panel(&panels[9]); - EndPanel(); - - Panel(&panels[10]); - EndPanel(); - } - EndPanel(); - - BeginFrame(&ed.rd); - - UVec2 ext = UVec2(GetExtent(&ed.rd)); - if (ext != ed.res) - { - ed.res = ext; - Ortho(&ed.pc.projection, 0.0, 0.0, cast(f32)(ext.x), cast(f32)(ext.y), 1000.0, 0.1); - } - - BeginRendering(&ed.rd); - - PushConstants(&ed.rd, ed.pipeline, &ed.pc); - - Bind(&ed.rd, ed.pipeline, ed.desc_set); - - EndBuild(); - - FinishRendering(&ed.rd); - - SubmitAndPresent(&ed.rd); -} - void DrawText(Editor* ed, f32 x, f32 y, f32 px, string str) { diff --git a/src/editor/ui.d b/src/editor/ui.d index ab04b8e..eb80b2c 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -7,6 +7,9 @@ import dlib.fonts; import vulkan; import widgets; import std.stdio; +import std.math.rounding : ceil; +import std.format : sformat; +import core.stdc.string : memset; import editor; @@ -31,8 +34,9 @@ enum UIFlags { None = 0x00, DrawBackground = 0x01, - Clickable = 0x02, - Draggable = 0x04, + DrawText = 0x02, + Clickable = 0x04, + Draggable = 0x08, } alias UIF = UIFlags; @@ -60,8 +64,10 @@ struct UICtx Arena arena; Renderer* rd; Inputs* inputs; + u64 frame; + u64 f_idx; - UIBuffer buffer; + UIBuffer[FRAME_OVERLAP] buffers; UIItem* root; UIItemNode* top_parent; @@ -77,6 +83,8 @@ struct UICtx Vec4 text_color; Axis2D layout_axis; Vec2 adjustment; + u32 tab_width; + f32 text_size; } struct UIItemStackList @@ -125,8 +133,8 @@ struct UISize struct UIBuffer { - MappedBuffer!(Vertex) mapped_vtx; - MappedBuffer!(u32) mapped_idx; + MappedBuffer!(Vertex) m_vtx; + MappedBuffer!(u32) m_idx; Vertex[] vtx; u32[] idx; u32 count; @@ -166,9 +174,17 @@ InitUICtx(Renderer* rd, FontAtlas atlas) g_UI_NIL_NODE = cast(UIItemNode*)&g_ui_nil_item_node; Arena arena = CreateArena(MB(4)); + + UIBuffer[FRAME_OVERLAP] buffers; - MappedBuffer!(Vertex) m_vtx = CreateMappedBuffer!(Vertex)(rd, BT.Vertex, 5000); - MappedBuffer!(u32) m_idx = CreateMappedBuffer!(u32)(rd, BT.Index, 15000); + u64 vertex_size = 10000; + for(u64 i = 0; i < FRAME_OVERLAP; i += 1) + { + buffers[i].m_vtx = CreateMappedBuffer!(Vertex)(rd, BT.Vertex, vertex_size); + buffers[i].m_idx = CreateMappedBuffer!(u32)(rd, BT.Index, cast(u64)(ceil(vertex_size * 1.5))); + buffers[i].vtx = buffers[i].m_vtx.data; + buffers[i].idx = buffers[i].m_idx.data; + } UICtx ctx = { rd: rd, @@ -179,12 +195,9 @@ InitUICtx(Renderer* rd, FontAtlas atlas) top_parent: g_UI_NIL_NODE, prev_sibling: g_UI_NIL_NODE, drag_item: g_UI_NIL, - buffer: { - mapped_vtx: m_vtx, - mapped_idx: m_idx, - vtx: m_vtx.data, - idx: m_idx.data, - }, + text_size: 14.0, + tab_width: 2, + buffers: buffers, }; g_ui_ctx = ctx; @@ -361,13 +374,25 @@ SetLayoutAxis(Axis2D axis) ctx.layout_axis = axis; } +void +SetTextSize(f32 size) +{ + g_ui_ctx.text_size = size; +} + void BeginBuild(Inputs* inputs) { UICtx* ctx = GetCtx(); + ctx.f_idx = ctx.frame%FRAME_OVERLAP; + ctx.inputs = inputs; - ctx.buffer.count = 0; + + memset(ctx.buffers[ctx.f_idx].vtx.ptr, 0, Vertex.sizeof * ctx.buffers[ctx.f_idx].count); + memset(ctx.buffers[ctx.f_idx].idx.ptr, 0, u32.sizeof * ctx.buffers[ctx.f_idx].count); + ctx.buffers[ctx.f_idx].count = 0; + ctx.panel_level = 0; ctx.root = Root(); @@ -400,8 +425,10 @@ EndBuild() first = true; } - BindBuffers(g_ui_ctx.rd, &g_ui_ctx.buffer.mapped_idx, &g_ui_ctx.buffer.mapped_vtx); - DrawIndexed(g_ui_ctx.rd, 6, g_ui_ctx.buffer.count, 0); + BindBuffers(ctx.rd, &ctx.buffers[ctx.f_idx].m_idx, &ctx.buffers[ctx.f_idx].m_vtx); + DrawIndexed(ctx.rd, 6, ctx.buffers[ctx.f_idx].count, 0); + + ctx.frame += 1; } void @@ -473,7 +500,15 @@ DrawUI(UICtx* ctx, UIItem* item) { if (!Nil(item)) { - DrawRect(ctx, item); + if (item.flags & UIF.DrawBackground) + { + DrawRect(ctx, item); + } + + if (item.flags & UIF.DrawText) + { + DrawLine(item); + } DrawUI(ctx, item.first); DrawUI(ctx, item.next); @@ -567,20 +602,33 @@ RootSize() return size; } + +UIKey +MakeKey(u8[] text, u8[] hash) +{ + UIKey key; + + key.text = text; + key.hash = Hash(text, hash); + + return key; +} + UIKey MakeKey(u8[] id) { UIKey key; - u32 pos = 0; + i64 pos = 0; u32 hash_count = 0; - for(u32 i = 0; i < id.length; i += 1) + for(i64 i = id.length-1; i >= 0; i -= 1) { if (hash_count == 2) { if (id[i] == '#') { hash_count += 1; + pos = i; } break; @@ -588,11 +636,7 @@ MakeKey(u8[] id) if (id[i] == '#') { - if (hash_count == 0) - { - pos = i; - } - + pos = i; hash_count += 1; } } @@ -616,6 +660,21 @@ MakeKey(u8[] id) return key; } +pragma(inline) UIItem* +Get(UIKey key) +{ + Result!(UIItem*) result = g_ui_ctx.items[key.hash]; + if (!result.ok) + { + result.value = Alloc!(UIItem)(&g_ui_ctx.arena); + Push(&g_ui_ctx.items, key.hash, result.value); + } + + result.value.key = key; + + return result.value; +} + UIItem* Get(string id) { @@ -627,48 +686,132 @@ UIItem* Get(u8[] id) { UIKey key = MakeKey(id); - - Result!(UIItem*) result = g_ui_ctx.items[key.hash]; - if (!result.ok) - { - result.value = Alloc!(UIItem)(&g_ui_ctx.arena); - Push(&g_ui_ctx.items, key.hash, result.value); - result.value.key = key; - } - - return result.value; + return Get(key); } f32 CalcTextWidth(u8[] str) { - u32 tab_width = 2; //g_ui_ctx.tab_width; + u32 tab_width = g_ui_ctx.tab_width; Glyph* space = g_ui_ctx.atlas.glyphs.ptr + ' '; - f32 width; + f32 width = 0.0; for(u64 i = 0; i < str.length; i += 1) { - Glyph* g = g_ui_ctx.atlas.glyphs.ptr + str.ptr[i]; + width += GlyphWidth(g_ui_ctx.atlas.glyphs.ptr + str.ptr[i]); + } - if (g.ch == '\t') + return width; +} + +pragma(inline) f32 +GlyphWidth(Glyph* g) +{ + f32 width = 0.0; + if (g.ch == '\t') + { + width += g_ui_ctx.atlas.glyphs[' '].advance * cast(f32)(g_ui_ctx.tab_width); + } + else + { + width += g.advance; + } + + return width; +} + +Node!(u8[])* +MakeMultiline(u8[] text, f32 width, u64 line_no) +{ + f32 scaled_width = width * (g_ui_ctx.atlas.size/g_ui_ctx.text_size); + f32 text_width = CalcTextWidth(text); + + u64 line_count = cast(u64)(ceil(text_width/scaled_width)); + Node!(u8[])* node = null; + if (line_count > 0) + { + f32 w = 0.0; + u64 line = 0; + u64 start = 0; + const u64 extra_buf = 10; + for(u64 i = 0; i < text.length; i += 1) { - width += space.advance * cast(f32)(tab_width); - } - else - { - width += g.advance; + f32 ch_w = GlyphWidth(g_ui_ctx.atlas.glyphs.ptr + text[i]); + + if (ch_w + w > scaled_width || i == text.length-1) + { + u64 len = i-start+1; + u8[10] buf = 0; + (cast(char[])buf).sformat("##%04s%04s", line_no, line); + + u8[] str = ScratchAlloc!(u8)(len+extra_buf); + str[0 .. len] = text[start .. start+len]; + str[len .. len+extra_buf] = buf[0 .. $]; + + Node!(u8[])* n = node; + for(;;) + { + if (node == null) + { + node = ScratchAlloc!(Node!(u8[]))(); + node.value = str; + node.next = null; + break; + } + + if (n.next == null) + { + n.next = ScratchAlloc!(Node!(u8[]))(); + n.next.value = str; + n.next.next = null; + break; + } + + n = n.next; + } + + line += 1; + start = i; + w = 0.0; + } + + w += ch_w; } } - return width; // * g_ui_ctx.text_scale; + return node; +} + + +void +DrawLine(UIItem* item) +{ + UICtx* ctx = GetCtx(); + f32 y = item.rect.y0 + ctx.text_size; + f32 x = item.rect.x0; + FontAtlas* atlas = &ctx.atlas; + + for(u64 i = 0; i < item.key.text.length && item.key.text[i] != '\0'; i += 1) + { + u8 ch = item.key.text.ptr[i]; + if (ch < 128) + { + DrawGlyph(&atlas.glyphs[ch], atlas.size/ctx.text_size, &x, y); + } + } } pragma(inline) void DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col = Vec4(1.0)) { - if (glyph.atlas_left != glyph.atlas_right) + if (glyph.ch == '\t') { - Vertex* v = g_ui_ctx.buffer.vtx.ptr + g_ui_ctx.buffer.count; + *x_pos += glyph.advance * (GetCtx().tab_width - 1); + } + else if (glyph.atlas_left != glyph.atlas_right && glyph.ch != '\n') + { + UICtx* ctx = GetCtx(); + Vertex* v = ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count; f32 r = glyph.plane_right * scale; f32 l = glyph.plane_left * scale; @@ -693,7 +836,7 @@ DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col = Vec4(1.0)) v.texture = 1; - AddUIIndices(); + AddUIIndices(ctx); } *x_pos += glyph.advance * scale; @@ -703,7 +846,7 @@ pragma(inline) void DrawRect(UICtx* ctx, UIItem* item) { // Y reversed - Vertex* v = ctx.buffer.vtx.ptr + ctx.buffer.count; + Vertex* v = ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count; v.dst_start = item.rect.vec0; v.dst_end = item.rect.vec1; v.cols = item.color; @@ -712,20 +855,20 @@ DrawRect(UICtx* ctx, UIItem* item) v.edge_softness = 0.0; v.raised = 0.0; - AddUIIndices(); + AddUIIndices(ctx); } -void -AddUIIndices() +pragma(inline) void +AddUIIndices(UICtx* ctx) { - g_ui_ctx.buffer.idx[0] = 0; - g_ui_ctx.buffer.idx[1] = 1; - g_ui_ctx.buffer.idx[2] = 2; - g_ui_ctx.buffer.idx[3] = 2; - g_ui_ctx.buffer.idx[4] = 1; - g_ui_ctx.buffer.idx[5] = 3; + ctx.buffers[ctx.f_idx].idx[0] = 0; + ctx.buffers[ctx.f_idx].idx[1] = 1; + ctx.buffers[ctx.f_idx].idx[2] = 2; + ctx.buffers[ctx.f_idx].idx[3] = 2; + ctx.buffers[ctx.f_idx].idx[4] = 1; + ctx.buffers[ctx.f_idx].idx[5] = 3; - g_ui_ctx.buffer.count += 1; + ctx.buffers[ctx.f_idx].count += 1; } bool diff --git a/src/editor/widgets.d b/src/editor/widgets.d index 23fb178..5d40a5a 100644 --- a/src/editor/widgets.d +++ b/src/editor/widgets.d @@ -114,8 +114,8 @@ Panel(UIPanel* panel) parent_end = parent.rect.vec1.y; } - u8[128] buf = 0; - (cast(char[])buf).sformat("sep_%s", cast(char[])panel.id); + u8[] buf = ScratchAlloc!(u8)(panel.id.length + 5); + (cast(char[])buf).sformat("%s_sep", cast(char[])panel.id); separator = Get(buf); @@ -127,12 +127,11 @@ Panel(UIPanel* panel) if (separator.signal & UIS.Dragged && pos != 0.0) { f32 pct = Remap(pos, 0.0, parent_start-parent_end, 0.0, 1.0); - panel.pct -= pct; - panel.prev.pct += pct; - - Logf("%f", pct); - - Logf("%s %f %s %f", cast(char[])panel.id, panel.pct, cast(char[])panel.prev.id, panel.prev.pct); + if (CheckPanelBounds(panel.pct - pct) && CheckPanelBounds(panel.prev.pct + pct)) + { + panel.pct -= pct; + panel.prev.pct += pct; + } } } @@ -151,6 +150,12 @@ Panel(UIPanel* panel) return item; } +bool +CheckPanelBounds(f32 pct) +{ + return pct >= 0.0 && pct <= 1.0; +} + void Separator(UIItem* item, f32 x_size, f32 y_size, Axis2D axis) { @@ -162,6 +167,25 @@ Separator(UIItem* item, f32 x_size, f32 y_size, Axis2D axis) BuildItem(item, UISize(x_t, x_size), UISize(y_t, y_size), UIF.DrawBackground|UIF.Draggable); } +void +TextLine(u8[] text, f32 text_size, u64 line_no) +{ + UIItem* parent = PeekParent(); + + if (!Nil(parent) && parent.size.x > 0.0) + { + SetColor(Vec4(1.0)); + SetTextSize(text_size); + + Node!(u8[])* lines = MakeMultiline(text, parent.size.x, line_no); + for(Node!(u8[])* line = lines; line != null; line = line.next) + { + UIItem* item = Get(line.value); + BuildItem(item, UISize(ST.Percentage, 1.0), UISize(ST.Pixels, text_size), UIF.DrawText); + } + } +} + void EndPanel() {