2025-07-06 20:43:53 +03:00
|
|
|
#pragma once
|
|
|
|
|
#include "Element.hpp"
|
2025-11-10 14:45:51 +03:00
|
|
|
#include "../../RichText/TextBox.hpp"
|
2025-10-20 17:30:36 +03:00
|
|
|
#include <limits>
|
2025-11-04 17:31:23 +03:00
|
|
|
#include <modules/skparagraph/include/TextStyle.h>
|
2025-11-15 14:05:05 +03:00
|
|
|
#include "../../FontData.hpp"
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
namespace GUIStuff {
|
|
|
|
|
|
|
|
|
|
template <typename T> class TextBox : public Element {
|
|
|
|
|
public:
|
2025-11-05 14:55:22 +03:00
|
|
|
bool update(UpdateInputData& io, T* newData, const std::function<std::optional<T>(const std::string&)>& newFromStr, const std::function<std::string(const T&)> newToStr, bool newSingleLine, bool updateEveryEdit, const std::function<void(SelectionHelper&)>& elemUpdate) {
|
|
|
|
|
bool isUpdating = false;
|
|
|
|
|
|
2025-10-20 17:30:36 +03:00
|
|
|
if(!textbox || !cur)
|
|
|
|
|
force_update_textbox(io, false);
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
data = newData;
|
|
|
|
|
fromStr = newFromStr;
|
|
|
|
|
toStr = newToStr;
|
2025-10-20 17:30:36 +03:00
|
|
|
force_update_textbox(io, false);
|
2025-07-06 20:43:53 +03:00
|
|
|
|
2025-11-15 14:05:05 +03:00
|
|
|
textbox->set_width(std::numeric_limits<float>::max());
|
2025-11-16 09:41:09 +03:00
|
|
|
textbox->set_text_direction_between(0, 0, textbox->get_suggested_direction(0));
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
CLAY({
|
|
|
|
|
.layout = {
|
2025-11-15 14:05:05 +03:00
|
|
|
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(io.fontSize * 2)), .height = CLAY_SIZING_FIXED(static_cast<float>(io.fontSize * 1.25f))}
|
2025-07-06 20:43:53 +03:00
|
|
|
},
|
|
|
|
|
.custom = { .customData = this }
|
|
|
|
|
}) {
|
|
|
|
|
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
|
|
|
|
if(data && selection.selected) {
|
2025-10-20 17:30:36 +03:00
|
|
|
io.richTextBoxEdit(textbox, cur);
|
|
|
|
|
textbox->process_mouse_left_button(*cur, io.mouse.pos - bb.min, io.mouse.leftClick, io.mouse.leftHeld, io.key.leftShift);
|
2025-11-08 20:50:23 +03:00
|
|
|
if((updateEveryEdit && textbox->inputChangedTextBox) || io.key.enter) {
|
2025-11-05 14:55:22 +03:00
|
|
|
update_on_edit();
|
|
|
|
|
isUpdating = true;
|
|
|
|
|
}
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
if(data && selection.justUnselected) {
|
2025-10-20 17:30:36 +03:00
|
|
|
std::optional<T> dataToAssign = fromStr(textbox->get_string());
|
2025-07-06 20:43:53 +03:00
|
|
|
if(dataToAssign)
|
|
|
|
|
*data = dataToAssign.value();
|
2025-10-20 17:30:36 +03:00
|
|
|
force_update_textbox(io, true);
|
2025-11-05 14:55:22 +03:00
|
|
|
isUpdating = true;
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
if(elemUpdate)
|
2025-10-06 09:07:11 +03:00
|
|
|
elemUpdate(selection);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
2025-11-05 14:55:22 +03:00
|
|
|
|
2025-11-08 20:50:23 +03:00
|
|
|
if(textbox)
|
|
|
|
|
textbox->inputChangedTextBox = false;
|
|
|
|
|
|
2025-11-05 14:55:22 +03:00
|
|
|
return isUpdating;
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override {
|
2025-10-20 17:30:36 +03:00
|
|
|
if(!data || !textbox || !cur)
|
2025-07-06 20:43:53 +03:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
bb = get_bb(command);
|
|
|
|
|
|
|
|
|
|
canvas->save();
|
|
|
|
|
SkRect r = SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height());
|
|
|
|
|
|
|
|
|
|
canvas->drawRect(r, SkPaint(io.theme->backColor2));
|
2025-11-01 16:07:19 +03:00
|
|
|
SkPaint outline(selection.selected ? io.theme->fillColor1 : io.theme->backColor2);
|
2025-07-06 20:43:53 +03:00
|
|
|
outline.setStroke(true);
|
|
|
|
|
outline.setStrokeWidth(2.0f);
|
|
|
|
|
canvas->drawRoundRect(r, 2.0f, 2.0f, outline);
|
|
|
|
|
|
|
|
|
|
canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()));
|
|
|
|
|
|
2025-11-15 14:05:05 +03:00
|
|
|
float yOffset = bb.height() * 0.5f;
|
|
|
|
|
yOffset -= textbox->get_height() * 0.5f;
|
2025-07-06 20:43:53 +03:00
|
|
|
|
2025-11-10 14:45:51 +03:00
|
|
|
RichText::TextBox::PaintOpts paintOpts;
|
2025-11-15 18:59:58 +03:00
|
|
|
if(selection.selected) {
|
2025-10-20 17:30:36 +03:00
|
|
|
paintOpts.cursor = *cur;
|
2025-11-15 18:59:58 +03:00
|
|
|
SkRect cursorRect = textbox->get_cursor_rect(cur->pos);
|
|
|
|
|
canvas->translate(bb.min.x() - std::max(cursorRect.fRight - bb.width(), -2.0f), bb.min.y() + yOffset);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
canvas->translate(bb.min.x() + 2.0f, bb.min.y() + yOffset);
|
2025-10-20 17:30:36 +03:00
|
|
|
paintOpts.cursorColor = {io.theme->fillColor1.fR, io.theme->fillColor1.fG, io.theme->fillColor1.fB};
|
2025-07-06 20:43:53 +03:00
|
|
|
|
2025-11-04 17:31:23 +03:00
|
|
|
skia::textlayout::TextStyle tStyle;
|
2025-11-16 10:42:01 +03:00
|
|
|
tStyle.setFontFamilies(io.fonts->get_default_font_families());
|
2025-11-04 17:31:23 +03:00
|
|
|
tStyle.setFontSize(io.fontSize);
|
|
|
|
|
tStyle.setForegroundPaint(SkPaint{io.theme->frontColor1});
|
|
|
|
|
textbox->set_initial_text_style(tStyle);
|
2025-11-04 10:58:26 +03:00
|
|
|
|
2025-10-20 17:30:36 +03:00
|
|
|
textbox->paint(canvas, paintOpts);
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
canvas->restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SelectionHelper selection;
|
|
|
|
|
private:
|
2025-10-20 17:30:36 +03:00
|
|
|
void force_update_textbox(UpdateInputData& io, bool reallyForce) {
|
|
|
|
|
if(data && (*data == oldData) && !reallyForce && textbox && cur)
|
2025-07-06 20:43:53 +03:00
|
|
|
return;
|
|
|
|
|
|
2025-11-10 14:45:51 +03:00
|
|
|
textbox = std::make_shared<RichText::TextBox>();
|
2025-11-16 10:42:01 +03:00
|
|
|
textbox->set_font_data(io.fonts);
|
2025-11-15 14:05:05 +03:00
|
|
|
textbox->set_allow_newlines(false);
|
2025-11-04 10:58:26 +03:00
|
|
|
|
2025-11-10 14:45:51 +03:00
|
|
|
cur = std::make_shared<RichText::TextBox::Cursor>();
|
2025-10-20 17:30:36 +03:00
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
if(data) {
|
2025-10-20 17:30:36 +03:00
|
|
|
cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*data));
|
2025-07-06 20:43:53 +03:00
|
|
|
oldData = *data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 14:55:22 +03:00
|
|
|
void update_on_edit() {
|
|
|
|
|
std::optional<T> dataToAssign = fromStr(textbox->get_string());
|
|
|
|
|
if(dataToAssign) {
|
|
|
|
|
*data = dataToAssign.value();
|
|
|
|
|
oldData = *data;
|
2025-09-06 19:06:30 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
T* data = nullptr;
|
|
|
|
|
T oldData;
|
|
|
|
|
|
|
|
|
|
std::function<std::optional<T>(const std::string&)> fromStr;
|
|
|
|
|
std::function<std::string(const T&)> toStr;
|
|
|
|
|
|
2025-11-10 14:45:51 +03:00
|
|
|
std::shared_ptr<RichText::TextBox> textbox;
|
|
|
|
|
std::shared_ptr<RichText::TextBox::Cursor> cur;
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
SCollision::AABB<float> bb;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|