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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 = [¤t_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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
145
src/app/gui/canvas/canvas_menu_builder.cc
Normal file
145
src/app/gui/canvas/canvas_menu_builder.cc
Normal 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
|
||||
|
||||
157
src/app/gui/canvas/canvas_menu_builder.h
Normal file
157
src/app/gui/canvas/canvas_menu_builder.h
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user