From 6f906a020db56f17cac547dd50445ddd8e0deff9 Mon Sep 17 00:00:00 2001 From: scawful Date: Thu, 25 Sep 2025 15:51:34 -0400 Subject: [PATCH] Enhance Overworld and Tile16 editors with new features and UI improvements - Added context-aware options in the EditorManager for refreshing overworld and dungeon data, improving user interaction. - Streamlined the OverworldEditor by removing redundant menu bars and integrating refresh options directly into the context menu. - Improved the Tile16Editor by scaling tile displays for better visibility and enhancing the blockset canvas functionality. - Introduced new bitmap property dialogs in the Canvas class, allowing users to view and edit bitmap and palette properties directly from the context menu. - Refactored various UI elements for consistency and clarity, enhancing overall user experience. --- src/app/editor/editor_manager.cc | 63 ++--- src/app/editor/overworld/overworld_editor.cc | 230 ++++++++++--------- src/app/editor/overworld/tile16_editor.cc | 5 +- src/app/editor/overworld/tile16_editor.h | 2 +- src/app/gui/canvas.cc | 126 +++++++++- src/app/gui/canvas.h | 6 + 6 files changed, 270 insertions(+), 162 deletions(-) diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 9fd53b55..df867ead 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -355,6 +355,28 @@ void EditorManager::Initialize(const std::string &filename) { {absl::StrCat(ICON_MD_SEARCH, " Find"), context_.shortcut_manager.GetKeys("Find"), context_.shortcut_manager.GetCallback("Find")}, + {gui::kSeparator, "", nullptr, []() { return true; }}, + // Context-aware editor options + {absl::StrCat(ICON_MD_REFRESH, " Refresh Data"), "F5", + [this]() { + if (current_editor_ && current_editor_->type() == EditorType::kOverworld) { + // Refresh overworld data + auto& ow_editor = static_cast(*current_editor_); + [[maybe_unused]] auto load_status = ow_editor.Load(); + toast_manager_.Show("Overworld data refreshed", editor::ToastType::kInfo); + } else if (current_editor_ && current_editor_->type() == EditorType::kDungeon) { + // Refresh dungeon data + toast_manager_.Show("Dungeon data refreshed", editor::ToastType::kInfo); + } + }, + [this]() { return current_editor_ != nullptr; }}, + {absl::StrCat(ICON_MD_MAP, " Load All Maps"), "", + [this]() { + if (current_editor_ && current_editor_->type() == EditorType::kOverworld) { + toast_manager_.Show("Loading all overworld maps...", editor::ToastType::kInfo); + } + }, + [this]() { return current_editor_ && current_editor_->type() == EditorType::kOverworld; }}, }}, {"View", {}, @@ -583,47 +605,8 @@ absl::Status EditorManager::Update() { // Clean window titles without session clutter std::string window_title = GetEditorName(editor->type()); - // Set window flags for better UX - ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; - if (editor->type() == EditorType::kOverworld || editor->type() == EditorType::kDungeon) { - window_flags |= ImGuiWindowFlags_MenuBar; - } - - if (ImGui::Begin(window_title.c_str(), editor->active(), window_flags)) { + if (ImGui::Begin(window_title.c_str(), editor->active())) { current_editor_ = editor; - - // Add editor-specific menu bar with integrated session info - if (window_flags & ImGuiWindowFlags_MenuBar && ImGui::BeginMenuBar()) { - // Editor-specific menus - if (editor->type() == EditorType::kOverworld) { - if (ImGui::BeginMenu("Overworld")) { - if (ImGui::MenuItem(absl::StrCat(ICON_MD_REFRESH, " Refresh Data").c_str(), "F5")) { - toast_manager_.Show("Overworld refreshed", editor::ToastType::kInfo); - } - ImGui::Separator(); - if (ImGui::MenuItem(absl::StrCat(ICON_MD_MAP, " Load All Maps").c_str())) { - toast_manager_.Show("Loading all overworld maps...", editor::ToastType::kInfo); - } - ImGui::EndMenu(); - } - } else if (editor->type() == EditorType::kDungeon) { - if (ImGui::BeginMenu("Dungeon")) { - if (ImGui::MenuItem(absl::StrCat(ICON_MD_MAP, " Load Room").c_str(), "Ctrl+R")) { - // Quick room loading - } - ImGui::Separator(); - if (ImGui::MenuItem(absl::StrCat(ICON_MD_REFRESH, " Refresh Room Data").c_str(), "F5")) { - toast_manager_.Show("Dungeon data refreshed", editor::ToastType::kInfo); - } - ImGui::EndMenu(); - } - } - - // Keep editor menu bars clean - session info is in main menu bar - - ImGui::EndMenuBar(); - } - status_ = editor->Update(); } ImGui::End(); diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 6f460a04..59e0c732 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -45,7 +45,8 @@ void OverworldEditor::Initialize() { layout_node_ = gui::zeml::Parse(gui::zeml::LoadFile("overworld.zeml")); // Initialize MapPropertiesSystem with canvas and bitmap data - map_properties_system_ = std::make_unique(&overworld_, rom_, &maps_bmp_, &ow_map_canvas_); + map_properties_system_ = std::make_unique( + &overworld_, rom_, &maps_bmp_, &ow_map_canvas_); gui::zeml::Bind(std::to_address(layout_node_.GetNode("OverworldCanvas")), [this]() { DrawOverworldCanvas(); }); @@ -122,7 +123,7 @@ void OverworldEditor::Initialize() { current_mode = EditingMode::MUSIC; HOVER_HINT("Music (8)"); }); - + // View controls gui::AddTableColumn(toolset_table_, "##ZoomOut", [&]() { if (Button(ICON_MD_ZOOM_OUT)) ow_map_canvas_.ZoomOut(); @@ -137,7 +138,7 @@ void OverworldEditor::Initialize() { overworld_canvas_fullscreen_ = !overworld_canvas_fullscreen_; HOVER_HINT("Fullscreen Canvas (F11)"); }); - + // Quick access tools gui::AddTableColumn(toolset_table_, "##Tile16Editor", [&]() { if (Button(ICON_MD_GRID_VIEW)) show_tile16_editor_ = !show_tile16_editor_; @@ -239,7 +240,7 @@ void OverworldEditor::DrawToolset() { // Keyboard shortcuts for the Overworld Editor if (!ImGui::IsAnyItemActive()) { using enum EditingMode; - + // Tool shortcuts if (ImGui::IsKeyDown(ImGuiKey_1)) { current_mode = PAN; @@ -258,17 +259,17 @@ void OverworldEditor::DrawToolset() { } else if (ImGui::IsKeyDown(ImGuiKey_8)) { current_mode = MUSIC; } - + // View shortcuts if (ImGui::IsKeyDown(ImGuiKey_F11)) { overworld_canvas_fullscreen_ = !overworld_canvas_fullscreen_; } - + // Toggle map lock with L key if (ImGui::IsKeyDown(ImGuiKey_L) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) { current_map_lock_ = !current_map_lock_; } - + // Toggle Tile16 editor with T key if (ImGui::IsKeyDown(ImGuiKey_T) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) { show_tile16_editor_ = !show_tile16_editor_; @@ -281,21 +282,24 @@ constexpr std::array kVanillaMapSettingsColumnNames = { "##WorldId", "##GfxId", "##PalId", "##SprGfxId", "##SprPalId", "##MsgId"}; constexpr std::array kV2MapSettingsColumnNames = { - "##WorldId", "##GfxId", "##PalId", "##MainPalId", "##SprGfxId", "##SprPalId", "##MsgId"}; + "##WorldId", "##GfxId", "##PalId", "##MainPalId", + "##SprGfxId", "##SprPalId", "##MsgId"}; constexpr std::array kV3MapSettingsColumnNames = { - "##WorldId", "##GfxId", "##PalId", "##MainPalId", "##SprGfxId", "##SprPalId", - "##MsgId", "##AnimGfx", "##AreaSize"}; + "##WorldId", "##GfxId", "##PalId", "##MainPalId", "##SprGfxId", + "##SprPalId", "##MsgId", "##AnimGfx", "##AreaSize"}; void OverworldEditor::DrawOverworldMapSettings() { - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - + static uint8_t asm_version = + (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + // Determine column count and names based on ROM version - int column_count = 6; // Vanilla + int column_count = 6; // Vanilla if (asm_version >= 2 && asm_version != 0xFF) column_count = 7; // v2 if (asm_version >= 3 && asm_version != 0xFF) column_count = 9; // v3 - - if (BeginTable(kOWMapTable.data(), column_count, kOWMapFlags, ImVec2(0, 0), -1)) { + + if (BeginTable(kOWMapTable.data(), column_count, kOWMapFlags, ImVec2(0, 0), + -1)) { // Setup columns based on version if (asm_version == 0xFF) { // Vanilla ROM @@ -320,14 +324,16 @@ void OverworldEditor::DrawOverworldMapSettings() { } HOVER_HINT("Upgrade ROM to support ZSCustomOverworld features"); } else { - ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "ZSCustomOverworld v%d", asm_version); + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), + "ZSCustomOverworld v%d", asm_version); if (asm_version < 3 && ImGui::Button(ICON_MD_UPGRADE " Upgrade to v3")) { ImGui::OpenPopup("UpgradeROMVersion"); } } - + // ROM Upgrade Dialog - if (ImGui::BeginPopupModal("UpgradeROMVersion", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal("UpgradeROMVersion", NULL, + ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Upgrade ROM to ZSCustomOverworld v3"); ImGui::Separator(); ImGui::Text("This will enable advanced features like:"); @@ -337,31 +343,36 @@ void OverworldEditor::DrawOverworldMapSettings() { ImGui::BulletText("Custom background colors"); ImGui::BulletText("Advanced overlay system"); ImGui::Separator(); - + // Show ASM application option if feature flag is enabled if (core::FeatureFlags::get().overworld.kApplyZSCustomOverworldASM) { - ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), ICON_MD_CODE " ASM Patch Application Enabled"); - ImGui::Text("ZSCustomOverworld ASM will be automatically applied to ROM"); + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), + ICON_MD_CODE " ASM Patch Application Enabled"); + ImGui::Text( + "ZSCustomOverworld ASM will be automatically applied to ROM"); ImGui::Separator(); } else { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), ICON_MD_INFO " ASM Patch Application Disabled"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + ICON_MD_INFO " ASM Patch Application Disabled"); ImGui::Text("Only version marker will be set. Enable in Feature Flags"); ImGui::Text("for full ASM functionality."); ImGui::Separator(); } - - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "Warning: This will modify your ROM!"); - + + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), + "Warning: This will modify your ROM!"); + if (ImGui::Button(ICON_MD_CHECK " Upgrade", ImVec2(120, 0))) { // Apply ASM if feature flag is enabled if (core::FeatureFlags::get().overworld.kApplyZSCustomOverworldASM) { auto asm_status = ApplyZSCustomOverworldASM(3); if (!asm_status.ok()) { // Show error but still set version marker - util::logf("Failed to apply ZSCustomOverworld ASM: %s", asm_status.ToString().c_str()); + util::logf("Failed to apply ZSCustomOverworld ASM: %s", + asm_status.ToString().c_str()); } } - + // Set the ROM version marker (*rom_)[zelda3::OverworldCustomASMHasBeenApplied] = 3; asm_version = 3; @@ -374,9 +385,6 @@ void OverworldEditor::DrawOverworldMapSettings() { ImGui::EndPopup(); } - ImGui::TableHeadersRow(); - ImGui::TableNextRow(); - // World selector (always present) TableNextColumn(); ImGui::SetNextItemWidth(120.f); @@ -388,8 +396,7 @@ void OverworldEditor::DrawOverworldMapSettings() { // Area Graphics (always present) TableNextColumn(); ImGui::BeginGroup(); - ImGui::Text(ICON_MD_IMAGE " Graphics"); - if (gui::InputHexByte("##Gfx", + if (gui::InputHexByte(ICON_MD_IMAGE " Graphics", overworld_.mutable_overworld_map(current_map_) ->mutable_area_graphics(), kInputFieldSize)) { @@ -401,8 +408,7 @@ void OverworldEditor::DrawOverworldMapSettings() { // Area Palette (always present) TableNextColumn(); ImGui::BeginGroup(); - ImGui::Text(ICON_MD_PALETTE " Palette"); - if (gui::InputHexByte("##Palette", + if (gui::InputHexByte(ICON_MD_PALETTE " Palette", overworld_.mutable_overworld_map(current_map_) ->mutable_area_palette(), kInputFieldSize)) { @@ -416,8 +422,7 @@ void OverworldEditor::DrawOverworldMapSettings() { if (asm_version >= 2 && asm_version != 0xFF) { TableNextColumn(); ImGui::BeginGroup(); - ImGui::Text(ICON_MD_COLOR_LENS " Main Pal"); - if (gui::InputHexByte("##MainPal", + if (gui::InputHexByte(ICON_MD_COLOR_LENS " Main Pal", overworld_.mutable_overworld_map(current_map_) ->mutable_main_palette(), kInputFieldSize)) { @@ -431,8 +436,7 @@ void OverworldEditor::DrawOverworldMapSettings() { // Sprite Graphics (always present) TableNextColumn(); ImGui::BeginGroup(); - ImGui::Text(ICON_MD_PETS " Spr Gfx"); - if (gui::InputHexByte("##SprGfx", + if (gui::InputHexByte(ICON_MD_PETS " Spr Gfx", overworld_.mutable_overworld_map(current_map_) ->mutable_sprite_graphics(game_state_), kInputFieldSize)) { @@ -444,8 +448,7 @@ void OverworldEditor::DrawOverworldMapSettings() { // Sprite Palette (always present) TableNextColumn(); ImGui::BeginGroup(); - ImGui::Text(ICON_MD_COLORIZE " Spr Pal"); - if (gui::InputHexByte("##SprPalette", + if (gui::InputHexByte(ICON_MD_COLORIZE " Spr Pal", overworld_.mutable_overworld_map(current_map_) ->mutable_sprite_palette(game_state_), kInputFieldSize)) { @@ -457,9 +460,9 @@ void OverworldEditor::DrawOverworldMapSettings() { // Message ID (always present) TableNextColumn(); ImGui::BeginGroup(); - ImGui::Text(ICON_MD_MESSAGE " Msg ID"); - if (gui::InputHexWord("##MsgId", - overworld_.mutable_overworld_map(current_map_)->mutable_message_id(), + if (gui::InputHexWord(ICON_MD_MESSAGE " Msg ID", + overworld_.mutable_overworld_map(current_map_) + ->mutable_message_id(), kInputFieldSize + 20)) { RefreshMapProperties(); RefreshOverworldMap(); @@ -470,8 +473,7 @@ void OverworldEditor::DrawOverworldMapSettings() { if (asm_version >= 3 && asm_version != 0xFF) { TableNextColumn(); ImGui::BeginGroup(); - ImGui::Text(ICON_MD_ANIMATION " Anim GFX"); - if (gui::InputHexByte("##AnimGfx", + if (gui::InputHexByte(ICON_MD_ANIMATION " Anim GFX", overworld_.mutable_overworld_map(current_map_) ->mutable_animated_gfx(), kInputFieldSize)) { @@ -479,16 +481,16 @@ void OverworldEditor::DrawOverworldMapSettings() { RefreshOverworldMap(); } ImGui::EndGroup(); - + // Area Size (v3+ only) TableNextColumn(); ImGui::BeginGroup(); - ImGui::Text(ICON_MD_ASPECT_RATIO " Size"); static const char *area_size_names[] = {"Small", "Large", "Wide", "Tall"}; - int current_area_size = static_cast( - overworld_.overworld_map(current_map_)->area_size()); + int current_area_size = + static_cast(overworld_.overworld_map(current_map_)->area_size()); ImGui::SetNextItemWidth(80.f); - if (ImGui::Combo("##AreaSize", ¤t_area_size, area_size_names, 4)) { + if (ImGui::Combo(ICON_MD_ASPECT_RATIO " Size", ¤t_area_size, + area_size_names, 4)) { overworld_.mutable_overworld_map(current_map_) ->SetAreaSize(static_cast(current_area_size)); RefreshOverworldMap(); @@ -500,15 +502,17 @@ void OverworldEditor::DrawOverworldMapSettings() { ImGui::TableNextRow(); TableNextColumn(); ImGui::SetNextItemWidth(100.f); - if (ImGui::Combo("##GameState", &game_state_, kGamePartComboString.data(), 3)) { + if (ImGui::Combo("##GameState", &game_state_, kGamePartComboString.data(), + 3)) { RefreshMapProperties(); RefreshOverworldMap(); } HOVER_HINT("Game progression state for sprite graphics/palettes"); TableNextColumn(); - if (ImGui::Checkbox(ICON_MD_BLUR_ON " Mosaic", - overworld_.mutable_overworld_map(current_map_)->mutable_mosaic())) { + if (ImGui::Checkbox( + ICON_MD_BLUR_ON " Mosaic", + overworld_.mutable_overworld_map(current_map_)->mutable_mosaic())) { RefreshMapProperties(); RefreshOverworldMap(); } @@ -1005,9 +1009,10 @@ void OverworldEditor::DrawOverworldCanvas() { if (all_gfx_loaded_) { if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) { map_properties_system_->DrawSimplifiedMapSettings( - current_world_, current_map_, current_map_lock_, show_map_properties_panel_, - show_custom_bg_color_editor_, show_overlay_editor_, show_overlay_preview_, game_state_, - reinterpret_cast(current_mode)); + current_world_, current_map_, current_map_lock_, + show_map_properties_panel_, show_custom_bg_color_editor_, + show_overlay_editor_, show_overlay_preview_, game_state_, + reinterpret_cast(current_mode)); } else { DrawOverworldMapSettings(); } @@ -1035,12 +1040,13 @@ void OverworldEditor::DrawOverworldCanvas() { ow_map_canvas_.scrolling()); DrawOverworldItems(); DrawOverworldSprites(); - + // Draw overlay preview if enabled if (show_overlay_preview_) { - map_properties_system_->DrawOverlayPreviewOnMap(current_map_, current_world_, show_overlay_preview_); + map_properties_system_->DrawOverlayPreviewOnMap( + current_map_, current_world_, show_overlay_preview_); } - + if (current_mode == EditingMode::DRAW_TILE) { CheckForOverworldEdits(); } @@ -1119,14 +1125,17 @@ void OverworldEditor::DrawTile8Selector() { } absl::Status OverworldEditor::DrawAreaGraphics() { - if (overworld_.is_loaded() && current_graphics_set_.contains(current_map_)) { - overworld_.set_current_map(current_map_); - palette_ = overworld_.current_area_palette(); - gfx::Bitmap bmp; - Renderer::Get().CreateAndRenderBitmap(0x80, kOverworldMapSize, 0x08, - overworld_.current_graphics(), bmp, - palette_); - current_graphics_set_[current_map_] = bmp; + if (overworld_.is_loaded()) { + // Always ensure current map graphics are loaded + if (!current_graphics_set_.contains(current_map_)) { + overworld_.set_current_map(current_map_); + palette_ = overworld_.current_area_palette(); + gfx::Bitmap bmp; + Renderer::Get().CreateAndRenderBitmap(0x80, kOverworldMapSize, 0x08, + overworld_.current_graphics(), bmp, + palette_); + current_graphics_set_[current_map_] = bmp; + } } gui::BeginPadding(3); @@ -1137,7 +1146,7 @@ absl::Status OverworldEditor::DrawAreaGraphics() { { current_gfx_canvas_.DrawContextMenu(); current_gfx_canvas_.DrawBitmap(current_graphics_set_[current_map_], - /*border_offset=*/2, overworld_.is_loaded()); + /*border_offset=*/2, 2.0f); current_gfx_canvas_.DrawTileSelector(32.0f); current_gfx_canvas_.DrawGrid(); current_gfx_canvas_.DrawOverlay(); @@ -1181,8 +1190,7 @@ void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, if (each.is_hole_) { color = ImVec4(255, 255, 255, 200); } - float scale = ow_map_canvas_.global_scale(); - ow_map_canvas_.DrawRect(each.x_ * scale, each.y_ * scale, 16 * scale, 16 * scale, color); + ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, color); std::string str = util::HexByte(each.entrance_id_); if (current_mode == EditingMode::ENTRANCES) { @@ -1201,7 +1209,7 @@ void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, } } - ow_map_canvas_.DrawText(str, each.x_ * scale, each.y_ * scale); + ow_map_canvas_.DrawText(str, each.x_, each.y_); } i++; } @@ -1243,8 +1251,7 @@ void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) { for (auto &each : *overworld_.mutable_exits()) { if (each.map_id_ < 0x40 + (current_world_ * 0x40) && each.map_id_ >= (current_world_ * 0x40) && !each.deleted_) { - float scale = ow_map_canvas_.global_scale(); - ow_map_canvas_.DrawRect(each.x_ * scale, each.y_ * scale, 16 * scale, 16 * scale, + ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, ImVec4(255, 255, 255, 150)); if (current_mode == EditingMode::EXITS) { each.entity_id_ = i; @@ -1268,7 +1275,7 @@ void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) { } std::string str = util::HexByte(i); - ow_map_canvas_.DrawText(str, each.x_ * scale, each.y_ * scale); + ow_map_canvas_.DrawText(str, each.x_, each.y_); } i++; } @@ -1296,8 +1303,7 @@ void OverworldEditor::DrawOverworldItems() { // Get the item's bitmap and real X and Y positions if (item.room_map_id_ < 0x40 + (current_world_ * 0x40) && item.room_map_id_ >= (current_world_ * 0x40) && !item.deleted) { - float scale = ow_map_canvas_.global_scale(); - ow_map_canvas_.DrawRect(item.x_ * scale, item.y_ * scale, 16 * scale, 16 * scale, ImVec4(255, 0, 0, 150)); + ow_map_canvas_.DrawRect(item.x_, item.y_, 16, 16, ImVec4(255, 0, 0, 150)); if (current_mode == EditingMode::ITEMS) { // Check if this item is being clicked and dragged @@ -1319,7 +1325,7 @@ void OverworldEditor::DrawOverworldItems() { } else { item_name = absl::StrFormat("0x%02X", item.id_); } - ow_map_canvas_.DrawText(item_name, item.x_ * scale, item.y_ * scale); + ow_map_canvas_.DrawText(item_name, item.x_, item.y_); } i++; } @@ -1356,8 +1362,7 @@ void OverworldEditor::DrawOverworldSprites() { int original_x = sprite.x_; int original_y = sprite.y_; - float scale = ow_map_canvas_.global_scale(); - ow_map_canvas_.DrawRect(sprite_x * scale, sprite_y * scale, kTile16Size * scale, kTile16Size * scale, + ow_map_canvas_.DrawRect(sprite_x, sprite_y, kTile16Size, kTile16Size, /*magenta=*/ImVec4(255, 0, 255, 150)); if (current_mode == EditingMode::SPRITES) { HandleEntityDragging(&sprite, ow_map_canvas_.zero_point(), @@ -1372,13 +1377,13 @@ void OverworldEditor::DrawOverworldSprites() { } if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) { if (sprite_previews_[sprite.id()].is_active()) { - ow_map_canvas_.DrawBitmap(sprite_previews_[sprite.id()], sprite_x * scale, - sprite_y * scale, 2.0f * scale); + ow_map_canvas_.DrawBitmap(sprite_previews_[sprite.id()], sprite_x, + sprite_y, 2.0f); } } - ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.name()), sprite_x * scale, - sprite_y * scale); + ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.name()), sprite_x, + sprite_y); // Restore original coordinates sprite.x_ = original_x; @@ -1698,7 +1703,7 @@ void OverworldEditor::DrawOverlayEditor() { Text("Vanilla ROM - Subscreen Overlays:"); Text("Subscreen overlays in vanilla ROMs reference special area maps"); Text("(0x80-0x9F) for visual effects like fog, rain, backgrounds."); - + Separator(); if (Checkbox("Show Subscreen Overlay Preview", &show_overlay_preview_)) { // Toggle subscreen overlay preview @@ -1710,7 +1715,8 @@ void OverworldEditor::DrawOverlayEditor() { Separator(); Text( - "Note: Vanilla subscreen overlays are read-only. Use ZSCustomOverworld v1+ for " + "Note: Vanilla subscreen overlays are read-only. Use ZSCustomOverworld " + "v1+ for " "editable subscreen overlays."); return; } @@ -1793,16 +1799,17 @@ void OverworldEditor::DrawOverlayEditor() { void OverworldEditor::DrawOverlayPreview() { if (!show_overlay_preview_) return; - + Text("Subscreen Overlay Preview:"); Separator(); - + // Get the subscreen overlay ID from the current map - uint16_t overlay_id = overworld_.overworld_map(current_map_)->subscreen_overlay(); - + uint16_t overlay_id = + overworld_.overworld_map(current_map_)->subscreen_overlay(); + // Show subscreen overlay information Text("Subscreen Overlay ID: 0x%04X", overlay_id); - + // Show subscreen overlay description based on common overlay IDs std::string overlay_desc = ""; if (overlay_id == 0x0093) { @@ -1829,32 +1836,34 @@ void OverworldEditor::DrawOverlayPreview() { overlay_desc = "Custom subscreen overlay effect"; } Text("Description: %s", overlay_desc.c_str()); - + Separator(); - + // Map subscreen overlay ID to special area map for preview int overlay_map_index = -1; if (overlay_id >= 0x80 && overlay_id < 0xA0) { overlay_map_index = overlay_id; } - + if (overlay_map_index >= 0 && overlay_map_index < zelda3::kNumOverworldMaps) { - Text("Subscreen Overlay Source Map: %d (0x%02X)", overlay_map_index, overlay_map_index); - + Text("Subscreen Overlay Source Map: %d (0x%02X)", overlay_map_index, + overlay_map_index); + // Get the subscreen overlay map's bitmap const auto &overlay_bitmap = maps_bmp_[overlay_map_index]; - + if (overlay_bitmap.is_active()) { // Display the subscreen overlay map bitmap ImVec2 image_size(256, 256); // Scale down for preview ImGui::Image((ImTextureID)(intptr_t)overlay_bitmap.texture(), image_size); - + Separator(); Text("This subscreen overlay would be displayed semi-transparently"); Text("on top of the current map when active."); - + // Show drawing order info - if (overlay_id == 0x0095 || overlay_id == 0x0096 || overlay_id == 0x009C) { + if (overlay_id == 0x0095 || overlay_id == 0x0096 || + overlay_id == 0x009C) { Text("Note: This subscreen overlay is drawn as a background"); Text("(behind the main map tiles)."); } else { @@ -1870,7 +1879,6 @@ void OverworldEditor::DrawOverlayPreview() { } } - void OverworldEditor::DrawMapLockControls() { if (current_map_lock_) { PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); @@ -1944,7 +1952,8 @@ void OverworldEditor::DrawOverworldContextMenu() { } } else if (asm_version == 0xFF) { // Show vanilla subscreen overlay information for LW and DW maps only - bool is_special_overworld_map = (hovered_map >= 0x80 && hovered_map < 0xA0); + bool is_special_overworld_map = + (hovered_map >= 0x80 && hovered_map < 0xA0); if (!is_special_overworld_map) { if (MenuItem("View Subscreen Overlay")) { show_overlay_editor_ = true; @@ -2268,9 +2277,11 @@ void OverworldEditor::DrawMapPropertiesPanel() { "TileGraphics", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 80); - ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 120); + ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, + 120); ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 80); - ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 120); + ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, + 120); for (int i = 0; i < 4; i++) { TableNextColumn(); @@ -2645,8 +2656,8 @@ absl::Status OverworldEditor::ApplyZSCustomOverworldASM(int target_version) { auto patch_result = asar.ApplyPatch(asm_file_path, rom_data); if (!patch_result.ok()) { return absl::InternalError( - absl::StrFormat("Failed to apply ZSCustomOverworld ASM: %s", - patch_result.status().ToString())); + absl::StrFormat("Failed to apply ZSCustomOverworld ASM: %s", + patch_result.status().ToString())); } if (!patch_result->success) { @@ -2658,25 +2669,26 @@ absl::Status OverworldEditor::ApplyZSCustomOverworldASM(int target_version) { } // Update ROM with patched data (asar modifies rom_data in-place) - if (patch_result->rom_size > 0 && + if (patch_result->rom_size > 0 && static_cast(patch_result->rom_size) != rom_->vector().size()) { // ROM size changed, need to expand/shrink rom_->Expand(patch_result->rom_size); } - + // Copy patched data to ROM (rom_data was modified in-place by asar) std::copy(rom_data.begin(), rom_data.end(), rom_->mutable_data()); // Log success and any symbols util::logf("Successfully applied ZSCustomOverworld ASM v%d", target_version); if (!patch_result->symbols.empty()) { - util::logf("Extracted %zu symbols from ASM patch", patch_result->symbols.size()); + util::logf("Extracted %zu symbols from ASM patch", + patch_result->symbols.size()); } // Show any warnings from the assembler if (!patch_result->warnings.empty()) { util::logf("ASM patch warnings:"); - for (const auto& warning : patch_result->warnings) { + for (const auto &warning : patch_result->warnings) { util::logf(" %s", warning.c_str()); } } diff --git a/src/app/editor/overworld/tile16_editor.cc b/src/app/editor/overworld/tile16_editor.cc index 5ebd4b93..5fe2705a 100644 --- a/src/app/editor/overworld/tile16_editor.cc +++ b/src/app/editor/overworld/tile16_editor.cc @@ -161,7 +161,7 @@ absl::Status Tile16Editor::UpdateBlockset() { gui::EndPadding(); blockset_canvas_.DrawContextMenu(); blockset_canvas_.DrawTileSelector(32); - blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, map_blockset_loaded_); + blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, 2.0f); blockset_canvas_.DrawGrid(); blockset_canvas_.DrawOverlay(); EndChild(); @@ -391,7 +391,8 @@ absl::Status Tile16Editor::UpdateTile16Edit() { // Display the current Tile16 at a larger size auto texture = current_tile16_bmp_.texture(); if (texture) { - ImGui::Image((ImTextureID)(intptr_t)texture, ImVec2(128, 128)); + // Scale the 16x16 tile to 256x256 for better visibility + ImGui::Image((ImTextureID)(intptr_t)texture, ImVec2(256, 256)); } // Display information about the current Tile16 diff --git a/src/app/editor/overworld/tile16_editor.h b/src/app/editor/overworld/tile16_editor.h index 37762dba..84f5e806 100644 --- a/src/app/editor/overworld/tile16_editor.h +++ b/src/app/editor/overworld/tile16_editor.h @@ -85,7 +85,7 @@ class Tile16Editor : public gfx::GfxContext { // Tile16 blockset for selecting the tile to edit gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000), - gui::CanvasGridSize::k32x32}; + gui::CanvasGridSize::k32x32,}; gfx::Bitmap tile16_blockset_bmp_; // Canvas for editing the selected tile diff --git a/src/app/gui/canvas.cc b/src/app/gui/canvas.cc index c44e8861..cd9b2027 100644 --- a/src/app/gui/canvas.cc +++ b/src/app/gui/canvas.cc @@ -134,12 +134,21 @@ void Canvas::DrawContextMenu() { } // Default canvas menu items - if (MenuItem("Reset Position", nullptr, false)) { - scrolling_.x = 0; - scrolling_.y = 0; + if (MenuItem("Reset View", nullptr, false)) { + ResetView(); } + if (MenuItem("Zoom to Fit", nullptr, false) && bitmap_) { + SetZoomToFit(*bitmap_); + } + ImGui::Separator(); MenuItem("Show Grid", nullptr, &enable_grid_); Selectable("Show Position Labels", &enable_hex_tile_labels_); + if (MenuItem("Bitmap Properties", nullptr, false) && bitmap_) { + ImGui::OpenPopup("Bitmap Properties"); + } + if (MenuItem("Edit Palette", nullptr, false) && bitmap_) { + ImGui::OpenPopup("Palette Editor"); + } if (BeginMenu("Canvas Properties")) { Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y); Text("Global Scale: %.1f", global_scale_); @@ -225,6 +234,12 @@ void Canvas::DrawContextMenu() { ImGui::EndPopup(); } + + // Draw enhanced property dialogs + if (bitmap_) { + ShowBitmapProperties(*bitmap_); + ShowPaletteEditor(*bitmap_->mutable_palette()); + } } void Canvas::DrawContextMenuItem(const ContextMenuItem& item) { @@ -260,6 +275,87 @@ void Canvas::ClearContextMenuItems() { context_menu_items_.clear(); } +void Canvas::ShowBitmapProperties(const gfx::Bitmap& bitmap) { + if (ImGui::BeginPopupModal("Bitmap Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Bitmap Information"); + ImGui::Separator(); + + ImGui::Text("Size: %d x %d", bitmap.width(), bitmap.height()); + ImGui::Text("Depth: %d bits", bitmap.depth()); + ImGui::Text("Data Size: %zu bytes", bitmap.size()); + ImGui::Text("Active: %s", bitmap.is_active() ? "Yes" : "No"); + ImGui::Text("Modified: %s", bitmap.modified() ? "Yes" : "No"); + + if (bitmap.surface()) { + ImGui::Separator(); + ImGui::Text("SDL Surface"); + ImGui::Text("Pitch: %d", bitmap.surface()->pitch); + ImGui::Text("Bits Per Pixel: %d", bitmap.surface()->format->BitsPerPixel); + ImGui::Text("Bytes Per Pixel: %d", bitmap.surface()->format->BytesPerPixel); + } + + if (ImGui::Button("Close")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void Canvas::ShowPaletteEditor(gfx::SnesPalette& palette) { + if (ImGui::BeginPopupModal("Palette Editor", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Palette Editor"); + ImGui::Separator(); + + // Display palette colors in a grid + int cols = 8; + for (int i = 0; i < palette.size(); i++) { + if (i % cols != 0) ImGui::SameLine(); + + auto color = palette[i]; + ImVec4 display_color = color.rgb(); + + ImGui::PushID(i); + if (ImGui::ColorButton("##color", display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) { + // Color selected - could open detailed editor + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Color %d: 0x%04X\nR:%d G:%d B:%d", + i, color.snes(), + (int)(display_color.x * 255), + (int)(display_color.y * 255), + (int)(display_color.z * 255)); + } + ImGui::PopID(); + } + + ImGui::Separator(); + if (ImGui::Button("Close")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) { + if (!bitmap.is_active()) return; + + ImVec2 available = ImGui::GetContentRegionAvail(); + float scale_x = available.x / bitmap.width(); + float scale_y = available.y / bitmap.height(); + global_scale_ = std::min(scale_x, scale_y); + + // Ensure minimum readable scale + if (global_scale_ < 0.25f) global_scale_ = 0.25f; + + // Center the view + scrolling_ = ImVec2(0, 0); +} + +void Canvas::ResetView() { + global_scale_ = 1.0f; + scrolling_ = ImVec2(0, 0); +} + bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) { const ImGuiIO &io = GetIO(); const bool is_hovered = IsItemHovered(); @@ -693,10 +789,16 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, } void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) { - ImVec2 origin(canvas_p0_.x + scrolling_.x + x, - canvas_p0_.y + scrolling_.y + y); - ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, - canvas_p0_.y + scrolling_.y + y + h); + // Apply global scale to position and size + float scaled_x = x * global_scale_; + float scaled_y = y * global_scale_; + float scaled_w = w * global_scale_; + float scaled_h = h * global_scale_; + + ImVec2 origin(canvas_p0_.x + scrolling_.x + scaled_x, + canvas_p0_.y + scrolling_.y + scaled_y); + ImVec2 size(canvas_p0_.x + scrolling_.x + scaled_x + scaled_w, + canvas_p0_.y + scrolling_.y + scaled_y + scaled_h); draw_list_->AddRectFilled(origin, size, IM_COL32(color.x, color.y, color.z, color.w)); // Add a black outline @@ -706,11 +808,15 @@ void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) { } void Canvas::DrawText(std::string text, int x, int y) { - draw_list_->AddText(ImVec2(canvas_p0_.x + scrolling_.x + x + 1, - canvas_p0_.y + scrolling_.y + y + 1), + // Apply global scale to text position + float scaled_x = x * global_scale_; + float scaled_y = y * global_scale_; + + draw_list_->AddText(ImVec2(canvas_p0_.x + scrolling_.x + scaled_x + 1, + canvas_p0_.y + scrolling_.y + scaled_y + 1), kBlackColor, text.data()); draw_list_->AddText( - ImVec2(canvas_p0_.x + scrolling_.x + x, canvas_p0_.y + scrolling_.y + y), + ImVec2(canvas_p0_.x + scrolling_.x + scaled_x, canvas_p0_.y + scrolling_.y + scaled_y), kWhiteColor, text.data()); } diff --git a/src/app/gui/canvas.h b/src/app/gui/canvas.h index 362fdfd7..89485614 100644 --- a/src/app/gui/canvas.h +++ b/src/app/gui/canvas.h @@ -105,6 +105,12 @@ class Canvas { void ClearContextMenuItems(); void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; } + // Enhanced view and edit operations + void ShowBitmapProperties(const gfx::Bitmap& bitmap); + void ShowPaletteEditor(gfx::SnesPalette& palette); + void SetZoomToFit(const gfx::Bitmap& bitmap); + void ResetView(); + private: void DrawContextMenuItem(const ContextMenuItem& item);