#include "ui.hpp" extern "C" { #include "memory.h" #include "plat.h" #include "str.h" } #include #define vertex_buffer_count (256) #define ui_padding 5 static constexpr int font_w = 960; static unsigned font_data[] = { 0x00000000, 0x00003000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0cc0c000, 0x8000301b, 0x60601803, 0x00000000, 0x20000000, 0x0780c078, 0x83f0e01e, 0xe0783f07, 0x60000001, 0x1e018000, 0x07e0c078, 0xe3f87e1f, 0xe0cc1e0f, 0x1e638f01, 0x1f0c6318, 0x07e1f07e, 0xc330fc1e, 0x30cc318c, 0x021e0fc3, 0x000101e0, 0x00e00000, 0x0000e000, 0xc00e0007, 0x3c038c00, 0x00000000, 0x00000000, 0x00001000, 0x00000000, 0x30380000, 0x0000c070, 0x0cc1e000, 0xc630f81b, 0xc0301806, 0x00000cc0, 0x30000000, 0x0cc0f0cc, 0xc030f033, 0x30cc330c, 0x30000003, 0x33030000, 0x8cc1e0cc, 0xc230cc33, 0xc0cc3308, 0x0c630600, 0x318ce3b8, 0x0cc318cc, 0xc330b433, 0x30cc318c, 0x06060cc3, 0x00038180, 0x00c00018, 0x8000c000, 0x000c000d, 0x30030000, 0x00000000, 0x00000000, 0x00001800, 0x00000000, 0x300c0000, 0x0409e0c0, 0x8481e000, 0xc3300c3f, 0x80180c06, 0x000c0781, 0x18000000, 0x0c00c0ec, 0xc1f0d830, 0x30cc1800, 0x180c0303, 0x30060000, 0x8cc330ec, 0xc0318c01, 0xc0cc3180, 0x0c330600, 0x318de3f8, 0x0cc318cc, 0xc3303003, 0x3078318c, 0x0c060643, 0x0006c180, 0x07c0f018, 0x81e0f81e, 0xf06c3701, 0x30330f00, 0x1e07c1f8, 0x0ee37076, 0xc330fc3e, 0x30c6318c, 0x300c0fc3, 0x0e0f20c0, 0x0001e000, 0x8180781b, 0x80180013, 0x000c1fe1, 0x0c000000, 0x0600c0dc, 0xc300cc1c, 0xe0780c07, 0x0c0c0303, 0x180c03f0, 0x87c330ec, 0xc1f18c01, 0xc0fc0187, 0x0c1f0600, 0x318fe358, 0x07c3187c, 0xc330300e, 0xe030358c, 0x18060301, 0x000c6180, 0x0cc18030, 0xc330cc33, 0xc0dc1987, 0x301b0c00, 0x330cc358, 0x0dc198cc, 0xc3301803, 0x306c358c, 0x00060643, 0x1b060180, 0x0000c000, 0xc0c0c01b, 0x8018001e, 0x003f0781, 0x060003f0, 0x0300c0cc, 0xc301fc30, 0x00cc0c0c, 0x0c000003, 0x0c0c0000, 0x8cc3f00c, 0xc0318c01, 0xc0cc3980, 0x0c330660, 0x318f6318, 0x06c3980c, 0xc3303018, 0xc078358c, 0x30060180, 0x00000180, 0x0cc1f000, 0x83f0cc03, 0xc0cc1981, 0x301f0c00, 0x330cc358, 0x00c198cc, 0xc330181e, 0x3038358c, 0x300c0303, 0x318000c0, 0x80000000, 0xc6607c3f, 0xc030000c, 0x300c0cc0, 0x03030000, 0x0180c0cc, 0xc330c033, 0x30cc0c0c, 0x180c0303, 0x000603f0, 0x8cc3308c, 0xc230cc33, 0xc0cc3300, 0x8c630660, 0x318e6318, 0x0cc1f00c, 0x83303033, 0xc0cc1f07, 0x600609c0, 0x00000180, 0x0cc19800, 0x8030cc33, 0xc0cc1f01, 0x30330cc0, 0x330cc358, 0x00c198cc, 0x8330d830, 0xe06c1f07, 0x300c0981, 0x318000c0, 0x0000c000, 0x8630301b, 0x6060001b, 0x300c0000, 0x01830000, 0x0fc3f078, 0x81e0c01e, 0xe0780c07, 0x300c0301, 0x0c030000, 0x07e33078, 0xe3f87e1f, 0xe0cc3e01, 0xfe6383c1, 0x1f0c6318, 0x0ce3c01e, 0x01e0781e, 0xe0cc1b03, 0xc01e0fc1, 0x000001e0, 0x07e3f000, 0xc1e0f81e, 0xf0ce1803, 0xfc338783, 0x1e0cc318, 0x01e1f07c, 0x06e0701f, 0x80c61b03, 0x30380fc1, 0x3f800070, 0x0000c000, 0x0000301b, 0x00000000, 0x18000000, 0x00800000, 0x00000000, 0x00000000, 0x00000000, 0x60060000, 0x00018000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x7f800000, 0x00000000, 0x00000000, 0x00000f00, 0x00000000, 0x00000000, 0x0001800c, 0x00000000, 0xf0000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0003c01e, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; static Texture_Id create_atlas(Device* d) { int x, y; int w = font_w + 10; int size = 10 * w; Buffer_Id buf = d->create_buffer( "font atlas stage", size, Buffer_Flags::copy_src | Buffer_Flags::cpu_readwrite ); Texture_Id tex; uint8_t* pixels = (uint8_t*)d->map_buffer(buf, 0, size); for (y = 0; y < 10; y++) { for (x = 0; x < font_w; x++) { int si = x + y * font_w; int di = x + y * w; unsigned bits = font_data[si >> 5]; int bit = bits & 1 << (si & 0x1f); pixels[di] = bit? 0xff: 0x00; } } /* for solid colours, one white square at the end */ for (y = 0; y < 10; y++) { for (x = font_w; x < w; x++) { pixels[x + y * w] = 0xff; } } d->unmap_buffer(buf); tex = d->create_texture( "font atlas", texture_format_r8i, Texture_Flags::sampleable | Texture_Flags::copy_dst, w, 10, 1, 1, 1, buf ); d->destroy_bufferi(buf); return tex; } static Sampler_Id create_clamped_point(Device* dev) { Sampler_State s{}; s.min = Filter_Mode::point; s.mag = Filter_Mode::point; s.address_u = Address_Mode::clamp; s.address_v = Address_Mode::clamp; return dev->create_sampler("clamped point", s); } UI::Colour::Colour(unsigned rgb, uint8_t a): r((rgb >> 16) & 0xff), g((rgb >> 8) & 0xff), b((rgb) & 0xff), a(a) {} UI::Rect::Rect(int x, int y, int w, int h): x(x), y(y), w(w), h(h) {} void UI::Rect::clip(const Rect& other) { int n; if ((n = other.x - x) > 0) { w -= n; x += n; } if ((n = other.y - y) > 0) { h -= n; y += n; } if ((n = x + w - (other.x + other.w)) > 0) w -= n; if ((n = y + h - (other.y + other.h)) > 0) h -= n; if (w < 0) w = 0; if (h < 0) h = 0; } void UI::Rect::shrink(int a) { x += a; y += a; w -= a * 2; h -= a * 2; if (w < 0) w = 0; if (h < 0) h = 0; } bool UI::Rect::contains(int px, int py) { return px >= x && py >= y && px < x + w && py < y + h; } void UI::Vertex_Buffer::init(Device* dev) { buf.init( dev, "UI mesh", vertex_buffer_count * sizeof(Vertex) * 4, Buffer_Flags::vertex_buffer ); init_indices(dev); usage = 0; start = 0; next = 0; } void UI::Vertex_Buffer::init_indices(Device* dev) { auto create_ind = [](uint16_t* target){ int i, i2, e = vertex_buffer_count * 6; for (i = 0, i2 = 0; i < e; i += 6, i2 += 4) { target[i ] = (uint16_t)(i2 ); target[i + 1] = (uint16_t)(i2 + 1); target[i + 2] = (uint16_t)(i2 + 2); target[i + 3] = (uint16_t)(i2 + 1); target[i + 4] = (uint16_t)(i2 + 3); target[i + 5] = (uint16_t)(i2 + 2); } }; int s = vertex_buffer_count * 6 * sizeof(uint16_t); Buffer_Id stage = dev->create_buffer( "UI mesh indices stage", s, Buffer_Flags::copy_src | Buffer_Flags::cpu_readwrite ); void* mem = dev->map_buffer(stage, 0, s); create_ind((uint16_t*)mem); indices = dev->create_buffer( "UI indices", s, Buffer_Flags::copy_dst | Buffer_Flags::index_buffer ); Context& ctx = dev->acquire(); ctx.copy(indices, stage); dev->submit(ctx); dev->unmap_buffer(stage); dev->destroy_bufferi(stage); } void UI::Vertex_Buffer::destroy(UI* ui) { Device* dev = ui->device; if (next) { next->destroy(ui); heap_free(ui->heap, next); } dev->destroy_buffer(indices); buf.destroy(dev); } void UI::Vertex_Buffer::add_quad( UI* ui, int x, int y, int w, int h, float u0, float v0, float u1, float v1, Colour col ) { Vertex* verts; int index = usage * 4; int* scissor = ui->pipeline->scissor; float r, g, b, a; if (usage >= vertex_buffer_count) { if (!next) { next = (Vertex_Buffer*)heap_alloc(ui->heap, sizeof *next); next->init(ui->device); } next->add_quad(ui, x, y, w, h, u0, v0, u1, v1, col); return; } if ( clip.x < scissor[0] || clip.y < scissor[1] || clip.x + clip.w > scissor[0] + scissor[2] || clip.y + clip.h > scissor[1] + scissor[3] || (( clip.x != scissor[0] || clip.y != scissor[1] || clip.w != scissor[2] || clip.h != scissor[3] ) && ( x < clip.x || y < clip.y || x + w > clip.x + clip.w || y + h > clip.y + clip.h )) ) { ui->mesh.draw( ui, ui->device->get_ctx(), *ui->pipeline, *ui->render_pass ); scissor[0] = clip.x; scissor[1] = clip.y; scissor[2] = clip.w; scissor[3] = clip.h; ui->pipeline->hash(); } r = col.r_f(); g = col.g_f(); b = col.b_f(); a = col.a_f(); verts = &((Vertex*)buf.map(ui->device))[index]; verts[0] = Vertex { (float)x, (float)y, u0, v0, r, g, b, a }; verts[1] = Vertex { (float)x + w, (float)y, u1, v0, r, g, b, a }; verts[2] = Vertex { (float)x, (float)y + h, u0, v1, r, g, b, a }; verts[3] = Vertex { (float)x + w, (float)y + h, u1, v1, r, g, b, a }; buf.unmap(ui->device); usage++; } void UI::Vertex_Buffer::add_rect( UI* ui, int x, int y, int w, int h, Colour col ) { constexpr float white_uv_x = (float)font_w / (float)(font_w + 10); add_quad(ui, x, y, w, h, white_uv_x, 0.0f, 1.0f, 1.0f, col); } void UI::Vertex_Buffer::add_char( UI* ui, int x, int y, char ch, Colour col ) { int off = (ch - ' ') * 10; float w = (float)(font_w + 10); float u0 = (float)off / w; float u1 = u0 + (10.0f / w); add_quad(ui, x, y, 10, 10, u0, 0.0f, u1, 1.0f, col); } void UI::Vertex_Buffer::add_text( UI* ui, int x, int y, const char* txt, Colour col ) { for (; *txt; txt++, x += 10) add_char(ui, x, y, *txt, col); } void UI::Vertex_Buffer::update_buffer(Context& ctx) { buf.update(ctx); } void UI::Vertex_Buffer::reset(const Rect& c) { start = 0; usage = 0; dirty = true; clip = c; if (next) next->reset(c); } void UI::Vertex_Buffer::set_clip(const Rect& r) { clip = r; assert(r.x >= 0 && r.y >= 0); assert(r.w >= 0 && r.h >= 0); if (next) next->set_clip(r); } void UI::Vertex_Buffer::draw( UI* ui, Context& ctx, Pipeline& pip, Render_Pass& rp ) { if (usage == start) { if (next) next->draw(ui, ctx, pip, rp); return; } if (dirty) { update_buffer(ctx); dirty = false; } Vertex_Buffer_Binding binding[] = {{ .id = buf.gpuonly, .offset = 0, .target = ui->shader_info.vert_binding }, {}}; Index_Buffer_Binding indb = { .id = indices, .offset = 0 }; Draw d{}; d.verts = binding; d.inds = indb; d.vertex_count = (usage - start) * 6; d.instance_count = 1; d.first_vertex = start * 6; ctx.submit(d, pip, rp); if (next) next->draw(ui, ctx, pip, rp); start = usage; } UI* UI::create( Device* dev, const App* app, Arena* a, Shader_Id sh ) { int hs; Texture_Id atlas = create_atlas(dev); UI* u = (UI*)arena_alloc(a, sizeof *u); Heap* h = (Heap*)arena_alloc(a, sizeof *h); hs = a->size - a->ptr - allocation_default_alignment - 1; init_heap(h, arena_alloc(a, hs), hs); u->init(dev, app, h, atlas, sh); return u; } int UI::text_width(const char* t) { int w; for (w = 0; *t; t++) w += 10; return w; } int UI::text_height(const char* t) { (void)t; return 10; } void UI::init( Device* dev, const App* a, Heap* h, Texture_Id at, Shader_Id sh ) { Shader* sp; heap = h; device = dev; app = a; atlas = at; shader = sh; hot = 0; hovered = 0; mesh.init(device); cbuffer.init( device, "UI CBuffer", sizeof(UI_CBuffer), Buffer_Flags::constant_buffer ); sp = &dev->get_shader(sh); vertex_format = sp->vf; shader_info.vert_binding = sp->binding_index("verts"); shader_info.atlas_binding = sp->descriptor_binding("atlas"); shader_info.config_binding = sp->descriptor_binding("config_buffer"); sampler = create_clamped_point(device); root = create_element(0); } void UI::destroy() { root->destroy(); device->destroy_texture(atlas); device->destroy_sampler(sampler); mesh.destroy(this); cbuffer.destroy(device); } void UI::layout(int w, int h) { area = Rect(0, 0, w, h); root->layout(area); layout_dirty = 0; } void UI::update(Arena* s) { (void)s; hovered = 0; root->update(); if (app->mjp(mbtn_left)) { if (hovered) { Message msg{}; msg.type = Message::Type::activate; hovered->message(msg); } hot = hovered; } if (app->mjr(mbtn_left)) { if (hot) { Message msg{}; msg.type = Message::Type::deactivate; hot->message(msg); if (hot == hovered) { Message msg2{}; msg2.type = Message::Type::click; hot->message(msg2); } hot = 0; } } if (layout_dirty) layout(area.w, area.h); } void UI::render(Arena* s, Texture_Id target) { Context& ctx = device->get_ctx(); Texture& t = device->get_texture(target); UI_CBuffer* config = (UI_CBuffer*)cbuffer.map(device); config->projection = m4f::orth( 0.0f, (float)t.w, 0.0f, (float)t.h, -1.0f, 1.0f ); cbuffer.unmap(device); cbuffer.update(ctx); Pipeline_Builder pb(s, device); pb.begin_rp(); pb.rp_target(device->get_backbuffer(), Clear_Mode::restore); render_pass = &pb.build_rp(); pb.begin(); pb.shader(shader); pb.vertex_format(vertex_format); pb.blend( Blend_Mode::add, Blend_Factor::src_alpha, Blend_Factor::inv_src_alpha ); pb.cbuffer(shader_info.config_binding, cbuffer.gpuonly); pb.texture(shader_info.atlas_binding, atlas, sampler); pipeline = &pb.build(); mesh.reset(Rect(0, 0, t.w, t.h)); root->render(); mesh.draw(this, ctx, *pipeline, *render_pass); } void UI::draw_container(const Rect& bound, Colour c) { /* todo line function lmao */ mesh.add_rect( this, bound.x, bound.y, bound.w, bound.h, c ); mesh.add_rect( this, bound.x, bound.y, bound.w, 1, 0x000000 ); mesh.add_rect( this, bound.x + 1, bound.y + 1, bound.w - 1, 1, 0xa7a7a7 ); mesh.add_rect( this, bound.x, bound.y, 1, bound.h, 0x000000 ); mesh.add_rect( this, bound.x + 1, bound.y + 1, 1, bound.h - 1, 0xa7a7a7 ); mesh.add_rect( this, bound.x + bound.w - 1, bound.y + 1, 1, bound.h - 2, 0x000000 ); mesh.add_rect( this, bound.x + 1, bound.y + bound.h - 1, bound.w - 1, 1, 0x000000 ); } void UI::draw_containeri(const Rect& bound, Colour c) { mesh.add_rect( this, bound.x, bound.y, bound.w, bound.h, c ); mesh.add_rect( this, bound.x, bound.y, bound.w, 1, 0x000000 ); mesh.add_rect( this, bound.x + 1, bound.y + bound.h - 2, bound.w - 2, 1, 0xa7a7a7 ); mesh.add_rect( this, bound.x, bound.y, 1, bound.h, 0x000000 ); mesh.add_rect( this, bound.x + bound.w - 2, bound.y + 1, 1, bound.h - 2, 0xa7a7a7 ); mesh.add_rect( this, bound.x + bound.w - 1, bound.y + 1, 1, bound.h - 2, 0x000000 ); mesh.add_rect( this, bound.x + 1, bound.y + bound.h - 1, bound.w - 1, 1, 0x000000 ); } UI::Element* UI::alloc_element(size_t size) { UI::Element* e = (UI::Element*)heap_alloc(heap, size); assert(e != 0); return e; } UI::Element::Element(UI* ui, Element* parent): ui(ui), parent(0), children(0), next(0), handler(0), flags(0) { if (parent) parent->add_child(this); } UI::Element::~Element() { Element* child; for (child = children; child;) { Element* n = child->next; child->~Element(); heap_free(ui->heap, child); child = n; } if (parent) parent->remove_child(this); } void UI::Element::destroy() { Message m{}; m.type = Message::Type::destroy; message(m); this->~Element(); heap_free(ui->heap, this); } UI::Rect UI::Element::layout(const Rect& avail) { return avail; } void UI::Element::update() { Element* child; if (enabled() && bound.contains(ui->app->mx, ui->app->my)) { ui->hovered = this; } on_update(); for (child = children; child; child = child->next) { child->update(); } } void UI::Element::on_update() { } void UI::Element::render() { Element* child; Rect old_clip = ui->mesh.clip; on_render(); for (child = children; child; child = child->next) { child->render(); } ui->mesh.clip = old_clip; } void UI::Element::on_message(const Message& msg) { (void)msg; } void UI::Element::on_add_child(Element* nch) { (void)nch; } void UI::Element::on_render() { } bool UI::Element::enabled() { return (flags & Flags::disabled) == 0; } void UI::Element::disable() { flags |= Flags::disabled; } void UI::Element::enable() { flags &= ~Flags::disabled; } void UI::Element::add_child(Element* ch) { assert(ch->parent == 0); assert(ch->next == 0); if (!children) children = ch; else { Element* child = children; for (; child && child->next; child = child->next); child->next = ch; } ch->parent = this; ui->layout_dirty = true; on_add_child(ch); } void UI::Element::remove_child(Element* ch) { Element* child = children; Element* prev = 0; for (; child && child != ch; child = child->next) prev = child; assert(child != 0); if (prev) prev->next = ch->next; else children = ch->next; ch->next = 0; ch->parent = 0; } void UI::Element::message(const Message& msg) { on_message(msg); if (handler) handler(this, msg); } UI::Container::Container(UI* ui, Element* parent): Element(ui, parent), padding(ui_padding) {} UI::Rect UI::Container::layout(const Rect& avail) { Element* child; Rect area = avail; Rect used = avail; used.w = used.h = 0; bound = avail; for (child = children; child; child = child->next) { int h; Rect r = child->layout(area); h = r.h + padding; area.y += h; area.h -= h; if (r.w > used.w) used.w = r.w; used.h += h; } bound = used; bound.clip(avail); clip = bound; clip.clip(ui->area); if (clip.x == 0 && clip.y == 0 && clip.w == 300 && clip.h == 80) { return bound; } return bound; } void UI::Container::on_render() { ui->mesh.set_clip(clip); } UI::Toolbar::Toolbar(UI* ui, Element* parent): Element(ui, parent), padding(0) {} UI::Rect UI::Toolbar::layout(const Rect& avail) { Element* child; Rect area = avail; Rect used = avail; used.w = used.h = 0; bound = avail; clip = avail; for (child = children; child; child = child->next) { Rect r = child->layout(area); area.x += r.w + padding; area.w -= r.w; if (r.h > used.h) used.h = r.h; used.w += r.w; } bound = used; bound.w = avail.w; clip = bound; clip.clip(ui->area); return used; } void UI::Toolbar::on_render() { ui->mesh.set_clip(clip); ui->draw_container( bound, 0xa7a7a7 ); } UI::Button::Button(UI* ui, Element* parent, const char* label): Label(ui, parent, label) { } UI::Rect UI::Button::layout(const Rect& avail) { Rect r = { avail.x, avail.y, ui->text_width(text) + ui_padding * 2, ui->text_height(text) + ui_padding * 2 }; r.clip(avail); bound = r; clip = r; clip.shrink(ui_padding); clip.clip(ui->area); return r; } void UI::Button::on_render() { Colour tc = 0x000000; int toff[] = { 0, 0 }; if (flags & Flags::disabled) tc = 0x4f4f4f; if (this == ui->hot) { toff[0] = 1; toff[1] = 1; ui->draw_containeri(bound, 0xffffff); } else ui->draw_container(bound, 0xffffff); ui->mesh.set_clip(clip); ui->mesh.add_text( ui, bound.x + ui_padding + toff[0], bound.y + ui_padding + toff[1], text, tc ); } UI::Label::Label(UI* ui, Element* parent, const char* label): Element(ui, parent) { text = dup_stringh(ui->heap, label); } UI::Label::~Label() { heap_free(ui->heap, text); } UI::Rect UI::Label::layout(const Rect& avail) { Rect r = { avail.x, avail.y, ui->text_width(text) + ui_padding * 2, ui->text_height(text) + ui_padding * 2 }; r.clip(avail); bound = r; clip = r; clip.shrink(ui_padding); clip.clip(ui->area); return r; } void UI::Label::on_render() { ui->mesh.set_clip(clip); ui->mesh.add_text( ui, bound.x + ui_padding, bound.y + ui_padding, text, 0x000000 ); } void UI::Label::set_text(const char* s) { heap_free(ui->heap, text); text = dup_stringh(ui->heap, s); ui->layout_dirty = 1; } static int title_handler(UI::Modal* modal, const UI::Message& msg) { switch (msg.type) { case UI::Message::Type::activate: { const App& app = *modal->ui->app; const UI::Rect& tb = modal->title_bar->bound; modal->dragging = true; modal->drag_offset[0] = app.mx - tb.x; modal->drag_offset[1] = app.my - tb.y; modal->bring_to_front(); } break; case UI::Message::Type::deactivate: modal->dragging = false; break; default: return 0; } return 0; }; UI::Modal::Modal(UI* ui, Element* parent, const char* text): Element(ui, parent), dragging(false) { pos[0] = 0; pos[1] = 0; contents = ui->create_element(this); title_bar = ui->create_element(contents); title_bar->handler = [](Element* e, const Message& m) { return title_handler((Modal*)e->parent->parent, m); }; close = ui->create_element