Merge pull request #520 from MihailRis/custom-ui-elements
Custom UI elements
This commit is contained in:
commit
ddd09a23d6
@ -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
|
||||
|
||||
|
||||
@ -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*
|
||||
|
||||
|
||||
@ -164,9 +164,10 @@ document["worlds-panel"]:clear()
|
||||
|
||||
Свойства:
|
||||
|
||||
| Название | Тип | Чтение | Запись | Описание |
|
||||
| -------- | ------ | ------ | ------ | --------------------- |
|
||||
| src | string | да | да | отображаемая текстура |
|
||||
| Название | Тип | Чтение | Запись | Описание |
|
||||
| -------- | ------ | ------ | ------ | ---------------------- |
|
||||
| src | string | да | да | отображаемая текстура |
|
||||
| region | vec4 | да | да | под-регион изображения |
|
||||
|
||||
|
||||
## Холст (canvas)
|
||||
|
||||
@ -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*
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ std::unique_ptr<UiDocument> 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 {};
|
||||
|
||||
@ -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<UINode>& 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<UINode>& 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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -66,6 +66,7 @@ namespace gui {
|
||||
class UINode : public std::enable_shared_from_this<UINode> {
|
||||
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<UINode>& node,
|
||||
const std::string& id
|
||||
);
|
||||
|
||||
void setMustRefresh() {
|
||||
mustRefresh = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ std::shared_ptr<gui::UINode> 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);
|
||||
}
|
||||
|
||||
|
||||
@ -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<UINode> read_button(
|
||||
|
||||
std::shared_ptr<Button> button;
|
||||
auto& elements = element.getElements();
|
||||
if (!elements.empty() && elements[0]->getTag() != "#") {
|
||||
if (!elements.empty() && !elements[0]->isText()) {
|
||||
auto inner = reader.readUINode(*elements.at(0));
|
||||
if (inner != nullptr) {
|
||||
button = std::make_shared<Button>(gui, inner, padding);
|
||||
@ -536,6 +544,11 @@ static std::shared_ptr<UINode> read_image(
|
||||
std::string src = element.attr("src", "").getText();
|
||||
auto image = std::make_shared<Image>(reader.getGUI(), src);
|
||||
read_uinode(reader, element, *image);
|
||||
|
||||
if (element.has("region")) {
|
||||
auto vec = element.attr("region").asVec4();
|
||||
image->setRegion(UVRegion(vec.x, vec.y, vec.z, vec.w));
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
@ -757,7 +770,7 @@ static std::shared_ptr<UINode> read_iframe(
|
||||
return iframe;
|
||||
}
|
||||
|
||||
UiXmlReader::UiXmlReader(gui::GUI& gui, const scriptenv& env) : gui(gui), env(env) {
|
||||
UiXmlReader::UiXmlReader(gui::GUI& gui, scriptenv&& env) : gui(gui), env(std::move(env)) {
|
||||
contextStack.emplace("");
|
||||
add("image", read_image);
|
||||
add("canvas", read_canvas);
|
||||
|
||||
@ -20,9 +20,9 @@ namespace gui {
|
||||
std::unordered_set<std::string> ignored;
|
||||
std::stack<std::string> contextStack;
|
||||
std::string filename;
|
||||
const scriptenv& env;
|
||||
scriptenv env;
|
||||
public:
|
||||
UiXmlReader(gui::GUI& gui, const scriptenv& env);
|
||||
UiXmlReader(gui::GUI& gui, scriptenv&& env);
|
||||
|
||||
void add(const std::string& tag, uinode_reader reader);
|
||||
bool hasReader(const std::string& tag) const;
|
||||
|
||||
@ -83,11 +83,19 @@ static int l_container_add(lua::State* L) {
|
||||
}
|
||||
auto xmlsrc = lua::require_string(L, 2);
|
||||
try {
|
||||
auto env = docnode.document->getEnvironment();
|
||||
if (lua::istable(L, 3)) {
|
||||
env = create_environment(std::move(env));
|
||||
lua::pushenv(L, *env);
|
||||
lua::pushvalue(L, 3);
|
||||
lua::setfield(L, "DATA");
|
||||
lua::pop(L);
|
||||
}
|
||||
auto subnode = guiutil::create(
|
||||
engine->getGUI(), xmlsrc, docnode.document->getEnvironment()
|
||||
engine->getGUI(), xmlsrc, std::move(env)
|
||||
);
|
||||
node->add(subnode);
|
||||
UINode::getIndices(subnode, docnode.document->getMapWriteable());
|
||||
node->add(std::move(subnode));
|
||||
} catch (const std::exception& err) {
|
||||
throw std::runtime_error(err.what());
|
||||
}
|
||||
@ -337,6 +345,14 @@ static int p_get_src(UINode* node, lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int p_get_region(UINode* node, lua::State* L) {
|
||||
if (auto image = dynamic_cast<Image*>(node)) {
|
||||
const auto& region = image->getRegion();
|
||||
return lua::pushvec4(L, {region.u1, region.v1, region.u2, region.v2});
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int p_get_data(UINode* node, lua::State* L) {
|
||||
if (auto canvas = dynamic_cast<Canvas*>(node)) {
|
||||
return lua::newuserdata<lua::LuaCanvas>(L, canvas->texture(), canvas->data());
|
||||
@ -548,6 +564,7 @@ static int l_gui_getattr(lua::State* L) {
|
||||
{"cursor", p_get_cursor},
|
||||
{"data", p_get_data},
|
||||
{"parent", p_get_parent},
|
||||
{"region", p_get_region},
|
||||
};
|
||||
auto func = getters.find(attr);
|
||||
if (func != getters.end()) {
|
||||
@ -651,6 +668,12 @@ static void p_set_src(UINode* node, lua::State* L, int idx) {
|
||||
iframe->setSrc(lua::require_string(L, idx));
|
||||
}
|
||||
}
|
||||
static void p_set_region(UINode* node, lua::State* L, int idx) {
|
||||
if (auto image = dynamic_cast<Image*>(node)) {
|
||||
auto vec = lua::tovec4(L, idx);
|
||||
image->setRegion(UVRegion(vec.x, vec.y, vec.z, vec.w));
|
||||
}
|
||||
}
|
||||
static void p_set_value(UINode* node, lua::State* L, int idx) {
|
||||
if (auto bar = dynamic_cast<TrackBar*>(node)) {
|
||||
bar->setValue(lua::tonumber(L, idx));
|
||||
@ -777,6 +800,7 @@ static int l_gui_setattr(lua::State* L) {
|
||||
{"inventory", p_set_inventory},
|
||||
{"cursor", p_set_cursor},
|
||||
{"focused", p_set_focused},
|
||||
{"region", p_set_region},
|
||||
};
|
||||
auto func = setters.find(attr);
|
||||
if (func != setters.end()) {
|
||||
|
||||
@ -154,6 +154,22 @@ std::unique_ptr<Process> scripting::start_coroutine(
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] scriptenv scripting::create_environment(
|
||||
const scriptenv& parent
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
int id = lua::create_environment(L, (parent ? *parent : 0));
|
||||
lua::pushenv(L, id);
|
||||
lua::pushvalue(L, -1);
|
||||
lua::setfield(L, "CUR_ENV");
|
||||
|
||||
lua::pop(L);
|
||||
return std::shared_ptr<int>(new int(id), [=](int* id) { //-V508
|
||||
lua::remove_environment(L, *id);
|
||||
delete id;
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] scriptenv scripting::create_doc_environment(
|
||||
const scriptenv& parent, const std::string& name
|
||||
) {
|
||||
|
||||
@ -58,6 +58,7 @@ namespace scripting {
|
||||
|
||||
scriptenv get_root_environment();
|
||||
scriptenv create_pack_environment(const ContentPack& pack);
|
||||
scriptenv create_environment(const scriptenv& parent);
|
||||
scriptenv create_doc_environment(
|
||||
const scriptenv& parent, const std::string& name
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user