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 gui::LayoutHelpers::PropertyRow("Variant ID", [&]() {
179 int observed_variant = object.size_;
180 ImGui::InputInt("##SelectedObjectVariantValue", &observed_variant, 0, 0,
181 ImGuiInputTextFlags_ReadOnly);
182 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
183 gui::AutoRegisterLastItem("input_int", "selected_object_variant_value",
184 "Current custom object variant");
185 });
186 } else {
187 const bool is_length = description.kind == ObjectSizeControlKind::kLength;
188 gui::LayoutHelpers::PropertyRow(is_length ? "Length" : "Size", [&]() {
189 if (description.kind == ObjectSizeControlKind::kFixed) {
190 ImGui::TextDisabled("Fixed size");
191 return;
192 }
193
194 const auto label_for_size = [&](uint8_t value) {
195 const auto dimensions = DimensionsAtSize(object, value);
196 if (is_length) {
197 const int tiles = description.length_horizontal
198 ? dimensions.width_tiles
199 : dimensions.height_tiles;
200 return absl::StrFormat("%d tiles (%d px)", tiles, tiles * 8);
201 }
202 return absl::StrFormat("%d x %d tiles", dimensions.width_tiles,
203 dimensions.height_tiles);
204 };
205 ImGui::SetNextItemWidth(-1);
206 if (ImGui::BeginCombo("##SelObjSize", label_for_size(size).c_str())) {
207 std::array<uint8_t, 16> sizes{};
208 for (uint8_t value = 0; value < sizes.size(); ++value) {
209 sizes[value] = value;
210 }
211 if (is_length) {
212 // Some draw routines use encoded zero for their longest span.
213 // Order these choices by the visible length, not the ROM encoding.
214 std::stable_sort(sizes.begin(), sizes.end(),
215 [&](uint8_t left, uint8_t right) {
216 const auto a = DimensionsAtSize(object, left);
217 const auto b = DimensionsAtSize(object, right);
218 return description.length_horizontal
219 ? a.width_tiles < b.width_tiles
220 : a.height_tiles < b.height_tiles;
221 });
222 }
223 for (const uint8_t value : sizes) {
224 ImGui::PushID(value);
225 if (ImGui::Selectable(label_for_size(value).c_str(),
226 size == value)) {
227 size = value;
228 }
229 {
230 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
232 "selectable", absl::StrFormat("object_size_%d", value));
233 }
234 if (size == value) {
235 ImGui::SetItemDefaultFocus();
236 }
237 ImGui::PopID();
238 }
239 ImGui::EndCombo();
240 }
241 {
242 gui::AutoWidgetScope automation_scope("Dungeon/Workbench");
243 gui::AutoRegisterLastItem("combo", "selected_object_size",
244 is_length ? "Object length in tiles"
245 : "Object footprint in tiles");
246 }
247 });
248 }
249 }
250 gui::LayoutHelpers::PropertyRow("Footprint", [&]() {
251 const auto dimensions = DimensionsAtSize(object, size);
252 ImGui::TextWrapped("%d x %d tiles", dimensions.width_tiles,
253 dimensions.height_tiles);
254 ImGui::TextDisabled("%d x %d px", dimensions.width_pixels(),
255 dimensions.height_pixels());
256 });
257 *requested_size = size;
258 return size != object.size_;
259}
260
261void DrawInspectorSectionHeader(const char* label) {
262 ImGui::SeparatorText(label);
263}
264
265bool BeginInspectorSection(const char* label, bool default_open) {
266 gui::StyleVarGuard frame_padding_guard(
267 ImGuiStyleVar_FramePadding,
268 ImVec2(ImGui::GetStyle().FramePadding.x,
269 std::max(5.0f, ImGui::GetStyle().FramePadding.y + 1.0f)));
270 return ImGui::CollapsingHeader(
271 label, default_open ? ImGuiTreeNodeFlags_DefaultOpen : 0);
272}
273
274bool DrawActionButton(const char* label, const ImVec2& size) {
275 gui::StyleVarGuard align_guard(ImGuiStyleVar_ButtonTextAlign,
276 ImVec2(0.08f, 0.5f));
277 return ImGui::Button(label, size);
278}
279
280} // namespace yaze::editor::workbench
static Flags & get()
Definition features.h:122
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:71
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.