chore: refactor

This commit is contained in:
2025-12-19 17:22:23 +03:00
parent 82998d688c
commit 4ada2c44ed
38 changed files with 1628 additions and 500 deletions

View File

@@ -11,8 +11,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
file(WRITE "${CMAKE_SOURCE_DIR}/.clangd"
"CompileFlags:
CompilationDatabase: ${CMAKE_BINARY_DIR}")
"CompileFlags:\n CompilationDatabase: ${CMAKE_BINARY_DIR}\n")
option(USE_SYSTEM_GLFW "Use system-installed GLFW instead of fetching it statically" OFF)
message(STATUS "USE_SYSTEM_GLFW: ${USE_SYSTEM_GLFW}")

View File

@@ -6,7 +6,7 @@
namespace clrsync::core
{
const std::string GIT_SEMVER = "0.1.7+git.g6ac9c03";
const std::string GIT_SEMVER = "0.1.7+git.g82998d6";
const std::string version_string();
} // namespace clrsync::core

View File

@@ -7,19 +7,23 @@ set(GUI_SOURCES
views/template_editor.cpp
controllers/palette_controller.cpp
controllers/template_controller.cpp
helpers/imgui_helpers.cpp
helpers/imgui_helpers.hpp
views/about_window.cpp
views/settings_window.cpp
widgets/colors.cpp
widgets/dialogs.cpp
widgets/palette_selector.cpp
widgets/input_dialog.cpp
widgets/action_buttons.cpp
widgets/styled_checkbox.cpp
widgets/autocomplete.cpp
widgets/form_field.cpp
layout/main_layout.cpp
platform/windows/font_loader_windows.cpp
platform/linux/font_loader_linux.cpp
platform/macos/font_loader_macos.cpp
platform/linux/file_browser_linux.cpp
platform/windows/file_browser_windows.cpp
${CMAKE_SOURCE_DIR}/lib/color_text_edit/TextEditor.cpp
platform/linux/font_loader_linux.cpp
platform/macos/font_loader_macos.cpp
platform/windows/font_loader_windows.cpp
backend/glfw_opengl.cpp
ui_manager.cpp
)

View File

@@ -9,7 +9,6 @@ namespace clrsync::gui::backend{
std::string title = "clrsync";
int width = 1280;
int height = 720;
bool resizable = true;
bool decorated = true;
bool transparent_framebuffer = true;
float clear_color[4] = {0.1f, 0.1f, 0.1f, 0.0f};

View File

@@ -1,5 +1,6 @@
#include "gui/backend/glfw_opengl.hpp"
#include <iostream>
#include <string>
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
@@ -33,7 +34,7 @@ bool glfw_opengl_backend::initialize(const window_config &config)
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
#endif
glfwWindowHint(GLFW_RESIZABLE, config.resizable ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE,GLFW_TRUE);
glfwWindowHint(GLFW_DECORATED, config.decorated ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, config.transparent_framebuffer ? GLFW_TRUE : GLFW_FALSE);
@@ -98,6 +99,22 @@ void *glfw_opengl_backend::get_graphics_context() const
return static_cast<void *>(m_window);
}
std::string glfw_opengl_backend::get_glfw_version() const
{
return glfwGetVersionString();
}
std::string glfw_opengl_backend::get_glfw_platform() const
{
switch (glfwGetPlatform()) {
case GLFW_PLATFORM_WAYLAND: return "Wayland";
case GLFW_PLATFORM_X11: return "X11";
case GLFW_PLATFORM_COCOA: return "Cocoa";
case GLFW_PLATFORM_WIN32: return "Win32";
default: return "Unknown";
}
}
bool glfw_opengl_backend::init_imgui_backend()
{
if (!m_window)

View File

@@ -23,6 +23,9 @@ namespace clrsync::gui::backend{
void* get_native_window() const override;
void* get_graphics_context() const override;
std::string get_glfw_version() const;
std::string get_glfw_platform() const;
bool init_imgui_backend() override;
void shutdown_imgui_backend() override;
void imgui_new_frame() override;

View File

@@ -1,266 +0,0 @@
#include <iostream>
#include <string>
#include "GLFW/glfw3.h"
#include "gui/views/settings_window.hpp"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include "imgui_helpers.hpp"
#include "imgui_internal.h"
GLFWwindow *init_glfw()
{
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);
#ifdef __APPLE__
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
GLFWwindow *w = glfwCreateWindow(1280, 720, "clrsync", nullptr, nullptr);
if (!w)
{
std::cerr << "Failed to create GLFW window\n";
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);
#ifdef __APPLE__
ImGui_ImplOpenGL3_Init("#version 150");
#else
ImGui_ImplOpenGL3_Init("#version 120");
#endif
}
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"))
{
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 | ImGuiWindowFlags_MenuBar;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.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 center, right;
ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.5f, &right, &center);
ImGuiDockNode *center_node = ImGui::DockBuilderGetNode(center);
if (center_node)
{
center_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode;
}
ImGui::DockBuilderDockWindow("Color Schemes", right);
ImGui::DockBuilderDockWindow("Color Preview", center);
ImGui::DockBuilderDockWindow("Templates", center);
ImGui::DockBuilderFinish(dockspace_id);
}
ImGui::DockSpace(dockspace_id, ImVec2{0, 0}, ImGuiDockNodeFlags_None);
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();
}
namespace palette_utils
{
ImVec4 get_color(const clrsync::core::palette &pal, const std::string &key,
const std::string &fallback)
{
auto colors = pal.colors();
if (colors.empty())
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
auto it = colors.find(key);
if (it == colors.end() && !fallback.empty())
{
it = colors.find(fallback);
}
if (it != colors.end())
{
const auto &col = it->second;
const uint32_t hex = col.hex();
const float r = ((hex >> 24) & 0xFF) / 255.0f;
const float g = ((hex >> 16) & 0xFF) / 255.0f;
const float b = ((hex >> 8) & 0xFF) / 255.0f;
const float a = (hex & 0xFF) / 255.0f;
return ImVec4(r, g, b, a);
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
}
uint32_t get_color_u32(const clrsync::core::palette &pal, const std::string &key,
const std::string &fallback)
{
auto colors = pal.colors();
if (colors.empty())
return 0xFFFFFFFF;
auto it = colors.find(key);
if (it == colors.end() && !fallback.empty())
{
it = colors.find(fallback);
}
if (it != colors.end())
{
const auto &col = it->second;
const uint32_t hex = col.hex();
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;
}
bool render_delete_confirmation_popup(const std::string &popup_title, const std::string &item_name,
const std::string &item_type,
const clrsync::core::palette &pal,
const std::function<void()> &on_delete)
{
bool result = false;
if (ImGui::BeginPopupModal(popup_title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImVec4 warning_color = get_color(pal, "warning", "accent");
ImGui::TextColored(warning_color, "Are you sure you want to delete '%s'?",
item_name.c_str());
ImGui::Text("This action cannot be undone.");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
float button_width = 120.0f;
float total_width = 2.0f * button_width + ImGui::GetStyle().ItemSpacing.x;
float window_width = ImGui::GetContentRegionAvail().x;
ImGui::SetCursorPosX((window_width - total_width) * 0.5f);
if (ImGui::Button("Delete", ImVec2(button_width, 0)))
{
on_delete();
result = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(button_width, 0)))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return result;
}
} // namespace palette_utils

View File

@@ -1,42 +0,0 @@
#ifndef CLRSYNC_IMGUI_HELPERS_HPP
#define CLRSYNC_IMGUI_HELPERS_HPP
#include "core/palette/palette.hpp"
#include "gui/views/about_window.hpp"
#include "imgui.h"
#include <functional>
#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);
namespace palette_utils
{
ImVec4 get_color(const clrsync::core::palette &pal, const std::string &key,
const std::string &fallback = "");
uint32_t get_color_u32(const clrsync::core::palette &pal, const std::string &key,
const std::string &fallback = "");
bool render_delete_confirmation_popup(const std::string &popup_title, const std::string &item_name,
const std::string &item_type,
const clrsync::core::palette &pal,
const std::function<void()> &on_delete);
} // namespace palette_utils
namespace imgui_helpers
{
inline ImVec4 get_palette_color(const clrsync::core::palette &pal, const std::string &key,
const std::string &fallback = "")
{
return palette_utils::get_color(pal, key, fallback);
}
} // namespace imgui_helpers
#endif // CLRSYNC_IMGUI_HELPERS_HPP

View File

@@ -0,0 +1,116 @@
#include "main_layout.hpp"
#include "imgui.h"
#include "imgui_internal.h"
namespace clrsync::gui::layout
{
void main_layout::render_menu_bar()
{
ImGuiViewport *vp = ImGui::GetMainViewport();
const float bar_height = 30.0f;
ImGui::SetNextWindowPos(vp->Pos);
ImGui::SetNextWindowSize(ImVec2(vp->Size.x, bar_height));
ImGui::SetNextWindowViewport(vp->ID);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 2));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 3));
ImGui::Begin("##TopBar", nullptr, flags);
ImGuiStyle &style = ImGui::GetStyle();
const char *settings_label = "Settings";
const char *about_label = "About";
ImVec2 settings_size = ImGui::CalcTextSize(settings_label);
ImVec2 about_size = ImGui::CalcTextSize(about_label);
float total_width = settings_size.x + style.FramePadding.x * 2.0f + about_size.x +
style.FramePadding.x * 2.0f + style.ItemSpacing.x;
float pos_x = ImGui::GetWindowWidth() - total_width - style.WindowPadding.x;
float button_height = ImGui::GetFrameHeight();
float window_height = ImGui::GetWindowHeight();
float center_y = (window_height - button_height) * 0.5f;
ImGui::SetCursorPos(ImVec2(pos_x, center_y));
if (ImGui::Button(settings_label))
{
m_show_settings = true;
}
ImGui::SameLine();
ImGui::SetCursorPosY(center_y);
if (ImGui::Button(about_label))
{
m_show_about = true;
}
ImGui::End();
ImGui::PopStyleVar(4);
}
void main_layout::setup_dockspace(bool &first_time)
{
const ImGuiViewport *viewport = ImGui::GetMainViewport();
const float topbar_height = 32.0f;
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + topbar_height));
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - topbar_height));
ImGui::SetNextWindowViewport(viewport->ID);
constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 3));
ImGui::Begin("MainDockSpace", nullptr, flags);
ImGui::PopStyleVar(4);
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 center, right;
ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.5f, &right, &center);
ImGuiDockNode *center_node = ImGui::DockBuilderGetNode(center);
if (center_node)
{
center_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode;
}
ImGui::DockBuilderDockWindow("Color Schemes", right);
ImGui::DockBuilderDockWindow("Color Preview", center);
ImGui::DockBuilderDockWindow("Templates", center);
ImGui::DockBuilderFinish(dockspace_id);
}
ImGui::DockSpace(dockspace_id, ImVec2{0, 0}, ImGuiDockNodeFlags_None);
ImGui::End();
}
} // namespace clrsync::gui::layout

