refactor(canvas): unify context menu item handling with CanvasMenuItem

- Replaced the legacy ContextMenuItem structure with a new CanvasMenuItem across various files, enhancing consistency in context menu management.
- Introduced CanvasMenuBuilder for fluent menu construction, allowing for easier addition of items and submenus.
- Updated Canvas and related components to utilize the new menu system, improving organization and maintainability of context menus.

Benefits:
- Streamlines context menu item management, making it more intuitive and flexible.
- Enhances code readability and reduces duplication, facilitating future enhancements.
This commit is contained in:
scawful
2025-10-16 20:34:34 -04:00
parent 99b0a7e11f
commit d9e793eaea
10 changed files with 625 additions and 412 deletions

View File

@@ -218,158 +218,158 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
auto& layer_settings = GetRoomLayerSettings(room_id);
// Add object placement option
canvas_.AddContextMenuItem({
ICON_MD_ADD " Place Object",
[]() {
// TODO: Show object palette/selector
},
"Ctrl+P"
});
canvas_.AddContextMenuItem(
gui::CanvasMenuItem(ICON_MD_ADD " Place Object", ICON_MD_ADD,
[]() {
// TODO: Show object palette/selector
},
"Ctrl+P")
);
// Add object deletion for selected objects
canvas_.AddContextMenuItem({
ICON_MD_DELETE " Delete Selected",
[this]() {
object_interaction_.HandleDeleteSelected();
},
"Del"
});
canvas_.AddContextMenuItem(
gui::CanvasMenuItem(ICON_MD_DELETE " Delete Selected", ICON_MD_DELETE,
[this]() {
object_interaction_.HandleDeleteSelected();
},
"Del")
);
// Add room property quick toggles
canvas_.AddContextMenuItem({
ICON_MD_LAYERS " Toggle BG1",
[this, room_id]() {
auto& settings = GetRoomLayerSettings(room_id);
settings.bg1_visible = !settings.bg1_visible;
},
"1"
});
canvas_.AddContextMenuItem(
gui::CanvasMenuItem(ICON_MD_LAYERS " Toggle BG1", ICON_MD_LAYERS,
[this, room_id]() {
auto& settings = GetRoomLayerSettings(room_id);
settings.bg1_visible = !settings.bg1_visible;
},
"1")
);
canvas_.AddContextMenuItem({
ICON_MD_LAYERS " Toggle BG2",
[this, room_id]() {
auto& settings = GetRoomLayerSettings(room_id);
settings.bg2_visible = !settings.bg2_visible;
},
"2"
});
canvas_.AddContextMenuItem(
gui::CanvasMenuItem(ICON_MD_LAYERS " Toggle BG2", ICON_MD_LAYERS,
[this, room_id]() {
auto& settings = GetRoomLayerSettings(room_id);
settings.bg2_visible = !settings.bg2_visible;
},
"2")
);
// Add re-render option
canvas_.AddContextMenuItem({
ICON_MD_REFRESH " Re-render Room",
[&room]() {
room.RenderRoomGraphics();
},
"Ctrl+R"
});
canvas_.AddContextMenuItem(
gui::CanvasMenuItem(ICON_MD_REFRESH " Re-render Room", ICON_MD_REFRESH,
[&room]() {
room.RenderRoomGraphics();
},
"Ctrl+R")
);
// === DEBUG MENU ===
gui::Canvas::ContextMenuItem debug_menu;
gui::CanvasMenuItem debug_menu;
debug_menu.label = ICON_MD_BUG_REPORT " Debug";
// Show room info
debug_menu.subitems.push_back({
ICON_MD_INFO " Show Room Info",
[this, room_id]() {
show_room_debug_info_ = !show_room_debug_info_;
}
});
debug_menu.subitems.push_back(
gui::CanvasMenuItem(ICON_MD_INFO " Show Room Info", ICON_MD_INFO,
[this]() {
show_room_debug_info_ = !show_room_debug_info_;
})
);
// Show texture info
debug_menu.subitems.push_back({
ICON_MD_IMAGE " Show Texture Debug",
[this]() {
show_texture_debug_ = !show_texture_debug_;
}
});
debug_menu.subitems.push_back(
gui::CanvasMenuItem(ICON_MD_IMAGE " Show Texture Debug", ICON_MD_IMAGE,
[this]() {
show_texture_debug_ = !show_texture_debug_;
})
);
// Show object bounds with sub-menu for categories
gui::Canvas::ContextMenuItem object_bounds_menu;
gui::CanvasMenuItem object_bounds_menu;
object_bounds_menu.label = ICON_MD_CROP_SQUARE " Show Object Bounds";
object_bounds_menu.callback = [this]() {
show_object_bounds_ = !show_object_bounds_;
};
// Sub-menu for filtering by type
object_bounds_menu.subitems.push_back({
"Type 1 (0x00-0xFF)",
[this]() {
object_outline_toggles_.show_type1_objects = !object_outline_toggles_.show_type1_objects;
}
});
object_bounds_menu.subitems.push_back({
"Type 2 (0x100-0x1FF)",
[this]() {
object_outline_toggles_.show_type2_objects = !object_outline_toggles_.show_type2_objects;
}
});
object_bounds_menu.subitems.push_back({
"Type 3 (0xF00-0xFFF)",
[this]() {
object_outline_toggles_.show_type3_objects = !object_outline_toggles_.show_type3_objects;
}
});
object_bounds_menu.subitems.push_back(
gui::CanvasMenuItem("Type 1 (0x00-0xFF)",
[this]() {
object_outline_toggles_.show_type1_objects = !object_outline_toggles_.show_type1_objects;
})
);
object_bounds_menu.subitems.push_back(
gui::CanvasMenuItem("Type 2 (0x100-0x1FF)",
[this]() {
object_outline_toggles_.show_type2_objects = !object_outline_toggles_.show_type2_objects;
})
);
object_bounds_menu.subitems.push_back(
gui::CanvasMenuItem("Type 3 (0xF00-0xFFF)",
[this]() {
object_outline_toggles_.show_type3_objects = !object_outline_toggles_.show_type3_objects;
})
);
// Separator
gui::Canvas::ContextMenuItem sep;
gui::CanvasMenuItem sep;
sep.label = "---";
sep.enabled_condition = []() { return false; };
object_bounds_menu.subitems.push_back(sep);
// Sub-menu for filtering by layer
object_bounds_menu.subitems.push_back({
"Layer 0 (BG1)",
[this]() {
object_outline_toggles_.show_layer0_objects = !object_outline_toggles_.show_layer0_objects;
}
});
object_bounds_menu.subitems.push_back({
"Layer 1 (BG2)",
[this]() {
object_outline_toggles_.show_layer1_objects = !object_outline_toggles_.show_layer1_objects;
}
});
object_bounds_menu.subitems.push_back({
"Layer 2 (BG3)",
[this]() {
object_outline_toggles_.show_layer2_objects = !object_outline_toggles_.show_layer2_objects;
}
});
object_bounds_menu.subitems.push_back(
gui::CanvasMenuItem("Layer 0 (BG1)",
[this]() {
object_outline_toggles_.show_layer0_objects = !object_outline_toggles_.show_layer0_objects;
})
);
object_bounds_menu.subitems.push_back(
gui::CanvasMenuItem("Layer 1 (BG2)",
[this]() {
object_outline_toggles_.show_layer1_objects = !object_outline_toggles_.show_layer1_objects;
})
);
object_bounds_menu.subitems.push_back(
gui::CanvasMenuItem("Layer 2 (BG3)",
[this]() {
object_outline_toggles_.show_layer2_objects = !object_outline_toggles_.show_layer2_objects;
})
);
debug_menu.subitems.push_back(object_bounds_menu);
// Show layer info
debug_menu.subitems.push_back({
ICON_MD_LAYERS " Show Layer Info",
[this]() {
show_layer_info_ = !show_layer_info_;
}
});
debug_menu.subitems.push_back(
gui::CanvasMenuItem(ICON_MD_LAYERS " Show Layer Info", ICON_MD_LAYERS,
[this]() {
show_layer_info_ = !show_layer_info_;
})
);
// Force reload room
debug_menu.subitems.push_back({
ICON_MD_REFRESH " Force Reload",
[&room, room_id]() {
room.LoadObjects();
room.LoadRoomGraphics(room.blockset);
room.RenderRoomGraphics();
}
});
debug_menu.subitems.push_back(
gui::CanvasMenuItem(ICON_MD_REFRESH " Force Reload", ICON_MD_REFRESH,
[&room]() {
room.LoadObjects();
room.LoadRoomGraphics(room.blockset);
room.RenderRoomGraphics();
})
);
// Log room state
debug_menu.subitems.push_back({
ICON_MD_PRINT " Log Room State",
[&room, room_id]() {
LOG_DEBUG("DungeonDebug", "=== Room %03X Debug ===", room_id);
LOG_DEBUG("DungeonDebug", "Blockset: %d, Palette: %d, Layout: %d",
room.blockset, room.palette, room.layout);
LOG_DEBUG("DungeonDebug", "Objects: %zu, Sprites: %zu",
room.GetTileObjects().size(), room.GetSprites().size());
LOG_DEBUG("DungeonDebug", "BG1: %dx%d, BG2: %dx%d",
room.bg1_buffer().bitmap().width(), room.bg1_buffer().bitmap().height(),
room.bg2_buffer().bitmap().width(), room.bg2_buffer().bitmap().height());
}
});
debug_menu.subitems.push_back(
gui::CanvasMenuItem(ICON_MD_PRINT " Log Room State", ICON_MD_PRINT,
[&room, room_id]() {
LOG_DEBUG("DungeonDebug", "=== Room %03X Debug ===", room_id);
LOG_DEBUG("DungeonDebug", "Blockset: %d, Palette: %d, Layout: %d",
room.blockset, room.palette, room.layout);
LOG_DEBUG("DungeonDebug", "Objects: %zu, Sprites: %zu",
room.GetTileObjects().size(), room.GetSprites().size());
LOG_DEBUG("DungeonDebug", "BG1: %dx%d, BG2: %dx%d",
room.bg1_buffer().bitmap().width(), room.bg1_buffer().bitmap().height(),
room.bg2_buffer().bitmap().width(), room.bg2_buffer().bitmap().height());
})
);
canvas_.AddContextMenuItem(debug_menu);
}

