diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 66315f29..85420fa2 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -164,9 +164,10 @@ Properties: Properties: -| Name | Type | Read | Write | Description | -| ----- | ------ | ---- | ----- | ------------ | -| src | string | yes | yes | texture name | +| Name | Type | Read | Write | Description | +| ------ | ------ | ---- | ----- | ---------------- | +| src | string | yes | yes | texture name | +| region | vec4 | yes | yes | image sub-region | ## Canvas diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 3f22c748..b25028f2 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -40,6 +40,8 @@ Examples: - `size-func` - element size provider (two numbers), called when the size of the container in which the element is located changes, or when an element is added to the container. Can be called before on_hud_open is called. - `onclick` - lua function called when an element is clicked. - `ondoubleclick` - lua function called when you double click on an element. +- `onfocus` - lua function called when focusing on an element. +- `ondefocus` - lua function called when the element loses focus. - `tooltip` - tooltip text - `tooltip-delay` - tooltip show-up delay - `gravity` - automatic positioning of the element in the container. (Does not work in automatic containers like panel). Values: *top-left, top-center, top-right, center-left, center-center, center-right, bottom-left, bottom-center, bottom-right*. @@ -112,6 +114,7 @@ Inner text is a button text. - `src` - name of an image stored in textures folder. Extension is not specified. Type: string. Example: *gui/error* +- `region` - image region x1, y1, x2, y2 from 0.0, 0.0 (upper left corner), 1.0, 1.0 (lower right corner) ## *canvas* diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index cdc7a9bd..8dae3f4d 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -164,9 +164,10 @@ document["worlds-panel"]:clear() Свойства: -| Название | Тип | Чтение | Запись | Описание | -| -------- | ------ | ------ | ------ | --------------------- | -| src | string | да | да | отображаемая текстура | +| Название | Тип | Чтение | Запись | Описание | +| -------- | ------ | ------ | ------ | ---------------------- | +| src | string | да | да | отображаемая текстура | +| region | vec4 | да | да | под-регион изображения | ## Холст (canvas) diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index df8e08e3..179f1bc0 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -44,6 +44,8 @@ - `size-func` - поставщик размера элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open. - `onclick` - lua функция вызываемая при нажатии на элемент. - `ondoubleclick` - lua функция вызываемая при двойном нажатии на элемент. +- `onfocus` - lua функция вызываемая при фокусировке на элемент. +- `ondefocus` - lua функция вызываемая при потере фокуса элеметом. - `tooltip` - текст всплывающей подсказки - `tooltip-delay` - задержка появления всплывающей подсказки - `gravity` - автоматическое позиционирование элемента в контейнере. (Не работает в автоматических контейнерах, как panel). Значения: *top-left, top-center, top-right, center-left, center-center, center-right, bottom-left, bottom-center, bottom-right*. @@ -113,6 +115,7 @@ ## Изображение - *image* - `src` - имя изображения в папке textures без указания расширения. Тип: строка. Например `gui/error` +- `region` - под-регион изображения x1, y1, x2, y2 от 0.0, 0.0 (левый верхний угол), 1.0, 1.0 (правый нижний угол) ## Холст - *canvas* diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index a0f18e6d..79482c3e 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -226,15 +226,20 @@ end function gui.template(name, params) local text = file.read(file.find("layouts/templates/"..name..".xml")) - for k,v in pairs(params) do - local arg = tostring(v):gsub("'", "\\'"):gsub('"', '\\"') - text = text:gsub("(%%{"..k.."})", arg) - end - text = text:gsub("if%s*=%s*'%%{%w+}'", "if=''") - text = text:gsub("if%s*=%s*\"%%{%w+}\"", "if=\"\"") + text = text:gsub("%%{([^}]+)}", function(n) + local s = params[n] + if type(s) ~= "string" then + return tostring(s) + end + if #s == 0 then + return + end + local e = string.escape(s) + return e:sub(2, #e-1) + end) + text = text:gsub('if%s*=%s*[\'"]%%{%w+}[\'"]', "if=\"\"") -- remove unsolved properties: attr='%{var}' - text = text:gsub("%s*%S+='%%{[^}]+}'%s*", " ") - text = text:gsub('%s*%S+="%%{[^}]+}"%s*', " ") + text = text:gsub('%s*%S+=[\'"]%%{[^}]+}[\'"]%s*', " ") return text end diff --git a/src/coders/xml.hpp b/src/coders/xml.hpp index 5831c29a..f626dd34 100644 --- a/src/coders/xml.hpp +++ b/src/coders/xml.hpp @@ -53,11 +53,11 @@ namespace xml { /// @brief Get element tag const std::string& getTag() const; - inline bool isText() const { + bool isText() const { return getTag() == "#"; } - inline const std::string& text() const { + const std::string& getInnerText() const { return attr("#").getText(); } diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 5e8df9f1..7c6f8d13 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -67,7 +67,7 @@ std::unique_ptr UiDocument::read( ? scripting::create_doc_environment(scripting::get_root_environment(), name) : scripting::create_doc_environment(penv, name); - gui::UiXmlReader reader(gui, env); + gui::UiXmlReader reader(gui, scriptenv(env)); auto view = reader.readXML(file.string(), *xmldoc->getRoot()); view->setId("root"); uidocscript script {}; diff --git a/src/graphics/ui/elements/Container.cpp b/src/graphics/ui/elements/Container.cpp index 72de920b..b930d76a 100644 --- a/src/graphics/ui/elements/Container.cpp +++ b/src/graphics/ui/elements/Container.cpp @@ -71,6 +71,7 @@ void Container::mouseRelease(int x, int y) { } void Container::act(float delta) { + UINode::act(delta); for (const auto& node : nodes) { if (node->isVisible()) { node->act(delta); @@ -162,7 +163,13 @@ void Container::add(const std::shared_ptr& node) { nodes.push_back(node); node->setParent(this); node->reposition(); - refresh(); + mustRefresh = true; + + auto parent = getParent(); + while (parent) { + parent->setMustRefresh(); + parent = parent->getParent(); + } } void Container::add(const std::shared_ptr& node, glm::vec2 pos) { @@ -202,7 +209,6 @@ void Container::listenInterval(float interval, ontimeout callback, int repeat) { void Container::setSize(glm::vec2 size) { if (size == getSize()) { - refresh(); return; } UINode::setSize(size); diff --git a/src/graphics/ui/elements/Image.cpp b/src/graphics/ui/elements/Image.cpp index 4a1fabd3..f321ddf8 100644 --- a/src/graphics/ui/elements/Image.cpp +++ b/src/graphics/ui/elements/Image.cpp @@ -55,7 +55,7 @@ void Image::draw(const DrawContext& pctx, const Assets& assets) { 0, 0, 0, - UVRegion(), + region, false, true, calcColor() @@ -76,3 +76,11 @@ const std::string& Image::getTexture() const { void Image::setTexture(const std::string& name) { texture = name; } + +void Image::setRegion(const UVRegion& region) { + this->region = region; +} + +const UVRegion& Image::getRegion() const { + return region; +} diff --git a/src/graphics/ui/elements/Image.hpp b/src/graphics/ui/elements/Image.hpp index cc5ff17c..40765a4c 100644 --- a/src/graphics/ui/elements/Image.hpp +++ b/src/graphics/ui/elements/Image.hpp @@ -1,11 +1,13 @@ #pragma once #include "UINode.hpp" +#include "maths/UVRegion.hpp" namespace gui { class Image : public UINode { protected: std::string texture; + UVRegion region {}; bool autoresize = false; public: Image(GUI& gui, std::string texture, glm::vec2 size=glm::vec2(32,32)); @@ -16,5 +18,7 @@ namespace gui { virtual bool isAutoResize() const; virtual const std::string& getTexture() const; virtual void setTexture(const std::string& name); + void setRegion(const UVRegion& region); + const UVRegion& getRegion() const; }; } diff --git a/src/graphics/ui/elements/UINode.cpp b/src/graphics/ui/elements/UINode.cpp index a5b0e4d5..1e8f48d6 100644 --- a/src/graphics/ui/elements/UINode.cpp +++ b/src/graphics/ui/elements/UINode.cpp @@ -74,6 +74,16 @@ UINode* UINode::listenDoubleClick(const onaction& action) { return this; } +UINode* UINode::listenFocus(const onaction& action) { + focusCallbacks.listen(action); + return this; +} + +UINode* UINode::listenDefocus(const onaction& action) { + defocusCallbacks.listen(action); + return this; +} + void UINode::click(int, int) { pressed = true; } @@ -96,8 +106,14 @@ bool UINode::isPressed() const { return pressed; } +void UINode::onFocus() { + focused = true; + focusCallbacks.notify(gui); +} + void UINode::defocus() { focused = false; + defocusCallbacks.notify(gui); } bool UINode::isFocused() const { diff --git a/src/graphics/ui/elements/UINode.hpp b/src/graphics/ui/elements/UINode.hpp index aad04343..53854db5 100644 --- a/src/graphics/ui/elements/UINode.hpp +++ b/src/graphics/ui/elements/UINode.hpp @@ -66,6 +66,7 @@ namespace gui { class UINode : public std::enable_shared_from_this { protected: GUI& gui; + bool mustRefresh = true; private: /// @brief element identifier used for direct access in UiDocument std::string id = ""; @@ -114,6 +115,10 @@ namespace gui { ActionsSet actions; /// @brief 'ondoubleclick' callbacks ActionsSet doubleClickCallbacks; + /// @brief 'onfocus' callbacks + ActionsSet focusCallbacks; + /// @brief 'ondefocus' callbacks + ActionsSet defocusCallbacks; /// @brief element tooltip text std::wstring tooltip; /// @brief element tooltip delay @@ -127,7 +132,12 @@ namespace gui { /// @brief Called every frame for all visible elements /// @param delta delta timУ - virtual void act(float delta) {}; + virtual void act(float delta) { + if (mustRefresh) { + mustRefresh = false; + refresh(); + } + }; virtual void draw(const DrawContext& pctx, const Assets& assets) = 0; virtual void setVisible(bool flag); @@ -169,10 +179,12 @@ namespace gui { /// @brief Get element z-index int getZIndex() const; - virtual UINode* listenAction(const onaction &action); - virtual UINode* listenDoubleClick(const onaction &action); + virtual UINode* listenAction(const onaction& action); + virtual UINode* listenDoubleClick(const onaction& action); + virtual UINode* listenFocus(const onaction& action); + virtual UINode* listenDefocus(const onaction& action); - virtual void onFocus() {focused = true;} + virtual void onFocus(); virtual void doubleClick(int x, int y); virtual void click(int x, int y); virtual void clicked(Mousecode button) {} @@ -269,5 +281,9 @@ namespace gui { const std::shared_ptr& node, const std::string& id ); + + void setMustRefresh() { + mustRefresh = true; + } }; } diff --git a/src/graphics/ui/gui_util.cpp b/src/graphics/ui/gui_util.cpp index 76ed43ca..6d28b990 100644 --- a/src/graphics/ui/gui_util.cpp +++ b/src/graphics/ui/gui_util.cpp @@ -21,7 +21,7 @@ std::shared_ptr guiutil::create( if (env == nullptr) { env = scripting::get_root_environment(); } - UiXmlReader reader(gui, env); + UiXmlReader reader(gui, std::move(env)); return reader.readXML("[string]", source); } diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index d76b76b7..d393dbae 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -63,7 +63,7 @@ static runnable create_runnable( const std::string& name ) { if (element.has(name)) { - std::string text = element.attr(name).getText(); + const std::string& text = element.attr(name).getText(); if (!text.empty()) { return scripting::create_runnable( reader.getEnvironment(), text, reader.getFilename() @@ -180,6 +180,14 @@ static void read_uinode( node.listenAction(onclick); } + if (auto onfocus = create_action(reader, element, "onfocus")) { + node.listenFocus(onfocus); + } + + if (auto ondefocus = create_action(reader, element, "ondefocus")) { + node.listenDefocus(ondefocus); + } + if (auto ondoubleclick = create_action(reader, element, "ondoubleclick")) { node.listenDoubleClick(ondoubleclick); } @@ -279,7 +287,7 @@ static std::wstring parse_inner_text( ) { std::wstring text = L""; if (element.size() == 1) { - std::string source = element.sub(0).attr("#").getText(); + std::string source = element.sub(0).getInnerText(); util::trim(source); text = util::str2wstr_utf8(source); if (text[0] == '@') { @@ -379,7 +387,7 @@ static std::shared_ptr read_button( std::shared_ptr