From 729b5a2887912b440b764f967be3949838b78605 Mon Sep 17 00:00:00 2001 From: quou Date: Tue, 14 Jan 2025 21:46:29 +1100 Subject: UI slider, float and text inputs --- c2.cpp | 1 + ui.cpp | 334 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- ui.hpp | 51 +++++++++- 3 files changed, 377 insertions(+), 9 deletions(-) diff --git a/c2.cpp b/c2.cpp index 26d1962..af843f2 100644 --- a/c2.cpp +++ b/c2.cpp @@ -807,6 +807,7 @@ struct C2 : public App { } void on_text_input(const char* buf) override { + ui->text_input(buf); } }; diff --git a/ui.cpp b/ui.cpp index aaa3e59..caca515 100644 --- a/ui.cpp +++ b/ui.cpp @@ -6,8 +6,11 @@ extern "C" { #include "str.h" } +#include #include +#include + #define vertex_buffer_count (256) #define ui_padding 5 @@ -426,6 +429,7 @@ void UI::init( shader = sh; hot = 0; hovered = 0; + active = 0; mesh.init(device); cbuffer.init( device, @@ -440,6 +444,7 @@ void UI::init( shader_info.config_binding = sp->descriptor_binding("config_buffer"); sampler = create_clamped_point(device); root = create_element(0); + ((Container*)root)->padding = 0; } void UI::destroy() { @@ -456,6 +461,17 @@ void UI::layout(int w, int h) { 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; @@ -467,6 +483,7 @@ void UI::update(Arena* s) { hovered->message(msg); } hot = hovered; + active = hovered; } if (app->mjr(mbtn_left)) { if (hot) { @@ -481,6 +498,20 @@ void UI::update(Arena* s) { 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); } @@ -722,11 +753,17 @@ bool UI::Element::enabled() { } 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) { @@ -758,10 +795,15 @@ void UI::Element::remove_child(Element* ch) { ch->parent = 0; } -void UI::Element::message(const Message& msg) { +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): @@ -775,20 +817,23 @@ UI::Rect UI::Container::layout(const Rect& avail) { bound = avail; for (child = children; child; child = child->next) { int h; - Rect r = child->layout(area); + 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; + 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); - if (clip.x == 0 && clip.y == 0 && clip.w == 300 && clip.h == 80) { - return bound; - } return bound; } @@ -868,6 +913,283 @@ void UI::Button::on_render() { ); } +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->mesh.add_rect( + ui, + c.x, + c.y, + c.w, + c.h, + 0x000000 + ); + } + if (buf) { + ui->mesh.set_clip(clip); + ui->mesh.add_text( + ui, + 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; + } + 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->mesh.add_rect( + ui, + sr.x, + sr.y, + sr.w, + sr.h, + 0xa7a7a7 + ); + ui->mesh.add_rect( + ui, + 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); diff --git a/ui.hpp b/ui.hpp index abb4f69..9d95827 100644 --- a/ui.hpp +++ b/ui.hpp @@ -97,7 +97,7 @@ struct UI { Vertex_Buffer mesh; Pipeline* pipeline; Render_Pass* render_pass; - Element* root, * hot, * hovered; + Element* root, * hot, * hovered, * active; Rect area; bool layout_dirty; @@ -129,6 +129,7 @@ struct UI { ); void destroy(); void layout(int w, int h); + void text_input(const char* buf); void update(Arena* s); void render(Arena* s, Texture_Id target); @@ -148,8 +149,13 @@ struct UI { click, destroy, activate, - deactivate + deactivate, + input_changed, + input_finalised, + text_typed, + text_backspaced } type; + void* payload; }; typedef int (*Message_Handler)(Element* e, const Message& m); @@ -182,7 +188,7 @@ struct UI { void add_child(Element* ch); void remove_child(Element* ch); - void message(const Message& msg); + void message(const Message& msg, bool recurse = false); void render(); bool enabled(); @@ -221,6 +227,45 @@ struct UI { void on_render() override; }; + typedef int (*Input_Filter)(char ch); + + struct Input : Element { + Input_Filter filter; + char* buf; + int buf_size; + Input(UI* ui, Element* parent, Input_Filter f); + ~Input(); + Rect layout(const Rect& avail) override; + void on_render() override; + void on_message(const Message& m) override; + void set_text(const char* t); + void add_text(const char* s); + }; + + struct Float_Input : Input { + float val; + Float_Input(UI* ui, Element* parent); + void on_message(const Message& m) override; + void set_val(float v); + }; + + struct Slider : Element { + Float_Input* input; + float minval, maxval; + int drag_off; + bool dragging; + Slider(UI* ui, Element* parent, float miv, float mav); + Rect layout(const Rect& avail) override; + void on_message(const Message& m) override; + void on_render() override; + void on_update() override; + + int track_w(); + Rect handle_rect(); + Rect track_rect(); + float val_from_coord(int coord); + }; + struct Modal : Element { Container* contents; Toolbar* title_bar; -- cgit v1.2.3-54-g00ecf