85 ImGui::TextUnformatted(
86 "Lyra ROM rev1: 192 stored / 160 active / read-only");
88 ImGui::TextDisabled(
"Sprite lists and area overlays unavailable");
90 ImGui::SetNextItemWidth(210.0f);
91 if (ImGui::SliderInt(
"Inspect map", &map_id, 0x80, 0xBF,
"0x%02X")) {
95 ImGui::TextDisabled(
"Sea cells are stored, not active gameplay maps");
109 if (editor_->sidebar_) {
110 editor_->sidebar_->DrawQuickProperties(
111 editor_->current_map_, editor_->current_map_lock_,
112 editor_->dependencies_.project, true);
125 const bool show_context_menu = !lyra_preview;
140 if (editor_->canvas_nav_) {
141 editor_->canvas_nav_->BeginCanvasViewport();
143 ImGui::BeginChild(ImGui::GetID(
reinterpret_cast<void*
>(intptr_t{7})),
144 ImGui::GetContentRegionAvail(),
true,
145 ImGuiWindowFlags_AlwaysVerticalScrollbar |
146 ImGuiWindowFlags_AlwaysHorizontalScrollbar |
147 ImGuiWindowFlags_NoScrollWithMouse);
151 editor_->ow_map_canvas_.set_scrolling(ImVec2(0, 0));
157 if (editor_->canvas_nav_) {
158 editor_->canvas_nav_->EndCanvasViewport(canvas_rt.hovered);
161 if (editor_->overworld_.is_loaded()) {
165 editor_->status_ = editor_->CheckForCurrentMap();
172 if (editor_->entity_renderer_ && editor_->show_entities_) {
173 editor_->entity_renderer_->DrawExits(canvas_rt, editor_->current_world_);
174 editor_->entity_renderer_->DrawEntrances(canvas_rt,
175 editor_->current_world_);
176 editor_->entity_renderer_->DrawItems(canvas_rt, editor_->current_world_);
177 editor_->entity_renderer_->DrawSprites(canvas_rt, editor_->current_world_,
178 editor_->game_state_);
179 FilterHoveredEntityByMode();
180 }
else if (editor_->entity_renderer_) {
182 editor_->entity_renderer_->ResetHoveredEntity();
190 editor_->status_ = editor_->CheckForCurrentMap();
191 if (canvas_rt.hovered) {
192 editor_->HandleMapInteraction();
197 editor_->CheckForOverworldEdits();
202 editor_->TryPlaceArmedItem()) {
205 editor_->entity_renderer_) {
206 auto hovered_entity = editor_->entity_renderer_->hovered_entity();
209 if (!editor_->is_dragging_entity_ && hovered_entity &&
210 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
211 editor_->dragged_entity_ = hovered_entity;
212 editor_->is_dragging_entity_ =
true;
213 editor_->drag_item_snapshot_.reset();
214 if (hovered_entity->entity_type_ ==
216 editor_->SelectItemByIdentity(
217 *
static_cast<zelda3::OverworldItem*
>(hovered_entity));
219 editor_->drag_item_snapshot_ = editor_->CaptureItemUndoSnapshot();
221 if (editor_->dragged_entity_->entity_type_ ==
223 editor_->dragged_entity_free_movement_ =
true;
228 if (editor_->is_dragging_entity_ && editor_->dragged_entity_ &&
229 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
230 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
231 ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
232 float scale = canvas_rt.scale;
234 editor_->dragged_entity_->x_ += mouse_delta.x / scale;
235 editor_->dragged_entity_->y_ += mouse_delta.y / scale;
240 if (editor_->is_dragging_entity_ &&
241 ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
242 if (editor_->dragged_entity_) {
243 float end_scale = canvas_rt.scale;
246 editor_->dragged_entity_free_movement_, end_scale);
248 editor_->dragged_entity_->UpdateMapProperties(
249 editor_->dragged_entity_->map_id_, &editor_->overworld_);
250 if (editor_->drag_item_snapshot_) {
252 editor_->PushItemUndoAction(
253 std::move(*editor_->drag_item_snapshot_),
254 "Move overworld item");
258 editor_->rom_->set_dirty(
true);
260 editor_->drag_item_snapshot_.reset();
261 editor_->is_dragging_entity_ =
false;
262 editor_->dragged_entity_ =
nullptr;
263 editor_->dragged_entity_free_movement_ =
false;
270 gui::TileDragPayload tile_drop;
272 editor_->RequestTile16Selection(tile_drop.tile_id);
283 editor_->DrawItemInsertPopup();
295void OverworldCanvasRenderer::DrawMapWithAreaLayers(
int map_index,
int map_x,
296 int map_y,
float scale) {
297 auto& canvas = editor_->ow_map_canvas_;
298 auto& bitmap = editor_->maps_bmp_[map_index];
300 const ImVec2 origin(canvas.zero_point().x + canvas.scrolling().x + map_x,
301 canvas.zero_point().y + canvas.scrolling().y + map_y);
303 if (!bitmap.palette().empty()) {
304 const ImVec4 rgb = bitmap.palette()[0].rgb();
305 ImGui::GetWindowDrawList()->AddRectFilled(
306 origin, ImVec2(origin.x + size, origin.y + size),
307 IM_COL32(
static_cast<int>(rgb.x),
static_cast<int>(rgb.y),
308 static_cast<int>(rgb.z), 255));
311 canvas.DrawBitmap(bitmap, map_x, map_y, scale);
313 if (!editor_->show_overlay_preview_) {
316 auto* layer = EnsureOverlayLayer(map_index);
317 if (layer && layer->bitmap.is_active() && layer->bitmap.texture()) {
318 canvas.DrawBitmap(layer->bitmap, map_x, map_y, scale,
319 layer->background ? 255 : 128);
328OverworldCanvasRenderer::EnsureOverlayLayer(
int map_index) {
329 const auto* map = editor_->overworld_.overworld_map(map_index);
333 const bool has_overlay =
335 auto it = overlay_layers_.find(map_index);
337 it == overlay_layers_.end() || it->second.
has_overlay != has_overlay;
338 const bool stale = !missing && map->is_built() &&
339 it->second.bitmap_serial != map->bitmap_serial();
340 if (missing || stale) {
341 auto& entry = overlay_layers_[map_index];
342 entry.has_overlay = has_overlay;
343 entry.bitmap_serial = map->bitmap_serial();
345 auto layer = editor_->overworld_.BuildSubscreenOverlayLayer(map_index);
346 if (!layer.ok() || layer->overlay_screen < 0) {
347 entry.has_overlay =
false;
350 map = editor_->overworld_.overworld_map(map_index);
351 entry.bitmap_serial = map->bitmap_serial();
352 entry.background = layer->background;
353 if (!entry.bitmap.is_active()) {
357 entry.bitmap.set_data(layer->pixels);
359 entry.bitmap.SetPalette(map->current_palette());
365 it = overlay_layers_.find(map_index);
367 if (!it->second.has_overlay) {
373void OverworldCanvasRenderer::DrawOverworldMaps() {
375 float scale = editor_->ow_map_canvas_.global_scale();
383 constexpr int kMaxMapBuildsPerFrame = 4;
384 int builds_this_frame = 0;
385 const ImVec2 clip_min = ImGui::GetWindowDrawList()->GetClipRectMin();
386 const ImVec2 clip_max = ImGui::GetWindowDrawList()->GetClipRectMax();
387 const ImVec2 canvas_origin(editor_->ow_map_canvas_.zero_point().x +
388 editor_->ow_map_canvas_.scrolling().x,
389 editor_->ow_map_canvas_.zero_point().y +
390 editor_->ow_map_canvas_.scrolling().y);
393 for (
int i = 0; i < 0x40; i++) {
394 const int xx = i % 8;
395 const int yy = i / 8;
396 int world_index = i + (editor_->current_world_ * 0x40);
399 if (world_index < 0 ||
400 world_index >= editor_->overworld_.inspected_map_count()) {
408 const float left = canvas_origin.x + map_x;
409 const float top = canvas_origin.y + map_y;
410 if (left >= clip_max.x || top >= clip_max.y ||
411 left + map_extent <= clip_min.x || top + map_extent <= clip_min.y) {
417 auto& map_bitmap = editor_->maps_bmp_[world_index];
418 if (!map_bitmap.is_active()) {
419 if (builds_this_frame < kMaxMapBuildsPerFrame) {
420 editor_->EnsureMapTexture(world_index);
423 }
else if (!map_bitmap.texture()) {
424 editor_->EnsureMapTexture(world_index);
430 bool can_draw = editor_->maps_bmp_[world_index].texture() &&
431 editor_->maps_bmp_[world_index].is_active();
434 DrawMapWithAreaLayers(world_index, map_x, map_y, scale);
437 ImDrawList* draw_list = ImGui::GetWindowDrawList();
438 ImVec2 canvas_pos = editor_->ow_map_canvas_.zero_point();
439 ImVec2 scrolling = editor_->ow_map_canvas_.scrolling();
441 ImVec2 placeholder_pos = ImVec2(canvas_pos.x + scrolling.x + map_x,
442 canvas_pos.y + scrolling.y + map_y);
445 ImVec2 placeholder_size = ImVec2(scaled_size, scaled_size);
448 const auto& theme = AgentUI::GetTheme();
449 draw_list->AddRectFilled(
451 ImVec2(placeholder_pos.x + placeholder_size.x,
452 placeholder_pos.y + placeholder_size.y),
454 theme.editor_background));
457 ImVec2 spinner_pos = ImVec2(placeholder_pos.x + placeholder_size.x / 2,
458 placeholder_pos.y + placeholder_size.y / 2);
460 const float spinner_radius = 8.0f * scale;
461 const float rotation =
static_cast<float>(ImGui::GetTime()) * 3.0f;
462 const float start_angle = rotation;
463 const float end_angle = rotation +
IM_PI * 1.5f;
465 draw_list->PathArcTo(spinner_pos, spinner_radius, start_angle, end_angle,
467 draw_list->PathStroke(ImGui::GetColorU32(theme.status_active), 0,
477absl::Status OverworldCanvasRenderer::DrawTile16Selector() {
478 if (!editor_->blockset_selector_) {
480 const auto& theme = AgentUI::GetTheme();
491 editor_->blockset_selector_ = std::make_unique<gui::TileSelectorWidget>(
492 "OwBlocksetSelector", selector_config);
493 editor_->blockset_selector_->AttachCanvas(&editor_->blockset_canvas_);
496 editor_->blockset_canvas_.SetShowBuiltinContextMenu(
false);
499 editor_->UpdateBlocksetSelectorState();
500 editor_->blockset_canvas_.ClearContextMenuItems();
502 auto open_tile16_editor = [
this](
int tile_id) {
503 editor_->RequestTile16Selection(tile_id);
504 if (editor_->dependencies_.window_manager) {
505 const size_t session_id =
506 editor_->dependencies_.window_manager->GetActiveSessionId();
507 editor_->dependencies_.window_manager->OpenWindowFloating(
508 session_id, OverworldPanelIds::kTile16Editor);
509 editor_->dependencies_.window_manager->MarkWindowRecentlyUsed(
510 OverworldPanelIds::kTile16Editor);
515 edit_tile_item.
label =
"Edit Tile16";
517 edit_tile_item.
shortcut =
"Double-click";
519 return editor_->blockset_selector_ &&
520 editor_->blockset_selector_->GetSelectedTileID() >= 0;
522 edit_tile_item.
callback = [
this, open_tile16_editor]() {
523 if (!editor_->blockset_selector_) {
526 open_tile16_editor(editor_->blockset_selector_->GetSelectedTileID());
528 editor_->blockset_canvas_.AddContextMenuItem(edit_tile_item);
529 auto graphics_item = edit_tile_item;
530 graphics_item.
label =
"Edit Graphics...";
532 graphics_item.callback = [
this]() {
533 if (editor_->blockset_selector_) {
534 editor_->status_ = editor_->OpenGraphicsForTile16Quadrant(
535 editor_->blockset_selector_->GetSelectedTileID(), 0);
538 editor_->blockset_canvas_.AddContextMenuItem(std::move(graphics_item));
539 editor_->blockset_canvas_.AddContextMenuItem(
540 editor_->blockset_selector_->CopyTileIdMenuItem());
542 const float selector_available_width = ImGui::GetContentRegionAvail().x;
543 DrawSheetScaleControl(
"##Tile16Scale", &tile16_scale_mode_);
544 const int tile_rows =
550 tile16_scale_mode_, 0.25f, 4.0f,
552 editor_->blockset_selector_->SetDisplayScale(selector_layout.
display_scale);
560 if (editor_->blockset_selector_->DrawFilterBar()) {
561 editor_->RequestTile16Selection(
562 editor_->blockset_selector_->GetSelectedTileID());
567 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
568 const bool grid_visible = ImGui::BeginChild(
569 "##Tile16Grid", ImVec2(0.0f, 0.0f), ImGuiChildFlags_None,
570 ImGuiWindowFlags_AlwaysVerticalScrollbar);
571 ImGui::PopStyleVar();
574 return absl::OkStatus();
577 gfx::Bitmap& atlas = editor_->tile16_blockset_.atlas;
578 bool atlas_ready = editor_->map_blockset_loaded_ && atlas.
is_active();
579 auto result = editor_->blockset_selector_->Render(atlas, atlas_ready);
581 if (result.selection_changed) {
582 editor_->RequestTile16Selection(result.selected_tile);
589 if (result.tile_double_clicked) {
590 open_tile16_editor(result.selected_tile);
594 return absl::OkStatus();
597void OverworldCanvasRenderer::DrawTile8Selector() {
608 auto canvas_rt =
gui::BeginCanvas(editor_->graphics_bin_canvas_, frame_opts);
610 if (editor_->all_gfx_loaded_) {
613 int offset = 0x40 * (key + 1);
614 int top_left_y = canvas_rt.canvas_p0.y + 2;
616 top_left_y = canvas_rt.canvas_p0.y + 0x40 * key;
618 auto texture = value.texture();
619 canvas_rt.draw_list->AddImage(
620 (ImTextureID)(intptr_t)texture,
621 ImVec2(canvas_rt.canvas_p0.x + 2, top_left_y),
622 ImVec2(canvas_rt.canvas_p0.x + 0x100, canvas_rt.canvas_p0.y + offset),
623 ImVec2(0, 0), ImVec2(1, 1), ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
628 gui::EndCanvas(editor_->graphics_bin_canvas_, canvas_rt, frame_opts);
631absl::Status OverworldCanvasRenderer::DrawAreaGraphics() {
632 if (editor_->overworld_.is_loaded()) {
634 if (!editor_->current_graphics_set_.contains(editor_->current_map_)) {
635 auto status = editor_->overworld_.EnsureMapBuilt(editor_->current_map_);
640 editor_->overworld_.overworld_map(editor_->current_map_);
642 return absl::FailedPreconditionError(
643 "Current overworld map not loaded");
645 editor_->palette_ = map->current_palette();
646 auto bmp = std::make_unique<gfx::Bitmap>();
648 bmp->SetPalette(editor_->palette_);
649 editor_->current_graphics_set_[editor_->current_map_] = std::move(bmp);
652 editor_->current_graphics_set_[editor_->current_map_].get());
656 const float available_width = ImGui::GetContentRegionAvail().x;
657 DrawSheetScaleControl(
"##AreaGraphicsScale", &area_graphics_scale_mode_);
660 0.35f, 4.0f, ImGui::GetStyle().ScrollbarSize);
679 auto canvas_rt =
gui::BeginCanvas(editor_->current_gfx_canvas_, frame_opts);
682 if (editor_->current_graphics_set_.contains(editor_->current_map_) &&
683 editor_->current_graphics_set_[editor_->current_map_]->is_active()) {
684 editor_->current_gfx_canvas_.DrawBitmap(
685 *editor_->current_graphics_set_[editor_->current_map_], 2, 2,
688 editor_->current_gfx_canvas_.DrawTileSelector(16.0f *
691 gui::EndCanvas(editor_->current_gfx_canvas_, canvas_rt, frame_opts);
694 return absl::OkStatus();
697void OverworldCanvasRenderer::DrawV3Settings() {
699 ImGui::TextWrapped(tr(
"ZSCustomOverworld v3 settings"));
702 if (!editor_->rom_ || !editor_->rom_->is_loaded()) {
707 const uint8_t asm_version =
709 ImGui::Text(tr(
"ASM Version: 0x%02X"), asm_version);
711 if (asm_version == 0x00 || asm_version == 0xFF || asm_version < 0x03) {
713 ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.25f, 1.0f),
714 tr(
"v3 controls require ZSCustomOverworld v3+"));
715 ImGui::TextDisabled(tr(
"Apply the v3 patch to enable map-size editing."));
719 auto* map = editor_->overworld_.overworld_map(editor_->current_map_);
721 ImGui::TextDisabled(tr(
"Current map is unavailable."));
726 ImGui::Text(tr(
"Current map: 0x%02X"), editor_->current_map_);
727 ImGui::Text(tr(
"Parent map: 0x%02X"), map->parent());
729 static int selected_area_size = 0;
730 selected_area_size =
static_cast<int>(map->area_size());
731 const char* area_size_labels[] = {
"Small",
"Wide",
"Tall",
"Large"};
732 ImGui::SetNextItemWidth(220.0f);
733 ImGui::Combo(tr(
"Area Size"), &selected_area_size, area_size_labels,
734 IM_ARRAYSIZE(area_size_labels));
737 auto status = editor_->overworld_.ConfigureMultiAreaMap(
740 editor_->RefreshOverworldMap();
741 editor_->RefreshSiblingMapGraphics(editor_->current_map_,
true);
747 tr(
"Area-size changes update sibling map relationships for this parent "
791void OverworldCanvasRenderer::DrawOverworldProperties() {
792 static bool init_properties =
false;
794 if (!init_properties) {
795 for (
int i = 0; i < 0x40; i++) {
796 std::string area_graphics_str = absl::StrFormat(
797 "%02hX", editor_->overworld_.overworld_map(i)->area_graphics());
798 editor_->properties_canvas_
799 .mutable_labels(OverworldEditor::OverworldProperty::LW_AREA_GFX)
800 ->push_back(area_graphics_str);
802 area_graphics_str = absl::StrFormat(
804 editor_->overworld_.overworld_map(i + 0x40)->area_graphics());
805 editor_->properties_canvas_
806 .mutable_labels(OverworldEditor::OverworldProperty::DW_AREA_GFX)
807 ->push_back(area_graphics_str);
809 std::string area_palette_str = absl::StrFormat(
810 "%02hX", editor_->overworld_.overworld_map(i)->area_palette());
811 editor_->properties_canvas_
812 .mutable_labels(OverworldEditor::OverworldProperty::LW_AREA_PAL)
813 ->push_back(area_palette_str);
815 area_palette_str = absl::StrFormat(
816 "%02hX", editor_->overworld_.overworld_map(i + 0x40)->area_palette());
817 editor_->properties_canvas_
818 .mutable_labels(OverworldEditor::OverworldProperty::DW_AREA_PAL)
819 ->push_back(area_palette_str);
821 std::string sprite_gfx_str = absl::StrFormat(
822 "%02hX", editor_->overworld_.overworld_map(i)->sprite_graphics(1));
823 editor_->properties_canvas_
824 .mutable_labels(OverworldEditor::OverworldProperty::LW_SPR_GFX_PART1)
825 ->push_back(sprite_gfx_str);
827 sprite_gfx_str = absl::StrFormat(
828 "%02hX", editor_->overworld_.overworld_map(i)->sprite_graphics(2));
829 editor_->properties_canvas_
830 .mutable_labels(OverworldEditor::OverworldProperty::LW_SPR_GFX_PART2)
831 ->push_back(sprite_gfx_str);
833 sprite_gfx_str = absl::StrFormat(
835 editor_->overworld_.overworld_map(i + 0x40)->sprite_graphics(1));
836 editor_->properties_canvas_
837 .mutable_labels(OverworldEditor::OverworldProperty::DW_SPR_GFX_PART1)
838 ->push_back(sprite_gfx_str);
840 sprite_gfx_str = absl::StrFormat(
842 editor_->overworld_.overworld_map(i + 0x40)->sprite_graphics(2));
843 editor_->properties_canvas_
844 .mutable_labels(OverworldEditor::OverworldProperty::DW_SPR_GFX_PART2)
845 ->push_back(sprite_gfx_str);
847 std::string sprite_palette_str = absl::StrFormat(
848 "%02hX", editor_->overworld_.overworld_map(i)->sprite_palette(1));
849 editor_->properties_canvas_
850 .mutable_labels(OverworldEditor::OverworldProperty::LW_SPR_PAL_PART1)
851 ->push_back(sprite_palette_str);
853 sprite_palette_str = absl::StrFormat(
854 "%02hX", editor_->overworld_.overworld_map(i)->sprite_palette(2));
855 editor_->properties_canvas_
856 .mutable_labels(OverworldEditor::OverworldProperty::LW_SPR_PAL_PART2)
857 ->push_back(sprite_palette_str);
859 sprite_palette_str = absl::StrFormat(
861 editor_->overworld_.overworld_map(i + 0x40)->sprite_palette(1));
862 editor_->properties_canvas_
863 .mutable_labels(OverworldEditor::OverworldProperty::DW_SPR_PAL_PART1)
864 ->push_back(sprite_palette_str);
866 sprite_palette_str = absl::StrFormat(
868 editor_->overworld_.overworld_map(i + 0x40)->sprite_palette(2));
869 editor_->properties_canvas_
870 .mutable_labels(OverworldEditor::OverworldProperty::DW_SPR_PAL_PART2)
871 ->push_back(sprite_palette_str);
873 init_properties =
true;
876 ImGui::Text(tr(
"Area Gfx LW/DW"));
877 editor_->properties_canvas_.UpdateInfoGrid(
878 ImVec2(256, 256), 32, OverworldEditor::OverworldProperty::LW_AREA_GFX);
880 editor_->properties_canvas_.UpdateInfoGrid(
881 ImVec2(256, 256), 32, OverworldEditor::OverworldProperty::DW_AREA_GFX);
884 ImGui::Text(tr(
"Sprite Gfx LW/DW"));
885 editor_->properties_canvas_.UpdateInfoGrid(
886 ImVec2(256, 256), 32,
887 OverworldEditor::OverworldProperty::LW_SPR_GFX_PART1);
889 editor_->properties_canvas_.UpdateInfoGrid(
890 ImVec2(256, 256), 32,
891 OverworldEditor::OverworldProperty::DW_SPR_GFX_PART1);
893 editor_->properties_canvas_.UpdateInfoGrid(
894 ImVec2(256, 256), 32,
895 OverworldEditor::OverworldProperty::LW_SPR_GFX_PART2);
897 editor_->properties_canvas_.UpdateInfoGrid(
898 ImVec2(256, 256), 32,
899 OverworldEditor::OverworldProperty::DW_SPR_GFX_PART2);
902 ImGui::Text(tr(
"Area Pal LW/DW"));
903 editor_->properties_canvas_.UpdateInfoGrid(
904 ImVec2(256, 256), 32, OverworldEditor::OverworldProperty::LW_AREA_PAL);
906 editor_->properties_canvas_.UpdateInfoGrid(
907 ImVec2(256, 256), 32, OverworldEditor::OverworldProperty::DW_AREA_PAL);
909 static bool show_gfx_group =
false;
910 ImGui::Checkbox(tr(
"Show Gfx Group Editor"), &show_gfx_group);
911 if (show_gfx_group) {
913 editor_->status_ = editor_->gfx_group_editor_.Update();