add popups to certain user actions

This commit is contained in:
2026-02-28 21:29:57 +01:00
parent bc8dd423be
commit 809fbf1b93
20 changed files with 513 additions and 251 deletions

View File

@ -24,7 +24,8 @@ public:
auto pan(Vector2 last_mouse, Vector2 mouse) -> void;
auto update(const Vector3& current_target, const Vector3& mass_center, bool lock, bool mass_center_lock) -> void;
auto update(const Vector3& current_target, const Vector3& mass_center, bool lock,
bool mass_center_lock) -> void;
};
#endif

View File

@ -5,9 +5,28 @@
#include "state_manager.hpp"
#include <functional>
#include <queue>
#include <raylib.h>
#include <raymath.h>
struct show_ok_message
{
std::string title;
std::string message;
};
struct show_yes_no_message
{
std::string title;
std::string message;
std::function<void(void)> on_yes;
};
struct show_save_preset_window
{};
using ui_command = std::variant<show_ok_message, show_yes_no_message, show_save_preset_window>;
class input_handler
{
struct generic_handler
@ -36,6 +55,8 @@ public:
state_manager& state;
orbit_camera& camera;
std::queue<ui_command> ui_commands;
bool disable = false;
// Block selection
@ -115,7 +136,7 @@ public:
auto load_next_preset() -> void;
auto goto_starting_state() -> void;
auto populate_graph() const -> void;
auto clear_graph() const -> void;
auto clear_graph() -> void;
auto toggle_mark_solutions() -> void;
auto toggle_connect_solutions() -> void;
auto toggle_mark_path() -> void;
@ -132,20 +153,25 @@ public:
auto add_board_row() const -> void;
auto toggle_editing() -> void;
auto clear_goal() const -> void;
auto save_preset() -> void;
// General
auto register_generic_handler(const std::function<void(input_handler&)>& handler) -> void;
auto register_mouse_pressed_handler(MouseButton button, const std::function<void(input_handler&)>& handler) -> void;
auto register_mouse_pressed_handler(MouseButton button,
const std::function<void(input_handler&)>& handler) -> void;
auto register_mouse_released_handler(MouseButton button, const std::function<void(input_handler&)>& handler)
auto register_mouse_released_handler(MouseButton button,
const std::function<void(input_handler&)>& handler)
-> void;
auto register_key_pressed_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
auto register_key_pressed_handler(KeyboardKey key,
const std::function<void(input_handler&)>& handler) -> void;
auto register_key_released_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
auto register_key_released_handler(KeyboardKey key,
const std::function<void(input_handler&)>& handler) -> void;
auto handle_input() -> void;
};
#endif
#endif

View File

@ -41,7 +41,8 @@ public:
[[nodiscard]] auto get_octant(int node_idx, const Vector3& pos) const -> int;
[[nodiscard]] auto get_child_bounds(int node_idx, int octant) const -> std::pair<Vector3, Vector3>;
[[nodiscard]] auto get_child_bounds(int node_idx, int octant) const
-> std::pair<Vector3, Vector3>;
auto insert(int node_idx, int mass_id, const Vector3& pos, float mass, int depth) -> void;

View File

@ -198,14 +198,8 @@ public:
auto clear_cmd() -> void;
auto add_mass_springs_cmd(size_t num_masses, const std::vector<std::pair<size_t, size_t>>& springs) -> void;
};
// https://en.cppreference.com/w/cpp/utility/variant/visit
template <class... Ts>
struct overloads : Ts...
{
using Ts::operator()...;
auto add_mass_springs_cmd(size_t num_masses,
const std::vector<std::pair<size_t, size_t>>& springs) -> void;
};
#endif

View File

@ -48,8 +48,8 @@ public:
bool immovable = false;
public:
block(const int _x, const int _y, const int _width, const int _height, const bool _target = false,
const bool _immovable = false)
block(const int _x, const int _y, const int _width, const int _height,
const bool _target = false, const bool _immovable = false)
: x(_x), y(_y), width(_width), height(_height), target(_target), immovable(_immovable)
{
if (_x < 0 || _x + _width > 9 || _y < 0 || _y + _height > 9) {
@ -84,7 +84,8 @@ public:
}
immovable = false;
constexpr std::array<char, 9> immovable_chars{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'};
constexpr std::array<char, 9> immovable_chars{'A', 'B', 'C', 'D', 'E',
'F', 'G', 'H', 'I'};
for (const char c : immovable_chars) {
if (b.contains(c)) {
immovable = true;
@ -151,7 +152,8 @@ private:
explicit block_iterator(const puzzle& _state) : state(_state), current_pos(0)
{}
block_iterator(const puzzle& _state, const int _current_pos) : state(_state), current_pos(_current_pos)
block_iterator(const puzzle& _state, const int _current_pos)
: state(_state), current_pos(_current_pos)
{}
auto operator*() const -> block
@ -199,7 +201,8 @@ public:
puzzle(const int w, const int h, const int tx, const int ty, const bool r)
: width(w), height(h), target_x(tx), target_y(ty), restricted(r),
state(std::format("{}{}{}{}{}{}", r ? "R" : "F", w, h, tx, ty, std::string(w * h * 2, '.')))
state(
std::format("{}{}{}{}{}{}", r ? "R" : "F", w, h, tx, ty, std::string(w * h * 2, '.')))
{
if (w < 1 || w > 9 || h < 1 || h > 9) {
errln("State width/height must be in [1, 9]!");
@ -217,8 +220,9 @@ public:
{}
explicit puzzle(const std::string& s)
: width(std::stoi(s.substr(1, 1))), height(std::stoi(s.substr(2, 1))), target_x(std::stoi(s.substr(3, 1))),
target_y(std::stoi(s.substr(4, 1))), restricted(s.substr(0, 1) == "R"), state(s)
: width(std::stoi(s.substr(1, 1))), height(std::stoi(s.substr(2, 1))),
target_x(std::stoi(s.substr(3, 1))), target_y(std::stoi(s.substr(4, 1))),
restricted(s.substr(0, 1) == "R"), state(s)
{
if (width < 1 || width > 9 || height < 1 || height > 9) {
errln("State width/height must be in [1, 9]!");
@ -271,7 +275,7 @@ public:
[[nodiscard]] auto has_win_condition() const -> bool;
[[nodiscard]] auto won() const -> bool;
[[nodiscard]] auto valid() const -> bool;
[[nodiscard]] auto valid_thorough() const -> bool;
[[nodiscard]] auto try_get_invalid_reason() const -> std::optional<std::string>;
// Repr helpers
@ -297,7 +301,8 @@ public:
// Playing
[[nodiscard]] auto try_move_block_at(int x, int y, direction dir) const -> std::optional<puzzle>;
[[nodiscard]] auto try_move_block_at(int x, int y, direction dir) const
-> std::optional<puzzle>;
// Statespace
@ -333,9 +338,11 @@ struct std::hash<std::pair<puzzle, puzzle>>
template <>
struct std::equal_to<std::pair<puzzle, puzzle>>
{
auto operator()(const std::pair<puzzle, puzzle>& a, const std::pair<puzzle, puzzle>& b) const noexcept -> bool
auto operator()(const std::pair<puzzle, puzzle>& a,
const std::pair<puzzle, puzzle>& b) const noexcept -> bool
{
return (a.first == b.first && a.second == b.second) || (a.first == b.second && a.second == b.first);
return (a.first == b.first && a.second == b.second) ||
(a.first == b.second && a.second == b.first);
}
};

View File

@ -18,8 +18,12 @@ private:
user_interface& gui;
const orbit_camera& camera;
RenderTexture render_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
RenderTexture klotski_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
RenderTexture render_target =
LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
// TODO: Those should be moved to the user_interface.h
RenderTexture klotski_target =
LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
RenderTexture menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT);
// Batching
@ -31,7 +35,8 @@ private:
std::vector<Color> colors;
Material vertex_mat = LoadMaterialDefault();
Mesh cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
Shader instancing_shader = LoadShader("shader/instancing_vertex.glsl", "shader/instancing_fragment.glsl");
Shader instancing_shader =
LoadShader("shader/instancing_vertex.glsl", "shader/instancing_fragment.glsl");
unsigned int color_vbo_id = 0;
@ -43,10 +48,13 @@ public:
instancing_shader.locs[SHADER_LOC_MATRIX_MVP] = GetShaderLocation(instancing_shader, "mvp");
instancing_shader.locs[SHADER_LOC_MATRIX_MODEL] =
GetShaderLocationAttrib(instancing_shader, "instanceTransform");
instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(instancing_shader, "viewPos");
instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] =
GetShaderLocation(instancing_shader, "viewPos");
infoln("LOC vertexPosition: {}", rlGetLocationAttrib(instancing_shader.id, "vertexPosition"));
infoln("LOC instanceTransform: {}", rlGetLocationAttrib(instancing_shader.id, "instanceTransform"));
infoln("LOC vertexPosition: {}",
rlGetLocationAttrib(instancing_shader.id, "vertexPosition"));
infoln("LOC instanceTransform: {}",
rlGetLocationAttrib(instancing_shader.id, "instanceTransform"));
infoln("LOC instanceColor: {}", rlGetLocationAttrib(instancing_shader.id, "instanceColor"));
// vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR;
@ -94,7 +102,8 @@ private:
auto draw_textures(int fps, int ups, size_t mass_count, size_t spring_count) const -> void;
public:
auto render(const std::vector<Vector3>& masses, int fps, int ups, size_t mass_count, size_t spring_count) -> void;
auto render(const std::vector<Vector3>& masses, int fps, int ups, size_t mass_count,
size_t spring_count) -> void;
};
#endif

View File

@ -22,16 +22,19 @@ private:
// State storage (store states twice for bidirectional lookup).
// Everything else should only store indices to state_pool.
std::vector<puzzle> state_pool; // Indices are equal to mass_springs mass indices
std::vector<puzzle> state_pool; // Indices are equal to mass_springs mass indices
std::unordered_map<puzzle, size_t> state_indices; // Maps states to indices
std::vector<std::pair<size_t, size_t>> links; // Indices are equal to mass_springs springs indices
std::vector<std::pair<size_t, size_t>>
links; // Indices are equal to mass_springs springs indices
graph_distances node_target_distances; // Buffered and reused if the graph doesn't change
std::unordered_set<size_t> winning_indices; // Indices of all states where the board is solved
std::vector<size_t> winning_path; // Ordered list of node indices leading to the nearest solved state
std::unordered_set<size_t> path_indices; // For faster lookup if a vertex is part of the path in renderer
std::vector<size_t>
winning_path; // Ordered list of node indices leading to the nearest solved state
std::unordered_set<size_t>
path_indices; // For faster lookup if a vertex is part of the path in renderer
std::stack<size_t> move_history; // Moves between the starting state and the current state
std::stack<size_t> move_history; // Moves between the starting state and the current state
std::unordered_map<size_t, int> visit_counts; // How often each state was visited
size_t starting_state_index = 0;

View File

@ -22,13 +22,15 @@ class user_interface
const int padding;
public:
grid(const int _x, const int _y, const int _width, const int _height, const int _columns, const int _rows,
const int _padding)
: x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows), padding(_padding)
grid(const int _x, const int _y, const int _width, const int _height, const int _columns,
const int _rows, const int _padding)
: x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows),
padding(_padding)
{}
public:
auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows) -> void;
auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows)
-> void;
auto update_bounds(int _x, int _y, int _width, int _height) -> void;
auto update_bounds(int _x, int _y) -> void;
@ -36,7 +38,8 @@ class user_interface
[[nodiscard]] auto bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
[[nodiscard]] auto square_bounds() const -> Rectangle;
[[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
[[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const
-> Rectangle;
};
struct style
@ -84,13 +87,22 @@ private:
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.get_current_state().width, state.get_current_state().height, BOARD_PADDING);
grid board_grid =
grid(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
state.get_current_state().width, state.get_current_state().height, BOARD_PADDING);
grid graph_overlay_grid = grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD);
grid debug_overlay_grid = grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD);
grid debug_overlay_grid =
grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD);
// Windows
std::string message_title;
std::string message_message;
std::function<void(void)> yes_no_handler;
bool ok_message = false;
bool yes_no_message = false;
bool save_window = false;
std::array<char, 256> preset_name = {};
bool help_window = false;
@ -119,29 +131,34 @@ private:
static auto get_component_style(int component) -> component_style;
static auto set_component_style(int component, const component_style& style) -> void;
[[nodiscard]] static auto popup_bounds() -> Rectangle;
auto draw_button(Rectangle bounds, const std::string& label, Color color, bool enabled = true,
int font_size = FONT_SIZE) const -> int;
auto draw_menu_button(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 draw_menu_button(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 draw_toggle_slider(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 draw_toggle_slider(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 draw_menu_toggle_slider(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;
const std::string& on_label, int* active, Color color,
bool enabled = true, int font_size = FONT_SIZE) const -> int;
auto draw_spinner(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 draw_spinner(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 draw_menu_spinner(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 draw_menu_spinner(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 draw_label(Rectangle bounds, const std::string& text, Color color, bool enabled = true,
int font_size = FONT_SIZE) const -> int;
auto draw_board_block(int x, int y, int width, int height, Color color, bool enabled = true) const -> bool;
auto draw_board_block(int x, int y, int width, int height, Color color,
bool enabled = true) const -> bool;
[[nodiscard]] auto window_open() const -> bool;
@ -158,10 +175,12 @@ public:
static auto get_background_color() -> Color;
auto help_popup() -> void;
auto draw_save_preset_popup() -> void;
auto draw_ok_message_box() -> void;
auto draw_yes_no_message_box() -> void;
auto draw_main_menu() -> void;
auto draw_puzzle_board() -> void;
auto draw_graph_overlay(int fps, int ups, size_t mass_count, size_t spring_count) -> void;
auto update() const -> void;
auto draw(int fps, int ups, size_t mass_count, size_t spring_count) -> void;
};
#endif

View File

@ -17,6 +17,13 @@ inline auto operator<<(std::ostream& os, const Vector3& v) -> std::ostream&
return os;
}
// https://en.cppreference.com/w/cpp/utility/variant/visit
template <class... Ts>
struct overloads : Ts...
{
using Ts::operator()...;
};
enum ctrl
{
reset = 0,