mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-11 05:51:51 +00:00
Add textbox selection and layout API for custom rendering
This commit is contained in:
parent
bb6bd27b94
commit
61b3f8f973
7 changed files with 393 additions and 5 deletions
|
|
@ -99,6 +99,9 @@ Properties:
|
||||||
| textColor | vec4 | yes | yes | text color |
|
| textColor | vec4 | yes | yes | text color |
|
||||||
| syntax | string | yes | yes | syntax highlighting ("lua" - Lua) |
|
| syntax | string | yes | yes | syntax highlighting ("lua" - Lua) |
|
||||||
| markup | string | yes | yes | text markup language ("md" - Markdown) |
|
| markup | string | yes | yes | text markup language ("md" - Markdown) |
|
||||||
|
| selection | ivec2 | yes | yes | selection anchor and caret; see below |
|
||||||
|
| externalRendering | bool | yes | yes | external text rendering (default: false) |
|
||||||
|
| textLayout | table | yes | no | snapshot of visible text layout; see below |
|
||||||
|
|
||||||
\* - false only
|
\* - false only
|
||||||
|
|
||||||
|
|
@ -110,6 +113,59 @@ Methods:
|
||||||
| lineAt(pos: int) -> int | determines the line number by position in the text |
|
| lineAt(pos: int) -> int | determines the line number by position in the text |
|
||||||
| linePos(line: int) -> int | determines the position of the beginning of the line in the text |
|
| linePos(line: int) -> int | determines the position of the beginning of the line in the text |
|
||||||
|
|
||||||
|
### Selection and external rendering
|
||||||
|
|
||||||
|
- `selection` (read/write): `{anchor, caret}`, preserving direction. Indices
|
||||||
|
are zero-based native caret character units, not UTF-8 byte offsets. Setters
|
||||||
|
require finite integers, clamp to the text length and reveal the caret,
|
||||||
|
including immediately after replacing text. Setting selection does not
|
||||||
|
edit text or reset undo history.
|
||||||
|
- `externalRendering` (read/write boolean, default `false`): hides native text,
|
||||||
|
caret, selection and current-line highlight. Input, hit testing, layout,
|
||||||
|
scrolling, background, line numbers and scrollbar remain native. Setting it
|
||||||
|
back to `false` restores native rendering without replacing the textbox.
|
||||||
|
Markup and external rendering cannot be combined: either setter rejects it.
|
||||||
|
- `textLayout` (read-only table): a snapshot of the current visible layout.
|
||||||
|
`ready=false` means the font is unavailable or markup is active. Otherwise:
|
||||||
|
- `text`: the value of `textbox.text`, including `placeholder` when input is empty;
|
||||||
|
`font`: font name.
|
||||||
|
- `lineHeight`: row pitch; `textHeight`: height to use for a label displaying a row.
|
||||||
|
- `caretRect`: `{x,y,cellWidth,rowHeight}` at the moving selection endpoint;
|
||||||
|
at end-of-line the cell width is the space advance. This is geometry,
|
||||||
|
independent of focus, editability and blink phase.
|
||||||
|
- `selectionRects`: `{x, y, width, height}` rectangles for visible selected rows,
|
||||||
|
including selected newline cells.
|
||||||
|
- `lines`: visible rows `{start, text, pos={x,y}, advances={...}}`;
|
||||||
|
`start` is a native caret index, text excludes the trailing newline,
|
||||||
|
`advances` contains the native width of each character. Empty input may
|
||||||
|
expose the hint as display rows; it does not replace `text` in the snapshot.
|
||||||
|
|
||||||
|
Coordinates are relative to the textbox, include scrolling and alignment,
|
||||||
|
and may extend outside its bounds. External painters must clip to the field.
|
||||||
|
Snapshots are independent Lua tables; modifying them does not modify the box.
|
||||||
|
Read them again after input, resizing or scrolling. Syntax colors are not
|
||||||
|
exported; the external painter supplies its own colors. Indexing and text input
|
||||||
|
follow the existing textbox behavior.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local field = document.input
|
||||||
|
field.text = "abcdef"
|
||||||
|
field.selection = {5, 2}
|
||||||
|
local anchor, caret = unpack(field.selection)
|
||||||
|
|
||||||
|
field.externalRendering = true
|
||||||
|
local layout = field.textLayout
|
||||||
|
if layout.ready then
|
||||||
|
for _, line in ipairs(layout.lines) do
|
||||||
|
print(line.start, line.text, line.pos[1], line.pos[2])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example the caret is at position 2 and the selection anchor is at
|
||||||
|
position 5. A negative selection index is clamped to zero; unlike `caret`,
|
||||||
|
it does not address characters from the end of the text.
|
||||||
|
|
||||||
## Slider (trackbar)
|
## Slider (trackbar)
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,9 @@ document["worlds-panel"]:clear()
|
||||||
| textColor | vec4 | да | да | цвет текста |
|
| textColor | vec4 | да | да | цвет текста |
|
||||||
| syntax | string | да | да | подсветка синтаксиса ("lua" - Lua) |
|
| syntax | string | да | да | подсветка синтаксиса ("lua" - Lua) |
|
||||||
| markup | string | да | да | язык разметки текста ("md" - Markdown) |
|
| markup | string | да | да | язык разметки текста ("md" - Markdown) |
|
||||||
|
| selection | ivec2 | да | да | закреплённый конец выделения и каретка; см. ниже |
|
||||||
|
| externalRendering | bool | да | да | внешняя отрисовка текста (по умолчанию false) |
|
||||||
|
| textLayout | table | да | нет | снимок видимой раскладки текста; см. ниже |
|
||||||
|
|
||||||
\* - только false
|
\* - только false
|
||||||
|
|
||||||
|
|
@ -110,6 +113,58 @@ document["worlds-panel"]:clear()
|
||||||
| lineAt(pos: int) -> int | определяет номер строки по позиции в тексте |
|
| lineAt(pos: int) -> int | определяет номер строки по позиции в тексте |
|
||||||
| linePos(line: int) -> int | определяет позицию начала строки в тексте |
|
| linePos(line: int) -> int | определяет позицию начала строки в тексте |
|
||||||
|
|
||||||
|
### Выделение и внешняя отрисовка
|
||||||
|
|
||||||
|
- `selection` (чтение/запись): `{anchor, caret}` - направленное выделение.
|
||||||
|
Индексы с нуля в единицах нативного `caret`, не байтах UTF-8. Допускаются
|
||||||
|
только конечные целые числа; значения ограничиваются длиной текста.
|
||||||
|
Установка прокручивает поле к каретке, в том числе сразу после замены текста,
|
||||||
|
не меняет текст и не сбрасывает undo.
|
||||||
|
- `externalRendering` (boolean, чтение/запись, по умолчанию `false`): скрывает
|
||||||
|
текст, каретку, выделение и подсветку текущей строки. Ввод, hit testing,
|
||||||
|
раскладка, прокрутка, фон, номера строк и скроллбар остаются нативными.
|
||||||
|
`false` возвращает нативную отрисовку без пересоздания поля. Совмещение с
|
||||||
|
`markup` запрещено в обоих направлениях и вызывает ошибку.
|
||||||
|
- `textLayout` (таблица, только чтение): снимок видимой раскладки.
|
||||||
|
`ready=false`, если шрифт недоступен либо используется markup. Иначе:
|
||||||
|
- `text` - значение `textbox.text`, включая `placeholder` при пустом вводе;
|
||||||
|
`font` - имя шрифта;
|
||||||
|
- `lineHeight` - шаг строки, `textHeight` - высота label для отрисовки строки;
|
||||||
|
- `caretRect={x,y,cellWidth,rowHeight}` - геометрия каретки, без учёта фокуса,
|
||||||
|
editable и мигания; на конце строки ширина ячейки равна ширине пробела;
|
||||||
|
- `selectionRects` - прямоугольники `{x, y, width, height}` для видимых
|
||||||
|
выделенных строк, включая ячейки переводов строк;
|
||||||
|
- `lines` - видимые строки `{start,text,pos={x,y},advances={...}}`: нативный
|
||||||
|
индекс начала, текст без завершающего перевода строки, позиция и ширины
|
||||||
|
отдельных символов. Для пустого ввода строки могут содержать hint,
|
||||||
|
при этом hint не подменяет поле `text` в снимке.
|
||||||
|
|
||||||
|
Координаты локальны textbox, учитывают выравнивание и прокрутку, могут выходить
|
||||||
|
за его границы. Внешняя отрисовка должна обрезаться областью поля. Снимок -
|
||||||
|
независимая Lua-таблица; её изменение не меняет textbox. После ввода, изменения
|
||||||
|
размеров и прокрутки нужно читать новый снимок. Цвета syntax не экспортируются:
|
||||||
|
внешний рендерер задаёт их самостоятельно. Индексация и ввод текста
|
||||||
|
следуют существующему поведению textbox.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local field = document.input
|
||||||
|
field.text = "abcdef"
|
||||||
|
field.selection = {5, 2}
|
||||||
|
local anchor, caret = unpack(field.selection)
|
||||||
|
|
||||||
|
field.externalRendering = true
|
||||||
|
local layout = field.textLayout
|
||||||
|
if layout.ready then
|
||||||
|
for _, line in ipairs(layout.lines) do
|
||||||
|
print(line.start, line.text, line.pos[1], line.pos[2])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
В примере каретка находится на позиции 2, закреплённый конец выделения - на
|
||||||
|
позиции 5. Отрицательный индекс выделения ограничивается нулём; в отличие
|
||||||
|
от `caret`, он не задаёт позицию относительно конца текста.
|
||||||
|
|
||||||
## Ползунок (trackbar)
|
## Ползунок (trackbar)
|
||||||
|
|
||||||
Свойства:
|
Свойства:
|
||||||
|
|
|
||||||
|
|
@ -204,11 +204,18 @@ uint Label::getLinesNumber() const {
|
||||||
return cache.lines.size();
|
return cache.lines.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
glm::vec2 Label::getTextOrigin() const {
|
||||||
auto batch = pctx.getBatch2D();
|
return textOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Label::setRenderText(bool value) {
|
||||||
|
renderText = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Label::prepareLayout(const Assets& assets) {
|
||||||
auto font = assets.getShared<Font>(fontName);
|
auto font = assets.getShared<Font>(fontName);
|
||||||
if (font == nullptr) {
|
if (font == nullptr) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
cache.prepare(
|
cache.prepare(
|
||||||
font,
|
font,
|
||||||
|
|
@ -222,7 +229,6 @@ void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
||||||
if (cache.resetFlag) {
|
if (cache.resetFlag) {
|
||||||
cache.update(text, multiline, textWrap);
|
cache.update(text, multiline, textWrap);
|
||||||
}
|
}
|
||||||
batch->setColor(calcColor());
|
|
||||||
|
|
||||||
uint lineHeight = font->getLineHeight();
|
uint lineHeight = font->getLineHeight();
|
||||||
if (cache.lines.size() > 1) {
|
if (cache.lines.size() > 1) {
|
||||||
|
|
@ -248,6 +254,18 @@ void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
||||||
textYOffset = pos.y-calcPos().y;
|
textYOffset = pos.y-calcPos().y;
|
||||||
totalLineHeight = lineHeight;
|
totalLineHeight = lineHeight;
|
||||||
|
|
||||||
|
textOrigin = pos;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
||||||
|
if (!prepareLayout(assets) || !renderText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto batch = pctx.getBatch2D();
|
||||||
|
auto font = assets.getShared<Font>(fontName);
|
||||||
|
batch->setColor(calcColor());
|
||||||
|
glm::vec2 pos = textOrigin;
|
||||||
const auto& viewport = pctx.getViewport();
|
const auto& viewport = pctx.getViewport();
|
||||||
glm::vec4 bounds {0, 0, viewport.x, viewport.y};
|
glm::vec4 bounds {0, 0, viewport.x, viewport.y};
|
||||||
if (parent) {
|
if (parent) {
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ namespace gui {
|
||||||
|
|
||||||
/// @brief Auto resize label to fit text
|
/// @brief Auto resize label to fit text
|
||||||
bool autoresize = false;
|
bool autoresize = false;
|
||||||
|
bool renderText = true;
|
||||||
|
glm::vec2 textOrigin {0};
|
||||||
|
|
||||||
/// @brief Text markup language
|
/// @brief Text markup language
|
||||||
std::string markup;
|
std::string markup;
|
||||||
|
|
@ -111,6 +113,10 @@ namespace gui {
|
||||||
uint getLinesNumber() const;
|
uint getLinesNumber() const;
|
||||||
bool isFakeLine(size_t line) const;
|
bool isFakeLine(size_t line) const;
|
||||||
|
|
||||||
|
bool prepareLayout(const Assets& assets);
|
||||||
|
glm::vec2 getTextOrigin() const;
|
||||||
|
void setRenderText(bool value);
|
||||||
|
|
||||||
void draw(const DrawContext& pctx, const Assets& assets) override;
|
void draw(const DrawContext& pctx, const Assets& assets) override;
|
||||||
|
|
||||||
void textSupplier(wstringsupplier supplier);
|
void textSupplier(wstringsupplier supplier);
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ TextBox::~TextBox() = default;
|
||||||
void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
||||||
Container::draw(pctx, assets);
|
Container::draw(pctx, assets);
|
||||||
|
|
||||||
if (!isFocused() && !keepLineSelection) {
|
if (externalRendering || (!isFocused() && !keepLineSelection)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto& labelText = getText();
|
const auto& labelText = getText();
|
||||||
|
|
@ -1274,6 +1274,9 @@ const std::string& TextBox::getSyntax() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::setMarkup(std::string_view lang) {
|
void TextBox::setMarkup(std::string_view lang) {
|
||||||
|
if (externalRendering && !lang.empty()) {
|
||||||
|
throw std::runtime_error("external textbox rendering does not support markup");
|
||||||
|
}
|
||||||
markup = lang;
|
markup = lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1284,3 +1287,121 @@ const std::string& TextBox::getMarkup() const {
|
||||||
std::shared_ptr<Label> TextBox::getLabel() const {
|
std::shared_ptr<Label> TextBox::getLabel() const {
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t TextBox::getSelectionAnchor() const {
|
||||||
|
return selectionStart == selectionEnd
|
||||||
|
? std::min(caret, input.size())
|
||||||
|
: std::min(selectionOrigin, input.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBox::setSelection(ptrdiff_t anchor, ptrdiff_t head) {
|
||||||
|
auto clamp = [this](ptrdiff_t index) {
|
||||||
|
return std::min(
|
||||||
|
static_cast<size_t>(std::max<ptrdiff_t>(0, index)), input.size()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
selectionOrigin = clamp(anchor);
|
||||||
|
const auto end = clamp(head);
|
||||||
|
selectionStart = std::min(selectionOrigin, end);
|
||||||
|
selectionEnd = std::max(selectionOrigin, end);
|
||||||
|
setCaret(end);
|
||||||
|
resetMaxLocalCaret();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBox::setExternalRendering(bool value) {
|
||||||
|
if (value && !markup.empty()) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"external textbox rendering does not support markup"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
externalRendering = value;
|
||||||
|
label->setRenderText(!value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextBox::isExternalRendering() const {
|
||||||
|
return externalRendering;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextBox::prepareTextLayout(const Assets& assets) {
|
||||||
|
if (!markup.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto font = assets.getShared<Font>(label->getFontName());
|
||||||
|
if (font == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
|
||||||
|
refreshLabel();
|
||||||
|
if (!label->prepareLayout(assets)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
refreshLabel();
|
||||||
|
return label->prepareLayout(assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextBoxLayout TextBox::getTextLayout(const Assets& assets) {
|
||||||
|
TextBoxLayout result;
|
||||||
|
if (!prepareTextLayout(assets)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto font = assets.getShared<Font>(label->getFontName());
|
||||||
|
result.ready = true;
|
||||||
|
result.font = label->getFontName();
|
||||||
|
result.lineHeight = multiline
|
||||||
|
? font->getLineHeight() * label->getLineInterval()
|
||||||
|
: font->getLineHeight();
|
||||||
|
result.textHeight = font->getLineHeight() + font->getYOffset();
|
||||||
|
const auto origin = label->getTextOrigin() - calcPos();
|
||||||
|
const auto& display = label->getText();
|
||||||
|
const size_t head = std::min(caret, input.size());
|
||||||
|
const size_t lo = std::min(selectionStart, input.size());
|
||||||
|
const size_t hi = std::min(selectionEnd, input.size());
|
||||||
|
const uint caretLine = label->getLineByTextIndex(head);
|
||||||
|
const size_t caretStart = label->getTextLineOffset(caretLine);
|
||||||
|
result.caret = {
|
||||||
|
origin.x + font->calcWidth(
|
||||||
|
input,
|
||||||
|
std::min(caretStart, input.size()),
|
||||||
|
head - std::min(caretStart, head)
|
||||||
|
),
|
||||||
|
origin.y + caretLine * result.lineHeight,
|
||||||
|
head < input.size() && input[head] != L'\n'
|
||||||
|
? font->calcWidth(input, head, 1)
|
||||||
|
: font->calcWidth(L" "),
|
||||||
|
result.lineHeight
|
||||||
|
};
|
||||||
|
for (uint i = 0; i < label->getLinesNumber(); ++i) {
|
||||||
|
const float y = origin.y + i * result.lineHeight;
|
||||||
|
if (y + result.lineHeight < 0 || y >= getSize().y) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const size_t start = label->getTextLineOffset(i);
|
||||||
|
const size_t next = i + 1 < label->getLinesNumber()
|
||||||
|
? label->getTextLineOffset(i + 1)
|
||||||
|
: display.size();
|
||||||
|
size_t end = next;
|
||||||
|
if (end > start && display[end - 1] == L'\n') {
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
TextBoxLineLayout row;
|
||||||
|
row.start = start;
|
||||||
|
row.text = display.substr(start, end - start);
|
||||||
|
row.pos = {origin.x, y};
|
||||||
|
for (size_t j = start; j < end; ++j) {
|
||||||
|
row.advances.push_back(font->calcWidth(display, j, 1));
|
||||||
|
}
|
||||||
|
result.lines.push_back(std::move(row));
|
||||||
|
if (hi > start && lo < next && lo != hi) {
|
||||||
|
const size_t a = std::min(end, std::max(start, lo));
|
||||||
|
const size_t b = std::min(end, hi);
|
||||||
|
const float x =
|
||||||
|
origin.x + font->calcWidth(display, start, a - start);
|
||||||
|
float width = font->calcWidth(display, a, b - a);
|
||||||
|
if (hi > end && next > end) {
|
||||||
|
width += font->calcWidth(L" ");
|
||||||
|
}
|
||||||
|
result.selection.emplace_back(x, y, width, result.lineHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,23 @@ class Font;
|
||||||
class ActionsHistory;
|
class ActionsHistory;
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
|
struct TextBoxLineLayout {
|
||||||
|
size_t start = 0;
|
||||||
|
std::wstring text;
|
||||||
|
glm::vec2 pos {0};
|
||||||
|
std::vector<int> advances;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextBoxLayout {
|
||||||
|
bool ready = false;
|
||||||
|
std::string font;
|
||||||
|
int lineHeight = 0;
|
||||||
|
int textHeight = 0;
|
||||||
|
glm::vec4 caret {0};
|
||||||
|
std::vector<glm::vec4> selection;
|
||||||
|
std::vector<TextBoxLineLayout> lines;
|
||||||
|
};
|
||||||
|
|
||||||
class TextBoxHistorian;
|
class TextBoxHistorian;
|
||||||
class TextBox final : public Container {
|
class TextBox final : public Container {
|
||||||
const Input& inputEvents;
|
const Input& inputEvents;
|
||||||
|
|
@ -58,6 +75,7 @@ namespace gui {
|
||||||
size_t selectionEnd = 0;
|
size_t selectionEnd = 0;
|
||||||
size_t selectionOrigin = 0;
|
size_t selectionOrigin = 0;
|
||||||
|
|
||||||
|
bool externalRendering = false;
|
||||||
bool multiline = false;
|
bool multiline = false;
|
||||||
bool editable = true;
|
bool editable = true;
|
||||||
bool autoresize = false;
|
bool autoresize = false;
|
||||||
|
|
@ -175,6 +193,15 @@ namespace gui {
|
||||||
/// @param end index of the last selected character + 1
|
/// @param end index of the last selected character + 1
|
||||||
void select(int start, int end);
|
void select(int start, int end);
|
||||||
|
|
||||||
|
void setSelection(ptrdiff_t anchor, ptrdiff_t head);
|
||||||
|
size_t getSelectionAnchor() const;
|
||||||
|
|
||||||
|
void setExternalRendering(bool value);
|
||||||
|
bool isExternalRendering() const;
|
||||||
|
|
||||||
|
bool prepareTextLayout(const Assets& assets);
|
||||||
|
TextBoxLayout getTextLayout(const Assets& assets);
|
||||||
|
|
||||||
/// @brief Get number of line at specific position in text
|
/// @brief Get number of line at specific position in text
|
||||||
/// @param position target position
|
/// @param position target position
|
||||||
/// @return line number
|
/// @return line number
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
#include "../usertypes/lua_type_canvas.hpp"
|
#include "../usertypes/lua_type_canvas.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
using namespace gui;
|
using namespace gui;
|
||||||
using namespace scripting;
|
using namespace scripting;
|
||||||
|
|
||||||
|
|
@ -325,6 +328,72 @@ static int p_get_caret(UINode* node, lua::State* L) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int p_get_selection(UINode* node, lua::State* L) {
|
||||||
|
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||||
|
lua::createtable(L, 2, 0);
|
||||||
|
lua::pushinteger(L, box->getSelectionAnchor());
|
||||||
|
lua::rawseti(L, 1);
|
||||||
|
lua::pushinteger(L, box->getCaret());
|
||||||
|
lua::rawseti(L, 2);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int p_get_external_rendering(UINode* node, lua::State* L) {
|
||||||
|
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||||
|
return lua::pushboolean(L, box->isExternalRendering());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int p_get_text_layout(UINode* node, lua::State* L) {
|
||||||
|
auto box = dynamic_cast<TextBox*>(node);
|
||||||
|
if (box == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const auto layout = box->getTextLayout(engine->requireAssets());
|
||||||
|
lua::createtable(L, 0, 10);
|
||||||
|
lua::pushboolean(L, layout.ready);
|
||||||
|
lua::setfield(L, "ready");
|
||||||
|
lua::pushwstring(L, box->getText());
|
||||||
|
lua::setfield(L, "text");
|
||||||
|
lua::pushstring(L, layout.font);
|
||||||
|
lua::setfield(L, "font");
|
||||||
|
lua::pushinteger(L, layout.lineHeight);
|
||||||
|
lua::setfield(L, "lineHeight");
|
||||||
|
lua::pushinteger(L, layout.textHeight);
|
||||||
|
lua::setfield(L, "textHeight");
|
||||||
|
lua::pushvec4(L, layout.caret);
|
||||||
|
lua::setfield(L, "caretRect");
|
||||||
|
lua::createtable(L, layout.selection.size(), 0);
|
||||||
|
for (size_t i = 0; i < layout.selection.size(); ++i) {
|
||||||
|
lua::pushvec4(L, layout.selection[i]);
|
||||||
|
lua::rawseti(L, i + 1);
|
||||||
|
}
|
||||||
|
lua::setfield(L, "selectionRects");
|
||||||
|
lua::createtable(L, layout.lines.size(), 0);
|
||||||
|
for (size_t i = 0; i < layout.lines.size(); ++i) {
|
||||||
|
const auto& line = layout.lines[i];
|
||||||
|
lua::createtable(L, 0, 4);
|
||||||
|
lua::pushinteger(L, line.start);
|
||||||
|
lua::setfield(L, "start");
|
||||||
|
lua::pushwstring(L, line.text);
|
||||||
|
lua::setfield(L, "text");
|
||||||
|
lua::pushvec2(L, line.pos);
|
||||||
|
lua::setfield(L, "pos");
|
||||||
|
lua::createtable(L, line.advances.size(), 0);
|
||||||
|
for (size_t j = 0; j < line.advances.size(); ++j) {
|
||||||
|
lua::pushinteger(L, line.advances[j]);
|
||||||
|
lua::rawseti(L, j + 1);
|
||||||
|
}
|
||||||
|
lua::setfield(L, "advances");
|
||||||
|
lua::rawseti(L, i + 1);
|
||||||
|
}
|
||||||
|
lua::setfield(L, "lines");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int p_get_placeholder(UINode* node, lua::State* L) {
|
static int p_get_placeholder(UINode* node, lua::State* L) {
|
||||||
if (auto box = dynamic_cast<TextBox*>(node)) {
|
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||||
return lua::pushwstring(L, box->getPlaceholder());
|
return lua::pushwstring(L, box->getPlaceholder());
|
||||||
|
|
@ -663,6 +732,9 @@ static int l_gui_getattr(lua::State* L) {
|
||||||
{"hint", p_get_hint},
|
{"hint", p_get_hint},
|
||||||
{"valid", p_is_valid},
|
{"valid", p_is_valid},
|
||||||
{"caret", p_get_caret},
|
{"caret", p_get_caret},
|
||||||
|
{"selection", p_get_selection},
|
||||||
|
{"externalRendering", p_get_external_rendering},
|
||||||
|
{"textLayout", p_get_text_layout},
|
||||||
{"text", p_get_text},
|
{"text", p_get_text},
|
||||||
{"editable", p_get_editable},
|
{"editable", p_get_editable},
|
||||||
{"edited", p_get_edited},
|
{"edited", p_get_edited},
|
||||||
|
|
@ -765,6 +837,37 @@ static void p_set_caret(UINode* node, lua::State* L, int idx) {
|
||||||
box->resetSelection();
|
box->resetSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static void p_set_selection(UINode* node, lua::State* L, int idx) {
|
||||||
|
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||||
|
if (!lua::istable(L, idx)) {
|
||||||
|
throw std::runtime_error("selection must be {anchor, caret}");
|
||||||
|
}
|
||||||
|
ptrdiff_t positions[2];
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
lua::rawgeti(L, i + 1, idx);
|
||||||
|
const double value = lua::tonumber(L, -1);
|
||||||
|
if (lua_type(L, -1) != LUA_TNUMBER || !std::isfinite(value) ||
|
||||||
|
std::floor(value) != value) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"selection indices must be finite integers"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
positions[i] = static_cast<ptrdiff_t>(std::clamp(
|
||||||
|
value, 0.0, static_cast<double>(box->getText().size())
|
||||||
|
));
|
||||||
|
lua::pop(L);
|
||||||
|
}
|
||||||
|
box->prepareTextLayout(engine->requireAssets());
|
||||||
|
box->setSelection(positions[0], positions[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void p_set_external_rendering(UINode* node, lua::State* L, int idx) {
|
||||||
|
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||||
|
box->setExternalRendering(lua::toboolean(L, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void p_set_editable(UINode* node, lua::State* L, int idx) {
|
static void p_set_editable(UINode* node, lua::State* L, int idx) {
|
||||||
if (auto box = dynamic_cast<TextBox*>(node)) {
|
if (auto box = dynamic_cast<TextBox*>(node)) {
|
||||||
box->setEditable(lua::toboolean(L, idx));
|
box->setEditable(lua::toboolean(L, idx));
|
||||||
|
|
@ -982,6 +1085,8 @@ static int l_gui_setattr(lua::State* L) {
|
||||||
{"src", p_set_src},
|
{"src", p_set_src},
|
||||||
{"fallback", p_set_fallback},
|
{"fallback", p_set_fallback},
|
||||||
{"caret", p_set_caret},
|
{"caret", p_set_caret},
|
||||||
|
{"selection", p_set_selection},
|
||||||
|
{"externalRendering", p_set_external_rendering},
|
||||||
{"value", p_set_value},
|
{"value", p_set_value},
|
||||||
{"min", p_set_min},
|
{"min", p_set_min},
|
||||||
{"max", p_set_max},
|
{"max", p_set_max},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue