chore: split color_scheme_editor

This commit is contained in:
2025-12-17 01:41:44 +03:00
parent 659c5f28e5
commit f7c290110e
17 changed files with 617 additions and 453 deletions

View File

@@ -17,9 +17,10 @@
baseVersion = nixpkgs.lib.removeSuffix "\n" (builtins.readFile ./VERSION);
semver =
if self ? rev
then "${baseVersion}+git.${builtins.substring 0 7 self.rev}"
else "${baseVersion}+dev";
if self ? rev then
"${baseVersion}+git.${builtins.substring 0 7 self.rev}"
else
"${baseVersion}+dev";
in
{
packages = forAllSystems (
@@ -66,12 +67,12 @@
system:
let
pkgs = nixpkgsFor.${system};
clrsync = self.packages.${system}.clrsync;
in
{
default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.clrsync ];
buildInputs = with pkgs; [
inputsFrom = [ clrsync ];
packages = with pkgs; [
cmake
ninja
clang-tools

View File

@@ -9,6 +9,7 @@
#ifdef _WIN32
#include "windows.h"
#endif
#include <iostream>
namespace clrsync::core
{
@@ -22,9 +23,10 @@ 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"};
if (!m_file)
throw std::runtime_error{"Config file is missing"};
if (!m_file->parse())
throw std::runtime_error{"Could not parse config file"};
}
std::filesystem::path config::get_user_config_dir()
@@ -62,13 +64,32 @@ void config::copy_file(const std::filesystem::path &src, const std::filesystem::
if (std::filesystem::exists(dst))
return;
if (!std::filesystem::exists(src))
{
std::cerr << "Warning: Source file does not exist: " << src << std::endl;
return;
}
std::ifstream in(src, std::ios::binary);
std::ofstream out(dst, std::ios::binary);
if (!in || !out)
{
std::cerr << "Warning: Failed to copy file from " << src << " to " << dst << std::endl;
return;
}
out << in.rdbuf();
}
void config::copy_dir(const std::filesystem::path &src, const std::filesystem::path &dst)
{
if (!std::filesystem::exists(src))
{
std::cerr << "Warning: Source directory does not exist: " << src << std::endl;
return;
}
for (auto const &entry : std::filesystem::recursive_directory_iterator(src))
{
auto rel = std::filesystem::relative(entry.path(), src);
@@ -92,6 +113,12 @@ void config::copy_default_configs()
std::filesystem::create_directories(user_dir);
if (system_dir.empty())
{
std::cerr << "Warning: No system data directory found, skipping default config copy\n";
return;
}
{
auto src = system_dir / "config.toml";
auto dst = user_dir / "config.toml";

View File

@@ -47,12 +47,15 @@ void theme_template::load_template()
{
if (!std::filesystem::exists(m_template_path))
{
std::cerr << "Template file '" << m_template_path << "' is missing\n";
std::cerr << "Warning: Template file '" << m_template_path << "' is missing\n";
return;
}
std::ifstream input(m_template_path, std::ios::binary);
if (!input)
throw std::runtime_error("Failed to open template file: " + m_template_path);
{
std::cerr << "Warning: Failed to open template file: " << m_template_path << std::endl;
return;
}
m_template_data.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
}
@@ -89,10 +92,22 @@ void theme_template::apply_palette(const core::palette &palette)
void theme_template::save_output() const
{
std::filesystem::create_directories(std::filesystem::path(m_output_path).parent_path());
try
{
std::filesystem::create_directories(std::filesystem::path(m_output_path).parent_path());
}
catch (const std::exception& e)
{
std::cerr << "Warning: Failed to create output directory for " << m_output_path << ": " << e.what() << std::endl;
return;
}
std::ofstream output(m_output_path, std::ios::binary);
if (!output)
throw std::runtime_error("Failed to write output file: " + m_output_path);
{
std::cerr << "Warning: Failed to write output file: " << m_output_path << std::endl;
return;
}
output << m_processed_data;
}

View File

@@ -13,6 +13,10 @@ void print_color_keys()
std::string get_default_config_path()
{
const char* env_path = std::getenv("CLRSYNC_CONFIG_PATH");
if (env_path && env_path[0] != '\0')
return expand_user(env_path);
auto home = expand_user("~");
#ifdef _WIN32
return home + "\\.config\\clrsync\\config.toml";

View File

@@ -7,7 +7,7 @@
namespace clrsync::core
{
const std::string GIT_SEMVER = "0.1.4+git.gcd81744";
const std::string GIT_SEMVER = "0.1.4+git.g659c5f2";
const std::string version_string();
} // namespace clrsync::core

View File

@@ -1,6 +1,9 @@
set(GUI_SOURCES
main.cpp
color_scheme_editor.cpp
color_table_renderer.cpp
preview_renderer.cpp
theme_applier.cpp
template_editor.cpp
palette_controller.cpp
template_controller.cpp

View File

@@ -1,86 +1,18 @@
#include "color_scheme_editor.hpp"
#include "template_editor.hpp"
#include "color_text_edit/TextEditor.h"
#include "theme_applier.hpp"
#include "imgui.h"
#include <iostream>
#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);
const auto &palettes = m_controller.palettes();
const auto &current = m_controller.current_palette();
if (!current.colors().empty())
{
apply_palette_to_imgui();
apply_palette_to_editor();
theme_applier::apply_to_imgui(current);
m_preview.apply_palette(current);
}
else
{
@@ -96,6 +28,14 @@ void color_scheme_editor::notify_palette_changed()
}
}
void color_scheme_editor::apply_themes()
{
const auto &current = m_controller.current_palette();
theme_applier::apply_to_imgui(current);
m_preview.apply_palette(current);
notify_palette_changed();
}
void color_scheme_editor::render_controls_and_colors()
{
ImGui::Begin("Color Schemes");
@@ -104,7 +44,8 @@ void color_scheme_editor::render_controls_and_colors()
ImGui::Separator();
ImGui::BeginChild("ColorTableContent", ImVec2(0, 0), false);
render_color_table();
m_color_table.render(m_controller.current_palette(), m_controller,
[this]() { apply_themes(); });
ImGui::EndChild();
ImGui::End();
@@ -114,7 +55,7 @@ void color_scheme_editor::render_preview()
{
ImGui::Begin("Color Preview");
render_preview_content();
m_preview.render(m_controller.current_palette());
ImGui::End();
}
@@ -137,9 +78,7 @@ void color_scheme_editor::render_controls()
if (ImGui::Selectable(name.c_str(), selected))
{
m_controller.select_palette(name);
apply_palette_to_imgui();
apply_palette_to_editor();
notify_palette_changed();
apply_themes();
}
if (selected)
ImGui::SetItemDefaultFocus();
@@ -169,10 +108,8 @@ void color_scheme_editor::render_controls()
if (strlen(new_palette_name_buf) > 0)
{
m_controller.create_palette(new_palette_name_buf);
apply_palette_to_imgui();
apply_palette_to_editor();
notify_palette_changed();
m_controller.select_palette(new_palette_name_buf);
apply_themes();
new_palette_name_buf[0] = 0;
}
ImGui::CloseCurrentPopup();
@@ -207,349 +144,3 @@ void color_scheme_editor::render_controls()
m_controller.apply_current_theme();
}
}
void color_scheme_editor::render_color_table()
{
const auto &current = 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("General UI", {"background", "on_background", "surface", "on_surface",
"surface_variant", "on_surface_variant", "foreground",
"cursor", "accent"});
draw_table("Borders", {"border_focused", "border"});
draw_table("Semantic Colors", {"success", "info", "warning", "error",
"on_success", "on_info", "on_warning", "on_error"});
draw_table("Editor", {"editor_background", "editor_command", "editor_comment",
"editor_disabled", "editor_emphasis", "editor_error",
"editor_inactive", "editor_line_number", "editor_link",
"editor_main", "editor_selected", "editor_selection_inactive",
"editor_string", "editor_success", "editor_warning"});
draw_table("Terminal (Base16)", {"base00", "base01", "base02", "base03",
"base04", "base05", "base06", "base07",
"base08", "base09", "base0A", "base0B",
"base0C", "base0D", "base0E", "base0F"});
}
void color_scheme_editor::render_preview_content()
{
const auto &current = m_controller.current_palette();
if (current.colors().empty())
{
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Current palette is empty");
return;
}
auto get_color = [&](const std::string &key) -> ImVec4 {
auto it = current.colors().find(key);
if (it != current.colors().end())
{
const auto &col = it->second;
const uint32_t hex = col.hex();
return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f,
((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f};
}
return {1, 1, 1, 1};
};
const ImVec4 editor_bg = get_color("editor_background");
const ImVec4 fg = get_color("foreground");
const ImVec4 accent = get_color("accent");
const ImVec4 border = get_color("border");
const ImVec4 error = get_color("error");
const ImVec4 warning = get_color("warning");
const ImVec4 success = get_color("success");
const ImVec4 info = get_color("info");
const float avail_height = ImGui::GetContentRegionAvail().y;
const float code_preview_height = std::max(250.0f, avail_height * 0.55f);
ImGui::Text("Code Editor:");
m_editor.Render("##CodeEditor", ImVec2(0, code_preview_height), true);
ImGui::Spacing();
ImGui::Text("Terminal Preview:");
ImGui::PushStyleColor(ImGuiCol_ChildBg, editor_bg);
ImGui::BeginChild("TerminalPreview", ImVec2(0, 0), true);
ImGui::PushStyleColor(ImGuiCol_Border, border);
struct term_line
{
const char *text{};
ImVec4 col;
};
term_line term_lines[] = {
{"$ ls -la", fg},
{"drwxr-xr-x 5 user group 4096 Dec 2 10:30 .", accent},
{"Build successful", success},
{"Error: file not found", error},
{"Warning: low disk space", warning},
{"Info: update available", info},
};
for (auto &[text, col] : term_lines)
{
ImGui::TextColored(col, "%s", text);
}
ImGui::PopStyleColor(2);
ImGui::EndChild();
}
void color_scheme_editor::apply_palette_to_editor()
{
const auto &current = m_controller.current_palette();
if (current.colors().empty())
return;
auto get_color_u32 = [&](const std::string &key, const std::string &fallback = "") -> uint32_t {
auto it = current.colors().find(key);
if (it == current.colors().end() && !fallback.empty())
{
it = current.colors().find(fallback);
}
if (it != current.colors().end())
{
const auto &col = it->second;
const uint32_t hex = col.hex();
// Convert from RRGGBBAA to AABBGGRR (ImGui format)
const uint32_t r = (hex >> 24) & 0xFF;
const uint32_t g = (hex >> 16) & 0xFF;
const uint32_t b = (hex >> 8) & 0xFF;
const uint32_t a = hex & 0xFF;
return (a << 24) | (b << 16) | (g << 8) | r;
}
return 0xFFFFFFFF;
};
auto palette = m_editor.GetPalette();
palette[int(TextEditor::PaletteIndex::Default)] = get_color_u32("editor_main");
palette[int(TextEditor::PaletteIndex::Keyword)] = get_color_u32("editor_command");
palette[int(TextEditor::PaletteIndex::Number)] = get_color_u32("editor_warning");
palette[int(TextEditor::PaletteIndex::String)] = get_color_u32("editor_string");
palette[int(TextEditor::PaletteIndex::CharLiteral)] = get_color_u32("editor_string");
palette[int(TextEditor::PaletteIndex::Punctuation)] = get_color_u32("editor_main");
palette[int(TextEditor::PaletteIndex::Preprocessor)] = get_color_u32("editor_emphasis");
palette[int(TextEditor::PaletteIndex::Identifier)] = get_color_u32("editor_main");
palette[int(TextEditor::PaletteIndex::KnownIdentifier)] = get_color_u32("editor_link");
palette[int(TextEditor::PaletteIndex::PreprocIdentifier)] = get_color_u32("editor_link");
palette[int(TextEditor::PaletteIndex::Comment)] = get_color_u32("editor_comment");
palette[int(TextEditor::PaletteIndex::MultiLineComment)] = get_color_u32("editor_comment");
palette[int(TextEditor::PaletteIndex::Background)] = get_color_u32("editor_background");
palette[int(TextEditor::PaletteIndex::Cursor)] = get_color_u32("cursor");
palette[int(TextEditor::PaletteIndex::Selection)] = get_color_u32("editor_selected");
palette[int(TextEditor::PaletteIndex::ErrorMarker)] = get_color_u32("editor_error");
palette[int(TextEditor::PaletteIndex::Breakpoint)] = get_color_u32("editor_error");
palette[int(TextEditor::PaletteIndex::LineNumber)] = get_color_u32("editor_line_number");
palette[int(TextEditor::PaletteIndex::CurrentLineFill)] = get_color_u32("surface_variant");
palette[int(TextEditor::PaletteIndex::CurrentLineFillInactive)] = get_color_u32("surface");
palette[int(TextEditor::PaletteIndex::CurrentLineEdge)] = get_color_u32("border_focused");
m_editor.SetPalette(palette);
}
void color_scheme_editor::apply_palette_to_imgui() const
{
const auto &current = m_controller.current_palette();
if (current.colors().empty())
return;
auto getColor = [&](const std::string &key, const std::string &fallback = "") -> ImVec4 {
auto it = current.colors().find(key);
if (it == current.colors().end() && !fallback.empty())
{
it = current.colors().find(fallback);
}
if (it != current.colors().end())
{
const uint32_t hex = it->second.hex();
return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f,
((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f};
}
std::cout << "WARNING: Color key '" << key << "' not found!\n";
return {1, 1, 1, 1};
};
ImGuiStyle &style = ImGui::GetStyle();
const ImVec4 bg = getColor("background");
const ImVec4 surface = getColor("surface");
const ImVec4 surfaceVariant = getColor("surface_variant");
const ImVec4 fg = getColor("foreground");
const ImVec4 fgInactive = getColor("editor_inactive");
const ImVec4 accent = getColor("accent");
const ImVec4 border = getColor("border");
style.Colors[ImGuiCol_WindowBg] = bg;
style.Colors[ImGuiCol_ChildBg] = surface;
style.Colors[ImGuiCol_PopupBg] = surface;
style.Colors[ImGuiCol_Border] = border;
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0);
style.Colors[ImGuiCol_Text] = fg;
style.Colors[ImGuiCol_TextDisabled] = fgInactive;
style.Colors[ImGuiCol_Header] = surfaceVariant;
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f);
style.Colors[ImGuiCol_HeaderActive] = accent;
style.Colors[ImGuiCol_Button] = surfaceVariant;
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
style.Colors[ImGuiCol_ButtonActive] = accent;
style.Colors[ImGuiCol_FrameBg] = surfaceVariant;
style.Colors[ImGuiCol_FrameBgHovered] =
ImVec4(surfaceVariant.x * 1.1f, surfaceVariant.y * 1.1f, surfaceVariant.z * 1.1f, 1.0f);
style.Colors[ImGuiCol_FrameBgActive] =
ImVec4(surfaceVariant.x * 1.2f, surfaceVariant.y * 1.2f, surfaceVariant.z * 1.2f, 1.0f);
style.Colors[ImGuiCol_TitleBg] = surface;
style.Colors[ImGuiCol_TitleBgActive] = surfaceVariant;
style.Colors[ImGuiCol_TitleBgCollapsed] = surface;
style.Colors[ImGuiCol_ScrollbarBg] = surface;
style.Colors[ImGuiCol_ScrollbarGrab] = surfaceVariant;
style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
style.Colors[ImGuiCol_ScrollbarGrabActive] = accent;
style.Colors[ImGuiCol_SliderGrab] = accent;
style.Colors[ImGuiCol_SliderGrabActive] =
ImVec4(accent.x * 1.2f, accent.y * 1.2f, accent.z * 1.2f, 1.0f);
style.Colors[ImGuiCol_CheckMark] = accent;
style.Colors[ImGuiCol_ResizeGrip] = surfaceVariant;
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
style.Colors[ImGuiCol_ResizeGripActive] = accent;
style.Colors[ImGuiCol_Tab] = surface;
style.Colors[ImGuiCol_TabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f);
style.Colors[ImGuiCol_TabActive] = surfaceVariant;
style.Colors[ImGuiCol_TabUnfocused] = surface;
style.Colors[ImGuiCol_TabUnfocusedActive] = surfaceVariant;
style.Colors[ImGuiCol_TabSelectedOverline] = accent;
style.Colors[ImGuiCol_TableHeaderBg] = surfaceVariant;
style.Colors[ImGuiCol_TableBorderStrong] = border;
style.Colors[ImGuiCol_TableBorderLight] =
ImVec4(border.x * 0.7f, border.y * 0.7f, border.z * 0.7f, border.w);
style.Colors[ImGuiCol_TableRowBg] = ImVec4(0, 0, 0, 0);
style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(fg.x, fg.y, fg.z, 0.06f);
style.Colors[ImGuiCol_Separator] = border;
style.Colors[ImGuiCol_SeparatorHovered] = accent;
style.Colors[ImGuiCol_SeparatorActive] = accent;
style.Colors[ImGuiCol_MenuBarBg] = surface;
style.Colors[ImGuiCol_DockingPreview] = ImVec4(accent.x, accent.y, accent.z, 0.7f);
style.Colors[ImGuiCol_DockingEmptyBg] = bg;
}

View File

@@ -1,8 +1,9 @@
#ifndef CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP
#define CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP
#include "color_text_edit/TextEditor.h"
#include "palette_controller.hpp"
#include "color_table_renderer.hpp"
#include "preview_renderer.hpp"
class template_editor;
@@ -18,15 +19,12 @@ public:
private:
void render_controls();
void render_color_table();
void render_preview_content();
void apply_palette_to_editor();
void apply_palette_to_imgui() const;
void apply_themes();
void notify_palette_changed();
palette_controller m_controller;
TextEditor m_editor;
color_table_renderer m_color_table;
preview_renderer m_preview;
template_editor* m_template_editor{nullptr};
};

View File

@@ -0,0 +1,122 @@
#include "color_table_renderer.hpp"
#include "imgui.h"
#include <vector>
void color_table_renderer::render_color_row(const std::string &name,
const clrsync::core::palette& current,
palette_controller& controller,
const OnColorChangedCallback& on_changed)
{
const auto &colors = current.colors();
auto it = colors.find(name);
if (it == colors.end())
return;
const clrsync::core::color &col = it->second;
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted(name.c_str());
ImGui::TableSetColumnIndex(1);
{
std::string hex_str = col.to_hex_string();
char buf[9];
strncpy(buf, hex_str.c_str(), sizeof(buf));
buf[8] = 0;
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::InputText(("##hex_" + name).c_str(), buf, sizeof(buf),
ImGuiInputTextFlags_CharsUppercase))
{
try
{
clrsync::core::color new_color;
new_color.from_hex_string(buf);
controller.set_color(name, new_color);
if (on_changed)
on_changed();
}
catch (...)
{
}
}
}
ImGui::TableSetColumnIndex(2);
ImGui::PushID(name.c_str());
float c[4] = {((col.hex() >> 24) & 0xFF) / 255.0f, ((col.hex() >> 16) & 0xFF) / 255.0f,
((col.hex() >> 8) & 0xFF) / 255.0f, (col.hex() & 0xFF) / 255.0f};
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::ColorEdit4(("##color_" + name).c_str(), c,
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel |
ImGuiColorEditFlags_AlphaBar))
{
uint32_t r = (uint32_t)(c[0] * 255.0f);
uint32_t g = (uint32_t)(c[1] * 255.0f);
uint32_t b = (uint32_t)(c[2] * 255.0f);
uint32_t a = (uint32_t)(c[3] * 255.0f);
uint32_t hex = (r << 24) | (g << 16) | (b << 8) | a;
controller.set_color(name, clrsync::core::color(hex));
if (on_changed)
on_changed();
}
ImGui::PopID();
}
void color_table_renderer::render(const clrsync::core::palette& current,
palette_controller& controller,
const OnColorChangedCallback& on_changed)
{
if (current.colors().empty())
{
ImGui::Text("No palette loaded");
return;
}
ImGui::Text("Color Variables");
ImGui::Separator();
auto draw_table = [&](const char *title, const std::vector<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, current, controller, on_changed);
ImGui::EndTable();
}
ImGui::Spacing();
};
draw_table("General UI", {"background", "on_background", "surface", "on_surface",
"surface_variant", "on_surface_variant", "foreground",
"cursor", "accent"});
draw_table("Borders", {"border_focused", "border"});
draw_table("Semantic Colors", {"success", "info", "warning", "error",
"on_success", "on_info", "on_warning", "on_error"});
draw_table("Editor", {"editor_background", "editor_command", "editor_comment",
"editor_disabled", "editor_emphasis", "editor_error",
"editor_inactive", "editor_line_number", "editor_link",
"editor_main", "editor_selected", "editor_selection_inactive",
"editor_string", "editor_success", "editor_warning"});
draw_table("Terminal (Base16)", {"base00", "base01", "base02", "base03",
"base04", "base05", "base06", "base07",
"base08", "base09", "base0A", "base0B",
"base0C", "base0D", "base0E", "base0F"});
}

View File

@@ -0,0 +1,24 @@
#ifndef CLRSYNC_GUI_COLOR_TABLE_RENDERER_HPP
#define CLRSYNC_GUI_COLOR_TABLE_RENDERER_HPP
#include "core/palette/palette.hpp"
#include "palette_controller.hpp"
#include <functional>
class color_table_renderer
{
public:
using OnColorChangedCallback = std::function<void()>;
void render(const clrsync::core::palette& palette,
palette_controller& controller,
const OnColorChangedCallback& on_changed);
private:
void render_color_row(const std::string& name,
const clrsync::core::palette& palette,
palette_controller& controller,
const OnColorChangedCallback& on_changed);
};
#endif // CLRSYNC_GUI_COLOR_TABLE_RENDERER_HPP

View File

@@ -1,3 +1,4 @@
#include <iostream>
#include <string>
#include "GLFW/glfw3.h"
@@ -11,14 +12,26 @@
GLFWwindow * init_glfw()
{
if (!glfwInit()) return nullptr;
glfwSetErrorCallback([](int error, const char* description) {
std::cerr << "GLFW Error " << error << ": " << description << std::endl;
});
if (!glfwInit())
{
std::cerr << "Failed to initialize GLFW\n";
return nullptr;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
GLFWwindow* w = glfwCreateWindow(1280, 720, "clrsync", nullptr, nullptr);
if (!w) return nullptr;
if (!w)
{
std::cerr << "Failed to create GLFW window\n";
return nullptr;
}
glfwMakeContextCurrent(w);
glfwSwapInterval(1);

View File

@@ -20,7 +20,17 @@ 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));
try
{
clrsync::core::config::instance().initialize(std::move(conf));
}
catch (const std::exception& e)
{
std::cerr << "Fatal error: " << e.what() << std::endl;
std::cerr << "Hint: Set CLRSYNC_CONFIG_PATH environment variable or ensure config exists at: " << config_path << std::endl;
return 1;
}
std::filesystem::path base = config_path;
static std::string ini_path = (base.parent_path() / "layout.ini").string();

View File

@@ -0,0 +1,152 @@
#include "preview_renderer.hpp"
#include "theme_applier.hpp"
#include "imgui.h"
#include <algorithm>
preview_renderer::preview_renderer()
{
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);
}
void preview_renderer::apply_palette(const clrsync::core::palette& palette)
{
theme_applier::apply_to_editor(m_editor, palette);
}
void preview_renderer::render_code_preview()
{
const float avail_height = ImGui::GetContentRegionAvail().y;
const float code_preview_height = std::max(250.0f, avail_height * 0.55f);
ImGui::Text("Code Editor:");
m_editor.Render("##CodeEditor", ImVec2(0, code_preview_height), true);
}
void preview_renderer::render_terminal_preview(const clrsync::core::palette& current)
{
auto get_color = [&](const std::string &key) -> ImVec4 {
auto it = current.colors().find(key);
if (it != current.colors().end())
{
const auto &col = it->second;
const uint32_t hex = col.hex();
return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f,
((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f};
}
return {1, 1, 1, 1};
};
const ImVec4 editor_bg = get_color("editor_background");
const ImVec4 fg = get_color("foreground");
const ImVec4 accent = get_color("accent");
const ImVec4 border = get_color("border");
const ImVec4 error = get_color("error");
const ImVec4 warning = get_color("warning");
const ImVec4 success = get_color("success");
const ImVec4 info = get_color("info");
ImGui::Spacing();
ImGui::Text("Terminal Preview:");
ImGui::PushStyleColor(ImGuiCol_ChildBg, editor_bg);
ImGui::BeginChild("TerminalPreview", ImVec2(0, 0), true);
ImGui::PushStyleColor(ImGuiCol_Border, border);
struct term_line
{
const char *text{};
ImVec4 col;
};
term_line term_lines[] = {
{"$ ls -la", fg},
{"drwxr-xr-x 5 user group 4096 Dec 2 10:30 .", accent},
{"Build successful", success},
{"Error: file not found", error},
{"Warning: low disk space", warning},
{"Info: update available", info},
};
for (auto &[text, col] : term_lines)
{
ImGui::TextColored(col, "%s", text);
}
ImGui::PopStyleColor(2);
ImGui::EndChild();
}
void preview_renderer::render(const clrsync::core::palette& current)
{
if (current.colors().empty())
{
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Current palette is empty");
return;
}
render_code_preview();
render_terminal_preview(current);
}

View File

@@ -0,0 +1,22 @@
#ifndef CLRSYNC_GUI_PREVIEW_RENDERER_HPP
#define CLRSYNC_GUI_PREVIEW_RENDERER_HPP
#include "core/palette/palette.hpp"
#include "color_text_edit/TextEditor.h"
class preview_renderer
{
public:
preview_renderer();
void render(const clrsync::core::palette& palette);
void apply_palette(const clrsync::core::palette& palette);
private:
void render_code_preview();
void render_terminal_preview(const clrsync::core::palette& palette);
TextEditor m_editor;
};
#endif // CLRSYNC_GUI_PREVIEW_RENDERER_HPP

View File

@@ -369,12 +369,15 @@ void template_editor::save_template()
std::string template_content = m_editor.GetText();
std::ofstream out(template_file);
if (out.is_open())
if (!out.is_open())
{
out << template_content;
out.close();
m_validation_error = "Failed to write template file";
return;
}
out << template_content;
out.close();
clrsync::core::theme_template tmpl(trimmed_name, template_file.string(), trimmed_path);
tmpl.set_reload_command(m_reload_command);
tmpl.set_enabled(m_enabled);

166
src/gui/theme_applier.cpp Normal file
View File

@@ -0,0 +1,166 @@
#include "theme_applier.hpp"
#include "imgui.h"
#include <iostream>
namespace theme_applier
{
void apply_to_editor(TextEditor& editor, const clrsync::core::palette& current)
{
if (current.colors().empty())
return;
auto get_color_u32 = [&](const std::string &key, const std::string &fallback = "") -> uint32_t {
auto it = current.colors().find(key);
if (it == current.colors().end() && !fallback.empty())
{
it = current.colors().find(fallback);
}
if (it != current.colors().end())
{
const auto &col = it->second;
const uint32_t hex = col.hex();
// Convert from RRGGBBAA to AABBGGRR (ImGui format)
const uint32_t r = (hex >> 24) & 0xFF;
const uint32_t g = (hex >> 16) & 0xFF;
const uint32_t b = (hex >> 8) & 0xFF;
const uint32_t a = hex & 0xFF;
return (a << 24) | (b << 16) | (g << 8) | r;
}
return 0xFFFFFFFF;
};
auto palette = editor.GetPalette();
palette[int(TextEditor::PaletteIndex::Default)] = get_color_u32("editor_main");
palette[int(TextEditor::PaletteIndex::Keyword)] = get_color_u32("editor_command");
palette[int(TextEditor::PaletteIndex::Number)] = get_color_u32("editor_warning");
palette[int(TextEditor::PaletteIndex::String)] = get_color_u32("editor_string");
palette[int(TextEditor::PaletteIndex::CharLiteral)] = get_color_u32("editor_string");
palette[int(TextEditor::PaletteIndex::Punctuation)] = get_color_u32("editor_main");
palette[int(TextEditor::PaletteIndex::Preprocessor)] = get_color_u32("editor_emphasis");
palette[int(TextEditor::PaletteIndex::Identifier)] = get_color_u32("editor_main");
palette[int(TextEditor::PaletteIndex::KnownIdentifier)] = get_color_u32("editor_link");
palette[int(TextEditor::PaletteIndex::PreprocIdentifier)] = get_color_u32("editor_link");
palette[int(TextEditor::PaletteIndex::Comment)] = get_color_u32("editor_comment");
palette[int(TextEditor::PaletteIndex::MultiLineComment)] = get_color_u32("editor_comment");
palette[int(TextEditor::PaletteIndex::Background)] = get_color_u32("editor_background");
palette[int(TextEditor::PaletteIndex::Cursor)] = get_color_u32("cursor");
palette[int(TextEditor::PaletteIndex::Selection)] = get_color_u32("editor_selected");
palette[int(TextEditor::PaletteIndex::ErrorMarker)] = get_color_u32("editor_error");
palette[int(TextEditor::PaletteIndex::Breakpoint)] = get_color_u32("editor_error");
palette[int(TextEditor::PaletteIndex::LineNumber)] = get_color_u32("editor_line_number");
palette[int(TextEditor::PaletteIndex::CurrentLineFill)] = get_color_u32("surface_variant");
palette[int(TextEditor::PaletteIndex::CurrentLineFillInactive)] = get_color_u32("surface");
palette[int(TextEditor::PaletteIndex::CurrentLineEdge)] = get_color_u32("border_focused");
editor.SetPalette(palette);
}
void apply_to_imgui(const clrsync::core::palette& current)
{
if (current.colors().empty())
return;
auto getColor = [&](const std::string &key, const std::string &fallback = "") -> ImVec4 {
auto it = current.colors().find(key);
if (it == current.colors().end() && !fallback.empty())
{
it = current.colors().find(fallback);
}
if (it != current.colors().end())
{
const uint32_t hex = it->second.hex();
return {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f,
((hex >> 8) & 0xFF) / 255.0f, ((hex) & 0xFF) / 255.0f};
}
std::cout << "WARNING: Color key '" << key << "' not found!\n";
return {1, 1, 1, 1};
};
ImGuiStyle &style = ImGui::GetStyle();
const ImVec4 bg = getColor("background");
const ImVec4 surface = getColor("surface");
const ImVec4 surfaceVariant = getColor("surface_variant");
const ImVec4 fg = getColor("foreground");
const ImVec4 fgInactive = getColor("editor_inactive");
const ImVec4 accent = getColor("accent");
const ImVec4 border = getColor("border");
style.Colors[ImGuiCol_WindowBg] = bg;
style.Colors[ImGuiCol_ChildBg] = surface;
style.Colors[ImGuiCol_PopupBg] = surface;
style.Colors[ImGuiCol_Border] = border;
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0);
style.Colors[ImGuiCol_Text] = fg;
style.Colors[ImGuiCol_TextDisabled] = fgInactive;
style.Colors[ImGuiCol_Header] = surfaceVariant;
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f);
style.Colors[ImGuiCol_HeaderActive] = accent;
style.Colors[ImGuiCol_Button] = surfaceVariant;
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
style.Colors[ImGuiCol_ButtonActive] = accent;
style.Colors[ImGuiCol_FrameBg] = surfaceVariant;
style.Colors[ImGuiCol_FrameBgHovered] =
ImVec4(surfaceVariant.x * 1.1f, surfaceVariant.y * 1.1f, surfaceVariant.z * 1.1f, 1.0f);
style.Colors[ImGuiCol_FrameBgActive] =
ImVec4(surfaceVariant.x * 1.2f, surfaceVariant.y * 1.2f, surfaceVariant.z * 1.2f, 1.0f);
style.Colors[ImGuiCol_TitleBg] = surface;
style.Colors[ImGuiCol_TitleBgActive] = surfaceVariant;
style.Colors[ImGuiCol_TitleBgCollapsed] = surface;
style.Colors[ImGuiCol_ScrollbarBg] = surface;
style.Colors[ImGuiCol_ScrollbarGrab] = surfaceVariant;
style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
style.Colors[ImGuiCol_ScrollbarGrabActive] = accent;
style.Colors[ImGuiCol_SliderGrab] = accent;
style.Colors[ImGuiCol_SliderGrabActive] =
ImVec4(accent.x * 1.2f, accent.y * 1.2f, accent.z * 1.2f, 1.0f);
style.Colors[ImGuiCol_CheckMark] = accent;
style.Colors[ImGuiCol_ResizeGrip] = surfaceVariant;
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(accent.x, accent.y, accent.z, 0.6f);
style.Colors[ImGuiCol_ResizeGripActive] = accent;
style.Colors[ImGuiCol_Tab] = surface;
style.Colors[ImGuiCol_TabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.8f);
style.Colors[ImGuiCol_TabActive] = surfaceVariant;
style.Colors[ImGuiCol_TabUnfocused] = surface;
style.Colors[ImGuiCol_TabUnfocusedActive] = surfaceVariant;
style.Colors[ImGuiCol_TabSelectedOverline] = accent;
style.Colors[ImGuiCol_TableHeaderBg] = surfaceVariant;
style.Colors[ImGuiCol_TableBorderStrong] = border;
style.Colors[ImGuiCol_TableBorderLight] =
ImVec4(border.x * 0.7f, border.y * 0.7f, border.z * 0.7f, border.w);
style.Colors[ImGuiCol_TableRowBg] = ImVec4(0, 0, 0, 0);
style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(fg.x, fg.y, fg.z, 0.06f);
style.Colors[ImGuiCol_Separator] = border;
style.Colors[ImGuiCol_SeparatorHovered] = accent;
style.Colors[ImGuiCol_SeparatorActive] = accent;
style.Colors[ImGuiCol_MenuBarBg] = surface;
style.Colors[ImGuiCol_DockingPreview] = ImVec4(accent.x, accent.y, accent.z, 0.7f);
style.Colors[ImGuiCol_DockingEmptyBg] = bg;
}
} // namespace theme_applier

13
src/gui/theme_applier.hpp Normal file
View File

@@ -0,0 +1,13 @@
#ifndef CLRSYNC_GUI_THEME_APPLIER_HPP
#define CLRSYNC_GUI_THEME_APPLIER_HPP
#include "core/palette/palette.hpp"
#include "color_text_edit/TextEditor.h"
namespace theme_applier
{
void apply_to_imgui(const clrsync::core::palette& pal);
void apply_to_editor(TextEditor& editor, const clrsync::core::palette& pal);
}
#endif // CLRSYNC_GUI_THEME_APPLIER_HPP