yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_canvas_renderer.cc
Go to the documentation of this file.
1// Related header
3
4#ifndef IM_PI
5#define IM_PI 3.14159265358979323846f
6#endif
7
8// C++ standard library headers
9#include <memory>
10#include <string>
11
12// Third-party library headers
13#include "absl/status/status.h"
14#include "absl/strings/str_format.h"
15#include "imgui/imgui.h"
16
17// Project headers
28#include "app/gfx/core/bitmap.h"
33#include "app/gui/core/icons.h"
34#include "app/gui/core/style.h"
37#include "rom/rom.h"
38#include "util/log.h"
40
41namespace yaze::editor {
42
45
46// =============================================================================
47// Main Canvas Drawing
48// =============================================================================
49
51 // Simplified map settings - compact row with popup panels for detailed
52 // editing
55 const EditingMode old_mode = editor_->current_mode;
56 bool has_selection = editor_->ow_map_canvas_.select_rect_active() &&
58
59 // Check if scratch space has data
60 bool scratch_has_data = editor_->scratch_space_.in_use;
61
62 // Pass PanelManager to toolbar for panel visibility management
63 editor_->toolbar_->Draw(
67 has_selection, scratch_has_data, editor_->rom_, &editor_->overworld_);
68
69 // Toolbar toggles don't currently update canvas usage mode.
70 if (old_mode != editor_->current_mode) {
74 } else {
76 }
77 }
78 }
79
80 // ==========================================================================
81 // PHASE 3: Modern BeginCanvas/EndCanvas Pattern
82 // ==========================================================================
83 // Context menu setup MUST happen BEFORE BeginCanvas (lesson from dungeon)
84 bool show_context_menu =
87 editor_->entity_renderer_->hovered_entity() == nullptr);
88
92 editor_->map_properties_system_->SetupCanvasContextMenu(
96 static_cast<int>(editor_->current_mode));
97 }
98
99 // Configure canvas frame options
100 gui::CanvasFrameOptions frame_opts;
102 frame_opts.draw_grid = true;
103 frame_opts.grid_step = 64.0f; // Map boundaries (512px / 8 maps)
104 frame_opts.draw_context_menu = show_context_menu;
105 frame_opts.draw_overlay = true;
106 frame_opts.render_popups = true;
107 frame_opts.use_child_window = false; // CRITICAL: Canvas has own pan logic
108
109 // Wrap in child window for scrollbars
112
113 // Keep canvas scroll at 0 - ImGui's child window handles all scrolling
114 // The scrollbars scroll the child window which moves the entire canvas
115 editor_->ow_map_canvas_.set_scrolling(ImVec2(0, 0));
116
117 // Begin canvas frame - this handles DrawBackground + DrawContextMenu
118 auto canvas_rt = gui::BeginCanvas(editor_->ow_map_canvas_, frame_opts);
120
121 // Handle pan via ImGui scrolling (instead of canvas internal scroll)
124
125 // Tile painting mode - handle tile edits and right-click tile picking
129 }
130
132 // Draw the 64 overworld map bitmaps
134
135 // Draw all entities using the new CanvasRuntime-based methods
137 editor_->entity_renderer_->DrawExits(canvas_rt, editor_->current_world_);
138 editor_->entity_renderer_->DrawEntrances(canvas_rt,
140 editor_->entity_renderer_->DrawItems(canvas_rt, editor_->current_world_);
141 editor_->entity_renderer_->DrawSprites(
143 }
144
145 // Draw overlay preview if enabled
147 editor_->map_properties_system_->DrawOverlayPreviewOnMap(
150 }
151
155 }
156
157 // Use canvas runtime hover state for map detection
158 if (canvas_rt.hovered) {
160 }
161
162 // --- BEGIN ENTITY DRAG/DROP LOGIC ---
165 auto hovered_entity = editor_->entity_renderer_->hovered_entity();
166
167 // 1. Initiate drag
168 if (!editor_->is_dragging_entity_ && hovered_entity &&
169 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
170 editor_->dragged_entity_ = hovered_entity;
175 }
176 }
177
178 // 2. Update drag
180 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
181 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
182 ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
183 float scale = canvas_rt.scale;
184 if (scale > 0.0f) {
185 editor_->dragged_entity_->x_ += mouse_delta.x / scale;
186 editor_->dragged_entity_->y_ += mouse_delta.y / scale;
187 }
188 }
189
190 // 3. End drag
192 ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
194 float end_scale = canvas_rt.scale;
195 MoveEntityOnGrid(editor_->dragged_entity_, canvas_rt.canvas_p0,
196 canvas_rt.scrolling,
198 // Pass overworld context for proper area size detection
201 editor_->rom_->set_dirty(true);
202 }
204 editor_->dragged_entity_ = nullptr;
206 }
207 }
208 // --- END ENTITY DRAG/DROP LOGIC ---
209
210 // --- TILE DROP TARGET ---
211 // Accept tile drops from the blockset selector onto the map canvas.
212 gui::TileDragPayload tile_drop;
213 if (gui::AcceptTileDrop(&tile_drop)) {
214 editor_->current_tile16_ = tile_drop.tile_id;
218 }
219 }
220 }
221
222 // End canvas frame - draws grid/overlay based on frame_opts
223 gui::EndCanvas(editor_->ow_map_canvas_, canvas_rt, frame_opts);
224 ImGui::EndChild();
225}
226
227// =============================================================================
228// Internal Canvas Drawing Helpers
229// =============================================================================
230
232 // Get the current zoom scale for positioning and sizing
233 float scale = editor_->ow_map_canvas_.global_scale();
234 if (scale <= 0.0f)
235 scale = 1.0f;
236
237 int xx = 0;
238 int yy = 0;
239 for (int i = 0; i < 0x40; i++) {
240 int world_index = i + (editor_->current_world_ * 0x40);
241
242 // Bounds checking to prevent crashes
243 if (world_index < 0 ||
244 world_index >= static_cast<int>(editor_->maps_bmp_.size())) {
245 continue; // Skip invalid map index
246 }
247
248 // Apply scale to positions for proper zoom support
249 int map_x = static_cast<int>(xx * kOverworldMapSize * scale);
250 int map_y = static_cast<int>(yy * kOverworldMapSize * scale);
251
252 // Check if the map has a texture, if not, ensure it gets loaded
253 if (!editor_->maps_bmp_[world_index].texture() &&
254 editor_->maps_bmp_[world_index].is_active()) {
255 editor_->EnsureMapTexture(world_index);
256 }
257
258 // Only draw if the map has a valid texture AND is active (has bitmap data)
259 // The current_map_ check was causing crashes when hovering over unbuilt maps
260 // because the bitmap would be drawn before EnsureMapBuilt() was called
261 bool can_draw = editor_->maps_bmp_[world_index].texture() &&
262 editor_->maps_bmp_[world_index].is_active();
263
264 if (can_draw) {
265 // Draw bitmap at scaled position with scale applied to size
267 map_x, map_y, scale);
268 } else {
269 // Draw a placeholder for maps that haven't loaded yet
270 ImDrawList* draw_list = ImGui::GetWindowDrawList();
271 ImVec2 canvas_pos = editor_->ow_map_canvas_.zero_point();
272 ImVec2 scrolling = editor_->ow_map_canvas_.scrolling();
273 // Apply scrolling offset and use already-scaled map_x/map_y
274 ImVec2 placeholder_pos = ImVec2(canvas_pos.x + scrolling.x + map_x,
275 canvas_pos.y + scrolling.y + map_y);
276 // Scale the placeholder size to match zoomed maps
277 float scaled_size = kOverworldMapSize * scale;
278 ImVec2 placeholder_size = ImVec2(scaled_size, scaled_size);
279
280 // Modern loading indicator with theme colors
281 const auto& theme = AgentUI::GetTheme();
282 draw_list->AddRectFilled(
283 placeholder_pos,
284 ImVec2(placeholder_pos.x + placeholder_size.x,
285 placeholder_pos.y + placeholder_size.y),
286 ImGui::GetColorU32(
287 theme.editor_background)); // Theme-aware background
288
289 // Animated loading spinner - scale spinner radius with zoom
290 ImVec2 spinner_pos = ImVec2(placeholder_pos.x + placeholder_size.x / 2,
291 placeholder_pos.y + placeholder_size.y / 2);
292
293 const float spinner_radius = 8.0f * scale;
294 const float rotation = static_cast<float>(ImGui::GetTime()) * 3.0f;
295 const float start_angle = rotation;
296 const float end_angle = rotation + IM_PI * 1.5f;
297
298 draw_list->PathArcTo(spinner_pos, spinner_radius, start_angle, end_angle,
299 12);
300 draw_list->PathStroke(ImGui::GetColorU32(theme.status_active), 0,
301 2.5f * scale);
302 }
303
304 xx++;
305 if (xx >= 8) {
306 yy++;
307 xx = 0;
308 }
309 }
310}
311
312// =============================================================================
313// Panel Drawing Methods
314// =============================================================================
315
318 ImGui::BeginGroup();
319 gui::BeginChildWithScrollbar("##Tile16SelectorScrollRegion");
321
323 gui::TileSelectorWidget::Config selector_config;
324 const auto& theme = AgentUI::GetTheme();
325 selector_config.tile_size = 16;
326 selector_config.display_scale = 2.0f;
327 selector_config.tiles_per_row = 8;
329 selector_config.draw_offset = ImVec2(2.0f, 0.0f);
330 selector_config.highlight_color = theme.selection_primary;
331
332 selector_config.enable_drag = true;
333 selector_config.show_hover_tooltip = true;
334
335 editor_->blockset_selector_ = std::make_unique<gui::TileSelectorWidget>(
336 "OwBlocksetSelector", selector_config);
338 }
339
341
342 // Tile ID search/jump bar
343 if (editor_->blockset_selector_->DrawFilterBar()) {
345 editor_->blockset_selector_->GetSelectedTileID();
348 if (!status.ok()) {
349 util::logf("Failed to set tile16: %s", status.message().data());
350 }
351 }
352
354 bool atlas_ready = editor_->map_blockset_loaded_ && atlas.is_active();
355 auto result = editor_->blockset_selector_->Render(atlas, atlas_ready);
356
357 if (result.selection_changed) {
358 editor_->current_tile16_ = result.selected_tile;
359 // Set the current tile in the editor (original behavior)
362 if (!status.ok()) {
363 util::logf("Failed to set tile16: %s", status.message().data());
364 }
365 // Note: We do NOT auto-scroll here because it breaks user interaction.
366 // The canvas should only scroll when explicitly requested (e.g., when
367 // selecting a tile from the overworld canvas via
368 // ScrollBlocksetCanvasToCurrentTile).
369 }
370
371 if (result.tile_double_clicked) {
375 }
376 }
377
378 ImGui::EndChild();
379 ImGui::EndGroup();
380 return absl::OkStatus();
381}
382
384 // Configure canvas frame options for graphics bin
385 gui::CanvasFrameOptions frame_opts;
387 frame_opts.draw_grid = true;
388 frame_opts.grid_step = 16.0f; // Tile8 grid
389 frame_opts.draw_context_menu = true;
390 frame_opts.draw_overlay = true;
391 frame_opts.render_popups = true;
392 frame_opts.use_child_window = false;
393
394 auto canvas_rt =
396
398 int key = 0;
399 for (auto& value : gfx::Arena::Get().gfx_sheets()) {
400 int offset = 0x40 * (key + 1);
401 int top_left_y = canvas_rt.canvas_p0.y + 2;
402 if (key >= 1) {
403 top_left_y = canvas_rt.canvas_p0.y + 0x40 * key;
404 }
405 auto texture = value.texture();
406 canvas_rt.draw_list->AddImage(
407 (ImTextureID)(intptr_t)texture,
408 ImVec2(canvas_rt.canvas_p0.x + 2, top_left_y),
409 ImVec2(canvas_rt.canvas_p0.x + 0x100,
410 canvas_rt.canvas_p0.y + offset));
411 key++;
412 }
413 }
414
415 gui::EndCanvas(editor_->graphics_bin_canvas_, canvas_rt, frame_opts);
416}
417
420 // Always ensure current map graphics are loaded
424 auto bmp = std::make_unique<gfx::Bitmap>();
425 bmp->Create(0x80, kOverworldMapSize, 0x08,
427 bmp->SetPalette(editor_->palette_);
432 }
433 }
434
435 // Configure canvas frame options for area graphics
436 gui::CanvasFrameOptions frame_opts;
438 frame_opts.draw_grid = true;
439 frame_opts.grid_step = 32.0f; // Tile selector grid
440 frame_opts.draw_context_menu = true;
441 frame_opts.draw_overlay = true;
442 frame_opts.render_popups = true;
443 frame_opts.use_child_window = false;
444
446 ImGui::BeginGroup();
447 gui::BeginChildWithScrollbar("##AreaGraphicsScrollRegion");
448
449 auto canvas_rt =
452
457 }
459
460 gui::EndCanvas(editor_->current_gfx_canvas_, canvas_rt, frame_opts);
461 ImGui::EndChild();
462 ImGui::EndGroup();
463 return absl::OkStatus();
464}
465
467 // v3 Settings panel - placeholder for ZSCustomOverworld configuration
468 ImGui::TextWrapped("ZSCustomOverworld v3 settings panel");
469 ImGui::Separator();
470
471 if (!editor_->rom_ || !editor_->rom_->is_loaded()) {
472 gui::CenterText("No ROM loaded");
473 return;
474 }
475
476 ImGui::TextWrapped(
477 "This panel will contain ZSCustomOverworld configuration options "
478 "such as custom map sizes, extended tile sets, and other v3 features.");
479
480 // TODO: Implement v3 settings UI
481 // Could include:
482 // - Custom map size toggles
483 // - Extended tileset configuration
484 // - Override settings
485 // - Version information display
486}
487
489 // Area Configuration panel
490 static bool show_custom_bg_color_editor = false;
491 static bool show_overlay_editor = false;
492 static int game_state = 0; // 0=Beginning, 1=Zelda Saved, 2=Master Sword
493
494 if (editor_->sidebar_) {
496 editor_->current_map_lock_, game_state,
497 show_custom_bg_color_editor, show_overlay_editor);
498 }
499
500 // Draw popups if triggered from sidebar
501 if (show_custom_bg_color_editor) {
502 ImGui::OpenPopup("CustomBGColorEditor");
503 show_custom_bg_color_editor = false; // Reset after opening
504 }
505 if (show_overlay_editor) {
506 ImGui::OpenPopup("OverlayEditor");
507 show_overlay_editor = false; // Reset after opening
508 }
509
510 if (ImGui::BeginPopup("CustomBGColorEditor")) {
512 editor_->map_properties_system_->DrawCustomBackgroundColorEditor(
513 editor_->current_map_, show_custom_bg_color_editor);
514 }
515 ImGui::EndPopup();
516 }
517
518 if (ImGui::BeginPopup("OverlayEditor")) {
520 editor_->map_properties_system_->DrawOverlayEditor(
521 editor_->current_map_, show_overlay_editor);
522 }
523 ImGui::EndPopup();
524 }
525}
526
528 static bool init_properties = false;
529
530 if (!init_properties) {
531 for (int i = 0; i < 0x40; i++) {
532 std::string area_graphics_str = absl::StrFormat(
533 "%02hX",
534 editor_->overworld_.overworld_map(i)->area_graphics());
537 ->push_back(area_graphics_str);
538
539 area_graphics_str = absl::StrFormat(
540 "%02hX",
541 editor_->overworld_.overworld_map(i + 0x40)->area_graphics());
544 ->push_back(area_graphics_str);
545
546 std::string area_palette_str = absl::StrFormat(
547 "%02hX",
548 editor_->overworld_.overworld_map(i)->area_palette());
551 ->push_back(area_palette_str);
552
553 area_palette_str = absl::StrFormat(
554 "%02hX",
555 editor_->overworld_.overworld_map(i + 0x40)->area_palette());
558 ->push_back(area_palette_str);
559
560 std::string sprite_gfx_str = absl::StrFormat(
561 "%02hX",
562 editor_->overworld_.overworld_map(i)->sprite_graphics(1));
566 ->push_back(sprite_gfx_str);
567
568 sprite_gfx_str = absl::StrFormat(
569 "%02hX",
570 editor_->overworld_.overworld_map(i)->sprite_graphics(2));
574 ->push_back(sprite_gfx_str);
575
576 sprite_gfx_str = absl::StrFormat(
577 "%02hX",
578 editor_->overworld_.overworld_map(i + 0x40)->sprite_graphics(1));
582 ->push_back(sprite_gfx_str);
583
584 sprite_gfx_str = absl::StrFormat(
585 "%02hX",
586 editor_->overworld_.overworld_map(i + 0x40)->sprite_graphics(2));
590 ->push_back(sprite_gfx_str);
591
592 std::string sprite_palette_str = absl::StrFormat(
593 "%02hX",
594 editor_->overworld_.overworld_map(i)->sprite_palette(1));
598 ->push_back(sprite_palette_str);
599
600 sprite_palette_str = absl::StrFormat(
601 "%02hX",
602 editor_->overworld_.overworld_map(i)->sprite_palette(2));
606 ->push_back(sprite_palette_str);
607
608 sprite_palette_str = absl::StrFormat(
609 "%02hX",
610 editor_->overworld_.overworld_map(i + 0x40)->sprite_palette(1));
614 ->push_back(sprite_palette_str);
615
616 sprite_palette_str = absl::StrFormat(
617 "%02hX",
618 editor_->overworld_.overworld_map(i + 0x40)->sprite_palette(2));
622 ->push_back(sprite_palette_str);
623 }
624 init_properties = true;
625 }
626
627 ImGui::Text("Area Gfx LW/DW");
629 ImVec2(256, 256), 32,
631 ImGui::SameLine();
633 ImVec2(256, 256), 32,
635 ImGui::Separator();
636
637 ImGui::Text("Sprite Gfx LW/DW");
639 ImVec2(256, 256), 32,
641 ImGui::SameLine();
643 ImVec2(256, 256), 32,
645 ImGui::SameLine();
647 ImVec2(256, 256), 32,
649 ImGui::SameLine();
651 ImVec2(256, 256), 32,
653 ImGui::Separator();
654
655 ImGui::Text("Area Pal LW/DW");
657 ImVec2(256, 256), 32,
659 ImGui::SameLine();
661 ImVec2(256, 256), 32,
663
664 static bool show_gfx_group = false;
665 ImGui::Checkbox("Show Gfx Group Editor", &show_gfx_group);
666 if (show_gfx_group) {
667 gui::BeginWindowWithDisplaySettings("Gfx Group Editor", &show_gfx_group);
670 }
671}
672
673} // namespace yaze::editor
void set_dirty(bool dirty)
Definition rom.h:134
bool is_loaded() const
Definition rom.h:132
EditorDependencies dependencies_
Definition editor.h:306
absl::Status DrawTile16Selector()
Draw the tile16 selector panel.
absl::Status DrawAreaGraphics()
Draw the area graphics panel.
void DrawMapProperties()
Draw the map properties panel (sidebar-based)
void DrawOverworldProperties()
Draw the overworld properties grid (debug/info view)
OverworldEditor * editor_
Non-owning pointer to the parent editor.
void DrawOverworldCanvas()
Draw the main overworld canvas with toolbar, maps, and entities. This is the primary entry point call...
void DrawV3Settings()
Draw the v3 settings panel.
void DrawTile8Selector()
Draw the tile8 selector panel (graphics bin)
void DrawOverworldMaps()
Render the 64 overworld map bitmaps to the canvas.
Main UI class for editing overworld maps in A Link to the Past.
std::unique_ptr< MapPropertiesSystem > map_properties_system_
absl::Status CheckForCurrentMap()
Check for map changes and refresh if needed.
zelda3::GameEntity * dragged_entity_
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > maps_bmp_
void CheckForOverworldEdits()
Check for tile edits - delegates to TilePaintingManager.
std::unique_ptr< OverworldSidebar > sidebar_
std::unique_ptr< OverworldEntityRenderer > entity_renderer_
void EnsureMapTexture(int map_index)
Ensure a specific map has its texture created.
std::unique_ptr< OverworldToolbar > toolbar_
std::unique_ptr< gui::TileSelectorWidget > blockset_selector_
bool ShowPanel(size_t session_id, const std::string &base_card_id)
absl::Status SetCurrentTile(int id)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Definition arena.h:152
static Arena & Get()
Definition arena.cc:21
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
bool is_active() const
Definition bitmap.h:384
void set_scrolling(ImVec2 scroll)
Definition canvas.h:446
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1157
auto global_scale() const
Definition canvas.h:491
auto select_rect_active() const
Definition canvas.h:487
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:280
auto selected_tiles() const
Definition canvas.h:488
void UpdateInfoGrid(ImVec2 bg_size, float grid_size=64.0f, int label_id=0)
Definition canvas.cc:582
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1093
void ClearContextMenuItems()
Definition canvas.cc:858
auto mutable_labels(int i)
Definition canvas.h:536
auto zero_point() const
Definition canvas.h:443
auto scrolling() const
Definition canvas.h:445
virtual void UpdateMapProperties(uint16_t map_id, const void *context=nullptr)=0
Update entity properties based on map position.
enum yaze::zelda3::GameEntity::EntityType entity_type_
auto current_area_palette() const
Definition overworld.h:567
auto is_loaded() const
Definition overworld.h:583
auto current_graphics() const
Definition overworld.h:553
auto overworld_map(int i) const
Definition overworld.h:528
void set_current_map(int i)
Definition overworld.h:589
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
constexpr ImVec2 kOverworldCanvasSize(kOverworldMapSize *8, kOverworldMapSize *8)
constexpr unsigned int kOverworldMapSize
constexpr ImVec2 kCurrentGfxCanvasSize(0x100+1, 0x10 *0x40+1)
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement, float scale)
Move entity to grid-aligned position based on mouse.
Definition entity.cc:62
constexpr ImVec2 kGraphicsBinCanvasSize(0x100+1, kNumSheetsToLoad *0x40+1)
void EndCanvas(Canvas &canvas)
Definition canvas.cc:1591
void BeginPadding(int i)
Definition style.cc:274
void BeginChildBothScrollbars(int id)
Definition style.cc:319
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1568
bool AcceptTileDrop(TileDragPayload *out)
Accept a tile16 drop. Returns true if a payload was accepted.
Definition drag_drop.h:106
void EndNoPadding()
Definition style.cc:286
void CenterText(const char *text)
void EndPadding()
Definition style.cc:278
void BeginNoPadding()
Definition style.cc:282
void EndWindowWithDisplaySettings()
Definition style.cc:269
void BeginWindowWithDisplaySettings(const char *id, bool *active, const ImVec2 &size, ImGuiWindowFlags flags)
Definition style.cc:249
void BeginChildWithScrollbar(const char *str_id)
Definition style.cc:290
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:115
constexpr int kNumTile16Individual
Definition overworld.h:239
#define IM_PI
static constexpr const char * kTile16Editor
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
std::optional< float > grid_step
Definition canvas.h:70