refactor: Rename CompactToolbar to Toolset and Enhance Toolbar Functionality

- Renamed CompactToolbar class to Toolset for improved clarity and consistency in naming.
- Introduced WidgetMeasurement for tracking widget dimensions, enabling debugging and test automation.
- Updated toolbar methods to include measurement functionality, logging overflow warnings, and improved layout handling.
- Removed the deprecated SettingsBar and Toolset classes to streamline the codebase.
- Adjusted CMake configuration to reflect the new file structure and included widget measurement source files.
This commit is contained in:
scawful
2025-10-05 17:19:02 -04:00
parent ea9409f767
commit 6daf0adf84
9 changed files with 329 additions and 614 deletions

View File

@@ -6,6 +6,7 @@
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/ui_helpers.h"
#include "app/gui/widget_measurement.h"
#include "app/gui/widgets/widget_id_registry.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
@@ -14,35 +15,60 @@ namespace yaze {
namespace gui {
// ============================================================================
// CompactToolbar Implementation
// Toolset Implementation
// ============================================================================
void CompactToolbar::Begin() {
void Toolset::Begin() {
// Ultra-compact toolbar with no padding waste
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 3));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 6));
ImGui::BeginGroup();
// Don't use BeginGroup - it causes stretching. Just use direct layout.
in_toolbar_ = true;
button_count_ = 0;
current_line_width_ = 0.0f;
// Begin measurement for debugging and test automation
WidgetMeasurement::Instance().BeginToolbarMeasurement("OverworldToolbar");
}
void CompactToolbar::End() {
ImGui::EndGroup();
void Toolset::End() {
// End the current line
ImGui::NewLine();
// End measurement and check for overflow
WidgetMeasurement::Instance().EndToolbarMeasurement();
float toolbar_width = WidgetMeasurement::Instance().GetToolbarWidth("OverworldToolbar");
float available_width = ImGui::GetContentRegionAvail().x;
// Log warning if toolbar overflows (for debugging)
if (toolbar_width > available_width && toolbar_width > 0) {
// Only log once per second to avoid spam
static float last_warning_time = 0.0f;
float current_time = ImGui::GetTime();
if (current_time - last_warning_time > 1.0f) {
ImGui::SetTooltip("⚠️ Toolbar overflow: %.0fpx / %.0fpx available",
toolbar_width, available_width);
last_warning_time = current_time;
}
}
ImGui::PopStyleVar(3);
ImGui::Separator();
in_toolbar_ = false;
current_line_width_ = 0.0f;
}
void CompactToolbar::BeginModeGroup() {
void Toolset::BeginModeGroup() {
// Visual grouping with subtle background - taller for better button visibility
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.17f, 0.5f));
ImGui::BeginChild("##ModeGroup", ImVec2(0, 40), true,
ImGuiWindowFlags_NoScrollbar);
}
bool CompactToolbar::ModeButton(const char* icon, bool selected, const char* tooltip) {
bool Toolset::ModeButton(const char* icon, bool selected, const char* tooltip) {
if (selected) {
ImGui::PushStyleColor(ImGuiCol_Button, GetAccentColor());
}
@@ -53,6 +79,10 @@ bool CompactToolbar::ModeButton(const char* icon, bool selected, const char* too
ImGui::PopStyleColor();
}
// Measure this widget
std::string widget_id = tooltip ? tooltip : absl::StrFormat("Button_%d", button_count_);
WidgetMeasurement::Instance().MeasureLastItem(widget_id, "mode_button");
// Register for test automation
if (ImGui::GetItemID() != 0 && tooltip) {
std::string button_path = absl::StrFormat("ModeButton:%s", tooltip);
@@ -70,25 +100,26 @@ bool CompactToolbar::ModeButton(const char* icon, bool selected, const char* too
return clicked;
}
void CompactToolbar::EndModeGroup() {
void Toolset::EndModeGroup() {
ImGui::EndChild();
ImGui::PopStyleColor();
ImGui::SameLine();
AddSeparator();
}
void CompactToolbar::AddSeparator() {
ImGui::TextDisabled("|");
void Toolset::AddSeparator() {
// Use a proper separator that doesn't stretch
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
}
void CompactToolbar::AddRomBadge(uint8_t version, std::function<void()> on_upgrade) {
void Toolset::AddRomBadge(uint8_t version, std::function<void()> on_upgrade) {
RomVersionBadge(version == 0xFF ? "Vanilla" :
absl::StrFormat("v%d", version).c_str(),
version == 0xFF);
if (on_upgrade && (version == 0xFF || version < 3)) {
ImGui::SameLine();
ImGui::SameLine(0, 2); // Tighter spacing
if (ImGui::SmallButton(ICON_MD_UPGRADE)) {
on_upgrade();
}
@@ -98,12 +129,11 @@ void CompactToolbar::AddRomBadge(uint8_t version, std::function<void()> on_upgra
}
ImGui::SameLine();
AddSeparator();
}
bool CompactToolbar::AddProperty(const char* icon, const char* label,
uint8_t* value,
std::function<void()> on_change) {
bool Toolset::AddProperty(const char* icon, const char* label,
uint8_t* value,
std::function<void()> on_change) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(55);
@@ -117,9 +147,9 @@ bool CompactToolbar::AddProperty(const char* icon, const char* label,
return changed;
}
bool CompactToolbar::AddProperty(const char* icon, const char* label,
uint16_t* value,
std::function<void()> on_change) {
bool Toolset::AddProperty(const char* icon, const char* label,
uint16_t* value,
std::function<void()> on_change) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(70);
@@ -133,25 +163,29 @@ bool CompactToolbar::AddProperty(const char* icon, const char* label,
return changed;
}
bool CompactToolbar::AddCombo(const char* icon, int* current,
const char* const items[], int count) {
bool Toolset::AddCombo(const char* icon, int* current,
const char* const items[], int count) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(110);
WidgetMeasurement::Instance().MeasureLastItem("combo_icon", "text");
ImGui::SameLine(0, 2); // Reduce spacing between icon and combo
ImGui::SetNextItemWidth(100); // Slightly narrower for better fit
bool changed = ImGui::Combo("##combo", current, items, count);
WidgetMeasurement::Instance().MeasureLastItem("combo_selector", "combo");
ImGui::SameLine();
return changed;
}
bool CompactToolbar::AddToggle(const char* icon, bool* state, const char* tooltip) {
bool Toolset::AddToggle(const char* icon, bool* state, const char* tooltip) {
bool result = ToggleIconButton(icon, icon, state, tooltip);
ImGui::SameLine();
return result;
}
bool CompactToolbar::AddAction(const char* icon, const char* tooltip) {
bool Toolset::AddAction(const char* icon, const char* tooltip) {
bool clicked = ImGui::SmallButton(icon);
// Register for test automation
@@ -169,7 +203,7 @@ bool CompactToolbar::AddAction(const char* icon, const char* tooltip) {
return clicked;
}
bool CompactToolbar::BeginCollapsibleSection(const char* label, bool* p_open) {
bool Toolset::BeginCollapsibleSection(const char* label, bool* p_open) {
ImGui::NewLine(); // Start on new line
bool is_open = ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_None);
if (p_open) *p_open = is_open;
@@ -177,11 +211,11 @@ bool CompactToolbar::BeginCollapsibleSection(const char* label, bool* p_open) {
return is_open;
}
void CompactToolbar::EndCollapsibleSection() {
void Toolset::EndCollapsibleSection() {
in_section_ = false;
}
void CompactToolbar::AddV3StatusBadge(uint8_t version, std::function<void()> on_settings) {
void Toolset::AddV3StatusBadge(uint8_t version, std::function<void()> on_settings) {
if (version >= 3 && version != 0xFF) {
StatusBadge("v3 Active", ButtonType::Success);
ImGui::SameLine();
@@ -198,7 +232,7 @@ void CompactToolbar::AddV3StatusBadge(uint8_t version, std::function<void()> on_
ImGui::SameLine();
}
bool CompactToolbar::AddUsageStatsButton(const char* tooltip) {
bool Toolset::AddUsageStatsButton(const char* tooltip) {
bool clicked = ImGui::SmallButton(ICON_MD_ANALYTICS " Usage");
if (tooltip && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", tooltip);

View File

@@ -11,7 +11,7 @@ namespace yaze {
namespace gui {
/**
* @class CompactToolbar
* @class Toolset
* @brief Ultra-compact toolbar that merges mode buttons with settings
*
* Design Philosophy:
@@ -23,9 +23,9 @@ namespace gui {
*
* Layout: [Mode Icons] | [ROM Badge] [World] [GFX] [Pal] [Spr] ... | [Quick Actions]
*/
class CompactToolbar {
class Toolset {
public:
CompactToolbar() = default;
Toolset() = default;
// Begin the toolbar
void Begin();
@@ -76,6 +76,7 @@ class CompactToolbar {
bool in_toolbar_ = false;
bool in_section_ = false;
int button_count_ = 0;
float current_line_width_ = 0.0f;
};
/**
@@ -162,7 +163,7 @@ class EditorLayout {
void End();
// Get toolbar reference
CompactToolbar& GetToolbar() { return toolbar_; }
Toolset& GetToolbar() { return toolbar_; }
// Begin main canvas area
void BeginMainCanvas();
@@ -172,7 +173,7 @@ class EditorLayout {
void RegisterCard(EditorCard* card);
private:
CompactToolbar toolbar_;
Toolset toolbar_;
std::vector<EditorCard*> cards_;
bool in_layout_ = false;
};

View File

@@ -16,9 +16,8 @@ set(
app/gui/widgets/widget_auto_register.cc
app/gui/widgets/widget_state_capture.cc
app/gui/ui_helpers.cc
app/gui/toolset.cc
app/gui/settings_bar.cc
app/gui/editor_layout.cc
app/gui/widget_measurement.cc
# Canvas system components
app/gui/canvas/canvas_modals.cc
app/gui/canvas/canvas_context_menu.cc

View File

@@ -1,157 +0,0 @@
#include "app/gui/settings_bar.h"
#include "absl/strings/str_format.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/ui_helpers.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
void SettingsBar::SetRomVersion(uint8_t version) {
rom_version_ = version;
}
void SettingsBar::BeginDraw() {
// Compact, modern settings bar
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12, 6));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 4));
// Single row layout using columns for alignment
ImGui::BeginGroup();
in_settings_bar_ = true;
current_column_ = 0;
}
void SettingsBar::EndDraw() {
ImGui::EndGroup();
ImGui::PopStyleVar(2);
ImGui::Separator();
in_settings_bar_ = false;
}
void SettingsBar::AddVersionBadge(std::function<void()> on_upgrade) {
if (IsVanilla()) {
RomVersionBadge("Vanilla ROM", true);
if (on_upgrade) {
ImGui::SameLine();
if (IconButton(ICON_MD_UPGRADE, "Upgrade to v3", ImVec2(0, 0))) {
on_upgrade();
}
}
} else {
std::string version_text = absl::StrFormat("v%d", rom_version_);
RomVersionBadge(version_text.c_str(), false);
if (rom_version_ < 3 && on_upgrade) {
ImGui::SameLine();
if (IconButton(ICON_MD_UPGRADE, "Upgrade to v3", ImVec2(0, 0))) {
on_upgrade();
}
}
}
ImGui::SameLine();
AddSeparator();
}
void SettingsBar::AddWorldSelector(int* current_world) {
ImGui::Text(ICON_MD_PUBLIC);
ImGui::SameLine();
ImGui::SetNextItemWidth(120);
const char* worlds[] = {"Light World", "Dark World", "Extra World"};
ImGui::Combo("##world", current_world, worlds, 3);
ImGui::SameLine();
}
void SettingsBar::AddHexByteInput(const char* icon, const char* label,
uint8_t* value,
std::function<void()> on_change) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(60);
if (InputHexByte(label, value)) {
if (on_change) on_change();
}
ImGui::SameLine();
}
void SettingsBar::AddHexWordInput(const char* icon, const char* label,
uint16_t* value,
std::function<void()> on_change) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(80);
if (InputHexWord(label, value)) {
if (on_change) on_change();
}
ImGui::SameLine();
}
void SettingsBar::AddCheckbox(const char* icon, const char* label, bool* value,
std::function<void()> on_change) {
ImGui::Text("%s", icon);
ImGui::SameLine();
if (ImGui::Checkbox(label, value)) {
if (on_change) on_change();
}
ImGui::SameLine();
}
void SettingsBar::AddCombo(const char* icon, const char* label, int* current,
const char* const items[], int count,
std::function<void()> on_change) {
ImGui::Text("%s", icon);
ImGui::SameLine();
ImGui::SetNextItemWidth(120);
if (ImGui::Combo(label, current, items, count)) {
if (on_change) on_change();
}
ImGui::SameLine();
}
void SettingsBar::AddAreaSizeSelector(int* area_size,
std::function<void()> on_change) {
if (!IsV3()) return;
const char* sizes[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"};
ImGui::Text(ICON_MD_ASPECT_RATIO);
ImGui::SameLine();
ImGui::SetNextItemWidth(110);
if (ImGui::Combo("##areasize", area_size, sizes, 4)) {
if (on_change) on_change();
}
ImGui::SameLine();
}
void SettingsBar::AddButton(const char* icon, const char* label,
std::function<void()> callback) {
if (IconButton(icon, label)) {
if (callback) callback();
}
ImGui::SameLine();
}
void SettingsBar::AddSeparator() {
ImGui::TextDisabled("|");
ImGui::SameLine();
}
} // namespace gui
} // namespace yaze

View File

@@ -1,111 +0,0 @@
#ifndef YAZE_APP_GUI_SETTINGS_BAR_H
#define YAZE_APP_GUI_SETTINGS_BAR_H
#include <functional>
#include <string>
#include <vector>
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @class SettingsBar
* @brief Modern, compact settings bar for editor properties
*
* A horizontal bar that consolidates mode selection, property editing,
* and quick actions into a single, space-efficient component.
*
* Design Philosophy:
* - Inline property editing with InputHex widgets
* - Compact layout saves vertical space
* - Visual ROM version indicator
* - Theme-aware styling
* - Responsive to different ROM versions
*
* Features:
* - World selector
* - Map properties (GFX, Palette, Sprites, Message ID)
* - Version-specific properties (v2: Main Palette, v3: Animated GFX, Area Size)
* - Visual ROM version badge
* - Upgrade prompts for vanilla ROMs
* - Mosaic effect toggle
*
* Usage:
* ```cpp
* SettingsBar settings;
* settings.SetRomVersion(asm_version);
* settings.BeginDraw();
*
* settings.AddWorldSelector(&current_world);
* settings.AddHexInput("Graphics", &map->area_graphics);
* settings.AddHexInput("Palette", &map->area_palette);
*
* if (settings.IsV3()) {
* settings.AddAreaSizeSelector(&map->area_size);
* }
*
* settings.EndDraw();
* ```
*/
class SettingsBar {
public:
SettingsBar() = default;
// Set ROM version to enable version-specific features
void SetRomVersion(uint8_t version);
// Check ROM version
bool IsVanilla() const { return rom_version_ == 0xFF; }
bool IsV2() const { return rom_version_ >= 2 && rom_version_ != 0xFF; }
bool IsV3() const { return rom_version_ >= 3 && rom_version_ != 0xFF; }
// Begin drawing the settings bar
void BeginDraw();
// End drawing the settings bar
void EndDraw();
// Add ROM version badge
void AddVersionBadge(std::function<void()> on_upgrade = nullptr);
// Add world selector dropdown
void AddWorldSelector(int* current_world);
// Add hex input for a property
void AddHexByteInput(const char* icon, const char* label, uint8_t* value,
std::function<void()> on_change = nullptr);
void AddHexWordInput(const char* icon, const char* label, uint16_t* value,
std::function<void()> on_change = nullptr);
// Add checkbox
void AddCheckbox(const char* icon, const char* label, bool* value,
std::function<void()> on_change = nullptr);
// Add combo box
void AddCombo(const char* icon, const char* label, int* current,
const char* const items[], int count,
std::function<void()> on_change = nullptr);
// Add area size selector (v3+ only)
void AddAreaSizeSelector(int* area_size,
std::function<void()> on_change = nullptr);
// Add custom button
void AddButton(const char* icon, const char* label,
std::function<void()> callback);
// Add spacing
void AddSeparator();
private:
uint8_t rom_version_ = 0xFF;
int current_column_ = 0;
bool in_settings_bar_ = false;
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_SETTINGS_BAR_H

View File

@@ -1,199 +0,0 @@
#include "app/gui/toolset.h"
#include "absl/strings/str_format.h"
#include "app/gui/icons.h"
#include "app/gui/ui_helpers.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
void Toolset::AddTool(const std::string& id, const char* icon,
const char* shortcut, std::function<void()> callback,
const char* tooltip) {
Tool tool;
tool.id = id;
tool.icon = icon;
tool.shortcut = shortcut;
tool.callback = callback;
if (tooltip) {
tool.tooltip = std::string(icon) + " " + tooltip;
if (shortcut && strlen(shortcut) > 0) {
tool.tooltip += " (" + std::string(shortcut) + ")";
}
} else {
tool.tooltip = id;
if (shortcut && strlen(shortcut) > 0) {
tool.tooltip += " (" + std::string(shortcut) + ")";
}
}
tools_.push_back(tool);
}
void Toolset::AddSeparator() {
if (!tools_.empty()) {
tools_.back().separator_after = true;
}
}
void Toolset::SetSelected(const std::string& id) {
selected_ = id;
}
bool Toolset::Draw() {
bool clicked = false;
// Calculate columns based on mode
int columns = max_columns_;
if (columns == 0) {
// Auto-fit to available width
float button_width = compact_mode_ ? 32.0f : 80.0f;
columns = static_cast<int>(ImGui::GetContentRegionAvail().x / button_width);
if (columns < 1) columns = 1;
if (columns > static_cast<int>(tools_.size()))
columns = static_cast<int>(tools_.size());
}
// Modern toolset with reduced padding
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(2, 2));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
if (ImGui::BeginTable("##Toolset", columns,
ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_BordersInnerV)) {
for (const auto& tool : tools_) {
if (!tool.enabled) continue;
ImGui::TableNextColumn();
bool is_selected = (tool.id == selected_);
DrawTool(tool, is_selected);
if (tool.separator_after) {
ImGui::TableNextColumn();
ImGui::Dummy(ImVec2(1, 1)); // Small separator space
}
}
ImGui::EndTable();
}
ImGui::PopStyleVar(2);
return clicked;
}
void Toolset::DrawTool(const Tool& tool, bool is_selected) {
ImVec2 button_size = compact_mode_ ? ImVec2(28, 28) : ImVec2(0, 0);
if (is_selected) {
ImGui::PushStyleColor(ImGuiCol_Button, GetAccentColor());
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(GetAccentColor().x * 1.1f, GetAccentColor().y * 1.1f,
GetAccentColor().z * 1.1f, GetAccentColor().w));
}
bool clicked = false;
if (compact_mode_) {
clicked = ImGui::Button(tool.icon.c_str(), button_size);
} else {
std::string label = tool.icon + " " + tool.id;
clicked = ImGui::Button(label.c_str(), button_size);
}
if (is_selected) {
ImGui::PopStyleColor(2);
}
if (clicked && tool.callback) {
tool.callback();
selected_ = tool.id;
}
if (ImGui::IsItemHovered() && !tool.tooltip.empty()) {
ImGui::SetTooltip("%s", tool.tooltip.c_str());
}
}
void Toolset::Clear() {
tools_.clear();
selected_.clear();
}
// ============================================================================
// Editor Toolset Factories
// ============================================================================
namespace EditorToolset {
Toolset CreateOverworldToolset() {
Toolset toolset;
toolset.SetCompactMode(true);
// Editing tools
toolset.AddTool("Pan", ICON_MD_PAN_TOOL_ALT, "1", nullptr,
"Pan - Middle click and drag");
toolset.AddTool("Draw", ICON_MD_DRAW, "2", nullptr,
"Draw Tile");
toolset.AddTool("Entrances", ICON_MD_DOOR_FRONT, "3", nullptr,
"Entrances");
toolset.AddTool("Exits", ICON_MD_DOOR_BACK, "4", nullptr,
"Exits");
toolset.AddTool("Items", ICON_MD_GRASS, "5", nullptr,
"Items");
toolset.AddTool("Sprites", ICON_MD_PEST_CONTROL_RODENT, "6", nullptr,
"Sprites");
toolset.AddTool("Transports", ICON_MD_ADD_LOCATION, "7", nullptr,
"Transports");
toolset.AddTool("Music", ICON_MD_MUSIC_NOTE, "8", nullptr,
"Music");
toolset.AddSeparator();
// View controls
toolset.AddTool("ZoomOut", ICON_MD_ZOOM_OUT, "", nullptr,
"Zoom Out");
toolset.AddTool("ZoomIn", ICON_MD_ZOOM_IN, "", nullptr,
"Zoom In");
toolset.AddTool("Fullscreen", ICON_MD_OPEN_IN_FULL, "F11", nullptr,
"Fullscreen Canvas");
toolset.AddSeparator();
// Quick access
toolset.AddTool("Tile16", ICON_MD_GRID_VIEW, "Ctrl+T", nullptr,
"Tile16 Editor");
toolset.AddTool("Copy", ICON_MD_CONTENT_COPY, "", nullptr,
"Copy Map");
return toolset;
}
Toolset CreateDungeonToolset() {
Toolset toolset;
toolset.SetCompactMode(true);
toolset.AddTool("Pan", ICON_MD_PAN_TOOL_ALT, "1", nullptr,
"Pan");
toolset.AddTool("Draw", ICON_MD_DRAW, "2", nullptr,
"Draw Object");
toolset.AddTool("Select", ICON_MD_SELECT_ALL, "3", nullptr,
"Select");
toolset.AddSeparator();
toolset.AddTool("ZoomOut", ICON_MD_ZOOM_OUT, "", nullptr,
"Zoom Out");
toolset.AddTool("ZoomIn", ICON_MD_ZOOM_IN, "", nullptr,
"Zoom In");
return toolset;
}
} // namespace EditorToolset
} // namespace gui
} // namespace yaze

View File

@@ -1,111 +0,0 @@
#ifndef YAZE_APP_GUI_TOOLSET_H
#define YAZE_APP_GUI_TOOLSET_H
#include <functional>
#include <string>
#include <vector>
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @class Toolset
* @brief Modern, space-efficient toolset for editor mode switching
*
* A toolset is a horizontal bar of icon buttons for switching between
* editor modes (Pan, Draw, Select, etc.). This provides a consistent,
* beautiful interface across all editors.
*
* Features:
* - Compact, icon-based design
* - Visual selection indicator
* - Keyboard shortcut hints
* - Automatic tooltips
* - Theme-aware styling
* - Separators for logical grouping
*
* Usage:
* ```cpp
* Toolset toolset;
* toolset.AddTool("Pan", ICON_MD_PAN_TOOL, "1", []() { mode = PAN; });
* toolset.AddTool("Draw", ICON_MD_DRAW, "2", []() { mode = DRAW; });
* toolset.AddSeparator();
* toolset.AddTool("Zoom+", ICON_MD_ZOOM_IN, "", []() { ZoomIn(); });
*
* if (toolset.Draw()) {
* // A tool was clicked
* }
* ```
*/
class Toolset {
public:
struct Tool {
std::string id;
std::string icon;
std::string tooltip;
std::string shortcut;
std::function<void()> callback;
bool enabled = true;
bool separator_after = false;
};
Toolset() = default;
// Add a tool to the toolset
void AddTool(const std::string& id, const char* icon,
const char* shortcut, std::function<void()> callback,
const char* tooltip = nullptr);
// Add a separator for visual grouping
void AddSeparator();
// Set the currently selected tool
void SetSelected(const std::string& id);
// Get the currently selected tool ID
const std::string& GetSelected() const { return selected_; }
// Draw the toolset (returns true if a tool was clicked)
bool Draw();
// Compact mode (smaller buttons, no labels)
void SetCompactMode(bool compact) { compact_mode_ = compact; }
// Set the number of columns (0 = auto-fit to window width)
void SetMaxColumns(int columns) { max_columns_ = columns; }
// Clear all tools
void Clear();
private:
void DrawTool(const Tool& tool, bool is_selected);
std::vector<Tool> tools_;
std::string selected_;
bool compact_mode_ = true;
int max_columns_ = 0; // 0 = auto-fit
};
/**
* @namespace EditorToolset
* @brief Helper functions for creating common editor toolsets
*/
namespace EditorToolset {
// Create standard editing mode toolset
Toolset CreateStandardToolset();
// Create overworld-specific toolset
Toolset CreateOverworldToolset();
// Create dungeon-specific toolset
Toolset CreateDungeonToolset();
} // namespace EditorToolset
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_TOOLSET_H

View File

@@ -0,0 +1,140 @@
#include "app/gui/widget_measurement.h"
#include "absl/strings/str_format.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
namespace yaze {
namespace gui {
WidgetMetrics WidgetMeasurement::MeasureLastItem(const std::string& widget_id,
const std::string& type) {
if (!enabled_) {
return WidgetMetrics{};
}
WidgetMetrics metrics;
metrics.widget_id = widget_id;
metrics.type = type;
// Get item rect (bounding box)
metrics.rect_min = ImGui::GetItemRectMin();
metrics.rect_max = ImGui::GetItemRectMax();
// Calculate size
metrics.size = ImVec2(metrics.rect_max.x - metrics.rect_min.x,
metrics.rect_max.y - metrics.rect_min.y);
// Store position
metrics.position = metrics.rect_min;
// Get content region
metrics.content_size = ImGui::GetContentRegionAvail();
// Get cursor position after item
metrics.cursor_pos_x = ImGui::GetCursorPosX();
// Store in current toolbar if active
if (!current_toolbar_id_.empty()) {
toolbar_metrics_[current_toolbar_id_].push_back(metrics);
}
// Store in frame metrics
frame_metrics_.push_back(metrics);
return metrics;
}
void WidgetMeasurement::BeginToolbarMeasurement(const std::string& toolbar_id) {
current_toolbar_id_ = toolbar_id;
current_toolbar_start_x_ = ImGui::GetCursorPosX();
current_toolbar_width_ = 0.0f;
// Clear previous measurements for this toolbar
toolbar_metrics_[toolbar_id].clear();
}
void WidgetMeasurement::EndToolbarMeasurement() {
if (current_toolbar_id_.empty()) return;
// Calculate total width from cursor movement
float end_x = ImGui::GetCursorPosX();
current_toolbar_width_ = end_x - current_toolbar_start_x_;
// Store the width
toolbar_widths_[current_toolbar_id_] = current_toolbar_width_;
// Reset state
current_toolbar_id_.clear();
current_toolbar_width_ = 0.0f;
current_toolbar_start_x_ = 0.0f;
}
float WidgetMeasurement::GetToolbarWidth(const std::string& toolbar_id) const {
auto it = toolbar_widths_.find(toolbar_id);
if (it != toolbar_widths_.end()) {
return it->second;
}
return 0.0f;
}
bool WidgetMeasurement::WouldToolbarOverflow(const std::string& toolbar_id,
float available_width) const {
float toolbar_width = GetToolbarWidth(toolbar_id);
return toolbar_width > available_width;
}
const std::vector<WidgetMetrics>& WidgetMeasurement::GetToolbarMetrics(
const std::string& toolbar_id) const {
static const std::vector<WidgetMetrics> empty;
auto it = toolbar_metrics_.find(toolbar_id);
if (it != toolbar_metrics_.end()) {
return it->second;
}
return empty;
}
void WidgetMeasurement::ClearFrame() {
frame_metrics_.clear();
// Don't clear toolbar metrics - they persist across frames for debugging
}
std::string WidgetMeasurement::ExportMetricsJSON() const {
std::string json = "{\n \"toolbars\": {\n";
bool first_toolbar = true;
for (const auto& [toolbar_id, metrics] : toolbar_metrics_) {
if (!first_toolbar) json += ",\n";
first_toolbar = false;
json += absl::StrFormat(" \"%s\": {\n", toolbar_id);
json += absl::StrFormat(" \"total_width\": %.1f,\n",
GetToolbarWidth(toolbar_id));
json += " \"widgets\": [\n";
bool first_widget = true;
for (const auto& metric : metrics) {
if (!first_widget) json += ",\n";
first_widget = false;
json += " {\n";
json += absl::StrFormat(" \"id\": \"%s\",\n", metric.widget_id);
json += absl::StrFormat(" \"type\": \"%s\",\n", metric.type);
json += absl::StrFormat(" \"width\": %.1f,\n", metric.size.x);
json += absl::StrFormat(" \"height\": %.1f,\n", metric.size.y);
json += absl::StrFormat(" \"x\": %.1f,\n", metric.position.x);
json += absl::StrFormat(" \"y\": %.1f\n", metric.position.y);
json += " }";
}
json += "\n ]\n";
json += " }";
}
json += "\n }\n}\n";
return json;
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,119 @@
#ifndef YAZE_APP_GUI_WIDGET_MEASUREMENT_H
#define YAZE_APP_GUI_WIDGET_MEASUREMENT_H
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/strings/str_format.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @class WidgetMeasurement
* @brief Tracks widget dimensions for debugging and test automation
*
* Integrates with ImGui Test Engine to provide accurate measurements
* of UI elements, helping prevent layout issues and enabling automated
* testing of widget sizes and positions.
*/
struct WidgetMetrics {
ImVec2 size; // Width and height
ImVec2 position; // Screen position
ImVec2 content_size; // Available content region
ImVec2 rect_min; // Bounding box min
ImVec2 rect_max; // Bounding box max
float cursor_pos_x; // Cursor X after rendering
std::string widget_id; // Widget identifier
std::string type; // Widget type (button, input, combo, etc.)
std::string ToString() const {
return absl::StrFormat(
"Widget '%s' (%s): size=(%.1f,%.1f) pos=(%.1f,%.1f) content=(%.1f,%.1f) cursor_x=%.1f",
widget_id, type, size.x, size.y, position.x, position.y,
content_size.x, content_size.y, cursor_pos_x);
}
};
class WidgetMeasurement {
public:
static WidgetMeasurement& Instance() {
static WidgetMeasurement instance;
return instance;
}
/**
* @brief Measure the last rendered ImGui item
* @param widget_id Unique identifier for this widget
* @param type Widget type (button, input, etc.)
* @return WidgetMetrics containing all measurements
*/
WidgetMetrics MeasureLastItem(const std::string& widget_id,
const std::string& type = "unknown");
/**
* @brief Begin measuring a toolbar section
*/
void BeginToolbarMeasurement(const std::string& toolbar_id);
/**
* @brief End measuring a toolbar section and store total width
*/
void EndToolbarMeasurement();
/**
* @brief Get total measured width of a toolbar
*/
float GetToolbarWidth(const std::string& toolbar_id) const;
/**
* @brief Check if toolbar would overflow given window width
*/
bool WouldToolbarOverflow(const std::string& toolbar_id,
float available_width) const;
/**
* @brief Get all measurements for a specific toolbar
*/
const std::vector<WidgetMetrics>& GetToolbarMetrics(
const std::string& toolbar_id) const;
/**
* @brief Clear all measurements (call once per frame)
*/
void ClearFrame();
/**
* @brief Export measurements for test automation
*/
std::string ExportMetricsJSON() const;
/**
* @brief Enable/disable measurement (performance option)
*/
void SetEnabled(bool enabled) { enabled_ = enabled; }
bool IsEnabled() const { return enabled_; }
private:
WidgetMeasurement() = default;
bool enabled_ = true;
std::string current_toolbar_id_;
float current_toolbar_width_ = 0.0f;
float current_toolbar_start_x_ = 0.0f;
// Store measurements per toolbar
std::unordered_map<std::string, std::vector<WidgetMetrics>> toolbar_metrics_;
std::unordered_map<std::string, float> toolbar_widths_;
// All measurements from current frame
std::vector<WidgetMetrics> frame_metrics_;
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_WIDGET_MEASUREMENT_H