diff --git a/example_config/palettes/dark.toml b/example_config/palettes/dark.toml index baf452e..49fbe27 100644 --- a/example_config/palettes/dark.toml +++ b/example_config/palettes/dark.toml @@ -1,5 +1,6 @@ [colors] accent = '#9A8652FF' +accent_secondary = '#9A8652FF' background = '#111111FF' base00 = '#111111FF' base01 = '#668A51FF' diff --git a/example_config/palettes/light.toml b/example_config/palettes/light.toml index f3de92b..047bf15 100644 --- a/example_config/palettes/light.toml +++ b/example_config/palettes/light.toml @@ -1,5 +1,6 @@ [colors] accent = '#9A8652FF' +accent_secondary = '#9A8652FF' background = '#E0E0E0FF' base00 = '#E0E0E0FF' base01 = '#668A51FF' diff --git a/example_config/templates/kitty.conf b/example_config/templates/kitty.conf index 4bee4b1..036a74a 100644 --- a/example_config/templates/kitty.conf +++ b/example_config/templates/kitty.conf @@ -5,7 +5,7 @@ foreground {foreground} background {background} selection_foreground {on_surface} selection_background {surface} -url_color {accent} +url_color {accent_secondary} color0 {base00} color8 {base08} diff --git a/extra/palettes/dark/cursed/cursed.toml b/extra/palettes/dark/cursed/cursed.toml index 3c9bc06..451ed8f 100644 --- a/extra/palettes/dark/cursed/cursed.toml +++ b/extra/palettes/dark/cursed/cursed.toml @@ -1,5 +1,6 @@ [colors] accent = '#95A328FF' +accent_secondary = '#95A328FF' background = '#151515FF' base00 = '#151515FF' base01 = '#B44242FF' diff --git a/extra/palettes/dark/nord/nord.toml b/extra/palettes/dark/nord/nord.toml index dcb4251..19d1714 100644 --- a/extra/palettes/dark/nord/nord.toml +++ b/extra/palettes/dark/nord/nord.toml @@ -1,5 +1,6 @@ [colors] accent = '#A1CDFAFF' +accent_secondary = '#A1CDFAFF' background = '#2E3440FF' base00 = '#2E3440FF' base01 = '#BF616AFF' diff --git a/extra/palettes/light/cursed-light/cursed-light.toml b/extra/palettes/light/cursed-light/cursed-light.toml index 893ae82..f938c68 100644 --- a/extra/palettes/light/cursed-light/cursed-light.toml +++ b/extra/palettes/light/cursed-light/cursed-light.toml @@ -1,5 +1,6 @@ [colors] accent = '#95A328FF' +accent_secondary = '#95A328FF' background = '#F5F5F5FF' base00 = '#F5F5F5FF' base01 = '#B44242FF' diff --git a/extra/templates/apps/telegram/telegram.tdesktop-theme b/extra/templates/apps/telegram/telegram.tdesktop-theme index 538a563..ae92b59 100644 --- a/extra/templates/apps/telegram/telegram.tdesktop-theme +++ b/extra/templates/apps/telegram/telegram.tdesktop-theme @@ -39,7 +39,7 @@ dialogsDraftFgActive: {foreground}; // Draft label for active chat dialogsDraftFgOver: {foreground}; // Draft label on hover dialogsVerifiedIconBg: {accent}; // Verified badge background -dialogsVerifiedIconBgActive: {accent}; // Verified badge for active chat +dialogsVerifiedIconBgActive: {accent_secondary}; // Verified badge for active chat dialogsVerifiedIconBgOver: {accent}; // Verified badge on hover dialogsVerifiedIconFg: {background}; // Verified icon dialogsVerifiedIconFgActive: {background}; // Verified icon for active chat @@ -50,11 +50,11 @@ dialogsSendingIconFgActive: {foreground}; // Sending icon for active chat dialogsSendingIconFgOver: {foreground}; // Sending icon on hover dialogsSentIconFg: {accent}; // Sent icon (tick) -dialogsSentIconFgActive: {accent}; // Sent icon for active chat +dialogsSentIconFgActive: {accent_secondary}; // Sent icon for active chat dialogsSentIconFgOver: {accent}; // Sent icon on hover dialogsUnreadBg: {accent}; // Unread badge background -dialogsUnreadBgActive: {accent}; // Unread badge for active chat +dialogsUnreadBgActive: {accent_secondary}; // Unread badge for active chat dialogsUnreadBgOver: {accent}; // Unread badge on hover dialogsUnreadBgMuted: {foreground}; // Muted unread badge dialogsUnreadBgMutedActive: {foreground}; // Muted unread badge for active chat @@ -79,7 +79,7 @@ windowBoldFg: {on_background}; // Bold text windowBoldFgOver: {on_surface_variant}; // Bold text on hover windowBgActive: {surface}; // Active items background windowFgActive: {foreground}; // Active items text -windowActiveTextFg: {accent}; // Active items text +windowActiveTextFg: {accent_secondary}; // Active items text windowShadowFg: {border}; // Window shadow windowShadowFgFallback: {border}; // Fallback for shadow historyOutIconFg: {accent}; @@ -105,7 +105,7 @@ slideFadeOutShadowFg: {border}; imageBg: {surface}; imageBgTransparent: {surface}; -activeButtonBg: {accent}; // Active button background +activeButtonBg: {accent_secondary}; // Active button background activeButtonBgOver: {surface_variant}; // Active button hover background activeButtonBgRipple: {on_surface_variant}; // Active button ripple activeButtonFg: {on_background}; // Active button text @@ -128,12 +128,12 @@ attentionButtonBgRipple: {on_surface}; outlineButtonBg: {surface}; // Outline button background outlineButtonBgOver: {surface_variant}; // Outline button hover background -outlineButtonOutlineFg: {accent}; // Outline button color -outlineButtonBgRipple: {accent}; // Outline button ripple +outlineButtonOutlineFg: {accent_secondary}; // Outline button color +outlineButtonBgRipple: {accent_secondary}; // Outline button ripple menuBg: {surface}; menuBgOver: {surface_variant}; -menuBgRipple: {accent}; +menuBgRipple: {accent_secondary}; menuIconFg: {on_surface}; menuIconFgOver: {on_surface_variant}; menuSubmenuArrowFg: {border}; @@ -141,22 +141,22 @@ menuFgDisabled: {border}; menuSeparatorFg: {border}; scrollBarBg: {accent}40; // Scroll bar background (40% opacity) -scrollBarBgOver: {accent}60; // Scroll bar hover background (60% opacity) +scrollBarBgOver: {accent_secondary}60; // Scroll bar hover background (60% opacity) scrollBg: {surface_variant}40; // Scroll bar track (40% opacity) scrollBgOver: {surface_variant}60; // Scroll bar track on hover (60% opacity) smallCloseIconFg: {border}; smallCloseIconFgOver: {on_surface_variant}; -radialFg: {accent}; +radialFg: {accent_secondary}; radialBg: {surface}; placeholderFg: {border}; // Placeholder text -placeholderFgActive: {accent}; // Active placeholder text +placeholderFgActive: {accent_secondary}; // Active placeholder text inputBorderFg: {border}; // Input border filterInputBorderFg: {border}; // Search input border filterInputInactiveBg: {surface}; // Inactive search input background -checkboxFg: {accent}; // Checkbox color +checkboxFg: {accent_secondary}; // Checkbox color // Filters sidebar (left side bar with folder filters) sideBarBg: {surface}; // Filters sidebar background @@ -192,7 +192,7 @@ cancelIconFgOver: {error}; // Cancel icon on hover boxBg: {surface}; // Box background boxTextFg: {on_surface}; // Box text -boxTextFgGood: {accent}; // Box good text +boxTextFgGood: {accent_secondary}; // Box good text boxTextFgError: {error}; // Box error text boxTitleFg: {on_surface}; // Box title text boxSearchBg: {surface}; // Box search field background @@ -204,10 +204,10 @@ contactsBgOver: {surface_variant}; // Contacts background on hover contactsNameFg: {on_surface}; // Contact name contactsStatusFg: {border}; // Contact status contactsStatusFgOver: {on_surface_variant}; // Contact status on hover -contactsStatusFgOnline: {accent}; // Online contact status +contactsStatusFgOnline: {accent_secondary}; // Online contact status photoCropFadeBg: {surface}cc; // Photo crop fade background -photoCropPointFg: {accent}; // Photo crop points +photoCropPointFg: {accent_secondary}; // Photo crop points chat_inBubbleSelected: {surface_variant}; // inbox selected chat background chat_outBubbleSelected: {surface_variant}; // outbox selected chat background \ No newline at end of file diff --git a/extra/templates/wms/hyprland/hyprland.conf b/extra/templates/wms/hyprland/hyprland.conf index 6505d57..9ed55e4 100644 --- a/extra/templates/wms/hyprland/hyprland.conf +++ b/extra/templates/wms/hyprland/hyprland.conf @@ -1,6 +1,6 @@ $primary = rgb({accent_stripped}) $surface = rgb({surface_stripped}) -$secondary = rgb({base04_stripped}) +$secondary = rgb({accent_secondary_stripped}) $error = rgb({error_stripped}) $tertiary = rgb({base06_stripped}) $surface_lowest = rgb({background_stripped}) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 75dd566..93c9396 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -9,6 +9,8 @@ #include "core/common/version.hpp" #include "core/config/config.hpp" #include "core/io/toml_file.hpp" +#include "core/palette/hellwal_generator.hpp" +#include "core/palette/matugen_generator.hpp" #include "core/palette/palette_file.hpp" #include "core/palette/palette_manager.hpp" #include "core/theme/theme_renderer.hpp" @@ -91,6 +93,48 @@ void setup_argument_parser(argparse::ArgumentParser &program) auto &group = program.add_mutually_exclusive_group(); group.add_argument("-t", "--theme").help("sets theme to apply"); group.add_argument("-p", "--path").help("sets theme file to apply"); + + program.add_argument("-g", "--generate").nargs(1).help("generate palette from "); + program.add_argument("--generate-color") + .nargs(1) + .help("generate palette from a color (hex), used with --generator matugen"); + + program.add_argument("--generator") + .default_value(std::string("hellwal")) + .help("palette generator to use (hellwal)") + .metavar("GENERATOR"); + + program.add_argument("--matugen-type") + .default_value(std::string("scheme-tonal-spot")) + .help("matugen: Sets a custom color scheme type") + .metavar("TYPE"); + program.add_argument("--matugen-mode") + .default_value(std::string("dark")) + .help("matugen: Which mode to use for the color scheme (light,dark)") + .metavar("MODE"); + program.add_argument("--matugen-contrast") + .default_value(std::string("0.0")) + .help("matugen: contrast value from -1 to 1") + .metavar("FLOAT"); + + // hellwal generator options + program.add_argument("--hellwal-neon").help("hellwal: enable neon mode").flag(); + program.add_argument("--hellwal-dark").help("hellwal: prefer dark palettes").flag(); + program.add_argument("--hellwal-light").help("hellwal: prefer light palettes").flag(); + program.add_argument("--hellwal-color").help("hellwal: enable color mode").flag(); + program.add_argument("--hellwal-invert").help("hellwal: invert colors").flag(); + program.add_argument("--hellwal-dark-offset") + .default_value(std::string("0.0")) + .help("hellwal: dark offset (float)") + .metavar("FLOAT"); + program.add_argument("--hellwal-bright-offset") + .default_value(std::string("0.0")) + .help("hellwal: bright offset (float)") + .metavar("FLOAT"); + program.add_argument("--hellwal-gray-scale") + .default_value(std::string("0.0")) + .help("hellwal: gray scale factor (float)") + .metavar("FLOAT"); } int main(int argc, char *argv[]) @@ -136,6 +180,136 @@ int main(int argc, char *argv[]) return handle_apply_theme(program, default_theme); } + if (program.is_used("--generate")) + { + std::string image_path; + if (program.is_used("--generate")) + image_path = program.get("--generate"); + std::string generator_name = program.get("--generator"); + + clrsync::core::palette pal; + if (generator_name == "hellwal") + { + clrsync::core::hellwal_generator gen; + clrsync::core::hellwal_generator::options opts{}; + + if (program.is_used("--hellwal-neon")) + opts.neon = true; + if (program.is_used("--hellwal-dark")) + opts.dark = true; + if (program.is_used("--hellwal-light")) + opts.light = true; + if (program.is_used("--hellwal-color")) + opts.color = true; + if (program.is_used("--hellwal-invert")) + opts.invert = true; + + try + { + std::string s1 = program.get("--hellwal-dark-offset"); + opts.dark_offset = std::stof(s1); + } + catch (...) + { + } + + try + { + std::string s2 = program.get("--hellwal-bright-offset"); + opts.bright_offset = std::stof(s2); + } + catch (...) + { + } + + try + { + std::string s3 = program.get("--hellwal-gray-scale"); + opts.gray_scale = std::stof(s3); + } + catch (...) + { + } + + pal = gen.generate_from_image(image_path, opts); + } + else if (generator_name == "matugen") + { + clrsync::core::matugen_generator gen; + clrsync::core::matugen_generator::options opts{}; + + try + { + opts.type = program.get("--matugen-type"); + } + catch (...) + { + } + + try + { + opts.type = program.get("--matugen-type"); + } + catch (...) + { + } + + try + { + opts.mode = program.get("--matugen-mode"); + } + catch (...) + { + } + + try + { + std::string s = program.get("--matugen-contrast"); + opts.contrast = std::stof(s); + } + catch (...) + { + } + + if (program.is_used("--generate-color")) + { + std::string color = program.get("--generate-color"); + pal = gen.generate_from_color(color, opts); + } + else + { + pal = gen.generate_from_image(image_path, opts); + } + } + else + { + std::cerr << "Unknown generator: " << generator_name << std::endl; + return 1; + } + + if (pal.name().empty()) + { + std::filesystem::path p(image_path); + pal.set_name("generated:" + p.filename().string()); + } + + auto dir = clrsync::core::config::instance().palettes_path(); + clrsync::core::palette_manager pal_mgr; + pal_mgr.save_palette_to_file(pal, dir); + + clrsync::core::theme_renderer renderer; + std::filesystem::path file_path = std::filesystem::path(dir) / (pal.name() + ".toml"); + auto res = renderer.apply_theme_from_path(file_path.string()); + if (!res) + { + std::cerr << "Failed to apply generated palette: " << res.error().description() + << std::endl; + return 1; + } + std::cout << "Generated and applied palette: " << pal.name() << std::endl; + return 0; + } + std::cout << program << std::endl; return 0; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5a50688..4707c3b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,5 +1,7 @@ set(CORE_SOURCES palette/color.cpp + palette/hellwal_generator.cpp + palette/matugen_generator.cpp io/toml_file.cpp config/config.cpp common/utils.cpp diff --git a/src/core/common/version.hpp b/src/core/common/version.hpp index 7f83be8..7f19563 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.0.5+git.g6fc80da"; +const std::string GIT_SEMVER = "1.0.5+git.g3cd637d"; const std::string version_string(); } // namespace clrsync::core diff --git a/src/core/palette/color_keys.hpp b/src/core/palette/color_keys.hpp index 5debfe5..51c7de6 100644 --- a/src/core/palette/color_keys.hpp +++ b/src/core/palette/color_keys.hpp @@ -26,6 +26,7 @@ constexpr const char *COLOR_KEYS[] = { "cursor", "accent", + "accent_secondary", // Semantic "success", @@ -93,6 +94,7 @@ inline const std::unordered_map DEFAULT_COLORS = { {"cursor", 0xd2d2d2ff}, {"accent", 0x9a8652ff}, + {"accent_secondary", 0x9a8652ff}, {"success", 0x668a51ff}, {"info", 0x3a898cff}, diff --git a/src/core/palette/generator.hpp b/src/core/palette/generator.hpp new file mode 100644 index 0000000..1fd9cb3 --- /dev/null +++ b/src/core/palette/generator.hpp @@ -0,0 +1,19 @@ +#ifndef CLRSYNC_CORE_PALETTE_GENERATOR_HPP +#define CLRSYNC_CORE_PALETTE_GENERATOR_HPP + +#include +#include "core/palette/palette.hpp" + +namespace clrsync::core +{ +class generator +{ + public: + generator() = default; + virtual ~generator() = default; + + virtual palette generate_from_image(const std::string &image_path) = 0; +}; +} // namespace clrsync::core + +#endif diff --git a/src/core/palette/hellwal_generator.cpp b/src/core/palette/hellwal_generator.cpp new file mode 100644 index 0000000..80e5a3a --- /dev/null +++ b/src/core/palette/hellwal_generator.cpp @@ -0,0 +1,176 @@ +#include "hellwal_generator.hpp" + +#include "core/palette/color.hpp" + +#include +#include +#include +#include +#include + +namespace clrsync::core +{ +static std::string run_command_capture_output(const std::string &cmd) +{ + std::array buffer; + std::string result; + FILE *pipe = popen(cmd.c_str(), "r"); + if (!pipe) + return {}; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != nullptr) + { + result += buffer.data(); + } + int rc = pclose(pipe); + (void)rc; + return result; +} + +palette hellwal_generator::generate_from_image(const std::string &image_path) +{ + options default_opts{}; + return generate_from_image(image_path, default_opts); +} + +palette hellwal_generator::generate_from_image(const std::string &image_path, const options &opts) +{ + palette pal; + + std::filesystem::path p(image_path); + pal.set_name("hellwal:" + p.filename().string()); + pal.set_file_path(image_path); + + std::string cmd = "hellwal -i '" + image_path + "' --json"; + if (opts.neon) + cmd += " --neon-mode"; + if (opts.dark) + cmd += " --dark"; + if (opts.light) + cmd += " --light"; + if (opts.color) + cmd += " --color"; + if (opts.dark_offset > 0.0f) + cmd += " --dark-offset " + std::to_string(opts.dark_offset); + if (opts.bright_offset > 0.0f) + cmd += " --bright-offset " + std::to_string(opts.bright_offset); + if (opts.invert) + cmd += " --invert"; + if (opts.gray_scale > 0.0f) + cmd += " --gray-scale " + std::to_string(opts.gray_scale); + + std::string out = run_command_capture_output(cmd); + if (out.empty()) + return {}; + + std::regex special_re( + "\"(background|foreground|cursor|border)\"\\s*:\\s*\"(#[0-9A-Fa-f]{6,8})\""); + for (std::sregex_iterator it(out.begin(), out.end(), special_re), end; it != end; ++it) + { + std::smatch m = *it; + std::string key = m[1].str(); + std::string hex = m[2].str(); + try + { + color col; + col.from_hex_string(hex); + pal.set_color(key, col); + } + catch (...) + { + } + } + + std::regex color_re("\"color(\\d{1,2})\"\\s*:\\s*\"(#[0-9A-Fa-f]{6,8})\""); + for (std::sregex_iterator it(out.begin(), out.end(), color_re), end; it != end; ++it) + { + std::smatch m = *it; + int idx = std::stoi(m[1].str()); + if (idx < 0 || idx > 15) + continue; + std::string hex = m[2].str(); + + std::string key = "base0"; + if (idx < 10) + key += std::to_string(idx); + else + key += static_cast('A' + (idx - 10)); + + try + { + color col; + col.from_hex_string(hex); + pal.set_color(key, col); + } + catch (...) + { + } + } + + auto get_color_by_index = [&](int idx) -> const color & { + std::string key = "base0"; + if (idx < 10) + key += std::to_string(idx); + else + key += static_cast('A' + (idx - 10)); + 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)); + + pal.set_color("editor_background", get_color_by_index(0)); + pal.set_color("editor_main", get_color_by_index(7)); + pal.set_color("editor_comment", get_color_by_index(8)); + pal.set_color("editor_disabled", get_color_by_index(8)); + pal.set_color("editor_inactive", get_color_by_index(8)); + pal.set_color("editor_string", get_color_by_index(2)); + pal.set_color("editor_command", get_color_by_index(5)); + pal.set_color("editor_emphasis", get_color_by_index(11)); + pal.set_color("editor_link", get_color_by_index(4)); + pal.set_color("editor_line_number", get_color_by_index(8)); + pal.set_color("editor_selected", get_color_by_index(8)); + pal.set_color("editor_selection_inactive", get_color_by_index(8)); + pal.set_color("editor_error", get_color_by_index(1)); + pal.set_color("editor_warning", get_color_by_index(3)); + pal.set_color("editor_success", get_color_by_index(2)); + + return pal; +} + +} // namespace clrsync::core diff --git a/src/core/palette/hellwal_generator.hpp b/src/core/palette/hellwal_generator.hpp new file mode 100644 index 0000000..469915a --- /dev/null +++ b/src/core/palette/hellwal_generator.hpp @@ -0,0 +1,32 @@ +#ifndef CLRSYNC_CORE_PALETTE_HELLWAL_GENERATOR_HPP +#define CLRSYNC_CORE_PALETTE_HELLWAL_GENERATOR_HPP + +#include "core/palette/palette.hpp" +#include "generator.hpp" + +namespace clrsync::core +{ +class hellwal_generator : public generator +{ + public: + hellwal_generator() = default; + ~hellwal_generator() override = default; + + struct options + { + 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; + }; + + 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/matugen_generator.cpp b/src/core/palette/matugen_generator.cpp new file mode 100644 index 0000000..08bff0c --- /dev/null +++ b/src/core/palette/matugen_generator.cpp @@ -0,0 +1,221 @@ +#include "matugen_generator.hpp" + +#include "core/palette/color.hpp" + +#include +#include +#include +#include +#include +#include + +namespace clrsync::core +{ +static std::string run_command_capture_output(const std::string &cmd) +{ + std::array buffer; + std::string result; + FILE *pipe = popen(cmd.c_str(), "r"); + if (!pipe) + return {}; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != nullptr) + { + result += buffer.data(); + } + int rc = pclose(pipe); + (void)rc; + return result; +} + +static palette parse_matugen_output(const std::string &out, const matugen_generator::options &opts, + const std::string &pal_name, const std::string &file_path) +{ + if (out.empty()) + return {}; + + auto extract_json_object = [&](const std::string &s, + const std::string &obj_key) -> std::string { + std::regex re("\"" + obj_key + "\"\\s*:\\s*\\{"); + std::smatch m; + if (!std::regex_search(s, m, re)) + return {}; + size_t open_pos = s.find('{', m.position(0)); + if (open_pos == std::string::npos) + return {}; + size_t i = open_pos + 1; + int depth = 1; + for (; i < s.size(); ++i) + { + if (s[i] == '{') + ++depth; + else if (s[i] == '}') + { + --depth; + if (depth == 0) + break; + } + } + if (depth != 0) + return {}; + return s.substr(open_pos + 1, i - open_pos - 1); + }; + + std::string mode_section = extract_json_object(out, "colors"); + std::string target_section; + if (!mode_section.empty()) + { + std::string wrapped = std::string("{") + mode_section + std::string("}"); + target_section = extract_json_object(wrapped, opts.mode); + } + if (target_section.empty()) + { + target_section = extract_json_object(out, opts.mode); + } + const std::string &parse_src = (target_section.empty() ? out : target_section); + + std::regex kv_re("\"([a-zA-Z0-9_-]+)\"\\s*:\\s*\"(#?[A-Fa-f0-9]{6,8})\""); + + 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_background", "background"}, + {"editor_main", "on_surface"}, + {"editor_comment", "outline"}, + {"editor_string", "tertiary"}, + {"editor_emphasis", "primary"}, + {"editor_command", "secondary"}, + {"editor_link", "primary_container"}, + {"editor_error", "error"}, + {"editor_warning", "secondary"}, + {"editor_success", "primary"}, + {"editor_disabled", "outline_variant"}, + {"editor_inactive", "outline_variant"}, + {"editor_line_number", "outline"}, + {"editor_selected", "primary_container"}, + {"editor_selection_inactive", "surface_container_low"}, + + {"base00", "background"}, + {"base01", "surface_container_lowest"}, + {"base02", "surface_container_low"}, + {"base03", "outline_variant"}, + {"base04", "on_surface_variant"}, + {"base05", "on_surface"}, + {"base06", "inverse_on_surface"}, + {"base07", "surface_bright"}, + {"base08", "error"}, + {"base09", "tertiary"}, + {"base0A", "secondary"}, + {"base0B", "primary"}, + {"base0C", "tertiary_container"}, + {"base0D", "primary_container"}, + {"base0E", "secondary_container"}, + {"base0F", "on_primary_container"}, + }; + + std::unordered_map matu_kv_map; + auto begin = std::sregex_iterator(parse_src.begin(), parse_src.end(), kv_re); + auto endit = std::sregex_iterator(); + for (auto it = begin; it != endit; ++it) + { + std::smatch match = *it; + std::string key = match[1].str(); + for (auto &c : key) + if (c == '-') + c = '_'; + std::string val = match[2].str(); + matu_kv_map[key] = val; + } + + palette pal; + pal.set_name(pal_name); + pal.set_file_path(file_path); + + for (const auto &[clrsync_key, matu_key] : clrsync_to_matu) + { + auto matu_it = matu_kv_map.find(matu_key); + if (matu_it == matu_kv_map.end()) + continue; + color col; + col.from_hex_string(matu_it->second); + pal.set_color(clrsync_key, col); + } + + return pal; +} + +palette matugen_generator::generate_from_image(const std::string &image_path) +{ + options default_opts{}; + return generate_from_image(image_path, default_opts); +} + +palette matugen_generator::generate_from_image(const std::string &image_path, const options &opts) +{ + std::filesystem::path p(image_path); + std::string cmd = "matugen image '" + image_path + "'"; + if (!opts.type.empty()) + cmd += " --type '" + opts.type + "'"; + if (!opts.mode.empty()) + cmd += " --mode " + opts.mode; + if (opts.contrast != 0.0f) + cmd += " --contrast " + std::to_string(opts.contrast); + cmd += " --json hex --dry-run"; + + + std::string out = run_command_capture_output(cmd); + if (out.empty()) + return {}; + return parse_matugen_output(out, opts, std::string("matugen:") + p.filename().string(), + image_path); +} + +palette matugen_generator::generate_from_color(const std::string &color_hex) +{ + options default_opts{}; + return generate_from_color(color_hex, default_opts); +} + +palette matugen_generator::generate_from_color(const std::string &color_hex, const options &opts) +{ + std::string c = color_hex; + if (!c.empty() && c[0] == '#') + c = c.substr(1); + std::string cmd = "matugen color hex '" + c + "'"; + if (!opts.type.empty()) + cmd += " --type '" + opts.type + "'"; + if (!opts.mode.empty()) + cmd += " --mode " + opts.mode; + if (opts.contrast != 0.0f) + cmd += " --contrast " + std::to_string(opts.contrast); + cmd += " --json hex --dry-run"; + + + std::string out = run_command_capture_output(cmd); + if (out.empty()) + return {}; + + return parse_matugen_output(out, opts, std::string("matugen:color:") + color_hex, color_hex); +} +} // namespace clrsync::core \ No newline at end of file diff --git a/src/core/palette/matugen_generator.hpp b/src/core/palette/matugen_generator.hpp new file mode 100644 index 0000000..24a3e69 --- /dev/null +++ b/src/core/palette/matugen_generator.hpp @@ -0,0 +1,29 @@ +#ifndef CLRSYNC_CORE_PALETTE_MATUGEN_GENERATOR_HPP +#define CLRSYNC_CORE_PALETTE_MATUGEN_GENERATOR_HPP + +#include "core/palette/palette.hpp" +#include "generator.hpp" + +namespace clrsync::core +{ +class matugen_generator : public generator +{ + public: + matugen_generator() = default; + ~matugen_generator() override = default; + + struct options + { + std::string type = "scheme-tonal-spot"; + std::string mode = "dark"; + float contrast = 0.0f; // -1..1 + }; + + palette generate_from_image(const std::string &image_path) override; + palette generate_from_image(const std::string &image_path, const options &opts); + palette generate_from_color(const std::string &color_hex); + palette generate_from_color(const std::string &color_hex, const options &opts); +}; +} // namespace clrsync::core + +#endif diff --git a/src/gui/controllers/palette_controller.cpp b/src/gui/controllers/palette_controller.cpp index f6153c2..cccd601 100644 --- a/src/gui/controllers/palette_controller.cpp +++ b/src/gui/controllers/palette_controller.cpp @@ -61,6 +61,14 @@ void palette_controller::delete_current_palette() reload_palettes(); } +void palette_controller::import_palette(const clrsync::core::palette &pal) +{ + auto dir = clrsync::core::config::instance().palettes_path(); + m_palette_manager.save_palette_to_file(pal, dir); + reload_palettes(); + m_current_palette = pal; +} + void palette_controller::apply_current_theme() const { clrsync::core::theme_renderer theme_renderer; diff --git a/src/gui/controllers/palette_controller.hpp b/src/gui/controllers/palette_controller.hpp index 6916696..b74a4ac 100644 --- a/src/gui/controllers/palette_controller.hpp +++ b/src/gui/controllers/palette_controller.hpp @@ -25,6 +25,7 @@ class palette_controller void save_current_palette(); void delete_current_palette(); void apply_current_theme() const; + void import_palette(const clrsync::core::palette &pal); void set_color(const std::string &key, const clrsync::core::color &color); private: diff --git a/src/gui/views/color_scheme_editor.cpp b/src/gui/views/color_scheme_editor.cpp index daed7b4..6b48889 100644 --- a/src/gui/views/color_scheme_editor.cpp +++ b/src/gui/views/color_scheme_editor.cpp @@ -1,12 +1,17 @@ #include "color_scheme_editor.hpp" +#include "core/palette/hellwal_generator.hpp" +#include "core/palette/matugen_generator.hpp" #include "gui/controllers/theme_applier.hpp" -#include "gui/widgets/dialogs.hpp" -#include "gui/widgets/palette_selector.hpp" -#include "gui/widgets/input_dialog.hpp" +#include "gui/platform/file_browser.hpp" #include "gui/widgets/action_buttons.hpp" +#include "gui/widgets/dialogs.hpp" +#include "gui/widgets/input_dialog.hpp" +#include "gui/widgets/palette_selector.hpp" #include "imgui.h" #include "settings_window.hpp" #include "template_editor.hpp" +#include +#include #include color_scheme_editor::color_scheme_editor() @@ -22,7 +27,7 @@ color_scheme_editor::color_scheme_editor() { std::cout << "WARNING: No palette loaded, skipping theme application\n"; } - + setup_widgets(); } @@ -89,16 +94,251 @@ void color_scheme_editor::render_controls() if (ImGui::Button(" + New ")) { - m_new_palette_dialog.open("New Palette", "Enter a name for the new palette:", "Palette name..."); + m_new_palette_dialog.open("New Palette", + "Enter a name for the new palette:", "Palette name..."); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Create a new palette"); m_new_palette_dialog.render(); + m_generate_dialog.render(); ImGui::SameLine(); m_action_buttons.render(current); + ImGui::SameLine(); + ImGui::SameLine(); + 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:"); + const char *generators[] = {"hellwal", "matugen"}; + ImGui::SameLine(); + ImGui::SetNextItemWidth(160.0f); + ImGui::Combo("##gen_select", &m_generator_idx, generators, IM_ARRAYSIZE(generators)); + + if (m_generator_idx == 0) // 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, + {"png", "jpg", "jpeg", "bmp"}); + 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 (m_generator_idx == 1) // 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, + {"png", "jpg", "jpeg", "bmp"}); + 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::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(); + if (ImGui::Button("Generate", ImVec2(120, 0))) + { + try + { + if (m_generator_idx == 0) + { + clrsync::core::hellwal_generator gen; + 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", "", + {"png", "jpg", "jpeg", "bmp"}); + } + + 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 (m_generator_idx == 1) + { + clrsync::core::matugen_generator gen; + clrsync::core::matugen_generator::options opts; + opts.mode = m_matugen_mode; + opts.type = m_matugen_type; + opts.contrast = m_matugen_contrast; + + 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", "", {"png", "jpg", "jpeg", "bmp"}); + } + 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; + } + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + if (m_show_delete_confirmation) { ImGui::OpenPopup("Delete Palette?"); @@ -106,10 +346,10 @@ void color_scheme_editor::render_controls() } clrsync::gui::widgets::delete_confirmation_dialog("Delete Palette?", current.name(), "palette", - current, [this]() { - m_controller.delete_current_palette(); - apply_themes(); - }); + current, [this]() { + m_controller.delete_current_palette(); + apply_themes(); + }); ImGui::PopStyleVar(2); } @@ -120,32 +360,46 @@ void color_scheme_editor::setup_widgets() m_controller.select_palette(name); apply_themes(); }); - + m_new_palette_dialog.set_on_submit([this](const std::string &name) { m_controller.create_palette(name); m_controller.select_palette(name); apply_themes(); }); - - m_action_buttons.add_button({ - " Save ", - "Save current palette to file", - [this]() { m_controller.save_current_palette(); } + + m_generate_dialog.set_on_submit([this](const std::string &image_path) { + try + { + clrsync::core::hellwal_generator gen; + auto pal = gen.generate_from_image(image_path); + 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(); + } + catch (const std::exception &e) + { + std::cerr << "Failed to generate palette: " << e.what() << std::endl; + } }); - - m_action_buttons.add_button({ - " Delete ", - "Delete current palette", - [this]() { m_show_delete_confirmation = true; }, - true, - true - }); - - m_action_buttons.add_button({ - " Apply Theme ", - "Apply current palette to all enabled templates", - [this]() { m_controller.apply_current_theme(); } - }); - + m_generate_dialog.set_path_browse_callback( + [this](const std::string ¤t_path) -> std::string { + return file_dialogs::open_file_dialog("Select Image", current_path, + {"png", "jpg", "jpeg", "bmp"}); + }); + + m_action_buttons.add_button({" Save ", "Save current palette to file", + [this]() { m_controller.save_current_palette(); }}); + + m_action_buttons.add_button({" Delete ", "Delete current palette", + [this]() { m_show_delete_confirmation = true; }, true, true}); + + m_action_buttons.add_button({" Apply Theme ", "Apply current palette to all enabled templates", + [this]() { m_controller.apply_current_theme(); }}); + m_action_buttons.set_spacing(16.0f); } diff --git a/src/gui/views/color_scheme_editor.hpp b/src/gui/views/color_scheme_editor.hpp index e113ca7..e630330 100644 --- a/src/gui/views/color_scheme_editor.hpp +++ b/src/gui/views/color_scheme_editor.hpp @@ -4,9 +4,9 @@ #include "gui/controllers/palette_controller.hpp" #include "gui/views/color_table_renderer.hpp" #include "gui/views/preview_renderer.hpp" -#include "gui/widgets/palette_selector.hpp" -#include "gui/widgets/input_dialog.hpp" #include "gui/widgets/action_buttons.hpp" +#include "gui/widgets/input_dialog.hpp" +#include "gui/widgets/palette_selector.hpp" class template_editor; class settings_window; @@ -43,9 +43,30 @@ class color_scheme_editor template_editor *m_template_editor{nullptr}; settings_window *m_settings_window{nullptr}; bool m_show_delete_confirmation{false}; - + clrsync::gui::widgets::palette_selector m_palette_selector; clrsync::gui::widgets::input_dialog m_new_palette_dialog; + clrsync::gui::widgets::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}; + // 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"}; 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 3713c86..64dc945 100644 --- a/src/gui/views/color_table_renderer.cpp +++ b/src/gui/views/color_table_renderer.cpp @@ -118,11 +118,8 @@ void color_table_renderer::render(const clrsync::core::palette ¤t, ImGui::Text("Filter:"); ImGui::SameLine(); ImGui::SetNextItemWidth(200); - bool filter_changed = - ImGui::InputTextWithHint("##color_filter", - "Search colors...", - m_filter_text, - sizeof(m_filter_text)); + bool filter_changed = ImGui::InputTextWithHint("##color_filter", "Search colors...", + m_filter_text, sizeof(m_filter_text)); if (m_filter_text[0] != '\0') { @@ -157,7 +154,8 @@ void color_table_renderer::render(const clrsync::core::palette ¤t, if (!has_matches) return; - ImGui::PushStyleColor(ImGuiCol_Text, clrsync::gui::widgets::palette_color(current, "accent")); + ImGui::PushStyleColor(ImGuiCol_Text, + clrsync::gui::widgets::palette_color(current, "accent")); bool header_open = ImGui::TreeNodeEx(title, ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::PopStyleColor(); @@ -186,7 +184,7 @@ void color_table_renderer::render(const clrsync::core::palette ¤t, draw_table("General UI", "##general_ui", {"background", "on_background", "surface", "on_surface", "surface_variant", - "on_surface_variant", "foreground", "cursor", "accent"}); + "on_surface_variant", "foreground", "cursor", "accent", "accent_secondary"}); draw_table("Borders", "##borders", {"border_focused", "border"}); diff --git a/src/gui/widgets/input_dialog.cpp b/src/gui/widgets/input_dialog.cpp index abb4171..2c916c2 100644 --- a/src/gui/widgets/input_dialog.cpp +++ b/src/gui/widgets/input_dialog.cpp @@ -1,6 +1,7 @@ #include "input_dialog.hpp" #include "imgui.h" #include +#include namespace clrsync::gui::widgets { @@ -41,6 +42,21 @@ bool input_dialog::render() IM_ARRAYSIZE(m_input_buffer), ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::SameLine(); + if (ImGui::Button("Browse")) + { + if (m_on_browse) + { + std::string initial = m_input_buffer; + std::string res = m_on_browse(initial); + if (!res.empty()) + { + std::strncpy(m_input_buffer, res.c_str(), IM_ARRAYSIZE(m_input_buffer) - 1); + m_input_buffer[IM_ARRAYSIZE(m_input_buffer) - 1] = '\0'; + } + } + } + ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); @@ -108,4 +124,9 @@ void input_dialog::set_on_cancel(const std::function &callback) m_on_cancel = callback; } +void input_dialog::set_path_browse_callback(const std::function &callback) +{ + m_on_browse = callback; +} + } // namespace clrsync::gui::widgets \ No newline at end of file diff --git a/src/gui/widgets/input_dialog.hpp b/src/gui/widgets/input_dialog.hpp index ae0a70f..11e128a 100644 --- a/src/gui/widgets/input_dialog.hpp +++ b/src/gui/widgets/input_dialog.hpp @@ -18,6 +18,7 @@ class input_dialog void set_on_submit(const std::function &callback); void set_on_cancel(const std::function &callback); + void set_path_browse_callback(const std::function &callback); bool is_open() const { return m_is_open; } @@ -31,6 +32,7 @@ class input_dialog std::function m_on_submit; std::function m_on_cancel; + std::function m_on_browse; }; } // namespace clrsync::gui::widgets