refactor(gui): reorganize GUI includes and introduce new components

- Updated include paths for various GUI-related headers to improve organization and clarity.
- Introduced new components for better modularity, including PaletteEditorWidget and EditorCardManager.
- Refactored existing code to utilize the new components, ensuring consistency across the GUI subsystem.

Benefits:
- Enhances maintainability and readability of the GUI code.
- Facilitates future enhancements and optimizations within the GUI subsystem.
This commit is contained in:
scawful
2025-10-13 10:21:03 -04:00
parent 6374da6194
commit 58f3213c62
139 changed files with 890 additions and 1006 deletions

318
src/app/gui/core/color.cc Normal file
View File

@@ -0,0 +1,318 @@
#include "color.h"
#include "app/gfx/types/snes_color.h"
#include "app/gfx/types/snes_palette.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor& color) {
return ImVec4(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
color.rgb().z / 255.0f, 1.0f);
}
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4& color) {
return gfx::SnesColor(color);
}
IMGUI_API bool SnesColorButton(absl::string_view id, gfx::SnesColor& color,
ImGuiColorEditFlags flags,
const ImVec2& size_arg) {
// Convert the SNES color values to ImGui color values
ImVec4 displayColor = ConvertSnesColorToImVec4(color);
// Call the original ImGui::ColorButton with the converted color
bool pressed = ImGui::ColorButton(id.data(), displayColor, flags, size_arg);
// Add the SNES color representation to the tooltip
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("SNES: $%04X", color.snes());
ImGui::EndTooltip();
}
return pressed;
}
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor* color,
ImGuiColorEditFlags flags) {
// Convert from internal 0-255 storage to 0-1 for ImGui
ImVec4 displayColor = ConvertSnesColorToImVec4(*color);
// Call the original ImGui::ColorEdit4 with the converted color
bool changed =
ImGui::ColorEdit4(label.data(), (float*)&displayColor.x, flags);
// Only update if the user actually changed the color
if (changed) {
// set_rgb() handles conversion from 0-1 (ImGui) to 0-255 (internal)
// and automatically calculates snes_ value - no need to call set_snes separately
color->set_rgb(displayColor);
}
return changed;
}
IMGUI_API bool DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
ImGuiColorEditFlags_NoDragDrop |
ImGuiColorEditFlags_NoOptions;
// Generate a default palette. The palette will persist and can be edited.
static bool init = false;
static ImVec4 saved_palette[32] = {};
if (loaded && !init) {
for (int n = 0; n < palette.size(); n++) {
auto color = palette[n];
saved_palette[n].x = color.rgb().x / 255;
saved_palette[n].y = color.rgb().y / 255;
saved_palette[n].z = color.rgb().z / 255;
saved_palette[n].w = 255; // Alpha
}
init = true;
}
static ImVec4 backup_color;
ImGui::Text("Current ==>");
ImGui::SameLine();
ImGui::Text("Previous");
ImGui::ColorButton(
"##current", color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40));
ImGui::SameLine();
if (ImGui::ColorButton(
"##previous", backup_color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40)))
color = backup_color;
ImGui::Separator();
ImGui::BeginGroup(); // Lock X position
ImGui::Text("Palette");
for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) {
ImGui::PushID(n);
if ((n % 4) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip;
if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags,
ImVec2(20, 20)))
color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z,
color.w); // Preserve alpha!
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
ImGui::EndGroup();
ImGui::SameLine();
ImGui::ColorPicker4("##picker", (float*)&color,
misc_flags | ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview);
return true;
}
void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics,
gfx::SnesPalette& palette) {
const auto palette_row_size = 7;
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)100);
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
ImGui::BeginGroup(); // Lock X position
ImGui::Text("Palette");
for (int n = 0; n < palette.size(); n++) {
ImGui::PushID(n);
if ((n % palette_row_size) != 0)
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
// Check if the current row is selected
bool is_selected = (palette_id == n / palette_row_size);
// Add outline rectangle to the selected row
if (is_selected) {
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
}
if (gui::SnesColorButton("##palette", palette[n],
ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip,
ImVec2(20, 20))) {
palette_id = n / palette_row_size;
refresh_graphics = true;
}
if (is_selected) {
ImGui::PopStyleColor();
ImGui::PopStyleVar();
}
ImGui::PopID();
}
ImGui::EndGroup();
}
ImGui::EndChild();
}
absl::Status DisplayEditablePalette(gfx::SnesPalette& palette,
const std::string& title,
bool show_color_picker, int colors_per_row,
ImGuiColorEditFlags flags) {
// Default flags if none provided
if (flags == 0) {
flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip;
}
// Display title if provided
if (!title.empty()) {
ImGui::Text("%s", title.c_str());
}
static int selected_color = 0;
if (show_color_picker) {
ImGui::Separator();
static ImVec4 current_color = ImVec4(0, 0, 0, 1.0f);
if (ImGui::ColorPicker4("Color Picker", (float*)&current_color,
ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview)) {
// Convert the selected color to SNES format and add it to the palette
gfx::SnesColor snes_color(current_color);
palette.UpdateColor(selected_color, snes_color);
}
}
// Display the palette colors in a grid
ImGui::BeginGroup(); // Lock X position
for (int n = 0; n < palette.size(); n++) {
ImGui::PushID(n);
if ((n % colors_per_row) != 0) {
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
}
// Create a unique ID for this color button
std::string button_id = "##palette_" + std::to_string(n);
// Display the color button
if (SnesColorButton(button_id, palette[n], flags, ImVec2(20, 20))) {
// Color was clicked, could be used to select this color
selected_color = n;
}
if (ImGui::BeginPopupContextItem()) {
if (ImGui::MenuItem("Edit Color")) {
// Open color picker for this color
ImGui::OpenPopup(("Edit Color##" + std::to_string(n)).c_str());
}
if (ImGui::MenuItem("Copy as SNES Value")) {
std::string clipboard = absl::StrFormat("$%04X", palette[n].snes());
ImGui::SetClipboardText(clipboard.c_str());
}
if (ImGui::MenuItem("Copy as RGB")) {
auto rgb = palette[n].rgb();
// rgb is already in 0-255 range, no need to multiply
std::string clipboard =
absl::StrFormat("(%d,%d,%d)", (int)rgb.x, (int)rgb.y, (int)rgb.z);
ImGui::SetClipboardText(clipboard.c_str());
}
if (ImGui::MenuItem("Copy as Hex")) {
auto rgb = palette[n].rgb();
// rgb is already in 0-255 range, no need to multiply
std::string clipboard =
absl::StrFormat("#%02X%02X%02X", (int)rgb.x, (int)rgb.y, (int)rgb.z);
ImGui::SetClipboardText(clipboard.c_str());
}
ImGui::EndPopup();
}
// Color picker popup
if (ImGui::BeginPopup(("Edit Color##" + std::to_string(n)).c_str())) {
ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview;
ImVec4 color = ConvertSnesColorToImVec4(palette[n]);
if (ImGui::ColorPicker4("##picker", (float*)&color, picker_flags)) {
// Update the SNES color when the picker changes
palette[n] = ConvertImVec4ToSnesColor(color);
}
ImGui::EndPopup();
}
ImGui::PopID();
}
ImGui::EndGroup();
return absl::OkStatus();
}
IMGUI_API bool PaletteColorButton(const char* id, const gfx::SnesColor& color,
bool is_selected, bool is_modified,
const ImVec2& size,
ImGuiColorEditFlags flags) {
ImVec4 display_color = ConvertSnesColorToImVec4(color);
// Add visual indicators for selection and modification
ImGui::PushID(id);
// Selection border
if (is_selected) {
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
}
bool clicked = ImGui::ColorButton(id, display_color, flags, size);
if (is_selected) {
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}
// Modification indicator (small dot in corner)
if (is_modified) {
ImVec2 pos = ImGui::GetItemRectMin();
ImVec2 dot_pos = ImVec2(pos.x + size.x - 6, pos.y + 2);
ImGui::GetWindowDrawList()->AddCircleFilled(dot_pos, 3.0f,
IM_COL32(255, 128, 0, 255));
}
// Tooltip with color info
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("SNES: $%04X", color.snes());
auto rgb = color.rgb();
ImGui::Text("RGB: (%d, %d, %d)",
static_cast<int>(rgb.x),
static_cast<int>(rgb.y),
static_cast<int>(rgb.z));
if (is_modified) {
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Modified");
}
ImGui::EndTooltip();
}
ImGui::PopID();
return clicked;
}
} // namespace gui
} // namespace yaze

68
src/app/gui/core/color.h Normal file
View File

