chore :refactored remaining views

This commit is contained in:
2025-12-19 17:37:42 +03:00
parent 4ada2c44ed
commit 8112096647
23 changed files with 898 additions and 569 deletions

View File

@@ -0,0 +1,23 @@
#include "centered_text.hpp"
namespace clrsync::gui::widgets
{
void centered_text(const std::string& text, const ImVec4& color)
{
float window_width = ImGui::GetContentRegionAvail().x;
float text_width = ImGui::CalcTextSize(text.c_str()).x;
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
ImGui::TextColored(color, "%s", text.c_str());
}
void centered_buttons(float total_width, const std::function<void()>& render_buttons)
{
float window_width = ImGui::GetContentRegionAvail().x;
float start_pos = (window_width - total_width) * 0.5f;
if (start_pos > 0)
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + start_pos);
render_buttons();
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,16 @@
#ifndef CLRSYNC_GUI_WIDGETS_CENTERED_TEXT_HPP
#define CLRSYNC_GUI_WIDGETS_CENTERED_TEXT_HPP
#include "imgui.h"
#include <functional>
#include <string>
namespace clrsync::gui::widgets
{
void centered_text(const std::string& text, const ImVec4& color = ImVec4(1, 1, 1, 1));
void centered_buttons(float total_width, const std::function<void()>& render_buttons);
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_CENTERED_TEXT_HPP

View File

@@ -0,0 +1,71 @@
#include "error_message.hpp"
#include "colors.hpp"
#include "imgui.h"
namespace clrsync::gui::widgets
{
void error_message::set(const std::string& message)
{
m_message = message;
}
void error_message::clear()
{
m_message.clear();
}
bool error_message::has_error() const
{
return !m_message.empty();
}
const std::string& error_message::get() const
{
return m_message;
}
void error_message::render(const core::palette& palette)
{
if (m_message.empty())
return;
ImGui::Spacing();
auto error_bg_color = palette_color(palette, "error");
auto error_text_color = palette_color(palette, "on_error");
ImGui::PushStyleColor(ImGuiCol_ChildBg, error_bg_color);
ImGui::PushStyleColor(ImGuiCol_Border, error_bg_color);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
if (ImGui::BeginChild("##error_box", ImVec2(0, 0),
ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders))
{
ImGui::PushStyleColor(ImGuiCol_Text, error_text_color);
ImGui::TextWrapped("Error: %s", m_message.c_str());
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::PushStyleColor(ImGuiCol_Button,
ImVec4(error_bg_color.x * 0.8f, error_bg_color.y * 0.8f,
error_bg_color.z * 0.8f, error_bg_color.w));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(error_bg_color.x * 0.6f, error_bg_color.y * 0.6f,
error_bg_color.z * 0.6f, error_bg_color.w));
ImGui::PushStyleColor(ImGuiCol_Text, error_text_color);
if (ImGui::Button("Dismiss##error"))
m_message.clear();
ImGui::PopStyleColor(3);
}
ImGui::EndChild();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(2);
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,25 @@
#ifndef CLRSYNC_GUI_WIDGETS_ERROR_MESSAGE_HPP
#define CLRSYNC_GUI_WIDGETS_ERROR_MESSAGE_HPP
#include "core/palette/palette.hpp"
#include <string>
namespace clrsync::gui::widgets
{
class error_message
{
public:
void set(const std::string& message);
void clear();
bool has_error() const;
const std::string& get() const;
void render(const core::palette& palette);
private:
std::string m_message;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_ERROR_MESSAGE_HPP

View File

@@ -181,4 +181,66 @@ void form_field::set_path_browse_callback(const std::function<std::string(const
m_path_browse_callback = callback;
}
bool form_field::render_slider(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;
std::string format = config.format.empty() ? "%d" : config.format.c_str();
int old_value = value;
bool changed = ImGui::SliderInt(id.c_str(), &value, (int)config.min_value, (int)config.max_value, format.c_str());
if (config.show_reset)
{
ImGui::SameLine();
std::string reset_id = "Reset##" + config.label;
if (ImGui::Button(reset_id.c_str()))
{
value = config.default_value;
changed = (old_value != value);
}
}
render_tooltip(config);
return changed;
}
bool form_field::render_combo(const form_field_config& config, const std::vector<std::string>& items, int& selected_idx, 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);
std::string id = "##" + config.label;
bool changed = false;
if (ImGui::BeginCombo(id.c_str(), value.c_str()))
{
for (int i = 0; i < static_cast<int>(items.size()); i++)
{
bool is_selected = (i == selected_idx);
if (ImGui::Selectable(items[i].c_str(), is_selected))
{
selected_idx = i;
value = items[i];
changed = true;
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
render_tooltip(config);
return changed;
}
} // namespace clrsync::gui::widgets

View File

@@ -3,6 +3,7 @@
#include <functional>
#include <string>
#include <vector>
namespace clrsync::gui::widgets
{
@@ -12,7 +13,9 @@ enum class field_type
text,
text_with_hint,
number,
slider,
path,
combo,
readonly_text
};
@@ -27,6 +30,9 @@ struct form_field_config
std::string hint;
float min_value = 0.0f;
float max_value = 100.0f;
std::string format;
bool show_reset = false;
int default_value = 0;
};
class form_field
@@ -41,6 +47,12 @@ class form_field
bool render_number(const form_field_config& config, int& value);
bool render_number(const form_field_config& config, float& value);
// Render a slider field
bool render_slider(const form_field_config& config, int& value);
// Render a combo box field
bool render_combo(const form_field_config& config, const std::vector<std::string>& items, int& selected_idx, std::string& value);
// Render a path input field with browse button
bool render_path(const form_field_config& config, std::string& value);

View File

@@ -0,0 +1,28 @@
#include "link_button.hpp"
#include "imgui.h"
#include <cstdlib>
namespace clrsync::gui::widgets
{
void open_url(const std::string& url)
{
#ifdef _WIN32
std::string cmd = "start " + url;
#elif __APPLE__
std::string cmd = "open " + url;
#else
std::string cmd = "xdg-open " + url;
#endif
std::system(cmd.c_str());
}
bool link_button(const std::string& label, const std::string& url, float width)
{
bool clicked = ImGui::Button(label.c_str(), ImVec2(width, 0));
if (clicked)
open_url(url);
return clicked;
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,14 @@
#ifndef CLRSYNC_GUI_WIDGETS_LINK_BUTTON_HPP
#define CLRSYNC_GUI_WIDGETS_LINK_BUTTON_HPP
#include <string>
namespace clrsync::gui::widgets
{
void open_url(const std::string& url);
bool link_button(const std::string& label, const std::string& url, float width = 0.0f);
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_LINK_BUTTON_HPP

View File

@@ -0,0 +1,17 @@
#include "section_header.hpp"
#include "colors.hpp"
#include "imgui.h"
namespace clrsync::gui::widgets
{
void section_header(const std::string& title, const core::palette& palette)
{
ImGui::Spacing();
auto accent_color = palette_color(palette, "accent");
ImGui::TextColored(accent_color, "%s", title.c_str());
ImGui::Separator();
ImGui::Spacing();
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,14 @@
#ifndef CLRSYNC_GUI_WIDGETS_SECTION_HEADER_HPP
#define CLRSYNC_GUI_WIDGETS_SECTION_HEADER_HPP
#include "core/palette/palette.hpp"
#include <string>
namespace clrsync::gui::widgets
{
void section_header(const std::string& title, const core::palette& palette);
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_SECTION_HEADER_HPP

View File

@@ -0,0 +1,56 @@
#include "settings_buttons.hpp"
#include "imgui.h"
namespace clrsync::gui::widgets
{
void settings_buttons::render(const settings_buttons_callbacks& callbacks, bool apply_enabled)
{
ImGui::Spacing();
float spacing = ImGui::GetStyle().ItemSpacing.x;
float window_width = ImGui::GetContentRegionAvail().x;
float total_buttons_width = 4 * m_button_width + 3 * spacing;
float start_pos = (window_width - total_buttons_width) * 0.5f;
if (start_pos > 0)
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + start_pos);
if (ImGui::Button("OK", ImVec2(m_button_width, 0)))
{
if (callbacks.on_ok)
callbacks.on_ok();
}
ImGui::SameLine();
if (!apply_enabled)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f);
if (ImGui::Button("Apply", ImVec2(m_button_width, 0)) && apply_enabled)
{
if (callbacks.on_apply)
callbacks.on_apply();
}
if (!apply_enabled)
ImGui::PopStyleVar();
ImGui::SameLine();
if (ImGui::Button("Reset", ImVec2(m_button_width, 0)))
{
if (callbacks.on_reset)
callbacks.on_reset();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(m_button_width, 0)))
{
if (callbacks.on_cancel)
callbacks.on_cancel();
}
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,30 @@
#ifndef CLRSYNC_GUI_WIDGETS_SETTINGS_BUTTONS_HPP
#define CLRSYNC_GUI_WIDGETS_SETTINGS_BUTTONS_HPP
#include <functional>
namespace clrsync::gui::widgets
{
struct settings_buttons_callbacks
{
std::function<void()> on_ok;
std::function<void()> on_apply;
std::function<void()> on_reset;
std::function<void()> on_cancel;
};
class settings_buttons
{
public:
void render(const settings_buttons_callbacks& callbacks, bool apply_enabled);
void set_button_width(float width) { m_button_width = width; }
private:
float m_button_width = 100.0f;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_SETTINGS_BUTTONS_HPP

View File

@@ -0,0 +1,181 @@
#include "template_controls.hpp"
#include "colors.hpp"
#include "styled_checkbox.hpp"
#include "imgui.h"
namespace clrsync::gui::widgets
{
template_controls::template_controls() = default;
void template_controls::render(template_control_state& state,
const template_control_callbacks& callbacks,
const core::palette& palette,
validation_message& validation)
{
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 8));
render_action_buttons(state, callbacks, palette);
ImGui::PopStyleVar();
ImGui::Spacing();
render_fields(state, callbacks);
validation.render(palette);
}
void template_controls::render_action_buttons(template_control_state& state,
const template_control_callbacks& callbacks,
const core::palette& palette)
{
if (ImGui::Button(" + New "))
{
if (callbacks.on_new)
callbacks.on_new();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Create a new template");
ImGui::SameLine();
if (ImGui::Button(" Save "))
{
if (callbacks.on_save)
callbacks.on_save();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Save template (Ctrl+S)");
if (state.is_editing_existing)
{
ImGui::SameLine();
auto error = palette_color(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(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);
if (ImGui::Button(" Delete "))
{
if (callbacks.on_delete)
callbacks.on_delete();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Delete this template");
ImGui::PopStyleColor(4);
}
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10);
bool old_enabled = state.enabled;
styled_checkbox checkbox;
checkbox.render("Enabled", &state.enabled, palette,
state.enabled ? checkbox_style::success : checkbox_style::error);
if (old_enabled != state.enabled && state.is_editing_existing)
{
if (callbacks.on_enabled_changed)
callbacks.on_enabled_changed(state.enabled);
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Enable/disable this template for theme application");
}
void template_controls::render_fields(template_control_state& state,
const template_control_callbacks& callbacks)
{
ImGui::AlignTextToFramePadding();
ImGui::Text("Name:");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180.0f);
char name_buf[256] = {0};
snprintf(name_buf, sizeof(name_buf), "%s", state.name.c_str());
if (ImGui::InputText("##template_name", name_buf, sizeof(name_buf)))
{
state.name = name_buf;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Unique name for this template");
ImGui::AlignTextToFramePadding();
ImGui::Text("Input:");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(-120.0f);
char input_path_buf[512] = {0};
snprintf(input_path_buf, sizeof(input_path_buf), "%s", state.input_path.c_str());
if (ImGui::InputTextWithHint("##input_path", "Path to template file...", input_path_buf,
sizeof(input_path_buf)))
{
state.input_path = input_path_buf;
if (state.is_editing_existing && callbacks.on_input_path_changed)
callbacks.on_input_path_changed(state.input_path);
}
ImGui::SameLine();
if (ImGui::Button("Browse##input"))
{
if (callbacks.on_browse_input)
{
std::string selected = callbacks.on_browse_input(state.input_path);
if (!selected.empty())
{
state.input_path = selected;
if (state.is_editing_existing && callbacks.on_input_path_changed)
callbacks.on_input_path_changed(state.input_path);
}
}
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Path where the template source file is stored");
ImGui::AlignTextToFramePadding();
ImGui::Text("Output:");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(-120.0f);
char path_buf[512] = {0};
snprintf(path_buf, sizeof(path_buf), "%s", state.output_path.c_str());
if (ImGui::InputTextWithHint("##output_path", "Path for generated config...", path_buf,
sizeof(path_buf)))
{
state.output_path = path_buf;
if (state.is_editing_existing && callbacks.on_output_path_changed)
callbacks.on_output_path_changed(state.output_path);
}
ImGui::SameLine();
if (ImGui::Button("Browse##output"))
{
if (callbacks.on_browse_output)
{
std::string selected = callbacks.on_browse_output(state.output_path);
if (!selected.empty())
{
state.output_path = selected;
if (state.is_editing_existing && callbacks.on_output_path_changed)
callbacks.on_output_path_changed(state.output_path);
}
}
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Path where the processed config will be written");
ImGui::AlignTextToFramePadding();
ImGui::Text("Reload:");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(-FLT_MIN);
char reload_buf[512] = {0};
snprintf(reload_buf, sizeof(reload_buf), "%s", state.reload_command.c_str());
if (ImGui::InputTextWithHint("##reload_cmd", "Command to reload app (optional)...", reload_buf,
sizeof(reload_buf)))
{
state.reload_command = reload_buf;
if (state.is_editing_existing && callbacks.on_reload_command_changed)
callbacks.on_reload_command_changed(state.reload_command);
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Shell command to run after applying theme (e.g., 'pkill -USR1 kitty')");
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,59 @@
#ifndef CLRSYNC_GUI_WIDGETS_TEMPLATE_CONTROLS_HPP
#define CLRSYNC_GUI_WIDGETS_TEMPLATE_CONTROLS_HPP
#include "core/palette/palette.hpp"
#include "gui/widgets/form_field.hpp"
#include "gui/widgets/validation_message.hpp"
#include <functional>
#include <string>
namespace clrsync::gui::widgets
{
struct template_control_state
{
std::string name;
std::string input_path;
std::string output_path;
std::string reload_command;
bool enabled{true};
bool is_editing_existing{false};
};
struct template_control_callbacks
{
std::function<void()> on_new;
std::function<void()> on_save;
std::function<void()> on_delete;
std::function<void(bool)> on_enabled_changed;
std::function<std::string(const std::string&)> on_browse_input;
std::function<std::string(const std::string&)> on_browse_output;
std::function<void(const std::string&)> on_input_path_changed;
std::function<void(const std::string&)> on_output_path_changed;
std::function<void(const std::string&)> on_reload_command_changed;
};
class template_controls
{
public:
template_controls();
void render(template_control_state& state,
const template_control_callbacks& callbacks,
const core::palette& palette,
validation_message& validation);
private:
void render_action_buttons(template_control_state& state,
const template_control_callbacks& callbacks,
const core::palette& palette);
void render_fields(template_control_state& state,
const template_control_callbacks& callbacks);
form_field m_form;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_TEMPLATE_CONTROLS_HPP

View File

@@ -0,0 +1,40 @@
#include "validation_message.hpp"
#include "colors.hpp"
#include "imgui.h"
namespace clrsync::gui::widgets
{
void validation_message::set(const std::string& message)
{
m_message = message;
}
void validation_message::clear()
{
m_message.clear();
}
bool validation_message::has_error() const
{
return !m_message.empty();
}
const std::string& validation_message::get() const
{
return m_message;
}
void validation_message::render(const core::palette& palette)
{
if (m_message.empty())
return;
ImGui::Spacing();
ImVec4 error_color = palette_color(palette, "error", "accent");
ImGui::PushStyleColor(ImGuiCol_Text, error_color);
ImGui::TextWrapped("%s", m_message.c_str());
ImGui::PopStyleColor();
}
} // namespace clrsync::gui::widgets

View File

@@ -0,0 +1,25 @@
#ifndef CLRSYNC_GUI_WIDGETS_VALIDATION_MESSAGE_HPP
#define CLRSYNC_GUI_WIDGETS_VALIDATION_MESSAGE_HPP
#include "core/palette/palette.hpp"
#include <string>
namespace clrsync::gui::widgets
{
class validation_message
{
public:
void set(const std::string& message);
void clear();
bool has_error() const;
const std::string& get() const;
void render(const core::palette& palette);
private:
std::string m_message;
};
} // namespace clrsync::gui::widgets
#endif // CLRSYNC_GUI_WIDGETS_VALIDATION_MESSAGE_HPP