Merge pull request #443 from MihailRis/notifications

Notifications
This commit is contained in:
MihailRis 2025-01-18 07:36:12 +03:00 committed by GitHub
commit ce63b1b27b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 208 additions and 64 deletions

View File

@ -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

View File

@ -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

View File

@ -56,6 +56,7 @@ document["worlds-panel"]:clear()
| ------------------- | ----------------------------------------------------------------------- |
| moveInto(container) | перемещает элемент в указанный контейнер (указывается элемент, а не id) |
| destruct() | удаляет элемент |
| reposition() | обновляет позицию элемента на основе функции позиционирования |
## Контейнеры

View File

@ -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.
# Основные элементы

View File

@ -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

View File

@ -0,0 +1,8 @@
<panel size="300,0" margin="0,0,0,70" max-length='300' min-length='0'
size-func="gui.get_viewport()[1]/2,-1"
padding="0"
min-size="0"
color="0"
interval="0"
gravity="bottom-left" interactive="false">
</panel>

View File

@ -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

View File

@ -0,0 +1,3 @@
<container id='%{id}' color="#00000050" size="-1,20">
<label pos='5' id='%{id}L' markup='md'/>
</container>

View File

@ -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

View File

@ -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
)

View File

@ -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"

View File

@ -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() {

View File

@ -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());

View File

@ -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<UINode> node);

View File

@ -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<UINode> &node) {
fullRefresh();
}
void Panel::remove(const std::shared_ptr<UINode> &node) {
Container::remove(node);
fullRefresh();
}
void Panel::refresh() {
UINode::refresh();
std::stable_sort(nodes.begin(), nodes.end(), [](auto a, auto b) {

View File

@ -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<UINode>& node) override;
virtual void remove(const std::shared_ptr<UINode>& 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;
};

View File

@ -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) {

View File

@ -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") {

View File

@ -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<Container>(node.node)) {
@ -328,6 +334,10 @@ static int p_get_destruct(UINode*, lua::State* L) {
return lua::pushcfunction(L, lua::wrap<l_node_destruct>);
}
static int p_get_reposition(UINode*, lua::State* L) {
return lua::pushcfunction(L, lua::wrap<l_node_reposition>);
}
static int p_get_clear(UINode* node, lua::State* L) {
if (dynamic_cast<Container*>(node)) {
return lua::pushcfunction(L, lua::wrap<l_container_clear>);
@ -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},