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

@@ -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