From a8c8e1770c65d38d6db9b578bb18af54fca03dc7 Mon Sep 17 00:00:00 2001 From: sebastian Date: Sun, 15 Feb 2026 16:16:00 +0100 Subject: [PATCH] ADD: ClickableUiComponent --- CMakeLists.txt | 6 ++ src/engine/core/Application.cpp | 3 +- .../gui/uiComponent/ClickableUiComponent.cpp | 64 +++++++++++++++++++ .../gui/uiComponent/ClickableUiComponent.h | 62 ++++++++++++++++++ src/engine/core/gui/uiComponent/EventData.cpp | 5 ++ src/engine/core/gui/uiComponent/EventData.h | 39 +++++++++++ src/engine/core/gui/uiComponent/UiButton.cpp | 24 ++----- src/engine/core/gui/uiComponent/UiButton.h | 17 +---- .../core/gui/uiComponent/UiComponent.cpp | 16 ----- src/engine/core/gui/uiComponent/UiComponent.h | 3 - .../core/inputsOutputs/inputs/Mouse.cpp | 10 ++- src/engine/core/inputsOutputs/inputs/Mouse.h | 2 + .../stateControl/inputUser/UiInputUser.cpp | 5 ++ .../stateControl/inputUser/UiInputUser.h | 14 ++++ src/game/UILayer.cpp | 23 ++++++- src/game/UILayer.h | 2 + 16 files changed, 236 insertions(+), 59 deletions(-) create mode 100644 src/engine/core/gui/uiComponent/ClickableUiComponent.cpp create mode 100644 src/engine/core/gui/uiComponent/ClickableUiComponent.h create mode 100644 src/engine/core/gui/uiComponent/EventData.cpp create mode 100644 src/engine/core/gui/uiComponent/EventData.h create mode 100644 src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.cpp create mode 100644 src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a36a823..41b0b95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,6 +236,12 @@ add_executable(Dicewars_Siedler src/main.cpp src/engine/core/inputsOutputs/stateControl/inputUser/GameInputUser.h src/engine/core/inputsOutputs/stateControl/StateRegistry.cpp src/engine/core/inputsOutputs/stateControl/StateRegistry.h + src/engine/core/gui/uiComponent/ClickableUiComponent.cpp + src/engine/core/gui/uiComponent/ClickableUiComponent.h + src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.cpp + src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.h + src/engine/core/gui/uiComponent/EventData.cpp + src/engine/core/gui/uiComponent/EventData.h ) target_compile_options(Dicewars_Siedler PRIVATE diff --git a/src/engine/core/Application.cpp b/src/engine/core/Application.cpp index 2558ff8..d74e335 100644 --- a/src/engine/core/Application.cpp +++ b/src/engine/core/Application.cpp @@ -50,12 +50,13 @@ Application::~Application() void Application::run() { while (!window->shouldClose()) { + window->OnUpdate(); updateTime(); for (Layer* layer : layers) { layer->onUpdate(); } - window->OnUpdate(); + InputManager::update(); mouse->update(); diff --git a/src/engine/core/gui/uiComponent/ClickableUiComponent.cpp b/src/engine/core/gui/uiComponent/ClickableUiComponent.cpp new file mode 100644 index 0000000..57c8833 --- /dev/null +++ b/src/engine/core/gui/uiComponent/ClickableUiComponent.cpp @@ -0,0 +1,64 @@ +// +// Created by sebastian on 15.02.26. +// + +#include "ClickableUiComponent.h" + +#include "../../Application.h" + +void ClickableUiComponent::checkMouseOver() { + glm::vec2 mousepos = Application::getInstance().mouse->getXY(); + bool mouseOverStatus = UiComponent::isMouseOver(mousepos.x, mousepos.y); + if (this->mousedOver != mouseOverStatus) { + mouseOver(mouseOverStatus); + } +} + +void ClickableUiComponent::checkClicks() { + checkMouseButton(MouseButton::LEFT, *Application::getInstance().mouse); + checkMouseButton(MouseButton::RIGHT, *Application::getInstance().mouse); + checkMouseButton(MouseButton::MIDDLE, *Application::getInstance().mouse); +} + +void ClickableUiComponent::checkMouseButton(MouseButton button, Mouse &mouse) { + if (Application::getInstance().mouse->isClickEvent(button)) { + click(button, true); + } else if (Application::getInstance().mouse->isReleaseEvent(button)) { + click(button, false); + } +} + +void ClickableUiComponent::onUpdate(float dt) { + if (blocked) return; + + checkMouseOver(); + if (mousedOver) { + checkClicks(); + } else { + applyCurrentStyle(UiEventType::NONE); + } +} + +void ClickableUiComponent::mouseOver(bool newMouseOverState) { + this->mousedOver = newMouseOverState; + // Todo: When complex animation system is integrated, triggering of animation starts here + applyCurrentStyle(UiEventType::MOUSE_OVER); + MouseEventData mouse_event_data = MouseEventData(std::nullopt, mousedOver); + fireEvent(mouse_event_data); +} + +void ClickableUiComponent::click(MouseButton button, bool buttonDown) { + if (buttonDown) { + applyCurrentStyle(UiEventType::MOUSE_CLICK); + } else { + applyCurrentStyle(UiEventType::NONE); + } + fireEvent(MouseEventData(button, buttonDown)); +} + +void ClickableUiComponent::applyCurrentStyle(UiEventType eventType) { + if (visualStyles.contains(eventType)) { + currentVisualStyle = visualStyles.at(eventType); + } + +} diff --git a/src/engine/core/gui/uiComponent/ClickableUiComponent.h b/src/engine/core/gui/uiComponent/ClickableUiComponent.h new file mode 100644 index 0000000..c3b72e7 --- /dev/null +++ b/src/engine/core/gui/uiComponent/ClickableUiComponent.h @@ -0,0 +1,62 @@ +// +// Created by sebastian on 15.02.26. +// + +#ifndef DICEWARS_SIEDLER_CLICKABLEUICOMPONENT_H +#define DICEWARS_SIEDLER_CLICKABLEUICOMPONENT_H +#include "EventData.h" +#include "UiComponent.h" + +struct VisualStyle { + float brightness = 1.0f; + glm::vec3 tintColor = glm::vec3(0.0f); + float tintStrength = 0.0f; +}; + +enum class UiEventType { + MOUSE_OVER, + MOUSE_CLICK, + NONE +}; + +class Mouse; + +class ClickableUiComponent : public UiComponent { +public: + using MouseCallback = std::function; + ClickableUiComponent(const LayoutStyle &style) : UiComponent(style) {}; + + void addMouseListener(MouseCallback callback) { + mouseListeners.push_back(std::move(callback)); + } + + void fireEvent(const MouseEventData& event) { + for (auto& listener: mouseListeners) { + listener(event); + } + } + + void setVisualStyle(UiEventType eventType, const VisualStyle& style) { + visualStyles[eventType] = style; + } +private: + std::vector mouseListeners; + bool blocked = false; + bool mousedOver = false; + + void checkMouseOver(); + void checkClicks(); + void checkMouseButton(MouseButton button, Mouse& mouse); +protected: + void onUpdate(float dt) override; + void mouseOver(bool newMouseOverState); + void click(MouseButton button, bool buttonDown); + + VisualStyle currentVisualStyle; // für Rendering + std::unordered_map visualStyles; + + void applyCurrentStyle(UiEventType eventType); +}; + + +#endif //DICEWARS_SIEDLER_CLICKABLEUICOMPONENT_H \ No newline at end of file diff --git a/src/engine/core/gui/uiComponent/EventData.cpp b/src/engine/core/gui/uiComponent/EventData.cpp new file mode 100644 index 0000000..72972ec --- /dev/null +++ b/src/engine/core/gui/uiComponent/EventData.cpp @@ -0,0 +1,5 @@ +// +// Created by sebastian on 15.02.26. +// + +#include "EventData.h" \ No newline at end of file diff --git a/src/engine/core/gui/uiComponent/EventData.h b/src/engine/core/gui/uiComponent/EventData.h new file mode 100644 index 0000000..25e09c2 --- /dev/null +++ b/src/engine/core/gui/uiComponent/EventData.h @@ -0,0 +1,39 @@ +// +// Created by sebastian on 15.02.26. +// + +#ifndef DICEWARS_SIEDLER_EVENTDATA_H +#define DICEWARS_SIEDLER_EVENTDATA_H + + +enum class MouseButton; + +#include + +struct MouseEventData { + std::optional button; + bool eventState; + + MouseEventData(std::optional btn, bool state) + : button(btn), eventState(state) {} + + bool isClick(MouseButton btn) const { + return eventState && button.has_value() && button.value() == btn; + } + + bool isCompleteClick(MouseButton btn) const { + return !eventState && button.has_value() && button.value() == btn; + } + + bool isMouseOver() const { + return eventState && !button.has_value(); + } + + bool isMouseOff() const { + return !eventState && !button.has_value(); + } +}; + + + +#endif //DICEWARS_SIEDLER_EVENTDATA_H \ No newline at end of file diff --git a/src/engine/core/gui/uiComponent/UiButton.cpp b/src/engine/core/gui/uiComponent/UiButton.cpp index f64af0f..81205c2 100644 --- a/src/engine/core/gui/uiComponent/UiButton.cpp +++ b/src/engine/core/gui/uiComponent/UiButton.cpp @@ -8,20 +8,17 @@ #include "UiText.h" -UiButton::UiButton(const GLuint textureID, std::string label, Font &font, const LayoutStyle &style): textureID(textureID), text(std::move(label)), font(font), UiComponent(style) { -} - -void UiButton::onClick() { - if (clickListener) { - clickListener(); - } +UiButton::UiButton(const GLuint textureID, std::string label, Font &font, const LayoutStyle &style): ClickableUiComponent(style), text(std::move(label)), font(font), textureID(textureID) { + visualStyles[UiEventType::NONE] = {1.0f, glm::vec3(0.0f), 0.0f}; + visualStyles[UiEventType::MOUSE_OVER] = {1.15f, glm::vec3(0.0f), 0.0f}; + visualStyles[UiEventType::MOUSE_CLICK] = {1.0f, glm::vec3(0.3f, 0.6f, 1.0f), 0.15f}; } void UiButton::onCollectRenderData(UiRenderBundle &uiRenderBundle) { glm::vec2 position = glm::vec2(uiPositioner.screenSpace.x, uiPositioner.screenSpace.y); glm::vec2 size = glm::vec2(uiPositioner.screenSpace.width, uiPositioner.screenSpace.height); - VisualStyle visualStyle = getVisualStyle(); + VisualStyle visualStyle = currentVisualStyle; GUITextureBuilder textureBuilder; textureBuilder = textureBuilder.Foreground(textureID); @@ -35,14 +32,3 @@ void UiButton::onCollectRenderData(UiRenderBundle &uiRenderBundle) { uiRenderBundle.addGUIText(std::make_shared(font, text, uiPositioner.screenSpace)); } - -VisualStyle UiButton::getVisualStyle() const { - switch (state) { - case UiElementState::PRESSED: - return {1.0f, glm::vec3(0.3, 0.6, 1.0), 0.15f}; - case UiElementState::HOVERED: - return {1.15f, glm::vec3(0.0f), 0.0f}; - default: - return {}; - } -} diff --git a/src/engine/core/gui/uiComponent/UiButton.h b/src/engine/core/gui/uiComponent/UiButton.h index 47fe2bd..bcc6043 100644 --- a/src/engine/core/gui/uiComponent/UiButton.h +++ b/src/engine/core/gui/uiComponent/UiButton.h @@ -4,25 +4,16 @@ #ifndef DICEWARS_SIEDLER_UIBUTTON_H #define DICEWARS_SIEDLER_UIBUTTON_H +#include "ClickableUiComponent.h" #include "UiComponent.h" -struct VisualStyle { - float brightness = 1.0f; - glm::vec3 tintColor = glm::vec3(0.0f); - float tintStrength = 0.0f; -}; class Font; -class UiButton : public UiComponent{ +class UiButton : public ClickableUiComponent { public: UiButton(GLuint textureID, std::string label, Font& font, const LayoutStyle& style); - void setOnClick(std::function callback) { - printf("Clicklister added!\n"); - clickListener = std::move(callback); - } - void onClick() override; protected: void onCollectRenderData(UiRenderBundle &uiRenderBundle) override; @@ -30,10 +21,6 @@ private: std::string text; Font& font; GLuint textureID; - - VisualStyle getVisualStyle() const; - - std::function clickListener; }; diff --git a/src/engine/core/gui/uiComponent/UiComponent.cpp b/src/engine/core/gui/uiComponent/UiComponent.cpp index 935257b..c94c777 100644 --- a/src/engine/core/gui/uiComponent/UiComponent.cpp +++ b/src/engine/core/gui/uiComponent/UiComponent.cpp @@ -52,22 +52,6 @@ UiComponent* UiComponent::getChildAtIndex(size_t t) const { } void UiComponent::onUpdate(float) { - glm::vec2 mousePos = InputManager::getMousePositionNormalized(); - bool hoveredNow = isMouseOver(mousePos.x, mousePos.y); - bool pressedThisFrame = hoveredNow && InputManager::isMouseButtonPressed(GLFW_MOUSE_BUTTON_LEFT); - bool releasedFrame = InputManager::isMouseButtonReleased(GLFW_MOUSE_BUTTON_LEFT); - - if (pressedThisFrame) { - state = UiElementState::PRESSED; - } else if (releasedFrame) { - if (state == UiElementState::PRESSED && hoveredNow) { - onClick(); - } - state = hoveredNow ? UiElementState::HOVERED : UiElementState::NORMAL; - } else { - if (state != UiElementState::PRESSED) - state = hoveredNow ? UiElementState::HOVERED : UiElementState::NORMAL; - } } diff --git a/src/engine/core/gui/uiComponent/UiComponent.h b/src/engine/core/gui/uiComponent/UiComponent.h index edf7650..9142f29 100644 --- a/src/engine/core/gui/uiComponent/UiComponent.h +++ b/src/engine/core/gui/uiComponent/UiComponent.h @@ -37,14 +37,11 @@ public: void setLayoutStyle(const LayoutStyle& style) { uiPositioner.setLayout(style); } - - [[nodiscard]] bool isHovered() const { return state == UiElementState::HOVERED; } virtual void onClick() {}; UiComponent* getChildAtIndex(size_t t) const; protected: bool visible = true; - UiElementState state = UiElementState::NORMAL; virtual void onUpdate(float ); virtual void onCollectRenderData(UiRenderBundle& uiRenderBundle) {} diff --git a/src/engine/core/inputsOutputs/inputs/Mouse.cpp b/src/engine/core/inputsOutputs/inputs/Mouse.cpp index c24671c..5e93430 100644 --- a/src/engine/core/inputsOutputs/inputs/Mouse.cpp +++ b/src/engine/core/inputsOutputs/inputs/Mouse.cpp @@ -46,10 +46,16 @@ float Mouse::getScroll() { } void Mouse::update() { - buttonsClickedThisFrame.clear(); - buttonsReleasedThisFrame.clear(); + updateDetlas(); scroll = 0.0f; + + buttonsClickedThisFrame.clear(); + buttonsReleasedThisFrame.clear(); +} + +glm::vec2 Mouse::getXY() const { + return {x, y}; } void Mouse::reportButtonClick(int button) { diff --git a/src/engine/core/inputsOutputs/inputs/Mouse.h b/src/engine/core/inputsOutputs/inputs/Mouse.h index b62bfbc..118d51b 100644 --- a/src/engine/core/inputsOutputs/inputs/Mouse.h +++ b/src/engine/core/inputsOutputs/inputs/Mouse.h @@ -7,6 +7,7 @@ #include #include "MouseButton.h" +#include "glm/vec2.hpp" class Window; @@ -22,6 +23,7 @@ public: float getDY(); float getScroll(); void update(); + glm::vec2 getXY() const; private: std::unordered_set buttonsDown; std::unordered_set buttonsClickedThisFrame; diff --git a/src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.cpp b/src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.cpp new file mode 100644 index 0000000..f7b4828 --- /dev/null +++ b/src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.cpp @@ -0,0 +1,5 @@ +// +// Created by sebastian on 15.02.26. +// + +#include "UiInputUser.h" \ No newline at end of file diff --git a/src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.h b/src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.h new file mode 100644 index 0000000..66c1628 --- /dev/null +++ b/src/engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.h @@ -0,0 +1,14 @@ +// +// Created by sebastian on 15.02.26. +// + +#ifndef DICEWARS_SIEDLER_UIINPUTUSER_H +#define DICEWARS_SIEDLER_UIINPUTUSER_H +#include "../InputUser.h" + + +class UiInputUser : public InputUser { +}; + + +#endif //DICEWARS_SIEDLER_UIINPUTUSER_H \ No newline at end of file diff --git a/src/game/UILayer.cpp b/src/game/UILayer.cpp index 7cf2406..3868748 100644 --- a/src/game/UILayer.cpp +++ b/src/game/UILayer.cpp @@ -27,6 +27,7 @@ UILayer::UILayer() { Loader& loader = Loader::instance(); guiRenderer = std::make_unique(loader); textRenderer = std::make_unique(); + uiInputUser = std::make_unique(); } void UILayer::onAttach() { @@ -94,8 +95,19 @@ void UILayer::onAttach() { auto minimap = std::make_unique(minimapTextureID, backgroundTextureID, minimapStyle); rootContainer->addChild(std::move(minimap)); - float screenHeight = Application::getInstance().getWindow().GetHeight(); - float menuHeight = 200.f; + LayoutStyle style; + style.width = SizeValue(200, SizeUnit::Pixels); + style.height = SizeValue(50.f, SizeUnit::Pixels); + style.margin.top = {10.f, SizeUnit::Pixels}; + style.margin.left = {20.f, SizeUnit::Pixels}; + + auto uiButton = std::make_unique(AssetManager::getTexture("background")->getTextureID(), "Test", *font , style); + uiButton->addMouseListener([](const MouseEventData& e) { + if (e.isClick(MouseButton::LEFT)) { + std::cout << "Button clicked!" << std::endl; + } + }); + rootContainer->addChild(std::move(uiButton)); auto buildingMenuContainer = std::make_unique(); auto buildingMenuContainerContainer = std::make_unique(); @@ -107,6 +119,8 @@ void UILayer::onAttach() { buildingMenuContainerContainer->addChild(std::move(buildingMenuContainer)); rootContainer->addChild(std::move(buildingMenuContainerContainer)); + + Application::getInstance().stateManager->registerMouseUser(uiInputUser.get(), {StateRegistry::get().uiState}); } void UILayer::onUpdate() { @@ -124,7 +138,10 @@ void UILayer::onUpdate() { Dimensions rootParent {0.0, 0.0, 1.0, 1.0f}; rootContainer->uiPositioner.compute(rootParent); - rootContainer->update(0.f); //Todo: Determine frame time + if (uiInputUser->isMouseEnabled()) { + rootContainer->update(0.f); //Todo: Determine frame time + } + UiRenderBundle renderBundle; if (rootContainer) { diff --git a/src/game/UILayer.h b/src/game/UILayer.h index cf18301..9dd8f16 100644 --- a/src/game/UILayer.h +++ b/src/game/UILayer.h @@ -10,6 +10,7 @@ #include "../engine/core/events/EventBus.h" #include "../engine/core/gui/text/Font.h" #include "../engine/core/gui/uiMain/UiContainer.h" +#include "../engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.h" #include "../engine/layer/Layer.h" #include "../engine/renderer/GUIRenderer.h" #include "../engine/renderer/TextRenderer.h" @@ -25,6 +26,7 @@ private: std::unique_ptr mediumFont; std::unique_ptr rootContainer; + std::unique_ptr uiInputUser; size_t inventoryContainerID; size_t turnTextID;