feature: UI (#31)

* refactor(ui): replace Text with Label/Widget component system

Replace the old `Text` class with a new component-based UI system consisting of `Widget` and `Label` classes. Key changes:

- Remove `Text` class and its GPU upload logic; introduce `Label` as a `Widget` subclass with dirty-flag vertex update.
- Add `UIVertexData` struct to manage per-label vertex buffer/array and memory.
- Refactor `DebugCollector` to store labels in a `Widget` tree, removing hash-based lookup.
- Simplify `Font::vertices()` signature by removing position/scale parameters; scaling is now applied in the renderer’s model matrix.
- Update `Renderer` with `render_label()` and move UI rendering to a `render_ui()` that renders the widget tree.
- Add new source files `label.cpp`, `widget.cpp`, `ui_vertex_data.cpp` and update CMakeLists.

* refactor(render): replace UI shaders with image shaders and Image widget

* refactor(core): add event system and scene management

- Introduce Event variant and Overloaded helper for input handling
- Move game state (camera, client_world, dev_panel) from App to WorldScene
- Add SceneManager with push/pop/change operations for scene stack
- Refactor input callbacks to dispatch events through scene hierarchy
- Extract Argument struct to separate header
- Update Renderer and WorldRenderer to accept world reference
- Replace global input state with event-driven processing in ClientPlayer, Camera, etc.

* refactor: add framebuffer resize event and separate UI management

* fix(font): use correct texture format and clamp to edge for font atlas

* feat(input): add KeyAction::REPEAT for key repeat events

Add a new REPEAT value to the KeyAction enum and update the input callbacks to map GLFW_REPEAT actions to the new value instead of treating them as press.

* feat(client-player): add block cooldown and track mouse state

Introduce a time-based cooldown for block placement (PLACE_BLOCK_INTERVAL) to prevent rapid clicking. Replace the global InputState with per-player MouseState tracking in ClientPlayer. Rename place_block() to get_current_block() for clarity. Remove the old src/input.cpp and the Cubed/input.hpp header, moving input structures to Cubed/input/input.hpp.

* ui: add main menu scene and interactive button widget

- Implement MainMenuScene with a "Start Game" button that pushes the WorldScene.
- Extend Button class with background/foreground, hover detection, and size queries.
- Add virtual width/height and event handlers to Widget base class.
- Update WorldUIManager to propagate mouse move events.
- Adjust Window mouse mode based on game running state.

* feat(ui): add button scaling and change square vertices to top-left origin

* refactor(font): return TextMesh from Font::vertices to include bounding box

Replace the plain vertex vector with a TextMesh struct that carries width, height, and min coordinates. Update Label to compute and store these values immediately on text change (removing lazy dirty update). Fix Button's width/height to factor in scale and use concrete widget types for background and foreground.

* feat(ui): support scaling of labels and images

Modify width() and height() methods to multiply by m_scale for both Label and Image. Rename internal dimensions to m_real_width and m_real_height for clarity. Adjust Button foreground position to vertically center when label is scaled.

* refactor(ui): move scale property to subclasses and fix resize event handling

Remove scale from base Widget class and add per-type set_scale/scale methods to Button, Image, and Label. Dispatch separate WindowResizeEvent alongside existing FrameBufferResizeEvent. Correct centering calculations from `+` to `-` in main menu and world UI managers.

* fix(render): remove duplicate scaling in render_image model matrix

* feat(ui): introduce anchor-based widget positioning system

Replace set_position with anchor and offset for relative positioning. Add parent pointer to widget hierarchy. Remove manual resize logic in UI managers.

* feat(ui): introduce ColumnLayout widget and refactor widget parenting

- Add ColumnLayout widget that arranges children vertically with spacing.
- Refactor Widget::add_child to automatically pass `this` as parent.
- Update DebugCollector to use ColumnLayout for consistent spacing.
- Expose children() accessor in Widget for layout management.

* feat(main_menu): add exit button and refactor layout with ColumnLayout

- Add `should_close_window()` method to force window close from UI.
- Replace standalone button with ColumnLayout for proper centering.
- Add "Exit" button that calls `should_close_window()`.
- Update ESC key handling to only close window when game is not running.
- Fix ColumnLayout to respect parent's anchor instead of hardcoded TOP_LEFT.
- Remove unused parameter name in Image::update for consistency.

* feat(ui): add Rect widget with fill, color, and parent-relative sizing

* refactor(ui): extract common UI manager logic into base class

* refactor: move server logic to WorldScene and refactor UI methods

* feat(scene): implement pause menu with UI manager and escape key toggling

Add PauseMenuUIManager class and integrate pause state into WorldScene.
Escape key toggles pause in ClientPlayer and PauseMenuUIManager.
Refactor Window mouse and imgui control to support pause state.

* refactor(scene): dispatch events via typed handlers and move pause toggle to WorldScene

* feat(ui): add HUD visibility toggle and image fill features

* refactor(app): remove redundant ImGui WantCaptureMouse check in cursor callback

* feat(scene): add credits scene and UI

* feat(credits): add ESC key handling; remove about tab from dev panel

* feat(ui): add Slider widget for value selection

* refactor(ui/slider): improve track/thumb initialization and validation

Rename track/thumb accessors from set_track/set_thumb to get_track/get_thumb.
Add private init methods and a constant THUMB_WIDTH.
Add validation to set_width with log and assert.
Modify set_height to assume non-null m_thumb.

* refactor(ui): simplify Button API with property setters

* feat(ui): add label and image support to slider

- Add `set_text()`, `set_track_image()`, `set_thumb_image()` methods.
- Include label rendering and auto-scaling based on slider dimensions.
- Replace static `THUMB_WIDTH` with dynamic thumb sizing.
- Add slider thumb and track texture assets.

* feat(ui): add int slider support and fix update_text_scale calls

* refactor(config): rename hot_reload to reload_config and add settings scene

* feat(ui): add auto-scaling option for button and slider text

* feat(ui): add button enable/disable and reset on re-enter

Prevent double-clicking on buttons that push new scenes by disabling them on click and re-enabling when the UI manager's on_re_enter is called.

* fix(input): reset key status on pause

* feat(ui): add ComboButton widget and integrate into settings

Introduce ComboButton, a button that cycles through options on click.
Used in SettingsUI for fullscreen, V-Sync, and anisotropic filtering.
Adds texture reload support in SettingsScene and makes Button methods
virtual to allow overriding. Also fixes default mouse_enable to true.

* feat(ui): add child anchor and content sizing to column layout

* feat(ui): add TextField widget with UTF-8 text input support

* feat(scene): add host game scene and world parameter handling

* feat(scene): add join game scene and UI

* feat(ui): add clipboard paste to TextField

* feat(ui): add blinking cursor to text field

* feat(ui): add error UI for connection failures and network errors

Introduce ErrorUI to display error messages in-world when the client encounters connection failures or network errors. The network client now stores error strings thread-safely and exposes them via `get_error_string()`. The world scene suspends normal update/render when an error is active and shows the error overlay, allowing the user to return to the previous scene. Additionally, the client world checks for connection errors during exit to avoid hanging, and the join game UI is fixed to correctly default to client mode.

* refactor: move connect error check to WorldScene and add UI titles

* feat(ui): add error display in host and join game screens

* feat(ui): add configurable child traversal order for event handling

Add TraversalOrder enum to Widget, allowing event handlers to iterate children in back-to-front (default) or front-to-back order.
Set ColumnLayout to front-to-back order.
Change TextField mouse button handler to return false, enabling event propagation.

* fix: window build fail

* style(ui): remove extra blank line
This commit is contained in:
zhenyan121
2026-07-16 11:30:27 +08:00
committed by GitHub
parent 03259f323c
commit cc2df5e4ec
115 changed files with 5505 additions and 865 deletions

View File

@@ -0,0 +1,15 @@
#pragma once
enum class Anchor {
TOP_LEFT,
TOP_CENTER,
TOP_RIGHT,
CENTER_LEFT,
CENTER,
CENTER_RIGHT,
BOTTOM_LEFT,
BOTTOM_CENTER,
BOTTOM_RIGHT,
};

View File

@@ -0,0 +1,64 @@
#pragma once
#include "Cubed/ui/image.hpp"
#include "Cubed/ui/label.hpp"
#include "Cubed/ui/widget.hpp"
#include <functional>
namespace Cubed {
class Button : public Widget {
public:
Button(Widget* parent);
Button(const Button&) = delete;
Button(Button&&) = delete;
Button& operator=(const Button&) = delete;
Button& operator=(Button&&) = delete;
bool handle_mouse_move_event(const MouseMoveEvent& e) override;
virtual bool handle_mouse_button_event(const MouseButtonEvent& e) override;
Button& set_scale(float scale);
float width() const override;
float height() const override;
Button& set_width(float width);
Button& set_height(float height);
Button& set_background_image(const std::string& path,
TextureManager& texture_manager);
Button& set_default_image(TextureManager& texture_manager);
virtual Button& set_text(const std::string& text);
Button& set_auto_scale(bool auto_scale);
Button& set_enable(bool enable);
float scale() const;
template <typename F> Button& set_clicked(F&& f) {
m_clicked = std::forward<F>(f);
return *this;
}
protected:
std::unique_ptr<Image> m_background;
std::unique_ptr<Label> m_foreground;
bool m_hovered = false;
bool m_enable = true;
void update_text_scale();
private:
static constexpr float PADDING = 5.0f;
static constexpr float DEFAULT_SCALE = 3.0f;
static constexpr float TEXT_SCALE = 0.6f;
static constexpr const char* DEFAULT_BUTTON_IMAGE =
"texture/ui/button001.png";
std::function<void()> m_clicked;
float m_width = NORMAL_BUTTON_WIDTH;
float m_height = NORMAL_BUTTON_HEIGHT;
float m_scale = DEFAULT_SCALE;
bool m_auto_scale = false;
void on_render(Renderer& renderer) override;
void on_update(float dt) override;
};
} // namespace Cubed

View File

@@ -0,0 +1,33 @@
#pragma once
#include "Cubed/ui/widget.hpp"
namespace Cubed {
enum class ColumnLayoutAnchor { LEFT, CENTER, RIGHT };
class ColumnLayout : public Widget {
public:
ColumnLayout(const ColumnLayout&) = delete;
ColumnLayout(ColumnLayout&&) = delete;
ColumnLayout& operator=(const ColumnLayout&) = delete;
ColumnLayout& operator=(ColumnLayout&&) = delete;
ColumnLayout(Widget* parent);
~ColumnLayout();
void update(float dt) override;
float width() const override;
float height() const override;
ColumnLayout& set_spacing(int spacing);
// No need for parent node pointer; do not modify children's anchors and
// scale.
ColumnLayout& set_child_anchor(ColumnLayoutAnchor anchor);
void layout();
private:
int m_spacing = 0;
float m_content_height = 0;
float m_content_width = 0;
ColumnLayoutAnchor m_layout_anchor = ColumnLayoutAnchor::CENTER;
};
} // namespace Cubed

View File

@@ -0,0 +1,30 @@
#pragma once
#include "Cubed/ui/button.hpp"
#include <span>
namespace Cubed {
using ComboPair = std::pair<std::string, std::function<void()>>;
class ComboButton : public Button {
public:
ComboButton(const ComboButton&) = delete;
ComboButton(ComboButton&&) = delete;
ComboButton& operator=(const ComboButton&) = delete;
ComboButton& operator=(ComboButton&&) = delete;
ComboButton(Widget* parent);
Button& set_text(const std::string& text) override;
bool handle_mouse_button_event(const MouseButtonEvent& e) override;
ComboButton& set_combos(std::span<ComboPair> combos);
ComboButton& set_index(int index);
private:
void update_text();
std::vector<std::string> m_suffix;
std::vector<std::function<void()>> m_funs;
std::string m_text;
int m_index = 0;
int m_sum = 0;
};
} // namespace Cubed

View File

@@ -0,0 +1,17 @@
#pragma once
#include "Cubed/ui/ui_manager.hpp"
namespace Cubed {
class CreditsScene;
class CreditsUI : public UIManager {
public:
CreditsUI(CreditsScene& m_scene);
void init() override;
private:
bool handle_key_event(const KeyEvent& e) override;
CreditsScene& m_scene;
};
} // namespace Cubed

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Cubed/ui/label.hpp"
#include "Cubed/ui/ui_manager.hpp"
namespace Cubed {
class WorldScene;
class ErrorUI : public UIManager {
public:
ErrorUI(WorldScene& scene);
void init() override;
void on_re_enter();
void set_error(std::string_view error);
bool has_error() const;
void clear_error();
private:
bool handle_key_event(const KeyEvent& e) override;
WorldScene& m_scene;
bool m_has_error = false;
std::string m_error_str;
Label* m_error_label = nullptr;
};
} // namespace Cubed

View File

@@ -0,0 +1,20 @@
#pragma once
#include "Cubed/ui/label.hpp"
#include "Cubed/ui/ui_manager.hpp"
namespace Cubed {
class HostGameScene;
class HostGameUI : public UIManager {
public:
HostGameUI(HostGameScene& scene);
void init() override;
void on_re_enter();
private:
HostGameScene& m_scene;
Label* m_error_label;
void set_error(std::string_view error);
void clear_error();
};
} // namespace Cubed

View File

@@ -0,0 +1,40 @@
#pragma once
#include "Cubed/render/texture.hpp"
#include "Cubed/ui/widget.hpp"
#include "glm/ext/vector_float2.hpp"
namespace Cubed {
class TextureManager;
class Image : public Widget {
public:
Image(const Image&) = delete;
Image(Image&&) = delete;
Image& operator=(const Image&) = delete;
Image& operator=(Image&&) = delete;
Image(Widget* parent);
Image& set_image(const std::string& path, TextureManager& texture_manager);
float width() const override;
float height() const override;
const Texture* texture() const;
Image& set_scale(float scale);
float scale() const;
Image& set_height(float height);
Image& set_width(float width);
Image& set_fill(bool fill);
private:
void on_render(Renderer& renderer) override;
void on_update(float dt) override;
float m_width = 0.0f;
float m_height = 0.0f;
bool m_fill = false;
const Texture* m_texture = nullptr;
glm::vec2 m_pos{0.0f, 0.0f};
float m_scale = 1.0f;
};
} // namespace Cubed

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Cubed/ui/label.hpp"
#include "Cubed/ui/ui_manager.hpp"
namespace Cubed {
class JoinGameScene;
class JoinGameUI : public UIManager {
public:
JoinGameUI(const JoinGameUI&) = delete;
JoinGameUI(JoinGameUI&&) = delete;
JoinGameUI& operator=(const JoinGameUI&) = delete;
JoinGameUI& operator=(JoinGameUI&&) = delete;
JoinGameUI(JoinGameScene& m_scene);
void init() override;
void on_re_enter();
private:
JoinGameScene& m_scene;
Label* m_error_label;
void set_error(std::string_view error);
void clear_error();
};
} // namespace Cubed

View File

@@ -0,0 +1,49 @@
#pragma once
#include "Cubed/ui/text.hpp"
#include "Cubed/ui/ui_vertex_data.hpp"
#include "Cubed/ui/widget.hpp"
namespace Cubed {
class Label : public Widget {
public:
Label(const Label&) = delete;
Label(Label&&) = delete;
Label& operator=(const Label&) = delete;
Label& operator=(Label&&) = delete;
Label(const std::string& id, Widget* parent);
Label(Widget* parent);
virtual ~Label() = default;
Label& set_text(std::string_view text);
Label& set_color(Color color);
Label& set_scale(float scale);
const UIVertexData& data() const;
const TextStyle& text_style() const;
float width() const override;
float height() const override;
float real_width() const;
float real_height() const;
float offset_x() const;
float offset_y() const;
float scale() const;
const std::string& text() const;
protected:
virtual void on_update(float dt) override;
virtual void on_render(Renderer& renderer) override;
private:
TextStyle m_text;
UIVertexData m_data;
float m_real_width = 0.0f;
float m_real_height = 0.0f;
float m_offset_x = 0.0f;
float m_offset_y = 0.0f;
float m_scale = 1.0f;
void update_vertices();
};
} // namespace Cubed

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Cubed/ui/button.hpp"
#include "Cubed/ui/ui_manager.hpp"
namespace Cubed {
class Renderer;
class MainMenuScene;
class MainMenuUIManager : public UIManager {
public:
MainMenuUIManager(MainMenuScene& m_scene);
MainMenuUIManager(const MainMenuUIManager&) = delete;
MainMenuUIManager(MainMenuUIManager&&) = delete;
MainMenuUIManager& operator=(const MainMenuUIManager&) = delete;
MainMenuUIManager& operator=(MainMenuUIManager&&) = delete;
~MainMenuUIManager();
void init() override;
void on_re_enter();
private:
MainMenuScene& m_scene;
std::vector<Button*> m_pending_enable;
};
} // namespace Cubed

View File

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/ui/button.hpp"
#include "Cubed/ui/ui_manager.hpp"
namespace Cubed {
class WorldScene;
class PauseMenuUIManager : public UIManager {
public:
PauseMenuUIManager(const PauseMenuUIManager&) = delete;
PauseMenuUIManager(PauseMenuUIManager&&) = delete;
PauseMenuUIManager& operator=(const PauseMenuUIManager&) = delete;
PauseMenuUIManager& operator=(PauseMenuUIManager&&) = delete;
PauseMenuUIManager(WorldScene& scene);
void init() override;
void on_re_enter();
private:
WorldScene& m_scene;
std::vector<Button*> m_pending_enable;
};
} // namespace Cubed

41
include/Cubed/ui/rect.hpp Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include "Cubed/ui/color.hpp"
#include "Cubed/ui/widget.hpp"
namespace Cubed {
class Rect : public Widget {
public:
Rect(const Rect&) = delete;
Rect(Rect&&) = delete;
Rect& operator=(const Rect&) = delete;
Rect& operator=(Rect&&) = delete;
Rect(Widget* parent);
~Rect();
float width() const override;
float height() const override;
float alpha() const;
Rect& set_width(float width);
Rect& set_height(float height);
Rect& set_fill(bool fill);
Rect& set_scale(float scale);
Rect& set_color(Color color);
Rect& set_alpha(float alpha);
Color color() const;
private:
void on_update(float dt) override;
void on_render(Renderer& renderer) override;
Color m_color = Color::WHITE;
float m_width = 0.0f;
float m_height = 0.0f;
float m_scale = 1.0f;
float m_alpha = 1.0f;
bool m_fill = false;
};
} // namespace Cubed

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Cubed/ui/ui_manager.hpp"
namespace Cubed {
class SettingsScene;
class SettingsUI : public UIManager {
public:
SettingsUI(SettingsScene& scene);
void init() override;
private:
bool handle_key_event(const KeyEvent& e) override;
SettingsScene& m_scene;
};
} // namespace Cubed

View File

@@ -0,0 +1,72 @@
#pragma once
#include "Cubed/ui/image.hpp"
#include "Cubed/ui/label.hpp"
#include "Cubed/ui/widget.hpp"
namespace Cubed {
enum class ValueType {
FLOAT,
INT,
};
class Slider : public Widget {
public:
Slider(Widget* parent);
Slider& set_slider(float* value, const float& min, const float& max);
Slider& set_slider(int* value, const int& min, const int& max);
float width() const override;
float height() const override;
Slider& set_scale(float scale);
Slider& set_width(float width);
Slider& set_height(float h);
Slider& set_text(const std::string& text);
Slider& set_track_image(const std::string& path,
TextureManager& texture_manager);
Slider& set_thumb_image(const std::string& path,
TextureManager& texture_manager);
Slider& set_default_image(TextureManager& texture_manager);
Slider& set_auto_scale(bool auto_scale);
bool handle_mouse_move_event(const MouseMoveEvent& e) override;
bool handle_mouse_button_event(const MouseButtonEvent& e) override;
private:
static constexpr float PADDING = 5.0f;
static constexpr float TEXT_SCALE = 0.6f;
static constexpr float DEFAULT_SCALE = 3.0f;
static constexpr const char* DEFAULT_TRACK_IMAGE =
"texture/ui/slider_track001.png";
static constexpr const char* DEFAULT_THUMB_IMAGE =
"texture/ui/slider_thumb001.png";
std::unique_ptr<Image> m_track;
std::unique_ptr<Image> m_thumb;
std::unique_ptr<Label> m_label;
ValueType m_type = ValueType::FLOAT;
float m_scale = DEFAULT_SCALE;
float m_width = NORMAL_SLIDER_WIDTH;
float m_height = NORMAL_SLIDER_HEIGHT;
float m_xpos = 0.0f;
bool m_dragging = false;
bool m_inside = false;
bool m_auto_scale = false;
float* m_float_value = nullptr;
int* m_int_value = nullptr;
float m_min = 0;
float m_max = 0;
std::string m_text;
void init_track();
void init_thumb();
void update_value(float mouse_x);
void on_update(float) override;
void on_render(Renderer& renderer) override;
void update_text_scale();
};
} // namespace Cubed

View File

@@ -1,8 +1,5 @@
#pragma once
#include "Cubed/primitive_data.hpp"
#include "Cubed/render/vertex_array.hpp"
#include "Cubed/render/vertex_buffer.hpp"
#include "Cubed/ui/color.hpp"
#include <glad/glad.h>
@@ -10,46 +7,9 @@
#include <string>
namespace Cubed {
class Shader;
class Text {
public:
explicit Text(std::string_view name);
Text(std::string_view name, std::string_view str,
glm::vec2 pos = glm::vec2{0.0f, 0.0f}, Color color = Color::BLACK);
~Text();
Text(const Text&) = delete;
Text(Text&&) noexcept;
Text& operator=(const Text&) = delete;
Text& operator=(Text&&) noexcept = delete;
Text& color(Color color);
// Text& color(const glm::vec4& color, int pos);
Text& position(float x, float y);
Text& scale(float s);
Text& text(std::string_view str);
std::size_t uuid() const;
void render(const Shader& shader);
bool operator==(const Text& other) const;
private:
float m_scale = 1.0f;
glm::vec2 m_pos{0.0f, 0.0f};
const std::string NAME;
const std::size_t UUID;
std::string m_text;
glm::vec4 m_color{1.0f, 1.0f, 1.0f, 1.0f};
glm::mat4 m_model_matrix;
std::vector<Vertex2D> m_vertices;
std::unique_ptr<VertexBuffer> m_vbo;
std::unique_ptr<VertexArray> m_vao;
void update_vertices();
void upload_to_gpu();
struct TextStyle {
std::string text;
Color color = Color::WHITE;
};
} // namespace Cubed

View File

@@ -0,0 +1,73 @@
#pragma once
#include "Cubed/ui/image.hpp"
#include "Cubed/ui/label.hpp"
#include "Cubed/ui/rect.hpp"
#include "Cubed/ui/widget.hpp"
namespace Cubed {
class App;
class TextField : public Widget {
public:
TextField(const TextField&) = delete;
TextField(TextField&&) = delete;
TextField& operator=(const TextField&) = delete;
TextField& operator=(TextField&&) = delete;
TextField(Widget* parent);
TextField& set_scale(float scale);
float width() const override;
float height() const override;
TextField& set_width(float width);
TextField& set_height(float height);
TextField& set_show_text(const std::string& text);
TextField& set_background_image(const std::string& path,
TextureManager& texture_manager);
TextField& set_default_image(TextureManager& texture_manager);
TextField& set_auto_scale(bool auto_scale);
TextField& set_app(App* app);
bool handle_mouse_move_event(const MouseMoveEvent& e) override;
bool handle_mouse_button_event(const MouseButtonEvent& e) override;
bool handle_text_input_event(const TextInputEvent& e) override;
bool handle_key_event(const KeyEvent& e) override;
const std::string& input_text() const;
template <typename F> TextField& set_on_finish(F&& f) {
m_on_finished = std::forward<F>(f);
return *this;
}
private:
static constexpr float PADDING = 5.0f;
static constexpr float DEFAULT_SCALE = 3.0f;
static constexpr float TEXT_SCALE = 0.6f;
static constexpr float CURSOR_INTERVAL = 0.5f;
static constexpr const char* DEFAULT_TEXT_FIELD_IMAGE =
"texture/ui/textfield001.png";
App* m_app;
std::unique_ptr<Image> m_background;
std::unique_ptr<Label> m_foreground;
std::unique_ptr<Rect> m_cursor;
float m_cursor_timer = 0.0f;
bool m_cursor_visible = true;
bool m_inside = false;
bool m_typing = false;
bool m_auto_scale = false;
bool m_ctrl_press = false;
float m_width = NORMAL_TEXTFIELD_WIDTH;
float m_height = NORMAL_TEXTFIELD_HEIGHT;
float m_scale = DEFAULT_SCALE;
std::string m_input_text;
std::string m_show_text;
std::function<void()> m_on_finished;
void update_text_scale();
void on_render(Renderer& renderer) override;
void on_update(float dt) override;
void update_show_text();
};
} // namespace Cubed

View File

@@ -0,0 +1,35 @@
#pragma once
#include "Cubed/ui/widget.hpp"
#include <memory>
namespace Cubed {
class UIManager {
public:
UIManager() = default;
UIManager(const UIManager&) = delete;
UIManager(UIManager&&) = delete;
UIManager& operator=(const UIManager&) = delete;
UIManager& operator=(UIManager&&) = delete;
virtual ~UIManager();
virtual void init();
virtual void update(float dt);
virtual void render(Renderer& renderer);
virtual bool handle_event(const Event& e);
virtual Widget* get_widget(std::string name);
virtual bool handle_mouse_move_event(const MouseMoveEvent& e);
virtual bool handle_mouse_button_event(const MouseButtonEvent& e);
virtual bool handle_window_resize_event(const WindowResizeEvent& e);
virtual bool handle_mouse_wheel_event(const MouseWheelEvent& e);
virtual bool handle_key_event(const KeyEvent& e);
virtual bool handle_text_input_event(const TextInputEvent& e);
protected:
std::unique_ptr<Widget> m_root_widget;
std::unordered_map<std::string, Widget*> m_widgets;
};
} // namespace Cubed

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Cubed/primitive_data.hpp"
#include "Cubed/render/vertex_array.hpp"
#include "Cubed/render/vertex_buffer.hpp"
#include <atomic>
#include <memory>
#include <vector>
namespace Cubed {
struct UIVertexData {
std::vector<Vertex2D> m_vertices;
std::unique_ptr<VertexBuffer> m_vbo;
std::unique_ptr<VertexArray> m_vao;
std::atomic<std::size_t> m_sum{0};
UIVertexData();
~UIVertexData();
UIVertexData(const UIVertexData&) = delete;
UIVertexData(UIVertexData&&) noexcept;
UIVertexData& operator=(const UIVertexData&) = delete;
UIVertexData& operator=(UIVertexData&&) noexcept;
void upload();
void update_sum();
};
} // namespace Cubed

View File

@@ -0,0 +1,82 @@
#pragma once
#include "Cubed/input/event.hpp"
#include "Cubed/ui/anchor.hpp"
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace Cubed {
class Renderer;
constexpr float NORMAL_BUTTON_WIDTH = 225.0f;
constexpr float NORMAL_BUTTON_HEIGHT = 20.0f;
constexpr float NORMAL_SLIDER_WIDTH = 225.0f;
constexpr float NORMAL_SLIDER_HEIGHT = 20.0f;
constexpr float NORMAL_TEXTFIELD_WIDTH = 225.0f;
constexpr float NORMAL_TEXTFIELD_HEIGHT = 20.0f;
enum class TraversalOrder { FRONT_TO_BACK, BACK_TO_FRONT };
class Widget {
public:
Widget(const Widget&) = delete;
Widget(Widget&&) = delete;
Widget& operator=(const Widget&) = delete;
Widget& operator=(Widget&&) = delete;
Widget(const std::string& id, Widget* parent);
Widget(Widget* parent);
virtual ~Widget() = default;
virtual void update(float dt);
virtual void render(Renderer& renderer);
virtual const std::string& id() const;
virtual Widget& set_anchor(Anchor anchor);
virtual Widget& set_offset(glm::ivec2 offset);
virtual Widget& set_window_size(int width, int height);
virtual Widget& set_visible(bool visible);
// Returns the final display size
virtual float width() const = 0;
virtual float height() const = 0;
virtual glm::vec2 pos() const;
virtual bool handle_key_event(const KeyEvent& e);
virtual bool handle_mouse_button_event(const MouseButtonEvent& e);
virtual bool handle_mouse_wheel_event(const MouseWheelEvent& e);
virtual bool handle_window_resize_event(const WindowResizeEvent& e);
virtual bool handle_mouse_move_event(const MouseMoveEvent& e);
virtual bool handle_text_input_event(const TextInputEvent& e);
void set_order(TraversalOrder order);
template <typename T, typename... Args> T& add_child(Args&&... args) {
auto widget = std::make_unique<T>(std::forward<Args>(args)..., this);
T& ref = *widget;
m_children.emplace_back(std::move(widget));
return ref;
};
protected:
Widget* m_parent = nullptr;
std::string m_id;
float m_window_height = 0;
float m_window_width = 0;
// Center is at the top-left corner, position is at the top-left corner
Anchor m_anchor = Anchor::TOP_LEFT;
bool m_visible = true;
glm::ivec2 m_offset{0, 0};
std::vector<std::unique_ptr<Widget>>& children();
const std::vector<std::unique_ptr<Widget>>& children() const;
virtual void on_update(float dt);
virtual void on_render(Renderer& renderer);
virtual glm::vec2 compute_position() const;
private:
std::vector<std::unique_ptr<Widget>> m_children;
TraversalOrder m_order = TraversalOrder::BACK_TO_FRONT;
};
} // namespace Cubed

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Cubed/ui/ui_manager.hpp"
#include "Cubed/ui/widget.hpp"
namespace Cubed {
class WorldScene;
class WorldUIManager : public UIManager {
public:
WorldUIManager(const WorldUIManager&) = delete;
WorldUIManager(WorldUIManager&&) = delete;
WorldUIManager& operator=(const WorldUIManager&) = delete;
WorldUIManager& operator=(WorldUIManager&&) = delete;
WorldUIManager(WorldScene& scene);
~WorldUIManager();
void init() override;
void render(Renderer& renderer) override;
private:
WorldScene& m_scene;
};
} // namespace Cubed