diff --git a/doc/en/scripting/builtins/libapp.md b/doc/en/scripting/builtins/libapp.md index 2b5a74cd..0ec75568 100644 --- a/doc/en/scripting/builtins/libapp.md +++ b/doc/en/scripting/builtins/libapp.md @@ -159,6 +159,11 @@ app.get_setting_info(name: str) -> { Returns a table with information about a setting. Throws an exception if the setting does not exist. +```lua +app.focus() +``` + +Brings the window to front and sets input focus. ```lua app.create_memory_device( diff --git a/doc/en/scripting/builtins/libinput.md b/doc/en/scripting/builtins/libinput.md index 2c577389..9856abc0 100644 --- a/doc/en/scripting/builtins/libinput.md +++ b/doc/en/scripting/builtins/libinput.md @@ -13,7 +13,16 @@ input.mousecode(mousename: str) --> int Returns mouse button code or -1 if unknown ```lua -input.add_callback(bindname: str, callback: function) +input.add_callback( + -- Binding name + bindname: str, + -- Handler + callback: function + -- UI element that owns the handler (responsible for the handler's lifetime) + [optional] owner: Element, + -- Ignore input capture by UI elements + [optional] istoplevel: bool +) ``` Add binding activation callback. Example: diff --git a/doc/en/scripting/builtins/libnetwork.md b/doc/en/scripting/builtins/libnetwork.md index 515488f7..3ceac9c8 100644 --- a/doc/en/scripting/builtins/libnetwork.md +++ b/doc/en/scripting/builtins/libnetwork.md @@ -82,6 +82,16 @@ socket:recv( -- Returns nil on error (socket is closed or does not exist). -- If there is no data yet, returns an empty byte array. +-- Asynchronous version for use in coroutines. +-- Waits for the entire specified number of bytes to be received. +-- If socket closes, function works like socket:recv +socket:recv_async( + -- Size of the byte array to read + length: int, + -- Use table instead of Bytearray + [optional] usetable: bool=false +) -> nil|table|Bytearray + -- Closes the connection socket:close() @@ -137,5 +147,5 @@ network.get_total_download() --> int ```lua -- Looks for a free port to use. -network.find_free_port() --> int +network.find_free_port() --> int or nil ``` diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 1127d79f..5a4d7be4 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -136,6 +136,7 @@ The key code for comparison can be obtained via `input.keycode("key_name")` - `text-wrap` - allows automatic text wrapping (works only with multiline: "true") - `editable` - determines whether the text can be edited. - `line-numbers` - enables line numbers display. +- `keep-line-selection` - keep showing selected line after defocus. - `error-color` - color when entering incorrect data (the text does not pass the validator check). Type: RGBA color. - `text-color` - text color. Type: RGBA color. - `validator` - lua function that checks text for correctness. Takes a string as input, returns true if the text is correct. diff --git a/doc/ru/scripting/builtins/libapp.md b/doc/ru/scripting/builtins/libapp.md index 128232bf..d8a1e81c 100644 --- a/doc/ru/scripting/builtins/libapp.md +++ b/doc/ru/scripting/builtins/libapp.md @@ -161,6 +161,11 @@ app.get_setting_info(name: str) -> { Возвращает таблицу с информацией о настройке. Бросает исключение, если настройки не существует. ```lua +app.focus() +``` + +Переводит окно на передний план и устанавливает фокус ввода. + app.create_memory_device( -- имя точки входа name: str diff --git a/doc/ru/scripting/builtins/libinput.md b/doc/ru/scripting/builtins/libinput.md index 96bc3105..6aadbdc1 100644 --- a/doc/ru/scripting/builtins/libinput.md +++ b/doc/ru/scripting/builtins/libinput.md @@ -13,7 +13,16 @@ input.mousecode(mousename: str) --> int Возвращает код кнопки мыши по имени, либо -1 ```lua -input.add_callback(bindname: str, callback: function) +input.add_callback( + -- Имя привязки + bindname: str, + -- Обработчик + callback: function + -- UI элемент-владелец обработчика (отвечает за срок жизни) + [опционально] owner: Element, + -- Игнорировать захват ввода UI элементами + [опционально] istoplevel: bool +) ``` Назначает функцию, которая будет вызываться при активации привязки. Пример: diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index 269b1615..8c574f36 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -82,6 +82,16 @@ socket:recv( -- В случае ошибки возвращает nil (сокет закрыт или несуществует). -- Если данных пока нет, возвращает пустой массив байт. +-- Асинхронный вариант для использования в корутинах. +-- Ожидает получение всего указанного числа байт. +-- При закрытии сокета работает как socket:recv +socket:recv_async( + -- Размер читаемого массива байт + length: int, + -- Использовать таблицу вместо Bytearray + [опционально] usetable: bool=false +) -> nil|table|Bytearray + -- Закрывает соединение socket:close() @@ -202,5 +212,5 @@ network.get_total_download() --> int ```lua -- Ищет свободный для использования порт. -network.find_free_port() --> int +network.find_free_port() --> int или nil ``` diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index a2f068ca..b6efa7b2 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -137,6 +137,7 @@ - `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true") - `editable`- определяет возможность редактирования текста. - `line-numbers` - включает отображение номеров строк. +- `keep-line-selection` - продолжать отображать выбранную строку при потере фокуса. - `error-color` - цвет при вводе некорректных данных (текст не проходит проверку валидатора). Тип: RGBA цвет. - `text-color` - цвет текста. Тип: RGBA цвет. - `validator` - lua функция, проверяющая текст на корректность. Принимает на вход строку, возвращает true если текст корректен. diff --git a/doc/specs/debugging_protocol.md b/doc/specs/debugging_protocol.md new file mode 100644 index 00000000..ba4cf96a --- /dev/null +++ b/doc/specs/debugging_protocol.md @@ -0,0 +1,153 @@ +# VC-DBG protocol v1 + +## Notes + +- '?' in name means that the attribute is optional. + +## Connecting + +### Step 1 + +Connection initiating with binary header exchange: + +``` +'v' 'c' '-' 'd' 'b' 'g' NUL XX +76 63 2D 64 62 67 00 XX +``` + +XX - protocol version number + +Client sends header to the server. Then server responds. +Server closes connection after sending header if it mismatches. + +### Step 2 + +Client sends 'connect' command. + +## Messages + +Message is: +- 32 bit little-endian unsigned integer - number of encoded message bytes. +- message itself (UTF-8 encoded json). + +## Client-to-server + +### Establishing connection + +```json +{ + "type": "connect", + "?disconnect-action": "resume|detach|terminate" +} +``` + +Configuring connection. Disconnect-action is action that debugged instance must perform on debugging client connection closed/refused. + +- `resume` - Resume from pause mode and start listening for client. +- `detach` - Resume from pause mode and stop server. +- `terminate` - Stop the debugged application. + + +### Specific action signals. + +```json +{ + "type": "pause|resume|terminate|detach" +} +``` + +### Breakpoints management + +```json +{ + "type": "set-breakpoint|remove-breakpoint", + "source": "entry_point:path", + "line": 1 +} +``` + +### Local value details request + +```json +{ + "type": "get-value", + "frame": 0, + "local": 1, + "path": ["path", "to", 1, "value"] +} +``` + +- `frame` - Call stack frame index (indexing from most recent call) +- `local` - Local variable index (based on `paused` event stack trace) +- `path` - Requsted value path segments. Example: `['a', 'b', 5]` is `local_variable.a.b[5]` + +Responds with: + +```json +{ + "type": "value", + "frame": 0, + "local": 1, + "path": ["path", "to", 1, "value"], + "value": "value itself" +} +``` + +Example: actual value is table: +```lua +{a=5, b="test", 2={}} +``` + +Then `value` is: +```json +{ + "a": { + "type": "number", + "short": "5" + }, + "b": { + "type": "string", + "short": "test" + }, + "1": { + "type": "table", + "short": "{...}" + } +} +``` + +## Server-to-client + +### Response signals + +```json +{ + "type": "success|resumed" +} +``` + +### Pause event + +```json +{ + "type": "paused", + "?reason": "breakpoint|exception|step", + "?message": "...", + "?stack": [ + { + "?function": "function name", + "source": "source name", + "what": "what", + "line": 1, + "locals": [ + "name": { + "type": "local type", + "index": 1, + "short": "short value", + "size": 0 + } + ] + } + ] +} +``` diff --git a/res/devtools/syntax/lua.toml b/res/devtools/syntax/lua.toml index cbccce75..d2469d2f 100644 --- a/res/devtools/syntax/lua.toml +++ b/res/devtools/syntax/lua.toml @@ -8,5 +8,5 @@ multiline-string-end = "]]" keywords = [ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", - "until", "while" + "until", "while", "self", "error" ] diff --git a/res/modules/internal/debugging.lua b/res/modules/internal/debugging.lua index cef40202..1b56ee4b 100644 --- a/res/modules/internal/debugging.lua +++ b/res/modules/internal/debugging.lua @@ -60,7 +60,9 @@ if is_debugging then current_func = _debug_getinfo(2).func current_func_stack_size = calc_stack_size() __pause("breakpoint") - debug.pull_events() + while debug.pull_events() do + __pause() + end end, "lr") end @@ -92,6 +94,7 @@ function debug.pull_events() if not events then return end + local keepPaused = false for i, event in ipairs(events) do if event[1] == DBG_EVENT_SET_BREAKPOINT then debug.set_breakpoint(event[2], event[3]) @@ -116,9 +119,10 @@ function debug.pull_events() value = value[key] end __sendvalue(value, event[2], event[3], event[4]) - __pause() + keepPaused = true end end + return keepPaused end function debug.set_breakpoint(source, line) diff --git a/res/modules/schedule.lua b/res/modules/schedule.lua index 457cbf90..3c75ead8 100644 --- a/res/modules/schedule.lua +++ b/res/modules/schedule.lua @@ -3,7 +3,7 @@ local Schedule = { set_interval = function(self, ms, callback, repetions) local id = self._next_interval self._intervals[id] = { - last_called = 0.0, + last_called = self._timer, delay = ms / 1000.0, callback = callback, repetions = repetions, diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 9076808c..f55a503b 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -39,6 +39,16 @@ end local Socket = {__index={ send=function(self, ...) return network.__send(self.id, ...) end, recv=function(self, ...) return network.__recv(self.id, ...) end, + recv_async=function(self, length, usetable) + while self:is_alive() do + local available = self:available() + if available >= length then + return self:recv(length, usetable) + end + coroutine.yield() + end + return self:recv(length, usetable) + end, close=function(self) return network.__close(self.id) end, available=function(self) return network.__available(self.id) or 0 end, is_alive=function(self) return network.__is_alive(self.id) end, diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 945f63a2..f8cb1c07 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -47,6 +47,7 @@ local function complete_app_lib(app) end app.reset_content = core.reset_content app.is_content_loaded = core.is_content_loaded + app.set_title = core.set_title function app.config_packs(packs_list) -- Check if packs are valid and add dependencies to the configuration diff --git a/src/devtools/DebuggingServer.cpp b/src/devtools/DebuggingServer.cpp index 09a2b368..b1aba638 100644 --- a/src/devtools/DebuggingServer.cpp +++ b/src/devtools/DebuggingServer.cpp @@ -197,6 +197,7 @@ bool DebuggingServer::performCommand( connectionEstablished = true; logger.info() << "client connection established"; connection->sendResponse("success"); + return true; } if (!connectionEstablished) { return false; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 6598562f..67ff6797 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -70,6 +70,7 @@ void Engine::onContentLoad() { for (auto& pack : content->getAllContentPacks()) { auto configFolder = pack.folder / "config"; auto bindsFile = configFolder / "bindings.toml"; + logger.info() << "loading bindings: " << bindsFile.string(); if (io::is_regular_file(bindsFile)) { input->getBindings().read( toml::parse( diff --git a/src/engine/EnginePaths.cpp b/src/engine/EnginePaths.cpp index 82aea253..6c05614e 100644 --- a/src/engine/EnginePaths.cpp +++ b/src/engine/EnginePaths.cpp @@ -72,6 +72,14 @@ EnginePaths::EnginePaths(CoreParameters& params) io::create_subdevice("config", "user", "config"); } +std::filesystem::path EnginePaths::getResourcesFolder() const { + return resourcesFolder; +} + +std::filesystem::path EnginePaths::getUserFilesFolder() const { + return userFilesFolder; +} + io::path EnginePaths::getNewScreenshotFile(const std::string& ext) const { auto folder = SCREENSHOTS_FOLDER; if (!io::is_directory(folder)) { diff --git a/src/engine/EnginePaths.hpp b/src/engine/EnginePaths.hpp index 8e5f71a2..876394b4 100644 --- a/src/engine/EnginePaths.hpp +++ b/src/engine/EnginePaths.hpp @@ -48,6 +48,9 @@ public: EnginePaths(CoreParameters& params); + std::filesystem::path getResourcesFolder() const; + std::filesystem::path getUserFilesFolder() const; + io::path getWorldFolderByName(const std::string& name); io::path getWorldsFolder() const; diff --git a/src/engine/WindowControl.cpp b/src/engine/WindowControl.cpp index 5ae96965..a93c1599 100644 --- a/src/engine/WindowControl.cpp +++ b/src/engine/WindowControl.cpp @@ -33,11 +33,12 @@ WindowControl::Result WindowControl::initialize() { auto& settings = engine.getSettings(); std::string title = project.title; - if (title.empty()) { - title = "VoxelCore v" + - std::to_string(ENGINE_VERSION_MAJOR) + "." + - std::to_string(ENGINE_VERSION_MINOR); + if (!title.empty()) { + title += " - "; } + title += "VoxelCore v" + + std::to_string(ENGINE_VERSION_MAJOR) + "." + + std::to_string(ENGINE_VERSION_MINOR); if (ENGINE_DEBUG_BUILD) { title += " [debug]"; } diff --git a/src/graphics/ui/elements/Container.cpp b/src/graphics/ui/elements/Container.cpp index 69217637..aa9b5977 100644 --- a/src/graphics/ui/elements/Container.cpp +++ b/src/graphics/ui/elements/Container.cpp @@ -93,10 +93,9 @@ void Container::act(float delta) { } } } - GUI& gui = this->gui; intervalEvents.erase(std::remove_if( intervalEvents.begin(), intervalEvents.end(), - [&gui](const IntervalEvent& event) { + [](const IntervalEvent& event) { return event.repeat == 0; } ), intervalEvents.end()); @@ -177,6 +176,7 @@ void Container::add(const std::shared_ptr& node) { parent->setMustRefresh(); parent = parent->getParent(); } + gui.getWindow().setShouldRefresh(); } void Container::add(const std::shared_ptr& node, glm::vec2 pos) { diff --git a/src/graphics/ui/elements/Container.hpp b/src/graphics/ui/elements/Container.hpp index dd7dfd34..0506bf38 100644 --- a/src/graphics/ui/elements/Container.hpp +++ b/src/graphics/ui/elements/Container.hpp @@ -37,7 +37,9 @@ namespace gui { virtual void scrolled(int value) override; virtual void setScrollable(bool flag); void listenInterval(float interval, OnTimeOut callback, int repeat=-1); - virtual glm::vec2 getContentOffset() override {return glm::vec2(0.0f, scroll);}; + virtual glm::vec2 getContentOffset() const override { + return glm::vec2(0.0f, scroll); + }; virtual void setSize(const glm::vec2& size) override; virtual int getScrollStep() const; virtual void setScrollStep(int step); diff --git a/src/graphics/ui/elements/InlineFrame.cpp b/src/graphics/ui/elements/InlineFrame.cpp index 35c7b639..50fc7976 100644 --- a/src/graphics/ui/elements/InlineFrame.cpp +++ b/src/graphics/ui/elements/InlineFrame.cpp @@ -36,12 +36,11 @@ void InlineFrame::setDocument(const std::shared_ptr& document) { } void InlineFrame::act(float delta) { - Container::act(delta); - if (document || src.empty()) { - return; + if (document == nullptr && !src.empty()) { + const auto& assets = *gui.getEngine().getAssets(); + setDocument(assets.getShared(src)); } - const auto& assets = *gui.getEngine().getAssets(); - setDocument(assets.getShared(src)); + Container::act(delta); } void InlineFrame::setSize(const glm::vec2& size) { diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 5e7ab370..83c15544 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -231,7 +231,7 @@ TextBox::~TextBox() = default; void TextBox::draw(const DrawContext& pctx, const Assets& assets) { Container::draw(pctx, assets); - if (!isFocused()) { + if (!isFocused() && !keepLineSelection) { return; } const auto& labelText = getText(); @@ -252,7 +252,7 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { float time = gui.getWindow().time(); - if (editable && static_cast((time - caretLastMove) * 2) % 2 == 0) { + if (isFocused() && editable && static_cast((time - caretLastMove) * 2) % 2 == 0) { uint line = label->getLineByTextIndex(caret); uint lcaret = caret - label->getTextLineOffset(line); int width = rawTextCache.metrics.calcWidth(input, 0, lcaret); @@ -308,42 +308,44 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { } } - if (isFocused() && multiline) { - auto selectionCtx = subctx.sub(batch); - selectionCtx.setBlendMode(BlendMode::addition); - - batch->setColor(glm::vec4(1, 1, 1, 0.1f)); - - uint line = label->getLineByTextIndex(caret); - while (label->isFakeLine(line)) { - line--; - } - do { - int lineY = label->getLineYOffset(line); - - batch->setColor(glm::vec4(1, 1, 1, 0.05f)); - if (showLineNumbers) { - batch->rect( - lcoord.x - 8, - lcoord.y + lineY, - label->getSize().x, - lineHeight - ); - batch->setColor(glm::vec4(1, 1, 1, 0.10f)); - batch->rect( - lcoord.x - LINE_NUMBERS_PANE_WIDTH, - lcoord.y + lineY, - LINE_NUMBERS_PANE_WIDTH - 8, - lineHeight - ); - } else { - batch->rect( - lcoord.x, lcoord.y + lineY, label->getSize().x, lineHeight - ); - } - line++; - } while (line < label->getLinesNumber() && label->isFakeLine(line)); + if (!multiline) { + return; } + + auto selectionCtx = subctx.sub(batch); + selectionCtx.setBlendMode(BlendMode::addition); + + batch->setColor(glm::vec4(1, 1, 1, 0.1f)); + + uint line = label->getLineByTextIndex(caret); + while (label->isFakeLine(line)) { + line--; + } + do { + int lineY = label->getLineYOffset(line); + + batch->setColor(glm::vec4(1, 1, 1, 0.05f)); + if (showLineNumbers) { + batch->rect( + lcoord.x - 8, + lcoord.y + lineY, + label->getSize().x, + lineHeight + ); + batch->setColor(glm::vec4(1, 1, 1, 0.10f)); + batch->rect( + lcoord.x - LINE_NUMBERS_PANE_WIDTH, + lcoord.y + lineY, + LINE_NUMBERS_PANE_WIDTH - 8, + lineHeight + ); + } else { + batch->rect( + lcoord.x, lcoord.y + lineY, label->getSize().x, lineHeight + ); + } + line++; + } while (line < label->getLinesNumber() && label->isFakeLine(line)); } void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) { @@ -605,6 +607,14 @@ size_t TextBox::getSelectionEnd() const { return selectionEnd; } +void TextBox::setKeepLineSelection(bool flag) { + keepLineSelection = flag; +} + +bool TextBox::isKeepLineSelection() const { + return keepLineSelection; +} + void TextBox::setOnEditStart(runnable oneditstart) { onEditStart = oneditstart; } @@ -671,6 +681,11 @@ int TextBox::calcIndexAt(int x, int y) const { ); } +int TextBox::getLineYOffset(int line) const { + if (rawTextCache.fontId == 0) return 0; + return label->getLineYOffset(line); +} + static inline std::wstring get_alphabet(wchar_t c) { std::wstring alphabet {c}; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') { diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index d5b640ac..2f9080a5 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -62,6 +62,7 @@ namespace gui { bool editable = true; bool autoresize = false; bool showLineNumbers = false; + bool keepLineSelection = false; std::string markup; std::string syntax; @@ -74,7 +75,6 @@ namespace gui { size_t normalizeIndex(int index); - int calcIndexAt(int x, int y) const; void setTextOffset(uint x); bool eraseSelected(); void resetSelection(); @@ -186,6 +186,9 @@ namespace gui { /// @return line position in text virtual size_t getLinePos(uint line) const; + int calcIndexAt(int x, int y) const; + int getLineYOffset(int line) const; + /// @brief Check text with validator set with setTextValidator /// @return true if text is valid virtual bool validate(); @@ -220,6 +223,9 @@ namespace gui { size_t getSelectionStart() const; size_t getSelectionEnd() const; + void setKeepLineSelection(bool flag); + bool isKeepLineSelection() const; + /// @brief Set runnable called on textbox focus virtual void setOnEditStart(runnable oneditstart); diff --git a/src/graphics/ui/elements/UINode.cpp b/src/graphics/ui/elements/UINode.cpp index 047c19ad..91973150 100644 --- a/src/graphics/ui/elements/UINode.cpp +++ b/src/graphics/ui/elements/UINode.cpp @@ -68,6 +68,10 @@ void UINode::listenClick(OnAction action) { actions.listen(UIAction::CLICK, std::move(action)); } +void UINode::listenRightClick(OnAction action) { + actions.listen(UIAction::RIGHT_CLICK, std::move(action)); +} + void UINode::listenDoubleClick(OnAction action) { actions.listen(UIAction::DOUBLE_CLICK, std::move(action)); } @@ -84,6 +88,12 @@ void UINode::click(int, int) { pressed = true; } +void UINode::clicked(Mousecode button) { + if (button == Mousecode::BUTTON_2) { + actions.notify(UIAction::RIGHT_CLICK, gui); + } +} + void UINode::doubleClick(int x, int y) { pressed = true; if (isInside(glm::vec2(x, y))) { diff --git a/src/graphics/ui/elements/UINode.hpp b/src/graphics/ui/elements/UINode.hpp index 695624bb..db9b97dc 100644 --- a/src/graphics/ui/elements/UINode.hpp +++ b/src/graphics/ui/elements/UINode.hpp @@ -75,7 +75,7 @@ namespace gui { }; enum class UIAction { - CLICK, DOUBLE_CLICK, FOCUS, DEFOCUS + CLICK, DOUBLE_CLICK, FOCUS, DEFOCUS, RIGHT_CLICK }; using ActionsSet = TaggedCallbacksSet; @@ -214,6 +214,7 @@ namespace gui { int getZIndex() const; virtual void listenClick(OnAction action); + virtual void listenRightClick(OnAction action); virtual void listenDoubleClick(OnAction action); virtual void listenFocus(OnAction action); virtual void listenDefocus(OnAction action); @@ -221,7 +222,7 @@ namespace gui { virtual void onFocus(); virtual void doubleClick(int x, int y); virtual void click(int x, int y); - virtual void clicked(Mousecode button) {} + virtual void clicked(Mousecode button); virtual void mouseMove(int x, int y) {}; virtual void mouseRelease(int x, int y); virtual void scrolled(int value); @@ -266,7 +267,7 @@ namespace gui { virtual glm::vec4 calcColor() const; /// @brief Get inner content offset. Used for scroll - virtual glm::vec2 getContentOffset() {return glm::vec2(0.0f);}; + virtual glm::vec2 getContentOffset() const {return glm::vec2(0.0f);}; /// @brief Calculate screen position of the element virtual glm::vec2 calcPos() const; virtual void setPos(const glm::vec2& pos); diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index 52febdbc..3e8bcf5d 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -181,6 +181,10 @@ static void read_uinode( node.listenClick(onclick); } + if (auto onclick = create_action(reader, element, "onrightclick")) { + node.listenRightClick(onclick); + } + if (auto onfocus = create_action(reader, element, "onfocus")) { node.listenFocus(onfocus); } @@ -569,6 +573,11 @@ static std::shared_ptr read_text_box( if (element.has("line-numbers")) { textbox->setShowLineNumbers(element.attr("line-numbers").asBool()); } + if (element.has("keep-line-selection")) { + textbox->setKeepLineSelection( + element.attr("keep-line-selection").asBool() + ); + } if (element.has("markup")) { textbox->setMarkup(element.attr("markup").getText()); } diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 7837c0a4..724bac65 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -2,12 +2,40 @@ #include "io/io.hpp" #include "io/devices/MemoryDevice.hpp" -#include "engine/Engine.hpp" -#include "content/ContentControl.hpp" #include "logic/scripting/scripting.hpp" +#include "content/ContentControl.hpp" +#include "engine/Engine.hpp" +#include "engine/EnginePaths.hpp" +#include "network/Network.hpp" +#include "util/platform.hpp" +#include "window/Window.hpp" using namespace scripting; +static int l_start_debug_instance(lua::State* L) { + int port = lua::tointeger(L, 1); + if (port == 0) { + port = engine->getNetwork().findFreePort(); + if (port == -1) { + throw std::runtime_error("could not find free port"); + } + } + const auto& paths = engine->getPaths(); + + std::vector args { + "--res", paths.getResourcesFolder().u8string(), + "--dir", paths.getUserFilesFolder().u8string(), + "--dbg-server", "tcp:" + std::to_string(port), + }; + platform::new_engine_instance(std::move(args)); + return lua::pushinteger(L, port); +} + +static int l_focus(lua::State* L) { + engine->getWindow().focus(); + return 0; +} + static int l_create_memory_device(lua::State* L) { std::string name = lua::require_string(L, 1); if (io::get_device(name)) { @@ -54,10 +82,12 @@ static int l_reset_content_sources(lua::State* L) { } const luaL_Reg applib[] = { + {"start_debug_instance", lua::wrap}, + {"focus", lua::wrap}, {"create_memory_device", lua::wrap}, {"get_content_sources", lua::wrap}, {"set_content_sources", lua::wrap}, {"reset_content_sources", lua::wrap}, - // see libcore.cpp an stdlib.lua + // for other functions see libcore.cpp and stdlib.lua {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 8454e5c3..e4d5474c 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -25,6 +25,7 @@ #include "graphics/ui/gui_util.hpp" #include "graphics/ui/GUI.hpp" #include "graphics/ui/elements/Menu.hpp" +#include "window/Window.hpp" using namespace scripting; @@ -299,6 +300,12 @@ static int l_capture_output(lua::State* L) { return 1; } +static int l_set_title(lua::State* L) { + auto title = lua::require_string(L, 1); + engine->getWindow().setTitle(title); + return 0; +} + const luaL_Reg corelib[] = { {"blank", lua::wrap}, {"get_version", lua::wrap}, @@ -320,5 +327,6 @@ const luaL_Reg corelib[] = { {"open_url", lua::wrap}, {"quit", lua::wrap}, {"capture_output", lua::wrap}, + {"set_title", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 6e703104..01bfd0d6 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -173,7 +173,25 @@ static int l_get_line_at(lua::State* L) { auto node = get_document_node(L, 1); auto position = lua::tointeger(L, 2); if (auto box = dynamic_cast(node.node.get())) { - return lua::pushinteger(L, box->getLineAt(position)); + return lua::pushinteger(L, box->getLineAt(position) + 1); + } + return 0; +} + +static int l_get_index_by_pos(lua::State* L) { + auto node = get_document_node(L, 1); + auto position = lua::tovec2(L, 2); + if (auto box = dynamic_cast(node.node.get())) { + return lua::pushinteger(L, box->calcIndexAt(position.x, position.y)); + } + return 0; +} + +static int l_get_line_y(lua::State* L) { + auto node = get_document_node(L, 1); + auto line = lua::tointeger(L, 2); + if (auto box = dynamic_cast(node.node.get())) { + return lua::pushinteger(L, box->getLineYOffset(line - 1)); } return 0; } @@ -509,6 +527,12 @@ static int p_get_focused(UINode* node, lua::State* L) { static int p_get_line_at(UINode*, lua::State* L) { return lua::pushcfunction(L, l_get_line_at); } +static int p_get_index_by_pos(UINode*, lua::State* L) { + return lua::pushcfunction(L, l_get_index_by_pos); +} +static int p_get_line_y(UINode*, lua::State* L) { + return lua::pushcfunction(L, l_get_line_y); +} static int p_get_line_pos(UINode*, lua::State* L) { return lua::pushcfunction(L, l_get_line_pos); } @@ -619,6 +643,8 @@ static int l_gui_getattr(lua::State* L) { {"edited", p_get_edited}, {"lineNumbers", p_get_line_numbers}, {"lineAt", p_get_line_at}, + {"indexByPos", p_get_index_by_pos}, + {"lineY", p_get_line_y}, {"linePos", p_get_line_pos}, {"syntax", p_get_syntax}, {"markup", p_get_markup}, diff --git a/src/logic/scripting/lua/libs/libinput.cpp b/src/logic/scripting/lua/libs/libinput.cpp index eb605671..28f87f3c 100644 --- a/src/logic/scripting/lua/libs/libinput.cpp +++ b/src/logic/scripting/lua/libs/libinput.cpp @@ -50,8 +50,11 @@ static int l_add_callback(lua::State* L) { handler = input.addKeyCallback(key, actual_callback); } } - auto callback = [&gui, actual_callback]() -> bool { - if (!gui.isFocusCaught()) { + + bool isTopLevel = lua::toboolean(L, 4); + + auto callback = [&gui, actual_callback, isTopLevel]() -> bool { + if (isTopLevel || !gui.isFocusCaught()) { return actual_callback(); } return false; diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 580176cd..b1b3a41f 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -410,7 +410,11 @@ static int l_get_total_download(lua::State* L, network::Network& network) { } static int l_find_free_port(lua::State* L, network::Network& network) { - return lua::pushinteger(L, network.findFreePort()); + int port = network.findFreePort(); + if (port == -1) { + return 0; + } + return lua::pushinteger(L, port); } static int l_set_nodelay(lua::State* L, network::Network& network) { diff --git a/src/logic/scripting/lua/lua_extensions.cpp b/src/logic/scripting/lua/lua_extensions.cpp index 27b47fae..a7de3fdd 100644 --- a/src/logic/scripting/lua/lua_extensions.cpp +++ b/src/logic/scripting/lua/lua_extensions.cpp @@ -309,14 +309,15 @@ static int l_debug_sendvalue(lua::State* L) { lua::pushnil(L); while (lua::next(L, 1)) { - auto key = lua::tolstring(L, -2); + lua::pushvalue(L, -2); - int type = lua::type(L, -1); + auto key = lua::tolstring(L, -1); + int type = lua::type(L, -2); table[std::string(key)] = dv::object({ {"type", std::string(lua::type_name(L, type))}, - {"short", get_short_value(L, -1, type)}, + {"short", get_short_value(L, -2, type)}, }); - lua::pop(L); + lua::pop(L, 2); } lua::pop(L); value = std::move(table); diff --git a/src/window/Window.hpp b/src/window/Window.hpp index 71004470..f5a50a73 100644 --- a/src/window/Window.hpp +++ b/src/window/Window.hpp @@ -36,6 +36,9 @@ public: virtual void setMode(WindowMode mode) = 0; virtual WindowMode getMode() const = 0; + virtual void focus() = 0; + + virtual void setTitle(const std::string& title) = 0; virtual void setIcon(const ImageData* image) = 0; virtual void pushScissor(glm::vec4 area) = 0; diff --git a/src/window/detail/GLFWWindow.cpp b/src/window/detail/GLFWWindow.cpp index a80c10e0..07469306 100644 --- a/src/window/detail/GLFWWindow.cpp +++ b/src/window/detail/GLFWWindow.cpp @@ -188,7 +188,7 @@ public: codepoints.clear(); pressedKeys.clear(); if (waitForRefresh) { - glfwWaitEvents(); + glfwWaitEventsTimeout(0.5); } else { glfwPollEvents(); } @@ -468,6 +468,14 @@ public: return mode; } + void focus() override { + glfwFocusWindow(window); + } + + void setTitle(const std::string& title) override { + glfwSetWindowTitle(window, title.c_str()); + } + void setIcon(const ImageData* image) override { if (image == nullptr) { glfwSetWindowIcon(window, 0, nullptr);