From f7c290110e1db4b57d626d8754ae1cc654b584dd Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Wed, 17 Dec 2025 01:41:44 +0300 Subject: [PATCH] chore: split color_scheme_editor --- flake.nix | 15 +- src/core/config/config.cpp | 33 ++- src/core/theme/theme_template.cpp | 23 +- src/core/utils.cpp | 4 + src/core/version.hpp | 2 +- src/gui/CMakeLists.txt | 3 + src/gui/color_scheme_editor.cpp | 441 ++---------------------------- src/gui/color_scheme_editor.hpp | 12 +- src/gui/color_table_renderer.cpp | 122 +++++++++ src/gui/color_table_renderer.hpp | 24 ++ src/gui/imgui_helpers.cpp | 17 +- src/gui/main.cpp | 12 +- src/gui/preview_renderer.cpp | 152 ++++++++++ src/gui/preview_renderer.hpp | 22 ++ src/gui/template_editor.cpp | 9 +- src/gui/theme_applier.cpp | 166 +++++++++++ src/gui/theme_applier.hpp | 13 + 17 files changed, 617 insertions(+), 453 deletions(-) create mode 100644 src/gui/color_table_renderer.cpp create mode 100644 src/gui/color_table_renderer.hpp create mode 100644 src/gui/preview_renderer.cpp create mode 100644 src/gui/preview_renderer.hpp create mode 100644 src/gui/theme_applier.cpp create mode 100644 src/gui/theme_applier.hpp diff --git a/flake.nix b/flake.nix index a68b6f2..3df89c4 100644 --- a/flake.nix +++ b/flake.nix @@ -16,10 +16,11 @@ baseVersion = nixpkgs.lib.removeSuffix "\n" (builtins.readFile ./VERSION); - semver = - if self ? rev - then "${baseVersion}+git.${builtins.substring 0 7 self.rev}" - else "${baseVersion}+dev"; + semver = + if self ? rev then + "${baseVersion}+git.${builtins.substring 0 7 self.rev}" + else + "${baseVersion}+dev"; in { packages = forAllSystems ( @@ -66,12 +67,12 @@ system: let pkgs = nixpkgsFor.${system}; + clrsync = self.packages.${system}.clrsync; in { default = pkgs.mkShell { - inputsFrom = [ self.packages.${system}.clrsync ]; - - buildInputs = with pkgs; [ + inputsFrom = [ clrsync ]; + packages = with pkgs; [ cmake ninja clang-tools diff --git a/src/core/config/config.cpp b/src/core/config/config.cpp index c978fc0..b456bcc 100644 --- a/src/core/config/config.cpp +++ b/src/core/config/config.cpp @@ -9,6 +9,7 @@ #ifdef _WIN32 #include "windows.h" #endif +#include namespace clrsync::core { @@ -22,9 +23,10 @@ void config::initialize(std::unique_ptr file) { copy_default_configs(); m_file = std::move(file); - if (m_file) - if (!m_file->parse()) - throw std::runtime_error{"Could not parse config file"}; + if (!m_file) + throw std::runtime_error{"Config file is missing"}; + if (!m_file->parse()) + throw std::runtime_error{"Could not parse config file"}; } std::filesystem::path config::get_user_config_dir() @@ -62,13 +64,32 @@ void config::copy_file(const std::filesystem::path &src, const std::filesystem:: if (std::filesystem::exists(dst)) return; + if (!std::filesystem::exists(src)) + { + std::cerr << "Warning: Source file does not exist: " << src << std::endl; + return; + } + std::ifstream in(src, std::ios::binary); std::ofstream out(dst, std::ios::binary); + + if (!in || !out) + { + std::cerr << "Warning: Failed to copy file from " << src << " to " << dst << std::endl; + return; + } + out << in.rdbuf(); } void config::copy_dir(const std::filesystem::path &src, const std::filesystem::path &dst) { + if (!std::filesystem::exists(src)) + { + std::cerr << "Warning: Source directory does not exist: " << src << std::endl; + return; + } + for (auto const &entry : std::filesystem::recursive_directory_iterator(src)) { auto rel = std::filesystem::relative(entry.path(), src); @@ -92,6 +113,12 @@ void config::copy_default_configs() std::filesystem::create_directories(user_dir); + if (system_dir.empty()) + { + std::cerr << "Warning: No system data directory found, skipping default config copy\n"; + return; + } + { auto src = system_dir / "config.toml"; auto dst = user_dir / "config.toml"; diff --git a/src/core/theme/theme_template.cpp b/src/core/theme/theme_template.cpp index 7fb1f8c..a51458a 100644 --- a/src/core/theme/theme_template.cpp +++ b/src/core/theme/theme_template.cpp @@ -47,12 +47,15 @@ void theme_template::load_template() { if (!std::filesystem::exists(m_template_path)) { - std::cerr << "Template file '" << m_template_path << "' is missing\n"; + std::cerr << "Warning: Template file '" << m_template_path << "' is missing\n"; return; } std::ifstream input(m_template_path, std::ios::binary); if (!input) - throw std::runtime_error("Failed to open template file: " + m_template_path); + { + std::cerr << "Warning: Failed to open template file: " << m_template_path << std::endl; + return; + } m_template_data.assign(std::istreambuf_iterator(input), std::istreambuf_iterator()); } @@ -89,10 +92,22 @@ void theme_template::apply_palette(const core::palette &palette) void theme_template::save_output() const { - std::filesystem::create_directories(std::filesystem::path(m_output_path).parent_path()); + try + { + std::filesystem::create_directories(std::filesystem::path(m_output_path).parent_path()); + } + catch (const std::exception& e) + { + std::cerr << "Warning: Failed to create output directory for " << m_output_path << ": " << e.what() << std::endl; + return; + } + std::ofstream output(m_output_path, std::ios::binary); if (!output) - throw std::runtime_error("Failed to write output file: " + m_output_path); + { + std::cerr << "Warning: Failed to write output file: " << m_output_path << std::endl; + return; + } output << m_processed_data; } diff --git a/src/core/utils.cpp b/src/core/utils.cpp index 257271c..0501387 100644 --- a/src/core/utils.cpp +++ b/src/core/utils.cpp @@ -13,6 +13,10 @@ void print_color_keys() std::string get_default_config_path() { + const char* env_path = std::getenv("CLRSYNC_CONFIG_PATH"); + if (env_path && env_path[0] != '\0') + return expand_user(env_path); + auto home = expand_user("~"); #ifdef _WIN32 return home + "\\.config\\clrsync\\config.toml"; diff --git a/src/core/version.hpp b/src/core/version.hpp index 2eb1d22..8950f06 100644 --- a/src/core/version.hpp +++ b/src/core/version.hpp @@ -7,7 +7,7 @@ namespace clrsync::core { -const std::string GIT_SEMVER = "0.1.4+git.gcd81744"; +const std::string GIT_SEMVER = "0.1.4+git.g659c5f2"; const std::string version_string(); } // namespace clrsync::core diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 6250be8..27737be 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,6 +1,9 @@ set(GUI_SOURCES main.cpp color_scheme_editor.cpp + color_table_renderer.cpp + preview_renderer.cpp + theme_applier.cpp template_editor.cpp palette_controller.cpp template_controller.cpp diff --git a/src/gui/color_scheme_editor.cpp b/src/gui/color_scheme_editor.cpp index 265758b..1484adb 100644 --- a/src/gui/color_scheme_editor.cpp +++ b/src/gui/color_scheme_editor.cpp @@ -1,86 +1,18 @@ #include "color_scheme_editor.hpp" #include "template_editor.hpp" -#include "color_text_edit/TextEditor.h" +#include "theme_applier.hpp" #include "imgui.h" #include #include color_scheme_editor::color_scheme_editor() { - m_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); - m_editor.SetText(R"(#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -// Expands ~ to the user's home directory -std::string expand_user(const std::string &path) -{ - if (path.empty()) return ""; - - std::string result; - if (path[0] == '~') - { -#ifdef _WIN32 - const char* home = std::getenv("USERPROFILE"); -#else - const char* home = std::getenv("HOME"); -#endif - result = home ? std::string(home) : "~"; - result += path.substr(1); - } - else - { - result = path; - } - - return result; -} - -// Lists all files in a directory -std::vector list_files(const std::string &dir_path) -{ - std::vector files; - try - { - for (const auto &entry : fs::directory_iterator(dir_path)) - { - if (entry.is_regular_file()) - files.push_back(entry.path().string()); - } - } - catch (const std::exception &e) - { - std::cerr << "Error: " << e.what() << std::endl; - } - return files; -} - -int main() -{ - std::string path = expand_user("~/Documents"); - std::cout << "Listing files in: " << path << std::endl; - - auto files = list_files(path); - for (const auto &f : files) - std::cout << " " << f << std::endl; - - return 0; -})"); - - m_editor.SetShowWhitespaces(false); - - const auto &palettes = m_controller.palettes(); - const auto ¤t = m_controller.current_palette(); if (!current.colors().empty()) { - apply_palette_to_imgui(); - apply_palette_to_editor(); + theme_applier::apply_to_imgui(current); + m_preview.apply_palette(current); } else { @@ -96,6 +28,14 @@ void color_scheme_editor::notify_palette_changed() } } +void color_scheme_editor::apply_themes() +{ + const auto ¤t = m_controller.current_palette(); + theme_applier::apply_to_imgui(current); + m_preview.apply_palette(current); + notify_palette_changed(); +} + void color_scheme_editor::render_controls_and_colors() { ImGui::Begin("Color Schemes"); @@ -104,7 +44,8 @@ void color_scheme_editor::render_controls_and_colors() ImGui::Separator(); ImGui::BeginChild("ColorTableContent", ImVec2(0, 0), false); - render_color_table(); + m_color_table.render(m_controller.current_palette(), m_controller, + [this]() { apply_themes(); }); ImGui::EndChild(); ImGui::End(); @@ -114,7 +55,7 @@ void color_scheme_editor::render_preview() { ImGui::Begin("Color Preview"); - render_preview_content(); + m_preview.render(m_controller.current_palette()); ImGui::End(); } @@ -137,9 +78,7 @@ void color_scheme_editor::render_controls() if (ImGui::Selectable(name.c_str(), selected)) { m_controller.select_palette(name); - apply_palette_to_imgui(); - apply_palette_to_editor(); - notify_palette_changed(); + apply_themes(); } if (selected) ImGui::SetItemDefaultFocus(); @@ -169,10 +108,8 @@ void color_scheme_editor::render_controls() if (strlen(new_palette_name_buf) > 0) { m_controller.create_palette(new_palette_name_buf); - apply_palette_to_imgui(); - apply_palette_to_editor(); - notify_palette_changed(); m_controller.select_palette(new_palette_name_buf); + apply_themes(); new_palette_name_buf[0] = 0; } ImGui::CloseCurrentPopup(); @@ -207,349 +144,3 @@ void color_scheme_editor::render_controls() m_controller.apply_current_theme(); } } - -void color_scheme_editor::render_color_table() -{ - const auto ¤t = m_controller.current_palette(); - - if (current.colors().empty()) - { - ImGui::Text("No palette loaded"); - return; - } - - ImGui::Text("Color Variables"); - ImGui::Separator(); - - auto render_color_row = [&](const std::string &name) { - const auto &colors = current.colors(); - auto it = colors.find(name); - if (it == colors.end()) - return; - - const clrsync::core::color &col = it->second; - - ImGui::TableNextRow(); - - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted(name.c_str()); - - ImGui::TableSetColumnIndex(1); - { - std::string hex_str = col.to_hex_string(); - char buf[9]; - strncpy(buf, hex_str.c_str(), sizeof(buf)); - buf[8] = 0; - - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::InputText(("##hex_" + name).c_str(), buf, sizeof(buf), - ImGuiInputTextFlags_CharsUppercase)) - { - try - { - clrsync::core::color new_color; - new_color.from_hex_string(buf); - m_controller.set_color(name, new_color); - apply_palette_to_imgui(); - apply_palette_to_editor(); - notify_palette_changed(); - } - catch (...) - { - } - } - } - - ImGui::TableSetColumnIndex(2); - ImGui::PushID(name.c_str()); - float c[4] = {((col.hex() >> 24) & 0xFF) / 255.0f, ((col.hex() >> 16) & 0xFF) / 255.0f, - ((col.hex() >> 8) & 0xFF) / 255.0f, (col.hex() & 0xFF) / 255.0f}; - - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::ColorEdit4(("##color_" + name).c_str(), c, - ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | - ImGuiColorEditFlags_AlphaBar)) - { - uint32_t r = (uint32_t)(c[0] * 255.0f); - uint32_t g = (uint32_t)(c[1] * 255.0f); - uint32_t b = (uint32_t)(c[2] * 255.0f); - uint32_t a = (uint32_t)(c[3] * 255.0f); - uint32_t hex = (r << 24) | (g << 16) | (b << 8) | a; - - m_controller.set_color(name, clrsync::core::color(hex)); - apply_palette_to_imgui(); - apply_palette_to_editor(); - notify_palette_changed(); - } - - ImGui::PopID(); - }; - - auto draw_table = [&](const char *title, const std::vector &keys) { - ImGui::TextUnformatted(title); - - if (ImGui::BeginTable(title, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) - { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 160.0f); - ImGui::TableSetupColumn("HEX", ImGuiTableColumnFlags_WidthFixed, 90.0f); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableHeadersRow(); - - for (auto *k : keys) - render_color_row(k); - - ImGui::EndTable(); - } - - ImGui::Spacing(); - }; - - draw_table("General UI", {"background", "on_background", "surface", "on_surface", - "surface_variant", "on_surface_variant", "foreground", - "cursor", "accent"}); - - draw_table("Borders", {"border_focused", "border"}); - - draw_table("Semantic Colors", {"success", "info", "warning", "error", - "on_success", "on_info", "on_warning", "on_error"}); - - draw_table("Editor", {"editor_background", "editor_command", "editor_comment", - "editor_disabled", "editor_emphasis", "editor_error", - "editor_inactive", "editor_line_number", "editor_link", - "editor_main", "editor_selected", "editor_selection_inactive", - "editor_string", "editor_success", "editor_warning"}); - - draw_table("Terminal (Base16)", {"base00", "base01", "base02", "base03", - "base04", "base05", "base06", "base07", - "base08", "base09", "base0A", "base0B", - "base0C", "base0D", "base0E", "base0F"}); -} - -void color_scheme_editor::render_preview_content() -{ - const auto ¤t = m_controller.current_palette(); - - if (current.colors().empty()) - { - ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Current palette is empty"); - return; - } - - auto get_color = [&](const std::string &key) -> ImVec4 { - auto it = current.colors().find(key); - if (it != current.colors().end()) - { - const auto &col = it->second; - const uint32_t hex = col.hex(); - return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f, - ((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f}; - } - return {1, 1, 1, 1}; - }; - - const ImVec4 editor_bg = get_color("editor_background"); - const ImVec4 fg = get_color("foreground"); - const ImVec4 accent = get_color("accent"); - const ImVec4 border = get_color("border"); - const ImVec4 error = get_color("error"); - const ImVec4 warning = get_color("warning"); - const ImVec4 success = get_color("success"); - const ImVec4 info = get_color("info"); - - const float avail_height = ImGui::GetContentRegionAvail().y; - const float code_preview_height = std::max(250.0f, avail_height * 0.55f); - - ImGui::Text("Code Editor:"); - - m_editor.Render("##CodeEditor", ImVec2(0, code_preview_height), true); - - ImGui::Spacing(); - ImGui::Text("Terminal Preview:"); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, editor_bg); - ImGui::BeginChild("TerminalPreview", ImVec2(0, 0), true); - ImGui::PushStyleColor(ImGuiCol_Border, border); - - struct term_line - { - const char *text{}; - ImVec4 col; - }; - term_line term_lines[] = { - {"$ ls -la", fg}, - {"drwxr-xr-x 5 user group 4096 Dec 2 10:30 .", accent}, - {"Build successful", success}, - {"Error: file not found", error}, - {"Warning: low disk space", warning}, - {"Info: update available", info}, - }; - - for (auto &[text, col] : term_lines) - { - ImGui::TextColored(col, "%s", text); - } - - ImGui::PopStyleColor(2); - ImGui::EndChild(); -} - -void color_scheme_editor::apply_palette_to_editor() -{ - const auto ¤t = m_controller.current_palette(); - if (current.colors().empty()) - return; - - auto get_color_u32 = [&](const std::string &key, const std::string &fallback = "") -> uint32_t { - auto it = current.colors().find(key); - if (it == current.colors().end() && !fallback.empty()) - { - it = current.colors().find(fallback); - } - - if (it != current.colors().end()) - { - const auto &col = it->second; - const uint32_t hex = col.hex(); - // Convert from RRGGBBAA to AABBGGRR (ImGui format) - 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; - }; - - auto palette = m_editor.GetPalette(); - - palette[int(TextEditor::PaletteIndex::Default)] = get_color_u32("editor_main"); - palette[int(TextEditor::PaletteIndex::Keyword)] = get_color_u32("editor_command"); - palette[int(TextEditor::PaletteIndex::Number)] = get_color_u32("editor_warning"); - palette[int(TextEditor::PaletteIndex::String)] = get_color_u32("editor_string"); - palette[int(TextEditor::PaletteIndex::CharLiteral)] = get_color_u32("editor_string"); - palette[int(TextEditor::PaletteIndex::Punctuation)] = get_color_u32("editor_main"); - palette[int(TextEditor::PaletteIndex::Preprocessor)] = get_color_u32("editor_emphasis"); - palette[int(TextEditor::PaletteIndex::Identifier)] = get_color_u32("editor_main"); - palette[int(TextEditor::PaletteIndex::KnownIdentifier)] = get_color_u32("editor_link"); - palette[int(TextEditor::PaletteIndex::PreprocIdentifier)] = get_color_u32("editor_link"); - - palette[int(TextEditor::PaletteIndex::Comment)] = get_color_u32("editor_comment"); - palette[int(TextEditor::PaletteIndex::MultiLineComment)] = get_color_u32("editor_comment"); - - palette[int(TextEditor::PaletteIndex::Background)] = get_color_u32("editor_background"); - palette[int(TextEditor::PaletteIndex::Cursor)] = get_color_u32("cursor"); - - palette[int(TextEditor::PaletteIndex::Selection)] = get_color_u32("editor_selected"); - palette[int(TextEditor::PaletteIndex::ErrorMarker)] = get_color_u32("editor_error"); - palette[int(TextEditor::PaletteIndex::Breakpoint)] = get_color_u32("editor_error"); - - palette[int(TextEditor::PaletteIndex::LineNumber)] = get_color_u32("editor_line_number"); - palette[int(TextEditor::PaletteIndex::CurrentLineFill)] = get_color_u32("surface_variant"); - palette[int(TextEditor::PaletteIndex::CurrentLineFillInactive)] = get_color_u32("surface"); - - palette[int(TextEditor::PaletteIndex::CurrentLineEdge)] = get_color_u32("border_focused"); - - m_editor.SetPalette(palette); -} - -void color_scheme_editor::apply_palette_to_imgui() const -{ - const auto ¤t = m_controller.current_palette(); - - if (current.colors().empty()) - return; - - auto getColor = [&](const std::string &key, const std::string &fallback = "") -> ImVec4 { - auto it = current.colors().find(key); - if (it == current.colors().end() && !fallback.empty()) - { - it = current.colors().find(fallback); - } - - if (it != current.colors().end()) - { - const uint32_t hex = it->second.hex(); - return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f, - ((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f}; - } - - std::cout << "WARNING: Color key '" << key << "' not found!\n"; - return {1, 1, 1, 1}; - }; - - ImGuiStyle &style = ImGui::GetStyle(); - - const ImVec4 bg = getColor("background"); - const ImVec4 surface = getColor("surface"); - const ImVec4 surfaceVariant = getColor("surface_variant"); - const ImVec4 fg = getColor("foreground"); - const ImVec4 fgInactive = getColor("editor_inactive"); - const ImVec4 accent = getColor("accent"); - const ImVec4 border = getColor("border"); - - style.Colors[ImGuiCol_WindowBg] = bg; - style.Colors[ImGuiCol_ChildBg] = surface; - style.Colors[ImGuiCol_PopupBg] = surface; - - style.Colors[ImGuiCol_Border] = border; - style.Colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0); - - style.Colors[ImGuiCol_Text] = fg; - style.Colors[ImGuiCol_TextDisabled] = fgInactive; - - style.Colors[ImGuiCol_Header] = surfaceVariant; - style.Colors[ImGuiCol_HeaderHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f); - style.Colors[ImGuiCol_HeaderActive] = accent; - - style.Colors[ImGuiCol_Button] = surfaceVariant; - style.Colors[ImGuiCol_ButtonHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f); - style.Colors[ImGuiCol_ButtonActive] = accent; - - style.Colors[ImGuiCol_FrameBg] = surfaceVariant; - style.Colors[ImGuiCol_FrameBgHovered] = - ImVec4(surfaceVariant.x * 1.1f, surfaceVariant.y * 1.1f, surfaceVariant.z * 1.1f, 1.0f); - style.Colors[ImGuiCol_FrameBgActive] = - ImVec4(surfaceVariant.x * 1.2f, surfaceVariant.y * 1.2f, surfaceVariant.z * 1.2f, 1.0f); - - style.Colors[ImGuiCol_TitleBg] = surface; - style.Colors[ImGuiCol_TitleBgActive] = surfaceVariant; - style.Colors[ImGuiCol_TitleBgCollapsed] = surface; - - style.Colors[ImGuiCol_ScrollbarBg] = surface; - style.Colors[ImGuiCol_ScrollbarGrab] = surfaceVariant; - style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f); - style.Colors[ImGuiCol_ScrollbarGrabActive] = accent; - - style.Colors[ImGuiCol_SliderGrab] = accent; - style.Colors[ImGuiCol_SliderGrabActive] = - ImVec4(accent.x * 1.2f, accent.y * 1.2f, accent.z * 1.2f, 1.0f); - - style.Colors[ImGuiCol_CheckMark] = accent; - style.Colors[ImGuiCol_ResizeGrip] = surfaceVariant; - style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f); - style.Colors[ImGuiCol_ResizeGripActive] = accent; - - style.Colors[ImGuiCol_Tab] = surface; - style.Colors[ImGuiCol_TabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f); - style.Colors[ImGuiCol_TabActive] = surfaceVariant; - style.Colors[ImGuiCol_TabUnfocused] = surface; - style.Colors[ImGuiCol_TabUnfocusedActive] = surfaceVariant; - style.Colors[ImGuiCol_TabSelectedOverline] = accent; - - style.Colors[ImGuiCol_TableHeaderBg] = surfaceVariant; - style.Colors[ImGuiCol_TableBorderStrong] = border; - style.Colors[ImGuiCol_TableBorderLight] = - ImVec4(border.x * 0.7f, border.y * 0.7f, border.z * 0.7f, border.w); - - style.Colors[ImGuiCol_TableRowBg] = ImVec4(0, 0, 0, 0); - style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(fg.x, fg.y, fg.z, 0.06f); - - style.Colors[ImGuiCol_Separator] = border; - style.Colors[ImGuiCol_SeparatorHovered] = accent; - style.Colors[ImGuiCol_SeparatorActive] = accent; - - style.Colors[ImGuiCol_MenuBarBg] = surface; - - style.Colors[ImGuiCol_DockingPreview] = ImVec4(accent.x, accent.y, accent.z, 0.7f); - style.Colors[ImGuiCol_DockingEmptyBg] = bg; -} \ No newline at end of file diff --git a/src/gui/color_scheme_editor.hpp b/src/gui/color_scheme_editor.hpp index 04c54da..9baead8 100644 --- a/src/gui/color_scheme_editor.hpp +++ b/src/gui/color_scheme_editor.hpp @@ -1,8 +1,9 @@ #ifndef CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP #define CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP -#include "color_text_edit/TextEditor.h" #include "palette_controller.hpp" +#include "color_table_renderer.hpp" +#include "preview_renderer.hpp" class template_editor; @@ -18,15 +19,12 @@ public: private: void render_controls(); - void render_color_table(); - void render_preview_content(); - - void apply_palette_to_editor(); - void apply_palette_to_imgui() const; + void apply_themes(); void notify_palette_changed(); palette_controller m_controller; - TextEditor m_editor; + color_table_renderer m_color_table; + preview_renderer m_preview; template_editor* m_template_editor{nullptr}; }; diff --git a/src/gui/color_table_renderer.cpp b/src/gui/color_table_renderer.cpp new file mode 100644 index 0000000..d22ee3b --- /dev/null +++ b/src/gui/color_table_renderer.cpp @@ -0,0 +1,122 @@ +#include "color_table_renderer.hpp" +#include "imgui.h" +#include + +void color_table_renderer::render_color_row(const std::string &name, + const clrsync::core::palette& current, + palette_controller& controller, + const OnColorChangedCallback& on_changed) +{ + const auto &colors = current.colors(); + auto it = colors.find(name); + if (it == colors.end()) + return; + + const clrsync::core::color &col = it->second; + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(name.c_str()); + + ImGui::TableSetColumnIndex(1); + { + std::string hex_str = col.to_hex_string(); + char buf[9]; + strncpy(buf, hex_str.c_str(), sizeof(buf)); + buf[8] = 0; + + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::InputText(("##hex_" + name).c_str(), buf, sizeof(buf), + ImGuiInputTextFlags_CharsUppercase)) + { + try + { + clrsync::core::color new_color; + new_color.from_hex_string(buf); + controller.set_color(name, new_color); + if (on_changed) + on_changed(); + } + catch (...) + { + } + } + } + + ImGui::TableSetColumnIndex(2); + ImGui::PushID(name.c_str()); + float c[4] = {((col.hex() >> 24) & 0xFF) / 255.0f, ((col.hex() >> 16) & 0xFF) / 255.0f, + ((col.hex() >> 8) & 0xFF) / 255.0f, (col.hex() & 0xFF) / 255.0f}; + + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::ColorEdit4(("##color_" + name).c_str(), c, + ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | + ImGuiColorEditFlags_AlphaBar)) + { + uint32_t r = (uint32_t)(c[0] * 255.0f); + uint32_t g = (uint32_t)(c[1] * 255.0f); + uint32_t b = (uint32_t)(c[2] * 255.0f); + uint32_t a = (uint32_t)(c[3] * 255.0f); + uint32_t hex = (r << 24) | (g << 16) | (b << 8) | a; + + controller.set_color(name, clrsync::core::color(hex)); + if (on_changed) + on_changed(); + } + + ImGui::PopID(); +} + +void color_table_renderer::render(const clrsync::core::palette& current, + palette_controller& controller, + const OnColorChangedCallback& on_changed) +{ + if (current.colors().empty()) + { + ImGui::Text("No palette loaded"); + return; + } + + ImGui::Text("Color Variables"); + ImGui::Separator(); + + auto draw_table = [&](const char *title, const std::vector &keys) { + ImGui::TextUnformatted(title); + + if (ImGui::BeginTable(title, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) + { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 160.0f); + ImGui::TableSetupColumn("HEX", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); + + for (auto *k : keys) + render_color_row(k, current, controller, on_changed); + + ImGui::EndTable(); + } + + ImGui::Spacing(); + }; + + draw_table("General UI", {"background", "on_background", "surface", "on_surface", + "surface_variant", "on_surface_variant", "foreground", + "cursor", "accent"}); + + draw_table("Borders", {"border_focused", "border"}); + + draw_table("Semantic Colors", {"success", "info", "warning", "error", + "on_success", "on_info", "on_warning", "on_error"}); + + draw_table("Editor", {"editor_background", "editor_command", "editor_comment", + "editor_disabled", "editor_emphasis", "editor_error", + "editor_inactive", "editor_line_number", "editor_link", + "editor_main", "editor_selected", "editor_selection_inactive", + "editor_string", "editor_success", "editor_warning"}); + + draw_table("Terminal (Base16)", {"base00", "base01", "base02", "base03", + "base04", "base05", "base06", "base07", + "base08", "base09", "base0A", "base0B", + "base0C", "base0D", "base0E", "base0F"}); +} diff --git a/src/gui/color_table_renderer.hpp b/src/gui/color_table_renderer.hpp new file mode 100644 index 0000000..7823362 --- /dev/null +++ b/src/gui/color_table_renderer.hpp @@ -0,0 +1,24 @@ +#ifndef CLRSYNC_GUI_COLOR_TABLE_RENDERER_HPP +#define CLRSYNC_GUI_COLOR_TABLE_RENDERER_HPP + +#include "core/palette/palette.hpp" +#include "palette_controller.hpp" +#include + +class color_table_renderer +{ +public: + using OnColorChangedCallback = std::function; + + void render(const clrsync::core::palette& palette, + palette_controller& controller, + const OnColorChangedCallback& on_changed); + +private: + void render_color_row(const std::string& name, + const clrsync::core::palette& palette, + palette_controller& controller, + const OnColorChangedCallback& on_changed); +}; + +#endif // CLRSYNC_GUI_COLOR_TABLE_RENDERER_HPP diff --git a/src/gui/imgui_helpers.cpp b/src/gui/imgui_helpers.cpp index 48d6a98..ac84ec8 100644 --- a/src/gui/imgui_helpers.cpp +++ b/src/gui/imgui_helpers.cpp @@ -1,3 +1,4 @@ +#include #include #include "GLFW/glfw3.h" @@ -11,14 +12,26 @@ GLFWwindow * init_glfw() { - if (!glfwInit()) return nullptr; + 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); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); GLFWwindow* w = glfwCreateWindow(1280, 720, "clrsync", nullptr, nullptr); - if (!w) return nullptr; + if (!w) + { + std::cerr << "Failed to create GLFW window\n"; + return nullptr; + } glfwMakeContextCurrent(w); glfwSwapInterval(1); diff --git a/src/gui/main.cpp b/src/gui/main.cpp index abc7609..9cdb249 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -20,7 +20,17 @@ int main(int, char**) { auto config_path = clrsync::core::get_default_config_path(); auto conf = std::make_unique(config_path); - clrsync::core::config::instance().initialize(std::move(conf)); + + try + { + clrsync::core::config::instance().initialize(std::move(conf)); + } + catch (const std::exception& e) + { + std::cerr << "Fatal error: " << e.what() << std::endl; + std::cerr << "Hint: Set CLRSYNC_CONFIG_PATH environment variable or ensure config exists at: " << config_path << std::endl; + return 1; + } std::filesystem::path base = config_path; static std::string ini_path = (base.parent_path() / "layout.ini").string(); diff --git a/src/gui/preview_renderer.cpp b/src/gui/preview_renderer.cpp new file mode 100644 index 0000000..534e91e --- /dev/null +++ b/src/gui/preview_renderer.cpp @@ -0,0 +1,152 @@ +#include "preview_renderer.hpp" +#include "theme_applier.hpp" +#include "imgui.h" +#include + +preview_renderer::preview_renderer() +{ + m_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + m_editor.SetText(R"(#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// Expands ~ to the user's home directory +std::string expand_user(const std::string &path) +{ + if (path.empty()) return ""; + + std::string result; + if (path[0] == '~') + { +#ifdef _WIN32 + const char* home = std::getenv("USERPROFILE"); +#else + const char* home = std::getenv("HOME"); +#endif + result = home ? std::string(home) : "~"; + result += path.substr(1); + } + else + { + result = path; + } + + return result; +} + +// Lists all files in a directory +std::vector list_files(const std::string &dir_path) +{ + std::vector files; + try + { + for (const auto &entry : fs::directory_iterator(dir_path)) + { + if (entry.is_regular_file()) + files.push_back(entry.path().string()); + } + } + catch (const std::exception &e) + { + std::cerr << "Error: " << e.what() << std::endl; + } + return files; +} + +int main() +{ + std::string path = expand_user("~/Documents"); + std::cout << "Listing files in: " << path << std::endl; + + auto files = list_files(path); + for (const auto &f : files) + std::cout << " " << f << std::endl; + + return 0; +})"); + + m_editor.SetShowWhitespaces(false); +} + +void preview_renderer::apply_palette(const clrsync::core::palette& palette) +{ + theme_applier::apply_to_editor(m_editor, palette); +} + +void preview_renderer::render_code_preview() +{ + const float avail_height = ImGui::GetContentRegionAvail().y; + const float code_preview_height = std::max(250.0f, avail_height * 0.55f); + + ImGui::Text("Code Editor:"); + m_editor.Render("##CodeEditor", ImVec2(0, code_preview_height), true); +} + +void preview_renderer::render_terminal_preview(const clrsync::core::palette& current) +{ + auto get_color = [&](const std::string &key) -> ImVec4 { + auto it = current.colors().find(key); + if (it != current.colors().end()) + { + const auto &col = it->second; + const uint32_t hex = col.hex(); + return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f, + ((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f}; + } + return {1, 1, 1, 1}; + }; + + const ImVec4 editor_bg = get_color("editor_background"); + const ImVec4 fg = get_color("foreground"); + const ImVec4 accent = get_color("accent"); + const ImVec4 border = get_color("border"); + const ImVec4 error = get_color("error"); + const ImVec4 warning = get_color("warning"); + const ImVec4 success = get_color("success"); + const ImVec4 info = get_color("info"); + + ImGui::Spacing(); + ImGui::Text("Terminal Preview:"); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, editor_bg); + ImGui::BeginChild("TerminalPreview", ImVec2(0, 0), true); + ImGui::PushStyleColor(ImGuiCol_Border, border); + + struct term_line + { + const char *text{}; + ImVec4 col; + }; + term_line term_lines[] = { + {"$ ls -la", fg}, + {"drwxr-xr-x 5 user group 4096 Dec 2 10:30 .", accent}, + {"Build successful", success}, + {"Error: file not found", error}, + {"Warning: low disk space", warning}, + {"Info: update available", info}, + }; + + for (auto &[text, col] : term_lines) + { + ImGui::TextColored(col, "%s", text); + } + + ImGui::PopStyleColor(2); + ImGui::EndChild(); +} + +void preview_renderer::render(const clrsync::core::palette& current) +{ + if (current.colors().empty()) + { + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Current palette is empty"); + return; + } + + render_code_preview(); + render_terminal_preview(current); +} diff --git a/src/gui/preview_renderer.hpp b/src/gui/preview_renderer.hpp new file mode 100644 index 0000000..315ae3f --- /dev/null +++ b/src/gui/preview_renderer.hpp @@ -0,0 +1,22 @@ +#ifndef CLRSYNC_GUI_PREVIEW_RENDERER_HPP +#define CLRSYNC_GUI_PREVIEW_RENDERER_HPP + +#include "core/palette/palette.hpp" +#include "color_text_edit/TextEditor.h" + +class preview_renderer +{ +public: + preview_renderer(); + + void render(const clrsync::core::palette& palette); + void apply_palette(const clrsync::core::palette& palette); + +private: + void render_code_preview(); + void render_terminal_preview(const clrsync::core::palette& palette); + + TextEditor m_editor; +}; + +#endif // CLRSYNC_GUI_PREVIEW_RENDERER_HPP diff --git a/src/gui/template_editor.cpp b/src/gui/template_editor.cpp index 68db00b..0db0563 100644 --- a/src/gui/template_editor.cpp +++ b/src/gui/template_editor.cpp @@ -369,11 +369,14 @@ void template_editor::save_template() std::string template_content = m_editor.GetText(); std::ofstream out(template_file); - if (out.is_open()) + if (!out.is_open()) { - out << template_content; - out.close(); + m_validation_error = "Failed to write template file"; + return; } + + out << template_content; + out.close(); clrsync::core::theme_template tmpl(trimmed_name, template_file.string(), trimmed_path); tmpl.set_reload_command(m_reload_command); diff --git a/src/gui/theme_applier.cpp b/src/gui/theme_applier.cpp new file mode 100644 index 0000000..5337792 --- /dev/null +++ b/src/gui/theme_applier.cpp @@ -0,0 +1,166 @@ +#include "theme_applier.hpp" +#include "imgui.h" +#include + +namespace theme_applier +{ + +void apply_to_editor(TextEditor& editor, const clrsync::core::palette& current) +{ + if (current.colors().empty()) + return; + + auto get_color_u32 = [&](const std::string &key, const std::string &fallback = "") -> uint32_t { + auto it = current.colors().find(key); + if (it == current.colors().end() && !fallback.empty()) + { + it = current.colors().find(fallback); + } + + if (it != current.colors().end()) + { + const auto &col = it->second; + const uint32_t hex = col.hex(); + // Convert from RRGGBBAA to AABBGGRR (ImGui format) + 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; + }; + + auto palette = editor.GetPalette(); + + palette[int(TextEditor::PaletteIndex::Default)] = get_color_u32("editor_main"); + palette[int(TextEditor::PaletteIndex::Keyword)] = get_color_u32("editor_command"); + palette[int(TextEditor::PaletteIndex::Number)] = get_color_u32("editor_warning"); + palette[int(TextEditor::PaletteIndex::String)] = get_color_u32("editor_string"); + palette[int(TextEditor::PaletteIndex::CharLiteral)] = get_color_u32("editor_string"); + palette[int(TextEditor::PaletteIndex::Punctuation)] = get_color_u32("editor_main"); + palette[int(TextEditor::PaletteIndex::Preprocessor)] = get_color_u32("editor_emphasis"); + palette[int(TextEditor::PaletteIndex::Identifier)] = get_color_u32("editor_main"); + palette[int(TextEditor::PaletteIndex::KnownIdentifier)] = get_color_u32("editor_link"); + palette[int(TextEditor::PaletteIndex::PreprocIdentifier)] = get_color_u32("editor_link"); + + palette[int(TextEditor::PaletteIndex::Comment)] = get_color_u32("editor_comment"); + palette[int(TextEditor::PaletteIndex::MultiLineComment)] = get_color_u32("editor_comment"); + + palette[int(TextEditor::PaletteIndex::Background)] = get_color_u32("editor_background"); + palette[int(TextEditor::PaletteIndex::Cursor)] = get_color_u32("cursor"); + + palette[int(TextEditor::PaletteIndex::Selection)] = get_color_u32("editor_selected"); + palette[int(TextEditor::PaletteIndex::ErrorMarker)] = get_color_u32("editor_error"); + palette[int(TextEditor::PaletteIndex::Breakpoint)] = get_color_u32("editor_error"); + + palette[int(TextEditor::PaletteIndex::LineNumber)] = get_color_u32("editor_line_number"); + palette[int(TextEditor::PaletteIndex::CurrentLineFill)] = get_color_u32("surface_variant"); + palette[int(TextEditor::PaletteIndex::CurrentLineFillInactive)] = get_color_u32("surface"); + + palette[int(TextEditor::PaletteIndex::CurrentLineEdge)] = get_color_u32("border_focused"); + + editor.SetPalette(palette); +} + +void apply_to_imgui(const clrsync::core::palette& current) +{ + if (current.colors().empty()) + return; + + auto getColor = [&](const std::string &key, const std::string &fallback = "") -> ImVec4 { + auto it = current.colors().find(key); + if (it == current.colors().end() && !fallback.empty()) + { + it = current.colors().find(fallback); + } + + if (it != current.colors().end()) + { + const uint32_t hex = it->second.hex(); + return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f, + ((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f}; + } + + std::cout << "WARNING: Color key '" << key << "' not found!\n"; + return {1, 1, 1, 1}; + }; + + ImGuiStyle &style = ImGui::GetStyle(); + + const ImVec4 bg = getColor("background"); + const ImVec4 surface = getColor("surface"); + const ImVec4 surfaceVariant = getColor("surface_variant"); + const ImVec4 fg = getColor("foreground"); + const ImVec4 fgInactive = getColor("editor_inactive"); + const ImVec4 accent = getColor("accent"); + const ImVec4 border = getColor("border"); + + style.Colors[ImGuiCol_WindowBg] = bg; + style.Colors[ImGuiCol_ChildBg] = surface; + style.Colors[ImGuiCol_PopupBg] = surface; + + style.Colors[ImGuiCol_Border] = border; + style.Colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0); + + style.Colors[ImGuiCol_Text] = fg; + style.Colors[ImGuiCol_TextDisabled] = fgInactive; + + style.Colors[ImGuiCol_Header] = surfaceVariant; + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f); + style.Colors[ImGuiCol_HeaderActive] = accent; + + style.Colors[ImGuiCol_Button] = surfaceVariant; + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f); + style.Colors[ImGuiCol_ButtonActive] = accent; + + style.Colors[ImGuiCol_FrameBg] = surfaceVariant; + style.Colors[ImGuiCol_FrameBgHovered] = + ImVec4(surfaceVariant.x * 1.1f, surfaceVariant.y * 1.1f, surfaceVariant.z * 1.1f, 1.0f); + style.Colors[ImGuiCol_FrameBgActive] = + ImVec4(surfaceVariant.x * 1.2f, surfaceVariant.y * 1.2f, surfaceVariant.z * 1.2f, 1.0f); + + style.Colors[ImGuiCol_TitleBg] = surface; + style.Colors[ImGuiCol_TitleBgActive] = surfaceVariant; + style.Colors[ImGuiCol_TitleBgCollapsed] = surface; + + style.Colors[ImGuiCol_ScrollbarBg] = surface; + style.Colors[ImGuiCol_ScrollbarGrab] = surfaceVariant; + style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f); + style.Colors[ImGuiCol_ScrollbarGrabActive] = accent; + + style.Colors[ImGuiCol_SliderGrab] = accent; + style.Colors[ImGuiCol_SliderGrabActive] = + ImVec4(accent.x * 1.2f, accent.y * 1.2f, accent.z * 1.2f, 1.0f); + + style.Colors[ImGuiCol_CheckMark] = accent; + style.Colors[ImGuiCol_ResizeGrip] = surfaceVariant; + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f); + style.Colors[ImGuiCol_ResizeGripActive] = accent; + + style.Colors[ImGuiCol_Tab] = surface; + style.Colors[ImGuiCol_TabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f); + style.Colors[ImGuiCol_TabActive] = surfaceVariant; + style.Colors[ImGuiCol_TabUnfocused] = surface; + style.Colors[ImGuiCol_TabUnfocusedActive] = surfaceVariant; + style.Colors[ImGuiCol_TabSelectedOverline] = accent; + + style.Colors[ImGuiCol_TableHeaderBg] = surfaceVariant; + style.Colors[ImGuiCol_TableBorderStrong] = border; + style.Colors[ImGuiCol_TableBorderLight] = + ImVec4(border.x * 0.7f, border.y * 0.7f, border.z * 0.7f, border.w); + + style.Colors[ImGuiCol_TableRowBg] = ImVec4(0, 0, 0, 0); + style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(fg.x, fg.y, fg.z, 0.06f); + + style.Colors[ImGuiCol_Separator] = border; + style.Colors[ImGuiCol_SeparatorHovered] = accent; + style.Colors[ImGuiCol_SeparatorActive] = accent; + + style.Colors[ImGuiCol_MenuBarBg] = surface; + + style.Colors[ImGuiCol_DockingPreview] = ImVec4(accent.x, accent.y, accent.z, 0.7f); + style.Colors[ImGuiCol_DockingEmptyBg] = bg; +} + +} // namespace theme_applier diff --git a/src/gui/theme_applier.hpp b/src/gui/theme_applier.hpp new file mode 100644 index 0000000..6d4c463 --- /dev/null +++ b/src/gui/theme_applier.hpp @@ -0,0 +1,13 @@ +#ifndef CLRSYNC_GUI_THEME_APPLIER_HPP +#define CLRSYNC_GUI_THEME_APPLIER_HPP + +#include "core/palette/palette.hpp" +#include "color_text_edit/TextEditor.h" + +namespace theme_applier +{ + void apply_to_imgui(const clrsync::core::palette& pal); + void apply_to_editor(TextEditor& editor, const clrsync::core::palette& pal); +} + +#endif // CLRSYNC_GUI_THEME_APPLIER_HPP