complete rework of the user interface (using raygui)

This commit is contained in:
2026-02-27 02:58:35 +01:00
parent bd1bd79825
commit 2517a9d33b
20 changed files with 1781 additions and 586 deletions

View File

@ -7,43 +7,24 @@
#include <raymath.h>
class OrbitCamera3D {
friend class Renderer;
public:
Vector3 position = Vector3Zero();
Vector3 target = Vector3Zero();
float distance = CAMERA_DISTANCE;
float fov = CAMERA_FOV;
CameraProjection projection = CAMERA_PERSPECTIVE;
float angle_x = 0.0;
float angle_y = 0.0;
private:
Camera camera;
Vector3 position;
Vector3 target;
float distance;
float angle_x;
float angle_y;
// Input
Vector2 last_mouse;
bool rotating;
bool panning;
bool target_lock;
Camera camera = Camera{Vector3(0, 0, -distance), target, Vector3(0, 1.0, 0),
fov, projection};
public:
OrbitCamera3D()
: camera(Camera(Vector3Zero(), Vector3Zero(), Vector3Zero(), 0.0, 0)),
position(Vector3Zero()), target(Vector3Zero()),
distance(CAMERA_DISTANCE), angle_x(0.0), angle_y(0.0),
last_mouse(Vector2Zero()), rotating(false), panning(false),
target_lock(true) {
camera.position = Vector3(0, 0, -1.0 * distance);
camera.target = target;
camera.up = Vector3(0, 1.0, 0);
camera.fovy = CAMERA_FOV;
camera.projection = CAMERA_PERSPECTIVE;
}
auto Rotate(Vector2 last_mouse, Vector2 mouse) -> void;
~OrbitCamera3D() {}
auto Pan(Vector2 last_mouse, Vector2 mouse) -> void;
public:
auto HandleCameraInput() -> Vector2;
auto Update(const Vector3 &current_target) -> void;
auto Update(const Vector3 &current_target, bool lock) -> void;
};
#endif

View File

@ -10,15 +10,17 @@
// #define TRACY // Enable tracy profiling support
// Window
constexpr int INITIAL_WIDTH = 800;
constexpr int INITIAL_HEIGHT = 800;
constexpr int MENU_HEIGHT = 250;
constexpr int INITIAL_WIDTH = 600;
constexpr int INITIAL_HEIGHT = 600;
constexpr int MENU_HEIGHT = 350;
// Menu
constexpr int MENU_PAD = 5;
constexpr int BUTTON_PAD = 12;
constexpr int MENU_ROWS = 5;
constexpr int MENU_ROWS = 7;
constexpr int MENU_COLS = 3;
constexpr const char *FONT = "fonts/SpaceMono.ttf";
constexpr int FONT_SIZE = 26;
// Camera Controls
constexpr float CAMERA_FOV = 120.0;
@ -26,6 +28,7 @@ constexpr float CAMERA_DISTANCE = 20.0;
constexpr float MIN_CAMERA_DISTANCE = 2.0;
constexpr float MAX_CAMERA_DISTANCE = 2000.0;
constexpr float ZOOM_SPEED = 2.5;
constexpr float FOV_SPEED = 1.0;
constexpr float ZOOM_MULTIPLIER = 4.0;
constexpr float PAN_SPEED = 2.0;
constexpr float PAN_MULTIPLIER = 10.0;
@ -52,13 +55,13 @@ constexpr Color EDGE_COLOR = DARKGREEN;
constexpr int DRAW_VERTICES_LIMIT = 1000000;
// Klotski Drawing
constexpr int BOARD_PADDING = 5;
constexpr int BLOCK_PADDING = 5;
constexpr Color BLOCK_COLOR = DARKGREEN;
constexpr Color HL_BLOCK_COLOR = GREEN;
constexpr int BOARD_PADDING = 10;
constexpr Color BOARD_COLOR_WON = DARKGREEN;
constexpr Color BOARD_COLOR_RESTRICTED = GRAY;
constexpr Color BOARD_COLOR_FREE = RAYWHITE;
constexpr Color BLOCK_COLOR = DARKBLUE;
constexpr Color TARGET_BLOCK_COLOR = RED;
constexpr Color HL_TARGET_BLOCK_COLOR = ORANGE;
constexpr Color WALL_COLOR = BLACK;
constexpr Color HL_WALL_COLOR = DARKGRAY;
constexpr Color GOAL_COLOR = ORANGE;
#endif

