diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f8e6c9..f03cca0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,11 +8,11 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(WIN32) - set(IMGUI_BACKEND "imgui_impl_dx11.cpp") # DirectX 11 for Windows + set(IMGUI_BACKEND "imgui_impl_dx11.cpp") # DirectX 11 for Windows elseif(APPLE) - set(IMGUI_BACKEND "imgui_impl_metal.mm") # Metal for macOS + set(IMGUI_BACKEND "imgui_impl_metal.mm") # Metal for macOS elseif(UNIX) - set(IMGUI_BACKEND "imgui_impl_opengl3.cpp") # OpenGL for Linux + set(IMGUI_BACKEND "imgui_impl_opengl3.cpp") # OpenGL for Linux endif() # includes @@ -21,25 +21,25 @@ include_directories(./includes) # raylib include(FetchContent) FetchContent_Declare( - raylib - GIT_REPOSITORY https://github.com/raysan5/raylib.git - GIT_TAG 5.5 - GIT_SHALLOW 1 + raylib + GIT_REPOSITORY https://github.com/raysan5/raylib.git + GIT_TAG 5.5 + GIT_SHALLOW 1 ) FetchContent_GetProperties(raylib) if (NOT raylib_POPULATED) - set(FETCHCONTENT_QUIET NO) - FetchContent_Populate(raylib) - set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(BUILD_GAMES OFF CACHE BOOL "" FORCE) - add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) + set(FETCHCONTENT_QUIET NO) + FetchContent_Populate(raylib) + set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(BUILD_GAMES OFF CACHE BOOL "" FORCE) + add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) endif() # imgui FetchContent_Declare( - imgui - GIT_REPOSITORY https://github.com/ocornut/imgui.git - GIT_TAG docking + imgui + GIT_REPOSITORY https://github.com/ocornut/imgui.git + GIT_TAG docking ) FetchContent_MakeAvailable(imgui) @@ -48,9 +48,9 @@ set(imgui_SOURCE_DIR ${CMAKE_BINARY_DIR}/_deps/imgui-src) # rlimgui FetchContent_Declare( - rlImGui - GIT_REPOSITORY https://github.com/raylib-extras/rlImGui.git - GIT_TAG main + rlImGui + GIT_REPOSITORY https://github.com/raylib-extras/rlImGui.git + GIT_TAG main ) FetchContent_MakeAvailable(rlImGui) set(rlImGui_SOURCE_DIR ${CMAKE_BINARY_DIR}/_deps/rlimgui-src) @@ -58,24 +58,29 @@ set(rlImGui_SOURCE_DIR ${CMAKE_BINARY_DIR}/_deps/rlimgui-src) # nlohmann::json FetchContent_Declare( - json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.11.3 + json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.3 ) FetchContent_MakeAvailable(json) include_directories(${imgui_SOURCE_DIR} ${rlImGui_SOURCE_DIR}) -set(SRC_CXX_FILES "./src/main.cpp" - "./src/rules.cpp" - "./src/world.cpp" - "./src/render.cpp" - "${rlImGui_SOURCE_DIR}/rlImGui.cpp" - "${imgui_SOURCE_DIR}/imgui.cpp" - "${imgui_SOURCE_DIR}/imgui_draw.cpp" - "${imgui_SOURCE_DIR}/imgui_widgets.cpp" - "${imgui_SOURCE_DIR}/imgui_tables.cpp" - "${imgui_SOURCE_DIR}/backends/${IMGUI_BACKEND}" +set(SRC_CXX_FILES "./src/main.cpp" + "./src/rules.cpp" + "./src/world.cpp" + "./src/render.cpp" + "./src/control_menu.cpp" + "./src/settings_menu.cpp" + "./src/selection_menu.cpp" + "./src/selection.cpp" + "./src/paterns_menu.cpp" + "${rlImGui_SOURCE_DIR}/rlImGui.cpp" + "${imgui_SOURCE_DIR}/imgui.cpp" + "${imgui_SOURCE_DIR}/imgui_draw.cpp" + "${imgui_SOURCE_DIR}/imgui_widgets.cpp" + "${imgui_SOURCE_DIR}/imgui_tables.cpp" + "${imgui_SOURCE_DIR}/backends/${IMGUI_BACKEND}" ) # Setup the example diff --git a/includes/context.hpp b/includes/context.hpp new file mode 100644 index 0000000..40f8704 --- /dev/null +++ b/includes/context.hpp @@ -0,0 +1,38 @@ +/* +* File name: context.hpp +* Author: lejulien +* Date created: 01-01-1970 00:59:59 +// Date modified: 12-01-2026 21:30:10 +* ------ +*/ + +#pragma once + +#include + +#include + +class World; +class Rules; +class Render; + +namespace gol { +class SettingsMenu; +class SelectionMenu; +class ControlMenu; +class Selection; +class PaternsMenu; + +typedef struct ctx { + std::shared_ptr world = nullptr; + std::shared_ptr rules = nullptr; + std::shared_ptr render = nullptr; + std::shared_ptr settings_menu = nullptr; + std::shared_ptr selection_menu = nullptr; + std::shared_ptr control_menu = nullptr; + std::shared_ptr selection = nullptr; + std::shared_ptr paterns_menu = nullptr; + nlohmann::json config_json; +} ctx; + +} // namespace gol diff --git a/includes/control_menu.hpp b/includes/control_menu.hpp new file mode 100644 index 0000000..c9c71b5 --- /dev/null +++ b/includes/control_menu.hpp @@ -0,0 +1,36 @@ +/* +* File name: control_menu.hpp +* Author: lejulien +* Date created: 10-01-2026 22:00:33 +// Date modified: 12-01-2026 22:18:26 +* ------ +*/ + +#pragma once + +#include + +#include +#include + +namespace gol { + +class ControlMenu { +public: + ControlMenu(std::shared_ptr context); + ~ControlMenu() = default; + void update(); + void display(); +private: + std::shared_ptr context_; +public: // Keep those public for easy access + MenuState menu_state_ = MenuState::NONE; + bool play_ctrl_ = true; + bool step_ctrl_ = false; + bool step_back_ctrl_ = false; + bool rand_ctrl_ = false; + bool edit_ctrl_ = false; + bool clear_ctrl_ = false; +}; + +} // namespace gol diff --git a/includes/paterns_menu.hpp b/includes/paterns_menu.hpp new file mode 100644 index 0000000..9af513a --- /dev/null +++ b/includes/paterns_menu.hpp @@ -0,0 +1,24 @@ +/* +* File name: paterns_menu.hpp +* Author: lejulien +* Date created: 01-01-1970 00:59:59 +// Date modified: 12-01-2026 21:30:10 +* ------ +*/ + +#pragma once + +namespace gol { + +class PaternsMenu { +public: + PaternsMenu() = default; + ~PaternsMenu() = default; + void Toogle(); + bool isOpen(); + void display(); +private: + bool is_open_ = false; +}; + +} // namespace gol diff --git a/includes/render.hpp b/includes/render.hpp index 3332b33..0714998 100644 --- a/includes/render.hpp +++ b/includes/render.hpp @@ -1,14 +1,12 @@ -/* ************************************************************************** */ -/* */ -/* / ) */ -/* render.hpp (\__/) ( ( */ -/* ) ( ) ) */ -/* By: lejulien ={ }= / / */ -/* ) `-------/ / */ -/* Created: 2023/01/09 12:44:54 by lejulien ( / */ -/* Updated: 2023/01/14 16:51:15 by lejulien \ | */ -/* */ -/* ************************************************************************** */ +/* +* File name: render.hpp +* Author: lejulien +* Date created: 10-01-2026 21:54:12 +// Date modified: 12-01-2026 21:56:03 +* ------ +*/ + +#include #include "world.hpp" @@ -20,7 +18,7 @@ public: Render(int cell_size); // Member function - void display(World *world); + void display(std::shared_ptr world); void updateCellSize(int new_size); private: diff --git a/includes/rules.hpp b/includes/rules.hpp index 5ae7bef..32b120e 100644 --- a/includes/rules.hpp +++ b/includes/rules.hpp @@ -1,14 +1,10 @@ -/* ************************************************************************** */ -/* */ -/* / ) */ -/* rules.hpp (\__/) ( ( */ -/* ) ( ) ) */ -/* By: lejulien ={ }= / / */ -/* ) `-------/ / */ -/* Created: 2023/01/09 12:16:47 by lejulien ( / */ -/* Updated: 2023/01/14 16:47:04 by lejulien \ | */ -/* */ -/* ************************************************************************** */ +/* +* File name: rules.hpp +* Author: lejulien +* Date created: 09-01-2026 23:59:55 +// Date modified: 12-01-2026 21:58:17 +* ------ +*/ #include "world.hpp" @@ -23,12 +19,12 @@ private: public: Rules(); - void setup(World *world); - void newWorld(World *world); + void setup(std::shared_ptr world); + void newWorld(std::shared_ptr world); void update(); private: - World *_world; + std::shared_ptr _world; std::vector _buffer; int _width; int _height; diff --git a/includes/selection.hpp b/includes/selection.hpp new file mode 100644 index 0000000..eb1de78 --- /dev/null +++ b/includes/selection.hpp @@ -0,0 +1,32 @@ +/* +* File name: selection.hpp +* Author: lejulien +* Date created: 01-01-1970 00:59:59 +// Date modified: 12-01-2026 21:30:10 +* ------ +*/ + +#pragma once + +#include + +#include +#include +#include + +namespace gol { + +class Selection { +public: + Selection(std::shared_ptr); + ~Selection() = default; + void update(); + void display(); +private: + std::shared_ptr context_ = nullptr; + Vector2 sel_pos_ = {0., 0.}; + Vector2 mouse_pos_ = {0., 0.}; + bool selecting_ = false; +}; + +} // namespace gol diff --git a/includes/selection_menu.hpp b/includes/selection_menu.hpp new file mode 100644 index 0000000..ccc9124 --- /dev/null +++ b/includes/selection_menu.hpp @@ -0,0 +1,38 @@ +/* +* File name: selection_menu.hpp +* Author: lejulien +* Date created: 13-01-2026 22:12:44 +// Date modified: 13-01-2026 22:18:58 +* ------ +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +#include + +namespace gol { + +class SelectionMenu { +public: + SelectionMenu(std::shared_ptr context); + ~SelectionMenu(); + void update(); + void display(); + void setSelection(std::vector selection); + void open(); +private: + std::shared_ptr context_; + RenderTexture2D selectionTexture_; + std::vector sel_data_ = {}; + bool sel_ctrl_ = false; + char patern_name_[255]; +}; + +} // namespace gol diff --git a/includes/settings_menu.hpp b/includes/settings_menu.hpp new file mode 100644 index 0000000..965fade --- /dev/null +++ b/includes/settings_menu.hpp @@ -0,0 +1,46 @@ +/* +* File name: settings_menu.hpp +* Author: lejulien +* Date created: 01-01-1970 00:59:59 +// Date modified: 12-01-2026 22:16:29 +* ------ +*/ + +#pragma once + +#include + +#include + +namespace gol { + +class SettingsMenu { +public: + SettingsMenu(std::shared_ptr context); + ~SettingsMenu() = default; + void update(); + void display(); + bool isOpen(); + void Toogle(); + // Getter/Setters + int getFPS(); + void setFPS(int); + int getCellSize(); + void setCellSize(int); + int getWidth(); + void setWidth(int); + int getHeight(); + void setHeight(int); +private: + std::shared_ptr context_ = nullptr; + int fps_ctrl_ = false; + int cell_size_ctrl_ = false; + bool settings_window_ = false; + int width_ctrl_ = false; + int height_ctrl_ = false; + bool dark_theme_ctrl_ = false; + bool apply_ctrl_ = false; +}; + + +} // namespace gol diff --git a/includes/snapping.hpp b/includes/snapping.hpp new file mode 100644 index 0000000..dfcc0fb --- /dev/null +++ b/includes/snapping.hpp @@ -0,0 +1,24 @@ +/* +* File name: selection.cpp +* Author: lejulien +* Date created: 01-01-1970 00:59:59 +// Date modified: 12-01-2026 21:30:10 +* ------ +*/ + +#pragma once + +#include + +#include +#include + +Vector2 snapToGrid(Vector2 screen, int cell_size) { + return {static_cast(round(screen.x / cell_size) * cell_size), + static_cast(round(screen.y / cell_size) * cell_size)}; +} + +Vector2i screenToGrid(Vector2 screen, int cell_size) { + return {static_cast(round(screen.x / cell_size)), + static_cast(round(screen.y / cell_size))}; +} diff --git a/includes/types.hpp b/includes/types.hpp index eec79a2..1e4f043 100644 --- a/includes/types.hpp +++ b/includes/types.hpp @@ -1,3 +1,11 @@ +/* +* File name: types.hpp +* Author: lejulien +* Date created: 09-01-2026 23:59:55 +* Date modified: 10-01-2026 21:49:36 +* ------ +*/ + #pragma once enum class MenuState { diff --git a/includes/world.hpp b/includes/world.hpp index ec97798..c61e373 100644 --- a/includes/world.hpp +++ b/includes/world.hpp @@ -1,11 +1,20 @@ +/* +* File name: world.hpp +* Author: lejulien +* Date created: 09-01-2026 23:59:55 +// Date modified: 12-01-2026 22:20:26 +* ------ +*/ + #include #include #include -#include +#include #include #include -#include "../includes/types.hpp" +#include +#include #pragma once @@ -14,7 +23,7 @@ class World { public: - World(int width, int height); + World(std::shared_ptr ctx); ~World(); std::vector *getWorldData(); diff --git a/src/control_menu.cpp b/src/control_menu.cpp new file mode 100644 index 0000000..2ec86cf --- /dev/null +++ b/src/control_menu.cpp @@ -0,0 +1,88 @@ +/* +* File name: control_menu.cpp +* Author: lejulien +* Date created: 10-01-2026 22:12:44 +// Date modified: 12-01-2026 22:18:58 +* ------ +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace gol { + +ControlMenu::ControlMenu(std::shared_ptr context):context_(context) { +} + +void ControlMenu::update() { + if (menu_state_ == MenuState::PLAY || menu_state_ == MenuState::EDIT) { + if (!play_ctrl_ && !edit_ctrl_) { + menu_state_ = MenuState::NONE; + } + } + if (rand_ctrl_) { + context_->world->randomize(); + rand_ctrl_ = false; + } + if (clear_ctrl_) { + context_->world->clear(); + clear_ctrl_ = false; + } + if (edit_ctrl_ && play_ctrl_) { + if (menu_state_ == MenuState::PLAY) { + menu_state_ = MenuState::EDIT; + play_ctrl_ = false; + } else if (menu_state_ == MenuState::EDIT) { + menu_state_ = MenuState::PLAY; + edit_ctrl_ = false; + } + } else if (play_ctrl_) { + menu_state_ = MenuState::PLAY; + } else if (edit_ctrl_) { + menu_state_ = MenuState::EDIT; + } + if (step_ctrl_) { + context_->world->saveCompressed(); + context_->rules->update(); + step_ctrl_ = false; + } + if (step_back_ctrl_) { + context_->world->stepBack(); + step_back_ctrl_ = false; + } +} + +void ControlMenu::display() { + bool control_panel_open = true; + ImGuiWindowFlags control_panel_flags = ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoResize; + ImVec2 menu_pos(3, 3); + ImGui::SetNextWindowPos(menu_pos, ImGuiCond_Always); + ImGui::Begin("Control Panel", &control_panel_open, control_panel_flags); + ImGui::Checkbox("Play", &play_ctrl_); + if (ImGui::Button("Step")) { + step_ctrl_ = true; + } + if (ImGui::Button("Step Back")) { + step_back_ctrl_ = true; + } + ImGui::Checkbox("Edit", &edit_ctrl_); + ImGui::Checkbox("Clear", &clear_ctrl_); + ImGui::Checkbox("Randomize", &rand_ctrl_); + if (ImGui::Button((context_->paterns_menu->isOpen()) ? "Hide paterns" : "Show paterns")) { + context_->paterns_menu->Toogle(); + } + if (ImGui::Button((context_->settings_menu->isOpen()) ? "Hide settings" : "Show settings")) { + context_->settings_menu->Toogle(); + } + ImGui::Text("Generation: %zu", context_->world->getCycle()); + ImGui::End();} + +} // namespace gol diff --git a/src/main.cpp b/src/main.cpp index 7c1a398..6f7ebb2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,33 +1,35 @@ +/* +* File name: main.cpp +* Author: lejulien +* Date created: 10-01-2026 21:59:32 +// Date modified: 12-01-2026 22:17:54 +* ------ +*/ + #include #include -#include #include #include -#include "../includes/render.hpp" -#include "../includes/rules.hpp" -#include "../includes/world.hpp" -#include "../includes/types.hpp" -#include "imgui.h" -#include "raylib.h" -#include "rlImGui.h" +#include +#include +#include - -Vector2 snapToGrid(Vector2 screen, int cell_size) { - return {static_cast(round(screen.x / cell_size) * cell_size), - static_cast(round(screen.y / cell_size) * cell_size)}; -} - -Vector2i screenToGrid(Vector2 screen, int cell_size) { - return {static_cast(round(screen.x / cell_size)), - static_cast(round(screen.y / cell_size))}; -} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include int main(int ac, char **av) { + std::shared_ptr context = std::make_shared(); // Load or default config const std::string config_file_name = "config.json"; - nlohmann::json config_json; - MenuState menu_state = MenuState::NONE; std::fstream config_file(config_file_name, std::ios::in | std::ios::out); @@ -47,7 +49,7 @@ int main(int ac, char **av) { // Try reading the configuration try { config_file.seekg(0); - config_file >> config_json; + config_file >> context->config_json; } catch (const nlohmann::json::parse_error &e) { std::cerr << "An error occured while loading config : " << e.what() << std::endl; @@ -58,187 +60,74 @@ int main(int ac, char **av) { config_file.close(); // Check config values or populate them - if (!config_json.contains("cell_size") || - (config_json["cell_size"] < 4 || config_json["cell_size"] > 100)) { - config_json["cell_size"] = 10; + if (!context->config_json.contains("cell_size") || + (context->config_json["cell_size"] < 4 || context->config_json["cell_size"] > 100)) { + context->config_json["cell_size"] = 10; } - if (!config_json.contains("screen_width") || - (config_json["screen_width"] < 800 || - config_json["screen_width"] > 1920)) { - config_json["screen_width"] = 800; + if (!context->config_json.contains("screen_width") || + (context->config_json["screen_width"] < 800 || + context->config_json["screen_width"] > 1920)) { + context->config_json["screen_width"] = 800; } - if (!config_json.contains("screen_height") || - (config_json["screen_height"] < 600 || - config_json["screen_height"] > 1200)) { - config_json["screen_height"] = 600; + if (!context->config_json.contains("screen_height") || + (context->config_json["screen_height"] < 600 || + context->config_json["screen_height"] > 1200)) { + context->config_json["screen_height"] = 600; } - if (!config_json.contains("dark_theme")) { - config_json["dark_theme"] = true; + if (!context->config_json.contains("dark_theme")) { + context->config_json["dark_theme"] = true; } - if (!config_json.contains("fps") || - (config_json["fps"] < 0 || config_json["fps"] > 30)) { - config_json["fps"] = 800; + if (!context->config_json.contains("fps") || + (context->config_json["fps"] < 0 || context->config_json["fps"] > 30)) { + context->config_json["fps"] = 800; } - InitWindow(config_json["screen_width"], config_json["screen_height"], + InitWindow(context->config_json["screen_width"], context->config_json["screen_height"], &av[0][2]); SetTargetFPS(60); - rlImGuiSetup(config_json["dark_theme"]); - // Selection window - - RenderTexture2D selectionTexture = LoadRenderTexture(200, 200); + rlImGuiSetup(context->config_json["dark_theme"]); // Initialize objects - World world(config_json["screen_width"].get() / - config_json["cell_size"].get(), - config_json["screen_height"].get() / - config_json["cell_size"].get()); - Rules rules = Rules(); - Render render(config_json["cell_size"]); - - // Imgui control menu - int fps_ctrl = config_json["fps"].get(); - int cell_size_ctrl = config_json["cell_size"].get(); - bool play_ctrl = true; - bool step_ctrl = false; - bool step_back_ctrl = false; - bool rand_ctrl = false; - bool edit_ctrl = false; - bool clear_ctrl = false; - bool settings_window = false; - bool paterns_ctrl = false; - int width_ctrl = config_json["screen_width"].get(); - int height_ctrl = config_json["screen_height"].get(); - bool dark_theme_ctrl = config_json["dark_theme"].get(); - bool apply_ctrl = false; + context->control_menu = std::make_shared(context); + context->world = std::make_shared(context); + context->rules = std::make_shared(); + context->settings_menu = std::make_shared(context); + context->render = std::make_shared(context->settings_menu->getCellSize()); + context->selection_menu = std::make_shared(context); + context->selection = std::make_shared(context); + context->paterns_menu = std::make_shared(); // Speed handling values float sim_speed = 1.0f; float deltaTimeAccumulator = 0.0f; float timePerUpdate = 1.0f / 10.0f; - // Selection window - Vector2 sel_pos = {0., 0.}; - bool selecting = false; - std::vector sel_data = {}; - bool sel_ctrl = false; - char patern_name[255]; - patern_name[0] = '\0'; - std::string sel_txt_input_hint("patern name"); - // Setups - rules.setup(&world); + context->rules->setup(context->world); // Diplay generations while (!WindowShouldClose()) { - if (menu_state == MenuState::PLAY || menu_state == MenuState::EDIT) { - if (!play_ctrl && !edit_ctrl) { - menu_state = MenuState::NONE; - } - } // Frames shinenigans float deltaTime = GetFrameTime(); - sim_speed = fps_ctrl / 10.0f; + sim_speed = context->settings_menu->getFPS() / 10.0f; timePerUpdate = (1.0f / 10.0f) / sim_speed; + context->control_menu->update(); + auto gesture = GetGestureDetected(); auto mousePos = GetMousePosition(); - if (rand_ctrl) { - world.randomize(); - rand_ctrl = false; - } - if (clear_ctrl) { - world.clear(); - clear_ctrl = false; - } - if (edit_ctrl && play_ctrl) { - if (menu_state == MenuState::PLAY) { - menu_state = MenuState::EDIT; - play_ctrl = false; - } else if (menu_state == MenuState::EDIT) { - menu_state = MenuState::PLAY; - edit_ctrl = false; - } - } else if (play_ctrl) { - menu_state = MenuState::PLAY; - } else if (edit_ctrl) { - menu_state = MenuState::EDIT; - } - if (edit_ctrl && IsMouseButtonPressed(0) && + if (context->control_menu->edit_ctrl_ && IsMouseButtonPressed(0) && !ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) { - menu_state = MenuState::EDIT; - world.setCell(mousePos.x / config_json["cell_size"].get(), - mousePos.y / config_json["cell_size"].get()); - } - // Selection behaviour - if (!edit_ctrl && !play_ctrl) { - if (gesture == GESTURE_TAP && !ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) { - sel_pos = snapToGrid(mousePos, cell_size_ctrl); - selecting = true; - } - if (ImGui::IsMouseReleased(0) && selecting == true && mousePos.x >=0 && mousePos.x < width_ctrl && mousePos.y >=0 && mousePos.y < height_ctrl ) { - selecting = false; - Vector2i p1 = screenToGrid(sel_pos, cell_size_ctrl); - Vector2i p2 = screenToGrid(mousePos, cell_size_ctrl); - // Get origin - Vector2i orig = { - (p1.x < p2.x)?p1.x:p2.x, - (p1.y < p2.y)?p1.y:p2.y, - }; - // Get selection size - Vector2i sel_size = { - (p1.x > p2.x)?p1.x-p2.x:p2.x-p1.x, - (p1.y > p2.y)?p1.y-p2.y:p2.y-p1.y, - }; - // Ensure there is at least one cell selected - if (!(sel_size.x == 0 || sel_size.y == 0)) { - sel_data = std::move(world.getSelection(orig, sel_size)); - sel_ctrl = true; - } - } - } - if (!sel_ctrl) { - patern_name[0] = '\0'; - } - if (step_ctrl) { - world.saveCompressed(); - rules.update(); - step_ctrl = false; - } - if (step_back_ctrl) { - world.stepBack(); - step_back_ctrl = false; + context->control_menu->menu_state_ = MenuState::EDIT; + context->world->setCell(mousePos.x / context->config_json["cell_size"].get(), + mousePos.y / context->config_json["cell_size"].get()); } - if (apply_ctrl) { - bool resize_needed = false; - config_json["fps"] = fps_ctrl; - if (config_json["screen_width"].get() != width_ctrl || - config_json["screen_height"].get() != height_ctrl) { - config_json["screen_width"] = width_ctrl; - config_json["screen_height"] = height_ctrl; - rlImGuiShutdown(); - SetWindowSize(width_ctrl, height_ctrl); - rlImGuiSetup(dark_theme_ctrl); - resize_needed = true; - } - if (cell_size_ctrl != config_json["cell_size"].get() || - resize_needed) { - world.resize(config_json["screen_width"].get() / cell_size_ctrl, - config_json["screen_height"].get() / cell_size_ctrl); - render.updateCellSize(cell_size_ctrl); - rules.newWorld(&world); - config_json["cell_size"] = cell_size_ctrl; - } - if (dark_theme_ctrl != config_json["dark_theme"]) { - rlImGuiShutdown(); - rlImGuiSetup(dark_theme_ctrl); - config_json["dark_theme"] = dark_theme_ctrl; - } - apply_ctrl = false; - settings_window = false; - } + // Selection behaviour + context->selection->update(); + context->selection_menu->update(); + context->settings_menu->update(); // Accumulate time and update simulation at the adjusted speed deltaTimeAccumulator += deltaTime; @@ -246,145 +135,28 @@ int main(int ac, char **av) { if (deltaTimeAccumulator >= timePerUpdate) { // Reset accumulator deltaTimeAccumulator -= timePerUpdate; - if (menu_state == MenuState::PLAY) { - world.saveCompressed(); - rules.update(); + if (context->control_menu->menu_state_ == MenuState::PLAY) { + context->world->saveCompressed(); + context->rules->update(); } } BeginDrawing(); ClearBackground(BLACK); - render.display(&world); - if (selecting) { - auto grid_mouse = snapToGrid(mousePos, cell_size_ctrl); - bool sel_x_less = (sel_pos.x < grid_mouse.x); - bool sel_y_less = (sel_pos.y < grid_mouse.y); - DrawRectangleLines(((sel_x_less)? sel_pos.x: grid_mouse.x), ((sel_y_less)? sel_pos.y: grid_mouse.y), - ((sel_x_less)? grid_mouse.x - sel_pos.x : sel_pos.x - grid_mouse.x), - ((sel_y_less)? grid_mouse.y - sel_pos.y : sel_pos.y - grid_mouse.y), - RED); - } + context->render->display(context->world); + context->selection->display(); // Start ImGui frame rlImGuiBegin(); - - bool control_panel_open = true; - ImGuiWindowFlags control_panel_flags = ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoResize; - ImVec2 menu_pos(3, 3); - ImGui::SetNextWindowPos(menu_pos, ImGuiCond_Always); - ImGui::Begin("Control Panel", &control_panel_open, control_panel_flags); - ImGui::Checkbox("Play", &play_ctrl); - if (ImGui::Button("Step")) { - step_ctrl = true; - } - if (ImGui::Button("Step Back")) { - step_back_ctrl = true; - } - ImGui::Checkbox("Edit", &edit_ctrl); - ImGui::Checkbox("Clear", &clear_ctrl); - ImGui::Checkbox("Randomize", &rand_ctrl); - if (ImGui::Button((paterns_ctrl) ? "Hide paterns" : "Show paterns")) { - paterns_ctrl = !paterns_ctrl; - } - if (ImGui::Button((settings_window) ? "Hide settings" : "Show settings")) { - settings_window = !settings_window; - } - ImGui::Text("Generation: %zu", world.getCycle()); - ImGui::End(); - if (paterns_ctrl) { - ImGuiWindowFlags paterns_flags = - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; - ImGui::SetNextWindowSize(ImVec2(150, 200), ImGuiCond_Always); - ImGui::Begin("paterns", &paterns_ctrl, paterns_flags); - ImGui::Button("refresh"); - ImGui::End(); - } - if (settings_window) { - ImGuiWindowFlags settings_flags = - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; - ImGui::Begin("Settings", &settings_window, settings_flags); - ImGui::SliderInt("Window Width", &width_ctrl, 800, 1920); - ImGui::SliderInt("Window Height", &height_ctrl, 600, 1200); - ImGui::SliderInt("FPS", &fps_ctrl, 0, 30); - ImGui::SliderInt("Cell Size", &cell_size_ctrl, 4, 100); - ImGui::Checkbox("Dark Theme", &dark_theme_ctrl); - if (ImGui::Button("Save & Apply")) { - apply_ctrl = true; - } - ImGui::End(); - } - if (sel_ctrl) { - BeginTextureMode(selectionTexture); - ClearBackground(BLACK); - auto max_size = (sel_data[0] > sel_data[1]) ? sel_data[0] : sel_data[1]; - int fitted_width = 200 / max_size; - auto sel_it = sel_data.begin(); - sel_it += 2; // skip dimensions - for (int j = 0; j < sel_data[1]; j++) { - for (int i = 0; i < sel_data[0]; i++) { - if (*sel_it == 1) { - DrawRectangle(i * fitted_width, (sel_data[1] -j-1) * fitted_width, fitted_width, fitted_width, WHITE); - } - sel_it++; - } - } - EndTextureMode(); - ImGuiWindowFlags settings_flags = - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; - ImGui::Begin("Selection", &sel_ctrl, settings_flags); - rlImGuiImageSize(&selectionTexture.texture, 200, 200); - ImGui::InputText(sel_txt_input_hint.c_str(), patern_name, 255); - if (ImGui::Button("Save")) { - char path_buf[1024]; - ssize_t len = readlink("/proc/self/exe", path_buf, sizeof(path_buf)-1); - if (len != -1) { - // Create paterns dir if not present - std::filesystem::path paterns_dir = std::filesystem::path(path_buf).parent_path() / "paterns"; - if (!std::filesystem::exists(paterns_dir) && !std::filesystem::create_directory(paterns_dir)) { - std::cerr << "Failed to create paterns directory" << std::endl; - } else { // Could be optimized by early returning in a function - std::ofstream patern_file; - paterns_dir += '/'; - paterns_dir += patern_name; - patern_file.open(paterns_dir); - if (!patern_file) { - std::cerr << "Failed to create the patern file" << std::endl; - } else { - auto sel_it = sel_data.begin(); - sel_it += 2; // skip dimensions - patern_file << sel_data[0]; - patern_file << "|"; // Separator needed to split as ascii values - patern_file << sel_data[1]; - patern_file << "|"; // Separator needed to split as ascii values - for (int j = 0; j < sel_data[1]; j++) { - for (int i = 0; i < sel_data[0]; i++) { - patern_file << std::to_string(*sel_it); - if (*sel_it == 1) { - } - sel_it++; - } - } - patern_file << std::flush; - patern_file.close(); - } - } - sel_ctrl = false; - } - } - ImGui::SameLine(); - if (ImGui::Button("Discard")) { - sel_ctrl = false; - } - ImGui::End(); - } + context->control_menu->display(); + context->paterns_menu->display(); + context->settings_menu->display(); + context->selection_menu->display(); // End ImGui frame rlImGuiEnd(); EndDrawing(); } config_file.open(config_file_name, std::ios::out | std::ios::trunc); - config_file << config_json.dump(2); + config_file << context->config_json.dump(2); config_file.close(); - // Cleanup Selection texture - UnloadRenderTexture(selectionTexture); rlImGuiShutdown(); CloseWindow(); return 0; diff --git a/src/paterns_menu.cpp b/src/paterns_menu.cpp new file mode 100644 index 0000000..ad450fa --- /dev/null +++ b/src/paterns_menu.cpp @@ -0,0 +1,35 @@ +/* +* File name: paterns_menu.cpp +* Author: lejulien +* Date created: 01-01-1970 00:59:59 +// Date modified: 12-01-2026 21:30:10 +* ------ +*/ + +#include + +#include +#include +#include + +namespace gol { + +void PaternsMenu::Toogle() { + is_open_ = !is_open_; +} + +bool PaternsMenu::isOpen() { + return is_open_; +} + +void PaternsMenu::display() { + if (is_open_) { + ImGuiWindowFlags paterns_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; + ImGui::SetNextWindowSize(ImVec2(150, 200), ImGuiCond_Always); + ImGui::Begin("paterns", &is_open_, paterns_flags); + ImGui::Button("refresh"); + ImGui::End(); + } +} + +} // namespace gol diff --git a/src/render.cpp b/src/render.cpp index f74fa19..f4c995e 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -1,14 +1,10 @@ -/* ************************************************************************** */ -/* */ -/* / ) */ -/* render.cpp (\__/) ( ( */ -/* ) ( ) ) */ -/* By: lejulien ={ }= / / */ -/* ) `-------/ / */ -/* Created: 2023/01/09 12:44:30 by lejulien ( / */ -/* Updated: 2023/01/14 17:02:00 by lejulien \ | */ -/* */ -/* ************************************************************************** */ +/* +* File name: render.cpp +* Author: lejulien +* Date created: 10-01-2026 21:49:04 +// Date modified: 12-01-2026 21:54:56 +* ------ +*/ #include "../includes/render.hpp" @@ -75,7 +71,7 @@ void Render::display_world(std::vector *data, int width, int height) { // Render loop -void Render::display(World *world) { +void Render::display(std::shared_ptr world) { display_world(world->getWorldData(), world->getWidth(), world->getHeight()); } diff --git a/src/rules.cpp b/src/rules.cpp index e9c0298..5094c00 100644 --- a/src/rules.cpp +++ b/src/rules.cpp @@ -1,14 +1,10 @@ -/* ************************************************************************** */ -/* */ -/* / ) */ -/* rules.cpp (\__/) ( ( */ -/* ) ( ) ) */ -/* By: lejulien ={ }= / / */ -/* ) `-------/ / */ -/* Created: 2023/01/09 12:18:36 by lejulien ( / */ -/* Updated: 2023/01/14 16:47:01 by lejulien \ | */ -/* */ -/* ************************************************************************** */ +/* +* File name: rules.cpp +* Author: lejulien +* Date created: 10-01-2026 21:49:14 +// Date modified: 12-01-2026 21:59:00 +* ------ +*/ #include "../includes/rules.hpp" #include "../includes/world.hpp" @@ -71,13 +67,13 @@ void Rules::diag_neighbors(int &neighbors, int i, int j) { // Member function -void Rules::setup(World *world) { +void Rules::setup(std::shared_ptr world) { _world = world; _width = world->getWidth(); _height = world->getHeight(); } -void Rules::newWorld(World *world) { +void Rules::newWorld(std::shared_ptr world) { _world = world; _width = world->getWidth(); _height = world->getHeight(); diff --git a/src/selection.cpp b/src/selection.cpp new file mode 100644 index 0000000..fac32b7 --- /dev/null +++ b/src/selection.cpp @@ -0,0 +1,68 @@ +/* +* File name: selection.cpp +* Author: lejulien +* Date created: 01-01-1970 00:59:59 +// Date modified: 12-01-2026 21:30:10 +* ------ +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace gol { + +Selection::Selection(std::shared_ptr context): context_(context) { + +} + +void Selection::update() { + auto gesture = GetGestureDetected(); + mouse_pos_ = GetMousePosition(); + if (!context_->control_menu->edit_ctrl_ && !context_->control_menu->play_ctrl_ && !context_->paterns_menu->isOpen() && !context_->settings_menu->isOpen()) { + if (gesture == GESTURE_TAP && !ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) { + sel_pos_ = snapToGrid(mouse_pos_, context_->settings_menu->getCellSize()); + selecting_ = true; + } + if (ImGui::IsMouseReleased(0) && selecting_ == true && mouse_pos_.x >=0 && mouse_pos_.x < context_->settings_menu->getWidth() && mouse_pos_.y >=0 && mouse_pos_.y < context_->settings_menu->getHeight() ) { + selecting_ = false; + Vector2i p1 = screenToGrid(sel_pos_, context_->settings_menu->getCellSize()); + Vector2i p2 = screenToGrid(mouse_pos_, context_->settings_menu->getCellSize()); + // Get origin + Vector2i orig = { + (p1.x < p2.x)?p1.x:p2.x, + (p1.y < p2.y)?p1.y:p2.y, + }; + // Get selection size + Vector2i sel_size = { + (p1.x > p2.x)?p1.x-p2.x:p2.x-p1.x, + (p1.y > p2.y)?p1.y-p2.y:p2.y-p1.y, + }; + // Ensure there is at least one cell selected + if (!(sel_size.x == 0 || sel_size.y == 0)) { + context_->selection_menu->setSelection(context_->world->getSelection(orig, sel_size)); + context_->selection_menu->open(); + } + } + } +} + +void Selection::display() { + if (selecting_) { + auto grid_mouse = snapToGrid(mouse_pos_, context_->settings_menu->getCellSize()); + bool sel_x_less = (sel_pos_.x < grid_mouse.x); + bool sel_y_less = (sel_pos_.y < grid_mouse.y); + DrawRectangleLines(((sel_x_less)? sel_pos_.x: grid_mouse.x), ((sel_y_less)? sel_pos_.y: grid_mouse.y), + ((sel_x_less)? grid_mouse.x - sel_pos_.x : sel_pos_.x - grid_mouse.x), + ((sel_y_less)? grid_mouse.y - sel_pos_.y : sel_pos_.y - grid_mouse.y), + RED); + } +} + +} // namespace gol diff --git a/src/selection_menu.cpp b/src/selection_menu.cpp new file mode 100644 index 0000000..de9f56d --- /dev/null +++ b/src/selection_menu.cpp @@ -0,0 +1,109 @@ +/* +* File name: selection_menu.cpp +* Author: lejulien +* Date created: 13-01-2026 22:12:44 +// Date modified: 13-01-2026 22:18:58 +* ------ +*/ + +#include +#include +#include +#include + +#include + +namespace gol { + +SelectionMenu::SelectionMenu(std::shared_ptr context): context_(context) { + selectionTexture_ = LoadRenderTexture(200, 200); + patern_name_[0] = '\0'; + +} + +SelectionMenu::~SelectionMenu() { + UnloadRenderTexture(selectionTexture_); +} + +void SelectionMenu::update() { + if (!sel_ctrl_) { + patern_name_[0] = '\0'; + } +} + +void SelectionMenu::open() { + sel_ctrl_ = true; +} + +void SelectionMenu::setSelection(std::vector selection) { + sel_data_ = selection; +} + +void SelectionMenu::display() { + if (sel_ctrl_) { + BeginTextureMode(selectionTexture_); + ClearBackground(BLACK); + auto max_size = (sel_data_[0] > sel_data_[1]) ? sel_data_[0] : sel_data_[1]; + int fitted_width = 200 / max_size; + auto sel_it = sel_data_.begin(); + sel_it += 2; // skip dimensions + for (int j = 0; j < sel_data_[1]; j++) { + for (int i = 0; i < sel_data_[0]; i++) { + if (*sel_it == 1) { + DrawRectangle(i * fitted_width, (sel_data_[1] -j-1) * fitted_width, fitted_width, fitted_width, WHITE); + } + sel_it++; + } + } + EndTextureMode(); + ImGuiWindowFlags settings_flags = + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; + ImGui::Begin("Selection", &sel_ctrl_, settings_flags); + rlImGuiImageSize(&selectionTexture_.texture, 200, 200); + ImGui::InputText("patern_name", patern_name_, 255); + if (ImGui::Button("Save")) { + char path_buf[1024]; + ssize_t len = readlink("/proc/self/exe", path_buf, sizeof(path_buf)-1); + if (len != -1) { + // Create paterns dir if not present + std::filesystem::path paterns_dir = std::filesystem::path(path_buf).parent_path() / "paterns"; + if (!std::filesystem::exists(paterns_dir) && !std::filesystem::create_directory(paterns_dir)) { + std::cerr << "Failed to create paterns directory" << std::endl; + } else { // Could be optimized by early returning in a function + std::ofstream patern_file; + paterns_dir += '/'; + paterns_dir += patern_name_; + patern_file.open(paterns_dir); + if (!patern_file) { + std::cerr << "Failed to create the patern file" << std::endl; + } else { + auto sel_it = sel_data_.begin(); + sel_it += 2; // skip dimensions + patern_file << sel_data_[0]; + patern_file << "|"; // Separator needed to split as ascii values + patern_file << sel_data_[1]; + patern_file << "|"; // Separator needed to split as ascii values + for (int j = 0; j < sel_data_[1]; j++) { + for (int i = 0; i < sel_data_[0]; i++) { + patern_file << std::to_string(*sel_it); + if (*sel_it == 1) { + } + sel_it++; + } + } + patern_file << std::flush; + patern_file.close(); + } + } + sel_ctrl_ = false; + } + } + ImGui::SameLine(); + if (ImGui::Button("Discard")) { + sel_ctrl_ = false; + } + ImGui::End(); + } +} + +} // namespace gol diff --git a/src/settings_menu.cpp b/src/settings_menu.cpp new file mode 100644 index 0000000..0ce1e54 --- /dev/null +++ b/src/settings_menu.cpp @@ -0,0 +1,122 @@ +/* +* File name: settings_menu.cpp +* Author: lejulien +* Date created: 01-01-1970 00:59:59 +// Date modified: 12-01-2026 22:21:49 +* ------ +*/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace gol { + +SettingsMenu::SettingsMenu(std::shared_ptr context): context_(context) { + fps_ctrl_ = context->config_json["fps"].get(); + cell_size_ctrl_ = context->config_json["cell_size"].get(); + settings_window_ = false; + width_ctrl_ = context->config_json["screen_width"].get(); + height_ctrl_ = context->config_json["screen_height"].get(); + dark_theme_ctrl_ = context->config_json["dark_theme"].get(); + apply_ctrl_ = false; +} + +void SettingsMenu::update() { + if (apply_ctrl_) { + bool resize_needed = false; + context_->config_json["fps"] = fps_ctrl_; + if (context_->config_json["screen_width"].get() != width_ctrl_ || + context_->config_json["screen_height"].get() != height_ctrl_) { + context_->config_json["screen_width"] = width_ctrl_; + context_->config_json["screen_height"] = height_ctrl_; + rlImGuiShutdown(); + SetWindowSize(width_ctrl_, height_ctrl_); + rlImGuiSetup(dark_theme_ctrl_); + resize_needed = true; + } + if (cell_size_ctrl_ != context_->config_json["cell_size"].get() || + resize_needed) { + context_->world->resize(context_->config_json["screen_width"].get() / cell_size_ctrl_, + context_->config_json["screen_height"].get() / cell_size_ctrl_); + context_->render->updateCellSize(cell_size_ctrl_); + context_->rules->newWorld(context_->world); + context_->config_json["cell_size"] = cell_size_ctrl_; + } + if (dark_theme_ctrl_ != context_->config_json["dark_theme"]) { + rlImGuiShutdown(); + rlImGuiSetup(dark_theme_ctrl_); + context_->config_json["dark_theme"] = dark_theme_ctrl_; + } + apply_ctrl_ = false; + settings_window_ = false; + } +} + +void SettingsMenu::display() { + if (settings_window_) { + ImGuiWindowFlags settings_flags = + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; + ImGui::Begin("Settings", &settings_window_, settings_flags); + ImGui::SliderInt("Window Width", &width_ctrl_, 800, 1920); + ImGui::SliderInt("Window Height", &height_ctrl_, 600, 1200); + ImGui::SliderInt("FPS", &fps_ctrl_, 0, 30); + ImGui::SliderInt("Cell Size", &cell_size_ctrl_, 4, 100); + ImGui::Checkbox("Dark Theme", &dark_theme_ctrl_); + if (ImGui::Button("Save & Apply")) { + apply_ctrl_ = true; + } + ImGui::End(); + } +} + +bool SettingsMenu::isOpen() { + return settings_window_; +} + +void SettingsMenu::Toogle() { + settings_window_ = !settings_window_; +} + +// Getters/Setters +int SettingsMenu::getFPS() { + return fps_ctrl_; +} + +void SettingsMenu::setFPS(int new_fps) { + fps_ctrl_ = new_fps; +} + +int SettingsMenu::getCellSize() { + return cell_size_ctrl_; +} + +void SettingsMenu::setCellSize(int new_cell_size) { + cell_size_ctrl_ = new_cell_size; +} + +int SettingsMenu::getWidth() { + return width_ctrl_; +} + +void SettingsMenu::setWidth(int new_width) { + width_ctrl_ = new_width; +} + +int SettingsMenu::getHeight() { + return height_ctrl_; +} + +void SettingsMenu::setHeight(int new_height) { + height_ctrl_ = new_height; +} + +} // namespace gol diff --git a/src/world.cpp b/src/world.cpp index 3752280..ae653ba 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -1,14 +1,10 @@ -/* ************************************************************************** */ -/* */ -/* / ) */ -/* world.cpp (\__/) ( ( */ -/* ) ( ) ) */ -/* By: lejulien ={ }= / / */ -/* ) `-------/ / */ -/* Created: 2023/01/09 12:25:55 by lejulien ( / */ -/* Updated: 2023/01/14 17:06:54 by lejulien \ | */ -/* */ -/* ************************************************************************** */ +/* +* File name: world.cpp +* Author: lejulien +* Date created: 09-01-2026 23:59:55 +// Date modified: 12-01-2026 22:14:46 +* ------ +*/ #include "../includes/world.hpp" @@ -16,9 +12,13 @@ // Constructor and destructor -World::World(int width, int height) : _width(width), _height(height) { +World::World(std::shared_ptr context) { + _width = context->config_json["screen_width"].get() / + context->config_json["cell_size"].get(); + _height = context->config_json["screen_height"].get() / + context->config_json["cell_size"].get(); // create world data - this->_data = new std::vector(width * height, false); + this->_data = new std::vector(_width * _height, false); } World::~World() { delete this->_data; }