@@ -0,0 +1,68 @@
#ifndef YAZE_GUI_COLOR_H
#define YAZE_GUI_COLOR_H
#include "absl/strings/str_format.h"
#include <string>
#include "absl/status/status.h"
#include "app/gfx/types/snes_palette.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
struct Color {
float red;
float green;
float blue;
float alpha;
};
inline ImVec4 ConvertColorToImVec4(const Color &color) {
return ImVec4(color.red, color.green, color.blue, color.alpha);
}
inline std::string ColorToHexString(const Color &color) {
return absl::StrFormat("%02X%02X%02X%02X",
static_cast<int>(color.red * 255),
static_cast<int>(color.green * 255),
static_cast<int>(color.blue * 255),
static_cast<int>(color.alpha * 255));
}
// A utility function to convert an SnesColor object to an ImVec4 with
// normalized color values
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color);
// A utility function to convert an ImVec4 to an SnesColor object
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color);
// The wrapper function for ImGui::ColorButton that takes a SnesColor reference
IMGUI_API bool SnesColorButton(absl::string_view id, gfx::SnesColor &color,
ImGuiColorEditFlags flags = 0,
const ImVec2 &size_arg = ImVec2(0, 0));
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color,
ImGuiColorEditFlags flags = 0);
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded);
IMGUI_API absl::Status DisplayEditablePalette(gfx::SnesPalette &palette,
const std::string &title = "",
bool show_color_picker = false,
int colors_per_row = 8,
ImGuiColorEditFlags flags = 0);
void SelectablePalettePipeline(uint64_t &palette_id, bool &refresh_graphics,
gfx::SnesPalette &palette);
// Palette color button with selection and modification indicators
IMGUI_API bool PaletteColorButton(const char* id, const gfx::SnesColor& color,
bool is_selected, bool is_modified,
const ImVec2& size = ImVec2(28, 28),
ImGuiColorEditFlags flags = 0);
} // namespace gui
} // namespace yaze
#endif

2195
src/app/gui/core/icons.h Normal file

File diff suppressed because it is too large Load Diff

516
src/app/gui/core/input.cc Normal file
View File

@@ -0,0 +1,516 @@
#include "input.h"
#include <functional>
#include <string>
#include <variant>
#include "absl/strings/string_view.h"
#include "app/gfx/types/snes_tile.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include "imgui_memory_editor.h"
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
namespace ImGui {
static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
ImGuiDataType data_type, const char* format) {
if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
return ImGuiInputTextFlags_CharsScientific;
const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
return (format_last_char == 'x' || format_last_char == 'X')
? ImGuiInputTextFlags_CharsHexadecimal
: ImGuiInputTextFlags_CharsDecimal;
}
// Helper: returns true if label is "invisible" (starts with "##")
static inline bool IsInvisibleLabel(const char* label) {
return label && label[0] == '#' && label[1] == '#';
}
bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
const void* p_step, const void* p_step_fast,
const char* format, float input_width,
ImGuiInputTextFlags flags, bool no_step = false) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGuiContext& g = *GImGui;
ImGuiStyle& style = g.Style;
if (format == NULL)
format = DataTypeGetInfo(data_type)->PrintFmt;
char buf[64];
DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
ImGuiInputTextFlags_CharsHexadecimal |
ImGuiInputTextFlags_CharsScientific)) == 0)
flags |= InputScalar_DefaultCharsFilter(data_type, format);
flags |= ImGuiInputTextFlags_AutoSelectAll;
bool value_changed = false;
const float button_size = GetFrameHeight();
// Support invisible labels (##) by not rendering the label, but still using it for ID
bool invisible_label = IsInvisibleLabel(label);
if (!invisible_label) {
AlignTextToFramePadding();
Text("%s", label);
SameLine();
}
BeginGroup(); // The only purpose of the group here is to allow the caller
// to query item data e.g. IsItemActive()
PushID(label);
SetNextItemWidth(ImMax(
1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
// Place the label on the left of the input field, unless invisible
PushStyleVar(ImGuiStyleVar_ItemSpacing,
ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
PushStyleVar(ImGuiStyleVar_FramePadding,
ImVec2{style.FramePadding.x, style.FramePadding.y});
SetNextItemWidth(input_width);
if (InputText("", buf, IM_ARRAYSIZE(buf),
flags)) // PushId(label) + "" gives us the expected ID
// from outside point of view
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
IMGUI_TEST_ENGINE_ITEM_INFO(
g.LastItemData.ID, label,
g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
// Mouse wheel support
if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
float scroll_amount = g.IO.MouseWheel;
float scroll_speed = 0.25f; // Adjust the scroll speed as needed
if (g.IO.KeyCtrl && p_step_fast)
scroll_amount *= *(const float*)p_step_fast;
else
scroll_amount *= *(const float*)p_step;
if (scroll_amount > 0.0f) {
scroll_amount *= scroll_speed; // Adjust the scroll speed as needed
DataTypeApplyOp(data_type, '+', p_data, p_data, &scroll_amount);
value_changed = true;
} else if (scroll_amount < 0.0f) {
scroll_amount *= -scroll_speed; // Adjust the scroll speed as needed
DataTypeApplyOp(data_type, '-', p_data, p_data, &scroll_amount);
value_changed = true;
}
}
// Step buttons
if (!no_step) {
const ImVec2 backup_frame_padding = style.FramePadding;
style.FramePadding.x = style.FramePadding.y;
ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
if (flags & ImGuiInputTextFlags_ReadOnly)
BeginDisabled();
SameLine(0, style.ItemInnerSpacing.x);
if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) {
DataTypeApplyOp(data_type, '-', p_data, p_data,
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
value_changed = true;
}
SameLine(0, style.ItemInnerSpacing.x);
if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) {
DataTypeApplyOp(data_type, '+', p_data, p_data,
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
value_changed = true;
}
if (flags & ImGuiInputTextFlags_ReadOnly)
EndDisabled();
style.FramePadding = backup_frame_padding;
}
PopID();
EndGroup();
ImGui::PopStyleVar(2);
if (value_changed)
MarkItemEdited(g.LastItemData.ID);
return value_changed;
}
} // namespace ImGui
namespace yaze {
namespace gui {
const int kStepOneHex = 0x01;
const int kStepFastHex = 0x0F;
bool InputHex(const char* label, uint64_t* data) {
return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex,
&kStepFastHex, "%06X",
ImGuiInputTextFlags_CharsHexadecimal);
}
bool InputHex(const char* label, int* data, int num_digits, float input_width) {
const std::string format = "%0" + std::to_string(num_digits) + "X";
return ImGui::InputScalarLeft(label, ImGuiDataType_S32, data, &kStepOneHex,
&kStepFastHex, format.c_str(), input_width,
ImGuiInputTextFlags_CharsHexadecimal);
}
bool InputHexShort(const char* label, uint32_t* data) {
return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
&kStepFastHex, "%06X",
ImGuiInputTextFlags_CharsHexadecimal);
}
bool InputHexWord(const char* label, uint16_t* data, float input_width,
bool no_step) {
return ImGui::InputScalarLeft(label, ImGuiDataType_U16, data, &kStepOneHex,
&kStepFastHex, "%04X", input_width,
ImGuiInputTextFlags_CharsHexadecimal, no_step);
}
bool InputHexWord(const char* label, int16_t* data, float input_width,
bool no_step) {
return ImGui::InputScalarLeft(label, ImGuiDataType_S16, data, &kStepOneHex,
&kStepFastHex, "%04X", input_width,
ImGuiInputTextFlags_CharsHexadecimal, no_step);
}
bool InputHexByte(const char* label, uint8_t* data, float input_width,
bool no_step) {
return ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex,
&kStepFastHex, "%02X", input_width,
ImGuiInputTextFlags_CharsHexadecimal, no_step);
}
bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value,
float input_width, bool no_step) {
if (ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex,
&kStepFastHex, "%02X", input_width,
ImGuiInputTextFlags_CharsHexadecimal, no_step)) {
if (*data > max_value) {
*data = max_value;
}
return true;
}
return false;
}
void Paragraph(const std::string& text) {
ImGui::TextWrapped("%s", text.c_str());
}
// TODO: Setup themes and text/clickable colors
bool ClickableText(const std::string& text) {
ImGui::BeginGroup();
ImGui::PushID(text.c_str());
// Calculate text size
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
// Get cursor position for hover detection
ImVec2 pos = ImGui::GetCursorScreenPos();
ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
// Add item
const ImGuiID id = ImGui::GetID(text.c_str());
bool result = false;
if (ImGui::ItemAdd(bb, id)) {
bool hovered = ImGui::IsItemHovered();
bool clicked = ImGui::IsItemClicked();
// Render text with high-contrast appropriate color
ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
// Ensure good contrast against background
float contrast_factor =
(bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
ImVec4 color;
if (hovered) {
// Brighter color on hover for better visibility
color = ImVec4(std::min(1.0f, link_color.x + 0.3f),
std::min(1.0f, link_color.y + 0.3f),
std::min(1.0f, link_color.z + 0.3f), 1.0f);
} else {
// Ensure link color has good contrast
color = ImVec4(std::max(contrast_factor, link_color.x),
std::max(contrast_factor, link_color.y),
std::max(contrast_factor, link_color.z), 1.0f);
}
ImGui::GetWindowDrawList()->AddText(
pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
result = clicked;
}
ImGui::PopID();
// Advance cursor past the text
ImGui::Dummy(text_size);
ImGui::EndGroup();
return result;
}
void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
const ImVec2 lineStart = ImGui::GetCursorScreenPos();
const ImGuiStyle& style = ImGui::GetStyle();
float fullWidth = ImGui::GetContentRegionAvail().x;
float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
ImVec2 textSize =
ImGui::CalcTextSize(title.data(), title.data() + title.size());
ImRect textRect;
textRect.Min = ImGui::GetCursorScreenPos();
if (flags & ItemLabelFlag::Right)
textRect.Min.x = textRect.Min.x + itemWidth;
textRect.Max = textRect.Min;
textRect.Max.x += fullWidth - itemWidth;
textRect.Max.y += textSize.y;
ImGui::SetCursorScreenPos(textRect.Min);
ImGui::AlignTextToFramePadding();
// Adjust text rect manually because we render it directly into a drawlist
// instead of using public functions.
textRect.Min.y += window->DC.CurrLineTextBaseOffset;
textRect.Max.y += window->DC.CurrLineTextBaseOffset;
ImGui::ItemSize(textRect);
if (ImGui::ItemAdd(
textRect, window->GetID(title.data(), title.data() + title.size()))) {
ImGui::RenderTextEllipsis(ImGui::GetWindowDrawList(), textRect.Min,
textRect.Max, textRect.Max.x, title.data(),
title.data() + title.size(), &textSize);
if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
}
if (flags & ItemLabelFlag::Left) {
ImVec2 result;
auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
result.x = textRect.Max.x - other.x;
result.y = textRect.Max.y - other.y;
ImGui::SetCursorScreenPos(result);
ImGui::SameLine();
} else if (flags & ItemLabelFlag::Right)
ImGui::SetCursorScreenPos(lineStart);
}
bool ListBox(const char* label, int* current_item,
const std::vector<std::string>& items, int height_in_items) {
std::vector<const char*> items_ptr;
items_ptr.reserve(items.size());
for (const auto& item : items) {
items_ptr.push_back(item.c_str());
}
int items_count = static_cast<int>(items.size());
return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
height_in_items);
}
bool InputTileInfo(const char* label, gfx::TileInfo* tile_info) {
ImGui::PushID(label);
ImGui::BeginGroup();
bool changed = false;
changed |= InputHexWord(label, &tile_info->id_);
changed |= InputHexByte("Palette", &tile_info->palette_);
changed |= ImGui::Checkbox("Priority", &tile_info->over_);
changed |= ImGui::Checkbox("Vertical Flip", &tile_info->vertical_mirror_);
changed |= ImGui::Checkbox("Horizontal Flip", &tile_info->horizontal_mirror_);
ImGui::EndGroup();
ImGui::PopID();
return changed;
}
ImGuiID GetID(const std::string& id) {
return ImGui::GetID(id.c_str());
}
ImGuiKey MapKeyToImGuiKey(char key) {
switch (key) {
case 'A':
return ImGuiKey_A;
case 'B':
return ImGuiKey_B;
case 'C':
return ImGuiKey_C;
case 'D':
return ImGuiKey_D;
case 'E':
return ImGuiKey_E;
case 'F':
return ImGuiKey_F;
case 'G':
return ImGuiKey_G;
case 'H':
return ImGuiKey_H;
case 'I':
return ImGuiKey_I;
case 'J':
return ImGuiKey_J;
case 'K':
return ImGuiKey_K;
case 'L':
return ImGuiKey_L;
case 'M':
return ImGuiKey_M;
case 'N':
return ImGuiKey_N;
case 'O':
return ImGuiKey_O;
case 'P':
return ImGuiKey_P;
case 'Q':
return ImGuiKey_Q;
case 'R':
return ImGuiKey_R;
case 'S':
return ImGuiKey_S;
case 'T':
return ImGuiKey_T;
case 'U':
return ImGuiKey_U;
case 'V':
return ImGuiKey_V;
case 'W':
return ImGuiKey_W;
case 'X':
return ImGuiKey_X;
case 'Y':
return ImGuiKey_Y;
case 'Z':
return ImGuiKey_Z;
case '/':
return ImGuiKey_Slash;
case '-':
return ImGuiKey_Minus;
default:
return ImGuiKey_COUNT;
}
}
void AddTableColumn(Table& table, const std::string& label,
GuiElement element) {
table.column_labels.push_back(label);
table.column_contents.push_back(element);
}
void DrawTable(Table& params) {
if (ImGui::BeginTable(params.id, params.num_columns, params.flags,
params.size)) {
for (int i = 0; i < params.num_columns; ++i)
ImGui::TableSetupColumn(params.column_labels[i].c_str());
for (int i = 0; i < params.num_columns; ++i) {
ImGui::TableNextColumn();
switch (params.column_contents[i].index()) {
case 0:
std::get<0>(params.column_contents[i])();
break;
case 1:
ImGui::Text("%s", std::get<1>(params.column_contents[i]).c_str());
break;
}
}
ImGui::EndTable();
}
}
bool OpenUrl(const std::string& url) {
// if iOS
#ifdef __APPLE__
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
// no system call on iOS
return false;
#else
return system(("open " + url).c_str()) == 0;
#endif
#endif
#ifdef __linux__
return system(("xdg-open " + url).c_str()) == 0;
#endif
#ifdef __windows__
return system(("start " + url).c_str()) == 0;
#endif
return false;
}
void MemoryEditorPopup(const std::string& label, std::span<uint8_t> memory) {
static bool open = false;
static MemoryEditor editor;
if (ImGui::Button("View Data")) {
open = true;
}
if (open) {
ImGui::Begin(label.c_str(), &open);
editor.DrawContents(memory.data(), memory.size());
ImGui::End();
}
}
// Custom hex input functions that properly respect width
bool InputHexByteCustom(const char* label, uint8_t* data, float input_width) {
ImGui::PushID(label);
// Create a simple hex input that respects width
char buf[8];
snprintf(buf, sizeof(buf), "%02X", *data);
ImGui::SetNextItemWidth(input_width);
bool changed = ImGui::InputText(
label, buf, sizeof(buf),
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
if (changed) {
unsigned int temp;
if (sscanf(buf, "%X", &temp) == 1) {
*data = static_cast<uint8_t>(temp & 0xFF);
}
}
ImGui::PopID();
return changed;
}
bool InputHexWordCustom(const char* label, uint16_t* data, float input_width) {
ImGui::PushID(label);
// Create a simple hex input that respects width
char buf[8];
snprintf(buf, sizeof(buf), "%04X", *data);
ImGui::SetNextItemWidth(input_width);
bool changed = ImGui::InputText(
label, buf, sizeof(buf),
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
if (changed) {
unsigned int temp;
if (sscanf(buf, "%X", &temp) == 1) {
*data = static_cast<uint16_t>(temp & 0xFFFF);
}
}
ImGui::PopID();
return changed;
}
} // namespace gui
} // namespace yaze

