summaryrefslogtreecommitdiff
path: root/ui.cpp
diff options
context:
space:
mode:
authorquou <quou@disroot.org>2025-01-14 21:46:29 +1100
committerquou <quou@disroot.org>2025-01-14 21:46:29 +1100
commit729b5a2887912b440b764f967be3949838b78605 (patch)
treeafcd44bc865609713f7ad59c198abba4b9892488 /ui.cpp
parent274c2ba0eac94a00f942f7f6a78b2b9a1d0759a3 (diff)
UI slider, float and text inputs
Diffstat (limited to 'ui.cpp')
-rw-r--r--ui.cpp334
1 files changed, 328 insertions, 6 deletions
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 <stdio.h>
#include <string.h>
+#include <algorithm>
+
#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<Container>(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<char*>(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<UI::Float_Input>(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);