From 7838e1b92d20b8bb1df17baaf372365f5bc66ffd Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Wed, 1 Mar 2023 09:25:07 -0800 Subject: [PATCH] Updated more scene item coponents to state system --- src/dawn/display/RenderPipeline.cpp | 2 +- .../scene/components/example/ExampleSpin.cpp | 20 +- .../scene/components/example/ExampleSpin.hpp | 35 ++- .../components/scene/SubSceneCameraAlign.cpp | 91 ++----- .../components/scene/SubSceneCameraAlign.hpp | 25 +- .../components/scene/SubSceneController.cpp | 71 ++--- .../components/scene/SubSceneController.hpp | 11 +- src/dawn/scene/components/ui/UICanvas.cpp | 91 +++---- src/dawn/scene/components/ui/UICanvas.hpp | 14 +- src/dawn/state/StateProperty.hpp | 6 +- src/dawn/ui/UIMenu.cpp | 250 +++++++++--------- 11 files changed, 256 insertions(+), 360 deletions(-) diff --git a/src/dawn/display/RenderPipeline.cpp b/src/dawn/display/RenderPipeline.cpp index 83807ed6..6d153c48 100644 --- a/src/dawn/display/RenderPipeline.cpp +++ b/src/dawn/display/RenderPipeline.cpp @@ -34,7 +34,7 @@ void RenderPipeline::renderScene(Scene *scene) { auto subSceneControllers = scene->findComponents(); auto itSubScene = subSceneControllers.begin(); while(itSubScene != subSceneControllers.end()) { - auto subScene = (*itSubScene)->getSubScene(); + Scene *subScene = (Scene *)((*itSubScene)->subScene); if(subScene == nullptr) { ++itSubScene; continue; diff --git a/src/dawn/scene/components/example/ExampleSpin.cpp b/src/dawn/scene/components/example/ExampleSpin.cpp index 87454629..6beba387 100644 --- a/src/dawn/scene/components/example/ExampleSpin.cpp +++ b/src/dawn/scene/components/example/ExampleSpin.cpp @@ -19,24 +19,20 @@ SceneItem * ExampleSpin::create(Scene *scene) { CubeMesh::buffer(mr->mesh, glm::vec3(-0.5f, -0.5f, -0.5f), glm::vec3(1, 1, 1), 0, 0); auto mat = item->addComponent(); item->addComponent(); - return item; } ExampleSpin::ExampleSpin(SceneItem *item) : SceneItemComponent(item) { - getScene()->eventSceneUnpausedUpdate.addListener(this, &ExampleSpin::onUnpausedUpdate); } -void ExampleSpin::onUnpausedUpdate() { - auto quat = this->transform->getLocalRotation(); - quat = glm::rotate(quat, getGame()->timeManager.delta, glm::vec3(0, 1, 0)); - quat = glm::rotate(quat, getGame()->timeManager.delta / 2.0f, glm::vec3(1, 0, 0)); - quat = glm::rotate(quat, getGame()->timeManager.delta / 4.0f, glm::vec3(0, 0, 1)); - this->transform->setLocalRotation(quat); -} - -void ExampleSpin::onDispose() { - getScene()->eventSceneUnpausedUpdate.removeListener(this, &ExampleSpin::onUnpausedUpdate); +void ExampleSpin::onStart() { + useEventLegacy([&]{ + auto quat = this->transform->getLocalRotation(); + quat = glm::rotate(quat, getGame()->timeManager.delta, glm::vec3(0, 1, 0)); + quat = glm::rotate(quat, getGame()->timeManager.delta / 2.0f, glm::vec3(1, 0, 0)); + quat = glm::rotate(quat, getGame()->timeManager.delta / 4.0f, glm::vec3(0, 0, 1)); + this->transform->setLocalRotation(quat); + }, getScene()->eventSceneUnpausedUpdate); } \ No newline at end of file diff --git a/src/dawn/scene/components/example/ExampleSpin.hpp b/src/dawn/scene/components/example/ExampleSpin.hpp index 458905ff..ea7b9dae 100644 --- a/src/dawn/scene/components/example/ExampleSpin.hpp +++ b/src/dawn/scene/components/example/ExampleSpin.hpp @@ -1,19 +1,18 @@ -// Copyright (c) 2022 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#pragma once -#include "scene/SceneItemComponent.hpp" -#include "display/shader/Shader.hpp" - -namespace Dawn { - class ExampleSpin : public SceneItemComponent { - public: - static SceneItem * create(Scene *scene); - - ExampleSpin(SceneItem *item); - void onUnpausedUpdate(); - void onDispose() override; - }; +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "scene/SceneItemComponent.hpp" +#include "display/shader/Shader.hpp" + +namespace Dawn { + class ExampleSpin : public SceneItemComponent { + public: + static SceneItem * create(Scene *scene); + + ExampleSpin(SceneItem *item); + void onStart() override; + }; } \ No newline at end of file diff --git a/src/dawn/scene/components/scene/SubSceneCameraAlign.cpp b/src/dawn/scene/components/scene/SubSceneCameraAlign.cpp index 7caf990e..d92a68b2 100644 --- a/src/dawn/scene/components/scene/SubSceneCameraAlign.cpp +++ b/src/dawn/scene/components/scene/SubSceneCameraAlign.cpp @@ -7,18 +7,11 @@ using namespace Dawn; -SubSceneCameraAlign::SubSceneCameraAlign(SceneItem *i) : SceneItemComponent(i) { - -} - -void SubSceneCameraAlign::onRenderTargetResize( - RenderTarget *target, float_t w, float_t h -) { - this->realign(); -} - -void SubSceneCameraAlign::onCameraResize(float_t w, float_t h) { - this->realign(); +SubSceneCameraAlign::SubSceneCameraAlign(SceneItem *i) : + SceneItemComponent(i), + camera(nullptr), + renderTarget(nullptr) +{ } void SubSceneCameraAlign::realign() { @@ -27,7 +20,7 @@ void SubSceneCameraAlign::realign() { if(this->renderTarget == nullptr) return; float_t ratio = this->renderTarget->getWidth() / this->renderTarget->getHeight(); - float_t myRatio = this->camera->getRenderTarget()->getWidth() / this->camera->getRenderTarget()->getHeight(); + float_t myRatio = this->camera->getAspect(); this->camera->type = CAMERA_TYPE_ORTHONOGRAPHIC; this->camera->transform->lookAt(glm::vec3(0, 0, 10), glm::vec3(0, 0, 0)); @@ -49,57 +42,27 @@ void SubSceneCameraAlign::realign() { } } -void SubSceneCameraAlign::setRenderTarget(TextureRenderTarget *renderTarget) { - assertTrue(this->renderTarget != renderTarget); - - if(this->renderTarget != nullptr) { - this->renderTarget->eventRenderTargetResized.removeListener( - this, &SubSceneCameraAlign::onRenderTargetResize - ); - } - - this->renderTarget = renderTarget; - this->realign(); - - if(this->renderTarget != nullptr) { - this->renderTarget->eventRenderTargetResized.addListener( - this, &SubSceneCameraAlign::onRenderTargetResize - ); - } -} - -void SubSceneCameraAlign::setCamera(Camera *camera) { - assertTrue(this->camera != camera); - - if(this->camera != nullptr) { - this->camera->eventRenderTargetResized.removeListener( - this, &SubSceneCameraAlign::onCameraResize - ); - } - - this->camera = camera; - this->realign(); - - if(this->camera != nullptr) { - this->camera->eventRenderTargetResized.addListener( - this, &SubSceneCameraAlign::onCameraResize - ); - } -} - void SubSceneCameraAlign::onStart() { - this->realign(); -} + auto cameraEffect = useEffectWithTeardown([&]{ + if(camera == nullptr) return evtCameraResized = [&] {}; -void SubSceneCameraAlign::onDispose() { - if(this->renderTarget != nullptr) { - this->renderTarget->eventRenderTargetResized.removeListener( - this, &SubSceneCameraAlign::onRenderTargetResize - ); - } - if(this->camera != nullptr) { - this->camera->eventRenderTargetResized.removeListener( - this, &SubSceneCameraAlign::onCameraResize - ); - } + this->realign(); + + return evtCameraResized = useEvent([&](float_t w, float_t h){ + this->realign(); + }, this->camera->event2RenderTargetResized); + }, this->camera); + + auto renderEffect = useEffectWithTeardown([&]{ + if(renderTarget == nullptr) return evtRenderResized = [&]{}; + + this->realign(); + + return evtRenderResized = useEventLegacy([&](RenderTarget *t, float_t w, float_t h) { + this->realign(); + }, renderTarget->eventRenderTargetResized); + }, this->renderTarget); + + cameraEffect(); + renderEffect(); } \ No newline at end of file diff --git a/src/dawn/scene/components/scene/SubSceneCameraAlign.hpp b/src/dawn/scene/components/scene/SubSceneCameraAlign.hpp index b7305a2f..060f41d2 100644 --- a/src/dawn/scene/components/scene/SubSceneCameraAlign.hpp +++ b/src/dawn/scene/components/scene/SubSceneCameraAlign.hpp @@ -11,11 +11,8 @@ namespace Dawn { class SubSceneCameraAlign : public SceneItemComponent { protected: - TextureRenderTarget *renderTarget = nullptr; - Camera *camera = nullptr; - - void onRenderTargetResize(RenderTarget *target, float_t w, float_t h); - void onCameraResize(float_t w, float_t h); + std::function evtCameraResized; + std::function evtRenderResized; /** * Realigns the camera to match the render target quad. @@ -23,6 +20,9 @@ namespace Dawn { void realign(); public: + StateProperty camera; + StateProperty renderTarget; + /** * Create the sub scene camera align component. This will align a camera * to match a render target quad (Refer SimpleRenderTargetQuad) @@ -31,21 +31,6 @@ namespace Dawn { */ SubSceneCameraAlign(SceneItem *item); - /** - * Set the render target for this alignment to use. - * - * @param renderTarget Render target to align to. - */ - void setRenderTarget(TextureRenderTarget *renderTarget); - - /** - * Sets the camera that will be aligned. - * - * @param camera Camera to align. - */ - void setCamera(Camera *camera); - void onStart() override; - void onDispose() override; }; } \ No newline at end of file diff --git a/src/dawn/scene/components/scene/SubSceneController.cpp b/src/dawn/scene/components/scene/SubSceneController.cpp index fc85ae7f..70c3f6b4 100644 --- a/src/dawn/scene/components/scene/SubSceneController.cpp +++ b/src/dawn/scene/components/scene/SubSceneController.cpp @@ -1,45 +1,28 @@ -// Copyright (c) 2023 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#include "SubSceneController.hpp" - -using namespace Dawn; - -SubSceneController::SubSceneController(SceneItem *i) : SceneItemComponent(i) { - -} - -void SubSceneController::onSceneUpdate() { - if(this->onlyUpdateUnpaused) return; - if(this->subScene == nullptr) return; - this->subScene->update(); -} - -void SubSceneController::onSceneUnpausedUpdate() { - if(!this->onlyUpdateUnpaused) return; - if(this->subScene == nullptr) return; - this->subScene->update(); -} - -Scene * SubSceneController::getSubScene() { - return this->subScene; -} - -void SubSceneController::setSubScene(Scene *scene) { - assertTrue(scene != this->subScene); - this->subScene = scene; -} - -void SubSceneController::onStart() { - auto myScene = this->getScene(); - myScene->eventSceneUnpausedUpdate.addListener(this, &SubSceneController::onSceneUnpausedUpdate); - myScene->eventSceneUpdate.addListener(this, &SubSceneController::onSceneUpdate); -} - -void SubSceneController::onDispose() { - auto myScene = this->getScene(); - myScene->eventSceneUnpausedUpdate.removeListener(this, &SubSceneController::onSceneUnpausedUpdate); - myScene->eventSceneUpdate.removeListener(this, &SubSceneController::onSceneUpdate); +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "SubSceneController.hpp" + +using namespace Dawn; + +SubSceneController::SubSceneController(SceneItem *i) : SceneItemComponent(i) { + +} + +void SubSceneController::onStart() { + auto myScene = this->getScene(); + + useEventLegacy([&]{ + if(!this->onlyUpdateUnpaused) return; + if(this->subScene == nullptr) return; + this->subScene->update(); + }, myScene->eventSceneUnpausedUpdate); + + useEventLegacy([&]{ + if(this->onlyUpdateUnpaused) return; + if(this->subScene == nullptr) return; + this->subScene->update(); + }, myScene->eventSceneUpdate); } \ No newline at end of file diff --git a/src/dawn/scene/components/scene/SubSceneController.hpp b/src/dawn/scene/components/scene/SubSceneController.hpp index 30ae85ac..d894ccad 100644 --- a/src/dawn/scene/components/scene/SubSceneController.hpp +++ b/src/dawn/scene/components/scene/SubSceneController.hpp @@ -8,21 +8,12 @@ namespace Dawn { class SubSceneController : public SceneItemComponent { - protected: - Scene *subScene = nullptr; - - void onSceneUpdate(); - void onSceneUnpausedUpdate(); - public: + Scene *subScene = nullptr; bool_t onlyUpdateUnpaused = false; SubSceneController(SceneItem *item); - Scene * getSubScene(); - void setSubScene(Scene *scene); - void onStart() override; - void onDispose() override; }; } \ No newline at end of file diff --git a/src/dawn/scene/components/ui/UICanvas.cpp b/src/dawn/scene/components/ui/UICanvas.cpp index dd53671f..3c5677d3 100644 --- a/src/dawn/scene/components/ui/UICanvas.cpp +++ b/src/dawn/scene/components/ui/UICanvas.cpp @@ -15,39 +15,11 @@ UICanvas * UICanvas::create(Scene *scene) { return item->addComponent(); } -UICanvas::UICanvas(SceneItem *item) : SceneItemComponent(item) { -} - -void UICanvas::onRenderTargetResize(float_t w, float_t h){ - auto it = this->children.begin(); - while(it != this->children.end()) { - (*it)->updatePositions(); - ++it; - } -} - -void UICanvas::onSceneUpdate() { - if(this->currentMenu != nullptr) { - this->currentMenu->onTick(); - } -} - -void UICanvas::setCamera(Camera *camera) { - assertTrue(camera != this->camera); - - if(this->camera != nullptr) { - this->camera->eventRenderTargetResized.removeListener( - this, &UICanvas::onRenderTargetResize - ); - } - - this->camera = camera; - - if(this->camera != nullptr) { - this->camera->eventRenderTargetResized.addListener( - this, &UICanvas::onRenderTargetResize - ); - } +UICanvas::UICanvas(SceneItem *item) : + SceneItemComponent(item), + camera(nullptr), + currentMenu(nullptr) +{ } float_t UICanvas::getWidth() { @@ -64,34 +36,47 @@ float_t UICanvas::getHeight() { return this->camera->getRenderTarget()->getHeight(); } -struct UIMenu * UICanvas::getCurrentMenu() { - return this->currentMenu; -} - void UICanvas::setCurrentMenu(struct UIMenu *menu) { - if(this->currentMenu != nullptr) this->currentMenu->onInactive(); - this->currentMenu = menu; - if(menu != nullptr) menu->onActive(); } void UICanvas::onStart() { - if(this->camera == nullptr) { - auto camera = this->getScene()->findComponent(); - this->setCamera(camera); - } + useEffectWithTeardown([&]{ + if(this->camera == nullptr) return evtCamResize = [&]{}; + + auto it = this->children.begin(); + while(it != this->children.end()) { + (*it)->updatePositions(); + ++it; + } - this->getScene()->eventSceneUpdate.addListener(this, &UICanvas::onSceneUpdate); + return evtCamResize = useEvent([&](float_t w, float_t h){ + auto it = this->children.begin(); + while(it != this->children.end()) { + (*it)->updatePositions(); + ++it; + } + }, camera->event2RenderTargetResized); + }, camera); + + useEffectWithTeardown([&]{ + if(currentMenu != nullptr) currentMenu->onActive(); + + return [&] { + if(currentMenu == nullptr) currentMenu->onInactive(); + }; + }, this->currentMenu); + + // Scene Update + useEventLegacy([&]{ + if(this->currentMenu == nullptr) return; + this->currentMenu->onTick(); + }, getScene()->eventSceneUpdate); + + // Find Camera if we need to. + if(camera == nullptr) camera = this->getScene()->findComponent(); } void UICanvas::onDispose() { - if(this->camera != nullptr) { - this->camera->eventRenderTargetResized.removeListener( - this, &UICanvas::onRenderTargetResize - ); - } - - this->getScene()->eventSceneUpdate.removeListener(this, &UICanvas::onSceneUpdate); - auto it = this->children.begin(); while(it != this->children.end()) { delete *it; diff --git a/src/dawn/scene/components/ui/UICanvas.hpp b/src/dawn/scene/components/ui/UICanvas.hpp index b48d9d32..e07f26e0 100644 --- a/src/dawn/scene/components/ui/UICanvas.hpp +++ b/src/dawn/scene/components/ui/UICanvas.hpp @@ -20,13 +20,14 @@ namespace Dawn { class UICanvas : public SceneItemComponent { protected: - Camera *camera = nullptr; - struct UIMenu *currentMenu = nullptr; + std::function evtCamResize; void onRenderTargetResize(float_t w, float_t h); - void onSceneUpdate(); public: + StateProperty currentMenu; + StateProperty camera; + /** * Creates a UI Canvas Scene Item Element, and attaches it to the provided * scene. @@ -47,13 +48,6 @@ namespace Dawn { */ UICanvas(SceneItem *item); - /** - * Sets the camera used by the UI canvas. - * - * @param camera Camera to set for the UI canvas. - */ - void setCamera(Camera *camera); - /** * Construct and append a UI item to this UI Canvas. * diff --git a/src/dawn/state/StateProperty.hpp b/src/dawn/state/StateProperty.hpp index bac52500..64de32ee 100644 --- a/src/dawn/state/StateProperty.hpp +++ b/src/dawn/state/StateProperty.hpp @@ -18,8 +18,6 @@ namespace Dawn { */ void setInternal(V val) { if(val == this->_realValue) return;// TODO: can I omit this? kinda bad tbh. - this->previous = this->_realValue; - this->_realValue = val; // Run the teardowns auto itTeardown = this->_effectTeardowns.begin(); @@ -29,6 +27,10 @@ namespace Dawn { } this->_effectTeardowns.clear(); + // Update the values + this->previous = this->_realValue; + this->_realValue = val; + // Notify the effect listeners auto itEffect = this->_effectListners.begin(); while(itEffect != this->_effectListners.end()) { diff --git a/src/dawn/ui/UIMenu.cpp b/src/dawn/ui/UIMenu.cpp index 0078de40..1c2babce 100644 --- a/src/dawn/ui/UIMenu.cpp +++ b/src/dawn/ui/UIMenu.cpp @@ -1,127 +1,125 @@ -// Copyright (c) 2023 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#include "UIMenu.hpp" -#include "game/DawnGame.hpp" - -using namespace Dawn; - -UIMenu::UIMenu(UICanvas *canvas, int32_t columns, int32_t rows) { - assertNotNull(canvas); - assertTrue(columns > 0); - assertTrue(rows > 0); - - this->canvas = canvas; - this->rows = rows; - this->columns = columns; - this->items = (UIMenuItem**)memoryFillWithZero(sizeof(UIMenuItem*)*columns*rows); -} - -void UIMenu::setSize(int32_t cols, int32_t rows) { - assertNotNull(this->items); - assertTrue(columns > 0); - assertTrue(rows > 0); - assertTrue(columns != this->columns); - assertTrue(rows != this->rows); - - memoryFree(this->items); - this->items = (UIMenuItem**)memoryFillWithZero(sizeof(UIMenuItem*)*columns*rows); -} - -UIMenuItem * UIMenu::getItem(int32_t x, int32_t y) { - return this->items[x * this->columns + y]; -} - -void UIMenu::setPosition(int32_t x, int32_t y) { - assertTrue(x >= 0 && x < this->columns); - assertTrue(y >= 0 && y < this->rows); - UIMenuItem *item; - - if(this->x != -1) { - assertTrue(this->y != -1); - item = this->getItem(this->x, this->y); - if(item != nullptr) { - item->onItemOff(); - } - } - - this->eventCursorChange.invoke(this->x, this->y, x, y); - - this->x = x; - this->y = y; - item = this->getItem(x, y); - if(item != nullptr && item->canBeOvered()) { - item->onItemOver(); - } -} - -void UIMenu::moveRelative(int32_t x, int32_t y) { - int32_t x2 = this->x + x; - if(x2 < 0 || x2 >= this->columns) return; - int32_t y2 = this->y + y; - if(y2 < 0 || y2 >= this->rows) return; - this->setPosition(x2, y2); -} - -void UIMenu::setItem(int32_t x, int32_t y, UIMenuItem *item) { - assertTrue(x >= 0); - assertTrue(y >= 0); - assertTrue(x < this->columns); - assertTrue(y < this->rows); - assertNotNull(item); - assertNotNull(this->items); - assertNull(this->getItem(x, y)); - - this->items[x * this->columns + y] = item; -} - -void UIMenu::onInactive() { - this->eventMenuInactive.invoke(); -} - -void UIMenu::onActive() { - this->eventMenuActive.invoke(); -} - -void UIMenu::onTick() { - auto im = &this->canvas->getGame()->inputManager; - - if(im->isPressed(INPUT_BIND_ACCEPT)) { - auto item = this->getItem(this->x, this->y); - if(item != nullptr && item->canBeSelected()) { - item->onItemSelected(); - return; - } - } - - if(im->isPressed(INPUT_BIND_NEGATIVE_Y)) { - this->moveRelative(0, 1); - return; - } - - if(im->isPressed(INPUT_BIND_POSITIVE_Y)) { - this->moveRelative(0, -1); - return; - } - - if(im->isPressed(INPUT_BIND_NEGATIVE_X)) { - this->moveRelative(-1, 0); - return; - } - - if(im->isPressed(INPUT_BIND_POSITIVE_X)) { - this->moveRelative(1, 0); - return; - } - - // TODO: Support more modes and holding buttons to scroll. -} - -UIMenu::~UIMenu() { - if(this->canvas->getCurrentMenu() == this) { - this->canvas->setCurrentMenu(nullptr); - } - memoryFree(this->items); +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "UIMenu.hpp" +#include "game/DawnGame.hpp" + +using namespace Dawn; + +UIMenu::UIMenu(UICanvas *canvas, int32_t columns, int32_t rows) { + assertNotNull(canvas); + assertTrue(columns > 0); + assertTrue(rows > 0); + + this->canvas = canvas; + this->rows = rows; + this->columns = columns; + this->items = (UIMenuItem**)memoryFillWithZero(sizeof(UIMenuItem*)*columns*rows); +} + +void UIMenu::setSize(int32_t cols, int32_t rows) { + assertNotNull(this->items); + assertTrue(columns > 0); + assertTrue(rows > 0); + assertTrue(columns != this->columns); + assertTrue(rows != this->rows); + + memoryFree(this->items); + this->items = (UIMenuItem**)memoryFillWithZero(sizeof(UIMenuItem*)*columns*rows); +} + +UIMenuItem * UIMenu::getItem(int32_t x, int32_t y) { + return this->items[x * this->columns + y]; +} + +void UIMenu::setPosition(int32_t x, int32_t y) { + assertTrue(x >= 0 && x < this->columns); + assertTrue(y >= 0 && y < this->rows); + UIMenuItem *item; + + if(this->x != -1) { + assertTrue(this->y != -1); + item = this->getItem(this->x, this->y); + if(item != nullptr) { + item->onItemOff(); + } + } + + this->eventCursorChange.invoke(this->x, this->y, x, y); + + this->x = x; + this->y = y; + item = this->getItem(x, y); + if(item != nullptr && item->canBeOvered()) { + item->onItemOver(); + } +} + +void UIMenu::moveRelative(int32_t x, int32_t y) { + int32_t x2 = this->x + x; + if(x2 < 0 || x2 >= this->columns) return; + int32_t y2 = this->y + y; + if(y2 < 0 || y2 >= this->rows) return; + this->setPosition(x2, y2); +} + +void UIMenu::setItem(int32_t x, int32_t y, UIMenuItem *item) { + assertTrue(x >= 0); + assertTrue(y >= 0); + assertTrue(x < this->columns); + assertTrue(y < this->rows); + assertNotNull(item); + assertNotNull(this->items); + assertNull(this->getItem(x, y)); + + this->items[x * this->columns + y] = item; +} + +void UIMenu::onInactive() { + this->eventMenuInactive.invoke(); +} + +void UIMenu::onActive() { + this->eventMenuActive.invoke(); +} + +void UIMenu::onTick() { + auto im = &this->canvas->getGame()->inputManager; + + if(im->isPressed(INPUT_BIND_ACCEPT)) { + auto item = this->getItem(this->x, this->y); + if(item != nullptr && item->canBeSelected()) { + item->onItemSelected(); + return; + } + } + + if(im->isPressed(INPUT_BIND_NEGATIVE_Y)) { + this->moveRelative(0, 1); + return; + } + + if(im->isPressed(INPUT_BIND_POSITIVE_Y)) { + this->moveRelative(0, -1); + return; + } + + if(im->isPressed(INPUT_BIND_NEGATIVE_X)) { + this->moveRelative(-1, 0); + return; + } + + if(im->isPressed(INPUT_BIND_POSITIVE_X)) { + this->moveRelative(1, 0); + return; + } + + // TODO: Support more modes and holding buttons to scroll. +} + +UIMenu::~UIMenu() { + if(this->canvas->currentMenu == this) this->canvas->currentMenu = nullptr; + memoryFree(this->items); } \ No newline at end of file