mirror of
https://github.com/obsqrbtz/clrsync.git
synced 2026-04-09 04:29:04 +03:00
init
This commit is contained in:
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