yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
object_editor_panel.cc
Go to the documentation of this file.
1// Related header
3#include <algorithm>
4#include <cstddef>
5#include <cstdint>
6#include <memory>
7#include <vector>
8
9// Third-party library headers
10#include "absl/strings/str_format.h"
13#include "imgui/imgui.h"
14
15// Project headers
19#include "app/gui/core/icons.h"
21#include "rom/rom.h"
27
28namespace yaze {
29namespace editor {
30
32 gfx::IRenderer* renderer, Rom* rom, DungeonCanvasViewer* canvas_viewer,
33 std::shared_ptr<zelda3::DungeonObjectEditor> object_editor)
34 : renderer_(renderer),
35 rom_(rom),
36 canvas_viewer_(canvas_viewer),
37 object_selector_(rom),
38 object_editor_(object_editor) {
39 emulator_preview_.Initialize(renderer, rom);
40
41 // Initialize object parser for static editor info lookup
42 if (rom) {
43 object_parser_ = std::make_unique<zelda3::ObjectParser>(rom);
44 }
45
46 // Wire up object selector callback
48 [this](const zelda3::RoomObject& obj) {
49 preview_object_ = obj;
51 if (canvas_viewer_) {
54 }
55
56 // Sync with backend editor if available
57 if (object_editor_) {
59 object_editor_->SetCurrentObjectType(obj.id_);
60 }
61 });
62
63 // Wire up double-click callback for static object editor
65 [this](int obj_id) { OpenStaticObjectEditor(obj_id); });
66
67 // Wire up selection change callback for property panel sync
69}
70
80
82 if (!canvas_viewer_)
83 return;
84
85 auto& interaction = canvas_viewer_->object_interaction();
87
88 // Sync with backend editor if available
89 if (object_editor_) {
90 auto indices = interaction.GetSelectedObjectIndices();
91 // Clear backend selection first
92 (void)object_editor_->ClearSelection();
93
94 // Add each selected index to backend
95 for (size_t idx : indices) {
96 (void)object_editor_->AddToSelection(idx);
97 }
98 }
99}
100
101void ObjectEditorPanel::Draw(bool* p_open) {
102 const auto& theme = AgentUI::GetTheme();
103
104 // Door Section (Collapsible)
105 if (ImGui::CollapsingHeader(ICON_MD_DOOR_FRONT " Doors",
106 ImGuiTreeNodeFlags_DefaultOpen)) {
108 }
109
110 ImGui::Separator();
111
112 // Object Browser - takes up available space
113 float available_height = ImGui::GetContentRegionAvail().y;
114 // Reserve space for status indicator at bottom
115 float reserved_height = 60.0f;
116 // Reduce browser height when static editor is open to give it more space
118 reserved_height += 200.0f;
119 }
120 float browser_height = std::max(150.0f, available_height - reserved_height);
121
122 ImGui::BeginChild("ObjectBrowserRegion", ImVec2(0, browser_height), true);
124 ImGui::EndChild();
125
126 ImGui::Separator();
127
128 // Static Object Editor (if open)
131 ImGui::Separator();
132 }
133
134 // Status indicator: show current interaction state
135 {
136 bool is_placing = has_preview_object_ && canvas_viewer_ &&
138 if (!is_placing && has_preview_object_) {
139 has_preview_object_ = false;
140 }
141 if (is_placing) {
142 ImGui::TextColored(theme.status_warning,
143 ICON_MD_ADD_CIRCLE " Placing: Object 0x%02X",
145 ImGui::SameLine();
146 if (ImGui::SmallButton(ICON_MD_CANCEL " Cancel")) {
148 }
149 } else {
150 ImGui::TextColored(
151 theme.text_secondary_gray, ICON_MD_MOUSE
152 " Selection Mode - Click to select, drag to multi-select");
153 ImGui::TextColored(theme.text_secondary_gray, ICON_MD_MENU
154 " Right-click the canvas for Cut/Copy/Paste options");
155 }
156 }
157
158 // Current object info
160
161 ImGui::Separator();
162
163 // Emulator Preview (Collapsible)
164 bool preview_open = ImGui::CollapsingHeader(ICON_MD_MONITOR " Preview");
165 show_emulator_preview_ = preview_open;
166
167 if (preview_open) {
168 ImGui::PushID("PreviewSection");
170 ImGui::PopID();
171 }
172
173 // Handle keyboard shortcuts
175}
176
180
182 // In agent mode, we might force tabs open or change layout
183 (void)enabled;
184}
185
187 // Delegate to the DungeonObjectSelector component
189}
190
192 const auto& theme = AgentUI::GetTheme();
193
194 // Common door types for the grid
195 static constexpr std::array<zelda3::DoorType, 20> kDoorTypes = {{
216 }};
217
218 // Placement mode indicator
220 ImGui::TextColored(
221 theme.status_warning,
222 ICON_MD_PLACE " Placing: %s - Click wall to place",
223 std::string(zelda3::GetDoorTypeName(selected_door_type_)).c_str());
224 if (ImGui::SmallButton(ICON_MD_CANCEL " Cancel")) {
225 door_placement_mode_ = false;
226 if (canvas_viewer_) {
229 }
230 }
231 ImGui::Separator();
232 }
233
234 // Door type selector grid with preview thumbnails
235 ImGui::Text(ICON_MD_CATEGORY " Select Door Type:");
236
237 constexpr float kPreviewSize = 32.0f;
238 constexpr int kItemsPerRow = 5;
239 float panel_width = ImGui::GetContentRegionAvail().x;
240 int items_per_row =
241 std::max(1, static_cast<int>(panel_width / (kPreviewSize + 8)));
242
243 ImGui::BeginChild("##DoorTypeGrid", ImVec2(0, 80), true,
244 ImGuiWindowFlags_HorizontalScrollbar);
245
246 int col = 0;
247 for (size_t i = 0; i < kDoorTypes.size(); ++i) {
248 auto door_type = kDoorTypes[i];
249 bool is_selected = (selected_door_type_ == door_type);
250
251 ImGui::PushID(static_cast<int>(i));
252
253 // Color-coded button for door type
254 ImVec4 button_color;
255 // Color-code by door category
256 int type_val = static_cast<int>(door_type);
257 if (type_val <= 0x12) { // Standard doors
258 button_color = ImVec4(0.3f, 0.5f, 0.7f, 1.0f); // Blue
259 } else if (type_val <= 0x1E) { // Shutter/special
260 button_color = ImVec4(0.7f, 0.5f, 0.3f, 1.0f); // Orange
261 } else { // Markers
262 button_color = ImVec4(0.5f, 0.7f, 0.3f, 1.0f); // Green
263 }
264
265 if (is_selected) {
266 button_color.x += 0.2f;
267 button_color.y += 0.2f;
268 button_color.z += 0.2f;
269 }
270
271 ImGui::PushStyleColor(ImGuiCol_Button, button_color);
272 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
273 ImVec4(button_color.x + 0.1f, button_color.y + 0.1f,
274 button_color.z + 0.1f, 1.0f));
275 ImGui::PushStyleColor(ImGuiCol_ButtonActive,
276 ImVec4(button_color.x + 0.2f, button_color.y + 0.2f,
277 button_color.z + 0.2f, 1.0f));
278
279 // Draw button with door type abbreviation
280 std::string label = absl::StrFormat("%02X", type_val);
281 if (ImGui::Button(label.c_str(), ImVec2(kPreviewSize, kPreviewSize))) {
282 selected_door_type_ = door_type;
284 if (canvas_viewer_) {
286 true, selected_door_type_);
287 }
288 }
289
290 ImGui::PopStyleColor(3);
291
292 // Tooltip with full name
293 if (ImGui::IsItemHovered()) {
294 ImGui::SetTooltip("%s (0x%02X)\nClick to select for placement",
295 std::string(zelda3::GetDoorTypeName(door_type)).c_str(),
296 type_val);
297 }
298
299 // Selection highlight
300 if (is_selected) {
301 ImVec2 min = ImGui::GetItemRectMin();
302 ImVec2 max = ImGui::GetItemRectMax();
303 ImGui::GetWindowDrawList()->AddRect(min, max, IM_COL32(255, 255, 0, 255),
304 0.0f, 0, 2.0f);
305 }
306
307 ImGui::PopID();
308
309 col++;
310 if (col < items_per_row && i < kDoorTypes.size() - 1) {
311 ImGui::SameLine();
312 } else {
313 col = 0;
314 }
315 }
316
317 ImGui::EndChild();
318
319 // Show current room's doors
320 auto* rooms = object_selector_.get_rooms();
321 if (rooms && current_room_id_ >= 0 && current_room_id_ < 296) {
322 const auto& room = (*rooms)[current_room_id_];
323 const auto& doors = room.GetDoors();
324
325 if (!doors.empty()) {
326 ImGui::Text(ICON_MD_LIST " Room Doors (%zu):", doors.size());
327
328 ImGui::BeginChild("##DoorList", ImVec2(0, 80), true);
329 for (size_t i = 0; i < doors.size(); ++i) {
330 const auto& door = doors[i];
331 auto [tile_x, tile_y] = door.GetTileCoords();
332
333 ImGui::PushID(static_cast<int>(i));
334
335 std::string type_name(zelda3::GetDoorTypeName(door.type));
336 std::string dir_name(zelda3::GetDoorDirectionName(door.direction));
337
338 ImGui::Text("[%zu] %s (%s)", i, type_name.c_str(), dir_name.c_str());
339 ImGui::SameLine();
340 ImGui::TextColored(theme.text_secondary_gray, "@ (%d,%d)", tile_x,
341 tile_y);
342
343 ImGui::SameLine();
344 if (ImGui::SmallButton(ICON_MD_DELETE "##Del")) {
345 auto& mutable_room = (*rooms)[current_room_id_];
346 mutable_room.RemoveDoor(i);
347 }
348
349 ImGui::PopID();
350 }
351 ImGui::EndChild();
352 } else {
353 ImGui::TextColored(theme.text_secondary_gray,
354 ICON_MD_INFO " No doors in this room");
355 }
356 }
357}
358
360 const auto& theme = AgentUI::GetTheme();
361
362 ImGui::TextColored(theme.text_secondary_gray,
363 ICON_MD_INFO " Real-time object rendering preview");
365 "Uses SNES emulation to render objects accurately.\n"
366 "May impact performance.");
367
368 ImGui::Separator();
369
370 ImGui::BeginChild("##EmulatorPreviewRegion", ImVec2(0, 260), true);
372 ImGui::EndChild();
373}
374
376 const auto& theme = AgentUI::GetTheme();
377
378 // Show selection state at top - with extra safety checks
380 auto& interaction = canvas_viewer_->object_interaction();
381 auto selected = interaction.GetSelectedObjectIndices();
382
383 if (!selected.empty()) {
384 ImGui::TextColored(theme.status_success,
385 ICON_MD_CHECK_CIRCLE " Selected:");
386 ImGui::SameLine();
387
388 if (selected.size() == 1) {
389 if (object_editor_) {
390 const auto& objects = object_editor_->GetObjects();
391 if (selected[0] < objects.size()) {
392 const auto& obj = objects[selected[0]];
393 ImGui::Text("Object #%zu (ID: 0x%02X)", selected[0], obj.id_);
394 ImGui::TextColored(theme.text_secondary_gray,
395 " Position: (%d, %d) Size: 0x%02X Layer: %s",
396 obj.x_, obj.y_, obj.size_,
397 obj.layer_ == zelda3::RoomObject::BG1 ? "BG1"
398 : obj.layer_ == zelda3::RoomObject::BG2 ? "BG2"
399 : "BG3");
400 }
401 } else {
402 ImGui::Text("1 object");
403 }
404 } else {
405 ImGui::Text("%zu objects", selected.size());
406 ImGui::SameLine();
407 ImGui::TextColored(theme.text_secondary_gray,
408 "(Shift+click to add, Ctrl+click to toggle)");
409 }
410 ImGui::Separator();
411 }
412 }
413
414 ImGui::BeginGroup();
415
416 ImGui::TextColored(theme.text_info, ICON_MD_INFO " Current:");
417
419 ImGui::SameLine();
420 ImGui::Text("ID: 0x%02X", preview_object_.id_);
421 ImGui::SameLine();
422 ImGui::Text("Layer: %s",
425 : "BG3");
426 }
427
428 ImGui::EndGroup();
429 ImGui::Separator();
430
431 // Delegate property editing to the backend
432 if (object_editor_) {
433 object_editor_->DrawPropertyUI();
434 }
435}
436
437// =============================================================================
438// Static Object Editor (opened via double-click)
439// =============================================================================
440
442 static_editor_open_ = true;
443 static_editor_object_id_ = object_id;
445
446 // Sync with object selector for visual indicator
448
449 // Fetch draw routine info for this object
450 if (object_parser_) {
451 static_editor_draw_info_ = object_parser_->GetObjectDrawInfo(object_id);
452 }
453
454 // Render the object preview using ObjectDrawer
455 auto* rooms = object_selector_.get_rooms();
456 if (rom_ && rom_->is_loaded() && rooms && current_room_id_ >= 0 &&
457 current_room_id_ < static_cast<int>(rooms->size())) {
458 auto& room = (*rooms)[current_room_id_];
459
460 // Ensure room graphics are loaded
461 if (!room.IsLoaded()) {
462 room.LoadRoomGraphics(room.blockset);
463 }
464
465 // Clear preview buffer and initialize bitmap
468
469 // Create a preview object at top-left of canvas (tile 2,2 = pixel 16,16)
470 // to fit within the 128x128 preview area with some margin
471 zelda3::RoomObject preview_obj(object_id, 2, 2, 0x12, 0);
472 preview_obj.SetRom(rom_);
473 preview_obj.EnsureTilesLoaded();
474
475 if (preview_obj.tiles().empty()) {
476 return; // No tiles to draw
477 }
478
479 // Get room graphics data
480 const uint8_t* gfx_data = room.get_gfx_buffer().data();
481
482 // Apply palette to bitmap
483 auto& bitmap = static_preview_buffer_.bitmap();
484 gfx::PaletteGroup palette_group;
485 auto* game_data = object_selector_.game_data();
486 if (game_data && !game_data->palette_groups.dungeon_main.empty()) {
487 // Use the entire dungeon_main palette group
488 palette_group = game_data->palette_groups.dungeon_main;
489
490 std::vector<SDL_Color> colors(256);
491 size_t color_index = 0;
492 for (size_t pal_idx = 0;
493 pal_idx < palette_group.size() && color_index < 256; ++pal_idx) {
494 const auto& pal = palette_group[pal_idx];
495 for (size_t i = 0; i < pal.size() && color_index < 256; ++i) {
496 ImVec4 rgb = pal[i].rgb();
497 colors[color_index++] = {static_cast<Uint8>(rgb.x),
498 static_cast<Uint8>(rgb.y),
499 static_cast<Uint8>(rgb.z), 255};
500 }
501 }
502 colors[255] = {0, 0, 0, 0}; // Transparent
503 bitmap.SetPalette(colors);
504 if (bitmap.surface()) {
505 SDL_SetColorKey(bitmap.surface(), SDL_TRUE, 255);
506 SDL_SetSurfaceBlendMode(bitmap.surface(), SDL_BLENDMODE_BLEND);
507 }
508 }
509
510 // Create drawer with room's graphics data
511 zelda3::ObjectDrawer drawer(rom_, current_room_id_, gfx_data);
512 drawer.InitializeDrawRoutines();
513
514 auto status = drawer.DrawObject(preview_obj, static_preview_buffer_,
515 static_preview_buffer_, palette_group);
516 if (status.ok()) {
517 // Sync bitmap data to SDL surface
518 if (bitmap.modified() && bitmap.surface() &&
519 bitmap.mutable_data().size() > 0) {
520 SDL_LockSurface(bitmap.surface());
521 size_t surface_size = bitmap.surface()->h * bitmap.surface()->pitch;
522 size_t data_size = bitmap.mutable_data().size();
523 if (surface_size >= data_size) {
524 memcpy(bitmap.surface()->pixels, bitmap.mutable_data().data(),
525 data_size);
526 }
527 SDL_UnlockSurface(bitmap.surface());
528 }
529
530 // Create texture
534
535 static_preview_rendered_ = bitmap.texture() != nullptr;
536 }
537 }
538}
539
541 static_editor_open_ = false;
543
544 // Clear the visual indicator in object selector
546}
547
549 const auto& theme = AgentUI::GetTheme();
550
551 ImGui::PushStyleColor(
552 ImGuiCol_Header, ImVec4(0.15f, 0.25f, 0.35f, 1.0f)); // Slate blue header
553 ImGui::PushStyleColor(ImGuiCol_HeaderHovered,
554 ImVec4(0.20f, 0.30f, 0.40f, 1.0f));
555
556 bool header_open = ImGui::CollapsingHeader(
557 absl::StrFormat(ICON_MD_CONSTRUCTION " Object 0x%02X - %s",
560 .c_str(),
561 ImGuiTreeNodeFlags_DefaultOpen);
562
563 ImGui::PopStyleColor(2);
564
565 if (header_open) {
566 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 6));
567
568 // Two-column layout: Info | Preview
569 if (ImGui::BeginTable("StaticEditorLayout", 2,
570 ImGuiTableFlags_BordersInnerV)) {
571 ImGui::TableSetupColumn("Info", ImGuiTableColumnFlags_WidthFixed, 200);
572 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch);
573
574 ImGui::TableNextRow();
575
576 // Left column: Object information
577 ImGui::TableNextColumn();
578 {
579 // Object ID with hex/decimal display
580 ImGui::TextColored(theme.text_info, ICON_MD_TAG " Object ID");
581 ImGui::SameLine();
582 ImGui::Text("0x%02X (%d)", static_editor_object_id_,
584
585 ImGui::Spacing();
586
587 // Draw routine info
588 ImGui::TextColored(theme.text_info, ICON_MD_BRUSH " Draw Routine");
589 ImGui::Indent();
590 ImGui::Text("ID: %d", static_editor_draw_info_.draw_routine_id);
591 ImGui::Text("Name: %s", static_editor_draw_info_.routine_name.c_str());
592 ImGui::Unindent();
593
594 ImGui::Spacing();
595
596 // Tile and size info
597 ImGui::TextColored(theme.text_info, ICON_MD_GRID_VIEW " Tile Info");
598 ImGui::Indent();
599 ImGui::Text("Tile Count: %d", static_editor_draw_info_.tile_count);
600 ImGui::Text("Orientation: %s",
603 : "Both");
605 ImGui::TextColored(theme.status_warning, ICON_MD_LAYERS " Both BG");
606 }
607 ImGui::Unindent();
608
609 ImGui::Spacing();
610 ImGui::Separator();
611 ImGui::Spacing();
612
613 // Action buttons (vertical layout)
614 if (ImGui::Button(ICON_MD_CONTENT_COPY " Copy ID", ImVec2(-1, 0))) {
615 ImGui::SetClipboardText(
616 absl::StrFormat("0x%02X", static_editor_object_id_).c_str());
617 }
618 if (ImGui::IsItemHovered()) {
619 ImGui::SetTooltip("Copy object ID to clipboard");
620 }
621
622 if (ImGui::Button(ICON_MD_CODE " Export ASM", ImVec2(-1, 0))) {
623 // TODO: Implement ASM export (Phase 5)
624 }
625 if (ImGui::IsItemHovered()) {
626 ImGui::SetTooltip("Export object draw routine as ASM (Phase 5)");
627 }
628
629 ImGui::Spacing();
630
631 // Close button at bottom
632 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.2f, 0.2f, 1.0f));
633 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
634 ImVec4(0.6f, 0.3f, 0.3f, 1.0f));
635 if (ImGui::Button(ICON_MD_CLOSE " Close", ImVec2(-1, 0))) {
637 }
638 ImGui::PopStyleColor(2);
639 }
640
641 // Right column: Preview canvas
642 ImGui::TableNextColumn();
643 {
644 ImGui::TextColored(theme.text_secondary_gray, "Preview:");
645
646 gui::PreviewPanelOpts preview_opts;
647 preview_opts.canvas_size = ImVec2(128, 128);
648 preview_opts.render_popups = false;
649 preview_opts.grid_step = 0.0f;
650 preview_opts.ensure_texture = true;
651
652 gui::CanvasFrameOptions frame_opts;
653 frame_opts.canvas_size = preview_opts.canvas_size;
654 frame_opts.draw_context_menu = false;
655 frame_opts.draw_grid = preview_opts.grid_step > 0.0f;
656 if (preview_opts.grid_step > 0.0f) {
657 frame_opts.grid_step = preview_opts.grid_step;
658 }
659 frame_opts.draw_overlay = true;
660 frame_opts.render_popups = preview_opts.render_popups;
661
662 auto rt = gui::BeginCanvas(static_preview_canvas_, frame_opts);
663
665 auto& bitmap = static_preview_buffer_.bitmap();
666 gui::RenderPreviewPanel(rt, bitmap, preview_opts);
667 } else {
669 preview_opts);
671 ImVec2(24, 56), "No preview available",
672 ImGui::GetColorU32(theme.text_secondary_gray));
673 }
674 gui::EndCanvas(static_preview_canvas_, rt, frame_opts);
675
676 // Usage hint
677 ImGui::Spacing();
678 ImGui::TextColored(theme.text_secondary_gray, ICON_MD_INFO
679 " Double-click objects in browser\n"
680 "to view their draw routine info.");
681 }
682
683 ImGui::EndTable();
684 }
685
686 ImGui::PopStyleVar();
687 }
688}
689
690// =============================================================================
691// Keyboard Shortcuts
692// =============================================================================
693
695 if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
696 return;
697 }
698
699 const ImGuiIO& io = ImGui::GetIO();
700
701 // Ctrl+A: Select all objects
702 if (ImGui::IsKeyPressed(ImGuiKey_A) && io.KeyCtrl && !io.KeyShift) {
704 }
705
706 // Ctrl+Shift+A: Deselect all
707 if (ImGui::IsKeyPressed(ImGuiKey_A) && io.KeyCtrl && io.KeyShift) {
709 }
710
711 // Delete: Remove selected objects
712 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
714 }
715
716 // Ctrl+D: Duplicate selected objects
717 if (ImGui::IsKeyPressed(ImGuiKey_D) && io.KeyCtrl) {
719 }
720
721 // Ctrl+C: Copy selected objects
722 if (ImGui::IsKeyPressed(ImGuiKey_C) && io.KeyCtrl) {
724 }
725
726 // Ctrl+V: Paste objects
727 if (ImGui::IsKeyPressed(ImGuiKey_V) && io.KeyCtrl) {
728 PasteObjects();
729 }
730
731 // Ctrl+Z: Undo
732 if (ImGui::IsKeyPressed(ImGuiKey_Z) && io.KeyCtrl && !io.KeyShift) {
733 if (object_editor_) {
734 object_editor_->Undo();
735 }
736 }
737
738 // Ctrl+Shift+Z or Ctrl+Y: Redo
739 if ((ImGui::IsKeyPressed(ImGuiKey_Z) && io.KeyCtrl && io.KeyShift) ||
740 (ImGui::IsKeyPressed(ImGuiKey_Y) && io.KeyCtrl)) {
741 if (object_editor_) {
742 object_editor_->Redo();
743 }
744 }
745
746 // G: Toggle grid
747 if (ImGui::IsKeyPressed(ImGuiKey_G) && !io.KeyCtrl) {
749 }
750
751 // I: Toggle object ID labels
752 if (ImGui::IsKeyPressed(ImGuiKey_I) && !io.KeyCtrl) {
754 }
755
756 // Arrow keys: Nudge selected objects
757 if (!io.KeyCtrl) {
758 int dx = 0, dy = 0;
759 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
760 dx = -1;
761 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow))
762 dx = 1;
763 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
764 dy = -1;
765 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
766 dy = 1;
767
768 if (dx != 0 || dy != 0) {
769 NudgeSelectedObjects(dx, dy);
770 }
771 }
772
773 // Tab: Cycle through objects
774 if (ImGui::IsKeyPressed(ImGuiKey_Tab) && !io.KeyCtrl) {
775 CycleObjectSelection(io.KeyShift ? -1 : 1);
776 }
777
778 // Escape: Cancel placement or deselect all
779 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
783 } else {
785 }
786 }
787}
788
796
799 return;
800
801 auto& interaction = canvas_viewer_->object_interaction();
802 const auto& objects = object_editor_->GetObjects();
803 std::vector<size_t> all_indices;
804
805 for (size_t i = 0; i < objects.size(); ++i) {
806 all_indices.push_back(i);
807 }
808
809 interaction.SetSelectedObjects(all_indices);
810}
811
817
820 return;
821
822 auto& interaction = canvas_viewer_->object_interaction();
823 const auto& selected = interaction.GetSelectedObjectIndices();
824
825 if (selected.empty())
826 return;
827
828 // Show confirmation for bulk delete (more than 5 objects)
829 if (selected.size() > 5) {
831 return;
832 }
833
835}
836
839 return;
840
841 auto& interaction = canvas_viewer_->object_interaction();
842 const auto& selected = interaction.GetSelectedObjectIndices();
843
844 if (selected.empty())
845 return;
846
847 // Delete in reverse order to maintain indices
848 std::vector<size_t> sorted_indices(selected.begin(), selected.end());
849 std::sort(sorted_indices.rbegin(), sorted_indices.rend());
850
851 for (size_t idx : sorted_indices) {
852 object_editor_->DeleteObject(idx);
853 }
854
855 interaction.ClearSelection();
856}
857
860 return;
861
862 auto& interaction = canvas_viewer_->object_interaction();
863 const auto& selected = interaction.GetSelectedObjectIndices();
864
865 if (selected.empty())
866 return;
867
868 std::vector<size_t> new_indices;
869
870 for (size_t idx : selected) {
871 auto new_idx = object_editor_->DuplicateObject(idx, 1, 1);
872 if (new_idx.has_value()) {
873 new_indices.push_back(*new_idx);
874 }
875 }
876
877 interaction.SetSelectedObjects(new_indices);
878}
879
882 return;
883
884 auto& interaction = canvas_viewer_->object_interaction();
885 const auto& selected = interaction.GetSelectedObjectIndices();
886
887 if (selected.empty())
888 return;
889
890 object_editor_->CopySelectedObjects(selected);
891}
892
895 return;
896
897 auto new_indices = object_editor_->PasteObjects();
898
899 if (!new_indices.empty()) {
901 }
902}
903
906 return;
907
908 auto& interaction = canvas_viewer_->object_interaction();
909 const auto& selected = interaction.GetSelectedObjectIndices();
910
911 if (selected.empty())
912 return;
913
914 for (size_t idx : selected) {
915 object_editor_->MoveObject(idx, dx, dy);
916 }
917}
918
921 return;
922
923 auto& interaction = canvas_viewer_->object_interaction();
924 const auto& selected = interaction.GetSelectedObjectIndices();
925 const auto& objects = object_editor_->GetObjects();
926
927 size_t total_objects = objects.size();
928 if (total_objects == 0)
929 return;
930
931 size_t current_idx = selected.empty() ? 0 : selected.front();
932 size_t next_idx = (current_idx + direction + total_objects) % total_objects;
933
934 interaction.SetSelectedObjects({next_idx});
935}
936
939 return;
940
941 const auto& objects = object_editor_->GetObjects();
942 if (index >= objects.size())
943 return;
944
945 // TODO: Implement ScrollTo in DungeonCanvasViewer
946 (void)objects;
947}
948
949} // namespace editor
950} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
bool is_loaded() const
Definition rom.h:128
DungeonObjectInteraction & object_interaction()
void SetPreviewObject(const zelda3::RoomObject &object)
void SetSelectedObjects(const std::vector< size_t > &indices)
std::vector< size_t > GetSelectedObjectIndices() const
void SetSelectionChangeCallback(std::function< void()> callback)
void SetDoorPlacementMode(bool enabled, zelda3::DoorType type=zelda3::DoorType::NormalDoor)
void SetObjectDoubleClickCallback(std::function< void(int)> callback)
std::array< zelda3::Room, 0x128 > * get_rooms()
void SetObjectSelectedCallback(std::function< void(const zelda3::RoomObject &)> callback)
ObjectEditorPanel(gfx::IRenderer *renderer, Rom *rom, DungeonCanvasViewer *canvas_viewer, std::shared_ptr< zelda3::DungeonObjectEditor > object_editor=nullptr)
std::unique_ptr< zelda3::ObjectParser > object_parser_
gui::DungeonObjectEmulatorPreview emulator_preview_
gfx::BackgroundBuffer static_preview_buffer_
void Draw(bool *p_open) override
Draw the panel content.
std::shared_ptr< zelda3::DungeonObjectEditor > object_editor_
zelda3::ObjectDrawInfo static_editor_draw_info_
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:35
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:115
static Arena & Get()
Definition arena.cc:20
Defines an abstract interface for all rendering operations.
Definition irenderer.h:40
void AddTextAt(ImVec2 local_pos, const std::string &text, uint32_t color)
Definition canvas.cc:2425
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
Definition room_object.h:88
void SetRom(Rom *rom)
Definition room_object.h:68
#define ICON_MD_GRID_VIEW
Definition icons.h:897
#define ICON_MD_MONITOR
Definition icons.h:1233
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_PLACE
Definition icons.h:1477
#define ICON_MD_CONSTRUCTION
Definition icons.h:458
#define ICON_MD_BRUSH
Definition icons.h:325
#define ICON_MD_CODE
Definition icons.h:434
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_LAYERS
Definition icons.h:1068
#define ICON_MD_DOOR_FRONT
Definition icons.h:613
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_MOUSE
Definition icons.h:1251
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_MENU
Definition icons.h:1196
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_CATEGORY
Definition icons.h:382
#define ICON_MD_TAG
Definition icons.h:1940
#define ICON_MD_ADD_CIRCLE
Definition icons.h:95
const AgentUITheme & GetTheme()
void EndCanvas(Canvas &canvas)
Definition canvas.cc:1509
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1486
void HelpMarker(const char *desc)
bool RenderPreviewPanel(const CanvasRuntime &rt, gfx::Bitmap &bmp, const PreviewPanelOpts &opts)
Definition canvas.cc:2156
@ 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.
@ ExitMarker
Exit marker.
@ DoubleSidedShutter
Double sided shutter door.
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
Definition door_types.h:161
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
Definition door_types.h:106
Represents a group of palettes.
std::optional< float > grid_step
Definition canvas.h:70