yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_object_selector.cc
Go to the documentation of this file.
1// Related header
3
4// C system headers
5#include <cstring>
6
7// C++ standard library headers
8#include <algorithm>
9#include <iterator>
10
11// Third-party library headers
12#include "imgui/imgui.h"
13
14// Project headers
16#include "app/platform/window.h"
18#include "core/features.h"
19#include "rom/rom.h"
25#include "zelda3/dungeon/room.h"
26#include "zelda3/dungeon/room.h"
27#include "zelda3/dungeon/room_object.h" // For GetObjectName()
28#include "zelda3/dungeon/custom_object.h" // For CustomObjectManager
29
30namespace yaze::editor {
31
32using ImGui::BeginChild;
33using ImGui::EndChild;
34using ImGui::EndTabBar;
35using ImGui::EndTabItem;
36using ImGui::Separator;
37
40 if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
41 if (ImGui::BeginTabItem("Room Graphics")) {
42 if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3);
43 BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
44 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
46 }
47 EndChild();
48 EndTabItem();
49 }
50
51 if (ImGui::BeginTabItem("Object Renderer")) {
53 EndTabItem();
54 }
55 EndTabBar();
56 }
57}
58
61 // Use AssetBrowser for better object selection
62 if (ImGui::BeginTable(
63 "DungeonObjectEditorTable", 2,
64 ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
65 ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
66 ImGuiTableFlags_BordersV,
67 ImVec2(0, 0))) {
68 ImGui::TableSetupColumn("Object Browser", ImGuiTableColumnFlags_WidthFixed,
69 400);
70 ImGui::TableSetupColumn("Preview Canvas",
71 ImGuiTableColumnFlags_WidthStretch);
72 ImGui::TableHeadersRow();
73
74 // Left column: AssetBrowser for object selection
75 ImGui::TableNextColumn();
76 ImGui::BeginChild("AssetBrowser", ImVec2(0, 0), true,
77 ImGuiWindowFlags_AlwaysVerticalScrollbar);
78
80
81 ImGui::EndChild();
82
83 // Right column: Preview and placement controls
84 ImGui::TableNextColumn();
85 ImGui::BeginChild("PreviewCanvas", ImVec2(0, 0), true);
86
87 // Object placement controls
88 ImGui::SeparatorText("Object Placement");
89 ImGui::InputInt("X Position", &place_x_);
90 ImGui::InputInt("Y Position", &place_y_);
91
92 if (ImGui::Button("Place Object") && object_loaded_) {
94 }
95
96 ImGui::Separator();
97
98 // Preview canvas
99 gui::CanvasFrameOptions frame_opts;
100 frame_opts.canvas_size = ImVec2(256 + 1, 0x10 * 0x40 + 1);
101 frame_opts.draw_grid = true;
102 frame_opts.grid_step = 32.0f;
103 frame_opts.render_popups = true;
104 gui::CanvasFrame frame(object_canvas_, frame_opts);
105
106 // Render selected object preview with primitive fallback
107 if (object_loaded_ && preview_object_.id_ >= 0) {
108 int preview_x = 128 - 16; // Center horizontally
109 int preview_y = 128 - 16; // Center vertically
110
111 // TODO: Implement preview using ObjectDrawer + small BackgroundBuffer
112 // For now, use primitive shape rendering (shows object ID and rough
113 // dimensions)
114 RenderObjectPrimitive(preview_object_, preview_x, preview_y);
115 }
116
117 ImGui::EndChild();
118 ImGui::EndTable();
119 }
120
121 // Object details window
122 if (object_loaded_) {
123 ImGui::Begin("Object Details", &object_loaded_, 0);
124 ImGui::Text("Object ID: 0x%02X", preview_object_.id_);
125 ImGui::Text("Position: (%d, %d)", preview_object_.x_, preview_object_.y_);
126 ImGui::Text("Size: 0x%02X", preview_object_.size_);
127 ImGui::Text("Layer: %d", static_cast<int>(preview_object_.layer_));
128
129 // Add object placement controls
130 ImGui::Separator();
131 ImGui::Text("Placement Controls:");
132 ImGui::InputInt("X Position", &place_x_);
133 ImGui::InputInt("Y Position", &place_y_);
134
135 if (ImGui::Button("Place Object")) {
137 }
138
139 ImGui::End();
140 }
141}
142
144 if (ImGui::BeginTabBar("##ObjectSelectorTabBar")) {
145 // Object Selector tab - for placing objects with new AssetBrowser
146 if (ImGui::BeginTabItem("Object Selector")) {
148 ImGui::EndTabItem();
149 }
150
151 // Room Graphics tab - 8 bitmaps viewer
152 if (ImGui::BeginTabItem("Room Graphics")) {
154 ImGui::EndTabItem();
155 }
156
157 // Object Editor tab - experimental editor
158 if (ImGui::BeginTabItem("Object Editor")) {
160 ImGui::EndTabItem();
161 }
162
163 ImGui::EndTabBar();
164 }
165}
166
168 const auto height = 0x40;
169 gui::CanvasFrameOptions frame_opts;
170 frame_opts.draw_grid = true;
171 frame_opts.grid_step = 32.0f;
172 frame_opts.render_popups = true;
173 gui::CanvasFrame frame(room_gfx_canvas_, frame_opts);
175
176 if (rom_ && rom_->is_loaded() && rooms_) {
177 int active_room_id = current_room_id_;
178 auto& room = (*rooms_)[active_room_id];
179 auto blocks = room.blocks();
180
181 // Load graphics for this room if not already loaded
182 if (blocks.empty()) {
183 room.LoadRoomGraphics(room.blockset);
184 blocks = room.blocks();
185 }
186
187 int current_block = 0;
188 const int max_blocks_per_row = 2; // 2 blocks per row for 300px column
189 const int block_width = 128; // Reduced size to fit column
190 const int block_height = 32; // Reduced height
191
192 for (int block : blocks) {
193 if (current_block >= 16)
194 break; // Only show first 16 blocks
195
196 // Ensure the graphics sheet is loaded and has a valid texture
197 if (block < gfx::Arena::Get().gfx_sheets().size()) {
198 auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
199
200 // Calculate position in a grid layout instead of horizontal
201 // concatenation
202 int row = current_block / max_blocks_per_row;
203 int col = current_block % max_blocks_per_row;
204
205 ImVec2 local_pos(2 + (col * block_width), 2 + (row * block_height));
206
207 // Ensure we don't exceed canvas bounds
208 if (local_pos.x + block_width <= room_gfx_canvas_.width() &&
209 local_pos.y + block_height <= room_gfx_canvas_.height()) {
210 if (gfx_sheet.texture() != 0) {
212 (ImTextureID)(intptr_t)gfx_sheet.texture(), local_pos,
213 ImVec2(block_width, block_height));
214 }
215 }
216 }
217 current_block += 1;
218 }
219 }
220}
221
224 ImGui::Text("Editor systems not initialized");
225 return;
226 }
227
228 // Create a tabbed interface for different editing modes
229 if (ImGui::BeginTabBar("##EditingPanels")) {
230 // Object Editor Tab
231 if (ImGui::BeginTabItem("Objects")) {
233 ImGui::EndTabItem();
234 }
235
236 // Sprite Editor Tab
237 if (ImGui::BeginTabItem("Sprites")) {
239 ImGui::EndTabItem();
240 }
241
242 // Item Editor Tab
243 if (ImGui::BeginTabItem("Items")) {
245 ImGui::EndTabItem();
246 }
247
248 // Entrance Editor Tab
249 if (ImGui::BeginTabItem("Entrances")) {
251 ImGui::EndTabItem();
252 }
253
254 // Door Editor Tab
255 if (ImGui::BeginTabItem("Doors")) {
257 ImGui::EndTabItem();
258 }
259
260 // Chest Editor Tab
261 if (ImGui::BeginTabItem("Chests")) {
263 ImGui::EndTabItem();
264 }
265
266 // Properties Tab
267 if (ImGui::BeginTabItem("Properties")) {
269 ImGui::EndTabItem();
270 }
271
272 // Minecart Editor Tab
273 ImGui::EndTabBar();
274 }
275}
276
278 if (!object_editor_) {
279 ImGui::Text("Object editor not initialized");
280 return;
281 }
282
283 auto& editor = *object_editor_;
284
285 ImGui::Text("Object Editor");
286 Separator();
287
288 // Display current editing mode
289 auto mode = editor.GetMode();
290 const char* mode_names[] = {"Select", "Insert", "Delete",
291 "Edit", "Layer", "Preview"};
292 ImGui::Text("Mode: %s", mode_names[static_cast<int>(mode)]);
293
294 // Compact mode selection
295 if (ImGui::Button("Select"))
297 ImGui::SameLine();
298 if (ImGui::Button("Insert"))
300 ImGui::SameLine();
301 if (ImGui::Button("Edit"))
303
304 // Layer and object type selection
305 int current_layer = editor.GetCurrentLayer();
306 if (ImGui::SliderInt("Layer", &current_layer, 0, 2)) {
307 editor.SetCurrentLayer(current_layer);
308 }
309
310 int current_object_type = editor.GetCurrentObjectType();
311 if (ImGui::InputInt("Object Type", &current_object_type, 1, 16)) {
312 if (current_object_type >= 0 && current_object_type <= 0x3FF) {
313 editor.SetCurrentObjectType(current_object_type);
314 }
315 }
316
317 // Quick configuration checkboxes
318 auto config = editor.GetConfig();
319 if (ImGui::Checkbox("Snap to Grid", &config.snap_to_grid)) {
320 editor.SetConfig(config);
321 }
322 ImGui::SameLine();
323 if (ImGui::Checkbox("Show Grid", &config.show_grid)) {
324 editor.SetConfig(config);
325 }
326
327 // Object count and selection info
328 Separator();
329 ImGui::Text("Objects: %zu", editor.GetObjectCount());
330
331 auto selection = editor.GetSelection();
332 if (!selection.selected_objects.empty()) {
333 ImGui::Text("Selected: %zu", selection.selected_objects.size());
334 }
335
336 // Undo/Redo buttons
337 Separator();
338 if (ImGui::Button("Undo") && editor.CanUndo()) {
339 (void)editor.Undo();
340 }
341 ImGui::SameLine();
342 if (ImGui::Button("Redo") && editor.CanRedo()) {
343 (void)editor.Redo();
344 }
345}
346
348 const auto& theme = AgentUI::GetTheme();
349
350 // Type 3 objects (0xF80-0xFFF) - Special room features
351 if (object_id >= 0xF80) {
352 if (object_id >= 0xF80 && object_id <= 0xF8F) {
353 return IM_COL32(100, 200, 255, 255); // Light blue for layer indicators
354 } else if (object_id >= 0xF90 && object_id <= 0xF9F) {
355 return IM_COL32(255, 200, 100, 255); // Orange for door indicators
356 } else {
357 return IM_COL32(200, 150, 255, 255); // Purple for misc Type 3
358 }
359 }
360
361 // Type 2 objects (0x100-0x141) - Torches, blocks, switches
362 if (object_id >= 0x100 && object_id < 0x200) {
363 if (object_id >= 0x100 && object_id <= 0x10F) {
364 return IM_COL32(255, 150, 50, 255); // Orange for torches
365 } else if (object_id >= 0x110 && object_id <= 0x11F) {
366 return IM_COL32(150, 150, 200, 255); // Blue-gray for blocks
367 } else if (object_id >= 0x120 && object_id <= 0x12F) {
368 return IM_COL32(100, 200, 100, 255); // Green for switches
369 } else if (object_id >= 0x130 && object_id <= 0x13F) {
370 return ImGui::GetColorU32(theme.dungeon_selection_primary); // Yellow for stairs
371 } else {
372 return IM_COL32(180, 180, 180, 255); // Gray for other Type 2
373 }
374 }
375
376 // Type 1 objects (0x00-0xFF) - Base room objects
377 if (object_id >= 0x10 && object_id <= 0x1F) {
378 return ImGui::GetColorU32(theme.dungeon_object_wall); // Gray for walls
379 } else if (object_id >= 0x20 && object_id <= 0x2F) {
380 return ImGui::GetColorU32(theme.dungeon_object_floor); // Brown for floors
381 } else if (object_id == 0xF9 || object_id == 0xFA) {
382 return ImGui::GetColorU32(theme.dungeon_object_chest); // Gold for chests
383 } else if (object_id >= 0x17 && object_id <= 0x1E) {
384 return ImGui::GetColorU32(theme.dungeon_object_floor); // Brown for doors
385 } else if (object_id == 0x2F || object_id == 0x2B) {
386 return ImGui::GetColorU32(theme.dungeon_object_pot); // Saddle brown for pots
387 } else if (object_id >= 0x30 && object_id <= 0x3F) {
388 return ImGui::GetColorU32(theme.dungeon_object_decoration); // Dim gray for decorations
389 } else if (object_id >= 0x00 && object_id <= 0x0F) {
390 return IM_COL32(120, 120, 180, 255); // Blue-gray for corners
391 } else {
392 return ImGui::GetColorU32(theme.dungeon_object_default); // Default gray
393 }
394}
395
397 // Type 3 objects (0xF80-0xFFF) - Special room features
398 if (object_id >= 0xF80) {
399 if (object_id >= 0xF80 && object_id <= 0xF8F) {
400 return "L"; // Layer
401 } else if (object_id >= 0xF90 && object_id <= 0xF9F) {
402 return "D"; // Door indicator
403 } else {
404 return "S"; // Special
405 }
406 }
407
408 // Type 2 objects (0x100-0x141) - Torches, blocks, switches
409 if (object_id >= 0x100 && object_id < 0x200) {
410 if (object_id >= 0x100 && object_id <= 0x10F) {
411 return "*"; // Torch (flame)
412 } else if (object_id >= 0x110 && object_id <= 0x11F) {
413 return "#"; // Block
414 } else if (object_id >= 0x120 && object_id <= 0x12F) {
415 return "o"; // Switch
416 } else if (object_id >= 0x130 && object_id <= 0x13F) {
417 return "^"; // Stairs
418 } else {
419 return "2"; // Type 2
420 }
421 }
422
423 // Type 1 objects (0x00-0xFF) - Base room objects
424 if (object_id >= 0x10 && object_id <= 0x1F) {
425 return "|"; // Wall
426 } else if (object_id >= 0x20 && object_id <= 0x2F) {
427 return "_"; // Floor
428 } else if (object_id == 0xF9 || object_id == 0xFA) {
429 return "C"; // Chest
430 } else if (object_id >= 0x17 && object_id <= 0x1E) {
431 return "+"; // Door
432 } else if (object_id == 0x2F || object_id == 0x2B) {
433 return "o"; // Pot
434 } else if (object_id >= 0x30 && object_id <= 0x3F) {
435 return "~"; // Decoration
436 } else if (object_id >= 0x00 && object_id <= 0x0F) {
437 return "/"; // Corner
438 } else {
439 return "?"; // Unknown
440 }
441}
442
444 const zelda3::RoomObject& object, int x, int y) {
445 const auto& theme = AgentUI::GetTheme();
446 // Render object as primitive shape on canvas
447 ImU32 color = GetObjectTypeColor(object.id_);
448
449 // Calculate object size with proper wall length handling
450 int obj_width, obj_height;
451 CalculateObjectDimensions(object, obj_width, obj_height);
452
453 // Draw object rectangle
454 ImVec4 color_vec = ImGui::ColorConvertU32ToFloat4(color);
455 object_canvas_.DrawRect(x, y, obj_width, obj_height, color_vec);
456 object_canvas_.DrawRect(x, y, obj_width, obj_height, theme.panel_bg_darker);
457
458 // Draw object ID as text
459 std::string obj_text = absl::StrFormat("0x%X", object.id_);
460 object_canvas_.DrawText(obj_text, x + obj_width + 2, y + 4);
461}
462
464 selected_object_id_ = obj_id;
465
466 // Create and update preview object
467 preview_object_ = zelda3::RoomObject(obj_id, 0, 0, 0x12, 0);
469 if (game_data_) {
470 auto palette =
472 preview_palette_ = palette;
473 }
474 object_loaded_ = true;
475
476 // Notify callback
479 }
480}
481
483 const auto& theme = AgentUI::GetTheme();
484
485 // Object ranges: Type 1 (0x00-0xFF), Type 2 (0x100-0x141), Type 3 (0xF80-0xFFF)
486 struct ObjectRange {
487 int start;
488 int end;
489 const char* label;
490 ImU32 header_color;
491 };
492 static const ObjectRange ranges[] = {
493 {0x00, 0xFF, "Type 1", IM_COL32(80, 120, 180, 255)},
494 {0x100, 0x141, "Type 2", IM_COL32(120, 80, 180, 255)},
495 {0xF80, 0xFFF, "Type 3", IM_COL32(180, 120, 80, 255)},
496 };
497
498 // Total object count
499 int total_objects = (0xFF - 0x00 + 1) + (0x141 - 0x100 + 1) + (0xFFF - 0xF80 + 1);
500
501 // Preview toggle (disabled by default for performance)
502 ImGui::Checkbox(ICON_MD_IMAGE " Previews", &enable_object_previews_);
503 if (ImGui::IsItemHovered()) {
504 ImGui::SetTooltip(
505 "Enable to show actual object graphics.\n"
506 "Requires a room to be loaded.\n"
507 "May impact performance.");
508 }
509 ImGui::SameLine();
510 ImGui::TextDisabled("(%d objects)", total_objects);
511
512 // Create asset browser-style grid
513 const float item_size = 72.0f;
514 const float item_spacing = 6.0f;
515 const int columns = std::max(
516 1, static_cast<int>((ImGui::GetContentRegionAvail().x - item_spacing) /
517 (item_size + item_spacing)));
518
519 // Scrollable child region for grid - use all available space
520 float child_height = ImGui::GetContentRegionAvail().y;
521 if (ImGui::BeginChild("##ObjectGrid", ImVec2(0, child_height), false,
522 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
523
524 // Iterate through all object ranges
525 for (const auto& range : ranges) {
526 // Section header for each type
527 ImGui::PushStyleColor(ImGuiCol_Header, range.header_color);
528 ImGui::PushStyleColor(ImGuiCol_HeaderHovered,
529 IM_COL32((range.header_color & 0xFF) + 30,
530 ((range.header_color >> 8) & 0xFF) + 30,
531 ((range.header_color >> 16) & 0xFF) + 30, 255));
532 bool section_open = ImGui::CollapsingHeader(
533 absl::StrFormat("%s (0x%03X-0x%03X)", range.label, range.start, range.end).c_str(),
534 ImGuiTreeNodeFlags_DefaultOpen);
535 ImGui::PopStyleColor(2);
536
537 if (!section_open) continue;
538
539 int current_column = 0;
540
541 for (int obj_id = range.start; obj_id <= range.end; ++obj_id) {
542 if (current_column > 0) {
543 ImGui::SameLine();
544 }
545
546 ImGui::PushID(obj_id);
547
548 // Create selectable button for object
549 bool is_selected = (selected_object_id_ == obj_id);
550 ImVec2 button_size(item_size, item_size);
551
552 if (ImGui::Selectable("", is_selected,
553 ImGuiSelectableFlags_AllowDoubleClick,
554 button_size)) {
555 selected_object_id_ = obj_id;
556
557 // Create and update preview object
558 preview_object_ = zelda3::RoomObject(obj_id, 0, 0, 0x12, 0);
562 auto palette =
564 preview_palette_ = palette;
565 }
566 object_loaded_ = true;
567
568 // Notify callbacks
571 }
572
573 // Handle double-click to open static object editor
574 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
577 }
578 }
579 }
580
581 // Draw object preview on the button; fall back to styled placeholder
582 ImVec2 button_pos = ImGui::GetItemRectMin();
583 ImDrawList* draw_list = ImGui::GetWindowDrawList();
584
585 // Only attempt graphical preview if enabled (performance optimization)
586 bool rendered = false;
588 rendered = DrawObjectPreview(MakePreviewObject(obj_id), button_pos,
589 item_size);
590 }
591
592 if (!rendered) {
593 // Draw a styled fallback with gradient background
594 ImU32 obj_color = GetObjectTypeColor(obj_id);
595 ImU32 darker_color = IM_COL32((obj_color & 0xFF) * 0.6f,
596 ((obj_color >> 8) & 0xFF) * 0.6f,
597 ((obj_color >> 16) & 0xFF) * 0.6f, 255);
598
599 // Gradient background
600 draw_list->AddRectFilledMultiColor(
601 button_pos,
602 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
603 darker_color, darker_color, obj_color, obj_color);
604
605 // Draw object type symbol in center
606 std::string symbol = GetObjectTypeSymbol(obj_id);
607 ImVec2 symbol_size = ImGui::CalcTextSize(symbol.c_str());
608 ImVec2 symbol_pos(button_pos.x + (item_size - symbol_size.x) / 2,
609 button_pos.y + (item_size - symbol_size.y) / 2 - 10);
610 draw_list->AddText(symbol_pos, IM_COL32(255, 255, 255, 180),
611 symbol.c_str());
612 }
613
614 // Draw border with special highlight for static editor object
615 bool is_static_editor_obj = (obj_id == static_editor_object_id_);
616 ImU32 border_color;
617 float border_thickness;
618
619 if (is_static_editor_obj) {
620 border_color = IM_COL32(0, 200, 255, 255);
621 border_thickness = 3.0f;
622 } else if (is_selected) {
623 border_color = ImGui::GetColorU32(theme.dungeon_selection_primary);
624 border_thickness = 3.0f;
625 } else {
626 border_color = ImGui::GetColorU32(theme.panel_bg_darker);
627 border_thickness = 1.0f;
628 }
629
630 draw_list->AddRect(
631 button_pos,
632 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
633 border_color, 0.0f, 0, border_thickness);
634
635 // Static editor indicator icon
636 if (is_static_editor_obj) {
637 ImVec2 icon_pos(button_pos.x + item_size - 14, button_pos.y + 2);
638 draw_list->AddCircleFilled(ImVec2(icon_pos.x + 6, icon_pos.y + 6), 6,
639 IM_COL32(0, 200, 255, 200));
640 draw_list->AddText(icon_pos, IM_COL32(255, 255, 255, 255), "i");
641 }
642
643 // Get object name for display
644 std::string full_name = zelda3::GetObjectName(obj_id);
645
646 // Truncate name for display
647 std::string display_name = full_name;
648 const size_t kMaxDisplayChars = 12;
649 if (display_name.length() > kMaxDisplayChars) {
650 display_name = display_name.substr(0, kMaxDisplayChars - 2) + "..";
651 }
652
653 // Draw object name (smaller, above ID)
654 ImVec2 name_size = ImGui::CalcTextSize(display_name.c_str());
655 ImVec2 name_pos = ImVec2(button_pos.x + (item_size - name_size.x) / 2,
656 button_pos.y + item_size - 26);
657 draw_list->AddText(name_pos,
658 ImGui::GetColorU32(theme.text_secondary_gray),
659 display_name.c_str());
660
661 // Draw object ID at bottom (hex format)
662 std::string id_text = absl::StrFormat("%03X", obj_id);
663 ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
664 ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
665 button_pos.y + item_size - id_size.y - 2);
666 draw_list->AddText(id_pos, ImGui::GetColorU32(theme.text_primary),
667 id_text.c_str());
668
669 // Enhanced tooltip
670 if (ImGui::IsItemHovered()) {
671 ImGui::BeginTooltip();
672 ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.4f, 1.0f), "Object 0x%03X",
673 obj_id);
674 ImGui::Text("%s", full_name.c_str());
675 int subtype = zelda3::GetObjectSubtype(obj_id);
676 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "Subtype %d",
677 subtype);
678 ImGui::Separator();
679 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
680 "Click to select for placement");
681 ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f),
682 "Double-click to view details");
683 ImGui::EndTooltip();
684 }
685
686 ImGui::PopID();
687
688 current_column = (current_column + 1) % columns;
689 } // end object loop
690 } // end range loop
691
692 // Custom Objects Section
693 ImGui::PushStyleColor(ImGuiCol_Header, IM_COL32(100, 180, 120, 255));
694 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, IM_COL32(130, 210, 150, 255));
695 bool custom_open = ImGui::CollapsingHeader("Custom Objects", ImGuiTreeNodeFlags_DefaultOpen);
696 ImGui::PopStyleColor(2);
697
698 if (custom_open) {
699 int custom_col = 0;
700 auto& obj_manager = zelda3::CustomObjectManager::Get();
701
702 // Initialize if needed (hacky lazy init if drawer hasn't done it yet)
703 // Ideally should be initialized by system.
704 // We'll skip init here and assume ObjectDrawer did it or will do it.
705 // But we need counts. If uninitialized, counts might be wrong?
706 // GetSubtypeCount checks static lists, so it's safe even if not fully init with paths.
707
708 for (int obj_id : {0x31, 0x32}) {
709 int subtype_count = obj_manager.GetSubtypeCount(obj_id);
710 for (int subtype = 0; subtype < subtype_count; ++subtype) {
711 if (custom_col > 0) ImGui::SameLine();
712
713 ImGui::PushID(obj_id * 1000 + subtype);
714
715 bool is_selected = (selected_object_id_ == obj_id && (preview_object_.size_ & 0x1F) == subtype);
716 ImVec2 button_size(item_size, item_size);
717
718 if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_AllowDoubleClick, button_size)) {
719 SelectObject(obj_id);
720 // Update size to subtype
721 preview_object_.size_ = subtype;
722
723 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
725 }
726 }
727
728 // Draw Preview
729 ImVec2 button_pos = ImGui::GetItemRectMin();
730 ImDrawList* draw_list = ImGui::GetWindowDrawList();
731
732 bool rendered = false;
733 // Native preview requires loaded ROM and correct pathing, might fail if not init.
734 // But we can try constructing a temp object with correct subtype.
736 auto temp_obj = MakePreviewObject(obj_id);
737 temp_obj.size_ = subtype;
738 rendered = DrawObjectPreview(temp_obj, button_pos, item_size);
739 }
740
741 if (!rendered) {
742 // Fallback visuals
743 ImU32 obj_color = IM_COL32(100, 180, 120, 255);
744 ImU32 darker_color = IM_COL32(60, 100, 70, 255);
745
746 draw_list->AddRectFilledMultiColor(
747 button_pos,
748 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
749 darker_color, darker_color, obj_color, obj_color);
750
751 std::string symbol = (obj_id == 0x31) ? "Trk" : "Cus";
752 // Subtype
753 std::string sub_text = absl::StrFormat("%02X", subtype);
754 ImVec2 sub_size = ImGui::CalcTextSize(sub_text.c_str());
755 ImVec2 sub_pos(button_pos.x + (item_size - sub_size.x) / 2,
756 button_pos.y + (item_size - sub_size.y) / 2);
757 draw_list->AddText(sub_pos, IM_COL32(255, 255, 255, 220), sub_text.c_str());
758 }
759
760 // Border
761 bool is_static_editor_obj = (obj_id == static_editor_object_id_ && static_editor_object_id_ != -1);
762 // Static editor doesn't track subtype currently, so highlighting all subtypes of 0x31 is correct
763 // if we are editing 0x31 generic. But maybe we only edit specific subtype?
764 // Static editor usually edits the code/logic common to ID.
765 ImU32 border_color = is_selected ? ImGui::GetColorU32(theme.dungeon_selection_primary) : ImGui::GetColorU32(theme.panel_bg_darker);
766 float border_thickness = is_selected ? 3.0f : 1.0f;
767 draw_list->AddRect(
768 button_pos,
769 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
770 border_color, 0.0f, 0, border_thickness);
771
772 // Name/ID
773 std::string id_text = absl::StrFormat("%02X:%02X", obj_id, subtype);
774 ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
775 ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
776 button_pos.y + item_size - id_size.y - 2);
777 draw_list->AddText(id_pos, ImGui::GetColorU32(theme.text_primary),
778 id_text.c_str());
779
780 ImGui::PopID();
781 custom_col = (custom_col + 1) % columns;
782 }
783 }
784 }
785 }
786
787 ImGui::EndChild();
788}
789
790bool DungeonObjectSelector::MatchesObjectFilter(int obj_id, int filter_type) {
791 switch (filter_type) {
792 case 1: // Walls
793 return obj_id >= 0x10 && obj_id <= 0x1F;
794 case 2: // Floors
795 return obj_id >= 0x20 && obj_id <= 0x2F;
796 case 3: // Chests
797 return obj_id == 0xF9 || obj_id == 0xFA;
798 case 4: // Doors
799 return obj_id >= 0x17 && obj_id <= 0x1E;
800 case 5: // Decorations
801 return obj_id >= 0x30 && obj_id <= 0x3F;
802 case 6: // Stairs
803 return obj_id >= 0x138 && obj_id <= 0x13B;
804 default: // All
805 return true;
806 }
807}
808
810 const zelda3::RoomObject& object, int& width, int& height) {
811 // Size is a single 4-bit value (0-15), NOT two separate nibbles
812 // Size represents repetition count for the object's draw routine
813 int size = object.size_ & 0x0F;
814
815 // Base 16x16 (2x2 tiles), extension depends on object orientation
816 // Most objects extend horizontally, some (0x60-0x7F) extend vertically
817 if (object.id_ >= 0x60 && object.id_ <= 0x7F) {
818 // Vertical objects
819 width = 16;
820 height = 16 + size * 16;
821 } else {
822 // Horizontal objects (default)
823 width = 16 + size * 16;
824 height = 16;
825 }
826
827 width = std::min(width, 256);
828 height = std::min(height, 256);
829}
830
833 return;
834 }
835
836 // Create object with specified position
837 auto placed_object = preview_object_;
838 placed_object.set_x(static_cast<uint8_t>(x));
839 placed_object.set_y(static_cast<uint8_t>(y));
840
841 // Call placement callback
842 object_placement_callback_(placed_object);
843}
844
847 ImGui::Text("Dungeon editor system not initialized");
848 return;
849 }
850
851 auto& system = **dungeon_editor_system_;
852
853 ImGui::Text("Sprite Editor");
854 Separator();
855
856 // Display current room sprites
857 auto current_room = system.GetCurrentRoom();
858 auto sprites_result = system.GetSpritesByRoom(current_room);
859
860 if (sprites_result.ok()) {
861 auto sprites = sprites_result.value();
862 ImGui::Text("Sprites in room %d: %zu", current_room, sprites.size());
863
864 // Show first few sprites in compact format
865 int display_count = std::min(3, static_cast<int>(sprites.size()));
866 for (int i = 0; i < display_count; ++i) {
867 const auto& sprite = sprites[i];
868 ImGui::Text("ID:%d Type:%d (%d,%d)", sprite.sprite_id,
869 static_cast<int>(sprite.type), sprite.x, sprite.y);
870 }
871 if (sprites.size() > 3) {
872 ImGui::Text("... and %zu more", sprites.size() - 3);
873 }
874 } else {
875 ImGui::Text("Error loading sprites");
876 }
877
878 // Quick sprite placement
879 Separator();
880 ImGui::Text("Quick Add Sprite");
881
882 ImGui::InputInt("ID", &new_sprite_id_);
883 ImGui::InputInt("X", &new_sprite_x_);
884 ImGui::InputInt("Y", &new_sprite_y_);
885
886 if (ImGui::Button("Add Sprite")) {
888 sprite_data.sprite_id = new_sprite_id_;
890 sprite_data.x = new_sprite_x_;
891 sprite_data.y = new_sprite_y_;
892 sprite_data.layer = 0;
893
894 auto status = system.AddSprite(sprite_data);
895 if (!status.ok()) {
896 ImGui::Text("Error adding sprite");
897 }
898 }
899}
900
903 ImGui::Text("Dungeon editor system not initialized");
904 return;
905 }
906
907 auto& system = **dungeon_editor_system_;
908
909 ImGui::Text("Item Editor");
910 Separator();
911
912 // Display current room items
913 auto current_room = system.GetCurrentRoom();
914 auto items_result = system.GetItemsByRoom(current_room);
915
916 if (items_result.ok()) {
917 auto items = items_result.value();
918 ImGui::Text("Items in room %d: %zu", current_room, items.size());
919
920 // Show first few items in compact format
921 int display_count = std::min(3, static_cast<int>(items.size()));
922 for (int i = 0; i < display_count; ++i) {
923 const auto& item = items[i];
924 ImGui::Text("ID:%d Type:%d (%d,%d)", item.item_id,
925 static_cast<int>(item.type), item.x, item.y);
926 }
927 if (items.size() > 3) {
928 ImGui::Text("... and %zu more", items.size() - 3);
929 }
930 } else {
931 ImGui::Text("Error loading items");
932 }
933
934 // Quick item placement
935 Separator();
936 ImGui::Text("Quick Add Item");
937
938 ImGui::InputInt("ID", &new_item_id_);
939 ImGui::InputInt("X", &new_item_x_);
940 ImGui::InputInt("Y", &new_item_y_);
941
942 if (ImGui::Button("Add Item")) {
944 item_data.item_id = new_item_id_;
946 item_data.x = new_item_x_;
947 item_data.y = new_item_y_;
948 item_data.room_id = current_room;
949 item_data.is_hidden = false;
950
951 auto status = system.AddItem(item_data);
952 if (!status.ok()) {
953 ImGui::Text("Error adding item");
954 }
955 }
956}
957
960 ImGui::Text("Dungeon editor system not initialized");
961 return;
962 }
963
964 auto& system = **dungeon_editor_system_;
965
966 ImGui::Text("Entrance Editor");
967 Separator();
968
969 // Display current room entrances
970 auto current_room = system.GetCurrentRoom();
971 auto entrances_result = system.GetEntrancesByRoom(current_room);
972
973 if (entrances_result.ok()) {
974 auto entrances = entrances_result.value();
975 ImGui::Text("Entrances: %zu", entrances.size());
976
977 for (const auto& entrance : entrances) {
978 ImGui::Text("ID:%d -> Room:%d (%d,%d)", entrance.entrance_id,
979 entrance.target_room_id, entrance.target_x,
980 entrance.target_y);
981 }
982 } else {
983 ImGui::Text("Error loading entrances");
984 }
985
986 // Quick room connection
987 Separator();
988 ImGui::Text("Connect Rooms");
989
990 ImGui::InputInt("Target Room", &entrance_target_room_id_);
991 ImGui::InputInt("Source X", &entrance_source_x_);
992 ImGui::InputInt("Source Y", &entrance_source_y_);
993 ImGui::InputInt("Target X", &entrance_target_x_);
994 ImGui::InputInt("Target Y", &entrance_target_y_);
995
996 if (ImGui::Button("Connect")) {
997 auto status = system.ConnectRooms(current_room, entrance_target_room_id_,
1000 if (!status.ok()) {
1001 ImGui::Text("Error connecting rooms");
1002 }
1003 }
1004}
1005
1007 const auto& theme = AgentUI::GetTheme();
1008
1009 ImGui::Text("Door Editor");
1010 Separator();
1011
1012 // Show doors from the Room data (if available)
1013 if (rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) {
1014 const auto& room = (*rooms_)[current_room_id_];
1015 const auto& doors = room.GetDoors();
1016
1017 ImGui::Text("Room Doors: %zu", doors.size());
1018
1019 if (!doors.empty()) {
1020 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.15f, 0.5f));
1021 if (ImGui::BeginChild("##DoorList", ImVec2(-1, 120), true)) {
1022 for (size_t i = 0; i < doors.size(); ++i) {
1023 const auto& door = doors[i];
1024 auto [tile_x, tile_y] = door.GetTileCoords();
1025
1026 ImGui::PushID(static_cast<int>(i));
1027
1028 // Draw door info with type name
1029 std::string type_name(zelda3::GetDoorTypeName(door.type));
1030 std::string dir_name(zelda3::GetDoorDirectionName(door.direction));
1031 ImGui::Text("[%zu] %s (%s) at tile(%d,%d)",
1032 i, type_name.c_str(), dir_name.c_str(), tile_x, tile_y);
1033
1034 // Delete button
1035 ImGui::SameLine();
1036 if (ImGui::SmallButton("X")) {
1037 // Remove door (mutable ref needed)
1038 auto& mutable_room = (*rooms_)[current_room_id_];
1039 mutable_room.RemoveDoor(i);
1040 }
1041
1042 ImGui::PopID();
1043 }
1044 }
1045 ImGui::EndChild();
1046 ImGui::PopStyleColor();
1047 }
1048
1049 // Door type selector
1050 Separator();
1051 ImGui::Text("Door Type:");
1052 static int selected_door_type = static_cast<int>(zelda3::DoorType::NormalDoor);
1053
1054 // Build door type combo items (common types)
1055 constexpr std::array<zelda3::DoorType, 20> door_types = {
1076 };
1077
1078 if (ImGui::BeginCombo("##DoorType",
1079 std::string(zelda3::GetDoorTypeName(
1080 static_cast<zelda3::DoorType>(selected_door_type))).c_str())) {
1081 for (auto door_type : door_types) {
1082 bool is_selected = (selected_door_type == static_cast<int>(door_type));
1083 if (ImGui::Selectable(std::string(zelda3::GetDoorTypeName(door_type)).c_str(),
1084 is_selected)) {
1085 selected_door_type = static_cast<int>(door_type);
1086 }
1087 if (is_selected) {
1088 ImGui::SetItemDefaultFocus();
1089 }
1090 }
1091 ImGui::EndCombo();
1092 }
1093
1094 // Instructions
1095 ImGui::TextWrapped("Click on a room wall edge to place a door. Doors snap to valid positions.");
1096 } else {
1097 ImGui::Text("No room selected");
1098 }
1099
1100 // Legacy dungeon editor system support (if available)
1102 Separator();
1103 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "Advanced Door System:");
1104
1105 auto& system = **dungeon_editor_system_;
1106 auto current_room = system.GetCurrentRoom();
1107 auto doors_result = system.GetDoorsByRoom(current_room);
1108
1109 if (doors_result.ok()) {
1110 auto doors = doors_result.value();
1111 ImGui::Text("System Doors: %zu", doors.size());
1112 }
1113
1114 // Legacy door creation (for target room linking)
1115 ImGui::InputInt("Target Room", &door_target_room_);
1116 if (ImGui::Button("Link to Room")) {
1118 door_data.room_id = current_room;
1119 door_data.x = door_x_;
1120 door_data.y = door_y_;
1121 door_data.direction = door_direction_;
1123 door_data.target_x = door_x_;
1124 door_data.target_y = door_y_;
1125 door_data.is_locked = false;
1126 door_data.requires_key = false;
1127 door_data.key_type = 0;
1128
1129 auto status = system.AddDoor(door_data);
1130 if (!status.ok()) {
1131 ImGui::Text("Error linking door to room");
1132 }
1133 }
1134 }
1135}
1136
1139 ImGui::Text("Dungeon editor system not initialized");
1140 return;
1141 }
1142
1143 auto& system = **dungeon_editor_system_;
1144
1145 ImGui::Text("Chest Editor");
1146 Separator();
1147
1148 // Display current room chests
1149 auto current_room = system.GetCurrentRoom();
1150 auto chests_result = system.GetChestsByRoom(current_room);
1151
1152 if (chests_result.ok()) {
1153 auto chests = chests_result.value();
1154 ImGui::Text("Chests: %zu", chests.size());
1155
1156 for (const auto& chest : chests) {
1157 ImGui::Text("ID:%d (%d,%d) Item:%d", chest.chest_id, chest.x, chest.y,
1158 chest.item_id);
1159 }
1160 } else {
1161 ImGui::Text("Error loading chests");
1162 }
1163
1164 // Quick chest creation
1165 Separator();
1166 ImGui::Text("Add Chest");
1167
1168 ImGui::InputInt("X", &chest_x_);
1169 ImGui::InputInt("Y", &chest_y_);
1170 ImGui::InputInt("Item ID", &chest_item_id_);
1171 ImGui::Checkbox("Big", &chest_big_);
1172
1173 if (ImGui::Button("Add Chest")) {
1175 chest_data.room_id = current_room;
1176 chest_data.x = chest_x_;
1177 chest_data.y = chest_y_;
1178 chest_data.is_big_chest = chest_big_;
1179 chest_data.item_id = chest_item_id_;
1180 chest_data.item_quantity = 1;
1181
1182 auto status = system.AddChest(chest_data);
1183 if (!status.ok()) {
1184 ImGui::Text("Error adding chest");
1185 }
1186 }
1187}
1188
1191 ImGui::Text("Dungeon editor system not initialized");
1192 return;
1193 }
1194
1195 auto& system = **dungeon_editor_system_;
1196
1197 ImGui::Text("Room Properties");
1198 Separator();
1199
1200 auto current_room = system.GetCurrentRoom();
1201 auto properties_result = system.GetRoomProperties(current_room);
1202
1203 if (properties_result.ok()) {
1204 auto properties = properties_result.value();
1205
1206 // Copy current values (only update from ROM data, not every frame)
1207 // Safe string copy with bounds checking
1208 size_t name_len =
1209 std::min(properties.name.length(), sizeof(room_name_) - 1);
1210 std::memcpy(room_name_, properties.name.c_str(), name_len);
1211 room_name_[name_len] = '\0';
1212 dungeon_id_ = properties.dungeon_id;
1213 floor_level_ = properties.floor_level;
1214 is_boss_room_ = properties.is_boss_room;
1215 is_save_room_ = properties.is_save_room;
1216 music_id_ = properties.music_id;
1217
1218 ImGui::InputText("Name", room_name_, sizeof(room_name_));
1219 ImGui::InputInt("Dungeon ID", &dungeon_id_);
1220 ImGui::InputInt("Floor", &floor_level_);
1221 ImGui::InputInt("Music", &music_id_);
1222 ImGui::Checkbox("Boss Room", &is_boss_room_);
1223 ImGui::Checkbox("Save Room", &is_save_room_);
1224
1225 if (ImGui::Button("Save Properties")) {
1227 new_properties.room_id = current_room;
1228 new_properties.name = room_name_;
1229 new_properties.dungeon_id = dungeon_id_;
1230 new_properties.floor_level = floor_level_;
1231 new_properties.is_boss_room = is_boss_room_;
1232 new_properties.is_save_room = is_save_room_;
1233 new_properties.music_id = music_id_;
1234
1235 auto status = system.SetRoomProperties(current_room, new_properties);
1236 if (!status.ok()) {
1237 ImGui::Text("Error saving properties");
1238 }
1239 }
1240 } else {
1241 ImGui::Text("Error loading properties");
1242 }
1243
1244 // Dungeon settings summary
1245 Separator();
1246 ImGui::Text("Dungeon Settings");
1247
1248 auto dungeon_settings_result = system.GetDungeonSettings();
1249 if (dungeon_settings_result.ok()) {
1250 auto settings = dungeon_settings_result.value();
1251 ImGui::Text("Dungeon: %s", settings.name.c_str());
1252 ImGui::Text("Rooms: %d", settings.total_rooms);
1253 ImGui::Text("Start: %d", settings.starting_room_id);
1254 ImGui::Text("Boss: %d", settings.boss_room_id);
1255 }
1256}
1257
1263
1265 zelda3::RoomObject obj(obj_id, 0, 0, 0x12, 0);
1266 obj.SetRom(rom_);
1267 obj.EnsureTilesLoaded();
1268 return obj;
1269}
1270
1274
1276 gfx::BackgroundBuffer** out) {
1277 if (!rom_ || !rom_->is_loaded()) {
1278 return false;
1279 }
1280
1281 // Check if room context changed - invalidate cache if so
1282 if (rooms_ && current_room_id_ < static_cast<int>(rooms_->size())) {
1283 const auto& room = (*rooms_)[current_room_id_];
1284 if (!room.IsLoaded()) {
1285 return false; // Can't render without loaded room
1286 }
1287
1288 // Invalidate cache if room/palette/blockset changed
1290 room.blockset != cached_preview_blockset_ ||
1291 room.palette != cached_preview_palette_) {
1294 cached_preview_blockset_ = room.blockset;
1295 cached_preview_palette_ = room.palette;
1296 }
1297 } else {
1298 return false;
1299 }
1300
1301 // Check if already in cache
1302 auto it = preview_cache_.find(obj_id);
1303 if (it != preview_cache_.end()) {
1304 *out = it->second.get();
1305 return (*out)->bitmap().texture() != nullptr;
1306 }
1307
1308 // Create new preview buffer
1309 auto& room = (*rooms_)[current_room_id_];
1310 const uint8_t* gfx_data = room.get_gfx_buffer().data();
1311
1312 // Create preview buffer large enough for object
1313 // Use a reasonable size based on object dimensions (minimum 64x64)
1314 int buffer_size = std::max(static_cast<int>(size), 128);
1315 auto preview = std::make_unique<gfx::BackgroundBuffer>(buffer_size, buffer_size);
1316
1317 // CRITICAL: Initialize bitmap before drawing
1318 preview->EnsureBitmapInitialized();
1319
1320 // Create object and render it at (1,1) for preview with small margin
1321 // This places the object at pixel (8,8) giving some padding from edges
1322 zelda3::RoomObject obj(obj_id, 1, 1, 0x12, 0);
1323 obj.SetRom(rom_);
1324 obj.EnsureTilesLoaded();
1325
1326 if (obj.tiles().empty()) {
1327 // Try to render even without tiles (some objects may still work)
1328 // Fall through to drawer which has fallback handling
1329 }
1330
1331 // Apply palette to bitmap surface (match Room::RenderRoomGraphics approach)
1332 auto& bitmap = preview->bitmap();
1333 {
1334 std::vector<SDL_Color> colors(256);
1335 // Flatten palette group into SDL colors
1336 // Dungeon palettes have 6 sub-palettes of 15 colors each = 90 colors
1337 size_t color_index = 0;
1338 for (size_t pal_idx = 0; pal_idx < current_palette_group_.size() && color_index < 256; ++pal_idx) {
1339 const auto& pal = current_palette_group_[pal_idx];
1340 for (size_t i = 0; i < pal.size() && color_index < 256; ++i) {
1341 ImVec4 rgb = pal[i].rgb();
1342 colors[color_index++] = {
1343 static_cast<Uint8>(rgb.x),
1344 static_cast<Uint8>(rgb.y),
1345 static_cast<Uint8>(rgb.z),
1346 255
1347 };
1348 }
1349 }
1350 // Transparent color key at index 255
1351 colors[255] = {0, 0, 0, 0};
1352 bitmap.SetPalette(colors);
1353 if (bitmap.surface()) {
1354 SDL_SetColorKey(bitmap.surface(), SDL_TRUE, 255);
1355 SDL_SetSurfaceBlendMode(bitmap.surface(), SDL_BLENDMODE_BLEND);
1356 }
1357 }
1358
1359 zelda3::ObjectDrawer drawer(rom_, current_room_id_, gfx_data);
1360 drawer.InitializeDrawRoutines();
1361
1362 auto status =
1363 drawer.DrawObject(obj, *preview, *preview, current_palette_group_);
1364 if (!status.ok()) {
1365 return false;
1366 }
1367
1368 // Sync bitmap data to SDL surface after drawing
1369 if (bitmap.modified() && bitmap.surface() && bitmap.mutable_data().size() > 0) {
1370 SDL_LockSurface(bitmap.surface());
1371 size_t surface_size = bitmap.surface()->h * bitmap.surface()->pitch;
1372 size_t data_size = bitmap.mutable_data().size();
1373 if (surface_size >= data_size) {
1374 memcpy(bitmap.surface()->pixels, bitmap.mutable_data().data(), data_size);
1375 }
1376 SDL_UnlockSurface(bitmap.surface());
1377 }
1378
1379 // Check if bitmap has content
1380 if (bitmap.size() == 0) {
1381 return false;
1382 }
1383
1384 // Create texture
1388
1389 if (!bitmap.texture()) {
1390 return false;
1391 }
1392
1393 // Store in cache and return
1394 *out = preview.get();
1395 preview_cache_[obj_id] = std::move(preview);
1396 return true;
1397}
1398
1400 const zelda3::RoomObject& object, ImVec2 top_left, float size) {
1401 gfx::BackgroundBuffer* preview = nullptr;
1402 if (!GetOrCreatePreview(object.id_, size, &preview)) {
1403 return false;
1404 }
1405
1406 // Draw the cached preview image
1407 auto& bitmap = preview->bitmap();
1408 if (!bitmap.texture()) {
1409 return false;
1410 }
1411
1412 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1413 ImVec2 bottom_right(top_left.x + size, top_left.y + size);
1414 draw_list->AddImage((ImTextureID)(intptr_t)bitmap.texture(), top_left,
1415 bottom_right);
1416 return true;
1417}
1418
1419} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:128
std::function< void(const zelda3::RoomObject &) object_placement_callback_)
std::map< int, std::unique_ptr< gfx::BackgroundBuffer > > preview_cache_
bool GetOrCreatePreview(int obj_id, float size, gfx::BackgroundBuffer **out)
zelda3::DungeonObjectEditor * object_editor_
std::array< zelda3::Room, 0x128 > * rooms_
void CalculateObjectDimensions(const zelda3::RoomObject &object, int &width, int &height)
zelda3::DungeonObjectRegistry object_registry_
std::function< void(int)> object_double_click_callback_
void RenderObjectPrimitive(const zelda3::RoomObject &object, int x, int y)
std::unique_ptr< zelda3::DungeonEditorSystem > * dungeon_editor_system_
std::function< void(const zelda3::RoomObject &) object_selected_callback_)
zelda3::RoomObject MakePreviewObject(int obj_id) const
bool DrawObjectPreview(const zelda3::RoomObject &object, ImVec2 top_left, float size)
bool MatchesObjectFilter(int obj_id, int filter_type)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:34
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:110
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Definition arena.h:101
static Arena & Get()
Definition arena.cc:19
Lightweight RAII guard for existing Canvas instances.
Definition canvas.h:872
auto height() const
Definition canvas.h:498
auto width() const
Definition canvas.h:497
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1011
void AddImageAt(ImTextureID texture, ImVec2 local_top_left, ImVec2 size)
Definition canvas.cc:2407
void DrawRect(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1341
void DrawText(const std::string &text, int x, int y)
Definition canvas.cc:1346
static CustomObjectManager & Get()
void RegisterVanillaRange(int16_t start_id, int16_t end_id)
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 set_x(uint8_t x)
Definition room_object.h:73
void SetRom(Rom *rom)
Definition room_object.h:68
struct chest_data chest_data
Legacy chest data structure.
#define ICON_MD_IMAGE
Definition icons.h:982
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
Definition agent_chat.cc:23
DoorType
Door types from ALTTP.
Definition door_types.h:33
@ 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.
int GetObjectSubtype(int object_id)
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
Definition door_types.h:161
std::string GetObjectName(int object_id)
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
Definition door_types.h:106
Legacy chest data structure.
Definition zelda.h:438
Treasure chest.
Definition zelda.h:425
uint8_t y
Definition zelda.h:427
uint8_t x
Definition zelda.h:426
std::optional< float > grid_step
Definition canvas.h:70
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89