10#include "absl/strings/str_format.h"
13#include "imgui/imgui.h"
33 std::shared_ptr<zelda3::DungeonObjectEditor> object_editor)
34 : renderer_(renderer),
36 canvas_viewer_(canvas_viewer),
37 object_selector_(rom),
38 object_editor_(object_editor) {
90 auto indices = interaction.GetSelectedObjectIndices();
95 for (
size_t idx : indices) {
105 if (ImGui::CollapsingHeader(
ICON_MD_DOOR_FRONT " Doors", ImGuiTreeNodeFlags_DefaultOpen)) {
112 float available_height = ImGui::GetContentRegionAvail().y;
114 float reserved_height = 60.0f;
117 reserved_height += 200.0f;
119 float browser_height = std::max(150.0f, available_height - reserved_height);
121 ImGui::BeginChild(
"ObjectBrowserRegion", ImVec2(0, browser_height),
true);
141 ImGui::TextColored(theme.status_warning,
151 " Selection Mode - Click to select, drag to multi-select");
152 ImGui::TextColored(theme.text_secondary_gray,
ICON_MD_MENU
153 " Right-click the canvas for Cut/Copy/Paste options");
163 bool preview_open = ImGui::CollapsingHeader(
ICON_MD_MONITOR " Preview");
167 ImGui::PushID(
"PreviewSection");
194 static constexpr std::array<zelda3::DoorType, 20> kDoorTypes = {{
219 ImGui::TextColored(theme.status_warning,
235 constexpr float kPreviewSize = 32.0f;
236 constexpr int kItemsPerRow = 5;
237 float panel_width = ImGui::GetContentRegionAvail().x;
238 int items_per_row = std::max(1,
static_cast<int>(panel_width / (kPreviewSize + 8)));
240 ImGui::BeginChild(
"##DoorTypeGrid", ImVec2(0, 80),
true, ImGuiWindowFlags_HorizontalScrollbar);
243 for (
size_t i = 0; i < kDoorTypes.size(); ++i) {
244 auto door_type = kDoorTypes[i];
247 ImGui::PushID(
static_cast<int>(i));
252 int type_val =
static_cast<int>(door_type);
253 if (type_val <= 0x12) {
254 button_color = ImVec4(0.3f, 0.5f, 0.7f, 1.0f);
255 }
else if (type_val <= 0x1E) {
256 button_color = ImVec4(0.7f, 0.5f, 0.3f, 1.0f);
258 button_color = ImVec4(0.5f, 0.7f, 0.3f, 1.0f);
262 button_color.x += 0.2f;
263 button_color.y += 0.2f;
264 button_color.z += 0.2f;
267 ImGui::PushStyleColor(ImGuiCol_Button, button_color);
268 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
269 ImVec4(button_color.x + 0.1f, button_color.y + 0.1f, button_color.z + 0.1f, 1.0f));
270 ImGui::PushStyleColor(ImGuiCol_ButtonActive,
271 ImVec4(button_color.x + 0.2f, button_color.y + 0.2f, button_color.z + 0.2f, 1.0f));
274 std::string label = absl::StrFormat(
"%02X", type_val);
275 if (ImGui::Button(label.c_str(), ImVec2(kPreviewSize, kPreviewSize))) {
284 ImGui::PopStyleColor(3);
287 if (ImGui::IsItemHovered()) {
288 ImGui::SetTooltip(
"%s (0x%02X)\nClick to select for placement",
294 ImVec2 min = ImGui::GetItemRectMin();
295 ImVec2 max = ImGui::GetItemRectMax();
296 ImGui::GetWindowDrawList()->AddRect(
297 min, max, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
303 if (col < items_per_row && i < kDoorTypes.size() - 1) {
316 const auto& doors = room.GetDoors();
318 if (!doors.empty()) {
319 ImGui::Text(
ICON_MD_LIST " Room Doors (%zu):", doors.size());
321 ImGui::BeginChild(
"##DoorList", ImVec2(0, 80),
true);
322 for (
size_t i = 0; i < doors.size(); ++i) {
323 const auto& door = doors[i];
324 auto [tile_x, tile_y] = door.GetTileCoords();
326 ImGui::PushID(
static_cast<int>(i));
331 ImGui::Text(
"[%zu] %s (%s)", i, type_name.c_str(), dir_name.c_str());
333 ImGui::TextColored(theme.text_secondary_gray,
"@ (%d,%d)", tile_x, tile_y);
338 mutable_room.RemoveDoor(i);
345 ImGui::TextColored(theme.text_secondary_gray,
354 ImGui::TextColored(theme.text_secondary_gray,
357 "Uses SNES emulation to render objects accurately.\n"
358 "May impact performance.");
362 ImGui::BeginChild(
"##EmulatorPreviewRegion", ImVec2(0, 260),
true);
375 if (!selected.empty()) {
376 ImGui::TextColored(theme.status_success,
380 if (selected.size() == 1) {
383 if (selected[0] < objects.size()) {
384 const auto& obj = objects[selected[0]];
385 ImGui::Text(
"Object #%zu (ID: 0x%02X)", selected[0], obj.id_);
386 ImGui::TextColored(theme.text_secondary_gray,
387 " Position: (%d, %d) Size: 0x%02X Layer: %s",
388 obj.x_, obj.y_, obj.size_,
394 ImGui::Text(
"1 object");
397 ImGui::Text(
"%zu objects", selected.size());
399 ImGui::TextColored(theme.text_secondary_gray,
400 "(Shift+click to add, Ctrl+click to toggle)");
408 ImGui::TextColored(theme.text_info,
ICON_MD_INFO " Current:");
414 ImGui::Text(
"Layer: %s",
453 if (!room.IsLoaded()) {
454 room.LoadRoomGraphics(room.blockset);
467 if (preview_obj.
tiles().empty()) {
472 const uint8_t* gfx_data = room.get_gfx_buffer().data();
478 if (game_data && !game_data->palette_groups.dungeon_main.empty()) {
480 palette_group = game_data->palette_groups.dungeon_main;
482 std::vector<SDL_Color> colors(256);
483 size_t color_index = 0;
484 for (
size_t pal_idx = 0; pal_idx < palette_group.
size() && color_index < 256; ++pal_idx) {
485 const auto& pal = palette_group[pal_idx];
486 for (
size_t i = 0; i < pal.size() && color_index < 256; ++i) {
487 ImVec4 rgb = pal[i].rgb();
488 colors[color_index++] = {
489 static_cast<Uint8
>(rgb.x),
490 static_cast<Uint8
>(rgb.y),
491 static_cast<Uint8
>(rgb.z),
496 colors[255] = {0, 0, 0, 0};
497 bitmap.SetPalette(colors);
498 if (bitmap.surface()) {
499 SDL_SetColorKey(bitmap.surface(), SDL_TRUE, 255);
500 SDL_SetSurfaceBlendMode(bitmap.surface(), SDL_BLENDMODE_BLEND);
512 if (bitmap.modified() && bitmap.surface() && bitmap.mutable_data().size() > 0) {
513 SDL_LockSurface(bitmap.surface());
514 size_t surface_size = bitmap.surface()->h * bitmap.surface()->pitch;
515 size_t data_size = bitmap.mutable_data().size();
516 if (surface_size >= data_size) {
517 memcpy(bitmap.surface()->pixels, bitmap.mutable_data().data(), data_size);
519 SDL_UnlockSurface(bitmap.surface());
543 ImGui::PushStyleColor(
544 ImGuiCol_Header, ImVec4(0.15f, 0.25f, 0.35f, 1.0f));
545 ImGui::PushStyleColor(ImGuiCol_HeaderHovered,
546 ImVec4(0.20f, 0.30f, 0.40f, 1.0f));
548 bool header_open = ImGui::CollapsingHeader(
553 ImGuiTreeNodeFlags_DefaultOpen);
555 ImGui::PopStyleColor(2);
558 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 6));
561 if (ImGui::BeginTable(
"StaticEditorLayout", 2,
562 ImGuiTableFlags_BordersInnerV)) {
563 ImGui::TableSetupColumn(
"Info", ImGuiTableColumnFlags_WidthFixed, 200);
564 ImGui::TableSetupColumn(
"Preview", ImGuiTableColumnFlags_WidthStretch);
566 ImGui::TableNextRow();
569 ImGui::TableNextColumn();
572 ImGui::TextColored(theme.text_info,
ICON_MD_TAG " Object ID");
580 ImGui::TextColored(theme.text_info,
ICON_MD_BRUSH " Draw Routine");
592 ImGui::Text(
"Orientation: %s",
597 ImGui::TextColored(theme.status_warning,
ICON_MD_LAYERS " Both BG");
607 ImGui::SetClipboardText(
610 if (ImGui::IsItemHovered()) {
611 ImGui::SetTooltip(
"Copy object ID to clipboard");
614 if (ImGui::Button(
ICON_MD_CODE " Export ASM", ImVec2(-1, 0))) {
617 if (ImGui::IsItemHovered()) {
618 ImGui::SetTooltip(
"Export object draw routine as ASM (Phase 5)");
624 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.2f, 0.2f, 1.0f));
625 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
626 ImVec4(0.6f, 0.3f, 0.3f, 1.0f));
630 ImGui::PopStyleColor(2);
634 ImGui::TableNextColumn();
636 ImGui::TextColored(theme.text_secondary_gray,
"Preview:");
663 ImVec2(24, 56),
"No preview available",
664 ImGui::GetColorU32(theme.text_secondary_gray));
670 ImGui::TextColored(theme.text_secondary_gray,
ICON_MD_INFO
671 " Double-click objects in browser\n"
672 "to view their draw routine info.");
678 ImGui::PopStyleVar();
687 if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
691 const ImGuiIO& io = ImGui::GetIO();
694 if (ImGui::IsKeyPressed(ImGuiKey_A) && io.KeyCtrl && !io.KeyShift) {
699 if (ImGui::IsKeyPressed(ImGuiKey_A) && io.KeyCtrl && io.KeyShift) {
704 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
709 if (ImGui::IsKeyPressed(ImGuiKey_D) && io.KeyCtrl) {
714 if (ImGui::IsKeyPressed(ImGuiKey_C) && io.KeyCtrl) {
719 if (ImGui::IsKeyPressed(ImGuiKey_V) && io.KeyCtrl) {
724 if (ImGui::IsKeyPressed(ImGuiKey_Z) && io.KeyCtrl && !io.KeyShift) {
731 if ((ImGui::IsKeyPressed(ImGuiKey_Z) && io.KeyCtrl && io.KeyShift) ||
732 (ImGui::IsKeyPressed(ImGuiKey_Y) && io.KeyCtrl)) {
739 if (ImGui::IsKeyPressed(ImGuiKey_G) && !io.KeyCtrl) {
744 if (ImGui::IsKeyPressed(ImGuiKey_I) && !io.KeyCtrl) {
751 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
753 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow))
755 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
757 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
760 if (dx != 0 || dy != 0) {
766 if (ImGui::IsKeyPressed(ImGuiKey_Tab) && !io.KeyCtrl) {
771 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
795 std::vector<size_t> all_indices;
797 for (
size_t i = 0; i < objects.size(); ++i) {
798 all_indices.push_back(i);
817 if (selected.empty())
821 if (selected.size() > 5) {
836 if (selected.empty())
840 std::vector<size_t> sorted_indices(selected.begin(), selected.end());
841 std::sort(sorted_indices.rbegin(), sorted_indices.rend());
843 for (
size_t idx : sorted_indices) {
847 interaction.ClearSelection();
857 if (selected.empty())
860 std::vector<size_t> new_indices;
862 for (
size_t idx : selected) {
864 if (new_idx.has_value()) {
865 new_indices.push_back(*new_idx);
869 interaction.SetSelectedObjects(new_indices);
879 if (selected.empty())
891 if (!new_indices.empty()) {
903 if (selected.empty())
906 for (
size_t idx : selected) {
919 size_t total_objects = objects.size();
920 if (total_objects == 0)
923 size_t current_idx = selected.empty() ? 0 : selected.front();
924 size_t next_idx = (current_idx + direction + total_objects) % total_objects;
926 interaction.SetSelectedObjects({next_idx});
934 if (index >= objects.size())
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
DungeonObjectInteraction & object_interaction()
void ClearPreviewObject()
void SetPreviewObject(const zelda3::RoomObject &object)
void SetObjectInteractionEnabled(bool enabled)
void SetSelectedObjects(const std::vector< size_t > &indices)
std::vector< size_t > GetSelectedObjectIndices() const
void SetSelectionChangeCallback(std::function< void()> callback)
size_t GetSelectionCount() const
bool IsObjectLoaded() const
void SetDoorPlacementMode(bool enabled, zelda3::DoorType type=zelda3::DoorType::NormalDoor)
void SetObjectDoubleClickCallback(std::function< void(int)> callback)
zelda3::GameData * game_data() const
void SelectObject(int obj_id)
std::array< zelda3::Room, 0x128 > * get_rooms()
void SetStaticEditorObjectId(int obj_id)
void DrawObjectAssetBrowser()
void SetObjectSelectedCallback(std::function< void(const zelda3::RoomObject &)> callback)
DungeonCanvasViewer * canvas_viewer_
void DrawStaticObjectEditor()
zelda3::RoomObject preview_object_
bool show_delete_confirmation_modal_
bool show_emulator_preview_
void SelectObject(int obj_id)
gui::Canvas static_preview_canvas_
ObjectEditorPanel(gfx::IRenderer *renderer, Rom *rom, DungeonCanvasViewer *canvas_viewer, std::shared_ptr< zelda3::DungeonObjectEditor > object_editor=nullptr)
void DeleteSelectedObjects()
void OnSelectionChanged()
void DrawObjectSelector()
size_t cached_selection_count_
DungeonObjectSelector object_selector_
std::unique_ptr< zelda3::ObjectParser > object_parser_
void DeselectAllObjects()
void NudgeSelectedObjects(int dx, int dy)
bool static_preview_rendered_
void DrawSelectedObjectInfo()
zelda3::DoorType selected_door_type_
gui::DungeonObjectEmulatorPreview emulator_preview_
void CloseStaticObjectEditor()
gfx::IRenderer * renderer_
bool door_placement_mode_
bool selection_callbacks_setup_
void ScrollToObject(size_t index)
gfx::BackgroundBuffer static_preview_buffer_
void OpenStaticObjectEditor(int object_id)
void Draw(bool *p_open) override
Draw the panel content.
std::shared_ptr< zelda3::DungeonObjectEditor > object_editor_
void CycleObjectSelection(int direction)
void DrawEmulatorPreview()
void CopySelectedObjects()
void SetupSelectionCallbacks()
void SetAgentOptimizedLayout(bool enabled)
int static_editor_object_id_
void DuplicateSelectedObjects()
zelda3::ObjectDrawInfo static_editor_draw_info_
void HandleKeyboardShortcuts()
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
void ProcessTextureQueue(IRenderer *renderer)
void EnsureBitmapInitialized()
Defines an abstract interface for all rendering operations.
void AddTextAt(ImVec2 local_pos, const std::string &text, uint32_t color)
void Initialize(gfx::IRenderer *renderer, Rom *rom, zelda3::GameData *game_data=nullptr, emu::render::EmulatorRenderService *render_service=nullptr)
Draws dungeon objects to background buffers using game patterns.
void InitializeDrawRoutines()
Initialize draw routine registry Must be called before drawing objects.
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw a room object to background buffers.
const std::vector< gfx::TileInfo > & tiles() const
#define ICON_MD_GRID_VIEW
#define ICON_MD_CONSTRUCTION
#define ICON_MD_DOOR_FRONT
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_CONTENT_COPY
#define ICON_MD_ADD_CIRCLE
const AgentUITheme & GetTheme()
void EndCanvas(Canvas &canvas)
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
void HelpMarker(const char *desc)
bool RenderPreviewPanel(const CanvasRuntime &rt, gfx::Bitmap &bmp, const PreviewPanelOpts &opts)
@ FancyDungeonExit
Fancy dungeon exit.
@ SmallKeyDoor
Small key door.
@ SmallKeyStairsDown
Small key stairs (downwards)
@ SmallKeyStairsUp
Small key stairs (upwards)
@ DungeonSwapMarker
Dungeon swap marker.
@ NormalDoor
Normal door (upper layer)
@ BombableDoor
Bombable door.
@ LayerSwapMarker
Layer swap marker.
@ ExplodingWall
Exploding wall.
@ TopSidedShutter
Top-sided shutter door.
@ NormalDoorLower
Normal door (lower layer)
@ BottomSidedShutter
Bottom-sided shutter door.
@ CurtainDoor
Curtain door.
@ WaterfallDoor
Waterfall door.
@ BigKeyDoor
Big key door.
@ EyeWatchDoor
Eye watch door.
@ DoubleSidedShutter
Double sided shutter door.
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
Represents a group of palettes.
std::optional< float > grid_step