From 591bd4c6761c9a7a47fa39cdf30f0a1dd059ce4d Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Thu, 21 May 2026 23:43:29 +0300 Subject: [PATCH 1/4] updated layouts and action button styles --- src/core/common/version.hpp | 2 +- src/gui/CMakeLists.txt | 1 + src/gui/layout/main_layout.cpp | 5 +- src/gui/layout/ui_layout.hpp | 19 + src/gui/views/color_scheme_editor.cpp | 445 ++++++-------------- src/gui/views/color_scheme_editor.hpp | 38 +- src/gui/views/color_table_renderer.cpp | 57 ++- src/gui/views/preview_renderer.cpp | 7 +- src/gui/views/template_editor.cpp | 26 +- src/gui/widgets/action_buttons.cpp | 7 +- src/gui/widgets/action_buttons.hpp | 1 + src/gui/widgets/form_field.hpp | 2 +- src/gui/widgets/generate_palette_dialog.cpp | 300 +++++++++++++ src/gui/widgets/generate_palette_dialog.hpp | 64 +++ src/gui/widgets/settings_buttons.cpp | 7 +- src/gui/widgets/template_controls.cpp | 69 +-- 16 files changed, 643 insertions(+), 407 deletions(-) create mode 100644 src/gui/layout/ui_layout.hpp create mode 100644 src/gui/widgets/generate_palette_dialog.cpp create mode 100644 src/gui/widgets/generate_palette_dialog.hpp diff --git a/src/core/common/version.hpp b/src/core/common/version.hpp index 6f45b35..f385795 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 = "1.2.1+git.g815f162"; +const std::string GIT_SEMVER = "1.2.2+git.g6e994ee"; const std::string version_string(); } // namespace clrsync::core diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8f40724..2bbea34 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -25,6 +25,7 @@ set(GUI_SOURCES widgets/centered_text.cpp widgets/validation_message.cpp widgets/template_controls.cpp + widgets/generate_palette_dialog.cpp layout/main_layout.cpp platform/windows/font_loader_windows.cpp platform/linux/font_loader_linux.cpp diff --git a/src/gui/layout/main_layout.cpp b/src/gui/layout/main_layout.cpp index 72f5549..977c64e 100644 --- a/src/gui/layout/main_layout.cpp +++ b/src/gui/layout/main_layout.cpp @@ -1,4 +1,5 @@ #include "main_layout.hpp" +#include "ui_layout.hpp" #include "imgui.h" #include "imgui_internal.h" @@ -10,7 +11,6 @@ namespace constexpr float TOPBAR_HEIGHT = 36.0f; constexpr float TOPBAR_PADDING_X = 12.0f; constexpr float TOPBAR_PADDING_Y = 4.0f; -constexpr float BUTTON_SPACING = 8.0f; } void main_layout::render_menu_bar() @@ -93,7 +93,8 @@ void main_layout::setup_dockspace(bool &first_time) ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size); ImGuiID center, right; - ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.40f, &right, ¢er); + ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, DOCK_COLOR_PANEL_RATIO, &right, + ¢er); ImGuiDockNode *center_node = ImGui::DockBuilderGetNode(center); if (center_node) diff --git a/src/gui/layout/ui_layout.hpp b/src/gui/layout/ui_layout.hpp new file mode 100644 index 0000000..27ce978 --- /dev/null +++ b/src/gui/layout/ui_layout.hpp @@ -0,0 +1,19 @@ +#ifndef CLRSYNC_GUI_LAYOUT_UI_LAYOUT_HPP +#define CLRSYNC_GUI_LAYOUT_UI_LAYOUT_HPP + +namespace clrsync::gui::layout +{ + +constexpr float BUTTON_SPACING = 8.0f; +constexpr float FORM_LABEL_WIDTH = 72.0f; +constexpr float BROWSE_BUTTON_WIDTH = 72.0f; +constexpr float COLOR_SWATCH_SIZE = 28.0f; +constexpr float HEX_COLUMN_WIDTH = 88.0f; +constexpr float COLOR_COLUMN_WIDTH = 40.0f; +constexpr float TEMPLATE_LIST_MIN_WIDTH = 260.0f; +constexpr float TEMPLATE_LIST_WIDTH_RATIO = 0.34f; +constexpr float DOCK_COLOR_PANEL_RATIO = 0.36f; + +} // namespace clrsync::gui::layout + +#endif // CLRSYNC_GUI_LAYOUT_UI_LAYOUT_HPP diff --git a/src/gui/views/color_scheme_editor.cpp b/src/gui/views/color_scheme_editor.cpp index 55848ec..3fbca53 100644 --- a/src/gui/views/color_scheme_editor.cpp +++ b/src/gui/views/color_scheme_editor.cpp @@ -1,4 +1,5 @@ #include "color_scheme_editor.hpp" +#include "gui/layout/ui_layout.hpp" #include "core/palette/hellwal_generator.hpp" #include "core/palette/matugen_generator.hpp" #include "gui/controllers/theme_applier.hpp" @@ -10,7 +11,6 @@ #include "imgui.h" #include "settings_window.hpp" #include "template_editor.hpp" -#include #include #include @@ -22,27 +22,129 @@ void color_scheme_editor::refresh_available_generators() clrsync::core::hellwal_generator hellwal_gen; if (hellwal_gen.supports_current_system()) { - m_available_generators.push_back(generator_kind::hellwal); + m_available_generators.push_back(clrsync::gui::widgets::palette_generator_kind::hellwal); m_generator_labels.push_back("hellwal"); } clrsync::core::matugen_generator matugen_gen; if (matugen_gen.supports_current_system()) { - m_available_generators.push_back(generator_kind::matugen); + m_available_generators.push_back(clrsync::gui::widgets::palette_generator_kind::matugen); m_generator_labels.push_back("matugen"); } - if (m_generator_idx < 0 || m_generator_idx >= static_cast(m_available_generators.size())) - m_generator_idx = 0; + if (m_generate_state.generator_idx < 0 || + m_generate_state.generator_idx >= static_cast(m_available_generators.size())) + m_generate_state.generator_idx = 0; } -std::optional color_scheme_editor::selected_generator_kind() const +std::optional +color_scheme_editor::selected_generator_kind() const { - if (m_generator_idx < 0 || m_generator_idx >= static_cast(m_available_generators.size())) + if (m_generate_state.generator_idx < 0 || + m_generate_state.generator_idx >= static_cast(m_available_generators.size())) return std::nullopt; - return m_available_generators[m_generator_idx]; + return m_available_generators[m_generate_state.generator_idx]; +} + +void color_scheme_editor::execute_palette_generation() +{ + const auto selected_kind = selected_generator_kind(); + if (!selected_kind) + return; + + try + { + if (*selected_kind == clrsync::gui::widgets::palette_generator_kind::hellwal) + { + clrsync::core::hellwal_generator gen; + if (!gen.supports_current_system()) + { + std::cerr << "Generation failed: hellwal is not supported on " + << clrsync::core::generator::system_name( + clrsync::core::generator::current_system()) + << std::endl; + return; + } + clrsync::core::hellwal_generator::options opts; + opts.neon = m_generate_state.neon; + opts.dark = m_generate_state.dark; + opts.light = m_generate_state.light; + opts.color = m_generate_state.color; + opts.dark_offset = m_generate_state.dark_offset; + opts.bright_offset = m_generate_state.bright_offset; + opts.invert = m_generate_state.invert; + opts.gray_scale = m_generate_state.gray_scale; + + auto image_path = m_generate_state.image_path; + if (image_path.empty()) + { + image_path = file_dialogs::open_file_dialog("Select Image", "", + file_dialogs::image_file_filters()); + } + + auto pal = gen.generate_from_image(image_path, opts); + if (pal.name().empty()) + { + std::filesystem::path p(image_path); + pal.set_name(std::string("hellwal:") + p.filename().string()); + } + m_controller.import_palette(pal); + m_controller.select_palette(pal.name()); + apply_themes(); + } + else if (*selected_kind == clrsync::gui::widgets::palette_generator_kind::matugen) + { + clrsync::core::matugen_generator gen; + if (!gen.supports_current_system()) + { + std::cerr << "Generation failed: matugen is not supported on " + << clrsync::core::generator::system_name( + clrsync::core::generator::current_system()) + << std::endl; + return; + } + clrsync::core::matugen_generator::options opts; + opts.mode = m_generate_state.matugen_mode; + opts.type = m_generate_state.matugen_type; + opts.contrast = m_generate_state.matugen_contrast; + opts.source_color_index = m_generate_state.matugen_source_color_index; + + clrsync::core::palette pal; + if (m_generate_state.matugen_use_color) + { + std::string hex = m_generate_state.matugen_color_hex; + if (!hex.empty() && hex[0] == '#') + hex = hex.substr(1); + pal = gen.generate_from_color(hex, opts); + if (pal.name().empty()) + pal.set_name(std::string("matugen:color:") + hex); + } + else + { + auto image_path = m_generate_state.image_path; + if (image_path.empty()) + { + image_path = file_dialogs::open_file_dialog("Select Image", "", + file_dialogs::image_file_filters()); + } + pal = gen.generate_from_image(image_path, opts); + if (pal.name().empty()) + { + std::filesystem::path p(image_path); + pal.set_name(std::string("matugen:") + p.filename().string()); + } + } + m_controller.import_palette(pal); + m_controller.select_palette(pal.name()); + apply_themes(); + } + } + catch (const std::exception &e) + { + std::cerr << "Generation failed: " << e.what() << std::endl; + } } color_scheme_editor::color_scheme_editor() @@ -112,7 +214,8 @@ void color_scheme_editor::render_controls() const auto ¤t = m_controller.current_palette(); const auto &palettes = m_controller.palettes(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6, 8)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + ImVec2(clrsync::gui::layout::BUTTON_SPACING, 8)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 5)); ImGui::AlignTextToFramePadding(); @@ -121,8 +224,7 @@ void color_scheme_editor::render_controls() m_palette_selector.render(m_controller, 200.0f); - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); + ImGui::SameLine(0, clrsync::gui::layout::BUTTON_SPACING); if (ImGui::Button(" + New ")) { @@ -135,293 +237,15 @@ void color_scheme_editor::render_controls() m_new_palette_dialog.render(); m_generate_dialog.render(); - ImGui::SameLine(); + ImGui::SameLine(0, clrsync::gui::layout::BUTTON_SPACING); m_action_buttons.render(current); - ImGui::SameLine(); - ImGui::SameLine(); + ImGui::SameLine(0, clrsync::gui::layout::BUTTON_SPACING); if (ImGui::Button("Generate")) - { - m_show_generate_modal = true; - } - - if (m_show_generate_modal) - { - ImGui::OpenPopup("Generate Palette"); - m_show_generate_modal = false; - } - - if (ImGui::BeginPopupModal("Generate Palette", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::Text("Generator:"); - std::vector generator_items; - generator_items.reserve(m_generator_labels.size()); - for (const auto &label : m_generator_labels) - generator_items.push_back(label.c_str()); - - if (!generator_items.empty()) - { - ImGui::SameLine(); - ImGui::SetNextItemWidth(160.0f); - ImGui::Combo("##gen_select", &m_generator_idx, generator_items.data(), - static_cast(generator_items.size())); - } - else - { - ImGui::SameLine(); - ImGui::TextDisabled("No supported generators"); - } + m_generate_palette_dialog.open(); - const auto selected_kind = selected_generator_kind(); - - if (selected_kind && *selected_kind == generator_kind::hellwal) - { - ImGui::Separator(); - ImGui::Text("hellwal options"); - ImGui::Spacing(); - - // image selector - ImGui::Text("Image:"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(300.0f); - { - char buf[1024]; - std::strncpy(buf, m_gen_image_path.c_str(), sizeof(buf)); - buf[sizeof(buf) - 1] = '\0'; - if (ImGui::InputText("##gen_image", buf, sizeof(buf))) - { - m_gen_image_path = buf; - } - } - ImGui::SameLine(); - if (ImGui::Button("Browse##gen_image")) - { - std::string res = - file_dialogs::open_file_dialog("Select Image", m_gen_image_path, - file_dialogs::image_file_filters()); - if (!res.empty()) - m_gen_image_path = res; - } - - ImGui::Checkbox("Neon mode", &m_gen_neon); - - ImGui::Text("Modes (can combine):"); - ImGui::Checkbox("Dark", &m_gen_dark); - ImGui::SameLine(); - ImGui::Checkbox("Light", &m_gen_light); - ImGui::SameLine(); - ImGui::Checkbox("Color", &m_gen_color); - - ImGui::SliderFloat("Dark offset", &m_gen_dark_offset, 0.0f, 1.0f); - ImGui::SliderFloat("Bright offset", &m_gen_bright_offset, 0.0f, 1.0f); - ImGui::Checkbox("Invert colors", &m_gen_invert); - ImGui::SliderFloat("Gray scale", &m_gen_gray_scale, 0.0f, 1.0f); - } - - if (selected_kind && *selected_kind == generator_kind::matugen) - { - ImGui::Separator(); - ImGui::Text("matugen options"); - ImGui::Spacing(); - - ImGui::Text("Image:"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(300.0f); - { - char buf[1024]; - std::strncpy(buf, m_gen_image_path.c_str(), sizeof(buf)); - buf[sizeof(buf) - 1] = '\0'; - if (ImGui::InputText("##gen_image", buf, sizeof(buf))) - { - m_gen_image_path = buf; - } - } - ImGui::SameLine(); - if (ImGui::Button("Browse##gen_image")) - { - std::string res = - file_dialogs::open_file_dialog("Select Image", m_gen_image_path, - file_dialogs::image_file_filters()); - if (!res.empty()) - m_gen_image_path = res; - } - - ImGui::Text("Mode:"); - ImGui::SameLine(); - const char *modes[] = {"dark", "light"}; - int mode_idx = (m_matugen_mode == "light") ? 1 : 0; - ImGui::SetNextItemWidth(120.0f); - ImGui::Combo("##matugen_mode", &mode_idx, modes, IM_ARRAYSIZE(modes)); - m_matugen_mode = (mode_idx == 1) ? "light" : "dark"; - - ImGui::Text("Type:"); - ImGui::SameLine(); - const char *types[] = {"scheme-content", "scheme-expressive", "scheme-fidelity", - "scheme-fruit-salad", "scheme-monochrome", "scheme-neutral", - "scheme-rainbow", "scheme-tonal-spot"}; - int type_idx = 7; // default index for scheme-tonal-spot - for (int i = 0; i < IM_ARRAYSIZE(types); ++i) - { - if (m_matugen_type == types[i]) - { - type_idx = i; - break; - } - } - ImGui::SetNextItemWidth(260.0f); - ImGui::Combo("##matugen_type", &type_idx, types, IM_ARRAYSIZE(types)); - m_matugen_type = types[type_idx]; - - ImGui::SliderFloat("Contrast", &m_matugen_contrast, -1.0f, 1.0f); - - ImGui::SliderInt("Source Color Index", &m_matugen_source_color_index, 0, 3); - - ImGui::Spacing(); - ImGui::Checkbox("Use color (instead of image)", &m_matugen_use_color); - if (m_matugen_use_color) - { - ImGui::Text("Color:"); - ImGui::SameLine(); - ImGui::ColorEdit3("##matugen_color", m_matugen_color_vec); - // update hex string from vec - int r = static_cast(m_matugen_color_vec[0] * 255.0f + 0.5f); - int g = static_cast(m_matugen_color_vec[1] * 255.0f + 0.5f); - int b = static_cast(m_matugen_color_vec[2] * 255.0f + 0.5f); - char hexbuf[8]; - std::snprintf(hexbuf, sizeof(hexbuf), "%02X%02X%02X", r, g, b); - m_matugen_color_hex = hexbuf; - } - } - - ImGui::Separator(); - const bool can_generate = selected_kind.has_value(); - if (!can_generate) - ImGui::BeginDisabled(); - if (ImGui::Button("Generate", ImVec2(120, 0))) - { - try - { - if (selected_kind && *selected_kind == generator_kind::hellwal) - { - clrsync::core::hellwal_generator gen; - if (!gen.supports_current_system()) - { - std::cerr << "Generation failed: hellwal is not supported on " - << clrsync::core::generator::system_name( - clrsync::core::generator::current_system()) - << std::endl; - ImGui::CloseCurrentPopup(); - if (!can_generate) - ImGui::EndDisabled(); - return; - } - clrsync::core::hellwal_generator::options opts; - opts.neon = m_gen_neon; - opts.dark = m_gen_dark; - opts.light = m_gen_light; - opts.color = m_gen_color; - opts.dark_offset = m_gen_dark_offset; - opts.bright_offset = m_gen_bright_offset; - opts.invert = m_gen_invert; - opts.gray_scale = m_gen_gray_scale; - - auto image_path = m_gen_image_path; - if (image_path.empty()) - { - image_path = - file_dialogs::open_file_dialog("Select Image", "", - file_dialogs::image_file_filters()); - } - - auto pal = gen.generate_from_image(image_path, opts); - if (pal.name().empty()) - { - std::filesystem::path p(image_path); - pal.set_name(std::string("hellwal:") + p.filename().string()); - } - m_controller.import_palette(pal); - m_controller.select_palette(pal.name()); - apply_themes(); - } - else if (selected_kind && *selected_kind == generator_kind::matugen) - { - clrsync::core::matugen_generator gen; - if (!gen.supports_current_system()) - { - std::cerr << "Generation failed: matugen is not supported on " - << clrsync::core::generator::system_name( - clrsync::core::generator::current_system()) - << std::endl; - ImGui::CloseCurrentPopup(); - if (!can_generate) - ImGui::EndDisabled(); - return; - } - clrsync::core::matugen_generator::options opts; - opts.mode = m_matugen_mode; - opts.type = m_matugen_type; - opts.contrast = m_matugen_contrast; - opts.source_color_index = m_matugen_source_color_index; - - auto image_path = m_gen_image_path; - - clrsync::core::palette pal; - if (m_matugen_use_color) - { - // pass hex without '#' to generator - std::string hex = m_matugen_color_hex; - if (!hex.empty() && hex[0] == '#') - hex = hex.substr(1); - pal = gen.generate_from_color(hex, opts); - if (pal.name().empty()) - { - pal.set_name(std::string("matugen:color:") + hex); - } - } - else - { - if (image_path.empty()) - { - image_path = file_dialogs::open_file_dialog( - "Select Image", "", file_dialogs::image_file_filters()); - } - pal = gen.generate_from_image(image_path, opts); - if (pal.name().empty()) - { - std::filesystem::path p(image_path); - pal.set_name(std::string("matugen:") + p.filename().string()); - } - } - if (pal.name().empty()) - { - std::filesystem::path p(image_path); - pal.set_name(std::string("matugen:") + p.filename().string()); - } - m_controller.import_palette(pal); - m_controller.select_palette(pal.name()); - apply_themes(); - } - } - catch (const std::exception &e) - { - std::cerr << "Generation failed: " << e.what() << std::endl; - } - if (!can_generate) - ImGui::EndDisabled(); - ImGui::CloseCurrentPopup(); - } - else if (!can_generate) - { - ImGui::EndDisabled(); - } - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) - { - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } + m_generate_palette_dialog.render(m_generate_state, m_available_generators, m_generator_labels, + current, [this]() { execute_palette_generation(); }); if (m_show_delete_confirmation) { @@ -452,31 +276,17 @@ void color_scheme_editor::setup_widgets() }); m_generate_dialog.set_on_submit([this](const std::string &image_path) { - try + m_generate_state.image_path = image_path; + for (int i = 0; i < static_cast(m_available_generators.size()); ++i) { - clrsync::core::hellwal_generator gen; - if (!gen.supports_current_system()) - { - std::cerr << "Failed to generate palette: hellwal is not supported on " - << clrsync::core::generator::system_name( - clrsync::core::generator::current_system()) - << std::endl; - return; - } - auto pal = gen.generate_from_image(image_path); - if (pal.name().empty()) + if (m_available_generators[i] == + clrsync::gui::widgets::palette_generator_kind::hellwal) { - std::filesystem::path p(image_path); - pal.set_name(std::string("hellwal:") + p.filename().string()); + m_generate_state.generator_idx = i; + break; } - m_controller.import_palette(pal); - m_controller.select_palette(pal.name()); - apply_themes(); - } - catch (const std::exception &e) - { - std::cerr << "Failed to generate palette: " << e.what() << std::endl; } + execute_palette_generation(); }); m_generate_dialog.set_path_browse_callback( [this](const std::string ¤t_path) -> std::string { @@ -491,7 +301,8 @@ void color_scheme_editor::setup_widgets() [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(); }}); + [this]() { m_controller.apply_current_theme(); }, + true, false, false, true}); - m_action_buttons.set_spacing(16.0f); + m_action_buttons.set_spacing(clrsync::gui::layout::BUTTON_SPACING); } diff --git a/src/gui/views/color_scheme_editor.hpp b/src/gui/views/color_scheme_editor.hpp index b1979c2..92bfd93 100644 --- a/src/gui/views/color_scheme_editor.hpp +++ b/src/gui/views/color_scheme_editor.hpp @@ -5,10 +5,11 @@ #include "gui/views/color_table_renderer.hpp" #include "gui/views/preview_renderer.hpp" #include "gui/widgets/action_buttons.hpp" +#include "gui/widgets/generate_palette_dialog.hpp" #include "gui/widgets/input_dialog.hpp" #include "gui/widgets/palette_selector.hpp" -#include #include +#include #include class template_editor; @@ -17,12 +18,6 @@ class settings_window; class color_scheme_editor { public: - enum class generator_kind - { - hellwal, - matugen - }; - color_scheme_editor(); void render_controls_and_colors(); @@ -46,7 +41,9 @@ class color_scheme_editor void notify_palette_changed(); void setup_widgets(); void refresh_available_generators(); - [[nodiscard]] std::optional selected_generator_kind() const; + void execute_palette_generation(); + [[nodiscard]] std::optional + selected_generator_kind() const; palette_controller m_controller; color_table_renderer m_color_table; @@ -58,28 +55,9 @@ class color_scheme_editor clrsync::gui::widgets::palette_selector m_palette_selector; clrsync::gui::widgets::input_dialog m_new_palette_dialog; clrsync::gui::widgets::input_dialog m_generate_dialog; - int m_generator_idx{0}; - bool m_show_generate_modal{false}; - // hellwal - std::string m_gen_image_path; - bool m_gen_neon{false}; - bool m_gen_dark{true}; - bool m_gen_light{false}; - bool m_gen_color{false}; - float m_gen_dark_offset{0.0f}; - float m_gen_bright_offset{0.0f}; - bool m_gen_invert{false}; - float m_gen_gray_scale{0.0f}; - // matugen - std::string m_matugen_mode{"dark"}; - std::string m_matugen_type{"scheme-tonal-spot"}; - float m_matugen_contrast{0.0f}; - int m_matugen_source_color_index{0}; - // matugen color option - bool m_matugen_use_color{false}; - float m_matugen_color_vec[3]{1.0f, 0.0f, 0.0f}; - std::string m_matugen_color_hex{"FF0000"}; - std::vector m_available_generators; + clrsync::gui::widgets::generate_palette_dialog m_generate_palette_dialog; + clrsync::gui::widgets::generate_palette_state m_generate_state; + std::vector m_available_generators; std::vector m_generator_labels; clrsync::gui::widgets::action_buttons m_action_buttons; }; diff --git a/src/gui/views/color_table_renderer.cpp b/src/gui/views/color_table_renderer.cpp index 63b95b0..08f2405 100644 --- a/src/gui/views/color_table_renderer.cpp +++ b/src/gui/views/color_table_renderer.cpp @@ -1,4 +1,5 @@ #include "gui/views/color_table_renderer.hpp" +#include "gui/layout/ui_layout.hpp" #include "gui/widgets/colors.hpp" #include "imgui.h" #include @@ -81,21 +82,33 @@ void color_table_renderer::render_color_row(const std::string &name, 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 | ImGuiColorEditFlags_AlphaPreviewHalf)) + const ImVec4 color_vec(c[0], c[1], c[2], c[3]); + const ImVec2 swatch_size(clrsync::gui::layout::COLOR_SWATCH_SIZE, + clrsync::gui::layout::COLOR_SWATCH_SIZE); + const std::string popup_id = "##picker_" + name; + const std::string button_id = "##swatch_" + name; + + if (ImGui::ColorButton(button_id.c_str(), color_vec, + ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip, + swatch_size)) + ImGui::OpenPopup(popup_id.c_str()); + + if (ImGui::BeginPopup(popup_id.c_str())) { - 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(); + if (ImGui::ColorPicker4(("##picker4_" + name).c_str(), c, + ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf | + ImGuiColorEditFlags_DisplayRGB)) + { + uint32_t r = static_cast(c[0] * 255.0f); + uint32_t g = static_cast(c[1] * 255.0f); + uint32_t b = static_cast(c[2] * 255.0f); + uint32_t a = static_cast(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::EndPopup(); } ImGui::PopID(); @@ -156,19 +169,23 @@ void color_table_renderer::render(const clrsync::core::palette ¤t, ImGui::PushStyleColor(ImGuiCol_Text, clrsync::gui::widgets::palette_color(current, "accent")); - bool header_open = ImGui::TreeNodeEx(title, ImGuiTreeNodeFlags_DefaultOpen | - ImGuiTreeNodeFlags_SpanAvailWidth); + constexpr ImGuiTreeNodeFlags tree_flags = + ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAvailWidth | + ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_Framed; + bool header_open = ImGui::TreeNodeEx(title, tree_flags); ImGui::PopStyleColor(); if (header_open) { if (ImGui::BeginTable(id, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | - ImGuiTableFlags_SizingStretchProp)) + ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 160.0f); - ImGui::TableSetupColumn("HEX", ImGuiTableColumnFlags_WidthFixed, 95.0f); - ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.0f, 2.0f); + ImGui::TableSetupColumn("HEX", ImGuiTableColumnFlags_WidthFixed, + clrsync::gui::layout::HEX_COLUMN_WIDTH); + ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, + clrsync::gui::layout::COLOR_COLUMN_WIDTH); ImGui::TableHeadersRow(); for (auto *k : keys) diff --git a/src/gui/views/preview_renderer.cpp b/src/gui/views/preview_renderer.cpp index e697adb..6edfbdd 100644 --- a/src/gui/views/preview_renderer.cpp +++ b/src/gui/views/preview_renderer.cpp @@ -91,7 +91,12 @@ void preview_renderer::render_code_preview() ImGui::Text("Code Editor Preview:"); ImGui::SameLine(); ImGui::TextDisabled("(editor_* colors)"); - m_editor.Render("##CodeEditor", ImVec2(0, code_preview_height), true); + ImGui::BeginChild("CodePreviewScroll", ImVec2(0, code_preview_height), true, + ImGuiWindowFlags_HorizontalScrollbar); + m_editor.SetImGuiChildIgnored(true); + m_editor.Render("##CodeEditor", ImVec2(0, 0), false); + m_editor.SetImGuiChildIgnored(false); + ImGui::EndChild(); } void preview_renderer::render_terminal_preview(const clrsync::core::palette ¤t) diff --git a/src/gui/views/template_editor.cpp b/src/gui/views/template_editor.cpp index 0c4b190..b8ca0cb 100644 --- a/src/gui/views/template_editor.cpp +++ b/src/gui/views/template_editor.cpp @@ -3,6 +3,7 @@ #include "core/config/config.hpp" #include "core/palette/color_keys.hpp" #include "core/theme/theme_template.hpp" +#include "gui/layout/ui_layout.hpp" #include "gui/theme/app_theme.hpp" #include "gui/widgets/colors.hpp" #include "gui/widgets/dialogs.hpp" @@ -519,8 +520,11 @@ void template_editor::render() ImGui::Separator(); const float panel_width = ImGui::GetContentRegionAvail().x; - constexpr float left_panel_width = 200.0f; - const float right_panel_width = panel_width - left_panel_width - 10; + const float left_panel_width = + std::max(clrsync::gui::layout::TEMPLATE_LIST_MIN_WIDTH, + panel_width * clrsync::gui::layout::TEMPLATE_LIST_WIDTH_RATIO); + const float panel_gap = clrsync::gui::layout::BUTTON_SPACING; + const float right_panel_width = panel_width - left_panel_width - panel_gap; ImGui::BeginChild("TemplateList", ImVec2(left_panel_width, 0), true); render_template_list(); @@ -685,8 +689,14 @@ void template_editor::render_editor() } ImVec2 editor_pos = ImGui::GetCursorScreenPos(); + const float editor_height = ImGui::GetContentRegionAvail().y; - m_editor.Render("##TemplateEditor", ImVec2(0, 0), true); + ImGui::BeginChild("TemplateEditorScroll", ImVec2(0, editor_height), true, + ImGuiWindowFlags_HorizontalScrollbar); + m_editor.SetImGuiChildIgnored(true); + m_editor.Render("##TemplateEditor", ImVec2(0, 0), false); + m_editor.SetImGuiChildIgnored(false); + ImGui::EndChild(); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && m_editor.HasSelection()) { @@ -750,8 +760,14 @@ void template_editor::render_template_list() ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 6)); ImGui::Text("Templates"); - ImGui::SameLine(ImGui::GetContentRegionAvail().x - 20); - ImGui::TextDisabled("(%d)", (int)m_template_controller.templates().size()); + char count_label[32]; + const int template_count = static_cast(m_template_controller.templates().size()); + snprintf(count_label, sizeof(count_label), "(%d)", template_count); + const ImVec2 count_size = ImGui::CalcTextSize(count_label); + const float count_x = + ImGui::GetWindowContentRegionMax().x - count_size.x - ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::SameLine(count_x); + ImGui::TextDisabled("%s", count_label); ImGui::Separator(); if (!m_control_state.is_editing_existing) diff --git a/src/gui/widgets/action_buttons.cpp b/src/gui/widgets/action_buttons.cpp index b1b7e73..ff9e6da 100644 --- a/src/gui/widgets/action_buttons.cpp +++ b/src/gui/widgets/action_buttons.cpp @@ -34,7 +34,7 @@ void action_buttons::render(const core::palette &) for (const auto &button : m_buttons) { if (!first) - ImGui::SameLine(); + ImGui::SameLine(0, m_spacing); first = false; bool has_style = false; @@ -48,6 +48,11 @@ void action_buttons::render(const core::palette &) push_success_button_style(); has_style = true; } + else if (button.use_info_style) + { + push_info_button_style(); + has_style = true; + } bool disabled = !button.enabled; if (disabled) diff --git a/src/gui/widgets/action_buttons.hpp b/src/gui/widgets/action_buttons.hpp index e1cf68e..ac3be61 100644 --- a/src/gui/widgets/action_buttons.hpp +++ b/src/gui/widgets/action_buttons.hpp @@ -18,6 +18,7 @@ struct action_button bool use_error_style = false; bool use_success_style = false; + bool use_info_style = false; }; class action_buttons diff --git a/src/gui/widgets/form_field.hpp b/src/gui/widgets/form_field.hpp index 433c276..9144f9c 100644 --- a/src/gui/widgets/form_field.hpp +++ b/src/gui/widgets/form_field.hpp @@ -23,7 +23,7 @@ struct form_field_config { std::string label; std::string tooltip; - float label_width = 80.0f; + float label_width = 72.0f; float field_width = -1.0f; bool required = false; field_type type = field_type::text; diff --git a/src/gui/widgets/generate_palette_dialog.cpp b/src/gui/widgets/generate_palette_dialog.cpp new file mode 100644 index 0000000..d74a2c2 --- /dev/null +++ b/src/gui/widgets/generate_palette_dialog.cpp @@ -0,0 +1,300 @@ +#include "generate_palette_dialog.hpp" +#include "colors.hpp" +#include "gui/layout/ui_layout.hpp" +#include "section_header.hpp" +#include "gui/platform/file_browser.hpp" +#include "imgui.h" +#include +#include +#include + +namespace clrsync::gui::widgets +{ + +namespace +{ +constexpr float WINDOW_WIDTH = 520.0f; +constexpr float DIALOG_LABEL_WIDTH = 132.0f; +constexpr float MODAL_BUTTON_WIDTH = 120.0f; +constexpr const char* WINDOW_TITLE = "Generate Palette"; + +std::optional selected_kind( + const generate_palette_state& state, + const std::vector& available_generators) +{ + if (state.generator_idx < 0 || + state.generator_idx >= static_cast(available_generators.size())) + return std::nullopt; + return available_generators[state.generator_idx]; +} + +bool begin_field_table(const char* id) +{ + const float width = ImGui::GetContentRegionAvail().x; + if (!ImGui::BeginTable(id, 2, + ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_PadOuterX, + ImVec2(width, 0.0f))) + return false; + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, DIALOG_LABEL_WIDTH); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + return true; +} + +void table_label(const char* text) +{ + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(text); + ImGui::TableSetColumnIndex(1); +} + +void table_slider(const char* label, const char* id, float* value, float min_v, float max_v, + const char* format) +{ + table_label(label); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderFloat(id, value, min_v, max_v, format); +} +} // namespace + +generate_palette_dialog::generate_palette_dialog() +{ + m_form.set_path_browse_callback([](const std::string& current_path) -> std::string { + return file_dialogs::open_file_dialog("Select Image", current_path, + file_dialogs::image_file_filters()); + }); +} + +void generate_palette_dialog::open() +{ + m_is_open = true; +} + +void generate_palette_dialog::render(generate_palette_state& state, + const std::vector& available_generators, + const std::vector& generator_labels, + const core::palette& palette, + const std::function& on_generate) +{ + if (!m_is_open) + return; + + if (state.generator_idx < 0 || + state.generator_idx >= static_cast(available_generators.size())) + state.generator_idx = 0; + + ImGui::SetNextWindowSize(ImVec2(WINDOW_WIDTH, 0), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, + ImVec2(0.5f, 0.5f)); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16, 14)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(layout::BUTTON_SPACING, 10)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 6)); + + if (!ImGui::Begin(WINDOW_TITLE, &m_is_open, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse)) + { + ImGui::PopStyleVar(3); + return; + } + + ImGui::TextDisabled("Generate a palette from an image using a system tool."); + ImGui::Spacing(); + + render_generator_row(state, generator_labels); + + const auto kind = selected_kind(state, available_generators); + if (kind == palette_generator_kind::hellwal) + render_hellwal_options(state, palette); + else if (kind == palette_generator_kind::matugen) + render_matugen_options(state, palette); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + const bool submit_generate = render_footer(kind.has_value(), palette); + + ImGui::End(); + ImGui::PopStyleVar(3); + + if (submit_generate && on_generate) + on_generate(); +} + +void generate_palette_dialog::render_generator_row( + generate_palette_state& state, const std::vector& generator_labels) +{ + if (!begin_field_table("##gen_main")) + return; + + table_label("Generator"); + if (generator_labels.empty()) + { + ImGui::TextDisabled("No supported generators on this system"); + } + else + { + std::vector items; + items.reserve(generator_labels.size()); + for (const auto& label : generator_labels) + items.push_back(label.c_str()); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::Combo("##gen_select", &state.generator_idx, items.data(), + static_cast(items.size())); + } + + ImGui::EndTable(); + ImGui::Spacing(); +} + +void generate_palette_dialog::render_hellwal_options(generate_palette_state& state, + const core::palette& palette) +{ + section_header("hellwal options", palette); + + form_field_config image_cfg; + image_cfg.label = "Image"; + image_cfg.label_width = DIALOG_LABEL_WIDTH; + image_cfg.field_width = -1.0f; + image_cfg.type = field_type::path; + image_cfg.hint = "Select an image file..."; + image_cfg.tooltip = "Wallpaper or photo used as the color source"; + m_form.render_path(image_cfg, state.image_path); + + ImGui::Spacing(); + + if (begin_field_table("##gen_hellwal_flags")) + { + table_label("Neon mode"); + ImGui::Checkbox("Enable neon highlights", &state.neon); + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::TextUnformatted("Modes (can combine)"); + ImGui::Checkbox("Dark", &state.dark); + ImGui::SameLine(0, layout::BUTTON_SPACING * 3); + ImGui::Checkbox("Light", &state.light); + ImGui::SameLine(0, layout::BUTTON_SPACING * 3); + ImGui::Checkbox("Color", &state.color); + + ImGui::Spacing(); + + if (begin_field_table("##gen_hellwal_sliders")) + { + table_slider("Dark offset", "##dark_offset", &state.dark_offset, 0.0f, 1.0f, "%.3f"); + table_slider("Bright offset", "##bright_offset", &state.bright_offset, 0.0f, 1.0f, "%.3f"); + table_label("Invert colors"); + ImGui::Checkbox("Invert extracted colors", &state.invert); + table_slider("Gray scale", "##gray_scale", &state.gray_scale, 0.0f, 1.0f, "%.3f"); + ImGui::EndTable(); + } +} + +void generate_palette_dialog::render_matugen_options(generate_palette_state& state, + const core::palette& palette) +{ + section_header("matugen options", palette); + + if (!state.matugen_use_color) + { + form_field_config image_cfg; + image_cfg.label = "Image"; + image_cfg.label_width = DIALOG_LABEL_WIDTH; + image_cfg.field_width = -1.0f; + image_cfg.type = field_type::path; + image_cfg.hint = "Select an image file..."; + m_form.render_path(image_cfg, state.image_path); + ImGui::Spacing(); + } + + if (begin_field_table("##gen_matugen")) + { + table_label("Mode"); + const char* modes[] = {"dark", "light"}; + int mode_idx = (state.matugen_mode == "light") ? 1 : 0; + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::Combo("##matugen_mode", &mode_idx, modes, IM_ARRAYSIZE(modes))) + state.matugen_mode = (mode_idx == 1) ? "light" : "dark"; + + table_label("Type"); + const char* types[] = {"scheme-content", "scheme-expressive", "scheme-fidelity", + "scheme-fruit-salad", "scheme-monochrome", "scheme-neutral", + "scheme-rainbow", "scheme-tonal-spot"}; + int type_idx = 7; + for (int i = 0; i < IM_ARRAYSIZE(types); ++i) + { + if (state.matugen_type == types[i]) + { + type_idx = i; + break; + } + } + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::Combo("##matugen_type", &type_idx, types, IM_ARRAYSIZE(types))) + state.matugen_type = types[type_idx]; + + table_slider("Contrast", "##matugen_contrast", &state.matugen_contrast, -1.0f, 1.0f, + "%.2f"); + + table_label("Source color"); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderInt("##matugen_source", &state.matugen_source_color_index, 0, 3); + + table_label("Input"); + ImGui::Checkbox("Use solid color instead of image", &state.matugen_use_color); + + if (state.matugen_use_color) + { + table_label("Color"); + if (ImGui::ColorEdit3("##matugen_color", state.matugen_color_vec)) + { + const int r = static_cast(state.matugen_color_vec[0] * 255.0f + 0.5f); + const int g = static_cast(state.matugen_color_vec[1] * 255.0f + 0.5f); + const int b = static_cast(state.matugen_color_vec[2] * 255.0f + 0.5f); + char hexbuf[8]; + std::snprintf(hexbuf, sizeof(hexbuf), "%02X%02X%02X", r, g, b); + state.matugen_color_hex = hexbuf; + } + } + + ImGui::EndTable(); + } +} + +bool generate_palette_dialog::render_footer(bool can_generate, const core::palette& palette) +{ + (void)palette; + bool submit_generate = false; + + if (!can_generate) + ImGui::BeginDisabled(); + + const float spacing = ImGui::GetStyle().ItemSpacing.x; + const float total_width = 2.0f * MODAL_BUTTON_WIDTH + spacing; + const float start_x = (ImGui::GetContentRegionAvail().x - total_width) * 0.5f; + if (start_x > 0.0f) + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + start_x); + + push_success_button_style(); + if (ImGui::Button("Generate", ImVec2(MODAL_BUTTON_WIDTH, 0))) + { + submit_generate = true; + m_is_open = false; + } + pop_button_style(); + + if (!can_generate) + ImGui::EndDisabled(); + + ImGui::SameLine(0, layout::BUTTON_SPACING); + + if (ImGui::Button("Cancel", ImVec2(MODAL_BUTTON_WIDTH, 0))) + m_is_open = false; + + return submit_generate; +} + +} // namespace clrsync::gui::widgets diff --git a/src/gui/widgets/generate_palette_dialog.hpp b/src/gui/widgets/generate_palette_dialog.hpp new file mode 100644 index 0000000..5ccdf03 --- /dev/null +++ b/src/gui/widgets/generate_palette_dialog.hpp @@ -0,0 +1,64 @@ +#ifndef CLRSYNC_GUI_WIDGETS_GENERATE_PALETTE_DIALOG_HPP +#define CLRSYNC_GUI_WIDGETS_GENERATE_PALETTE_DIALOG_HPP + +#include "core/palette/palette.hpp" +#include "form_field.hpp" +#include +#include +#include + +namespace clrsync::gui::widgets +{ + +enum class palette_generator_kind +{ + hellwal, + matugen +}; + +struct generate_palette_state +{ + int generator_idx = 0; + std::string image_path; + bool neon = false; + bool dark = true; + bool light = false; + bool color = false; + float dark_offset = 0.0f; + float bright_offset = 0.0f; + bool invert = false; + float gray_scale = 0.0f; + std::string matugen_mode{"dark"}; + std::string matugen_type{"scheme-tonal-spot"}; + float matugen_contrast = 0.0f; + int matugen_source_color_index = 0; + bool matugen_use_color = false; + float matugen_color_vec[3]{1.0f, 0.0f, 0.0f}; + std::string matugen_color_hex{"FF0000"}; +}; + +class generate_palette_dialog +{ + public: + generate_palette_dialog(); + + void open(); + void render(generate_palette_state& state, + const std::vector& available_generators, + const std::vector& generator_labels, const core::palette& palette, + const std::function& on_generate); + + private: + void render_generator_row(generate_palette_state& state, + const std::vector& generator_labels); + void render_hellwal_options(generate_palette_state& state, const core::palette& palette); + void render_matugen_options(generate_palette_state& state, const core::palette& palette); + bool render_footer(bool can_generate, const core::palette& palette); + + form_field m_form; + bool m_is_open = false; +}; + +} // namespace clrsync::gui::widgets + +#endif // CLRSYNC_GUI_WIDGETS_GENERATE_PALETTE_DIALOG_HPP diff --git a/src/gui/widgets/settings_buttons.cpp b/src/gui/widgets/settings_buttons.cpp index 8198575..d2d4ed6 100644 --- a/src/gui/widgets/settings_buttons.cpp +++ b/src/gui/widgets/settings_buttons.cpp @@ -1,4 +1,5 @@ #include "settings_buttons.hpp" +#include "colors.hpp" #include "imgui.h" namespace clrsync::gui::widgets @@ -25,16 +26,18 @@ void settings_buttons::render(const settings_buttons_callbacks& callbacks, bool ImGui::SameLine(); if (!apply_enabled) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f); + ImGui::BeginDisabled(); + push_info_button_style(); if (ImGui::Button("Apply", ImVec2(m_button_width, 0)) && apply_enabled) { if (callbacks.on_apply) callbacks.on_apply(); } + pop_button_style(); if (!apply_enabled) - ImGui::PopStyleVar(); + ImGui::EndDisabled(); ImGui::SameLine(); diff --git a/src/gui/widgets/template_controls.cpp b/src/gui/widgets/template_controls.cpp index 9af6139..b9b08ea 100644 --- a/src/gui/widgets/template_controls.cpp +++ b/src/gui/widgets/template_controls.cpp @@ -1,11 +1,16 @@ #include "template_controls.hpp" #include "colors.hpp" +#include "gui/layout/ui_layout.hpp" #include "styled_checkbox.hpp" #include "imgui.h" namespace clrsync::gui::widgets { +using layout::BROWSE_BUTTON_WIDTH; +using layout::BUTTON_SPACING; +using layout::FORM_LABEL_WIDTH; + template_controls::template_controls() = default; void template_controls::render(template_control_state& state, @@ -13,7 +18,7 @@ void template_controls::render(template_control_state& state, const core::palette& palette, validation_message& validation) { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 8)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(BUTTON_SPACING, 8)); render_action_buttons(state, callbacks, palette); @@ -38,7 +43,7 @@ void template_controls::render_action_buttons(template_control_state& state, if (ImGui::IsItemHovered()) ImGui::SetTooltip("Create a new template"); - ImGui::SameLine(); + ImGui::SameLine(0, BUTTON_SPACING); if (ImGui::Button(" Save ")) { if (callbacks.on_save) @@ -49,7 +54,7 @@ void template_controls::render_action_buttons(template_control_state& state, if (state.is_editing_existing) { - ImGui::SameLine(); + ImGui::SameLine(0, BUTTON_SPACING); auto error = palette_color(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); @@ -68,9 +73,7 @@ void template_controls::render_action_buttons(template_control_state& state, ImGui::PopStyleColor(4); } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10); - + ImGui::SameLine(0, BUTTON_SPACING * 2); bool old_enabled = state.enabled; styled_checkbox checkbox; checkbox.render("Enabled", &state.enabled, palette, @@ -88,23 +91,33 @@ void template_controls::render_action_buttons(template_control_state& state, void template_controls::render_fields(template_control_state& state, const template_control_callbacks& callbacks) { - ImGui::AlignTextToFramePadding(); - ImGui::Text("Name:"); - ImGui::SameLine(80); - ImGui::SetNextItemWidth(180.0f); + if (!ImGui::BeginTable("##template_fields", 3, + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_PadOuterX)) + return; + + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, FORM_LABEL_WIDTH); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed, BROWSE_BUTTON_WIDTH); + + auto label_cell = [](const char* text) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(text); + ImGui::TableSetColumnIndex(1); + }; + + label_cell("Name:"); + ImGui::SetNextItemWidth(-FLT_MIN); char name_buf[256] = {0}; snprintf(name_buf, sizeof(name_buf), "%s", state.name.c_str()); if (ImGui::InputText("##template_name", name_buf, sizeof(name_buf))) - { state.name = name_buf; - } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Unique name for this template"); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Input:"); - ImGui::SameLine(80); - ImGui::SetNextItemWidth(-120.0f); + label_cell("Input:"); + ImGui::SetNextItemWidth(-FLT_MIN); char input_path_buf[512] = {0}; snprintf(input_path_buf, sizeof(input_path_buf), "%s", state.input_path.c_str()); if (ImGui::InputTextWithHint("##input_path", "Path to template file...", input_path_buf, @@ -114,7 +127,9 @@ void template_controls::render_fields(template_control_state& state, if (state.is_editing_existing && callbacks.on_input_path_changed) callbacks.on_input_path_changed(state.input_path); } - ImGui::SameLine(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Path where the template source file is stored"); + ImGui::TableSetColumnIndex(2); if (ImGui::Button("Browse##input")) { if (callbacks.on_browse_input) @@ -129,12 +144,10 @@ void template_controls::render_fields(template_control_state& state, } } if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Path where the template source file is stored"); + ImGui::SetTooltip("Browse for template source file"); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Output:"); - ImGui::SameLine(80); - ImGui::SetNextItemWidth(-120.0f); + label_cell("Output:"); + ImGui::SetNextItemWidth(-FLT_MIN); char path_buf[512] = {0}; snprintf(path_buf, sizeof(path_buf), "%s", state.output_path.c_str()); if (ImGui::InputTextWithHint("##output_path", "Path for generated config...", path_buf, @@ -144,7 +157,9 @@ void template_controls::render_fields(template_control_state& state, if (state.is_editing_existing && callbacks.on_output_path_changed) callbacks.on_output_path_changed(state.output_path); } - ImGui::SameLine(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Path where the processed config will be written"); + ImGui::TableSetColumnIndex(2); if (ImGui::Button("Browse##output")) { if (callbacks.on_browse_output) @@ -159,11 +174,9 @@ void template_controls::render_fields(template_control_state& state, } } if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Path where the processed config will be written"); + ImGui::SetTooltip("Browse for output path"); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Reload:"); - ImGui::SameLine(80); + label_cell("Reload:"); ImGui::SetNextItemWidth(-FLT_MIN); char reload_buf[512] = {0}; snprintf(reload_buf, sizeof(reload_buf), "%s", state.reload_command.c_str()); @@ -176,6 +189,8 @@ void template_controls::render_fields(template_control_state& state, } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Shell command to run after applying theme (e.g., 'pkill -USR1 kitty')"); + + ImGui::EndTable(); } } // namespace clrsync::gui::widgets From 8144ce0c003b1f39b5f4702fe5fe363526a6b249 Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Fri, 22 May 2026 01:30:07 +0300 Subject: [PATCH 2/4] added pywal16 generator --- src/cli/main.cpp | 59 +++++++ src/core/CMakeLists.txt | 1 + src/core/common/process.cpp | 94 +++++++++++- src/core/common/process.hpp | 6 +- src/core/common/version.hpp | 2 +- src/core/palette/generator_mappings.hpp | 36 +++++ src/core/palette/hellwal_generator.cpp | 112 +------------- src/core/palette/hellwal_mappings.hpp | 117 ++++++++++++++ src/core/palette/matugen_generator.cpp | 121 +-------------- src/core/palette/matugen_mappings.hpp | 114 ++++++++++++++ src/core/palette/pywal16_generator.cpp | 162 ++++++++++++++++++++ src/core/palette/pywal16_generator.hpp | 32 ++++ src/core/palette/pywal16_mappings.hpp | 117 ++++++++++++++ src/gui/views/color_scheme_editor.cpp | 90 +++++++++-- src/gui/views/color_scheme_editor.hpp | 2 +- src/gui/widgets/generate_palette_dialog.cpp | 144 ++++++++++++++++- src/gui/widgets/generate_palette_dialog.hpp | 20 ++- 17 files changed, 977 insertions(+), 252 deletions(-) create mode 100644 src/core/palette/generator_mappings.hpp create mode 100644 src/core/palette/hellwal_mappings.hpp create mode 100644 src/core/palette/matugen_mappings.hpp create mode 100644 src/core/palette/pywal16_generator.cpp create mode 100644 src/core/palette/pywal16_generator.hpp create mode 100644 src/core/palette/pywal16_mappings.hpp diff --git a/src/cli/main.cpp b/src/cli/main.cpp index a95b6f2..03fba29 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -11,6 +11,7 @@ #include "core/io/toml_file.hpp" #include "core/palette/hellwal_generator.hpp" #include "core/palette/matugen_generator.hpp" +#include "core/palette/pywal16_generator.hpp" #include "core/palette/palette_file.hpp" #include "core/palette/palette_manager.hpp" #include "core/theme/theme_renderer.hpp" @@ -139,6 +140,21 @@ void setup_argument_parser(argparse::ArgumentParser &program) .default_value(std::string("0.0")) .help("hellwal: gray scale factor (float)") .metavar("FLOAT"); + + program.add_argument("--pywal16-background") + .help("pywal16: custom background color (hex)") + .metavar("COLOR"); + program.add_argument("--pywal16-foreground") + .help("pywal16: custom foreground color (hex)") + .metavar("COLOR"); + program.add_argument("--pywal16-backend") + .help("pywal16: color extraction backend") + .metavar("BACKEND"); + program.add_argument("--pywal16-saturate") + .default_value(std::string("-1.0")) + .help("pywal16: color saturation from 0.0 to 1.0") + .metavar("FLOAT"); + program.add_argument("--pywal16-light").help("pywal16: generate a light colorscheme").flag(); } int main(int argc, char *argv[]) @@ -325,6 +341,49 @@ int main(int argc, char *argv[]) pal = gen.generate_from_image(image_path, opts); } } + else if (generator_name == "pywal16") + { + clrsync::core::pywal16_generator gen; + if (!gen.supports_current_system()) + { + std::cerr << "Error: pywal16 is not supported on " + << clrsync::core::generator::system_name( + clrsync::core::generator::current_system()) + << ". Supported systems: " << gen.supported_systems_description() + << std::endl; + return 1; + } + + if (program.is_used("--generate-color")) + { + std::cerr << "Error: --generate-color is only supported with --generator matugen" + << std::endl; + return 1; + } + + clrsync::core::pywal16_generator::options opts{}; + if (program.is_used("--pywal16-background")) + opts.background = program.get("--pywal16-background"); + if (program.is_used("--pywal16-foreground")) + opts.foreground = program.get("--pywal16-foreground"); + if (program.is_used("--pywal16-backend")) + opts.backend = program.get("--pywal16-backend"); + if (program.is_used("--pywal16-light")) + opts.light = true; + + try + { + std::string s = program.get("--pywal16-saturate"); + const float saturate = std::stof(s); + if (saturate >= 0.0f && saturate <= 1.0f) + opts.saturate = saturate; + } + catch (...) + { + } + + pal = gen.generate_from_image(image_path, opts); + } else { std::cerr << "Unknown generator: " << generator_name << std::endl; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c545aae..de43af6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2,6 +2,7 @@ set(CORE_SOURCES palette/color.cpp palette/hellwal_generator.cpp palette/matugen_generator.cpp + palette/pywal16_generator.cpp io/toml_file.cpp config/config.cpp common/process.cpp diff --git a/src/core/common/process.cpp b/src/core/common/process.cpp index c763bc7..9e5ce31 100644 --- a/src/core/common/process.cpp +++ b/src/core/common/process.cpp @@ -127,7 +127,7 @@ std::wstring build_windows_command_line(const std::vector &args) return command_line; } -std::string run_windows_process(const std::vector &args) +std::string run_windows_process(const std::vector &args, int *exit_code) { SECURITY_ATTRIBUTES sa{}; sa.nLength = sizeof(sa); @@ -159,6 +159,8 @@ std::string run_windows_process(const std::vector &args) if (!CreateProcessW(nullptr, mutable_command_line.data(), nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &startup, &process_info)) { + if (exit_code) + *exit_code = -1; return {}; } @@ -177,20 +179,34 @@ std::string run_windows_process(const std::vector &args) } WaitForSingleObject(process_handle.get(), INFINITE); + if (exit_code) + { + DWORD code = STILL_ACTIVE; + if (GetExitCodeProcess(process_handle.get(), &code)) + *exit_code = static_cast(code); + else + *exit_code = -1; + } return output; } #else -std::string run_posix_process(const std::vector &args) +std::string run_posix_process(const std::vector &args, int *exit_code) { int pipe_fds[2]; if (pipe(pipe_fds) != 0) + { + if (exit_code) + *exit_code = -1; return {}; + } pid_t pid = fork(); if (pid < 0) { close(pipe_fds[0]); close(pipe_fds[1]); + if (exit_code) + *exit_code = -1; return {}; } @@ -225,22 +241,90 @@ std::string run_posix_process(const std::vector &args) close(pipe_fds[0]); int status = 0; (void)waitpid(pid, &status, 0); + if (exit_code) + { + if (WIFEXITED(status)) + *exit_code = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + *exit_code = 128 + WTERMSIG(status); + else + *exit_code = -1; + } return output; } #endif } // namespace +namespace +{ +bool is_ansi_bracket_param_char(unsigned char c) +{ + return (c >= '0' && c <= '9') || c == ';'; +} + +void skip_ansi_brackets(const std::string &text, size_t &index) +{ + while (index < text.size() && + is_ansi_bracket_param_char(static_cast(text[index]))) + ++index; + if (index < text.size() && text[index] >= 0x40 && text[index] <= 0x7E) + ++index; +} +} // namespace + namespace clrsync::core { -std::string run_process_capture_output(const std::vector &args) +std::string strip_ansi_escapes(const std::string &text) +{ + std::string result; + result.reserve(text.size()); + for (size_t i = 0; i < text.size();) + { + const unsigned char c = static_cast(text[i]); + if (c == 0x1B || c == 0x9B) + { + if (c == 0x1B) + ++i; + if (i < text.size() && text[i] == '[') + { + ++i; + skip_ansi_brackets(text, i); + } + continue; + } + if (i + 1 < text.size() && text[i] == '?' && text[i + 1] == '[') + { + i += 2; + skip_ansi_brackets(text, i); + continue; + } + result.push_back(text[i]); + ++i; + } + return result; +} + +std::string process_failure_message(const std::string &output, const char *fallback_message) +{ + const std::string cleaned = strip_ansi_escapes(output); + if (!cleaned.empty()) + return cleaned; + return fallback_message ? fallback_message : "command failed"; +} + +std::string run_process_capture_output(const std::vector &args, int *exit_code) { if (args.empty()) + { + if (exit_code) + *exit_code = -1; return {}; + } #ifdef _WIN32 - return run_windows_process(args); + return run_windows_process(args, exit_code); #else - return run_posix_process(args); + return run_posix_process(args, exit_code); #endif } } // namespace clrsync::core diff --git a/src/core/common/process.hpp b/src/core/common/process.hpp index 4230b3e..611c92f 100644 --- a/src/core/common/process.hpp +++ b/src/core/common/process.hpp @@ -6,7 +6,11 @@ namespace clrsync::core { -std::string run_process_capture_output(const std::vector &args); +std::string run_process_capture_output(const std::vector &args, + int *exit_code = nullptr); +std::string strip_ansi_escapes(const std::string &text); +std::string process_failure_message(const std::string &output, + const char *fallback_message = "command failed"); } // namespace clrsync::core #endif // CLRSYNC_CORE_COMMON_PROCESS_HPP diff --git a/src/core/common/version.hpp b/src/core/common/version.hpp index f385795..57afdbb 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 = "1.2.2+git.g6e994ee"; +const std::string GIT_SEMVER = "1.2.2+git.g591bd4c"; const std::string version_string(); } // namespace clrsync::core diff --git a/src/core/palette/generator_mappings.hpp b/src/core/palette/generator_mappings.hpp new file mode 100644 index 0000000..191ff95 --- /dev/null +++ b/src/core/palette/generator_mappings.hpp @@ -0,0 +1,36 @@ +#ifndef CLRSYNC_CORE_PALETTE_GENERATOR_MAPPINGS_HPP +#define CLRSYNC_CORE_PALETTE_GENERATOR_MAPPINGS_HPP + +#include "core/palette/palette.hpp" + +#include +#include + +namespace clrsync::core +{ + +struct index_color_mapping +{ + const char *palette_key; + int source_index; +}; + +struct key_color_mapping +{ + const char *palette_key; + const char *source_key; +}; + +inline void apply_index_mappings( + palette &pal, + const index_color_mapping *mappings, + std::size_t count, + const std::function &get_by_index) +{ + for (std::size_t i = 0; i < count; ++i) + pal.set_color(mappings[i].palette_key, get_by_index(mappings[i].source_index)); +} + +} // namespace clrsync::core + +#endif diff --git a/src/core/palette/hellwal_generator.cpp b/src/core/palette/hellwal_generator.cpp index 682a313..1e3e91a 100644 --- a/src/core/palette/hellwal_generator.cpp +++ b/src/core/palette/hellwal_generator.cpp @@ -2,6 +2,7 @@ #include "core/common/process.hpp" #include "core/palette/color.hpp" +#include "core/palette/hellwal_mappings.hpp" #include "core/palette/json_utils.hpp" #include @@ -77,7 +78,7 @@ palette hellwal_generator::generate_from_image(const std::string &image_path, co pal.set_name("hellwal:" + p.filename().string()); pal.set_file_path(image_path); - std::vector args = {"hellwal", "-i", image_path, "--json"}; + std::vector args = {"hellwal", "-i", image_path, "--json", "--skip-term-colors"}; if (opts.neon) args.push_back("--neon-mode"); if (opts.dark) @@ -116,113 +117,8 @@ palette hellwal_generator::generate_from_image(const std::string &image_path, co std::string key = "color" + std::to_string(idx); return pal.get_color(key); }; - - pal.set_color("base00", get_color_by_index(0)); - pal.set_color("base01", get_color_by_index(8)); - pal.set_color("base02", get_color_by_index(8)); - pal.set_color("base03", get_color_by_index(8)); - pal.set_color("base04", get_color_by_index(7)); - pal.set_color("base05", get_color_by_index(7)); - pal.set_color("base06", get_color_by_index(15)); - pal.set_color("base07", get_color_by_index(15)); - pal.set_color("base08", get_color_by_index(1)); - pal.set_color("base09", get_color_by_index(9)); - pal.set_color("base0A", get_color_by_index(3)); - pal.set_color("base0B", get_color_by_index(2)); - pal.set_color("base0C", get_color_by_index(6)); - pal.set_color("base0D", get_color_by_index(4)); - pal.set_color("base0E", get_color_by_index(5)); - pal.set_color("base0F", get_color_by_index(11)); - - pal.set_color("accent", get_color_by_index(4)); - pal.set_color("accent_secondary", get_color_by_index(6)); - - pal.set_color("border", get_color_by_index(8)); - pal.set_color("border_focused", get_color_by_index(4)); - - pal.set_color("error", get_color_by_index(1)); - pal.set_color("warning", get_color_by_index(3)); - pal.set_color("success", get_color_by_index(2)); - pal.set_color("info", get_color_by_index(4)); - - pal.set_color("on_error", get_color_by_index(0)); - pal.set_color("on_warning", get_color_by_index(0)); - pal.set_color("on_success", get_color_by_index(0)); - pal.set_color("on_info", get_color_by_index(0)); - - pal.set_color("surface", get_color_by_index(0)); - pal.set_color("surface_variant", get_color_by_index(8)); - pal.set_color("on_surface", get_color_by_index(7)); - pal.set_color("on_surface_variant", get_color_by_index(7)); - pal.set_color("on_background", get_color_by_index(7)); - - // Editor - Basic - pal.set_color("editor_background", get_color_by_index(0)); - pal.set_color("editor_foreground", get_color_by_index(7)); - pal.set_color("editor_line_highlight", get_color_by_index(8)); - pal.set_color("editor_selection", get_color_by_index(8)); - pal.set_color("editor_selection_inactive", get_color_by_index(8)); - pal.set_color("editor_cursor", get_color_by_index(7)); - pal.set_color("editor_whitespace", get_color_by_index(8)); - - // Editor - Gutter - pal.set_color("editor_gutter_background", get_color_by_index(0)); - pal.set_color("editor_gutter_foreground", get_color_by_index(8)); - pal.set_color("editor_line_number", get_color_by_index(8)); - pal.set_color("editor_line_number_active", get_color_by_index(7)); - - // Editor - Syntax - pal.set_color("editor_comment", get_color_by_index(8)); - pal.set_color("editor_string", get_color_by_index(2)); - pal.set_color("editor_number", get_color_by_index(3)); - pal.set_color("editor_boolean", get_color_by_index(3)); - pal.set_color("editor_keyword", get_color_by_index(5)); - pal.set_color("editor_operator", get_color_by_index(7)); - pal.set_color("editor_function", get_color_by_index(11)); - pal.set_color("editor_variable", get_color_by_index(7)); - pal.set_color("editor_parameter", get_color_by_index(4)); - pal.set_color("editor_property", get_color_by_index(2)); - pal.set_color("editor_constant", get_color_by_index(3)); - pal.set_color("editor_type", get_color_by_index(6)); - pal.set_color("editor_class", get_color_by_index(6)); - pal.set_color("editor_interface", get_color_by_index(6)); - pal.set_color("editor_enum", get_color_by_index(6)); - pal.set_color("editor_namespace", get_color_by_index(4)); - pal.set_color("editor_attribute", get_color_by_index(11)); - pal.set_color("editor_decorator", get_color_by_index(11)); - pal.set_color("editor_tag", get_color_by_index(1)); - pal.set_color("editor_punctuation", get_color_by_index(7)); - pal.set_color("editor_link", get_color_by_index(4)); - pal.set_color("editor_regex", get_color_by_index(5)); - pal.set_color("editor_escape_character", get_color_by_index(3)); - - // Editor - Diagnostics - pal.set_color("editor_invalid", get_color_by_index(1)); - pal.set_color("editor_error", get_color_by_index(1)); - pal.set_color("editor_error_background", get_color_by_index(0)); - pal.set_color("editor_warning", get_color_by_index(3)); - pal.set_color("editor_warning_background", get_color_by_index(0)); - pal.set_color("editor_info", get_color_by_index(4)); - pal.set_color("editor_info_background", get_color_by_index(0)); - pal.set_color("editor_hint", get_color_by_index(2)); - pal.set_color("editor_hint_background", get_color_by_index(0)); - - // Editor - UI Elements - pal.set_color("editor_active_line_border", get_color_by_index(8)); - pal.set_color("editor_indent_guide", get_color_by_index(8)); - pal.set_color("editor_indent_guide_active", get_color_by_index(7)); - pal.set_color("editor_bracket_match", get_color_by_index(6)); - pal.set_color("editor_search_match", get_color_by_index(3)); - pal.set_color("editor_search_match_active", get_color_by_index(3)); - pal.set_color("editor_find_range_highlight", get_color_by_index(3)); - - // Editor - Diff - pal.set_color("editor_deleted", get_color_by_index(1)); - pal.set_color("editor_inserted", get_color_by_index(2)); - pal.set_color("editor_modified", get_color_by_index(3)); - pal.set_color("editor_ignored", get_color_by_index(8)); - pal.set_color("editor_folded_background", get_color_by_index(8)); - + apply_index_mappings(pal, HELLWAL_COLOR_MAPPINGS, HELLWAL_COLOR_MAPPINGS_COUNT, + get_color_by_index); return pal; } diff --git a/src/core/palette/hellwal_mappings.hpp b/src/core/palette/hellwal_mappings.hpp new file mode 100644 index 0000000..fd674a5 --- /dev/null +++ b/src/core/palette/hellwal_mappings.hpp @@ -0,0 +1,117 @@ +#ifndef CLRSYNC_CORE_PALETTE_HELLWAL_MAPPINGS_HPP +#define CLRSYNC_CORE_PALETTE_HELLWAL_MAPPINGS_HPP + +#include "core/palette/generator_mappings.hpp" + +namespace clrsync::core +{ + +inline constexpr index_color_mapping HELLWAL_COLOR_MAPPINGS[] = { + // General UI + {"background", 0}, + {"foreground", 7}, + {"cursor", 15}, + {"surface", 0}, + {"surface_variant", 8}, + {"on_background", 7}, + {"on_surface", 7}, + {"on_surface_variant", 8}, + {"border", 8}, + {"border_focused", 4}, + {"accent", 4}, + {"accent_secondary", 6}, + // Semantic + {"success", 2}, + {"warning", 3}, + {"error", 1}, + {"info", 4}, + {"on_success", 15}, + {"on_warning", 0}, + {"on_error", 15}, + {"on_info", 15}, + // Base16 + {"base00", 0}, + {"base01", 8}, + {"base02", 8}, + {"base03", 8}, + {"base04", 8}, + {"base05", 7}, + {"base06", 15}, + {"base07", 15}, + {"base08", 1}, + {"base09", 9}, + {"base0A", 3}, + {"base0B", 2}, + {"base0C", 6}, + {"base0D", 4}, + {"base0E", 5}, + {"base0F", 11}, + // Editor - Basic + {"editor_background", 0}, + {"editor_foreground", 7}, + {"editor_line_highlight", 8}, + {"editor_selection", 4}, + {"editor_selection_inactive", 8}, + {"editor_cursor", 15}, + {"editor_whitespace", 8}, + // Editor - Gutter + {"editor_gutter_background", 0}, + {"editor_gutter_foreground", 8}, + {"editor_line_number", 8}, + {"editor_line_number_active", 7}, + // Editor - Syntax + {"editor_comment", 8}, + {"editor_string", 2}, + {"editor_number", 3}, + {"editor_boolean", 3}, + {"editor_keyword", 5}, + {"editor_operator", 7}, + {"editor_function", 12}, + {"editor_variable", 7}, + {"editor_parameter", 4}, + {"editor_property", 10}, + {"editor_constant", 9}, + {"editor_type", 14}, + {"editor_class", 14}, + {"editor_interface", 14}, + {"editor_enum", 14}, + {"editor_namespace", 4}, + {"editor_attribute", 13}, + {"editor_decorator", 13}, + {"editor_tag", 1}, + {"editor_punctuation", 7}, + {"editor_link", 12}, + {"editor_regex", 5}, + {"editor_escape_character", 9}, + // Editor - Diagnostics + {"editor_invalid", 1}, + {"editor_error", 1}, + {"editor_error_background", 0}, + {"editor_warning", 3}, + {"editor_warning_background", 0}, + {"editor_info", 4}, + {"editor_info_background", 0}, + {"editor_hint", 2}, + {"editor_hint_background", 0}, + // Editor - UI Elements + {"editor_active_line_border", 8}, + {"editor_indent_guide", 8}, + {"editor_indent_guide_active", 7}, + {"editor_bracket_match", 6}, + {"editor_search_match", 3}, + {"editor_search_match_active", 11}, + {"editor_find_range_highlight", 3}, + // Editor - Diff + {"editor_deleted", 1}, + {"editor_inserted", 2}, + {"editor_modified", 3}, + {"editor_ignored", 8}, + {"editor_folded_background", 8}, +}; + +inline constexpr std::size_t HELLWAL_COLOR_MAPPINGS_COUNT = + sizeof(HELLWAL_COLOR_MAPPINGS) / sizeof(HELLWAL_COLOR_MAPPINGS[0]); + +} // namespace clrsync::core + +#endif diff --git a/src/core/palette/matugen_generator.cpp b/src/core/palette/matugen_generator.cpp index 567589f..1a29818 100644 --- a/src/core/palette/matugen_generator.cpp +++ b/src/core/palette/matugen_generator.cpp @@ -3,10 +3,11 @@ #include "core/common/process.hpp" #include "core/palette/color.hpp" #include "core/palette/json_utils.hpp" +#include "core/palette/matugen_mappings.hpp" +#include #include #include -#include namespace clrsync::core { @@ -22,124 +23,16 @@ static palette parse_matugen_output(const std::string &out, const matugen_genera if (!json_utils::parse_json_output(out, doc)) return {}; - std::unordered_map clrsync_to_matu = { - {"accent", "primary"}, - {"accent_secondary", "secondary"}, - {"background", "background"}, - {"foreground", "on_surface"}, - {"on_background", "on_background"}, - - {"surface", "surface_container"}, - {"on_surface", "on_surface"}, - {"surface_variant", "surface_variant"}, - {"on_surface_variant", "on_surface_variant"}, - - {"border", "outline_variant"}, - {"border_focused", "outline"}, - {"cursor", "on_surface"}, - - {"success", "primary"}, - {"on_success", "on_primary"}, - {"info", "tertiary"}, - {"on_info", "on_tertiary"}, - {"warning", "secondary"}, - {"on_warning", "on_secondary"}, - {"error", "error"}, - {"on_error", "on_error"}, - - // Editor - Basic - {"editor_background", "background"}, - {"editor_foreground", "on_surface"}, - {"editor_line_highlight", "surface_container"}, - {"editor_selection", "primary_container"}, - {"editor_selection_inactive", "surface_container_low"}, - {"editor_cursor", "on_surface"}, - {"editor_whitespace", "outline_variant"}, - - // Editor - Gutter - {"editor_gutter_background", "background"}, - {"editor_gutter_foreground", "outline"}, - {"editor_line_number", "outline"}, - {"editor_line_number_active", "on_surface"}, - - // Editor - Syntax - {"editor_comment", "outline"}, - {"editor_string", "tertiary"}, - {"editor_number", "secondary"}, - {"editor_boolean", "secondary"}, - {"editor_keyword", "primary"}, - {"editor_operator", "on_surface"}, - {"editor_function", "tertiary_container"}, - {"editor_variable", "on_surface"}, - {"editor_parameter", "tertiary"}, - {"editor_property", "tertiary"}, - {"editor_constant", "secondary"}, - {"editor_type", "primary"}, - {"editor_class", "primary"}, - {"editor_interface", "primary"}, - {"editor_enum", "primary"}, - {"editor_namespace", "primary_container"}, - {"editor_attribute", "tertiary_container"}, - {"editor_decorator", "tertiary_container"}, - {"editor_tag", "error"}, - {"editor_punctuation", "on_surface"}, - {"editor_link", "primary_container"}, - {"editor_regex", "secondary_container"}, - {"editor_escape_character", "secondary"}, - - // Editor - Diagnostics - {"editor_invalid", "error"}, - {"editor_error", "error"}, - {"editor_error_background", "error_container"}, - {"editor_warning", "secondary"}, - {"editor_warning_background", "secondary_container"}, - {"editor_info", "tertiary"}, - {"editor_info_background", "tertiary_container"}, - {"editor_hint", "primary"}, - {"editor_hint_background", "primary_container"}, - - // Editor - UI Elements - {"editor_active_line_border", "outline_variant"}, - {"editor_indent_guide", "outline_variant"}, - {"editor_indent_guide_active", "outline"}, - {"editor_bracket_match", "primary"}, - {"editor_search_match", "tertiary_container"}, - {"editor_search_match_active", "tertiary"}, - {"editor_find_range_highlight", "tertiary_container"}, - - // Editor - Diff - {"editor_deleted", "error"}, - {"editor_inserted", "primary"}, - {"editor_modified", "secondary"}, - {"editor_ignored", "outline_variant"}, - {"editor_folded_background", "surface_container"}, - - {"base00", "background"}, - {"base01", "base16.base01"}, - {"base02", "base16.base02"}, - {"base03", "base16.base03"}, - {"base04", "base16.base04"}, - {"base05", "base16.base05"}, - {"base06", "base16.base06"}, - {"base07", "base16.base07"}, - {"base08", "base16.base08"}, - {"base09", "base16.base09"}, - {"base0A", "base16.base0A"}, - {"base0B", "base16.base0B"}, - {"base0C", "base16.base0C"}, - {"base0D", "base16.base0D"}, - {"base0E", "base16.base0E"}, - {"base0F", "base16.base0F"}, - }; - palette pal; pal.set_name(pal_name); pal.set_file_path(file_path); - for (const auto &[clrsync_key, matu_key] : clrsync_to_matu) + for (std::size_t i = 0; i < MATUGEN_COLOR_MAPPINGS_COUNT; ++i) { + const key_color_mapping &mapping = MATUGEN_COLOR_MAPPINGS[i]; + const std::string matu_key = mapping.source_key; std::string hex; - if (matu_key.find("base16.") == 0) + if (matu_key.rfind("base16.", 0) == 0) { std::string base_key = matu_key.substr(7); if (doc.contains("base16") && doc["base16"].contains(base_key) && @@ -165,7 +58,7 @@ static palette parse_matugen_output(const std::string &out, const matugen_genera { color col; col.from_hex_string(hex); - pal.set_color(clrsync_key, col); + pal.set_color(mapping.palette_key, col); } } } diff --git a/src/core/palette/matugen_mappings.hpp b/src/core/palette/matugen_mappings.hpp new file mode 100644 index 0000000..ec8d84c --- /dev/null +++ b/src/core/palette/matugen_mappings.hpp @@ -0,0 +1,114 @@ +#ifndef CLRSYNC_CORE_PALETTE_MATUGEN_MAPPINGS_HPP +#define CLRSYNC_CORE_PALETTE_MATUGEN_MAPPINGS_HPP + +#include "core/palette/generator_mappings.hpp" + +namespace clrsync::core +{ + +inline constexpr key_color_mapping MATUGEN_COLOR_MAPPINGS[] = { + {"accent", "primary"}, + {"accent_secondary", "secondary"}, + {"background", "background"}, + {"foreground", "on_surface"}, + {"on_background", "on_background"}, + {"surface", "surface_container"}, + {"on_surface", "on_surface"}, + {"surface_variant", "surface_variant"}, + {"on_surface_variant", "on_surface_variant"}, + {"border", "outline_variant"}, + {"border_focused", "outline"}, + {"cursor", "on_surface"}, + {"success", "primary"}, + {"on_success", "on_primary"}, + {"info", "tertiary"}, + {"on_info", "on_tertiary"}, + {"warning", "secondary"}, + {"on_warning", "on_secondary"}, + {"error", "error"}, + {"on_error", "on_error"}, + // Editor - Basic + {"editor_background", "background"}, + {"editor_foreground", "on_surface"}, + {"editor_line_highlight", "surface_container"}, + {"editor_selection", "primary_container"}, + {"editor_selection_inactive", "surface_container_low"}, + {"editor_cursor", "on_surface"}, + {"editor_whitespace", "outline_variant"}, + // Editor - Gutter + {"editor_gutter_background", "background"}, + {"editor_gutter_foreground", "outline"}, + {"editor_line_number", "outline"}, + {"editor_line_number_active", "on_surface"}, + // Editor - Syntax + {"editor_comment", "outline"}, + {"editor_string", "tertiary"}, + {"editor_number", "secondary"}, + {"editor_boolean", "secondary"}, + {"editor_keyword", "primary"}, + {"editor_operator", "on_surface"}, + {"editor_function", "tertiary_container"}, + {"editor_variable", "on_surface"}, + {"editor_parameter", "tertiary"}, + {"editor_property", "tertiary"}, + {"editor_constant", "secondary"}, + {"editor_type", "primary"}, + {"editor_class", "primary"}, + {"editor_interface", "primary"}, + {"editor_enum", "primary"}, + {"editor_namespace", "primary_container"}, + {"editor_attribute", "tertiary_container"}, + {"editor_decorator", "tertiary_container"}, + {"editor_tag", "error"}, + {"editor_punctuation", "on_surface"}, + {"editor_link", "primary_container"}, + {"editor_regex", "secondary_container"}, + {"editor_escape_character", "secondary"}, + // Editor - Diagnostics + {"editor_invalid", "error"}, + {"editor_error", "error"}, + {"editor_error_background", "error_container"}, + {"editor_warning", "secondary"}, + {"editor_warning_background", "secondary_container"}, + {"editor_info", "tertiary"}, + {"editor_info_background", "tertiary_container"}, + {"editor_hint", "primary"}, + {"editor_hint_background", "primary_container"}, + // Editor - UI Elements + {"editor_active_line_border", "outline_variant"}, + {"editor_indent_guide", "outline_variant"}, + {"editor_indent_guide_active", "outline"}, + {"editor_bracket_match", "primary"}, + {"editor_search_match", "tertiary_container"}, + {"editor_search_match_active", "tertiary"}, + {"editor_find_range_highlight", "tertiary_container"}, + // Editor - Diff + {"editor_deleted", "error"}, + {"editor_inserted", "primary"}, + {"editor_modified", "secondary"}, + {"editor_ignored", "outline_variant"}, + {"editor_folded_background", "surface_container"}, + {"base00", "background"}, + {"base01", "base16.base01"}, + {"base02", "base16.base02"}, + {"base03", "base16.base03"}, + {"base04", "base16.base04"}, + {"base05", "base16.base05"}, + {"base06", "base16.base06"}, + {"base07", "base16.base07"}, + {"base08", "base16.base08"}, + {"base09", "base16.base09"}, + {"base0A", "base16.base0A"}, + {"base0B", "base16.base0B"}, + {"base0C", "base16.base0C"}, + {"base0D", "base16.base0D"}, + {"base0E", "base16.base0E"}, + {"base0F", "base16.base0F"}, +}; + +inline constexpr std::size_t MATUGEN_COLOR_MAPPINGS_COUNT = + sizeof(MATUGEN_COLOR_MAPPINGS) / sizeof(MATUGEN_COLOR_MAPPINGS[0]); + +} // namespace clrsync::core + +#endif diff --git a/src/core/palette/pywal16_generator.cpp b/src/core/palette/pywal16_generator.cpp new file mode 100644 index 0000000..24ee1dc --- /dev/null +++ b/src/core/palette/pywal16_generator.cpp @@ -0,0 +1,162 @@ +#include "pywal16_generator.hpp" + +#include "core/common/process.hpp" +#include "core/common/utils.hpp" +#include "core/palette/color.hpp" +#include "core/palette/json_utils.hpp" +#include "core/palette/pywal16_mappings.hpp" + +#include +#include +#include +#include + +namespace clrsync::core +{ +using json = json_utils::json; + +namespace +{ +constexpr const char *COLORS_JSON_PATH = "~/.cache/wal/colors.json"; + +static std::string read_text_file(const std::filesystem::path &path) +{ + std::ifstream in(path, std::ios::binary); + if (!in) + return {}; + return std::string((std::istreambuf_iterator(in)), std::istreambuf_iterator()); +} + +static bool set_hex_color(palette &pal, const std::string &key, const std::string &hex_value) +{ + const std::string hex = json_utils::normalize_hex_string(hex_value); + if (hex.empty()) + return false; + try + { + color col; + col.from_hex_string(hex); + pal.set_color(key, col); + return true; + } + catch (...) + { + return false; + } +} + +static bool wallpaper_matches_image(const json &doc, const std::filesystem::path &image_path) +{ + if (!doc.contains("wallpaper") || !doc["wallpaper"].is_string()) + return true; + const std::filesystem::path wallpaper_path = + normalize_path(doc["wallpaper"].get()); + const std::filesystem::path expected_path = normalize_path(image_path.string()); + if (wallpaper_path == expected_path) + return true; + std::error_code ec; + if (std::filesystem::exists(wallpaper_path, ec) && std::filesystem::exists(expected_path, ec)) + return std::filesystem::equivalent(wallpaper_path, expected_path, ec); + return false; +} + +static palette parse_pywal_colors_json(const std::string &content, + const std::filesystem::path &image_path, + const std::string &pal_name) +{ + if (content.empty()) + return {}; + + json doc; + if (!json_utils::parse_json_output(content, doc)) + return {}; + + if (!wallpaper_matches_image(doc, image_path)) + return {}; + + palette pal; + pal.set_name(pal_name); + pal.set_file_path(image_path.string()); + + if (doc.contains("special") && doc["special"].is_object()) + { + const json &special = doc["special"]; + if (special.contains("background") && special["background"].is_string()) + set_hex_color(pal, "background", special["background"].get()); + if (special.contains("foreground") && special["foreground"].is_string()) + set_hex_color(pal, "foreground", special["foreground"].get()); + if (special.contains("cursor") && special["cursor"].is_string()) + set_hex_color(pal, "cursor", special["cursor"].get()); + } + + if (doc.contains("colors") && doc["colors"].is_object()) + { + const json &colors = doc["colors"]; + for (int i = 0; i < 16; ++i) + { + const std::string key = "color" + std::to_string(i); + if (colors.contains(key) && colors[key].is_string()) + set_hex_color(pal, key, colors[key].get()); + } + } + + auto get_color_by_index = [&](int idx) -> const color & { + return pal.get_color("color" + std::to_string(idx)); + }; + apply_index_mappings(pal, PYWAL16_COLOR_MAPPINGS, PYWAL16_COLOR_MAPPINGS_COUNT, + get_color_by_index); + return pal; +} +} // namespace + +palette pywal16_generator::generate_from_image(const std::string &image_path) +{ + options default_opts{}; + return generate_from_image(image_path, default_opts); +} + +palette pywal16_generator::generate_from_image(const std::string &image_path, const options &opts) +{ + ensure_supported("pywal16"); + + std::filesystem::path p(image_path); + const std::string pal_name = std::string("pywal16:") + p.filename().string(); + + std::vector args = {"wal", "-i", image_path, "-n", "-s", "-e", "-t"}; + if (opts.light) + args.push_back("-l"); + if (!opts.background.empty()) + { + args.push_back("-b"); + args.push_back(opts.background); + } + if (!opts.foreground.empty()) + { + args.push_back("--fg"); + args.push_back(opts.foreground); + } + if (!opts.backend.empty()) + { + args.push_back("--backend"); + args.push_back(opts.backend); + } + if (opts.saturate >= 0.0f && opts.saturate <= 1.0f) + { + args.push_back("--saturate"); + args.push_back(std::to_string(opts.saturate)); + } + + int exit_code = -1; + const std::string output = run_process_capture_output(args, &exit_code); + if (exit_code != 0) + throw std::runtime_error(process_failure_message(output, "wal failed")); + + const std::filesystem::path colors_path = normalize_path(COLORS_JSON_PATH); + const std::string colors_json = read_text_file(colors_path); + palette pal = parse_pywal_colors_json(colors_json, p, pal_name); + if (pal.name().empty()) + throw std::runtime_error("wal did not produce a colorscheme"); + return pal; +} + +} // namespace clrsync::core diff --git a/src/core/palette/pywal16_generator.hpp b/src/core/palette/pywal16_generator.hpp new file mode 100644 index 0000000..695d02b --- /dev/null +++ b/src/core/palette/pywal16_generator.hpp @@ -0,0 +1,32 @@ +#ifndef CLRSYNC_CORE_PALETTE_PYWAL16_GENERATOR_HPP +#define CLRSYNC_CORE_PALETTE_PYWAL16_GENERATOR_HPP + +#include "core/palette/palette.hpp" +#include "generator.hpp" + +namespace clrsync::core +{ +class pywal16_generator : public generator +{ + public: + pywal16_generator() + : generator({generator::system::linux_os}) + { + } + ~pywal16_generator() override = default; + + struct options + { + std::string background; + std::string foreground; + std::string backend; + float saturate = -1.0f; + bool light = false; + }; + + palette generate_from_image(const std::string &image_path) override; + palette generate_from_image(const std::string &image_path, const options &opts); +}; +} // namespace clrsync::core + +#endif diff --git a/src/core/palette/pywal16_mappings.hpp b/src/core/palette/pywal16_mappings.hpp new file mode 100644 index 0000000..eb18e98 --- /dev/null +++ b/src/core/palette/pywal16_mappings.hpp @@ -0,0 +1,117 @@ +#ifndef CLRSYNC_CORE_PALETTE_PYWAL16_MAPPINGS_HPP +#define CLRSYNC_CORE_PALETTE_PYWAL16_MAPPINGS_HPP + +#include "core/palette/generator_mappings.hpp" + +namespace clrsync::core +{ + +inline constexpr index_color_mapping PYWAL16_COLOR_MAPPINGS[] = { + // General UI + {"background", 0}, + {"foreground", 7}, + {"cursor", 15}, + {"surface", 0}, + {"surface_variant", 8}, + {"on_background", 7}, + {"on_surface", 7}, + {"on_surface_variant", 8}, + {"border", 8}, + {"border_focused", 4}, + {"accent", 4}, + {"accent_secondary", 6}, + // Semantic + {"success", 2}, + {"warning", 3}, + {"error", 1}, + {"info", 4}, + {"on_success", 15}, + {"on_warning", 0}, + {"on_error", 15}, + {"on_info", 15}, + // Base16 + {"base00", 0}, + {"base01", 8}, + {"base02", 8}, + {"base03", 8}, + {"base04", 8}, + {"base05", 7}, + {"base06", 15}, + {"base07", 15}, + {"base08", 1}, + {"base09", 9}, + {"base0A", 3}, + {"base0B", 2}, + {"base0C", 6}, + {"base0D", 4}, + {"base0E", 5}, + {"base0F", 11}, + // Editor - Basic + {"editor_background", 0}, + {"editor_foreground", 7}, + {"editor_line_highlight", 8}, + {"editor_selection", 4}, + {"editor_selection_inactive", 8}, + {"editor_cursor", 15}, + {"editor_whitespace", 8}, + // Editor - Gutter + {"editor_gutter_background", 0}, + {"editor_gutter_foreground", 8}, + {"editor_line_number", 8}, + {"editor_line_number_active", 7}, + // Editor - Syntax + {"editor_comment", 8}, + {"editor_string", 2}, + {"editor_number", 3}, + {"editor_boolean", 3}, + {"editor_keyword", 5}, + {"editor_operator", 7}, + {"editor_function", 12}, + {"editor_variable", 7}, + {"editor_parameter", 4}, + {"editor_property", 10}, + {"editor_constant", 9}, + {"editor_type", 14}, + {"editor_class", 14}, + {"editor_interface", 14}, + {"editor_enum", 14}, + {"editor_namespace", 4}, + {"editor_attribute", 13}, + {"editor_decorator", 13}, + {"editor_tag", 1}, + {"editor_punctuation", 7}, + {"editor_link", 12}, + {"editor_regex", 5}, + {"editor_escape_character", 9}, + // Editor - Diagnostics + {"editor_invalid", 1}, + {"editor_error", 1}, + {"editor_error_background", 0}, + {"editor_warning", 3}, + {"editor_warning_background", 0}, + {"editor_info", 4}, + {"editor_info_background", 0}, + {"editor_hint", 2}, + {"editor_hint_background", 0}, + // Editor - UI Elements + {"editor_active_line_border", 8}, + {"editor_indent_guide", 8}, + {"editor_indent_guide_active", 7}, + {"editor_bracket_match", 6}, + {"editor_search_match", 3}, + {"editor_search_match_active", 11}, + {"editor_find_range_highlight", 3}, + // Editor - Diff + {"editor_deleted", 1}, + {"editor_inserted", 2}, + {"editor_modified", 3}, + {"editor_ignored", 8}, + {"editor_folded_background", 8}, +}; + +inline constexpr std::size_t PYWAL16_COLOR_MAPPINGS_COUNT = + sizeof(PYWAL16_COLOR_MAPPINGS) / sizeof(PYWAL16_COLOR_MAPPINGS[0]); + +} // namespace clrsync::core + +#endif diff --git a/src/gui/views/color_scheme_editor.cpp b/src/gui/views/color_scheme_editor.cpp index 3fbca53..98ae6b1 100644 --- a/src/gui/views/color_scheme_editor.cpp +++ b/src/gui/views/color_scheme_editor.cpp @@ -2,6 +2,7 @@ #include "gui/layout/ui_layout.hpp" #include "core/palette/hellwal_generator.hpp" #include "core/palette/matugen_generator.hpp" +#include "core/palette/pywal16_generator.hpp" #include "gui/controllers/theme_applier.hpp" #include "gui/platform/file_browser.hpp" #include "gui/widgets/action_buttons.hpp" @@ -13,6 +14,7 @@ #include "template_editor.hpp" #include #include +#include void color_scheme_editor::refresh_available_generators() { @@ -33,6 +35,13 @@ void color_scheme_editor::refresh_available_generators() m_generator_labels.push_back("matugen"); } + clrsync::core::pywal16_generator pywal16_gen; + if (pywal16_gen.supports_current_system()) + { + m_available_generators.push_back(clrsync::gui::widgets::palette_generator_kind::pywal16); + m_generator_labels.push_back("pywal16"); + } + if (m_generate_state.generator_idx < 0 || m_generate_state.generator_idx >= static_cast(m_available_generators.size())) m_generate_state.generator_idx = 0; @@ -48,11 +57,19 @@ color_scheme_editor::selected_generator_kind() const return m_available_generators[m_generate_state.generator_idx]; } -void color_scheme_editor::execute_palette_generation() +namespace +{ +bool palette_has_colors(const clrsync::core::palette &pal) +{ + return !pal.colors().empty(); +} +} // namespace + +std::optional color_scheme_editor::execute_palette_generation() { const auto selected_kind = selected_generator_kind(); if (!selected_kind) - return; + return "No palette generator is available on this system."; try { @@ -61,11 +78,9 @@ void color_scheme_editor::execute_palette_generation() clrsync::core::hellwal_generator gen; if (!gen.supports_current_system()) { - std::cerr << "Generation failed: hellwal is not supported on " - << clrsync::core::generator::system_name( - clrsync::core::generator::current_system()) - << std::endl; - return; + return std::string("hellwal is not supported on ") + + clrsync::core::generator::system_name( + clrsync::core::generator::current_system()); } clrsync::core::hellwal_generator::options opts; opts.neon = m_generate_state.neon; @@ -85,6 +100,8 @@ void color_scheme_editor::execute_palette_generation() } auto pal = gen.generate_from_image(image_path, opts); + if (!palette_has_colors(pal)) + return "hellwal did not produce any colors."; if (pal.name().empty()) { std::filesystem::path p(image_path); @@ -99,11 +116,9 @@ void color_scheme_editor::execute_palette_generation() clrsync::core::matugen_generator gen; if (!gen.supports_current_system()) { - std::cerr << "Generation failed: matugen is not supported on " - << clrsync::core::generator::system_name( - clrsync::core::generator::current_system()) - << std::endl; - return; + return std::string("matugen is not supported on ") + + clrsync::core::generator::system_name( + clrsync::core::generator::current_system()); } clrsync::core::matugen_generator::options opts; opts.mode = m_generate_state.matugen_mode; @@ -118,6 +133,8 @@ void color_scheme_editor::execute_palette_generation() if (!hex.empty() && hex[0] == '#') hex = hex.substr(1); pal = gen.generate_from_color(hex, opts); + if (!palette_has_colors(pal)) + return "matugen did not produce any colors."; if (pal.name().empty()) pal.set_name(std::string("matugen:color:") + hex); } @@ -130,6 +147,8 @@ void color_scheme_editor::execute_palette_generation() file_dialogs::image_file_filters()); } pal = gen.generate_from_image(image_path, opts); + if (!palette_has_colors(pal)) + return "matugen did not produce any colors."; if (pal.name().empty()) { std::filesystem::path p(image_path); @@ -140,11 +159,51 @@ void color_scheme_editor::execute_palette_generation() m_controller.select_palette(pal.name()); apply_themes(); } + else if (*selected_kind == clrsync::gui::widgets::palette_generator_kind::pywal16) + { + clrsync::core::pywal16_generator gen; + if (!gen.supports_current_system()) + { + return std::string("pywal16 is not supported on ") + + clrsync::core::generator::system_name( + clrsync::core::generator::current_system()); + } + clrsync::core::pywal16_generator::options opts; + if (m_generate_state.pywal16_use_background) + opts.background = m_generate_state.pywal16_background; + if (m_generate_state.pywal16_use_foreground) + opts.foreground = m_generate_state.pywal16_foreground; + opts.backend = m_generate_state.pywal16_backend; + opts.light = m_generate_state.pywal16_light; + if (m_generate_state.pywal16_use_saturate) + opts.saturate = m_generate_state.pywal16_saturate; + + auto image_path = m_generate_state.image_path; + if (image_path.empty()) + { + image_path = file_dialogs::open_file_dialog("Select Image", "", + file_dialogs::image_file_filters()); + } + + auto pal = gen.generate_from_image(image_path, opts); + if (!palette_has_colors(pal)) + return "pywal16 did not produce any colors."; + if (pal.name().empty()) + { + std::filesystem::path p(image_path); + pal.set_name(std::string("pywal16:") + p.filename().string()); + } + m_controller.import_palette(pal); + m_controller.select_palette(pal.name()); + apply_themes(); + } } catch (const std::exception &e) { - std::cerr << "Generation failed: " << e.what() << std::endl; + return e.what(); } + + return std::nullopt; } color_scheme_editor::color_scheme_editor() @@ -245,7 +304,7 @@ void color_scheme_editor::render_controls() m_generate_palette_dialog.open(); m_generate_palette_dialog.render(m_generate_state, m_available_generators, m_generator_labels, - current, [this]() { execute_palette_generation(); }); + current, [this]() { return execute_palette_generation(); }); if (m_show_delete_confirmation) { @@ -286,7 +345,8 @@ void color_scheme_editor::setup_widgets() break; } } - execute_palette_generation(); + if (const std::optional error = execute_palette_generation()) + m_generate_palette_dialog.set_error(*error); }); m_generate_dialog.set_path_browse_callback( [this](const std::string ¤t_path) -> std::string { diff --git a/src/gui/views/color_scheme_editor.hpp b/src/gui/views/color_scheme_editor.hpp index 92bfd93..cbfdeba 100644 --- a/src/gui/views/color_scheme_editor.hpp +++ b/src/gui/views/color_scheme_editor.hpp @@ -41,7 +41,7 @@ class color_scheme_editor void notify_palette_changed(); void setup_widgets(); void refresh_available_generators(); - void execute_palette_generation(); + [[nodiscard]] std::optional execute_palette_generation(); [[nodiscard]] std::optional selected_generator_kind() const; diff --git a/src/gui/widgets/generate_palette_dialog.cpp b/src/gui/widgets/generate_palette_dialog.cpp index d74a2c2..b7a7a97 100644 --- a/src/gui/widgets/generate_palette_dialog.cpp +++ b/src/gui/widgets/generate_palette_dialog.cpp @@ -1,5 +1,6 @@ #include "generate_palette_dialog.hpp" #include "colors.hpp" +#include "core/palette/color.hpp" #include "gui/layout/ui_layout.hpp" #include "section_header.hpp" #include "gui/platform/file_browser.hpp" @@ -7,6 +8,7 @@ #include #include #include +#include namespace clrsync::gui::widgets { @@ -56,6 +58,70 @@ void table_slider(const char* label, const char* id, float* value, float min_v, ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SliderFloat(id, value, min_v, max_v, format); } + +void sync_hex_string_to_rgb(const std::string& hex, float rgb[3]) +{ + if (hex.empty()) + return; + try + { + clrsync::core::color col; + col.from_hex_string(hex); + const clrsync::core::rgb channels = col.to_rgb(); + rgb[0] = channels.r / 255.0f; + rgb[1] = channels.g / 255.0f; + rgb[2] = channels.b / 255.0f; + } + catch (...) + { + } +} + +void sync_rgb_to_hex_string(const float rgb[3], std::string& hex) +{ + const int r = static_cast(rgb[0] * 255.0f + 0.5f); + const int g = static_cast(rgb[1] * 255.0f + 0.5f); + const int b = static_cast(rgb[2] * 255.0f + 0.5f); + char buf[16]; + std::snprintf(buf, sizeof(buf), "#%02X%02X%02X", r, g, b); + hex = buf; +} + +void render_hex_color_swatch(const char* id_prefix, float rgb[3], std::string& hex_value) +{ + const ImVec2 swatch_size(layout::COLOR_SWATCH_SIZE, layout::COLOR_SWATCH_SIZE); + const ImVec4 color_vec(rgb[0], rgb[1], rgb[2], 1.0f); + const std::string button_id = std::string("##") + id_prefix + "_swatch"; + const std::string popup_id = std::string("##") + id_prefix + "_picker"; + const std::string picker_id = std::string("##") + id_prefix + "_picker3"; + if (ImGui::ColorButton(button_id.c_str(), color_vec, ImGuiColorEditFlags_NoTooltip, + swatch_size)) + ImGui::OpenPopup(popup_id.c_str()); + if (ImGui::BeginPopup(popup_id.c_str())) + { + if (ImGui::ColorPicker3(picker_id.c_str(), rgb, + ImGuiColorEditFlags_NoSidePreview | + ImGuiColorEditFlags_DisplayRGB)) + sync_rgb_to_hex_string(rgb, hex_value); + ImGui::EndPopup(); + } +} + +void render_optional_hex_color_row(const char* label, const char* id_prefix, bool& enabled, + float rgb[3], std::string& hex_value) +{ + table_label(label); + if (ImGui::Checkbox((std::string("Override##") + id_prefix).c_str(), &enabled) && enabled && + hex_value.empty()) + sync_rgb_to_hex_string(rgb, hex_value); + if (enabled) + { + if (!hex_value.empty()) + sync_hex_string_to_rgb(hex_value, rgb); + ImGui::SameLine(0.0f, layout::BUTTON_SPACING); + render_hex_color_swatch(id_prefix, rgb, hex_value); + } +} } // namespace generate_palette_dialog::generate_palette_dialog() @@ -68,6 +134,13 @@ generate_palette_dialog::generate_palette_dialog() void generate_palette_dialog::open() { + m_error.clear(); + m_is_open = true; +} + +void generate_palette_dialog::set_error(const std::string &message) +{ + m_error.set(message); m_is_open = true; } @@ -75,7 +148,7 @@ void generate_palette_dialog::render(generate_palette_state& state, const std::vector& available_generators, const std::vector& generator_labels, const core::palette& palette, - const std::function& on_generate) + const std::function()>& on_generate) { if (!m_is_open) return; @@ -109,6 +182,10 @@ void generate_palette_dialog::render(generate_palette_state& state, render_hellwal_options(state, palette); else if (kind == palette_generator_kind::matugen) render_matugen_options(state, palette); + else if (kind == palette_generator_kind::pywal16) + render_pywal16_options(state, palette); + + m_error.render(palette); ImGui::Spacing(); ImGui::Separator(); @@ -120,7 +197,12 @@ void generate_palette_dialog::render(generate_palette_state& state, ImGui::PopStyleVar(3); if (submit_generate && on_generate) - on_generate(); + { + if (const std::optional error = on_generate()) + m_error.set(*error); + else + m_is_open = false; + } } void generate_palette_dialog::render_generator_row( @@ -264,6 +346,61 @@ void generate_palette_dialog::render_matugen_options(generate_palette_state& sta } } +void generate_palette_dialog::render_pywal16_options(generate_palette_state& state, + const core::palette& palette) +{ + section_header("pywal16 options", palette); + + form_field_config image_cfg; + image_cfg.label = "Image"; + image_cfg.label_width = DIALOG_LABEL_WIDTH; + image_cfg.field_width = -1.0f; + image_cfg.type = field_type::path; + image_cfg.hint = "Select an image file..."; + image_cfg.tooltip = "Wallpaper or photo used as the color source"; + m_form.render_path(image_cfg, state.image_path); + + ImGui::Spacing(); + + if (begin_field_table("##gen_pywal16")) + { + table_label("Light scheme"); + ImGui::Checkbox("Generate light colorscheme", &state.pywal16_light); + + table_label("Backend"); + const char* backends[] = {"default", "haishoku", "colorz", + "modern_colorthief", "colorthief", "wal", + "fast_colorthief", "okthief"}; + int backend_idx = 0; + for (int i = 1; i < IM_ARRAYSIZE(backends); ++i) + { + if (state.pywal16_backend == backends[i]) + { + backend_idx = i; + break; + } + } + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::Combo("##pywal16_backend", &backend_idx, backends, IM_ARRAYSIZE(backends))) + state.pywal16_backend = (backend_idx == 0) ? "" : backends[backend_idx]; + + table_label("Saturation"); + ImGui::Checkbox("Override saturation", &state.pywal16_use_saturate); + if (state.pywal16_use_saturate) + { + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderFloat("##pywal16_saturate", &state.pywal16_saturate, 0.0f, 1.0f, "%.2f"); + } + + render_optional_hex_color_row("Background", "pywal16_bg", state.pywal16_use_background, + state.pywal16_background_vec, state.pywal16_background); + render_optional_hex_color_row("Foreground", "pywal16_fg", state.pywal16_use_foreground, + state.pywal16_foreground_vec, state.pywal16_foreground); + + ImGui::EndTable(); + } +} + bool generate_palette_dialog::render_footer(bool can_generate, const core::palette& palette) { (void)palette; @@ -280,10 +417,7 @@ bool generate_palette_dialog::render_footer(bool can_generate, const core::palet push_success_button_style(); if (ImGui::Button("Generate", ImVec2(MODAL_BUTTON_WIDTH, 0))) - { submit_generate = true; - m_is_open = false; - } pop_button_style(); if (!can_generate) diff --git a/src/gui/widgets/generate_palette_dialog.hpp b/src/gui/widgets/generate_palette_dialog.hpp index 5ccdf03..df4bc61 100644 --- a/src/gui/widgets/generate_palette_dialog.hpp +++ b/src/gui/widgets/generate_palette_dialog.hpp @@ -3,7 +3,9 @@ #include "core/palette/palette.hpp" #include "form_field.hpp" +#include "gui/widgets/error_message.hpp" #include +#include #include #include @@ -13,7 +15,8 @@ namespace clrsync::gui::widgets enum class palette_generator_kind { hellwal, - matugen + matugen, + pywal16 }; struct generate_palette_state @@ -35,6 +38,16 @@ struct generate_palette_state bool matugen_use_color = false; float matugen_color_vec[3]{1.0f, 0.0f, 0.0f}; std::string matugen_color_hex{"FF0000"}; + bool pywal16_use_background = false; + float pywal16_background_vec[3]{0.063f, 0.071f, 0.071f}; + std::string pywal16_background; + bool pywal16_use_foreground = false; + float pywal16_foreground_vec[3]{0.765f, 0.765f, 0.765f}; + std::string pywal16_foreground; + std::string pywal16_backend; + bool pywal16_use_saturate = false; + float pywal16_saturate = 0.5f; + bool pywal16_light = false; }; class generate_palette_dialog @@ -43,19 +56,22 @@ class generate_palette_dialog generate_palette_dialog(); void open(); + void set_error(const std::string &message); void render(generate_palette_state& state, const std::vector& available_generators, const std::vector& generator_labels, const core::palette& palette, - const std::function& on_generate); + const std::function()>& on_generate); private: void render_generator_row(generate_palette_state& state, const std::vector& generator_labels); void render_hellwal_options(generate_palette_state& state, const core::palette& palette); void render_matugen_options(generate_palette_state& state, const core::palette& palette); + void render_pywal16_options(generate_palette_state& state, const core::palette& palette); bool render_footer(bool can_generate, const core::palette& palette); form_field m_form; + error_message m_error; bool m_is_open = false; }; From 84cff2ab2afff4c41681877f2989322dfcc7ed4c Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Mon, 25 May 2026 23:21:40 +0300 Subject: [PATCH 3/4] fix: fix opengl paths for niuxos --- flake.nix | 5 ++++- package.nix | 14 ++++++++++++++ src/core/common/version.hpp | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 3df89c4..6cb6bed 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,10 @@ pkgs = nixpkgsFor.${system}; in rec { - clrsync = pkgs.callPackage ./package.nix { inherit semver; }; + clrsync = pkgs.callPackage ./package.nix { + inherit semver; + addDriverRunpath = pkgs.addDriverRunpath; + }; default = clrsync; } ); diff --git a/package.nix b/package.nix index 0a141e2..deffbf9 100644 --- a/package.nix +++ b/package.nix @@ -13,6 +13,7 @@ fontconfig, xxd, mesa, + libglvnd, xorg, wayland, libxkbcommon, @@ -22,6 +23,7 @@ gtk3, glib, gsettings-desktop-schemas, + addDriverRunpath, semver, }: @@ -61,6 +63,7 @@ stdenv.mkDerivation rec { buildInputs = [ glfw + libglvnd freetype libpng fontconfig @@ -97,6 +100,17 @@ stdenv.mkDerivation rec { dontWrapGApps = false; + preFixup = lib.optionalString stdenv.hostPlatform.isLinux '' + gappsWrapperArgs+=( + "--prefix" "LD_LIBRARY_PATH" ":" "${addDriverRunpath.driverLink}/lib:${lib.makeLibraryPath [ + libglvnd + mesa.out + ]}" + "--set-default" "__EGL_VENDOR_LIBRARY_FILENAMES__" "${mesa}/share/glvnd/egl_vendor.d/50_mesa.json" + "--prefix" "LIBGL_DRIVERS_PATH" ":" "${addDriverRunpath.driverLink}/lib/dri:${mesa}/lib/dri" + ) + ''; + meta = with lib; { description = "Color scheme manager with GUI and CLI"; homepage = "https://github.com/obsqrbtz/clrsync"; diff --git a/src/core/common/version.hpp b/src/core/common/version.hpp index 57afdbb..5e71d29 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 = "1.2.2+git.g591bd4c"; +const std::string GIT_SEMVER = "1.2.2+git.g8144ce0"; const std::string version_string(); } // namespace clrsync::core From 283303b2cdc6a9237393eb9333c76dde0580daf5 Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Tue, 26 May 2026 00:27:58 +0300 Subject: [PATCH 4/4] wrap only gui target --- flake.nix | 6 ++---- package.nix | 18 ++++++++---------- src/core/common/version.hpp | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/flake.nix b/flake.nix index 6cb6bed..b9f8450 100644 --- a/flake.nix +++ b/flake.nix @@ -29,10 +29,7 @@ pkgs = nixpkgsFor.${system}; in rec { - clrsync = pkgs.callPackage ./package.nix { - inherit semver; - addDriverRunpath = pkgs.addDriverRunpath; - }; + clrsync = pkgs.callPackage ./package.nix { inherit semver; }; default = clrsync; } ); @@ -95,3 +92,4 @@ }; }; } + diff --git a/package.nix b/package.nix index deffbf9..edafd03 100644 --- a/package.nix +++ b/package.nix @@ -23,7 +23,6 @@ gtk3, glib, gsettings-desktop-schemas, - addDriverRunpath, semver, }: @@ -100,15 +99,14 @@ stdenv.mkDerivation rec { dontWrapGApps = false; - preFixup = lib.optionalString stdenv.hostPlatform.isLinux '' - gappsWrapperArgs+=( - "--prefix" "LD_LIBRARY_PATH" ":" "${addDriverRunpath.driverLink}/lib:${lib.makeLibraryPath [ - libglvnd - mesa.out - ]}" - "--set-default" "__EGL_VENDOR_LIBRARY_FILENAMES__" "${mesa}/share/glvnd/egl_vendor.d/50_mesa.json" - "--prefix" "LIBGL_DRIVERS_PATH" ":" "${addDriverRunpath.driverLink}/lib/dri:${mesa}/lib/dri" - ) + postFixup = lib.optionalString stdenv.hostPlatform.isLinux '' + if [ -f "$out/bin/.clrsync_gui-wrapped" ]; then + wrapProgram "$out/bin/.clrsync_gui-wrapped" \ + --prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath [ mesa.out libglvnd ]}" \ + --set-default GBM_BACKENDS_PATH "${mesa}/lib/gbm" \ + --set-default __EGL_VENDOR_LIBRARY_FILENAMES__ "${mesa}/share/glvnd/egl_vendor.d/50_mesa.json" \ + --prefix LIBGL_DRIVERS_PATH : "${mesa}/lib/dri" + fi ''; meta = with lib; { diff --git a/src/core/common/version.hpp b/src/core/common/version.hpp index 5e71d29..ff5b5aa 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 = "1.2.2+git.g8144ce0"; +const std::string GIT_SEMVER = "1.2.2+git.g84cff2a"; const std::string version_string(); } // namespace clrsync::core