diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md
index 5013b4487..f5bb8401a 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 3f0bff071..8856338ad 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 3c203d37a..7e10a1804 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 f562ee4cb..00db07547 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 11d7bd14a..7e41ddf66 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 000000000..45ad47490
--- /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 000000000..5e08ac9d9
--- /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 000000000..b9939218d
--- /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 877d4b01a..3cf84a33b 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 ccb65a71d..95b370078 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 0df1c3872..78228254c 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 003824202..ce29e8c17 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 95d655405..2cc280244 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 f8cea3ef0..cb6b037ef 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 af4a0de3a..d90b8830c 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 d3d60c30d..5a6399ce8 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 a6fa38c2e..4ebe8c7a0 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 c705d16d9..972b118ff 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 48cf73cf4..2947d80c9 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},