1343 lines
26 KiB
D

import dlib;
import dlib.util : HTPush = Push;
import vulkan;
import buffer;
import parsing;
import editor;
import std.stdio;
import std.math.traits : isNaN;
import std.math.rounding : ceil;
import std.format : sformat;
import core.stdc.string : memset;
import core.stdc.math : fabsf;
import std.conv;
import core.stdc.stdio : sprintf;
const Vec4[4] DEFAULT_COL = Vec4(0.13, 0.13, 0.13, 1.0);
const Vec4[4] CMD_PALETTE_COL = Vec4(0.21, 0.21, 0.21, 1.0);
const Vec4[4] DEFAULT_BORDER_COL = Vec4(0.254, 0.254, 0.266, 1.0);
const Vec4[4] HL_BORDER_COL = Vec4(0.035, 0.549, 0.824, 1.0);
const Vec4[4] LC_COLOR = Vec4(0.12, 0.12, 0.12, 1.0);
const Vec4[4] LC_HL_COLOR = Vec4(0.012, 0.176, 0.29, 1.0);
const Vec4[4] CMD_PALETTE_INPUT_HL = Vec4(0.24, 0.45, 0.81, 1.0);
const Vec4[4] CMD_INPUT_BORDER_COL = Vec4(0.003, 0.48, 0.68, 1.0);
const Vec4[4] CMD_INPUT_BORDER_HL = Vec4(0.05, 0.56, 0.76, 1.0);
const Vec4[4] CMD_PALETTE_INPUT_COL = [
Vec4(0.14, 0.14, 0.14, 1.0),
Vec4(0.14, 0.14, 0.14, 1.0),
Vec4(0.17, 0.17, 0.17, 1.0),
Vec4(0.17, 0.17, 0.17, 1.0),
];
const u64 VERTEX_MAX_COUNT = 10000;
// TODO: add setting
const f32 TEXT_SIZE = 16.0;
const UIPanel g_ui_nil_panel;
UIPanel* g_UI_NIL_PANEL;
UIPanel g_root_panel;
const u8[] FONT_BYTES = import("pc-9800.ttf");
const u8[] VERTEX_BYTES = import("gui.vert.spv");
const u8[] FRAGMENT_BYTES = import("gui.frag.spv");
const UI_COUNT = 5000;
UICtx g_ui_ctx;
const UIItem g_ui_nil_item;
UIItem* g_UI_NIL;
const UIItemNode g_ui_nil_item_node;
UIItemNode* g_UI_NIL_NODE;
enum Axis2D
{
X,
Y,
Max
}
alias A2D = Axis2D;
enum UIFlags
{
None = 0x0000,
DrawBackground = 0x0001,
DrawText = 0x0002,
DrawBorder = 0x0004,
Clickable = 0x0008,
Draggable = 0x0010,
DragBorder = 0x0020,
ClickBorder = 0x0040,
BorderX0 = 0x0080,
BorderX1 = 0x0100,
BorderY0 = 0x0200,
BorderY1 = 0x0400,
TextInput = 0x0800,
Window = 0x1000,
DeferredBorder = 0x2000,
}
alias UIF = UIFlags;
enum UISignal
{
None = 0x00,
Clicked = 0x01,
Dragged = 0x02,
ClickedBorder = 0x04,
DraggedBorder = 0x08,
BorderX0 = 0x080,
BorderX1 = 0x100,
BorderY0 = 0x200,
BorderY1 = 0x400,
}
alias UIS = UISignal;
enum SizeType
{
Pixels,
Percentage,
FitChild,
}
alias ST = SizeType;
struct UICtx
{
HashTable!(UIHash, UIItem*) items;
Arena arena;
Arena temp_arena;
Inputs* inputs;
u64 frame;
u64 f_idx;
PlatformWindow* window;
Renderer rd;
ImageView font_atlas;
Pipeline pipeline;
DescSetLayout desc_set_layout;
DescSet desc_set;
PipelineLayout pipeline_layout;
PushConst pc;
Vec2 res;
u8[] font_data;
FontFace font;
FontAtlasBuf atlas_buf;
UIBuffer[FRAME_OVERLAP] buffers;
u32 tab_width;
f32 text_size;
UIItem* drag_item;
UIPanel* parent;
UIPanel* focused_panel;
debug bool dbg;
}
struct UIStack(T)
{
UIStack!(T)* next;
T value;
}
struct UIItemStackList
{
UIItemNode* first;
UIItemNode* last;
}
struct UIItemNode
{
UIItem* item;
UIItemNode* next;
}
struct UIItem
{
UIKey key;
u64 last_frame;
Vec2 dragged;
}
struct UISize
{
SizeType type;
f32 value;
}
struct UIBuffer
{
MappedBuffer!(Vertex) m_vtx;
MappedBuffer!(u32) m_idx;
Vertex[] vtx;
u32[] idx;
u32 count;
}
struct PushConst
{
Mat4 projection;
}
struct GlyphBounds
{
f32 r = 0.0;
f32 l = 0.0;
f32 t = 0.0;
f32 b = 0.0;
f32 w = 0.0;
f32 h = 0.0;
f32 atlas_r = 0.0;
f32 atlas_l = 0.0;
f32 atlas_t = 0.0;
f32 atlas_b = 0.0;
}
struct Vertex
{
Vec4[4] cols;
Vec2 dst_start;
Vec2 dst_end;
Vec2 src_start;
Vec2 src_end;
f32 border_thickness;
f32 corner_radius;
f32 edge_softness;
f32 raised;
f32 z_index;
u32 texture;
}
union Rect
{
Vec2[2] v;
struct
{
Vec2 vec0;
Vec2 vec1;
};
struct
{
f32 x0;
f32 y0;
f32 x1;
f32 y1;
};
}
alias UIHash = u64;
alias UIPair = KVPair!(UIHash, UIItem*);
struct UIKey
{
u8[] text;
u8[] hash_text;
u64 hash;
}
struct UIPanel
{
AnimState anim;
u8[] id;
Editor* ed;
UIPanel* parent;
UIPanel* next;
UIPanel* prev;
UIPanel* first;
UIPanel* last;
UIPanel* list_next;
Axis2D axis;
f32 pct;
Vec4 color;
Rect rect;
Vec2 size;
}
struct AnimState
{
f32 start_value;
f32 end_value;
f32 start_time;
f32 remaining_time;
}
struct TextPart
{
UIItem* item;
TextPart* next;
u32 count;
}
void
BeginUI(EditorCtx* edctx, Inputs* inputs)
{
UICtx* ctx = GetCtx();
Reset(&ctx.temp_arena);
ctx.f_idx = ctx.frame%FRAME_OVERLAP;
ctx.inputs = inputs;
PrepRendering(ctx);
SetPanelSizes(edctx.base_panel);
}
void
EndUI()
{
UICtx* ctx = GetCtx();
with(ctx)
{
BindBuffers(&rd, &buffers[f_idx].m_idx, &buffers[f_idx].m_vtx);
DrawIndexed(&rd, 6, buffers[f_idx].count, 0);
}
CompleteRendering(ctx);
ctx.frame += 1;
}
void
SetPanelSizes(UIPanel* panel)
{
UICtx* ctx = GetCtx();
panel.size.x = ctx.res.x;
panel.size.y = ctx.res.y;
panel.rect.vec0 = 0.0;
panel.rect.vec1 = 0.0;
static foreach(axis; A2D.min .. A2D.max)
{
for(UIPanel* p = panel; !Nil(p); p = Recurse(p))
{
f32 pos = p.rect.vec0.v[axis];
for(UIPanel* c = p.first; !Nil(c); c = c.next)
{
c.size.v[axis] = p.axis == axis ? c.pct * p.size.v[axis] : p.size.v[axis];
c.rect.vec0.v[axis] = pos;
c.rect.vec1.v[axis] = pos + c.size.v[axis];
if(axis == p.axis)
{
pos += c.size.v[axis];
}
}
}
}
}
pragma(inline) void
DrawChar(FontAtlas* atlas, u8 ch, f32* x_pos, f32 y, Vec4 col = Vec4(1.0))
{
if(ch < atlas.glyphs.length)
{
Glyph* g = atlas.glyphs.ptr + ch;
DrawGlyph(g, 1.0, x_pos, y, col);
}
}
bool
PrintInputs()
{
bool result;
UICtx* ctx = GetCtx();
for(auto n = ctx.inputs.list.first; n != null; n = n.next)
{
Logf("key %s %s %s", n.value.key, n.value.x, n.value.y);
result = true;
}
return result;
}
void
Panel(UIPanel* panel)
{
UICtx* ctx = GetCtx();
UIItem* item = Get(panel.id);
Editor* ed = panel.ed;
bool focused = GetFocusedPanel() == panel;
UIPanel* parent = panel.parent;
UIPanel* prev = panel.prev;
Axis2D pax = parent.axis;
Vec2 adj = Vec2(
parent.axis == A2D.X ? 10 : 0,
parent.axis == A2D.Y ? 10 : 0
);
Vec2 p0 = panel.rect.vec0+adj;
Vec2 p1 = panel.rect.vec1-adj;
if(!Nil(prev)) with(panel)
{
Vec2 d0 = rect.vec0-adj;
Vec2 d1 = Vec2(
pax == A2D.X ? rect.vec0.x+adj.x : rect.vec1.x,
pax == A2D.Y ? rect.vec0.y+adj.y : rect.vec1.y
);
if(Dragged(item, d0, d1) && item.dragged.v[pax] != 0.0)
{
f32 mov_pct = Remap(item.dragged.v[pax], 0.0, panel.parent.size.v[pax], 0.0, 1.0);
if(CheckPanelBounds(pct + mov_pct) && CheckPanelBounds(panel.prev.pct - mov_pct))
{
pct += mov_pct;
panel.prev.pct -= mov_pct;
}
}
}
if(panel.ed != null)
{
if(Clicked(item, p0, p1))
{
SetFocusedPanel(panel);
}
i64 rows = cast(i64)ceil(panel.size.y/TEXT_SIZE);
GetLines(&ed.buf, &ed.linebufs, rows);
char[64] ch_buf = '\0';
char[] fmt = ch_buf.sformat("%%0%ss", u64(ed.linebufs.end.toChars().length));
u8[] end_line_text = ScratchAlloc!(u8)(ed.linebufs.end.toChars().length, '0');
f32 lcw = CalcTextWidth(end_line_text);
f32 padding = 4.0;
f32 border = 2.0, radius = 2.0, softness = 0.1;
DrawRect(panel.rect.vec0, panel.size, radius, border, DEFAULT_COL);
DrawRect(panel.rect.vec0, Vec2(lcw+padding*2, panel.size.y), 0.0, 0.0, focused ? LC_HL_COLOR : LC_COLOR);
DrawBorder(panel.rect.vec0, panel.size, border, radius, softness, focused ? HL_BORDER_COL : DEFAULT_BORDER_COL);
f32 x = panel.rect.x0;
f32 y = panel.rect.y0 + TEXT_SIZE;
f32 code_view_width = panel.size.x-lcw-border*2.0-padding*2.0;
FontAtlas* atlas = &ctx.atlas_buf.atlas;
bool edit = EditModeActive();
U64Vec2 pos = VecPos(&ed.buf);
u64 i;
for(auto buf = ed.linebufs.first; buf != null; buf = buf.next, i += 1)
{
f32 x_pos = x + padding;
DrawLineCount(atlas, fmt, &x_pos, y, ed.linebufs.start+i);
x_pos += padding;
u64 ch_offset;
auto parts = MakeMultiline(buf.text, code_view_width, buf.style);
for(auto n = parts; n != null; n = n.next)
{
auto l = &n.value;
if(pos.y == ed.buf.offset+i)
{
DrawCursor(atlas, l.text, pos.x, x+lcw+padding*2.0, y, ch_offset, edit);
}
foreach(j; 0 .. l.text.length)
{
bool hl = pos.y == i && !edit && j == pos.x-ch_offset;
DrawChar(atlas, l.text[j], &x_pos, y, hl ? Vec4(Vec3(0.0), 1.0) : SYNTAX_COLORS[l.style[j]]);
}
y += TEXT_SIZE;
x_pos = x + lcw + padding*2.0;
ch_offset += l.text.length;
}
}
}
}
pragma(inline) void
DrawCursor(FontAtlas* atlas, u8[] text, u64 ch_x, f32 x, f32 y, u64 offset, bool edit)
{
Glyph* g = GetGlyph(' ');
foreach(j; 0 .. text.length)
{
bool hl = j == ch_x-offset;
if(hl)
{
break;
}
g = j == text.length-1 ? GetGlyph(' ') : GetGlyph(text[j]);
x += GlyphWidth(g);
}
DrawRect(x, y, atlas, cast(u8)g.ch, Vec4(1.0), edit);
}
pragma(inline) void
DrawLineCount(FontAtlas* atlas, char[] fmt, f32* x_pos, f32 y, u64 line)
{
char[32] line_buf = '\0';
char[] line_str = sformat(line_buf, fmt, line);
foreach(j; 0 .. line_str.length)
{
DrawChar(atlas, line_str[j], x_pos, y, Vec4(1.0));
}
}
void
CommandPalette(CmdPalette* cmd)
{
UICtx* ctx = GetCtx();
u8[] text = cmd.buffer[0 .. cmd.icount];
u8[][] options = cmd.opt_strs;
Vec2 size = RootSize();
f32 x = size.x*0.15;
f32 y = size.y*0.1;
f32 w = size.x*0.7;
f32 h = 40.0;
f32 corner_r = 2.0, edge_softness = 0.1, border_thickness = 2.0;
DrawBorderedRect(Vec2(x, y), Vec2(w, h), border_thickness, corner_r, edge_softness, CMD_PALETTE_INPUT_COL, CMD_INPUT_BORDER_COL);
f32 y_off = h*0.5 + 6;
f32 ch_x = x + 6.0;
f32 ch_y = y + y_off;
foreach(i; 0 .. text.length)
{
DrawChar(&ctx.atlas_buf.atlas, text[i], &ch_x, ch_y, Vec4(1.0));
}
for(u64 i = 0; i < options.length; i += 1)
{
y += h;
ch_x = x + 6.0;
ch_y = y + y_off;
DrawBorderedRect(
Vec2(x, y),
Vec2(w, h),
border_thickness,
corner_r,
edge_softness,
cmd.selected == i ? CMD_PALETTE_INPUT_HL : CMD_PALETTE_INPUT_COL,
cmd.selected == i ? CMD_INPUT_BORDER_HL : CMD_INPUT_BORDER_COL
);
foreach(j; 0 .. options[i].length)
{
DrawChar(&ctx.atlas_buf.atlas, options[i][j], &ch_x, ch_y, Vec4(1.0));
}
if(y+h > size.y+h) break;
}
}
UIPanel*
Recurse(UIPanel* panel)
{
UIPanel* result = g_UI_NIL_PANEL;
if(!Nil(panel.first))
{
result = panel.first;
}
else if(!Nil(panel.next))
{
result = panel.next;
}
else for(UIPanel* p = panel.parent; !Nil(p); p = p.parent)
{
if(!Nil(p.next))
{
result = p.next;
break;
}
}
return result;
}
bool
CheckPanelBounds(f32 pct)
{
return pct >= 0.0 && pct <= 1.0;
}
void
PushPanel(UIPanel* parent, UIPanel* panel)
{
DLLPush(parent, panel, g_UI_NIL_PANEL);
panel.parent = parent;
}
void
InsertPanel(UIPanel* parent, UIPanel* prev, UIPanel* panel)
{
DLLInsert(parent, panel, prev, g_UI_NIL_PANEL);
panel.parent = prev.parent;
}
void
InitWidgets()
{
g_UI_NIL_PANEL = cast(UIPanel*)&g_ui_nil_panel;
g_ui_ctx.parent = g_UI_NIL_PANEL;
}
void
SetFocusedPanel(UIPanel* panel)
{
if(!CheckNil(g_UI_NIL_PANEL, panel))
{
Logf("set focus");
g_ui_ctx.focused_panel = panel;
}
}
UIPanel*
GetFocusedPanel()
{
return Nil(g_ui_ctx.focused_panel) ? g_UI_NIL_PANEL : g_ui_ctx.focused_panel;
}
bool
Nil(UIPanel* panel)
{
return panel == null || panel == g_UI_NIL_PANEL;
}
void
InitUICtx(PlatformWindow* window)
{
version(linux)
{
PlatformHandles handles = {
window: window.window,
conn: window.conn,
};
}
version(Windows)
{
}
g_UI_NIL = cast(UIItem*)&g_ui_nil_item;
g_UI_NIL_NODE = cast(UIItemNode*)&g_ui_nil_item_node;
Arena arena = CreateArena(MB(4));
UIBuffer[FRAME_OVERLAP] buffers;
FontFace font = OpenFont(cast(u8[])FONT_BYTES);
FontAtlasBuf atlas_buf = CreateAtlas(&arena, font, 16.0, 256);
Vec4 b = Vec4(Vec3(0.0), 1.0);
UICtx ctx = {
rd: InitRenderer(handles, MB(16), MB(8)),
items: CreateHashTable!(UIHash, UIItem*)(12),
arena: arena,
temp_arena: CreateArena(MB(1)),
drag_item: g_UI_NIL,
atlas_buf: atlas_buf,
font: font,
font_data: cast(u8[])FONT_BYTES,
text_size: 16.0,
tab_width: 2,
};
for(u64 i = 0; i < FRAME_OVERLAP; i += 1)
{
ctx.buffers[i].m_vtx = CreateMappedBuffer!(Vertex)(&ctx.rd, BT.Vertex, VERTEX_MAX_COUNT);
ctx.buffers[i].m_idx = CreateMappedBuffer!(u32)(&ctx.rd, BT.Index, cast(u64)(ceil(VERTEX_MAX_COUNT*1.5)));
ctx.buffers[i].vtx = ctx.buffers[i].m_vtx.data;
ctx.buffers[i].idx = ctx.buffers[i].m_idx.data;
}
DescLayoutBinding[2] layout_bindings = [
{ binding: 0, descriptorType: DT.Image, descriptorCount: 1, stageFlags: SS.All },
{ binding: 1, descriptorType: DT.Storage, descriptorCount: 1, stageFlags: SS.All },
];
ctx.desc_set_layout = CreateDescSetLayout(&ctx.rd, layout_bindings);
ctx.desc_set = AllocDescSet(&ctx.rd, ctx.desc_set_layout);
ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, PushConst.sizeof);
Attribute[14] attributes = [
{ binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 0},
{ binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 1},
{ binding: 0, location: 2, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 2},
{ binding: 0, location: 3, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 3},
{ binding: 0, location: 4, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof },
{ binding: 0, location: 5, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof },
{ binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.src_start.offsetof },
{ binding: 0, location: 7, format: FMT.RG_F32, offset: Vertex.src_end.offsetof },
{ binding: 0, location: 8, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof },
{ binding: 0, location: 9, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof },
{ binding: 0, location: 10, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof },
{ binding: 0, location: 11, format: FMT.R_F32, offset: Vertex.raised.offsetof },
{ binding: 0, location: 12, format: FMT.R_F32, offset: Vertex.z_index.offsetof },
{ binding: 0, location: 13, format: FMT.R_U32, offset: Vertex.texture.offsetof },
];
GfxPipelineInfo ui_info = {
vertex_shader: cast(u8[])VERTEX_BYTES,
frag_shader: cast(u8[])FRAGMENT_BYTES,
input_rate: IR.Instance,
input_rate_stride: Vertex.sizeof,
layout: ctx.pipeline_layout,
vertex_attributes: attributes,
src_color: BF.SrcAlpha,
dst_color: BF.OneMinusSrcAlpha,
color_op: BO.Add,
src_alpha: BF.One,
dst_alpha: BF.Zero,
alpha_op: BO.Add,
};
CreateGraphicsPipeline(&ctx.rd, &ctx.pipeline, &ui_info);
CreateImageView(&ctx.rd, &ctx.font_atlas, ctx.atlas_buf.atlas.width, ctx.atlas_buf.atlas.height, 4, ctx.atlas_buf.data);
Write(&ctx.rd, ctx.desc_set, &ctx.font_atlas, 0, DT.Image);
SetClearColors(&ctx.rd, [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]);
g_ui_ctx = ctx;
InitWidgets();
}
void
SetDebug(bool dbg)
{
debug
{
g_ui_ctx.dbg = dbg;
}
}
void
PrepRendering(UICtx* ctx)
{
BeginFrame(&ctx.rd);
BeginRendering(&ctx.rd);
Vec2 ext = GetExtent(&ctx.rd);
if(ext != ctx.res)
{
ctx.res = ext;
Ortho(&ctx.pc.projection, 0.0, 0.0, ext.x, ext.y, -10.0, 10.0);
}
PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc);
Bind(&ctx.rd, ctx.pipeline, ctx.desc_set);
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;
}
void
CompleteRendering(UICtx* ctx)
{
FinishRendering(&ctx.rd);
SubmitAndPresent(&ctx.rd);
}
UICtx*
GetCtx()
{
return &g_ui_ctx;
}
Vec2
RootSize()
{
return cast(Vec2)GetExtent(&g_ui_ctx.rd);
}
UIKey
MakeKey(u8[] id)
{
UIKey key;
bool hash_only = false;
i64 pos = 0;
u32 hash_count = 0;
for(i64 i = id.length-1; i >= 0; i -= 1)
{
if(i == 0 && id[i] == '#')
{
hash_only = true;
}
if(hash_count == 2)
{
if(id[i] == '#')
{
hash_count += 1;
pos = i;
}
break;
}
if(id[i] == '#')
{
pos = i;
hash_count += 1;
}
}
if(hash_count < 2 || hash_only)
{
key.text = hash_only ? [] : id;
key.hash_text = hash_only ? id : [];
key.hash = Hash(id);
}
else if(hash_count == 2)
{
key.text = id[0 .. pos];
key.hash_text = id[pos .. $];
key.hash = Hash(id);
}
else if(hash_count == 3)
{
key.text = id[0 .. pos];
key.hash_text = id[pos .. $];
key.hash = Hash(id[pos+hash_count .. $]);
}
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);
HTPush(&g_ui_ctx.items, key.hash, result.value);
}
result.value.key = key;
return result.value;
}
UIItem*
Get(string id)
{
u8[] u8_id = CastStr!(u8)(id);
return Get(u8_id);
}
UIItem*
Get(u8[] id)
{
UIKey key = MakeKey(id);
return Get(key);
}
f32
CalcTextWidth(u8[] str)
{
u32 tab_width = g_ui_ctx.tab_width;
Glyph* space = g_ui_ctx.atlas_buf.atlas.glyphs.ptr + ' ';
f32 width = 0.0;
for(u64 i = 0; i < str.length; i += 1)
{
width += GlyphWidth(g_ui_ctx.atlas_buf.atlas.glyphs.ptr + str.ptr[i]);
}
return width;
}
pragma(inline) f32
GlyphWidth(Glyph* g)
{
f32 width = 0.0;
if(g.ch == '\t')
{
width += g_ui_ctx.atlas_buf.atlas.glyphs[' '].advance * cast(f32)(g_ui_ctx.tab_width);
}
else
{
width += g.advance;
}
return width;
}
struct TextBuffer
{
u8[] text;
TS[] style;
}
Node!(TextBuffer)*
MakeMultiline(u8[] text, f32 width, TS[] style = [])
{
f32 scaled_width = width * (g_ui_ctx.atlas_buf.atlas.size/g_ui_ctx.text_size);
f32 text_width = CalcTextWidth(text);
u64 line_count = cast(u64)(ceil(text_width/scaled_width));
Node!(TextBuffer)* node = null;
if(line_count > 0)
{
f32 w = 0.0;
u64 line = 0;
u64 start = 0;
const u64 extra_buf = 20;
for(u64 i = 0; i < text.length; i += 1)
{
f32 ch_w = GlyphWidth(g_ui_ctx.atlas_buf.atlas.glyphs.ptr + text[i]);
if(ch_w + w > scaled_width || i == text.length-1)
{
u64 len = i-start;
u8[] str = ScratchAlloc!(u8)(text, start, len);
TS[] stl = [];
if(style.length > 0)
{
stl = ScratchAlloc!(TS)(style, start, len);
}
Node!(TextBuffer)* n = node;
for(;;)
{
if(node == null)
{
node = ScratchAlloc!(Node!(TextBuffer))();
node.value.text = str;
node.value.style = stl;
node.next = null;
break;
}
if(n.next == null)
{
n.next = ScratchAlloc!(Node!(TextBuffer))();
n.next.value.text = str;
n.next.value.style = stl;
n.next.next = null;
break;
}
n = n.next;
}
line += 1;
start = i;
w = 0.0;
}
w += ch_w;
}
}
return node;
}
/*
pragma(inline) bool
CullText(UIItem* item, GlyphBounds* gb)
{
bool skip = false;
if(item.culling.x0 != 0.0 || item.culling.x1 != 0.0)
{
if(gb.w <= item.culling.x0+item.culling.x1)
{
skip = true;
}
else
{
f32 start_pct = 1.0-((gb.w-item.culling.x0)/gb.w);
f32 end_pct = 1.0-((gb.w-item.culling.x1)/gb.w);
f32 atlas_len = gb.atlas_r - gb.atlas_l;
gb.atlas_l += atlas_len * start_pct;
gb.atlas_r -= atlas_len * end_pct;
gb.l += item.culling.x0;
gb.r -= item.culling.x1;
gb.w = gb.r-gb.l;
}
}
if(!skip && (item.culling.y0 != 0.0 || item.culling.y1 != 0.0))
{
if(gb.h <= item.culling.y0+item.culling.y1)
{
skip = true;
}
else
{
f32 start_pct = 1.0-((gb.h-item.culling.y0)/gb.h);
f32 end_pct = 1.0-((gb.h-item.culling.y1)/gb.h);
f32 atlas_len = gb.atlas_b-gb.atlas_t;
gb.atlas_t += atlas_len * start_pct;
gb.atlas_b -= atlas_len * end_pct;
gb.t += item.culling.y0;
gb.b -= item.culling.y1;
gb.h = gb.b-gb.t;
}
}
return skip;
}
*/
void
DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col)
{
Vertex* v = DrawGlyph(glyph, scale, x_pos, y);
if(v)
{
v.cols = col;
v.texture = 1;
}
}
void
DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y, TokenStyle ts)
{
Vertex* v = DrawGlyph(glyph, scale, x_pos, y);
if(v)
{
v.cols = SYNTAX_COLORS[ts];
v.texture = 1;
}
}
Vertex*
DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y)
{
UICtx* ctx = GetCtx();
Vertex* v = null;
if(glyph.ch == '\t')
{
*x_pos += glyph.advance * (GetCtx().tab_width - 1);
}
f32 r = glyph.plane_right * scale;
f32 l = glyph.plane_left * scale;
f32 t = glyph.plane_top * scale;
f32 b = glyph.plane_bottom * scale;
GlyphBounds gb = {
r: r,
l: l,
t: t,
b: b,
w: r - l,
h: b - t,
atlas_r: glyph.atlas_right,
atlas_l: glyph.atlas_left,
atlas_t: glyph.atlas_top,
atlas_b: glyph.atlas_bottom,
};
// TODO: readd culling
//bool skip = CullText(item, &gb);
f32 y_pos = t;
v = ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count;
v.dst_start.x = *x_pos + gb.l;
v.dst_start.y = y - y_pos;
v.dst_end.x = *x_pos + gb.w + gb.l;
v.dst_end.y = y + gb.h - y_pos;
if(glyph.ch != '\t' && glyph.ch != '\n')
{
v.src_start.x = gb.atlas_l;
v.src_start.y = gb.atlas_t;
v.src_end.x = gb.atlas_r;
v.src_end.y = gb.atlas_b;
}
AddUIIndices(ctx);
*x_pos += glyph.advance * scale;
return v;
}
pragma(inline) Vertex*
GetVertex(UICtx* ctx)
{
return ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count;
}
pragma(inline) void
DrawBorderedRect(Vec2 pos, Vec2 size, f32 border, f32 radius, f32 softness, Vec4[4] cols, Vec4[4] border_col)
{
DrawRect(pos, size, radius, border, cols);
DrawBorder(pos, size, border, radius, softness, border_col);
}
pragma(inline) Glyph*
GetGlyph(u8 ch)
{
FontAtlas* a = &g_ui_ctx.atlas_buf.atlas;
return ch < a.glyphs.length ? a.glyphs.ptr + ch : a.glyphs.ptr;
}
pragma(inline) void
DrawRect(f32 x, f32 y, FontAtlas* atlas, u8 ch, Vec4 col, bool edit_mode)
{
Glyph* g = GetGlyph(ch);
Vec4[4] cols = col;
DrawRect(Vec2(x, y-TEXT_SIZE*0.8), Vec2(edit_mode ? 1.0 : g.advance, TEXT_SIZE), 0.0, 0.0, cols);
}
pragma(inline) void
DrawRect(Vec2 pos, Vec2 size, f32 corner_radius, f32 border, Vec4[4] cols)
{
DrawRect(pos.x, pos.y, size.x, size.y, corner_radius, border, cols);
}
void
DrawRect(f32 x, f32 y, f32 w, f32 h, f32 corner_radius, f32 border, Vec4[4] cols)
{
UICtx* ctx = GetCtx();
Vertex* v = GetVertex(ctx);
v.dst_start.x = x;
v.dst_start.y = y;
v.dst_end.x = x+w;
v.dst_end.y = y+h;
v.cols = cols;
v.corner_radius = corner_radius;
v.border_thickness = v.edge_softness = v.raised = 0.0;
if(border > 0.0)
{
v.dst_start += border;
v.dst_end -= border;
}
AddUIIndices(ctx);
}
void
DrawBorder(Vec2 pos, Vec2 size, f32 border, f32 radius, f32 softness, Vec4[4] cols)
{
UICtx* ctx = GetCtx();
Vertex* v = GetVertex(ctx);
v.dst_start = pos;
v.dst_end = pos + size;
v.cols = cols;
v.corner_radius = radius;
v.border_thickness = border;
v.edge_softness = softness;
v.raised = 0.0;
AddUIIndices(ctx);
}
pragma(inline) void
AddUIIndices(UICtx* ctx)
{
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;
ctx.buffers[ctx.f_idx].count += 1;
assert(ctx.buffers[ctx.f_idx].count < VERTEX_MAX_COUNT);
}
bool
Nil(UIItem* item)
{
return item == null || item == g_UI_NIL;
}
pragma(inline) bool
InBounds(InputEvent* ev, Vec2 p0, Vec2 p1)
{
return ev.x >= p0.x && ev.x <= p1.x && ev.y >= p0.y && ev.y <= p1.y;
}
bool
Clicked(UIItem* item, Vec2 p0, Vec2 p1)
{
bool result;
UICtx* ctx = GetCtx();
for(auto n = ctx.inputs.list.first; !CheckNil(null, n) && Nil(ctx.drag_item); n = n.next)
{
InputEvent* ev = &n.value;
if(ev.key == Input.LeftClick && ev.pressed && InBounds(ev, p0, p1))
{
result = true;
DLLRemove(&ctx.inputs.list, n, null);
break;
}
}
return result;
}
bool
Dragged(UIItem* item, Vec2 p0, Vec2 p1)
{
bool result;
UICtx* ctx = GetCtx();
item.dragged = 0.0;
if(Nil(ctx.drag_item) && Clicked(item, p0, p1))
{
ctx.drag_item = item;
}
for(auto n = ctx.inputs.list.first; !CheckNil(null, n) && ctx.drag_item == item; n = n.next)
{
InputEvent* ev = &n.value;
bool taken;
if(ev.key == Input.MouseMotion)
{
item.dragged.x += cast(f32)ev.rel_x;
item.dragged.y += cast(f32)ev.rel_y;
result = true;
taken = true;
}
else if(ev.key == Input.LeftClick && !ev.pressed)
{
ctx.drag_item = g_UI_NIL;
taken = true;
}
if(taken)
{
DLLRemove(&ctx.inputs.list, n, null);
}
}
return result;
}
u8[]
ScratchName(u64 num_len, u8[] base, u64 iteration)
{
import core.stdc.stdio : sprintf;
char[64] ch_buf;
char[] s = ch_buf.sformat("%%0%sllu%%s", num_len);
u8[] id = ScratchAlloc!(u8)(base.length+num_len);
sprintf(cast(char*)id.ptr, s.ptr, iteration, cast(char*)base.ptr);
return id;
}
u8[]
ScratchName(string fmt, u64 len, u64 iteration)
{
u8[] id = ScratchAlloc!(u8)(len);
(cast(char[])id).sformat(fmt, iteration);
return id;
}
u8[]
ScratchName(u8[] base, u8[] append)
{
u8[] id = ScratchAlloc!(u8)(base.length+append.length);
id[0 .. base.length] = base[0 .. $];
id[base.length .. $] = append[0 .. $];
return id;
}
u8[]
ScratchName(u8[] base, string append)
{
u8[] u8_append = CastStr!(u8)(append);
return ScratchName(base, u8_append);
}
unittest
{
{ // UI Key
u8[] str = cast(u8[])r"Test String##123";
UIKey key = MakeKey(str);
assert(key.text == cast(u8[])r"Test String");
assert(key.hash == Hash(str));
u8[] str1 = cast(u8[])r"Test String###123123";
key = MakeKey(str1);
assert(key.text == cast(u8[])r"Test String");
assert(key.hash == Hash(r"123123"));
}
}