yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_toolbar.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <string>
5
6#include "absl/strings/str_format.h"
14#include "zelda3/common.h"
16
17namespace yaze::editor {
18
19namespace {
20
23 const char* label;
24 const char* tooltip;
25};
26
28 {EntityEditMode::ENTRANCES, "Entrances",
29 "Entrances (3)\nOnly entrances respond to hover and drag"},
30 {EntityEditMode::EXITS, "Exits",
31 "Exits (4)\nOnly exits respond to hover and drag"},
32 {EntityEditMode::ITEMS, "Items",
33 "Items (5)\nOnly items respond to hover and drag.\n"
34 "Arrows nudge the selected item (Shift = 16px), Cmd/Ctrl+D duplicates"},
35 {EntityEditMode::SPRITES, "Sprites",
36 "Sprites (6)\nOnly sprites respond to hover and drag"},
37};
38
39bool ToolButton(const char* label, const char* tooltip, bool active,
40 const char* automation_id = nullptr) {
41 const bool clicked = gui::ToggleButton(label, active);
42 if (automation_id) {
44 std::string("OverworldToolbar/button:") + automation_id, "button",
45 ImGui::GetItemID());
46 }
47 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
48 ImGui::SetTooltip("%s", tooltip);
49 }
50 return clicked;
51}
52
54 ImGui::SameLine(0, 6.0f);
55 const ImVec2 pos = ImGui::GetCursorScreenPos();
56 const float height = ImGui::GetFrameHeight();
57 ImGui::GetWindowDrawList()->AddLine(ImVec2(pos.x, pos.y + 3.0f),
58 ImVec2(pos.x, pos.y + height - 3.0f),
59 ImGui::GetColorU32(ImGuiCol_Separator));
60 ImGui::Dummy(ImVec2(1.0f, 0.0f));
61 ImGui::SameLine(0, 6.0f);
62}
63
64} // namespace
65
66std::string OverworldToolbar::WithHint(const char* text,
67 const char* shortcut_name) const {
68 if (!shortcuts || !shortcut_name) {
69 return text;
70 }
71 const std::string keys = shortcuts->GetDisplayString(shortcut_name);
72 if (keys.empty()) {
73 return text;
74 }
75 // Put the hint on the first line: "Zoom In (=)\n<details>".
76 std::string result = text;
77 const size_t newline = result.find('\n');
78 const std::string hint = " (" + keys + ")";
79 if (newline == std::string::npos) {
80 return result + hint;
81 }
82 return result.insert(newline, hint);
83}
84
86 const auto toggle = [&](const char* icon, const char* label,
87 const char* shortcut, const std::function<bool()>& on,
88 const std::function<void()>& flip) {
89 if (!on || !flip) {
90 return;
91 }
92 const std::string tip = WithHint(label, shortcut);
93 if (in_menu) {
94 const std::string keys =
95 shortcuts && shortcut ? shortcuts->GetDisplayString(shortcut) : "";
96 const std::string title =
97 std::string(label).substr(0, std::string(label).find('\n'));
98 const std::string item = absl::StrFormat("%s %s", icon, title);
99 if (ImGui::MenuItem(item.c_str(), keys.empty() ? nullptr : keys.c_str(),
100 on())) {
101 flip();
102 }
103 if (ImGui::IsItemHovered()) {
104 ImGui::SetTooltip("%s", tip.c_str());
105 }
106 return;
107 }
108 if (gui::ToolbarIconButton(icon, tip.c_str(), on())) {
109 flip();
110 }
111 ImGui::SameLine(0, 2);
112 };
113 const auto action = [&](const char* icon, const char* label,
114 const char* shortcut,
115 const std::function<void()>& run) {
116 if (!run) {
117 return;
118 }
119 if (in_menu) {
120 const std::string keys =
121 shortcuts && shortcut ? shortcuts->GetDisplayString(shortcut) : "";
122 const std::string title =
123 std::string(label).substr(0, std::string(label).find('\n'));
124 const std::string item = absl::StrFormat("%s %s", icon, title);
125 if (ImGui::MenuItem(item.c_str(),
126 keys.empty() ? nullptr : keys.c_str())) {
127 run();
128 }
129 if (ImGui::IsItemHovered()) {
130 ImGui::SetTooltip("%s", WithHint(label, shortcut).c_str());
131 }
132 return;
133 }
134 const std::string tip = WithHint(label, shortcut);
135 if (gui::ToolbarIconButton(icon, tip.c_str())) {
136 run();
137 }
138 ImGui::SameLine(0, 2);
139 };
140
141 toggle(ICON_MD_GRID_4X4, "Grid", "overworld.toggle_grid", is_grid_visible,
143 toggle(ICON_MD_PLACE, "Entities\nShow entrances, exits, items and sprites",
144 "overworld.toggle_entities", are_entities_visible, on_toggle_entities);
145 toggle(ICON_MD_VISIBILITY, "Overlay preview\nDraw this map's overlay",
147 if (in_menu) {
148 ImGui::Separator();
149 }
150 action(ICON_MD_ZOOM_OUT, "Zoom out\nCmd/Ctrl+wheel zooms at the cursor",
151 "overworld.zoom_out", on_zoom_out);
152 if (!in_menu && get_zoom) {
153 ImGui::AlignTextToFramePadding();
154 ImGui::TextDisabled("%3.0f%%", get_zoom() * 100.0f);
155 ImGui::SameLine(0, 2);
156 }
157 action(ICON_MD_ZOOM_IN, "Zoom in\nCmd/Ctrl+wheel zooms at the cursor",
158 "overworld.zoom_in", on_zoom_in);
159 action(ICON_MD_FIT_SCREEN, "Zoom to fit world", "overworld.zoom_fit",
161 action(ICON_MD_CENTER_FOCUS_STRONG, "Center on selected map",
162 "overworld.center_map", on_center_map);
163}
164
165void OverworldToolbar::Draw(int& current_world, int& current_map,
166 bool& current_map_lock, EditingMode& current_mode,
167 EntityEditMode& entity_edit_mode,
168 WorkspaceWindowManager* window_manager, Rom* rom,
169 zelda3::Overworld* overworld,
170 project::YazeProject* project, int game_state,
171 const std::function<void()>& draw_appearance) {
172 if (!overworld || !overworld->is_loaded() || !window_manager || !rom) {
173 return;
174 }
175 if (!overworld->overworld_map(current_map)) {
176 return;
177 }
178
179 const float avail = ImGui::GetContentRegionAvail().x;
180 const float right_edge = ImGui::GetCursorScreenPos().x + avail;
181 const bool tiny = avail < ImGui::GetFontSize() * 38.0f;
182
183 gui::StyleVarGuard toolbar_style_guard(
184 {{ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)},
185 {ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 4.0f)}});
186 ImGui::PushID("OverworldToolbar");
187
188 // --- World -----------------------------------------------------------------
189 static constexpr const char* kWorldButtons[] = {"LW", "DW", "SW"};
190 static constexpr const char* kWorldShortcuts[] = {"overworld.world_light",
191 "overworld.world_dark",
192 "overworld.world_special"};
193 for (int world_index = 0; !tiny && world_index < 3; ++world_index) {
194 if (world_index > 0) {
195 ImGui::SameLine(0, 2);
196 }
197 if (gui::ToggleButton(kWorldButtons[world_index],
198 current_world == world_index, ImVec2(30.0f, 0.0f)) &&
200 on_world_changed(world_index);
201 }
202 if (ImGui::IsItemHovered()) {
203 ImGui::SetTooltip(
204 "%s", WithHint(kWorldNames[world_index], kWorldShortcuts[world_index])
205 .c_str());
206 }
207 }
208
209 // --- Map -------------------------------------------------------------------
210 if (!tiny)
211 GroupSeparator();
212 const auto metadata = BuildOverworldMapMetadata(*overworld, rom, project,
213 current_map, game_state);
214 if (ImGui::Button(metadata.map_id_label.c_str()) && on_open_map_properties) {
216 }
217 if (ImGui::IsItemHovered()) {
218 ImGui::SetTooltip(
219 "%s\n%s\nClick this button or double-click the canvas map for "
220 "properties.\n"
221 "%s / %s: adjacent map",
222 metadata.map_title.c_str(), metadata.area_size_label.c_str(),
223 WithHint("Previous", "overworld.map_left").c_str(),
224 WithHint("Next", "overworld.map_right").c_str());
225 }
226 ImGui::SameLine(0, 2);
227 const std::string pin_tip =
228 current_map_lock
229 ? WithHint("Unpin map\nResume following the cursor",
230 "overworld.toggle_lock")
231 : WithHint("Pin map\nKeep properties on this map while navigating",
232 "overworld.toggle_lock");
234 current_map_lock ? ICON_MD_PUSH_PIN : ICON_MD_GPS_FIXED,
235 pin_tip.c_str(),
236 ImVec2(ImGui::GetFrameHeight(), ImGui::GetFrameHeight()),
237 current_map_lock)) {
238 current_map_lock = !current_map_lock;
239 }
240
241 // Keep frequent map properties visible even when secondary tools overflow.
242 ImGui::SameLine(0, 4);
243 const std::string story_label = absl::StrFormat("Story %d", game_state);
244 ImGui::SetNextItemWidth(ImGui::CalcTextSize("Story 0").x +
245 ImGui::GetFrameHeight() + 8.0f);
246 const bool story_open = ImGui::BeginCombo("##Story", story_label.c_str());
248 "OverworldToolbar/combo:Story", "combobox", ImGui::GetItemID());
249 if (ImGui::IsItemHovered()) {
250 ImGui::SetTooltip(
251 "Story part to preview. Changes the active sprite set, "
252 "sprite graphics and sprite palettes. "
253 "Shared item placements stay the same.");
254 }
255 if (story_open) {
256 for (int state = 0; state < 3; ++state) {
257 const std::string label =
258 absl::StrFormat("%d — %s", state, kGameStateNames[state]);
259 if (ImGui::Selectable(label.c_str(), game_state == state) &&
261 on_story_changed(state);
262 }
264 absl::StrFormat("OverworldToolbar/story:%d", state), "selectable",
265 ImGui::GetItemID());
266 }
267 ImGui::EndCombo();
268 }
269 if (draw_appearance) {
270 ImGui::SameLine(0, 4);
271 draw_appearance();
272 }
273
274 const char* entity_label = "Entities";
275 for (const auto& button : kEntityModeButtons) {
276 if (entity_edit_mode == button.mode) {
277 entity_label = button.label;
278 }
279 }
280 const auto button_width = [](const char* label) {
281 return ImGui::CalcTextSize(label, nullptr, true).x +
282 ImGui::GetStyle().FramePadding.x * 2.0f;
283 };
284 // Match the actual buttons and gaps below. Shorten secondary labels before
285 // moving tools into overflow; input fields do not imply a narrow toolbar.
286 const float drawing_width =
287 button_width(ICON_MD_MOUSE) + button_width(ICON_MD_DRAW) +
288 button_width(ICON_MD_FORMAT_COLOR_FILL) +
289 button_width(ICON_MD_GRASS " Insert Item") + 3.0f * 2.0f;
290 const auto tools_width = [&](bool compact_labels) {
291 return 3.0f * 13.0f + drawing_width +
292 button_width(compact_labels ? ICON_MD_PLACE : entity_label) +
293 button_width(compact_labels ? ICON_MD_VISIBILITY : "View") + 2.0f +
294 button_width(compact_labels ? ICON_MD_WINDOW : "Windows");
295 };
296 const float remaining = right_edge - ImGui::GetItemRectMax().x;
297 const bool compact = tools_width(false) > remaining;
298 // Tiny docks also need access to the world selector through this popup.
299 const bool overflow = tiny || tools_width(true) > remaining;
300 if (overflow) {
301 ImGui::SameLine(0, 4);
302 if (ToolButton(ICON_MD_MORE_HORIZ, "Drawing tools and map options", false,
303 "Tools")) {
304 ImGui::OpenPopup("ToolsOverflow");
305 }
306 if (!ImGui::BeginPopup("ToolsOverflow")) {
307 ImGui::PopID();
308 return;
309 }
310 if (tiny) {
311 for (int world = 0; world < 3; ++world) {
312 if (ImGui::MenuItem(kWorldNames[world], nullptr,
313 current_world == world) &&
315 on_world_changed(world);
316 }
317 }
318 ImGui::Separator();
319 }
320 } else {
321 GroupSeparator();
322 }
323 // --- Tool ------------------------------------------------------------------
324 const auto set_mode = [&](EditingMode mode) {
325 if (on_set_mode) {
326 on_set_mode(mode);
327 } else {
328 current_mode = mode;
329 }
330 };
331 if (ToolButton(ICON_MD_MOUSE,
332 "Select (1)\nLeft-drag empty map: pan Middle-drag: pan\n"
333 "Drag entities to move them Right-click: map menu\n"
334 "Double-click a map: Map Properties",
335 current_mode == EditingMode::MOUSE, "Select")) {
336 set_mode(EditingMode::MOUSE);
337 }
338 ImGui::SameLine(0, 2);
339 const std::string brush_tip = WithHint(
340 "Brush (2)\nLeft-drag: paint the selected tile16\n"
341 "Right-click: sample tile16 (or I) Right-drag: capture a brush\n"
342 "[ / ]: previous / next tile16 Shift+Right-click: map menu",
343 "overworld.brush_toggle");
344 if (ToolButton(ICON_MD_DRAW, brush_tip.c_str(),
345 current_mode == EditingMode::DRAW_TILE, "Paint")) {
346 set_mode(EditingMode::DRAW_TILE);
347 }
348 ImGui::SameLine(0, 2);
349 const std::string fill_tip = WithHint(
350 "Fill screen\nClick fills the 32x32 screen under the cursor\n"
351 "Right-click: sample tile16 Shift+Right-click: map menu",
352 "overworld.fill");
353 if (ToolButton(ICON_MD_FORMAT_COLOR_FILL, fill_tip.c_str(),
354 current_mode == EditingMode::FILL_TILE, "Fill")) {
355 set_mode(EditingMode::FILL_TILE);
356 }
357 ImGui::SameLine(0, 2);
358 if (ToolButton(ICON_MD_GRASS " Insert Item",
359 "Pick an item from the list, then click the map to place it",
360 false, "Insert Item")) {
361 if (on_insert_item) {
363 }
364 }
365
366 // --- Entity modes ------------------------------------------------------------
367 GroupSeparator();
368 if (ImGui::Button(compact ? ICON_MD_PLACE : entity_label)) {
369 ImGui::OpenPopup("EntityTools");
370 }
372 "OverworldToolbar/button:Entities", "button", ImGui::GetItemID());
373 if (ImGui::IsItemHovered()) {
374 ImGui::SetTooltip(
375 "Choose which entities respond to hover and drag.\n"
376 "Entrances (3), exits (4), items (5), sprites (6).");
377 }
378 if (ImGui::BeginPopup("EntityTools")) {
379 const auto choose = [&](EntityEditMode mode) {
380 if (on_set_entity_mode) {
381 on_set_entity_mode(mode);
382 } else {
383 entity_edit_mode = mode;
384 }
385 };
386 if (ImGui::MenuItem("All entities", nullptr,
387 entity_edit_mode == EntityEditMode::NONE)) {
388 choose(EntityEditMode::NONE);
389 }
390 for (const auto& button : kEntityModeButtons) {
391 if (ImGui::MenuItem(button.label, nullptr,
392 entity_edit_mode == button.mode)) {
393 choose(button.mode);
394 }
395 if (ImGui::IsItemHovered()) {
396 ImGui::SetTooltip("%s", button.tooltip);
397 }
398 }
399 ImGui::EndPopup();
400 }
401
402 // Secondary display controls stay in a named menu, leaving space for tools.
403 GroupSeparator();
404 const size_t session_id = window_manager->GetActiveSessionId();
405 if (ToolButton(compact ? ICON_MD_VISIBILITY : "View",
406 "Grid, entities, overlay and zoom", false, "View")) {
407 ImGui::OpenPopup("OverworldViewMenu");
408 }
409 if (ImGui::BeginPopup("OverworldViewMenu")) {
410 DrawViewControls(/*in_menu=*/true);
411 ImGui::EndPopup();
412 }
413 ImGui::SameLine(0, 2);
414 if (ToolButton(compact ? ICON_MD_WINDOW : "Windows",
415 "Open overworld editing windows", false, "Windows")) {
416 ImGui::OpenPopup("OverworldWindowsPopup");
417 }
418 if (ImGui::BeginPopup("OverworldWindowsPopup")) {
419 const auto item = [&](const char* label, const char* panel_id,
420 const char* shortcut_name) {
421 const std::string keys = shortcuts && shortcut_name
422 ? shortcuts->GetDisplayString(shortcut_name)
423 : "";
424 if (ImGui::MenuItem(label, keys.empty() ? nullptr : keys.c_str(),
425 window_manager->IsWindowOpen(panel_id))) {
426 const bool opening = !window_manager->IsWindowOpen(panel_id);
427 window_manager->ToggleWindow(session_id, panel_id);
428 if (opening && panel_id == OverworldPanelIds::kItemList &&
431 }
432 }
433 };
434 item(ICON_MD_TUNE " Map Properties", OverworldPanelIds::kMapProperties,
435 nullptr);
437 nullptr);
438 item(ICON_MD_EDIT " Tile16 Editor", OverworldPanelIds::kTile16Editor,
439 "overworld.toggle_tile16_editor");
441 nullptr);
442 item(ICON_MD_IMAGE " Area Graphics", OverworldPanelIds::kAreaGraphics,
443 nullptr);
444 item(ICON_MD_LAYERS " GFX Groups", OverworldPanelIds::kGfxGroups, nullptr);
446 "overworld.toggle_item_list");
447 item(ICON_MD_AUTO_FIX_HIGH " Scratch Workspace",
449 item(ICON_MD_ANALYTICS " Usage Statistics", OverworldPanelIds::kUsageStats,
450 nullptr);
451 const auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom);
452 if (rom_version == zelda3::OverworldVersion::kVanilla &&
454 ImGui::Separator();
455 if (ImGui::MenuItem(ICON_MD_UPGRADE
456 " Upgrade to ZSCustomOverworld v3...")) {
458 }
459 }
460 ImGui::EndPopup();
461 }
462 if (overflow) {
463 ImGui::EndPopup();
464 }
465 ImGui::PopID();
466}
467
468} // namespace yaze::editor
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
ShortcutManager * shortcuts
Used for shortcut hints in tooltips (GetDisplayString).
std::function< bool()> are_entities_visible
std::function< void()> on_insert_item
std::function< void()> on_zoom_fit
std::function< void()> on_center_map
std::function< void(int)> on_upgrade_rom_version
std::string WithHint(const char *text, const char *shortcut_name) const
"Label (Shortcut)" when the shortcut is bound, else "Label".
void Draw(int &current_world, int &current_map, bool &current_map_lock, EditingMode &current_mode, EntityEditMode &entity_edit_mode, WorkspaceWindowManager *window_manager, Rom *rom, zelda3::Overworld *overworld, project::YazeProject *project, int game_state, const std::function< void()> &draw_appearance={})
std::function< float()> get_zoom
std::function< void(int)> on_story_changed
std::function< bool()> is_overlay_preview_enabled
std::function< void(int)> on_world_changed
std::function< void()> on_zoom_in
std::function< void()> on_open_map_properties
std::function< void()> on_zoom_out
std::function< void()> on_toggle_grid
std::function< void()> on_toggle_entities
std::function< bool()> is_grid_visible
std::function< void()> on_toggle_overlay_preview
std::function< void(EntityEditMode)> on_set_entity_mode
std::function< void(EditingMode)> on_set_mode
std::string GetDisplayString(const std::string &name) const
Live, platform-formatted binding for a shortcut name.
Central registry for all editor cards with session awareness and dependency injection.
bool IsWindowOpen(size_t session_id, const std::string &base_window_id) const
bool ToggleWindow(size_t session_id, const std::string &base_window_id)
RAII guard for ImGui style vars.
Definition style_guard.h:68
void RegisterWidget(const std::string &full_path, const std::string &type, ImGuiID imgui_id, const std::string &description="", const WidgetMetadata &metadata=WidgetMetadata())
static WidgetIdRegistry & Instance()
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
auto is_loaded() const
Definition overworld.h:803
auto overworld_map(int i) const
Definition overworld.h:737
#define ICON_MD_GRID_VIEW
Definition icons.h:897
#define ICON_MD_CENTER_FOCUS_STRONG
Definition icons.h:386
#define ICON_MD_FIT_SCREEN
Definition icons.h:781
#define ICON_MD_GPS_FIXED
Definition icons.h:883
#define ICON_MD_UPGRADE
Definition icons.h:2047
#define ICON_MD_PLACE
Definition icons.h:1477
#define ICON_MD_DRAW
Definition icons.h:625
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_ZOOM_OUT
Definition icons.h:2196
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_GRASS
Definition icons.h:891
#define ICON_MD_FORMAT_COLOR_FILL
Definition icons.h:830
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_MORE_HORIZ
Definition icons.h:1241
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:218
#define ICON_MD_GRID_ON
Definition icons.h:896
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_LAYERS
Definition icons.h:1068
#define ICON_MD_WINDOW
Definition icons.h:2176
#define ICON_MD_IMAGE
Definition icons.h:982
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_GRID_4X4
Definition icons.h:893
#define ICON_MD_MOUSE
Definition icons.h:1251
#define ICON_MD_PUSH_PIN
Definition icons.h:1529
#define ICON_MD_ANALYTICS
Definition icons.h:154
bool ToolButton(const char *label, const char *tooltip, bool active, const char *automation_id=nullptr)
Editors are the view controllers for the application.
constexpr const char * kWorldNames[]
OverworldMapMetadata BuildOverworldMapMetadata(const zelda3::Overworld &overworld, const Rom *rom, const project::YazeProject *project, int map_id, int game_state)
constexpr const char * kGameStateNames[]
Definition ui_constants.h:8
bool ThemedIconButton(const char *icon, const char *tooltip, const ImVec2 &size, bool is_active, bool is_disabled, const char *panel_id, const char *anim_id)
Draw a standard icon button with theme-aware colors.
bool ToggleButton(const char *label, bool active, const ImVec2 &size)
bool ToolbarIconButton(const char *icon, const char *tooltip, bool is_active)
Convenience wrapper for toolbar-sized icon buttons.
@ kVanilla
0xFF in ROM, no ZScream ASM applied
static constexpr const char * kMapProperties
static constexpr const char * kTile8Selector
static constexpr const char * kAreaGraphics
static constexpr const char * kItemList
static constexpr const char * kScratchSpace
static constexpr const char * kTile16Editor
static constexpr const char * kTile16Selector
static constexpr const char * kGfxGroups
static constexpr const char * kUsageStats
Modern project structure with comprehensive settings consolidation.
Definition project.h:287