ADD: ClickableUiComponent

This commit is contained in:
sebastian 2026-02-15 16:16:00 +01:00
parent 16b4747175
commit a8c8e1770c
16 changed files with 236 additions and 59 deletions

View File

@ -236,6 +236,12 @@ add_executable(Dicewars_Siedler src/main.cpp
src/engine/core/inputsOutputs/stateControl/inputUser/GameInputUser.h src/engine/core/inputsOutputs/stateControl/inputUser/GameInputUser.h
src/engine/core/inputsOutputs/stateControl/StateRegistry.cpp src/engine/core/inputsOutputs/stateControl/StateRegistry.cpp
src/engine/core/inputsOutputs/stateControl/StateRegistry.h 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 target_compile_options(Dicewars_Siedler PRIVATE

View File

@ -50,12 +50,13 @@ Application::~Application()
void Application::run() { void Application::run() {
while (!window->shouldClose()) while (!window->shouldClose())
{ {
window->OnUpdate();
updateTime(); updateTime();
for (Layer* layer : layers) for (Layer* layer : layers)
{ {
layer->onUpdate(); layer->onUpdate();
} }
window->OnUpdate();
InputManager::update(); InputManager::update();
mouse->update(); mouse->update();

View File

@ -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);
}
}

View File

@ -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<void(const MouseEventData&)>;
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<MouseCallback> 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<UiEventType, VisualStyle> visualStyles;
void applyCurrentStyle(UiEventType eventType);
};
#endif //DICEWARS_SIEDLER_CLICKABLEUICOMPONENT_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 15.02.26.
//
#include "EventData.h"

View File

@ -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 <optional>
struct MouseEventData {
std::optional<MouseButton> button;
bool eventState;
MouseEventData(std::optional<MouseButton> 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

View File

@ -8,20 +8,17 @@
#include "UiText.h" #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) { 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};
void UiButton::onClick() { visualStyles[UiEventType::MOUSE_CLICK] = {1.0f, glm::vec3(0.3f, 0.6f, 1.0f), 0.15f};
if (clickListener) {
clickListener();
}
} }
void UiButton::onCollectRenderData(UiRenderBundle &uiRenderBundle) { void UiButton::onCollectRenderData(UiRenderBundle &uiRenderBundle) {
glm::vec2 position = glm::vec2(uiPositioner.screenSpace.x, uiPositioner.screenSpace.y); glm::vec2 position = glm::vec2(uiPositioner.screenSpace.x, uiPositioner.screenSpace.y);
glm::vec2 size = glm::vec2(uiPositioner.screenSpace.width, uiPositioner.screenSpace.height); glm::vec2 size = glm::vec2(uiPositioner.screenSpace.width, uiPositioner.screenSpace.height);
VisualStyle visualStyle = getVisualStyle(); VisualStyle visualStyle = currentVisualStyle;
GUITextureBuilder textureBuilder; GUITextureBuilder textureBuilder;
textureBuilder = textureBuilder.Foreground(textureID); textureBuilder = textureBuilder.Foreground(textureID);
@ -35,14 +32,3 @@ void UiButton::onCollectRenderData(UiRenderBundle &uiRenderBundle) {
uiRenderBundle.addGUIText(std::make_shared<GUIText>(font, text, uiPositioner.screenSpace)); uiRenderBundle.addGUIText(std::make_shared<GUIText>(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 {};
}
}

View File

@ -4,25 +4,16 @@
#ifndef DICEWARS_SIEDLER_UIBUTTON_H #ifndef DICEWARS_SIEDLER_UIBUTTON_H
#define DICEWARS_SIEDLER_UIBUTTON_H #define DICEWARS_SIEDLER_UIBUTTON_H
#include "ClickableUiComponent.h"
#include "UiComponent.h" #include "UiComponent.h"
struct VisualStyle {
float brightness = 1.0f;
glm::vec3 tintColor = glm::vec3(0.0f);
float tintStrength = 0.0f;
};
class Font; class Font;
class UiButton : public UiComponent{ class UiButton : public ClickableUiComponent {
public: public:
UiButton(GLuint textureID, std::string label, Font& font, const LayoutStyle& style); UiButton(GLuint textureID, std::string label, Font& font, const LayoutStyle& style);
void setOnClick(std::function<void()> callback) {
printf("Clicklister added!\n");
clickListener = std::move(callback);
}
void onClick() override;
protected: protected:
void onCollectRenderData(UiRenderBundle &uiRenderBundle) override; void onCollectRenderData(UiRenderBundle &uiRenderBundle) override;
@ -30,10 +21,6 @@ private:
std::string text; std::string text;
Font& font; Font& font;
GLuint textureID; GLuint textureID;
VisualStyle getVisualStyle() const;
std::function<void()> clickListener;
}; };

View File

@ -52,22 +52,6 @@ UiComponent* UiComponent::getChildAtIndex(size_t t) const {
} }
void UiComponent::onUpdate(float) { 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;
}
} }

View File

@ -37,14 +37,11 @@ public:
void setLayoutStyle(const LayoutStyle& style) { void setLayoutStyle(const LayoutStyle& style) {
uiPositioner.setLayout(style); uiPositioner.setLayout(style);
} }
[[nodiscard]] bool isHovered() const { return state == UiElementState::HOVERED; }
virtual void onClick() {}; virtual void onClick() {};
UiComponent* getChildAtIndex(size_t t) const; UiComponent* getChildAtIndex(size_t t) const;
protected: protected:
bool visible = true; bool visible = true;
UiElementState state = UiElementState::NORMAL;
virtual void onUpdate(float ); virtual void onUpdate(float );
virtual void onCollectRenderData(UiRenderBundle& uiRenderBundle) {} virtual void onCollectRenderData(UiRenderBundle& uiRenderBundle) {}

View File

@ -46,10 +46,16 @@ float Mouse::getScroll() {
} }
void Mouse::update() { void Mouse::update() {
buttonsClickedThisFrame.clear();
buttonsReleasedThisFrame.clear();
updateDetlas(); updateDetlas();
scroll = 0.0f; scroll = 0.0f;
buttonsClickedThisFrame.clear();
buttonsReleasedThisFrame.clear();
}
glm::vec2 Mouse::getXY() const {
return {x, y};
} }
void Mouse::reportButtonClick(int button) { void Mouse::reportButtonClick(int button) {

View File

@ -7,6 +7,7 @@
#include <unordered_set> #include <unordered_set>
#include "MouseButton.h" #include "MouseButton.h"
#include "glm/vec2.hpp"
class Window; class Window;
@ -22,6 +23,7 @@ public:
float getDY(); float getDY();
float getScroll(); float getScroll();
void update(); void update();
glm::vec2 getXY() const;
private: private:
std::unordered_set<int> buttonsDown; std::unordered_set<int> buttonsDown;
std::unordered_set<int> buttonsClickedThisFrame; std::unordered_set<int> buttonsClickedThisFrame;

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 15.02.26.
//
#include "UiInputUser.h"

View File

@ -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

View File

@ -27,6 +27,7 @@ UILayer::UILayer() {
Loader& loader = Loader::instance(); Loader& loader = Loader::instance();
guiRenderer = std::make_unique<GUIRenderer>(loader); guiRenderer = std::make_unique<GUIRenderer>(loader);
textRenderer = std::make_unique<TextRenderer>(); textRenderer = std::make_unique<TextRenderer>();
uiInputUser = std::make_unique<UiInputUser>();
} }
void UILayer::onAttach() { void UILayer::onAttach() {
@ -94,8 +95,19 @@ void UILayer::onAttach() {
auto minimap = std::make_unique<UiImage>(minimapTextureID, backgroundTextureID, minimapStyle); auto minimap = std::make_unique<UiImage>(minimapTextureID, backgroundTextureID, minimapStyle);
rootContainer->addChild(std::move(minimap)); rootContainer->addChild(std::move(minimap));
float screenHeight = Application::getInstance().getWindow().GetHeight(); LayoutStyle style;
float menuHeight = 200.f; 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<UiButton>(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<UiBuildingMenuContainer>(); auto buildingMenuContainer = std::make_unique<UiBuildingMenuContainer>();
auto buildingMenuContainerContainer = std::make_unique<UiContainer>(); auto buildingMenuContainerContainer = std::make_unique<UiContainer>();
@ -107,6 +119,8 @@ void UILayer::onAttach() {
buildingMenuContainerContainer->addChild(std::move(buildingMenuContainer)); buildingMenuContainerContainer->addChild(std::move(buildingMenuContainer));
rootContainer->addChild(std::move(buildingMenuContainerContainer)); rootContainer->addChild(std::move(buildingMenuContainerContainer));
Application::getInstance().stateManager->registerMouseUser(uiInputUser.get(), {StateRegistry::get().uiState});
} }
void UILayer::onUpdate() { void UILayer::onUpdate() {
@ -124,7 +138,10 @@ void UILayer::onUpdate() {
Dimensions rootParent {0.0, 0.0, 1.0, 1.0f}; Dimensions rootParent {0.0, 0.0, 1.0, 1.0f};
rootContainer->uiPositioner.compute(rootParent); rootContainer->uiPositioner.compute(rootParent);
if (uiInputUser->isMouseEnabled()) {
rootContainer->update(0.f); //Todo: Determine frame time rootContainer->update(0.f); //Todo: Determine frame time
}
UiRenderBundle renderBundle; UiRenderBundle renderBundle;
if (rootContainer) { if (rootContainer) {

View File

@ -10,6 +10,7 @@
#include "../engine/core/events/EventBus.h" #include "../engine/core/events/EventBus.h"
#include "../engine/core/gui/text/Font.h" #include "../engine/core/gui/text/Font.h"
#include "../engine/core/gui/uiMain/UiContainer.h" #include "../engine/core/gui/uiMain/UiContainer.h"
#include "../engine/core/inputsOutputs/stateControl/inputUser/UiInputUser.h"
#include "../engine/layer/Layer.h" #include "../engine/layer/Layer.h"
#include "../engine/renderer/GUIRenderer.h" #include "../engine/renderer/GUIRenderer.h"
#include "../engine/renderer/TextRenderer.h" #include "../engine/renderer/TextRenderer.h"
@ -25,6 +26,7 @@ private:
std::unique_ptr<Font> mediumFont; std::unique_ptr<Font> mediumFont;
std::unique_ptr<UiContainer> rootContainer; std::unique_ptr<UiContainer> rootContainer;
std::unique_ptr<UiInputUser> uiInputUser;
size_t inventoryContainerID; size_t inventoryContainerID;
size_t turnTextID; size_t turnTextID;