yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_workbench_inspector_helpers.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <filesystem>
6
7#include "absl/strings/str_format.h"
12#include "core/features.h"
15
17
18namespace {
19
21
22Dimensions DimensionsAtSize(const zelda3::RoomObject& object, uint8_t size) {
23 const zelda3::RoomObject candidate(object.id_, object.x_, object.y_, size,
24 object.GetLayerValue());
26}
27
28std::string FootprintLabel(const Dimensions& dimensions) {
29 return absl::StrFormat("%d x %d tiles (%d x %d px)", dimensions.width_tiles,
30 dimensions.height_tiles, dimensions.width_pixels(),
31 dimensions.height_pixels());
32}
33
34} // namespace
35
36bool HasEditableRoomObjectSize(std::span<const zelda3::RoomObject> objects,
37 std::span<const size_t> selected_indices) {
38 return std::any_of(selected_indices.begin(), selected_indices.end(),
39 [&](size_t index) {
40 return index < objects.size() &&
41 zelda3::IsRoomObjectResizable(objects[index].id_);
42 });
43}
44
46 const auto dimensions = zelda3::DimensionService::Get().GetDimensions(object);
47 ObjectSizeDescription description;
48 description.width_tiles = dimensions.width_tiles;
49 description.height_tiles = dimensions.height_tiles;
50 description.footprint = FootprintLabel(dimensions);
51
52 if (core::FeatureFlags::get().kEnableCustomObjects &&
54 zelda3::CustomObjectManager::Get().GetSubtypeCount(object.id_) > 0) {
55 description.kind = ObjectSizeControlKind::kVariant;
56 } else if (zelda3::RoomObjectSizeAxisStep(object.id_) > 0) {
57 description.kind = ObjectSizeControlKind::kArea;
58 description.wheel_hint = "Wheel: grow / shrink. Shift + wheel: width.";
59 } else if (zelda3::IsRoomObjectResizable(object.id_)) {
60 bool width_changes = false;
61 bool height_changes = false;
62 for (uint8_t size = 0; size < 16; ++size) {
63 const auto candidate = DimensionsAtSize(object, size);
64 width_changes |= candidate.width_tiles != dimensions.width_tiles;
65 height_changes |= candidate.height_tiles != dimensions.height_tiles;
66 }
67 description.kind = width_changes != height_changes
70 description.length_horizontal = width_changes;
71 description.wheel_hint = description.kind == ObjectSizeControlKind::kLength
72 ? "Wheel: length."
73 : "Wheel: size.";
74 }
75 return description;
76}
77
79 uint8_t* requested_size) {
80 if (!requested_size) {
81 return false;
82 }
83 uint8_t size = object.size_;
84 const auto description = DescribeObjectSize(object);
85 const int axis_step = zelda3::RoomObjectSizeAxisStep(object.id_);
86 if (axis_step > 0) {
87 for (const bool horizontal : {true, false}) {
88 gui::LayoutHelpers::PropertyRow(horizontal ? "Width" : "Height", [&]() {
89 const int shift = horizontal ? 2 : 0;
90 const int current = (size >> shift) & 3;
91 const auto preview = absl::StrFormat(
92 "%d tiles",
93 zelda3::RoomObjectSizeAxisTiles(object.id_, size, horizontal));
94 ImGui::SetNextItemWidth(-1);
95 const bool open = ImGui::BeginCombo(
96 horizontal ? "##SelObjWidth" : "##SelObjHeight", preview.c_str());
97 if (open) {
98 for (int value = 0; value < 4; ++value) {
99 const auto candidate =
100 static_cast<uint8_t>((size & ~(3 << shift)) | (value << shift));
101 const auto label = absl::StrFormat(
103 object.id_, candidate, horizontal));
104 if (ImGui::Selectable(label.c_str(), value == current)) {
105 size = candidate;
106 }
107 {
108 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
110 "selectable",
111 absl::StrFormat("object_%s_%d",
112 horizontal ? "width" : "height", value));
113 }
114 if (value == current) {
115 ImGui::SetItemDefaultFocus();
116 }
117 }
118 ImGui::EndCombo();
119 }
120 // EndCombo restores the control's item data after the popup window.
121 {
122 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
124 "combo",
125 horizontal ? "selected_object_width" : "selected_object_height",
126 horizontal ? "Selected object width in tiles"
127 : "Selected object height in tiles");
128 }
129 });
130 }
131 } else {
132 auto& manager = zelda3::CustomObjectManager::Get();
133 const int variants = core::FeatureFlags::get().kEnableCustomObjects
134 ? std::min(manager.GetSubtypeCount(object.id_), 16)
135 : 0;
136 if (variants > 0 && zelda3::IsRoomObjectSizeEditable(object.id_)) {
137 const auto variant_label = [&](int subtype) {
139 object.id_) > 0) {
140 return GetDungeonCustomObjectSlotName(object.id_, subtype);
141 }
142 const auto filename = manager.ResolveFilename(object.id_, subtype);
143 return filename.empty()
144 ? absl::StrFormat("Variant %d (unmapped)", subtype)
145 : std::filesystem::path(filename).stem().string();
146 };
147 gui::LayoutHelpers::PropertyRow("Variant", [&]() {
148 const auto preview = size < variants
149 ? variant_label(size)
150 : absl::StrFormat("Invalid variant %d", size);
151 ImGui::SetNextItemWidth(-1);
152 const bool open = ImGui::BeginCombo("##SelObjVariant", preview.c_str());
153 if (open) {
154 for (int subtype = 0; subtype < variants; ++subtype) {
155 const auto label =
156 absl::StrFormat("%02X %s", subtype, variant_label(subtype));
157 const bool mapped =
158 !manager.ResolveFilename(object.id_, subtype).empty();
159 ImGui::BeginDisabled(!mapped);
160 if (ImGui::Selectable(label.c_str(), size == subtype)) {
161 size = static_cast<uint8_t>(subtype);
162 }
163 {
164 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
166 "selectable", absl::StrFormat("object_variant_%d", subtype));
167 }
168 ImGui::EndDisabled();
169 }
170 ImGui::EndCombo();
171 }
172 {
173 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
174 gui::AutoRegisterLastItem("combo", "selected_object_variant",
175 "Selected custom object variant");
176 }
177 });
178 } else {
179 const bool is_length = description.kind == ObjectSizeControlKind::kLength;
180 gui::LayoutHelpers::PropertyRow(is_length ? "Length" : "Size", [&]() {
181 if (description.kind == ObjectSizeControlKind::kFixed) {
182 ImGui::TextDisabled("Fixed size");
183 return;
184 }
185
186 const auto label_for_size = [&](uint8_t value) {
187 const auto dimensions = DimensionsAtSize(object, value);
188 if (is_length) {
189 const int tiles = description.length_horizontal
190 ? dimensions.width_tiles
191 : dimensions.height_tiles;
192 return absl::StrFormat("%d tiles (%d px)", tiles, tiles * 8);
193 }
194 return absl::StrFormat("%d x %d tiles", dimensions.width_tiles,
195 dimensions.height_tiles);
196 };
197 ImGui::SetNextItemWidth(-1);
198 if (ImGui::BeginCombo("##SelObjSize", label_for_size(size).c_str())) {
199 std::array<uint8_t, 16> sizes{};
200 for (uint8_t value = 0; value < sizes.size(); ++value) {
201 sizes[value] = value;
202 }
203 if (is_length) {
204 // Some draw routines use encoded zero for their longest span.
205 // Order these choices by the visible length, not the ROM encoding.
206 std::stable_sort(sizes.begin(), sizes.end(),
207 [&](uint8_t left, uint8_t right) {
208 const auto a = DimensionsAtSize(object, left);
209 const auto b = DimensionsAtSize(object, right);
210 return description.length_horizontal
211 ? a.width_tiles < b.width_tiles
212 : a.height_tiles < b.height_tiles;
213 });
214 }
215 for (const uint8_t value : sizes) {
216 ImGui::PushID(value);
217 if (ImGui::Selectable(label_for_size(value).c_str(),
218 size == value)) {
219 size = value;
220 }
221 {
222 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
224 "selectable", absl::StrFormat("object_size_%d", value));
225 }
226 if (size == value) {
227 ImGui::SetItemDefaultFocus();
228 }
229 ImGui::PopID();
230 }
231 ImGui::EndCombo();
232 }
233 {
234 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
235 gui::AutoRegisterLastItem("combo", "selected_object_size",
236 is_length ? "Object length in tiles"
237 : "Object footprint in tiles");
238 }
239 });
240 }
241 }
242 gui::LayoutHelpers::PropertyRow("Footprint", [&]() {
243 const auto dimensions = DimensionsAtSize(object, size);
244 ImGui::TextWrapped("%d x %d tiles", dimensions.width_tiles,
245 dimensions.height_tiles);
246 ImGui::TextDisabled("%d x %d px", dimensions.width_pixels(),
247 dimensions.height_pixels());
248 });
249 *requested_size = size;
250 return size != object.size_;
251}
252
253void DrawInspectorSectionHeader(const char* label) {
254 ImGui::SeparatorText(label);
255}
256
257bool BeginInspectorSection(const char* label, bool default_open) {
258 gui::StyleVarGuard frame_padding_guard(
259 ImGuiStyleVar_FramePadding,
260 ImVec2(ImGui::GetStyle().FramePadding.x,
261 std::max(5.0f, ImGui::GetStyle().FramePadding.y + 1.0f)));
262 return ImGui::CollapsingHeader(
263 label, default_open ? ImGuiTreeNodeFlags_DefaultOpen : 0);
264}
265
266bool DrawActionButton(const char* label, const ImVec2& size) {
267 gui::StyleVarGuard align_guard(ImGuiStyleVar_ButtonTextAlign,
268 ImVec2(0.08f, 0.5f));
269 return ImGui::Button(label, size);
270}
271
272} // namespace yaze::editor::workbench
static Flags & get()
Definition features.h:119
RAII scope that enables automatic widget registration.
static void PropertyRow(const char *label, std::function< void()> widget_callback)
RAII guard for ImGui style vars.
Definition style_guard.h:68
static int RuntimeSubtypeCountForObject(int object_id)
static CustomObjectManager & Get()
static DimensionService & Get()
DimensionResult GetDimensions(const RoomObject &obj) const
bool BeginInspectorSection(const char *label, bool default_open)
ObjectSizeDescription DescribeObjectSize(const zelda3::RoomObject &object)
bool DrawObjectSizeControls(const zelda3::RoomObject &object, uint8_t *requested_size)
bool DrawActionButton(const char *label, const ImVec2 &size)
bool HasEditableRoomObjectSize(std::span< const zelda3::RoomObject > objects, std::span< const size_t > selected_indices)
std::string GetDungeonCustomObjectSlotName(int object_id, int subtype)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
bool IsRoomObjectSizeEditable(int object_id)
int RoomObjectSizeAxisStep(int object_id)
bool IsRoomObjectResizable(int object_id)
int RoomObjectSizeAxisTiles(int object_id, uint8_t size, bool horizontal)
Automatic widget registration helpers for ImGui Test Engine integration.