190
include/gui.hpp Normal file
View File

@ -0,0 +1,190 @@
#ifndef __GUI_HPP_
#define __GUI_HPP_
#include "camera.hpp"
#include "config.hpp"
#include "input.hpp"
#include "state.hpp"
#include <raygui.h>
#include <raylib.h>
class Grid {
public:
int x;
int y;
int width;
int height;
int columns;
int rows;
const int padding;
public:
Grid(int _x, int _y, int _width, int _height, int _columns, int _rows,
int _padding)
: x(_x), y(_y), width(_width), height(_height), columns(_columns),
rows(_rows), padding(_padding) {}
public:
auto UpdateBounds(int _x, int _y, int _width, int _height, int _columns,
int _rows) -> void;
auto UpdateBounds(int _x, int _y, int _width, int _height) -> void;
auto UpdateBounds(int _x, int _y) -> void;
auto Bounds() const -> Rectangle;
auto Bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
auto SquareBounds() const -> Rectangle;
auto SquareBounds(int _x, int _y, int _width, int _height) const -> Rectangle;
};
class Gui {
struct Style {
int border_color_normal;
int base_color_normal;
int text_color_normal;
int border_color_focused;
int base_color_focused;
int text_color_focused;
int border_color_pressed;
int base_color_pressed;
int text_color_pressed;
int border_color_disabled;
int base_color_disabled;
int text_color_disabled;
};
struct DefaultStyle : Style {
int background_color;
int line_color;
int text_size;
int text_spacing;
int text_line_spacing;
int text_alignment_vertical;
int text_wrap_mode;
};
struct ComponentStyle : Style {
int border_width;
int text_padding;
int text_alignment;
};
private:
InputHandler &input;
StateManager &state;
const OrbitCamera3D &camera;
Grid menu_grid =
Grid(0, 0, GetScreenWidth(), MENU_HEIGHT, MENU_COLS, MENU_ROWS, MENU_PAD);
Grid board_grid = Grid(
0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
state.current_state.width, state.current_state.height, BOARD_PADDING);
Grid graph_overlay_grid =
Grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD);
bool save_window = false;
std::array<char, 256> preset_name = {0};
bool help_window = false;
public:
Gui(InputHandler &_input, StateManager &_state, const OrbitCamera3D &_camera)
: input(_input), state(_state), camera(_camera) {
Init();
}
Gui(const Gui &copy) = delete;
Gui &operator=(const Gui &copy) = delete;
Gui(Gui &&move) = delete;
Gui &operator=(Gui &&move) = delete;
private:
auto Init() const -> void;
auto ApplyColor(Style &style, Color color) const -> void;
auto ApplyBlockColor(Style &style, Color color) const -> void;
auto ApplyTextColor(Style &style, Color color) const -> void;
auto GetDefaultStyle() const -> DefaultStyle;
auto SetDefaultStyle(const DefaultStyle &style) const -> void;
auto GetComponentStyle(int component) const -> ComponentStyle;
auto SetComponentStyle(int component, const ComponentStyle &style) const
-> void;
auto DrawButton(Rectangle bounds, const std::string &label, Color color,
bool enabled = true, int font_size = FONT_SIZE) const -> int;
auto DrawMenuButton(int x, int y, int width, int height,
const std::string &label, Color color,
bool enabled = true, int font_size = FONT_SIZE) const
-> int;
auto DrawToggleSlider(Rectangle bounds, const std::string &off_label,
const std::string &on_label, int *active, Color color,
bool enabled = true, int font_size = FONT_SIZE) const
-> int;
auto DrawMenuToggleSlider(int x, int y, int width, int height,
const std::string &off_label,
const std::string &on_label, int *active,
Color color, bool enabled = true,
int font_size = FONT_SIZE) const -> int;
auto DrawSpinner(Rectangle bounds, const std::string &label, int *value,
int min, int max, Color color, bool enabled = true,
int font_size = FONT_SIZE) const -> int;
auto DrawMenuSpinner(int x, int y, int width, int height,
const std::string &label, int *value, int min, int max,
Color color, bool enabled = true,
int font_size = FONT_SIZE) const -> int;
auto DrawLabel(Rectangle bounds, const std::string &text, Color color,
bool enabled = true, int font_size = FONT_SIZE) const -> int;
auto DrawBoardBlock(int x, int y, int width, int height, Color color,
bool enabled = true) const -> bool;
auto WindowOpen() const -> bool;
// Different menu sections
auto DrawMenuHeader(Color color) const -> void;
auto DrawGraphInfo(Color color) const -> void;
auto DrawGraphControls(Color color) const -> void;
auto DrawCameraControls(Color color) const -> void;
auto DrawPuzzleControls(Color color) const -> void;
auto DrawEditControls(Color color) const -> void;
auto DrawMenuFooter(Color color) -> void;
public:
auto GetBackgroundColor() const -> Color;
auto HelpPopup() -> void;
auto DrawSavePresetPopup() -> void;
auto DrawMainMenu() -> void;
auto DrawPuzzleBoard() -> void;
auto DrawGraphOverlay(int fps, int ups) -> void;
auto Update() -> void;
};
#endif

