#include "ui.hpp" extern "C" { #include "memory.h" #include "plat.h" #include "str.h" } #include #include #include #define vertex_buffer_count (2048) #define ui_padding 5 #define DEBUG_CLEARS 0 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 }; 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::clip_src(const Rect& other, Rect& src) { int n; if ((n = other.x - x) > 0) { w -= n; x += n; src.w -= n; src.x += n; } if ((n = other.y - y) > 0) { h -= n; y += n; src.h -= n; src.y += n; } if ((n = x + w - (other.x + other.w)) > 0) { w -= n; src.w -= n; } if ((n = y + h - (other.y + other.h)) > 0) { h -= n; src.h -= n; } } 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; } static inline UI::Colour blend(UI::Colour dst, UI::Colour src) { int ia; ia = 0xff - src.a; dst.r = (uint8_t)(((src.r * src.a) + (dst.r * ia)) >> 8); dst.g = (uint8_t)(((src.g * src.a) + (dst.g * ia)) >> 8); dst.b = (uint8_t)(((src.b * src.a) + (dst.b * ia)) >> 8); dst.a = (uint8_t)(((src.a * src.a) + (dst.a * ia)) >> 8); return dst; } void UI::Renderer::blit_rect( int x, int y, int w, int h, Colour col ) { int i, j, ex, ey, s; Rect r = { x, y, w, h }; r.clip(clip); Colour* dst = pixels + r.x + r.y * bound.w; ex = r.x + r.w; ey = r.y + r.h; s = bound.w - r.w; for (j = r.y; j < ey; j++) { for (i = r.x; i < ex; i++) { *dst = blend(*dst, col); dst++; } dst += s; } } void UI::Renderer::blit_char(char ch, int x, int y, Colour col) { Rect re, su; int i, j, sx, sy, ex, ey, s; Colour* dst; re.x = x; re.y = y; re.w = 10; re.h = 10; su.x = (ch - ' ') * 10; su.y = 0; su.w = 10; su.h = 10; re.clip_src(clip, su); ex = re.x + re.w; ey = re.y + re.h; dst = pixels + re.x + re.y * bound.w; s = bound.w - re.w; for (j = re.y, sy = su.y; j < ey; j++, sy++) { for (i = re.x, sx = su.x; i < ex; i++, sx++) { int si = (sx + sy * font_w); int bit = (1 << (si & 0x1f)); bit &= font_data[si >> 5]; if (bit) *dst = blend(*dst, col); dst++; } dst += s; } } UI::Renderer::Cmd* UI::Renderer::add_cmd(int size) { Cmd* r; size = align_size(size, 8); assert(cmd_buf_ptr + size <= cmd_buf_size); r = (Cmd*)&cmd_buf[cmd_buf_ptr]; r->size = size; cmd_buf_ptr += size; return r; } void UI::Renderer::commit_cmd(Cmd* c) { int x1 = std::min(c->r.x / grid_cell_size[0], grid_size); int y1 = std::min(c->r.y / grid_cell_size[1], grid_size); int x2 = std::min((c->r.x + c->r.w + grid_cell_size[0]) / grid_cell_size[0], grid_size); int y2 = std::min((c->r.y + c->r.h + grid_cell_size[1]) / grid_cell_size[1], grid_size); int s = grid_size - (x2 - x1); uint64_t* h = grid + x1 + y1 * grid_size; int i, j; for (j = y1; j < y2; j++) { for (i = x1; i < x2; i++) { *h = fnv1a64_2(*h, (uint8_t*)c, c->size); h++; } h += s; } } void UI::Renderer::add_rect( int x, int y, int w, int h, Colour col ) { Cmd* c = add_cmd(sizeof *c); c->type = Cmd::RECT; c->col = col; c->r.x = x; c->r.y = y; c->r.w = w; c->r.h = h; commit_cmd(c); } void UI::Renderer::add_text( int x, int y, const char* txt, Colour col ) { int len = string_len(txt); Cmd* c = add_cmd(sizeof *c + len + 1); c->type = Cmd::TEXT; c->col = col; c->r.x = x; c->r.y = y; c->r.w = 10 * len; c->r.h = 10; string_copy((char*)(c + 1), txt); commit_cmd(c); } void UI::Renderer::reset(const Rect& cl) { int i, e; uint64_t* dst = grid; clip = cl; e = grid_size * grid_size; for (i = 0; i < e; i++, dst++) *dst = fnv1a64(0, 0); grid_cell_size[0] = cl.w / grid_size; grid_cell_size[1] = cl.h / grid_size; cmd_buf_ptr = 0; } void UI::Renderer::set_clip(const Rect& r) { Cmd* c = add_cmd(sizeof *c); c->type = Cmd::CLIP; c->col = Colour(0, 0); c->r = r; clip = r; } void UI::Renderer::clear(const Rect& r) { int x, y; int ex = r.x + r.w; int ey = r.y + r.h; int s = bound.w - r.w; Colour* dst = pixels + r.x + r.y * bound.w; #if DEBUG_CLEARS Colour col(rand(), 0x30); #else Colour col(0x000000, 0x00); #endif for (y = r.y; y < ey; y++) { for (x = r.x; x < ex; x++) { *dst = col; dst++; } dst += s; } } void UI::Renderer::flush_cell(int x, int y) { Cmd* cmd = (Cmd*)cmd_buf; Cmd* last = (Cmd*)(cmd_buf + cmd_buf_ptr); Rect cell = Rect( x * grid_cell_size[0], y * grid_cell_size[1], grid_cell_size[0], grid_cell_size[1] ); clip = cell; clear(cell); while (cmd != last) { switch (cmd->type) { case Cmd::RECT: { Rect& r = cmd->r; blit_rect(r.x, r.y, r.w, r.h, cmd->col); } break; case Cmd::TEXT: { int x = cmd->r.x; char* txt = (char*)(cmd + 1); for (; *txt; txt++, x += 10) blit_char(*txt, x, cmd->r.y, cmd->col); } break; case Cmd::CLIP: { clip = cmd->r; clip.clip(cell); break; } } cmd = (Cmd*)((uint8_t*)cmd + cmd->size); } } bool UI::Renderer::flush(void* target) { uint64_t* next = grid == grid_a? grid_b: grid_a; uint64_t* h = grid; uint64_t* p = next; bool dirty = false; pixels = (Colour*)target; int i, j; for (j = 0; j < grid_size; j++) { for (i = 0; i < grid_size; i++) { if (*p != *h) { dirty = true; flush_cell(i, j); } h++; p++; } } grid = next; return dirty; } void UI::Renderer::resize(int w, int h) { int i, e = grid_size * grid_size; uint64_t* next = grid == grid_a? grid_b: grid_a; bound = Rect(0, 0, w, h); for (i = 0; i < e; i++) next[i] = 0; } void UI::Renderer::init(Heap* heap, int w, int h) { arena = heap; int i, e = grid_size * grid_size; bound = Rect(0, 0, w, h); bound = Rect(0, 0, w, h); grid = grid_a; for (i = 0; i < e; i++) grid[i] = 0; } UI* UI::create( const App* app, Arena* a ) { int hs; 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(app, h); 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( const App* a, Heap* h ) { heap = h; app = a; hot = 0; hovered = 0; active = 0; ren.init(heap, a->w, a->h); root = create_element(0); ((Container*)root)->padding = 0; } void UI::destroy() { root->destroy(); } void UI::layout(int w, int h) { area = Rect(0, 0, w, h); root->layout(area); layout_dirty = 0; } void UI::text_input(const char* buf) { if (active) { Message msg{}; msg.type = Message::Type::text_typed; msg.payload = const_cast(buf); active->message(msg); msg.type = Message::Type::input_changed; active->message(msg); } } 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; active = 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 (active) { if (app->kjp(key_backspace)) { Message msg{}; msg.type = Message::Type::text_backspaced; active->message(msg); msg.type = Message::Type::input_changed; active->message(msg); } if (app->kjp(key_return)) { Message msg{}; msg.type = Message::Type::input_finalised; active->message(msg); } } if (layout_dirty) layout(area.w, area.h); } bool UI::render(Arena* s, void* pixels) { if (area.w != ren.bound.w || area.h != ren.bound.h) ren.resize(area.w, area.h); ren.reset(ren.bound); root->render(); return ren.flush(pixels); } void UI::draw_container(const Rect& bound, Colour c) { /* todo line function lmao */ ren.add_rect( bound.x, bound.y, bound.w, bound.h, c ); ren.add_rect( bound.x, bound.y, bound.w, 1, 0x000000 ); ren.add_rect( bound.x + 1, bound.y + 1, bound.w - 1, 1, 0xa7a7a7 ); ren.add_rect( bound.x, bound.y, 1, bound.h, 0x000000 ); ren.add_rect( bound.x + 1, bound.y + 1, 1, bound.h - 1, 0xa7a7a7 ); ren.add_rect( bound.x + bound.w - 1, bound.y + 1, 1, bound.h - 2, 0x000000 ); ren.add_rect( bound.x + 1, bound.y + bound.h - 1, bound.w - 1, 1, 0x000000 ); } void UI::draw_containeri(const Rect& bound, Colour c) { ren.add_rect( bound.x, bound.y, bound.w, bound.h, c ); ren.add_rect( bound.x, bound.y, bound.w, 1, 0x000000 ); ren.add_rect( bound.x + 1, bound.y + bound.h - 2, bound.w - 2, 1, 0xa7a7a7 ); ren.add_rect( bound.x, bound.y, 1, bound.h, 0x000000 ); ren.add_rect( bound.x + bound.w - 2, bound.y + 1, 1, bound.h - 2, 0xa7a7a7 ); ren.add_rect( bound.x + bound.w - 1, bound.y + 1, 1, bound.h - 2, 0x000000 ); ren.add_rect( 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->ren.clip; on_render(); for (child = children; child; child = child->next) { child->render(); } ui->ren.set_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() { UI::Element* child = children; flags |= Flags::disabled; for (; child; child = child->next) child->disable(); } void UI::Element::enable() { UI::Element* child = children; flags &= ~Flags::disabled; for (; child; child = child->next) child->enable(); } 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, bool recurse) { on_message(msg); if (handler) handler(this, msg); if (children && recurse) { Element* child = children; for (; child; child = child->next) child->message(msg, true); } } 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 cav = { area.x + padding, area.y, area.w - padding * 2, area.h }; Rect r = child->layout(cav); h = r.h + padding; area.y += h; area.h -= h; if (r.w > used.w) used.w = r.w + padding * 2; used.h += h; } bound = used; bound.clip(avail); clip = bound; clip.clip(ui->area); return bound; } void UI::Container::on_render() { ui->ren.set_clip(clip); } UI::Table::Table(UI* ui, Element* parent, const int* rs): Container(ui, parent) { int c = 0, i; const int* r = rs; for (; *r; r++, c++); rows = (int*)heap_alloc(ui->heap, c + 1 * sizeof *rows); for (i = 0; i < c; i++) rows[i] = rs[i]; rows[i] = 0; } UI::Table::~Table() { heap_free(ui->heap, rows); } UI::Rect UI::Table::layout(const Rect& avail) { Rect used = avail; Rect area = avail; Element* child; int* row = rows; int cx = area.x + padding; int rh = 0; used.w = used.h = 0; for (; *row; row++) used.w += *row + padding; row = rows; for (child = children; child; child = child->next) { if (!*row) { int i = rh + padding; cx = area.x + padding; area.y += i; area.h -= i * 2; used.h += i; rh = 0; row = rows; } Rect carea = { cx, area.y, used.w - padding * 2, area.h }; Rect r = child->layout(carea); if (r.h > rh) rh = r.h; cx += *row; row++; } used.h += rh + padding; bound = used; bound.clip(avail); clip = bound; clip.clip(ui->area); return bound; } 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->ren.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->ren.set_clip(clip); ui->ren.add_text( bound.x + ui_padding + toff[0], bound.y + ui_padding + toff[1], text, tc ); } UI::Input::Input(UI* ui, Element* parent, Input_Filter f): Element(ui, parent), filter(f), buf(0), buf_size(0) {} UI::Input::~Input() { if (buf) heap_free(ui->heap, buf); } UI::Rect UI::Input::layout(const Rect& avail) { constexpr int default_input_w = 250; Rect r = { avail.x, avail.y, default_input_w, 10 + ui_padding * 2 }; r.clip(avail); bound = r; clip = r; clip.shrink(ui_padding); clip.clip(ui->area); return r; } void UI::Input::on_render() { Colour tc = 0x000000; int tx = clip.x; if (flags & Flags::disabled) tc = 0x4f4f4f; if (this == ui->hot) { ui->draw_containeri(bound, 0xffffff); } else ui->draw_container(bound, 0xffffff); if (this == ui->active) { Rect c = { tx + 1, clip.y, 1, 10 }; int n, w = 0; if (buf) { w = ui->text_width(buf); c.x += w; } if ((n = c.x - (clip.x + clip.w)) > 0) tx -= n; c.x = tx + w; ui->ren.add_rect( c.x, c.y, c.w, c.h, 0x000000 ); } if (buf) { ui->ren.set_clip(clip); ui->ren.add_text( tx, bound.y + ui_padding, buf, tc ); } } void UI::Input::on_message(const Message& msg) { switch (msg.type) { case UI::Message::Type::text_typed: add_text((char*)msg.payload); break; case UI::Message::Type::text_backspaced: if (buf) { int l = string_len(buf); if (l) buf[l - 1] = 0; } break; case UI::Message::Type::input_finalised: if (this == ui->active) ui->active = 0; break; default: break; } } void UI::Input::set_text(const char* t) { int l = string_len(t); if (l >= buf_size) { char* nb = (char*)heap_alloc(ui->heap, l + 1); if (buf) heap_free(ui->heap, buf); buf = nb; buf_size = l; } string_copy(buf, t); } void UI::Input::add_text(const char* s) { int l = string_len(s); int bl = 0, fl; int i; if (buf) bl = string_len(buf); fl = bl + l; if (fl >= buf_size) { char* nb = (char*)heap_alloc(ui->heap, fl + 1); if (buf) { string_copy(nb, buf); heap_free(ui->heap, buf); } buf = nb; } for (i = 0; i < l; i++) { if (filter(s[i])) { string_copy(&buf[bl + i], &s[i]); } } } static int float_filter(char ch) { return (ch >= '0' && ch <= '9') || ch == '.' || ch == '-'; } UI::Float_Input::Float_Input(UI* ui, Element* parent): Input(ui, parent, float_filter), val(0.0f) { set_text("0.0"); } void UI::Float_Input::on_message(const Message& m) { Input::on_message(m); if (m.type == UI::Message::Type::input_finalised) { float v = (float)strtod(buf, 0); set_val(v); } } void UI::Float_Input::set_val(float v) { char b[64]; val = v; sprintf(b, "%g", v); set_text(b); } UI::Slider::Slider( UI* ui, Element* parent, float miv, float mav ): Element(ui, parent), minval(miv), maxval(mav), dragging(false) { input = ui->create_element(this); } UI::Rect UI::Slider::layout(const Rect& avail) { constexpr int default_slider_w = 250; Rect r = { avail.x, avail.y, default_slider_w, 10 + ui_padding * 2 }; r.clip(avail); Rect inr = { r.x + r.w - 50, r.y, 50, r.h }; input->layout(inr); bound = r; clip = r; clip.clip(ui->area); return r; } void UI::Slider::on_render() { unsigned hc = dragging? 0x000000: 0x003fc3; if (flags & Flags::disabled) hc = 0x4f4f4f; Rect sr = track_rect(); Rect ha = handle_rect(); ui->ren.add_rect( sr.x, sr.y, sr.w, sr.h, 0xa7a7a7 ); ui->ren.add_rect( ha.x, ha.y, ha.w, ha.h, hc ); } int UI::Slider::track_w() { return bound.w - (50 + ui_padding); } UI::Rect UI::Slider::track_rect() { Rect r = { bound.x, bound.y + 9, track_w(), bound.h - 15 }; return r; } UI::Rect UI::Slider::handle_rect() { float ra = (maxval - minval); float perc = (std::clamp(input->val, minval, maxval) - minval) / ra; perc *= (float)track_w(); Rect r = { (bound.x + (int)perc) - 3, bound.y, 6, bound.h }; return r; } void UI::Slider::on_message(const Message& m) { switch (m.type) { case UI::Message::Type::activate: { int mx = ui->app->mx; int my = ui->app->my; Rect h = handle_rect(); if (h.contains(mx, my)) { dragging = true; drag_off = mx - h.x - 3; } else { Rect t = track_rect(); if (t.contains(mx, my)) { input->set_val(val_from_coord(mx)); } } Message msg{}; msg.type = Message::Type::input_changed; message(msg, true); } break; case UI::Message::Type::deactivate: dragging = false; break; default: break; } } float UI::Slider::val_from_coord(int coord) { float val, r; r = maxval - minval; val = (float)(coord - bound.x) / (float)track_w(); val *= r; val += minval; val = std::clamp(val, minval, maxval); return val; } void UI::Slider::on_update() { Message msg{}; msg.type = Message::Type::input_changed; if (!dragging) return; input->set_val(val_from_coord(ui->app->mx - drag_off)); message(msg, true); } 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->ren.set_clip(clip); ui->ren.add_text( 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; } void UI::Label::set_int(int v) { char buf[32]; sprintf(buf, "%d", v); set_text(buf); } void UI::Label::set_hex(uint64_t v) { char buf[32]; sprintf(buf, "0x%lx", v); set_text(buf); } void UI::Label::set_float(float v) { char buf[32]; sprintf(buf, "%g", v); set_text(buf); } 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