From 4ada2c44edddd8cf26565f65f9cab3440560282b Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Fri, 19 Dec 2025 17:22:23 +0300 Subject: [PATCH] chore: refactor --- CMakeLists.txt | 3 +- src/core/common/version.hpp | 2 +- src/gui/CMakeLists.txt | 14 +- src/gui/backend/backend.hpp | 1 - src/gui/backend/glfw_opengl.cpp | 19 +- src/gui/backend/glfw_opengl.hpp | 3 + src/gui/helpers/imgui_helpers.cpp | 266 ----------------------- src/gui/helpers/imgui_helpers.hpp | 42 ---- src/gui/layout/main_layout.cpp | 116 ++++++++++ src/gui/layout/main_layout.hpp | 28 +++ src/gui/main.cpp | 47 ++-- src/gui/ui_manager.cpp | 68 +++++- src/gui/ui_manager.hpp | 20 ++ src/gui/views/about_window.cpp | 10 +- src/gui/views/color_scheme_editor.cpp | 144 ++++--------- src/gui/views/color_scheme_editor.hpp | 8 + src/gui/views/color_table_renderer.cpp | 8 +- src/gui/views/preview_renderer.cpp | 4 +- src/gui/views/settings_window.cpp | 38 ++-- src/gui/views/settings_window.hpp | 8 +- src/gui/views/template_editor.cpp | 52 ++--- src/gui/views/template_editor.hpp | 8 +- src/gui/widgets/action_buttons.cpp | 93 ++++++++ src/gui/widgets/action_buttons.hpp | 46 ++++ src/gui/widgets/autocomplete.cpp | 287 +++++++++++++++++++++++++ src/gui/widgets/autocomplete.hpp | 53 +++++ src/gui/widgets/colors.cpp | 58 +++++ src/gui/widgets/colors.hpp | 19 ++ src/gui/widgets/dialogs.cpp | 46 ++++ src/gui/widgets/dialogs.hpp | 17 ++ src/gui/widgets/form_field.cpp | 184 ++++++++++++++++ src/gui/widgets/form_field.hpp | 62 ++++++ src/gui/widgets/input_dialog.cpp | 111 ++++++++++ src/gui/widgets/input_dialog.hpp | 38 ++++ src/gui/widgets/palette_selector.cpp | 46 ++++ src/gui/widgets/palette_selector.hpp | 26 +++ src/gui/widgets/styled_checkbox.cpp | 95 ++++++++ src/gui/widgets/styled_checkbox.hpp | 38 ++++ 38 files changed, 1628 insertions(+), 500 deletions(-) delete mode 100644 src/gui/helpers/imgui_helpers.cpp delete mode 100644 src/gui/helpers/imgui_helpers.hpp create mode 100644 src/gui/layout/main_layout.cpp create mode 100644 src/gui/layout/main_layout.hpp create mode 100644 src/gui/widgets/action_buttons.cpp create mode 100644 src/gui/widgets/action_buttons.hpp create mode 100644 src/gui/widgets/autocomplete.cpp create mode 100644 src/gui/widgets/autocomplete.hpp create mode 100644 src/gui/widgets/colors.cpp create mode 100644 src/gui/widgets/colors.hpp create mode 100644 src/gui/widgets/dialogs.cpp create mode 100644 src/gui/widgets/dialogs.hpp create mode 100644 src/gui/widgets/form_field.cpp create mode 100644 src/gui/widgets/form_field.hpp create mode 100644 src/gui/widgets/input_dialog.cpp create mode 100644 src/gui/widgets/input_dialog.hpp create mode 100644 src/gui/widgets/palette_selector.cpp create mode 100644 src/gui/widgets/palette_selector.hpp create mode 100644 src/gui/widgets/styled_checkbox.cpp create mode 100644 src/gui/widgets/styled_checkbox.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e7c0439..375d44c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) file(WRITE "${CMAKE_SOURCE_DIR}/.clangd" - "CompileFlags: - CompilationDatabase: ${CMAKE_BINARY_DIR}") + "CompileFlags:\n CompilationDatabase: ${CMAKE_BINARY_DIR}\n") option(USE_SYSTEM_GLFW "Use system-installed GLFW instead of fetching it statically" OFF) message(STATUS "USE_SYSTEM_GLFW: ${USE_SYSTEM_GLFW}") diff --git a/src/core/common/version.hpp b/src/core/common/version.hpp index 3753ba1..b011ea6 100644 --- a/src/core/common/version.hpp +++ b/src/core/common/version.hpp @@ -6,7 +6,7 @@ namespace clrsync::core { -const std::string GIT_SEMVER = "0.1.7+git.g6ac9c03"; +const std::string GIT_SEMVER = "0.1.7+git.g82998d6"; const std::string version_string(); } // namespace clrsync::core diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a8e90d7..345fbd0 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -7,19 +7,23 @@ set(GUI_SOURCES views/template_editor.cpp controllers/palette_controller.cpp controllers/template_controller.cpp - helpers/imgui_helpers.cpp - helpers/imgui_helpers.hpp views/about_window.cpp views/settings_window.cpp + widgets/colors.cpp + widgets/dialogs.cpp + widgets/palette_selector.cpp + widgets/input_dialog.cpp + widgets/action_buttons.cpp + widgets/styled_checkbox.cpp + widgets/autocomplete.cpp + widgets/form_field.cpp + layout/main_layout.cpp platform/windows/font_loader_windows.cpp platform/linux/font_loader_linux.cpp platform/macos/font_loader_macos.cpp platform/linux/file_browser_linux.cpp platform/windows/file_browser_windows.cpp ${CMAKE_SOURCE_DIR}/lib/color_text_edit/TextEditor.cpp - platform/linux/font_loader_linux.cpp - platform/macos/font_loader_macos.cpp - platform/windows/font_loader_windows.cpp backend/glfw_opengl.cpp ui_manager.cpp ) diff --git a/src/gui/backend/backend.hpp b/src/gui/backend/backend.hpp index a02a531..378000d 100644 --- a/src/gui/backend/backend.hpp +++ b/src/gui/backend/backend.hpp @@ -9,7 +9,6 @@ namespace clrsync::gui::backend{ std::string title = "clrsync"; int width = 1280; int height = 720; - bool resizable = true; bool decorated = true; bool transparent_framebuffer = true; float clear_color[4] = {0.1f, 0.1f, 0.1f, 0.0f}; diff --git a/src/gui/backend/glfw_opengl.cpp b/src/gui/backend/glfw_opengl.cpp index 3f8a6ae..40bf1a6 100644 --- a/src/gui/backend/glfw_opengl.cpp +++ b/src/gui/backend/glfw_opengl.cpp @@ -1,5 +1,6 @@ #include "gui/backend/glfw_opengl.hpp" #include +#include #include #include #include @@ -33,7 +34,7 @@ bool glfw_opengl_backend::initialize(const window_config &config) #else glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); #endif - glfwWindowHint(GLFW_RESIZABLE, config.resizable ? GLFW_TRUE : GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE,GLFW_TRUE); glfwWindowHint(GLFW_DECORATED, config.decorated ? GLFW_TRUE : GLFW_FALSE); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, config.transparent_framebuffer ? GLFW_TRUE : GLFW_FALSE); @@ -98,6 +99,22 @@ void *glfw_opengl_backend::get_graphics_context() const return static_cast(m_window); } +std::string glfw_opengl_backend::get_glfw_version() const +{ + return glfwGetVersionString(); +} + +std::string glfw_opengl_backend::get_glfw_platform() const +{ + switch (glfwGetPlatform()) { + case GLFW_PLATFORM_WAYLAND: return "Wayland"; + case GLFW_PLATFORM_X11: return "X11"; + case GLFW_PLATFORM_COCOA: return "Cocoa"; + case GLFW_PLATFORM_WIN32: return "Win32"; + default: return "Unknown"; + } +} + bool glfw_opengl_backend::init_imgui_backend() { if (!m_window) diff --git a/src/gui/backend/glfw_opengl.hpp b/src/gui/backend/glfw_opengl.hpp index db93f35..23e0fb8 100644 --- a/src/gui/backend/glfw_opengl.hpp +++ b/src/gui/backend/glfw_opengl.hpp @@ -23,6 +23,9 @@ namespace clrsync::gui::backend{ void* get_native_window() const override; void* get_graphics_context() const override; + std::string get_glfw_version() const; + std::string get_glfw_platform() const; + bool init_imgui_backend() override; void shutdown_imgui_backend() override; void imgui_new_frame() override; diff --git a/src/gui/helpers/imgui_helpers.cpp b/src/gui/helpers/imgui_helpers.cpp deleted file mode 100644 index b7034e5..0000000 --- a/src/gui/helpers/imgui_helpers.cpp +++ /dev/null @@ -1,266 +0,0 @@ -#include -#include - -#include "GLFW/glfw3.h" -#include "gui/views/settings_window.hpp" -#include "imgui_impl_glfw.h" -#include "imgui_impl_opengl3.h" - -#include "imgui_helpers.hpp" - -#include "imgui_internal.h" - -GLFWwindow *init_glfw() -{ - glfwSetErrorCallback([](int error, const char *description) { - std::cerr << "GLFW Error " << error << ": " << description << std::endl; - }); - - if (!glfwInit()) - { - std::cerr << "Failed to initialize GLFW\n"; - return nullptr; - } - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); -#ifdef __APPLE__ - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); -#else - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); -#endif - glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); - - GLFWwindow *w = glfwCreateWindow(1280, 720, "clrsync", nullptr, nullptr); - if (!w) - { - std::cerr << "Failed to create GLFW window\n"; - return nullptr; - } - - glfwMakeContextCurrent(w); - glfwSwapInterval(1); - return w; -} - -void init_imgui(GLFWwindow *window, const std::string &ini_path) -{ - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - ImGuiIO &io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - io.IniFilename = ini_path.c_str(); - - ImGui::StyleColorsDark(); - ImGui_ImplGlfw_InitForOpenGL(window, true); -#ifdef __APPLE__ - ImGui_ImplOpenGL3_Init("#version 150"); -#else - ImGui_ImplOpenGL3_Init("#version 120"); -#endif -} - -void begin_frame() -{ - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); -} - -void render_menu_bar(about_window *about, settings_window *settings) -{ - if (ImGui::BeginMainMenuBar()) - { - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("Settings")) - { - if (settings) - settings->show(); - } - ImGui::Separator(); - if (ImGui::MenuItem("Exit")) - { - glfwSetWindowShouldClose(glfwGetCurrentContext(), GLFW_TRUE); - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Help")) - { - if (ImGui::MenuItem("About")) - { - if (about) - about->show(); - } - ImGui::EndMenu(); - } - ImGui::EndMainMenuBar(); - } -} - -void setup_main_dockspace(bool &first_time) -{ - const ImGuiViewport *viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->Pos); - ImGui::SetNextWindowSize(viewport->Size); - ImGui::SetNextWindowViewport(viewport->ID); - - constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_MenuBar; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::Begin("MainDockSpace", nullptr, flags); - ImGui::PopStyleVar(3); - - ImGuiID dockspace_id = ImGui::GetID("MainDockSpace"); - - if (first_time) - { - first_time = false; - - ImGui::DockBuilderRemoveNode(dockspace_id); - ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); - ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size); - - ImGuiID center, right; - ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.5f, &right, ¢er); - - ImGuiDockNode *center_node = ImGui::DockBuilderGetNode(center); - if (center_node) - { - center_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; - } - - ImGui::DockBuilderDockWindow("Color Schemes", right); - ImGui::DockBuilderDockWindow("Color Preview", center); - ImGui::DockBuilderDockWindow("Templates", center); - - ImGui::DockBuilderFinish(dockspace_id); - } - - ImGui::DockSpace(dockspace_id, ImVec2{0, 0}, ImGuiDockNodeFlags_None); - ImGui::End(); -} - -void end_frame(GLFWwindow *window) -{ - ImGui::Render(); - int w, h; - glfwGetFramebufferSize(window, &w, &h); - glViewport(0, 0, w, h); - glClearColor(0.1f, 0.1f, 0.1f, 0.f); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(window); -} - -void shutdown(GLFWwindow *window) -{ - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - glfwDestroyWindow(window); - glfwTerminate(); -} - -namespace palette_utils -{ - -ImVec4 get_color(const clrsync::core::palette &pal, const std::string &key, - const std::string &fallback) -{ - auto colors = pal.colors(); - if (colors.empty()) - return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - - auto it = colors.find(key); - if (it == colors.end() && !fallback.empty()) - { - it = colors.find(fallback); - } - - if (it != colors.end()) - { - const auto &col = it->second; - const uint32_t hex = col.hex(); - const float r = ((hex >> 24) & 0xFF) / 255.0f; - const float g = ((hex >> 16) & 0xFF) / 255.0f; - const float b = ((hex >> 8) & 0xFF) / 255.0f; - const float a = (hex & 0xFF) / 255.0f; - return ImVec4(r, g, b, a); - } - return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); -} - -uint32_t get_color_u32(const clrsync::core::palette &pal, const std::string &key, - const std::string &fallback) -{ - auto colors = pal.colors(); - if (colors.empty()) - return 0xFFFFFFFF; - - auto it = colors.find(key); - if (it == colors.end() && !fallback.empty()) - { - it = colors.find(fallback); - } - - if (it != colors.end()) - { - const auto &col = it->second; - const uint32_t hex = col.hex(); - const uint32_t r = (hex >> 24) & 0xFF; - const uint32_t g = (hex >> 16) & 0xFF; - const uint32_t b = (hex >> 8) & 0xFF; - const uint32_t a = hex & 0xFF; - return (a << 24) | (b << 16) | (g << 8) | r; - } - return 0xFFFFFFFF; -} - -bool render_delete_confirmation_popup(const std::string &popup_title, const std::string &item_name, - const std::string &item_type, - const clrsync::core::palette &pal, - const std::function &on_delete) -{ - bool result = false; - if (ImGui::BeginPopupModal(popup_title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImVec4 warning_color = get_color(pal, "warning", "accent"); - ImGui::TextColored(warning_color, "Are you sure you want to delete '%s'?", - item_name.c_str()); - ImGui::Text("This action cannot be undone."); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - float button_width = 120.0f; - float total_width = 2.0f * button_width + ImGui::GetStyle().ItemSpacing.x; - float window_width = ImGui::GetContentRegionAvail().x; - ImGui::SetCursorPosX((window_width - total_width) * 0.5f); - - if (ImGui::Button("Delete", ImVec2(button_width, 0))) - { - on_delete(); - result = true; - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(button_width, 0))) - { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - return result; -} - -} // namespace palette_utils \ No newline at end of file diff --git a/src/gui/helpers/imgui_helpers.hpp b/src/gui/helpers/imgui_helpers.hpp deleted file mode 100644 index a57e8f4..0000000 --- a/src/gui/helpers/imgui_helpers.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CLRSYNC_IMGUI_HELPERS_HPP -#define CLRSYNC_IMGUI_HELPERS_HPP - -#include "core/palette/palette.hpp" -#include "gui/views/about_window.hpp" -#include "imgui.h" -#include -#include - -struct GLFWwindow; -class settings_window; - -GLFWwindow *init_glfw(); -void init_imgui(GLFWwindow *window, const std::string &ini_path); -void begin_frame(); -void setup_main_dockspace(bool &first_time); -void end_frame(GLFWwindow *window); -void shutdown(GLFWwindow *window); -void render_menu_bar(about_window *about, settings_window *settings); - -namespace palette_utils -{ -ImVec4 get_color(const clrsync::core::palette &pal, const std::string &key, - const std::string &fallback = ""); -uint32_t get_color_u32(const clrsync::core::palette &pal, const std::string &key, - const std::string &fallback = ""); -bool render_delete_confirmation_popup(const std::string &popup_title, const std::string &item_name, - const std::string &item_type, - const clrsync::core::palette &pal, - const std::function &on_delete); -} // namespace palette_utils - -namespace imgui_helpers -{ -inline ImVec4 get_palette_color(const clrsync::core::palette &pal, const std::string &key, - const std::string &fallback = "") -{ - return palette_utils::get_color(pal, key, fallback); -} -} // namespace imgui_helpers - -#endif // CLRSYNC_IMGUI_HELPERS_HPP diff --git a/src/gui/layout/main_layout.cpp b/src/gui/layout/main_layout.cpp new file mode 100644 index 0000000..b0536e1 --- /dev/null +++ b/src/gui/layout/main_layout.cpp @@ -0,0 +1,116 @@ +#include "main_layout.hpp" +#include "imgui.h" +#include "imgui_internal.h" + +namespace clrsync::gui::layout +{ + +void main_layout::render_menu_bar() +{ + ImGuiViewport *vp = ImGui::GetMainViewport(); + + const float bar_height = 30.0f; + + ImGui::SetNextWindowPos(vp->Pos); + ImGui::SetNextWindowSize(ImVec2(vp->Size.x, bar_height)); + ImGui::SetNextWindowViewport(vp->ID); + + ImGuiWindowFlags flags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 3)); + + ImGui::Begin("##TopBar", nullptr, flags); + + ImGuiStyle &style = ImGui::GetStyle(); + + const char *settings_label = "Settings"; + const char *about_label = "About"; + + ImVec2 settings_size = ImGui::CalcTextSize(settings_label); + ImVec2 about_size = ImGui::CalcTextSize(about_label); + + float total_width = settings_size.x + style.FramePadding.x * 2.0f + about_size.x + + style.FramePadding.x * 2.0f + style.ItemSpacing.x; + + float pos_x = ImGui::GetWindowWidth() - total_width - style.WindowPadding.x; + + float button_height = ImGui::GetFrameHeight(); + float window_height = ImGui::GetWindowHeight(); + float center_y = (window_height - button_height) * 0.5f; + + ImGui::SetCursorPos(ImVec2(pos_x, center_y)); + + if (ImGui::Button(settings_label)) + { + m_show_settings = true; + } + + ImGui::SameLine(); + ImGui::SetCursorPosY(center_y); + + if (ImGui::Button(about_label)) + { + m_show_about = true; + } + + ImGui::End(); + + ImGui::PopStyleVar(4); +} + +void main_layout::setup_dockspace(bool &first_time) +{ + const ImGuiViewport *viewport = ImGui::GetMainViewport(); + const float topbar_height = 32.0f; + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + topbar_height)); + ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - topbar_height)); + ImGui::SetNextWindowViewport(viewport->ID); + + constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoNavFocus; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 3)); + ImGui::Begin("MainDockSpace", nullptr, flags); + ImGui::PopStyleVar(4); + + ImGuiID dockspace_id = ImGui::GetID("MainDockSpace"); + + if (first_time) + { + first_time = false; + + ImGui::DockBuilderRemoveNode(dockspace_id); + ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size); + + ImGuiID center, right; + ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.5f, &right, ¢er); + + ImGuiDockNode *center_node = ImGui::DockBuilderGetNode(center); + if (center_node) + { + center_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode; + } + + ImGui::DockBuilderDockWindow("Color Schemes", right); + ImGui::DockBuilderDockWindow("Color Preview", center); + ImGui::DockBuilderDockWindow("Templates", center); + + ImGui::DockBuilderFinish(dockspace_id); + } + + ImGui::DockSpace(dockspace_id, ImVec2{0, 0}, ImGuiDockNodeFlags_None); + ImGui::End(); +} + +} // namespace clrsync::gui::layout diff --git a/src/gui/layout/main_layout.hpp b/src/gui/layout/main_layout.hpp new file mode 100644 index 0000000..8e70220 --- /dev/null +++ b/src/gui/layout/main_layout.hpp @@ -0,0 +1,28 @@ +#ifndef CLRSYNC_GUI_LAYOUT_MAIN_LAYOUT_HPP +#define CLRSYNC_GUI_LAYOUT_MAIN_LAYOUT_HPP + +namespace clrsync::gui::layout +{ + +class main_layout +{ + public: + void setup_dockspace(bool &first_time); + void render_menu_bar(); + + bool should_show_about() const { return m_show_about; } + bool should_show_settings() const { return m_show_settings; } + void clear_actions() + { + m_show_about = false; + m_show_settings = false; + } + + private: + bool m_show_about = false; + bool m_show_settings = false; +}; + +} // namespace clrsync::gui::layout + +#endif // CLRSYNC_GUI_LAYOUT_MAIN_LAYOUT_HPP diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 1c83d85..bd7e4e8 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -1,17 +1,13 @@ #include #include -#include -#include - #include "core/common/error.hpp" #include "core/common/utils.hpp" #include "core/config/config.hpp" #include "core/io/toml_file.hpp" #include "gui/backend/glfw_opengl.hpp" -#include "gui/helpers/imgui_helpers.hpp" -#include "gui/platform/font_loader.hpp" +#include "gui/layout/main_layout.hpp" #include "gui/ui_manager.hpp" #include "gui/views/about_window.hpp" #include "gui/views/color_scheme_editor.hpp" @@ -37,13 +33,12 @@ int main(int, char **) static std::string ini_path = (base.parent_path() / "layout.ini").string(); bool first_time = !std::filesystem::exists(ini_path); - printf("GLFW Version: %s\n", glfwGetVersionString()); - auto backend = clrsync::gui::backend::glfw_opengl_backend(); auto window_config = clrsync::gui::backend::window_config(); window_config.title = "clrsync"; window_config.width = 1280; window_config.height = 720; + window_config.decorated = true; window_config.transparent_framebuffer = true; if (!backend.initialize(window_config)) @@ -52,14 +47,8 @@ int main(int, char **) return 1; } - std::cout << "GLFW runtime platform: "; - switch (glfwGetPlatform()) { - case GLFW_PLATFORM_WAYLAND: std::cout << "Wayland\n"; break; - case GLFW_PLATFORM_X11: std::cout << "X11\n"; break; - case GLFW_PLATFORM_COCOA: std::cout << "Cocoa\n"; break; - case GLFW_PLATFORM_WIN32: std::cout << "Win32\n"; break; - default: std::cout << "Unknown\n"; - } + std::cout << "GLFW Version: " << backend.get_glfw_version() << std::endl; + std::cout << "GLFW runtime platform: " << backend.get_glfw_platform() << std::endl; clrsync::gui::ui_manager ui_manager(&backend); @@ -74,18 +63,11 @@ int main(int, char **) return 1; } - font_loader loader; - - ImFont *font = loader.load_font(clrsync::core::config::instance().font().c_str(), - clrsync::core::config::instance().font_size()); - - if (font) - ImGui::GetIO().FontDefault = font; - + clrsync::gui::layout::main_layout main_layout; color_scheme_editor colorEditor; - template_editor templateEditor; + template_editor templateEditor(&ui_manager); about_window aboutWindow; - settings_window settingsWindow; + settings_window settingsWindow(&ui_manager); colorEditor.set_template_editor(&templateEditor); colorEditor.set_settings_window(&settingsWindow); @@ -96,18 +78,25 @@ int main(int, char **) { backend.begin_frame(); - loader.push_default_font(); + ui_manager.push_default_font(); ui_manager.begin_frame(); - render_menu_bar(&aboutWindow, &settingsWindow); - setup_main_dockspace(first_time); + main_layout.render_menu_bar(); + main_layout.setup_dockspace(first_time); + + if (main_layout.should_show_about()) + aboutWindow.show(); + if (main_layout.should_show_settings()) + settingsWindow.show(); + main_layout.clear_actions(); + templateEditor.render(); colorEditor.render_controls_and_colors(); colorEditor.render_preview(); aboutWindow.render(colorEditor.controller().current_palette()); settingsWindow.render(); - loader.pop_font(); + ui_manager.pop_font(); ui_manager.end_frame(); backend.end_frame(); diff --git a/src/gui/ui_manager.cpp b/src/gui/ui_manager.cpp index d377b63..0ae1622 100644 --- a/src/gui/ui_manager.cpp +++ b/src/gui/ui_manager.cpp @@ -1,5 +1,8 @@ #include "gui/ui_manager.hpp" #include "gui/backend/backend.hpp" +#include "gui/platform/font_loader.hpp" +#include "gui/platform/file_browser.hpp" +#include "core/config/config.hpp" #include @@ -8,11 +11,14 @@ namespace clrsync::gui ui_manager::ui_manager(backend::backend_interface* backend) : m_backend(backend) { + m_font_loader = new font_loader(); } ui_manager::~ui_manager() { shutdown(); + delete m_font_loader; + m_font_loader = nullptr; } bool ui_manager::initialize(const ui_config& config) @@ -44,6 +50,13 @@ bool ui_manager::initialize(const ui_config& config) return false; } + ImFont *font = m_font_loader->load_font( + clrsync::core::config::instance().font().c_str(), + clrsync::core::config::instance().font_size()); + + if (font) + io.FontDefault = font; + return true; } @@ -68,4 +81,57 @@ void ui_manager::end_frame() ImGui::Render(); m_backend->imgui_render_draw_data(ImGui::GetDrawData()); } -} \ No newline at end of file + +void ui_manager::push_default_font() +{ + if (m_font_loader) + m_font_loader->push_default_font(); +} + +void ui_manager::pop_font() +{ + if (m_font_loader) + m_font_loader->pop_font(); +} + +std::vector ui_manager::get_system_fonts() const +{ + if (m_font_loader) + return m_font_loader->get_system_fonts(); + return {}; +} + +bool ui_manager::reload_font(const char* font_name, float size) +{ + if (!m_font_loader) + return false; + + ImFont* font = m_font_loader->load_font(font_name, size); + if (font) + { + ImGui::GetIO().FontDefault = font; + return true; + } + return false; +} + +std::string ui_manager::open_file_dialog(const std::string& title, + const std::string& initial_path, + const std::vector& filters) +{ + return file_dialogs::open_file_dialog(title, initial_path, filters); +} + +std::string ui_manager::save_file_dialog(const std::string& title, + const std::string& initial_path, + const std::vector& filters) +{ + return file_dialogs::save_file_dialog(title, initial_path, filters); +} + +std::string ui_manager::select_folder_dialog(const std::string& title, + const std::string& initial_path) +{ + return file_dialogs::select_folder_dialog(title, initial_path); +} +} diff --git a/src/gui/ui_manager.hpp b/src/gui/ui_manager.hpp index 77738d1..3888291 100644 --- a/src/gui/ui_manager.hpp +++ b/src/gui/ui_manager.hpp @@ -1,6 +1,10 @@ #ifndef CLRSYNC_UI_MANAGER_HPP #define CLRSYNC_UI_MANAGER_HPP #include +#include + +class font_loader; +struct ImFont; namespace clrsync::gui::backend { @@ -29,9 +33,25 @@ public: void begin_frame(); void end_frame(); + void push_default_font(); + void pop_font(); + + std::vector get_system_fonts() const; + bool reload_font(const char* font_name, float size); + + std::string open_file_dialog(const std::string& title = "Open File", + const std::string& initial_path = "", + const std::vector& filters = {}); + std::string save_file_dialog(const std::string& title = "Save File", + const std::string& initial_path = "", + const std::vector& filters = {}); + std::string select_folder_dialog(const std::string& title = "Select Folder", + const std::string& initial_path = ""); + private: backend::backend_interface *m_backend; void *m_imgui_context = nullptr; + font_loader *m_font_loader = nullptr; }; } diff --git a/src/gui/views/about_window.cpp b/src/gui/views/about_window.cpp index d270230..43b75e1 100644 --- a/src/gui/views/about_window.cpp +++ b/src/gui/views/about_window.cpp @@ -1,6 +1,6 @@ #include "gui/views/about_window.hpp" #include "core/common/version.hpp" -#include "gui/helpers/imgui_helpers.hpp" +#include "gui/widgets/colors.hpp" #include "imgui.h" about_window::about_window() @@ -22,14 +22,14 @@ void about_window::render(const clrsync::core::palette &pal) const char *title = "clrsync"; const float title_size = ImGui::CalcTextSize(title).x; ImGui::SetCursorPosX((window_width - title_size) * 0.5f); - ImVec4 title_color = palette_utils::get_color(pal, "info", "accent"); + ImVec4 title_color = clrsync::gui::widgets::palette_color(pal, "info", "accent"); ImGui::TextColored(title_color, "%s", title); ImGui::PopFont(); std::string version = "Version " + clrsync::core::version_string(); const float version_size = ImGui::CalcTextSize(version.c_str()).x; ImGui::SetCursorPosX((window_width - version_size) * 0.5f); - ImVec4 subtitle_color = palette_utils::get_color(pal, "editor_inactive", "foreground"); + ImVec4 subtitle_color = clrsync::gui::widgets::palette_color(pal, "editor_inactive", "foreground"); ImGui::TextColored(subtitle_color, "%s", version.c_str()); ImGui::Spacing(); @@ -79,7 +79,7 @@ void about_window::render(const clrsync::core::palette &pal) ImGui::Separator(); ImGui::Spacing(); - ImVec4 license_color = palette_utils::get_color(pal, "editor_inactive", "foreground"); + ImVec4 license_color = clrsync::gui::widgets::palette_color(pal, "editor_inactive", "foreground"); ImGui::TextColored(license_color, "MIT License"); ImGui::TextWrapped( "Copyright (c) 2025 Daniel Dada\n\n" @@ -93,4 +93,4 @@ void about_window::render(const clrsync::core::palette &pal) "copies or substantial portions of the Software."); } ImGui::End(); -} \ No newline at end of file +} diff --git a/src/gui/views/color_scheme_editor.cpp b/src/gui/views/color_scheme_editor.cpp index 818a6ec..daed7b4 100644 --- a/src/gui/views/color_scheme_editor.cpp +++ b/src/gui/views/color_scheme_editor.cpp @@ -1,11 +1,13 @@ #include "color_scheme_editor.hpp" #include "gui/controllers/theme_applier.hpp" -#include "gui/helpers/imgui_helpers.hpp" +#include "gui/widgets/dialogs.hpp" +#include "gui/widgets/palette_selector.hpp" +#include "gui/widgets/input_dialog.hpp" +#include "gui/widgets/action_buttons.hpp" #include "imgui.h" #include "settings_window.hpp" #include "template_editor.hpp" #include -#include color_scheme_editor::color_scheme_editor() { @@ -20,6 +22,8 @@ color_scheme_editor::color_scheme_editor() { std::cout << "WARNING: No palette loaded, skipping theme application\n"; } + + setup_widgets(); } void color_scheme_editor::notify_palette_changed() @@ -78,102 +82,22 @@ void color_scheme_editor::render_controls() ImGui::Text("Palette:"); ImGui::SameLine(); - ImGui::SetNextItemWidth(200.0f); - if (ImGui::BeginCombo("##scheme", current.name().c_str())) - { - for (const auto &name : palettes | std::views::keys) - { - const bool selected = current.name() == name; - if (ImGui::Selectable(name.c_str(), selected)) - { - m_controller.select_palette(name); - apply_themes(); - } - if (selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Select a color palette to edit"); + m_palette_selector.render(m_controller, 200.0f); ImGui::SameLine(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); - static char new_palette_name_buf[128] = ""; if (ImGui::Button(" + New ")) { - new_palette_name_buf[0] = 0; - ImGui::OpenPopup("New Palette"); + m_new_palette_dialog.open("New Palette", "Enter a name for the new palette:", "Palette name..."); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Create a new palette"); - if (ImGui::BeginPopupModal("New Palette", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::Text("Enter a name for the new palette:"); - ImGui::Spacing(); - - ImGui::SetNextItemWidth(250); - ImGui::InputTextWithHint("##new_palette_input", "Palette name...", new_palette_name_buf, - IM_ARRAYSIZE(new_palette_name_buf)); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - bool can_create = strlen(new_palette_name_buf) > 0; - - if (!can_create) - ImGui::BeginDisabled(); - - if (ImGui::Button("Create", ImVec2(120, 0))) - { - m_controller.create_palette(new_palette_name_buf); - m_controller.select_palette(new_palette_name_buf); - apply_themes(); - new_palette_name_buf[0] = 0; - ImGui::CloseCurrentPopup(); - } - - if (!can_create) - ImGui::EndDisabled(); - - ImGui::SameLine(); - - if (ImGui::Button("Cancel", ImVec2(120, 0))) - { - new_palette_name_buf[0] = 0; - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } + m_new_palette_dialog.render(); ImGui::SameLine(); - if (ImGui::Button(" Save ")) - { - m_controller.save_current_palette(); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Save current palette to file"); - - ImGui::SameLine(); - auto error = palette_utils::get_color(current, "error"); - auto error_hover = ImVec4(error.x * 1.1f, error.y * 1.1f, error.z * 1.1f, error.w); - auto error_active = ImVec4(error.x * 0.8f, error.y * 0.8f, error.z * 0.8f, error.w); - auto on_error = palette_utils::get_color(current, "on_error"); - ImGui::PushStyleColor(ImGuiCol_Button, error); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, error_hover); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, error_active); - ImGui::PushStyleColor(ImGuiCol_Text, on_error); - if (ImGui::Button(" Delete ")) - { - m_show_delete_confirmation = true; - } - ImGui::PopStyleColor(4); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Delete current palette"); + m_action_buttons.render(current); if (m_show_delete_confirmation) { @@ -181,21 +105,47 @@ void color_scheme_editor::render_controls() m_show_delete_confirmation = false; } - palette_utils::render_delete_confirmation_popup("Delete Palette?", current.name(), "palette", + clrsync::gui::widgets::delete_confirmation_dialog("Delete Palette?", current.name(), "palette", current, [this]() { m_controller.delete_current_palette(); apply_themes(); }); - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 16); - - if (ImGui::Button(" Apply Theme ")) - { - m_controller.apply_current_theme(); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Apply current palette to all enabled templates"); - ImGui::PopStyleVar(2); } + +void color_scheme_editor::setup_widgets() +{ + m_palette_selector.set_on_selection_changed([this](const std::string &name) { + m_controller.select_palette(name); + apply_themes(); + }); + + m_new_palette_dialog.set_on_submit([this](const std::string &name) { + m_controller.create_palette(name); + m_controller.select_palette(name); + apply_themes(); + }); + + m_action_buttons.add_button({ + " Save ", + "Save current palette to file", + [this]() { m_controller.save_current_palette(); } + }); + + m_action_buttons.add_button({ + " Delete ", + "Delete current palette", + [this]() { m_show_delete_confirmation = true; }, + true, + true + }); + + m_action_buttons.add_button({ + " Apply Theme ", + "Apply current palette to all enabled templates", + [this]() { m_controller.apply_current_theme(); } + }); + + m_action_buttons.set_spacing(16.0f); +} diff --git a/src/gui/views/color_scheme_editor.hpp b/src/gui/views/color_scheme_editor.hpp index 8297595..e113ca7 100644 --- a/src/gui/views/color_scheme_editor.hpp +++ b/src/gui/views/color_scheme_editor.hpp @@ -4,6 +4,9 @@ #include "gui/controllers/palette_controller.hpp" #include "gui/views/color_table_renderer.hpp" #include "gui/views/preview_renderer.hpp" +#include "gui/widgets/palette_selector.hpp" +#include "gui/widgets/input_dialog.hpp" +#include "gui/widgets/action_buttons.hpp" class template_editor; class settings_window; @@ -32,6 +35,7 @@ class color_scheme_editor void render_controls(); void apply_themes(); void notify_palette_changed(); + void setup_widgets(); palette_controller m_controller; color_table_renderer m_color_table; @@ -39,6 +43,10 @@ class color_scheme_editor template_editor *m_template_editor{nullptr}; settings_window *m_settings_window{nullptr}; bool m_show_delete_confirmation{false}; + + clrsync::gui::widgets::palette_selector m_palette_selector; + clrsync::gui::widgets::input_dialog m_new_palette_dialog; + clrsync::gui::widgets::action_buttons m_action_buttons; }; #endif // CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP \ No newline at end of file diff --git a/src/gui/views/color_table_renderer.cpp b/src/gui/views/color_table_renderer.cpp index bc31bbf..c8a29b5 100644 --- a/src/gui/views/color_table_renderer.cpp +++ b/src/gui/views/color_table_renderer.cpp @@ -1,5 +1,5 @@ #include "gui/views/color_table_renderer.hpp" -#include "gui/helpers/imgui_helpers.hpp" +#include "gui/widgets/colors.hpp" #include "imgui.h" #include #include @@ -36,7 +36,7 @@ void color_table_renderer::render_color_row(const std::string &name, ImGui::TableSetColumnIndex(0); const float key_col_width = ImGui::GetContentRegionAvail().x; - ImVec4 text_color = palette_utils::get_color(current, "info", "accent"); + ImVec4 text_color = clrsync::gui::widgets::palette_color(current, "info", "accent"); ImGui::PushStyleColor(ImGuiCol_Text, text_color); const bool copied = ImGui::Selectable(name.c_str(), false, 0, ImVec2(key_col_width, 0.0f)); ImGui::PopStyleColor(); @@ -107,7 +107,7 @@ void color_table_renderer::render(const clrsync::core::palette ¤t, { if (current.colors().empty()) { - ImVec4 warning_color = palette_utils::get_color(current, "warning", "accent"); + ImVec4 warning_color = clrsync::gui::widgets::palette_color(current, "warning", "accent"); ImGui::TextColored(warning_color, "No palette loaded"); return; } @@ -153,7 +153,7 @@ void color_table_renderer::render(const clrsync::core::palette ¤t, if (!has_matches) return; - ImGui::PushStyleColor(ImGuiCol_Text, palette_utils::get_color(current, "accent")); + ImGui::PushStyleColor(ImGuiCol_Text, clrsync::gui::widgets::palette_color(current, "accent")); bool header_open = ImGui::TreeNodeEx(title, ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::PopStyleColor(); diff --git a/src/gui/views/preview_renderer.cpp b/src/gui/views/preview_renderer.cpp index 2c60d9c..e697adb 100644 --- a/src/gui/views/preview_renderer.cpp +++ b/src/gui/views/preview_renderer.cpp @@ -1,6 +1,6 @@ #include "gui/views/preview_renderer.hpp" #include "gui/controllers/theme_applier.hpp" -#include "gui/helpers/imgui_helpers.hpp" +#include "gui/widgets/colors.hpp" #include "imgui.h" #include #include @@ -236,7 +236,7 @@ void preview_renderer::render(const clrsync::core::palette ¤t) { if (current.colors().empty()) { - ImVec4 error_color = palette_utils::get_color(current, "error", "accent"); + ImVec4 error_color = clrsync::gui::widgets::palette_color(current, "error", "accent"); ImGui::TextColored(error_color, "Current palette is empty"); return; } diff --git a/src/gui/views/settings_window.cpp b/src/gui/views/settings_window.cpp index 2d03aad..ac317ba 100644 --- a/src/gui/views/settings_window.cpp +++ b/src/gui/views/settings_window.cpp @@ -1,21 +1,21 @@ #include "gui/views/settings_window.hpp" #include "core/common/error.hpp" #include "core/config/config.hpp" -#include "gui/helpers/imgui_helpers.hpp" -#include "gui/platform/file_browser.hpp" -#include "gui/platform/font_loader.hpp" +#include "gui/widgets/colors.hpp" +#include "gui/ui_manager.hpp" #include "imgui.h" #include -settings_window::settings_window() - : m_font_size(14), m_selected_font_idx(0), m_settings_changed(false), m_current_tab(0) +settings_window::settings_window(clrsync::gui::ui_manager* ui_mgr) + : m_font_size(14), m_selected_font_idx(0), m_settings_changed(false), m_current_tab(0), + m_ui_manager(ui_mgr) { m_default_theme[0] = '\0'; m_palettes_path[0] = '\0'; m_font[0] = '\0'; - font_loader loader; - m_available_fonts = loader.get_system_fonts(); + if (m_ui_manager) + m_available_fonts = m_ui_manager->get_system_fonts(); load_settings(); } @@ -145,10 +145,14 @@ void settings_window::apply_settings() return; } - font_loader fn_loader; - auto font = fn_loader.load_font(m_font, m_font_size); - if (font) - ImGui::GetIO().FontDefault = font; + if (m_ui_manager) + { + if (!m_ui_manager->reload_font(m_font, m_font_size)) + { + m_error_message = "Failed to load font: " + std::string(m_font); + return; + } + } m_error_message.clear(); m_settings_changed = false; @@ -158,7 +162,7 @@ void settings_window::render_general_tab() { ImGui::Spacing(); - auto accent_color = palette_utils::get_color(m_current_palette, "accent"); + auto accent_color = clrsync::gui::widgets::palette_color(m_current_palette, "accent"); ImGui::TextColored(accent_color, "Theme Settings"); ImGui::Separator(); ImGui::Spacing(); @@ -186,7 +190,7 @@ void settings_window::render_general_tab() if (ImGui::Button("Browse")) { std::string selected_path = - file_dialogs::select_folder_dialog("Select Palettes Directory", m_palettes_path); + m_ui_manager->select_folder_dialog("Select Palettes Directory", m_palettes_path); if (!selected_path.empty()) { strncpy(m_palettes_path, selected_path.c_str(), sizeof(m_palettes_path) - 1); @@ -200,7 +204,7 @@ void settings_window::render_appearance_tab() { ImGui::Spacing(); - auto accent_color = palette_utils::get_color(m_current_palette, "accent"); + auto accent_color = clrsync::gui::widgets::palette_color(m_current_palette, "accent"); ImGui::TextColored(accent_color, "Font Settings"); ImGui::Separator(); ImGui::Spacing(); @@ -254,8 +258,8 @@ void settings_window::render_status_messages() { ImGui::Spacing(); - auto error_bg_color = palette_utils::get_color(m_current_palette, "error"); - auto error_text_color = palette_utils::get_color(m_current_palette, "on_error"); + auto error_bg_color = clrsync::gui::widgets::palette_color(m_current_palette, "error"); + auto error_text_color = clrsync::gui::widgets::palette_color(m_current_palette, "on_error"); ImGui::PushStyleColor(ImGuiCol_ChildBg, error_bg_color); ImGui::PushStyleColor(ImGuiCol_Border, error_bg_color); @@ -375,4 +379,4 @@ void settings_window::reset_to_defaults() m_font_size = 14; m_error_message.clear(); m_settings_changed = true; -} \ No newline at end of file +} diff --git a/src/gui/views/settings_window.hpp b/src/gui/views/settings_window.hpp index c27f175..194638d 100644 --- a/src/gui/views/settings_window.hpp +++ b/src/gui/views/settings_window.hpp @@ -5,10 +5,15 @@ #include #include +namespace clrsync::gui +{ +class ui_manager; +} + class settings_window { public: - settings_window(); + settings_window(clrsync::gui::ui_manager* ui_mgr); void render(); void show() { @@ -55,6 +60,7 @@ class settings_window int m_current_tab; clrsync::core::palette m_current_palette; + clrsync::gui::ui_manager* m_ui_manager; }; #endif // CLRSYNC_GUI_SETTINGS_WINDOW_HPP \ No newline at end of file diff --git a/src/gui/views/template_editor.cpp b/src/gui/views/template_editor.cpp index 4c1b000..efa1d41 100644 --- a/src/gui/views/template_editor.cpp +++ b/src/gui/views/template_editor.cpp @@ -3,8 +3,9 @@ #include "core/config/config.hpp" #include "core/palette/color_keys.hpp" #include "core/theme/theme_template.hpp" -#include "gui/helpers/imgui_helpers.hpp" -#include "gui/platform/file_browser.hpp" +#include "gui/widgets/colors.hpp" +#include "gui/widgets/dialogs.hpp" +#include "gui/ui_manager.hpp" #include "imgui.h" #include #include @@ -18,7 +19,8 @@ const std::vector COLOR_FORMATS = { "l", "hsl", "hsla"}; } -template_editor::template_editor() : m_template_name("new_template") +template_editor::template_editor(clrsync::gui::ui_manager* ui_mgr) + : m_template_name("new_template"), m_ui_manager(ui_mgr) { m_autocomplete_bg_color = ImVec4(0.12f, 0.12f, 0.15f, 0.98f); m_autocomplete_border_color = ImVec4(0.4f, 0.4f, 0.45f, 1.0f); @@ -56,7 +58,7 @@ void template_editor::apply_current_palette(const clrsync::core::palette &pal) if (colors.empty()) return; auto get_color_u32 = [&](const std::string &key, const std::string &fallback = "") -> uint32_t { - return palette_utils::get_color_u32(pal, key, fallback); + return clrsync::gui::widgets::palette_color_u32(pal, key, fallback); }; auto palette = m_editor.GetPalette(); @@ -99,14 +101,14 @@ void template_editor::apply_current_palette(const clrsync::core::palette &pal) m_editor.SetPalette(palette); - m_autocomplete_bg_color = palette_utils::get_color(pal, "surface", "background"); + m_autocomplete_bg_color = clrsync::gui::widgets::palette_color(pal, "surface", "background"); m_autocomplete_bg_color.w = 0.98f; - m_autocomplete_border_color = palette_utils::get_color(pal, "border", "surface_variant"); - m_autocomplete_selected_color = palette_utils::get_color(pal, "accent", "surface_variant"); - m_autocomplete_text_color = palette_utils::get_color(pal, "on_surface", "foreground"); - m_autocomplete_selected_text_color = palette_utils::get_color(pal, "on_surface", "foreground"); + m_autocomplete_border_color = clrsync::gui::widgets::palette_color(pal, "border", "surface_variant"); + m_autocomplete_selected_color = clrsync::gui::widgets::palette_color(pal, "accent", "surface_variant"); + m_autocomplete_text_color = clrsync::gui::widgets::palette_color(pal, "on_surface", "foreground"); + m_autocomplete_selected_text_color = clrsync::gui::widgets::palette_color(pal, "on_surface", "foreground"); m_autocomplete_dim_text_color = - palette_utils::get_color(pal, "on_surface_variant", "editor_inactive"); + clrsync::gui::widgets::palette_color(pal, "on_surface_variant", "editor_inactive"); } void template_editor::update_autocomplete_suggestions() @@ -358,7 +360,7 @@ void template_editor::render() m_show_delete_confirmation = false; } - palette_utils::render_delete_confirmation_popup( + clrsync::gui::widgets::delete_confirmation_dialog( "Delete Template?", m_template_name, "template", m_current_palette, [this]() { bool success = m_template_controller.remove_template(m_template_name); if (success) @@ -403,10 +405,10 @@ void template_editor::render_controls() if (m_is_editing_existing) { ImGui::SameLine(); - auto error = palette_utils::get_color(m_current_palette, "error"); + auto error = clrsync::gui::widgets::palette_color(m_current_palette, "error"); auto error_hover = ImVec4(error.x * 1.1f, error.y * 1.1f, error.z * 1.1f, error.w); auto error_active = ImVec4(error.x * 0.8f, error.y * 0.8f, error.z * 0.8f, error.w); - auto on_error = palette_utils::get_color(m_current_palette, "on_error"); + auto on_error = clrsync::gui::widgets::palette_color(m_current_palette, "on_error"); ImGui::PushStyleColor(ImGuiCol_Button, error); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, error_hover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, error_active); @@ -426,9 +428,9 @@ void template_editor::render_controls() bool enabled_changed = false; if (m_enabled) { - ImVec4 success_color = palette_utils::get_color(m_current_palette, "success", "accent"); + ImVec4 success_color = clrsync::gui::widgets::palette_color(m_current_palette, "success", "accent"); ImVec4 success_on_color = - palette_utils::get_color(m_current_palette, "on_success", "on_surface"); + clrsync::gui::widgets::palette_color(m_current_palette, "on_success", "on_surface"); ImVec4 success_hover = ImVec4(success_color.x * 1.2f, success_color.y * 1.2f, success_color.z * 1.2f, 0.6f); ImGui::PushStyleColor(ImGuiCol_FrameBg, @@ -438,9 +440,9 @@ void template_editor::render_controls() } else { - ImVec4 error_color = palette_utils::get_color(m_current_palette, "error", "accent"); + ImVec4 error_color = clrsync::gui::widgets::palette_color(m_current_palette, "error", "accent"); ImVec4 error_on_color = - palette_utils::get_color(m_current_palette, "on_error", "on_surface"); + clrsync::gui::widgets::palette_color(m_current_palette, "on_error", "on_surface"); ImVec4 error_hover = ImVec4(error_color.x * 1.2f, error_color.y * 1.2f, error_color.z * 1.2f, 0.6f); ImGui::PushStyleColor(ImGuiCol_FrameBg, @@ -503,7 +505,7 @@ void template_editor::render_controls() if (ImGui::Button("Browse##input")) { std::string selected_path = - file_dialogs::open_file_dialog("Select Template File", m_input_path); + m_ui_manager->open_file_dialog("Select Template File", m_input_path); if (!selected_path.empty()) { m_input_path = selected_path; @@ -540,7 +542,7 @@ void template_editor::render_controls() if (ImGui::Button("Browse##output")) { std::string selected_path = - file_dialogs::save_file_dialog("Select Output File", m_output_path); + m_ui_manager->save_file_dialog("Select Output File", m_output_path); if (!selected_path.empty()) { m_output_path = selected_path; @@ -575,7 +577,7 @@ void template_editor::render_controls() if (!m_validation_error.empty()) { ImGui::Spacing(); - ImVec4 error_color = palette_utils::get_color(m_current_palette, "error", "accent"); + ImVec4 error_color = clrsync::gui::widgets::palette_color(m_current_palette, "error", "accent"); ImGui::PushStyleColor(ImGuiCol_Text, error_color); ImGui::TextWrapped("%s", m_validation_error.c_str()); ImGui::PopStyleColor(); @@ -588,7 +590,7 @@ void template_editor::render_editor() if (!m_is_editing_existing) { - ImVec4 success_color = palette_utils::get_color(m_current_palette, "success", "accent"); + ImVec4 success_color = clrsync::gui::widgets::palette_color(m_current_palette, "success", "accent"); ImGui::PushStyleColor(ImGuiCol_Text, success_color); ImGui::Text(" New Template"); ImGui::PopStyleColor(); @@ -608,7 +610,7 @@ void template_editor::render_editor() if (m_has_unsaved_changes) { ImGui::SameLine(); - ImVec4 warning_color = palette_utils::get_color(m_current_palette, "warning", "accent"); + ImVec4 warning_color = clrsync::gui::widgets::palette_color(m_current_palette, "warning", "accent"); ImGui::PushStyleColor(ImGuiCol_Text, warning_color); ImGui::Text("(unsaved)"); ImGui::PopStyleColor(); @@ -697,7 +699,7 @@ void template_editor::render_template_list() if (!m_is_editing_existing) { - ImVec4 success_color = palette_utils::get_color(m_current_palette, "success", "accent"); + ImVec4 success_color = clrsync::gui::widgets::palette_color(m_current_palette, "success", "accent"); ImVec4 success_bg = ImVec4(success_color.x, success_color.y, success_color.z, 0.5f); ImGui::PushStyleColor(ImGuiCol_Text, success_color); ImGui::PushStyleColor(ImGuiCol_Header, success_bg); @@ -714,7 +716,7 @@ void template_editor::render_template_list() if (!tmpl.enabled()) { - ImVec4 disabled_color = palette_utils::get_color( + ImVec4 disabled_color = clrsync::gui::widgets::palette_color( m_current_palette, "on_surface_variant", "editor_inactive"); ImGui::PushStyleColor(ImGuiCol_Text, disabled_color); } @@ -951,4 +953,4 @@ void template_editor::delete_template() void template_editor::refresh_templates() { m_template_controller.refresh(); -} \ No newline at end of file +} diff --git a/src/gui/views/template_editor.hpp b/src/gui/views/template_editor.hpp index 7a9dcf3..761c860 100644 --- a/src/gui/views/template_editor.hpp +++ b/src/gui/views/template_editor.hpp @@ -8,10 +8,15 @@ #include #include +namespace clrsync::gui +{ +class ui_manager; +} + class template_editor { public: - template_editor(); + template_editor(clrsync::gui::ui_manager* ui_mgr); void render(); void apply_current_palette(const clrsync::core::palette &pal); @@ -62,6 +67,7 @@ class template_editor ImVec4 m_autocomplete_dim_text_color; clrsync::core::palette m_current_palette; + clrsync::gui::ui_manager* m_ui_manager; }; #endif // CLRSYNC_GUI_TEMPLATE_EDITOR_HPP \ No newline at end of file diff --git a/src/gui/widgets/action_buttons.cpp b/src/gui/widgets/action_buttons.cpp new file mode 100644 index 0000000..7aa8139 --- /dev/null +++ b/src/gui/widgets/action_buttons.cpp @@ -0,0 +1,93 @@ +#include "action_buttons.hpp" +#include "colors.hpp" +#include "imgui.h" + +namespace clrsync::gui::widgets +{ + +action_buttons::action_buttons() = default; + +void action_buttons::add_button(const action_button &button) +{ + m_buttons.push_back(button); +} + +void action_buttons::clear() +{ + m_buttons.clear(); +} + +void action_buttons::render(const core::palette &theme_palette) +{ + if (m_buttons.empty()) + return; + + if (m_use_separator) + { + ImGui::Separator(); + ImGui::Spacing(); + } + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(m_spacing, 8)); + + bool first = true; + for (const auto &button : m_buttons) + { + if (!first) + ImGui::SameLine(); + first = false; + + int style_colors_pushed = 0; + if (button.use_error_style) + { + auto error = palette_color(theme_palette, "error"); + auto error_hover = ImVec4(error.x * 1.1f, error.y * 1.1f, error.z * 1.1f, error.w); + auto error_active = ImVec4(error.x * 0.8f, error.y * 0.8f, error.z * 0.8f, error.w); + auto on_error = palette_color(theme_palette, "on_error"); + ImGui::PushStyleColor(ImGuiCol_Button, error); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, error_hover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, error_active); + ImGui::PushStyleColor(ImGuiCol_Text, on_error); + style_colors_pushed = 4; + } + else if (button.use_success_style) + { + auto success = palette_color(theme_palette, "success", "accent"); + auto success_hover = ImVec4(success.x * 1.1f, success.y * 1.1f, success.z * 1.1f, success.w); + auto success_active = ImVec4(success.x * 0.8f, success.y * 0.8f, success.z * 0.8f, success.w); + auto on_success = palette_color(theme_palette, "on_success", "on_surface"); + ImGui::PushStyleColor(ImGuiCol_Button, success); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, success_hover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, success_active); + ImGui::PushStyleColor(ImGuiCol_Text, on_success); + style_colors_pushed = 4; + } + + bool disabled = !button.enabled; + if (disabled) + ImGui::BeginDisabled(); + + if (ImGui::Button(button.label.c_str())) + { + if (button.on_click) + { + button.on_click(); + } + } + + if (disabled) + ImGui::EndDisabled(); + + if (style_colors_pushed > 0) + ImGui::PopStyleColor(style_colors_pushed); + + if (!button.tooltip.empty() && ImGui::IsItemHovered()) + { + ImGui::SetTooltip("%s", button.tooltip.c_str()); + } + } + + ImGui::PopStyleVar(); +} + +} // namespace clrsync::gui::widgets \ No newline at end of file diff --git a/src/gui/widgets/action_buttons.hpp b/src/gui/widgets/action_buttons.hpp new file mode 100644 index 0000000..e1cf68e --- /dev/null +++ b/src/gui/widgets/action_buttons.hpp @@ -0,0 +1,46 @@ +#ifndef CLRSYNC_GUI_WIDGETS_ACTION_BUTTONS_HPP +#define CLRSYNC_GUI_WIDGETS_ACTION_BUTTONS_HPP + +#include "core/palette/palette.hpp" +#include +#include +#include + +namespace clrsync::gui::widgets +{ + +struct action_button +{ + std::string label; + std::string tooltip; + std::function on_click; + bool enabled = true; + + bool use_error_style = false; + bool use_success_style = false; +}; + +class action_buttons +{ + public: + action_buttons(); + + void add_button(const action_button &button); + + void clear(); + + void render(const core::palette &theme_palette); + + void set_spacing(float spacing) { m_spacing = spacing; } + + void set_use_separator(bool use) { m_use_separator = use; } + + private: + std::vector m_buttons; + float m_spacing = 8.0f; + bool m_use_separator = false; +}; + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_ACTION_BUTTONS_HPP \ No newline at end of file diff --git a/src/gui/widgets/autocomplete.cpp b/src/gui/widgets/autocomplete.cpp new file mode 100644 index 0000000..cff8488 --- /dev/null +++ b/src/gui/widgets/autocomplete.cpp @@ -0,0 +1,287 @@ +#include "autocomplete.hpp" +#include "colors.hpp" +#include "imgui.h" +#include + +namespace clrsync::gui::widgets +{ + +autocomplete_widget::autocomplete_widget() + : m_selected_index(0), m_show_autocomplete(false), m_dismissed(false), m_dismiss_brace_pos(-1) +{ + m_bg_color = ImVec4(0.12f, 0.12f, 0.15f, 0.98f); + m_border_color = ImVec4(0.4f, 0.4f, 0.45f, 1.0f); + m_selected_color = ImVec4(0.25f, 0.45f, 0.75f, 0.9f); + m_text_color = ImVec4(0.85f, 0.85f, 0.9f, 1.0f); + m_selected_text_color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + m_dim_text_color = ImVec4(0.6f, 0.6f, 0.7f, 1.0f); +} + +void autocomplete_widget::update_suggestions(const TextEditor& editor, + const std::vector& available_keys, + const std::vector& available_formats) +{ + m_suggestions.clear(); + + auto cursor = editor.GetCursorPosition(); + std::string line = editor.GetCurrentLineText(); + int col = cursor.mColumn; + + // Check if inside '{' + int brace_pos = -1; + for (int i = col - 1; i >= 0; --i) + { + if (i < (int)line.length()) + { + if (line[i] == '{') + { + brace_pos = i; + break; + } + else if (line[i] == '}' || line[i] == ' ' || line[i] == '\t') + { + break; + } + } + } + + if (brace_pos < 0) + { + m_show_autocomplete = false; + m_dismissed = false; + return; + } + + if (m_dismissed) + { + bool should_reset_dismissal = false; + + if (cursor.mLine != m_dismiss_position.mLine || brace_pos != m_dismiss_brace_pos || + abs(cursor.mColumn - m_dismiss_position.mColumn) > 3) + { + should_reset_dismissal = true; + } + + if (should_reset_dismissal) + { + m_dismissed = false; + } + else + { + m_show_autocomplete = false; + return; + } + } + + m_prefix = line.substr(brace_pos + 1, col - brace_pos - 1); + m_start_pos = TextEditor::Coordinates(cursor.mLine, brace_pos + 1); + + size_t dot_pos = m_prefix.find('.'); + + if (dot_pos != std::string::npos) + { + std::string color_key = m_prefix.substr(0, dot_pos); + std::string format_prefix = m_prefix.substr(dot_pos + 1); + + bool valid_key = std::find(available_keys.begin(), available_keys.end(), color_key) != available_keys.end(); + + if (valid_key) + { + for (const auto &fmt : available_formats) + { + if (format_prefix.empty() || fmt.find(format_prefix) == 0 || + fmt.find(format_prefix) != std::string::npos) + { + m_suggestions.push_back(color_key + "." + fmt); + } + } + } + } + else + { + for (const auto& key : available_keys) + { + if (m_prefix.empty() || key.find(m_prefix) == 0 || + key.find(m_prefix) != std::string::npos) + { + m_suggestions.push_back(key); + } + } + } + + std::sort(m_suggestions.begin(), m_suggestions.end(), + [this](const std::string &a, const std::string &b) { + bool a_prefix = a.find(m_prefix) == 0; + bool b_prefix = b.find(m_prefix) == 0; + if (a_prefix != b_prefix) + return a_prefix; + return a < b; + }); + + m_show_autocomplete = !m_suggestions.empty(); + if (m_show_autocomplete && m_selected_index >= (int)m_suggestions.size()) + { + m_selected_index = 0; + } +} + +void autocomplete_widget::render(const ImVec2& editor_pos, TextEditor& editor) +{ + if (!m_show_autocomplete || m_suggestions.empty()) + return; + + float line_height = ImGui::GetTextLineHeightWithSpacing(); + float char_width = ImGui::GetFontSize() * 0.5f; + auto cursor = editor.GetCursorPosition(); + + const float line_number_width = 50.0f; + + ImVec2 popup_pos; + popup_pos.x = editor_pos.x + line_number_width + (m_start_pos.mColumn * char_width); + popup_pos.y = editor_pos.y + ((cursor.mLine + 1) * line_height); + + ImGui::SetNextWindowPos(popup_pos, ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(300, 0)); + + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_AlwaysAutoResize; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 2)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, m_bg_color); + ImGui::PushStyleColor(ImGuiCol_Border, m_border_color); + + if (ImGui::Begin("##autocomplete", nullptr, flags)) + { + ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color); + if (m_prefix.find('.') != std::string::npos) + ImGui::Text("Formats"); + else + ImGui::Text("Color Keys"); + ImGui::PopStyleColor(); + ImGui::Separator(); + + int max_items = std::min((int)m_suggestions.size(), 8); + + for (int i = 0; i < max_items; ++i) + { + const auto &suggestion = m_suggestions[i]; + bool is_selected = (i == m_selected_index); + + if (is_selected) + { + ImVec4 selected_hover = m_selected_color; + selected_hover.w = std::min(selected_hover.w + 0.1f, 1.0f); + ImGui::PushStyleColor(ImGuiCol_Header, m_selected_color); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, selected_hover); + ImGui::PushStyleColor(ImGuiCol_Text, m_selected_text_color); + } + else + { + ImVec4 normal_bg = m_bg_color; + normal_bg.w = 0.5f; + ImVec4 hover_bg = m_selected_color; + hover_bg.w = 0.3f; + ImGui::PushStyleColor(ImGuiCol_Header, normal_bg); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hover_bg); + ImGui::PushStyleColor(ImGuiCol_Text, m_text_color); + } + + std::string display_text = " " + suggestion; + + if (ImGui::Selectable(display_text.c_str(), is_selected, ImGuiSelectableFlags_None, ImVec2(0, 0))) + { + auto start = m_start_pos; + auto end = editor.GetCursorPosition(); + editor.SetSelection(start, end); + editor.Delete(); + editor.InsertText(suggestion + "}"); + m_show_autocomplete = false; + m_dismissed = false; + } + + ImGui::PopStyleColor(3); + + if (is_selected && ImGui::IsWindowAppearing()) + { + ImGui::SetScrollHereY(); + } + } + + if (m_suggestions.size() > 8) + { + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color); + ImGui::Text(" +%d more", (int)m_suggestions.size() - 8); + ImGui::PopStyleColor(); + } + + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color); + ImGui::Text(" Tab/Enter: accept | Esc: dismiss"); + ImGui::PopStyleColor(); + } + ImGui::End(); + + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(3); +} + +void autocomplete_widget::apply_palette(const core::palette& palette) +{ + m_bg_color = palette_color(palette, "surface", "background"); + m_bg_color.w = 0.98f; + m_border_color = palette_color(palette, "border", "surface_variant"); + m_selected_color = palette_color(palette, "accent", "surface_variant"); + m_text_color = palette_color(palette, "on_surface", "foreground"); + m_selected_text_color = palette_color(palette, "on_surface", "foreground"); + m_dim_text_color = palette_color(palette, "on_surface_variant", "editor_inactive"); +} + +bool autocomplete_widget::handle_input(TextEditor& editor) +{ + if (!m_show_autocomplete || m_suggestions.empty()) + return false; + + bool handled = false; + + if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) + { + m_selected_index = (m_selected_index + 1) % (int)m_suggestions.size(); + handled = true; + } + else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) + { + m_selected_index = (m_selected_index - 1 + (int)m_suggestions.size()) % (int)m_suggestions.size(); + handled = true; + } + else if (ImGui::IsKeyPressed(ImGuiKey_Tab) || ImGui::IsKeyPressed(ImGuiKey_Enter)) + { + if (m_selected_index >= 0 && m_selected_index < (int)m_suggestions.size()) + { + auto start = m_start_pos; + auto end = editor.GetCursorPosition(); + editor.SetSelection(start, end); + editor.Delete(); + editor.InsertText(m_suggestions[m_selected_index] + "}"); + m_show_autocomplete = false; + m_dismissed = false; + } + handled = true; + } + else if (ImGui::IsKeyPressed(ImGuiKey_Escape)) + { + m_dismissed = true; + m_dismiss_position = editor.GetCursorPosition(); + m_dismiss_brace_pos = -1; + m_show_autocomplete = false; + handled = true; + } + + return handled; +} + +} // namespace clrsync::gui::widgets \ No newline at end of file diff --git a/src/gui/widgets/autocomplete.hpp b/src/gui/widgets/autocomplete.hpp new file mode 100644 index 0000000..0d1a64e --- /dev/null +++ b/src/gui/widgets/autocomplete.hpp @@ -0,0 +1,53 @@ +#ifndef CLRSYNC_GUI_WIDGETS_AUTOCOMPLETE_HPP +#define CLRSYNC_GUI_WIDGETS_AUTOCOMPLETE_HPP + +#include "core/palette/palette.hpp" +#include "color_text_edit/TextEditor.h" +#include "imgui.h" +#include +#include + +namespace clrsync::gui::widgets +{ + +class autocomplete_widget +{ + public: + autocomplete_widget(); + + void update_suggestions(const TextEditor& editor, + const std::vector& available_keys, + const std::vector& available_formats); + + void render(const ImVec2& editor_pos, TextEditor& editor); + + void apply_palette(const core::palette& palette); + + bool handle_input(TextEditor& editor); + + bool is_visible() const { return m_show_autocomplete; } + + void dismiss() { m_dismissed = true; m_show_autocomplete = false; } + + private: + std::vector m_suggestions; + std::string m_prefix; + TextEditor::Coordinates m_start_pos; + int m_selected_index; + bool m_show_autocomplete; + bool m_dismissed; + + TextEditor::Coordinates m_dismiss_position; + int m_dismiss_brace_pos; + + ImVec4 m_bg_color; + ImVec4 m_border_color; + ImVec4 m_selected_color; + ImVec4 m_text_color; + ImVec4 m_selected_text_color; + ImVec4 m_dim_text_color; +}; + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_AUTOCOMPLETE_HPP \ No newline at end of file diff --git a/src/gui/widgets/colors.cpp b/src/gui/widgets/colors.cpp new file mode 100644 index 0000000..565e350 --- /dev/null +++ b/src/gui/widgets/colors.cpp @@ -0,0 +1,58 @@ +#include "colors.hpp" + +namespace clrsync::gui::widgets +{ + +ImVec4 palette_color(const core::palette &pal, const std::string &key, + const std::string &fallback) +{ + auto colors = pal.colors(); + if (colors.empty()) + return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + + auto it = colors.find(key); + if (it == colors.end() && !fallback.empty()) + { + it = colors.find(fallback); + } + + if (it != colors.end()) + { + const auto &col = it->second; + const uint32_t hex = col.hex(); + const float r = ((hex >> 24) & 0xFF) / 255.0f; + const float g = ((hex >> 16) & 0xFF) / 255.0f; + const float b = ((hex >> 8) & 0xFF) / 255.0f; + const float a = (hex & 0xFF) / 255.0f; + return ImVec4(r, g, b, a); + } + return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); +} + +uint32_t palette_color_u32(const core::palette &pal, const std::string &key, + const std::string &fallback) +{ + auto colors = pal.colors(); + if (colors.empty()) + return 0xFFFFFFFF; + + auto it = colors.find(key); + if (it == colors.end() && !fallback.empty()) + { + it = colors.find(fallback); + } + + if (it != colors.end()) + { + const auto &col = it->second; + const uint32_t hex = col.hex(); + const uint32_t r = (hex >> 24) & 0xFF; + const uint32_t g = (hex >> 16) & 0xFF; + const uint32_t b = (hex >> 8) & 0xFF; + const uint32_t a = hex & 0xFF; + return (a << 24) | (b << 16) | (g << 8) | r; + } + return 0xFFFFFFFF; +} + +} // namespace clrsync::gui::widgets diff --git a/src/gui/widgets/colors.hpp b/src/gui/widgets/colors.hpp new file mode 100644 index 0000000..19cfa49 --- /dev/null +++ b/src/gui/widgets/colors.hpp @@ -0,0 +1,19 @@ +#ifndef CLRSYNC_GUI_WIDGETS_COLORS_HPP +#define CLRSYNC_GUI_WIDGETS_COLORS_HPP + +#include "core/palette/palette.hpp" +#include "imgui.h" +#include + +namespace clrsync::gui::widgets +{ + +ImVec4 palette_color(const core::palette &pal, const std::string &key, + const std::string &fallback = ""); + +uint32_t palette_color_u32(const core::palette &pal, const std::string &key, + const std::string &fallback = ""); + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_COLORS_HPP diff --git a/src/gui/widgets/dialogs.cpp b/src/gui/widgets/dialogs.cpp new file mode 100644 index 0000000..5dd69e2 --- /dev/null +++ b/src/gui/widgets/dialogs.cpp @@ -0,0 +1,46 @@ +#include "dialogs.hpp" +#include "colors.hpp" +#include "imgui.h" + +namespace clrsync::gui::widgets +{ + +bool delete_confirmation_dialog(const std::string &popup_title, const std::string &item_name, + const std::string &item_type, const core::palette &theme_palette, + const std::function &on_delete) +{ + bool result = false; + if (ImGui::BeginPopupModal(popup_title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImVec4 warning_color = palette_color(theme_palette, "warning", "accent"); + ImGui::TextColored(warning_color, "Are you sure you want to delete '%s'?", + item_name.c_str()); + ImGui::Text("This action cannot be undone."); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + float button_width = 120.0f; + float total_width = 2.0f * button_width + ImGui::GetStyle().ItemSpacing.x; + float window_width = ImGui::GetContentRegionAvail().x; + ImGui::SetCursorPosX((window_width - total_width) * 0.5f); + + if (ImGui::Button("Delete", ImVec2(button_width, 0))) + { + on_delete(); + result = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(button_width, 0))) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + return result; +} + +} // namespace clrsync::gui::widgets diff --git a/src/gui/widgets/dialogs.hpp b/src/gui/widgets/dialogs.hpp new file mode 100644 index 0000000..ac77c03 --- /dev/null +++ b/src/gui/widgets/dialogs.hpp @@ -0,0 +1,17 @@ +#ifndef CLRSYNC_GUI_WIDGETS_DIALOGS_HPP +#define CLRSYNC_GUI_WIDGETS_DIALOGS_HPP + +#include "core/palette/palette.hpp" +#include +#include + +namespace clrsync::gui::widgets +{ + +bool delete_confirmation_dialog(const std::string &popup_title, const std::string &item_name, + const std::string &item_type, const core::palette &theme_palette, + const std::function &on_delete); + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_DIALOGS_HPP diff --git a/src/gui/widgets/form_field.cpp b/src/gui/widgets/form_field.cpp new file mode 100644 index 0000000..f84811a --- /dev/null +++ b/src/gui/widgets/form_field.cpp @@ -0,0 +1,184 @@ +#include "form_field.hpp" +#include "imgui.h" +#include + +namespace clrsync::gui::widgets +{ + +form_field::form_field() = default; + +void form_field::render_label(const form_field_config& config) +{ + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s:", config.label.c_str()); + if (config.label_width > 0) + { + ImGui::SameLine(config.label_width); + } + else + { + ImGui::SameLine(); + } +} + +void form_field::render_tooltip(const form_field_config& config) +{ + if (!config.tooltip.empty() && ImGui::IsItemHovered()) + { + ImGui::SetTooltip("%s", config.tooltip.c_str()); + } +} + +bool form_field::render_text(const form_field_config& config, std::string& value) +{ + render_label(config); + + if (config.field_width > 0) + { + ImGui::SetNextItemWidth(config.field_width); + } + else if (config.field_width < 0) + { + ImGui::SetNextItemWidth(config.field_width); + } + + char buffer[512] = {0}; + strncpy(buffer, value.c_str(), sizeof(buffer) - 1); + + bool changed = false; + std::string id = "##" + config.label; + + if (config.type == field_type::text_with_hint) + { + changed = ImGui::InputTextWithHint(id.c_str(), config.hint.c_str(), buffer, sizeof(buffer)); + } + else + { + changed = ImGui::InputText(id.c_str(), buffer, sizeof(buffer)); + } + + if (changed) + { + value = buffer; + } + + render_tooltip(config); + return changed; +} + +bool form_field::render_number(const form_field_config& config, int& value) +{ + render_label(config); + + if (config.field_width > 0) + { + ImGui::SetNextItemWidth(config.field_width); + } + else if (config.field_width < 0) + { + ImGui::SetNextItemWidth(config.field_width); + } + + std::string id = "##" + config.label; + bool changed = ImGui::InputInt(id.c_str(), &value); + + if (value < (int)config.min_value) value = (int)config.min_value; + if (value > (int)config.max_value) value = (int)config.max_value; + + render_tooltip(config); + return changed; +} + +bool form_field::render_number(const form_field_config& config, float& value) +{ + render_label(config); + + if (config.field_width > 0) + { + ImGui::SetNextItemWidth(config.field_width); + } + else if (config.field_width < 0) + { + ImGui::SetNextItemWidth(config.field_width); + } + + std::string id = "##" + config.label; + bool changed = ImGui::InputFloat(id.c_str(), &value); + + if (value < config.min_value) value = config.min_value; + if (value > config.max_value) value = config.max_value; + + render_tooltip(config); + return changed; +} + +bool form_field::render_path(const form_field_config& config, std::string& value) +{ + render_label(config); + + float browse_button_width = 80.0f; + float available_width = ImGui::GetContentRegionAvail().x; + float input_width = available_width - browse_button_width - ImGui::GetStyle().ItemSpacing.x; + + if (config.field_width > 0) + { + input_width = config.field_width - browse_button_width - ImGui::GetStyle().ItemSpacing.x; + } + + ImGui::SetNextItemWidth(input_width); + + char buffer[512] = {0}; + strncpy(buffer, value.c_str(), sizeof(buffer) - 1); + + std::string input_id = "##" + config.label + "_input"; + bool changed = false; + + if (config.type == field_type::text_with_hint) + { + changed = ImGui::InputTextWithHint(input_id.c_str(), config.hint.c_str(), buffer, sizeof(buffer)); + } + else + { + changed = ImGui::InputText(input_id.c_str(), buffer, sizeof(buffer)); + } + + if (changed) + { + value = buffer; + } + + ImGui::SameLine(); + + std::string button_id = "Browse##" + config.label; + if (ImGui::Button(button_id.c_str())) + { + if (m_path_browse_callback) + { + std::string selected_path = m_path_browse_callback(value); + if (!selected_path.empty()) + { + value = selected_path; + changed = true; + } + } + } + + render_tooltip(config); + return changed; +} + +void form_field::render_readonly(const form_field_config& config, const std::string& value) +{ + render_label(config); + + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", value.c_str()); + + render_tooltip(config); +} + +void form_field::set_path_browse_callback(const std::function& callback) +{ + m_path_browse_callback = callback; +} + +} // namespace clrsync::gui::widgets \ No newline at end of file diff --git a/src/gui/widgets/form_field.hpp b/src/gui/widgets/form_field.hpp new file mode 100644 index 0000000..1018f2b --- /dev/null +++ b/src/gui/widgets/form_field.hpp @@ -0,0 +1,62 @@ +#ifndef CLRSYNC_GUI_WIDGETS_FORM_FIELD_HPP +#define CLRSYNC_GUI_WIDGETS_FORM_FIELD_HPP + +#include +#include + +namespace clrsync::gui::widgets +{ + +enum class field_type +{ + text, + text_with_hint, + number, + path, + readonly_text +}; + +struct form_field_config +{ + std::string label; + std::string tooltip; + float label_width = 80.0f; + float field_width = -1.0f; + bool required = false; + field_type type = field_type::text; + std::string hint; + float min_value = 0.0f; + float max_value = 100.0f; +}; + +class form_field +{ + public: + form_field(); + + // Render a text input field + bool render_text(const form_field_config& config, std::string& value); + + // Render a number input field + bool render_number(const form_field_config& config, int& value); + bool render_number(const form_field_config& config, float& value); + + // Render a path input field with browse button + bool render_path(const form_field_config& config, std::string& value); + + // Render readonly text + void render_readonly(const form_field_config& config, const std::string& value); + + // Set callback for path browsing + void set_path_browse_callback(const std::function& callback); + + private: + std::function m_path_browse_callback; + + void render_label(const form_field_config& config); + void render_tooltip(const form_field_config& config); +}; + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_FORM_FIELD_HPP \ No newline at end of file diff --git a/src/gui/widgets/input_dialog.cpp b/src/gui/widgets/input_dialog.cpp new file mode 100644 index 0000000..abb4171 --- /dev/null +++ b/src/gui/widgets/input_dialog.cpp @@ -0,0 +1,111 @@ +#include "input_dialog.hpp" +#include "imgui.h" +#include + +namespace clrsync::gui::widgets +{ + +input_dialog::input_dialog() : m_input_buffer{0}, m_is_open(false), m_focus_input(false) {} + +void input_dialog::open(const std::string &title, const std::string &prompt, const std::string &hint) +{ + m_title = title; + m_prompt = prompt; + m_hint = hint; + m_input_buffer[0] = 0; + m_is_open = true; + m_focus_input = true; + ImGui::OpenPopup(m_title.c_str()); +} + +bool input_dialog::render() +{ + bool submitted = false; + + if (!m_is_open) + return false; + + if (ImGui::BeginPopupModal(m_title.c_str(), &m_is_open, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("%s", m_prompt.c_str()); + ImGui::Spacing(); + + ImGui::SetNextItemWidth(250); + if (m_focus_input) + { + ImGui::SetKeyboardFocusHere(); + m_focus_input = false; + } + + bool enter_pressed = ImGui::InputTextWithHint("##input", m_hint.c_str(), m_input_buffer, + IM_ARRAYSIZE(m_input_buffer), + ImGuiInputTextFlags_EnterReturnsTrue); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + bool can_submit = strlen(m_input_buffer) > 0; + bool submit_clicked = false; + + if (!can_submit) + ImGui::BeginDisabled(); + + if (ImGui::Button("OK", ImVec2(120, 0)) || (enter_pressed && can_submit)) + { + if (m_on_submit) + { + m_on_submit(m_input_buffer); + } + submitted = true; + submit_clicked = true; + m_is_open = false; + ImGui::CloseCurrentPopup(); + } + + if (!can_submit) + ImGui::EndDisabled(); + + ImGui::SameLine(); + + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + if (m_on_cancel) + { + m_on_cancel(); + } + m_is_open = false; + ImGui::CloseCurrentPopup(); + } + + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) + { + if (m_on_cancel) + { + m_on_cancel(); + } + m_is_open = false; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + else + { + m_is_open = false; + } + + return submitted; +} + +void input_dialog::set_on_submit(const std::function &callback) +{ + m_on_submit = callback; +} + +void input_dialog::set_on_cancel(const std::function &callback) +{ + m_on_cancel = callback; +} + +} // namespace clrsync::gui::widgets \ No newline at end of file diff --git a/src/gui/widgets/input_dialog.hpp b/src/gui/widgets/input_dialog.hpp new file mode 100644 index 0000000..ae0a70f --- /dev/null +++ b/src/gui/widgets/input_dialog.hpp @@ -0,0 +1,38 @@ +#ifndef CLRSYNC_GUI_WIDGETS_INPUT_DIALOG_HPP +#define CLRSYNC_GUI_WIDGETS_INPUT_DIALOG_HPP + +#include +#include + +namespace clrsync::gui::widgets +{ + +class input_dialog +{ + public: + input_dialog(); + + void open(const std::string &title, const std::string &prompt, const std::string &hint = ""); + + bool render(); + + void set_on_submit(const std::function &callback); + void set_on_cancel(const std::function &callback); + + bool is_open() const { return m_is_open; } + + private: + std::string m_title; + std::string m_prompt; + std::string m_hint; + char m_input_buffer[256]; + bool m_is_open; + bool m_focus_input; + + std::function m_on_submit; + std::function m_on_cancel; +}; + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_INPUT_DIALOG_HPP \ No newline at end of file diff --git a/src/gui/widgets/palette_selector.cpp b/src/gui/widgets/palette_selector.cpp new file mode 100644 index 0000000..0644f2a --- /dev/null +++ b/src/gui/widgets/palette_selector.cpp @@ -0,0 +1,46 @@ +#include "palette_selector.hpp" +#include "imgui.h" +#include + +namespace clrsync::gui::widgets +{ + +palette_selector::palette_selector() = default; + +bool palette_selector::render(const palette_controller &controller, float width) +{ + const auto ¤t = controller.current_palette(); + const auto &palettes = controller.palettes(); + bool selection_changed = false; + + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##palette", current.name().c_str())) + { + for (const auto &name : palettes | std::views::keys) + { + const bool selected = current.name() == name; + if (ImGui::Selectable(name.c_str(), selected)) + { + if (m_on_selection_changed) + { + m_on_selection_changed(name); + } + selection_changed = true; + } + if (selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Select a color palette to edit"); + + return selection_changed; +} + +void palette_selector::set_on_selection_changed(const std::function &callback) +{ + m_on_selection_changed = callback; +} + +} // namespace clrsync::gui::widgets \ No newline at end of file diff --git a/src/gui/widgets/palette_selector.hpp b/src/gui/widgets/palette_selector.hpp new file mode 100644 index 0000000..4c1ca97 --- /dev/null +++ b/src/gui/widgets/palette_selector.hpp @@ -0,0 +1,26 @@ +#ifndef CLRSYNC_GUI_WIDGETS_PALETTE_SELECTOR_HPP +#define CLRSYNC_GUI_WIDGETS_PALETTE_SELECTOR_HPP + +#include "gui/controllers/palette_controller.hpp" +#include +#include + +namespace clrsync::gui::widgets +{ + +class palette_selector +{ + public: + palette_selector(); + + bool render(const palette_controller &controller, float width = 200.0f); + + void set_on_selection_changed(const std::function &callback); + + private: + std::function m_on_selection_changed; +}; + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_PALETTE_SELECTOR_HPP \ No newline at end of file diff --git a/src/gui/widgets/styled_checkbox.cpp b/src/gui/widgets/styled_checkbox.cpp new file mode 100644 index 0000000..7ac324d --- /dev/null +++ b/src/gui/widgets/styled_checkbox.cpp @@ -0,0 +1,95 @@ +#include "styled_checkbox.hpp" +#include "colors.hpp" +#include "imgui.h" + +namespace clrsync::gui::widgets +{ + +styled_checkbox::styled_checkbox() = default; + +bool styled_checkbox::render(const std::string &label, bool *value, const core::palette &theme_palette, + checkbox_style style) +{ + ImVec4 bg_color, hover_color, check_color; + + switch (style) + { + case checkbox_style::success: + if (*value) + { + bg_color = palette_color(theme_palette, "success", "accent"); + bg_color.w = 0.5f; + hover_color = ImVec4(bg_color.x * 1.2f, bg_color.y * 1.2f, bg_color.z * 1.2f, 0.6f); + check_color = palette_color(theme_palette, "on_success", "on_surface"); + } + else + { + bg_color = palette_color(theme_palette, "surface", "background"); + hover_color = palette_color(theme_palette, "surface_variant", "surface"); + check_color = palette_color(theme_palette, "on_surface", "foreground"); + } + break; + + case checkbox_style::error: + if (*value) + { + bg_color = palette_color(theme_palette, "error", "accent"); + bg_color.w = 0.5f; + hover_color = ImVec4(bg_color.x * 1.2f, bg_color.y * 1.2f, bg_color.z * 1.2f, 0.6f); + check_color = palette_color(theme_palette, "on_error", "on_surface"); + } + else + { + bg_color = palette_color(theme_palette, "error", "accent"); + bg_color.w = 0.2f; + hover_color = ImVec4(bg_color.x, bg_color.y, bg_color.z, 0.3f); + check_color = palette_color(theme_palette, "on_error", "on_surface"); + } + break; + + case checkbox_style::warning: + if (*value) + { + bg_color = palette_color(theme_palette, "warning", "accent"); + bg_color.w = 0.5f; + hover_color = ImVec4(bg_color.x * 1.2f, bg_color.y * 1.2f, bg_color.z * 1.2f, 0.6f); + check_color = palette_color(theme_palette, "on_warning", "on_surface"); + } + else + { + bg_color = palette_color(theme_palette, "surface", "background"); + hover_color = palette_color(theme_palette, "surface_variant", "surface"); + check_color = palette_color(theme_palette, "on_surface", "foreground"); + } + break; + + case checkbox_style::normal: + default: + bg_color = palette_color(theme_palette, "surface", "background"); + hover_color = palette_color(theme_palette, "surface_variant", "surface"); + check_color = palette_color(theme_palette, "accent", "foreground"); + break; + } + + ImGui::PushStyleColor(ImGuiCol_FrameBg, bg_color); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, hover_color); + ImGui::PushStyleColor(ImGuiCol_CheckMark, check_color); + + bool changed = ImGui::Checkbox(label.c_str(), value); + + ImGui::PopStyleColor(3); + + if (changed && m_on_changed) + { + m_on_changed(*value); + } + + if (!m_tooltip.empty() && ImGui::IsItemHovered()) + { + ImGui::SetTooltip("%s", m_tooltip.c_str()); + } + + return changed; +} + +} // namespace clrsync::gui::widgets \ No newline at end of file diff --git a/src/gui/widgets/styled_checkbox.hpp b/src/gui/widgets/styled_checkbox.hpp new file mode 100644 index 0000000..e70d39e --- /dev/null +++ b/src/gui/widgets/styled_checkbox.hpp @@ -0,0 +1,38 @@ +#ifndef CLRSYNC_GUI_WIDGETS_STYLED_CHECKBOX_HPP +#define CLRSYNC_GUI_WIDGETS_STYLED_CHECKBOX_HPP + +#include "core/palette/palette.hpp" +#include +#include + +namespace clrsync::gui::widgets +{ + +enum class checkbox_style +{ + normal, + success, + error, + warning +}; + +class styled_checkbox +{ + public: + styled_checkbox(); + + bool render(const std::string &label, bool *value, const core::palette &theme_palette, + checkbox_style style = checkbox_style::normal); + + void set_tooltip(const std::string &tooltip) { m_tooltip = tooltip; } + + void set_on_changed(const std::function &callback) { m_on_changed = callback; } + + private: + std::string m_tooltip; + std::function m_on_changed; +}; + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_STYLED_CHECKBOX_HPP \ No newline at end of file