From 5af6b91b22a33e5e1abcf2f6375ccab0857b0091 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 10 Mar 2025 18:06:39 +0300 Subject: [PATCH 01/36] add debug GUI render mode (F8) --- src/constants.hpp | 2 + src/engine/Engine.cpp | 3 ++ src/graphics/core/Batch2D.cpp | 7 +++- src/graphics/core/Batch2D.hpp | 6 ++- src/graphics/render/TextsRenderer.cpp | 3 +- src/graphics/ui/GUI.cpp | 44 ++++++++++++++++++++-- src/graphics/ui/GUI.hpp | 10 ++++- src/graphics/ui/elements/Container.cpp | 6 +-- src/graphics/ui/elements/Container.hpp | 2 +- src/graphics/ui/elements/InventoryView.cpp | 2 +- src/graphics/ui/elements/Label.hpp | 5 ++- src/graphics/ui/elements/Menu.cpp | 4 +- src/graphics/ui/elements/Panel.cpp | 2 +- src/graphics/ui/elements/Panel.hpp | 6 +-- src/graphics/ui/elements/Plotter.cpp | 3 +- src/graphics/ui/elements/UINode.cpp | 2 +- src/logic/scripting/lua/libs/libgui.cpp | 2 +- src/window/Camera.cpp | 13 ++++--- 18 files changed, 93 insertions(+), 29 deletions(-) diff --git a/src/constants.hpp b/src/constants.hpp index 9337513f..1accd619 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -68,3 +68,5 @@ inline const std::string LAYOUTS_FOLDER = "layouts"; inline const std::string SOUNDS_FOLDER = "sounds"; inline const std::string MODELS_FOLDER = "models"; inline const std::string SKELETONS_FOLDER = "skeletons"; + +inline const std::string FONT_DEFAULT = "normal"; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 80d774be..c9184383 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -163,6 +163,9 @@ void Engine::updateHotkeys() { if (Events::jpressed(keycode::F2)) { saveScreenshot(); } + if (Events::jpressed(keycode::F8)) { + gui->toggleDebug(); + } if (Events::jpressed(keycode::F11)) { settings.display.fullscreen.toggle(); } diff --git a/src/graphics/core/Batch2D.cpp b/src/graphics/core/Batch2D.cpp index b7ccf5ff..73ac9be2 100644 --- a/src/graphics/core/Batch2D.cpp +++ b/src/graphics/core/Batch2D.cpp @@ -142,7 +142,7 @@ void Batch2D::rect( bool flippedY, glm::vec4 tint ) { - if (index + 6*B2D_VERTEX_SIZE >= capacity) { + if (index + 6 * B2D_VERTEX_SIZE >= capacity) { flush(); } setPrimitive(DrawPrimitive::triangle); @@ -230,6 +230,11 @@ void Batch2D::rect( } void Batch2D::lineRect(float x, float y, float w, float h) { + if (index + 8 * B2D_VERTEX_SIZE >= capacity) { + flush(); + } + setPrimitive(DrawPrimitive::line); + vertex(x, y, 0.0f, 0.0f, color.r, color.g, color.b, color.a); vertex(x, y+h, 0.0f, 1.0f, color.r, color.g, color.b, color.a); diff --git a/src/graphics/core/Batch2D.hpp b/src/graphics/core/Batch2D.hpp index 5610f603..8db99d99 100644 --- a/src/graphics/core/Batch2D.hpp +++ b/src/graphics/core/Batch2D.hpp @@ -48,10 +48,14 @@ public: void sprite(float x, float y, float w, float h, float skew, int atlasRes, int index, glm::vec4 tint); void point(float x, float y, float r, float g, float b, float a); - void setColor(glm::vec4 color) { + void setColor(const glm::vec4& color) { this->color = color; } + void setColor(int r, int g, int b, int a=255) { + this->color = glm::vec4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); + } + void resetColor() { this->color = glm::vec4(1.0f); } diff --git a/src/graphics/render/TextsRenderer.cpp b/src/graphics/render/TextsRenderer.cpp index 32681ce5..283dacaa 100644 --- a/src/graphics/render/TextsRenderer.cpp +++ b/src/graphics/render/TextsRenderer.cpp @@ -10,6 +10,7 @@ #include "graphics/core/Batch3D.hpp" #include "graphics/core/Shader.hpp" #include "presets/NotePreset.hpp" +#include "constants.hpp" TextsRenderer::TextsRenderer( Batch3D& batch, const Assets& assets, const Frustum& frustum @@ -44,7 +45,7 @@ void TextsRenderer::renderNote( } opacity = preset.xrayOpacity; } - const auto& font = assets.require("normal"); + const auto& font = assets.require(FONT_DEFAULT); glm::vec3 xvec = note.getAxisX(); glm::vec3 yvec = note.getAxisY(); diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 55dd8ceb..7821973b 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -11,14 +11,15 @@ #include "frontend/UiDocument.hpp" #include "frontend/locale.hpp" #include "graphics/core/Batch2D.hpp" +#include "graphics/core/LineBatch.hpp" #include "graphics/core/Shader.hpp" +#include "graphics/core/Font.hpp" #include "graphics/core/DrawContext.hpp" #include "window/Events.hpp" #include "window/Window.hpp" #include "window/input.hpp" #include "window/Camera.hpp" -#include #include #include @@ -238,6 +239,39 @@ void GUI::draw(const DrawContext& pctx, const Assets& assets) { if (hover) { Window::setCursor(hover->getCursor()); } + if (hover && debug) { + auto pos = hover->calcPos(); + const auto& id = hover->getId(); + if (!id.empty()) { + auto& font = assets.require(FONT_DEFAULT); + auto text = util::str2wstr_utf8(id); + int width = font.calcWidth(text); + int height = font.getLineHeight(); + + batch2D->untexture(); + batch2D->setColor(0, 0, 0); + batch2D->rect(pos.x, pos.y, width, height); + + batch2D->resetColor(); + font.draw(*batch2D, text, pos.x, pos.y, nullptr, 0); + } + + batch2D->untexture(); + auto node = hover->getParent(); + while (node) { + auto pos = node->calcPos(); + auto size = node->getSize(); + + batch2D->setColor(0, 0, 255); + batch2D->lineRect(pos.x, pos.y, size.x-1, size.y-1); + + node = node->getParent(); + } + // debug draw + auto size = hover->getSize(); + batch2D->setColor(0, 255, 0); + batch2D->lineRect(pos.x, pos.y, size.x-1, size.y-1); + } } std::shared_ptr GUI::getFocused() const { @@ -252,8 +286,8 @@ void GUI::add(std::shared_ptr node) { container->add(std::move(node)); } -void GUI::remove(std::shared_ptr node) noexcept { - container->remove(std::move(node)); +void GUI::remove(UINode* node) noexcept { + container->remove(node); } void GUI::store(const std::string& name, std::shared_ptr node) { @@ -297,3 +331,7 @@ void GUI::setDoubleClickDelay(float delay) { float GUI::getDoubleClickDelay() const { return doubleClickDelay; } + +void GUI::toggleDebug() { + debug = !debug; +} diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index cb6b037e..1099cc89 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -14,6 +14,7 @@ class DrawContext; class Assets; class Camera; class Batch2D; +class LineBatch; /* Some info about padding and margin. @@ -73,6 +74,7 @@ namespace gui { float doubleClickTimer = 0.0f; float doubleClickDelay = 0.5f; bool doubleClicked = false; + bool debug = false; void actMouse(float delta); void actFocused(); @@ -113,7 +115,11 @@ namespace gui { void add(std::shared_ptr node); /// @brief Remove node from the main container - void remove(std::shared_ptr node) noexcept; + void remove(UINode* node) noexcept; + + void remove(const std::shared_ptr& node) noexcept { + return remove(node.get()); + } /// @brief Store node in the GUI nodes dictionary /// (does not add node to the main container) @@ -144,5 +150,7 @@ namespace gui { void setDoubleClickDelay(float delay); float getDoubleClickDelay() const; + + void toggleDebug(); }; } diff --git a/src/graphics/ui/elements/Container.cpp b/src/graphics/ui/elements/Container.cpp index 2d4eaf53..ddf12de7 100644 --- a/src/graphics/ui/elements/Container.cpp +++ b/src/graphics/ui/elements/Container.cpp @@ -172,11 +172,11 @@ void Container::add(const std::shared_ptr& node, glm::vec2 pos) { add(node); } -void Container::remove(const std::shared_ptr& selected) { +void Container::remove(UINode* selected) { selected->setParent(nullptr); nodes.erase(std::remove_if(nodes.begin(), nodes.end(), [selected](const std::shared_ptr& node) { - return node == selected; + return node.get() == selected; } ), nodes.end()); refresh(); @@ -185,7 +185,7 @@ void Container::remove(const std::shared_ptr& selected) { void Container::remove(const std::string& id) { for (auto& node : nodes) { if (node->getId() == id) { - return remove(node); + return remove(node.get()); } } } diff --git a/src/graphics/ui/elements/Container.hpp b/src/graphics/ui/elements/Container.hpp index c51d528a..79d3fe62 100644 --- a/src/graphics/ui/elements/Container.hpp +++ b/src/graphics/ui/elements/Container.hpp @@ -32,7 +32,7 @@ namespace gui { virtual void add(const std::shared_ptr& node); virtual void add(const std::shared_ptr& node, glm::vec2 pos); virtual void clear(); - virtual void remove(const std::shared_ptr& node); + virtual void remove(UINode* node); virtual void remove(const std::string& id); virtual void scrolled(int value) override; virtual void setScrollable(bool flag); diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index a5a2c8a5..099b9c1b 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -207,7 +207,7 @@ void SlotView::draw(const DrawContext& pctx, const Assets& assets) { drawItemIcon(batch, stack, item, assets, tint, pos); if (stack.getCount() > 1 || stack.getFields() != nullptr) { - const auto& font = assets.require("normal"); + const auto& font = assets.require(FONT_DEFAULT); drawItemInfo(batch, stack, item, font, pos); } } diff --git a/src/graphics/ui/elements/Label.hpp b/src/graphics/ui/elements/Label.hpp index 63637d5f..1fbf5f5e 100644 --- a/src/graphics/ui/elements/Label.hpp +++ b/src/graphics/ui/elements/Label.hpp @@ -1,6 +1,7 @@ #pragma once #include "UINode.hpp" +#include "constants.hpp" class Font; struct FontStylesScheme; @@ -61,8 +62,8 @@ namespace gui { std::unique_ptr styles; public: - Label(const std::string& text, std::string fontName="normal"); - Label(const std::wstring& text, std::string fontName="normal"); + Label(const std::string& text, std::string fontName=FONT_DEFAULT); + Label(const std::wstring& text, std::string fontName=FONT_DEFAULT); virtual ~Label(); diff --git a/src/graphics/ui/elements/Menu.cpp b/src/graphics/ui/elements/Menu.cpp index d059d693..d18f725a 100644 --- a/src/graphics/ui/elements/Menu.cpp +++ b/src/graphics/ui/elements/Menu.cpp @@ -55,7 +55,7 @@ void Menu::setPage(const std::string &name, bool history) { void Menu::setPage(Page page, bool history) { if (current.panel) { - Container::remove(current.panel); + Container::remove(current.panel.get()); if (history && !current.temporal) { pageStack.push(current); } @@ -104,7 +104,7 @@ void Menu::clearHistory() { void Menu::reset() { clearHistory(); if (current.panel) { - Container::remove(current.panel); + Container::remove(current.panel.get()); current = Page {"", nullptr}; } } diff --git a/src/graphics/ui/elements/Panel.cpp b/src/graphics/ui/elements/Panel.cpp index 70446d7d..4f3d91f2 100644 --- a/src/graphics/ui/elements/Panel.cpp +++ b/src/graphics/ui/elements/Panel.cpp @@ -63,7 +63,7 @@ void Panel::add(const std::shared_ptr &node) { fullRefresh(); } -void Panel::remove(const std::shared_ptr &node) { +void Panel::remove(UINode* node) { Container::remove(node); fullRefresh(); } diff --git a/src/graphics/ui/elements/Panel.hpp b/src/graphics/ui/elements/Panel.hpp index 5a6399ce..b2e12c1f 100644 --- a/src/graphics/ui/elements/Panel.hpp +++ b/src/graphics/ui/elements/Panel.hpp @@ -7,14 +7,14 @@ namespace gui { class Panel : public Container { protected: Orientation orientation = Orientation::vertical; - glm::vec4 padding {2.0f}; + glm::vec4 padding; float interval = 2.0f; int minLength = 0; int maxLength = 0; public: Panel( glm::vec2 size, - glm::vec4 padding=glm::vec4(2.0f), + glm::vec4 padding=glm::vec4(0.0f), float interval=2.0f ); virtual ~Panel(); @@ -25,7 +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 remove(UINode* node) override; virtual void refresh() override; virtual void fullRefresh() override; diff --git a/src/graphics/ui/elements/Plotter.cpp b/src/graphics/ui/elements/Plotter.cpp index 6cf484ea..860f6b69 100644 --- a/src/graphics/ui/elements/Plotter.cpp +++ b/src/graphics/ui/elements/Plotter.cpp @@ -5,6 +5,7 @@ #include "graphics/core/DrawContext.hpp" #include "assets/Assets.hpp" #include "util/stringutil.hpp" +#include "constants.hpp" using namespace gui; @@ -37,7 +38,7 @@ void Plotter::draw(const DrawContext& pctx, const Assets& assets) { } int current_point = static_cast(points[index % dmwidth]); - auto font = assets.get("normal"); + auto font = assets.get(FONT_DEFAULT); for (int y = 0; y < dmheight; y += labelsInterval) { std::wstring string; if (current_point/16 == y/labelsInterval) { diff --git a/src/graphics/ui/elements/UINode.cpp b/src/graphics/ui/elements/UINode.cpp index f6444f41..7a9e4b87 100644 --- a/src/graphics/ui/elements/UINode.cpp +++ b/src/graphics/ui/elements/UINode.cpp @@ -266,7 +266,7 @@ void UINode::moveInto( ) { auto parent = node->getParent(); if (auto container = dynamic_cast(parent)) { - container->remove(node); + container->remove(node.get()); } if (parent) { parent->scrolled(0); diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index cbb01517..273ee393 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -96,7 +96,7 @@ static int l_node_destruct(lua::State* L) { engine->getGUI()->postRunnable([node]() { auto parent = node->getParent(); if (auto container = dynamic_cast(parent)) { - container->remove(node); + container->remove(node.get()); } }); return 0; diff --git a/src/window/Camera.cpp b/src/window/Camera.cpp index f3780d0f..315acf56 100644 --- a/src/window/Camera.cpp +++ b/src/window/Camera.cpp @@ -33,14 +33,15 @@ glm::mat4 Camera::getProjection() const { constexpr float epsilon = 1e-6f; // 0.000001 float aspect_ratio = this->aspect; if (std::fabs(aspect_ratio) < epsilon) { - aspect_ratio = (float)Window::width / (float)Window::height; + aspect_ratio = Window::width / static_cast(Window::height); } - if (perspective) + if (perspective) { return glm::perspective(fov * zoom, aspect_ratio, near, far); - else if (flipped) - return glm::ortho(0.0f, fov * aspect_ratio, fov, 0.0f); - else - return glm::ortho(0.0f, fov * aspect_ratio, 0.0f, fov); + } else if (flipped) { + return glm::ortho(-0.5f, fov * aspect_ratio-0.5f, fov, 0.0f); + } else { + return glm::ortho(-0.5f, fov * aspect_ratio-0.5f, 0.0f, fov); + } } glm::mat4 Camera::getView(bool pos) const { From 567105dfce6a30b4476d965fd319bc16c4d5ad7e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 10 Mar 2025 23:00:55 +0300 Subject: [PATCH 02/36] add RGBA support to markdown --- src/graphics/ui/markdown.cpp | 49 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/graphics/ui/markdown.cpp b/src/graphics/ui/markdown.cpp index 6301c68d..0c84e546 100644 --- a/src/graphics/ui/markdown.cpp +++ b/src/graphics/ui/markdown.cpp @@ -23,10 +23,6 @@ static inline void emit_md( template static glm::vec4 parse_color(const std::basic_string_view& color_code) { - if (color_code.size() != 8 || color_code[0] != '#') { - return glm::vec4(1, 1, 1, 1); // default to white - } - auto hex_to_float = [](char high, char low) { int high_val = hexchar2int(high); int low_val = hexchar2int(low); @@ -36,11 +32,18 @@ static glm::vec4 parse_color(const std::basic_string_view& color_code) { return (high_val * 16 + low_val) / 255.0f; }; + if (color_code[0] != '#') { + return glm::vec4(1, 1, 1, 1); + } + + if (color_code.size() < 8) { + return glm::vec4(1, 1, 1, 1); + } return glm::vec4( hex_to_float(color_code[1], color_code[2]), hex_to_float(color_code[3], color_code[4]), hex_to_float(color_code[5], color_code[6]), - 1 + color_code.size() == 10 ? hex_to_float(color_code[7], color_code[8]) : 1 ); } @@ -99,18 +102,24 @@ Result process_markdown( pos++; continue; case '[': - if (pos + 9 < source.size() && source[pos + 1] == '#' && - source[pos + 8] == ']') { - if (!eraseMarkdown) { - emit_md(source[pos - 1], styles, ss); + if (source[pos + 1] == '#') { + int closingPos = -1; + if (pos + 8 < source.size() && source[pos + 8] == ']') { + closingPos = 8; + } else if (pos + 10 < source.size() && source[pos + 10] == ']') { + closingPos = 10; } - for (int i = 0; i < 10; ++i) { - - emit(source[pos + i], styles, ss); + if (closingPos != -1) { + if (!eraseMarkdown) { + emit_md(source[pos - 1], styles, ss); + } + int length = closingPos + 2; + for (int i = 0; i < length; ++i) { + emit(source[pos + i], styles, ss); + } + pos += length; + continue; } - - pos += 10; - continue; } } pos--; @@ -125,6 +134,16 @@ Result process_markdown( } pos += 9; // Skip past the color code continue; + } else if (first == '[' && pos + 11 <= source.size() && source[pos + 1] == '#' && source[pos + 10] == ']') { + std::basic_string_view color_code = source.substr(pos + 1, 10); + apply_color(color_code, styles, style); + if (!eraseMarkdown) { + for (int i = 0; i < 11; ++i) { + emit_md(source[pos + i], styles, ss); + } + } + pos += 11; // Skip past the color code + continue; } else if (first == '*') { if (pos + 1 < source.size() && source[pos + 1] == '*') { pos++; From c6492b3d252f07a40e11fc2441f499c557004f36 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 10 Mar 2025 23:01:34 +0300 Subject: [PATCH 03/36] add file.parent(...) --- res/scripts/stdmin.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index e2e4d3c7..b62bd9cc 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -543,3 +543,11 @@ end function file.prefix(path) return path:match("^([^:]+)") end + +function file.parent(path) + local pos = path:find("/") + if not pos then + return file.prefix(path)..":" + end + return path:sub(0, pos-1) +end From 9a01b5ad2d2bd37a9fa8c7ce2ebc4bd76e00060c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 10 Mar 2025 23:09:07 +0300 Subject: [PATCH 04/36] add splitbox ui element --- src/graphics/ui/elements/SplitBox.cpp | 62 +++++++++++++++++++++++++++ src/graphics/ui/elements/SplitBox.hpp | 18 ++++++++ src/graphics/ui/gui_xml.cpp | 16 +++++++ 3 files changed, 96 insertions(+) create mode 100644 src/graphics/ui/elements/SplitBox.cpp create mode 100644 src/graphics/ui/elements/SplitBox.hpp diff --git a/src/graphics/ui/elements/SplitBox.cpp b/src/graphics/ui/elements/SplitBox.cpp new file mode 100644 index 00000000..1d73e0fd --- /dev/null +++ b/src/graphics/ui/elements/SplitBox.cpp @@ -0,0 +1,62 @@ +#include "SplitBox.hpp" + +using namespace gui; + +SplitBox::SplitBox(const glm::vec2& size, float splitPos, Orientation orientation) + : Container(size), splitPos(splitPos), orientation(orientation) { + setCursor( + orientation == Orientation::vertical ? CursorShape::NS_RESIZE + : CursorShape::EW_RESIZE + ); +} + +void SplitBox::mouseMove(GUI*, int x, int y) { + auto pos = calcPos(); + auto size = getSize(); + + glm::ivec2 cursor(x - pos.x, y - pos.y); + int axis = orientation == Orientation::vertical; + + int v = cursor[axis]; + v = std::max(std::min(static_cast(size[axis]) - 10, v), 10); + float t = v / size[axis]; + splitPos = t; + + refresh(); +} + +void SplitBox::refresh() { + Container::refresh(); + + if (nodes.empty()) { + return; + } + glm::vec2 size = getSize(); + if (nodes.size() == 1) { + auto node = nodes.at(0); + node->setPos(glm::vec2()); + node->setSize(size); + return; + } + auto nodeA = nodes.at(0); + auto nodeB = nodes.at(1); + + nodeA->setPos(glm::vec2()); + if (orientation == Orientation::vertical) { + float splitPos = this->splitPos * size.y; + nodeA->setSize(glm::vec2(size.x, splitPos - splitRadius)); + nodeB->setSize(glm::vec2(size.x, size.y - splitPos - splitRadius)); + nodeB->setPos(glm::vec2(0.0f, splitPos + splitRadius)); + } else { + float splitPos = this->splitPos * size.x; + nodeA->setSize(glm::vec2(splitPos - splitRadius, size.y)); + nodeB->setSize(glm::vec2(size.x - splitPos - splitRadius, size.y)); + nodeB->setPos(glm::vec2(splitPos + splitRadius, 0.0f)); + } +} + +void SplitBox::fullRefresh() { + refresh(); + reposition(); + Container::fullRefresh(); +} diff --git a/src/graphics/ui/elements/SplitBox.hpp b/src/graphics/ui/elements/SplitBox.hpp new file mode 100644 index 00000000..0e9092a8 --- /dev/null +++ b/src/graphics/ui/elements/SplitBox.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "Container.hpp" + +namespace gui { + class SplitBox : public Container { + public: + SplitBox(const glm::vec2& size, float splitPos, Orientation orientation); + + virtual void mouseMove(GUI*, int x, int y) override; + virtual void refresh() override; + virtual void fullRefresh() override; + private: + float splitPos; + int splitRadius = 2; + Orientation orientation; + }; +} diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index ee0b7489..bb913f4d 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -7,6 +7,7 @@ #include "elements/Canvas.hpp" #include "elements/CheckBox.hpp" #include "elements/TextBox.hpp" +#include "elements/SplitBox.hpp" #include "elements/TrackBar.hpp" #include "elements/InputBindBox.hpp" #include "elements/InventoryView.hpp" @@ -313,6 +314,20 @@ static std::shared_ptr read_container( return container; } +static std::shared_ptr read_split_box( + UiXmlReader& reader, const xml::xmlelement& element +) { + float splitPos = element.attr("split-pos", "0.5").asFloat(); + Orientation orientation = + element.attr("orientation", "vertical").getText() == "horizontal" + ? Orientation::horizontal + : Orientation::vertical; + auto splitBox = + std::make_shared(glm::vec2(), splitPos, orientation); + read_container_impl(reader, element, *splitBox); + return splitBox; +} + static std::shared_ptr read_panel( UiXmlReader& reader, const xml::xmlelement& element ) { @@ -677,6 +692,7 @@ UiXmlReader::UiXmlReader(const scriptenv& env) : env(env) { add("button", read_button); add("textbox", read_text_box); add("pagebox", read_page_box); + add("splitbox", read_split_box); add("checkbox", read_check_box); add("trackbar", read_track_bar); add("container", read_container); From 4c9019e03cf8e982c2131830edff3abfbb7e1560 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 10 Mar 2025 23:09:50 +0300 Subject: [PATCH 05/36] small fixes --- src/frontend/hud.cpp | 8 +++++--- src/frontend/hud.hpp | 2 +- src/graphics/ui/GUI.cpp | 2 +- src/graphics/ui/elements/Plotter.hpp | 1 - src/graphics/ui/elements/TextBox.cpp | 6 ++++++ src/graphics/ui/elements/TextBox.hpp | 1 + src/graphics/ui/elements/UINode.cpp | 8 ++++++-- src/graphics/ui/elements/UINode.hpp | 2 +- 8 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 1684799f..48a699f1 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -262,7 +262,7 @@ void Hud::updateHotbarControl() { } } -void Hud::updateWorldGenDebugVisualization() { +void Hud::updateWorldGenDebug() { auto& level = frontend.getLevel(); const auto& chunks = *player.chunks; auto generator = @@ -314,7 +314,9 @@ void Hud::update(bool visible) { const auto& chunks = *player.chunks; const auto& menu = gui.getMenu(); - debugPanel->setVisible(debug && visible); + debugPanel->setVisible( + debug && visible && !(inventoryOpen && inventoryView == nullptr) + ); if (!visible && inventoryOpen) { closeInventory(); @@ -358,7 +360,7 @@ void Hud::update(bool visible) { debugMinimap->setVisible(debug && showGeneratorMinimap); if (debug && showGeneratorMinimap) { - updateWorldGenDebugVisualization(); + updateWorldGenDebug(); } } diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp index a978e282..3522cd34 100644 --- a/src/frontend/hud.hpp +++ b/src/frontend/hud.hpp @@ -135,7 +135,7 @@ class Hud : public util::ObjectsKeeper { void dropExchangeSlot(); void showExchangeSlot(); - void updateWorldGenDebugVisualization(); + void updateWorldGenDebug(); public: Hud(Engine& engine, LevelFrontend& frontend, Player& player); ~Hud(); diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 7821973b..60025d82 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -262,7 +262,7 @@ void GUI::draw(const DrawContext& pctx, const Assets& assets) { auto pos = node->calcPos(); auto size = node->getSize(); - batch2D->setColor(0, 0, 255); + batch2D->setColor(0, 255, 255); batch2D->lineRect(pos.x, pos.y, size.x-1, size.y-1); node = node->getParent(); diff --git a/src/graphics/ui/elements/Plotter.hpp b/src/graphics/ui/elements/Plotter.hpp index 776dda31..13fa3d89 100644 --- a/src/graphics/ui/elements/Plotter.hpp +++ b/src/graphics/ui/elements/Plotter.hpp @@ -4,7 +4,6 @@ #include "typedefs.hpp" #include -#include class Assets; class DrawContext; diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 791dcde8..609231c0 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -404,6 +404,12 @@ void TextBox::onFocus(GUI* gui) { } } +void TextBox::reposition() { + auto size = getSize(); + UINode::reposition(); + refreshLabel(); +} + void TextBox::refresh() { Container::refresh(); label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y)); diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index 0eff89f2..7dd77525 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -210,6 +210,7 @@ namespace gui { virtual void setShowLineNumbers(bool flag); virtual bool isShowLineNumbers() const; + virtual void reposition() override; virtual void onFocus(GUI*) override; virtual void refresh() override; virtual void doubleClick(GUI*, int x, int y) override; diff --git a/src/graphics/ui/elements/UINode.cpp b/src/graphics/ui/elements/UINode.cpp index 7a9e4b87..8dc04f68 100644 --- a/src/graphics/ui/elements/UINode.cpp +++ b/src/graphics/ui/elements/UINode.cpp @@ -301,9 +301,13 @@ const std::string& UINode::getId() const { void UINode::reposition() { if (sizefunc) { auto newSize = sizefunc(); + auto defsize = newSize; + if (parent) { + defsize = parent->getSize(); + } setSize( - {newSize.x < 0 ? size.x : newSize.x, - newSize.y < 0 ? size.y : newSize.y} + {newSize.x < 0 ? defsize.x : newSize.x, + newSize.y < 0 ? defsize.y : newSize.y} ); } if (positionfunc) { diff --git a/src/graphics/ui/elements/UINode.hpp b/src/graphics/ui/elements/UINode.hpp index 2f62dd27..58009e00 100644 --- a/src/graphics/ui/elements/UINode.hpp +++ b/src/graphics/ui/elements/UINode.hpp @@ -250,7 +250,7 @@ namespace gui { const std::string& getId() const; /// @brief Fetch pos from positionfunc if assigned - void reposition(); + virtual void reposition(); virtual void setGravity(Gravity gravity); From c86bad8def0e13760d8ff11532c381f445a997a0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 10 Mar 2025 23:10:33 +0300 Subject: [PATCH 06/36] update console layout & add files panel (WIP) --- res/layouts/console.xml | 60 ++++++++++++++++++++----------------- res/layouts/console.xml.lua | 1 - 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/res/layouts/console.xml b/res/layouts/console.xml index 18d72163..fd2dd947 100644 --- a/res/layouts/console.xml +++ b/res/layouts/console.xml @@ -20,38 +20,42 @@ margin='0' editable='false' multiline='true' - size-func="gui.get_viewport()[1]-350,40" + size-func="-1,40" gravity="bottom-left" markup="md" > - - - - - - - - + + + + + + + + + + + + + + + Date: Mon, 10 Mar 2025 23:44:43 +0300 Subject: [PATCH 07/36] add BasePanel & add padding property to SplitBox --- src/graphics/ui/elements/BasePanel.hpp | 43 ++++++++++++++++++++++++++ src/graphics/ui/elements/Panel.cpp | 21 +------------ src/graphics/ui/elements/Panel.hpp | 19 +++--------- src/graphics/ui/elements/SplitBox.cpp | 20 +++++++----- src/graphics/ui/elements/SplitBox.hpp | 6 ++-- src/graphics/ui/gui_xml.cpp | 37 ++++++++++++++++------ 6 files changed, 90 insertions(+), 56 deletions(-) create mode 100644 src/graphics/ui/elements/BasePanel.hpp diff --git a/src/graphics/ui/elements/BasePanel.hpp b/src/graphics/ui/elements/BasePanel.hpp new file mode 100644 index 00000000..6f9bf826 --- /dev/null +++ b/src/graphics/ui/elements/BasePanel.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "Container.hpp" + +namespace gui { + class BasePanel : public Container { + public: + virtual ~BasePanel() = default; + + virtual void setOrientation(Orientation orientation) { + this->orientation = orientation; + refresh(); + } + + Orientation getOrientation() const { + return orientation; + } + + virtual void setPadding(glm::vec4 padding) { + this->padding = padding; + refresh(); + } + + glm::vec4 getPadding() const { + return padding; + } + protected: + BasePanel( + glm::vec2 size, + glm::vec4 padding = glm::vec4(0.0f), + float interval = 2.0f, + Orientation orientation = Orientation::vertical + ) + : Container(std::move(size)), + padding(std::move(padding)), + interval(interval) { + } + + Orientation orientation = Orientation::vertical; + glm::vec4 padding; + float interval = 2.0f; + }; +} diff --git a/src/graphics/ui/elements/Panel.cpp b/src/graphics/ui/elements/Panel.cpp index 4f3d91f2..4cc3730e 100644 --- a/src/graphics/ui/elements/Panel.cpp +++ b/src/graphics/ui/elements/Panel.cpp @@ -5,9 +5,7 @@ using namespace gui; Panel::Panel(glm::vec2 size, glm::vec4 padding, float interval) - : Container(size), - padding(padding), - interval(interval) + : BasePanel(size, padding, interval, Orientation::vertical) { setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.75f)); } @@ -31,15 +29,6 @@ int Panel::getMinLength() const { return minLength; } -void Panel::setPadding(glm::vec4 padding) { - this->padding = padding; - refresh(); -} - -glm::vec4 Panel::getPadding() const { - return padding; -} - void Panel::cropToContent() { if (maxLength > 0.0f) { setSize(glm::vec2( @@ -109,11 +98,3 @@ void Panel::refresh() { actualLength = size.y; } } - -void Panel::setOrientation(Orientation orientation) { - this->orientation = orientation; -} - -Orientation Panel::getOrientation() const { - return orientation; -} diff --git a/src/graphics/ui/elements/Panel.hpp b/src/graphics/ui/elements/Panel.hpp index b2e12c1f..7a16c018 100644 --- a/src/graphics/ui/elements/Panel.hpp +++ b/src/graphics/ui/elements/Panel.hpp @@ -1,16 +1,10 @@ #pragma once #include "commons.hpp" -#include "Container.hpp" +#include "BasePanel.hpp" namespace gui { - class Panel : public Container { - protected: - Orientation orientation = Orientation::vertical; - glm::vec4 padding; - float interval = 2.0f; - int minLength = 0; - int maxLength = 0; + class Panel : public BasePanel { public: Panel( glm::vec2 size, @@ -21,9 +15,6 @@ namespace gui { virtual void cropToContent(); - virtual void setOrientation(Orientation orientation); - Orientation getOrientation() const; - virtual void add(const std::shared_ptr& node) override; virtual void remove(UINode* node) override; @@ -35,8 +26,8 @@ namespace gui { virtual void setMinLength(int value); int getMinLength() const; - - virtual void setPadding(glm::vec4 padding); - glm::vec4 getPadding() const; + protected: + int minLength = 0; + int maxLength = 0; }; } diff --git a/src/graphics/ui/elements/SplitBox.cpp b/src/graphics/ui/elements/SplitBox.cpp index 1d73e0fd..4ea5c5ff 100644 --- a/src/graphics/ui/elements/SplitBox.cpp +++ b/src/graphics/ui/elements/SplitBox.cpp @@ -3,7 +3,7 @@ using namespace gui; SplitBox::SplitBox(const glm::vec2& size, float splitPos, Orientation orientation) - : Container(size), splitPos(splitPos), orientation(orientation) { + : BasePanel(size, glm::vec4(), 4.0f, orientation), splitPos(splitPos) { setCursor( orientation == Orientation::vertical ? CursorShape::NS_RESIZE : CursorShape::EW_RESIZE @@ -40,18 +40,22 @@ void SplitBox::refresh() { } auto nodeA = nodes.at(0); auto nodeB = nodes.at(1); + + float sepRadius = interval / 2.0f; - nodeA->setPos(glm::vec2()); + nodeA->setPos(glm::vec2(padding)); + + const auto& p = padding; if (orientation == Orientation::vertical) { float splitPos = this->splitPos * size.y; - nodeA->setSize(glm::vec2(size.x, splitPos - splitRadius)); - nodeB->setSize(glm::vec2(size.x, size.y - splitPos - splitRadius)); - nodeB->setPos(glm::vec2(0.0f, splitPos + splitRadius)); + nodeA->setSize({size.x-p.x-p.z, splitPos - sepRadius - p.y}); + nodeB->setSize({size.x-p.x-p.z, size.y - splitPos - sepRadius - p.w}); + nodeB->setPos({p.x, splitPos + sepRadius}); } else { float splitPos = this->splitPos * size.x; - nodeA->setSize(glm::vec2(splitPos - splitRadius, size.y)); - nodeB->setSize(glm::vec2(size.x - splitPos - splitRadius, size.y)); - nodeB->setPos(glm::vec2(splitPos + splitRadius, 0.0f)); + nodeA->setSize({splitPos - sepRadius - p.x, size.y - p.y - p.w}); + nodeB->setSize({size.x - splitPos - sepRadius - p.z, size.y - p.y - p.w}); + nodeB->setPos({splitPos + sepRadius, p.y}); } } diff --git a/src/graphics/ui/elements/SplitBox.hpp b/src/graphics/ui/elements/SplitBox.hpp index 0e9092a8..00bab21e 100644 --- a/src/graphics/ui/elements/SplitBox.hpp +++ b/src/graphics/ui/elements/SplitBox.hpp @@ -1,9 +1,9 @@ #pragma once -#include "Container.hpp" +#include "BasePanel.hpp" namespace gui { - class SplitBox : public Container { + class SplitBox : public BasePanel { public: SplitBox(const glm::vec2& size, float splitPos, Orientation orientation); @@ -12,7 +12,5 @@ namespace gui { virtual void fullRefresh() override; private: float splitPos; - int splitRadius = 2; - Orientation orientation; }; } diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index bb913f4d..df0a89a9 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -207,11 +207,10 @@ void UiXmlReader::readUINode( read_uinode(reader, element, node); } -static void read_panel_impl( +static void read_base_panel_impl( UiXmlReader& reader, const xml::xmlelement& element, - Panel& panel, - bool subnodes = true + BasePanel& panel ) { read_uinode(reader, element, panel); @@ -224,6 +223,22 @@ static void read_panel_impl( size.y + padding.y + padding.w )); } + if (element.has("orientation")) { + auto &oname = element.attr("orientation").getText(); + if (oname == "horizontal") { + panel.setOrientation(Orientation::horizontal); + } + } +} + +static void read_panel_impl( + UiXmlReader& reader, + const xml::xmlelement& element, + Panel& panel, + bool subnodes = true +) { + read_base_panel_impl(reader, element, panel); + if (element.has("size")) { panel.setResizing(false); } @@ -233,12 +248,6 @@ static void read_panel_impl( 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") { - panel.setOrientation(Orientation::horizontal); - } - } if (subnodes) { for (auto& sub : element.getElements()) { if (sub->isText()) @@ -324,7 +333,15 @@ static std::shared_ptr read_split_box( : Orientation::vertical; auto splitBox = std::make_shared(glm::vec2(), splitPos, orientation); - read_container_impl(reader, element, *splitBox); + read_base_panel_impl(reader, element, *splitBox); + for (auto& sub : element.getElements()) { + if (sub->isText()) + continue; + auto subnode = reader.readUINode(*sub); + if (subnode) { + splitBox->add(subnode); + } + } return splitBox; } From 843fbad0bd73559c731ed8c2ecc51480a1b8bbc9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 11 Mar 2025 00:35:37 +0300 Subject: [PATCH 08/36] cleanup --- res/layouts/console.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/res/layouts/console.xml b/res/layouts/console.xml index fd2dd947..e0c5fa09 100644 --- a/res/layouts/console.xml +++ b/res/layouts/console.xml @@ -25,7 +25,8 @@ markup="md" > - + - + Date: Tue, 11 Mar 2025 03:48:27 +0300 Subject: [PATCH 09/36] update console layout --- res/layouts/console.xml | 75 +++++++++++++------------- res/layouts/console.xml.lua | 59 +++++++++++++++----- res/layouts/templates/problem.xml | 4 +- res/layouts/templates/script_file.xml | 6 +++ res/preload.json | 3 +- res/textures/gui/lock.png | Bin 0 -> 120 bytes 6 files changed, 95 insertions(+), 52 deletions(-) create mode 100644 res/layouts/templates/script_file.xml create mode 100644 res/textures/gui/lock.png diff --git a/res/layouts/console.xml b/res/layouts/console.xml index e0c5fa09..2262026e 100644 --- a/res/layouts/console.xml +++ b/res/layouts/console.xml @@ -2,17 +2,14 @@ + size-func="gui.get_viewport()[1],30"> - - - + size-func="unpack(vec2.add(gui.get_viewport(), {-450,-100}))"> - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - + diff --git a/res/layouts/templates/script_file.xml b/res/layouts/templates/script_file.xml new file mode 100644 index 00000000..ed2025ac --- /dev/null +++ b/res/layouts/templates/script_file.xml @@ -0,0 +1,6 @@ + diff --git a/res/preload.json b/res/preload.json index c4e60cad..de016e04 100644 --- a/res/preload.json +++ b/res/preload.json @@ -24,7 +24,8 @@ "misc/snow", "gui/check_mark", "gui/left_arrow", - "gui/right_arrow" + "gui/right_arrow", + "gui/lock" ], "fonts": [ { diff --git a/res/textures/gui/lock.png b/res/textures/gui/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..10c877a3a2e127b528084ff491c0c76869d92a70 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`_MR?|Ar*6y6Bh_1ARUv{T`_X+qsvIvNCutma!C@ SGFcvI6oaR$pUXO@geCxoha;K* literal 0 HcmV?d00001 From 9286f297c1acddaf68c7bda70c2b568e08a9af3d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 11 Mar 2025 16:19:09 +0300 Subject: [PATCH 10/36] add file.path(...), file.join(...) --- res/scripts/stdmin.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index b62bd9cc..5e6ac294 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -551,3 +551,15 @@ function file.parent(path) end return path:sub(0, pos-1) end + +function file.path(path) + local pos = path:find(':') + return path:sub(pos + 1) +end + +function file.join(a, b) + if a[#a] == ':' then + return a .. b + end + return a .. "/" .. b +end From fa990e393986aad07e9fb9a570338b08fd4f9a0c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 11 Mar 2025 16:19:33 +0300 Subject: [PATCH 11/36] add '@' prefix support to tooltips --- src/graphics/ui/gui_xml.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index df0a89a9..abe59c2c 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -158,7 +158,13 @@ static void read_uinode( } if (element.has("tooltip")) { - node.setTooltip(util::str2wstr_utf8(element.attr("tooltip").getText())); + auto tooltip = util::str2wstr_utf8(element.attr("tooltip").getText()); + if (!tooltip.empty() && tooltip[0] == '@') { + tooltip = langs::get( + tooltip.substr(1), util::str2wstr_utf8(reader.getContext()) + ); + } + node.setTooltip(tooltip); } if (element.has("tooltip-delay")) { node.setTooltipDelay(element.attr("tooltip-delay").asFloat()); From 223a846784d2e2a50923f899a9484249c05703b1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 01:51:46 +0300 Subject: [PATCH 12/36] add Action and ActionsHistory --- src/devtools/actions.hpp | 160 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/devtools/actions.hpp diff --git a/src/devtools/actions.hpp b/src/devtools/actions.hpp new file mode 100644 index 00000000..abe76bfc --- /dev/null +++ b/src/devtools/actions.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include +#include + +class Action { +public: + virtual ~Action() = default; + + virtual void apply() = 0; + virtual void revert() = 0; +}; + +class InversedAction : public Action { +public: + InversedAction(std::unique_ptr action) : action(std::move(action)) {} + + void apply() override { + action->revert(); + } + + void revert() override { + action->apply(); + } + private: + std::unique_ptr action; +}; + +class CombinedAction : public Action { +public: + CombinedAction(std::vector> actions) + : actions(std::move(actions)) { + } + + void apply() override { + for (auto& action : actions) { + action->apply(); + } + } + + void revert() override { + for (int i = actions.size() - 1; i >= 0; i--) { + actions[i]->revert(); + } + } +private: + std::vector> actions; +}; + +class ActionsHistory { +public: + ActionsHistory() {}; + + /// @brief Remove all actions available to redo + void clearRedo() { + if (actionPtr < actions.size()) { + actions.erase(actions.begin() + actionPtr, actions.end()); + } + } + + /// @brief Store action without applying + void store(std::unique_ptr action, bool reverse=false) { + if (lock) { + return; + } + if (reverse) { + action = std::make_unique(std::move(action)); + } + clearRedo(); + actions.emplace_back(std::move(action)); + actionPtr++; + } + + /// @brief Apply action and store it + void apply(std::unique_ptr action) { + if (lock) { + return; + } + clearRedo(); + lock = true; + action->apply(); + lock = false; + actions.emplace_back(std::move(action)); + actionPtr++; + } + + /// @brief Revert the last action + /// @return true if any action reverted + bool undo() { + if (lock || actionPtr == 0) { + return false; + } + auto& action = actions[--actionPtr]; + lock = true; + action->revert(); + lock = false; + return true; + } + + /// @brief Revert the last action + /// @return true if any action reapplied + bool redo() { + if (lock || actionPtr == actions.size()) { + return false; + } + auto& action = actions[actionPtr++]; + lock = true; + action->apply(); + lock = false; + return true; + } + + /// @brief Clear history without reverting actions + void clear() { + actionPtr = 0; + actions.clear(); + } + + /// @brief Squash last n actions into one CombinedAction + /// @param n number of actions to squash + void squash(ptrdiff_t n) { + if (n < 2) { + return; + } + n = std::min(n, static_cast(actionPtr)); + std::vector> squashing; + for (size_t i = actionPtr - n; i < actionPtr; i++) { + squashing.emplace_back(std::move(actions[i])); + } + actions.erase(actions.begin() + actionPtr - n, actions.end()); + actionPtr -= n; + store(std::make_unique(std::move(squashing))); + } + + size_t size() const { + return actionPtr; + } + + /// @brief On destruction squashing actions stored since initialization + struct Combination { + ActionsHistory& history; + size_t historySize; + + Combination(ActionsHistory& history) + : history(history), historySize(history.size()) { + } + + ~Combination() { + history.squash(history.size() - historySize); + } + }; + + Combination beginCombination() { + return Combination(*this); + } +private: + std::vector> actions; + size_t actionPtr = 0; + bool lock = false; +}; From 4c493aff258fc63a23d1aa91cd47568de4db9d21 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 01:52:08 +0300 Subject: [PATCH 13/36] add history to TextBox --- src/graphics/ui/GUI.cpp | 2 +- src/graphics/ui/elements/Container.cpp | 10 +- src/graphics/ui/elements/Container.hpp | 2 +- src/graphics/ui/elements/TextBox.cpp | 224 +++++++++++++++++++++++-- src/graphics/ui/elements/TextBox.hpp | 17 +- src/graphics/ui/elements/UINode.cpp | 4 +- src/graphics/ui/elements/UINode.hpp | 4 +- 7 files changed, 230 insertions(+), 33 deletions(-) diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 60025d82..a965e1e8 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -108,7 +108,7 @@ void GUI::actMouse(float delta) { doubleClicked = false; doubleClickTimer += delta + mouseDelta * 0.1f; - auto hover = container->getAt(Events::cursor, nullptr); + auto hover = container->getAt(Events::cursor); if (this->hover && this->hover != hover) { this->hover->setHover(false); } diff --git a/src/graphics/ui/elements/Container.cpp b/src/graphics/ui/elements/Container.cpp index ddf12de7..3fc25f66 100644 --- a/src/graphics/ui/elements/Container.cpp +++ b/src/graphics/ui/elements/Container.cpp @@ -17,9 +17,7 @@ Container::~Container() { Container::clear(); } -std::shared_ptr Container::getAt( - const glm::vec2& pos, const std::shared_ptr& self -) { +std::shared_ptr Container::getAt(const glm::vec2& pos) { if (!isInteractive() || !isEnabled()) { return nullptr; } @@ -28,19 +26,19 @@ std::shared_ptr Container::getAt( } int diff = (actualLength-size.y); if (scrollable && diff > 0 && pos.x > calcPos().x + getSize().x - scrollBarWidth) { - return UINode::getAt(pos, self); + return UINode::getAt(pos); } for (int i = nodes.size()-1; i >= 0; i--) { auto& node = nodes[i]; if (!node->isVisible()) continue; - auto hover = node->getAt(pos, node); + auto hover = node->getAt(pos); if (hover != nullptr) { return hover; } } - return UINode::getAt(pos, self); + return UINode::getAt(pos); } void Container::mouseMove(GUI* gui, int x, int y) { diff --git a/src/graphics/ui/elements/Container.hpp b/src/graphics/ui/elements/Container.hpp index 79d3fe62..f91fa915 100644 --- a/src/graphics/ui/elements/Container.hpp +++ b/src/graphics/ui/elements/Container.hpp @@ -28,7 +28,7 @@ namespace gui { virtual void act(float delta) override; virtual void drawBackground(const DrawContext& pctx, const Assets& assets); virtual void draw(const DrawContext& pctx, const Assets& assets) override; - virtual std::shared_ptr getAt(const glm::vec2& pos, const std::shared_ptr& self) override; + virtual std::shared_ptr getAt(const glm::vec2& pos) override; virtual void add(const std::shared_ptr& node); virtual void add(const std::shared_ptr& node, glm::vec2 pos); virtual void clear(); diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 609231c0..88b0c86f 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -13,18 +13,177 @@ #include "util/stringutil.hpp" #include "window/Events.hpp" #include "window/Window.hpp" +#include "devtools/actions.hpp" #include "../markdown.hpp" using namespace gui; inline constexpr int LINE_NUMBERS_PANE_WIDTH = 40; -TextBox::TextBox(std::wstring placeholder, glm::vec4 padding) - : Container(glm::vec2(200,32)), - padding(padding), - input(L""), - placeholder(std::move(placeholder)) -{ +class InputAction : public Action { + std::weak_ptr textbox; + size_t position; + std::wstring string; +public: + InputAction( + std::weak_ptr textbox, size_t position, std::wstring string + ) + : textbox(std::move(textbox)), + position(position), + string(std::move(string)) { + } + void apply() override { + if (auto box = textbox.lock()) { + box->select(position, position); + box->paste(string); + } + } + + void revert() override { + if (auto box = textbox.lock()) { + box->select(position, position); + box->erase(position, string.length()); + } + } +}; + +class SelectionAction : public Action { + std::weak_ptr textbox; + size_t start; + size_t end; +public: + SelectionAction(std::weak_ptr textbox, size_t start, size_t end) + : textbox(std::move(textbox)), start(start), end(end) {} + + void apply() override { + if (auto box = textbox.lock()) { + box->select(start, end); + } + } + + void revert() override { + if (auto box = textbox.lock()) { + box->select(0, 0); + } + } +}; + +namespace gui { + /// @brief Accumulates small changes into words for InputAction creation + class TextBoxHistorian { + public: + TextBoxHistorian(TextBox& textBox, ActionsHistory& history) + : textBox(textBox), history(history) { + } + + void onPaste(size_t pos, std::wstring_view text) { + if (locked) { + return; + } + if (erasing) { + sync(); + } + if (this->pos == static_cast(-1)) { + this->pos = pos; + } + if (this->pos + length != pos || text == L" " || text == L"\n") { + sync(); + this->pos = pos; + } + ss << text; + length += text.length(); + } + + void onErase(size_t pos, std::wstring_view text, bool selection=false) { + if (locked) { + return; + } + if (!erasing) { + sync(); + erasing = true; + } + if (selection) { + history.store( + std::make_unique( + getTextBoxWeakptr(), + textBox.getSelectionStart(), + textBox.getSelectionEnd() + ), + true + ); + } + if (this->pos == static_cast(-1)) { + this->pos = pos; + } else if (this->pos - text.length() != pos) { + sync(); + erasing = true; + this->pos = pos; + } + if (text == L" " || text == L"\n") { + sync(); + erasing = true; + this->pos = pos; + } + auto str = ss.str(); + ss.seekp(0); + ss << text << str; + + this->pos = pos; + length += text.length(); + } + + /// @brief Flush buffer and push all changes to the ActionsHistory + void sync() { + auto string = ss.str(); + if (string.empty()) { + return; + } + auto action = + std::make_unique(getTextBoxWeakptr(), pos, string); + history.store(std::move(action), erasing); + pos = -1; + length = 0; + ss = {}; + erasing = false; + } + + void undo() { + sync(); + locked = true; + history.undo(); + locked = false; + } + + void redo() { + sync(); + locked = true; + history.redo(); + locked = false; + } + private: + TextBox& textBox; + ActionsHistory& history; + std::wstringstream ss; + size_t pos = -1; + size_t length = 0; + bool erasing = false; + bool locked = false; + + std::weak_ptr getTextBoxWeakptr() { + return std::weak_ptr(std::dynamic_pointer_cast( + textBox.shared_from_this() + )); + } + }; +} + +TextBox::TextBox(std::wstring placeholder, glm::vec4 padding) + : Container(glm::vec2(200, 32)), + history(std::make_shared()), + historian(std::make_unique(*this, *history)), + padding(padding), + input(L""), + placeholder(std::move(placeholder)) { setCursor(CursorShape::TEXT); setOnUpPressed(nullptr); setOnDownPressed(nullptr); @@ -49,6 +208,8 @@ TextBox::TextBox(std::wstring placeholder, glm::vec4 padding) scrollStep = 0; } +TextBox::~TextBox() = default; + void TextBox::draw(const DrawContext& pctx, const Assets& assets) { Container::draw(pctx, assets); @@ -71,6 +232,7 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { auto batch = pctx.getBatch2D(); batch->texture(nullptr); batch->setColor(glm::vec4(1.0f)); + if (editable && int((Window::time() - caretLastMove) * 2) % 2 == 0) { uint line = rawTextCache.getLineByTextIndex(caret); uint lcaret = caret - rawTextCache.getTextLineOffset(line); @@ -260,18 +422,22 @@ void TextBox::refreshLabel() { /// @brief Insert text at the caret. Also selected text will be erased /// @param text Inserting text -void TextBox::paste(const std::wstring& text) { +void TextBox::paste(const std::wstring& text, bool history) { eraseSelected(); + auto inputText = text; + inputText.erase( + std::remove(inputText.begin(), inputText.end(), '\r'), inputText.end() + ); + historian->onPaste(caret, inputText); if (caret >= input.length()) { - input += text; + input += inputText; } else { auto left = input.substr(0, caret); auto right = input.substr(caret); - input = left + text + right; + input = left + inputText + right; } - input.erase(std::remove(input.begin(), input.end(), '\r'), input.end()); refreshLabel(); - setCaret(caret + text.length()); + setCaret(caret + inputText.length()); if (validate()) { onInput(); } @@ -296,6 +462,11 @@ bool TextBox::eraseSelected() { if (selectionStart == selectionEnd) { return false; } + historian->onErase( + selectionStart, + input.substr(selectionStart, selectionEnd - selectionStart), + true + ); erase(selectionStart, selectionEnd-selectionStart); resetSelection(); onInput(); @@ -336,7 +507,9 @@ void TextBox::setTextOffset(uint x) { void TextBox::typed(unsigned int codepoint) { if (editable) { - paste(std::wstring({(wchar_t)codepoint})); + // Combine deleting selected text and inserting a symbol + auto combination = history->beginCombination(); + paste(std::wstring({static_cast(codepoint)})); } } @@ -383,6 +556,15 @@ bool TextBox::isEditable() const { return editable; } +size_t TextBox::getSelectionStart() const { + return selectionStart; +} + +size_t TextBox::getSelectionEnd() const { + return selectionEnd; +} + + void TextBox::setOnEditStart(runnable oneditstart) { onEditStart = oneditstart; } @@ -615,6 +797,7 @@ void TextBox::performEditingKeyboardEvents(keycode key) { if (caret > input.length()) { caret = input.length(); } + historian->onErase(caret - 1, input.substr(caret - 1, caret)); input = input.substr(0, caret-1) + input.substr(caret); setCaret(caret-1); if (validate()) { @@ -623,6 +806,7 @@ void TextBox::performEditingKeyboardEvents(keycode key) { } } else if (key == keycode::DELETE) { if (!eraseSelected() && caret < input.length()) { + historian->onErase(caret, input.substr(caret, caret + 1)); input = input.substr(0, caret) + input.substr(caret + 1); if (validate()) { onInput(); @@ -669,7 +853,11 @@ void TextBox::keyPressed(keycode key) { if (key == keycode::V && editable) { const char* text = Window::getClipboardText(); if (text) { + historian->sync(); // flush buffer before combination + // Combine deleting selected text and pasing a clipboard content + auto combination = history->beginCombination(); paste(util::str2wstr_utf8(text)); + historian->sync(); } } // Select/deselect all @@ -680,6 +868,12 @@ void TextBox::keyPressed(keycode key) { resetSelection(); } } + if (key == keycode::Z) { + historian->undo(); + } + if (key == keycode::Y) { + historian->redo(); + } } } @@ -704,10 +898,8 @@ size_t TextBox::getLinePos(uint line) const { return label->getTextLineOffset(line); } -std::shared_ptr TextBox::getAt( - const glm::vec2& pos, const std::shared_ptr& self -) { - return UINode::getAt(pos, self); +std::shared_ptr TextBox::getAt(const glm::vec2& pos) { + return UINode::getAt(pos); } void TextBox::setOnUpPressed(const runnable &callback) { diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index 7dd77525..907c040b 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -4,10 +4,14 @@ #include "Label.hpp" class Font; +class ActionsHistory; namespace gui { + class TextBoxHistorian; class TextBox : public Container { LabelCache rawTextCache; + std::shared_ptr history; + std::unique_ptr historian; protected: glm::vec4 focusedColor {0.0f, 0.0f, 0.0f, 1.0f}; glm::vec4 invalidColor {0.1f, 0.05f, 0.03f, 1.0f}; @@ -68,7 +72,6 @@ namespace gui { int calcIndexAt(int x, int y) const; void setTextOffset(uint x); - void erase(size_t start, size_t length); bool eraseSelected(); void resetSelection(); void extendSelection(int index); @@ -93,8 +96,11 @@ namespace gui { std::wstring placeholder, glm::vec4 padding=glm::vec4(4.0f) ); + + virtual ~TextBox(); - void paste(const std::wstring& text); + void paste(const std::wstring& text, bool history=true); + void erase(size_t start, size_t length); virtual void setTextSupplier(wstringsupplier supplier); @@ -201,6 +207,9 @@ namespace gui { virtual void setPadding(glm::vec4 padding); glm::vec4 getPadding() const; + size_t getSelectionStart() const; + size_t getSelectionEnd() const; + /// @brief Set runnable called on textbox focus virtual void setOnEditStart(runnable oneditstart); @@ -221,9 +230,7 @@ namespace gui { virtual void drawBackground(const DrawContext& pctx, const Assets& assets) override; virtual void typed(unsigned int codepoint) override; virtual void keyPressed(keycode key) override; - virtual std::shared_ptr getAt( - const glm::vec2& pos, const std::shared_ptr& self - ) override; + virtual std::shared_ptr getAt(const glm::vec2& pos) override; virtual void setOnUpPressed(const runnable &callback); virtual void setOnDownPressed(const runnable &callback); diff --git a/src/graphics/ui/elements/UINode.cpp b/src/graphics/ui/elements/UINode.cpp index 8dc04f68..520a3256 100644 --- a/src/graphics/ui/elements/UINode.cpp +++ b/src/graphics/ui/elements/UINode.cpp @@ -111,11 +111,11 @@ bool UINode::isInside(glm::vec2 point) { point.x < pos.x + size.x && point.y < pos.y + size.y); } -std::shared_ptr UINode::getAt(const glm::vec2& point, const std::shared_ptr& self) { +std::shared_ptr UINode::getAt(const glm::vec2& point) { if (!isInteractive() || !enabled) { return nullptr; } - return isInside(point) ? self : nullptr; + return isInside(point) ? shared_from_this() : nullptr; } bool UINode::isInteractive() const { diff --git a/src/graphics/ui/elements/UINode.hpp b/src/graphics/ui/elements/UINode.hpp index 58009e00..beb1bf20 100644 --- a/src/graphics/ui/elements/UINode.hpp +++ b/src/graphics/ui/elements/UINode.hpp @@ -63,7 +63,7 @@ namespace gui { }; /// @brief Base abstract class for all UI elements - class UINode { + class UINode : public std::enable_shared_from_this { /// @brief element identifier used for direct access in UiDocument std::string id = ""; /// @brief element enabled state @@ -195,7 +195,7 @@ namespace gui { /// @param pos cursor screen position /// @param self shared pointer to element /// @return self, sub-element or nullptr if element is not interractive - virtual std::shared_ptr getAt(const glm::vec2& pos, const std::shared_ptr& self); + virtual std::shared_ptr getAt(const glm::vec2& pos); /// @brief Check if element is opaque for cursor virtual bool isInteractive() const; From 433c12a17a6e28dd267c76064895445594ed4ff8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 01:54:52 +0300 Subject: [PATCH 14/36] refresh syntax on undo/redo TextBox actions --- src/graphics/ui/elements/TextBox.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 88b0c86f..2510cef0 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -870,9 +870,11 @@ void TextBox::keyPressed(keycode key) { } if (key == keycode::Z) { historian->undo(); + refreshSyntax(); } if (key == keycode::Y) { historian->redo(); + refreshSyntax(); } } } From 93f20b1746b8913c831c782294362921b3f1ca4d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 02:32:41 +0300 Subject: [PATCH 15/36] add 'edited' read-only textbox property --- doc/en/scripting/ui.md | 1 + doc/ru/scripting/ui.md | 1 + src/graphics/ui/elements/TextBox.cpp | 22 ++++++++++++++++++---- src/graphics/ui/elements/TextBox.hpp | 2 ++ src/logic/scripting/lua/libs/libgui.cpp | 8 ++++++++ 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 4cac91b7..49c96edf 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -79,6 +79,7 @@ Properties: | hint | string | yes | yes | text to display when nothing is entered | | caret | int | yes | yes | carriage position. `textbox.caret = -1` will set the position to the end of the text | | editable | bool | yes | yes | text mutability | +| edited | bool | yes | no | is text edited since the last set (history is not empty) | | multiline | bool | yes | yes | multiline support | | lineNumbers | bool | yes | yes | display line numbers | | textWrap | bool | yes | yes | automatic text wrapping (only with multiline: "true") | diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 137ff57b..02789378 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -79,6 +79,7 @@ document["worlds-panel"]:clear() | hint | string | да | да | текст, отображаемый, когда ничего не введено | | caret | int | да | да | позиция каретки. `textbox.caret = -1` установит позицию в конец текста | | editable | bool | да | да | изменяемость текста | +| edited | bool | да | нет | был ли изменён текст с последней установки (история не пуста) | | multiline | bool | да | да | поддержка многострочности | | lineNumbers | bool | да | да | отображение номеров строк | | textWrap | bool | да | да | автоматический перенос текста (только при multiline: "true") | diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 2510cef0..f9e5c5e8 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -141,10 +141,7 @@ namespace gui { auto action = std::make_unique(getTextBoxWeakptr(), pos, string); history.store(std::move(action), erasing); - pos = -1; - length = 0; - ss = {}; - erasing = false; + reset(); } void undo() { @@ -160,6 +157,17 @@ namespace gui { history.redo(); locked = false; } + + void reset() { + pos = -1; + length = 0; + erasing = false; + ss = {}; + } + + bool isSynced() const { + return length == 0; + } private: TextBox& textBox; ActionsHistory& history; @@ -556,6 +564,10 @@ bool TextBox::isEditable() const { return editable; } +bool TextBox::isEdited() const { + return history->size() != 0 || !historian->isSynced(); +} + size_t TextBox::getSelectionStart() const { return selectionStart; } @@ -986,6 +998,8 @@ const std::wstring& TextBox::getText() const { void TextBox::setText(const std::wstring& value) { this->input = value; input.erase(std::remove(input.begin(), input.end(), '\r'), input.end()); + historian->reset(); + history->clear(); refreshSyntax(); } diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index 907c040b..9ebea29d 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -204,6 +204,8 @@ namespace gui { /// @brief Check if text editing feature is enabled virtual bool isEditable() const; + virtual bool isEdited() const; + virtual void setPadding(glm::vec4 padding); glm::vec4 getPadding() const; diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 273ee393..6b13f72c 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -294,6 +294,13 @@ static int p_get_editable(UINode* node, lua::State* L) { return 0; } +static int p_get_edited(UINode* node, lua::State* L) { + if (auto box = dynamic_cast(node)) { + return lua::pushboolean(L, box->isEdited()); + } + return 0; +} + static int p_get_line_numbers(UINode* node, lua::State* L) { if (auto box = dynamic_cast(node)) { return lua::pushboolean(L, box->isShowLineNumbers()); @@ -451,6 +458,7 @@ static int l_gui_getattr(lua::State* L) { {"caret", p_get_caret}, {"text", p_get_text}, {"editable", p_get_editable}, + {"edited", p_get_edited}, {"lineNumbers", p_get_line_numbers}, {"lineAt", p_get_line_at}, {"linePos", p_get_line_pos}, From 65ca46b2dabfd847077c125be8a9f7ac7d938d9d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 14:24:59 +0300 Subject: [PATCH 16/36] fix backspace/delete history behaviour --- src/graphics/ui/elements/TextBox.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index f9e5c5e8..0f85fa32 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -308,7 +308,6 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { } do { int lineY = label->getLineYOffset(line); - int lineHeight = font->getLineHeight() * label->getLineInterval(); batch->setColor(glm::vec4(1, 1, 1, 0.05f)); if (showLineNumbers) { @@ -809,7 +808,7 @@ void TextBox::performEditingKeyboardEvents(keycode key) { if (caret > input.length()) { caret = input.length(); } - historian->onErase(caret - 1, input.substr(caret - 1, caret)); + historian->onErase(caret - 1, input.substr(caret - 1, 1)); input = input.substr(0, caret-1) + input.substr(caret); setCaret(caret-1); if (validate()) { @@ -818,7 +817,7 @@ void TextBox::performEditingKeyboardEvents(keycode key) { } } else if (key == keycode::DELETE) { if (!eraseSelected() && caret < input.length()) { - historian->onErase(caret, input.substr(caret, caret + 1)); + historian->onErase(caret, input.substr(caret, 1)); input = input.substr(0, caret) + input.substr(caret + 1); if (validate()) { onInput(); From 4cdb1fbae28c29fa5071065bf4959f99ef69bf1f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 16:26:46 +0300 Subject: [PATCH 17/36] add util::base64_urlsafe_encode/decode --- src/util/stringutil.cpp | 92 ++++++++++++++++++++++++++++++++-------- src/util/stringutil.hpp | 8 ++++ test/util/stringutil.cpp | 16 +++++++ 3 files changed, 98 insertions(+), 18 deletions(-) diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 53f77239..79f8a1c5 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -306,6 +306,12 @@ const char B64ABC[] = "0123456789" "+/"; +const char URLSAFE_B64ABC[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_"; + inline ubyte base64_decode_char(char c) { if (c >= 'A' && c <= 'Z') return c - 'A'; if (c >= 'a' && c <= 'z') return c - 'a' + 26; @@ -315,16 +321,27 @@ inline ubyte base64_decode_char(char c) { return 0; } -inline void base64_encode_(const ubyte* segment, char* output) { - output[0] = B64ABC[(segment[0] & 0b11111100) >> 2]; - output[1] = - B64ABC[((segment[0] & 0b11) << 4) | ((segment[1] & 0b11110000) >> 4)]; - output[2] = - B64ABC[((segment[1] & 0b1111) << 2) | ((segment[2] & 0b11000000) >> 6)]; - output[3] = B64ABC[segment[2] & 0b111111]; +inline ubyte base64_urlsafe_decode_char(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '-') return 62; + if (c == '_') return 63; + return 0; } -std::string util::base64_encode(const ubyte* data, size_t size) { +template +static void base64_encode_(const ubyte* segment, char* output) { + output[0] = ABC[(segment[0] & 0b11111100) >> 2]; + output[1] = + ABC[((segment[0] & 0b11) << 4) | ((segment[1] & 0b11110000) >> 4)]; + output[2] = + ABC[((segment[1] & 0b1111) << 2) | ((segment[2] & 0b11000000) >> 6)]; + output[3] = ABC[segment[2] & 0b111111]; +} + +template +static std::string base64_encode_impl(const ubyte* data, size_t size) { std::stringstream ss; size_t fullsegments = (size / 3) * 3; @@ -332,7 +349,7 @@ std::string util::base64_encode(const ubyte* data, size_t size) { size_t i = 0; for (; i < fullsegments; i += 3) { char output[] = "===="; - base64_encode_(data + i, output); + base64_encode_(data + i, output); ss << output; } @@ -343,18 +360,27 @@ std::string util::base64_encode(const ubyte* data, size_t size) { size_t trailing = size - fullsegments; if (trailing) { char output[] = "===="; - output[0] = B64ABC[(ending[0] & 0b11111100) >> 2]; + output[0] = ABC[(ending[0] & 0b11111100) >> 2]; output[1] = - B64ABC[((ending[0] & 0b11) << 4) | ((ending[1] & 0b11110000) >> 4)]; + ABC[((ending[0] & 0b11) << 4) | ((ending[1] & 0b11110000) >> 4)]; if (trailing > 1) - output[2] = B64ABC - [((ending[1] & 0b1111) << 2) | ((ending[2] & 0b11000000) >> 6)]; - if (trailing > 2) output[3] = B64ABC[ending[2] & 0b111111]; + output[2] = + ABC[((ending[1] & 0b1111) << 2) | + ((ending[2] & 0b11000000) >> 6)]; + if (trailing > 2) output[3] = ABC[ending[2] & 0b111111]; ss << output; } return ss.str(); } +std::string util::base64_encode(const ubyte* data, size_t size) { + return base64_encode_impl(data, size); +} + +std::string util::base64_urlsafe_encode(const ubyte* data, size_t size) { + return base64_encode_impl(data, size); +} + std::string util::tohex(uint64_t value) { std::stringstream ss; ss << std::hex << value; @@ -366,7 +392,8 @@ std::string util::mangleid(uint64_t value) { return tohex(value); } -util::Buffer util::base64_decode(const char* str, size_t size) { +template +static util::Buffer base64_decode_impl(const char* str, size_t size) { util::Buffer bytes((size / 4) * 3); ubyte* dst = bytes.data(); for (size_t i = 0; i < (size / 4) * 4;) { @@ -387,18 +414,35 @@ util::Buffer util::base64_decode(const char* str, size_t size) { return bytes; } +util::Buffer util::base64_urlsafe_decode(const char* str, size_t size) { + return base64_decode_impl(str, size); +} + +util::Buffer util::base64_decode(const char* str, size_t size) { + return base64_decode_impl(str, size); +} + +util::Buffer util::base64_urlsafe_decode(std::string_view str) { + return base64_urlsafe_decode(str.data(), str.size()); +} + util::Buffer util::base64_decode(std::string_view str) { return base64_decode(str.data(), str.size()); } -int util::replaceAll( - std::string& str, const std::string& from, const std::string& to +template +static int replace_all( + std::basic_string& str, + const std::basic_string& from, + const std::basic_string& to ) { int count = 0; size_t offset = 0; while (true) { size_t start_pos = str.find(from, offset); - if (start_pos == std::string::npos) break; + if (start_pos == std::basic_string::npos) { + break; + } str.replace(start_pos, from.length(), to); offset = start_pos + to.length(); count++; @@ -406,6 +450,18 @@ int util::replaceAll( return count; } +int util::replaceAll( + std::string& str, const std::string& from, const std::string& to +) { + return replace_all(str, from, to); +} + +int util::replaceAll( + std::wstring& str, const std::wstring& from, const std::wstring& to +) { + return replace_all(str, from, to); +} + // replace it with std::from_chars in the far far future double util::parse_double(const std::string& str) { std::istringstream ss(str); diff --git a/src/util/stringutil.hpp b/src/util/stringutil.hpp index 522ad2ca..d85f93cb 100644 --- a/src/util/stringutil.hpp +++ b/src/util/stringutil.hpp @@ -74,8 +74,12 @@ namespace util { std::wstring to_wstring(double x, int precision); std::string base64_encode(const ubyte* data, size_t size); + std::string base64_urlsafe_encode(const ubyte* data, size_t size); + util::Buffer base64_decode(const char* str, size_t size); + util::Buffer base64_urlsafe_decode(const char* str, size_t size); util::Buffer base64_decode(std::string_view str); + util::Buffer base64_urlsafe_decode(std::string_view str); std::string tohex(uint64_t value); @@ -85,6 +89,10 @@ namespace util { std::string& str, const std::string& from, const std::string& to ); + int replaceAll( + std::wstring& str, const std::wstring& from, const std::wstring& to + ); + double parse_double(const std::string& str); double parse_double(const std::string& str, size_t offset, size_t len); diff --git a/test/util/stringutil.cpp b/test/util/stringutil.cpp index 2f27a243..fd459663 100644 --- a/test/util/stringutil.cpp +++ b/test/util/stringutil.cpp @@ -31,3 +31,19 @@ TEST(stringutil, base64) { } } } + +TEST(stringutil, base64_urlsafe) { + srand(2019); + for (size_t size = 0; size < 30; size++) { + auto bytes = std::make_unique(size); + for (int i = 0; i < size; i++) { + bytes[i] = rand(); + } + auto base64 = util::base64_urlsafe_encode(bytes.get(), size); + auto decoded = util::base64_urlsafe_decode(base64); + ASSERT_EQ(size, decoded.size()); + for (size_t i = 0; i < size; i++) { + ASSERT_EQ(bytes[i], decoded[i]); + } + } +} From 88b45e50987a2753ab3773d9f001f4cdda3f6f73 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 17:00:33 +0300 Subject: [PATCH 18/36] add pack.request_writeable(...) & update gui.confirm screen --- src/graphics/ui/GUI.cpp | 1 + src/graphics/ui/gui_util.cpp | 14 +++++++---- src/io/engine_paths.cpp | 31 ++++++++++++++++++++++++ src/io/engine_paths.hpp | 4 +++ src/logic/scripting/lua/libs/libfile.cpp | 27 +++++++++++++++------ src/logic/scripting/lua/libs/libpack.cpp | 21 +++++++++++++++- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index a965e1e8..5b028ac4 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -35,6 +35,7 @@ GUI::GUI() menu = std::make_shared(); menu->setId("menu"); + menu->setZIndex(10); container->add(menu); container->setScrollable(false); diff --git a/src/graphics/ui/gui_util.cpp b/src/graphics/ui/gui_util.cpp index 7fd40431..30b12390 100644 --- a/src/graphics/ui/gui_util.cpp +++ b/src/graphics/ui/gui_util.cpp @@ -91,7 +91,11 @@ void guiutil::confirm( if (yestext.empty()) yestext = langs::get(L"Yes"); if (notext.empty()) notext = langs::get(L"No"); + auto container = std::make_shared(glm::vec2(5000, 5000)); + container->setColor(glm::vec4(0.05f, 0.05f, 0.05f, 0.7f)); auto panel = std::make_shared(glm::vec2(600, 200), glm::vec4(8.0f), 8.0f); + panel->setGravity(Gravity::center_center); + container->add(panel); panel->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f)); panel->add(std::make_shared + + + + diff --git a/res/preload.json b/res/preload.json index 288c351d..a8426ba4 100644 --- a/res/preload.json +++ b/res/preload.json @@ -26,7 +26,10 @@ "gui/left_arrow", "gui/right_arrow", "gui/lock", - "gui/save" + "gui/save", + "gui/block", + "gui/item", + "gui/file" ], "fonts": [ { diff --git a/res/textures/gui/block.png b/res/textures/gui/block.png new file mode 100644 index 0000000000000000000000000000000000000000..84922d72eaae440d120419ca584161a1babddec0 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`v7RoDAr*6y6Bh_DWcm`nCAlx|rkc$oW!%7@G& y1&me><{a@i9{743X>d$Ba9}yJ6W7P5ZVctk9=<1g+@ydOF?hQAxvX#$5YOoz(YRzFj!FuI0(@{MM(*zA^28N|wGKSka SGnN32V(@hJb6Mw<&;$S?^&%Gl literal 0 HcmV?d00001 diff --git a/res/textures/gui/item.png b/res/textures/gui/item.png new file mode 100644 index 0000000000000000000000000000000000000000..8714b10f1372ca6975a2db3865a8570745c08ba9 GIT binary patch literal 4819 zcmeHKdsq`!77rq#CPLfF&APLATadWd#+1%H9d6_-+63`?mj?`6ih;_nhB3=XcLN zxp%cVGSt?_#fC&8*@lM&MT1{^;vPK;e7koIAS9CIRFg!CM3=VNZVe;rBIV89t_syPForZ&~So zA;;#yhJer>OY2aPM_C4y7wTG8dTP?x4IA!BRco-KSHUNoXwP+b7H5w<>_;7|eLaTP z{L_KDwAlr3(BEE44mCYk|7AtLG2guv>Qf14jwt!|!Sz>`t+|~!-h%Sh9B=1}g8eU! z3ieQ*KcKBnX^z;Ui*&uDKY5eqDfI6fhcl^lx&Dr(vE`5F<~vOIezs$Z(~YQ${biE+ z6~1Gvy*Ev!N^LHas~z1l9X+6?AKk7Qy}IO@9lYL-p36tch3gDoEGvHLliSjJEUr6i zNx@0crLJm^15$bQgGCp^X~GuU%0D_o8`?AtG<{y{*~^XAx8%5c(v|DMqR%R8{~ps4 z+iYVxU*BQ+LN2YhnEzN>9|ik0Ul+#+*!a!NRF6@8fvfrRUGVbCC}H;MdF=Xn%0ZtJ z%jkB`!;_m_nb*bk3th7sV{NCm&vg1yQ>SmKm5)C(LGs<=ap;R@p*`gtlMfeU@4Qnv z`zycW6QY7fyx|4f1|>WxeKe<&Mm`oXi9uQb+c_V zRQz7mZE9K_&A(En@_*WWxG=T&>Z)~2{62#8c>kmAD!cjna{UYM>@w*^W_Dm_6vDu+Cl3V0Sw~z2TWcRNdYQ05#|Dt17 zU1!%1%&3GLzs|eWaBFGs{)PiV3(ibYbMsuRZgY|)Y5nb8eJ@N*a-g28}`vqe}gevxIPTWX`+2dC=dimCE%_|+bhokCb?|_e?=vVs==^4jq z*vD_VMoBPb>;%*3{&AZ2xhF z6=qZE+PO63&Sr~VZDu*^K*c%#NcZIvQ}d5m*x6+^`k@Egcq+(eEv$xNJc2vLa zW!oYtXlpJQXkSuM1YfRIQxSz$hEk1c9cXVPl3##Phscvq9Fn0bOd}vaJN`2n!W05> z97_a?bV4)%3p44_7*nJ~Zc38#6yyMZ8$Tl-0H{$MfsE>8je&0zkj=P!@JxtlWXP<7 zCke<>kr)zc^(e%oGN~{n*odVu$o@8vpI)KlM+b!rL4Y>_IRVFYd>So1J)N5FOV#RC zG&+yRqrnUsgFyir6hnpvM~oDWVJZPJh!KPuZvk@%ejL4gWW>&#Yw><=_?O!=Oy4`L%m%;^jd z1em|W{XqL|?q+46B@*$2wDMFUyzn3anefk7XyurKZ+_&mQ50tI=oAiDCZ{mDFhY^R zG9?9p6>OBJU?MP`Hw-FVW55xO93`LtI28jp2p454lyoLV$zq@sCc;xt5V{~N+fxBR0Na)K*0#gMOiEsg~#RjQkX0Tn<8Vta*9kQLzpaJ6Y02~0Xn^&P||riBlZ0mLFiodN)}1;~Xj)T0Ql z)l0P6WC5872_h`bJG4xho}`*LT%zI+%a z(}ohJ5v%%b#(uQ_;l$6ZFsuy#yFnRfFQ8k|-nFYC&IlU+!q3oL{DmF>^d~1D#qTG& zKGF4241ARGr|SAd*GDn%QOcjH>;FcV&HDonssX#8ba0%hjW&!2N1;)&xuHR%0pcpI z+LH$)V{~Eh1`>%nmAEa8y8_aH&>9aH1zSI~9B1d|+2YiW0nul8uoM?+)x^G;WZur9 z3Md`J6Ch$c7nf=043uKSg90Vox~ffqb~W(m(~tTG8lJG9)_7z4^c$wu$O{XzM{|Tu zqYjed4bD4nr%G7q(8W`B_tUeeLDw4j_M$t?%!A|`&KLKVJv*6>TjxHlE_q#)LArGX Wo*Y-}_BY^x6doKIR6cw0ivI!$P9(qp literal 0 HcmV?d00001 From 3a4b334d0b841d3f07ee9400a6eced1c82e8dde4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 19:51:22 +0300 Subject: [PATCH 22/36] feat: 'edited' property reset & add 'oncontrolkey' textbox callback --- doc/en/scripting/ui.md | 4 +++- doc/en/xml-ui-layouts.md | 2 ++ doc/ru/scripting/ui.md | 4 +++- doc/ru/xml-ui-layouts.md | 2 ++ res/layouts/console.xml | 1 + res/layouts/console.xml.lua | 7 +++++++ src/delegates.hpp | 1 + src/devtools/actions.hpp | 4 ++++ src/graphics/ui/elements/TextBox.cpp | 20 +++++++++++++++--- src/graphics/ui/elements/TextBox.hpp | 5 +++++ src/graphics/ui/gui_xml.cpp | 7 +++++++ src/logic/scripting/lua/libs/libgui.cpp | 8 +++++++ src/logic/scripting/scripting_functional.cpp | 22 ++++++++++++++++++++ src/logic/scripting/scripting_functional.hpp | 6 ++++++ 14 files changed, 88 insertions(+), 5 deletions(-) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 49c96edf..c6da89c4 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -79,7 +79,7 @@ Properties: | hint | string | yes | yes | text to display when nothing is entered | | caret | int | yes | yes | carriage position. `textbox.caret = -1` will set the position to the end of the text | | editable | bool | yes | yes | text mutability | -| edited | bool | yes | no | is text edited since the last set (history is not empty) | +| edited | bool | yes | yes\* | is text edited since the last set / edited status reset | | multiline | bool | yes | yes | multiline support | | lineNumbers | bool | yes | yes | display line numbers | | textWrap | bool | yes | yes | automatic text wrapping (only with multiline: "true") | @@ -88,6 +88,8 @@ Properties: | syntax | string | yes | yes | syntax highlighting ("lua" - Lua) | | markup | string | yes | yes | text markup language ("md" - Markdown) | +\* - false only + Methods: | Method | Description | diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 6f687328..64579db1 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -110,6 +110,8 @@ Inner text - initially entered text - `supplier` - text supplier (called every frame) - `consumer` - lua function that receives the entered text. Called only when input is complete - `sub-consumer` - lua function-receiver of the input text. Called during text input or deletion. +- `oncontrolkey` - lua function called for combinations of the form (Ctrl + ?). The codepoint of the second key is given as the first argument. +The key code for comparison can be obtained via `input.keycode("key_name")` - `autoresize` - automatic change of element size (default - false). Does not affect font size. - `multiline` - allows display of multiline text. - `text-wrap` - allows automatic text wrapping (works only with multiline: "true") diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 02789378..62697c8f 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -79,7 +79,7 @@ document["worlds-panel"]:clear() | hint | string | да | да | текст, отображаемый, когда ничего не введено | | caret | int | да | да | позиция каретки. `textbox.caret = -1` установит позицию в конец текста | | editable | bool | да | да | изменяемость текста | -| edited | bool | да | нет | был ли изменён текст с последней установки (история не пуста) | +| edited | bool | да | да\* | был ли изменён текст с последней установки/сброса свойства | | multiline | bool | да | да | поддержка многострочности | | lineNumbers | bool | да | да | отображение номеров строк | | textWrap | bool | да | да | автоматический перенос текста (только при multiline: "true") | @@ -88,6 +88,8 @@ document["worlds-panel"]:clear() | syntax | string | да | да | подсветка синтаксиса ("lua" - Lua) | | markup | string | да | да | язык разметки текста ("md" - Markdown) | +\* - только false + Методы: | Метод | Описание | diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index 30c17315..e0524793 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -111,6 +111,8 @@ - `supplier` - поставщик текста (вызывается каждый кадр) - `consumer` - lua функция-приемник введенного текста. Вызывается только при завершении ввода - `sub-consumer` - lua функция-приемник вводимого текста. Вызывается во время ввода или удаления текста. +- `oncontrolkey` - lua функция вызываемая для сочетаний вида (Ctrl + ?). На вход подаётся числовой код второй клавиши. +Код клавиши для сравнения можно получить через `input.keycode("имя_клавиши")` - `autoresize` - автоматическое изменение размера элемента (по-умолчанию - false). Не влияет на размер шрифта. - `multiline` - разрешает отображение многострочного текста. - `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true") diff --git a/res/layouts/console.xml b/res/layouts/console.xml index 133ccb60..00ce2a87 100644 --- a/res/layouts/console.xml +++ b/res/layouts/console.xml @@ -57,6 +57,7 @@ padding='5' multiline='true' line-numbers='true' + oncontrolkey='on_control_combination' syntax='lua' size-func="-1,40" text-wrap='false' diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index 18e72690..62e9b6dd 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -81,6 +81,12 @@ local function refresh_file_title() ..(edited and ' *' or '') end +function on_control_combination(keycode) + if keycode == input.keycode("s") then + save_current_file() + end +end + function unlock_access() if current_file.filename == "" then return @@ -102,6 +108,7 @@ function save_current_file() current_file.modified = false document.saveIcon.enabled = false document.title.text = gui.str('File')..' - '..current_file.filename + document.editor.edited = false end function open_file_in_editor(filename, line, mutable) diff --git a/src/delegates.hpp b/src/delegates.hpp index 0e670b41..f583f951 100644 --- a/src/delegates.hpp +++ b/src/delegates.hpp @@ -15,6 +15,7 @@ using wstringsupplier = std::function; using doublesupplier = std::function; using boolsupplier = std::function; using vec2supplier = std::function; +using key_handler = std::function; using stringconsumer = std::function; using wstringconsumer = std::function; diff --git a/src/devtools/actions.hpp b/src/devtools/actions.hpp index abe76bfc..fffe9d67 100644 --- a/src/devtools/actions.hpp +++ b/src/devtools/actions.hpp @@ -145,6 +145,10 @@ public: : history(history), historySize(history.size()) { } + Combination(const Combination&) = delete; + + Combination(Combination&&) = default; + ~Combination() { history.squash(history.size() - historySize); } diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 0f85fa32..96d9ea01 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -564,7 +564,12 @@ bool TextBox::isEditable() const { } bool TextBox::isEdited() const { - return history->size() != 0 || !historian->isSynced(); + return history->size() != editedHistorySize || !historian->isSynced(); +} + +void TextBox::setUnedited() { + historian->sync(); + editedHistorySize = history->size(); } size_t TextBox::getSelectionStart() const { @@ -575,7 +580,6 @@ size_t TextBox::getSelectionEnd() const { return selectionEnd; } - void TextBox::setOnEditStart(runnable oneditstart) { onEditStart = oneditstart; } @@ -849,7 +853,12 @@ void TextBox::keyPressed(keycode key) { if (editable) { performEditingKeyboardEvents(key); } - if (Events::pressed(keycode::LEFT_CONTROL)) { + if (Events::pressed(keycode::LEFT_CONTROL) && key != keycode::LEFT_CONTROL) { + if (controlCombinationsHandler) { + if (controlCombinationsHandler(static_cast(key))) { + return; + } + } // Copy selected text to clipboard if (key == keycode::C || key == keycode::X) { std::string text = util::wstr2str_utf8(getSelection()); @@ -963,6 +972,10 @@ void TextBox::setTextValidator(wstringchecker validator) { this->validator = std::move(validator); } +void TextBox::setOnControlCombination(key_handler handler) { + this->controlCombinationsHandler = std::move(handler); +} + void TextBox::setFocusedColor(glm::vec4 color) { this->focusedColor = color; } @@ -999,6 +1012,7 @@ void TextBox::setText(const std::wstring& value) { input.erase(std::remove(input.begin(), input.end(), '\r'), input.end()); historian->reset(); history->clear(); + editedHistorySize = 0; refreshSyntax(); } diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index 9ebea29d..7fddfacb 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -12,6 +12,7 @@ namespace gui { LabelCache rawTextCache; std::shared_ptr history; std::unique_ptr historian; + int editedHistorySize = 0; protected: glm::vec4 focusedColor {0.0f, 0.0f, 0.0f, 1.0f}; glm::vec4 invalidColor {0.1f, 0.05f, 0.03f, 1.0f}; @@ -33,6 +34,7 @@ namespace gui { wstringconsumer subconsumer = nullptr; /// @brief Text validator returning boolean value wstringchecker validator = nullptr; + key_handler controlCombinationsHandler = nullptr; /// @brief Function called on focus runnable onEditStart = nullptr; /// @brief Function called on up arrow pressed @@ -117,6 +119,8 @@ namespace gui { /// @param validator std::wstring consumer returning boolean virtual void setTextValidator(wstringchecker validator); + virtual void setOnControlCombination(key_handler handler); + virtual void setFocusedColor(glm::vec4 color); virtual glm::vec4 getFocusedColor() const; @@ -205,6 +209,7 @@ namespace gui { virtual bool isEditable() const; virtual bool isEdited() const; + virtual void setUnedited(); virtual void setPadding(glm::vec4 padding); glm::vec4 getPadding() const; diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index abe59c2c..9cf14f54 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -493,6 +493,13 @@ static std::shared_ptr read_text_box( reader.getFilename() )); } + if (element.has("oncontrolkey")) { + textbox->setOnControlCombination(scripting::create_key_handler( + reader.getEnvironment(), + element.attr("oncontrolkey").getText(), + reader.getFilename() + )); + } if (auto onUpPressed = create_runnable(reader, element, "onup")) { textbox->setOnUpPressed(onUpPressed); } diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 6b13f72c..52f3c283 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -553,6 +553,13 @@ static void p_set_editable(UINode* node, lua::State* L, int idx) { box->setEditable(lua::toboolean(L, idx)); } } +static void p_set_edited(UINode* node, lua::State* L, int idx) { + if (auto box = dynamic_cast(node)) { + if (!lua::toboolean(L, idx)) { + box->setUnedited(); + } + } +} static void p_set_line_numbers(UINode* node, lua::State* L, int idx) { if (auto box = dynamic_cast(node)) { box->setShowLineNumbers(lua::toboolean(L, idx)); @@ -675,6 +682,7 @@ static int l_gui_setattr(lua::State* L) { {"hint", p_set_hint}, {"text", p_set_text}, {"editable", p_set_editable}, + {"edited", p_set_edited}, {"lineNumbers", p_set_line_numbers}, {"syntax", p_set_syntax}, {"markup", p_set_markup}, diff --git a/src/logic/scripting/scripting_functional.cpp b/src/logic/scripting/scripting_functional.cpp index d09c9432..96023d6f 100644 --- a/src/logic/scripting/scripting_functional.cpp +++ b/src/logic/scripting/scripting_functional.cpp @@ -36,6 +36,28 @@ static lua::State* process_callback( return nullptr; } +key_handler scripting::create_key_handler( + const scriptenv& env, const std::string& src, const std::string& file +) { + return [=](int code) { + if (auto L = process_callback(env, src, file)) { + int top = lua::gettop(L); + if (lua::isfunction(L, -1)) { + lua::pushinteger(L, code); + lua::call_nothrow(L, 1); + } + int returned = lua::gettop(L) - top + 1; + if (returned) { + bool x = lua::toboolean(L, -1); + lua::pop(L, returned); + return x; + } + return false; + } + return false; + }; +} + wstringconsumer scripting::create_wstring_consumer( const scriptenv& env, const std::string& src, const std::string& file ) { diff --git a/src/logic/scripting/scripting_functional.hpp b/src/logic/scripting/scripting_functional.hpp index f2d4c68b..be325740 100644 --- a/src/logic/scripting/scripting_functional.hpp +++ b/src/logic/scripting/scripting_functional.hpp @@ -17,6 +17,12 @@ namespace scripting { const std::string& file = "[string]" ); + key_handler create_key_handler( + const scriptenv& env, + const std::string& src, + const std::string& file = "[string]" + ); + wstringconsumer create_wstring_consumer( const scriptenv& env, const std::string& src, From b9c2b0ba4027421162b6153d5eba88ecf439561c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 23:33:40 +0300 Subject: [PATCH 23/36] add files filter to the editor --- res/layouts/console.xml | 26 ++++++++++----- res/layouts/console.xml.lua | 49 ++++++++++++++++++++--------- src/graphics/ui/elements/UINode.cpp | 4 +-- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/res/layouts/console.xml b/res/layouts/console.xml index 00ce2a87..6e5cc6c0 100644 --- a/res/layouts/console.xml +++ b/res/layouts/console.xml @@ -25,8 +25,12 @@ - - + + + + + - + + + + getSize(); } setSize( - {newSize.x < 0 ? defsize.x : newSize.x, - newSize.y < 0 ? defsize.y : newSize.y} + {newSize.x < 0 ? defsize.x + (newSize.x + 1) : newSize.x, + newSize.y < 0 ? defsize.y + (newSize.y + 1) : newSize.y} ); } if (positionfunc) { From 4b09b108fd4451c841a5c2fab5d833e6cd49b616 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 23:40:34 +0300 Subject: [PATCH 24/36] fix file.parent --- res/scripts/stdmin.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 5e6ac294..1e551118 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -545,11 +545,11 @@ function file.prefix(path) end function file.parent(path) - local pos = path:find("/") - if not pos then + local dir = path:match("(.*)/") + if not dir then return file.prefix(path)..":" end - return path:sub(0, pos-1) + return dir end function file.path(path) From 794d1d71f5ef83e1548e7afa1880294481c626d7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 12 Mar 2025 23:42:40 +0300 Subject: [PATCH 25/36] feat: writeable pack entry points cleanup --- src/io/engine_paths.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/io/engine_paths.cpp b/src/io/engine_paths.cpp index 147f4f89..03973d60 100644 --- a/src/io/engine_paths.cpp +++ b/src/io/engine_paths.cpp @@ -186,6 +186,9 @@ void EnginePaths::setContentPacks(std::vector* contentPacks) { for (const auto& id : contentEntryPoints) { io::remove_device(id); } + for (const auto& [_, entryPoint] : writeablePacks) { + io::remove_device(entryPoint); + } contentEntryPoints.clear(); this->contentPacks = contentPacks; // Create content devices From b66937c61d711536556988103e90f36f217097af Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 13 Mar 2025 00:28:40 +0300 Subject: [PATCH 26/36] feat: scripts classification --- res/layouts/console.xml.lua | 36 +++++++++++++++++++++++++++------ res/preload.json | 3 ++- res/scripts/post_content.lua | 2 +- res/textures/gui/block.png | Bin 150 -> 114 bytes res/textures/gui/module.png | Bin 0 -> 150 bytes src/content/ContentBuilder.cpp | 4 +++- src/content/ContentLoader.cpp | 5 ++++- src/items/ItemDef.hpp | 2 ++ src/voxels/Block.hpp | 2 ++ 9 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 res/textures/gui/module.png diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index 09092f6f..aab8d0dd 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -11,6 +11,7 @@ local error_id = 0 local writeables = {} local filenames = {} +local scripts_classification = {} local current_file = { filename = "", @@ -92,7 +93,7 @@ function build_files_list(filenames, selected) filename = filename:gsub(selected, "**"..selected.."**") end local parent = file.parent(filename) - local script_type = "file" + local script_type = scripts_classification[actual_filename] or "file" files_list:add(gui.template("script_file", { path = parent .. (parent[#parent] == ':' and '' or '/'), name = file.name(filename), @@ -319,6 +320,31 @@ local function collect_scripts(dirname, dest) end end +local function build_scripts_classification() + for id, props in pairs(block.properties) do + scripts_classification[props["script-file"]] = "block" + end + for id, props in pairs(item.properties) do + scripts_classification[props["script-file"]] = "item" + end +end + +local function load_scripts_list() + local packs = pack.get_installed() + for _, packid in ipairs(packs) do + collect_scripts(packid..":modules", filenames) + end + + for _, filename in ipairs(filenames) do + scripts_classification[filename] = "module" + end + + for _, packid in ipairs(packs) do + collect_scripts(packid..":scripts", filenames) + end + +end + function on_open(mode) if modes == nil then modes = RadioGroup({ @@ -330,12 +356,10 @@ function on_open(mode) end, mode or "console") local files_list = document.filesList - local packs = pack.get_installed() - for _, packid in ipairs(packs) do - collect_scripts(packid..":modules", filenames) - collect_scripts(packid..":scripts", filenames) - end + load_scripts_list() + build_scripts_classification() + table.sort(filenames) build_files_list(filenames) diff --git a/res/preload.json b/res/preload.json index a8426ba4..2b00a440 100644 --- a/res/preload.json +++ b/res/preload.json @@ -29,7 +29,8 @@ "gui/save", "gui/block", "gui/item", - "gui/file" + "gui/file", + "gui/module" ], "fonts": [ { diff --git a/res/scripts/post_content.lua b/res/scripts/post_content.lua index 8725ed97..e7361bb2 100644 --- a/res/scripts/post_content.lua +++ b/res/scripts/post_content.lua @@ -7,7 +7,7 @@ local names = { "hidden", "draw-group", "picking-item", "surface-replacement", "script-name", "ui-layout", "inventory-size", "tick-interval", "overlay-texture", "translucent", "fields", "particles", "icon-type", "icon", "placing-block", - "stack-size", "name" + "stack-size", "name", "script-file" } for name, _ in pairs(user_props) do table.insert(names, name) diff --git a/res/textures/gui/block.png b/res/textures/gui/block.png index 84922d72eaae440d120419ca584161a1babddec0..dfcc239a4ca4744ae7f9fb22d336350d8ffd33b5 100644 GIT binary patch delta 84 zcmbQnSTsS!&(hPyF{ENna^eDk4FN|R{yRAIB)KU#gz-NLKF%s4%}^}0ks;DHmr2le o!-t(I6WEmu8?9#vt(+jl$SW@qxmDY_hye&ZUHx3vIVCg!0P(dO>;M1& delta 120 zcmV-;0Eho_mI06?a$QM8K~#90V_~2Wu;7#Y4+J2Z5eP^!0H%=`gIKUBB*jH6#Aw84 z5DT)2#2SR*0(1#n2H^|?bibh+gsu_83)m#^X~b{=IzTrFU)bPHNW_3aV!*(s140V% aDWcm`nCAlx|rkc$oW!%7@G& y1&me><{a@i9{743X>d$Ba9}yJ6W7P5ZVctk9=<1g+@ydOF?hQAxvX ContentBuilder::build() { def->rt.surfaceReplacement = content->blocks.require(def->surfaceReplacement).rt.id; if (def->properties == nullptr) { def->properties = dv::object(); - def->properties["name"] = def->name; } + def->properties["name"] = def->name; + def->properties["script-file"] = def->scriptFile; } for (ItemDef* def : itemDefsIndices) { @@ -104,6 +105,7 @@ std::unique_ptr ContentBuilder::build() { def->properties = dv::object(); } def->properties["name"] = def->name; + def->properties["script-file"] = def->scriptFile; } for (auto& [name, def] : content->generators.getDefs()) { diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 9b14bc86..bc8598bd 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -394,6 +394,7 @@ void ContentLoader::loadBlock( if (def.hidden && def.pickingItem == def.name + BLOCK_ITEM_SUFFIX) { def.pickingItem = CORE_EMPTY; } + def.scriptFile = pack->id + ":scripts/" + def.scriptName + ".lua"; } void ContentLoader::loadItem( @@ -452,6 +453,8 @@ void ContentLoader::loadItem( def.emission[1] = emissionarr[1].asNumber(); def.emission[2] = emissionarr[2].asNumber(); } + + def.scriptFile = pack->id + ":scripts/" + def.scriptName + ".lua"; } void ContentLoader::loadEntity( @@ -865,7 +868,7 @@ static void load_scripts(Content& content, ContentUnitDefs& units) { runtime->getEnvironment(), name, scriptfile, - pack.id + ":scripts/" + def->scriptName + ".lua", + def->scriptFile, def->rt.funcsset ); } diff --git a/src/items/ItemDef.hpp b/src/items/ItemDef.hpp index db4ff9f3..00870cc3 100644 --- a/src/items/ItemDef.hpp +++ b/src/items/ItemDef.hpp @@ -59,6 +59,8 @@ struct ItemDef { std::string modelName = name + ".model"; + std::string scriptFile; + struct { itemid_t id; blockid_t placingBlock; diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index a7b3aeb1..972b405f 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -204,6 +204,8 @@ public: /// @brief Block script name in blocks/ without extension std::string scriptName = name.substr(name.find(':') + 1); + std::string scriptFile; + /// @brief Block will be used instead of this if generated on surface std::string surfaceReplacement = name; From ffac010e5439b67a68f0a6d2b03a59909e201f5d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 13 Mar 2025 22:03:45 +0300 Subject: [PATCH 27/36] update built-in error handling --- res/scripts/stdmin.lua | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 1e551118..d9be5cca 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -429,6 +429,18 @@ function file.readlines(path) return lines end +function debug.count_frames() + local frames = 1 + while true do + local info = debug.getinfo(frames) + if info then + frames = frames + 1 + else + return frames - 1 + end + end +end + function debug.get_traceback(start) local frames = {} local n = 2 + (start or 0) @@ -513,9 +525,13 @@ function __scripts_cleanup() end end -function __vc__error(msg, frame) +function __vc__error(msg, frame, n, lastn) if events then - events.emit("core:error", msg, debug.get_traceback(1)) + local frames = debug.get_traceback(1) + events.emit( + "core:error", msg, + table.sub(frames, 1 + (n or 0), lastn and #frames-lastn) + ) end return debug.traceback(msg, frame) end From 1bb25e99c6637d45da957c960b67455c4ec0c98b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 13 Mar 2025 22:05:14 +0300 Subject: [PATCH 28/36] add core.capture_output --- src/logic/scripting/lua/libs/libcore.cpp | 28 +++++++++++++++++++++++ src/logic/scripting/lua/lua_overrides.cpp | 8 ++++--- src/logic/scripting/scripting.cpp | 2 ++ src/logic/scripting/scripting.hpp | 2 ++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 7b8c152c..2a56644d 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -273,6 +273,33 @@ static int l_blank(lua::State*) { return 0; } +static int l_capture_output(lua::State* L) { + int argc = lua::gettop(L) - 1; + if (!lua::isfunction(L, 1)) { + throw std::runtime_error("function expected as argument 1"); + } + for (int i = 0; i < argc; i++) { + lua::pushvalue(L, i + 2); + } + lua::pushvalue(L, 1); + + auto prev_output = output_stream; + auto prev_error = error_stream; + + std::stringstream captured_output; + + output_stream = &captured_output; + error_stream = &captured_output; + + lua::call_nothrow(L, argc, 0); + + output_stream = prev_output; + error_stream = prev_error; + + lua::pushstring(L, captured_output.str()); + return 1; +} + const luaL_Reg corelib[] = { {"blank", lua::wrap}, {"get_version", lua::wrap}, @@ -292,6 +319,7 @@ const luaL_Reg corelib[] = { {"get_setting_info", lua::wrap}, {"open_folder", lua::wrap}, {"quit", lua::wrap}, + {"capture_output", lua::wrap}, {"__load_texture", lua::wrap}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/lua_overrides.cpp b/src/logic/scripting/lua/lua_overrides.cpp index 12b0bbff..24746ad7 100644 --- a/src/logic/scripting/lua/lua_overrides.cpp +++ b/src/logic/scripting/lua/lua_overrides.cpp @@ -2,6 +2,8 @@ #include "libs/api_lua.hpp" +using namespace scripting; + /// @brief Modified version of luaB_print from lbaselib.c int l_print(lua::State* L) { int n = lua::gettop(L); /* number of arguments */ @@ -16,10 +18,10 @@ int l_print(lua::State* L) { L, LUA_QL("tostring") " must return a string to " LUA_QL("print") ); - if (i > 1) std::cout << "\t"; - std::cout << s; + if (i > 1) *output_stream << "\t"; + *output_stream << s; lua::pop(L); /* pop result */ } - std::cout << std::endl; + *output_stream << std::endl; return 0; } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 66606776..4e7b37e0 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -34,6 +34,8 @@ static debug::Logger logger("scripting"); static inline const std::string STDCOMP = "stdcomp"; +std::ostream* scripting::output_stream = &std::cout; +std::ostream* scripting::error_stream = &std::cerr; Engine* scripting::engine = nullptr; Level* scripting::level = nullptr; const Content* scripting::content = nullptr; diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index bdd5d5d2..52de1ccb 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -42,6 +42,8 @@ namespace scripting { extern Level* level; extern BlocksController* blocks; extern LevelController* controller; + extern std::ostream* output_stream; + extern std::ostream* error_stream; void initialize(Engine* engine); From 4400f7fbfb0b022e19919b66c7bdf873b6f243d7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 13 Mar 2025 22:05:53 +0300 Subject: [PATCH 29/36] add block.reload_script, item.reload_script --- src/content/Content.hpp | 8 ++++ src/content/ContentLoader.cpp | 52 ++++++++++++++--------- src/content/ContentLoader.hpp | 2 + src/engine/Engine.cpp | 4 ++ src/engine/Engine.hpp | 2 + src/logic/scripting/lua/libs/libblock.cpp | 14 ++++++ src/logic/scripting/lua/libs/libitem.cpp | 14 ++++++ 7 files changed, 77 insertions(+), 19 deletions(-) diff --git a/src/content/Content.hpp b/src/content/Content.hpp index b95ef247..a7f56b56 100644 --- a/src/content/Content.hpp +++ b/src/content/Content.hpp @@ -121,6 +121,14 @@ public: return *found->second; } + T& require(const std::string& id) { + const auto& found = defs.find(id); + if (found == defs.end()) { + throw std::runtime_error("missing content unit " + id); + } + return *found->second; + } + const auto& getDefs() const { return defs; } diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index bc8598bd..47fca715 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -853,26 +853,40 @@ void ContentLoader::load() { } template -static void load_scripts(Content& content, ContentUnitDefs& units) { - for (const auto& [name, def] : units.getDefs()) { - size_t pos = name.find(':'); - if (pos == std::string::npos) { - throw std::runtime_error("invalid content unit name"); - } - const auto runtime = content.getPackRuntime(name.substr(0, pos)); - const auto& pack = runtime->getInfo(); - const auto& folder = pack.folder; - auto scriptfile = folder / ("scripts/" + def->scriptName + ".lua"); - if (io::is_regular_file(scriptfile)) { - scripting::load_content_script( - runtime->getEnvironment(), - name, - scriptfile, - def->scriptFile, - def->rt.funcsset - ); - } +static void load_script(const Content& content, T& def) { + const auto& name = def.name; + size_t pos = name.find(':'); + if (pos == std::string::npos) { + throw std::runtime_error("invalid content unit name"); } + const auto runtime = content.getPackRuntime(name.substr(0, pos)); + const auto& pack = runtime->getInfo(); + const auto& folder = pack.folder; + auto scriptfile = folder / ("scripts/" + def.scriptName + ".lua"); + if (io::is_regular_file(scriptfile)) { + scripting::load_content_script( + runtime->getEnvironment(), + name, + scriptfile, + def.scriptFile, + def.rt.funcsset + ); + } +} + +template +static void load_scripts(const Content& content, ContentUnitDefs& units) { + for (const auto& [_, def] : units.getDefs()) { + load_script(content, *def); + } +} + +void ContentLoader::reloadScript(const Content& content, Block& block) { + load_script(content, block); +} + +void ContentLoader::reloadScript(const Content& content, ItemDef& item) { + load_script(content, item); } void ContentLoader::loadScripts(Content& content) { diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp index f1135ee3..8860cc77 100644 --- a/src/content/ContentLoader.hpp +++ b/src/content/ContentLoader.hpp @@ -77,4 +77,6 @@ public: void load(); static void loadScripts(Content& content); + static void reloadScript(const Content& content, Block& block); + static void reloadScript(const Content& content, ItemDef& item); }; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index c9184383..5778ed98 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -484,6 +484,10 @@ const Content* Engine::getContent() const { return content.get(); } +Content* Engine::getWriteableContent() { + return content.get(); +} + std::vector Engine::getAllContentPacks() { auto packs = getContentPacks(); packs.insert(packs.begin(), ContentPack::createCore(paths)); diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index b05881f6..ccfabeed 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -151,6 +151,8 @@ public: /// @brief Get current Content instance const Content* getContent() const; + Content* getWriteableContent(); + /// @brief Get selected content packs std::vector& getContentPacks(); diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index f37b4ba3..8e1b82c4 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -1,4 +1,5 @@ #include "content/Content.hpp" +#include "content/ContentLoader.hpp" #include "lighting/Lighting.hpp" #include "logic/BlocksController.hpp" #include "logic/LevelController.hpp" @@ -12,6 +13,7 @@ #include "world/Level.hpp" #include "maths/voxmaths.hpp" #include "data/StructLayout.hpp" +#include "engine/Engine.hpp" #include "api_lua.hpp" using namespace scripting; @@ -617,6 +619,17 @@ static int l_set_field(lua::State* L) { return set_field(L, dst, *field, index, dataStruct, value); } +static int l_reload_script(lua::State* L) { + auto name = lua::require_string(L, 1); + if (content == nullptr) { + throw std::runtime_error("content is not initialized"); + } + auto& writeableContent = *engine->getWriteableContent(); + auto& def = writeableContent.blocks.require(name); + ContentLoader::reloadScript(writeableContent, def); + return 0; +} + const luaL_Reg blocklib[] = { {"index", lua::wrap}, {"name", lua::wrap}, @@ -652,5 +665,6 @@ const luaL_Reg blocklib[] = { {"decompose_state", lua::wrap}, {"get_field", lua::wrap}, {"set_field", lua::wrap}, + {"reload_script", lua::wrap}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/libs/libitem.cpp b/src/logic/scripting/lua/libs/libitem.cpp index b905f1d9..e7cc241b 100644 --- a/src/logic/scripting/lua/libs/libitem.cpp +++ b/src/logic/scripting/lua/libs/libitem.cpp @@ -1,6 +1,8 @@ #include "content/Content.hpp" +#include "content/ContentLoader.hpp" #include "items/ItemDef.hpp" #include "api_lua.hpp" +#include "engine/Engine.hpp" using namespace scripting; @@ -87,6 +89,17 @@ static int l_uses(lua::State* L) { return 0; } +static int l_reload_script(lua::State* L) { + auto name = lua::require_string(L, 1); + if (content == nullptr) { + throw std::runtime_error("content is not initialized"); + } + auto& writeableContent = *engine->getWriteableContent(); + auto& def = writeableContent.items.require(name); + ContentLoader::reloadScript(writeableContent, def); + return 0; +} + const luaL_Reg itemlib[] = { {"index", lua::wrap}, {"name", lua::wrap}, @@ -98,5 +111,6 @@ const luaL_Reg itemlib[] = { {"model_name", lua::wrap}, {"emission", lua::wrap}, {"uses", lua::wrap}, + {"reload_script", lua::wrap}, {NULL, NULL} }; From 5394f202a104a46efdfe2f2914c433d3d8db8407 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 13 Mar 2025 22:07:10 +0300 Subject: [PATCH 30/36] fix multiline width calculation & feat: multiline tooltips support --- src/graphics/core/Font.cpp | 4 ++-- src/graphics/core/Font.hpp | 4 ++-- src/graphics/ui/GUI.cpp | 2 +- src/graphics/ui/elements/Label.cpp | 32 ++++++++++++++++++++++++++++-- src/graphics/ui/elements/Label.hpp | 3 ++- 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp index 2116fdd3..74a78932 100644 --- a/src/graphics/core/Font.cpp +++ b/src/graphics/core/Font.cpp @@ -38,11 +38,11 @@ bool Font::isPrintableChar(uint codepoint) const { } } -int Font::calcWidth(const std::wstring& text, size_t length) const { +int Font::calcWidth(std::wstring_view text, size_t length) const { return calcWidth(text, 0, length); } -int Font::calcWidth(const std::wstring& text, size_t offset, size_t length) const { +int Font::calcWidth(std::wstring_view text, size_t offset, size_t length) const { return std::min(text.length()-offset, length) * glyphInterval; } diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp index 07fe0532..eabfbcc6 100644 --- a/src/graphics/core/Font.hpp +++ b/src/graphics/core/Font.hpp @@ -56,14 +56,14 @@ public: /// @param text selected text /// @param length max substring length (default: no limit) /// @return pixel width of the substring - int calcWidth(const std::wstring& text, size_t length=-1) const; + int calcWidth(std::wstring_view text, size_t length=-1) const; /// @brief Calculate text width in pixels /// @param text selected text /// @param offset start of the substring /// @param length max substring length /// @return pixel width of the substring - int calcWidth(const std::wstring& text, size_t offset, size_t length) const; + int calcWidth(std::wstring_view text, size_t offset, size_t length) const; /// @brief Check if character is visible (non-whitespace) /// @param codepoint character unicode codepoint diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 5b028ac4..95425da4 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -41,7 +41,7 @@ GUI::GUI() tooltip = guiutil::create( "" - "" + "" "" ); store("tooltip", tooltip); diff --git a/src/graphics/ui/elements/Label.cpp b/src/graphics/ui/elements/Label.cpp index 392a9434..9a174c74 100644 --- a/src/graphics/ui/elements/Label.cpp +++ b/src/graphics/ui/elements/Label.cpp @@ -35,7 +35,7 @@ uint LabelCache::getLineByTextIndex(size_t index) const { return lines.size()-1; } -void LabelCache::update(const std::wstring& text, bool multiline, bool wrap) { +void LabelCache::update(std::wstring_view text, bool multiline, bool wrap) { resetFlag = false; lines.clear(); lines.push_back(LineScheme {0, false}); @@ -59,6 +59,27 @@ void LabelCache::update(const std::wstring& text, bool multiline, bool wrap) { } } } + if (font != nullptr) { + int lineHeight = font->getLineHeight(); + int maxWidth = 0; + for (int i = 0; i < lines.size() - 1; i++) { + const auto& next = lines[i + 1]; + const auto& cur = lines[i]; + maxWidth = std::max( + font->calcWidth( + text.substr(cur.offset, next.offset - cur.offset) + ), + maxWidth + ); + } + maxWidth = std::max( + font->calcWidth( + text.substr(lines[lines.size() - 1].offset) + ), + maxWidth + ); + multilineWidth = maxWidth; + } } } @@ -89,8 +110,15 @@ glm::vec2 Label::calcSize() { if (cache.lines.size() > 1) { lineHeight *= lineInterval; } + auto view = std::wstring_view(text); + if (multiline) { + return glm::vec2( + cache.multilineWidth, + lineHeight * cache.lines.size() + font->getYOffset() + ); + } return glm::vec2 ( - cache.font->calcWidth(text), + cache.font->calcWidth(view), lineHeight * cache.lines.size() + font->getYOffset() ); } diff --git a/src/graphics/ui/elements/Label.hpp b/src/graphics/ui/elements/Label.hpp index 1fbf5f5e..00d3d0b0 100644 --- a/src/graphics/ui/elements/Label.hpp +++ b/src/graphics/ui/elements/Label.hpp @@ -18,9 +18,10 @@ namespace gui { /// @brief Reset cache flag bool resetFlag = true; size_t wrapWidth = -1; + int multilineWidth = 0; void prepare(Font* font, size_t wrapWidth); - void update(const std::wstring& text, bool multiline, bool wrap); + void update(std::wstring_view text, bool multiline, bool wrap); size_t getTextLineOffset(size_t line) const; uint getLineByTextIndex(size_t index) const; From bcbfdd50c7749619494541057cdcdbc005081ad5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 13 Mar 2025 22:08:22 +0300 Subject: [PATCH 31/36] feat: running files with and block/item script reload --- res/layouts/console.xml | 18 +++++--- res/layouts/console.xml.lua | 81 ++++++++++++++++++++++++++++++++---- res/preload.json | 4 +- res/texts/en_US.txt | 2 + res/texts/ru_RU.txt | 5 +++ res/textures/gui/info.png | Bin 0 -> 129 bytes res/textures/gui/play.png | Bin 0 -> 130 bytes 7 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 res/textures/gui/info.png create mode 100644 res/textures/gui/play.png diff --git a/res/layouts/console.xml b/res/layouts/console.xml index 6e5cc6c0..7b9bad3f 100644 --- a/res/layouts/console.xml +++ b/res/layouts/console.xml @@ -46,16 +46,19 @@ color="#FFFFFF80" size="16" pos="4,6" hover-color="#1080FF"> + size="60,16" padding="8" interval="8" color="0"> - + @@ -76,9 +79,12 @@ scroll-step='50' > - - + + + + + + %s: %s", + gui.str("Error at line %{0}"):gsub("%%{0}", line), message) + ) + return + end + local script_type, unit = xunpack(scripts_classification[current_file.filename]) + save_current_file() + + local func = function() + local stack_size = debug.count_frames() + xpcall(chunk, function(msg) __vc__error(msg, 1, 1, stack_size) end) + end + + if script_type == "block" then + func = function() block.reload_script(unit) end + elseif script_type == "item" then + func = function() item.reload_script(unit) end + end + local output = core.capture_output(func) + document.output:add( + string.format( + "", + output) + ) +end + function save_current_file() if not current_file.mutable then return @@ -161,14 +209,26 @@ function open_file_in_editor(filename, line, mutable) document.saveIcon.enabled = current_file.modified end +function clear_traceback() + local tb_list = document.traceback + tb_list:clear() + tb_list:add("") +end + +function clear_output() + local output = document.output + output:clear() + output:add("") +end + events.on("core:open_traceback", function(traceback_b64) local traceback = bjson.frombytes(base64.decode(traceback_b64)) modes:set('debug') + clear_traceback() + local tb_list = document.traceback local srcsize = tb_list.size - tb_list:clear() - tb_list:add("") for _, frame in ipairs(traceback.frames) do local callback = "" local framestr = "" @@ -322,10 +382,10 @@ end local function build_scripts_classification() for id, props in pairs(block.properties) do - scripts_classification[props["script-file"]] = "block" + scripts_classification[props["script-file"]] = {"block", block.name(id)} end for id, props in pairs(item.properties) do - scripts_classification[props["script-file"]] = "item" + scripts_classification[props["script-file"]] = {"item", item.name(id)} end end @@ -336,7 +396,7 @@ local function load_scripts_list() end for _, filename in ipairs(filenames) do - scripts_classification[filename] = "module" + scripts_classification[filename] = {"module"} end for _, packid in ipairs(packs) do @@ -364,6 +424,9 @@ function on_open(mode) build_files_list(filenames) document.editorContainer:setInterval(200, refresh_file_title) + + clear_traceback() + clear_output() elseif mode then modes:set(mode) end diff --git a/res/preload.json b/res/preload.json index 2b00a440..b5c24d09 100644 --- a/res/preload.json +++ b/res/preload.json @@ -30,7 +30,9 @@ "gui/block", "gui/item", "gui/file", - "gui/module" + "gui/module", + "gui/play", + "gui/info" ], "fonts": [ { diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 732e2d66..57a6cb6b 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -11,7 +11,9 @@ world.delete-confirm=Do you want to delete world forever? world.generators.default=Default world.generators.flat=Flat +editor.info.tooltip=CTRL+S - Save\nCTRL+R - Run\nCTRL+Z - Undo\nCTRL+Y - Redo devtools.traceback=Traceback (most recent call first) +devtools.output=Output # Tooltips graphics.gamma.tooltip=Lighting brightness curve diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 92eec3e8..65db4507 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -22,8 +22,13 @@ File=Файл Read only=Только для чтения Save=Сохранить Grant %{0} pack modification permission?=Выдать разрешение на модификацию пака %{0}? +Error at line %{0}=Ошибка на строке %{0} +Run=Запустить +editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить devtools.traceback=Стек вызовов (от последнего) +devtools.output=Вывод + error.pack-not-found=Не удалось найти пакет error.dependency-not-found=Используемая зависимость не найдена pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)? diff --git a/res/textures/gui/info.png b/res/textures/gui/info.png new file mode 100644 index 0000000000000000000000000000000000000000..a7bb70302217ff3fdee6aee497895e619c1bc946 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`9-c0aAr*6y6Bh_)PYsAsomiz?~t?9a4DEz?dPv;nF$Q7b_>GFekJL b7%?#9Y>@F(np>>}G?T&8)z4*}Q$iB}Mo1(m literal 0 HcmV?d00001 diff --git a/res/textures/gui/play.png b/res/textures/gui/play.png new file mode 100644 index 0000000000000000000000000000000000000000..2442b02ca5bd251e47abee9c7aa4683e07f74b1b GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`o}Mm_Ar*6y6Bh_<2)Nqt-+^I~ zWMhMp_=F^ZIDLU+fxyIu^9qxg1ZO*@9A(<%c%|1ZiScHfg0;ZOKDSN6LN`5Uiu6{* bIsolx?3IW-_weFjprH(&u6{1-oD!M<@V_V< literal 0 HcmV?d00001 From f9998f0a93189a35c9ab627ff118c03593e9bf48 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 14 Mar 2025 17:07:10 +0300 Subject: [PATCH 32/36] feat: world script reloading --- res/layouts/console.xml | 3 ++- res/layouts/console.xml.lua | 7 +++++- res/preload.json | 3 ++- res/textures/gui/world.png | Bin 0 -> 178 bytes src/content/Content.cpp | 8 +++++++ src/content/Content.hpp | 1 + src/content/ContentLoader.cpp | 27 ++++++++++++++-------- src/content/ContentLoader.hpp | 1 + src/logic/scripting/lua/libs/libworld.cpp | 13 +++++++++++ src/logic/scripting/scripting.cpp | 6 +++++ 10 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 res/textures/gui/world.png diff --git a/res/layouts/console.xml b/res/layouts/console.xml index 7b9bad3f..bc5fb343 100644 --- a/res/layouts/console.xml +++ b/res/layouts/console.xml @@ -39,7 +39,8 @@ - + -TA|cv8l2=nGryeNl~{+a*%AwM__qaqP3^ilaLf-yW>sDx5m?>ARP_KQ6n(*bA%A d^blo|KlbhN#~1qke}Rr*@O1TaS?83{1OS@*M*IK( literal 0 HcmV?d00001 diff --git a/src/content/Content.cpp b/src/content/Content.cpp index 1cc2fdd7..ce92be18 100644 --- a/src/content/Content.cpp +++ b/src/content/Content.cpp @@ -79,6 +79,14 @@ const ContentPackRuntime* Content::getPackRuntime(const std::string& id) const { return found->second.get(); } +ContentPackRuntime* Content::getPackRuntime(const std::string& id) { + auto found = packs.find(id); + if (found == packs.end()) { + return nullptr; + } + return found->second.get(); +} + const UptrsMap& Content::getBlockMaterials() const { return blockMaterials; } diff --git a/src/content/Content.hpp b/src/content/Content.hpp index a7f56b56..94650b25 100644 --- a/src/content/Content.hpp +++ b/src/content/Content.hpp @@ -248,6 +248,7 @@ public: const rigging::SkeletonConfig* getSkeleton(const std::string& id) const; const BlockMaterial* findBlockMaterial(const std::string& id) const; const ContentPackRuntime* getPackRuntime(const std::string& id) const; + ContentPackRuntime* getPackRuntime(const std::string& id); const UptrsMap& getBlockMaterials() const; const UptrsMap& getPacks() const; diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 47fca715..3fb2b8f6 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -889,6 +889,21 @@ void ContentLoader::reloadScript(const Content& content, ItemDef& item) { load_script(content, item); } +void ContentLoader::loadWorldScript(ContentPackRuntime& runtime) { + const auto& pack = runtime.getInfo(); + const auto& folder = pack.folder; + io::path scriptFile = folder / "scripts/world.lua"; + if (io::is_regular_file(scriptFile)) { + scripting::load_world_script( + runtime.getEnvironment(), + pack.id, + scriptFile, + pack.id + ":scripts/world.lua", + runtime.worldfuncsset + ); + } +} + void ContentLoader::loadScripts(Content& content) { load_scripts(content, content.blocks); load_scripts(content, content.items); @@ -898,16 +913,8 @@ void ContentLoader::loadScripts(Content& content) { const auto& folder = pack.folder; // Load main world script - io::path scriptFile = folder / "scripts/world.lua"; - if (io::is_regular_file(scriptFile)) { - scripting::load_world_script( - runtime->getEnvironment(), - pack.id, - scriptFile, - pack.id + ":scripts/world.lua", - runtime->worldfuncsset - ); - } + loadWorldScript(*runtime); + // Load entity components io::path componentsDir = folder / "scripts/components"; foreach_file(componentsDir, [&pack](const io::path& file) { diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp index 8860cc77..eeedf11b 100644 --- a/src/content/ContentLoader.hpp +++ b/src/content/ContentLoader.hpp @@ -77,6 +77,7 @@ public: void load(); static void loadScripts(Content& content); + static void loadWorldScript(ContentPackRuntime& pack); static void reloadScript(const Content& content, Block& block); static void reloadScript(const Content& content, ItemDef& item); }; diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index f54ce4e3..acf9bf64 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -6,6 +6,7 @@ #include "assets/AssetsLoader.hpp" #include "coders/json.hpp" #include "content/Content.hpp" +#include "content/ContentLoader.hpp" #include "engine/Engine.hpp" #include "world/files/WorldFiles.hpp" #include "io/engine_paths.hpp" @@ -213,6 +214,17 @@ static int l_count_chunks(lua::State* L) { return lua::pushinteger(L, level->chunks->size()); } +static int l_reload_script(lua::State* L) { + auto packid = lua::require_string(L, 1); + if (content == nullptr) { + throw std::runtime_error("content is not initialized"); + } + auto& writeableContent = *engine->getWriteableContent(); + auto pack = writeableContent.getPackRuntime(packid); + ContentLoader::loadWorldScript(*pack); + return 0; +} + const luaL_Reg worldlib[] = { {"is_open", lua::wrap}, {"get_list", lua::wrap}, @@ -230,5 +242,6 @@ const luaL_Reg worldlib[] = { {"set_chunk_data", lua::wrap}, {"save_chunk_data", lua::wrap}, {"count_chunks", lua::wrap}, + {"reload_script", lua::wrap}, {NULL, NULL} }; diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 4e7b37e0..eabbd392 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -836,6 +836,8 @@ void scripting::load_content_script( ) { int env = *senv; lua::pop(lua::get_main_state(), load_script(env, "block", file, fileName)); + + funcsset = {}; funcsset.init = register_event(env, "init", prefix + ".init"); funcsset.update = register_event(env, "on_update", prefix + ".update"); funcsset.randupdate = @@ -861,6 +863,8 @@ void scripting::load_content_script( ) { int env = *senv; lua::pop(lua::get_main_state(), load_script(env, "item", file, fileName)); + + funcsset = {}; funcsset.init = register_event(env, "init", prefix + ".init"); funcsset.on_use = register_event(env, "on_use", prefix + ".use"); funcsset.on_use_on_block = @@ -888,6 +892,8 @@ void scripting::load_world_script( ) { int env = *senv; lua::pop(lua::get_main_state(), load_script(env, "world", file, fileName)); + + funcsset = {}; register_event(env, "init", prefix + ".init"); register_event(env, "on_world_open", prefix + ":.worldopen"); register_event(env, "on_world_tick", prefix + ":.worldtick"); From 6fb14ee0a44755112b5e18235f14ce8393624630 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 15 Mar 2025 21:12:19 +0300 Subject: [PATCH 33/36] feat: reloading hud scripts --- res/layouts/console.xml.lua | 3 +++ res/preload.json | 3 ++- res/textures/gui/hud.png | Bin 0 -> 160 bytes src/logic/scripting/lua/libs/libhud.cpp | 18 ++++++++++++++++ src/logic/scripting/scripting.cpp | 26 ++++++++++++------------ 5 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 res/textures/gui/hud.png diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index eabf6dea..a9867e0b 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -173,6 +173,8 @@ function run_current_file() func = function() item.reload_script(unit) end elseif script_type == "world" then func = function() world.reload_script(unit) end + elseif script_type == "hud" then + func = function() hud.reload_script(unit) end end local output = core.capture_output(func) document.output:add( @@ -392,6 +394,7 @@ local function build_scripts_classification() local packs = pack.get_installed() for _, packid in ipairs(packs) do scripts_classification[packid..":scripts/world.lua"] = {"world", packid} + scripts_classification[packid..":scripts/hud.lua"] = {"hud", packid} end end diff --git a/res/preload.json b/res/preload.json index 5d9bdde5..394f0429 100644 --- a/res/preload.json +++ b/res/preload.json @@ -33,7 +33,8 @@ "gui/module", "gui/play", "gui/info", - "gui/world" + "gui/world", + "gui/hud" ], "fonts": [ { diff --git a/res/textures/gui/hud.png b/res/textures/gui/hud.png new file mode 100644 index 0000000000000000000000000000000000000000..4d32d6166e5754c05a656246d437527fe3e6d8f1 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`>7Fi*Ar*6yfAX{Nh%kr+nHn%L zo(Pigy}&TRN#eJ?$N>}Kr%WaKlio1JuqEwaxVdtPgv%!G3ygt@4d+WMcpbj!SLFzp zFxY;%xR`%EgetWriteableContent(); + auto pack = writeableContent.getPackRuntime(packid); + const auto& info = pack->getInfo(); + scripting::load_hud_script( + pack->getEnvironment(), + packid, + info.folder / "scripts/hud.lua", + pack->getId() + ":scripts/hud.lua" + ); + return 0; +} + const luaL_Reg hudlib[] = { {"open_inventory", wrap_hud}, {"close_inventory", wrap_hud}, @@ -189,5 +206,6 @@ const luaL_Reg hudlib[] = { {"_set_content_access", wrap_hud}, {"_set_debug_cheats", wrap_hud}, {"set_allow_pause", wrap_hud}, + {"reload_script", wrap_hud}, {NULL, NULL} }; diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index eabbd392..9ebd649d 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -806,21 +806,21 @@ bool scripting::register_event( if (lua::pushenv(L, env) == 0) { lua::pushglobals(L); } - if (lua::getfield(L, name)) { - lua::pop(L); - lua::getglobal(L, "events"); - lua::getfield(L, "reset"); - lua::pushstring(L, id); - lua::getfield(L, name, -4); - lua::call_nothrow(L, 2); - lua::pop(L); - - // remove previous name + bool success = true; + lua::getglobal(L, "events"); + lua::getfield(L, "reset"); + lua::pushstring(L, id); + if (!lua::getfield(L, name, -4)) { + success = false; lua::pushnil(L); - lua::setfield(L, name); - return true; } - return false; + lua::call_nothrow(L, 2); + lua::pop(L); + + // remove previous name + lua::pushnil(L); + lua::setfield(L, name); + return success; } int scripting::get_values_on_stack() { From 267aebe7bd9ad82bb06a5bb2d14fcb7291e2d87c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 16 Mar 2025 15:33:10 +0300 Subject: [PATCH 34/36] add core:internal/scripts_registry module --- res/layouts/console.xml.lua | 73 ++++------------------ res/modules/internal/scripts_registry.lua | 74 +++++++++++++++++++++++ res/scripts/post_content.lua | 3 + 3 files changed, 88 insertions(+), 62 deletions(-) create mode 100644 res/modules/internal/scripts_registry.lua diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index a9867e0b..e5b72e6f 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -10,21 +10,14 @@ local warning_id = 0 local error_id = 0 local writeables = {} -local filenames = {} -local scripts_classification = {} +local registry = require "core:internal/scripts_registry" +local filenames local current_file = { filename = "", mutable = nil } -local function xunpack(t) - if t == nil then - return nil - end - return unpack(t) -end - events.on("core:warning", function (wtype, text, traceback) local full = wtype..": "..text if table.has(warnings_all, full) then @@ -102,12 +95,11 @@ function build_files_list(filenames, selected) filename = filename:gsub(selected, "**"..selected.."**") end local parent = file.parent(filename) - local script_type, unit = xunpack(scripts_classification[actual_filename]) - script_type = script_type or "file" + local info = registry.get_info(actual_filename) files_list:add(gui.template("script_file", { path = parent .. (parent[#parent] == ':' and '' or '/'), name = file.name(filename), - type = script_type, + type = info and info.type or "file", filename = actual_filename })) end @@ -159,7 +151,8 @@ function run_current_file() ) return end - local script_type, unit = xunpack(scripts_classification[current_file.filename]) + local info = registry.get_info(current_file.filename) + local script_type = info and info.type or "file" save_current_file() local func = function() @@ -168,13 +161,13 @@ function run_current_file() end if script_type == "block" then - func = function() block.reload_script(unit) end + func = function() block.reload_script(info.unit) end elseif script_type == "item" then - func = function() item.reload_script(unit) end + func = function() item.reload_script(info.unit) end elseif script_type == "world" then - func = function() world.reload_script(unit) end + func = function() world.reload_script(info.unit) end elseif script_type == "hud" then - func = function() hud.reload_script(unit) end + func = function() hud.reload_script(info.unit) end end local output = core.capture_output(func) document.output:add( @@ -371,48 +364,6 @@ function set_mode(mode) console_mode = mode end -local function collect_scripts(dirname, dest) - if file.isdir(dirname) then - local files = file.list(dirname) - for i, filename in ipairs(files) do - if file.isdir(filename) then - collect_scripts(filename, dest) - elseif file.ext(filename) == "lua" then - table.insert(dest, filename) - end - end - end -end - -local function build_scripts_classification() - for id, props in pairs(block.properties) do - scripts_classification[props["script-file"]] = {"block", block.name(id)} - end - for id, props in pairs(item.properties) do - scripts_classification[props["script-file"]] = {"item", item.name(id)} - end - local packs = pack.get_installed() - for _, packid in ipairs(packs) do - scripts_classification[packid..":scripts/world.lua"] = {"world", packid} - scripts_classification[packid..":scripts/hud.lua"] = {"hud", packid} - end -end - -local function load_scripts_list() - local packs = pack.get_installed() - for _, packid in ipairs(packs) do - collect_scripts(packid..":modules", filenames) - end - - for _, filename in ipairs(filenames) do - scripts_classification[filename] = {"module"} - end - - for _, packid in ipairs(packs) do - collect_scripts(packid..":scripts", filenames) - end -end - function on_open(mode) if modes == nil then modes = RadioGroup({ @@ -425,9 +376,7 @@ function on_open(mode) local files_list = document.filesList - load_scripts_list() - build_scripts_classification() - + filenames = registry.filenames table.sort(filenames) build_files_list(filenames) diff --git a/res/modules/internal/scripts_registry.lua b/res/modules/internal/scripts_registry.lua new file mode 100644 index 00000000..405dc15c --- /dev/null +++ b/res/modules/internal/scripts_registry.lua @@ -0,0 +1,74 @@ +local export = {} + +local function collect_components(dirname, dest) + if file.isdir(dirname) then + local files = file.list(dirname) + for i, filename in ipairs(files) do + if file.ext(filename) == "lua" then + table.insert(dest, filename) + export.classification[filename] = { + type="entity", + unit=file.prefix(filename)..":"..file.name(filename) + } + end + end + end +end + +local function collect_scripts(dirname, dest) + if file.isdir(dirname) then + local files = file.list(dirname) + for i, filename in ipairs(files) do + if file.name(filename) == "components" then + collect_components(filename, dest) + elseif file.isdir(filename) then + collect_scripts(filename, dest) + elseif file.ext(filename) == "lua" then + table.insert(dest, filename) + end + end + end +end + +local function load_scripts_list() + local packs = pack.get_installed() + for _, packid in ipairs(packs) do + collect_scripts(packid..":modules", export.filenames) + end + + for _, filename in ipairs(export.filenames) do + export.classification[filename] = { + type="module", + unit=file.prefix(filename)..":"..filename:sub(filename:find("/")+1) + } + end + + for _, packid in ipairs(packs) do + collect_scripts(packid..":scripts", export.filenames) + end +end + +function export.build_classification() + local classification = {} + for id, props in pairs(block.properties) do + classification[props["script-file"]] = {type="block", unit=block.name(id)} + end + for id, props in pairs(item.properties) do + classification[props["script-file"]] = {type="item", unit=item.name(id)} + end + local packs = pack.get_installed() + for _, packid in ipairs(packs) do + classification[packid..":scripts/world.lua"] = {type="world", unit=packid} + classification[packid..":scripts/hud.lua"] = {type="hud", unit=packid} + end + export.classification = classification + export.filenames = {} + + load_scripts_list() +end + +function export.get_info(filename) + return export.classification[filename] +end + +return export diff --git a/res/scripts/post_content.lua b/res/scripts/post_content.lua index e7361bb2..85d7b28a 100644 --- a/res/scripts/post_content.lua +++ b/res/scripts/post_content.lua @@ -61,3 +61,6 @@ end cache_names(block) cache_names(item) + +local scripts_registry = require "core:internal/scripts_registry" +scripts_registry.build_classification() From 4761c520d5b5dee46bbb9d05757cf6f39d5d604c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 16 Mar 2025 22:03:37 +0300 Subject: [PATCH 35/36] feat: component script reloading --- res/layouts/console.xml.lua | 27 ++++++++++++--------- res/layouts/templates/script_file.xml | 3 ++- res/modules/internal/scripts_registry.lua | 18 +++++++------- res/preload.json | 3 ++- res/textures/gui/entity.png | Bin 0 -> 150 bytes src/logic/scripting/lua/libs/libentity.cpp | 13 ++++++++++ 6 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 res/textures/gui/entity.png diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index e5b72e6f..5a5a1101 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -96,10 +96,15 @@ function build_files_list(filenames, selected) end local parent = file.parent(filename) local info = registry.get_info(actual_filename) + local icon = "file" + if info then + icon = info.type == "component" and "entity" or info.type + end files_list:add(gui.template("script_file", { path = parent .. (parent[#parent] == ':' and '' or '/'), name = file.name(filename), - type = info and info.type or "file", + icon = icon, + unit = info and info.unit or '', filename = actual_filename })) end @@ -153,6 +158,7 @@ function run_current_file() end local info = registry.get_info(current_file.filename) local script_type = info and info.type or "file" + local unit = info and info.unit save_current_file() local func = function() @@ -160,16 +166,15 @@ function run_current_file() xpcall(chunk, function(msg) __vc__error(msg, 1, 1, stack_size) end) end - if script_type == "block" then - func = function() block.reload_script(info.unit) end - elseif script_type == "item" then - func = function() item.reload_script(info.unit) end - elseif script_type == "world" then - func = function() world.reload_script(info.unit) end - elseif script_type == "hud" then - func = function() hud.reload_script(info.unit) end - end - local output = core.capture_output(func) + local funcs = { + block = block.reload_script, + item = item.reload_script, + world = world.reload_script, + hud = hud.reload_script, + component = entities.reload_component, + } + func = funcs[script_type] or func + local output = core.capture_output(function() func(unit) end) document.output:add( string.format( "", diff --git a/res/layouts/templates/script_file.xml b/res/layouts/templates/script_file.xml index 2df77df1..94e1afd2 100644 --- a/res/layouts/templates/script_file.xml +++ b/res/layouts/templates/script_file.xml @@ -1,10 +1,11 @@ - + diff --git a/res/modules/internal/scripts_registry.lua b/res/modules/internal/scripts_registry.lua index 405dc15c..de685224 100644 --- a/res/modules/internal/scripts_registry.lua +++ b/res/modules/internal/scripts_registry.lua @@ -7,19 +7,19 @@ local function collect_components(dirname, dest) if file.ext(filename) == "lua" then table.insert(dest, filename) export.classification[filename] = { - type="entity", - unit=file.prefix(filename)..":"..file.name(filename) + type="component", + unit=file.prefix(filename)..":"..file.stem(filename) } end end end end -local function collect_scripts(dirname, dest) +local function collect_scripts(dirname, dest, ismodule) if file.isdir(dirname) then local files = file.list(dirname) for i, filename in ipairs(files) do - if file.name(filename) == "components" then + if file.name(filename) == "components" and not ismodule then collect_components(filename, dest) elseif file.isdir(filename) then collect_scripts(filename, dest) @@ -33,18 +33,18 @@ end local function load_scripts_list() local packs = pack.get_installed() for _, packid in ipairs(packs) do - collect_scripts(packid..":modules", export.filenames) + collect_scripts(packid..":modules", export.filenames, true) end - for _, filename in ipairs(export.filenames) do export.classification[filename] = { type="module", - unit=file.prefix(filename)..":"..filename:sub(filename:find("/")+1) + unit=file.join(file.parent(file.prefix(filename)..":".. + filename:sub(filename:find("/")+1)), + file.stem(filename)) } end - for _, packid in ipairs(packs) do - collect_scripts(packid..":scripts", export.filenames) + collect_scripts(packid..":scripts", export.filenames, false) end end diff --git a/res/preload.json b/res/preload.json index 394f0429..1cfd0747 100644 --- a/res/preload.json +++ b/res/preload.json @@ -34,7 +34,8 @@ "gui/play", "gui/info", "gui/world", - "gui/hud" + "gui/hud", + "gui/entity" ], "fonts": [ { diff --git a/res/textures/gui/entity.png b/res/textures/gui/entity.png new file mode 100644 index 0000000000000000000000000000000000000000..53207e8b97bc294fbb2dd33693617824beff33c6 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`v7RoDAr*6y6Bh_lzeo2!5U-O&cVM@Wp~4k yBS)KZq&Lnom}InzMXz~7s>YhQ15-b5U}Vr&H?ibS{8kCHh{4m<&t;ucLK6Vj%QTe$ literal 0 HcmV?d00001 diff --git a/src/logic/scripting/lua/libs/libentity.cpp b/src/logic/scripting/lua/libs/libentity.cpp index cecac6e9..28a7bd84 100644 --- a/src/logic/scripting/lua/libs/libentity.cpp +++ b/src/logic/scripting/lua/libs/libentity.cpp @@ -223,6 +223,18 @@ static int l_raycast(lua::State* L) { return 0; } +static int l_reload_component(lua::State* L) { + std::string name = lua::require_string(L, 1); + size_t pos = name.find(':'); + if (pos == std::string::npos) { + throw std::runtime_error("missing entry point"); + } + auto filename = name.substr(0, pos + 1) + "scripts/components/" + + name.substr(pos + 1) + ".lua"; + scripting::load_entity_component(name, filename, filename); + return 0; +} + const luaL_Reg entitylib[] = { {"exists", lua::wrap}, {"def_index", lua::wrap}, @@ -238,5 +250,6 @@ const luaL_Reg entitylib[] = { {"get_all_in_box", lua::wrap}, {"get_all_in_radius", lua::wrap}, {"raycast", lua::wrap}, + {"reload_component", lua::wrap}, {NULL, NULL} }; From 12aced92ccadd9df0211ca0b8778f41576dbe574 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 16 Mar 2025 22:27:08 +0300 Subject: [PATCH 36/36] feat: reloading modules --- res/layouts/console.xml.lua | 1 + res/scripts/stdmin.lua | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index 5a5a1101..cc9d7147 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -172,6 +172,7 @@ function run_current_file() world = world.reload_script, hud = hud.reload_script, component = entities.reload_component, + module = reload_module, } func = funcs[script_type] or func local output = core.capture_output(function() func(unit) end) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index d9be5cca..1366966a 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -475,6 +475,35 @@ function on_deprecated_call(name, alternatives) end end +function reload_module(name) + local prefix, name = parse_path(name) + local path = prefix..":modules/"..name..".lua" + + local previous = package.loaded[path] + if not previous then + debug.log("attempt to reload non-loaded module "..name.." ("..path..")") + return + end + local script, err = load(file.read(path), path) + if script == nil then + error(err) + end + local result = script() + if not result then + return + end + for i, value in ipairs(result) do + previous[i] = value + end + local copy = table.copy(result) + for key, value in pairs(result) do + result[key] = nil + end + for key, value in pairs(copy) do + previous[key] = value + end +end + -- Load script with caching -- -- path - script path `contentpack:filename`.