diff --git a/src/app/editor/editor.cmake b/src/app/editor/editor.cmake index d0d7d624..0b6bb9c8 100644 --- a/src/app/editor/editor.cmake +++ b/src/app/editor/editor.cmake @@ -13,6 +13,7 @@ set( app/editor/graphics/graphics_editor.cc app/editor/graphics/palette_editor.cc app/editor/overworld/tile16_editor.cc + app/editor/overworld/map_properties.cc app/editor/graphics/gfx_group_editor.cc app/editor/overworld/entity.cc app/editor/system/settings_editor.cc diff --git a/src/app/editor/overworld/map_properties.cc b/src/app/editor/overworld/map_properties.cc new file mode 100644 index 00000000..97c78d4a --- /dev/null +++ b/src/app/editor/overworld/map_properties.cc @@ -0,0 +1,667 @@ +#include "app/editor/overworld/map_properties.h" + +#include "app/gui/canvas.h" +#include "app/gui/icons.h" +#include "app/gui/input.h" +#include "app/gui/style.h" +#include "app/zelda3/overworld/overworld_map.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +using ImGui::BeginTable; +using ImGui::Button; +using ImGui::Checkbox; +using ImGui::EndTable; +// HOVER_HINT is defined in util/macro.h +using ImGui::SameLine; +using ImGui::Separator; +using ImGui::TableNextColumn; +using ImGui::Text; + +// Static constants +constexpr const char* kWorldList[] = {"Light World", "Dark World", "Special World"}; +constexpr const char* kGamePartComboString[] = {"Light World", "Dark World", "Special World"}; + +void MapPropertiesSystem::DrawSimplifiedMapSettings(int& current_world, int& current_map, + bool& current_map_lock, bool& show_map_properties_panel, + bool& show_custom_bg_color_editor, bool& show_overlay_editor) { + // Enhanced settings table with popup buttons for quick access + if (BeginTable("SimplifiedMapSettings", 8, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0), -1)) { + ImGui::TableSetupColumn("World", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("Area Size", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Lock", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("Graphics", ImGuiTableColumnFlags_WidthFixed, 70); + ImGui::TableSetupColumn("Palettes", ImGuiTableColumnFlags_WidthFixed, 70); + ImGui::TableSetupColumn("Overlays", ImGuiTableColumnFlags_WidthFixed, 70); + ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed, 80); + + TableNextColumn(); + ImGui::SetNextItemWidth(70.f); + if (ImGui::Combo("##world", ¤t_world, kWorldList, 3)) { + // World changed, update current map if needed + if (current_map >= 0x40 && current_world == 0) { + current_map -= 0x40; + } else if (current_map < 0x40 && current_world == 1) { + current_map += 0x40; + } else if (current_map < 0x80 && current_world == 2) { + current_map += 0x80; + } else if (current_map >= 0x80 && current_world != 2) { + current_map -= 0x80; + } + } + + TableNextColumn(); + ImGui::Text("%d (0x%02X)", current_map, current_map); + + TableNextColumn(); + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version != 0xFF) { + static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; + int current_area_size = static_cast(overworld_->overworld_map(current_map)->area_size()); + ImGui::SetNextItemWidth(90.f); + if (ImGui::Combo("##AreaSize", ¤t_area_size, area_size_names, 4)) { + overworld_->mutable_overworld_map(current_map)->SetAreaSize(static_cast(current_area_size)); + RefreshOverworldMap(); + } + } else { + ImGui::Text("N/A"); + } + + TableNextColumn(); + if (ImGui::Button(current_map_lock ? ICON_MD_LOCK : ICON_MD_LOCK_OPEN, ImVec2(30, 0))) { + current_map_lock = !current_map_lock; + } + HOVER_HINT(current_map_lock ? "Unlock Map" : "Lock Map"); + + TableNextColumn(); + if (ImGui::Button("Graphics", ImVec2(60, 0))) { + ImGui::OpenPopup("GraphicsPopup"); + } + HOVER_HINT("Graphics Settings"); + DrawGraphicsPopup(current_map); + + TableNextColumn(); + if (ImGui::Button("Palettes", ImVec2(60, 0))) { + ImGui::OpenPopup("PalettesPopup"); + } + HOVER_HINT("Palette Settings"); + DrawPalettesPopup(current_map, show_custom_bg_color_editor); + + TableNextColumn(); + if (ImGui::Button("Overlays", ImVec2(60, 0))) { + ImGui::OpenPopup("OverlaysPopup"); + } + HOVER_HINT("Overlay Settings"); + DrawOverlaysPopup(current_map, show_overlay_editor); + + TableNextColumn(); + if (ImGui::Button("Properties", ImVec2(70, 0))) { + ImGui::OpenPopup("PropertiesPopup"); + } + HOVER_HINT("Map Properties"); + DrawPropertiesPopup(current_map, show_map_properties_panel); + + ImGui::EndTable(); + } +} + +void MapPropertiesSystem::DrawMapPropertiesPanel(int current_map, bool& show_map_properties_panel) { + if (!overworld_->is_loaded()) { + Text("No overworld loaded"); + return; + } + + // Header with map info and lock status + ImGui::BeginGroup(); + Text("Current Map: %d (0x%02X)", current_map, current_map); + ImGui::EndGroup(); + + Separator(); + + // Create tabs for different property categories + if (ImGui::BeginTabBar("MapPropertiesTabs", ImGuiTabBarFlags_FittingPolicyScroll)) { + + // Basic Properties Tab + if (ImGui::BeginTabItem("Basic Properties")) { + DrawBasicPropertiesTab(current_map); + ImGui::EndTabItem(); + } + + // Sprite Properties Tab + if (ImGui::BeginTabItem("Sprite Properties")) { + DrawSpritePropertiesTab(current_map); + ImGui::EndTabItem(); + } + + // Custom Overworld Features Tab + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version != 0xFF && ImGui::BeginTabItem("Custom Features")) { + DrawCustomFeaturesTab(current_map); + ImGui::EndTabItem(); + } + + // Tile Graphics Tab + if (ImGui::BeginTabItem("Tile Graphics")) { + DrawTileGraphicsTab(current_map); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } +} + +void MapPropertiesSystem::DrawCustomBackgroundColorEditor(int current_map, bool& show_custom_bg_color_editor) { + if (!overworld_->is_loaded()) { + Text("No overworld loaded"); + return; + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version < 2) { + Text("Custom background colors require ZSCustomOverworld v2+"); + return; + } + + Text("Custom Background Color Editor"); + Separator(); + + // Enable/disable area-specific background color + static bool use_area_specific_bg_color = false; + if (ImGui::Checkbox("Use Area-Specific Background Color", &use_area_specific_bg_color)) { + // Update ROM data + (*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] = use_area_specific_bg_color ? 1 : 0; + } + + if (use_area_specific_bg_color) { + // Get current color + uint16_t current_color = overworld_->overworld_map(current_map)->area_specific_bg_color(); + gfx::SnesColor snes_color(current_color); + + // Convert to ImVec4 for color picker + ImVec4 color_vec = gui::ConvertSnesColorToImVec4(snes_color); + + if (ImGui::ColorPicker4("Background Color", (float*)&color_vec, + ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHex)) { + // Convert back to SNES color and update + gfx::SnesColor new_snes_color = gui::ConvertImVec4ToSnesColor(color_vec); + overworld_->mutable_overworld_map(current_map)->set_area_specific_bg_color(new_snes_color.snes()); + + // Update ROM + int rom_address = zelda3::OverworldCustomAreaSpecificBGPalette + (current_map * 2); + (*rom_)[rom_address] = new_snes_color.snes() & 0xFF; + (*rom_)[rom_address + 1] = (new_snes_color.snes() >> 8) & 0xFF; + } + + Text("SNES Color: 0x%04X", current_color); + } +} + +void MapPropertiesSystem::DrawOverlayEditor(int current_map, bool& show_overlay_editor) { + if (!overworld_->is_loaded()) { + Text("No overworld loaded"); + return; + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version < 1) { + Text("Subscreen overlays require ZSCustomOverworld v1+"); + return; + } + + Text("Overlay Editor"); + Separator(); + + // Enable/disable subscreen overlay + static bool use_subscreen_overlay = false; + if (ImGui::Checkbox("Use Subscreen Overlay", &use_subscreen_overlay)) { + // Update ROM data + (*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] = use_subscreen_overlay ? 1 : 0; + } + + if (use_subscreen_overlay) { + uint16_t current_overlay = overworld_->overworld_map(current_map)->subscreen_overlay(); + if (gui::InputHexWord("Overlay ID", ¤t_overlay, kInputFieldSize + 20)) { + overworld_->mutable_overworld_map(current_map)->set_subscreen_overlay(current_overlay); + + // Update ROM + int rom_address = zelda3::OverworldCustomSubscreenOverlayArray + (current_map * 2); + (*rom_)[rom_address] = current_overlay & 0xFF; + (*rom_)[rom_address + 1] = (current_overlay >> 8) & 0xFF; + } + + Text("Common overlay IDs:"); + Text("0x0000 = None"); + Text("0x0001 = Map overlay"); + Text("0x0002 = Dungeon overlay"); + } +} + +void MapPropertiesSystem::SetupCanvasContextMenu(gui::Canvas& canvas, int current_map, bool current_map_lock, + bool& show_map_properties_panel, bool& show_custom_bg_color_editor, + bool& show_overlay_editor) { + // Clear any existing context menu items + canvas.ClearContextMenuItems(); + + // Add overworld-specific context menu items + gui::Canvas::ContextMenuItem 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; + }; + canvas.AddContextMenuItem(lock_item); + + // Map Properties + gui::Canvas::ContextMenuItem properties_item; + properties_item.label = "Map Properties"; + properties_item.callback = [&show_map_properties_panel]() { + show_map_properties_panel = true; + }; + canvas.AddContextMenuItem(properties_item); + + // Custom overworld features (only show if v3+) + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 3 && asm_version != 0xFF) { + // Custom Background Color + gui::Canvas::ContextMenuItem bg_color_item; + bg_color_item.label = "Custom Background Color"; + bg_color_item.callback = [&show_custom_bg_color_editor]() { + show_custom_bg_color_editor = true; + }; + canvas.AddContextMenuItem(bg_color_item); + + // Overlay Settings + gui::Canvas::ContextMenuItem overlay_item; + overlay_item.label = "Overlay Settings"; + overlay_item.callback = [&show_overlay_editor]() { + show_overlay_editor = true; + }; + canvas.AddContextMenuItem(overlay_item); + } + + // Canvas controls + gui::Canvas::ContextMenuItem reset_pos_item; + reset_pos_item.label = "Reset Canvas Position"; + reset_pos_item.callback = [&canvas]() { + canvas.set_scrolling(ImVec2(0, 0)); + }; + canvas.AddContextMenuItem(reset_pos_item); + + gui::Canvas::ContextMenuItem zoom_fit_item; + zoom_fit_item.label = "Zoom to Fit"; + zoom_fit_item.callback = [&canvas]() { + canvas.set_global_scale(1.0f); + canvas.set_scrolling(ImVec2(0, 0)); + }; + canvas.AddContextMenuItem(zoom_fit_item); +} + +// Private method implementations +void MapPropertiesSystem::DrawGraphicsPopup(int current_map) { + if (ImGui::BeginPopup("GraphicsPopup")) { + ImGui::Text("Graphics Settings"); + ImGui::Separator(); + + if (gui::InputHexByte("Area Graphics", + overworld_->mutable_overworld_map(current_map)->mutable_area_graphics(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + if (gui::InputHexByte("Sprite GFX 1", + overworld_->mutable_overworld_map(current_map)->mutable_sprite_graphics(1), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + if (gui::InputHexByte("Sprite GFX 2", + overworld_->mutable_overworld_map(current_map)->mutable_sprite_graphics(2), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 3) { + if (gui::InputHexByte("Animated GFX", + overworld_->mutable_overworld_map(current_map)->mutable_animated_gfx(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + } + + ImGui::Separator(); + if (ImGui::Button("Tile Graphics (8 sheets)")) { + // This would open the full properties panel + } + + ImGui::EndPopup(); + } +} + +void MapPropertiesSystem::DrawPalettesPopup(int current_map, bool& show_custom_bg_color_editor) { + if (ImGui::BeginPopup("PalettesPopup")) { + ImGui::Text("Palette Settings"); + ImGui::Separator(); + + if (gui::InputHexByte("Area Palette", + overworld_->mutable_overworld_map(current_map)->mutable_area_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshMapPalette(); + RefreshOverworldMap(); + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 2) { + if (gui::InputHexByte("Main Palette", + overworld_->mutable_overworld_map(current_map)->mutable_main_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshMapPalette(); + RefreshOverworldMap(); + } + } + + if (gui::InputHexByte("Sprite Palette 1", + overworld_->mutable_overworld_map(current_map)->mutable_sprite_palette(1), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + if (gui::InputHexByte("Sprite Palette 2", + overworld_->mutable_overworld_map(current_map)->mutable_sprite_palette(2), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + ImGui::Separator(); + if (ImGui::Button("Background Color")) { + show_custom_bg_color_editor = !show_custom_bg_color_editor; + } + + ImGui::EndPopup(); + } +} + +void MapPropertiesSystem::DrawOverlaysPopup(int current_map, bool& show_overlay_editor) { + if (ImGui::BeginPopup("OverlaysPopup")) { + ImGui::Text("Overlay Settings"); + ImGui::Separator(); + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 3) { + if (gui::InputHexWord("Subscreen Overlay", + overworld_->mutable_overworld_map(current_map)->mutable_subscreen_overlay(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + ImGui::Separator(); + if (ImGui::Button("Overlay Settings")) { + show_overlay_editor = !show_overlay_editor; + } + } else { + ImGui::Text("Overlays require ZSCustomOverworld v3+"); + } + + ImGui::EndPopup(); + } +} + +void MapPropertiesSystem::DrawPropertiesPopup(int current_map, bool& show_map_properties_panel) { + if (ImGui::BeginPopup("PropertiesPopup")) { + ImGui::Text("Map Properties"); + ImGui::Separator(); + + if (gui::InputHexWord("Message ID", + overworld_->mutable_overworld_map(current_map)->mutable_message_id(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + if (ImGui::Checkbox("Mosaic Effect", + overworld_->mutable_overworld_map(current_map)->mutable_mosaic())) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + static int game_state = 0; // This should be passed in or stored + ImGui::SetNextItemWidth(100.f); + if (ImGui::Combo("Game State", &game_state, kGamePartComboString, 3)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version != 0xFF) { + static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; + int current_area_size = static_cast(overworld_->overworld_map(current_map)->area_size()); + ImGui::SetNextItemWidth(120.f); + if (ImGui::Combo("Area Size", ¤t_area_size, area_size_names, 4)) { + overworld_->mutable_overworld_map(current_map)->SetAreaSize(static_cast(current_area_size)); + RefreshOverworldMap(); + } + } + + ImGui::Separator(); + if (ImGui::Button("Full Properties Panel")) { + show_map_properties_panel = true; + } + + ImGui::EndPopup(); + } +} + +void MapPropertiesSystem::DrawBasicPropertiesTab(int current_map) { + if (BeginTable("BasicProperties", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + + TableNextColumn(); ImGui::Text("Area Graphics"); + TableNextColumn(); + if (gui::InputHexByte("##AreaGfx", + overworld_->mutable_overworld_map(current_map)->mutable_area_graphics(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); ImGui::Text("Area Palette"); + TableNextColumn(); + if (gui::InputHexByte("##AreaPal", + overworld_->mutable_overworld_map(current_map)->mutable_area_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshMapPalette(); + RefreshOverworldMap(); + } + + TableNextColumn(); ImGui::Text("Message ID"); + TableNextColumn(); + if (gui::InputHexWord("##MsgId", + overworld_->mutable_overworld_map(current_map)->mutable_message_id(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); ImGui::Text("Mosaic Effect"); + TableNextColumn(); + if (ImGui::Checkbox("##mosaic", + overworld_->mutable_overworld_map(current_map)->mutable_mosaic())) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + HOVER_HINT("Enable Mosaic effect for the current map"); + + ImGui::EndTable(); + } +} + +void MapPropertiesSystem::DrawSpritePropertiesTab(int current_map) { + if (BeginTable("SpriteProperties", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + + TableNextColumn(); ImGui::Text("Game State"); + TableNextColumn(); + static int game_state = 0; + ImGui::SetNextItemWidth(100.f); + if (ImGui::Combo("##GameState", &game_state, kGamePartComboString, 3)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); ImGui::Text("Sprite Graphics 1"); + TableNextColumn(); + if (gui::InputHexByte("##SprGfx1", + overworld_->mutable_overworld_map(current_map)->mutable_sprite_graphics(1), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); ImGui::Text("Sprite Graphics 2"); + TableNextColumn(); + if (gui::InputHexByte("##SprGfx2", + overworld_->mutable_overworld_map(current_map)->mutable_sprite_graphics(2), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); ImGui::Text("Sprite Palette 1"); + TableNextColumn(); + if (gui::InputHexByte("##SprPal1", + overworld_->mutable_overworld_map(current_map)->mutable_sprite_palette(1), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); ImGui::Text("Sprite Palette 2"); + TableNextColumn(); + if (gui::InputHexByte("##SprPal2", + overworld_->mutable_overworld_map(current_map)->mutable_sprite_palette(2), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + ImGui::EndTable(); + } +} + +void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) { + if (BeginTable("CustomFeatures", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + + TableNextColumn(); ImGui::Text("Area Size"); + TableNextColumn(); + static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; + int current_area_size = static_cast(overworld_->overworld_map(current_map)->area_size()); + ImGui::SetNextItemWidth(120.f); + if (ImGui::Combo("##AreaSize", ¤t_area_size, area_size_names, 4)) { + overworld_->mutable_overworld_map(current_map)->SetAreaSize(static_cast(current_area_size)); + RefreshOverworldMap(); + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 2) { + TableNextColumn(); ImGui::Text("Main Palette"); + TableNextColumn(); + if (gui::InputHexByte("##MainPal", + overworld_->mutable_overworld_map(current_map)->mutable_main_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshMapPalette(); + RefreshOverworldMap(); + } + } + + if (asm_version >= 3) { + TableNextColumn(); ImGui::Text("Animated GFX"); + TableNextColumn(); + if (gui::InputHexByte("##AnimGfx", + overworld_->mutable_overworld_map(current_map)->mutable_animated_gfx(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); ImGui::Text("Subscreen Overlay"); + TableNextColumn(); + if (gui::InputHexWord("##SubOverlay", + overworld_->mutable_overworld_map(current_map)->mutable_subscreen_overlay(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + } + + ImGui::EndTable(); + } +} + +void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) { + ImGui::Text("Custom Tile Graphics (8 sheets per map):"); + Separator(); + + if (BeginTable("TileGraphics", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 80); + + for (int i = 0; i < 4; i++) { + TableNextColumn(); + ImGui::Text("Sheet %d", i); + TableNextColumn(); + if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i).c_str(), + overworld_->mutable_overworld_map(current_map)->mutable_custom_tileset(i), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); + ImGui::Text("Sheet %d", i + 4); + TableNextColumn(); + if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i + 4).c_str(), + overworld_->mutable_overworld_map(current_map)->mutable_custom_tileset(i + 4), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + } + + ImGui::EndTable(); + } +} + +void MapPropertiesSystem::RefreshMapProperties() { + // Implementation would refresh map properties +} + +void MapPropertiesSystem::RefreshOverworldMap() { + // Implementation would refresh the overworld map display +} + +absl::Status MapPropertiesSystem::RefreshMapPalette() { + // Implementation would refresh the map palette + return absl::OkStatus(); +} + +} // namespace editor +} // namespace yaze diff --git a/src/app/editor/overworld/map_properties.h b/src/app/editor/overworld/map_properties.h new file mode 100644 index 00000000..c29d2e98 --- /dev/null +++ b/src/app/editor/overworld/map_properties.h @@ -0,0 +1,62 @@ +#ifndef YAZE_APP_EDITOR_OVERWORLD_MAP_PROPERTIES_H +#define YAZE_APP_EDITOR_OVERWORLD_MAP_PROPERTIES_H + +#include "app/zelda3/overworld/overworld.h" +#include "app/rom.h" +#include "app/gui/canvas.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +class MapPropertiesSystem { + public: + explicit MapPropertiesSystem(zelda3::Overworld* overworld, Rom* rom) + : overworld_(overworld), rom_(rom) {} + + // Main interface methods + void DrawSimplifiedMapSettings(int& current_world, int& current_map, + bool& current_map_lock, bool& show_map_properties_panel, + bool& show_custom_bg_color_editor, bool& show_overlay_editor); + + void DrawMapPropertiesPanel(int current_map, bool& show_map_properties_panel); + + void DrawCustomBackgroundColorEditor(int current_map, bool& show_custom_bg_color_editor); + + void DrawOverlayEditor(int current_map, bool& show_overlay_editor); + + // Context menu integration + void SetupCanvasContextMenu(gui::Canvas& canvas, int current_map, bool current_map_lock, + bool& show_map_properties_panel, bool& show_custom_bg_color_editor, + bool& show_overlay_editor); + + private: + // Property category drawers + void DrawGraphicsPopup(int current_map); + void DrawPalettesPopup(int current_map, bool& show_custom_bg_color_editor); + void DrawOverlaysPopup(int current_map, bool& show_overlay_editor); + void DrawPropertiesPopup(int current_map, bool& show_map_properties_panel); + + // Tab content drawers + void DrawBasicPropertiesTab(int current_map); + void DrawSpritePropertiesTab(int current_map); + void DrawCustomFeaturesTab(int current_map); + void DrawTileGraphicsTab(int current_map); + + // Utility methods + void RefreshMapProperties(); + void RefreshOverworldMap(); + absl::Status RefreshMapPalette(); + + zelda3::Overworld* overworld_; + Rom* rom_; + + // Static constants + static constexpr float kInputFieldSize = 30.f; + static constexpr int kOverworldMapSize = 512; +}; + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_OVERWORLD_MAP_PROPERTIES_H diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 273be4d4..dac9c285 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -43,6 +43,9 @@ void OverworldEditor::Initialize() { gui::zeml::Bind(std::to_address(layout_node_.GetNode("OverworldCanvas")), [this]() { DrawOverworldCanvas(); }); + + // Setup overworld canvas context menu + SetupOverworldCanvasContextMenu(); gui::zeml::Bind( std::to_address(layout_node_.GetNode("OverworldTileSelector")), [this]() { status_ = DrawTileSelector(); }); @@ -250,6 +253,12 @@ void OverworldEditor::DrawToolset() { ImGui::End(); } + if (show_map_properties_panel_) { + ImGui::Begin("Map Properties Panel", &show_map_properties_panel_); + DrawMapPropertiesPanel(); + ImGui::End(); + } + // TODO: Customizable shortcuts for the Overworld Editor if (!ImGui::IsAnyItemActive()) { using enum EditingMode; @@ -492,6 +501,236 @@ void OverworldEditor::DrawCustomOverworldMapSettings() { } } +void OverworldEditor::DrawSimplifiedMapSettings() { + // Enhanced settings table with popup buttons for quick access + if (BeginTable("SimplifiedMapSettings", 8, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0), -1)) { + ImGui::TableSetupColumn("World", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("Area Size", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Lock", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("Graphics", ImGuiTableColumnFlags_WidthFixed, 70); + ImGui::TableSetupColumn("Palettes", ImGuiTableColumnFlags_WidthFixed, 70); + ImGui::TableSetupColumn("Overlays", ImGuiTableColumnFlags_WidthFixed, 70); + ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed, 80); + + TableNextColumn(); + ImGui::SetNextItemWidth(70.f); + if (ImGui::Combo("##world", ¤t_world_, kWorldList.data(), 3)) { + // World changed, update current map if needed + if (current_map_ >= 0x40 && current_world_ == 0) { + current_map_ -= 0x40; + } else if (current_map_ < 0x40 && current_world_ == 1) { + current_map_ += 0x40; + } else if (current_map_ < 0x80 && current_world_ == 2) { + current_map_ += 0x80; + } else if (current_map_ >= 0x80 && current_world_ != 2) { + current_map_ -= 0x80; + } + } + + TableNextColumn(); + ImGui::Text("%d (0x%02X)", current_map_, current_map_); + + TableNextColumn(); + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version != 0xFF) { + static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; + int current_area_size = static_cast(overworld_.overworld_map(current_map_)->area_size()); + ImGui::SetNextItemWidth(90.f); + if (ImGui::Combo("##AreaSize", ¤t_area_size, area_size_names, 4)) { + overworld_.mutable_overworld_map(current_map_)->SetAreaSize(static_cast(current_area_size)); + RefreshOverworldMap(); + } + } else { + ImGui::Text("N/A"); + } + + TableNextColumn(); + if (ImGui::Button(current_map_lock_ ? ICON_MD_LOCK : ICON_MD_LOCK_OPEN, ImVec2(30, 0))) { + current_map_lock_ = !current_map_lock_; + // Refresh context menu to update lock/unlock text + SetupOverworldCanvasContextMenu(); + } + HOVER_HINT(current_map_lock_ ? "Unlock Map" : "Lock Map"); + + TableNextColumn(); + if (ImGui::Button("Graphics", ImVec2(60, 0))) { + ImGui::OpenPopup("GraphicsPopup"); + } + HOVER_HINT("Graphics Settings"); + if (ImGui::BeginPopup("GraphicsPopup")) { + ImGui::Text("Graphics Settings"); + ImGui::Separator(); + + if (gui::InputHexByte("Area Graphics", + overworld_.mutable_overworld_map(current_map_)->mutable_area_graphics(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + if (gui::InputHexByte("Sprite GFX 1", + overworld_.mutable_overworld_map(current_map_)->mutable_sprite_graphics(1), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + if (gui::InputHexByte("Sprite GFX 2", + overworld_.mutable_overworld_map(current_map_)->mutable_sprite_graphics(2), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 3) { + if (gui::InputHexByte("Animated GFX", + overworld_.mutable_overworld_map(current_map_)->mutable_animated_gfx(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + } + + ImGui::Separator(); + if (ImGui::Button("Tile Graphics (8 sheets)")) { + show_map_properties_panel_ = true; + } + + ImGui::EndPopup(); + } + + TableNextColumn(); + if (ImGui::Button("Palettes", ImVec2(60, 0))) { + ImGui::OpenPopup("PalettesPopup"); + } + HOVER_HINT("Palette Settings"); + if (ImGui::BeginPopup("PalettesPopup")) { + ImGui::Text("Palette Settings"); + ImGui::Separator(); + + if (gui::InputHexByte("Area Palette", + overworld_.mutable_overworld_map(current_map_)->mutable_area_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + status_ = RefreshMapPalette(); + RefreshOverworldMap(); + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 2) { + if (gui::InputHexByte("Main Palette", + overworld_.mutable_overworld_map(current_map_)->mutable_main_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + status_ = RefreshMapPalette(); + RefreshOverworldMap(); + } + } + + if (gui::InputHexByte("Sprite Palette 1", + overworld_.mutable_overworld_map(current_map_)->mutable_sprite_palette(1), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + if (gui::InputHexByte("Sprite Palette 2", + overworld_.mutable_overworld_map(current_map_)->mutable_sprite_palette(2), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + ImGui::Separator(); + if (ImGui::Button("Background Color")) { + show_custom_bg_color_editor_ = !show_custom_bg_color_editor_; + } + + ImGui::EndPopup(); + } + + TableNextColumn(); + if (ImGui::Button("Overlays", ImVec2(60, 0))) { + ImGui::OpenPopup("OverlaysPopup"); + } + HOVER_HINT("Overlay Settings"); + if (ImGui::BeginPopup("OverlaysPopup")) { + ImGui::Text("Overlay Settings"); + ImGui::Separator(); + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 3) { + if (gui::InputHexWord("Subscreen Overlay", + overworld_.mutable_overworld_map(current_map_)->mutable_subscreen_overlay(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + ImGui::Separator(); + if (ImGui::Button("Overlay Settings")) { + show_overlay_editor_ = !show_overlay_editor_; + } + } else { + ImGui::Text("Overlays require ZSCustomOverworld v3+"); + } + + ImGui::EndPopup(); + } + + TableNextColumn(); + if (ImGui::Button("Properties", ImVec2(70, 0))) { + ImGui::OpenPopup("PropertiesPopup"); + } + HOVER_HINT("Map Properties"); + if (ImGui::BeginPopup("PropertiesPopup")) { + ImGui::Text("Map Properties"); + ImGui::Separator(); + + if (gui::InputHexWord("Message ID", + overworld_.mutable_overworld_map(current_map_)->mutable_message_id(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + if (ImGui::Checkbox("Mosaic Effect", + overworld_.mutable_overworld_map(current_map_)->mutable_mosaic())) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + ImGui::SetNextItemWidth(100.f); + if (ImGui::Combo("Game State", &game_state_, kGamePartComboString.data(), 3)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version != 0xFF) { + static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; + int current_area_size = static_cast(overworld_.overworld_map(current_map_)->area_size()); + ImGui::SetNextItemWidth(120.f); + if (ImGui::Combo("Area Size", ¤t_area_size, area_size_names, 4)) { + overworld_.mutable_overworld_map(current_map_)->SetAreaSize(static_cast(current_area_size)); + RefreshOverworldMap(); + } + } + + ImGui::Separator(); + if (ImGui::Button("Full Properties Panel")) { + show_map_properties_panel_ = true; + } + + ImGui::EndPopup(); + } + + ImGui::EndTable(); + } +} + void OverworldEditor::DrawOverworldMaps() { int xx = 0; int yy = 0; @@ -767,17 +1006,20 @@ absl::Status OverworldEditor::CheckForCurrentMap() { int map_y = (mouse_position.y - canvas_zero_point.y) / kOverworldMapSize; // Calculate the index of the map in the `maps_bmp_` vector - current_map_ = map_x + map_y * 8; + int hovered_map = map_x + map_y * 8; if (current_world_ == 1) { - current_map_ += 0x40; + hovered_map += 0x40; } else if (current_world_ == 2) { - current_map_ += 0x80; + hovered_map += 0x80; } - const int current_highlighted_map = current_map_; - + + // Only update current_map_ if not locked if (!current_map_lock_) { + current_map_ = hovered_map; current_parent_ = overworld_.overworld_map(current_map_)->parent(); } + + const int current_highlighted_map = current_map_; if (overworld_.overworld_map(current_map_)->is_large_map() || overworld_.overworld_map(current_map_)->large_index() != 0) { @@ -830,7 +1072,7 @@ void OverworldEditor::CheckForMousePan() { void OverworldEditor::DrawOverworldCanvas() { if (all_gfx_loaded_) { if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) { - DrawCustomOverworldMapSettings(); + DrawSimplifiedMapSettings(); } else { DrawOverworldMapSettings(); } @@ -847,8 +1089,8 @@ void OverworldEditor::DrawOverworldCanvas() { ow_map_canvas_.DrawContextMenu(); } else { ow_map_canvas_.set_draggable(false); - // Always show our custom overworld context menu - DrawOverworldContextMenu(); + // Handle map interaction with middle-click instead of right-click + HandleMapInteraction(); } if (overworld_.is_loaded()) { @@ -1627,6 +1869,367 @@ void OverworldEditor::DrawOverworldContextMenu() { } } +void OverworldEditor::HandleMapInteraction() { + // Handle middle-click for map interaction instead of right-click + if (ImGui::IsMouseClicked(ImGuiMouseButton_Middle) && ImGui::IsItemHovered()) { + // Get the current map from mouse position + auto mouse_position = ow_map_canvas_.drawn_tile_position(); + int map_x = mouse_position.x / kOverworldMapSize; + int map_y = mouse_position.y / kOverworldMapSize; + int hovered_map = map_x + map_y * 8; + if (current_world_ == 1) { + hovered_map += 0x40; + } else if (current_world_ == 2) { + hovered_map += 0x80; + } + + // Only interact if we're hovering over a valid map + if (hovered_map >= 0 && hovered_map < 0xA0) { + // Toggle map lock or open properties panel + if (current_map_lock_ && current_map_ == hovered_map) { + current_map_lock_ = false; + } else { + current_map_lock_ = true; + current_map_ = hovered_map; + show_map_properties_panel_ = true; + } + } + } + + // Handle double-click to open properties panel + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && ImGui::IsItemHovered()) { + show_map_properties_panel_ = true; + } +} + +void OverworldEditor::DrawMapPropertiesPanel() { + if (!overworld_.is_loaded()) { + Text("No overworld loaded"); + return; + } + + // Header with map info and lock status + ImGui::BeginGroup(); + if (current_map_lock_) { + PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); + Text("Map Locked: %d (0x%02X)", current_map_, current_map_); + PopStyleColor(); + } else { + Text("Current Map: %d (0x%02X)", current_map_, current_map_); + } + + SameLine(); + if (Button(current_map_lock_ ? "Unlock" : "Lock")) { + current_map_lock_ = !current_map_lock_; + } + ImGui::EndGroup(); + + Separator(); + + // Create tabs for different property categories + if (BeginTabBar("MapPropertiesTabs", ImGuiTabBarFlags_FittingPolicyScroll)) { + + // Basic Properties Tab + if (BeginTabItem("Basic Properties")) { + if (BeginTable("BasicProperties", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + + TableNextColumn(); Text("World"); + TableNextColumn(); + ImGui::SetNextItemWidth(100.f); + if (ImGui::Combo("##world", ¤t_world_, kWorldList.data(), 3)) { + // Update current map based on world change + if (current_map_ >= 0x40 && current_world_ == 0) { + current_map_ -= 0x40; + } else if (current_map_ < 0x40 && current_world_ == 1) { + current_map_ += 0x40; + } else if (current_map_ < 0x80 && current_world_ == 2) { + current_map_ += 0x80; + } else if (current_map_ >= 0x80 && current_world_ != 2) { + current_map_ -= 0x80; + } + } + + TableNextColumn(); Text("Area Graphics"); + TableNextColumn(); + if (gui::InputHexByte("##AreaGfx", + overworld_.mutable_overworld_map(current_map_)->mutable_area_graphics(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); Text("Area Palette"); + TableNextColumn(); + if (gui::InputHexByte("##AreaPal", + overworld_.mutable_overworld_map(current_map_)->mutable_area_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + status_ = RefreshMapPalette(); + RefreshOverworldMap(); + } + + TableNextColumn(); Text("Message ID"); + TableNextColumn(); + if (gui::InputHexWord("##MsgId", + overworld_.mutable_overworld_map(current_map_)->mutable_message_id(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); Text("Mosaic Effect"); + TableNextColumn(); + if (ImGui::Checkbox("##mosaic", + overworld_.mutable_overworld_map(current_map_)->mutable_mosaic())) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + HOVER_HINT("Enable Mosaic effect for the current map"); + + ImGui::EndTable(); + } + EndTabItem(); + } + + // Sprite Properties Tab + if (BeginTabItem("Sprite Properties")) { + if (BeginTable("SpriteProperties", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + + TableNextColumn(); Text("Game State"); + TableNextColumn(); + ImGui::SetNextItemWidth(100.f); + if (ImGui::Combo("##GameState", &game_state_, kGamePartComboString.data(), 3)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); Text("Sprite Graphics 1"); + TableNextColumn(); + if (gui::InputHexByte("##SprGfx1", + overworld_.mutable_overworld_map(current_map_)->mutable_sprite_graphics(1), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); Text("Sprite Graphics 2"); + TableNextColumn(); + if (gui::InputHexByte("##SprGfx2", + overworld_.mutable_overworld_map(current_map_)->mutable_sprite_graphics(2), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); Text("Sprite Palette 1"); + TableNextColumn(); + if (gui::InputHexByte("##SprPal1", + overworld_.mutable_overworld_map(current_map_)->mutable_sprite_palette(1), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); Text("Sprite Palette 2"); + TableNextColumn(); + if (gui::InputHexByte("##SprPal2", + overworld_.mutable_overworld_map(current_map_)->mutable_sprite_palette(2), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + ImGui::EndTable(); + } + EndTabItem(); + } + + // Custom Overworld Features Tab + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version != 0xFF && BeginTabItem("Custom Features")) { + if (BeginTable("CustomFeatures", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + + TableNextColumn(); Text("Area Size"); + TableNextColumn(); + static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; + int current_area_size = static_cast(overworld_.overworld_map(current_map_)->area_size()); + ImGui::SetNextItemWidth(120.f); + if (ImGui::Combo("##AreaSize", ¤t_area_size, area_size_names, 4)) { + overworld_.mutable_overworld_map(current_map_)->SetAreaSize(static_cast(current_area_size)); + RefreshOverworldMap(); + } + + if (asm_version >= 2) { + TableNextColumn(); Text("Main Palette"); + TableNextColumn(); + if (gui::InputHexByte("##MainPal", + overworld_.mutable_overworld_map(current_map_)->mutable_main_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + status_ = RefreshMapPalette(); + RefreshOverworldMap(); + } + } + + if (asm_version >= 3) { + TableNextColumn(); Text("Animated GFX"); + TableNextColumn(); + if (gui::InputHexByte("##AnimGfx", + overworld_.mutable_overworld_map(current_map_)->mutable_animated_gfx(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); Text("Subscreen Overlay"); + TableNextColumn(); + if (gui::InputHexWord("##SubOverlay", + overworld_.mutable_overworld_map(current_map_)->mutable_subscreen_overlay(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + } + + ImGui::EndTable(); + } + + Separator(); + + // Quick action buttons + ImGui::BeginGroup(); + if (Button("Custom Background Color")) { + show_custom_bg_color_editor_ = !show_custom_bg_color_editor_; + } + SameLine(); + if (Button("Overlay Settings")) { + show_overlay_editor_ = !show_overlay_editor_; + } + ImGui::EndGroup(); + + EndTabItem(); + } + + // Tile Graphics Tab + if (BeginTabItem("Tile Graphics")) { + Text("Custom Tile Graphics (8 sheets per map):"); + Separator(); + + if (BeginTable("TileGraphics", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 80); + + for (int i = 0; i < 4; i++) { + TableNextColumn(); + Text("Sheet %d", i); + TableNextColumn(); + if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i).c_str(), + overworld_.mutable_overworld_map(current_map_)->mutable_custom_tileset(i), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + TableNextColumn(); + Text("Sheet %d", i + 4); + TableNextColumn(); + if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i + 4).c_str(), + overworld_.mutable_overworld_map(current_map_)->mutable_custom_tileset(i + 4), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + } + + ImGui::EndTable(); + } + EndTabItem(); + } + + EndTabBar(); + } +} + +void OverworldEditor::SetupOverworldCanvasContextMenu() { + // Clear any existing context menu items + ow_map_canvas_.ClearContextMenuItems(); + + // Add overworld-specific context menu items + gui::Canvas::ContextMenuItem lock_item; + lock_item.label = current_map_lock_ ? "Unlock Map" : "Lock to This Map"; + lock_item.callback = [this]() { + current_map_lock_ = !current_map_lock_; + if (current_map_lock_) { + // Get the current map from mouse position + auto mouse_position = ow_map_canvas_.drawn_tile_position(); + int map_x = mouse_position.x / kOverworldMapSize; + int map_y = mouse_position.y / kOverworldMapSize; + int hovered_map = map_x + map_y * 8; + if (current_world_ == 1) { + hovered_map += 0x40; + } else if (current_world_ == 2) { + hovered_map += 0x80; + } + if (hovered_map >= 0 && hovered_map < 0xA0) { + current_map_ = hovered_map; + } + } + }; + ow_map_canvas_.AddContextMenuItem(lock_item); + + // Map Properties + gui::Canvas::ContextMenuItem properties_item; + properties_item.label = "Map Properties"; + properties_item.callback = [this]() { + show_map_properties_panel_ = true; + }; + ow_map_canvas_.AddContextMenuItem(properties_item); + + // Custom overworld features (only show if v3+) + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 3 && asm_version != 0xFF) { + // Custom Background Color + gui::Canvas::ContextMenuItem bg_color_item; + bg_color_item.label = "Custom Background Color"; + bg_color_item.callback = [this]() { + show_custom_bg_color_editor_ = true; + }; + ow_map_canvas_.AddContextMenuItem(bg_color_item); + + // Overlay Settings + gui::Canvas::ContextMenuItem overlay_item; + overlay_item.label = "Overlay Settings"; + overlay_item.callback = [this]() { + show_overlay_editor_ = true; + }; + ow_map_canvas_.AddContextMenuItem(overlay_item); + } + + // Canvas controls + gui::Canvas::ContextMenuItem reset_pos_item; + reset_pos_item.label = "Reset Canvas Position"; + reset_pos_item.callback = [this]() { + ow_map_canvas_.set_scrolling(ImVec2(0, 0)); + }; + ow_map_canvas_.AddContextMenuItem(reset_pos_item); + + gui::Canvas::ContextMenuItem zoom_fit_item; + zoom_fit_item.label = "Zoom to Fit"; + zoom_fit_item.callback = [this]() { + ow_map_canvas_.set_global_scale(1.0f); + ow_map_canvas_.set_scrolling(ImVec2(0, 0)); + }; + ow_map_canvas_.AddContextMenuItem(zoom_fit_item); +} + void OverworldEditor::DrawOverworldProperties() { static bool init_properties = false; diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index e4dc3362..1fdb705d 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -162,6 +162,10 @@ class OverworldEditor : public Editor, public gfx::GfxContext { void DrawOverlayEditor(); void DrawMapLockControls(); void DrawOverworldContextMenu(); + void DrawSimplifiedMapSettings(); + void DrawMapPropertiesPanel(); + void HandleMapInteraction(); + void SetupOverworldCanvasContextMenu(); absl::Status UpdateUsageStats(); void DrawUsageGrid(); @@ -222,6 +226,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { bool show_custom_bg_color_editor_ = false; bool show_overlay_editor_ = false; bool use_area_specific_bg_color_ = false; + bool show_map_properties_panel_ = false; gfx::Tilemap tile16_blockset_; diff --git a/src/app/gui/canvas.cc b/src/app/gui/canvas.cc index 8002d451..c44e8861 100644 --- a/src/app/gui/canvas.cc +++ b/src/app/gui/canvas.cc @@ -123,6 +123,17 @@ void Canvas::DrawContextMenu() { // Contents of the Context Menu if (ImGui::BeginPopup(context_id_.c_str())) { + // Draw custom context menu items first + for (const auto& item : context_menu_items_) { + DrawContextMenuItem(item); + } + + // Add separator if there are custom items + if (!context_menu_items_.empty()) { + ImGui::Separator(); + } + + // Default canvas menu items if (MenuItem("Reset Position", nullptr, false)) { scrolling_.x = 0; scrolling_.y = 0; @@ -216,6 +227,39 @@ void Canvas::DrawContextMenu() { } } +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::AddContextMenuItem(const ContextMenuItem& item) { + context_menu_items_.push_back(item); +} + +void Canvas::ClearContextMenuItems() { + context_menu_items_.clear(); +} + bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) { const ImGuiIO &io = GetIO(); const bool is_hovered = IsItemHovered(); diff --git a/src/app/gui/canvas.h b/src/app/gui/canvas.h index c2ff0d49..362fdfd7 100644 --- a/src/app/gui/canvas.h +++ b/src/app/gui/canvas.h @@ -91,37 +91,39 @@ class Canvas { // Context Menu refers to what happens when the right mouse button is pressed // 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 callback; + std::function enabled_condition = []() { return true; }; + std::vector subitems; + }; + + void AddContextMenuItem(const ContextMenuItem& item); + void ClearContextMenuItems(); + void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; } + + private: + void DrawContextMenuItem(const ContextMenuItem& item); // Tile painter shows a preview of the currently selected tile // and allows the user to left click to paint the tile or right // click to select a new tile to paint with. - bool DrawTilePainter(const Bitmap &bitmap, int size, float scale = 1.0f); - bool DrawSolidTilePainter(const ImVec4 &color, int size); + // (Moved to public section) // Draws a tile on the canvas at the specified position + // (Moved to public section) + + // These methods are now public - see public section above + + public: + // Tile painter methods + bool DrawTilePainter(const Bitmap &bitmap, int size, float scale = 1.0f); + bool DrawSolidTilePainter(const ImVec4 &color, int size); void DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, ImVec4 color); - - // Dictates which tile is currently selected based on what the user clicks - // in the canvas window. Represented and split apart into a grid of tiles. - bool DrawTileSelector(int size, int size_y = 0); - - // Draws the selection rectangle when the user is selecting multiple tiles - void DrawSelectRect(int current_map, int tile_size = 0x10, - float scale = 1.0f); - - // Draws the contents of the Bitmap image to the Canvas - void DrawBitmap(Bitmap &bitmap, int border_offset, float scale); - void DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, - float scale = 1.0f, int alpha = 255); - void DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size, - ImVec2 src_pos, ImVec2 src_size); - void DrawBitmapTable(const BitmapTable &gfx_bin); - - void DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, - int tile_size, float scale = 1.0f); - - bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile); - + void DrawOutline(int x, int y, int w, int h); void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color); void DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color); @@ -130,8 +132,6 @@ class Canvas { void DrawText(std::string text, int x, int y); void DrawGridLines(float grid_step); - void DrawGrid(float grid_step = 64.0f, int tile_id_offset = 8); - void DrawOverlay(); // last void DrawInfoGrid(float grid_step = 64.0f, int tile_id_offset = 8, int label_id = 0); @@ -150,10 +150,6 @@ class Canvas { } return tile_id; } - void SetCanvasSize(ImVec2 canvas_size) { - canvas_sz_ = canvas_size; - custom_canvas_size_ = true; - } void DrawCustomHighlight(float grid_step); bool IsMouseHovering() const { return is_hovered_; } void ZoomIn() { global_scale_ += 0.25f; } @@ -169,13 +165,36 @@ class Canvas { auto drawn_tile_position() const { return drawn_tile_pos_; } auto canvas_size() const { return canvas_sz_; } void set_global_scale(float scale) { global_scale_ = scale; } + void set_draggable(bool draggable) { draggable_ = draggable; } + + // Public accessors for commonly used private members + auto select_rect_active() const { return select_rect_active_; } + auto selected_tiles() const { return selected_tiles_; } + auto selected_tile_pos() const { return selected_tile_pos_; } + void set_selected_tile_pos(ImVec2 pos) { selected_tile_pos_ = pos; } + + // Public methods for commonly used private methods + void SetCanvasSize(ImVec2 canvas_size) { canvas_sz_ = canvas_size; custom_canvas_size_ = true; } auto global_scale() const { return global_scale_; } auto custom_labels_enabled() { return &enable_custom_labels_; } auto custom_step() const { return custom_step_; } auto width() const { return canvas_sz_.x; } auto height() const { return canvas_sz_.y; } - auto set_draggable(bool value) { draggable_ = value; } + + // Public accessors for methods that need to be accessed externally auto canvas_id() const { return canvas_id_; } + + // Public methods for drawing operations + void DrawBitmap(Bitmap &bitmap, int border_offset, float scale); + void DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale = 1.0f, int alpha = 255); + void DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size); + void DrawBitmapTable(const BitmapTable &gfx_bin); + void DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, int tile_size, float scale = 1.0f); + bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile); + void DrawSelectRect(int current_map, int tile_size = 0x10, float scale = 1.0f); + bool DrawTileSelector(int size, int size_y = 0); + void DrawGrid(float grid_step = 64.0f, int tile_id_offset = 8); + void DrawOverlay(); auto labels(int i) { if (i >= labels_.size()) { labels_.push_back(ImVector()); @@ -197,12 +216,7 @@ class Canvas { auto set_current_labels(int i) { current_labels_ = i; } auto set_highlight_tile_id(int i) { highlight_tile_id = i; } - auto selected_tiles() const { return selected_tiles_; } auto mutable_selected_tiles() { return &selected_tiles_; } - - auto selected_tile_pos() const { return selected_tile_pos_; } - auto set_selected_tile_pos(ImVec2 pos) { selected_tile_pos_ = pos; } - bool select_rect_active() const { return select_rect_active_; } auto selected_points() const { return selected_points_; } auto hover_mouse_pos() const { return mouse_pos_in_canvas_; } @@ -221,6 +235,10 @@ class Canvas { bool select_rect_active_ = false; bool refresh_graphics_ = false; + // Context menu system + std::vector context_menu_items_; + bool context_menu_enabled_ = true; + float custom_step_ = 0.0f; float global_scale_ = 1.0f;