From cb9690ebc7c34d4412cac120c3913f9def3f0e03 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 30 Dec 2024 21:41:05 +0300 Subject: [PATCH] add 'cursor' ui property --- doc/en/scripting/ui.md | 1 + doc/en/xml-ui-layouts.md | 1 + doc/ru/scripting/ui.md | 1 + doc/ru/xml-ui-layouts.md | 1 + src/graphics/core/commons.cpp | 39 ++++++++++++++++++++++ src/graphics/core/commons.hpp | 43 ++++++++++++++++++++++++- src/graphics/ui/GUI.cpp | 4 +++ src/graphics/ui/elements/TextBox.cpp | 1 + src/graphics/ui/elements/UINode.cpp | 8 +++++ src/graphics/ui/elements/UINode.hpp | 6 ++++ src/graphics/ui/gui_xml.cpp | 5 +++ src/logic/scripting/lua/libs/libgui.cpp | 12 +++++++ src/window/Window.cpp | 19 +++++++++++ src/window/Window.hpp | 4 +++ 14 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 src/graphics/core/commons.cpp diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index c990a197..5013b448 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -48,6 +48,7 @@ Properties that apply to all elements: | tooltip | string | yes | yes | tooltip text | | tooltipDelay | float | yes | yes | tooltip delay | | contentOffset | vec2 | yes | *no* | element content offset | +| cursor | string | yes | yes | cursor displayed on hover | Common element methods: diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index e7008682..3f0bff07 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -44,6 +44,7 @@ Examples: - `gravity` - automatic positioning of the element in the container. (Does not work in automatic containers like panel). Values: *top-left, top-center, top-right, center-left, center-center, center-right, bottom-left, bottom-center, bottom-right*. - `z-index` - determines the order of elements, with a larger value it will overlap elements with a smaller one. - `interactive` - if false, hovering over the element and all sub-elements will be ignored. +- `cursor` - the cursor displayed when hovering over the element (arrow/text/pointer/crosshair/ew-resize/ns-resize/...). # Template attributes diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 07d93013..3c203d37 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -48,6 +48,7 @@ document["worlds-panel"]:clear() | tooltip | string | да | да | текст всплывающей подсказки | | tooltipDelay | float | да | да | задержка всплывающей подсказки | | contentOffset | vec2 | да | *нет* | смещение содержимого | +| cursor | string | да | да | курсор, отображаемый при наведении | Общие методы элементов: diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index 1ad93c4f..f562ee4c 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -48,6 +48,7 @@ - `gravity` - автоматическое позиционирование элемента в контейнере. (Не работает в автоматических контейнерах, как panel). Значения: *top-left, top-center, top-right, center-left, center-center, center-right, bottom-left, bottom-center, bottom-right*. - `z-index` - определяет порядок элементов, при большем значении будет перекрывать элементы с меньшим. - `interactive` - при значении false наведение на элемент и все под-элементы будет игнорироваться. +- `cursor` - курсор, отображаемый при наведении на элемент (arrow/text/pointer/crosshair/ew-resize/ns-resize/...). # Атрибуты шаблонов diff --git a/src/graphics/core/commons.cpp b/src/graphics/core/commons.cpp new file mode 100644 index 00000000..98cc5c13 --- /dev/null +++ b/src/graphics/core/commons.cpp @@ -0,0 +1,39 @@ +#include "commons.hpp" + +#include + +std::optional CursorShape_from(std::string_view name) { + static std::map shapes = { + {"arrow", CursorShape::ARROW}, + {"text", CursorShape::TEXT}, + {"crosshair", CursorShape::CROSSHAIR}, + {"pointer", CursorShape::POINTER}, + {"ew-resize", CursorShape::EW_RESIZE}, + {"ns-resize", CursorShape::NS_RESIZE}, + {"nwse-resize", CursorShape::NWSE_RESIZE}, + {"nesw-resize", CursorShape::NESW_RESIZE}, + {"all-resize", CursorShape::ALL_RESIZE}, + {"not-allowed", CursorShape::NOT_ALLOWED} + }; + const auto& found = shapes.find(name); + if (found == shapes.end()) { + return std::nullopt; + } + return found->second; +} + +std::string to_string(CursorShape shape) { + static std::string names[] = { + "arrow", + "text", + "crosshair", + "pointer", + "ew-resize", + "ns-resize", + "nwse-resize", + "nesw-resize", + "all-resize", + "not-allowed" + }; + return names[static_cast(shape)]; +} diff --git a/src/graphics/core/commons.hpp b/src/graphics/core/commons.hpp index 4d3f376f..80819206 100644 --- a/src/graphics/core/commons.hpp +++ b/src/graphics/core/commons.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + enum class DrawPrimitive { point = 0, line, @@ -7,9 +10,47 @@ enum class DrawPrimitive { }; enum class BlendMode { - normal, addition, inversion + /// @brief Normal blending mode. + normal, + /// @brief Additive blending mode. + addition, + /// @brief Subtractive blending mode. + inversion }; +/// @brief Standard GLFW 3.4 cursor shapes (same order and count as in GLFW). +/// It also works in GLFW 3.3 (unsupported shapes will be replaced with the arrow). +enum class CursorShape { + /// @brief Regular arrow + ARROW, + /// @brief Text input I-beam + TEXT, + /// @brief Crosshair + CROSSHAIR, + /// @brief Pointing hand + POINTER, + /// @brief Horizontal resize arrow + EW_RESIZE, + /// @brief Vertical resize arrow + NS_RESIZE, + + // GLFW 3.4+ cursor shapes + + /// @brief Diagonal resize arrow (top-left to bottom-right) + NWSE_RESIZE, + /// @brief Diagonal resize arrow (top-right to bottom-left) + NESW_RESIZE, + /// @brief All-direction resize arrow + ALL_RESIZE, + /// @brief Operation not allowed + NOT_ALLOWED, + + LAST=NOT_ALLOWED +}; + +std::optional CursorShape_from(std::string_view name); +std::string to_string(CursorShape shape); + class Flushable { public: virtual ~Flushable() = default; diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 49a362e3..2af75903 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -212,6 +212,10 @@ void GUI::draw(const DrawContext& pctx, const Assets& assets) { batch2D->begin(); container->draw(ctx, assets); + + if (hover) { + Window::setCursor(hover->getCursor()); + } } std::shared_ptr GUI::getFocused() const { diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index fb24c7e8..7d2cb352 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -25,6 +25,7 @@ TextBox::TextBox(std::wstring placeholder, glm::vec4 padding) input(L""), placeholder(std::move(placeholder)) { + setCursor(CursorShape::TEXT); setOnUpPressed(nullptr); setOnDownPressed(nullptr); setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.75f)); diff --git a/src/graphics/ui/elements/UINode.cpp b/src/graphics/ui/elements/UINode.cpp index 0dc305ed..a6fa38c2 100644 --- a/src/graphics/ui/elements/UINode.cpp +++ b/src/graphics/ui/elements/UINode.cpp @@ -150,6 +150,14 @@ float UINode::getTooltipDelay() const { return tooltipDelay; } +void UINode::setCursor(CursorShape shape) { + cursor = shape; +} + +CursorShape UINode::getCursor() const { + return cursor; +} + glm::vec2 UINode::calcPos() const { if (parent) { return pos + parent->calcPos() + parent->getContentOffset(); diff --git a/src/graphics/ui/elements/UINode.hpp b/src/graphics/ui/elements/UINode.hpp index 19f3b8bb..af0ab145 100644 --- a/src/graphics/ui/elements/UINode.hpp +++ b/src/graphics/ui/elements/UINode.hpp @@ -1,6 +1,7 @@ #pragma once #include "delegates.hpp" +#include "graphics/core/commons.hpp" #include "window/input.hpp" #include @@ -112,6 +113,8 @@ namespace gui { std::wstring tooltip; /// @brief element tooltip delay float tooltipDelay = 0.5f; + /// @brief cursor shape when mouse is over the element + CursorShape cursor = CursorShape::ARROW; UINode(glm::vec2 size); public: @@ -206,6 +209,9 @@ namespace gui { virtual void setTooltipDelay(float delay); virtual float getTooltipDelay() const; + virtual void setCursor(CursorShape shape); + virtual CursorShape getCursor() const; + virtual glm::vec4 calcColor() const; /// @brief Get inner content offset. Used for scroll diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index 5ff1b6f2..2d6daa96 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -156,6 +156,11 @@ static void _readUINode( if (element.has("tooltip-delay")) { node.setTooltipDelay(element.attr("tooltip-delay").asFloat()); } + if (element.has("cursor")) { + if (auto cursor = CursorShape_from(element.attr("cursor").getText())) { + node.setCursor(*cursor); + } + } if (auto onclick = create_action(reader, element, "onclick")) { node.listenAction(onclick); diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 97b5a8cc..4328056a 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -401,6 +401,10 @@ static int p_get_line_pos(UINode*, lua::State* L) { return lua::pushcfunction(L, l_get_line_pos); } +static int p_get_cursor(UINode* node, lua::State* L) { + return lua::pushstring(L, to_string(node->getCursor())); +} + static int l_gui_getattr(lua::State* L) { auto docname = lua::require_string(L, 1); auto element = lua::require_string(L, 2); @@ -455,6 +459,7 @@ static int l_gui_getattr(lua::State* L) { {"paste", p_get_paste}, {"inventory", p_get_inventory}, {"focused", p_get_focused}, + {"cursor", p_get_cursor}, }; auto func = getters.find(attr); if (func != getters.end()) { @@ -616,6 +621,12 @@ static void p_set_focused( } } +static void p_set_cursor(UINode* node, lua::State* L, int idx) { + if (auto cursor = CursorShape_from(lua::require_string(L, idx))) { + node->setCursor(*cursor); + } +} + static int l_gui_setattr(lua::State* L) { auto docname = lua::require_string(L, 1); auto element = lua::require_string(L, 2); @@ -658,6 +669,7 @@ static int l_gui_setattr(lua::State* L) { {"checked", p_set_checked}, {"page", p_set_page}, {"inventory", p_set_inventory}, + {"cursor", p_set_cursor}, }; auto func = setters.find(attr); if (func != setters.end()) { diff --git a/src/window/Window.cpp b/src/window/Window.cpp index ffa9a327..90325bd9 100644 --- a/src/window/Window.cpp +++ b/src/window/Window.cpp @@ -29,6 +29,7 @@ int Window::posY = 0; int Window::framerate = -1; double Window::prevSwap = 0.0; bool Window::fullscreen = false; +CursorShape Window::cursor = CursorShape::ARROW; static util::ObjectsKeeper observers_keeper; @@ -125,6 +126,8 @@ void error_callback(int error, const char* description) { } } +static GLFWcursor* standard_cursors[static_cast(CursorShape::LAST) + 1] = {}; + int Window::initialize(DisplaySettings* settings) { Window::settings = settings; Window::width = settings->width.get(); @@ -219,6 +222,10 @@ int Window::initialize(DisplaySettings* settings) { logger.info() << "monitor content scale: " << scale.x << "x" << scale.y; input_util::initialize(); + + for (int i = 0; i <= static_cast(CursorShape::LAST); i++) { + standard_cursors[i] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR + i); + } return 0; } @@ -305,6 +312,9 @@ void Window::popScissor() { void Window::terminate() { observers_keeper = util::ObjectsKeeper(); + for (int i = 0; i <= static_cast(CursorShape::LAST); i++) { + glfwDestroyCursor(standard_cursors[i]); + } glfwTerminate(); } @@ -380,6 +390,15 @@ DisplaySettings* Window::getSettings() { return settings; } +void Window::setCursor(CursorShape shape) { + if (cursor == shape) { + return; + } + cursor = shape; + // NULL cursor is valid for GLFW + glfwSetCursor(window, standard_cursors[static_cast(shape)]); +} + std::unique_ptr Window::takeScreenshot() { auto data = std::make_unique(width * height * 3); glPixelStorei(GL_PACK_ALIGNMENT, 1); diff --git a/src/window/Window.hpp b/src/window/Window.hpp index 78d63ebe..77fd7076 100644 --- a/src/window/Window.hpp +++ b/src/window/Window.hpp @@ -5,6 +5,7 @@ #include #include +#include "graphics/core/commons.hpp" #include "typedefs.hpp" class ImageData; @@ -20,6 +21,7 @@ class Window { static bool fullscreen; static int framerate; static double prevSwap; + static CursorShape cursor; static bool tryToMaximize(GLFWwindow* window, GLFWmonitor* monitor); public: @@ -46,6 +48,8 @@ public: static void popScissor(); static void resetScissor(); + static void setCursor(CursorShape shape); + static void clear(); static void clearDepth(); static void setBgColor(glm::vec3 color);