From 206c80c7ff4f8c985c3df7f958e9fc2e8f5533bc Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Sat, 13 Jun 2026 17:29:12 +0300 Subject: [PATCH 1/6] AbstractScrollArea added AbstractItemView added ListView added --- editor/includes/editor/editorplatform.h | 6 +- editor/src/editor/editorplatform.cpp | 65 +- engine/includes/adapters/desktopadaptor.h | 2 + engine/includes/adapters/mobileadaptor.h | 7 + engine/includes/adapters/platformadaptor.h | 1 + engine/includes/input.h | 1 + engine/src/adapters/desktopadaptor.cpp | 23 + engine/src/adapters/mobileadaptor.cpp | 94 ++- engine/src/adapters/platformadaptor.cpp | 5 + engine/src/commandbuffer.cpp | 20 +- engine/src/input.cpp | 6 + engine/src/resources/font.cpp | 3 +- .../renders/rendergl/src/commandbuffergl.cpp | 3 +- .../renders/rendermt/src/commandbuffermt.cpp | 3 +- .../renders/rendervk/src/commandbuffervk.cpp | 5 +- .../includes/components/abstractitemview.h | 77 +++ .../includes/components/abstractscrollarea.h | 52 ++ modules/uikit/includes/components/canvas.h | 3 + modules/uikit/includes/components/label.h | 6 + modules/uikit/includes/components/lineedit.h | 1 + modules/uikit/includes/components/listview.h | 111 ++++ .../includes/components/listviewdelegate.h | 33 + modules/uikit/includes/components/widget.h | 37 +- .../uikit/src/components/abstractitemview.cpp | 102 +++ .../src/components/abstractscrollarea.cpp | 203 ++++++ .../uikit/src/components/abstractslider.cpp | 2 - modules/uikit/src/components/button.cpp | 4 + modules/uikit/src/components/canvas.cpp | 38 +- modules/uikit/src/components/frame.cpp | 4 +- modules/uikit/src/components/label.cpp | 31 +- modules/uikit/src/components/lineedit.cpp | 6 + modules/uikit/src/components/listview.cpp | 620 ++++++++++++++++++ .../uikit/src/components/listviewdelegate.cpp | 135 ++++ modules/uikit/src/components/menu.cpp | 4 + .../uikit/src/components/recttransform.cpp | 11 +- modules/uikit/src/components/scrollbar.cpp | 44 +- modules/uikit/src/components/widget.cpp | 110 +++- modules/uikit/src/editor/uiedit.cpp | 65 ++ modules/uikit/src/editor/widgetcontroller.cpp | 3 + modules/uikit/src/uikit.cpp | 7 +- modules/uikit/src/uisystem.cpp | 13 + modules/uikit/uikit.qbs | 3 +- thirdparty/next/inc/core/abstractitemmodel.h | 31 + thirdparty/next/inc/core/event.h | 6 + thirdparty/next/inc/core/metatype.h | 2 + thirdparty/next/inc/core/modelindex.h | 59 ++ thirdparty/next/inc/core/variant.h | 3 + .../next/src/core/abstractitemmodel.cpp | 11 + thirdparty/next/src/core/modelindex.cpp | 42 ++ thirdparty/next/src/core/variant.cpp | 7 + 50 files changed, 2023 insertions(+), 107 deletions(-) create mode 100644 modules/uikit/includes/components/abstractitemview.h create mode 100644 modules/uikit/includes/components/abstractscrollarea.h create mode 100644 modules/uikit/includes/components/listview.h create mode 100644 modules/uikit/includes/components/listviewdelegate.h create mode 100644 modules/uikit/src/components/abstractitemview.cpp create mode 100644 modules/uikit/src/components/abstractscrollarea.cpp create mode 100644 modules/uikit/src/components/listview.cpp create mode 100644 modules/uikit/src/components/listviewdelegate.cpp create mode 100644 thirdparty/next/inc/core/abstractitemmodel.h create mode 100644 thirdparty/next/inc/core/modelindex.h create mode 100644 thirdparty/next/src/core/abstractitemmodel.cpp create mode 100644 thirdparty/next/src/core/modelindex.cpp diff --git a/editor/includes/editor/editorplatform.h b/editor/includes/editor/editorplatform.h index fa031ff17..3d46422a0 100644 --- a/editor/includes/editor/editorplatform.h +++ b/editor/includes/editor/editorplatform.h @@ -58,6 +58,7 @@ class EDITOR_EXPORT EditorPlatform : public PlatformAdaptor { bool mouseButton(int) const override; bool mousePressed(int) const override; bool mouseReleased(int) const override; + bool mouseButtonDoubleClick(int) const override; Vector4 mousePosition() const override; void mouseLockCursor(bool lock) override; @@ -75,8 +76,9 @@ class EDITOR_EXPORT EditorPlatform : public PlatformAdaptor { void syncConfiguration(VariantMap &map) const override; protected: - QHash m_keys; - QHash m_mouseButtons; + std::unordered_map m_keys; + std::unordered_map m_mouseButtons; + std::unordered_map m_mouseDoubleClick; QSize m_screenSize; diff --git a/editor/src/editor/editorplatform.cpp b/editor/src/editor/editorplatform.cpp index cb42aa380..cd91511dc 100644 --- a/editor/src/editor/editorplatform.cpp +++ b/editor/src/editor/editorplatform.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -180,21 +181,29 @@ void EditorPlatform::update() { m_inputString.clear(); for(auto &it : m_keys) { - switch(it) { - case RELEASE: it = NONE; break; - case PRESS: it = REPEAT; break; + switch(it.second) { + case RELEASE: it.second = NONE; break; + case PRESS: it.second = REPEAT; break; default: break; } } for(auto &it : m_mouseButtons) { - switch(it) { - case RELEASE: it = NONE; break; - case PRESS: it = REPEAT; break; + switch(it.second) { + case RELEASE: it.second = NONE; break; + case PRESS: it.second = REPEAT; break; default: break; } } + for(auto &it : m_mouseDoubleClick) { + switch(it.second) { + case RELEASE: it.second = NONE; break; + case PRESS: it.second = REPEAT; break; + default: break; + } + } + m_mouseScrollDelta = 0.0f; PlatformAdaptor::update(); @@ -209,11 +218,11 @@ void EditorPlatform::reset() { m_mouseDelta = Vector4(); for(auto &it : m_keys) { - it = NONE; + it.second = NONE; } for(auto &it : m_mouseButtons) { - it = NONE; + it.second = NONE; } } @@ -222,15 +231,18 @@ bool EditorPlatform::isActive() const { } bool EditorPlatform::key(Input::KeyCode code) const { - return (m_keys.value(code) > RELEASE); + auto it = m_keys.find(code); + return (it != m_keys.end() && it->second > RELEASE); } bool EditorPlatform::keyPressed(Input::KeyCode code) const { - return (m_keys.value(code) == PRESS); + auto it = m_keys.find(code); + return (it != m_keys.end() && it->second == PRESS); } bool EditorPlatform::keyReleased(Input::KeyCode code) const { - return (m_keys.value(code) == RELEASE); + auto it = m_keys.find(code); + return (it != m_keys.end() && it->second == RELEASE); } TString EditorPlatform::inputString() const { @@ -238,15 +250,23 @@ TString EditorPlatform::inputString() const { } bool EditorPlatform::mouseButton(int button) const { - return (m_mouseButtons.value(button | 0x10000000) > RELEASE); + auto it = m_mouseButtons.find(button | 0x10000000); + return (it != m_mouseButtons.end() && it->second > RELEASE); } bool EditorPlatform::mousePressed(int button) const { - return (m_mouseButtons.value(button | 0x10000000) == PRESS); + auto it = m_mouseButtons.find(button | 0x10000000); + return (it != m_mouseButtons.end() && it->second == PRESS); } bool EditorPlatform::mouseReleased(int button) const { - return (m_mouseButtons.value(button | 0x10000000) == RELEASE); + auto it = m_mouseButtons.find(button | 0x10000000); + return (it != m_mouseButtons.end() && it->second == RELEASE); +} + +bool EditorPlatform::mouseButtonDoubleClick(int button) const { + auto it = m_mouseDoubleClick.find(button | 0x10000000); + return (it != m_mouseDoubleClick.end() && it->second == PRESS); } uint32_t EditorPlatform::screenWidth() const { return m_screenSize.width(); } @@ -289,6 +309,23 @@ void EditorPlatform::setMouseButtons(int button, int state) { } m_mouseButtons[btn] = state; + + if(state == PRESS) { + static QPoint lastPos; + static qint64 lastTime = 0; + + QPoint currentPos = QPoint(m_mousePosition.x, m_screenSize.height() - m_mousePosition.y); + qint64 currentTime = QDateTime::currentMSecsSinceEpoch(); + + if(lastTime > 0 && (currentTime - lastTime) < 300 && + (currentPos - lastPos).manhattanLength() < 20) { + m_mouseDoubleClick[btn] = PRESS; + lastTime = 0; + } else { + lastPos = currentPos; + lastTime = currentTime; + } + } } void EditorPlatform::setKeys(QKeyEvent *ev, bool release) { diff --git a/engine/includes/adapters/desktopadaptor.h b/engine/includes/adapters/desktopadaptor.h index e4c175efb..b126abc68 100644 --- a/engine/includes/adapters/desktopadaptor.h +++ b/engine/includes/adapters/desktopadaptor.h @@ -37,6 +37,7 @@ class DesktopAdaptor : public PlatformAdaptor { bool mouseButton(int button) const override; bool mousePressed(int button) const override; bool mouseReleased(int button) const override; + bool mouseButtonDoubleClick(int button) const override; void mouseLockCursor(bool lock) override; void mouseSetCursor(Input::CursorShape shape) override; @@ -80,6 +81,7 @@ class DesktopAdaptor : public PlatformAdaptor { static std::unordered_map s_keys; static std::unordered_map s_mouseButtons; + static std::unordered_map s_mouseDoubleClick; static std::unordered_map s_mouseCursors; static Vector4 s_mousePosition; diff --git a/engine/includes/adapters/mobileadaptor.h b/engine/includes/adapters/mobileadaptor.h index 11d79762f..b9a13de40 100644 --- a/engine/includes/adapters/mobileadaptor.h +++ b/engine/includes/adapters/mobileadaptor.h @@ -38,6 +38,7 @@ class MobileAdaptor : public PlatformAdaptor { bool mouseButton(int button) const override; bool mousePressed(int button) const override; bool mouseReleased(int button) const override; + bool mouseButtonDoubleClick(int button) const override; void mouseLockCursor(bool lock) override; @@ -50,6 +51,12 @@ class MobileAdaptor : public PlatformAdaptor { Vector4 joystickThumbs(int) const override; public: + static std::unordered_map s_keys; + static std::unordered_map> s_touches; + static std::unordered_map s_touchDoubleClick; + static uint64_t s_lastTouchTime; + static int32_t s_lastTouchButton; + static Vector4 s_thumbs; static Vector4 s_mousePosition; static Vector4 s_oldMousePosition; diff --git a/engine/includes/adapters/platformadaptor.h b/engine/includes/adapters/platformadaptor.h index ef6a04f24..b58892898 100644 --- a/engine/includes/adapters/platformadaptor.h +++ b/engine/includes/adapters/platformadaptor.h @@ -43,6 +43,7 @@ class ENGINE_EXPORT PlatformAdaptor { virtual bool mouseButton(int code) const; virtual bool mousePressed(int code) const; virtual bool mouseReleased(int code) const; + virtual bool mouseButtonDoubleClick(int code) const; virtual void mouseLockCursor(bool lock); virtual void mouseSetCursor(Input::CursorShape shape); diff --git a/engine/includes/input.h b/engine/includes/input.h index 96b0207ac..dc5c2a005 100644 --- a/engine/includes/input.h +++ b/engine/includes/input.h @@ -193,6 +193,7 @@ class ENGINE_EXPORT Input { static bool isMouseButton(int button); static bool isMouseButtonDown(int button); static bool isMouseButtonUp(int button); + static bool isMouseButtonDoubleClick(int button); static Vector4 mousePosition(); static Vector4 mouseDelta(); diff --git a/engine/src/adapters/desktopadaptor.cpp b/engine/src/adapters/desktopadaptor.cpp index e57b311ae..77886c328 100644 --- a/engine/src/adapters/desktopadaptor.cpp +++ b/engine/src/adapters/desktopadaptor.cpp @@ -49,6 +49,7 @@ TString DesktopAdaptor::s_inputString; std::unordered_map DesktopAdaptor::s_keys; std::unordered_map DesktopAdaptor::s_mouseButtons; +std::unordered_map DesktopAdaptor::s_mouseDoubleClick; std::unordered_map DesktopAdaptor::s_mouseCursors; DesktopAdaptor::DesktopAdaptor() : @@ -90,6 +91,14 @@ void DesktopAdaptor::update() { } } + for(auto &it : s_mouseDoubleClick) { + switch(it.second) { + case RELEASE: it.second = NONE; break; + case PRESS: it.second = REPEAT; break; + default: break; + } + } + s_oldMousePosition = s_mousePosition; s_mouseScrollDelta = 0.0f; @@ -233,6 +242,10 @@ bool DesktopAdaptor::mouseReleased(int button) const { return (s_mouseButtons[button | 0x10000000] == RELEASE); } +bool DesktopAdaptor::mouseButtonDoubleClick(int button) const { + return (s_mouseDoubleClick[button | 0x10000000] == PRESS); +} + void DesktopAdaptor::mouseLockCursor(bool lock) { glfwSetInputMode(m_window, GLFW_CURSOR, lock ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); } @@ -347,6 +360,16 @@ void DesktopAdaptor::charCallback(GLFWwindow *, unsigned int codepoint) { void DesktopAdaptor::buttonCallback(GLFWwindow *, int button, int action, int) { s_mouseButtons[button | 0x10000000] = action; + + if (action == PRESS) { + int key = button | 0x10000000; + auto it = s_mouseDoubleClick.find(key); + if (it != s_mouseDoubleClick.end() && it->second == REPEAT) { + s_mouseDoubleClick[key] = PRESS; + } else { + s_mouseDoubleClick[key] = action; + } + } } void DesktopAdaptor::scrollCallback(GLFWwindow *, double, double yoffset) { diff --git a/engine/src/adapters/mobileadaptor.cpp b/engine/src/adapters/mobileadaptor.cpp index 8c73c6b79..6ec8b8541 100644 --- a/engine/src/adapters/mobileadaptor.cpp +++ b/engine/src/adapters/mobileadaptor.cpp @@ -12,6 +12,7 @@ #include "engine.h" #include "input.h" +#include "timer.h" #ifdef __ANDROID__ #include "handlers/androidfilehandler.h" @@ -68,8 +69,12 @@ class DefaultHandler : public LogHandler { static GLFMDisplay *s_display = nullptr; -static std::unordered_map s_keys; -static std::unordered_map> s_touches; +std::unordered_map MobileAdaptor::s_keys; +std::unordered_map> MobileAdaptor::s_touches; + +uint64_t MobileAdaptor::s_lastTouchTime = 0; +int32_t MobileAdaptor::s_lastTouchButton = -1; +std::unordered_map MobileAdaptor::s_touchDoubleClick; static TString s_inputString; @@ -248,11 +253,26 @@ bool onTouch(GLFMDisplay *, int touch, GLFMTouchPhase phase, double x, double y) default: break; } - s_touches[index] = std::make_pair(state, pos); + MobileAdaptor::s_touches[index] = std::make_pair(state, pos); } else { - auto it = s_touches.find(touch); - if(it != s_touches.end()) { - s_touches.erase(it); + auto it = MobileAdaptor::s_touches.find(touch); + if(it != MobileAdaptor::s_touches.end()) { + MobileAdaptor::s_touches.erase(it); + } + } + + if(phase == GLFMTouchPhaseBegan) { + uint64_t currentTime = Timer::time(); + int key = touch | Input::MOUSE_LEFT; + + if (MobileAdaptor::s_lastTouchButton == touch && + (currentTime - MobileAdaptor::s_lastTouchTime) < 300) { + MobileAdaptor::s_touchDoubleClick[key] = PRESS; + MobileAdaptor::s_lastTouchTime = 0; + MobileAdaptor::s_lastTouchButton = -1; + } else { + MobileAdaptor::s_lastTouchTime = currentTime; + MobileAdaptor::s_lastTouchButton = touch; } } #endif @@ -268,7 +288,7 @@ bool onKey(GLFMDisplay *, GLFMKeyCode keyCode, GLFMKeyAction action, int) { default: break; } - s_keys[keyToInput(keyCode)] = state; + MobileAdaptor::s_keys[keyToInput(keyCode)] = state; return true; } @@ -327,7 +347,7 @@ bool MobileAdaptor::init() { void MobileAdaptor::update() { s_inputString.clear(); - for(auto &it : s_keys) { + for(auto &it : MobileAdaptor::s_keys) { switch(it.second) { case RELEASE: it.second = NONE; break; case PRESS: it.second = REPEAT; break; @@ -335,10 +355,10 @@ void MobileAdaptor::update() { } } - auto it = s_touches.begin(); - while(it != s_touches.end()) { + auto it = MobileAdaptor::s_touches.begin(); + while(it != MobileAdaptor::s_touches.end()) { if(it->second.first == RELEASE) { - it = s_touches.erase(it); + it = MobileAdaptor::s_touches.erase(it); } else { if(it->second.first == PRESS) { it->second.first = REPEAT; @@ -348,6 +368,14 @@ void MobileAdaptor::update() { } } + for(auto &it : s_touchDoubleClick) { + switch(it.second) { + case RELEASE: it.second = NONE; break; + case PRESS: it.second = REPEAT; break; + default: break; + } + } + MobileAdaptor::s_oldMousePosition = MobileAdaptor::s_mousePosition; MobileAdaptor::s_mouseScrollDelta = 0.0f; @@ -386,24 +414,24 @@ uint32_t MobileAdaptor::screenHeight() const { } bool MobileAdaptor::key(Input::KeyCode code) const { - auto it = s_keys.find(code); - if(it != s_keys.end()) { + auto it = MobileAdaptor::s_keys.find(code); + if(it != MobileAdaptor::s_keys.end()) { return it->second == PRESS || it->second == REPEAT; } return false; } bool MobileAdaptor::keyPressed(Input::KeyCode code) const { - auto it = s_keys.find(code); - if(it != s_keys.end()) { + auto it = MobileAdaptor::s_keys.find(code); + if(it != MobileAdaptor::s_keys.end()) { return it->second == PRESS; } return false; } bool MobileAdaptor::keyReleased(Input::KeyCode code) const { - auto it = s_keys.find(code); - if(it != s_keys.end()) { + auto it = MobileAdaptor::s_keys.find(code); + if(it != MobileAdaptor::s_keys.end()) { return it->second == RELEASE; } return false; @@ -432,36 +460,44 @@ float MobileAdaptor::mouseScrollDelta() const { } bool MobileAdaptor::mouseButton(int button) const { - auto it = s_touches.find(button); - if(it != s_touches.end()) { + auto it = MobileAdaptor::s_touches.find(button); + if(it != MobileAdaptor::s_touches.end()) { return it->second.first == PRESS || it->second.first == REPEAT; } return false; } bool MobileAdaptor::mousePressed(int button) const { - auto it = s_touches.find(button); - if(it != s_touches.end()) { + auto it = MobileAdaptor::s_touches.find(button); + if(it != MobileAdaptor::s_touches.end()) { return it->second.first == PRESS; } return false; } bool MobileAdaptor::mouseReleased(int button) const { - auto it = s_touches.find(button); - if(it != s_touches.end()) { + auto it = MobileAdaptor::s_touches.find(button); + if(it != MobileAdaptor::s_touches.end()) { return it->second.first == RELEASE; } return false; } +bool MobileAdaptor::mouseButtonDoubleClick(int button) const { + auto it = s_touchDoubleClick.find(button); + if(it != s_touchDoubleClick.end()) { + return it->second == PRESS; + } + return false; +} + void MobileAdaptor::mouseLockCursor(bool lock) { s_mouseLocked = lock; } uint32_t MobileAdaptor::touchCount() const { int result = 0; - for(auto it : s_touches) { + for(auto it : MobileAdaptor::s_touches) { if(it.second.first != NONE) { result++; } @@ -470,16 +506,16 @@ uint32_t MobileAdaptor::touchCount() const { } uint32_t MobileAdaptor::touchState(int index) const { - auto it = s_touches.find(index | Input::MOUSE_LEFT); - if(it != s_touches.end()) { + auto it = MobileAdaptor::s_touches.find(index | Input::MOUSE_LEFT); + if(it != MobileAdaptor::s_touches.end()) { return it->second.first; } return 0; } Vector4 MobileAdaptor::touchPosition(int index) const { - auto it = s_touches.find(index | Input::MOUSE_LEFT); - if(it != s_touches.end()) { + auto it = MobileAdaptor::s_touches.find(index | Input::MOUSE_LEFT); + if(it != MobileAdaptor::s_touches.end()) { return it->second.second; } return Vector4(); @@ -494,7 +530,7 @@ uint32_t MobileAdaptor::joystickCount() const { } uint32_t MobileAdaptor::joystickButtons(int index) const { - return s_keys[index]; + return MobileAdaptor::s_keys[index]; } Vector4 MobileAdaptor::joystickThumbs(int) const { diff --git a/engine/src/adapters/platformadaptor.cpp b/engine/src/adapters/platformadaptor.cpp index dcf9a46b8..6f94123e8 100644 --- a/engine/src/adapters/platformadaptor.cpp +++ b/engine/src/adapters/platformadaptor.cpp @@ -54,6 +54,11 @@ bool PlatformAdaptor::mouseReleased(int code) const { return false; } +bool PlatformAdaptor::mouseButtonDoubleClick(int code) const { + A_UNUSED(code); + return false; +} + void PlatformAdaptor::mouseLockCursor(bool lock) { A_UNUSED(lock); } diff --git a/engine/src/commandbuffer.cpp b/engine/src/commandbuffer.cpp index 1a7fcff70..7b0a391ee 100644 --- a/engine/src/commandbuffer.cpp +++ b/engine/src/commandbuffer.cpp @@ -4,6 +4,8 @@ #include "components/transform.h" #include "timer.h" +#include + static bool s_Inited = false; /*! @@ -181,7 +183,23 @@ void CommandBuffer::setCameraProperties(Camera *camera) { Parameters \a width and \a height scissor dimensions. */ void CommandBuffer::enableScissor(int32_t x, int32_t y, int32_t width, int32_t height) { - m_scissorStack.push({x, y, width, height}); + ScissorRect rect{x, y, width, height}; + + if(!m_scissorStack.empty()) { + const ScissorRect &parent = m_scissorStack.top(); + + int32_t left = std::max(rect.x, parent.x); + int32_t bottom = std::max(rect.y, parent.y); + int32_t right = std::min(rect.x + rect.width, parent.x + parent.width); + int32_t top = std::min(rect.y + rect.height, parent.y + parent.height); + + rect.x = left; + rect.y = bottom; + rect.width = std::max(0, right - left); + rect.height = std::max(0, top - bottom); + } + + m_scissorStack.push(rect); } /*! Disables scissor testing. diff --git a/engine/src/input.cpp b/engine/src/input.cpp index 4f57f132d..3a01f38d2 100644 --- a/engine/src/input.cpp +++ b/engine/src/input.cpp @@ -129,6 +129,12 @@ bool Input::isMouseButtonDown(int button) { bool Input::isMouseButtonUp(int button) { return s_pPlatform->mouseReleased(button); } +/*! + Returns true in case of the \a button is double clicked; otherwise returns false. +*/ +bool Input::isMouseButtonDoubleClick(int button) { + return s_pPlatform->mouseButtonDoubleClick(button); +} /*! Returns the number of connected joysticks. */ diff --git a/engine/src/resources/font.cpp b/engine/src/resources/font.cpp index 13d4d785a..81edbbdbc 100644 --- a/engine/src/resources/font.cpp +++ b/engine/src/resources/font.cpp @@ -130,8 +130,7 @@ float Font::textWidth(const TString &text, int size, int flags) { float spaceWidth = 0; FT_Error error = FT_Load_Glyph( face, FT_Get_Char_Index( face, ' ' ), FT_LOAD_BITMAP_METRICS_ONLY ); if(!error) { - spaceWidth = (adjustedSize == DF_GLYPH_SIZE) ? (DF_GLYPH_SIZE * 64.0f * size) : 64.0f; - spaceWidth = face->glyph->advance.x / spaceWidth; + spaceWidth = face->glyph->advance.x / 64.0f; } for(uint32_t i = 0; i < length; i++) { diff --git a/modules/renders/rendergl/src/commandbuffergl.cpp b/modules/renders/rendergl/src/commandbuffergl.cpp index f3e87487b..47f05a068 100644 --- a/modules/renders/rendergl/src/commandbuffergl.cpp +++ b/modules/renders/rendergl/src/commandbuffergl.cpp @@ -119,7 +119,8 @@ void CommandBufferGL::enableScissor(int32_t x, int32_t y, int32_t width, int32_t } CommandBuffer::enableScissor(x, y, width, height); - glScissor(x, y, width, height); + ScissorRect rect = m_scissorStack.top(); + glScissor(rect.x, rect.y, rect.width, rect.height); } void CommandBufferGL::disableScissor() { diff --git a/modules/renders/rendermt/src/commandbuffermt.cpp b/modules/renders/rendermt/src/commandbuffermt.cpp index 1c9b06b34..102987b95 100644 --- a/modules/renders/rendermt/src/commandbuffermt.cpp +++ b/modules/renders/rendermt/src/commandbuffermt.cpp @@ -131,7 +131,8 @@ void CommandBufferMt::setViewport(int32_t x, int32_t y, int32_t width, int32_t h void CommandBufferMt::enableScissor(int32_t x, int32_t y, int32_t width, int32_t height) { CommandBuffer::enableScissor(x, y, width, height); if(m_encoder) { - m_encoder->setScissorRect({(uint32_t)x, (uint32_t)y, (uint32_t)width, (uint32_t)height}); + ScissorRect rect = m_scissorStack.top(); + m_encoder->setScissorRect({(uint32_t)rect.x, (uint32_t)rect.y, (uint32_t)rect.width, (uint32_t)rect.height}); } } diff --git a/modules/renders/rendervk/src/commandbuffervk.cpp b/modules/renders/rendervk/src/commandbuffervk.cpp index 0ee7adfef..c5d6d77d1 100644 --- a/modules/renders/rendervk/src/commandbuffervk.cpp +++ b/modules/renders/rendervk/src/commandbuffervk.cpp @@ -180,9 +180,10 @@ void CommandBufferVk::enableScissor(int32_t x, int32_t y, int32_t width, int32_t CommandBuffer::enableScissor(x, y, width, height); + ScissorRect rect = m_scissorStack.top(); VkRect2D scissor = {}; - scissor.offset = { x, y }; - scissor.extent = { (uint32_t)width, (uint32_t)height }; + scissor.offset = { rect.x, rect.y }; + scissor.extent = { (uint32_t)rect.width, (uint32_t)rect.height }; vkCmdSetScissor(m_commandBuffer, 0, 1, &scissor); } diff --git a/modules/uikit/includes/components/abstractitemview.h b/modules/uikit/includes/components/abstractitemview.h new file mode 100644 index 000000000..324db8354 --- /dev/null +++ b/modules/uikit/includes/components/abstractitemview.h @@ -0,0 +1,77 @@ +#ifndef ABSTRACTITEMVIEW_H +#define ABSTRACTITEMVIEW_H + +#include "abstractscrollarea.h" +#include "abstractitemmodel.h" + +class UIKIT_EXPORT AbstractItemView : public AbstractScrollArea { + A_OBJECT(AbstractItemView, AbstractScrollArea, Components/UI) + + A_PROPERTIES( + A_PROPERTYEX(int, selectionMode, AbstractItemView::selectionMode, AbstractItemView::setSelectionMode, "editor=SelectionMode") + ) + A_METHODS( + A_SIGNAL(AbstractItemView::activated), + A_SIGNAL(AbstractItemView::clicked), + A_SIGNAL(AbstractItemView::pressed), + A_SIGNAL(AbstractItemView::selectionChanged) + ) + A_ENUMS( + A_ENUM(SelectionMode, + A_VALUE(NoSelection), + A_VALUE(SingleSelection), + A_VALUE(MultiSelection) + ) + ) + +public: + enum SelectionMode { + NoSelection = 0, + SingleSelection, + MultiSelection + }; + +public: + AbstractItemView(); + ~AbstractItemView(); + + virtual void setModel(AbstractItemModel *model); + AbstractItemModel *model() const; + + void setRootIndex(const ModelIndex &index); + ModelIndex rootIndex() const; + + int selectionMode() const; + void setSelectionMode(int mode); + + std::list selectedIndexes() const; + +public: // signals + void activated(const ModelIndex &index); + + void clicked(const ModelIndex &index); + + void pressed(const ModelIndex &index); + + void selectionChanged(); + +protected: + virtual void selectItem(const ModelIndex &index); + virtual void activateCurrentItem(); + + bool isIndexValid(const ModelIndex &index) const; + void clearSelection(); + +protected: + AbstractItemModel *m_model; + + std::list m_selected; + + ModelIndex m_rootIndex; + ModelIndex m_currentIndex; + + int m_selectionMode; + +}; + +#endif // ABSTRACTITEMVIEW_H diff --git a/modules/uikit/includes/components/abstractscrollarea.h b/modules/uikit/includes/components/abstractscrollarea.h new file mode 100644 index 000000000..6e208f402 --- /dev/null +++ b/modules/uikit/includes/components/abstractscrollarea.h @@ -0,0 +1,52 @@ +#ifndef ABSTRACTSCROLLAREA_H +#define ABSTRACTSCROLLAREA_H + +#include "frame.h" +#include "scrollbar.h" + +class UIKIT_EXPORT AbstractScrollArea : public Frame { + A_OBJECT(AbstractScrollArea, Frame, Components/UI) + + A_PROPERTIES( + A_PROPERTYEX(ScrollBar *, verticalScrollBar, AbstractScrollArea::verticalScrollBar, AbstractScrollArea::setVerticalScrollBar, "editor=Component"), + A_PROPERTYEX(ScrollBar *, horizontalScrollBar, AbstractScrollArea::horizontalScrollBar, AbstractScrollArea::setHorizontalScrollBar, "editor=Component") + ) + A_METHODS( + A_SLOT(AbstractScrollArea::onVScrollChanged), + A_SLOT(AbstractScrollArea::onHScrollChanged) + ) + A_NOENUMS() + +public: + AbstractScrollArea(); + ~AbstractScrollArea(); + + Widget *content() const; + void setContent(Widget *content); + + ScrollBar *verticalScrollBar() const; + void setVerticalScrollBar(ScrollBar *bar); + + ScrollBar *horizontalScrollBar() const; + void setHorizontalScrollBar(ScrollBar *bar); + +protected: + void composeComponent() override; + void boundChanged(const Vector2 &size) override; + + void drawSub() override; + + virtual void onVScrollChanged(int value); + virtual void onHScrollChanged(int value); + + void updateScrollRange(); + +protected: + Widget *m_content; + + ScrollBar *m_vScroll; + ScrollBar *m_hScroll; + +}; + +#endif // ABSTRACTSCROLLAREA_H diff --git a/modules/uikit/includes/components/canvas.h b/modules/uikit/includes/components/canvas.h index 9bb04cf9a..633803472 100644 --- a/modules/uikit/includes/components/canvas.h +++ b/modules/uikit/includes/components/canvas.h @@ -55,6 +55,9 @@ class UIKIT_EXPORT Canvas : public Component { bool m_dirty; + bool m_lastPositionValid; + Vector2 m_lastPosition; + }; #endif // CANVAS_H diff --git a/modules/uikit/includes/components/label.h b/modules/uikit/includes/components/label.h index 97b6b0bef..1e383a7d1 100644 --- a/modules/uikit/includes/components/label.h +++ b/modules/uikit/includes/components/label.h @@ -15,6 +15,7 @@ class UIKIT_EXPORT Label : public Widget { A_PROPERTIES( A_PROPERTY(TString, text, Label::text, Label::setText), A_PROPERTY(bool, translated, Label::translated, Label::setTranslated), + A_PROPERTY(bool, clip, Label::clip, Label::setClip), A_PROPERTYEX(int, alignment, Label::align, Label::setAlign, "editor=Alignment, css=text-align"), A_PROPERTYEX(Font *, font, Label::font, Label::setFont, "editor=Asset"), A_PROPERTYEX(int, fontSize, Label::fontSize, Label::setFontSize, "css=font-size"), @@ -44,6 +45,9 @@ class UIKIT_EXPORT Label : public Widget { bool translated() const; void setTranslated(bool enable); + bool clip() const; + void setClip(bool enable); + bool wordWrap() const; void setWordWrap(bool wrap); @@ -95,6 +99,8 @@ class UIKIT_EXPORT Label : public Widget { bool m_translated; + bool m_clip; + }; #endif // LABEL_H diff --git a/modules/uikit/includes/components/lineedit.h b/modules/uikit/includes/components/lineedit.h index 5f813b7e3..5e7236843 100644 --- a/modules/uikit/includes/components/lineedit.h +++ b/modules/uikit/includes/components/lineedit.h @@ -25,6 +25,7 @@ class UIKIT_EXPORT LineEdit : public Frame { public: LineEdit(); + ~LineEdit(); TString text() const; void setText(const TString &text); diff --git a/modules/uikit/includes/components/listview.h b/modules/uikit/includes/components/listview.h new file mode 100644 index 000000000..ba08a27e2 --- /dev/null +++ b/modules/uikit/includes/components/listview.h @@ -0,0 +1,111 @@ +#ifndef LISTVIEW_H +#define LISTVIEW_H + +#include +#include + +class Frame; +class ListViewDelegate; + +class UIKIT_EXPORT ListView : public AbstractItemView { + A_OBJECT(ListView, AbstractItemView, Components/UI) + + A_PROPERTIES( + A_PROPERTYEX(int, viewMode, ListView::viewMode, ListView::setViewMode, "enum=ViewMode"), + A_PROPERTY(int, rowHeight, ListView::rowHeight, ListView::setRowHeight), + A_PROPERTY(Vector2, gridSize, ListView::gridSize, ListView::setGridSize) + ) + A_NOMETHODS() + A_ENUMS( + A_ENUM(ViewMode, + A_VALUE(ListMode), + A_VALUE(IconMode) + ) + ) + +public: + enum ViewMode { + ListMode = 0, + IconMode = 1 + }; + +public: + ListView(); + ~ListView(); + + void setModel(AbstractItemModel *model) override; + + int viewMode() const; + void setViewMode(int mode); + + int rowHeight() const; + void setRowHeight(int height); + + Vector2 gridSize() const; + void setGridSize(const Vector2 &size); + + ListViewDelegate *delegate() const; + void setDelegate(ListViewDelegate *delegate); + +protected: + bool onMouseDown(int x, int y) override; + bool onMouseUp(int x, int y) override; + bool onMouseMove(int x, int y) override; + bool onMouseDoubleClick(int x, int y) override; + bool onMouseWheel(int delta, bool horizontal) override; + bool onKeyPress(KeyEvent *event) override; + + void update(const Vector2 &pos) override; + + void composeComponent() override; + void boundChanged(const Vector2 &size) override; + void activateCurrentItem() override; + + void onVScrollChanged(int value) override; + +private: + void rebuildItems(); + void updateHighlight(int index); + void handleItemClick(int index); + void handleItemDoubleClick(int index); + + void calculateGridParams(); + + int indexAtPosition(const Vector2 &pos); + Vector2 positionAtIndex(int index); + void scrollToItem(int index); + + Vector2 viewportSize(); + +private: + std::list m_items; + + Vector2 m_gridSize; + + Frame *m_highlightFrame; + + ListViewDelegate *m_delegate; + + int m_rowHeight; + + int m_viewMode; + + int m_highlightIndex; + + int m_firstVisibleIndex; + + int m_cachedColumns; + + int m_cachedRows; + + int m_selectedIndex = -1; + + int m_iconAlignment = Alignment::Left | Alignment::Middle; + + bool m_isPressed = false; + + bool m_dirtyItems; + +}; + +#endif // LISTVIEW_H diff --git a/modules/uikit/includes/components/listviewdelegate.h b/modules/uikit/includes/components/listviewdelegate.h new file mode 100644 index 000000000..fb67ea214 --- /dev/null +++ b/modules/uikit/includes/components/listviewdelegate.h @@ -0,0 +1,33 @@ +#ifndef LISTVIEWDELEGATE_H +#define LISTVIEWDELEGATE_H + +#include +#include +#include + +class ListView; + +class UIKIT_EXPORT ListViewDelegate : public Widget { + A_OBJECT(ListViewDelegate, Widget, Delegates) + +public: + ListViewDelegate(); + ~ListViewDelegate(); + + virtual void bind(ListView *view, const ModelIndex &index); + +protected: + virtual void updateData(ListView *view); + + void composeComponent() override; + +protected: + ModelIndex m_modelIndex; + + Image *m_icon; + + Label *m_label; + +}; + +#endif // LISTVIEWDELEGATE_H diff --git a/modules/uikit/includes/components/widget.h b/modules/uikit/includes/components/widget.h index fc06cd11f..60213f476 100644 --- a/modules/uikit/includes/components/widget.h +++ b/modules/uikit/includes/components/widget.h @@ -10,6 +10,24 @@ class StyleSheet; class Canvas; +class KeyEvent { +public: + KeyEvent(int keyCode, bool isPressed, bool isRepeat = false); + + int keyCode() const; + bool isPressed() const; + bool isRepeat() const; + + bool isShiftPressed() const; + bool isControlPressed() const; + bool isAltPressed() const; + +private: + int m_keyCode; + bool m_isPressed; + bool m_isRepeat; +}; + class UIKIT_EXPORT Widget : public Component { A_OBJECT(Widget, Component, Components/UI) @@ -62,6 +80,17 @@ class UIKIT_EXPORT Widget : public Component { void setEnabled(bool enable) override; + virtual bool onMouseDown(int x, int y) { return false; } + virtual bool onMouseUp(int x, int y) { return false; } + virtual bool onMouseMove(int x, int y) { return false; } + virtual bool onMouseDoubleClick(int x, int y) { return false; } + virtual bool onMouseWheel(int delta, bool horizontal) { return false; } + virtual bool onKeyPress(KeyEvent *event) { return false; } + virtual bool onKeyRelease(KeyEvent *event) { return false; } + + Widget *subWidget(const TString &name) const; + void setSubWidget(Widget *widget); + public: // slots void lower(); @@ -80,13 +109,14 @@ class UIKIT_EXPORT Widget : public Component { void composeComponent() override; + void dispatchKeyEvent(KeyEvent *event); + void dispatchMouseEvent(const Vector2 &pos, Event::Type type, int button); + void dispatchMouseWheelEvent(const Vector2 &pos, int delta, bool horizontal); + float styleLength(const TString &key, float value, bool &pixels); Vector2 styleBlock2Length(const TString &property, const Vector2 &value, bool &pixels); Vector4 styleBlock4Length(const TString &property, const Vector4 &value, bool &pixels); - Widget *subWidget(const TString &name) const; - void setSubWidget(Widget *widget); - static void setFocusWidget(Widget *widget); void updateStyleProperty(const TString &name, const float *v, int32_t size); @@ -104,7 +134,6 @@ class UIKIT_EXPORT Widget : public Component { private: friend class RectTransform; - //friend class UiLoader; friend class Canvas; friend class StyleSheet; diff --git a/modules/uikit/src/components/abstractitemview.cpp b/modules/uikit/src/components/abstractitemview.cpp new file mode 100644 index 000000000..38e1e6a9f --- /dev/null +++ b/modules/uikit/src/components/abstractitemview.cpp @@ -0,0 +1,102 @@ +#include "components/abstractitemview.h" + +#include + +AbstractItemView::AbstractItemView() : + m_model(nullptr), + m_rootIndex(), + m_selectionMode(SingleSelection) { +} + +AbstractItemView::~AbstractItemView() { +} + +void AbstractItemView::setModel(AbstractItemModel *model) { + m_model = model; +} + +AbstractItemModel *AbstractItemView::model() const { + return m_model; +} + +void AbstractItemView::setRootIndex(const ModelIndex &index) { + m_rootIndex = index; +} + +ModelIndex AbstractItemView::rootIndex() const { + return m_rootIndex; +} + +int AbstractItemView::selectionMode() const { + return m_selectionMode; +} + +void AbstractItemView::setSelectionMode(int mode) { + m_selectionMode = mode; +} + +std::list AbstractItemView::selectedIndexes() const { + return m_selected; +} + +void AbstractItemView::selectItem(const ModelIndex &index) { + if(!isIndexValid(index)) { + return; + } + + switch(m_selectionMode) { + case SingleSelection: { + if(!m_selected.empty() && m_selected.front() == index) { + return; + } + m_selected = {index}; + m_currentIndex = index; + selectionChanged(); + } break; + case MultiSelection: { + for(auto it = m_selected.begin(); it != m_selected.end(); ++it) { + if(*it == index) { + m_selected.erase(it); + selectionChanged(); + return; + } + } + m_selected.push_back(index); + m_currentIndex = index; + selectionChanged(); + } break; + default: break; + } +} + +void AbstractItemView::activateCurrentItem() { + if(m_currentIndex.isValid()) { + activated(m_currentIndex); + } +} + +bool AbstractItemView::isIndexValid(const ModelIndex &index) const { + return index.isValid() && index.model() == m_model; +} + +void AbstractItemView::clearSelection() { + m_selected.clear(); + m_currentIndex = ModelIndex(); + selectionChanged(); +} + +void AbstractItemView::activated(const ModelIndex &index) { + emitSignal(_SIGNAL(activated(ModelIndex)), index); +} + +void AbstractItemView::clicked(const ModelIndex &index) { + emitSignal(_SIGNAL(clicked(ModelIndex)), index); +} + +void AbstractItemView::pressed(const ModelIndex &index) { + emitSignal(_SIGNAL(pressed(ModelIndex)), index); +} + +void AbstractItemView::selectionChanged() { + emitSignal(_SIGNAL(selectionChanged())); +} diff --git a/modules/uikit/src/components/abstractscrollarea.cpp b/modules/uikit/src/components/abstractscrollarea.cpp new file mode 100644 index 000000000..d9435e15b --- /dev/null +++ b/modules/uikit/src/components/abstractscrollarea.cpp @@ -0,0 +1,203 @@ +#include "components/abstractscrollarea.h" + +#include +#include +#include +#include + +AbstractScrollArea::AbstractScrollArea() : + m_content(nullptr), + m_vScroll(nullptr), + m_hScroll(nullptr) { + +} + +AbstractScrollArea::~AbstractScrollArea() { + +} + +Widget *AbstractScrollArea::content() const { + return m_content; +} + +void AbstractScrollArea::setContent(Widget *content) { + if(content != m_content) { + m_content = content; + + if(m_content) { + setSubWidget(m_content); + + RectTransform *contentRect = m_content->rectTransform(); + if(contentRect) { + contentRect->setAnchors(Vector2(0, 1), Vector2(0, 1)); + contentRect->setPivot(Vector2(0, 1)); + } + } + } +} + +ScrollBar *AbstractScrollArea::verticalScrollBar() const { + return m_vScroll; +} + +void AbstractScrollArea::setVerticalScrollBar(ScrollBar *bar) { + if(bar != m_vScroll) { + m_vScroll = bar; + if(m_vScroll) { + m_vScroll->setOrientation(Widget::Vertical); + + RectTransform *rect = m_vScroll->rectTransform(); + rect->setAnchors(Vector2(1.0f, 0.0f), Vector2(1.0f, 1.0f)); + rect->setPivot(Vector2(1.0f, 0.5f)); + rect->setSize(Vector2(16.0f, 0.0f)); + rect->setMargin(Vector4(0.0f, 0.0f, 16.0f, 0.0f)); + + connect(m_vScroll, _SIGNAL(valueChanged(int)), this, _SLOT(onVScrollChanged(int))); + + setSubWidget(m_vScroll); + } + } +} + +ScrollBar *AbstractScrollArea::horizontalScrollBar() const { + return m_hScroll; +} + +void AbstractScrollArea::setHorizontalScrollBar(ScrollBar *bar) { + if(bar != m_hScroll) { + m_hScroll = bar; + if(m_hScroll) { + m_hScroll->setOrientation(Widget::Horizontal); + + RectTransform *rect = m_hScroll->rectTransform(); + rect->setAnchors(Vector2(0.0f, 0.0f), Vector2(1.0f, 0.0f)); + rect->setPivot(Vector2(0.5f, 0.0f)); + rect->setSize(Vector2(0.0f, 16.0f)); + rect->setMargin(Vector4(0.0f, 16.0f, 0.0f, 0.0f)); + + connect(m_hScroll, _SIGNAL(valueChanged(int)), this, _SLOT(onHScrollChanged(int))); + + setSubWidget(m_hScroll); + } + } +} + +void AbstractScrollArea::composeComponent() { + Frame::composeComponent(); + + // Create vertical scrollbar + Actor *vActor = Engine::composeActor("vScroll", actor()); + setVerticalScrollBar(vActor->getComponent()); + + // Create horizontal scrollbar + Actor *hActor = Engine::composeActor("hScroll", actor()); + setHorizontalScrollBar(hActor->getComponent()); +} + +void AbstractScrollArea::boundChanged(const Vector2 &size) { + Frame::boundChanged(size); + + updateScrollRange(); +} + +void AbstractScrollArea::drawSub() { + Canvas *canvas = Frame::canvas(); + + if(m_vScroll && m_vScroll->isEnabled()) { + m_vScroll->draw(); + } + if(m_hScroll && m_hScroll->isEnabled()) { + m_hScroll->draw(); + } + canvas->setClipRegion(rectTransform()->clipRegion()); + if(m_content && m_content->isEnabled()) { + m_content->draw(); + } + canvas->disableClip(); +} + +void AbstractScrollArea::onVScrollChanged(int value) { + if(m_content) { + RectTransform *rect = m_content->rectTransform(); + Vector3 v(rect->position()); + rect->setPosition(Vector3(v.x, static_cast(value), v.z)); + repaint(); + } +} + +void AbstractScrollArea::onHScrollChanged(int value) { + if(m_content) { + RectTransform *rect = m_content->rectTransform(); + Vector3 v(rect->position()); + rect->setPosition(Vector3(static_cast(-value), v.y, v.z)); + repaint(); + } +} + +void AbstractScrollArea::updateScrollRange() { + if(m_content) { + RectTransform *crect = m_content->rectTransform(); + Vector2 contentSize(crect->sizeHint()); + + RectTransform *rect = rectTransform(); + Vector2 size(rect->size()); + Vector4 padding(rect->padding()); + + float vScrollWidth = m_vScroll ? m_vScroll->rectTransform()->size().x : 0.0f; + float hScrollHeight = m_hScroll ? m_hScroll->rectTransform()->size().y : 0.0f; + + bool needVScroll = false; + bool needHScroll = false; + for(int i = 0; i < 2; ++i) { + Vector2 available(size); + if(needVScroll) available.x -= vScrollWidth; + if(needHScroll) available.y -= hScrollHeight; + + bool newNeedV = m_vScroll && contentSize.y > available.y; + bool newNeedH = m_hScroll && contentSize.x > available.x; + if(newNeedV == needVScroll && newNeedH == needHScroll) { + break; + } + needVScroll = newNeedV; + needHScroll = newNeedH; + } + + Vector2 available(size); + if(needVScroll) { + available.x -= vScrollWidth; + padding.y = vScrollWidth; + } + if(needHScroll) { + available.y -= hScrollHeight; + padding.z = hScrollHeight; + } + + if(m_vScroll) { + m_vScroll->setEnabled(needVScroll); + m_vScroll->setMaximum(MAX(0, contentSize.y - available.y)); + m_vScroll->setPageStep(available.y); + if(m_vScroll->value() > contentSize.y) { + m_vScroll->setValue(contentSize.y); + } + } + + if(m_hScroll) { + m_hScroll->setEnabled(needHScroll); + m_hScroll->setMaximum(MAX(0, contentSize.x - available.x)); + m_hScroll->setPageStep(available.x); + if(m_hScroll->value() > contentSize.x) { + m_hScroll->setValue(contentSize.x); + } + } + + rect->setPadding(padding); + } else { + if(m_vScroll) { + m_vScroll->setEnabled(false); + } + + if(m_hScroll) { + m_hScroll->setEnabled(false); + } + } +} diff --git a/modules/uikit/src/components/abstractslider.cpp b/modules/uikit/src/components/abstractslider.cpp index 15192b008..07a231f40 100644 --- a/modules/uikit/src/components/abstractslider.cpp +++ b/modules/uikit/src/components/abstractslider.cpp @@ -153,8 +153,6 @@ void AbstractSlider::update(const Vector2 &pos) { int newValue = round(MIX(m_minimum, m_maximum, f)); if(newValue != m_value) { setValue(newValue); - - valueChanged(newValue); } } } diff --git a/modules/uikit/src/components/button.cpp b/modules/uikit/src/components/button.cpp index 21a04fff7..f9902cacd 100644 --- a/modules/uikit/src/components/button.cpp +++ b/modules/uikit/src/components/button.cpp @@ -65,6 +65,10 @@ Button::Button() : } Button::~Button() { + if(m_font) { + m_font->unsubscribe(this); + } + delete m_textMesh; delete m_iconMaterial; diff --git a/modules/uikit/src/components/canvas.cpp b/modules/uikit/src/components/canvas.cpp index 9280f9ebf..a6d63e4c2 100644 --- a/modules/uikit/src/components/canvas.cpp +++ b/modules/uikit/src/components/canvas.cpp @@ -9,6 +9,7 @@ #include #include +#include /*! \class Canvas @@ -26,7 +27,8 @@ Canvas::Canvas() : m_transform(nullptr), m_buffer(nullptr), m_finalMaterial(nullptr), - m_dirty(true) { + m_dirty(true), + m_lastPositionValid(false) { m_texture->setFormat(Texture::RGBA8); m_texture->setFlags(Texture::Render); @@ -64,6 +66,40 @@ void Canvas::update(const Vector2 &position) { Widget *widget = rect->widget(); if(widget) { widget->m_canvas = this; + + Vector2 globalPos(position); + if(!m_lastPositionValid || globalPos != m_lastPosition) { + widget->dispatchMouseEvent(globalPos, Event::MouseMove, 0); + m_lastPosition = globalPos; + m_lastPositionValid = true; + } + + float wheelDelta = Input::mouseScrollDelta(); + if(wheelDelta != 0.0f) { + int delta = static_cast(wheelDelta); + if(delta == 0) { + delta = wheelDelta > 0.0f ? 1 : -1; + } + widget->dispatchMouseWheelEvent(globalPos, delta, false); + } + + for(int key = Input::KEY_SPACE; key <= Input::KEY_MENU; ++key) { + if(Input::isKeyDown((Input::KeyCode)key) || Input::isKeyUp((Input::KeyCode)key)) { + KeyEvent event(key, Input::isKeyDown((Input::KeyCode)key)); + widget->dispatchKeyEvent(&event); + } + } + + if(Input::isMouseButtonDown(Input::MOUSE_LEFT)) { + widget->dispatchMouseEvent(globalPos, Event::MouseDown, Input::MOUSE_LEFT); + } + if(Input::isMouseButtonUp(Input::MOUSE_LEFT)) { + widget->dispatchMouseEvent(globalPos, Event::MouseUp, Input::MOUSE_LEFT); + } + if(Input::isMouseButtonDoubleClick(Input::MOUSE_LEFT)) { + widget->dispatchMouseEvent(globalPos, Event::MouseDoubleClick, Input::MOUSE_LEFT); + } + widget->update(position); } } diff --git a/modules/uikit/src/components/frame.cpp b/modules/uikit/src/components/frame.cpp index 2e93be98f..12d356d76 100644 --- a/modules/uikit/src/components/frame.cpp +++ b/modules/uikit/src/components/frame.cpp @@ -253,10 +253,10 @@ void Frame::boundChanged(const Vector2 &size) { Widget::boundChanged(size); if(m_frameMaterial) { - Vector4 normCorners(m_borderRadius / size.y); + Vector4 normCorners(m_borderRadius / MAX(size.y, 1.0f)); m_frameMaterial->setVector4(gBorderRadius, &normCorners); - Vector4 normBorders(rectTransform()->border() / size.y); + Vector4 normBorders(rectTransform()->border() / MAX(size.y, 1.0f)); m_frameMaterial->setVector4(gBorderWidth, &normBorders); repaint(); } diff --git a/modules/uikit/src/components/label.cpp b/modules/uikit/src/components/label.cpp index 8ba875d41..703f54554 100644 --- a/modules/uikit/src/components/label.cpp +++ b/modules/uikit/src/components/label.cpp @@ -44,7 +44,8 @@ Label::Label() : m_alignment(Alignment::Center | Alignment::Middle), m_flags(Font::Wrap), m_dirty(true), - m_translated(false) { + m_translated(false), + m_clip(false) { m_mesh->makeDynamic(); @@ -66,6 +67,13 @@ Label::~Label() { \internal */ void Label::draw() { + Canvas *canvas = Label::canvas(); + RectTransform *rect = rectTransform(); + + if(m_clip && canvas && rect) { + canvas->setClipRegion(rect->clipRegion()); + } + if(m_material && !m_text.isEmpty()) { if(m_dirty && m_font) { m_mesh->setName(actor()->name()); @@ -77,7 +85,6 @@ void Label::draw() { m_dirty = false; } - RectTransform *rect = rectTransform(); Matrix4 mat(rect->worldTransform()); mat[12] += rect->padding().w; @@ -88,11 +95,14 @@ void Label::draw() { m_material->setTransform(mat, 0, hash); - Canvas *canvas = Label::canvas(); canvas->drawMesh(m_mesh, m_material); } Widget::draw(); + + if(m_clip && canvas && rect) { + canvas->disableClip(); + } } /*! \internal @@ -221,6 +231,21 @@ void Label::setTranslated(bool enable) { repaint(); } } +/*! + Returns true if clip mode is enabled; otherwise returns false. +*/ +bool Label::clip() const { + return m_clip; +} +/*! + Sets \a enable or disable clipping of contents to the label bounds. +*/ +void Label::setClip(bool enable) { + if(m_clip != enable) { + m_clip = enable; + repaint(); + } +} /*! Returns true if word wrap enabled; otherwise returns false. */ diff --git a/modules/uikit/src/components/lineedit.cpp b/modules/uikit/src/components/lineedit.cpp index 8540c7efd..d2adae454 100644 --- a/modules/uikit/src/components/lineedit.cpp +++ b/modules/uikit/src/components/lineedit.cpp @@ -66,6 +66,12 @@ LineEdit::LineEdit() : m_cursorTransform.scale(Vector3(2.0f, 16.0f, 1.0f)); } + +LineEdit::~LineEdit() { + if(m_font) { + m_font->unsubscribe(this); + } +} /*! \internal */ diff --git a/modules/uikit/src/components/listview.cpp b/modules/uikit/src/components/listview.cpp new file mode 100644 index 000000000..8de33c40d --- /dev/null +++ b/modules/uikit/src/components/listview.cpp @@ -0,0 +1,620 @@ +#include "components/listview.h" + +#include "components/recttransform.h" +#include "components/scrollbar.h" +#include "components/frame.h" +#include "components/listviewdelegate.h" + +#include +#include +#include + +ListView::ListView() : + m_gridSize(80, 100), + m_highlightFrame(nullptr), + m_delegate(nullptr), + m_rowHeight(20), + m_viewMode(ListMode), + m_highlightIndex(-1), + m_firstVisibleIndex(0), + m_cachedColumns(1), + m_cachedRows(1), + m_selectedIndex(-1), + m_isPressed(false) { + +} + +ListView::~ListView() { + +} + +void ListView::setModel(AbstractItemModel *model) { + AbstractItemView::setModel(model); + m_selectedIndex = -1; + m_dirtyItems = true; +} + +int ListView::viewMode() const { + return m_viewMode; +} + +void ListView::setViewMode(int mode) { + if (m_viewMode == mode) { + return; + } + + m_viewMode = mode; + m_dirtyItems = true; + calculateGridParams(); + updateHighlight(m_selectedIndex >= 0 ? m_selectedIndex : m_highlightIndex); + repaint(); +} + +int ListView::rowHeight() const { + return m_rowHeight; +} + +void ListView::setRowHeight(int height) { + m_rowHeight = height; + if(m_vScroll) { + m_vScroll->setSingleStep((m_viewMode == ListMode) ? m_rowHeight : m_gridSize.y); + } + if(m_hScroll) { + m_hScroll->setSingleStep((m_viewMode == ListMode) ? m_rowHeight : m_gridSize.x); + } + m_dirtyItems = true; +} + +Vector2 ListView::gridSize() const { + return m_gridSize; +} + +void ListView::setGridSize(const Vector2 &size) { + m_gridSize = size; + if(m_vScroll) { + m_vScroll->setSingleStep((m_viewMode == ListMode) ? m_rowHeight : m_gridSize.y); + } + if(m_hScroll) { + m_hScroll->setSingleStep((m_viewMode == ListMode) ? m_rowHeight : m_gridSize.x); + } + m_dirtyItems = true; +} + +ListViewDelegate *ListView::delegate() const { + return m_delegate; +} + +void ListView::setDelegate(ListViewDelegate *delegate) { + if(m_delegate) { + delete m_delegate; + } + + m_delegate = delegate; + m_dirtyItems = true; +} + +void ListView::composeComponent() { + AbstractItemView::composeComponent(); + + Actor *widgetActor = Engine::composeActor("content", actor()); + setContent(widgetActor->getComponent()); + + Actor *hlActor = Engine::composeActor("highlight", m_content->actor()); + m_highlightFrame = hlActor->getComponent(); + if(m_highlightFrame) { + RectTransform *rect = m_highlightFrame->rectTransform(); + if(rect) { + rect->setAnchors(Vector2(0.0f, 1.0f), Vector2(0.0f, 1.0f)); + rect->setPivot(Vector2(0.0f, 1.0f)); + rect->setSize(Vector2(0.0f, m_rowHeight)); + } + m_highlightFrame->setBackgroundColor(Vector4(0.0f, 0.0f, 0.0f, 0.5f)); + m_highlightFrame->setEnabled(false); + setSubWidget(m_highlightFrame); + } + + Actor *delegateActor = Engine::composeActor("delegate"); + ListViewDelegate *delegate = delegateActor->getComponent(); + if(delegate) { + setDelegate(delegate); + } + + setRowHeight(m_rowHeight); +} + +bool ListView::onMouseDown(int x, int y) { + if(!m_model || m_model->rowCount() == 0) { + return false; + } + + int index = indexAtPosition(Vector2(x, y)); + if(index >= 0 && index < m_model->rowCount()) { + m_isPressed = true; + handleItemClick(index); + return true; + } + + return false; +} + +bool ListView::onMouseUp(int x, int y) { + if(!m_isPressed) { + return false; + } + + m_isPressed = false; + + int index = indexAtPosition(Vector2(x, y)); + if(index >= 0 && index < m_model->rowCount()) { + ModelIndex idx = m_model->index(index, 0); + if(idx.isValid()) { + clicked(idx); + return true; + } + } + + return false; +} + +bool ListView::onMouseMove(int x, int y) { + int newHighlight = indexAtPosition(Vector2(x, y)); + if(newHighlight != m_highlightIndex) { + updateHighlight(newHighlight); + repaint(); + } + + return true; +} + +bool ListView::onMouseDoubleClick(int x, int y) { + if(!m_model || m_model->rowCount() == 0) { + return false; + } + + int index = indexAtPosition(Vector2(x, y)); + if(index >= 0 && index < m_model->rowCount()) { + handleItemDoubleClick(index); + return true; + } + + return false; +} + +bool ListView::onMouseWheel(int delta, bool horizontal) { + ScrollBar *vbar = verticalScrollBar(); + if(!vbar) { + return false; + } + + if(m_viewMode == IconMode && horizontal) { + ScrollBar *hbar = horizontalScrollBar(); + if(hbar) { + int newValue = hbar->value() - delta; + hbar->setValue(std::max(hbar->minimum(), std::min(hbar->maximum(), newValue))); + return true; + } + return false; + } + + int step = (m_viewMode == ListMode) ? m_rowHeight : m_gridSize.y; + int newValue = vbar->value() - static_cast(delta * step); + vbar->setValue(std::max(vbar->minimum(), std::min(vbar->maximum(), newValue))); + return true; +} + +bool ListView::onKeyPress(KeyEvent *event) { + if(!m_model || m_model->rowCount() == 0) { + return false; + } + + int totalItems = m_model->rowCount(); + int columns = (m_viewMode == IconMode) ? m_cachedColumns : 1; + int current = m_selectedIndex; + if (current < 0 && totalItems > 0) { + current = 0; + } + if (current < 0 || current >= totalItems) { + return false; + } + + int newIndex = -1; + bool needScroll = false; + + switch(event->keyCode()) { + case Input::KEY_UP: { + if (current >= columns) { + newIndex = current - columns; + needScroll = true; + } + } break; + case Input::KEY_DOWN: { + if(current + columns < totalItems) { + newIndex = current + columns; + needScroll = true; + } + } break; + case Input::KEY_LEFT: { + if(current > 0 && (current % columns) > 0) { + newIndex = current - 1; + needScroll = true; + } + } break; + case Input::KEY_RIGHT: { + if(current < totalItems - 1 && (current % columns) < columns - 1) { + newIndex = current + 1; + needScroll = true; + } + } break; + case Input::KEY_HOME: { + if(totalItems > 0) { + newIndex = 0; + needScroll = true; + } + } break; + case Input::KEY_END: { + if(totalItems > 0) { + newIndex = totalItems - 1; + needScroll = true; + } + } break; + case Input::KEY_PAGE_UP: { + int step = (m_viewMode == ListMode) ? m_rowHeight : m_gridSize.y; + ScrollBar* vbar = verticalScrollBar(); + if(vbar) { + int newValue = vbar->value() - step * columns; + vbar->setValue(std::max(vbar->minimum(), std::min(vbar->maximum(), newValue))); + return true; + } + } break; + case Input::KEY_PAGE_DOWN: { + int step = (m_viewMode == ListMode) ? m_rowHeight : m_gridSize.y; + ScrollBar* vbar = verticalScrollBar(); + if(vbar) { + int newValue = vbar->value() + step * columns; + vbar->setValue(std::max(vbar->minimum(), std::min(vbar->maximum(), newValue))); + return true; + } + break; + } + case Input::KEY_ENTER: { + activateCurrentItem(); + return true; + } + case Input::KEY_ESCAPE: { + m_selectedIndex = -1; + m_highlightIndex = -1; + updateHighlight(m_highlightIndex); + repaint(); + return true; + } + default: break; + } + + if(newIndex >= 0 && newIndex < totalItems) { + m_selectedIndex = newIndex; + m_highlightIndex = newIndex; + updateHighlight(m_highlightIndex); + repaint(); + + ModelIndex idx = m_model->index(newIndex, 0); + if(idx.isValid()) { + selectItem(idx); + } + + if(needScroll) { + scrollToItem(newIndex); + } + + return true; + } + + return false; +} + +void ListView::update(const Vector2 &pos) { + AbstractItemView::update(pos); + + if(m_dirtyItems) { + rebuildItems(); + m_dirtyItems = false; + } + + if(isHovered(pos)) { + float delta = Input::mouseScrollDelta(); + ScrollBar *vbar = verticalScrollBar(); + if(delta != 0.0f && vbar) { + int step = (m_viewMode == ListMode) ? m_rowHeight : (m_gridSize.y > 0 ? static_cast(m_gridSize.y) : m_rowHeight); + int nv = vbar->value() - static_cast(delta * step); + nv = MAX(vbar->minimum(), MIN(vbar->maximum(), nv)); + vbar->setValue(nv); + } + } +} + +Vector2 ListView::viewportSize() { + return rectTransform()->size(); +} + +void ListView::calculateGridParams() { + if(m_viewMode != IconMode || !m_model) { + return; + } + + if(m_gridSize.x <= 0 || m_gridSize.y <= 0) { + m_cachedColumns = 1; + m_cachedRows = 1; + return; + } + + int totalItems = m_model->rowCount(); + if(totalItems <= 0) { + m_cachedColumns = 1; + m_cachedRows = 1; + return; + } + + Vector2 viewportSize(ListView::viewportSize()); + m_cachedColumns = std::max(1, static_cast(viewportSize.x / m_gridSize.x)); + m_cachedColumns = std::max(1, std::min(m_cachedColumns, totalItems)); + m_cachedRows = std::max(1, (totalItems + m_cachedColumns - 1) / m_cachedColumns); +} + +void ListView::boundChanged(const Vector2 &size) { + AbstractItemView::boundChanged(size); + + m_dirtyItems = true; +} + +void ListView::onVScrollChanged(int value) { + AbstractItemView::onVScrollChanged(value); + + RectTransform *rect = m_content->rectTransform(); + if(rect) { + Vector3 pos(rect->position()); + int visibleIndex = m_firstVisibleIndex; + + if(m_viewMode == ListMode) { + int visibleStep = m_rowHeight > 0 ? m_rowHeight : 1; + visibleIndex = int(pos.y / visibleStep); + } else { + calculateGridParams(); + if(m_cachedColumns > 0) { + float scrollOffset = pos.y; + int row = static_cast(scrollOffset / m_gridSize.y); + visibleIndex = std::max(0, row * m_cachedColumns); + } + } + + if(visibleIndex != m_firstVisibleIndex) { + if(m_firstVisibleIndex > visibleIndex) { + m_items.splice(m_items.begin(), m_items, std::prev(m_items.end())); + } else { + m_items.splice(m_items.end(), m_items, m_items.begin()); + } + m_firstVisibleIndex = visibleIndex; + m_dirtyItems = true; + } + } +} + +void ListView::activateCurrentItem() { + if(m_selectedIndex < 0 || m_selectedIndex >= m_model->rowCount()) { + return; + } + + ModelIndex idx = m_model->index(m_selectedIndex, 0); + if(idx.isValid()) { + AbstractItemView::activateCurrentItem(); + } +} + +void ListView::rebuildItems() { + if(m_model && m_content) { + int totalItemCount = 0; + Vector2 viewportSize(ListView::viewportSize()); + if(m_viewMode == ListMode) { + totalItemCount = viewportSize.y / m_rowHeight + 1; + } else { + totalItemCount = viewportSize.x / m_gridSize.x; + totalItemCount *= int(viewportSize.y / m_gridSize.y) + 1; + } + + if(totalItemCount > m_items.size()) { + m_items.resize(totalItemCount); + } + + for(auto it = m_items.begin(); it != m_items.end(); ++it) { + if(*it) { + (*it)->setEnabled(false); + } + } + + float maxWidth = 0; + int rowCount = MIN(m_model->rowCount(), m_firstVisibleIndex + totalItemCount); + auto it = m_items.begin(); + for(int i = m_firstVisibleIndex; i < rowCount; ++i) { + ListViewDelegate *delegate = *it; + if(delegate == nullptr && m_delegate) { + Actor *itemActor = static_cast(m_delegate->actor()->clone(m_content->actor())); + itemActor->setName(itemActor->name() + TString::number(i)); + delegate = itemActor->getComponent(); + RectTransform *rect = delegate->rectTransform(); + rect->setPivot(Vector2(0.0f, 1.0f)); + if(m_viewMode == ListMode) { + rect->setAnchors(Vector2(0.0f, 1.0f), Vector2(0.0f, 1.0f)); + rect->setSize(Vector2(0.0f, m_rowHeight)); + } else { + rect->setAnchors(Vector2(0.0f, 1.0f), Vector2(0.0f, 1.0f)); + rect->setSize(m_gridSize); + } + rect->setParentTransform(m_content->rectTransform(), true); // required to update child widgets + + *it = delegate; + m_content->setSubWidget(delegate); + } + + if(delegate) { + delegate->setEnabled(true); + delegate->bind(this, m_model->index(i, 0)); + + RectTransform *rect = delegate->rectTransform(); + rect->setPosition(Vector3(positionAtIndex(i), 0.0f)); + + maxWidth = MAX(maxWidth, rect->size().x); + } + + ++it; + } + + RectTransform *contentRect = m_content->rectTransform(); + if(contentRect) { + float totalHeight; + if(m_viewMode == ListMode) { + totalHeight = m_model->rowCount() * m_rowHeight; + } else { + calculateGridParams(); + totalHeight = m_cachedRows * m_gridSize.y; + } + contentRect->setSize(Vector2(std::max(maxWidth, viewportSize.x), totalHeight)); + } + updateHighlight(m_highlightIndex); + + updateScrollRange(); + } +} + +void ListView::updateHighlight(int index) { + if(!m_highlightFrame || !m_model) { + return; + } + + m_highlightIndex = index; + + if(index >= 0 && index < m_model->rowCount()) { + RectTransform *rect = m_highlightFrame->rectTransform(); + if(rect) { + rect->setPosition(Vector3(positionAtIndex(index), 0.0f)); + + if (m_viewMode == ListMode) { + rect->setAnchors(Vector2(0.0f, 1.0f), Vector2(0.0f, 1.0f)); + rect->setPivot(Vector2(0.0f, 1.0f)); + rect->setSize(Vector2(viewportSize().x, static_cast(m_rowHeight))); + } else { + rect->setAnchors(Vector2(0.0f, 1.0f), Vector2(0.0f, 1.0f)); + rect->setPivot(Vector2(0.0f, 1.0f)); + rect->setSize(m_gridSize); + } + } + m_highlightFrame->setEnabled(true); + } else { + m_highlightFrame->setEnabled(false); + } +} + +void ListView::handleItemDoubleClick(int index) { + if(index < 0 || index >= m_model->rowCount()) { + return; + } + + ModelIndex idx = m_model->index(index, 0); + if(!idx.isValid()) { + return; + } + + handleItemClick(index); + activateCurrentItem(); +} + +void ListView::handleItemClick(int index) { + if(index < 0 || index >= m_model->rowCount()) { + return; + } + + ModelIndex idx = m_model->index(index, 0); + if(!idx.isValid()) { + return; + } + + m_selectedIndex = index; + m_highlightIndex = index; + updateHighlight(m_highlightIndex); + repaint(); + + selectItem(idx); + pressed(idx); +} + +int ListView::indexAtPosition(const Vector2 &pos) { + if(!m_model || m_model->rowCount() == 0) { + return -1; + } + + Vector2 localPos(pos); + if(m_content) { + RectTransform *contentRect = m_content->rectTransform(); + localPos += contentRect->position(); + } + + if(localPos.x < 0 || localPos.y < 0) { + return -1; + } + + int index = -1; + if(m_viewMode == ListMode) { + index = static_cast(localPos.y / m_rowHeight); + } else { + calculateGridParams(); + + int col = static_cast(localPos.x / m_gridSize.x); + int row = static_cast(localPos.y / m_gridSize.y); + + if(col >= 0 && col < m_cachedColumns && row >= 0) { + index = row * m_cachedColumns + col; + } + } + + if(index >= m_firstVisibleIndex && index < m_model->rowCount()) { + return index; + } + + return -1; +} + +Vector2 ListView::positionAtIndex(int index) { + if(m_viewMode == ListMode) { + return Vector2(0.0f, index * -m_rowHeight); + } else { + calculateGridParams(); + + int row = index / m_cachedColumns; + int col = index % m_cachedColumns; + + return Vector2(col * m_gridSize.x, row * -m_gridSize.y); + } +} + +void ListView::scrollToItem(int index) { + if(index < 0 || index >= m_model->rowCount()) { + return; + } + + Vector2 pos = positionAtIndex(index); + float targetY = -pos.y; + + ScrollBar *vbar = verticalScrollBar(); + if(!vbar) { + return; + } + + float viewportHeight = viewportSize().y; + float itemHeight = (m_viewMode == ListMode) ? m_rowHeight : m_gridSize.y; + float scrollPos = targetY - viewportHeight / 2 + itemHeight / 2; + + RectTransform *contentRect = m_content->rectTransform(); + if (contentRect) { + float maxScroll = contentRect->size().y - viewportHeight; + scrollPos = std::max(0.0f, std::min(scrollPos, maxScroll)); + } + + vbar->setValue(static_cast(scrollPos)); +} diff --git a/modules/uikit/src/components/listviewdelegate.cpp b/modules/uikit/src/components/listviewdelegate.cpp new file mode 100644 index 000000000..2a1de8821 --- /dev/null +++ b/modules/uikit/src/components/listviewdelegate.cpp @@ -0,0 +1,135 @@ +#include "components/listviewdelegate.h" + +#include "image.h" +#include "label.h" +#include "listview.h" +#include "recttransform.h" + +#include +#include + +ListViewDelegate::ListViewDelegate() : + m_icon(nullptr), + m_label(nullptr) { +} + +ListViewDelegate::~ListViewDelegate() { + +} + +void ListViewDelegate::bind(ListView *view, const ModelIndex &index) { + m_modelIndex = index; + + updateData(view); +} + +void ListViewDelegate::updateData(ListView *view) { + if(!m_modelIndex.isValid()) { + return; + } + + bool showIcon = false; + if(m_icon == nullptr) { + m_icon = actor()->findChild(); + } + + bool iconMode = view && view->viewMode() == ListView::IconMode; + float cellWidth = iconMode ? (view ? view->gridSize().x : 0.0f) : (view ? static_cast(view->rowHeight()) : 0.0f); + float cellHeight = iconMode ? (view ? view->gridSize().y : 0.0f) : (view ? static_cast(view->rowHeight()) : 0.0f); + float iconHeight = iconMode ? (cellHeight > 0.0f ? std::min(cellHeight, 80.0f) : 80.0f) : (cellHeight > 0.0f ? cellHeight : 16.0f); + Vector2 iconSize(cellWidth > 0.0f ? cellWidth : 32.0f); + + if(m_icon) { + Variant value = m_modelIndex.model()->data(m_modelIndex, AbstractItemModel::DecorationRole); + if(value.isValid()) { + Sprite *icon = value.value(); + if(icon) { + showIcon = true; + m_icon->setSprite(icon); + m_icon->setEnabled(showIcon); + + RectTransform *rect = m_icon->rectTransform(); + if(rect) { + if(iconMode) { + rect->setAnchors(Vector2(0.5f, 1.0f), Vector2(0.5f, 1.0f)); + rect->setPivot(Vector2(0.5f, 1.0f)); + rect->setPosition(Vector3(0.0f, -2.0f, 0.0f)); + } else { + rect->setAnchors(Vector2(0.0f, 0.5f), Vector2(0.0f, 0.5f)); + rect->setPivot(Vector2(0.0f, 0.5f)); + rect->setPosition(Vector3(2.0f, 0.0f, 0.0f)); + } + rect->setSize(iconSize); + } + } + } + } + + if(m_icon) { + m_icon->setEnabled(showIcon); + } + + if(m_label == nullptr) { + m_label = actor()->findChild