1343 lines
26 KiB
D
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"));
|
|
}
|
|
}
|
|
|