refactor: Integrate Canvas Automation API and Simplify Overworld Editor Controls

- Implemented Canvas Automation API integration in OverworldEditor, allowing for automated tile operations and enhanced control.
- Simplified editing modes in the toolbar, consolidating functionality for a more intuitive user experience.
- Updated entity editing shortcuts and context menu interactions to streamline entity management.
- Adjusted drawing methods to ensure consistent rendering without scale application, improving performance and clarity.
- Added comments for future enhancements regarding entity operations submenu.
This commit is contained in:
scawful
2025-10-05 23:59:48 -04:00
parent 6982d63173
commit 60ddf76331
3 changed files with 168 additions and 84 deletions

View File

@@ -476,6 +476,9 @@ void MapPropertiesSystem::SetupCanvasContextMenu(
canvas.set_global_scale(scale);
};
canvas.AddContextMenuItem(zoom_out_item);
// Entity Operations submenu will be added in future iteration
// For now, users can use keyboard shortcuts (3-8) to activate entity editing
}
// Private method implementations

View File

@@ -25,6 +25,7 @@
#include "app/gfx/snes_palette.h"
#include "app/gfx/tilemap.h"
#include "app/gui/canvas.h"
#include "app/gui/canvas/canvas_automation_api.h"
#include "app/gui/editor_layout.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
@@ -65,6 +66,9 @@ void OverworldEditor::Initialize() {
entity_renderer_ = std::make_unique<OverworldEntityRenderer>(
&overworld_, &ow_map_canvas_, &sprite_previews_);
// Setup Canvas Automation API callbacks (Phase 4)
SetupCanvasAutomation();
// Note: Context menu is now setup dynamically in DrawOverworldCanvas()
// for context-aware menu items based on current map state
@@ -283,44 +287,36 @@ void OverworldEditor::DrawToolset() {
toolbar.Begin();
// Mode buttons (editing tools) - compact inline row
// Mode buttons - simplified to 2 modes
toolbar.BeginModeGroup();
if (toolbar.ModeButton(ICON_MD_PAN_TOOL_ALT, current_mode == EditingMode::PAN, "Pan (1)")) {
current_mode = EditingMode::PAN;
ow_map_canvas_.set_draggable(true);
if (toolbar.ModeButton(ICON_MD_MOUSE, current_mode == EditingMode::MOUSE, "Mouse Mode (1)\nNavigate, pan, and manage entities")) {
current_mode = EditingMode::MOUSE;
}
if (toolbar.ModeButton(ICON_MD_DRAW, current_mode == EditingMode::DRAW_TILE, "Draw (2)")) {
if (toolbar.ModeButton(ICON_MD_DRAW, current_mode == EditingMode::DRAW_TILE, "Tile Paint Mode (2)\nDraw tiles on the map")) {
current_mode = EditingMode::DRAW_TILE;
}
if (toolbar.ModeButton(ICON_MD_DOOR_FRONT, current_mode == EditingMode::ENTRANCES, "Entrances (3)")) {
current_mode = EditingMode::ENTRANCES;
}
if (toolbar.ModeButton(ICON_MD_DOOR_BACK, current_mode == EditingMode::EXITS, "Exits (4)")) {
current_mode = EditingMode::EXITS;
}
if (toolbar.ModeButton(ICON_MD_GRASS, current_mode == EditingMode::ITEMS, "Items (5)")) {
current_mode = EditingMode::ITEMS;
}
if (toolbar.ModeButton(ICON_MD_PEST_CONTROL_RODENT, current_mode == EditingMode::SPRITES, "Sprites (6)")) {
current_mode = EditingMode::SPRITES;
}
if (toolbar.ModeButton(ICON_MD_ADD_LOCATION, current_mode == EditingMode::TRANSPORTS, "Transports (7)")) {
current_mode = EditingMode::TRANSPORTS;
}
if (toolbar.ModeButton(ICON_MD_MUSIC_NOTE, current_mode == EditingMode::MUSIC, "Music (8)")) {
current_mode = EditingMode::MUSIC;
}
toolbar.EndModeGroup();
// Entity editing indicator (shows current entity mode if active)
if (entity_edit_mode_ != EntityEditMode::NONE) {
toolbar.AddSeparator();
const char* entity_label = "";
const char* entity_icon = "";
switch (entity_edit_mode_) {
case EntityEditMode::ENTRANCES: entity_icon = ICON_MD_DOOR_FRONT; entity_label = "Entrances"; break;
case EntityEditMode::EXITS: entity_icon = ICON_MD_DOOR_BACK; entity_label = "Exits"; break;
case EntityEditMode::ITEMS: entity_icon = ICON_MD_GRASS; entity_label = "Items"; break;
case EntityEditMode::SPRITES: entity_icon = ICON_MD_PEST_CONTROL_RODENT; entity_label = "Sprites"; break;
case EntityEditMode::TRANSPORTS: entity_icon = ICON_MD_ADD_LOCATION; entity_label = "Transports"; break;
case EntityEditMode::MUSIC: entity_icon = ICON_MD_MUSIC_NOTE; entity_label = "Music"; break;
default: break;
}
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s Editing: %s", entity_icon, entity_label);
}
// ROM version badge (already read above)
toolbar.AddRomBadge(asm_version, [this]() {
ImGui::OpenPopup("UpgradeROMVersion");
@@ -467,23 +463,32 @@ void OverworldEditor::DrawToolset() {
if (!ImGui::IsAnyItemActive()) {
using enum EditingMode;
// Tool shortcuts
// Tool shortcuts (simplified)
if (ImGui::IsKeyDown(ImGuiKey_1)) {
current_mode = PAN;
current_mode = EditingMode::MOUSE;
} else if (ImGui::IsKeyDown(ImGuiKey_2)) {
current_mode = DRAW_TILE;
} else if (ImGui::IsKeyDown(ImGuiKey_3)) {
current_mode = ENTRANCES;
current_mode = EditingMode::DRAW_TILE;
}
// Entity editing shortcuts (3-8)
if (ImGui::IsKeyDown(ImGuiKey_3)) {
entity_edit_mode_ = EntityEditMode::ENTRANCES;
current_mode = EditingMode::MOUSE;
} else if (ImGui::IsKeyDown(ImGuiKey_4)) {
current_mode = EXITS;
entity_edit_mode_ = EntityEditMode::EXITS;
current_mode = EditingMode::MOUSE;
} else if (ImGui::IsKeyDown(ImGuiKey_5)) {
current_mode = ITEMS;
entity_edit_mode_ = EntityEditMode::ITEMS;
current_mode = EditingMode::MOUSE;
} else if (ImGui::IsKeyDown(ImGuiKey_6)) {
current_mode = SPRITES;
entity_edit_mode_ = EntityEditMode::SPRITES;
current_mode = EditingMode::MOUSE;
} else if (ImGui::IsKeyDown(ImGuiKey_7)) {
current_mode = TRANSPORTS;
entity_edit_mode_ = EntityEditMode::TRANSPORTS;
current_mode = EditingMode::MOUSE;
} else if (ImGui::IsKeyDown(ImGuiKey_8)) {
current_mode = MUSIC;
entity_edit_mode_ = EntityEditMode::MUSIC;
current_mode = EditingMode::MOUSE;
}
// View shortcuts
@@ -514,9 +519,9 @@ void OverworldEditor::DrawOverworldMaps() {
continue; // Skip invalid map index
}
int scale = static_cast<int>(ow_map_canvas_.global_scale());
int map_x = (xx * kOverworldMapSize * scale);
int map_y = (yy * kOverworldMapSize * scale);
// Don't apply scale to coordinates - scale is applied to canvas rendering
int map_x = xx * kOverworldMapSize;
int map_y = yy * kOverworldMapSize;
// Check if the map has a texture, if not, ensure it gets loaded
if (!maps_bmp_[world_index].texture() &&
@@ -526,8 +531,8 @@ void OverworldEditor::DrawOverworldMaps() {
// Only draw if the map has a texture or is the currently selected map
if (maps_bmp_[world_index].texture() || world_index == current_map_) {
ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y,
ow_map_canvas_.global_scale());
// Draw without applying scale here - canvas handles zoom uniformly
ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y, 1.0f);
} else {
// Draw a placeholder for maps that haven't loaded yet
ImDrawList* draw_list = ImGui::GetWindowDrawList();
@@ -535,7 +540,7 @@ void OverworldEditor::DrawOverworldMaps() {
ImVec2 placeholder_pos =
ImVec2(canvas_pos.x + map_x, canvas_pos.y + map_y);
ImVec2 placeholder_size =
ImVec2(kOverworldMapSize * scale, kOverworldMapSize * scale);
ImVec2(kOverworldMapSize, kOverworldMapSize);
// Modern loading indicator with theme colors
draw_list->AddRectFilled(
@@ -1120,13 +1125,9 @@ ImVec2 ClampScrollPosition(ImVec2 scroll, ImVec2 content_size, ImVec2 visible_si
} // namespace
void OverworldEditor::HandleOverworldPan() {
// Middle mouse button panning
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle)) {
if (!middle_mouse_dragging_) {
previous_mode = current_mode;
current_mode = EditingMode::PAN;
middle_mouse_dragging_ = true;
}
// Middle mouse button panning (works in all modes)
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle) && ImGui::IsItemHovered()) {
middle_mouse_dragging_ = true;
// Get mouse delta and apply to scroll
ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
@@ -1145,10 +1146,26 @@ void OverworldEditor::HandleOverworldPan() {
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Middle) && middle_mouse_dragging_) {
current_mode = previous_mode;
middle_mouse_dragging_ = false;
}
// In MOUSE mode, left-click drag also pans
if (current_mode == EditingMode::MOUSE &&
ImGui::IsMouseDragging(ImGuiMouseButton_Left) && ImGui::IsItemHovered()) {
ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
ImVec2 current_scroll = ow_map_canvas_.scrolling();
ImVec2 new_scroll = ImVec2(
current_scroll.x + mouse_delta.x,
current_scroll.y + mouse_delta.y
);
// Clamp scroll to boundaries
ImVec2 content_size = CalculateOverworldContentSize(ow_map_canvas_.global_scale());
ImVec2 visible_size = ow_map_canvas_.canvas_size();
new_scroll = ClampScrollPosition(new_scroll, content_size, visible_size);
ow_map_canvas_.set_scrolling(new_scroll);
}
}
void OverworldEditor::HandleOverworldZoom() {
@@ -1247,30 +1264,15 @@ void OverworldEditor::DrawOverworldCanvas() {
show_overlay_editor_);
}
// Handle pan and zoom
// Handle pan and zoom (works in all modes)
HandleOverworldPan();
HandleOverworldZoom();
if (current_mode == EditingMode::PAN) {
// In PAN mode, allow right-click drag for panning
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) {
ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
ImVec2 current_scroll = ow_map_canvas_.scrolling();
ImVec2 new_scroll = ImVec2(
current_scroll.x + mouse_delta.x,
current_scroll.y + mouse_delta.y
);
// Clamp scroll to boundaries
ImVec2 content_size = CalculateOverworldContentSize(ow_map_canvas_.global_scale());
ImVec2 visible_size = ow_map_canvas_.canvas_size();
new_scroll = ClampScrollPosition(new_scroll, content_size, visible_size);
ow_map_canvas_.set_scrolling(new_scroll);
}
// Context menu only in MOUSE mode
if (current_mode == EditingMode::MOUSE) {
ow_map_canvas_.DrawContextMenu();
} else {
// Handle map interaction (tile painting, etc.)
} else if (current_mode == EditingMode::DRAW_TILE) {
// Tile painting mode - handle tile edits and right-click tile picking
HandleMapInteraction();
}
@@ -1281,13 +1283,14 @@ void OverworldEditor::DrawOverworldCanvas() {
entity_renderer_->set_current_map(current_map_);
// Draw all entities using the entity renderer
int mode_int = static_cast<int>(current_mode);
// Convert entity_edit_mode_ to legacy mode int for entity renderer
int entity_mode_int = static_cast<int>(entity_edit_mode_);
entity_renderer_->DrawExits(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(),
current_world_, mode_int);
current_world_, entity_mode_int);
entity_renderer_->DrawEntrances(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(),
current_world_, mode_int);
entity_renderer_->DrawItems(current_world_, mode_int);
entity_renderer_->DrawSprites(current_world_, game_state_, mode_int);
current_world_, entity_mode_int);
entity_renderer_->DrawItems(current_world_, entity_mode_int);
entity_renderer_->DrawSprites(current_world_, game_state_, entity_mode_int);
// Check if entity renderer wants to jump to a tab (e.g., double-clicked entrance/exit)
if (entity_renderer_->jump_to_tab() != -1) {
@@ -2645,4 +2648,67 @@ void OverworldEditor::UpdateBlocksetSelectorState() {
blockset_selector_->SetSelectedTile(current_tile16_);
}
// ============================================================================
// Canvas Automation API Integration (Phase 4)
// ============================================================================
void OverworldEditor::SetupCanvasAutomation() {
auto* api = ow_map_canvas_.GetAutomationAPI();
// Set tile paint callback
api->SetTilePaintCallback([this](int x, int y, int tile_id) {
return AutomationSetTile(x, y, tile_id);
});
// Set tile query callback
api->SetTileQueryCallback([this](int x, int y) {
return AutomationGetTile(x, y);
});
}
bool OverworldEditor::AutomationSetTile(int x, int y, int tile_id) {
if (!overworld_.is_loaded()) {
return false;
}
// Bounds check
if (x < 0 || y < 0 || x >= 512 || y >= 512) {
return false;
}
// Set current world based on current_map_
overworld_.set_current_world(current_world_);
overworld_.set_current_map(current_map_);
// Set the tile in the overworld data structure
overworld_.SetTile(x, y, static_cast<uint16_t>(tile_id));
// Update the bitmap
auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile_id);
if (!tile_data.empty()) {
RenderUpdatedMapBitmap(ImVec2(static_cast<float>(x * 16),
static_cast<float>(y * 16)), tile_data);
return true;
}
return false;
}
int OverworldEditor::AutomationGetTile(int x, int y) {
if (!overworld_.is_loaded()) {
return -1;
}
// Bounds check
if (x < 0 || y < 0 || x >= 512 || y >= 512) {
return -1;
}
// Set current world
overworld_.set_current_world(current_world_);
overworld_.set_current_map(current_map_);
return overworld_.GetTile(x, y);
}
} // namespace yaze::editor

View File

@@ -192,6 +192,14 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
void ResetOverworldView();
void CenterOverworldView();
// Canvas Automation API integration (Phase 4)
void SetupCanvasAutomation();
gui::Canvas* GetOverworldCanvas() { return &ow_map_canvas_; }
// Tile operations for automation callbacks
bool AutomationSetTile(int x, int y, int tile_id);
int AutomationGetTile(int x, int y);
/**
* @brief Scroll the blockset canvas to show the current selected tile16
*/
@@ -212,18 +220,25 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
void DrawDebugWindow();
enum class EditingMode {
DRAW_TILE,
MOUSE, // Navigation, selection, entity management via context menu
DRAW_TILE // Tile painting mode
};
EditingMode current_mode = EditingMode::DRAW_TILE;
EditingMode previous_mode = EditingMode::DRAW_TILE;
// Entity editing state (managed via context menu now)
enum class EntityEditMode {
NONE,
ENTRANCES,
EXITS,
ITEMS,
SPRITES,
TRANSPORTS,
MUSIC,
PAN
MUSIC
};
EditingMode current_mode = EditingMode::DRAW_TILE;
EditingMode previous_mode = EditingMode::DRAW_TILE;
EntityEditMode entity_edit_mode_ = EntityEditMode::NONE;
enum OverworldProperty {
LW_AREA_GFX,