87
src/app/gui/core/input.h Normal file
View File

@@ -0,0 +1,87 @@
#ifndef YAZE_APP_CORE_INPUT_H
#define YAZE_APP_CORE_INPUT_H
#define IMGUI_DEFINE_MATH_OPERATORS
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
#include <variant>
#include <vector>
#include "absl/strings/string_view.h"
#include "app/gfx/types/snes_tile.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
constexpr ImVec2 kDefaultModalSize = ImVec2(200, 0);
constexpr ImVec2 kZeroPos = ImVec2(0, 0);
IMGUI_API bool InputHex(const char *label, uint64_t *data);
IMGUI_API bool InputHex(const char *label, int *data, int num_digits = 4,
float input_width = 50.f);
IMGUI_API bool InputHexShort(const char *label, uint32_t *data);
IMGUI_API bool InputHexWord(const char *label, uint16_t *data,
float input_width = 50.f, bool no_step = false);
IMGUI_API bool InputHexWord(const char *label, int16_t *data,
float input_width = 50.f, bool no_step = false);
IMGUI_API bool InputHexByte(const char *label, uint8_t *data,
float input_width = 50.f, bool no_step = false);
IMGUI_API bool InputHexByte(const char *label, uint8_t *data, uint8_t max_value,
float input_width = 50.f, bool no_step = false);
// Custom hex input functions that properly respect width
IMGUI_API bool InputHexByteCustom(const char *label, uint8_t *data,
float input_width = 50.f);
IMGUI_API bool InputHexWordCustom(const char *label, uint16_t *data,
float input_width = 70.f);
IMGUI_API void Paragraph(const std::string &text);
IMGUI_API bool ClickableText(const std::string &text);
IMGUI_API bool ListBox(const char *label, int *current_item,
const std::vector<std::string> &items,
int height_in_items = -1);
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info);
using ItemLabelFlags = enum ItemLabelFlag {
Left = 1u << 0u,
Right = 1u << 1u,
Default = Left,
};
IMGUI_API void ItemLabel(absl::string_view title, ItemLabelFlags flags);
IMGUI_API ImGuiID GetID(const std::string &id);
ImGuiKey MapKeyToImGuiKey(char key);
using GuiElement = std::variant<std::function<void()>, std::string>;
struct Table {
const char *id;
int num_columns;
ImGuiTableFlags flags;
ImVec2 size;
std::vector<std::string> column_labels;
std::vector<GuiElement> column_contents;
};
void AddTableColumn(Table &table, const std::string &label, GuiElement element);
IMGUI_API bool OpenUrl(const std::string &url);
void MemoryEditorPopup(const std::string &label, std::span<uint8_t> memory);
} // namespace gui
} // namespace yaze
#endif

