Files
2026-03-31 13:42:22 -05:00

896 lines
27 KiB
C++

#include "vui.h"
#include <cstring>
#include <algorithm>
namespace vui {
Element::Element() = default;
Element::~Element() = default;
Element::Element(Element &&other) noexcept
: posX(other.posX), posY(other.posY), w(other.w), h(other.h),
alpha(other.alpha), visible(other.visible),
name(std::move(other.name)), parent(other.parent),
children(std::move(other.children)), hovered_(other.hovered_) {
for (auto &c : children)
c->parent = this;
other.parent = nullptr;
}
Element &Element::operator=(Element &&other) noexcept {
if (this != &other) {
posX = other.posX;
posY = other.posY;
w = other.w;
h = other.h;
alpha = other.alpha;
visible = other.visible;
name = std::move(other.name);
parent = other.parent;
children = std::move(other.children);
hovered_ = other.hovered_;
for (auto &c : children)
c->parent = this;
other.parent = nullptr;
}
return *this;
}
Element *Element::addChild(std::unique_ptr<Element> child) {
child->parent = this;
auto *ptr = child.get();
children.push_back(std::move(child));
return ptr;
}
Element *Element::findChild(const std::string &searchName) const {
for (auto &c : children) {
if (c->name == searchName) return c.get();
Element *found = c->findChild(searchName);
if (found) return found;
}
return nullptr;
}
float Element::worldX() const {
float wx = posX;
if (anchorTarget && anchor != Anchor::None) {
float tx = anchorTarget->worldX();
float tw = anchorTarget->w;
switch (anchor) {
case Anchor::TopLeft: case Anchor::CenterLeft: case Anchor::BottomLeft:
wx += tx; break;
case Anchor::TopCenter: case Anchor::Center: case Anchor::BottomCenter:
wx += tx + tw * 0.5f; break;
case Anchor::TopRight: case Anchor::CenterRight: case Anchor::BottomRight:
wx += tx + tw; break;
default: break;
}
} else {
for (Element *p = parent; p; p = p->parent)
wx += p->posX;
}
return wx;
}
float Element::worldY() const {
float wy = posY;
if (anchorTarget && anchor != Anchor::None) {
float ty = anchorTarget->worldY();
float th = anchorTarget->h;
switch (anchor) {
case Anchor::TopLeft: case Anchor::TopCenter: case Anchor::TopRight:
wy += ty; break;
case Anchor::CenterLeft: case Anchor::Center: case Anchor::CenterRight:
wy += ty + th * 0.5f; break;
case Anchor::BottomLeft: case Anchor::BottomCenter: case Anchor::BottomRight:
wy += ty + th; break;
default: break;
}
} else {
for (Element *p = parent; p; p = p->parent)
wy += p->posY;
}
return wy;
}
bool Element::isPointInside(Renderer &r) const {
float wx = worldX();
float wy = worldY();
return r.isMouseOver(wx, wy, w, h);
}
void Element::updateTree(float dt) {
if (onUpdate) onUpdate(*this, dt);
for (auto &child : children)
child->updateTree(dt);
}
void Element::renderTree(Renderer &r) {
if (!visible) return;
r.pushTransform();
r.translate(posX, posY);
if (rotation != 0 || scaleX != 1 || scaleY != 1) {
r.translate(pivotX, pivotY);
if (rotation != 0) r.rotate(rotation);
if (scaleX != 1 || scaleY != 1) r.scale(scaleX, scaleY);
r.translate(-pivotX, -pivotY);
}
render(r);
for (auto &child : children)
child->renderTree(r);
r.popTransform();
}
void Element::handleInputTree(Renderer &r) {
if (!visible) return;
for (int i = static_cast<int>(children.size()) - 1; i >= 0; --i)
children[i]->handleInputTree(r);
handleInput(r);
}
void Element::render(Renderer &) {}
void Element::handleInput(Renderer &) {}
void Image::render(Renderer &r) {
if (texture < 0) return;
Color t = {tint.r, tint.g, tint.b, static_cast<uint8_t>(tint.a * alpha)};
float drawX = 0, drawY = 0, drawW = w, drawH = h;
if (preserveAspect) {
int tw = texW > 0 ? texW : r.textureWidth(texture);
int th = texH > 0 ? texH : r.textureHeight(texture);
if (tw > 0 && th > 0) {
float imgAR = (float)tw / (float)th;
float boxAR = w / h;
if (imgAR > boxAR) {
drawW = w;
drawH = w / imgAR;
} else {
drawH = h;
drawW = h * imgAR;
}
drawX = (w - drawW) * 0.5f;
drawY = (h - drawH) * 0.5f;
}
}
if (nineSlice) {
int sw = texW > 0 ? texW : r.textureWidth(texture);
int sh = texH > 0 ? texH : r.textureHeight(texture);
r.drawImage9Slice(texture, drawX, drawY, drawW, drawH,
sliceInsets[0], sliceInsets[1], sliceInsets[2], sliceInsets[3],
sw, sh, t);
} else if (useRegion) {
r.drawImageRegion(texture, drawX, drawY, drawW, drawH, u0, v0, u1, v1, t);
} else {
r.drawImage(texture, drawX, drawY, drawW, drawH, t);
}
}
void Text::render(Renderer &r) {
if (text.empty()) return;
Color col = {style.color.r, style.color.g, style.color.b,
static_cast<uint8_t>(style.color.a * alpha)};
Color shadow = {style.shadowColor.r, style.shadowColor.g, style.shadowColor.b,
static_cast<uint8_t>(style.shadowColor.a * alpha)};
float drawX = 0;
if (!wrap) {
if (style.align == Alignment::Center) {
float tw = r.measureText(text.c_str(), style.font);
drawX = (w - tw) * 0.5f;
} else if (style.align == Alignment::Right) {
float tw = r.measureText(text.c_str(), style.font);
drawX = w - tw;
}
}
if (wrap) {
if (style.shadowOffset > 0 && shadow.a > 0)
r.drawTextWrapped(text.c_str(), drawX + style.shadowOffset, style.shadowOffset,
w, shadow, style.font);
r.drawTextWrapped(text.c_str(), drawX, 0, w, col, style.font);
} else {
if (style.shadowOffset > 0 && shadow.a > 0)
r.drawText(text.c_str(), drawX + style.shadowOffset, style.shadowOffset,
shadow, style.font);
r.drawText(text.c_str(), drawX, 0, col, style.font);
}
}
void Panel::render(Renderer &r) {
if (style.backgroundTex >= 0) {
Color t = {255, 255, 255, static_cast<uint8_t>(255 * alpha)};
if (style.nineSlice) {
int sw = r.textureWidth(style.backgroundTex);
int sh = r.textureHeight(style.backgroundTex);
r.drawImage9Slice(style.backgroundTex, 0, 0, w, h,
style.sliceInsets[0], style.sliceInsets[1],
style.sliceInsets[2], style.sliceInsets[3],
sw, sh, t);
} else {
r.drawImage(style.backgroundTex, 0, 0, w, h, t);
}
} else if (style.backgroundColor.a > 0) {
Color col = {style.backgroundColor.r, style.backgroundColor.g, style.backgroundColor.b,
static_cast<uint8_t>(style.backgroundColor.a * alpha)};
r.drawRect(0, 0, w, h, col);
}
}
void Panel::renderTree(Renderer &r) {
if (!visible) return;
r.pushTransform();
r.translate(posX, posY);
render(r);
r.pushTransform();
r.translate(style.padding, style.padding);
for (auto &child : children)
child->renderTree(r);
r.popTransform();
r.popTransform();
}
void Button::render(Renderer &r) {
float wx = worldX();
float wy = worldY();
bool down = r.isMouseDown(wx, wy, w, h);
int tex = style.normalTex;
if (down && style.pressedTex >= 0)
tex = style.pressedTex;
else if (hovered_ && style.hoverTex >= 0)
tex = style.hoverTex;
if (tex >= 0) {
Color t = {255, 255, 255, static_cast<uint8_t>(255 * alpha)};
r.drawImage(tex, 0, 0, w, h, t);
}
if (!label.empty()) {
Color textCol = hovered_ ? style.hoverTextColor : style.textColor;
Color col = {textCol.r, textCol.g, textCol.b,
static_cast<uint8_t>(textCol.a * alpha)};
if (style.shadowOffset > 0 && style.shadowColor.a > 0) {
Color sc = {style.shadowColor.r, style.shadowColor.g, style.shadowColor.b,
static_cast<uint8_t>(style.shadowColor.a * alpha)};
r.drawTextCentered(label.c_str(), style.shadowOffset, style.shadowOffset,
w, h, sc, style.font);
}
r.drawTextCentered(label.c_str(), 0, 0, w, h, col, style.font);
}
}
void Button::handleInput(Renderer &r) {
hovered_ = isPointInside(r) || focused;
float wx = worldX();
float wy = worldY();
if (r.isMouseClicked(wx, wy, w, h) && onClick)
onClick();
if (focused && r.isKeyClicked(257) && onClick)
onClick();
}
void Slider::render(Renderer &r) {
Color t = {255, 255, 255, static_cast<uint8_t>(255 * alpha)};
float range = maxVal - minVal;
float norm = (range > 0) ? (value - minVal) / range : 0;
if (style.bgTex >= 0)
r.drawImage(style.bgTex, 0, 0, w, h, t);
if (style.trackTex >= 0)
r.drawImage(style.trackTex, 0, 0, w, h, t);
float fillW = w * norm;
if (style.fillTex >= 0 && fillW > 0)
r.drawImage(style.fillTex, 0, 0, fillW, h, t);
if (style.thumbTex >= 0) {
float thumbSize = h;
float thumbX = fillW - thumbSize * 0.5f;
thumbX = std::max(0.0f, std::min(thumbX, w - thumbSize));
r.drawImage(style.thumbTex, thumbX, 0, thumbSize, thumbSize, t);
}
if (!label.empty()) {
Color shadow = {15, 15, 15, t.a};
float textY = (h - r.fontHeight(style.font)) * 0.5f;
r.drawTextCentered(label.c_str(), 1, textY + 1, w, r.fontHeight(style.font), shadow, style.font);
r.drawTextCentered(label.c_str(), 0, textY, w, r.fontHeight(style.font), style.textColor, style.font);
}
}
void Slider::handleInput(Renderer &r) {
float wx = worldX();
float wy = worldY();
bool over = r.isMouseOver(wx, wy, w, h);
if (r.isMouseClickedAnywhere() && over)
dragging_ = true;
if (!r.isMouseDownAnywhere())
dragging_ = false;
if (dragging_) {
float localX = r.mouseX() - wx;
localX = std::max(0.0f, std::min(w, localX));
float range = maxVal - minVal;
float newVal = minVal + (localX / w) * range;
newVal = std::max(minVal, std::min(maxVal, newVal));
if (newVal != value) {
value = newVal;
if (onChange) onChange(value);
}
}
}
void Checkbox::render(Renderer &r) {
Color t = {255, 255, 255, static_cast<uint8_t>(255 * alpha)};
float boxSize = style.size;
int bgId = (hovered_ && style.hoverTex >= 0) ? style.hoverTex : style.bgTex;
if (bgId >= 0)
r.drawImage(bgId, 0, 0, boxSize, boxSize, t);
if (checked && style.checkTex >= 0)
r.drawImage(style.checkTex, 0, 0, boxSize, boxSize, t);
if (!label.empty()) {
Color col = {style.textColor.r, style.textColor.g, style.textColor.b,
static_cast<uint8_t>(style.textColor.a * alpha)};
float textX = boxSize + 8.0f;
float textY = (boxSize - r.fontHeight(style.font)) * 0.5f;
r.drawText(label.c_str(), textX, textY, col, style.font);
}
}
void Checkbox::handleInput(Renderer &r) {
float wx = worldX();
float wy = worldY();
hovered_ = r.isMouseOver(wx, wy, w, h) || focused;
if (r.isMouseClicked(wx, wy, w, h) || (focused && r.isKeyClicked(257))) {
checked = !checked;
if (onChange) onChange(checked);
}
}
void TextInput::render(Renderer &r) {
Color t = {255, 255, 255, static_cast<uint8_t>(255 * alpha)};
if (style.bgTex >= 0)
r.drawImage(style.bgTex, 0, 0, w, h, t);
int borderId = active ? style.activeBorderTex : style.borderTex;
if (borderId >= 0)
r.drawImage(borderId, 0, 0, w, h, t);
float textY = (h - r.fontHeight(style.font)) * 0.5f;
float pad = 6.0f;
r.pushScissor(worldX() + pad, worldY(), w - pad * 2, h);
if (text.empty() && !active && !placeholder.empty()) {
Color pc = {style.placeholderColor.r, style.placeholderColor.g,
style.placeholderColor.b,
static_cast<uint8_t>(style.placeholderColor.a * alpha)};
r.drawText(placeholder.c_str(), pad, textY, pc, style.font);
} else if (!text.empty()) {
Color tc = {style.textColor.r, style.textColor.g, style.textColor.b,
static_cast<uint8_t>(style.textColor.a * alpha)};
r.drawText(text.c_str(), pad, textY, tc, style.font);
}
if (active) {
std::string beforeCursor = text.substr(0, cursorPos);
float cursorX = pad + r.measureText(beforeCursor.c_str(), style.font);
Color cc = {style.textColor.r, style.textColor.g, style.textColor.b,
static_cast<uint8_t>(style.textColor.a * alpha)};
r.drawRect(cursorX, textY, 1.5f, r.fontHeight(style.font), cc);
}
r.popScissor();
}
void TextInput::handleInput(Renderer &r) {
float wx = worldX();
float wy = worldY();
if (r.isMouseClickedAnywhere()) {
active = r.isMouseOver(wx, wy, w, h);
if (active)
cursorPos = static_cast<int>(text.size());
}
if (!active) return;
int ch;
while ((ch = r.pollChar()) >= 0) {
if (ch >= 32 && static_cast<int>(text.size()) < maxLength) {
text.insert(text.begin() + cursorPos, static_cast<char>(ch));
cursorPos++;
if (onChange) onChange(text);
}
}
if (r.isKeyClicked(259)) {
if (cursorPos > 0 && !text.empty()) {
text.erase(text.begin() + cursorPos - 1);
cursorPos--;
if (onChange) onChange(text);
}
}
if (r.isKeyClicked(262)) {
if (cursorPos < static_cast<int>(text.size()))
cursorPos++;
}
if (r.isKeyClicked(263)) {
if (cursorPos > 0)
cursorPos--;
}
}
void ScrollList::render(Renderer &r) {
if (bgColor.a > 0) {
Color bg = {bgColor.r, bgColor.g, bgColor.b,
static_cast<uint8_t>(bgColor.a * alpha)};
r.drawRect(0, 0, w, h, bg);
}
r.pushScissor(worldX(), worldY(), w, h);
int startIdx = static_cast<int>(scrollOffset / itemHeight);
if (startIdx < 0) startIdx = 0;
int endIdx = startIdx + visibleCount + 1;
if (endIdx > static_cast<int>(items.size())) endIdx = static_cast<int>(items.size());
for (int i = startIdx; i < endIdx; i++) {
float iy = i * itemHeight - scrollOffset;
if (i == selectedIndex) {
Color sc = {selectedColor.r, selectedColor.g, selectedColor.b,
static_cast<uint8_t>(selectedColor.a * alpha)};
r.drawRect(0, iy, w, itemHeight, sc);
} else if (i == hoveredIndex_) {
Color hc = {hoverColor.r, hoverColor.g, hoverColor.b,
static_cast<uint8_t>(hoverColor.a * alpha)};
r.drawRect(0, iy, w, itemHeight, hc);
}
Color tc = {textColor.r, textColor.g, textColor.b,
static_cast<uint8_t>(textColor.a * alpha)};
float textY = iy + (itemHeight - r.fontHeight(font)) * 0.5f;
r.drawText(items[i].label.c_str(), 8.0f, textY, tc, font);
}
r.popScissor();
}
void ScrollList::handleInput(Renderer &r) {
float wx = worldX();
float wy = worldY();
bool over = r.isMouseOver(wx, wy, w, h);
hoveredIndex_ = -1;
if (over) {
float localY = r.mouseY() - wy + scrollOffset;
int idx = static_cast<int>(localY / itemHeight);
if (idx >= 0 && idx < static_cast<int>(items.size()))
hoveredIndex_ = idx;
float dy = r.scrollDeltaY();
if (dy != 0) {
scrollOffset -= dy * itemHeight * 0.5f;
float maxScroll = std::max(0.0f, static_cast<float>(items.size()) * itemHeight - h);
scrollOffset = std::max(0.0f, std::min(maxScroll, scrollOffset));
}
}
if (over && r.isMouseClickedAnywhere() && hoveredIndex_ >= 0) {
selectedIndex = hoveredIndex_;
if (onSelect) onSelect(selectedIndex);
}
if (over && r.isKeyClicked(265) && selectedIndex > 0) {
selectedIndex--;
if (onSelect) onSelect(selectedIndex);
float top = selectedIndex * itemHeight;
if (top < scrollOffset) scrollOffset = top;
}
if (over && r.isKeyClicked(264) && selectedIndex < static_cast<int>(items.size()) - 1) {
selectedIndex++;
if (onSelect) onSelect(selectedIndex);
float bottom = (selectedIndex + 1) * itemHeight;
if (bottom > scrollOffset + h) scrollOffset = bottom - h;
}
}
void VStack::render(Renderer &) {
float cy = 0;
for (auto &child : children) {
child->posY = cy;
cy += child->h + spacing;
}
}
Button &VStack::button(const std::string &lbl, const ButtonStyle &btnStyle,
std::function<void()> clickFn) {
auto b = std::make_unique<Button>();
b->label = lbl;
b->style = btnStyle;
b->onClick = std::move(clickFn);
b->w = btnStyle.width;
b->h = btnStyle.height;
auto *ptr = b.get();
addChild(std::move(b));
return *ptr;
}
Text &VStack::text(const std::string &str, const TextStyle &textStyle) {
auto t = std::make_unique<Text>();
t->text = str;
t->style = textStyle;
t->w = 400;
t->h = 30;
auto *ptr = t.get();
addChild(std::move(t));
return *ptr;
}
Slider &VStack::slider(const SliderStyle &sliderStyle, float min, float max, float val,
std::function<void(float)> changeFn) {
auto s = std::make_unique<Slider>();
s->style = sliderStyle;
s->minVal = min;
s->maxVal = max;
s->value = val;
s->onChange = std::move(changeFn);
s->w = sliderStyle.width;
s->h = sliderStyle.height;
auto *ptr = s.get();
addChild(std::move(s));
return *ptr;
}
Checkbox &VStack::checkbox(const std::string &lbl, const CheckboxStyle &cbStyle, bool isChecked,
std::function<void(bool)> changeFn) {
auto c = std::make_unique<Checkbox>();
c->label = lbl;
c->style = cbStyle;
c->checked = isChecked;
c->onChange = std::move(changeFn);
c->w = cbStyle.size + 8.0f + 200.0f;
c->h = cbStyle.size;
auto *ptr = c.get();
addChild(std::move(c));
return *ptr;
}
Image &VStack::image(int tex, float width, float height) {
auto img = std::make_unique<Image>();
img->texture = tex;
img->w = width;
img->h = height;
auto *ptr = img.get();
addChild(std::move(img));
return *ptr;
}
void HStack::render(Renderer &) {
float cx = 0;
for (auto &child : children) {
child->posX = cx;
cx += child->w + spacing;
}
}
Button &HStack::button(const std::string &lbl, const ButtonStyle &btnStyle,
std::function<void()> clickFn) {
auto b = std::make_unique<Button>();
b->label = lbl;
b->style = btnStyle;
b->onClick = std::move(clickFn);
b->w = btnStyle.width;
b->h = btnStyle.height;
auto *ptr = b.get();
addChild(std::move(b));
return *ptr;
}
Text &HStack::text(const std::string &str, const TextStyle &textStyle) {
auto t = std::make_unique<Text>();
t->text = str;
t->style = textStyle;
t->w = 400;
t->h = 30;
auto *ptr = t.get();
addChild(std::move(t));
return *ptr;
}
Image &HStack::image(int tex, float width, float height) {
auto img = std::make_unique<Image>();
img->texture = tex;
img->w = width;
img->h = height;
auto *ptr = img.get();
addChild(std::move(img));
return *ptr;
}
Scene::Scene() = default;
Scene::Scene(const std::string &sceneName) : name(sceneName) {}
void Scene::render(Renderer &r) {
root.updateTree(0.016f);
float s = (float)r.windowH() / baseH;
float virtualW = (float)r.windowW() / s;
float offsetX = (virtualW - baseW) * 0.5f;
r.pushTransform();
r.scale(s, s);
r.translate(offsetX, 0);
root.renderTree(r);
r.popTransform();
}
static void collectFocusable(Element &el, std::vector<Element *> &out) {
if (!el.visible) return;
if (el.focusable) out.push_back(&el);
for (auto &child : el.children)
collectFocusable(*child, out);
}
void Scene::handleInput(Renderer &r) {
if (onBack && r.isKeyClicked(256))
onBack();
std::vector<Element *> focusables;
collectFocusable(root, focusables);
bool upPressed = r.isKeyClicked(265);
bool downPressed = r.isKeyClicked(264);
if ((upPressed || downPressed) && !focusables.empty()) {
for (auto *el : focusables) el->focused = false;
if (focusIndex < 0) {
focusIndex = downPressed ? 0 : (int)focusables.size() - 1;
} else {
if (downPressed) focusIndex++;
if (upPressed) focusIndex--;
if (focusIndex < 0) focusIndex = (int)focusables.size() - 1;
if (focusIndex >= (int)focusables.size()) focusIndex = 0;
}
focusables[focusIndex]->focused = true;
}
if (r.isMouseClickedAnywhere()) {
for (auto *el : focusables) el->focused = false;
focusIndex = -1;
}
float s = (float)r.windowH() / baseH;
float virtualW = (float)r.windowW() / s;
float offsetX = (virtualW - baseW) * 0.5f;
float origMX = r.mouseX();
float origMY = r.mouseY();
r.feedMousePosition(origMX / s + offsetX, origMY / s);
root.handleInputTree(r);
r.feedMousePosition(origMX, origMY);
}
Element *Scene::findElement(const std::string &searchName) const {
return root.findChild(searchName);
}
Image &Scene::background(int tex) {
auto img = std::make_unique<Image>();
img->texture = tex;
img->w = 1280;
img->h = 720;
auto *ptr = img.get();
root.addChild(std::move(img));
return *ptr;
}
Image &Scene::image(int tex, Rect bounds) {
auto img = std::make_unique<Image>();
img->texture = tex;
img->posX = bounds.x;
img->posY = bounds.y;
img->w = bounds.w;
img->h = bounds.h;
auto *ptr = img.get();
root.addChild(std::move(img));
return *ptr;
}
Text &Scene::text(const std::string &str, Rect bounds, const TextStyle &textStyle) {
auto t = std::make_unique<Text>();
t->text = str;
t->style = textStyle;
t->posX = bounds.x;
t->posY = bounds.y;
t->w = bounds.w;
t->h = bounds.h;
auto *ptr = t.get();
root.addChild(std::move(t));
return *ptr;
}
Panel &Scene::panel(Rect bounds, const PanelStyle &panelStyle) {
auto p = std::make_unique<Panel>();
p->style = panelStyle;
p->posX = bounds.x;
p->posY = bounds.y;
p->w = bounds.w;
p->h = bounds.h;
auto *ptr = p.get();
root.addChild(std::move(p));
return *ptr;
}
Button &Scene::button(const std::string &lbl, Rect bounds, const ButtonStyle &btnStyle,
std::function<void()> clickFn) {
auto b = std::make_unique<Button>();
b->label = lbl;
b->style = btnStyle;
b->onClick = std::move(clickFn);
b->posX = bounds.x;
b->posY = bounds.y;
b->w = bounds.w;
b->h = bounds.h;
auto *ptr = b.get();
root.addChild(std::move(b));
return *ptr;
}
VStack &Scene::vstack(float x, float y, float sp) {
auto s = std::make_unique<VStack>();
s->posX = x;
s->posY = y;
s->spacing = sp;
auto *ptr = s.get();
root.addChild(std::move(s));
return *ptr;
}
HStack &Scene::hstack(float x, float y, float sp) {
auto s = std::make_unique<HStack>();
s->posX = x;
s->posY = y;
s->spacing = sp;
auto *ptr = s.get();
root.addChild(std::move(s));
return *ptr;
}
Slider &Scene::slider(Rect bounds, const SliderStyle &sliderStyle,
float min, float max, float val,
std::function<void(float)> changeFn) {
auto s = std::make_unique<Slider>();
s->style = sliderStyle;
s->minVal = min;
s->maxVal = max;
s->value = val;
s->onChange = std::move(changeFn);
s->posX = bounds.x;
s->posY = bounds.y;
s->w = bounds.w;
s->h = bounds.h;
auto *ptr = s.get();
root.addChild(std::move(s));
return *ptr;
}
Checkbox &Scene::checkbox(const std::string &lbl, float x, float y,
const CheckboxStyle &cbStyle, bool isChecked,
std::function<void(bool)> changeFn) {
auto c = std::make_unique<Checkbox>();
c->label = lbl;
c->style = cbStyle;
c->checked = isChecked;
c->onChange = std::move(changeFn);
c->posX = x;
c->posY = y;
c->w = cbStyle.size + 8.0f + 200.0f;
c->h = cbStyle.size;
auto *ptr = c.get();
root.addChild(std::move(c));
return *ptr;
}
TextInput &Scene::textInput(Rect bounds, const TextInputStyle &inputStyle,
const std::string &placeholderText,
std::function<void(const std::string &)> changeFn) {
auto t = std::make_unique<TextInput>();
t->style = inputStyle;
t->placeholder = placeholderText;
t->onChange = std::move(changeFn);
t->posX = bounds.x;
t->posY = bounds.y;
t->w = bounds.w;
t->h = bounds.h;
auto *ptr = t.get();
root.addChild(std::move(t));
return *ptr;
}
ScrollList &Scene::scrollList(Rect bounds, int visCount, float iHeight) {
auto s = std::make_unique<ScrollList>();
s->visibleCount = visCount;
s->itemHeight = iHeight;
s->posX = bounds.x;
s->posY = bounds.y;
s->w = bounds.w;
s->h = bounds.h;
auto *ptr = s.get();
root.addChild(std::move(s));
return *ptr;
}
void SceneManager::push(std::unique_ptr<Scene> scene) {
if (insideHandleInput_)
pendingPushes_.push_back(std::move(scene));
else
stack_.push_back(std::move(scene));
}
void SceneManager::push(Scene &&scene) {
push(std::make_unique<Scene>(std::move(scene)));
}
std::unique_ptr<Scene> SceneManager::pop() {
if (insideHandleInput_) {
pendingPops_++;
return nullptr;
}
if (stack_.empty()) return nullptr;
auto scene = std::move(stack_.back());
stack_.pop_back();
return scene;
}
Scene *SceneManager::top() const {
if (stack_.empty()) return nullptr;
return stack_.back().get();
}
bool SceneManager::empty() const { return stack_.empty(); }
int SceneManager::depth() const { return static_cast<int>(stack_.size()); }
void SceneManager::render(Renderer &r) {
if (!stack_.empty())
stack_.back()->render(r);
}
void SceneManager::handleInput(Renderer &r) {
insideHandleInput_ = true;
if (!stack_.empty())
stack_.back()->handleInput(r);
insideHandleInput_ = false;
flushDeferred();
}
void SceneManager::flushDeferred() {
while (pendingPops_ > 0 && !stack_.empty()) {
stack_.pop_back();
pendingPops_--;
}
pendingPops_ = 0;
for (auto &s : pendingPushes_)
stack_.push_back(std::move(s));
pendingPushes_.clear();
}
} // namespace vui