yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_room_render.cc
Go to the documentation of this file.
3
4#include <algorithm>
5#include <cstdio>
6
7#include "absl/strings/str_format.h"
14#include "imgui/imgui.h"
15
16namespace yaze::editor {
17
18namespace {
19
20ImVec4 WithAlpha(ImVec4 color, float alpha) {
21 color.w = alpha;
22 return color;
23}
24
26 if (!canvas_rt.draw_list) {
27 return;
28 }
29
30 const auto& theme = AgentUI::GetTheme();
31 const float scale = std::max(canvas_rt.scale, 0.01f);
32 const ImVec2 room_min(canvas_rt.canvas_p0.x + canvas_rt.scrolling.x,
33 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y);
34 const ImVec2 room_max(room_min.x + (512.0f * scale),
35 room_min.y + (512.0f * scale));
36 const ImVec2 canvas_max(canvas_rt.canvas_p0.x + canvas_rt.canvas_sz.x,
37 canvas_rt.canvas_p0.y + canvas_rt.canvas_sz.y);
38
39 canvas_rt.draw_list->PushClipRect(canvas_rt.canvas_p0, canvas_max, true);
40 canvas_rt.draw_list->AddRectFilled(
41 room_min, room_max,
42 ImGui::GetColorU32(WithAlpha(theme.editor_background, 0.92f)));
43 canvas_rt.draw_list->AddRect(room_min, room_max,
44 ImGui::GetColorU32(theme.panel_border_color),
45 0.0f, 1.5f);
46 canvas_rt.draw_list->AddText(ImVec2(room_min.x + 12.0f, room_min.y + 12.0f),
47 ImGui::GetColorU32(theme.text_secondary_gray),
48 "Room image pending");
49 canvas_rt.draw_list->PopClipRect();
50}
51
52} // namespace
53
55 const gui::CanvasRuntime& canvas_rt) {
57 canvas_rt.canvas_sz.x > 1.0f && canvas_rt.canvas_sz.y > 1.0f;
59 return;
60 }
61
62 canvas_capture_x_ = static_cast<int>(canvas_rt.canvas_p0.x);
63 canvas_capture_y_ = static_cast<int>(canvas_rt.canvas_p0.y);
64 canvas_capture_width_ = static_cast<int>(canvas_rt.canvas_sz.x);
65 canvas_capture_height_ = static_cast<int>(canvas_rt.canvas_sz.y);
66}
67
69 const gui::CanvasRuntime& canvas_rt) {
70 if (!pending_scroll_target_.has_value()) {
71 return;
72 }
73
74 constexpr int kRoomPixelWidth = 512;
75 constexpr int kRoomPixelHeight = 512;
76 constexpr int kDungeonTileSize = 8;
77
78 const auto [target_x, target_y] = pending_scroll_target_.value();
79 float scale = canvas_.global_scale();
80 if (scale <= 0.0f) {
81 scale = 1.0f;
82 }
83
84 const float pixel_x = static_cast<float>(target_x * kDungeonTileSize) * scale;
85 const float pixel_y = static_cast<float>(target_y * kDungeonTileSize) * scale;
86 const ImVec2 view_size = canvas_rt.canvas_sz;
87 const ImVec2 content_size(static_cast<float>(kRoomPixelWidth) * scale,
88 static_cast<float>(kRoomPixelHeight) * scale);
89
90 const ImVec2 desired_scroll((view_size.x * 0.5f) - pixel_x,
91 (view_size.y * 0.5f) - pixel_y);
93 gui::ClampScroll(desired_scroll, content_size, view_size));
95}
96
98 // Room tags are edited in Inspect; they must not cover canvas objects.
100 return;
101 }
102 const auto* room = rooms_ ? rooms_->GetIfLoaded(room_id) : nullptr;
103 if (!room) {
104 return;
105 }
106
107 const auto label = dungeon_project_labels::GetRoomLabel(project_, room_id);
108 char text1[160];
109 snprintf(text1, sizeof(text1), "[%03X] %s", room_id, label.c_str());
110
111 const auto metadata =
112 absl::StrFormat("B:%02X P:%02X L:%02X S:%02X%s", room->blockset(),
113 room->palette(), room->layout_id(), room->spriteset(),
114 object_interaction_enabled_ ? "" : " RO");
115 const float pad = 10.0f;
116 const ImVec2 hud_pos(canvas_.zero_point().x + pad,
117 canvas_.zero_point().y + pad);
118 const float content_width = std::max(ImGui::CalcTextSize(text1).x,
119 ImGui::CalcTextSize(metadata.c_str()).x);
120 const float width = std::min(
121 {560.0f, canvas_.canvas_size().x - pad * 2, content_width + 16.0f});
122 if (width <= 16.0f) {
123 return;
124 }
125 const ImVec2 hud_size(width, 0);
126 // A window ID must distinguish the editable and comparison viewers.
127 const auto hud_id = absl::StrFormat("##MetadataHUD_%p", this);
128
129 gui::DrawCanvasHUD(hud_id.c_str(), hud_pos, hud_size, [&]() {
130 ImGui::TextWrapped("%s", text1);
131 ImGui::PushTextWrapPos(0);
132 ImGui::TextDisabled("%s", metadata.c_str());
133 ImGui::PopTextWrapPos();
134 });
135}
136
138 int room_id) {
140
141 if (!room.AreObjectsLoaded()) {
142 room.LoadObjects();
143 }
144
145 if (!room.AreSpritesLoaded()) {
146 room.LoadSprites();
147 }
148
149 if (!room.ArePotItemsLoaded()) {
150 room.LoadPotItems();
151 }
152
153 // Existing room buffers can still be stale after palette, object, or layout
154 // edits. Let Room's dirty-state-aware preparation decide whether the cached
155 // graphics need to be rebuilt instead of treating an allocated BG1 surface
156 // as proof that the room is current.
158
159 if (rom_ && rom_->is_loaded()) {
161 }
162}
163
165 int room_id) {
167 if (gui::AcceptRoomObjectDrop(&obj_drop)) {
168 const DungeonCanvasTransform transform(
170 auto [tile_x, tile_y] = transform.ScreenToRoomTiles(
171 ImGui::GetMousePos(), dungeon_coords::kTileSize);
172 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
173 const uint8_t object_size = obj_drop.size;
174 zelda3::RoomObject new_obj(static_cast<int16_t>(obj_drop.object_id),
175 static_cast<uint8_t>(tile_x),
176 static_cast<uint8_t>(tile_y), object_size, 0);
177 const size_t before = room.GetTileObjects().size();
179 room_id, new_obj, tile_x, tile_y);
180 if (room.GetTileObjects().size() > before) {
182 }
183 }
184 }
185
186 gui::SpriteDragPayload sprite_drop;
187 if (gui::AcceptSpriteDrop(&sprite_drop)) {
188 const DungeonCanvasTransform transform(
190 const auto [room_x, room_y] =
191 transform.ScreenToRoomPixelCoordinates(ImGui::GetMousePos());
192 if (dungeon_coords::IsWithinBounds(room_x, room_y)) {
193 const int sprite_x = room_x / dungeon_coords::kSpriteTileSize;
194 const int sprite_y = room_y / dungeon_coords::kSpriteTileSize;
195 zelda3::Sprite new_sprite(static_cast<uint8_t>(sprite_drop.sprite_id),
196 static_cast<uint8_t>(sprite_x),
197 static_cast<uint8_t>(sprite_y), 0, 0);
200 .context()) {
201 ctx->NotifyMutation(MutationDomain::kSprites);
202 }
203 room.GetSprites().push_back(new_sprite);
204 room.MarkSpritesDirty();
207 .context()) {
208 ctx->NotifyInvalidateCache(MutationDomain::kSprites);
209 }
210 }
211 }
212}
213
215 zelda3::Room& room, int room_id) {
218 return false;
219 }
220 auto* panel = minecart_track_panel_;
221 const auto* payload = ImGui::GetDragDropPayload();
222 const bool palette_drag =
223 payload && (payload->IsDataType(kMinecartNodeDragPayload) ||
224 (payload->IsDataType(gui::kDragPayloadRoomObject) &&
225 payload->DataSize == sizeof(gui::RoomObjectDragPayload) &&
226 static_cast<const gui::RoomObjectDragPayload*>(payload->Data)
227 ->object_id == 0x31));
228 const bool own_actor_drag =
229 payload && payload->IsDataType(kMinecartNodeDragPayload) &&
230 payload->DataSize == sizeof(MinecartToolPayload) &&
231 static_cast<const MinecartToolPayload*>(payload->Data)->owner == panel &&
232 static_cast<const MinecartToolPayload*>(payload->Data)->source_viewer ==
233 this;
234 if ((panel->canvas_editing() || panel->collision_stamping() ||
235 panel->placement_brush()) &&
238 (!palette_drag || own_actor_drag)) {
239 // Choosing another placement/paint tool ends cart editing immediately.
240 panel->set_canvas_editing(false);
241 panel->set_collision_stamping(false);
242 panel->CancelNodePlacement();
243 }
244 const bool owns_input =
245 (panel->canvas_editing() || panel->collision_stamping() ||
246 panel->placement_brush() || palette_drag) &&
248 if (owns_input && !minecart_owned_input_) {
254 }
255 minecart_owned_input_ = owns_input;
256 if (!owns_input) {
258 return false;
259 }
260 if (DrawMinecartNodePlacement(rt, room, room_id)) {
263 return true;
264 }
265 const auto brush = panel->placement_brush();
266 if (panel->collision_stamping() || (brush && brush->collision_only)) {
268 if (room_canvas_shortcut_focus_ && ImGui::IsKeyPressed(ImGuiKey_Escape)) {
269 panel->set_collision_stamping(false);
270 panel->CancelNodePlacement();
272 return true;
273 }
276 rt, room.custom_collision(), room_id,
277 brush ? brush->collision : panel->collision_stamp_value(),
278 brush ? brush->size : panel->collision_stamp_size(),
279 panel->collision_stamp_callback());
280 return true;
281 }
283 const MinecartCanvasBinding binding{
284 .tracks = panel->GetTracks(),
285 .selected = panel->selected_track(),
286 .picking = panel->GetPickingTrackIndex(),
287 .revision = panel->canvas_revision(),
288 .enabled = panel->canvas_editing(),
289 .select = [panel](int id) { panel->SelectTrack(id); },
290 .move =
291 [panel](const MinecartTrack& before, const MinecartTrack& after) {
292 return panel->MoveCartStart(before, after);
293 },
294 .cancel_pick = [panel]() { panel->CancelPick(); }};
295 return minecart_canvas_tool_.Draw(rt, room, room_id,
298}
299
311
313 const gui::CanvasRuntime& canvas_rt, zelda3::Room& room, int room_id) {
314 PrepareRoomStateForCanvas(room, room_id);
315
316 const bool drew_background = DrawRoomBackgroundLayers(room_id);
317 if (!drew_background) {
318 DrawRoomRenderFallback(canvas_rt);
319 }
320
322 DrawMaskHighlights(canvas_rt, room);
323 }
324
325 RenderEntityOverlay(canvas_rt, room);
326
327 const bool crumble_owns_input = DrawCrumbleCanvas(canvas_rt);
328 const bool tool_owns_input =
329 crumble_owns_input || DrawMinecartCanvas(canvas_rt, room, room_id);
330 if (object_interaction_enabled_ && !tool_owns_input) {
336
337 const auto selected = object_interaction_.GetSelectedObjectIndices();
338 if (selected.size() == 1) {
339 const auto& objects = room.GetTileObjects();
340 const size_t idx = selected.front();
341 if (idx < objects.size()) {
342 const auto& obj = objects[idx];
343 gui::BeginRoomObjectDragSource(static_cast<uint16_t>(obj.id_), room_id,
344 obj.x_, obj.y_, obj.size_);
345 }
346 }
347
349 const auto sel = object_interaction_.GetSelectedEntity();
350 if (sel.type == EntityType::Sprite) {
351 const auto& sprites = room.GetSprites();
352 if (sel.index < sprites.size()) {
353 const auto& sprite = sprites[sel.index];
354 gui::BeginSpriteDragSource(sprite.id(), room_id);
355 }
356 }
357 }
358
359 HandleTouchLongPressContextMenu(canvas_rt, room);
360 }
361
362 if (object_interaction_enabled_ && !tool_owns_input) {
363 HandleRoomCanvasDropTargets(room, room_id);
364 }
365}
366
367} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:199
InteractionContext * context() const
Get the interaction context.
bool DrawCanvas(const gui::CanvasRuntime &rt, bool interactive, bool shortcut_focus)
std::pair< int, int > ScreenToRoomPixelCoordinates(ImVec2 screen) const
std::pair< int, int > ScreenToRoomTiles(ImVec2 screen, int tile_size) const
bool DrawMinecartNodePlacement(const gui::CanvasRuntime &rt, zelda3::Room &room, int room_id)
void HandleRoomCanvasDropTargets(zelda3::Room &room, int room_id)
std::optional< std::pair< int, int > > pending_scroll_target_
void SyncCanvasCaptureRegion(const gui::CanvasRuntime &canvas_rt)
bool DrawCrumbleCanvas(const gui::CanvasRuntime &rt)
bool DrawMinecartCanvas(const gui::CanvasRuntime &rt, zelda3::Room &room, int room_id)
DungeonObjectInteraction object_interaction_
void PrepareRoomStateForCanvas(zelda3::Room &room, int room_id)
void DrawRoomCanvasContent(const gui::CanvasRuntime &canvas_rt, zelda3::Room &room, int room_id)
const project::YazeProject * project_
void DrawMaskHighlights(const gui::CanvasRuntime &rt, const zelda3::Room &room)
void ConsumePendingCanvasScroll(const gui::CanvasRuntime &canvas_rt)
void RenderEntityOverlay(const gui::CanvasRuntime &rt, const zelda3::Room &room)
void HandleTouchLongPressContextMenu(const gui::CanvasRuntime &rt, const zelda3::Room &room)
MinecartTrackEditorPanel * minecart_track_panel_
void SetSelectedObjects(const std::vector< size_t > &indices)
void SetCurrentRoom(DungeonRoomStore *rooms, int room_id)
std::vector< size_t > GetSelectedObjectIndices() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
zelda3::Room * GetIfLoaded(int room_id)
SpriteInteractionHandler & sprite_handler()
void ClearAllEntitySelections()
Clear all entity selections.
void CancelCurrentMode()
Cancel current mode and return to Select.
bool IsSelectMode() const
Check if in normal selection mode.
bool Draw(const gui::CanvasRuntime &rt, const zelda3::Room &room, int room_id, bool editable, bool shortcut_focus, const MinecartCanvasBinding &binding)
void Draw(const gui::CanvasRuntime &rt, const zelda3::CustomCollisionMap &collision, int room_id, uint8_t value, int size, const MinecartCollisionApply &apply)
const std::optional< MinecartToolPayload > & placement_brush() const
bool PlaceObjectAt(int room_id, const zelda3::RoomObject &object, int x, int y)
Place a new object. Returns false if blocked by ROM limits.
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:211
static Arena & Get()
Definition arena.cc:24
void set_scrolling(ImVec2 scroll)
Definition canvas.h:367
auto global_scale() const
Definition canvas.h:413
auto canvas_size() const
Definition canvas.h:373
auto zero_point() const
Definition canvas.h:364
auto scrolling() const
Definition canvas.h:366
const CustomCollisionMap & custom_collision() const
Definition room.h:559
bool ArePotItemsLoaded() const
Definition room.h:975
void MarkSpritesDirty()
Definition room.h:319
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:316
void PrepareForRender(std::optional< uint8_t > entrance_blockset=std::nullopt)
Definition room.cc:1058
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:445
void LoadObjects()
Definition room.cc:1891
void LoadPotItems()
Definition room.cc:4612
bool AreObjectsLoaded() const
Definition room.h:972
void LoadSprites()
Definition room.cc:2887
bool AreSpritesLoaded() const
Definition room.h:973
A class for managing sprites in the overworld and underworld.
Definition sprite.h:39
const AgentUITheme & GetTheme()
bool IsWithinBounds(int canvas_x, int canvas_y, int margin=0)
Check if coordinates are within room bounds.
std::string GetRoomLabel(const project::YazeProject *project, int room_id)
Editors are the view controllers for the application.
constexpr const char * kMinecartNodeDragPayload
constexpr const char * kDragPayloadRoomObject
Definition drag_drop.h:21
bool BeginSpriteDragSource(int sprite_id, int room_id)
Definition drag_drop.h:76
ImVec2 ClampScroll(ImVec2 scroll, ImVec2 content_px, ImVec2 canvas_px)
void DrawCanvasHUD(const char *label, const ImVec2 &pos, const ImVec2 &size, std::function< void()> draw_content)
Draw a stylized Heads-Up Display (HUD) for canvas status.
bool AcceptRoomObjectDrop(RoomObjectDragPayload *out)
Definition drag_drop.h:171
bool AcceptSpriteDrop(SpriteDragPayload *out)
Definition drag_drop.h:145
bool BeginRoomObjectDragSource(uint16_t object_id, int room_id, int pos_x, int pos_y, uint8_t size=0)
Definition drag_drop.h:99
std::span< const MinecartTrack > tracks