View File

@@ -0,0 +1,285 @@
#include "app/gui/core/layout_helpers.h"
#include <vector>
#include "absl/strings/str_format.h"
#include "app/gui/core/icons.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include "app/gui/core/theme_manager.h"
#include "app/gui/core/color.h"
namespace yaze {
namespace gui {
// Core sizing functions
float LayoutHelpers::GetStandardWidgetHeight() {
const auto& theme = GetTheme();
return GetBaseFontSize() * theme.widget_height_multiplier * theme.compact_factor;
}
float LayoutHelpers::GetStandardSpacing() {
const auto& theme = GetTheme();
return GetBaseFontSize() * 0.5f * theme.spacing_multiplier * theme.compact_factor;
}
float LayoutHelpers::GetToolbarHeight() {
const auto& theme = GetTheme();
return GetBaseFontSize() * theme.toolbar_height_multiplier * theme.compact_factor;
}
float LayoutHelpers::GetPanelPadding() {
const auto& theme = GetTheme();
return GetBaseFontSize() * 0.5f * theme.panel_padding_multiplier * theme.compact_factor;
}
float LayoutHelpers::GetStandardInputWidth() {
const auto& theme = GetTheme();
return GetBaseFontSize() * 8.0f * theme.input_width_multiplier * theme.compact_factor;
}
float LayoutHelpers::GetButtonPadding() {
const auto& theme = GetTheme();
return GetBaseFontSize() * 0.3f * theme.button_padding_multiplier * theme.compact_factor;
}
float LayoutHelpers::GetTableRowHeight() {
const auto& theme = GetTheme();
return GetBaseFontSize() * theme.table_row_height_multiplier * theme.compact_factor;
}
float LayoutHelpers::GetCanvasToolbarHeight() {
const auto& theme = GetTheme();
return GetBaseFontSize() * theme.canvas_toolbar_multiplier * theme.compact_factor;
}
// Layout utilities
void LayoutHelpers::BeginPaddedPanel(const char* label, float padding) {
if (padding < 0.0f) {
padding = GetPanelPadding();
}
ImGui::BeginChild(label, ImVec2(0, 0), true);
ImGui::Dummy(ImVec2(padding, padding));
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::Dummy(ImVec2(0, padding));
}
void LayoutHelpers::EndPaddedPanel() {
ImGui::Dummy(ImVec2(0, GetPanelPadding()));
ImGui::EndGroup();
ImGui::SameLine();
ImGui::Dummy(ImVec2(GetPanelPadding(), 0));
ImGui::EndChild();
}
bool LayoutHelpers::BeginTableWithTheming(const char* str_id, int columns,
ImGuiTableFlags flags,
const ImVec2& outer_size,
float inner_width) {
const auto& theme = GetTheme();
// Apply theme colors to table
ImGui::PushStyleColor(ImGuiCol_TableHeaderBg, ConvertColorToImVec4(theme.table_header_bg));
ImGui::PushStyleColor(ImGuiCol_TableBorderStrong, ConvertColorToImVec4(theme.table_border_strong));
ImGui::PushStyleColor(ImGuiCol_TableBorderLight, ConvertColorToImVec4(theme.table_border_light));
ImGui::PushStyleColor(ImGuiCol_TableRowBg, ConvertColorToImVec4(theme.table_row_bg));
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ConvertColorToImVec4(theme.table_row_bg_alt));
// Set row height if not overridden by caller
if (!(flags & ImGuiTableFlags_NoHostExtendY)) {
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,
ImVec2(ImGui::GetStyle().CellPadding.x, GetTableRowHeight() * 0.25f));
}
return ImGui::BeginTable(str_id, columns, flags, outer_size, inner_width);
}
void LayoutHelpers::BeginCanvasPanel(const char* label, ImVec2* canvas_size) {
const auto& theme = GetTheme();
// Apply theme to canvas container
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.editor_background));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
if (canvas_size) {
ImGui::BeginChild(label, *canvas_size, true);
} else {
ImGui::BeginChild(label, ImVec2(0, 0), true);
}
}
void LayoutHelpers::EndCanvasPanel() {
ImGui::EndChild();
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(1);
}
// Input field helpers
bool LayoutHelpers::AutoSizedInputField(const char* label, char* buf,
size_t buf_size, ImGuiInputTextFlags flags) {
ImGui::SetNextItemWidth(GetStandardInputWidth());
return ImGui::InputText(label, buf, buf_size, flags);
}
bool LayoutHelpers::AutoSizedInputInt(const char* label, int* v, int step,
int step_fast, ImGuiInputTextFlags flags) {
ImGui::SetNextItemWidth(GetStandardInputWidth());
return ImGui::InputInt(label, v, step, step_fast, flags);
}
bool LayoutHelpers::AutoSizedInputFloat(const char* label, float* v, float step,
float step_fast, const char* format,
ImGuiInputTextFlags flags) {
ImGui::SetNextItemWidth(GetStandardInputWidth());
return ImGui::InputFloat(label, v, step, step_fast, format, flags);
}
// Input preset functions for common patterns
bool LayoutHelpers::InputHexRow(const char* label, uint8_t* data) {
const auto& theme = GetTheme();
float input_width = GetStandardInputWidth() * 0.5f; // Hex inputs are smaller
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", label);
ImGui::SameLine();
// Use theme-aware input width for hex byte (2 chars + controls)
ImGui::SetNextItemWidth(input_width);
char buf[8];
snprintf(buf, sizeof(buf), "%02X", *data);
bool changed = ImGui::InputText(
("##" + std::string(label)).c_str(), buf, sizeof(buf),
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
if (changed) {
unsigned int temp;
if (sscanf(buf, "%X", &temp) == 1) {
*data = static_cast<uint8_t>(temp & 0xFF);
}
}
return changed;
}
bool LayoutHelpers::InputHexRow(const char* label, uint16_t* data) {
const auto& theme = GetTheme();
float input_width = GetStandardInputWidth() * 0.6f; // Hex word slightly wider
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", label);
ImGui::SameLine();
// Use theme-aware input width for hex word (4 chars + controls)
ImGui::SetNextItemWidth(input_width);
char buf[8];
snprintf(buf, sizeof(buf), "%04X", *data);
bool changed = ImGui::InputText(
("##" + std::string(label)).c_str(), buf, sizeof(buf),
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
if (changed) {
unsigned int temp;
if (sscanf(buf, "%X", &temp) == 1) {
*data = static_cast<uint16_t>(temp & 0xFFFF);
}
}
return changed;
}
void LayoutHelpers::BeginPropertyGrid(const char* label) {
const auto& theme = GetTheme();
// Create a 2-column table for property editing
if (ImGui::BeginTable(label, 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
// Setup columns: label column (30%) and value column (70%)
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed,
GetStandardInputWidth() * 1.5f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
}
}
void LayoutHelpers::EndPropertyGrid() {
ImGui::EndTable();
}
bool LayoutHelpers::InputToolbarField(const char* label, char* buf, size_t buf_size) {
// Compact input field for toolbars
float compact_width = GetStandardInputWidth() * 0.8f * GetTheme().compact_factor;
ImGui::SetNextItemWidth(compact_width);
return ImGui::InputText(label, buf, buf_size,
ImGuiInputTextFlags_AutoSelectAll);
}
// Toolbar helpers
void LayoutHelpers::BeginToolbar(const char* label) {
const auto& theme = GetTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.menu_bar_bg));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
ImVec2(GetButtonPadding(), GetButtonPadding()));
ImGui::BeginChild(label, ImVec2(0, GetToolbarHeight()), true,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
}
void LayoutHelpers::EndToolbar() {
ImGui::EndChild();
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(1);
}
void LayoutHelpers::ToolbarSeparator() {
ImGui::SameLine();
ImGui::Dummy(ImVec2(GetStandardSpacing(), 0));
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
ImGui::Dummy(ImVec2(GetStandardSpacing(), 0));
ImGui::SameLine();
}
bool LayoutHelpers::ToolbarButton(const char* label, const ImVec2& size) {
const auto& theme = GetTheme();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
ImVec2(GetButtonPadding(), GetButtonPadding()));
bool result = ImGui::Button(label, size);
ImGui::PopStyleVar(1);
return result;
}
// Common layout patterns
void LayoutHelpers::PropertyRow(const char* label, std::function<void()> widget_callback) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", label);
ImGui::TableSetColumnIndex(1);
ImGui::SetNextItemWidth(-1);
widget_callback();
}
void LayoutHelpers::SectionHeader(const char* label) {
const auto& theme = GetTheme();
ImGui::PushStyleColor(ImGuiCol_Text, ConvertColorToImVec4(theme.accent));
ImGui::SeparatorText(label);
ImGui::PopStyleColor(1);
}
void LayoutHelpers::HelpMarker(const char* desc) {
ImGui::TextDisabled("(?)");
if (ImGui::BeginItemTooltip()) {
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,86 @@
#ifndef YAZE_APP_GUI_LAYOUT_HELPERS_H
#define YAZE_APP_GUI_LAYOUT_HELPERS_H
#include <vector>
#include "absl/strings/str_format.h"
#include "app/gui/core/theme_manager.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Theme-aware sizing helpers for consistent UI layout
*
* All sizing functions respect the current theme's compact_factor and
* semantic multipliers, ensuring layouts are consistent but customizable.
*/
class LayoutHelpers {
public:
// Core sizing functions (respect theme compact_factor + multipliers)
static float GetStandardWidgetHeight();
static float GetStandardSpacing();
static float GetToolbarHeight();
static float GetPanelPadding();
static float GetStandardInputWidth();
static float GetButtonPadding();
static float GetTableRowHeight();
static float GetCanvasToolbarHeight();
// Layout utilities
static void BeginPaddedPanel(const char* label, float padding = -1.0f);
static void EndPaddedPanel();
static bool BeginTableWithTheming(const char* str_id, int columns,
ImGuiTableFlags flags = 0,
const ImVec2& outer_size = ImVec2(0, 0),
float inner_width = 0.0f);
static void EndTable() { ImGui::EndTable(); }
static void BeginCanvasPanel(const char* label, ImVec2* canvas_size = nullptr);
static void EndCanvasPanel();
// Input field helpers
static bool AutoSizedInputField(const char* label, char* buf, size_t buf_size,
ImGuiInputTextFlags flags = 0);
static bool AutoSizedInputInt(const char* label, int* v, int step = 1,
int step_fast = 100, ImGuiInputTextFlags flags = 0);
static bool AutoSizedInputFloat(const char* label, float* v, float step = 0.0f,
float step_fast = 0.0f, const char* format = "%.3f",
ImGuiInputTextFlags flags = 0);
// Input preset functions for common patterns
static bool InputHexRow(const char* label, uint8_t* data);
static bool InputHexRow(const char* label, uint16_t* data);
static void BeginPropertyGrid(const char* label);
static void EndPropertyGrid();
static bool InputToolbarField(const char* label, char* buf, size_t buf_size);
// Toolbar helpers
static void BeginToolbar(const char* label);
static void EndToolbar();
static void ToolbarSeparator();
static bool ToolbarButton(const char* label, const ImVec2& size = ImVec2(0, 0));
// Common layout patterns
static void PropertyRow(const char* label, std::function<void()> widget_callback);
static void SectionHeader(const char* label);
static void HelpMarker(const char* desc);
// Get current theme
static const EnhancedTheme& GetTheme() {
return ThemeManager::Get().GetCurrentTheme();
}
private:
static float GetBaseFontSize() { return ImGui::GetFontSize(); }
static float ApplyCompactFactor(float base_value) {
return base_value * GetTheme().compact_factor;
}
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_LAYOUT_HELPERS_H

1326
src/app/gui/core/style.cc Normal file

File diff suppressed because it is too large Load Diff

205
src/app/gui/core/style.h Normal file
View File

@@ -0,0 +1,205 @@
#ifndef YAZE_APP_CORE_STYLE_H
#define YAZE_APP_CORE_STYLE_H
#include <functional>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "app/gfx/core/bitmap.h"
#include "app/gui/core/color.h"
#include "app/gui/widgets/text_editor.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
void ColorsYaze();
TextEditor::LanguageDefinition GetAssemblyLanguageDef();
void DrawBitmapViewer(const std::vector<gfx::Bitmap> &bitmaps, float scale,
int &current_bitmap);
void BeginWindowWithDisplaySettings(const char *id, bool *active,
const ImVec2 &size = ImVec2(0, 0),
ImGuiWindowFlags flags = 0);
void EndWindowWithDisplaySettings();
void BeginPadding(int i);
void EndPadding();
void BeginNoPadding();
void EndNoPadding();
void BeginChildWithScrollbar(const char *str_id);
void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size);
void BeginChildBothScrollbars(int id);
// Table canvas management helpers for GUI elements that need proper sizing
void BeginTableCanvas(const char* table_id, int columns, ImVec2 canvas_size = ImVec2(0, 0));
void EndTableCanvas();
void SetupCanvasTableColumn(const char* label, float width_ratio = 0.0f);
void BeginCanvasTableCell(ImVec2 min_size = ImVec2(0, 0));
void DrawDisplaySettings(ImGuiStyle *ref = nullptr);
void DrawDisplaySettingsForPopup(ImGuiStyle *ref = nullptr); // Popup-safe version
void TextWithSeparators(const absl::string_view &text);
void DrawFontManager();
struct TextBox {
std::string text;
std::string buffer;
int cursor_pos = 0;
int selection_start = 0;
int selection_end = 0;
int selection_length = 0;
bool has_selection = false;
bool has_focus = false;
bool changed = false;
bool can_undo = false;
void Undo() {
text = buffer;
cursor_pos = selection_start;
has_selection = false;
}
void clearUndo() { can_undo = false; }
void Copy() { ImGui::SetClipboardText(text.c_str()); }
void Cut() {
Copy();
text.erase(selection_start, selection_end - selection_start);
cursor_pos = selection_start;
has_selection = false;
changed = true;
}
void Paste() {
text.erase(selection_start, selection_end - selection_start);
text.insert(selection_start, ImGui::GetClipboardText());
std::string str = ImGui::GetClipboardText();
cursor_pos = selection_start + str.size();
has_selection = false;
changed = true;
}
void clear() {
text.clear();
buffer.clear();
cursor_pos = 0;
selection_start = 0;
selection_end = 0;
selection_length = 0;
has_selection = false;
has_focus = false;
changed = false;
can_undo = false;
}
void SelectAll() {
selection_start = 0;
selection_end = text.size();
selection_length = text.size();
has_selection = true;
}
void Focus() { has_focus = true; }
};
// Generic multi-select component that can be used with different types of data
template <typename T>
class MultiSelect {
public:
// Callback function type for rendering an item
using ItemRenderer =
std::function<void(int index, const T &item, bool is_selected)>;
// Constructor with optional title and default flags
MultiSelect(
const char *title = "Selection",
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape |
ImGuiMultiSelectFlags_BoxSelect1d)
: title_(title), flags_(flags), selection_() {}
// Set the items to display
void SetItems(const std::vector<T> &items) { items_ = items; }
// Set the renderer function for items
void SetItemRenderer(ItemRenderer renderer) { item_renderer_ = renderer; }
// Set the height of the selection area (in font size units)
void SetHeight(float height_in_font_units = 20.0f) {
height_in_font_units_ = height_in_font_units;
}
// Set the child window flags
void SetChildFlags(ImGuiChildFlags flags) { child_flags_ = flags; }
// Update and render the multi-select component
void Update() {
ImGui::Text("%s: %d/%d", title_, selection_.Size, items_.size());
if (ImGui::BeginChild(
"##MultiSelectChild",
ImVec2(-FLT_MIN, ImGui::GetFontSize() * height_in_font_units_),
child_flags_)) {
ImGuiMultiSelectIO *ms_io =
ImGui::BeginMultiSelect(flags_, selection_.Size, items_.size());
selection_.ApplyRequests(ms_io);
ImGuiListClipper clipper;
clipper.Begin(items_.size());
if (ms_io->RangeSrcItem != -1)
clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem);
while (clipper.Step()) {
for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++) {
bool item_is_selected = selection_.Contains((ImGuiID)n);
ImGui::SetNextItemSelectionUserData(n);
if (item_renderer_) {
item_renderer_(n, items_[n], item_is_selected);
} else {
// Default rendering if no custom renderer is provided
char label[64];
snprintf(label, sizeof(label), "Item %d", n);
ImGui::Selectable(label, item_is_selected);
}
}
}
ms_io = ImGui::EndMultiSelect();
selection_.ApplyRequests(ms_io);
}
ImGui::EndChild();
}
// Get the selected indices
std::vector<int> GetSelectedIndices() const {
std::vector<int> indices;
for (int i = 0; i < items_.size(); i++) {
if (selection_.Contains((ImGuiID)i)) {
indices.push_back(i);
}
}
return indices;
}
// Clear the selection
void ClearSelection() { selection_.Clear(); }
private:
const char *title_;
ImGuiMultiSelectFlags flags_;
ImGuiSelectionBasicStorage selection_;
std::vector<T> items_;
ItemRenderer item_renderer_;
float height_in_font_units_ = 20.0f;
ImGuiChildFlags child_flags_ =
ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY;
};
} // namespace gui
} // namespace yaze
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,241 @@
#ifndef YAZE_APP_GUI_THEME_MANAGER_H
#define YAZE_APP_GUI_THEME_MANAGER_H
#include <map>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/gui/core/color.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @struct EnhancedTheme
* @brief Comprehensive theme structure for YAZE
*/
struct EnhancedTheme {
std::string name;
std::string description;
std::string author;
// Primary colors
Color primary;
Color secondary;
Color accent;
Color background;
Color surface;
Color error;
Color warning;
Color success;
Color info;
// Text colors
Color text_primary;
Color text_secondary;
Color text_disabled;
// Window colors
Color window_bg;
Color child_bg;
Color popup_bg;
Color modal_bg;
// Interactive elements
Color button;
Color button_hovered;
Color button_active;
Color frame_bg;
Color frame_bg_hovered;
Color frame_bg_active;
// Navigation and selection
Color header;
Color header_hovered;
Color header_active;
Color tab;
Color tab_hovered;
Color tab_active;
Color menu_bar_bg;
Color title_bg;
Color title_bg_active;
Color title_bg_collapsed;
// Borders and separators
Color border;
Color border_shadow;
Color separator;
Color separator_hovered;
Color separator_active;
// Scrollbars and controls
Color scrollbar_bg;
Color scrollbar_grab;
Color scrollbar_grab_hovered;
Color scrollbar_grab_active;
// Special elements
Color resize_grip;
Color resize_grip_hovered;
Color resize_grip_active;
Color docking_preview;
Color docking_empty_bg;
// Complete ImGui color support
Color check_mark;
Color slider_grab;
Color slider_grab_active;
Color input_text_cursor;
Color nav_cursor;
Color nav_windowing_highlight;
Color nav_windowing_dim_bg;
Color modal_window_dim_bg;
Color text_selected_bg;
Color drag_drop_target;
Color table_header_bg;
Color table_border_strong;
Color table_border_light;
Color table_row_bg;
Color table_row_bg_alt;
Color text_link;
Color plot_lines;
Color plot_lines_hovered;
Color plot_histogram;
Color plot_histogram_hovered;
Color tree_lines;
// Additional ImGui colors for complete coverage
Color tab_unfocused;
Color tab_unfocused_active;
Color tab_dimmed;
Color tab_dimmed_selected;
Color tab_dimmed_selected_overline;
Color tab_selected_overline;
// Enhanced theme system - semantic colors
Color text_highlight; // For selected text, highlighted items
Color link_hover; // For hover state of links
Color code_background; // For code blocks, monospace text backgrounds
Color success_light; // Lighter variant of success color
Color warning_light; // Lighter variant of warning color
Color error_light; // Lighter variant of error color
Color info_light; // Lighter variant of info color
// UI state colors
Color active_selection; // For active/selected UI elements
Color hover_highlight; // General hover state
Color focus_border; // For focused input elements
Color disabled_overlay; // Semi-transparent overlay for disabled elements
// Editor-specific colors
Color editor_background; // Main editor canvas background
Color editor_grid; // Grid lines in editors
Color editor_cursor; // Cursor/selection in editors
Color editor_selection; // Selected area in editors
Color entrance_color;
Color hole_color;
Color exit_color;
Color item_color;
Color sprite_color;
// Style parameters
float window_rounding = 0.0f;
float frame_rounding = 5.0f;
float scrollbar_rounding = 5.0f;
float grab_rounding = 3.0f;
float tab_rounding = 0.0f;
float window_border_size = 0.0f;
float frame_border_size = 0.0f;
// Animation and effects
bool enable_animations = true;
float animation_speed = 1.0f;
bool enable_glow_effects = false;
// Theme-aware sizing system (relative to font size)
// compact_factor: 0.8 = very compact, 1.0 = normal, 1.2 = spacious
float compact_factor = 1.0f;
// Semantic sizing multipliers (applied on top of compact_factor)
float widget_height_multiplier = 1.0f; // Standard widget height
float spacing_multiplier = 1.0f; // Padding/margins between elements
float toolbar_height_multiplier = 0.8f; // Compact toolbars
float panel_padding_multiplier = 1.0f; // Panel interior padding
float input_width_multiplier = 1.0f; // Standard input field width
float button_padding_multiplier = 1.0f; // Button interior padding
float table_row_height_multiplier = 1.0f; // Table row height
float canvas_toolbar_multiplier = 0.75f; // Canvas overlay toolbars
// Helper methods
void ApplyToImGui() const;
};
/**
* @class ThemeManager
* @brief Manages themes, loading, saving, and switching
*/
class ThemeManager {
public:
static ThemeManager& Get();
// Theme management
absl::Status LoadTheme(const std::string& theme_name);
absl::Status SaveTheme(const EnhancedTheme& theme, const std::string& filename);
absl::Status LoadThemeFromFile(const std::string& filepath);
absl::Status SaveThemeToFile(const EnhancedTheme& theme, const std::string& filepath) const;
// Dynamic theme discovery - replaces hardcoded theme lists with automatic discovery
// This works across development builds, macOS app bundles, and other deployment scenarios
std::vector<std::string> DiscoverAvailableThemeFiles() const;
absl::Status LoadAllAvailableThemes();
absl::Status RefreshAvailableThemes(); // Public method to refresh at runtime
// Built-in themes
void InitializeBuiltInThemes();
std::vector<std::string> GetAvailableThemes() const;
const EnhancedTheme* GetTheme(const std::string& name) const;
const EnhancedTheme& GetCurrentTheme() const { return current_theme_; }
const std::string& GetCurrentThemeName() const { return current_theme_name_; }
// Theme application
void ApplyTheme(const std::string& theme_name);
void ApplyTheme(const EnhancedTheme& theme);
void ApplyClassicYazeTheme(); // Apply original ColorsYaze() function
// Theme creation and editing
EnhancedTheme CreateCustomTheme(const std::string& name);
void ShowThemeEditor(bool* p_open);
void ShowThemeSelector(bool* p_open);
void ShowSimpleThemeEditor(bool* p_open);
// Integration with welcome screen
Color GetWelcomeScreenBackground() const;
Color GetWelcomeScreenBorder() const;
Color GetWelcomeScreenAccent() const;
private:
ThemeManager() { InitializeBuiltInThemes(); }
std::map<std::string, EnhancedTheme> themes_;
EnhancedTheme current_theme_;
std::string current_theme_name_ = "Classic YAZE";
void CreateFallbackYazeClassic();
absl::Status ParseThemeFile(const std::string& content, EnhancedTheme& theme);
Color ParseColorFromString(const std::string& color_str) const;
std::string SerializeTheme(const EnhancedTheme& theme) const;
// Helper methods for path resolution
std::vector<std::string> GetThemeSearchPaths() const;
std::string GetThemesDirectory() const;
std::string GetCurrentThemeFilePath() const;
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_THEME_MANAGER_H

View File

@@ -0,0 +1,533 @@
#include "app/gui/core/ui_helpers.h"
#include "absl/strings/str_format.h"
#include "app/gui/core/icons.h"
#include "app/gui/core/color.h"
#include "app/gui/core/theme_manager.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
namespace yaze {
namespace gui {
// ============================================================================
// Theme and Semantic Colors
// ============================================================================
ImVec4 GetThemeColor(ImGuiCol idx) {
return ImGui::GetStyle().Colors[idx];
}
ImVec4 GetSuccessColor() {
const auto& theme = ThemeManager::Get().GetCurrentTheme();
return ConvertColorToImVec4(theme.success);
}
ImVec4 GetWarningColor() {
const auto& theme = ThemeManager::Get().GetCurrentTheme();
return ConvertColorToImVec4(theme.warning);
}
ImVec4 GetErrorColor() {
const auto& theme = ThemeManager::Get().GetCurrentTheme();
return ConvertColorToImVec4(theme.error);
}
ImVec4 GetInfoColor() {
const auto& theme = ThemeManager::Get().GetCurrentTheme();
return ConvertColorToImVec4(theme.info);
}
ImVec4 GetAccentColor() {
const auto& theme = ThemeManager::Get().GetCurrentTheme();
return ConvertColorToImVec4(theme.accent);
}
// Entity/Map marker colors (vibrant with good visibility)
ImVec4 GetEntranceColor() {
// Bright yellow with strong visibility
return ImVec4(1.0f, 0.9f, 0.0f, 0.85f); // Yellow-gold, high visibility
}
ImVec4 GetExitColor() {
// Bright cyan-white for contrast
return ImVec4(0.9f, 1.0f, 1.0f, 0.85f); // Cyan-white, high visibility
}
ImVec4 GetItemColor() {
// Vibrant red for items
return ImVec4(1.0f, 0.2f, 0.2f, 0.85f); // Bright red, high visibility
}
ImVec4 GetSpriteColor() {
// Bright magenta for sprites
return ImVec4(1.0f, 0.3f, 1.0f, 0.85f); // Bright magenta, high visibility
}
ImVec4 GetSelectedColor() {
const auto& theme = ThemeManager::Get().GetCurrentTheme();
return ConvertColorToImVec4(theme.accent);
}
ImVec4 GetLockedColor() {
return ImVec4(1.0f, 0.5f, 0.0f, 1.0f); // Orange for locked items
}
// Status colors
ImVec4 GetVanillaRomColor() {
return GetWarningColor(); // Yellow from theme
}
ImVec4 GetCustomRomColor() {
return GetSuccessColor(); // Green from theme
}
ImVec4 GetModifiedColor() {
const auto& theme = ThemeManager::Get().GetCurrentTheme();
return ConvertColorToImVec4(theme.accent);
}
// ============================================================================
// Layout Helpers
// ============================================================================
void BeginField(const char* label, float label_width) {
ImGui::BeginGroup();
if (label_width > 0.0f) {
ImGui::Text("%s:", label);
ImGui::SameLine(label_width);
} else {
ImGui::TextUnformatted(label);
ImGui::SameLine();
}
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.6f);
}
void EndField() {
ImGui::PopItemWidth();
ImGui::EndGroup();
}
bool BeginPropertyTable(const char* id, int columns, ImGuiTableFlags extra_flags) {
ImGuiTableFlags flags = ImGuiTableFlags_Borders |
ImGuiTableFlags_SizingFixedFit |
extra_flags;
if (ImGui::BeginTable(id, columns, flags)) {
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
return true;
}
return false;
}
void EndPropertyTable() {
ImGui::EndTable();
}
void PropertyRow(const char* label, const char* value) {
ImGui::TableNextColumn();
ImGui::Text("%s", label);
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", value);
}
void PropertyRow(const char* label, int value) {
ImGui::TableNextColumn();
ImGui::Text("%s", label);
ImGui::TableNextColumn();
ImGui::Text("%d", value);
}
void PropertyRowHex(const char* label, uint8_t value) {
ImGui::TableNextColumn();
ImGui::Text("%s", label);
ImGui::TableNextColumn();
ImGui::Text("0x%02X", value);
}
void PropertyRowHex(const char* label, uint16_t value) {
ImGui::TableNextColumn();
ImGui::Text("%s", label);
ImGui::TableNextColumn();
ImGui::Text("0x%04X", value);
}
void SectionHeader(const char* icon, const char* label, const ImVec4& color) {
ImGui::TextColored(color, "%s %s", icon, label);
ImGui::Separator();
}
// ============================================================================
// Common Widget Patterns
// ============================================================================
bool IconButton(const char* icon, const char* label, const ImVec2& size) {
std::string button_text = std::string(icon) + " " + std::string(label);
return ImGui::Button(button_text.c_str(), size);
}
bool ColoredButton(const char* label, ButtonType type, const ImVec2& size) {
ImVec4 color;
switch (type) {
case ButtonType::Success: color = GetSuccessColor(); break;
case ButtonType::Warning: color = GetWarningColor(); break;
case ButtonType::Error: color = GetErrorColor(); break;
case ButtonType::Info: color = GetInfoColor(); break;
default: color = GetThemeColor(ImGuiCol_Button); break;
}
ImGui::PushStyleColor(ImGuiCol_Button, color);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, color.w));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(color.x * 0.8f, color.y * 0.8f, color.z * 0.8f, color.w));
bool result = ImGui::Button(label, size);
ImGui::PopStyleColor(3);
return result;
}
bool ToggleIconButton(const char* icon_on, const char* icon_off,
bool* state, const char* tooltip) {
const char* icon = *state ? icon_on : icon_off;
ImVec4 color = *state ? GetSuccessColor() : GetThemeColor(ImGuiCol_Button);
ImGui::PushStyleColor(ImGuiCol_Button, color);
bool result = ImGui::SmallButton(icon);
ImGui::PopStyleColor();
if (result) *state = !*state;
if (tooltip && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", tooltip);
}
return result;
}
void HelpMarker(const char* desc) {
ImGui::TextDisabled(ICON_MD_HELP_OUTLINE);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
void SeparatorText(const char* label) {
ImGui::SeparatorText(label);
}
void StatusBadge(const char* text, ButtonType type) {
ImVec4 color;
switch (type) {
case ButtonType::Success: color = GetSuccessColor(); break;
case ButtonType::Warning: color = GetWarningColor(); break;
case ButtonType::Error: color = GetErrorColor(); break;
case ButtonType::Info: color = GetInfoColor(); break;
default: color = GetThemeColor(ImGuiCol_Text); break;
}
ImGui::PushStyleColor(ImGuiCol_Button, color);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 10.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 3));
ImGui::SmallButton(text);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
}
// ============================================================================
// Editor-Specific Patterns
// ============================================================================
void BeginToolset(const char* id) {
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(2, 2));
ImGui::BeginTable(id, 32, ImGuiTableFlags_SizingFixedFit);
}
void EndToolset() {
ImGui::EndTable();
ImGui::PopStyleVar();
}
void ToolsetButton(const char* icon, bool selected, const char* tooltip,
std::function<void()> on_click) {
ImGui::TableNextColumn();
if (selected) {
ImGui::PushStyleColor(ImGuiCol_Button, GetAccentColor());
}
if (ImGui::Button(icon)) {
if (on_click) on_click();
}
if (selected) {
ImGui::PopStyleColor();
}
if (tooltip && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", tooltip);
}
}
void BeginCanvasContainer(const char* id, bool scrollable) {
ImGuiWindowFlags flags = scrollable ?
ImGuiWindowFlags_AlwaysVerticalScrollbar :
ImGuiWindowFlags_None;
ImGui::BeginChild(id, ImVec2(0, 0), true, flags);
}
void EndCanvasContainer() {
ImGui::EndChild();
}
bool EditorTabItem(const char* icon, const char* label, bool* p_open) {
char tab_label[256];
snprintf(tab_label, sizeof(tab_label), "%s %s", icon, label);
return ImGui::BeginTabItem(tab_label, p_open);
}
bool ConfirmationDialog(const char* id, const char* title, const char* message,
const char* confirm_text, const char* cancel_text) {
bool confirmed = false;
if (ImGui::BeginPopupModal(id, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("%s", title);
ImGui::Separator();
ImGui::TextWrapped("%s", message);
ImGui::Separator();
if (ColoredButton(confirm_text, ButtonType::Warning, ImVec2(120, 0))) {
confirmed = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button(cancel_text, ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return confirmed;
}
// ============================================================================
// Visual Indicators
// ============================================================================
void StatusIndicator(const char* label, bool active, const char* tooltip) {
ImVec4 color = active ? GetSuccessColor() : GetThemeColor(ImGuiCol_TextDisabled);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
float radius = 5.0f;
pos.x += radius + 3;
pos.y += ImGui::GetTextLineHeight() * 0.5f;
draw_list->AddCircleFilled(pos, radius, ImGui::GetColorU32(color));
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + radius * 2 + 8);
ImGui::Text("%s", label);
if (tooltip && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", tooltip);
}
}
void RomVersionBadge(const char* version, bool is_vanilla) {
ImVec4 color = is_vanilla ? GetWarningColor() : GetSuccessColor();
const char* icon = is_vanilla ? ICON_MD_INFO : ICON_MD_CHECK_CIRCLE;
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::Text("%s %s", icon, version);
ImGui::PopStyleColor();
}
void LockIndicator(bool locked, const char* label) {
if (locked) {
ImGui::TextColored(GetLockedColor(), ICON_MD_LOCK " %s (Locked)", label);
} else {
ImGui::Text(ICON_MD_LOCK_OPEN " %s", label);
}
}
// ============================================================================
// Spacing and Alignment
// ============================================================================
void VerticalSpacing(float pixels) {
ImGui::Dummy(ImVec2(0, pixels));
}
void HorizontalSpacing(float pixels) {
ImGui::Dummy(ImVec2(pixels, 0));
ImGui::SameLine();
}
void CenterText(const char* text) {
float text_width = ImGui::CalcTextSize(text).x;
ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - text_width) * 0.5f);
ImGui::Text("%s", text);
}
void RightAlign(float width) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() +
ImGui::GetContentRegionAvail().x - width);
}
// ============================================================================
// Animation Helpers
// ============================================================================
float GetPulseAlpha(float speed) {
return 0.5f + 0.5f * sinf(static_cast<float>(ImGui::GetTime()) * speed * 2.0f);
}
float GetFadeIn(float duration) {
static double start_time = 0.0;
double current_time = ImGui::GetTime();
if (start_time == 0.0) {
start_time = current_time;
}
float elapsed = static_cast<float>(current_time - start_time);
float alpha = ImClamp(elapsed / duration, 0.0f, 1.0f);
// Reset after complete
if (alpha >= 1.0f) {
start_time = 0.0;
}
return alpha;
}
void PushPulseEffect(float speed) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
ImGui::GetStyle().Alpha * GetPulseAlpha(speed));
}
void PopPulseEffect() {
ImGui::PopStyleVar();
}
void LoadingSpinner(const char* label, float radius) {
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
pos.x += radius + 4;
pos.y += radius + 4;
const float rotation = static_cast<float>(ImGui::GetTime()) * 3.0f;
const int segments = 16;
const float thickness = 3.0f;
const float start_angle = rotation;
const float end_angle = rotation + IM_PI * 1.5f;
draw_list->PathArcTo(pos, radius, start_angle, end_angle, segments);
draw_list->PathStroke(ImGui::GetColorU32(GetAccentColor()), 0, thickness);
if (label) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + radius * 2 + 8);
ImGui::Text("%s", label);
} else {
ImGui::Dummy(ImVec2(radius * 2 + 8, radius * 2 + 8));
}
}
// ============================================================================
// Responsive Layout Helpers
// ============================================================================
float GetResponsiveWidth(float min_width, float max_width, float ratio) {
float available = ImGui::GetContentRegionAvail().x;
float target = available * ratio;
if (target < min_width) return min_width;
if (target > max_width) return max_width;
return target;
}
void SetupResponsiveColumns(int count, float min_col_width) {
float available = ImGui::GetContentRegionAvail().x;
float col_width = available / count;
if (col_width < min_col_width) {
col_width = min_col_width;
}
for (int i = 0; i < count; ++i) {
ImGui::TableSetupColumn("##col", ImGuiTableColumnFlags_WidthFixed, col_width);
}
}
static int g_two_col_table_active = 0;
void BeginTwoColumns(const char* id, float split_ratio) {
ImGuiTableFlags flags = ImGuiTableFlags_Resizable |
ImGuiTableFlags_BordersInnerV |
ImGuiTableFlags_SizingStretchProp;
if (ImGui::BeginTable(id, 2, flags)) {
float available = ImGui::GetContentRegionAvail().x;
ImGui::TableSetupColumn("##left", ImGuiTableColumnFlags_None,
available * split_ratio);
ImGui::TableSetupColumn("##right", ImGuiTableColumnFlags_None,
available * (1.0f - split_ratio));
ImGui::TableNextRow();
ImGui::TableNextColumn();
g_two_col_table_active++;
}
}
void SwitchColumn() {
ImGui::TableNextColumn();
}
void EndTwoColumns() {
ImGui::EndTable();
g_two_col_table_active--;
}
// ============================================================================
// Input Helpers
// ============================================================================
bool LabeledInputHex(const char* label, uint8_t* value) {
BeginField(label);
ImGui::PushItemWidth(60);
bool changed = ImGui::InputScalar("##hex", ImGuiDataType_U8, value, nullptr,
nullptr, "%02X",
ImGuiInputTextFlags_CharsHexadecimal);
ImGui::PopItemWidth();
EndField();
return changed;
}
bool LabeledInputHex(const char* label, uint16_t* value) {
BeginField(label);
ImGui::PushItemWidth(80);
bool changed = ImGui::InputScalar("##hex", ImGuiDataType_U16, value, nullptr,
nullptr, "%04X",
ImGuiInputTextFlags_CharsHexadecimal);
ImGui::PopItemWidth();
EndField();
return changed;
}
bool IconCombo(const char* icon, const char* label, int* current,
const char* const items[], int count) {
ImGui::Text("%s", icon);
ImGui::SameLine();
return ImGui::Combo(label, current, items, count);
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,182 @@
#ifndef YAZE_APP_GUI_UI_HELPERS_H
#define YAZE_APP_GUI_UI_HELPERS_H
#include "imgui/imgui.h"
#include <string>
#include <functional>
namespace yaze {
namespace gui {
// A collection of helper functions and widgets to standardize UI development
// and reduce boilerplate ImGui code across all editors.
// ============================================================================
// Theme and Semantic Colors
// ============================================================================
// Gets a color from the current theme
ImVec4 GetThemeColor(ImGuiCol idx);
// Semantic colors from current theme
ImVec4 GetSuccessColor();
ImVec4 GetWarningColor();
ImVec4 GetErrorColor();
ImVec4 GetInfoColor();
ImVec4 GetAccentColor();
// Entity/Map marker colors (for overworld, dungeon)
ImVec4 GetEntranceColor();
ImVec4 GetExitColor();
ImVec4 GetItemColor();
ImVec4 GetSpriteColor();
ImVec4 GetSelectedColor();
ImVec4 GetLockedColor();
// Status colors
ImVec4 GetVanillaRomColor();
ImVec4 GetCustomRomColor();
ImVec4 GetModifiedColor();
// ============================================================================
// Layout Helpers
// ============================================================================
// Label + widget field pattern
void BeginField(const char* label, float label_width = 0.0f);
void EndField();
// Property table pattern (common in editors)
bool BeginPropertyTable(const char* id, int columns = 2,
ImGuiTableFlags extra_flags = 0);
void EndPropertyTable();
// Property row helpers
void PropertyRow(const char* label, const char* value);
void PropertyRow(const char* label, int value);
void PropertyRowHex(const char* label, uint8_t value);
void PropertyRowHex(const char* label, uint16_t value);
// Section headers with icons
void SectionHeader(const char* icon, const char* label,
const ImVec4& color = ImVec4(1, 1, 1, 1));
// ============================================================================
// Common Widget Patterns
// ============================================================================
// Button with icon
bool IconButton(const char* icon, const char* label,
const ImVec2& size = ImVec2(0, 0));
// Colored button for status actions
enum class ButtonType { Default, Success, Warning, Error, Info };
bool ColoredButton(const char* label, ButtonType type,
const ImVec2& size = ImVec2(0, 0));
// Toggle button with visual state
bool ToggleIconButton(const char* icon_on, const char* icon_off,
bool* state, const char* tooltip = nullptr);
// Help marker with tooltip
void HelpMarker(const char* desc);
// Separator with text
void SeparatorText(const char* label);
// Status badge (pill-shaped colored label)
void StatusBadge(const char* text, ButtonType type = ButtonType::Default);
// ============================================================================
// Editor-Specific Patterns
// ============================================================================
// Toolset table (horizontal button bar)
void BeginToolset(const char* id);
void EndToolset();
void ToolsetButton(const char* icon, bool selected, const char* tooltip,
std::function<void()> on_click);
// Canvas container patterns
void BeginCanvasContainer(const char* id, bool scrollable = true);
void EndCanvasContainer();
// Tab pattern for editor modes
bool EditorTabItem(const char* icon, const char* label, bool* p_open = nullptr);
// Modal confirmation dialog
bool ConfirmationDialog(const char* id, const char* title, const char* message,
const char* confirm_text = "OK",
const char* cancel_text = "Cancel");
// ============================================================================
// Visual Indicators
// ============================================================================
// Status indicator dot + label
void StatusIndicator(const char* label, bool active,
const char* tooltip = nullptr);
// ROM version badge
void RomVersionBadge(const char* version, bool is_vanilla);
// Locked/Unlocked indicator
void LockIndicator(bool locked, const char* label);
// ============================================================================
// Spacing and Alignment
// ============================================================================
void VerticalSpacing(float pixels = 8.0f);
void HorizontalSpacing(float pixels = 8.0f);
void CenterText(const char* text);
void RightAlign(float width);
// ============================================================================
// Animation Helpers
// ============================================================================
// Pulsing alpha animation (for loading indicators, etc.)
float GetPulseAlpha(float speed = 1.0f);
// Fade-in animation (for panel transitions)
float GetFadeIn(float duration = 0.3f);
// Apply pulsing effect to next widget
void PushPulseEffect(float speed = 1.0f);
void PopPulseEffect();
// Loading spinner (animated circle)
void LoadingSpinner(const char* label = nullptr, float radius = 10.0f);
// ============================================================================
// Responsive Layout Helpers
// ============================================================================
// Get responsive width based on available space
float GetResponsiveWidth(float min_width, float max_width, float ratio = 0.5f);
// Auto-fit table columns
void SetupResponsiveColumns(int count, float min_col_width = 100.0f);
// Responsive two-column layout
void BeginTwoColumns(const char* id, float split_ratio = 0.6f);
void SwitchColumn();
void EndTwoColumns();
// ============================================================================
// Input Helpers (complement existing gui::InputHex functions)
// ============================================================================
// Labeled hex input with automatic formatting
bool LabeledInputHex(const char* label, uint8_t* value);
bool LabeledInputHex(const char* label, uint16_t* value);
// Combo with icon
bool IconCombo(const char* icon, const char* label, int* current,
const char* const items[], int count);
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_UI_HELPERS_H