yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
workspace_window_manager_state.cc
Go to the documentation of this file.
1#define IMGUI_DEFINE_MATH_OPERATORS
2
4
5#include <algorithm>
6
8#include "imgui/imgui.h"
9#include "util/log.h"
10
11namespace yaze {
12namespace editor {
13
14namespace {
15
16WindowDescriptor BuildDescriptorFromPanel(const WindowContent& panel) {
17 WindowDescriptor descriptor;
18 auto* panel_ptr = const_cast<WindowContent*>(&panel);
19 descriptor.card_id = panel.GetId();
20 descriptor.display_name = panel.GetDisplayName();
21 descriptor.icon = panel.GetIcon();
22 descriptor.category = panel.GetEditorCategory();
23 descriptor.priority = panel.GetPriority();
24 descriptor.workflow_group = panel.GetWorkflowGroup();
25 descriptor.workflow_label = panel.GetWorkflowLabel();
27 descriptor.workflow_priority = panel.GetWorkflowPriority();
28 descriptor.shortcut_hint = panel.GetShortcutHint();
29 descriptor.scope = panel.GetScope();
30 descriptor.window_lifecycle = panel.GetWindowLifecycle();
31 descriptor.context_scope = panel.GetContextScope();
32 descriptor.enabled_condition = [panel_ptr]() {
33 return panel_ptr->IsEnabled();
34 };
35 descriptor.disabled_tooltip = panel.GetDisabledTooltip();
36 descriptor.visibility_flag = nullptr;
37 descriptor.window_title = panel.GetIcon() + " " + panel.GetDisplayName();
38 return descriptor;
39}
40
41bool IsTransientPanelVisibilityId(const std::string& panel_id) {
42 constexpr char kRoomPanelPrefix[] = "dungeon.room_";
43 constexpr size_t kRoomPanelPrefixLength = 13;
44 if (panel_id.rfind(kRoomPanelPrefix, 0) != 0 ||
45 panel_id.size() <= kRoomPanelPrefixLength) {
46 return false;
47 }
48 return std::all_of(panel_id.begin() + kRoomPanelPrefixLength, panel_id.end(),
49 [](char ch) { return ch >= '0' && ch <= '9'; });
50}
51
52} // namespace
53
55 if (!FindSessionWindowIds(session_id)) {
56 session_state_.session_windows[session_id] = std::vector<std::string>();
58 std::unordered_map<std::string, std::string>();
60 std::unordered_map<std::string, std::string>();
61 for (const auto& global_panel_id : global_panel_ids_) {
62 TrackPanelForSession(session_id, global_panel_id, global_panel_id);
63 }
65 LOG_INFO("WorkspaceWindowManager", "Registered session %zu (total: %zu)",
66 session_id, session_count_);
67 }
68}
69
71 if (FindSessionWindowIds(session_id)) {
72 UnregisterSessionPanels(session_id);
73 session_state_.session_windows.erase(session_id);
74 session_state_.session_window_mapping.erase(session_id);
76 session_state_.session_context_keys.erase(session_id);
78
79 if (active_session_ == session_id) {
81 if (!session_state_.session_windows.empty()) {
83 }
84 }
85
86 LOG_INFO("WorkspaceWindowManager", "Unregistered session %zu (total: %zu)",
87 session_id, session_count_);
88 }
89}
90
92 active_session_ = session_id;
93}
94
97 std::string key) {
98 RegisterSession(session_id);
99 auto& session_map = session_state_.session_context_keys[session_id];
100 const std::string old_key =
101 (session_map.find(scope) != session_map.end()) ? session_map[scope] : "";
102 if (old_key == key) {
103 return;
104 }
105 session_map[scope] = std::move(key);
106 ApplyContextPolicy(session_id, scope, old_key, session_map[scope]);
107}
108
110 size_t session_id, WindowContextScope scope) const {
111 auto sit = session_state_.session_context_keys.find(session_id);
112 if (sit == session_state_.session_context_keys.end()) {
113 return "";
114 }
115 const auto& session_map = sit->second;
116 auto it = session_map.find(scope);
117 if (it == session_map.end()) {
118 return "";
119 }
120 return it->second;
121}
122
124 const std::string& legacy_base_id, const std::string& canonical_base_id) {
125 if (legacy_base_id.empty() || canonical_base_id.empty() ||
126 legacy_base_id == canonical_base_id) {
127 return;
128 }
129 panel_id_aliases_[legacy_base_id] = canonical_base_id;
130}
131
133 const std::string& panel_id) const {
134 return ResolveBaseWindowId(panel_id);
135}
136
138 const std::string& panel_id) const {
139 if (panel_id.empty()) {
140 return "";
141 }
142
143 std::string resolved = panel_id;
144 std::unordered_set<std::string> visited;
145 visited.insert(resolved);
146
147 for (int depth = 0; depth < 16; ++depth) {
148 auto alias_it = panel_id_aliases_.find(resolved);
149 if (alias_it == panel_id_aliases_.end() || alias_it->second.empty()) {
150 return resolved;
151 }
152
153 const std::string& next = alias_it->second;
154 if (next == resolved || visited.count(next) > 0) {
155 return resolved;
156 }
157
158 resolved = next;
159 visited.insert(resolved);
160 }
161
162 return resolved;
163}
164
166 WindowContextScope scope,
167 const std::string& old_key,
168 const std::string& new_key) {
169 (void)old_key;
170 if (!new_key.empty()) {
171 return;
172 }
173
174 const auto* session_windows = FindSessionWindowIds(session_id);
175 if (!session_windows) {
176 return;
177 }
178
179 for (const auto& prefixed_id : *session_windows) {
180 const auto* desc = FindDescriptorByPrefixedId(prefixed_id);
181 if (!desc) {
182 continue;
183 }
184 if (desc->context_scope != scope) {
185 continue;
186 }
187 if (!desc->visibility_flag || !*desc->visibility_flag) {
188 continue;
189 }
190 if (IsWindowPinnedImpl(prefixed_id)) {
191 continue;
192 }
193
194 const std::string base_id = GetBaseIdForPrefixedId(session_id, prefixed_id);
195 if (!base_id.empty()) {
196 (void)CloseWindowImpl(session_id, base_id);
197 }
198 }
199}
200
202 size_t session_id, const std::string& prefixed_id) const {
203 const auto* reverse = FindSessionReverseWindowMapping(session_id);
204 if (!reverse) {
205 return "";
206 }
207 auto it = reverse->find(prefixed_id);
208 if (it == reverse->end()) {
209 return "";
210 }
211 return it->second;
212}
213
215 size_t session_id, const std::vector<std::string>& panel_ids) {
216 const auto* window_mapping = FindSessionWindowMapping(session_id);
217 if (!window_mapping) {
218 return;
219 }
220
221 std::unordered_set<std::string> visible_set;
222 visible_set.reserve(panel_ids.size());
223 for (const auto& panel_id : panel_ids) {
224 visible_set.insert(ResolveBaseWindowId(panel_id));
225 }
226
227 for (const auto& [base_id, prefixed_id] : *window_mapping) {
228 if (auto* descriptor = FindDescriptorByPrefixedId(prefixed_id)) {
229 if (!descriptor->visibility_flag) {
230 continue;
231 }
232 *descriptor->visibility_flag = visible_set.count(base_id) > 0;
233 }
234 }
235
236 LOG_INFO("WorkspaceWindowManager", "Set %zu panels visible for session %zu",
237 panel_ids.size(), session_id);
238}
239
240std::unordered_map<std::string, bool>
242 std::unordered_map<std::string, bool> state;
243
244 const auto* session_mapping = FindSessionWindowMapping(session_id);
245 if (!session_mapping) {
246 return state;
247 }
248
249 for (const auto& [base_id, prefixed_id] : *session_mapping) {
250 if (IsTransientPanelVisibilityId(base_id)) {
251 continue;
252 }
253 if (const auto* descriptor = FindDescriptorByPrefixedId(prefixed_id)) {
254 if (descriptor->visibility_flag) {
255 state[base_id] = *descriptor->visibility_flag;
256 }
257 }
258 }
259
260 return state;
261}
262
264 size_t session_id, const std::unordered_map<std::string, bool>& state,
265 bool publish_events) {
266 const auto* session_mapping = FindSessionWindowMapping(session_id);
267 if (!session_mapping) {
268 LOG_WARN("WorkspaceWindowManager",
269 "Cannot restore visibility: session %zu not found", session_id);
270 return;
271 }
272
273 size_t restored = 0;
274 for (const auto& [base_id, visible] : state) {
275 const std::string canonical_base_id = ResolveBaseWindowId(base_id);
276 if (IsTransientPanelVisibilityId(canonical_base_id)) {
277 continue;
278 }
279 auto mapping_it = session_mapping->find(canonical_base_id);
280 if (mapping_it == session_mapping->end()) {
281 continue;
282 }
283 if (auto* descriptor = FindDescriptorByPrefixedId(mapping_it->second)) {
284 if (!descriptor->visibility_flag) {
285 continue;
286 }
287 *descriptor->visibility_flag = visible;
288 if (publish_events) {
289 PublishWindowVisibilityChanged(session_id, mapping_it->second,
290 canonical_base_id, descriptor->category,
291 visible);
292 }
293 restored++;
294 }
295 }
296
297 LOG_INFO("WorkspaceWindowManager",
298 "Restored visibility for %zu/%zu panels in session %zu", restored,
299 state.size(), session_id);
300}
301
302std::unordered_map<std::string, bool>
304 std::unordered_map<std::string, bool> state;
305
306 for (const auto& [prefixed_id, pinned] : pinned_panels_) {
307 std::string base_id = prefixed_id;
308 if (prefixed_id.size() > 2 && prefixed_id[0] == 's') {
309 size_t dot_pos = prefixed_id.find('.');
310 if (dot_pos != std::string::npos && dot_pos + 1 < prefixed_id.size()) {
311 base_id = prefixed_id.substr(dot_pos + 1);
312 }
313 }
314 state[base_id] = pinned;
315 }
316
317 // Include pins that were restored but never yet matched a live panel — e.g.
318 // the user pinned the emulator panel in a prior session and has not re-opened
319 // that editor yet this run. Without this, a save would drop those intentions.
320 for (const auto& [base_id, pinned] : pending_pinned_base_ids_) {
321 state.try_emplace(base_id, pinned);
322 }
323
324 return state;
325}
326
328 const std::unordered_map<std::string, bool>& state) {
329 std::unordered_map<std::string, bool> canonical_state;
330 canonical_state.reserve(state.size());
331 for (const auto& [base_id, pinned] : state) {
332 canonical_state[ResolveBaseWindowId(base_id)] = pinned;
333 }
334
335 // Seed pending with every restored entry. TrackPanelForSession consumes
336 // them as matching panels register; leftover entries persist so a repeat
337 // Save round-trips them without asking the panel to actually register.
338 pending_pinned_base_ids_ = canonical_state;
339
340 for (const auto& [session_id, card_mapping] : session_card_mapping_) {
341 for (const auto& [base_id, prefixed_id] : card_mapping) {
342 auto state_it = canonical_state.find(base_id);
343 if (state_it != canonical_state.end()) {
344 pinned_panels_[prefixed_id] = state_it->second;
345 pending_pinned_base_ids_.erase(base_id);
346 }
347 }
348 }
349
350 LOG_INFO("WorkspaceWindowManager",
351 "Restored pinned state for %zu panels (%zu still pending "
352 "registration)",
353 canonical_state.size() - pending_pinned_base_ids_.size(),
355}
356
358 size_t session_id, const std::string& base_card_id) const {
359 const WindowDescriptor* descriptor =
360 GetWindowDescriptorImpl(session_id, base_card_id);
361 if (!descriptor) {
362 return "";
363 }
364 return GetWindowNameImpl(*descriptor);
365}
366
368 const WindowDescriptor& descriptor) const {
369 return descriptor.GetImGuiWindowName();
370}
371
373 size_t session_id, const std::vector<WindowDescriptor>& cards) {
375 ImGui::IsWindowHovered(ImGuiHoveredFlags_None) &&
376 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
378 browser_state_.focused_window_index = cards.empty() ? -1 : 0;
379 }
380
381 if (!browser_state_.sidebar_has_focus || cards.empty()) {
382 return;
383 }
384
385 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
388 return;
389 }
390
391 int card_count = static_cast<int>(cards.size());
392 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
393 ImGui::IsKeyPressed(ImGuiKey_J)) {
395 std::min(browser_state_.focused_window_index + 1, card_count - 1);
396 }
397 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
398 ImGui::IsKeyPressed(ImGuiKey_K)) {
400 std::max(browser_state_.focused_window_index - 1, 0);
401 }
402 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
404 }
405 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
406 browser_state_.focused_window_index = card_count - 1;
407 }
408
411 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
412 ImGui::IsKeyPressed(ImGuiKey_Space)) {
413 const auto& card = cards[browser_state_.focused_window_index];
414 ToggleWindowImpl(session_id, card.card_id);
415 }
416 }
417}
418
420 const std::string& card_id) {
421 last_used_at_[card_id] = ++mru_counter_;
422}
423
425 size_t session_id, const std::string& category) const {
426 auto panels = GetWindowsInCategoryImpl(session_id, category);
427
428 std::sort(
429 panels.begin(), panels.end(),
430 [this, session_id](const WindowDescriptor& a, const WindowDescriptor& b) {
431 bool a_pinned = IsWindowPinnedImpl(session_id, a.card_id);
432 bool b_pinned = IsWindowPinnedImpl(session_id, b.card_id);
433 if (a_pinned != b_pinned) {
434 return a_pinned > b_pinned;
435 }
436
437 auto a_it = last_used_at_.find(a.card_id);
438 auto b_it = last_used_at_.find(b.card_id);
439 uint64_t a_time = (a_it != last_used_at_.end()) ? a_it->second : 0;
440 uint64_t b_time = (b_it != last_used_at_.end()) ? b_it->second : 0;
441 if (a_time != b_time) {
442 return a_time > b_time;
443 }
444
445 return a.priority < b.priority;
446 });
447
448 return panels;
449}
450
451size_t WorkspaceWindowManager::GetVisibleWindowCount(size_t session_id) const {
452 size_t count = 0;
453 const auto* session_windows = FindSessionWindowIds(session_id);
454 if (!session_windows) {
455 return count;
456 }
457 for (const auto& prefixed_card_id : *session_windows) {
458 if (const auto* descriptor = FindDescriptorByPrefixedId(prefixed_card_id)) {
459 if (descriptor->visibility_flag && *descriptor->visibility_flag) {
460 count++;
461 }
462 }
463 }
464 return count;
465}
466
467void WorkspaceWindowManager::UpdateSessionCount() {
468 session_count_ = session_cards_.size();
469}
470
471std::string WorkspaceWindowManager::GetPrefixedWindowId(
472 size_t session_id, const std::string& base_id) const {
473 const std::string resolved_base_id = ResolveBaseWindowId(base_id);
474
475 const auto* session_mapping = FindSessionWindowMapping(session_id);
476 if (session_mapping) {
477 auto card_it = session_mapping->find(resolved_base_id);
478 if (card_it != session_mapping->end()) {
479 return card_it->second;
480 }
481 }
482
483 if (cards_.find(resolved_base_id) != cards_.end()) {
484 return resolved_base_id;
485 }
486
487 return "";
488}
489
490void WorkspaceWindowManager::RegisterPanelDescriptorForSession(
491 size_t session_id, const WindowContent& panel) {
492 RegisterSession(session_id);
493 std::string panel_id =
494 MakeWindowId(session_id, panel.GetId(), panel.GetScope());
495 bool already_registered = (cards_.find(panel_id) != cards_.end());
496 WindowDescriptor descriptor = BuildDescriptorFromPanel(panel);
497 RegisterPanel(session_id, descriptor);
498 if (!already_registered && panel.IsVisibleByDefault()) {
499 OpenWindowImpl(session_id, panel.GetId());
500 }
501}
502
503void WorkspaceWindowManager::TrackPanelForSession(size_t session_id,
504 const std::string& base_id,
505 const std::string& panel_id) {
506 const std::string canonical_base_id = ResolveBaseWindowId(base_id);
507
508 auto& card_list = session_cards_[session_id];
509 if (std::find(card_list.begin(), card_list.end(), panel_id) ==
510 card_list.end()) {
511 card_list.push_back(panel_id);
512 }
513 session_card_mapping_[session_id][canonical_base_id] = panel_id;
514 session_reverse_card_mapping_[session_id][panel_id] = canonical_base_id;
515
516 // If RestorePinnedState previously carried a pin for this base id that
517 // couldn't be applied yet (panel hadn't registered), apply it now. User's
518 // live pinned_panels_ wins on unregister/re-register — pending is consumed
519 // exactly once.
520 auto pending_it = pending_pinned_base_ids_.find(canonical_base_id);
521 if (pending_it != pending_pinned_base_ids_.end()) {
522 pinned_panels_[panel_id] = pending_it->second;
523 pending_pinned_base_ids_.erase(pending_it);
524 }
525}
526
527void WorkspaceWindowManager::UnregisterSessionPanels(size_t session_id) {
528 const auto* session_windows = FindSessionWindowIds(session_id);
529 if (!session_windows) {
530 return;
531 }
532
533 for (const auto& prefixed_card_id : *session_windows) {
534 if (global_panel_ids_.find(prefixed_card_id) != global_panel_ids_.end()) {
535 continue;
536 }
537 const std::string base_card_id =
538 GetBaseIdForPrefixedId(session_id, prefixed_card_id);
539 // Registry instances are deliberately shared. Editor-created session
540 // content is owned by this descriptor and must die before its RomSession.
541 if (registry_panel_ids_.find(base_card_id) == registry_panel_ids_.end()) {
542 if (auto instance_it = panel_instances_.find(prefixed_card_id);
543 instance_it != panel_instances_.end()) {
544 instance_it->second->OnClose();
545 gui::GetAnimator().ClearAnimationsForPanel(prefixed_card_id);
546 UntrackResourceWindow(prefixed_card_id);
547 panel_instances_.erase(instance_it);
548 }
549 }
550 if (!base_card_id.empty()) {
551 RememberPinnedStateForRemovedWindow(session_id, base_card_id,
552 prefixed_card_id);
553 }
554 cards_.erase(prefixed_card_id);
555 centralized_visibility_.erase(prefixed_card_id);
556 pinned_panels_.erase(prefixed_card_id);
557 }
558}
559
560} // namespace editor
561} // namespace yaze
Base interface for all logical window content components.
virtual std::string GetWorkflowDescription() const
Optional workflow description for menus/command palette.
virtual std::string GetDisabledTooltip() const
Get tooltip text when panel is disabled.
virtual int GetWorkflowPriority() const
Optional workflow ordering priority (lower sorts first).
virtual bool IsVisibleByDefault() const
Whether this panel should be visible by default.
virtual std::string GetDisplayName() const =0
Human-readable name shown in menus and title bars.
virtual WindowLifecycle GetWindowLifecycle() const
Get the lifecycle category for this window.
virtual std::string GetEditorCategory() const =0
Editor category this panel belongs to.
virtual int GetPriority() const
Get display priority for menu ordering.
virtual WindowScope GetScope() const
Get the registration scope for this window.
virtual std::string GetIcon() const =0
Material Design icon for this panel.
virtual WindowContextScope GetContextScope() const
Optional context binding for this window (room/selection/etc)
virtual std::string GetWorkflowLabel() const
Optional workflow label for menus/command palette.
virtual std::string GetId() const =0
Unique identifier for this panel.
virtual std::string GetWorkflowGroup() const
Optional workflow group for hack-centric actions.
virtual std::string GetShortcutHint() const
Get keyboard shortcut hint for display.
bool IsWindowPinnedImpl(size_t session_id, const std::string &base_card_id) const
bool CloseWindowImpl(size_t session_id, const std::string &base_card_id)
std::string GetContextKey(size_t session_id, WindowContextScope scope) const
std::unordered_map< std::string, std::string > * FindSessionWindowMapping(size_t session_id)
std::string ResolveBaseWindowId(const std::string &panel_id) const
void RestoreVisibilityState(size_t session_id, const std::unordered_map< std::string, bool > &state, bool publish_events=false)
Restore panel visibility state from persistence.
std::vector< std::string > * FindSessionWindowIds(size_t session_id)
void ApplyContextPolicy(size_t session_id, WindowContextScope scope, const std::string &old_key, const std::string &new_key)
const WindowDescriptor * GetWindowDescriptorImpl(size_t session_id, const std::string &base_card_id) const
std::string GetBaseIdForPrefixedId(size_t session_id, const std::string &prefixed_id) const
std::vector< WindowDescriptor > GetWindowsInCategoryImpl(size_t session_id, const std::string &category) const
std::unordered_map< size_t, std::unordered_map< std::string, std::string > > & session_card_mapping_
std::unordered_map< std::string, bool > & pending_pinned_base_ids_
void TrackPanelForSession(size_t session_id, const std::string &base_id, const std::string &panel_id)
WindowDescriptor * FindDescriptorByPrefixedId(const std::string &prefixed_id)
void SetVisibleWindowsImpl(size_t session_id, const std::vector< std::string > &panel_ids)
void MarkWindowRecentlyUsedImpl(const std::string &card_id)
bool ToggleWindowImpl(size_t session_id, const std::string &base_card_id)
std::string ResolvePanelAlias(const std::string &panel_id) const
Resolve a panel ID through the alias table.
std::unordered_map< std::string, std::string > * FindSessionReverseWindowMapping(size_t session_id)
std::string GetWindowNameImpl(size_t session_id, const std::string &base_card_id) const
std::unordered_map< std::string, uint64_t > & last_used_at_
std::vector< WindowDescriptor > GetWindowsSortedByMRUImpl(size_t session_id, const std::string &category) const
std::unordered_map< std::string, bool > SerializeVisibilityState(size_t session_id) const
Serialize panel visibility state for persistence.
void HandleSidebarKeyboardNav(size_t session_id, const std::vector< WindowDescriptor > &cards)
Handle keyboard navigation in sidebar (click-to-focus modal)
std::unordered_map< std::string, std::string > & panel_id_aliases_
std::unordered_map< std::string, bool > & pinned_panels_
std::unordered_map< std::string, bool > SerializePinnedState() const
Serialize pinned panel state for persistence.
void SetContextKey(size_t session_id, WindowContextScope scope, std::string key)
Set a string key for a given context scope (room/selection/etc)
void PublishWindowVisibilityChanged(size_t session_id, const std::string &prefixed_window_id, const std::string &base_window_id, const std::string &category, bool visible) const
void RestorePinnedState(const std::unordered_map< std::string, bool > &state)
Restore pinned panel state from persistence.
void RegisterPanelAlias(const std::string &legacy_base_id, const std::string &canonical_base_id)
Register a legacy panel ID alias that resolves to a canonical ID.
std::unordered_set< std::string > & global_panel_ids_
void ClearAnimationsForPanel(const std::string &panel_id)
Definition animator.cc:83
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
WindowContextScope
Optional context binding for a window's behavior within an editor.
Animator & GetAnimator()
Definition animator.cc:318
Metadata for a dockable editor window (formerly PanelInfo)
std::string GetImGuiWindowName() const
Build the exact ImGui window name used by PanelWindow::Begin.
std::unordered_map< size_t, std::unordered_map< WindowContextScope, std::string, WindowContextScopeHash > > session_context_keys
std::unordered_map< size_t, std::vector< std::string > > session_windows
std::unordered_map< size_t, std::unordered_map< std::string, std::string > > session_reverse_window_mapping
std::unordered_map< size_t, std::unordered_map< std::string, std::string > > session_window_mapping