View File

@ -1,31 +1,71 @@
#ifndef __INPUT_HPP_
#define __INPUT_HPP_
#include "camera.hpp"
#include "config.hpp"
#include "state.hpp"
#include <functional>
#include <raylib.h>
#include <raymath.h>
class InputHandler {
struct GenericHandler {
std::function<void(InputHandler &)> handler;
};
struct MouseHandler : GenericHandler {
MouseButton button;
};
struct KeyboardHandler : GenericHandler {
KeyboardKey key;
};
private:
std::vector<GenericHandler> generic_handlers;
std::vector<MouseHandler> mouse_pressed_handlers;
std::vector<MouseHandler> mouse_released_handlers;
std::vector<KeyboardHandler> key_pressed_handlers;
std::vector<KeyboardHandler> key_released_handlers;
public:
StateManager &state;
OrbitCamera3D &camera;
int hov_x;
int hov_y;
int sel_x;
int sel_y;
bool disable = false;
bool has_block_add_xy;
int block_add_x;
int block_add_y;
// Block selection
int hov_x = -1;
int hov_y = -1;
int sel_x = 0;
int sel_y = 0;
bool mark_path;
bool mark_solutions;
bool connect_solutions;
// Editing
bool editing = false;
bool has_block_add_xy = false;
int block_add_x = -1;
int block_add_y = -1;
// Graph display
bool mark_path = false;
bool mark_solutions = false;
bool connect_solutions = false;
// Camera
bool camera_lock = true;
bool camera_panning = false;
bool camera_rotating = false;
// Mouse dragging
Vector2 mouse = Vector2Zero();
Vector2 last_mouse = Vector2Zero();
public:
InputHandler(StateManager &_state)
: state(_state), hov_x(-1), hov_y(-1), sel_x(0), sel_y(0),
has_block_add_xy(false), block_add_x(-1), block_add_y(-1),
mark_path(false), mark_solutions(false), connect_solutions(false) {}
InputHandler(StateManager &_state, OrbitCamera3D &_camera)
: state(_state), camera(_camera) {
InitHandlers();
}
InputHandler(const InputHandler &copy) = delete;
InputHandler &operator=(const InputHandler &copy) = delete;
@ -34,12 +74,81 @@ public:
~InputHandler() {}
private:
auto InitHandlers() -> void;
public:
auto HandleMouseHover() -> void;
// Helpers
auto MouseInMenuPane() -> bool;
auto MouseInBoardPane() -> bool;
auto MouseInGraphPane() -> bool;
auto HandleMouse() -> void;
// Mouse actions
auto MouseHover() -> void;
auto CameraStartPan() -> void;
auto CameraPan() -> void;
auto CameraStopPan() -> void;
auto CameraStartRotate() -> void;
auto CameraRotate() -> void;
auto CameraStopRotate() -> void;
auto CameraZoom() -> void;
auto CameraFov() -> void;
auto SelectBlock() -> void;
auto StartAddBlock() -> void;
auto ClearAddBlock() -> void;
auto AddBlock() -> void;
auto RemoveBlock() -> void;
auto PlaceGoal() -> void;
auto HandleKeys() -> void;
// Key actions
auto ToggleCameraLock() -> void;
auto ToggleCameraProjection() -> void;
auto MoveBlockNor() -> void;
auto MoveBlockWes() -> void;
auto MoveBlockSou() -> void;
auto MoveBlockEas() -> void;
auto PrintState() const -> void;
auto PreviousPreset() -> void;
auto NextPreset() -> void;
auto ResetState() -> void;
auto FillGraph() -> void;
auto ClearGraph() -> void;
auto ToggleMarkSolutions() -> void;
auto ToggleConnectSolutions() -> void;
auto ToggleMarkPath() -> void;
auto MakeOptimalMove() -> void;
auto GoToWorstState() -> void;
auto GoToNearestTarget() -> void;
auto UndoLastMove() -> void;
auto ToggleRestrictedMovement() -> void;
auto ToggleTargetBlock() -> void;
auto ToggleWallBlock() -> void;
auto RemoveBoardColumn() -> void;
auto AddBoardColumn() -> void;
auto RemoveBoardRow() -> void;
auto AddBoardRow() -> void;
auto ToggleEditing() -> void;
auto ClearGoal() -> void;
// General
auto RegisterGenericHandler(std::function<void(InputHandler &)> handler)
-> void;
auto RegisterMousePressedHandler(MouseButton button,
std::function<void(InputHandler &)> handler)
-> void;
auto RegisterMouseReleasedHandler(MouseButton button,
std::function<void(InputHandler &)> handler)
-> void;
auto RegisterKeyPressedHandler(KeyboardKey key,
std::function<void(InputHandler &)> handler)
-> void;
auto RegisterKeyReleasedHandler(KeyboardKey key,
std::function<void(InputHandler &)> handler)
-> void;
auto HandleInput() -> void;
};

