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<SubSceneController>();
   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<SimpleTexturedMaterial>();
   item->addComponent<ExampleSpin>();
-
   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<void()> evtCameraResized;
+      std::function<void()> evtRenderResized;
 
       /**
        * Realigns the camera to match the render target quad.
@@ -23,6 +20,9 @@ namespace Dawn {
       void realign();
 
     public:
+      StateProperty<Camera*> camera;
+      StateProperty<TextureRenderTarget*> 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::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<Camera>();
-    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<Camera>();
 }
 
 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<void()> evtCamResize;
 
       void onRenderTargetResize(float_t w, float_t h);
-      void onSceneUpdate();
 
     public:
+      StateProperty<struct UIMenu*> currentMenu;
+      StateProperty<Camera*> 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