View File

@@ -0,0 +1,28 @@
#ifndef CLRSYNC_GUI_LAYOUT_MAIN_LAYOUT_HPP
#define CLRSYNC_GUI_LAYOUT_MAIN_LAYOUT_HPP
namespace clrsync::gui::layout
{
class main_layout
{
public:
void setup_dockspace(bool &first_time);
void render_menu_bar();
bool should_show_about() const { return m_show_about; }
bool should_show_settings() const { return m_show_settings; }
void clear_actions()
{
m_show_about = false;
m_show_settings = false;
}
private:
bool m_show_about = false;
bool m_show_settings = false;
};
} // namespace clrsync::gui::layout
#endif // CLRSYNC_GUI_LAYOUT_MAIN_LAYOUT_HPP

View File

@@ -1,17 +1,13 @@
#include <iostream>
#include <memory>
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include "core/common/error.hpp"
#include "core/common/utils.hpp"
#include "core/config/config.hpp"
#include "core/io/toml_file.hpp"
#include "gui/backend/glfw_opengl.hpp"
#include "gui/helpers/imgui_helpers.hpp"
#include "gui/platform/font_loader.hpp"
#include "gui/layout/main_layout.hpp"
#include "gui/ui_manager.hpp"
#include "gui/views/about_window.hpp"
#include "gui/views/color_scheme_editor.hpp"
@@ -37,13 +33,12 @@ int main(int, char **)
static std::string ini_path = (base.parent_path() / "layout.ini").string();
bool first_time = !std::filesystem::exists(ini_path);
printf("GLFW Version: %s\n", glfwGetVersionString());
auto backend = clrsync::gui::backend::glfw_opengl_backend();
auto window_config = clrsync::gui::backend::window_config();
window_config.title = "clrsync";
window_config.width = 1280;
window_config.height = 720;
window_config.decorated = true;
window_config.transparent_framebuffer = true;
if (!backend.initialize(window_config))
@@ -52,14 +47,8 @@ int main(int, char **)
return 1;
}
std::cout << "GLFW runtime platform: ";
switch (glfwGetPlatform()) {
case GLFW_PLATFORM_WAYLAND: std::cout << "Wayland\n"; break;
case GLFW_PLATFORM_X11: std::cout << "X11\n"; break;
case GLFW_PLATFORM_COCOA: std::cout << "Cocoa\n"; break;
case GLFW_PLATFORM_WIN32: std::cout << "Win32\n"; break;
default: std::cout << "Unknown\n";
}
std::cout << "GLFW Version: " << backend.get_glfw_version() << std::endl;
std::cout << "GLFW runtime platform: " << backend.get_glfw_platform() << std::endl;
clrsync::gui::ui_manager ui_manager(&backend);
@@ -74,18 +63,11 @@ int main(int, char **)
return 1;
}
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;
clrsync::gui::layout::main_layout main_layout;
color_scheme_editor colorEditor;
template_editor templateEditor;
template_editor templateEditor(&ui_manager);
about_window aboutWindow;
settings_window settingsWindow;
settings_window settingsWindow(&ui_manager);
colorEditor.set_template_editor(&templateEditor);
colorEditor.set_settings_window(&settingsWindow);
@@ -96,18 +78,25 @@ int main(int, char **)
{
backend.begin_frame();
loader.push_default_font();
ui_manager.push_default_font();
ui_manager.begin_frame();
render_menu_bar(&aboutWindow, &settingsWindow);
setup_main_dockspace(first_time);
main_layout.render_menu_bar();
main_layout.setup_dockspace(first_time);
if (main_layout.should_show_about())
aboutWindow.show();
if (main_layout.should_show_settings())
settingsWindow.show();
main_layout.clear_actions();
templateEditor.render();
colorEditor.render_controls_and_colors();
colorEditor.render_preview();
aboutWindow.render(colorEditor.controller().current_palette());
settingsWindow.render();
loader.pop_font();
ui_manager.pop_font();
ui_manager.end_frame();
backend.end_frame();

View File

@@ -1,5 +1,8 @@
#include "gui/ui_manager.hpp"
#include "gui/backend/backend.hpp"
#include "gui/platform/font_loader.hpp"
#include "gui/platform/file_browser.hpp"
#include "core/config/config.hpp"
#include <imgui.h>
@@ -8,11 +11,14 @@ namespace clrsync::gui
ui_manager::ui_manager(backend::backend_interface* backend)
: m_backend(backend)
{
m_font_loader = new font_loader();
}
ui_manager::~ui_manager()
{
shutdown();
delete m_font_loader;
m_font_loader = nullptr;
}
bool ui_manager::initialize(const ui_config& config)
@@ -44,6 +50,13 @@ bool ui_manager::initialize(const ui_config& config)
return false;
}
ImFont *font = m_font_loader->load_font(
clrsync::core::config::instance().font().c_str(),
clrsync::core::config::instance().font_size());
if (font)
io.FontDefault = font;
return true;
}
@@ -68,4 +81,57 @@ void ui_manager::end_frame()
ImGui::Render();
m_backend->imgui_render_draw_data(ImGui::GetDrawData());
}
}
void ui_manager::push_default_font()
{
if (m_font_loader)
m_font_loader->push_default_font();
}
void ui_manager::pop_font()
{
if (m_font_loader)
m_font_loader->pop_font();
}
std::vector<std::string> ui_manager::get_system_fonts() const
{
if (m_font_loader)
return m_font_loader->get_system_fonts();
return {};
}
bool ui_manager::reload_font(const char* font_name, float size)
{
if (!m_font_loader)
return false;
ImFont* font = m_font_loader->load_font(font_name, size);
if (font)
{
ImGui::GetIO().FontDefault = font;
return true;
}
return false;
}
std::string ui_manager::open_file_dialog(const std::string& title,
const std::string& initial_path,
const std::vector<std::string>& filters)
{
return file_dialogs::open_file_dialog(title, initial_path, filters);
}
std::string ui_manager::save_file_dialog(const std::string& title,
const std::string& initial_path,
const std::vector<std::string>& filters)
{
return file_dialogs::save_file_dialog(title, initial_path, filters);
}
std::string ui_manager::select_folder_dialog(const std::string& title,
const std::string& initial_path)
{
return file_dialogs::select_folder_dialog(title, initial_path);
}
}

View File

