mirror of
https://github.com/obsqrbtz/clrsync.git
synced 2026-04-08 20:19:04 +03:00
init
This commit is contained in:
146
src/cli/main.cpp
Normal file
146
src/cli/main.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#include <argparse/argparse.hpp>
|
||||
|
||||
#include <core/utils.hpp>
|
||||
#include <core/config/config.hpp>
|
||||
#include <core/io/toml_file.hpp>
|
||||
#include <core/palette/palette_file.hpp>
|
||||
#include <core/palette/palette_manager.hpp>
|
||||
#include <core/theme/theme_template.hpp>
|
||||
#include <core/theme/theme_renderer.hpp>
|
||||
#include <core/version.hpp>
|
||||
|
||||
void handle_show_vars()
|
||||
{
|
||||
clrsync::core::print_color_keys();
|
||||
}
|
||||
|
||||
void handle_list_themes()
|
||||
{
|
||||
auto palette_manager = clrsync::core::palette_manager<clrsync::core::io::toml_file>();
|
||||
palette_manager.load_palettes_from_directory(
|
||||
clrsync::core::config::instance().palettes_path());
|
||||
|
||||
const auto &palettes = palette_manager.palettes();
|
||||
std::cout << "Available themes:" << std::endl;
|
||||
for (const auto &p : palettes)
|
||||
{
|
||||
std::cout << " - " << p.first << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int handle_apply_theme(const argparse::ArgumentParser &program, const std::string &default_theme)
|
||||
{
|
||||
clrsync::core::theme_renderer<clrsync::core::io::toml_file> renderer;
|
||||
std::string theme_identifier;
|
||||
|
||||
if (program.is_used("--theme"))
|
||||
{
|
||||
theme_identifier = program.get<std::string>("--theme");
|
||||
renderer.apply_theme(theme_identifier);
|
||||
}
|
||||
else if (program.is_used("--path"))
|
||||
{
|
||||
theme_identifier = program.get<std::string>("--path");
|
||||
renderer.apply_theme_from_path(theme_identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (default_theme.empty())
|
||||
{
|
||||
std::cerr << "Default theme is not set or missing." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
theme_identifier = default_theme;
|
||||
renderer.apply_theme(theme_identifier);
|
||||
}
|
||||
|
||||
std::cout << "Applied theme " << theme_identifier << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void initialize_config(const std::string &config_path)
|
||||
{
|
||||
auto conf = std::make_unique<clrsync::core::io::toml_file>(config_path);
|
||||
clrsync::core::config::instance().initialize(std::move(conf));
|
||||
}
|
||||
|
||||
void setup_argument_parser(argparse::ArgumentParser &program)
|
||||
{
|
||||
program.add_argument("-a", "--apply")
|
||||
.help("applies default theme")
|
||||
.flag();
|
||||
|
||||
program.add_argument("-c", "--config")
|
||||
.default_value(clrsync::core::get_default_config_path())
|
||||
.help("sets config file path")
|
||||
.metavar("PATH");
|
||||
|
||||
program.add_argument("-l", "--list-themes")
|
||||
.help("lists available themes")
|
||||
.flag();
|
||||
|
||||
program.add_argument("-s", "--show-vars")
|
||||
.help("shows color keys")
|
||||
.flag();
|
||||
|
||||
auto &group = program.add_mutually_exclusive_group();
|
||||
group.add_argument("-t", "--theme")
|
||||
.help("sets theme <theme_name> to apply");
|
||||
group.add_argument("-p", "--path")
|
||||
.help("sets theme file <path/to/theme> to apply");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
argparse::ArgumentParser program("clrsync", clrsync::core::version_string());
|
||||
setup_argument_parser(program);
|
||||
|
||||
try
|
||||
{
|
||||
program.parse_args(argc, argv);
|
||||
}
|
||||
catch (const std::exception &err)
|
||||
{
|
||||
std::cerr << err.what() << std::endl;
|
||||
std::cerr << program;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string config_path = program.get<std::string>("--config");
|
||||
|
||||
try
|
||||
{
|
||||
initialize_config(config_path);
|
||||
}
|
||||
catch (const std::exception &err)
|
||||
{
|
||||
std::cerr << "Error loading config: " << err.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (program.is_used("--show-vars"))
|
||||
{
|
||||
handle_show_vars();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (program.is_used("--list-themes"))
|
||||
{
|
||||
handle_list_themes();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (program.is_used("--apply"))
|
||||
{
|
||||
const std::string default_theme = clrsync::core::config::instance().default_theme();
|
||||
return handle_apply_theme(program, default_theme);
|
||||
}
|
||||
|
||||
std::cout << program << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
169
src/core/config/config.cpp
Normal file
169
src/core/config/config.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "config.hpp"
|
||||
|
||||
#include <core/palette/color.hpp>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
config &config::instance()
|
||||
{
|
||||
static config inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
void config::initialize(std::unique_ptr<clrsync::core::io::file> file)
|
||||
{
|
||||
copy_default_configs();
|
||||
m_file = std::move(file);
|
||||
if (m_file)
|
||||
if (!m_file->parse())
|
||||
throw std::runtime_error{"Could not parse config file"};
|
||||
}
|
||||
|
||||
std::filesystem::path config::get_user_config_dir()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (const char *appdata = std::getenv("APPDATA"))
|
||||
return fs::path(appdata) / "clrsync";
|
||||
else
|
||||
return fs::path("C:/clrsync");
|
||||
#else
|
||||
if (const char *xdg = std::getenv("XDG_CONFIG_HOME"))
|
||||
return std::filesystem::path(xdg) / "clrsync";
|
||||
else if (const char *home = std::getenv("HOME"))
|
||||
return std::filesystem::path(home) / ".config/clrsync";
|
||||
else
|
||||
return std::filesystem::path("/tmp/clrsync");
|
||||
#endif
|
||||
}
|
||||
|
||||
void config::copy_default_configs()
|
||||
{
|
||||
std::filesystem::path user_config = get_user_config_dir();
|
||||
|
||||
if (!std::filesystem::exists(user_config))
|
||||
{
|
||||
std::filesystem::create_directories(user_config);
|
||||
|
||||
std::filesystem::path default_dir = CLRSYNC_DATADIR;
|
||||
std::filesystem::copy(default_dir / "config.toml", user_config / "config.toml");
|
||||
std::filesystem::copy(default_dir / "templates", user_config / "templates",
|
||||
std::filesystem::copy_options::recursive);
|
||||
std::filesystem::copy(default_dir / "palettes", user_config / "palettes",
|
||||
std::filesystem::copy_options::recursive);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &config::palettes_path()
|
||||
{
|
||||
if (m_palettes_dir.empty() && m_file)
|
||||
m_palettes_dir = m_file->get_string_value("general", "palettes_path");
|
||||
return m_palettes_dir;
|
||||
}
|
||||
|
||||
const std::string config::default_theme() const
|
||||
{
|
||||
if (m_file)
|
||||
return m_file->get_string_value("general", "default_theme");
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::string config::font() const
|
||||
{
|
||||
if (m_file)
|
||||
return m_file->get_string_value("general", "font");
|
||||
return {};
|
||||
}
|
||||
|
||||
const uint32_t config::font_size() const
|
||||
{
|
||||
if (m_file)
|
||||
return m_file->get_uint_value("general", "font_size");
|
||||
return 14;
|
||||
}
|
||||
|
||||
void config::set_default_theme(const std::string &theme)
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
m_file->set_value("general", "default_theme", theme);
|
||||
m_file->save_file();
|
||||
}
|
||||
}
|
||||
|
||||
void config::set_palettes_path(const std::string &path)
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
m_file->set_value("general", "palettes_path", path);
|
||||
m_file->save_file();
|
||||
}
|
||||
}
|
||||
|
||||
void config::set_font(const std::string &font)
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
m_file->set_value("general", "font", font);
|
||||
m_file->save_file();
|
||||
}
|
||||
}
|
||||
void config::set_font_size(int font_size)
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
m_file->set_value("general", "font_size", font_size);
|
||||
m_file->save_file();
|
||||
}
|
||||
}
|
||||
|
||||
void config::update_template(const std::string &key,
|
||||
const clrsync::core::theme_template &theme_template)
|
||||
{
|
||||
m_themes[key] = theme_template;
|
||||
m_file->set_value("templates." + key, "input_path", theme_template.template_path());
|
||||
m_file->set_value("templates." + key, "output_path", theme_template.output_path());
|
||||
m_file->set_value("templates." + key, "enabled", theme_template.enabled());
|
||||
m_file->set_value("templates." + key, "reload_cmd", theme_template.reload_command());
|
||||
m_file->save_file();
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, clrsync::core::theme_template> config::templates()
|
||||
{
|
||||
if (m_themes.empty() && m_file)
|
||||
{
|
||||
auto themes = m_file->get_table("templates");
|
||||
for (const auto &t : themes)
|
||||
{
|
||||
auto current = m_file->get_table("templates." + t.first);
|
||||
clrsync::core::theme_template theme(t.first,
|
||||
std::get<std::string>(current["input_path"]),
|
||||
std::get<std::string>(current["output_path"]));
|
||||
if (std::holds_alternative<bool>(current["enabled"]))
|
||||
{
|
||||
theme.set_enabled(std::get<bool>(current["enabled"]));
|
||||
}
|
||||
else
|
||||
{
|
||||
theme.set_enabled(false);
|
||||
}
|
||||
theme.set_reload_command(std::get<std::string>(current["reload_cmd"]));
|
||||
theme.load_template();
|
||||
m_themes.insert({theme.name(), theme});
|
||||
}
|
||||
}
|
||||
return m_themes;
|
||||
}
|
||||
|
||||
const clrsync::core::theme_template &config::template_by_name(const std::string &name) const
|
||||
{
|
||||
auto it = m_themes.find(name);
|
||||
if (it != m_themes.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
throw std::runtime_error("Template not found: " + name);
|
||||
}
|
||||
|
||||
} // namespace clrsync::core
|
||||
48
src/core/config/config.hpp
Normal file
48
src/core/config/config.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef CLRSYNC_CORE_CONFIG_HPP
|
||||
#define CLRSYNC_CORE_CONFIG_HPP
|
||||
|
||||
#include <core/io/file.hpp>
|
||||
#include <core/theme/theme_template.hpp>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
class config
|
||||
{
|
||||
public:
|
||||
static config &instance();
|
||||
|
||||
void initialize(std::unique_ptr<clrsync::core::io::file> file);
|
||||
|
||||
const std::string font() const;
|
||||
const uint32_t font_size() const;
|
||||
const std::string &palettes_path();
|
||||
const std::string default_theme() const;
|
||||
const std::unordered_map<std::string, clrsync::core::theme_template> templates();
|
||||
const clrsync::core::theme_template &template_by_name(const std::string &name) const;
|
||||
std::filesystem::path get_user_config_dir();
|
||||
|
||||
|
||||
void set_default_theme(const std::string &theme);
|
||||
void set_palettes_path(const std::string &path);
|
||||
void set_font(const std::string &font);
|
||||
void set_font_size(int font_size);
|
||||
|
||||
void update_template(const std::string &key,
|
||||
const clrsync::core::theme_template &theme_template);
|
||||
|
||||
private:
|
||||
config() = default;
|
||||
config(const config &) = delete;
|
||||
config &operator=(const config &) = delete;
|
||||
|
||||
std::string m_palettes_dir{};
|
||||
std::unique_ptr<io::file> m_file;
|
||||
std::unordered_map<std::string, theme_template> m_themes{};
|
||||
void copy_default_configs();
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
45
src/core/io/file.hpp
Normal file
45
src/core/io/file.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef CLRSYNC_CORE_IO_FILE_HPP
|
||||
#define CLRSYNC_CORE_IO_FILE_HPP
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
using value_type = std::variant<std::string, uint32_t, int, bool>;
|
||||
|
||||
namespace clrsync::core::io
|
||||
{
|
||||
class file
|
||||
{
|
||||
public:
|
||||
file() = default;
|
||||
file(std::string path) {};
|
||||
virtual bool parse() { return false; };
|
||||
virtual const std::string get_string_value(const std::string §ion,
|
||||
const std::string &key) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual uint32_t get_uint_value(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual uint32_t get_bool_value(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual std::map<std::string, value_type> get_table(const std::string §ion_path) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual void set_value(const std::string §ion, const std::string &key,
|
||||
const value_type &value)
|
||||
{
|
||||
insert_or_update_value(section, key, value);
|
||||
}
|
||||
virtual void insert_or_update_value(const std::string §ion, const std::string &key,
|
||||
const value_type &value) {};
|
||||
virtual void save_file() {};
|
||||
};
|
||||
} // namespace clrsync::core::io
|
||||
#endif
|
||||
124
src/core/io/toml_file.cpp
Normal file
124
src/core/io/toml_file.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "toml_file.hpp"
|
||||
#include "core/utils.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
namespace clrsync::core::io
|
||||
{
|
||||
toml_file::toml_file(std::string path)
|
||||
{
|
||||
m_path = expand_user(path);
|
||||
}
|
||||
|
||||
bool toml_file::parse()
|
||||
{
|
||||
if (!std::filesystem::exists(m_path))
|
||||
return false;
|
||||
m_file = toml::parse_file(m_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string toml_file::get_string_value(const std::string §ion,
|
||||
const std::string &key) const
|
||||
{
|
||||
return m_file[section][key].value_or("");
|
||||
}
|
||||
|
||||
uint32_t toml_file::get_uint_value(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
return m_file[section][key].value_or(0);
|
||||
}
|
||||
|
||||
uint32_t toml_file::get_bool_value(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
return m_file[section][key].value_or(false);
|
||||
}
|
||||
|
||||
std::map<std::string, value_type> toml_file::get_table(const std::string §ion_path) const
|
||||
{
|
||||
auto parts = split(section_path, '.');
|
||||
const toml::table *tbl = m_file.as_table();
|
||||
|
||||
for (const auto &part : parts)
|
||||
{
|
||||
if (auto subtbl = tbl->at(part).as_table())
|
||||
tbl = subtbl;
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
std::map<std::string, value_type> result;
|
||||
|
||||
for (const auto &p : *tbl)
|
||||
{
|
||||
const auto &val = p.second;
|
||||
|
||||
if (auto b = val.value<bool>())
|
||||
result[std::string(p.first.str())] = *b;
|
||||
else if (auto s = val.value<std::string>())
|
||||
result[std::string(p.first.str())] = *s;
|
||||
else if (auto i = val.value<int64_t>())
|
||||
result[std::string(p.first.str())] = static_cast<uint32_t>(*i);
|
||||
else if (auto d = val.value<double>())
|
||||
result[std::string(p.first.str())] = static_cast<uint32_t>(*d);
|
||||
else
|
||||
result[std::string(p.first.str())] = {}; // fallback for unsupported types
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void toml_file::insert_or_update_value(const std::string §ion, const std::string &key,
|
||||
const value_type &value)
|
||||
{
|
||||
toml::table *tbl = m_file.as_table();
|
||||
|
||||
std::stringstream ss(section);
|
||||
std::string part;
|
||||
|
||||
while (std::getline(ss, part, '.'))
|
||||
{
|
||||
auto *sub = (*tbl)[part].as_table();
|
||||
if (!sub)
|
||||
{
|
||||
(*tbl).insert_or_assign(part, toml::table{});
|
||||
sub = (*tbl)[part].as_table();
|
||||
}
|
||||
tbl = sub;
|
||||
}
|
||||
|
||||
std::visit([&](auto &&v) { tbl->insert_or_assign(key, v); }, value);
|
||||
}
|
||||
|
||||
void toml_file::save_file()
|
||||
{
|
||||
std::filesystem::create_directories(std::filesystem::path(m_path).parent_path());
|
||||
std::ofstream stream(m_path, std::ios::binary);
|
||||
stream << m_file;
|
||||
}
|
||||
|
||||
std::vector<std::string> toml_file::split(const std::string &s, char delim) const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::string current;
|
||||
for (char c : s)
|
||||
{
|
||||
if (c == delim)
|
||||
{
|
||||
if (!current.empty())
|
||||
{
|
||||
result.push_back(current);
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current += c;
|
||||
}
|
||||
}
|
||||
if (!current.empty())
|
||||
result.push_back(current);
|
||||
return result;
|
||||
}
|
||||
} // namespace clrsync::core::io
|
||||
31
src/core/io/toml_file.hpp
Normal file
31
src/core/io/toml_file.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef CLRSYNC_CORE_IO_TOML_FILE_HPP
|
||||
#define CLRSYNC_CORE_IO_TOML_FILE_HPP
|
||||
#include <core/io/file.hpp>
|
||||
#include <string>
|
||||
#include <toml/toml.hpp>
|
||||
|
||||
namespace clrsync::core::io
|
||||
{
|
||||
class toml_file : public file
|
||||
{
|
||||
public:
|
||||
explicit toml_file(std::string path);
|
||||
bool parse() override;
|
||||
const std::string get_string_value(const std::string §ion,
|
||||
const std::string &key) const override;
|
||||
uint32_t get_uint_value(const std::string §ion, const std::string &key) const override;
|
||||
uint32_t get_bool_value(const std::string §ion, const std::string &key) const override;
|
||||
std::map<std::string, value_type> get_table(const std::string §ion_path) const override;
|
||||
void insert_or_update_value(const std::string §ion, const std::string &key,
|
||||
const value_type &value) override;
|
||||
void save_file() override;
|
||||
|
||||
private:
|
||||
toml::parse_result m_file{};
|
||||
std::string m_path{};
|
||||
|
||||
std::vector<std::string> split(const std::string &s, char delim) const;
|
||||
};
|
||||
} // namespace clrsync::core::io
|
||||
|
||||
#endif
|
||||
203
src/core/palette/color.cpp
Normal file
203
src/core/palette/color.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "color.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
uint32_t color::hex() const
|
||||
{
|
||||
return m_hex;
|
||||
}
|
||||
|
||||
rgb color::to_rgb() const
|
||||
{
|
||||
rgb result{};
|
||||
result.r = (m_hex >> 24) & 0xFF;
|
||||
result.g = (m_hex >> 16) & 0xFF;
|
||||
result.b = m_hex >> 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
rgba color::to_rgba() const
|
||||
{
|
||||
rgba result{};
|
||||
result.r = (m_hex >> 24) & 0xFF;
|
||||
result.g = (m_hex >> 16) & 0xFF;
|
||||
result.b = (m_hex >> 8) & 0xFF;
|
||||
result.a = m_hex & 0xFF;
|
||||
return result;
|
||||
}
|
||||
|
||||
hsl color::to_hsl() const
|
||||
{
|
||||
rgb c = to_rgb();
|
||||
float r = c.r / 255.0f;
|
||||
float g = c.g / 255.0f;
|
||||
float b = c.b / 255.0f;
|
||||
|
||||
float max = std::max({r, g, b});
|
||||
float min = std::min({r, g, b});
|
||||
float h, s, l = (max + min) / 2.0f;
|
||||
|
||||
if (max == min)
|
||||
{
|
||||
h = s = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
float d = max - min;
|
||||
s = l > 0.5f ? d / (2.0f - max - min) : d / (max + min);
|
||||
if (max == r)
|
||||
{
|
||||
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
|
||||
}
|
||||
else if (max == g)
|
||||
{
|
||||
h = (b - r) / d + 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
h = (r - g) / d + 4.0f;
|
||||
}
|
||||
h /= 6.0f;
|
||||
}
|
||||
|
||||
return hsl{h * 360.0f, s, l};
|
||||
}
|
||||
|
||||
hsla color::to_hsla() const
|
||||
{
|
||||
rgba c = to_rgba();
|
||||
float r = c.r / 255.0f;
|
||||
float g = c.g / 255.0f;
|
||||
float b = c.b / 255.0f;
|
||||
float a = c.a / 255.0f;
|
||||
|
||||
float max = std::max({r, g, b});
|
||||
float min = std::min({r, g, b});
|
||||
float h, s, l = (max + min) / 2.0f;
|
||||
|
||||
if (max == min)
|
||||
{
|
||||
h = s = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
float d = max - min;
|
||||
s = l > 0.5f ? d / (2.0f - max - min) : d / (max + min);
|
||||
if (max == r)
|
||||
{
|
||||
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
|
||||
}
|
||||
else if (max == g)
|
||||
{
|
||||
h = (b - r) / d + 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
h = (r - g) / d + 4.0f;
|
||||
}
|
||||
h /= 6.0f;
|
||||
}
|
||||
|
||||
return hsla{h * 360.0f, s, l, a};
|
||||
}
|
||||
|
||||
void color::from_hex_string(const std::string &str)
|
||||
{
|
||||
if (str.empty() || str[0] != '#')
|
||||
throw std::invalid_argument("Invalid hex color format");
|
||||
|
||||
if (str.size() == 7) {
|
||||
uint32_t rgb = static_cast<uint32_t>(std::stoul(str.substr(1), nullptr, 16));
|
||||
m_hex = (rgb << 8) | 0xFF;
|
||||
}
|
||||
else if (str.size() == 9) {
|
||||
m_hex = static_cast<uint32_t>(std::stoul(str.substr(1), nullptr, 16));
|
||||
}
|
||||
else {
|
||||
throw std::invalid_argument("Invalid hex color format");
|
||||
}
|
||||
}
|
||||
|
||||
const std::string color::to_hex_string() const
|
||||
{
|
||||
char buffer[8];
|
||||
std::snprintf(buffer, sizeof(buffer), "#%06X", m_hex >> 8);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
const std::string color::to_hex_string_with_alpha() const
|
||||
{
|
||||
char buffer[10];
|
||||
std::snprintf(buffer, sizeof(buffer), "#%08X", m_hex);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
std::string color::format(const std::string& field) const
|
||||
{
|
||||
auto rgb = to_rgb();
|
||||
auto rgba = to_rgba();
|
||||
auto hslv = to_hsl();
|
||||
auto hslav = to_hsla();
|
||||
|
||||
if (field == "hex") return to_hex_string();
|
||||
if (field == "hex_stripped") {
|
||||
auto s = to_hex_string();
|
||||
return s.substr(1);
|
||||
}
|
||||
|
||||
if (field == "hexa") return to_hex_string_with_alpha();
|
||||
if (field == "hexa_stripped") {
|
||||
auto s = to_hex_string_with_alpha();
|
||||
return s.substr(1);
|
||||
}
|
||||
|
||||
if (field == "r") return std::to_string(rgb.r);
|
||||
if (field == "g") return std::to_string(rgb.g);
|
||||
if (field == "b") return std::to_string(rgb.b);
|
||||
|
||||
if (field == "a") {
|
||||
float af = rgba.a / 255.0f;
|
||||
return std::format("{:.2f}", af);
|
||||
}
|
||||
|
||||
if (field == "rgb")
|
||||
return std::format("rgb({},{},{})", rgb.r, rgb.g, rgb.b);
|
||||
|
||||
if (field == "rgba")
|
||||
return std::format("rgba({},{},{},{:.2f})",
|
||||
rgba.r, rgba.g, rgba.b,
|
||||
rgba.a / 255.0f);
|
||||
|
||||
if (field == "h")
|
||||
return std::format("{:.0f}", hslv.h);
|
||||
if (field == "s")
|
||||
return std::format("{:.2f}", hslv.s);
|
||||
if (field == "l")
|
||||
return std::format("{:.2f}", hslv.l);
|
||||
|
||||
// TODO: probably unneded
|
||||
if (field == "hsla_a")
|
||||
return std::format("{:.2f}", hslav.a);
|
||||
|
||||
if (field == "hsl")
|
||||
return std::format("hsl({:.0f},{:.2f},{:.2f})",
|
||||
hslv.h, hslv.s, hslv.l);
|
||||
|
||||
if (field == "hsla")
|
||||
return std::format("hsla({:.0f},{:.2f},{:.2f},{:.2f})",
|
||||
hslav.h, hslav.s, hslav.l, hslav.a);
|
||||
|
||||
throw std::runtime_error("Unknown color format: " + field);
|
||||
}
|
||||
|
||||
void color::set(uint32_t hex)
|
||||
{
|
||||
m_hex = hex;
|
||||
}
|
||||
} // namespace clrsync::core
|
||||
72
src/core/palette/color.hpp
Normal file
72
src/core/palette/color.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_COLOR_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_COLOR_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
struct rgb
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
struct rgba
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
};
|
||||
|
||||
struct hsl
|
||||
{
|
||||
float h;
|
||||
float s;
|
||||
float l;
|
||||
};
|
||||
|
||||
struct hsla
|
||||
{
|
||||
float h;
|
||||
float s;
|
||||
float l;
|
||||
float a;
|
||||
};
|
||||
|
||||
class color
|
||||
{
|
||||
public:
|
||||
color() = default;
|
||||
explicit color(uint32_t hex) : m_hex(hex)
|
||||
{
|
||||
}
|
||||
uint32_t hex() const;
|
||||
|
||||
rgb to_rgb() const;
|
||||
|
||||
rgba to_rgba() const;
|
||||
|
||||
hsl to_hsl() const;
|
||||
|
||||
hsla to_hsla() const;
|
||||
|
||||
void from_hex_string(const std::string &str);
|
||||
|
||||
const std::string to_hex_string() const;
|
||||
|
||||
const std::string to_hex_string_with_alpha() const;
|
||||
|
||||
std::string format(const std::string& field) const;
|
||||
|
||||
void set(uint32_t hex);
|
||||
|
||||
private:
|
||||
uint32_t m_hex = 0x00000000;
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
55
src/core/palette/color_keys.hpp
Normal file
55
src/core/palette/color_keys.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_COLOR_KEYS_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_COLOR_KEYS_HPP
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
constexpr const char *COLOR_KEYS[] = {
|
||||
// UI / Surfaces
|
||||
"background", // main window / editor background
|
||||
"surface", // panels, cards
|
||||
"surface_variant", // alternate rows, subtle panels
|
||||
"foreground", // main text
|
||||
"foreground_secondary", // secondary text / hints
|
||||
"accent", // buttons, highlights, selection
|
||||
"outline", // borders, outlines
|
||||
"shadow", // drop shadows / depth
|
||||
"cursor", // caret / text cursor
|
||||
|
||||
// Editor-specific surfaces
|
||||
"editor_background", "sidebar_background", "popup_background", "floating_window_background",
|
||||
"menu_option_background",
|
||||
|
||||
// Editor text roles
|
||||
"text_main", "text_emphasis", "text_command", "text_inactive", "text_disabled",
|
||||
"text_line_number", "text_selected", "text_selection_inactive",
|
||||
|
||||
// Editor / Window borders
|
||||
"border_window", "border_focused", "border_emphasized",
|
||||
|
||||
// Syntax highlighting
|
||||
"syntax_function", "syntax_error", "syntax_keyword", "syntax_special_keyword",
|
||||
"syntax_operator",
|
||||
|
||||
// Semantic text colors
|
||||
"text_error", "text_warning", "text_link", "text_comment", "text_string", "text_success",
|
||||
"warning_emphasis", "foreground_emphasis",
|
||||
|
||||
// Extra
|
||||
"terminal_gray",
|
||||
|
||||
// Semantic / Status
|
||||
"error", "warning", "success", "info",
|
||||
|
||||
// Terminal colors (normal)
|
||||
"term_black", "term_red", "term_green", "term_yellow", "term_blue", "term_magenta", "term_cyan",
|
||||
"term_white",
|
||||
|
||||
// Terminal colors (bright)
|
||||
"term_black_bright", "term_red_bright", "term_green_bright", "term_yellow_bright",
|
||||
"term_blue_bright", "term_magenta_bright", "term_cyan_bright", "term_white_bright"};
|
||||
|
||||
constexpr size_t NUM_COLOR_KEYS = std::size(COLOR_KEYS);
|
||||
} // namespace clrsync::core
|
||||
#endif
|
||||
64
src/core/palette/palette.hpp
Normal file
64
src/core/palette/palette.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_PALETTE_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_PALETTE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <core/palette/color.hpp>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
class palette
|
||||
{
|
||||
public:
|
||||
palette() = default;
|
||||
palette(const std::string &name) : m_name(name)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &file_path() const
|
||||
{
|
||||
return m_file_path;
|
||||
}
|
||||
|
||||
void set_file_path(const std::string &path)
|
||||
{
|
||||
m_file_path = path;
|
||||
}
|
||||
|
||||
const std::string &name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
const color &get_color(const std::string &key) const
|
||||
{
|
||||
auto it = m_colors.find(key);
|
||||
if (it != m_colors.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
static color default_color{};
|
||||
return default_color;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, color> &colors() const
|
||||
{
|
||||
return m_colors;
|
||||
}
|
||||
|
||||
void set_name(const std::string &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
void set_color(const std::string &key, const color &col)
|
||||
{
|
||||
m_colors[key] = col;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name{};
|
||||
std::unordered_map<std::string, color> m_colors{};
|
||||
std::string m_file_path{};
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
#endif
|
||||
75
src/core/palette/palette_file.hpp
Normal file
75
src/core/palette/palette_file.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_PALETTE_FILE_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_PALETTE_FILE_HPP
|
||||
|
||||
#include "core/palette/color.hpp"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <core/io/file.hpp>
|
||||
#include <core/palette/color_keys.hpp>
|
||||
#include <core/palette/palette.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
template <typename FileType> class palette_file
|
||||
{
|
||||
public:
|
||||
palette_file() = default;
|
||||
palette_file(std::string file_path) : m_file(std::make_unique<FileType>(file_path))
|
||||
{
|
||||
m_palette.set_file_path(file_path);
|
||||
}
|
||||
bool parse()
|
||||
{
|
||||
if (!m_file->parse())
|
||||
return false;
|
||||
m_palette.set_name(m_file->get_string_value("general", "name"));
|
||||
for (const auto &color_key : COLOR_KEYS)
|
||||
{
|
||||
auto color_str = m_file->get_string_value("colors", color_key);
|
||||
core::color color{0x000000FF};
|
||||
if (!color_str.empty())
|
||||
color.from_hex_string(color_str);
|
||||
m_palette.set_color(color_key, color);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
core::palette palette() const
|
||||
{
|
||||
return m_palette;
|
||||
}
|
||||
void save_palette(const core::palette &pal)
|
||||
{
|
||||
set_palette(pal);
|
||||
save();
|
||||
}
|
||||
void set_color(const std::string &color_key, uint32_t color)
|
||||
{
|
||||
core::color col(color);
|
||||
m_file->insert_or_update_value("colors", color_key, col.to_hex_string());
|
||||
}
|
||||
void save()
|
||||
{
|
||||
m_file->save_file();
|
||||
}
|
||||
|
||||
private:
|
||||
core::palette m_palette{};
|
||||
std::unique_ptr<io::file> m_file;
|
||||
|
||||
void set_palette(const core::palette &pal)
|
||||
{
|
||||
m_palette = pal;
|
||||
m_file->insert_or_update_value("general", "name", pal.name());
|
||||
for (const auto &color_key : COLOR_KEYS)
|
||||
{
|
||||
const auto &col = pal.get_color(color_key);
|
||||
m_file->insert_or_update_value("colors", color_key, col.to_hex_string_with_alpha());
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
73
src/core/palette/palette_manager.hpp
Normal file
73
src/core/palette/palette_manager.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_PALETTE_MANAGER_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_PALETTE_MANAGER_HPP
|
||||
|
||||
#include "core/utils.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <core/config/config.hpp>
|
||||
#include <core/palette/palette.hpp>
|
||||
#include <core/palette/palette_file.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
template <typename FileType> class palette_manager
|
||||
{
|
||||
public:
|
||||
palette_manager() = default;
|
||||
void load_palettes_from_directory(const std::string &directory_path)
|
||||
{
|
||||
auto directory_path_expanded = expand_user(directory_path);
|
||||
for (const auto &entry : std::filesystem::directory_iterator(directory_path_expanded))
|
||||
{
|
||||
if (entry.is_regular_file())
|
||||
{
|
||||
palette_file<FileType> pal_file(entry.path().string());
|
||||
if (pal_file.parse())
|
||||
add_palette(pal_file.palette());
|
||||
}
|
||||
}
|
||||
}
|
||||
void save_palette_to_file(const palette &pal, const std::string &directory_path) const
|
||||
{
|
||||
std::string file_path = directory_path + "/" + pal.name() + ".toml";
|
||||
palette_file<FileType> pal_file(file_path);
|
||||
pal_file.save_palette(pal);
|
||||
}
|
||||
|
||||
const palette load_palette_from_file(const std::string &file_path) const
|
||||
{
|
||||
palette_file<FileType> pal_file(file_path);
|
||||
if (pal_file.parse())
|
||||
return pal_file.palette(); // TODO: report missing/invalid file
|
||||
return {};
|
||||
}
|
||||
void add_palette(const palette &pal)
|
||||
{
|
||||
m_palettes[pal.name()] = pal;
|
||||
}
|
||||
void delete_palette(const std::string &file_path, const std::string &name)
|
||||
{
|
||||
std::filesystem::remove(file_path);
|
||||
m_palettes.erase(name);
|
||||
}
|
||||
const palette *get_palette(const std::string &name) const
|
||||
{
|
||||
auto it = m_palettes.find(name);
|
||||
if (it != m_palettes.end())
|
||||
{
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
const std::unordered_map<std::string, palette> &palettes() const
|
||||
{
|
||||
return m_palettes;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, palette> m_palettes{};
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
#endif
|
||||
35
src/core/theme/template_manager.hpp
Normal file
35
src/core/theme/template_manager.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef CLRSYNC_CORE_THEME_TEMPLATE_MANAGER_HPP
|
||||
#define CLRSYNC_CORE_THEME_TEMPLATE_MANAGER_HPP
|
||||
|
||||
#include <core/config/config.hpp>
|
||||
#include <core/theme/theme_template.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
template <typename FileType>
|
||||
class template_manager
|
||||
{
|
||||
public:
|
||||
template_manager() = default;
|
||||
std::unordered_map<std::string, theme_template> &templates()
|
||||
{
|
||||
auto themes = config::instance().templates();
|
||||
m_templates.clear();
|
||||
for (const auto &t : themes)
|
||||
{
|
||||
m_templates.insert({t.first, t.second});
|
||||
}
|
||||
return m_templates;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, theme_template> m_templates{};
|
||||
};
|
||||
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
58
src/core/theme/theme_renderer.hpp
Normal file
58
src/core/theme/theme_renderer.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef CLRSYNC_CORE_THEME_THEME_RENDERER_HPP
|
||||
#define CLRSYNC_CORE_THEME_THEME_RENDERER_HPP
|
||||
#include <core/config/config.hpp>
|
||||
#include <core/palette/palette_manager.hpp>
|
||||
#include <core/theme/template_manager.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
template <typename FileType> class theme_renderer
|
||||
{
|
||||
public:
|
||||
theme_renderer()
|
||||
{
|
||||
auto &cfg = config::instance();
|
||||
m_pal_manager.load_palettes_from_directory(cfg.palettes_path());
|
||||
m_template_manager = template_manager<FileType>();
|
||||
}
|
||||
|
||||
void apply_theme(const std::string &theme_name)
|
||||
{
|
||||
auto palette = m_pal_manager.get_palette(theme_name);
|
||||
if (!palette)
|
||||
throw std::runtime_error("Palette not found: " + theme_name);
|
||||
apply_palette_to_all_templates(*palette);
|
||||
}
|
||||
void apply_theme_from_path(const std::string &path)
|
||||
{
|
||||
auto palette = m_pal_manager.load_palette_from_file(path);
|
||||
apply_palette_to_all_templates(palette);
|
||||
}
|
||||
|
||||
private:
|
||||
palette_manager<FileType> m_pal_manager;
|
||||
template_manager<FileType> m_template_manager;
|
||||
|
||||
void apply_palette_to_all_templates(const palette &pal)
|
||||
{
|
||||
for (auto &t_pair : m_template_manager.templates())
|
||||
{
|
||||
auto &tmpl = t_pair.second;
|
||||
if (!tmpl.enabled())
|
||||
continue;
|
||||
tmpl.load_template();
|
||||
tmpl.apply_palette(pal);
|
||||
tmpl.save_output();
|
||||
if (!tmpl.reload_command().empty())
|
||||
{
|
||||
std::system(tmpl.reload_command().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
138
src/core/theme/theme_template.cpp
Normal file
138
src/core/theme/theme_template.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "theme_template.hpp"
|
||||
#include "core/utils.hpp"
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
theme_template::theme_template(const std::string &name, const std::string &template_path,
|
||||
const std::string &out_path)
|
||||
: m_name(name), m_template_path(expand_user(template_path)),
|
||||
m_output_path(expand_user(out_path))
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &theme_template::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void theme_template::set_name(const std::string &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
const std::string &theme_template::template_path() const
|
||||
{
|
||||
return m_template_path;
|
||||
}
|
||||
|
||||
void theme_template::set_template_path(const std::string &path)
|
||||
{
|
||||
m_template_path = expand_user(path);
|
||||
}
|
||||
|
||||
const std::string &theme_template::output_path() const
|
||||
{
|
||||
return m_output_path;
|
||||
}
|
||||
|
||||
void theme_template::set_output_path(const std::string &path)
|
||||
{
|
||||
m_output_path = expand_user(path);
|
||||
}
|
||||
|
||||
void theme_template::load_template()
|
||||
{
|
||||
std::ifstream input(m_template_path, std::ios::binary);
|
||||
if (!input)
|
||||
throw std::runtime_error("Failed to open template file: " + m_template_path);
|
||||
|
||||
m_template_data.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
void theme_template::apply_palette(const core::palette &palette)
|
||||
{
|
||||
m_processed_data = m_template_data;
|
||||
|
||||
for (const auto& [key, color] : palette.colors())
|
||||
{
|
||||
// simple replacement: {foreground}
|
||||
replace_all(m_processed_data, "{" + key + "}", color.format("hex"));
|
||||
|
||||
// mutli-component: {foreground.r}, {foreground.rgb}, etc.
|
||||
std::string prefix = "{" + key + ".";
|
||||
size_t pos = 0;
|
||||
while ((pos = m_processed_data.find(prefix, pos)) != std::string::npos)
|
||||
{
|
||||
size_t end = m_processed_data.find('}', pos);
|
||||
if (end == std::string::npos)
|
||||
break;
|
||||
|
||||
const size_t field_start = pos + prefix.size();
|
||||
std::string field = m_processed_data.substr(field_start, end - field_start);
|
||||
|
||||
std::string value = color.format(field);
|
||||
|
||||
m_processed_data.replace(pos, end - pos + 1, value);
|
||||
|
||||
pos += value.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void theme_template::save_output() const
|
||||
{
|
||||
std::filesystem::create_directories(std::filesystem::path(m_output_path).parent_path());
|
||||
std::ofstream output(m_output_path, std::ios::binary);
|
||||
if (!output)
|
||||
throw std::runtime_error("Failed to write output file: " + m_output_path);
|
||||
|
||||
output << m_processed_data;
|
||||
}
|
||||
|
||||
const std::string &theme_template::raw_template() const
|
||||
{
|
||||
return m_template_data;
|
||||
}
|
||||
|
||||
const std::string &theme_template::processed_template() const
|
||||
{
|
||||
return m_processed_data;
|
||||
}
|
||||
|
||||
void theme_template::replace_all(std::string &str, const std::string &from, const std::string &to)
|
||||
{
|
||||
if (from.empty())
|
||||
return;
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = str.find(from, pos)) != std::string::npos)
|
||||
{
|
||||
str.replace(pos, from.length(), to);
|
||||
pos += to.length();
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &theme_template::reload_command() const
|
||||
{
|
||||
return m_reload_cmd;
|
||||
}
|
||||
|
||||
void theme_template::set_reload_command(const std::string &cmd)
|
||||
{
|
||||
m_reload_cmd = cmd;
|
||||
}
|
||||
|
||||
bool theme_template::enabled() const
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
void theme_template::set_enabled(bool enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
} // namespace clrsync::core
|
||||
60
src/core/theme/theme_template.hpp
Normal file
60
src/core/theme/theme_template.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef clrsync_CORE_IO_THEME_TEMPLATE_HPP
|
||||
#define clrsync_CORE_IO_THEME_TEMPLATE_HPP
|
||||
|
||||
#include <core/palette/palette.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
class theme_template
|
||||
{
|
||||
public:
|
||||
theme_template() = default;
|
||||
theme_template(const std::string &name, const std::string &template_path,
|
||||
const std::string &out_path);
|
||||
|
||||
const std::string &name() const;
|
||||
|
||||
void set_name(const std::string &name);
|
||||
|
||||
const std::string &template_path() const;
|
||||
|
||||
void set_template_path(const std::string &path);
|
||||
|
||||
const std::string &output_path() const;
|
||||
|
||||
void set_output_path(const std::string &path);
|
||||
|
||||
void load_template();
|
||||
|
||||
void apply_palette(const core::palette &palette);
|
||||
|
||||
void save_output() const;
|
||||
|
||||
const std::string &raw_template() const;
|
||||
|
||||
const std::string &processed_template() const;
|
||||
|
||||
const std::string &reload_command() const;
|
||||
|
||||
void set_reload_command(const std::string &cmd);
|
||||
|
||||
bool enabled() const;
|
||||
|
||||
void set_enabled(bool enabled);
|
||||
|
||||
private:
|
||||
std::string m_name{};
|
||||
std::string m_template_path{};
|
||||
std::string m_output_path{};
|
||||
bool m_enabled = true;
|
||||
std::string m_template_data{};
|
||||
std::string m_processed_data{};
|
||||
std::string m_reload_cmd{};
|
||||
|
||||
static void replace_all(std::string &str, const std::string &from, const std::string &to);
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
45
src/core/utils.cpp
Normal file
45
src/core/utils.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "utils.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
void print_color_keys()
|
||||
{
|
||||
for (const auto &key : clrsync::core::COLOR_KEYS)
|
||||
{
|
||||
std::cout << key << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_default_config_path()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const char *appdata = std::getenv("APPDATA"); // "C:\Users\<User>\AppData\Roaming"
|
||||
if (!appdata)
|
||||
throw std::runtime_error("APPDATA environment variable not set");
|
||||
return std::string(appdata) + "\\clrsync\\config.toml";
|
||||
#else
|
||||
const char *home = std::getenv("HOME");
|
||||
if (!home)
|
||||
throw std::runtime_error("HOME environment variable not set");
|
||||
return std::string(home) + "/.config/clrsync/config.toml";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string expand_user(const std::string &path)
|
||||
{
|
||||
if (!path.empty() && path[0] == '~')
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const char *home = std::getenv("USERPROFILE");
|
||||
#else
|
||||
const char *home = std::getenv("HOME");
|
||||
#endif
|
||||
if (!home)
|
||||
return path;
|
||||
return std::string(home) + path.substr(1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
} // namespace clrsync::core
|
||||
14
src/core/utils.hpp
Normal file
14
src/core/utils.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef CLRSYNC_CORE_UTILS_HPP
|
||||
#define CLRSYNC_CORE_UTILS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <core/palette/color_keys.hpp>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
void print_color_keys();
|
||||
std::string get_default_config_path();
|
||||
std::string expand_user(const std::string &path);
|
||||
} // namespace clrsync::core
|
||||
#endif // CLRSYNC_CORE_UTILS_HPP
|
||||
10
src/core/version.cpp
Normal file
10
src/core/version.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "version.hpp"
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
const std::string version_string()
|
||||
{
|
||||
return "v" + std::to_string(VERSION_MAJOR) + "." + std::to_string(VERSION_MINOR) + "." +
|
||||
std::to_string(VERSION_PATCH);
|
||||
}
|
||||
} // namespace clrsync::core
|
||||
17
src/core/version.hpp
Normal file
17
src/core/version.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef CLRSYNC_CORE_VERSION_HPP
|
||||
#define CLRSYNC_CORE_VERSION_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
constexpr uint8_t VERSION_MAJOR = 0;
|
||||
constexpr uint8_t VERSION_MINOR = 0;
|
||||
constexpr uint8_t VERSION_PATCH = 1;
|
||||
|
||||
const std::string version_string();
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif // CLRSYNC_CORE_VERSION_HPP
|
||||
89
src/gui/about_window.cpp
Normal file
89
src/gui/about_window.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "about_window.hpp"
|
||||
#include "core/version.hpp"
|
||||
#include "imgui.h"
|
||||
|
||||
about_window::about_window()
|
||||
{
|
||||
}
|
||||
|
||||
void about_window::render()
|
||||
{
|
||||
if (!m_visible)
|
||||
return;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::Begin("About clrsync", &m_visible, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
const float window_width = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont());
|
||||
const char* title = "clrsync";
|
||||
const float title_size = ImGui::CalcTextSize(title).x;
|
||||
ImGui::SetCursorPosX((window_width - title_size) * 0.5f);
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s", title);
|
||||
ImGui::PopFont();
|
||||
|
||||
std::string version = "Version " + clrsync::core::version_string();
|
||||
const float version_size = ImGui::CalcTextSize(version.c_str()).x;
|
||||
ImGui::SetCursorPosX((window_width - version_size) * 0.5f);
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", version.c_str());
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"A color scheme management tool."
|
||||
);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Links:");
|
||||
|
||||
if (ImGui::Button("GitHub Repository", ImVec2(200, 0)))
|
||||
{
|
||||
#ifdef _WIN32
|
||||
system("start https://github.com/obsqrbtz/clrsync");
|
||||
#elif __APPLE__
|
||||
system("open https://github.com/obsqrbtz/clrsync");
|
||||
#else
|
||||
system("xdg-open https://github.com/obsqrbtz/clrsync");
|
||||
#endif
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "MIT License");
|
||||
ImGui::TextWrapped(
|
||||
"Copyright (c) 2025 Daniel Dada\n\n"
|
||||
"Permission is hereby granted, free of charge, to any person obtaining a copy "
|
||||
"of this software and associated documentation files (the \"Software\"), to deal "
|
||||
"in the Software without restriction, including without limitation the rights "
|
||||
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell "
|
||||
"copies of the Software, and to permit persons to whom the Software is "
|
||||
"furnished to do so, subject to the following conditions:\n\n"
|
||||
"The above copyright notice and this permission notice shall be included in all "
|
||||
"copies or substantial portions of the Software."
|
||||
);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
const float button_width = 120.0f;
|
||||
ImGui::SetCursorPosX((window_width - button_width) * 0.5f);
|
||||
if (ImGui::Button("Close", ImVec2(button_width, 0)))
|
||||
{
|
||||
m_visible = false;
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
17
src/gui/about_window.hpp
Normal file
17
src/gui/about_window.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef CLRSYNC_GUI_ABOUT_WINDOW_HPP
|
||||
#define CLRSYNC_GUI_ABOUT_WINDOW_HPP
|
||||
|
||||
class about_window
|
||||
{
|
||||
public:
|
||||
about_window();
|
||||
void render();
|
||||
void show() { m_visible = true; }
|
||||
void hide() { m_visible = false; }
|
||||
bool is_visible() const { return m_visible; }
|
||||
|
||||
private:
|
||||
bool m_visible{false};
|
||||
};
|
||||
|
||||
#endif // CLRSYNC_GUI_ABOUT_WINDOW_HPP
|
||||
533
src/gui/color_scheme_editor.cpp
Normal file
533
src/gui/color_scheme_editor.cpp
Normal file
@@ -0,0 +1,533 @@
|
||||
#include "color_scheme_editor.hpp"
|
||||
#include "template_editor.hpp"
|
||||
#include "color_text_edit/TextEditor.h"
|
||||
#include "imgui.h"
|
||||
#include <ranges>
|
||||
|
||||
color_scheme_editor::color_scheme_editor()
|
||||
{
|
||||
m_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
|
||||
m_editor.SetText(R"(#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Expands ~ to the user's home directory
|
||||
std::string expand_user(const std::string &path)
|
||||
{
|
||||
if (path.empty()) return "";
|
||||
|
||||
std::string result;
|
||||
if (path[0] == '~')
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const char* home = std::getenv("USERPROFILE");
|
||||
#else
|
||||
const char* home = std::getenv("HOME");
|
||||
#endif
|
||||
result = home ? std::string(home) : "~";
|
||||
result += path.substr(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = path;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Lists all files in a directory
|
||||
std::vector<std::string> list_files(const std::string &dir_path)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
try
|
||||
{
|
||||
for (const auto &entry : fs::directory_iterator(dir_path))
|
||||
{
|
||||
if (entry.is_regular_file())
|
||||
files.push_back(entry.path().string());
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::string path = expand_user("~/Documents");
|
||||
std::cout << "Listing files in: " << path << std::endl;
|
||||
|
||||
auto files = list_files(path);
|
||||
for (const auto &f : files)
|
||||
std::cout << " " << f << std::endl;
|
||||
|
||||
return 0;
|
||||
})");
|
||||
|
||||
m_editor.SetShowWhitespaces(false);
|
||||
apply_palette_to_imgui();
|
||||
apply_palette_to_editor();
|
||||
}
|
||||
|
||||
void color_scheme_editor::notify_palette_changed()
|
||||
{
|
||||
if (m_template_editor)
|
||||
{
|
||||
m_template_editor->apply_current_palette(m_controller.current_palette());
|
||||
}
|
||||
}
|
||||
|
||||
void color_scheme_editor::render_controls_and_colors()
|
||||
{
|
||||
ImGui::Begin("Color Schemes");
|
||||
|
||||
render_controls();
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::BeginChild("ColorTableContent", ImVec2(0, 0), false);
|
||||
render_color_table();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void color_scheme_editor::render_preview()
|
||||
{
|
||||
ImGui::Begin("Color Preview");
|
||||
|
||||
render_preview_content();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void color_scheme_editor::render_controls()
|
||||
{
|
||||
const auto ¤t = m_controller.current_palette();
|
||||
const auto &palettes = m_controller.palettes();
|
||||
|
||||
const float avail_width = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::Text("Color Scheme:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(std::min(200.0f, avail_width * 0.3f));
|
||||
if (ImGui::BeginCombo("##scheme", current.name().c_str()))
|
||||
{
|
||||
for (const auto &name : palettes | std::views::keys)
|
||||
{
|
||||
const bool selected = current.name() == name;
|
||||
if (ImGui::Selectable(name.c_str(), selected))
|
||||
{
|
||||
m_controller.select_palette(name);
|
||||
apply_palette_to_imgui();
|
||||
apply_palette_to_editor();
|
||||
notify_palette_changed();
|
||||
}
|
||||
if (selected)
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
static char new_palette_name_buf[128] = "";
|
||||
if (ImGui::Button("New"))
|
||||
{
|
||||
new_palette_name_buf[0] = 0;
|
||||
ImGui::OpenPopup("New Palette");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("New Palette", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
ImGui::Text("New palette name:");
|
||||
ImGui::InputText("##new_palette_input", new_palette_name_buf,
|
||||
IM_ARRAYSIZE(new_palette_name_buf));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Create", ImVec2(120, 0)))
|
||||
{
|
||||
if (strlen(new_palette_name_buf) > 0)
|
||||
{
|
||||
m_controller.create_palette(new_palette_name_buf);
|
||||
apply_palette_to_imgui();
|
||||
apply_palette_to_editor();
|
||||
notify_palette_changed();
|
||||
new_palette_name_buf[0] = 0;
|
||||
}
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0)))
|
||||
{
|
||||
new_palette_name_buf[0] = 0;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Save"))
|
||||
{
|
||||
m_controller.save_current_palette();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete"))
|
||||
{
|
||||
m_controller.delete_current_palette();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Apply"))
|
||||
{
|
||||
m_controller.apply_current_theme();
|
||||
}
|
||||
}
|
||||
|
||||
void color_scheme_editor::render_color_table()
|
||||
{
|
||||
const auto ¤t = m_controller.current_palette();
|
||||
|
||||
if (current.colors().empty())
|
||||
{
|
||||
ImGui::Text("No palette loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Text("Color Variables");
|
||||
ImGui::Separator();
|
||||
|
||||
auto render_color_row = [&](const std::string &name) {
|
||||
const auto &colors = current.colors();
|
||||
auto it = colors.find(name);
|
||||
if (it == colors.end())
|
||||
return;
|
||||
|
||||
const clrsync::core::color &col = it->second;
|
||||
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted(name.c_str());
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
{
|
||||
std::string hex_str = col.to_hex_string();
|
||||
char buf[9];
|
||||
strncpy(buf, hex_str.c_str(), sizeof(buf));
|
||||
buf[8] = 0;
|
||||
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
if (ImGui::InputText(("##hex_" + name).c_str(), buf, sizeof(buf),
|
||||
ImGuiInputTextFlags_CharsUppercase))
|
||||
{
|
||||
try
|
||||
{
|
||||
clrsync::core::color new_color;
|
||||
new_color.from_hex_string(buf);
|
||||
m_controller.set_color(name, new_color);
|
||||
apply_palette_to_imgui();
|
||||
apply_palette_to_editor();
|
||||
notify_palette_changed();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::PushID(name.c_str());
|
||||
float c[4] = {((col.hex() >> 24) & 0xFF) / 255.0f, ((col.hex() >> 16) & 0xFF) / 255.0f,
|
||||
((col.hex() >> 8) & 0xFF) / 255.0f, (col.hex() & 0xFF) / 255.0f};
|
||||
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
if (ImGui::ColorEdit4(("##color_" + name).c_str(), c,
|
||||
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel |
|
||||
ImGuiColorEditFlags_AlphaBar))
|
||||
{
|
||||
uint32_t r = (uint32_t)(c[0] * 255.0f);
|
||||
uint32_t g = (uint32_t)(c[1] * 255.0f);
|
||||
uint32_t b = (uint32_t)(c[2] * 255.0f);
|
||||
uint32_t a = (uint32_t)(c[3] * 255.0f);
|
||||
uint32_t hex = (r << 24) | (g << 16) | (b << 8) | a;
|
||||
|
||||
m_controller.set_color(name, clrsync::core::color(hex));
|
||||
apply_palette_to_imgui();
|
||||
apply_palette_to_editor();
|
||||
notify_palette_changed();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
};
|
||||
|
||||
auto draw_table = [&](const char *title, const std::vector<const char *> &keys) {
|
||||
ImGui::TextUnformatted(title);
|
||||
|
||||
if (ImGui::BeginTable(title, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
|
||||
{
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 160.0f);
|
||||
ImGui::TableSetupColumn("HEX", ImGuiTableColumnFlags_WidthFixed, 90.0f);
|
||||
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (auto *k : keys)
|
||||
render_color_row(k);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
};
|
||||
|
||||
draw_table("UI / Surfaces", {"background", "surface", "surface_variant", "foreground",
|
||||
"foreground_secondary", "accent", "outline", "shadow", "cursor"});
|
||||
|
||||
draw_table("Editor Surfaces", {"editor_background", "sidebar_background", "popup_background",
|
||||
"floating_window_background", "menu_option_background"});
|
||||
|
||||
draw_table("Editor Text",
|
||||
{"text_main", "text_emphasis", "text_command", "text_inactive", "text_disabled",
|
||||
"text_line_number", "text_selected", "text_selection_inactive"});
|
||||
|
||||
draw_table("Window Borders", {"border_window", "border_focused", "border_emphasized"});
|
||||
|
||||
draw_table("Syntax Highlighting", {"syntax_function", "syntax_error", "syntax_keyword",
|
||||
"syntax_special_keyword", "syntax_operator"});
|
||||
|
||||
draw_table("Semantic Text",
|
||||
{"text_error", "text_warning", "text_link", "text_comment", "text_string",
|
||||
"text_success", "warning_emphasis", "foreground_emphasis"});
|
||||
|
||||
draw_table("Extra", {"terminal_gray"});
|
||||
|
||||
draw_table("Status Colors", {"error", "warning", "success", "info"});
|
||||
|
||||
draw_table("Terminal Colors",
|
||||
{"term_black", "term_red", "term_green", "term_yellow", "term_blue", "term_magenta",
|
||||
"term_cyan", "term_white", "term_black_bright", "term_red_bright",
|
||||
"term_green_bright", "term_yellow_bright", "term_blue_bright",
|
||||
"term_magenta_bright", "term_cyan_bright", "term_white_bright"});
|
||||
}
|
||||
|
||||
void color_scheme_editor::render_preview_content()
|
||||
{
|
||||
const auto ¤t = m_controller.current_palette();
|
||||
|
||||
auto get_color = [&](const std::string &key) -> ImVec4 {
|
||||
auto it = current.colors().find(key);
|
||||
if (it != current.colors().end())
|
||||
{
|
||||
const auto &col = it->second;
|
||||
const uint32_t hex = col.hex();
|
||||
return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f,
|
||||
((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f};
|
||||
}
|
||||
return {1, 1, 1, 1};
|
||||
};
|
||||
|
||||
const ImVec4 editor_bg = get_color("editor_background");
|
||||
const ImVec4 fg = get_color("foreground");
|
||||
const ImVec4 accent = get_color("accent");
|
||||
const ImVec4 outline = get_color("outline");
|
||||
const ImVec4 error = get_color("error");
|
||||
const ImVec4 warning = get_color("warning");
|
||||
const ImVec4 success = get_color("success");
|
||||
const ImVec4 info = get_color("info");
|
||||
|
||||
const float avail_height = ImGui::GetContentRegionAvail().y;
|
||||
const float code_preview_height = std::max(250.0f, avail_height * 0.55f);
|
||||
|
||||
ImGui::Text("Code Editor:");
|
||||
|
||||
m_editor.Render("##CodeEditor", ImVec2(0, code_preview_height), true);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Terminal Preview:");
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, editor_bg);
|
||||
ImGui::BeginChild("TerminalPreview", ImVec2(0, 0), true);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, outline);
|
||||
|
||||
struct term_line
|
||||
{
|
||||
const char *text{};
|
||||
ImVec4 col;
|
||||
};
|
||||
term_line term_lines[] = {
|
||||
{"$ ls -la", fg},
|
||||
{"drwxr-xr-x 5 user group 4096 Dec 2 10:30 .", accent},
|
||||
{"Build successful", success},
|
||||
{"Error: file not found", error},
|
||||
{"Warning: low disk space", warning},
|
||||
{"Info: update available", info},
|
||||
};
|
||||
|
||||
for (auto &[text, col] : term_lines)
|
||||
{
|
||||
ImGui::TextColored(col, "%s", text);
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void color_scheme_editor::apply_palette_to_editor()
|
||||
{
|
||||
const auto ¤t = m_controller.current_palette();
|
||||
|
||||
auto get_color_u32 = [&](const std::string &key) -> uint32_t {
|
||||
auto it = current.colors().find(key);
|
||||
if (it != current.colors().end())
|
||||
{
|
||||
const auto &col = it->second;
|
||||
const uint32_t hex = col.hex();
|
||||
// Convert from RRGGBBAA to AABBGGRR (ImGui format)
|
||||
const uint32_t r = (hex >> 24) & 0xFF;
|
||||
const uint32_t g = (hex >> 16) & 0xFF;
|
||||
const uint32_t b = (hex >> 8) & 0xFF;
|
||||
const uint32_t a = hex & 0xFF;
|
||||
return (a << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
return 0xFFFFFFFF;
|
||||
};
|
||||
|
||||
auto palette = m_editor.GetPalette();
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::Default)] = get_color_u32("text_main");
|
||||
palette[int(TextEditor::PaletteIndex::Keyword)] = get_color_u32("syntax_keyword");
|
||||
palette[int(TextEditor::PaletteIndex::Number)] = get_color_u32("text_warning");
|
||||
palette[int(TextEditor::PaletteIndex::String)] = get_color_u32("text_string");
|
||||
palette[int(TextEditor::PaletteIndex::CharLiteral)] = get_color_u32("text_string");
|
||||
palette[int(TextEditor::PaletteIndex::Punctuation)] = get_color_u32("text_main");
|
||||
palette[int(TextEditor::PaletteIndex::Preprocessor)] = get_color_u32("syntax_special_keyword");
|
||||
palette[int(TextEditor::PaletteIndex::Identifier)] = get_color_u32("text_main");
|
||||
palette[int(TextEditor::PaletteIndex::KnownIdentifier)] = get_color_u32("text_link");
|
||||
palette[int(TextEditor::PaletteIndex::PreprocIdentifier)] = get_color_u32("text_link");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::Comment)] = get_color_u32("text_comment");
|
||||
palette[int(TextEditor::PaletteIndex::MultiLineComment)] = get_color_u32("text_comment");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::Background)] = get_color_u32("editor_background");
|
||||
palette[int(TextEditor::PaletteIndex::Cursor)] = get_color_u32("cursor");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::Selection)] = get_color_u32("text_selected");
|
||||
palette[int(TextEditor::PaletteIndex::ErrorMarker)] = get_color_u32("syntax_error");
|
||||
palette[int(TextEditor::PaletteIndex::Breakpoint)] = get_color_u32("syntax_error");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::LineNumber)] = get_color_u32("text_line_number");
|
||||
palette[int(TextEditor::PaletteIndex::CurrentLineFill)] = get_color_u32("surface_variant");
|
||||
palette[int(TextEditor::PaletteIndex::CurrentLineFillInactive)] = get_color_u32("surface");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::CurrentLineEdge)] = get_color_u32("border_emphasized");
|
||||
|
||||
m_editor.SetPalette(palette);
|
||||
}
|
||||
|
||||
void color_scheme_editor::apply_palette_to_imgui() const
|
||||
{
|
||||
const auto ¤t = m_controller.current_palette();
|
||||
|
||||
auto getColor = [&](const std::string &key) -> ImVec4 {
|
||||
auto it = current.colors().find(key);
|
||||
if (it != current.colors().end())
|
||||
{
|
||||
const uint32_t hex = it->second.hex();
|
||||
return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f,
|
||||
((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f};
|
||||
}
|
||||
return {1, 1, 1, 1};
|
||||
};
|
||||
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
|
||||
const ImVec4 bg = getColor("editor_background");
|
||||
const ImVec4 sidebar = getColor("sidebar_background");
|
||||
const ImVec4 popup = getColor("popup_background");
|
||||
const ImVec4 menuOpt = getColor("menu_option_background");
|
||||
|
||||
const ImVec4 surface = getColor("surface");
|
||||
const ImVec4 surfaceVariant = getColor("surface_variant");
|
||||
|
||||
const ImVec4 fg = getColor("text_main");
|
||||
const ImVec4 fgSecondary = getColor("text_inactive");
|
||||
|
||||
const ImVec4 accent = getColor("accent");
|
||||
|
||||
const ImVec4 border = getColor("border_window");
|
||||
|
||||
style.Colors[ImGuiCol_WindowBg] = bg;
|
||||
style.Colors[ImGuiCol_ChildBg] = surface;
|
||||
style.Colors[ImGuiCol_PopupBg] = popup;
|
||||
|
||||
style.Colors[ImGuiCol_Border] = border;
|
||||
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0);
|
||||
|
||||
style.Colors[ImGuiCol_Text] = fg;
|
||||
style.Colors[ImGuiCol_TextDisabled] = fgSecondary;
|
||||
|
||||
style.Colors[ImGuiCol_Header] = surfaceVariant;
|
||||
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f);
|
||||
style.Colors[ImGuiCol_HeaderActive] = accent;
|
||||
|
||||
style.Colors[ImGuiCol_Button] = surfaceVariant;
|
||||
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
|
||||
style.Colors[ImGuiCol_ButtonActive] = accent;
|
||||
|
||||
style.Colors[ImGuiCol_FrameBg] = surfaceVariant;
|
||||
style.Colors[ImGuiCol_FrameBgHovered] =
|
||||
ImVec4(surfaceVariant.x * 1.1f, surfaceVariant.y * 1.1f, surfaceVariant.z * 1.1f, 1.0f);
|
||||
style.Colors[ImGuiCol_FrameBgActive] =
|
||||
ImVec4(surfaceVariant.x * 1.2f, surfaceVariant.y * 1.2f, surfaceVariant.z * 1.2f, 1.0f);
|
||||
|
||||
style.Colors[ImGuiCol_TitleBg] = sidebar;
|
||||
style.Colors[ImGuiCol_TitleBgActive] = surfaceVariant;
|
||||
style.Colors[ImGuiCol_TitleBgCollapsed] = sidebar;
|
||||
|
||||
style.Colors[ImGuiCol_ScrollbarBg] = surface;
|
||||
style.Colors[ImGuiCol_ScrollbarGrab] = surfaceVariant;
|
||||
style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
|
||||
style.Colors[ImGuiCol_ScrollbarGrabActive] = accent;
|
||||
|
||||
style.Colors[ImGuiCol_SliderGrab] = accent;
|
||||
style.Colors[ImGuiCol_SliderGrabActive] =
|
||||
ImVec4(accent.x * 1.2f, accent.y * 1.2f, accent.z * 1.2f, 1.0f);
|
||||
|
||||
style.Colors[ImGuiCol_CheckMark] = accent;
|
||||
style.Colors[ImGuiCol_ResizeGrip] = surfaceVariant;
|
||||
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
|
||||
style.Colors[ImGuiCol_ResizeGripActive] = accent;
|
||||
|
||||
style.Colors[ImGuiCol_Tab] = surface;
|
||||
style.Colors[ImGuiCol_TabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f);
|
||||
style.Colors[ImGuiCol_TabActive] = surfaceVariant;
|
||||
style.Colors[ImGuiCol_TabUnfocused] = surface;
|
||||
style.Colors[ImGuiCol_TabUnfocusedActive] = surfaceVariant;
|
||||
|
||||
style.Colors[ImGuiCol_TableHeaderBg] = surfaceVariant;
|
||||
style.Colors[ImGuiCol_TableBorderStrong] = border;
|
||||
style.Colors[ImGuiCol_TableBorderLight] =
|
||||
ImVec4(border.x * 0.7f, border.y * 0.7f, border.z * 0.7f, border.w);
|
||||
|
||||
style.Colors[ImGuiCol_TableRowBg] = ImVec4(0, 0, 0, 0);
|
||||
style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(fg.x, fg.y, fg.z, 0.06f);
|
||||
|
||||
style.Colors[ImGuiCol_Separator] = border;
|
||||
style.Colors[ImGuiCol_SeparatorHovered] = accent;
|
||||
style.Colors[ImGuiCol_SeparatorActive] = accent;
|
||||
|
||||
style.Colors[ImGuiCol_MenuBarBg] = menuOpt;
|
||||
|
||||
style.Colors[ImGuiCol_DockingPreview] = ImVec4(accent.x, accent.y, accent.z, 0.7f);
|
||||
style.Colors[ImGuiCol_DockingEmptyBg] = bg;
|
||||
}
|
||||
33
src/gui/color_scheme_editor.hpp
Normal file
33
src/gui/color_scheme_editor.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP
|
||||
#define CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP
|
||||
|
||||
#include "color_text_edit/TextEditor.h"
|
||||
#include "palette_controller.hpp"
|
||||
|
||||
class template_editor;
|
||||
|
||||
class color_scheme_editor
|
||||
{
|
||||
public:
|
||||
color_scheme_editor();
|
||||
|
||||
void render_controls_and_colors();
|
||||
void render_preview();
|
||||
void set_template_editor(template_editor* editor) { m_template_editor = editor; }
|
||||
const palette_controller& controller() const { return m_controller; }
|
||||
|
||||
private:
|
||||
void render_controls();
|
||||
void render_color_table();
|
||||
void render_preview_content();
|
||||
|
||||
void apply_palette_to_editor();
|
||||
void apply_palette_to_imgui() const;
|
||||
void notify_palette_changed();
|
||||
|
||||
palette_controller m_controller;
|
||||
TextEditor m_editor;
|
||||
template_editor* m_template_editor{nullptr};
|
||||
};
|
||||
|
||||
#endif // CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP
|
||||
228
src/gui/font_loader.cpp
Normal file
228
src/gui/font_loader.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "font_loader.hpp"
|
||||
#include "core/config/config.hpp"
|
||||
#include "imgui_internal.h"
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include <algorithm>
|
||||
#include <windows.h>
|
||||
#include <winreg.h>
|
||||
|
||||
static std::string search_registry_for_font(HKEY root_key, const char* subkey, const std::string& font_name_lower, const char* default_font_dir)
|
||||
{
|
||||
HKEY hKey;
|
||||
LONG result = RegOpenKeyExA(root_key, subkey, 0, KEY_READ, &hKey);
|
||||
|
||||
if (result != ERROR_SUCCESS)
|
||||
return {};
|
||||
|
||||
char value_name[512];
|
||||
BYTE value_data[512];
|
||||
DWORD value_name_size, value_data_size, type;
|
||||
DWORD index = 0;
|
||||
|
||||
std::string found_path;
|
||||
|
||||
while (true)
|
||||
{
|
||||
value_name_size = sizeof(value_name);
|
||||
value_data_size = sizeof(value_data);
|
||||
|
||||
result = RegEnumValueA(hKey, index++, value_name, &value_name_size, nullptr, &type, value_data, &value_data_size);
|
||||
|
||||
if (result != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
if (type != REG_SZ)
|
||||
continue;
|
||||
|
||||
std::string reg_font_name = value_name;
|
||||
std::transform(reg_font_name.begin(), reg_font_name.end(), reg_font_name.begin(), ::tolower);
|
||||
|
||||
if (reg_font_name.find(font_name_lower) != std::string::npos)
|
||||
{
|
||||
std::string font_file = reinterpret_cast<char*>(value_data);
|
||||
|
||||
// If path is not absolute, prepend default font directory
|
||||
if (font_file.find(":\\") == std::string::npos)
|
||||
{
|
||||
found_path = std::string(default_font_dir) + "\\" + font_file;
|
||||
}
|
||||
else
|
||||
{
|
||||
found_path = font_file;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return found_path;
|
||||
}
|
||||
|
||||
std::string font_loader::find_font_windows(const char* font_name)
|
||||
{
|
||||
std::string font_name_lower = font_name;
|
||||
std::transform(font_name_lower.begin(), font_name_lower.end(), font_name_lower.begin(), ::tolower);
|
||||
|
||||
char windows_dir[MAX_PATH];
|
||||
GetWindowsDirectoryA(windows_dir, MAX_PATH);
|
||||
std::string system_fonts_dir = std::string(windows_dir) + "\\Fonts";
|
||||
|
||||
// First, try system-wide fonts (HKEY_LOCAL_MACHINE)
|
||||
std::string path = search_registry_for_font(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts",
|
||||
font_name_lower,
|
||||
system_fonts_dir.c_str()
|
||||
);
|
||||
|
||||
if (!path.empty())
|
||||
return path;
|
||||
|
||||
// If not found, try per-user fonts (HKEY_CURRENT_USER)
|
||||
char local_appdata[MAX_PATH];
|
||||
if (GetEnvironmentVariableA("LOCALAPPDATA", local_appdata, MAX_PATH) > 0)
|
||||
{
|
||||
std::string user_fonts_dir = std::string(local_appdata) + "\\Microsoft\\Windows\\Fonts";
|
||||
|
||||
path = search_registry_for_font(
|
||||
HKEY_CURRENT_USER,
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts",
|
||||
font_name_lower,
|
||||
user_fonts_dir.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#include <CoreText/CoreText.h>
|
||||
|
||||
std::vector<unsigned char> font_loader::load_font_macos(const char* font_name)
|
||||
{
|
||||
std::vector<unsigned char> out;
|
||||
|
||||
CFStringRef cf_name = CFStringCreateWithCString(nullptr, font_name, kCFStringEncodingUTF8);
|
||||
if (!cf_name)
|
||||
return out;
|
||||
|
||||
CTFontDescriptorRef desc = CTFontDescriptorCreateWithNameAndSize(cf_name, 12);
|
||||
CFRelease(cf_name);
|
||||
|
||||
if (!desc)
|
||||
return out;
|
||||
|
||||
CTFontRef font = CTFontCreateWithFontDescriptor(desc, 0, nullptr);
|
||||
CFRelease(desc);
|
||||
|
||||
if (!font)
|
||||
return out;
|
||||
|
||||
CFDataRef data = CTFontCopyTable(font, kCTFontTableCFF, 0);
|
||||
if (!data)
|
||||
data = CTFontCopyTable(font, kCTFontTableHead, 0);
|
||||
|
||||
if (data)
|
||||
{
|
||||
CFIndex size = CFDataGetLength(data);
|
||||
out.resize(size);
|
||||
CFDataGetBytes(data, CFRangeMake(0, size), out.data());
|
||||
CFRelease(data);
|
||||
}
|
||||
|
||||
CFRelease(font);
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
std::string font_loader::find_font_linux(const char* font_name)
|
||||
{
|
||||
FcInit();
|
||||
|
||||
FcPattern* pattern = FcNameParse(reinterpret_cast<const FcChar8*>(font_name));
|
||||
if (!pattern)
|
||||
return {};
|
||||
|
||||
FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
|
||||
FcDefaultSubstitute(pattern);
|
||||
|
||||
FcResult result;
|
||||
FcPattern* match = FcFontMatch(nullptr, pattern, &result);
|
||||
|
||||
std::string out;
|
||||
|
||||
if (match)
|
||||
{
|
||||
FcChar8* file = nullptr;
|
||||
if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch)
|
||||
out = reinterpret_cast<const char*>(file);
|
||||
|
||||
FcPatternDestroy(match);
|
||||
}
|
||||
|
||||
FcPatternDestroy(pattern);
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::string font_loader::find_font_path(const char* font_name)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return find_font_windows(font_name);
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
(void)font_name;
|
||||
return {};
|
||||
|
||||
#else
|
||||
return find_font_linux(font_name);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
ImFont* font_loader::load_font(const char* font_name, float size_px)
|
||||
{
|
||||
#if defined(__APPLE__)
|
||||
std::vector<unsigned char> buf = load_font_macos(font_name);
|
||||
if (buf.empty())
|
||||
return nullptr;
|
||||
|
||||
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(
|
||||
buf.data(),
|
||||
static_cast<int>(buf.size()),
|
||||
size_px
|
||||
);
|
||||
|
||||
#else
|
||||
std::string path = find_font_path(font_name);
|
||||
if (path.empty())
|
||||
return nullptr;
|
||||
|
||||
float scale = ImGui::GetIO().DisplayFramebufferScale.y;
|
||||
return ImGui::GetIO().Fonts->AddFontFromFileTTF(
|
||||
path.c_str(),
|
||||
size_px * scale
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void font_loader::push_default_font()
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetDefaultFont(), clrsync::core::config::instance().font_size());
|
||||
}
|
||||
void font_loader::pop_font()
|
||||
{
|
||||
ImGui::PopFont();
|
||||
}
|
||||
30
src/gui/font_loader.hpp
Normal file
30
src/gui/font_loader.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef CLRSYNC_GUI_SYSTEM_FONT_LOADER_HPP
|
||||
#define CLRSYNC_GUI_SYSTEM_FONT_LOADER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <imgui.h>
|
||||
|
||||
class font_loader
|
||||
{
|
||||
public:
|
||||
font_loader() = default;
|
||||
|
||||
// Loads system font by name and returns an ImFont* or nullptr.
|
||||
ImFont* load_font(const char* font_name, float size_px);
|
||||
void push_default_font();
|
||||
void pop_font();
|
||||
|
||||
private:
|
||||
std::string find_font_path(const char* font_name);
|
||||
|
||||
#if defined(_WIN32)
|
||||
static std::string find_font_windows(const char* font_name);
|
||||
#elif defined(__APPLE__)
|
||||
std::vector<unsigned char> load_font_macos(const char* font_name);
|
||||
#else
|
||||
std::string find_font_linux(const char* font_name);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // CLRSYNC_GUI_SYSTEM_FONT_LOADER_HPP
|
||||
150
src/gui/imgui_helpers.cpp
Normal file
150
src/gui/imgui_helpers.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#include <string>
|
||||
|
||||
#include "GLFW/glfw3.h"
|
||||
#include "gui/settings_window.hpp"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
|
||||
#include "imgui_helpers.hpp"
|
||||
|
||||
#include "imgui_internal.h"
|
||||
|
||||
GLFWwindow * init_glfw()
|
||||
{
|
||||
if (!glfwInit()) return nullptr;
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
|
||||
|
||||
GLFWwindow* w = glfwCreateWindow(1280, 720, "clrsync", nullptr, nullptr);
|
||||
if (!w) return nullptr;
|
||||
|
||||
glfwMakeContextCurrent(w);
|
||||
glfwSwapInterval(1);
|
||||
return w;
|
||||
}
|
||||
|
||||
void init_imgui(GLFWwindow* window, const std::string& ini_path)
|
||||
{
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.IniFilename = ini_path.c_str();
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 130");
|
||||
}
|
||||
|
||||
void begin_frame()
|
||||
{
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void render_menu_bar(about_window* about, settings_window* settings)
|
||||
{
|
||||
if (ImGui::BeginMainMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("File"))
|
||||
{
|
||||
if (ImGui::MenuItem("Settings"))
|
||||
{
|
||||
if (settings)
|
||||
settings->show();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit"))
|
||||
{
|
||||
// Will be handled by checking window should close
|
||||
glfwSetWindowShouldClose(glfwGetCurrentContext(), GLFW_TRUE);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Help"))
|
||||
{
|
||||
if (ImGui::MenuItem("About"))
|
||||
{
|
||||
if (about)
|
||||
about->show();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
void setup_main_dockspace(bool& first_time)
|
||||
{
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->Pos);
|
||||
ImGui::SetNextWindowSize(viewport->Size);
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
|
||||
constexpr ImGuiWindowFlags flags =
|
||||
// ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoNavFocus;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,0));
|
||||
ImGui::Begin("MainDockSpace", nullptr, flags);
|
||||
ImGui::PopStyleVar(3);
|
||||
|
||||
ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");
|
||||
|
||||
if (first_time)
|
||||
{
|
||||
first_time = false;
|
||||
|
||||
ImGui::DockBuilderRemoveNode(dockspace_id);
|
||||
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
|
||||
ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size);
|
||||
|
||||
ImGuiID left, right;
|
||||
ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.45f, &left, &right);
|
||||
|
||||
ImGuiID right_top, right_bottom;
|
||||
ImGui::DockBuilderSplitNode(right, ImGuiDir_Up, 0.6f, &right_top, &right_bottom);
|
||||
|
||||
ImGui::DockBuilderDockWindow("Templates", left);
|
||||
ImGui::DockBuilderDockWindow("Color Schemes", right_top);
|
||||
ImGui::DockBuilderDockWindow("Color Preview", right_bottom);
|
||||
|
||||
ImGui::DockBuilderFinish(dockspace_id);
|
||||
}
|
||||
|
||||
ImGui::DockSpace(dockspace_id, ImVec2{0,0});
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void end_frame(GLFWwindow* window)
|
||||
{
|
||||
ImGui::Render();
|
||||
int w, h;
|
||||
glfwGetFramebufferSize(window, &w, &h);
|
||||
glViewport(0, 0, w, h);
|
||||
glClearColor(0.1f, 0.1f, 0.1f, 0.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
void shutdown(GLFWwindow* window)
|
||||
{
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
}
|
||||
18
src/gui/imgui_helpers.hpp
Normal file
18
src/gui/imgui_helpers.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef CLRSYNC_IMGUI_HELPERS_HPP
|
||||
#define CLRSYNC_IMGUI_HELPERS_HPP
|
||||
|
||||
#include "gui/about_window.hpp"
|
||||
#include <string>
|
||||
|
||||
struct GLFWwindow;
|
||||
class settings_window;
|
||||
|
||||
GLFWwindow * init_glfw();
|
||||
void init_imgui(GLFWwindow* window, const std::string& ini_path);
|
||||
void begin_frame();
|
||||
void setup_main_dockspace(bool& first_time);
|
||||
void end_frame(GLFWwindow* window);
|
||||
void shutdown(GLFWwindow* window);
|
||||
void render_menu_bar(about_window* about, settings_window* settings);
|
||||
|
||||
#endif // CLRSYNC_IMGUI_HELPERS_HPP
|
||||
67
src/gui/main.cpp
Normal file
67
src/gui/main.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <memory>
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "core/config/config.hpp"
|
||||
#include "core/io/toml_file.hpp"
|
||||
#include "core/utils.hpp"
|
||||
|
||||
#include "color_scheme_editor.hpp"
|
||||
#include "gui/font_loader.hpp"
|
||||
#include "gui/settings_window.hpp"
|
||||
#include "imgui_helpers.hpp"
|
||||
#include "template_editor.hpp"
|
||||
#include "about_window.hpp"
|
||||
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
auto config_path = clrsync::core::get_default_config_path();
|
||||
auto conf = std::make_unique<clrsync::core::io::toml_file>(config_path);
|
||||
clrsync::core::config::instance().initialize(std::move(conf));
|
||||
|
||||
std::filesystem::path base = config_path;
|
||||
static std::string ini_path = (base.parent_path() / "layout.ini").string();
|
||||
bool first_time = !std::filesystem::exists(ini_path);
|
||||
|
||||
GLFWwindow* window = init_glfw();
|
||||
if (!window) return 1;
|
||||
|
||||
init_imgui(window, ini_path);
|
||||
|
||||
font_loader loader;
|
||||
|
||||
ImFont* font =
|
||||
loader.load_font(clrsync::core::config::instance().font().c_str(), clrsync::core::config::instance().font_size());
|
||||
|
||||
if (font)
|
||||
ImGui::GetIO().FontDefault = font;
|
||||
|
||||
color_scheme_editor colorEditor;
|
||||
template_editor templateEditor;
|
||||
about_window aboutWindow;
|
||||
settings_window settingsWindow;
|
||||
|
||||
colorEditor.set_template_editor(&templateEditor);
|
||||
templateEditor.apply_current_palette(colorEditor.controller().current_palette());
|
||||
|
||||
while (!glfwWindowShouldClose(window))
|
||||
{
|
||||
glfwPollEvents();
|
||||
loader.push_default_font();
|
||||
begin_frame();
|
||||
|
||||
render_menu_bar(&aboutWindow, &settingsWindow);
|
||||
setup_main_dockspace(first_time);
|
||||
colorEditor.render_controls_and_colors();
|
||||
colorEditor.render_preview();
|
||||
templateEditor.render();
|
||||
aboutWindow.render();
|
||||
settingsWindow.render();
|
||||
|
||||
loader.pop_font();
|
||||
end_frame(window);
|
||||
}
|
||||
shutdown(window);
|
||||
return 0;
|
||||
}
|
||||
75
src/gui/palette_controller.cpp
Normal file
75
src/gui/palette_controller.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "palette_controller.hpp"
|
||||
#include "core/config/config.hpp"
|
||||
#include "core/theme/theme_renderer.hpp"
|
||||
|
||||
palette_controller::palette_controller()
|
||||
{
|
||||
m_palette_manager.load_palettes_from_directory(
|
||||
clrsync::core::config::instance().palettes_path());
|
||||
m_palettes = m_palette_manager.palettes();
|
||||
|
||||
if (m_palettes.empty())
|
||||
return;
|
||||
|
||||
try {
|
||||
m_current_palette = m_palettes[clrsync::core::config::instance().default_theme()];
|
||||
} catch (...) {
|
||||
m_current_palette = m_palettes.begin()->second;
|
||||
}
|
||||
}
|
||||
|
||||
void palette_controller::select_palette(const std::string& name)
|
||||
{
|
||||
auto it = m_palettes.find(name);
|
||||
if (it != m_palettes.end()) {
|
||||
m_current_palette = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void palette_controller::create_palette(const std::string& name)
|
||||
{
|
||||
clrsync::core::palette new_palette = m_current_palette;
|
||||
new_palette.set_name(name);
|
||||
|
||||
auto colors = m_current_palette.colors();
|
||||
for (auto& pair : colors) {
|
||||
new_palette.set_color(pair.first, pair.second);
|
||||
}
|
||||
|
||||
auto dir = clrsync::core::config::instance().palettes_path();
|
||||
m_palette_manager.save_palette_to_file(new_palette, dir);
|
||||
|
||||
reload_palettes();
|
||||
m_current_palette = new_palette;
|
||||
}
|
||||
|
||||
void palette_controller::save_current_palette()
|
||||
{
|
||||
auto dir = clrsync::core::config::instance().palettes_path();
|
||||
m_palette_manager.save_palette_to_file(m_current_palette, dir);
|
||||
reload_palettes();
|
||||
}
|
||||
|
||||
void palette_controller::delete_current_palette()
|
||||
{
|
||||
m_palette_manager.delete_palette(m_current_palette.file_path(), m_current_palette.name());
|
||||
reload_palettes();
|
||||
}
|
||||
|
||||
void palette_controller::apply_current_theme() const
|
||||
{
|
||||
clrsync::core::theme_renderer<clrsync::core::io::toml_file> theme_renderer;
|
||||
theme_renderer.apply_theme(m_current_palette.name());
|
||||
}
|
||||
|
||||
void palette_controller::set_color(const std::string& key, const clrsync::core::color& color)
|
||||
{
|
||||
m_current_palette.set_color(key, color);
|
||||
}
|
||||
|
||||
void palette_controller::reload_palettes()
|
||||
{
|
||||
m_palette_manager.load_palettes_from_directory(
|
||||
clrsync::core::config::instance().palettes_path());
|
||||
m_palettes = m_palette_manager.palettes();
|
||||
}
|
||||
31
src/gui/palette_controller.hpp
Normal file
31
src/gui/palette_controller.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef CLRSYNC_GUI_PALETTE_CONTROLLER_HPP
|
||||
#define CLRSYNC_GUI_PALETTE_CONTROLLER_HPP
|
||||
|
||||
#include "core/io/toml_file.hpp"
|
||||
#include "core/palette/palette_manager.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class palette_controller {
|
||||
public:
|
||||
palette_controller();
|
||||
|
||||
const clrsync::core::palette& current_palette() const { return m_current_palette; }
|
||||
const std::unordered_map<std::string, clrsync::core::palette>& palettes() const { return m_palettes; }
|
||||
|
||||
void select_palette(const std::string& name);
|
||||
void create_palette(const std::string& name);
|
||||
void save_current_palette();
|
||||
void delete_current_palette();
|
||||
void apply_current_theme() const;
|
||||
void set_color(const std::string& key, const clrsync::core::color& color);
|
||||
|
||||
private:
|
||||
void reload_palettes();
|
||||
|
||||
clrsync::core::palette_manager<clrsync::core::io::toml_file> m_palette_manager;
|
||||
std::unordered_map<std::string, clrsync::core::palette> m_palettes;
|
||||
clrsync::core::palette m_current_palette;
|
||||
};
|
||||
|
||||
#endif // CLRSYNC_GUI_PALETTE_CONTROLLER_HPP
|
||||
201
src/gui/settings_window.cpp
Normal file
201
src/gui/settings_window.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "settings_window.hpp"
|
||||
#include "core/config/config.hpp"
|
||||
#include "gui/font_loader.hpp"
|
||||
#include "imgui.h"
|
||||
#include <cstring>
|
||||
|
||||
settings_window::settings_window()
|
||||
: m_font_size(14)
|
||||
{
|
||||
m_default_theme[0] = '\0';
|
||||
m_palettes_path[0] = '\0';
|
||||
m_font[0] = '\0';
|
||||
load_settings();
|
||||
}
|
||||
|
||||
void settings_window::render()
|
||||
{
|
||||
if (!m_visible)
|
||||
return;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::Begin("Settings", &m_visible))
|
||||
{
|
||||
ImGui::Text("General Settings");
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Default Theme:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(300.0f);
|
||||
ImGui::InputText("##default_theme", m_default_theme, sizeof(m_default_theme));
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetTooltip("The default color scheme to load on startup");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Palettes Path:");
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
ImGui::InputText("##palettes_path", m_palettes_path, sizeof(m_palettes_path));
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetTooltip("Directory where color palettes are stored\nSupports ~ for home directory");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Font:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(300.0f);
|
||||
ImGui::InputText("##font", m_font, sizeof(m_font));
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetTooltip("Font");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Font Size:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(100.0f);
|
||||
ImGui::InputInt("##font_size", &m_font_size, 1, 1);
|
||||
if (m_font_size < 8) m_font_size = 8;
|
||||
if (m_font_size > 48) m_font_size = 48;
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetTooltip("Font size");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!m_error_message.empty())
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
||||
ImGui::TextWrapped("%s", m_error_message.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("OK", ImVec2(120, 0)))
|
||||
{
|
||||
apply_settings();
|
||||
m_visible = false;
|
||||
m_error_message = "";
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Apply", ImVec2(120, 0)))
|
||||
{
|
||||
apply_settings();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset to Defaults", ImVec2(150, 0)))
|
||||
{
|
||||
strncpy(m_default_theme, "dark", sizeof(m_default_theme));
|
||||
strncpy(m_palettes_path, "~/.config/clrsync/palettes", sizeof(m_palettes_path));
|
||||
strncpy(m_font, "JetBrains Mono Nerd Font", sizeof(m_font));
|
||||
m_font_size = 14;
|
||||
m_error_message = "";
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0)))
|
||||
{
|
||||
load_settings();
|
||||
m_visible = false;
|
||||
m_error_message = "";
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void settings_window::load_settings()
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& cfg = clrsync::core::config::instance();
|
||||
|
||||
std::string default_theme = cfg.default_theme();
|
||||
strncpy(m_default_theme, default_theme.c_str(), sizeof(m_default_theme) - 1);
|
||||
m_default_theme[sizeof(m_default_theme) - 1] = '\0';
|
||||
|
||||
std::string palettes_path = cfg.palettes_path();
|
||||
strncpy(m_palettes_path, palettes_path.c_str(), sizeof(m_palettes_path) - 1);
|
||||
m_palettes_path[sizeof(m_palettes_path) - 1] = '\0';
|
||||
|
||||
std::string font = cfg.font();
|
||||
strncpy(m_font, font.c_str(), sizeof(m_font) - 1);
|
||||
m_font[sizeof(m_font) - 1] = '\0';
|
||||
|
||||
m_font_size = cfg.font_size();
|
||||
|
||||
m_error_message = "";
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
m_error_message = std::string("Failed to load settings: ") + e.what();
|
||||
|
||||
// Set defaults on error
|
||||
strncpy(m_default_theme, "dark", sizeof(m_default_theme));
|
||||
strncpy(m_palettes_path, "~/.config/clrsync/palettes", sizeof(m_palettes_path));
|
||||
strncpy(m_font, "JetBrains Mono Nerd Font", sizeof(m_font));
|
||||
m_font_size = 14;
|
||||
}
|
||||
}
|
||||
|
||||
void settings_window::apply_settings()
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& cfg = clrsync::core::config::instance();
|
||||
|
||||
if (strlen(m_default_theme) == 0)
|
||||
{
|
||||
m_error_message = "Default theme cannot be empty";
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(m_palettes_path) == 0)
|
||||
{
|
||||
m_error_message = "Palettes path cannot be empty";
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(m_font) == 0)
|
||||
{
|
||||
m_error_message = "Font cannot be empty";
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_font_size < 8 || m_font_size > 48)
|
||||
{
|
||||
m_error_message = "Font size must be between 8 and 48";
|
||||
return;
|
||||
}
|
||||
|
||||
cfg.set_default_theme(m_default_theme);
|
||||
cfg.set_palettes_path(m_palettes_path);
|
||||
cfg.set_font(m_font);
|
||||
cfg.set_font_size(m_font_size);
|
||||
|
||||
|
||||
font_loader fn_loader;
|
||||
auto font = fn_loader.load_font(m_font, m_font_size);
|
||||
if (font)
|
||||
ImGui::GetIO().FontDefault = font;
|
||||
|
||||
m_error_message = "";
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
m_error_message = std::string("Failed to apply settings: ") + e.what();
|
||||
}
|
||||
}
|
||||
30
src/gui/settings_window.hpp
Normal file
30
src/gui/settings_window.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef CLRSYNC_GUI_SETTINGS_WINDOW_HPP
|
||||
#define CLRSYNC_GUI_SETTINGS_WINDOW_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
class settings_window
|
||||
{
|
||||
public:
|
||||
settings_window();
|
||||
void render();
|
||||
void show() { m_visible = true; }
|
||||
void hide() { m_visible = false; }
|
||||
bool is_visible() const { return m_visible; }
|
||||
|
||||
private:
|
||||
void load_settings();
|
||||
void save_settings();
|
||||
void apply_settings();
|
||||
|
||||
bool m_visible{false};
|
||||
|
||||
char m_default_theme[128];
|
||||
char m_palettes_path[512];
|
||||
char m_font[128];
|
||||
int m_font_size;
|
||||
|
||||
std::string m_error_message;
|
||||
};
|
||||
|
||||
#endif // CLRSYNC_GUI_SETTINGS_WINDOW_HPP
|
||||
39
src/gui/template_controller.cpp
Normal file
39
src/gui/template_controller.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "template_controller.hpp"
|
||||
#include "core/config/config.hpp"
|
||||
|
||||
template_controller::template_controller()
|
||||
{
|
||||
m_templates = m_template_manager.templates();
|
||||
}
|
||||
|
||||
void template_controller::set_template_enabled(const std::string& key, bool enabled)
|
||||
{
|
||||
auto it = m_templates.find(key);
|
||||
if (it != m_templates.end()) {
|
||||
it->second.set_enabled(enabled);
|
||||
clrsync::core::config::instance().update_template(key, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void template_controller::set_template_output_path(const std::string& key, const std::string& path)
|
||||
{
|
||||
auto it = m_templates.find(key);
|
||||
if (it != m_templates.end()) {
|
||||
it->second.set_output_path(path);
|
||||
clrsync::core::config::instance().update_template(key, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void template_controller::set_template_reload_command(const std::string& key, const std::string& cmd)
|
||||
{
|
||||
auto it = m_templates.find(key);
|
||||
if (it != m_templates.end()) {
|
||||
it->second.set_reload_command(cmd);
|
||||
clrsync::core::config::instance().update_template(key, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void template_controller::refresh()
|
||||
{
|
||||
m_templates = m_template_manager.templates();
|
||||
}
|
||||
24
src/gui/template_controller.hpp
Normal file
24
src/gui/template_controller.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef CLRSYNC_GUI_TEMPLATE_CONTROLLER_HPP
|
||||
#define CLRSYNC_GUI_TEMPLATE_CONTROLLER_HPP
|
||||
|
||||
#include "core/theme/theme_template.hpp"
|
||||
#include "core/theme/template_manager.hpp"
|
||||
#include "core/io/toml_file.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class template_controller {
|
||||
public:
|
||||
template_controller();
|
||||
[[nodiscard]] const std::unordered_map<std::string, clrsync::core::theme_template>& templates() const { return m_templates; }
|
||||
void set_template_enabled(const std::string& key, bool enabled);
|
||||
void set_template_output_path(const std::string& key, const std::string& path);
|
||||
void set_template_reload_command(const std::string& key, const std::string& cmd);
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
clrsync::core::template_manager<clrsync::core::io::toml_file> m_template_manager;
|
||||
std::unordered_map<std::string, clrsync::core::theme_template> m_templates;
|
||||
};
|
||||
|
||||
#endif // CLRSYNC_GUI_TEMPLATE_CONTROLLER_HPP
|
||||
410
src/gui/template_editor.cpp
Normal file
410
src/gui/template_editor.cpp
Normal file
@@ -0,0 +1,410 @@
|
||||
#include "template_editor.hpp"
|
||||
#include "core/config/config.hpp"
|
||||
#include "core/theme/theme_template.hpp"
|
||||
#include "imgui.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <ranges>
|
||||
|
||||
template_editor::template_editor()
|
||||
: m_template_name("new_template")
|
||||
{
|
||||
TextEditor::LanguageDefinition lang;
|
||||
lang.mName = "Template";
|
||||
|
||||
lang.mCommentStart = "/*";
|
||||
lang.mCommentEnd = "*/";
|
||||
lang.mSingleLineComment = "#";
|
||||
|
||||
lang.mTokenRegexStrings.push_back(std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"\\{[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)?\\}",
|
||||
TextEditor::PaletteIndex::KnownIdentifier));
|
||||
|
||||
lang.mTokenRegexStrings.push_back(std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"\"([^\"]*)\"", TextEditor::PaletteIndex::String));
|
||||
lang.mTokenRegexStrings.push_back(std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"'([^']*)'", TextEditor::PaletteIndex::String));
|
||||
|
||||
m_editor.SetLanguageDefinition(lang);
|
||||
m_editor.SetText("# Enter your template here\n# Use {color_key} for color variables\n# Examples: {color.hex}, {color.rgb}, {color.r}\n\n");
|
||||
m_editor.SetShowWhitespaces(false);
|
||||
}
|
||||
|
||||
void template_editor::apply_current_palette(const clrsync::core::palette& pal)
|
||||
{
|
||||
auto colors = pal.colors();
|
||||
auto get_color_u32 = [&](const std::string &key) -> uint32_t {
|
||||
auto it = colors.find(key);
|
||||
if (it != colors.end())
|
||||
{
|
||||
const auto &col = it->second;
|
||||
const uint32_t hex = col.hex();
|
||||
// Convert from RRGGBBAA to AABBGGRR (ImGui format)
|
||||
const uint32_t r = (hex >> 24) & 0xFF;
|
||||
const uint32_t g = (hex >> 16) & 0xFF;
|
||||
const uint32_t b = (hex >> 8) & 0xFF;
|
||||
const uint32_t a = hex & 0xFF;
|
||||
return (a << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
return 0xFFFFFFFF;
|
||||
};
|
||||
|
||||
auto palette = m_editor.GetPalette();
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::Default)] = get_color_u32("text_main");
|
||||
palette[int(TextEditor::PaletteIndex::Keyword)] = get_color_u32("syntax_keyword");
|
||||
palette[int(TextEditor::PaletteIndex::Number)] = get_color_u32("text_warning");
|
||||
palette[int(TextEditor::PaletteIndex::String)] = get_color_u32("text_string");
|
||||
palette[int(TextEditor::PaletteIndex::CharLiteral)] = get_color_u32("text_string");
|
||||
palette[int(TextEditor::PaletteIndex::Punctuation)] = get_color_u32("text_main");
|
||||
palette[int(TextEditor::PaletteIndex::Preprocessor)] = get_color_u32("syntax_special_keyword");
|
||||
palette[int(TextEditor::PaletteIndex::Identifier)] = get_color_u32("text_main");
|
||||
palette[int(TextEditor::PaletteIndex::KnownIdentifier)] = get_color_u32("text_link");
|
||||
palette[int(TextEditor::PaletteIndex::PreprocIdentifier)] = get_color_u32("text_link");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::Comment)] = get_color_u32("text_comment");
|
||||
palette[int(TextEditor::PaletteIndex::MultiLineComment)] = get_color_u32("text_comment");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::Background)] = get_color_u32("editor_background");
|
||||
palette[int(TextEditor::PaletteIndex::Cursor)] = get_color_u32("cursor");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::Selection)] = get_color_u32("text_selected");
|
||||
palette[int(TextEditor::PaletteIndex::ErrorMarker)] = get_color_u32("syntax_error");
|
||||
palette[int(TextEditor::PaletteIndex::Breakpoint)] = get_color_u32("syntax_error");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::LineNumber)] = get_color_u32("text_line_number");
|
||||
palette[int(TextEditor::PaletteIndex::CurrentLineFill)] = get_color_u32("surface_variant");
|
||||
palette[int(TextEditor::PaletteIndex::CurrentLineFillInactive)] = get_color_u32("surface");
|
||||
|
||||
palette[int(TextEditor::PaletteIndex::CurrentLineEdge)] = get_color_u32("border_emphasized");
|
||||
|
||||
m_editor.SetPalette(palette);
|
||||
}
|
||||
|
||||
void template_editor::render()
|
||||
{
|
||||
ImGui::Begin("Templates");
|
||||
|
||||
render_controls();
|
||||
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;
|
||||
|
||||
ImGui::BeginChild("TemplateList", ImVec2(left_panel_width, 0), true);
|
||||
render_template_list();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginChild("EditorPanel", ImVec2(right_panel_width, 0), false);
|
||||
render_editor();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void template_editor::render_controls()
|
||||
{
|
||||
if (ImGui::Button("New Template"))
|
||||
{
|
||||
new_template();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Save"))
|
||||
{
|
||||
save_template();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Template Name:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(150.0f);
|
||||
char name_buf[256] = {0};
|
||||
snprintf(name_buf, sizeof(name_buf), "%s", m_template_name.c_str());
|
||||
if (ImGui::InputText("##template_name", name_buf, sizeof(name_buf)))
|
||||
{
|
||||
m_template_name = name_buf;
|
||||
if (!m_template_name.empty())
|
||||
{
|
||||
m_validation_error = "";
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Enabled", &m_enabled))
|
||||
{
|
||||
if (m_is_editing_existing)
|
||||
{
|
||||
m_template_controller.set_template_enabled(m_template_name, m_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("Output Path:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
char path_buf[512] = {0};
|
||||
snprintf(path_buf, sizeof(path_buf), "%s", m_output_path.c_str());
|
||||
if (ImGui::InputText("##output_path", path_buf, sizeof(path_buf)))
|
||||
{
|
||||
m_output_path = path_buf;
|
||||
if (!m_output_path.empty())
|
||||
{
|
||||
m_validation_error = "";
|
||||
}
|
||||
if (m_is_editing_existing)
|
||||
{
|
||||
m_template_controller.set_template_output_path(m_template_name, m_output_path);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("Reload Command:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
char reload_buf[512] = {0};
|
||||
snprintf(reload_buf, sizeof(reload_buf), "%s", m_reload_command.c_str());
|
||||
if (ImGui::InputText("##reload_cmd", reload_buf, sizeof(reload_buf)))
|
||||
{
|
||||
m_reload_command = reload_buf;
|
||||
if (m_is_editing_existing)
|
||||
{
|
||||
m_template_controller.set_template_reload_command(m_template_name, m_reload_command);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_validation_error.empty())
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
||||
ImGui::TextWrapped("%s", m_validation_error.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
void template_editor::render_editor()
|
||||
{
|
||||
if (!m_is_editing_existing)
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 0.4f, 1.0f));
|
||||
ImGui::Text("New Template");
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::Text("Editing: %s", m_template_name.c_str());
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
m_editor.Render("##TemplateEditor", ImVec2(0, 0), true);
|
||||
}
|
||||
|
||||
void template_editor::render_template_list()
|
||||
{
|
||||
ImGui::Text("Templates");
|
||||
ImGui::Separator();
|
||||
|
||||
if (!m_is_editing_existing)
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 0.4f, 1.0f));
|
||||
ImGui::Selectable("* New Template *", true);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
const auto &templates = m_template_controller.templates();
|
||||
|
||||
for (const auto &key : templates | std::views::keys)
|
||||
{
|
||||
const bool selected = (m_template_name == key && m_is_editing_existing);
|
||||
if (ImGui::Selectable(key.c_str(), selected))
|
||||
{
|
||||
load_template(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool template_editor::is_valid_path(const std::string &path)
|
||||
{
|
||||
const std::string invalid_chars = "<>|\"";
|
||||
for (const char c : invalid_chars)
|
||||
{
|
||||
if (path.find(c) != std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.find_first_not_of(" \t\n\r./\\") == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::filesystem::path fs_path(path);
|
||||
|
||||
const auto parent = fs_path.parent_path();
|
||||
|
||||
if (parent.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parent.empty() && !std::filesystem::exists(parent))
|
||||
{
|
||||
if (parent.string().find_first_of("<>|\"") != std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto filename = fs_path.filename().string();
|
||||
if (filename.empty() || filename == "." || filename == "..")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void template_editor::save_template()
|
||||
{
|
||||
std::string trimmed_name = m_template_name;
|
||||
trimmed_name.erase(0, trimmed_name.find_first_not_of(" \t\n\r"));
|
||||
trimmed_name.erase(trimmed_name.find_last_not_of(" \t\n\r") + 1);
|
||||
|
||||
std::string trimmed_path = m_output_path;
|
||||
trimmed_path.erase(0, trimmed_path.find_first_not_of(" \t\n\r"));
|
||||
trimmed_path.erase(trimmed_path.find_last_not_of(" \t\n\r") + 1);
|
||||
|
||||
if (trimmed_name.empty())
|
||||
{
|
||||
m_validation_error = "Error: Template name cannot be empty!";
|
||||
return;
|
||||
}
|
||||
|
||||
if (trimmed_path.empty())
|
||||
{
|
||||
m_validation_error = "Error: Output path cannot be empty!";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_valid_path(trimmed_path))
|
||||
{
|
||||
m_validation_error =
|
||||
"Error: Output path is invalid! Must be a valid file path with directory.";
|
||||
return;
|
||||
}
|
||||
|
||||
m_validation_error = "";
|
||||
|
||||
try
|
||||
{
|
||||
auto &cfg = clrsync::core::config::instance();
|
||||
std::string palettes_path = cfg.palettes_path();
|
||||
std::filesystem::path templates_dir =
|
||||
std::filesystem::path(palettes_path).parent_path() / "templates";
|
||||
|
||||
if (!std::filesystem::exists(templates_dir))
|
||||
{
|
||||
std::filesystem::create_directories(templates_dir);
|
||||
}
|
||||
|
||||
std::filesystem::path template_file;
|
||||
if (m_is_editing_existing)
|
||||
{
|
||||
const auto &existing_template = cfg.template_by_name(trimmed_name);
|
||||
template_file = existing_template.template_path();
|
||||
}
|
||||
else
|
||||
{
|
||||
template_file = templates_dir / trimmed_name;
|
||||
}
|
||||
|
||||
std::string template_content = m_editor.GetText();
|
||||
|
||||
std::ofstream out(template_file);
|
||||
if (out.is_open())
|
||||
{
|
||||
out << template_content;
|
||||
out.close();
|
||||
}
|
||||
|
||||
clrsync::core::theme_template tmpl(trimmed_name, template_file.string(), trimmed_path);
|
||||
tmpl.set_reload_command(m_reload_command);
|
||||
tmpl.set_enabled(m_enabled);
|
||||
|
||||
cfg.update_template(trimmed_name, tmpl);
|
||||
|
||||
m_template_name = trimmed_name;
|
||||
m_output_path = trimmed_path;
|
||||
m_is_editing_existing = true;
|
||||
|
||||
refresh_templates();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
m_validation_error = std::string("Error saving template: ") + e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void template_editor::load_template(const std::string &name)
|
||||
{
|
||||
const auto &templates = m_template_controller.templates();
|
||||
auto it = templates.find(name);
|
||||
|
||||
if (it != templates.end())
|
||||
{
|
||||
const auto &tmpl = it->second;
|
||||
m_template_name = name;
|
||||
m_output_path = tmpl.output_path();
|
||||
m_reload_command = tmpl.reload_command();
|
||||
m_enabled = tmpl.enabled();
|
||||
m_is_editing_existing = true;
|
||||
m_validation_error = "";
|
||||
|
||||
try
|
||||
{
|
||||
std::ifstream in(tmpl.template_path());
|
||||
if (in.is_open())
|
||||
{
|
||||
std::string content;
|
||||
std::string line;
|
||||
while (std::getline(in, line))
|
||||
{
|
||||
content += line + "\n";
|
||||
}
|
||||
in.close();
|
||||
|
||||
m_editor.SetText(content);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
m_validation_error = std::string("Error loading template: ") + e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void template_editor::new_template()
|
||||
{
|
||||
m_template_name = "new_template";
|
||||
m_editor.SetText("# Enter your template here\n# Use {color_key} for color variables\n# Examples: {color.hex}, {color.rgb}, {color.r}\n\n");
|
||||
m_output_path = "";
|
||||
m_reload_command = "";
|
||||
m_enabled = true;
|
||||
m_is_editing_existing = false;
|
||||
m_validation_error = "";
|
||||
}
|
||||
|
||||
void template_editor::refresh_templates()
|
||||
{
|
||||
m_template_controller.refresh();
|
||||
}
|
||||
40
src/gui/template_editor.hpp
Normal file
40
src/gui/template_editor.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef CLRSYNC_GUI_TEMPLATE_EDITOR_HPP
|
||||
#define CLRSYNC_GUI_TEMPLATE_EDITOR_HPP
|
||||
|
||||
#include "template_controller.hpp"
|
||||
#include <core/palette/palette.hpp>
|
||||
#include "color_text_edit/TextEditor.h"
|
||||
#include <string>
|
||||
|
||||
class template_editor
|
||||
{
|
||||
public:
|
||||
template_editor();
|
||||
void render();
|
||||
void apply_current_palette(const clrsync::core::palette& pal);
|
||||
|
||||
private:
|
||||
void render_controls();
|
||||
void render_editor();
|
||||
void render_template_list();
|
||||
|
||||
void save_template();
|
||||
void load_template(const std::string &name);
|
||||
void new_template();
|
||||
void refresh_templates();
|
||||
|
||||
bool is_valid_path(const std::string &path);
|
||||
|
||||
template_controller m_template_controller;
|
||||
TextEditor m_editor;
|
||||
|
||||
std::string m_template_name;
|
||||
std::string m_output_path;
|
||||
std::string m_reload_command;
|
||||
std::string m_validation_error;
|
||||
|
||||
bool m_enabled{true};
|
||||
bool m_is_editing_existing{false};
|
||||
};
|
||||
|
||||
#endif // CLRSYNC_GUI_TEMPLATE_EDITOR_HPP
|
||||
Reference in New Issue
Block a user