yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
object_editor_content.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <vector>
5
9#include "imgui/imgui.h"
12
13namespace yaze::editor {
14
16 std::shared_ptr<zelda3::DungeonObjectEditor> object_editor)
17 : object_editor_(std::move(object_editor)) {}
18
26
38
50
52 auto* viewer = ResolveCanvasViewer();
53 if (!viewer) {
55 return;
56 }
57
58 auto& interaction = viewer->object_interaction();
59 cached_selection_count_ = interaction.GetSelectionCount();
60
61 if (!object_editor_) {
62 return;
63 }
64
65 auto indices = interaction.GetSelectedObjectIndices();
66 (void)object_editor_->ClearSelection();
67 for (size_t idx : indices) {
68 (void)object_editor_->AddToSelection(idx);
69 }
70}
71
72void ObjectEditorContent::Draw(bool* p_open) {
73 (void)p_open;
74 auto* viewer = ResolveCanvasViewer();
75 const auto& theme = AgentUI::GetTheme();
76
77 gui::SectionHeader(ICON_MD_TUNE, "Object Editor", theme.text_info);
78
79 if (!viewer || !object_editor_) {
80 ImGui::TextDisabled("Object editor unavailable");
81 return;
82 }
83
86
89 object_editor_->DrawPropertyUI();
90 } else if (viewer->object_interaction().HasEntitySelection()) {
91 ImGui::Spacing();
92 ImGui::TextDisabled(
93 "An entity is selected. Use the matching editor for entity-specific "
94 "properties.");
95 } else {
96 ImGui::Spacing();
97 ImGui::TextDisabled(
98 "Select one or more room objects to edit their properties here.");
99 }
100
103}
104
106 const auto& theme = AgentUI::GetTheme();
107 auto* viewer = ResolveCanvasViewer();
108 if (!viewer) {
109 return;
110 }
111
112 const auto& interaction = viewer->object_interaction();
113 const size_t selection_count = interaction.GetSelectionCount();
114
115 if (selection_count == 0) {
116 ImGui::TextColored(theme.text_secondary_gray,
118 " Click room objects to edit them here.");
119 ImGui::SameLine();
120 if (ImGui::SmallButton(ICON_MD_HELP_OUTLINE " Shortcuts")) {
121 show_shortcut_help_ = true;
122 }
123 return;
124 }
125
126 if (selection_count == 1) {
127 ImGui::TextColored(theme.status_success,
128 ICON_MD_CHECK_CIRCLE " Editing selected room object");
129 } else {
130 ImGui::TextColored(theme.status_success,
131 ICON_MD_SELECT_ALL " Editing %zu selected objects",
132 selection_count);
133 }
134}
135
137 auto* viewer = ResolveCanvasViewer();
138 if (!viewer || cached_selection_count_ == 0) {
139 return;
140 }
141
142 ImGui::Spacing();
143 if (ImGui::BeginTable("##ObjectEditorActions", 5,
144 ImGuiTableFlags_SizingStretchSame |
145 ImGuiTableFlags_NoPadOuterX)) {
146 ImGui::TableNextRow();
147
148 ImGui::TableNextColumn();
149 if (ImGui::Button(ICON_MD_CONTENT_COPY " Copy", ImVec2(-1, 0))) {
151 }
152
153 ImGui::TableNextColumn();
154 if (ImGui::Button(ICON_MD_CONTENT_PASTE " Paste", ImVec2(-1, 0))) {
155 PasteObjects();
156 }
157
158 ImGui::TableNextColumn();
159 if (ImGui::Button(ICON_MD_FILTER_NONE " Duplicate", ImVec2(-1, 0))) {
161 }
162
163 ImGui::TableNextColumn();
164 if (ImGui::Button(ICON_MD_CLEAR " Clear", ImVec2(-1, 0))) {
166 }
167
168 ImGui::TableNextColumn();
169 gui::StyleVarGuard align_guard(ImGuiStyleVar_ButtonTextAlign,
170 ImVec2(0.08f, 0.5f));
171 if (ImGui::Button(ICON_MD_DELETE " Delete", ImVec2(-1, 0))) {
173 }
174
175 ImGui::EndTable();
176 }
177
178 ImGui::Separator();
179}
180
182 const auto& theme = AgentUI::GetTheme();
183 auto* viewer = ResolveCanvasViewer();
184 if (!viewer || !viewer->HasRooms()) {
185 return;
186 }
187
188 auto& interaction = viewer->object_interaction();
189 auto selected = interaction.GetSelectedObjectIndices();
190 if (selected.empty()) {
191 return;
192 }
193
194 if (selected.size() == 1) {
195 const auto& objects = object_editor_->GetObjects();
196 if (selected[0] < objects.size()) {
197 const auto& obj = objects[selected[0]];
198 const auto semantics = zelda3::GetObjectLayerSemantics(obj);
199 ImGui::TextColored(theme.status_success,
201 " Selected object #%zu ยท 0x%03X %s",
202 selected[0], obj.id_,
203 zelda3::GetObjectName(obj.id_).c_str());
204 ImGui::TextColored(theme.text_secondary_gray,
205 "Position (%d, %d) Size 0x%02X Layer %s Draws %s",
206 obj.x_, obj.y_, obj.size_,
207 obj.layer_ == zelda3::RoomObject::BG1
208 ? "BG1"
209 : obj.layer_ == zelda3::RoomObject::BG2 ? "BG2"
210 : "BG3",
212 semantics.effective_bg_layer));
213 ImGui::Spacing();
214 }
215 return;
216 }
217
218 ImGui::TextColored(theme.status_success, ICON_MD_SELECT_ALL " %zu objects selected",
219 selected.size());
220 ImGui::TextColored(theme.text_secondary_gray,
221 "Use Ctrl+D to duplicate, Delete to remove, or Arrow Keys "
222 "to nudge the selection.");
223 ImGui::Spacing();
224}
225
227 if (!show_shortcut_help_) {
228 return;
229 }
230
231 ImGui::SetNextWindowSize(ImVec2(340, 0), ImGuiCond_Appearing);
232 if (ImGui::Begin("Keyboard Shortcuts##DungeonObjectEditor", &show_shortcut_help_,
233 ImGuiWindowFlags_NoCollapse)) {
234 const auto& theme = AgentUI::GetTheme();
235 auto shortcut_row = [&](const char* keys, const char* desc) {
236 ImGui::TextColored(theme.status_warning, "%-18s", keys);
237 ImGui::SameLine();
238 ImGui::TextUnformatted(desc);
239 };
240
241 ImGui::TextColored(theme.status_success, ICON_MD_KEYBOARD " Selection");
242 ImGui::Separator();
243 shortcut_row("Ctrl+A", "Select all objects");
244 shortcut_row("Ctrl+Shift+A", "Deselect all");
245 shortcut_row("Tab / Shift+Tab", "Cycle selection");
246 shortcut_row("Escape", "Clear selection");
247
248 ImGui::Spacing();
249 ImGui::TextColored(theme.status_success, ICON_MD_EDIT " Editing");
250 ImGui::Separator();
251 shortcut_row("Delete", "Remove selected");
252 shortcut_row("Ctrl+D", "Duplicate selected");
253 shortcut_row("Ctrl+C", "Copy selected");
254 shortcut_row("Ctrl+V", "Paste");
255 shortcut_row("Ctrl+Z", "Undo");
256 shortcut_row("Ctrl+Shift+Z", "Redo");
257
258 ImGui::Spacing();
259 ImGui::TextColored(theme.status_success, ICON_MD_OPEN_WITH " Movement");
260 ImGui::Separator();
261 shortcut_row("Arrow Keys", "Nudge selected (1px)");
262 }
263 ImGui::End();
264}
265
267 if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
268 return;
269 }
270
271 const ImGuiIO& io = ImGui::GetIO();
272
273 if (ImGui::IsKeyPressed(ImGuiKey_A) && io.KeyCtrl && !io.KeyShift) {
275 }
276 if (ImGui::IsKeyPressed(ImGuiKey_A) && io.KeyCtrl && io.KeyShift) {
278 }
279 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
281 }
282 if (ImGui::IsKeyPressed(ImGuiKey_D) && io.KeyCtrl) {
284 }
285 if (ImGui::IsKeyPressed(ImGuiKey_C) && io.KeyCtrl) {
287 }
288 if (ImGui::IsKeyPressed(ImGuiKey_V) && io.KeyCtrl) {
289 PasteObjects();
290 }
291 if (ImGui::IsKeyPressed(ImGuiKey_Z) && io.KeyCtrl && !io.KeyShift) {
292 object_editor_->Undo();
293 }
294 if ((ImGui::IsKeyPressed(ImGuiKey_Z) && io.KeyCtrl && io.KeyShift) ||
295 (ImGui::IsKeyPressed(ImGuiKey_Y) && io.KeyCtrl)) {
296 object_editor_->Redo();
297 }
298
299 if (!io.KeyCtrl) {
300 int dx = 0;
301 int dy = 0;
302 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
303 dx = -1;
304 }
305 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
306 dx = 1;
307 }
308 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
309 dy = -1;
310 }
311 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
312 dy = 1;
313 }
314 if (dx != 0 || dy != 0) {
315 NudgeSelectedObjects(dx, dy);
316 }
317 }
318
319 if (ImGui::IsKeyPressed(ImGuiKey_Tab) && !io.KeyCtrl) {
320 CycleObjectSelection(io.KeyShift ? -1 : 1);
321 }
322
323 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
325 }
326
327 if (ImGui::IsKeyPressed(ImGuiKey_Slash) && io.KeyShift) {
329 }
330}
331
334 return;
335 }
336
337 auto& interaction = canvas_viewer_->object_interaction();
338 const auto& objects = object_editor_->GetObjects();
339 std::vector<size_t> all_indices;
340 all_indices.reserve(objects.size());
341 for (size_t i = 0; i < objects.size(); ++i) {
342 all_indices.push_back(i);
343 }
344 interaction.SetSelectedObjects(all_indices);
345}
346
353
356 return;
357 }
358
359 auto& interaction = canvas_viewer_->object_interaction();
360 const auto& selected = interaction.GetSelectedObjectIndices();
361 if (selected.empty()) {
362 return;
363 }
364
365 std::vector<size_t> sorted_indices(selected.begin(), selected.end());
366 std::sort(sorted_indices.rbegin(), sorted_indices.rend());
367 for (size_t idx : sorted_indices) {
368 object_editor_->DeleteObject(idx);
369 }
370 interaction.ClearSelection();
371}
372
375 return;
376 }
377
378 auto& interaction = canvas_viewer_->object_interaction();
379 const auto& selected = interaction.GetSelectedObjectIndices();
380 if (selected.empty()) {
381 return;
382 }
383
384 std::vector<size_t> new_indices;
385 for (size_t idx : selected) {
386 auto new_idx = object_editor_->DuplicateObject(idx, 1, 1);
387 if (new_idx.has_value()) {
388 new_indices.push_back(*new_idx);
389 }
390 }
391 interaction.SetSelectedObjects(new_indices);
392}
393
401
404 return;
405 }
406
407 auto new_indices = object_editor_->PasteObjects();
408 if (!new_indices.empty()) {
410 }
411}
412
415 return;
416 }
417
418 const auto& selected =
420 if (selected.empty()) {
421 return;
422 }
423
424 for (size_t idx : selected) {
425 object_editor_->MoveObject(idx, dx, dy);
426 }
427}
428
431 return;
432 }
433
434 auto& interaction = canvas_viewer_->object_interaction();
435 const auto& selected = interaction.GetSelectedObjectIndices();
436 const auto& objects = object_editor_->GetObjects();
437 const size_t total_objects = objects.size();
438 if (total_objects == 0) {
439 return;
440 }
441
442 const size_t current_idx = selected.empty() ? 0 : selected.front();
443 const size_t next_idx =
444 (current_idx + direction + total_objects) % total_objects;
445 interaction.SetSelectedObjects({next_idx});
446 ScrollToObject(next_idx);
447}
448
451 return;
452 }
453
454 const auto& objects = object_editor_->GetObjects();
455 if (index >= objects.size()) {
456 return;
457 }
458
459 const auto& obj = objects[index];
460 canvas_viewer_->ScrollToTile(obj.x(), obj.y());
461}
462
463} // namespace yaze::editor
DungeonObjectInteraction & object_interaction()
void ScrollToTile(int tile_x, int tile_y)
void SetSelectedObjects(const std::vector< size_t > &indices)
std::vector< size_t > GetSelectedObjectIndices() const
void SetSelectionChangeCallback(std::function< void()> callback)
void Draw(bool *p_open) override
Draw the panel content.
DungeonCanvasViewer * ResolveCanvasViewer()
std::function< DungeonCanvasViewer *()> canvas_viewer_provider_
ObjectEditorContent(std::shared_ptr< zelda3::DungeonObjectEditor > object_editor=nullptr)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
std::shared_ptr< zelda3::DungeonObjectEditor > object_editor_
RAII guard for ImGui style vars.
Definition style_guard.h:68
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_OPEN_WITH
Definition icons.h:1356
#define ICON_MD_FILTER_NONE
Definition icons.h:771
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_CONTENT_PASTE
Definition icons.h:467
#define ICON_MD_KEYBOARD
Definition icons.h:1028
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_CLEAR
Definition icons.h:416
#define ICON_MD_HELP_OUTLINE
Definition icons.h:935
#define ICON_MD_MOUSE
Definition icons.h:1251
#define ICON_MD_SELECT_ALL
Definition icons.h:1680
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
void SectionHeader(const char *icon, const char *label, const ImVec4 &color)
ObjectLayerSemantics GetObjectLayerSemantics(const RoomObject &object)
std::string GetObjectName(int object_id)
const char * EffectiveBgLayerLabel(EffectiveBgLayer layer)