@@ -1,6 +1,10 @@
#ifndef CLRSYNC_UI_MANAGER_HPP
#define CLRSYNC_UI_MANAGER_HPP
#include <string>
#include <vector>
class font_loader;
struct ImFont;
namespace clrsync::gui::backend
{
@@ -29,9 +33,25 @@ public:
void begin_frame();
void end_frame();
void push_default_font();
void pop_font();
std::vector<std::string> get_system_fonts() const;
bool reload_font(const char* font_name, float size);
std::string open_file_dialog(const std::string& title = "Open File",
const std::string& initial_path = "",
const std::vector<std::string>& filters = {});
std::string save_file_dialog(const std::string& title = "Save File",
const std::string& initial_path = "",
const std::vector<std::string>& filters = {});
std::string select_folder_dialog(const std::string& title = "Select Folder",
const std::string& initial_path = "");
private:
backend::backend_interface *m_backend;
void *m_imgui_context = nullptr;
font_loader *m_font_loader = nullptr;
};
}

View File

@@ -1,6 +1,6 @@
#include "gui/views/about_window.hpp"
#include "core/common/version.hpp"
#include "gui/helpers/imgui_helpers.hpp"
#include "gui/widgets/colors.hpp"
#include "imgui.h"
about_window::about_window()
@@ -22,14 +22,14 @@ void about_window::render(const clrsync::core::palette &pal)
const char *title = "clrsync";
const float title_size = ImGui::CalcTextSize(title).x;
ImGui::SetCursorPosX((window_width - title_size) * 0.5f);
ImVec4 title_color = palette_utils::get_color(pal, "info", "accent");
ImVec4 title_color = clrsync::gui::widgets::palette_color(pal, "info", "accent");
ImGui::TextColored(title_color, "%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);
ImVec4 subtitle_color = palette_utils::get_color(pal, "editor_inactive", "foreground");
ImVec4 subtitle_color = clrsync::gui::widgets::palette_color(pal, "editor_inactive", "foreground");
ImGui::TextColored(subtitle_color, "%s", version.c_str());
ImGui::Spacing();
@@ -79,7 +79,7 @@ void about_window::render(const clrsync::core::palette &pal)
ImGui::Separator();
ImGui::Spacing();
ImVec4 license_color = palette_utils::get_color(pal, "editor_inactive", "foreground");
ImVec4 license_color = clrsync::gui::widgets::palette_color(pal, "editor_inactive", "foreground");
ImGui::TextColored(license_color, "MIT License");
ImGui::TextWrapped(
"Copyright (c) 2025 Daniel Dada\n\n"
@@ -93,4 +93,4 @@ void about_window::render(const clrsync::core::palette &pal)
"copies or substantial portions of the Software.");
}
ImGui::End();
}
}

View File

@@ -1,11 +1,13 @@
#include "color_scheme_editor.hpp"
#include "gui/controllers/theme_applier.hpp"
#include "gui/helpers/imgui_helpers.hpp"
#include "gui/widgets/dialogs.hpp"
#include "gui/widgets/palette_selector.hpp"
#include "gui/widgets/input_dialog.hpp"
#include "gui/widgets/action_buttons.hpp"
#include "imgui.h"
#include "settings_window.hpp"
#include "template_editor.hpp"
#include <iostream>
#include <ranges>
color_scheme_editor::color_scheme_editor()
{
@@ -20,6 +22,8 @@ color_scheme_editor::color_scheme_editor()
{
std::cout << "WARNING: No palette loaded, skipping theme application\n";
}
setup_widgets();
}
void color_scheme_editor::notify_palette_changed()
@@ -78,102 +82,22 @@ void color_scheme_editor::render_controls()
ImGui::Text("Palette:");
ImGui::SameLine();
ImGui::SetNextItemWidth(200.0f);
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_themes();
}
if (selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Select a color palette to edit");
m_palette_selector.render(m_controller, 200.0f);
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
static char new_palette_name_buf[128] = "";
if (ImGui::Button(" + New "))
{
new_palette_name_buf[0] = 0;
ImGui::OpenPopup("New Palette");
m_new_palette_dialog.open("New Palette", "Enter a name for the new palette:", "Palette name...");
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Create a new palette");
if (ImGui::BeginPopupModal("New Palette", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Enter a name for the new palette:");
ImGui::Spacing();
ImGui::SetNextItemWidth(250);
ImGui::InputTextWithHint("##new_palette_input", "Palette name...", new_palette_name_buf,
IM_ARRAYSIZE(new_palette_name_buf));
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
bool can_create = strlen(new_palette_name_buf) > 0;
if (!can_create)
ImGui::BeginDisabled();
if (ImGui::Button("Create", ImVec2(120, 0)))
{
m_controller.create_palette(new_palette_name_buf);
m_controller.select_palette(new_palette_name_buf);
apply_themes();
new_palette_name_buf[0] = 0;
ImGui::CloseCurrentPopup();
}
if (!can_create)
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0)))
{
new_palette_name_buf[0] = 0;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
m_new_palette_dialog.render();
ImGui::SameLine();
if (ImGui::Button(" Save "))
{
m_controller.save_current_palette();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Save current palette to file");
ImGui::SameLine();
auto error = palette_utils::get_color(current, "error");
auto error_hover = ImVec4(error.x * 1.1f, error.y * 1.1f, error.z * 1.1f, error.w);
auto error_active = ImVec4(error.x * 0.8f, error.y * 0.8f, error.z * 0.8f, error.w);
auto on_error = palette_utils::get_color(current, "on_error");
ImGui::PushStyleColor(ImGuiCol_Button, error);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, error_hover);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, error_active);
ImGui::PushStyleColor(ImGuiCol_Text, on_error);
if (ImGui::Button(" Delete "))
{
m_show_delete_confirmation = true;
}
ImGui::PopStyleColor(4);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Delete current palette");
m_action_buttons.render(current);
if (m_show_delete_confirmation)
{
@@ -181,21 +105,47 @@ void color_scheme_editor::render_controls()
m_show_delete_confirmation = false;
}
palette_utils::render_delete_confirmation_popup("Delete Palette?", current.name(), "palette",
clrsync::gui::widgets::delete_confirmation_dialog("Delete Palette?", current.name(), "palette",
current, [this]() {
m_controller.delete_current_palette();
apply_themes();
});
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 16);
if (ImGui::Button(" Apply Theme "))
{
m_controller.apply_current_theme();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Apply current palette to all enabled templates");
ImGui::PopStyleVar(2);
}
void color_scheme_editor::setup_widgets()
{
m_palette_selector.set_on_selection_changed([this](const std::string &name) {
m_controller.select_palette(name);
apply_themes();
});
m_new_palette_dialog.set_on_submit([this](const std::string &name) {
m_controller.create_palette(name);
m_controller.select_palette(name);
apply_themes();
});
m_action_buttons.add_button({
" Save ",
"Save current palette to file",
[this]() { m_controller.save_current_palette(); }
});
m_action_buttons.add_button({
" Delete ",
"Delete current palette",
[this]() { m_show_delete_confirmation = true; },
true,
true
});
m_action_buttons.add_button({
" Apply Theme ",
"Apply current palette to all enabled templates",
[this]() { m_controller.apply_current_theme(); }
});
m_action_buttons.set_spacing(16.0f);
}

View File

@@ -4,6 +4,9 @@
#include "gui/controllers/palette_controller.hpp"
#include "gui/views/color_table_renderer.hpp"
#include "gui/views/preview_renderer.hpp"
#include "gui/widgets/palette_selector.hpp"
#include "gui/widgets/input_dialog.hpp"
#include "gui/widgets/action_buttons.hpp"
class template_editor;
class settings_window;
@@ -32,6 +35,7 @@ class color_scheme_editor
void render_controls();
void apply_themes();
void notify_palette_changed();
void setup_widgets();
palette_controller m_controller;
color_table_renderer m_color_table;
@@ -39,6 +43,10 @@ class color_scheme_editor
template_editor *m_template_editor{nullptr};
settings_window *m_settings_window{nullptr};
bool m_show_delete_confirmation{false};
clrsync::gui::widgets::palette_selector m_palette_selector;
clrsync::gui::widgets::input_dialog m_new_palette_dialog;
clrsync::gui::widgets::action_buttons m_action_buttons;
};
#endif // CLRSYNC_GUI_COLOR_SCHEME_EDITOR_HPP

View File