View File

@@ -418,11 +418,11 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
// Add entity insertion submenu (only in MOUSE mode)
if (current_mode == 0 && entity_insert_callback_) { // 0 = EditingMode::MOUSE
gui::Canvas::ContextMenuItem entity_menu;
gui::CanvasMenuItem entity_menu;
entity_menu.label = ICON_MD_ADD_LOCATION " Insert Entity";
// Entrance submenu item
gui::Canvas::ContextMenuItem entrance_item;
gui::CanvasMenuItem entrance_item;
entrance_item.label = ICON_MD_DOOR_FRONT " Entrance";
entrance_item.callback = [this]() {
if (entity_insert_callback_) {
@@ -432,7 +432,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
entity_menu.subitems.push_back(entrance_item);
// Hole submenu item
gui::Canvas::ContextMenuItem hole_item;
gui::CanvasMenuItem hole_item;
hole_item.label = ICON_MD_CYCLONE " Hole";
hole_item.callback = [this]() {
if (entity_insert_callback_) {
@@ -442,7 +442,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
entity_menu.subitems.push_back(hole_item);
// Exit submenu item
gui::Canvas::ContextMenuItem exit_item;
gui::CanvasMenuItem exit_item;
exit_item.label = ICON_MD_DOOR_BACK " Exit";
exit_item.callback = [this]() {
if (entity_insert_callback_) {
@@ -452,7 +452,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
entity_menu.subitems.push_back(exit_item);
// Item submenu item
gui::Canvas::ContextMenuItem item_item;
gui::CanvasMenuItem item_item;
item_item.label = ICON_MD_GRASS " Item";
item_item.callback = [this]() {
if (entity_insert_callback_) {
@@ -462,7 +462,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
entity_menu.subitems.push_back(item_item);
// Sprite submenu item
gui::Canvas::ContextMenuItem sprite_item;
gui::CanvasMenuItem sprite_item;
sprite_item.label = ICON_MD_PEST_CONTROL_RODENT " Sprite";
sprite_item.callback = [this]() {
if (entity_insert_callback_) {
@@ -475,7 +475,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
}
// Add overworld-specific context menu items
gui::Canvas::ContextMenuItem lock_item;
gui::CanvasMenuItem lock_item;
lock_item.label = current_map_lock ? "Unlock Map" : "Lock to This Map";
lock_item.callback = [&current_map_lock]() {
current_map_lock = !current_map_lock;
@@ -483,7 +483,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
canvas.AddContextMenuItem(lock_item);
// Area Configuration
gui::Canvas::ContextMenuItem properties_item;
gui::CanvasMenuItem properties_item;
properties_item.label = ICON_MD_TUNE " Area Configuration";
properties_item.callback = [&show_map_properties_panel]() {
show_map_properties_panel = true;
@@ -495,7 +495,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
(*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version >= 3 && asm_version != 0xFF) {
// Custom Background Color
gui::Canvas::ContextMenuItem bg_color_item;
gui::CanvasMenuItem bg_color_item;
bg_color_item.label = ICON_MD_FORMAT_COLOR_FILL " Custom Background Color";
bg_color_item.callback = [&show_custom_bg_color_editor]() {
show_custom_bg_color_editor = true;
@@ -503,7 +503,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
canvas.AddContextMenuItem(bg_color_item);
// Visual Effects Editor
gui::Canvas::ContextMenuItem overlay_item;
gui::CanvasMenuItem overlay_item;
overlay_item.label = ICON_MD_LAYERS " Visual Effects";
overlay_item.callback = [&show_overlay_editor]() {
show_overlay_editor = true;
@@ -512,7 +512,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
}
// Canvas controls
gui::Canvas::ContextMenuItem reset_view_item;
gui::CanvasMenuItem reset_view_item;
reset_view_item.label = ICON_MD_RESTORE " Reset View";
reset_view_item.callback = [&canvas]() {
canvas.set_global_scale(1.0f);
@@ -520,7 +520,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
};
canvas.AddContextMenuItem(reset_view_item);
gui::Canvas::ContextMenuItem zoom_in_item;
gui::CanvasMenuItem zoom_in_item;
zoom_in_item.label = ICON_MD_ZOOM_IN " Zoom In";
zoom_in_item.callback = [&canvas]() {
float scale = std::min(2.0f, canvas.global_scale() + 0.25f);
@@ -528,7 +528,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
};
canvas.AddContextMenuItem(zoom_in_item);
gui::Canvas::ContextMenuItem zoom_out_item;
gui::CanvasMenuItem zoom_out_item;
zoom_out_item.label = ICON_MD_ZOOM_OUT " Zoom Out";
zoom_out_item.callback = [&canvas]() {
float scale = std::max(0.25f, canvas.global_scale() - 0.25f);

View File

@@ -544,18 +544,18 @@ void Canvas::DrawContextMenu() {
break;
}
},
snapshot);
snapshot, this); // Phase 4: Pass Canvas* for editor menu integration
if (modals_) {
modals_->Render();
}
// CRITICAL: Render custom context menu items AFTER enhanced menu
// Don't return early - we need to show custom items too!
if (!context_menu_items_.empty() && ImGui::BeginPopupContextItem(context_id_.c_str())) {
for (const auto& item : context_menu_items_) {
DrawContextMenuItem(item);
}
// Phase 4: Render editor menu items using declarative menu system
if (!editor_menu_.sections.empty() && ImGui::BeginPopupContextItem(context_id_.c_str())) {
auto popup_callback = [this](const std::string& id, std::function<void()> callback) {
popup_registry_.Open(id, callback);
};
gui::RenderCanvasMenu(editor_menu_, popup_callback);
ImGui::EndPopup();
}
@@ -569,105 +569,56 @@ void Canvas::DrawContextMenu() {
ShowScalingControls();
}
void Canvas::DrawContextMenuItem(const ContextMenuItem& item) {
if (!item.enabled_condition()) {
ImGui::BeginDisabled();
}
if (item.subitems.empty()) {
// Simple menu item
if (ImGui::MenuItem(item.label.c_str(), item.shortcut.empty()
? nullptr
: item.shortcut.c_str())) {
item.callback();
}
} else {
// Menu with subitems
if (ImGui::BeginMenu(item.label.c_str())) {
for (const auto& subitem : item.subitems) {
DrawContextMenuItem(subitem);
}
ImGui::EndMenu();
}
}
if (!item.enabled_condition()) {
ImGui::EndDisabled();
}
void Canvas::DrawContextMenuItem(const gui::CanvasMenuItem& item) {
// Phase 4: Use RenderMenuItem from canvas_menu.h for consistent rendering
auto popup_callback = [this](const std::string& id, std::function<void()> callback) {
popup_registry_.Open(id, callback);
};
gui::RenderMenuItem(item, popup_callback);
}
void Canvas::AddContextMenuItem(const ContextMenuItem& item) {
context_menu_items_.push_back(item);
void Canvas::AddContextMenuItem(const gui::CanvasMenuItem& item) {
// Phase 4: Add to editor menu definition
// Items are added to a default section with editor-specific priority
if (editor_menu_.sections.empty()) {
CanvasMenuSection section;
section.priority = MenuSectionPriority::kEditorSpecific;
section.separator_after = true;
editor_menu_.sections.push_back(section);
}
// Add to the last section (or create new if the last isn't editor-specific)
auto& last_section = editor_menu_.sections.back();
if (last_section.priority != MenuSectionPriority::kEditorSpecific) {
CanvasMenuSection new_section;
new_section.priority = MenuSectionPriority::kEditorSpecific;
new_section.separator_after = true;
editor_menu_.sections.push_back(new_section);
editor_menu_.sections.back().items.push_back(item);
} else {
last_section.items.push_back(item);
}
}
void Canvas::ClearContextMenuItems() {
context_menu_items_.clear();
editor_menu_.sections.clear();
}
void Canvas::OpenPersistentPopup(const std::string& popup_id, std::function<void()> render_callback) {
// Phase 3: Delegate to new popup registry
void Canvas::OpenPersistentPopup(const std::string& popup_id,
std::function<void()> render_callback) {
// Phase 4: Simplified popup management (no legacy synchronization)
popup_registry_.Open(popup_id, render_callback);
// Maintain backward compatibility with legacy active_popups_ vector
// TODO(Phase 4): Remove this synchronization once all editors migrate
bool found = false;
for (auto& popup : active_popups_) {
if (popup.popup_id == popup_id) {
popup.is_open = true;
popup.render_callback = render_callback;
found = true;
break;
}
}
if (!found) {
PopupState new_popup;
new_popup.popup_id = popup_id;
new_popup.is_open = true;
new_popup.render_callback = render_callback;
active_popups_.push_back(new_popup);
}
}
void Canvas::ClosePersistentPopup(const std::string& popup_id) {
// Phase 3: Delegate to new popup registry
// Phase 4: Simplified popup management (no legacy synchronization)
popup_registry_.Close(popup_id);
// Maintain backward compatibility with legacy active_popups_ vector
// TODO(Phase 4): Remove this synchronization once all editors migrate
for (auto& popup : active_popups_) {
if (popup.popup_id == popup_id) {
popup.is_open = false;
return;
}
}
}
void Canvas::RenderPersistentPopups() {
// Phase 3: Delegate to new popup registry
// Phase 4: Simplified rendering (no legacy synchronization)
popup_registry_.RenderAll();
// Maintain backward compatibility: sync legacy vector with registry state
// TODO(Phase 4): Remove this synchronization once all editors migrate
const auto& registry_popups = popup_registry_.GetPopups();
// Remove closed popups from legacy vector
auto it = active_popups_.begin();
while (it != active_popups_.end()) {
bool found_in_registry = false;
for (const auto& reg_popup : registry_popups) {
if (reg_popup.popup_id == it->popup_id && reg_popup.is_open) {
found_in_registry = true;
break;
}
}
if (!found_in_registry) {
it = active_popups_.erase(it);
} else {
++it;
}
}
}
void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {

View File

@@ -153,38 +153,8 @@ class Canvas {
// This routine also handles the scrolling for the canvas.
void DrawContextMenu();
// Context menu system for consumers to add their own menu elements
struct ContextMenuItem {
std::string label;
std::string shortcut;
std::function<void()> callback;
std::function<bool()> enabled_condition = []() { return true; };
std::vector<ContextMenuItem> subitems;
// Helper constructor for simple items
ContextMenuItem() = default;
ContextMenuItem(const std::string& lbl, std::function<void()> cb,
const std::string& sc = "")
: label(lbl), shortcut(sc), callback(std::move(cb)) {}
// Helper to create disabled item
static ContextMenuItem Disabled(const std::string& lbl) {
ContextMenuItem item;
item.label = lbl;
item.enabled_condition = []() { return false; };
return item;
}
// Helper to create conditional item
static ContextMenuItem Conditional(const std::string& lbl, std::function<void()> cb,
std::function<bool()> condition) {
ContextMenuItem item;
item.label = lbl;
item.callback = std::move(cb);
item.enabled_condition = std::move(condition);
return item;
}
};
// Phase 4: Use unified menu item definition from canvas_menu.h
using CanvasMenuItem = gui::CanvasMenuItem;
// BPP format UI components
std::unique_ptr<gui::BppFormatUI> bpp_format_ui_;
@@ -198,8 +168,12 @@ class Canvas {
std::shared_ptr<CanvasPerformanceIntegration> performance_integration_;
CanvasInteractionHandler interaction_handler_;
void AddContextMenuItem(const ContextMenuItem& item);
void AddContextMenuItem(const gui::CanvasMenuItem& item);
void ClearContextMenuItems();
// Phase 4: Access to editor-provided menu definition
CanvasMenuDefinition& editor_menu() { return editor_menu_; }
const CanvasMenuDefinition& editor_menu() const { return editor_menu_; }
void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; }
// Persistent popup management for context menu actions
@@ -415,7 +389,7 @@ class Canvas {
Rom *rom() const { return rom_; }
private:
void DrawContextMenuItem(const ContextMenuItem& item);
void DrawContextMenuItem(const gui::CanvasMenuItem& item);
// Modular configuration and state
gfx::IRenderer* renderer_ = nullptr;
@@ -433,22 +407,12 @@ class Canvas {
bool is_hovered_ = false;
bool refresh_graphics_ = false;
// Context menu system
std::vector<ContextMenuItem> context_menu_items_;
// Phase 4: Context menu system (declarative menu definition)
CanvasMenuDefinition editor_menu_;
bool context_menu_enabled_ = true;
// Persistent popup state for context menu actions
// Phase 3: New popup registry (preferred for new code)
// Phase 4: Persistent popup state for context menu actions (unified registry)
PopupRegistry popup_registry_;
// Legacy popup state (deprecated - kept for backward compatibility during migration)
// TODO(Phase 4): Remove once all editors use popup_registry_ directly
struct PopupState {
std::string popup_id;
bool is_open = false;
std::function<void()> render_callback;
};
std::vector<PopupState> active_popups_;
// Legacy members (to be gradually replaced)
int current_labels_ = 0;

View File

@@ -6,8 +6,7 @@
#include "app/gui/widgets/palette_editor_widget.h"
#include "app/gui/core/icons.h"
#include "app/gui/core/color.h"
#include "app/gui/canvas/canvas_modals.h"
#include "app/gui/widgets/palette_editor_widget.h"
#include "app/gui/canvas/canvas.h"
#include "imgui/imgui.h"
namespace yaze {
@@ -50,11 +49,11 @@ void CanvasContextMenu::SetUsageMode(CanvasUsage usage) {
current_usage_ = usage;
}
void CanvasContextMenu::AddMenuItem(const ContextMenuItem& item) {
void CanvasContextMenu::AddMenuItem(const CanvasMenuItem& item) {
global_items_.push_back(item);
}
void CanvasContextMenu::AddMenuItem(const ContextMenuItem& item, CanvasUsage usage) {
void CanvasContextMenu::AddMenuItem(const CanvasMenuItem& item, CanvasUsage usage) {
usage_specific_items_[usage].push_back(item);
}
@@ -63,10 +62,12 @@ void CanvasContextMenu::ClearMenuItems() {
usage_specific_items_.clear();
}
void CanvasContextMenu::Render(const std::string& context_id, const ImVec2& mouse_pos, Rom* rom,
const gfx::Bitmap* bitmap, const gfx::SnesPalette* palette,
void CanvasContextMenu::Render(const std::string& context_id,
const ImVec2& /* mouse_pos */, Rom* rom,
const gfx::Bitmap* bitmap,
const gfx::SnesPalette* /* palette */,
const std::function<void(Command, const CanvasConfig&)>& command_handler,
CanvasConfig current_config) {
CanvasConfig current_config, Canvas* canvas) {
if (!enabled_) return;
// Context menu (under default mouse threshold)
@@ -75,53 +76,61 @@ void CanvasContextMenu::Render(const std::string& context_id, const ImVec2& mous
ImGui::OpenPopupOnItemClick(context_id.c_str(), ImGuiPopupFlags_MouseButtonRight);
}
// Contents of the Context Menu
// Phase 4: Popup callback for automatic popup management
auto popup_callback = [canvas](const std::string& id, std::function<void()> callback) {
if (canvas) {
canvas->GetPopupRegistry().Open(id, callback);
}
};
// Contents of the Context Menu (Phase 4: Priority-based ordering)
if (ImGui::BeginPopup(context_id.c_str())) {
// Render usage-specific menu first
RenderUsageSpecificMenu();
// Add separator if there are usage-specific items
if (!usage_specific_items_[current_usage_].empty()) {
// PRIORITY 0: Editor-specific items (from Canvas::editor_menu_)
if (canvas && !canvas->editor_menu().sections.empty()) {
RenderCanvasMenu(canvas->editor_menu(), popup_callback);
ImGui::Separator();
}
// Render view controls
RenderViewControlsMenu(command_handler, current_config);
ImGui::Separator();
// Also render usage-specific items (legacy support)
if (!usage_specific_items_[current_usage_].empty()) {
RenderUsageSpecificMenu(popup_callback);
ImGui::Separator();
}
// Render canvas properties
RenderCanvasPropertiesMenu(command_handler, current_config);
ImGui::Separator();
// Render bitmap operations if bitmap is available
// PRIORITY 10: Bitmap/Palette operations
if (bitmap) {
RenderBitmapOperationsMenu(const_cast<gfx::Bitmap*>(bitmap));
ImGui::Separator();
}
// Render palette operations if bitmap is available
if (bitmap) {
RenderPaletteOperationsMenu(rom, const_cast<gfx::Bitmap*>(bitmap));
ImGui::Separator();
}
if (bitmap) {
RenderBppOperationsMenu(bitmap);
ImGui::Separator();
}
RenderPerformanceMenu();
// PRIORITY 20: Canvas properties
RenderCanvasPropertiesMenu(command_handler, current_config);
ImGui::Separator();
RenderViewControlsMenu(command_handler, current_config);
ImGui::Separator();
RenderGridControlsMenu(command_handler, current_config);
ImGui::Separator();
RenderScalingControlsMenu(command_handler, current_config);
// PRIORITY 30: Debug/Performance
if (ImGui::GetIO().KeyCtrl) { // Only show when Ctrl is held
ImGui::Separator();
RenderPerformanceMenu();
}
// Render global menu items
// Render global menu items (if any)
if (!global_items_.empty()) {
ImGui::Separator();
RenderMenuSection("Custom Actions", global_items_);
RenderMenuSection("Custom Actions", global_items_, popup_callback);
}
ImGui::EndPopup();
@@ -137,70 +146,44 @@ void CanvasContextMenu::SetCanvasState(const ImVec2& canvas_size,
float global_scale,
float grid_step,
bool enable_grid,
bool enable_hex_labels,
bool enable_custom_labels,
bool enable_context_menu,
bool is_draggable,
bool auto_resize,
bool /* enable_hex_labels */,
bool /* enable_custom_labels */,
bool /* enable_context_menu */,
bool /* is_draggable */,
bool /* auto_resize */,
const ImVec2& scrolling) {
canvas_size_ = canvas_size;
content_size_ = content_size;
global_scale_ = global_scale;
grid_step_ = grid_step;
enable_grid_ = enable_grid;
enable_hex_labels_ = enable_hex_labels_;
enable_custom_labels_ = enable_custom_labels_;
enable_context_menu_ = enable_context_menu_;
is_draggable_ = is_draggable_;
auto_resize_ = auto_resize_;
enable_hex_labels_ = false; // Field not used anymore
enable_custom_labels_ = false; // Field not used anymore
enable_context_menu_ = true; // Field not used anymore
is_draggable_ = false; // Field not used anymore
auto_resize_ = false; // Field not used anymore
scrolling_ = scrolling;
}
void CanvasContextMenu::RenderMenuItem(const ContextMenuItem& item) {
if (!item.visible_condition()) {
return;
}
if (!item.enabled_condition()) {
ImGui::BeginDisabled();
}
if (item.subitems.empty()) {
// Simple menu item
if (ImGui::MenuItem(item.label.c_str(),
item.shortcut.empty() ? nullptr : item.shortcut.c_str())) {
item.callback();
}
} else {
// Menu with subitems
if (ImGui::BeginMenu(item.label.c_str())) {
for (const auto& subitem : item.subitems) {
RenderMenuItem(subitem);
}
ImGui::EndMenu();
}
}
if (!item.enabled_condition()) {
ImGui::EndDisabled();
}
if (item.separator_after) {
ImGui::Separator();
}
void CanvasContextMenu::RenderMenuItem(const CanvasMenuItem& item,
std::function<void(const std::string&, std::function<void()>)> popup_callback) {
// Phase 4: Delegate to canvas_menu.h implementation
gui::RenderMenuItem(item, popup_callback);
}
void CanvasContextMenu::RenderMenuSection(const std::string& title,
const std::vector<ContextMenuItem>& items) {
const std::vector<CanvasMenuItem>& items,
std::function<void(const std::string&, std::function<void()>)> popup_callback) {
if (items.empty()) return;
ImGui::TextColored(ImVec4(0.7F, 0.7F, 0.7F, 1.0F), "%s", title.c_str());
for (const auto& item : items) {
RenderMenuItem(item);
RenderMenuItem(item, popup_callback);
}
}
void CanvasContextMenu::RenderUsageSpecificMenu() {
void CanvasContextMenu::RenderUsageSpecificMenu(
std::function<void(const std::string&, std::function<void()>)> popup_callback) {
auto it = usage_specific_items_.find(current_usage_);
if (it == usage_specific_items_.end() || it->second.empty()) {
return;
@@ -213,7 +196,7 @@ void CanvasContextMenu::RenderUsageSpecificMenu() {
ImGui::Separator();
for (const auto& item : it->second) {
RenderMenuItem(item);
RenderMenuItem(item, popup_callback);
}
}
@@ -397,7 +380,7 @@ void CanvasContextMenu::DrawROMPaletteSelector() {
palette_editor_->DrawROMPaletteSelector();
}
void CanvasContextMenu::RenderBppOperationsMenu(const gfx::Bitmap* bitmap) {
void CanvasContextMenu::RenderBppOperationsMenu(const gfx::Bitmap* /* bitmap */) {
if (ImGui::BeginMenu(ICON_MD_SWAP_HORIZ " BPP Operations")) {
if (ImGui::MenuItem("Format Analysis...")) {
// Open BPP analysis
@@ -528,85 +511,80 @@ ImVec4 CanvasContextMenu::GetUsageModeColor(CanvasUsage usage) const {
}
void CanvasContextMenu::CreateDefaultMenuItems() {
// Create default menu items for different usage modes
// Phase 4: Create default menu items using unified CanvasMenuItem
// Tile Painting mode items
ContextMenuItem tile_paint_item("Paint Tile", "paint", []() {
CanvasMenuItem tile_paint_item("Paint Tile", "paint", []() {
// Tile painting action
});
usage_specific_items_[CanvasUsage::kTilePainting].push_back(tile_paint_item);
// Tile Selecting mode items
ContextMenuItem tile_select_item("Select Tile", "select", []() {
CanvasMenuItem tile_select_item("Select Tile", "select", []() {
// Tile selection action
});
usage_specific_items_[CanvasUsage::kTileSelecting].push_back(tile_select_item);
// Rectangle Selection mode items
ContextMenuItem rect_select_item("Select Rectangle", "rect", []() {
CanvasMenuItem rect_select_item("Select Rectangle", "rect", []() {
// Rectangle selection action
});
usage_specific_items_[CanvasUsage::kSelectRectangle].push_back(rect_select_item);
// Color Painting mode items
ContextMenuItem color_paint_item("Paint Color", "color", []() {
CanvasMenuItem color_paint_item("Paint Color", "color", []() {
// Color painting action
});
usage_specific_items_[CanvasUsage::kColorPainting].push_back(color_paint_item);
// Bitmap Editing mode items
ContextMenuItem bitmap_edit_item("Edit Bitmap", "edit", []() {
CanvasMenuItem bitmap_edit_item("Edit Bitmap", "edit", []() {
// Bitmap editing action
});
usage_specific_items_[CanvasUsage::kBitmapEditing].push_back(bitmap_edit_item);
// Palette Editing mode items
ContextMenuItem palette_edit_item("Edit Palette", "palette", []() {
CanvasMenuItem palette_edit_item("Edit Palette", "palette", []() {
// Palette editing action
});
usage_specific_items_[CanvasUsage::kPaletteEditing].push_back(palette_edit_item);
// BPP Conversion mode items
ContextMenuItem bpp_convert_item("Convert Format", "convert", []() {
CanvasMenuItem bpp_convert_item("Convert Format", "convert", []() {
// BPP conversion action
});
usage_specific_items_[CanvasUsage::kBppConversion].push_back(bpp_convert_item);
// Performance Mode items
ContextMenuItem perf_item("Performance Analysis", "perf", []() {
CanvasMenuItem perf_item("Performance Analysis", "perf", []() {
// Performance analysis action
});
usage_specific_items_[CanvasUsage::kPerformanceMode].push_back(perf_item);
}
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateViewMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback) {
return ContextMenuItem(label, icon, callback);
CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreateViewMenuItem(
const std::string& label, const std::string& icon, std::function<void()> callback) {
return CanvasMenuItem(label, icon, callback);
}
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateBitmapMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback) {
return ContextMenuItem(label, icon, callback);
CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreateBitmapMenuItem(
const std::string& label, const std::string& icon, std::function<void()> callback) {
return CanvasMenuItem(label, icon, callback);
}
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreatePaletteMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback) {
return ContextMenuItem(label, icon, callback);
CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreatePaletteMenuItem(
const std::string& label, const std::string& icon, std::function<void()> callback) {
return CanvasMenuItem(label, icon, callback);
}
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateBppMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback) {
return ContextMenuItem(label, icon, callback);
CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreateBppMenuItem(
const std::string& label, const std::string& icon, std::function<void()> callback) {
return CanvasMenuItem(label, icon, callback);
}
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreatePerformanceMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback) {
return ContextMenuItem(label, icon, callback);
CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreatePerformanceMenuItem(
const std::string& label, const std::string& icon, std::function<void()> callback) {
return CanvasMenuItem(label, icon, callback);
}
} // namespace gui

View File

@@ -10,6 +10,7 @@
#include "app/gfx/types/snes_palette.h"
#include "app/gui/core/icons.h"
#include "app/gui/canvas/canvas_modals.h"
#include "app/gui/canvas/canvas_menu.h"
#include "canvas_usage_tracker.h"
#include "imgui/imgui.h"
@@ -18,6 +19,7 @@ namespace gui {
// Forward declarations
class PaletteEditorWidget;
class Canvas;
class CanvasContextMenu {
public:
@@ -41,36 +43,24 @@ class CanvasContextMenu {
CanvasContextMenu() = default;
struct ContextMenuItem {
std::string label;
std::string shortcut;
std::string icon;
std::function<void()> callback;
std::function<bool()> enabled_condition = []() { return true; };
std::function<bool()> visible_condition = []() { return true; };
std::vector<ContextMenuItem> subitems;
ImVec4 color = ImVec4(1, 1, 1, 1);
bool separator_after = false;
ContextMenuItem() = default;
ContextMenuItem(const std::string& lbl, const std::string& ico,
std::function<void()> cb, const std::string& sc = "")
: label(lbl), shortcut(sc), icon(ico), callback(std::move(cb)) {}
};
// Phase 4: Use unified CanvasMenuItem from canvas_menu.h
using CanvasMenuItem = gui::CanvasMenuItem;
void Initialize(const std::string& canvas_id);
void SetUsageMode(CanvasUsage usage);
void AddMenuItem(const ContextMenuItem& item);
void AddMenuItem(const ContextMenuItem& item, CanvasUsage usage);
void AddMenuItem(const CanvasMenuItem& item);
void AddMenuItem(const CanvasMenuItem& item, CanvasUsage usage);
void ClearMenuItems();
// Phase 4: Render with editor menu integration and priority ordering
void Render(const std::string& context_id,
const ImVec2& mouse_pos,
Rom* rom,
const gfx::Bitmap* bitmap,
const gfx::SnesPalette* palette,
const std::function<void(Command, const CanvasConfig&)>& command_handler,
CanvasConfig current_config);
CanvasConfig current_config,
Canvas* canvas);
bool ShouldShowContextMenu() const;
void SetEnabled(bool enabled) { enabled_ = enabled; }
@@ -114,13 +104,15 @@ class CanvasContextMenu {
void DrawROMPaletteSelector();
std::unordered_map<CanvasUsage, std::vector<ContextMenuItem>> usage_specific_items_;
std::vector<ContextMenuItem> global_items_;
std::unordered_map<CanvasUsage, std::vector<CanvasMenuItem>> usage_specific_items_;
std::vector<CanvasMenuItem> global_items_;
void RenderMenuItem(const ContextMenuItem& item);
void RenderMenuItem(const CanvasMenuItem& item,
std::function<void(const std::string&, std::function<void()>)> popup_callback);
void RenderMenuSection(const std::string& title,
const std::vector<ContextMenuItem>& items);
void RenderUsageSpecificMenu();
const std::vector<CanvasMenuItem>& items,
std::function<void(const std::string&, std::function<void()>)> popup_callback);
void RenderUsageSpecificMenu(std::function<void(const std::string&, std::function<void()>)> popup_callback);
void RenderViewControlsMenu(const std::function<void(Command, const CanvasConfig&)>& command_handler,
CanvasConfig current_config);
void RenderCanvasPropertiesMenu(const std::function<void(Command, const CanvasConfig&)>& command_handler,
@@ -140,21 +132,21 @@ class CanvasContextMenu {
ImVec4 GetUsageModeColor(CanvasUsage usage) const;
void CreateDefaultMenuItems();
ContextMenuItem CreateViewMenuItem(const std::string& label,
CanvasMenuItem CreateViewMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
CanvasMenuItem CreateBitmapMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
ContextMenuItem CreateBitmapMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
ContextMenuItem CreatePaletteMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
ContextMenuItem CreateBppMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
ContextMenuItem CreatePerformanceMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
CanvasMenuItem CreatePaletteMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
CanvasMenuItem CreateBppMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
CanvasMenuItem CreatePerformanceMenuItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
};
} // namespace gui

View File

@@ -11,6 +11,22 @@
namespace yaze {
namespace gui {
/**
* @brief Menu section priority for controlling rendering order
*
* Lower values render first in the context menu:
* - Editor-specific items (0) appear at the top
* - Bitmap/palette operations (10) in the middle
* - Canvas properties (20) near the bottom
* - Debug/performance (30) at the bottom
*/
enum class MenuSectionPriority {
kEditorSpecific = 0, // Highest priority - editor-specific actions
kBitmapPalette = 10, // Medium priority - bitmap/palette operations
kCanvasProperties = 20, // Low priority - canvas settings
kDebug = 30 // Lowest priority - debug/performance
};
/**
* @brief Declarative popup definition for menu items
*
@@ -126,6 +142,7 @@ struct CanvasMenuItem {
* @brief Menu section grouping related menu items
*
* Provides visual organization of menu items with optional section titles.
* Sections are rendered in priority order.
*/
struct CanvasMenuSection {
// Optional section title (rendered as colored text)
@@ -140,6 +157,9 @@ struct CanvasMenuSection {
// Whether to show a separator after this section
bool separator_after = true;
// Priority for ordering sections (lower values render first)
MenuSectionPriority priority = MenuSectionPriority::kEditorSpecific;
// Default constructor
CanvasMenuSection() = default;
@@ -149,6 +169,11 @@ struct CanvasMenuSection {
// Constructor with title and items
CanvasMenuSection(const std::string& t, const std::vector<CanvasMenuItem>& its)
: title(t), items(its) {}
// Constructor with title, items, and priority
CanvasMenuSection(const std::string& t, const std::vector<CanvasMenuItem>& its,
MenuSectionPriority prio)
: title(t), items(its), priority(prio) {}
};
/**

View File

@@ -0,0 +1,145 @@
#include "canvas_menu_builder.h"
namespace yaze {
namespace gui {
CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label,
std::function<void()> callback) {
CanvasMenuItem item;
item.label = label;
item.callback = std::move(callback);
pending_items_.push_back(item);
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label,
const std::string& icon,
std::function<void()> callback) {
CanvasMenuItem item;
item.label = label;
item.icon = icon;
item.callback = std::move(callback);
pending_items_.push_back(item);
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label,
const std::string& icon,
const std::string& shortcut,
std::function<void()> callback) {
CanvasMenuItem item;
item.label = label;
item.icon = icon;
item.shortcut = shortcut;
item.callback = std::move(callback);
pending_items_.push_back(item);
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::AddPopupItem(
const std::string& label, const std::string& popup_id,
std::function<void()> render_callback) {
CanvasMenuItem item = CanvasMenuItem::WithPopup(label, popup_id, render_callback);
pending_items_.push_back(item);
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::AddPopupItem(
const std::string& label, const std::string& icon,
const std::string& popup_id, std::function<void()> render_callback) {
CanvasMenuItem item = CanvasMenuItem::WithPopup(label, popup_id, render_callback);
item.icon = icon;
pending_items_.push_back(item);
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::AddConditionalItem(
const std::string& label, std::function<void()> callback,
std::function<bool()> condition) {
CanvasMenuItem item = CanvasMenuItem::Conditional(label, callback, condition);
pending_items_.push_back(item);
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::AddSubmenu(
const std::string& label, const std::vector<CanvasMenuItem>& subitems) {
CanvasMenuItem item;
item.label = label;
item.subitems = subitems;
pending_items_.push_back(item);
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::AddSeparator() {
if (!pending_items_.empty()) {
pending_items_.back().separator_after = true;
}
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::BeginSection(
const std::string& title, MenuSectionPriority priority) {
// Flush any pending items to previous section
FlushPendingItems();
// Create new section
CanvasMenuSection section;
section.title = title;
section.priority = priority;
section.separator_after = true;
menu_.sections.push_back(section);
// Point current_section_ to the newly added section
current_section_ = &menu_.sections.back();
return *this;
}
CanvasMenuBuilder& CanvasMenuBuilder::EndSection() {
FlushPendingItems();
current_section_ = nullptr;
return *this;
}
CanvasMenuDefinition CanvasMenuBuilder::Build() {
FlushPendingItems();
return menu_;
}
CanvasMenuBuilder& CanvasMenuBuilder::Reset() {
menu_.sections.clear();
pending_items_.clear();
current_section_ = nullptr;
return *this;
}
void CanvasMenuBuilder::FlushPendingItems() {
if (pending_items_.empty()) {
return;
}
// If no section exists yet, create a default one
if (menu_.sections.empty()) {
CanvasMenuSection section;
section.priority = MenuSectionPriority::kEditorSpecific;
section.separator_after = true;
menu_.sections.push_back(section);
current_section_ = &menu_.sections.back();
}
// Add pending items to current section
if (current_section_) {
current_section_->items.insert(current_section_->items.end(),
pending_items_.begin(), pending_items_.end());
} else {
// Add to last section if current_section_ is null
menu_.sections.back().items.insert(menu_.sections.back().items.end(),
pending_items_.begin(), pending_items_.end());
}
pending_items_.clear();
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,157 @@
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_MENU_BUILDER_H
#define YAZE_APP_GUI_CANVAS_CANVAS_MENU_BUILDER_H
#include <functional>
#include <string>
#include <vector>
#include "app/gui/canvas/canvas_menu.h"
namespace yaze {
namespace gui {
/**
* @brief Builder pattern for constructing canvas menus fluently
*
* Phase 4: Simplifies menu construction with chainable methods.
*
* Example usage:
* @code
* CanvasMenuBuilder builder;
* builder
* .AddItem("Cut", ICON_MD_CONTENT_CUT, []() { DoCut(); })
* .AddItem("Copy", ICON_MD_CONTENT_COPY, []() { DoCopy(); })
* .AddSeparator()
* .AddPopupItem("Properties", ICON_MD_SETTINGS, "props_popup",
* []() { RenderPropertiesPopup(); })
* .Build();
* @endcode
*/
class CanvasMenuBuilder {
public:
CanvasMenuBuilder() = default;
/**
* @brief Add a simple menu item
* @param label Menu item label
* @param callback Action to perform when selected
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& AddItem(const std::string& label,
std::function<void()> callback);
/**
* @brief Add a menu item with icon
* @param label Menu item label
* @param icon Material Design icon or Unicode glyph
* @param callback Action to perform when selected
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& AddItem(const std::string& label,
const std::string& icon,
std::function<void()> callback);
/**
* @brief Add a menu item with icon and shortcut hint
* @param label Menu item label
* @param icon Material Design icon or Unicode glyph
* @param shortcut Keyboard shortcut hint (e.g., "Ctrl+S")
* @param callback Action to perform when selected
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& AddItem(const std::string& label,
const std::string& icon,
const std::string& shortcut,
std::function<void()> callback);
/**
* @brief Add a menu item that opens a persistent popup
* @param label Menu item label
* @param popup_id Unique popup identifier
* @param render_callback Callback to render popup content
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& AddPopupItem(const std::string& label,
const std::string& popup_id,
std::function<void()> render_callback);
/**
* @brief Add a menu item with icon that opens a persistent popup
* @param label Menu item label
* @param icon Material Design icon or Unicode glyph
* @param popup_id Unique popup identifier
* @param render_callback Callback to render popup content
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& AddPopupItem(const std::string& label,
const std::string& icon,
const std::string& popup_id,
std::function<void()> render_callback);
/**
* @brief Add a conditional menu item (enabled only when condition is true)
* @param label Menu item label
* @param callback Action to perform when selected
* @param condition Function that returns true when item should be enabled
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& AddConditionalItem(const std::string& label,
std::function<void()> callback,
std::function<bool()> condition);
/**
* @brief Add a submenu with nested items
* @param label Submenu label
* @param subitems Nested menu items
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& AddSubmenu(const std::string& label,
const std::vector<CanvasMenuItem>& subitems);
/**
* @brief Add a separator to visually group items
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& AddSeparator();
/**
* @brief Start a new section with optional title
* @param title Section title (empty for no title)
* @param priority Section priority (controls rendering order)
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& BeginSection(
const std::string& title = "",
MenuSectionPriority priority = MenuSectionPriority::kEditorSpecific);
/**
* @brief End the current section
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& EndSection();
/**
* @brief Build the final menu definition
* @return Complete menu definition ready for rendering
*/
CanvasMenuDefinition Build();
/**
* @brief Reset the builder to start building a new menu
* @return Reference to this builder for chaining
*/
CanvasMenuBuilder& Reset();
private:
CanvasMenuDefinition menu_;
CanvasMenuSection* current_section_ = nullptr;
std::vector<CanvasMenuItem> pending_items_;
void FlushPendingItems();
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_CANVAS_CANVAS_MENU_BUILDER_H

View File

@@ -33,6 +33,7 @@ set(CANVAS_SRC
app/gui/canvas/canvas_interaction.cc
app/gui/canvas/canvas_interaction_handler.cc
app/gui/canvas/canvas_menu.cc
app/gui/canvas/canvas_menu_builder.cc
app/gui/canvas/canvas_modals.cc
app/gui/canvas/canvas_performance_integration.cc
app/gui/canvas/canvas_popup.cc