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:
318
src/app/gui/core/color.cc
Normal file
318
src/app/gui/core/color.cc
Normal 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*)¤t_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
68
src/app/gui/core/color.h
Normal 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
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
516
src/app/gui/core/input.cc
Normal 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
87
src/app/gui/core/input.h
Normal 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
|
||||
285
src/app/gui/core/layout_helpers.cc
Normal file
285
src/app/gui/core/layout_helpers.cc
Normal 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
|
||||
86
src/app/gui/core/layout_helpers.h
Normal file
86
src/app/gui/core/layout_helpers.h
Normal 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
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
205
src/app/gui/core/style.h
Normal 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 ¤t_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
|
||||
2088
src/app/gui/core/theme_manager.cc
Normal file
2088
src/app/gui/core/theme_manager.cc
Normal file
File diff suppressed because it is too large
Load Diff
241
src/app/gui/core/theme_manager.h
Normal file
241
src/app/gui/core/theme_manager.h
Normal 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
|
||||
533
src/app/gui/core/ui_helpers.cc
Normal file
533
src/app/gui/core/ui_helpers.cc
Normal 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
|
||||
182
src/app/gui/core/ui_helpers.h
Normal file
182
src/app/gui/core/ui_helpers.h
Normal 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
|
||||
Reference in New Issue
Block a user