@@ -1,5 +1,5 @@
#include "gui/views/color_table_renderer.hpp"
#include "gui/helpers/imgui_helpers.hpp"
#include "gui/widgets/colors.hpp"
#include "imgui.h"
#include <algorithm>
#include <cctype>
@@ -36,7 +36,7 @@ void color_table_renderer::render_color_row(const std::string &name,
ImGui::TableSetColumnIndex(0);
const float key_col_width = ImGui::GetContentRegionAvail().x;
ImVec4 text_color = palette_utils::get_color(current, "info", "accent");
ImVec4 text_color = clrsync::gui::widgets::palette_color(current, "info", "accent");
ImGui::PushStyleColor(ImGuiCol_Text, text_color);
const bool copied = ImGui::Selectable(name.c_str(), false, 0, ImVec2(key_col_width, 0.0f));
ImGui::PopStyleColor();
@@ -107,7 +107,7 @@ void color_table_renderer::render(const clrsync::core::palette &current,
{
if (current.colors().empty())
{
ImVec4 warning_color = palette_utils::get_color(current, "warning", "accent");
ImVec4 warning_color = clrsync::gui::widgets::palette_color(current, "warning", "accent");
ImGui::TextColored(warning_color, "No palette loaded");
return;
}
@@ -153,7 +153,7 @@ void color_table_renderer::render(const clrsync::core::palette &current,
if (!has_matches)
return;
ImGui::PushStyleColor(ImGuiCol_Text, palette_utils::get_color(current, "accent"));
ImGui::PushStyleColor(ImGuiCol_Text, clrsync::gui::widgets::palette_color(current, "accent"));
bool header_open = ImGui::TreeNodeEx(title, ImGuiTreeNodeFlags_DefaultOpen |
ImGuiTreeNodeFlags_SpanAvailWidth);
ImGui::PopStyleColor();

View File

@@ -1,6 +1,6 @@
#include "gui/views/preview_renderer.hpp"
#include "gui/controllers/theme_applier.hpp"
#include "gui/helpers/imgui_helpers.hpp"
#include "gui/widgets/colors.hpp"
#include "imgui.h"
#include <algorithm>
#include <array>
@@ -236,7 +236,7 @@ void preview_renderer::render(const clrsync::core::palette &current)
{
if (current.colors().empty())
{
ImVec4 error_color = palette_utils::get_color(current, "error", "accent");
ImVec4 error_color = clrsync::gui::widgets::palette_color(current, "error", "accent");
ImGui::TextColored(error_color, "Current palette is empty");
return;
}

View File

@@ -1,21 +1,21 @@
#include "gui/views/settings_window.hpp"
#include "core/common/error.hpp"
#include "core/config/config.hpp"
#include "gui/helpers/imgui_helpers.hpp"
#include "gui/platform/file_browser.hpp"
#include "gui/platform/font_loader.hpp"
#include "gui/widgets/colors.hpp"
#include "gui/ui_manager.hpp"
#include "imgui.h"
#include <cstring>
settings_window::settings_window()
: m_font_size(14), m_selected_font_idx(0), m_settings_changed(false), m_current_tab(0)
settings_window::settings_window(clrsync::gui::ui_manager* ui_mgr)
: m_font_size(14), m_selected_font_idx(0), m_settings_changed(false), m_current_tab(0),
m_ui_manager(ui_mgr)
{
m_default_theme[0] = '\0';
m_palettes_path[0] = '\0';
m_font[0] = '\0';
font_loader loader;
m_available_fonts = loader.get_system_fonts();
if (m_ui_manager)
m_available_fonts = m_ui_manager->get_system_fonts();
load_settings();
}
@@ -145,10 +145,14 @@ void settings_window::apply_settings()
return;
}
font_loader fn_loader;
auto font = fn_loader.load_font(m_font, m_font_size);
if (font)
ImGui::GetIO().FontDefault = font;
if (m_ui_manager)
{
if (!m_ui_manager->reload_font(m_font, m_font_size))
{
m_error_message = "Failed to load font: " + std::string(m_font);
return;
}
}
m_error_message.clear();
m_settings_changed = false;
@@ -158,7 +162,7 @@ void settings_window::render_general_tab()
{
ImGui::Spacing();
auto accent_color = palette_utils::get_color(m_current_palette, "accent");
auto accent_color = clrsync::gui::widgets::palette_color(m_current_palette, "accent");
ImGui::TextColored(accent_color, "Theme Settings");
ImGui::Separator();
ImGui::Spacing();
@@ -186,7 +190,7 @@ void settings_window::render_general_tab()
if (ImGui::Button("Browse"))
{
std::string selected_path =
file_dialogs::select_folder_dialog("Select Palettes Directory", m_palettes_path);
m_ui_manager->select_folder_dialog("Select Palettes Directory", m_palettes_path);
if (!selected_path.empty())
{
strncpy(m_palettes_path, selected_path.c_str(), sizeof(m_palettes_path) - 1);
@@ -200,7 +204,7 @@ void settings_window::render_appearance_tab()
{
ImGui::Spacing();
auto accent_color = palette_utils::get_color(m_current_palette, "accent");
auto accent_color = clrsync::gui::widgets::palette_color(m_current_palette, "accent");
ImGui::TextColored(accent_color, "Font Settings");
ImGui::Separator();
ImGui::Spacing();
@@ -254,8 +258,8 @@ void settings_window::render_status_messages()
{
ImGui::Spacing();
auto error_bg_color = palette_utils::get_color(m_current_palette, "error");
auto error_text_color = palette_utils::get_color(m_current_palette, "on_error");
auto error_bg_color = clrsync::gui::widgets::palette_color(m_current_palette, "error");
auto error_text_color = clrsync::gui::widgets::palette_color(m_current_palette, "on_error");
ImGui::PushStyleColor(ImGuiCol_ChildBg, error_bg_color);
ImGui::PushStyleColor(ImGuiCol_Border, error_bg_color);
@@ -375,4 +379,4 @@ void settings_window::reset_to_defaults()
m_font_size = 14;
m_error_message.clear();
m_settings_changed = true;
}
}

View File

@@ -5,10 +5,15 @@
#include <string>
#include <vector>
namespace clrsync::gui
{
class ui_manager;
}
class settings_window
{
public:
settings_window();
settings_window(clrsync::gui::ui_manager* ui_mgr);
void render();
void show()
{
@@ -55,6 +60,7 @@ class settings_window
int m_current_tab;
clrsync::core::palette m_current_palette;
clrsync::gui::ui_manager* m_ui_manager;
};
#endif // CLRSYNC_GUI_SETTINGS_WINDOW_HPP

View File

@@ -3,8 +3,9 @@
#include "core/config/config.hpp"
#include "core/palette/color_keys.hpp"
#include "core/theme/theme_template.hpp"
#include "gui/helpers/imgui_helpers.hpp"
#include "gui/platform/file_browser.hpp"
#include "gui/widgets/colors.hpp"
#include "gui/widgets/dialogs.hpp"
#include "gui/ui_manager.hpp"
#include "imgui.h"
#include <algorithm>
#include <filesystem>
@@ -18,7 +19,8 @@ const std::vector<std::string> COLOR_FORMATS = {
"l", "hsl", "hsla"};
}
template_editor::template_editor() : m_template_name("new_template")
template_editor::template_editor(clrsync::gui::ui_manager* ui_mgr)
: m_template_name("new_template"), m_ui_manager(ui_mgr)
{
m_autocomplete_bg_color = ImVec4(0.12f, 0.12f, 0.15f, 0.98f);
m_autocomplete_border_color = ImVec4(0.4f, 0.4f, 0.45f, 1.0f);
@@ -56,7 +58,7 @@ void template_editor::apply_current_palette(const clrsync::core::palette &pal)
if (colors.empty())
return;
auto get_color_u32 = [&](const std::string &key, const std::string &fallback = "") -> uint32_t {
return palette_utils::get_color_u32(pal, key, fallback);
return clrsync::gui::widgets::palette_color_u32(pal, key, fallback);
};
auto palette = m_editor.GetPalette();
@@ -99,14 +101,14 @@ void template_editor::apply_current_palette(const clrsync::core::palette &pal)
m_editor.SetPalette(palette);
m_autocomplete_bg_color = palette_utils::get_color(pal, "surface", "background");
m_autocomplete_bg_color = clrsync::gui::widgets::palette_color(pal, "surface", "background");
m_autocomplete_bg_color.w = 0.98f;
m_autocomplete_border_color = palette_utils::get_color(pal, "border", "surface_variant");
m_autocomplete_selected_color = palette_utils::get_color(pal, "accent", "surface_variant");
m_autocomplete_text_color = palette_utils::get_color(pal, "on_surface", "foreground");
m_autocomplete_selected_text_color = palette_utils::get_color(pal, "on_surface", "foreground");
m_autocomplete_border_color = clrsync::gui::widgets::palette_color(pal, "border", "surface_variant");
m_autocomplete_selected_color = clrsync::gui::widgets::palette_color(pal, "accent", "surface_variant");
m_autocomplete_text_color = clrsync::gui::widgets::palette_color(pal, "on_surface", "foreground");
m_autocomplete_selected_text_color = clrsync::gui::widgets::palette_color(pal, "on_surface", "foreground");
m_autocomplete_dim_text_color =
palette_utils::get_color(pal, "on_surface_variant", "editor_inactive");
clrsync::gui::widgets::palette_color(pal, "on_surface_variant", "editor_inactive");
}
void template_editor::update_autocomplete_suggestions()
@@ -358,7 +360,7 @@ void template_editor::render()
m_show_delete_confirmation = false;
}
palette_utils::render_delete_confirmation_popup(
clrsync::gui::widgets::delete_confirmation_dialog(
"Delete Template?", m_template_name, "template", m_current_palette, [this]() {
bool success = m_template_controller.remove_template(m_template_name);
if (success)
@@ -403,10 +405,10 @@ void template_editor::render_controls()
if (m_is_editing_existing)
{
ImGui::SameLine();
auto error = palette_utils::get_color(m_current_palette, "error");
auto error = clrsync::gui::widgets::palette_color(m_current_palette, "error");
auto error_hover = ImVec4(error.x * 1.1f, error.y * 1.1f, error.z * 1.1f, error.w);
auto error_active = ImVec4(error.x * 0.8f, error.y * 0.8f, error.z * 0.8f, error.w);
auto on_error = palette_utils::get_color(m_current_palette, "on_error");
auto on_error = clrsync::gui::widgets::palette_color(m_current_palette, "on_error");
ImGui::PushStyleColor(ImGuiCol_Button, error);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, error_hover);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, error_active);
@@ -426,9 +428,9 @@ void template_editor::render_controls()
bool enabled_changed = false;
if (m_enabled)
{
ImVec4 success_color = palette_utils::get_color(m_current_palette, "success", "accent");
ImVec4 success_color = clrsync::gui::widgets::palette_color(m_current_palette, "success", "accent");
ImVec4 success_on_color =
palette_utils::get_color(m_current_palette, "on_success", "on_surface");
clrsync::gui::widgets::palette_color(m_current_palette, "on_success", "on_surface");
ImVec4 success_hover =
ImVec4(success_color.x * 1.2f, success_color.y * 1.2f, success_color.z * 1.2f, 0.6f);
ImGui::PushStyleColor(ImGuiCol_FrameBg,
@@ -438,9 +440,9 @@ void template_editor::render_controls()
}
else
{
ImVec4 error_color = palette_utils::get_color(m_current_palette, "error", "accent");
ImVec4 error_color = clrsync::gui::widgets::palette_color(m_current_palette, "error", "accent");
ImVec4 error_on_color =
palette_utils::get_color(m_current_palette, "on_error", "on_surface");
clrsync::gui::widgets::palette_color(m_current_palette, "on_error", "on_surface");
ImVec4 error_hover =
ImVec4(error_color.x * 1.2f, error_color.y * 1.2f, error_color.z * 1.2f, 0.6f);
ImGui::PushStyleColor(ImGuiCol_FrameBg,
@@ -503,7 +505,7 @@ void template_editor::render_controls()
if (ImGui::Button("Browse##input"))
{
std::string selected_path =
file_dialogs::open_file_dialog("Select Template File", m_input_path);
m_ui_manager->open_file_dialog("Select Template File", m_input_path);
if (!selected_path.empty())
{
m_input_path = selected_path;
@@ -540,7 +542,7 @@ void template_editor::render_controls()
if (ImGui::Button("Browse##output"))
{
std::string selected_path =
file_dialogs::save_file_dialog("Select Output File", m_output_path);
m_ui_manager->save_file_dialog("Select Output File", m_output_path);
if (!selected_path.empty())
{
m_output_path = selected_path;
@@ -575,7 +577,7 @@ void template_editor::render_controls()
if (!m_validation_error.empty())
{
ImGui::Spacing();
ImVec4 error_color = palette_utils::get_color(m_current_palette, "error", "accent");
ImVec4 error_color = clrsync::gui::widgets::palette_color(m_current_palette, "error", "accent");
ImGui::PushStyleColor(ImGuiCol_Text, error_color);
ImGui::TextWrapped("%s", m_validation_error.c_str());
ImGui::PopStyleColor();
@@ -588,7 +590,7 @@ void template_editor::render_editor()
if (!m_is_editing_existing)
{
ImVec4 success_color = palette_utils::get_color(m_current_palette, "success", "accent");
ImVec4 success_color = clrsync::gui::widgets::palette_color(m_current_palette, "success", "accent");
ImGui::PushStyleColor(ImGuiCol_Text, success_color);
ImGui::Text(" New Template");
ImGui::PopStyleColor();
@@ -608,7 +610,7 @@ void template_editor::render_editor()
if (m_has_unsaved_changes)
{
ImGui::SameLine();
ImVec4 warning_color = palette_utils::get_color(m_current_palette, "warning", "accent");
ImVec4 warning_color = clrsync::gui::widgets::palette_color(m_current_palette, "warning", "accent");
ImGui::PushStyleColor(ImGuiCol_Text, warning_color);
ImGui::Text("(unsaved)");
ImGui::PopStyleColor();
@@ -697,7 +699,7 @@ void template_editor::render_template_list()
if (!m_is_editing_existing)
{
ImVec4 success_color = palette_utils::get_color(m_current_palette, "success", "accent");
ImVec4 success_color = clrsync::gui::widgets::palette_color(m_current_palette, "success", "accent");
ImVec4 success_bg = ImVec4(success_color.x, success_color.y, success_color.z, 0.5f);
ImGui::PushStyleColor(ImGuiCol_Text, success_color);
ImGui::PushStyleColor(ImGuiCol_Header, success_bg);
@@ -714,7 +716,7 @@ void template_editor::render_template_list()
if (!tmpl.enabled())
{
ImVec4 disabled_color = palette_utils::get_color(
ImVec4 disabled_color = clrsync::gui::widgets::palette_color(
m_current_palette, "on_surface_variant", "editor_inactive");
ImGui::PushStyleColor(ImGuiCol_Text, disabled_color);
}
@@ -951,4 +953,4 @@ void template_editor::delete_template()
void template_editor::refresh_templates()
{
m_template_controller.refresh();
}
}

View File

@@ -8,10 +8,15 @@
#include <string>
#include <vector>
namespace clrsync::gui
{
class ui_manager;
}
class template_editor
{
public:
template_editor();
template_editor(clrsync::gui::ui_manager* ui_mgr);
void render();
void apply_current_palette(const clrsync::core::palette &pal);
@@ -62,6 +67,7 @@ class template_editor
ImVec4 m_autocomplete_dim_text_color;
clrsync::core::palette m_current_palette;
clrsync::gui::ui_manager* m_ui_manager;
};
#endif // CLRSYNC_GUI_TEMPLATE_EDITOR_HPP

View File

@@ -0,0 +1,93 @@
#include "action_buttons.hpp"
#include "colors.hpp"
#include "imgui.h"
namespace clrsync::gui::widgets
{
action_buttons::action_buttons() = default;
void action_buttons::add_button(const action_button &button)
{
m_buttons.push_back(button);
}
void action_buttons::clear()
{
m_buttons.clear();
}
void action_buttons::render(const core::palette &theme_palette)
{
if (m_buttons.empty())
return;
if (m_use_separator)
{
ImGui::Separator();
ImGui::Spacing();
}
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(m_spacing, 8));
bool first = true;
for (const auto &button : m_buttons)
{
if (!first)
ImGui::SameLine();
first = false;
int style_colors_pushed = 0;
if (button.use_error_style)
{
auto error = palette_color(theme_palette, "error");
auto error_hover = ImVec4(error.x * 1.1f, error.y * 1.1f, error.z * 1.1f, error.w);
auto error_active = ImVec4(error.x * 0.8f, error.y * 0.8f, error.z * 0.8f, error.w);
auto on_error = palette_color(theme_palette, "on_error");
ImGui::PushStyleColor(ImGuiCol_Button, error);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, error_hover);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, error_active);
ImGui::PushStyleColor(ImGuiCol_Text, on_error);
style_colors_pushed = 4;
}
else if (button.use_success_style)
{
auto success = palette_color(theme_palette, "success", "accent");
auto success_hover = ImVec4(success.x * 1.1f, success.y * 1.1f, success.z * 1.1f, success.w);
auto success_active = ImVec4(success.x * 0.8f, success.y * 0.8f, success.z * 0.8f, success.w);
auto on_success = palette_color(theme_palette, "on_success", "on_surface");
ImGui::PushStyleColor(ImGuiCol_Button, success);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, success_hover);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, success_active);
ImGui::PushStyleColor(ImGuiCol_Text, on_success);
style_colors_pushed = 4;
}
bool disabled = !button.enabled;
if (disabled)
ImGui::BeginDisabled();
if (ImGui::Button(button.label.c_str()))
{
if (button.on_click)
{
button.on_click();
}
}
if (disabled)
ImGui::EndDisabled();
if (style_colors_pushed > 0)
ImGui::PopStyleColor(style_colors_pushed);
if (!button.tooltip.empty() && ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", button.tooltip.c_str());
}
}
ImGui::PopStyleVar();
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,46 @@
#ifndef CLRSYNC_GUI_WIDGETS_ACTION_BUTTONS_HPP
#define CLRSYNC_GUI_WIDGETS_ACTION_BUTTONS_HPP
#include "core/palette/palette.hpp"
#include <functional>
#include <string>
#include <vector>
namespace clrsync::gui::widgets
{
struct action_button
{
std::string label;
std::string tooltip;
std::function<void()> on_click;
bool enabled = true;
bool use_error_style = false;
bool use_success_style = false;
};
class action_buttons
{
public:
action_buttons();
void add_button(const action_button &button);
void clear();
void render(const core::palette &theme_palette);
void set_spacing(float spacing) { m_spacing = spacing; }
void set_use_separator(bool use) { m_use_separator = use; }
private:
std::vector<action_button> m_buttons;
float m_spacing = 8.0f;
bool m_use_separator = false;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_ACTION_BUTTONS_HPP

View File

@@ -0,0 +1,287 @@
#include "autocomplete.hpp"
#include "colors.hpp"
#include "imgui.h"
#include <algorithm>
namespace clrsync::gui::widgets
{
autocomplete_widget::autocomplete_widget()
: m_selected_index(0), m_show_autocomplete(false), m_dismissed(false), m_dismiss_brace_pos(-1)
{
m_bg_color = ImVec4(0.12f, 0.12f, 0.15f, 0.98f);
m_border_color = ImVec4(0.4f, 0.4f, 0.45f, 1.0f);
m_selected_color = ImVec4(0.25f, 0.45f, 0.75f, 0.9f);
m_text_color = ImVec4(0.85f, 0.85f, 0.9f, 1.0f);
m_selected_text_color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
m_dim_text_color = ImVec4(0.6f, 0.6f, 0.7f, 1.0f);
}
void autocomplete_widget::update_suggestions(const TextEditor& editor,
const std::vector<std::string>& available_keys,
const std::vector<std::string>& available_formats)
{
m_suggestions.clear();
auto cursor = editor.GetCursorPosition();
std::string line = editor.GetCurrentLineText();
int col = cursor.mColumn;
// Check if inside '{'
int brace_pos = -1;
for (int i = col - 1; i >= 0; --i)
{
if (i < (int)line.length())
{
if (line[i] == '{')
{
brace_pos = i;
break;
}
else if (line[i] == '}' || line[i] == ' ' || line[i] == '\t')
{
break;
}
}
}
if (brace_pos < 0)
{
m_show_autocomplete = false;
m_dismissed = false;
return;
}
if (m_dismissed)
{
bool should_reset_dismissal = false;
if (cursor.mLine != m_dismiss_position.mLine || brace_pos != m_dismiss_brace_pos ||
abs(cursor.mColumn - m_dismiss_position.mColumn) > 3)
{
should_reset_dismissal = true;
}
if (should_reset_dismissal)
{
m_dismissed = false;
}
else
{
m_show_autocomplete = false;
return;
}
}
m_prefix = line.substr(brace_pos + 1, col - brace_pos - 1);
m_start_pos = TextEditor::Coordinates(cursor.mLine, brace_pos + 1);
size_t dot_pos = m_prefix.find('.');
if (dot_pos != std::string::npos)
{
std::string color_key = m_prefix.substr(0, dot_pos);
std::string format_prefix = m_prefix.substr(dot_pos + 1);
bool valid_key = std::find(available_keys.begin(), available_keys.end(), color_key) != available_keys.end();
if (valid_key)
{
for (const auto &fmt : available_formats)
{
if (format_prefix.empty() || fmt.find(format_prefix) == 0 ||
fmt.find(format_prefix) != std::string::npos)
{
m_suggestions.push_back(color_key + "." + fmt);
}
}
}
}
else
{
for (const auto& key : available_keys)
{
if (m_prefix.empty() || key.find(m_prefix) == 0 ||
key.find(m_prefix) != std::string::npos)
{
m_suggestions.push_back(key);
}
}
}
std::sort(m_suggestions.begin(), m_suggestions.end(),
[this](const std::string &a, const std::string &b) {
bool a_prefix = a.find(m_prefix) == 0;
bool b_prefix = b.find(m_prefix) == 0;
if (a_prefix != b_prefix)
return a_prefix;
return a < b;
});
m_show_autocomplete = !m_suggestions.empty();
if (m_show_autocomplete && m_selected_index >= (int)m_suggestions.size())
{
m_selected_index = 0;
}
}
void autocomplete_widget::render(const ImVec2& editor_pos, TextEditor& editor)
{
if (!m_show_autocomplete || m_suggestions.empty())
return;
float line_height = ImGui::GetTextLineHeightWithSpacing();
float char_width = ImGui::GetFontSize() * 0.5f;
auto cursor = editor.GetCursorPosition();
const float line_number_width = 50.0f;
ImVec2 popup_pos;
popup_pos.x = editor_pos.x + line_number_width + (m_start_pos.mColumn * char_width);
popup_pos.y = editor_pos.y + ((cursor.mLine + 1) * line_height);
ImGui::SetNextWindowPos(popup_pos, ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(300, 0));
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_AlwaysAutoResize;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 2));
ImGui::PushStyleColor(ImGuiCol_WindowBg, m_bg_color);
ImGui::PushStyleColor(ImGuiCol_Border, m_border_color);
if (ImGui::Begin("##autocomplete", nullptr, flags))
{
ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color);
if (m_prefix.find('.') != std::string::npos)
ImGui::Text("Formats");
else
ImGui::Text("Color Keys");
ImGui::PopStyleColor();
ImGui::Separator();
int max_items = std::min((int)m_suggestions.size(), 8);
for (int i = 0; i < max_items; ++i)
{
const auto &suggestion = m_suggestions[i];
bool is_selected = (i == m_selected_index);
if (is_selected)
{
ImVec4 selected_hover = m_selected_color;
selected_hover.w = std::min(selected_hover.w + 0.1f, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Header, m_selected_color);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, selected_hover);
ImGui::PushStyleColor(ImGuiCol_Text, m_selected_text_color);
}
else
{
ImVec4 normal_bg = m_bg_color;
normal_bg.w = 0.5f;
ImVec4 hover_bg = m_selected_color;
hover_bg.w = 0.3f;
ImGui::PushStyleColor(ImGuiCol_Header, normal_bg);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hover_bg);
ImGui::PushStyleColor(ImGuiCol_Text, m_text_color);
}
std::string display_text = " " + suggestion;
if (ImGui::Selectable(display_text.c_str(), is_selected, ImGuiSelectableFlags_None, ImVec2(0, 0)))
{
auto start = m_start_pos;
auto end = editor.GetCursorPosition();
editor.SetSelection(start, end);
editor.Delete();
editor.InsertText(suggestion + "}");
m_show_autocomplete = false;
m_dismissed = false;
}
ImGui::PopStyleColor(3);
if (is_selected && ImGui::IsWindowAppearing())
{
ImGui::SetScrollHereY();
}
}
if (m_suggestions.size() > 8)
{
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color);
ImGui::Text(" +%d more", (int)m_suggestions.size() - 8);
ImGui::PopStyleColor();
}
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color);
ImGui::Text(" Tab/Enter: accept | Esc: dismiss");
ImGui::PopStyleColor();
}
ImGui::End();
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(3);
}
void autocomplete_widget::apply_palette(const core::palette& palette)
{
m_bg_color = palette_color(palette, "surface", "background");
m_bg_color.w = 0.98f;
m_border_color = palette_color(palette, "border", "surface_variant");
m_selected_color = palette_color(palette, "accent", "surface_variant");
m_text_color = palette_color(palette, "on_surface", "foreground");
m_selected_text_color = palette_color(palette, "on_surface", "foreground");
m_dim_text_color = palette_color(palette, "on_surface_variant", "editor_inactive");
}
bool autocomplete_widget::handle_input(TextEditor& editor)
{
if (!m_show_autocomplete || m_suggestions.empty())
return false;
bool handled = false;
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
{
m_selected_index = (m_selected_index + 1) % (int)m_suggestions.size();
handled = true;
}
else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
{
m_selected_index = (m_selected_index - 1 + (int)m_suggestions.size()) % (int)m_suggestions.size();
handled = true;
}
else if (ImGui::IsKeyPressed(ImGuiKey_Tab) || ImGui::IsKeyPressed(ImGuiKey_Enter))
{
if (m_selected_index >= 0 && m_selected_index < (int)m_suggestions.size())
{
auto start = m_start_pos;
auto end = editor.GetCursorPosition();
editor.SetSelection(start, end);
editor.Delete();
editor.InsertText(m_suggestions[m_selected_index] + "}");
m_show_autocomplete = false;
m_dismissed = false;
}
handled = true;
}
else if (ImGui::IsKeyPressed(ImGuiKey_Escape))
{
m_dismissed = true;
m_dismiss_position = editor.GetCursorPosition();
m_dismiss_brace_pos = -1;
m_show_autocomplete = false;
handled = true;
}
return handled;
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,53 @@
#ifndef CLRSYNC_GUI_WIDGETS_AUTOCOMPLETE_HPP
#define CLRSYNC_GUI_WIDGETS_AUTOCOMPLETE_HPP
#include "core/palette/palette.hpp"
#include "color_text_edit/TextEditor.h"
#include "imgui.h"
#include <string>
#include <vector>
namespace clrsync::gui::widgets
{
class autocomplete_widget
{
public:
autocomplete_widget();
void update_suggestions(const TextEditor& editor,
const std::vector<std::string>& available_keys,
const std::vector<std::string>& available_formats);
void render(const ImVec2& editor_pos, TextEditor& editor);
void apply_palette(const core::palette& palette);
bool handle_input(TextEditor& editor);
bool is_visible() const { return m_show_autocomplete; }
void dismiss() { m_dismissed = true; m_show_autocomplete = false; }
private:
std::vector<std::string> m_suggestions;
std::string m_prefix;
TextEditor::Coordinates m_start_pos;
int m_selected_index;
bool m_show_autocomplete;
bool m_dismissed;
TextEditor::Coordinates m_dismiss_position;
int m_dismiss_brace_pos;
ImVec4 m_bg_color;
ImVec4 m_border_color;
ImVec4 m_selected_color;
ImVec4 m_text_color;
ImVec4 m_selected_text_color;
ImVec4 m_dim_text_color;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_AUTOCOMPLETE_HPP

View File

@@ -0,0 +1,58 @@
#include "colors.hpp"
namespace clrsync::gui::widgets
{
ImVec4 palette_color(const core::palette &pal, const std::string &key,
const std::string &fallback)
{
auto colors = pal.colors();
if (colors.empty())
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
auto it = colors.find(key);
if (it == colors.end() && !fallback.empty())
{
it = colors.find(fallback);
}
if (it != colors.end())
{
const auto &col = it->second;
const uint32_t hex = col.hex();
const float r = ((hex >> 24) & 0xFF) / 255.0f;
const float g = ((hex >> 16) & 0xFF) / 255.0f;
const float b = ((hex >> 8) & 0xFF) / 255.0f;
const float a = (hex & 0xFF) / 255.0f;
return ImVec4(r, g, b, a);
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
}
uint32_t palette_color_u32(const core::palette &pal, const std::string &key,
const std::string &fallback)
{
auto colors = pal.colors();
if (colors.empty())
return 0xFFFFFFFF;
auto it = colors.find(key);
if (it == colors.end() && !fallback.empty())
{
it = colors.find(fallback);
}
if (it != colors.end())
{
const auto &col = it->second;
const uint32_t hex = col.hex();
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;
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,19 @@
#ifndef CLRSYNC_GUI_WIDGETS_COLORS_HPP
#define CLRSYNC_GUI_WIDGETS_COLORS_HPP
#include "core/palette/palette.hpp"
#include "imgui.h"
#include <string>
namespace clrsync::gui::widgets
{
ImVec4 palette_color(const core::palette &pal, const std::string &key,
const std::string &fallback = "");
uint32_t palette_color_u32(const core::palette &pal, const std::string &key,
const std::string &fallback = "");
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_COLORS_HPP

View File

@@ -0,0 +1,46 @@
#include "dialogs.hpp"
#include "colors.hpp"
#include "imgui.h"
namespace clrsync::gui::widgets
{
bool delete_confirmation_dialog(const std::string &popup_title, const std::string &item_name,
const std::string &item_type, const core::palette &theme_palette,
const std::function<void()> &on_delete)
{
bool result = false;
if (ImGui::BeginPopupModal(popup_title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImVec4 warning_color = palette_color(theme_palette, "warning", "accent");
ImGui::TextColored(warning_color, "Are you sure you want to delete '%s'?",
item_name.c_str());
ImGui::Text("This action cannot be undone.");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
float button_width = 120.0f;
float total_width = 2.0f * button_width + ImGui::GetStyle().ItemSpacing.x;
float window_width = ImGui::GetContentRegionAvail().x;
ImGui::SetCursorPosX((window_width - total_width) * 0.5f);
if (ImGui::Button("Delete", ImVec2(button_width, 0)))
{
on_delete();
result = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(button_width, 0)))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return result;
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,17 @@
#ifndef CLRSYNC_GUI_WIDGETS_DIALOGS_HPP
#define CLRSYNC_GUI_WIDGETS_DIALOGS_HPP
#include "core/palette/palette.hpp"
#include <functional>
#include <string>
namespace clrsync::gui::widgets
{
bool delete_confirmation_dialog(const std::string &popup_title, const std::string &item_name,
const std::string &item_type, const core::palette &theme_palette,
const std::function<void()> &on_delete);
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_DIALOGS_HPP

View File

@@ -0,0 +1,184 @@
#include "form_field.hpp"
#include "imgui.h"
#include <cstring>
namespace clrsync::gui::widgets
{
form_field::form_field() = default;
void form_field::render_label(const form_field_config& config)
{
ImGui::AlignTextToFramePadding();
ImGui::Text("%s:", config.label.c_str());
if (config.label_width > 0)
{
ImGui::SameLine(config.label_width);
}
else
{
ImGui::SameLine();
}
}
void form_field::render_tooltip(const form_field_config& config)
{
if (!config.tooltip.empty() && ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", config.tooltip.c_str());
}
}
bool form_field::render_text(const form_field_config& config, std::string& value)
{
render_label(config);
if (config.field_width > 0)
{
ImGui::SetNextItemWidth(config.field_width);
}
else if (config.field_width < 0)
{
ImGui::SetNextItemWidth(config.field_width);
}
char buffer[512] = {0};
strncpy(buffer, value.c_str(), sizeof(buffer) - 1);
bool changed = false;
std::string id = "##" + config.label;
if (config.type == field_type::text_with_hint)
{
changed = ImGui::InputTextWithHint(id.c_str(), config.hint.c_str(), buffer, sizeof(buffer));
}
else
{
changed = ImGui::InputText(id.c_str(), buffer, sizeof(buffer));
}
if (changed)
{
value = buffer;
}
render_tooltip(config);
return changed;
}
bool form_field::render_number(const form_field_config& config, int& value)
{
render_label(config);
if (config.field_width > 0)
{
ImGui::SetNextItemWidth(config.field_width);
}
else if (config.field_width < 0)
{
ImGui::SetNextItemWidth(config.field_width);
}
std::string id = "##" + config.label;
bool changed = ImGui::InputInt(id.c_str(), &value);
if (value < (int)config.min_value) value = (int)config.min_value;
if (value > (int)config.max_value) value = (int)config.max_value;
render_tooltip(config);
return changed;
}
bool form_field::render_number(const form_field_config& config, float& value)
{
render_label(config);
if (config.field_width > 0)
{
ImGui::SetNextItemWidth(config.field_width);
}
else if (config.field_width < 0)
{
ImGui::SetNextItemWidth(config.field_width);
}
std::string id = "##" + config.label;
bool changed = ImGui::InputFloat(id.c_str(), &value);
if (value < config.min_value) value = config.min_value;
if (value > config.max_value) value = config.max_value;
render_tooltip(config);
return changed;
}
bool form_field::render_path(const form_field_config& config, std::string& value)
{
render_label(config);
float browse_button_width = 80.0f;
float available_width = ImGui::GetContentRegionAvail().x;
float input_width = available_width - browse_button_width - ImGui::GetStyle().ItemSpacing.x;
if (config.field_width > 0)
{
input_width = config.field_width - browse_button_width - ImGui::GetStyle().ItemSpacing.x;
}
ImGui::SetNextItemWidth(input_width);
char buffer[512] = {0};
strncpy(buffer, value.c_str(), sizeof(buffer) - 1);
std::string input_id = "##" + config.label + "_input";
bool changed = false;
if (config.type == field_type::text_with_hint)
{
changed = ImGui::InputTextWithHint(input_id.c_str(), config.hint.c_str(), buffer, sizeof(buffer));
}
else
{
changed = ImGui::InputText(input_id.c_str(), buffer, sizeof(buffer));
}
if (changed)
{
value = buffer;
}
ImGui::SameLine();
std::string button_id = "Browse##" + config.label;
if (ImGui::Button(button_id.c_str()))
{
if (m_path_browse_callback)
{
std::string selected_path = m_path_browse_callback(value);
if (!selected_path.empty())
{
value = selected_path;
changed = true;
}
}
}
render_tooltip(config);
return changed;
}
void form_field::render_readonly(const form_field_config& config, const std::string& value)
{
render_label(config);
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", value.c_str());
render_tooltip(config);
}
void form_field::set_path_browse_callback(const std::function<std::string(const std::string&)>& callback)
{
m_path_browse_callback = callback;
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,62 @@
#ifndef CLRSYNC_GUI_WIDGETS_FORM_FIELD_HPP
#define CLRSYNC_GUI_WIDGETS_FORM_FIELD_HPP
#include <functional>
#include <string>
namespace clrsync::gui::widgets
{
enum class field_type
{
text,
text_with_hint,
number,
path,
readonly_text
};
struct form_field_config
{
std::string label;
std::string tooltip;
float label_width = 80.0f;
float field_width = -1.0f;
bool required = false;
field_type type = field_type::text;
std::string hint;
float min_value = 0.0f;
float max_value = 100.0f;
};
class form_field
{
public:
form_field();
// Render a text input field
bool render_text(const form_field_config& config, std::string& value);
// Render a number input field
bool render_number(const form_field_config& config, int& value);
bool render_number(const form_field_config& config, float& value);
// Render a path input field with browse button
bool render_path(const form_field_config& config, std::string& value);
// Render readonly text
void render_readonly(const form_field_config& config, const std::string& value);
// Set callback for path browsing
void set_path_browse_callback(const std::function<std::string(const std::string&)>& callback);
private:
std::function<std::string(const std::string&)> m_path_browse_callback;
void render_label(const form_field_config& config);
void render_tooltip(const form_field_config& config);
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_FORM_FIELD_HPP

View File

@@ -0,0 +1,111 @@
#include "input_dialog.hpp"
#include "imgui.h"
#include <cstring>
namespace clrsync::gui::widgets
{
input_dialog::input_dialog() : m_input_buffer{0}, m_is_open(false), m_focus_input(false) {}
void input_dialog::open(const std::string &title, const std::string &prompt, const std::string &hint)
{
m_title = title;
m_prompt = prompt;
m_hint = hint;
m_input_buffer[0] = 0;
m_is_open = true;
m_focus_input = true;
ImGui::OpenPopup(m_title.c_str());
}
bool input_dialog::render()
{
bool submitted = false;
if (!m_is_open)
return false;
if (ImGui::BeginPopupModal(m_title.c_str(), &m_is_open, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("%s", m_prompt.c_str());
ImGui::Spacing();
ImGui::SetNextItemWidth(250);
if (m_focus_input)
{
ImGui::SetKeyboardFocusHere();
m_focus_input = false;
}
bool enter_pressed = ImGui::InputTextWithHint("##input", m_hint.c_str(), m_input_buffer,
IM_ARRAYSIZE(m_input_buffer),
ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
bool can_submit = strlen(m_input_buffer) > 0;
bool submit_clicked = false;
if (!can_submit)
ImGui::BeginDisabled();
if (ImGui::Button("OK", ImVec2(120, 0)) || (enter_pressed && can_submit))
{
if (m_on_submit)
{
m_on_submit(m_input_buffer);
}
submitted = true;
submit_clicked = true;
m_is_open = false;
ImGui::CloseCurrentPopup();
}
if (!can_submit)
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0)))
{
if (m_on_cancel)
{
m_on_cancel();
}
m_is_open = false;
ImGui::CloseCurrentPopup();
}
if (ImGui::IsKeyPressed(ImGuiKey_Escape))
{
if (m_on_cancel)
{
m_on_cancel();
}
m_is_open = false;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
else
{
m_is_open = false;
}
return submitted;
}
void input_dialog::set_on_submit(const std::function<void(const std::string &)> &callback)
{
m_on_submit = callback;
}
void input_dialog::set_on_cancel(const std::function<void()> &callback)
{
m_on_cancel = callback;
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,38 @@
#ifndef CLRSYNC_GUI_WIDGETS_INPUT_DIALOG_HPP
#define CLRSYNC_GUI_WIDGETS_INPUT_DIALOG_HPP
#include <functional>
#include <string>
namespace clrsync::gui::widgets
{
class input_dialog
{
public:
input_dialog();
void open(const std::string &title, const std::string &prompt, const std::string &hint = "");
bool render();
void set_on_submit(const std::function<void(const std::string &)> &callback);
void set_on_cancel(const std::function<void()> &callback);
bool is_open() const { return m_is_open; }
private:
std::string m_title;
std::string m_prompt;
std::string m_hint;
char m_input_buffer[256];
bool m_is_open;
bool m_focus_input;
std::function<void(const std::string &)> m_on_submit;
std::function<void()> m_on_cancel;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_INPUT_DIALOG_HPP

View File

@@ -0,0 +1,46 @@
#include "palette_selector.hpp"
#include "imgui.h"
#include <ranges>
namespace clrsync::gui::widgets
{
palette_selector::palette_selector() = default;
bool palette_selector::render(const palette_controller &controller, float width)
{
const auto &current = controller.current_palette();
const auto &palettes = controller.palettes();
bool selection_changed = false;
ImGui::SetNextItemWidth(width);
if (ImGui::BeginCombo("##palette", 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))
{
if (m_on_selection_changed)
{
m_on_selection_changed(name);
}
selection_changed = true;
}
if (selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Select a color palette to edit");
return selection_changed;
}
void palette_selector::set_on_selection_changed(const std::function<void(const std::string &)> &callback)
{
m_on_selection_changed = callback;
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,26 @@
#ifndef CLRSYNC_GUI_WIDGETS_PALETTE_SELECTOR_HPP
#define CLRSYNC_GUI_WIDGETS_PALETTE_SELECTOR_HPP
#include "gui/controllers/palette_controller.hpp"
#include <functional>
#include <string>
namespace clrsync::gui::widgets
{
class palette_selector
{
public:
palette_selector();
bool render(const palette_controller &controller, float width = 200.0f);
void set_on_selection_changed(const std::function<void(const std::string &)> &callback);
private:
std::function<void(const std::string &)> m_on_selection_changed;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_PALETTE_SELECTOR_HPP

View File

@@ -0,0 +1,95 @@
#include "styled_checkbox.hpp"
#include "colors.hpp"
#include "imgui.h"
namespace clrsync::gui::widgets
{
styled_checkbox::styled_checkbox() = default;
bool styled_checkbox::render(const std::string &label, bool *value, const core::palette &theme_palette,
checkbox_style style)
{
ImVec4 bg_color, hover_color, check_color;
switch (style)
{
case checkbox_style::success:
if (*value)
{
bg_color = palette_color(theme_palette, "success", "accent");
bg_color.w = 0.5f;
hover_color = ImVec4(bg_color.x * 1.2f, bg_color.y * 1.2f, bg_color.z * 1.2f, 0.6f);
check_color = palette_color(theme_palette, "on_success", "on_surface");
}
else
{
bg_color = palette_color(theme_palette, "surface", "background");
hover_color = palette_color(theme_palette, "surface_variant", "surface");
check_color = palette_color(theme_palette, "on_surface", "foreground");
}
break;
case checkbox_style::error:
if (*value)
{
bg_color = palette_color(theme_palette, "error", "accent");
bg_color.w = 0.5f;
hover_color = ImVec4(bg_color.x * 1.2f, bg_color.y * 1.2f, bg_color.z * 1.2f, 0.6f);
check_color = palette_color(theme_palette, "on_error", "on_surface");
}
else
{
bg_color = palette_color(theme_palette, "error", "accent");
bg_color.w = 0.2f;
hover_color = ImVec4(bg_color.x, bg_color.y, bg_color.z, 0.3f);
check_color = palette_color(theme_palette, "on_error", "on_surface");
}
break;
case checkbox_style::warning:
if (*value)
{
bg_color = palette_color(theme_palette, "warning", "accent");
bg_color.w = 0.5f;
hover_color = ImVec4(bg_color.x * 1.2f, bg_color.y * 1.2f, bg_color.z * 1.2f, 0.6f);
check_color = palette_color(theme_palette, "on_warning", "on_surface");
}
else
{
bg_color = palette_color(theme_palette, "surface", "background");
hover_color = palette_color(theme_palette, "surface_variant", "surface");
check_color = palette_color(theme_palette, "on_surface", "foreground");
}
break;
case checkbox_style::normal:
default:
bg_color = palette_color(theme_palette, "surface", "background");
hover_color = palette_color(theme_palette, "surface_variant", "surface");
check_color = palette_color(theme_palette, "accent", "foreground");
break;
}
ImGui::PushStyleColor(ImGuiCol_FrameBg, bg_color);
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, hover_color);
ImGui::PushStyleColor(ImGuiCol_CheckMark, check_color);
bool changed = ImGui::Checkbox(label.c_str(), value);
ImGui::PopStyleColor(3);
if (changed && m_on_changed)
{
m_on_changed(*value);
}
if (!m_tooltip.empty() && ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", m_tooltip.c_str());
}
return changed;
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,38 @@
#ifndef CLRSYNC_GUI_WIDGETS_STYLED_CHECKBOX_HPP
#define CLRSYNC_GUI_WIDGETS_STYLED_CHECKBOX_HPP
#include "core/palette/palette.hpp"
#include <functional>
#include <string>
namespace clrsync::gui::widgets
{
enum class checkbox_style
{
normal,
success,
error,
warning
};
class styled_checkbox
{
public:
styled_checkbox();
bool render(const std::string &label, bool *value, const core::palette &theme_palette,
checkbox_style style = checkbox_style::normal);
void set_tooltip(const std::string &tooltip) { m_tooltip = tooltip; }
void set_on_changed(const std::function<void(bool)> &callback) { m_on_changed = callback; }
private:
std::string m_tooltip;
std::function<void(bool)> m_on_changed;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_STYLED_CHECKBOX_HPP