View File

@ -49,8 +49,6 @@ public:
-> void;
auto CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3;
auto Print() const -> void;
};
#endif

View File

@ -7,8 +7,8 @@
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <iostream>
#include <mutex>
#include <print>
#include <queue>
#include <raylib.h>
#include <raymath.h>
@ -78,12 +78,10 @@ public:
: threads(std::thread::hardware_concurrency() - 1, SetThreadName)
#endif
{
std::cout << "Using Barnes-Hut + octree repulsion force calculation."
<< std::endl;
std::println("Using Barnes-Hut + Octree repulsion force calculation.");
#ifdef THREADPOOL
std::cout << "Thread-Pool: " << threads.get_thread_count() << " threads."
<< std::endl;
std::println("Thread-pool: {} threads.", threads.get_thread_count());
#endif
};

View File

@ -7,7 +7,7 @@
#include <cstddef>
#include <format>
#include <functional>
#include <iostream>
#include <print>
#include <string>
#include <vector>
@ -40,7 +40,7 @@ public:
: x(_x), y(_y), width(_width), height(_height), target(_target),
immovable(_immovable) {
if (_x < 0 || _x + _width >= 10 || _y < 0 || _y + _height >= 10) {
std::cerr << "Block must fit on a 9x9 board!" << std::endl;
std::println("Block must fit in a 9x9 board!");
exit(1);
}
}
@ -92,11 +92,11 @@ public:
}
if (_x < 0 || _x + width >= 10 || _y < 0 || _y + height >= 10) {
std::cerr << "Block must fit on a 9x9 board!" << std::endl;
std::println("Block must fit in a 9x9 board!");
exit(1);
}
if (block.length() != 2) {
std::cerr << "Block representation must have length [2]!" << std::endl;
std::println("Block representation must have length 2!");
exit(1);
}
}
@ -109,7 +109,7 @@ public:
bool operator!=(const Block &other) { return !(*this == other); }
public:
auto Hash() const -> int;
auto Hash() const -> std::size_t;
static auto Invalid() -> Block;
@ -186,13 +186,12 @@ public:
_height, _target_x, _target_y,
std::string(_width * _height * 2, '.'))) {
if (_width < 1 || _width > 9 || _height < 1 || _height > 9) {
std::cerr << "State width/height must be in [1, 9]!" << std::endl;
std::println("State width/height must be in [1, 9]!");
exit(1);
}
if (_target_x < 0 || _target_x >= 9 || _target_y < 0 || _target_y >= 9) {
if (_target_x != 9 && _target_y != 9) {
std::cerr << "State target must be within the board bounds!"
<< std::endl;
std::println("State target must be within the board bounds!");
exit(1);
}
}
@ -210,20 +209,19 @@ public:
target_y(std::stoi(_state.substr(4, 1))),
restricted(_state.substr(0, 1) == "R"), state(_state) {
if (width < 1 || width > 9 || height < 1 || height > 9) {
std::cerr << "State width/height must be in [1, 9]!" << std::endl;
std::println("State width/height must be in [1, 9]!");
exit(1);
}
if (target_x < 0 || target_x >= 9 || target_y < 0 || target_y >= 9) {
if (target_x != 9 && target_y != 9) {
std::cerr << "State target must be within the board bounds!"
<< std::endl;
std::println("State target must be within the board bounds!");
exit(1);
}
}
if (static_cast<int>(_state.length()) != width * height * 2 + prefix) {
std::cerr
<< "State representation must have length [width * height * 2 + "
<< prefix << "]!" << std::endl;
std::println(
"State representation must have length width * height * 2 + {}!",
prefix);
exit(1);
}
}
@ -243,7 +241,7 @@ public:
BlockIterator end() const { return BlockIterator(*this, width * height); }
public:
auto Hash() const -> int;
auto Hash() const -> std::size_t;
auto HasWinCondition() const -> bool;
@ -251,6 +249,8 @@ public:
auto SetGoal(int x, int y) -> bool;
auto ClearGoal() -> void;
auto AddColumn() const -> State;
auto RemoveColumn() const -> State;

View File

@ -3,6 +3,7 @@
#include "camera.hpp"
#include "config.hpp"
#include "gui.hpp"
#include "input.hpp"
#include "state.hpp"
@ -13,6 +14,7 @@ class Renderer {
private:
const StateManager &state;
const InputHandler &input;
Gui &gui;
const OrbitCamera3D &camera;
RenderTexture render_target;
@ -21,16 +23,15 @@ private:
// Instancing
Material vertex_mat;
std::size_t transforms_size;
Matrix *transforms;
std::size_t transforms_size = 0;
Matrix *transforms = nullptr;
Mesh cube_instance;
Shader instancing_shader;
public:
Renderer(const OrbitCamera3D &_camera, const StateManager &_state,
const InputHandler &_input)
: state(_state), input(_input), camera(_camera), transforms_size(0),
transforms(nullptr) {
const InputHandler &_input, Gui &_gui)
: state(_state), input(_input), gui(_gui), camera(_camera) {
render_target = LoadRenderTexture(GetScreenWidth() / 2.0,
GetScreenHeight() - MENU_HEIGHT);
klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0,
@ -73,7 +74,7 @@ public:
auto DrawMenu(const std::vector<Vector3> &masses) -> void;
auto DrawTextures(float ups) -> void;
auto DrawTextures(int fps, int ups) -> void;
};
#endif

View File

@ -15,17 +15,17 @@ class StateManager {
public:
ThreadedPhysics &physics;
std::vector<State> presets;
std::vector<std::string> comments;
std::vector<State> presets = {State()};
std::vector<std::string> comments = {"Empty"};
// Some stuff is faster to map from state to mass (e.g. in the renderer)
std::unordered_map<State, std::size_t> states;
std::unordered_map<State, std::size_t> states; // State to mass id
std::unordered_set<State> winning_states;
std::unordered_set<State> visited_states;
std::unordered_map<State, int> visited_states; // How often we've been here
std::stack<State> history;
// Other stuff maps from mass to state :/
std::unordered_map<std::size_t, State> masses;
std::unordered_map<std::size_t, State> masses; // Mass id to state
std::vector<std::size_t> winning_path;
// Fuck it, duplicate the springs too, we don't even need to copy them from
@ -36,7 +36,10 @@ public:
// path on the same graph
DistanceResult target_distances;
int current_preset;
std::string preset_file;
int total_moves = 0;
int current_preset = 0;
State starting_state;
State current_state;
State previous_state;
@ -45,8 +48,7 @@ public:
public:
StateManager(ThreadedPhysics &_physics, const std::string &preset_file)
: physics(_physics), presets({State()}), current_preset(0),
edited(false) {
: physics(_physics) {
ParsePresetFile(preset_file);
current_state = presets.at(current_preset);
ClearGraph();
@ -60,9 +62,11 @@ public:
~StateManager() {}
private:
auto ParsePresetFile(const std::string &preset_file) -> void;
auto ParsePresetFile(const std::string &_preset_file) -> bool;
public:
auto AppendPresetFile(const std::string preset_name) -> void;
auto LoadPreset(int preset) -> void;
auto ResetState() -> void;