diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 5013b448..f5bb8401 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -56,6 +56,7 @@ Common element methods: | ------------------- | ----------------------------------------------------------------------------------- | | moveInto(container) | moves the element to the specified container (the element is specified, not the id) | | destruct() | removes element | +| reposition() | updates the element position based on the `positionfunc` | ## Containers diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 3f0bff07..8856338a 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -35,6 +35,7 @@ Examples: - `margin` - element margin. Type: 4D vector *left, top, right, bottom* - `visible` - element visibility. Type: boolean (true/false) +- `min-size` - minimal element size. Type: 2D vector. - `position-func` - position supplier for an element (two numbers), called on every parent container size update or on element adding on a container. May be called before *on_hud_open* - `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. @@ -65,6 +66,7 @@ Buttons and panels are also containers. Buttons are also panels. - `max-length` - maximal length of panel stretching before scrolling (if scrollable = true). Type: number +- `min-length` - minimal length of panel. Type: number - `orientation` - panel orientation: horizontal/vertical. # Common elements diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 3c203d37..7e10a180 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -56,6 +56,7 @@ document["worlds-panel"]:clear() | ------------------- | ----------------------------------------------------------------------- | | moveInto(container) | перемещает элемент в указанный контейнер (указывается элемент, а не id) | | destruct() | удаляет элемент | +| reposition() | обновляет позицию элемента на основе функции позиционирования | ## Контейнеры diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index f562ee4c..00db0754 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -39,6 +39,7 @@ - `margin` - внешний отступ элемента. Тип: 4D вектор. Порядок: `"left,top,right,bottom"` - `visible` - видимость элемента. Тип: логический ("true"/"false"). +- `min-size` - минимальный размер элемента. Тип: 2D вектор. - `position-func` - поставщик позиции элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open. - `size-func` - поставщик размера элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open. - `onclick` - lua функция вызываемая при нажатии на элемент. @@ -67,6 +68,7 @@ В число панелей также входят кнопки. - `max-length` - максимальная длина, на которую растягивается панель до начала скроллинга (если scrollable = true). Тип: число +- `min-length` - минимальная длина панели. Тип: число - `orientation` - ориентация панели: horizontal/vertical. # Основные элементы diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index 11d7bd14..7e41ddf6 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -134,6 +134,10 @@ function add_to_history(text) end function submit(text) + if #text == 0 then + document.prompt.focused = true + return + end text = text:trim() add_to_history(text) @@ -204,4 +208,11 @@ function on_open(mode) elseif mode then modes:set(mode) end + hud.close("core:ingame_chat") +end + +function on_close() + time.post_runnable(function() + hud.open_permanent("core:ingame_chat") + end) end diff --git a/res/layouts/ingame_chat.xml b/res/layouts/ingame_chat.xml new file mode 100644 index 00000000..45ad4749 --- /dev/null +++ b/res/layouts/ingame_chat.xml @@ -0,0 +1,8 @@ + + diff --git a/res/layouts/ingame_chat.xml.lua b/res/layouts/ingame_chat.xml.lua new file mode 100644 index 00000000..5e08ac9d --- /dev/null +++ b/res/layouts/ingame_chat.xml.lua @@ -0,0 +1,59 @@ +local lines = {} +local dead_lines = {} +local nextid = 0 +local timeout = 7 +local fadeout = 1 +local initialized = false +local max_lines = 15 +local animation_fps = 30 + +local function remove_line(line) + document[line[1]]:destruct() + time.post_runnable(function() document.root:reposition() end) +end + +local function update_line(line, uptime) + local diff = uptime - line[2] + if diff > timeout then + remove_line(line) + table.insert(dead_lines, i) + elseif diff > timeout-fadeout then + local opacity = (timeout - diff) / fadeout + document[line[1]].color = {0, 0, 0, opacity * 80} + document[line[1].."L"].color = {255, 255, 255, opacity * 255} + end +end + +events.on("core:chat", function(message) + local current_time = time.uptime() + local id = 'l'..tostring(nextid) + document.root:add(gui.template("chat_line", {id=id})) + document.root:reposition() + document[id.."L"].text = message + nextid = nextid + 1 + if #lines == max_lines then + remove_line(lines[1]) + table.remove(lines, 1) + end + table.insert(lines, {id, current_time}) +end) + +function on_open() + if not initialized then + initialized = true + + document.root:setInterval(1/animation_fps, function () + local uptime = time.uptime() + for _, line in ipairs(lines) do + update_line(line, uptime) + end + if #dead_lines > 0 then + for i = #dead_lines, 1, -1 do + local index = dead_lines[i] + table.remove(lines, i) + end + dead_lines = {} + end + end) + end +end diff --git a/res/layouts/templates/chat_line.xml b/res/layouts/templates/chat_line.xml new file mode 100644 index 00000000..b9939218 --- /dev/null +++ b/res/layouts/templates/chat_line.xml @@ -0,0 +1,3 @@ + + diff --git a/res/modules/gui_util.lua b/res/modules/internal/gui_util.lua similarity index 50% rename from res/modules/gui_util.lua rename to res/modules/internal/gui_util.lua index 877d4b01..3cf84a33 100644 --- a/res/modules/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -51,4 +51,57 @@ function gui_util.reset_local() gui_util.local_dispatchers = {} end +-- class designed for simple UI-nodes access via properties syntax +local Element = {} +function Element.new(docname, name) + return setmetatable({docname=docname, name=name}, { + __index=function(self, k) + return gui.getattr(self.docname, self.name, k) + end, + __newindex=function(self, k, v) + gui.setattr(self.docname, self.name, k, v) + end + }) +end + +-- the engine automatically creates an instance for every ui document (layout) +local Document = {} +function Document.new(docname) + return setmetatable({name=docname}, { + __index=function(self, k) + local elem = Element.new(self.name, k) + rawset(self, k, elem) + return elem + end + }) +end + +local RadioGroup = {} +function RadioGroup:set(key) + if type(self) ~= 'table' then + error("called as non-OOP via '.', use radiogroup:set") + end + if self.current then + self.elements[self.current].enabled = true + end + self.elements[key].enabled = false + self.current = key + if self.callback then + self.callback(key) + end +end +function RadioGroup:__call(elements, onset, default) + local group = setmetatable({ + elements=elements, + callback=onset, + current=nil + }, {__index=self}) + group:set(default) + return group +end +setmetatable(RadioGroup, RadioGroup) + +gui_util.Document = Document +gui_util.RadioGroup = RadioGroup + return gui_util diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index ccb65a71..95b37007 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -260,7 +260,7 @@ console.add_command( "chat text:str", "Send chat message", function (args, kwargs) - console.log("[you] "..args[1]) + console.chat("[you] "..args[1]) end ) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 0df1c387..78228254 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -146,64 +146,16 @@ function events.emit(event, ...) return result end --- class designed for simple UI-nodes access via properties syntax -local Element = {} -function Element.new(docname, name) - return setmetatable({docname=docname, name=name}, { - __index=function(self, k) - return gui.getattr(self.docname, self.name, k) - end, - __newindex=function(self, k, v) - gui.setattr(self.docname, self.name, k, v) - end - }) -end +gui_util = require "core:internal/gui_util" --- the engine automatically creates an instance for every ui document (layout) -Document = {} -function Document.new(docname) - return setmetatable({name=docname}, { - __index=function(self, k) - local elem = Element.new(self.name, k) - rawset(self, k, elem) - return elem - end - }) -end - -local _RadioGroup = {} -function _RadioGroup:set(key) - if type(self) ~= 'table' then - error("called as non-OOP via '.', use radiogroup:set") - end - if self.current then - self.elements[self.current].enabled = true - end - self.elements[key].enabled = false - self.current = key - if self.callback then - self.callback(key) - end -end -function _RadioGroup:__call(elements, onset, default) - local group = setmetatable({ - elements=elements, - callback=onset, - current=nil - }, {__index=_RadioGroup}) - group:set(default) - return group -end -setmetatable(_RadioGroup, _RadioGroup) -RadioGroup = _RadioGroup +Document = gui_util.Document +RadioGroup = gui_util.RadioGroup +__vc_page_loader = gui_util.load_page _GUI_ROOT = Document.new("core:root") _MENU = _GUI_ROOT.menu menu = _MENU -gui_util = require "core:gui_util" -__vc_page_loader = gui_util.load_page - --- Console library extension --- console.cheats = {} @@ -225,6 +177,11 @@ function console.log(...) log_element:paste(text) end +function console.chat(...) + console.log(...) + events.emit("core:chat", ...) +end + function gui.template(name, params) local text = file.read(file.find("layouts/templates/"..name..".xml")) for k,v in pairs(params) do @@ -394,6 +351,7 @@ function __vc_on_hud_open() hud.pause() end end) + hud.open_permanent("core:ingame_chat") end local RULES_FILE = "world:rules.toml" diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 00382420..ce29e8c1 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -217,6 +217,7 @@ void Engine::renderFrame() { Viewport viewport(Window::width, Window::height); DrawContext ctx(nullptr, viewport, nullptr); gui->draw(ctx, *assets); + gui->postAct(); } void Engine::saveSettings() { diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 95d65540..2cc28024 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -178,12 +178,6 @@ void GUI::actFocused() { } void GUI::act(float delta, const Viewport& vp) { - while (!postRunnables.empty()) { - runnable callback = postRunnables.back(); - postRunnables.pop(); - callback(); - } - container->setSize(vp.size()); container->act(delta); auto prevfocus = focus; @@ -206,6 +200,14 @@ void GUI::act(float delta, const Viewport& vp) { } } +void GUI::postAct() { + while (!postRunnables.empty()) { + runnable callback = postRunnables.back(); + postRunnables.pop(); + callback(); + } +} + void GUI::draw(const DrawContext& pctx, const Assets& assets) { auto ctx = pctx.sub(batch2D.get()); diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index f8cea3ef..cb6b037e 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -106,6 +106,8 @@ namespace gui { /// @param assets active assets storage void draw(const DrawContext& pctx, const Assets& assets); + void postAct(); + /// @brief Add element to the main container /// @param node UI element void add(std::shared_ptr node); diff --git a/src/graphics/ui/elements/Panel.cpp b/src/graphics/ui/elements/Panel.cpp index af4a0de3..d90b8830 100644 --- a/src/graphics/ui/elements/Panel.cpp +++ b/src/graphics/ui/elements/Panel.cpp @@ -23,6 +23,14 @@ int Panel::getMaxLength() const { return maxLength; } +void Panel::setMinLength(int value) { + minLength = value; +} + +int Panel::getMinLength() const { + return minLength; +} + void Panel::setPadding(glm::vec4 padding) { this->padding = padding; refresh(); @@ -34,9 +42,11 @@ glm::vec4 Panel::getPadding() const { void Panel::cropToContent() { if (maxLength > 0.0f) { - setSize(glm::vec2(getSize().x, glm::min(maxLength, actualLength))); + setSize(glm::vec2( + getSize().x, glm::max(minLength, glm::min(maxLength, actualLength)) + )); } else { - setSize(glm::vec2(getSize().x, actualLength)); + setSize(glm::vec2(getSize().x, glm::max(minLength, actualLength))); } } @@ -52,6 +62,11 @@ void Panel::add(const std::shared_ptr &node) { fullRefresh(); } +void Panel::remove(const std::shared_ptr &node) { + Container::remove(node); + fullRefresh(); +} + void Panel::refresh() { UINode::refresh(); std::stable_sort(nodes.begin(), nodes.end(), [](auto a, auto b) { diff --git a/src/graphics/ui/elements/Panel.hpp b/src/graphics/ui/elements/Panel.hpp index d3d60c30..5a6399ce 100644 --- a/src/graphics/ui/elements/Panel.hpp +++ b/src/graphics/ui/elements/Panel.hpp @@ -9,6 +9,7 @@ namespace gui { Orientation orientation = Orientation::vertical; glm::vec4 padding {2.0f}; float interval = 2.0f; + int minLength = 0; int maxLength = 0; public: Panel( @@ -24,6 +25,7 @@ namespace gui { Orientation getOrientation() const; virtual void add(const std::shared_ptr& node) override; + virtual void remove(const std::shared_ptr& node) override; virtual void refresh() override; virtual void fullRefresh() override; @@ -31,6 +33,9 @@ namespace gui { virtual void setMaxLength(int value); int getMaxLength() const; + virtual void setMinLength(int value); + int getMinLength() const; + virtual void setPadding(glm::vec4 padding); glm::vec4 getPadding() const; }; diff --git a/src/graphics/ui/elements/UINode.cpp b/src/graphics/ui/elements/UINode.cpp index a6fa38c2..4ebe8c7a 100644 --- a/src/graphics/ui/elements/UINode.cpp +++ b/src/graphics/ui/elements/UINode.cpp @@ -289,12 +289,16 @@ const std::string& UINode::getId() const { } void UINode::reposition() { + if (sizefunc) { + auto newSize = sizefunc(); + setSize( + {newSize.x < 0 ? size.x : newSize.x, + newSize.y < 0 ? size.y : newSize.y} + ); + } if (positionfunc) { setPos(positionfunc()); } - if (sizefunc) { - setSize(sizefunc()); - } } void UINode::setGravity(Gravity gravity) { diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index c705d16d..972b118f 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -91,6 +91,9 @@ static void _readUINode( if (element.has("pos")) { node.setPos(element.attr("pos").asVec2()); } + if (element.has("min-size")) { + node.setMinSize(element.attr("min-size").asVec2()); + } if (element.has("size")) { node.setSize(element.attr("size").asVec2()); } @@ -220,6 +223,9 @@ static void _readPanel(UiXmlReader& reader, const xml::xmlelement& element, Pane if (element.has("max-length")) { panel.setMaxLength(element.attr("max-length").asInt()); } + if (element.has("min-length")) { + panel.setMinLength(element.attr("min-length").asInt()); + } if (element.has("orientation")) { auto &oname = element.attr("orientation").getText(); if (oname == "horizontal") { diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 48cf73cf..2947d80c 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -101,6 +101,12 @@ static int l_node_destruct(lua::State* L) { return 0; } +static int l_node_reposition(lua::State* L) { + auto docnode = get_document_node(L); + docnode.node->reposition(); + return 0; +} + static int l_container_clear(lua::State* L) { auto node = get_document_node(L, 1); if (auto container = std::dynamic_pointer_cast(node.node)) { @@ -328,6 +334,10 @@ static int p_get_destruct(UINode*, lua::State* L) { return lua::pushcfunction(L, lua::wrap); } +static int p_get_reposition(UINode*, lua::State* L) { + return lua::pushcfunction(L, lua::wrap); +} + static int p_get_clear(UINode* node, lua::State* L) { if (dynamic_cast(node)) { return lua::pushcfunction(L, lua::wrap); @@ -424,6 +434,7 @@ static int l_gui_getattr(lua::State* L) { {"moveInto", p_move_into}, {"add", p_get_add}, {"destruct", p_get_destruct}, + {"reposition", p_get_reposition}, {"clear", p_get_clear}, {"setInterval", p_set_interval}, {"placeholder", p_get_placeholder},