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, 0, 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 if ((panel->canvas_editing() || panel->collision_stamping()) &&
224 // Choosing another placement/paint tool ends cart editing immediately.
225 panel->set_canvas_editing(false);
226 panel->set_collision_stamping(false);
227 }
228 const bool owns_input =
229 (panel->canvas_editing() || panel->collision_stamping()) &&
231 if (owns_input && !minecart_owned_input_) {
237 }
238 minecart_owned_input_ = owns_input;
240 panel->SetActiveRoom(room_id);
241 if (panel->collision_stamping() && object_interaction_enabled_) {
243 if (room_canvas_shortcut_focus_ && ImGui::IsKeyPressed(ImGuiKey_Escape)) {
244 panel->set_collision_stamping(false);
245 return true;
246 }
249 rt, room.custom_collision(), room_id, panel->collision_stamp_value(),
250 panel->collision_stamp_size(), panel->collision_stamp_callback());
251 return true;
252 }
253 const MinecartCanvasBinding binding{
254 .tracks = panel->GetTracks(),
255 .selected = panel->selected_track(),
256 .picking = panel->GetPickingTrackIndex(),
257 .revision = panel->canvas_revision(),
258 .enabled = panel->canvas_editing(),
259 .select = [panel](int id) { panel->SelectTrack(id); },
260 .move =
261 [panel](const MinecartTrack& before, const MinecartTrack& after) {
262 return panel->MoveCartStart(before, after);
263 },
264 .cancel_pick = [panel]() { panel->CancelPick(); }};
265 return minecart_canvas_tool_.Draw(rt, room, room_id,
268}
269
281
283 const gui::CanvasRuntime& canvas_rt, zelda3::Room& room, int room_id) {
284 PrepareRoomStateForCanvas(room, room_id);
285
286 const bool drew_background = DrawRoomBackgroundLayers(room_id);
287 if (!drew_background) {
288 DrawRoomRenderFallback(canvas_rt);
289 }
290
292 DrawMaskHighlights(canvas_rt, room);
293 }
294
295 RenderEntityOverlay(canvas_rt, room);
296
297 const bool crumble_owns_input = DrawCrumbleCanvas(canvas_rt);
298 const bool tool_owns_input =
299 crumble_owns_input || DrawMinecartCanvas(canvas_rt, room, room_id);
300 if (object_interaction_enabled_ && !tool_owns_input) {
306
307 const auto selected = object_interaction_.GetSelectedObjectIndices();
308 if (selected.size() == 1) {
309 const auto& objects = room.GetTileObjects();
310 const size_t idx = selected.front();
311 if (idx < objects.size()) {
312 const auto& obj = objects[idx];
313 gui::BeginRoomObjectDragSource(static_cast<uint16_t>(obj.id_), room_id,
314 obj.x_, obj.y_, obj.size_);
315 }
316 }
317
319 const auto sel = object_interaction_.GetSelectedEntity();
320 if (sel.type == EntityType::Sprite) {
321 const auto& sprites = room.GetSprites();
322 if (sel.index < sprites.size()) {
323 const auto& sprite = sprites[sel.index];
324 gui::BeginSpriteDragSource(sprite.id(), room_id);
325 }
326 }
327 }
328
329 HandleTouchLongPressContextMenu(canvas_rt, room);
330 }
331
332 if (object_interaction_enabled_ && !tool_owns_input) {
333 HandleRoomCanvasDropTargets(room, room_id);
334 }
335}
336
337} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:187
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
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)
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:4611
bool AreObjectsLoaded() const
Definition room.h:972
void LoadSprites()
Definition room.cc:2886
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.
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