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
9#include "imgui/imgui.h"
10#include "util/log.h"
11
12namespace yaze {
13namespace editor {
14
15namespace {
16
17WindowDescriptor BuildDescriptorFromPanel(const WindowContent& panel) {
18 WindowDescriptor descriptor;
19 auto* panel_ptr = const_cast<WindowContent*>(&panel);
20 descriptor.card_id = panel.GetId();
21 descriptor.display_name = panel.GetDisplayName();
22 descriptor.icon = panel.GetIcon();
23 descriptor.category = panel.GetEditorCategory();
24 descriptor.priority = panel.GetPriority();
25 descriptor.workflow_group = panel.GetWorkflowGroup();
26 descriptor.workflow_label = panel.GetWorkflowLabel();
28 descriptor.workflow_priority = panel.GetWorkflowPriority();
29 descriptor.shortcut_hint = panel.GetShortcutHint();
30 descriptor.scope = panel.GetScope();
31 descriptor.window_lifecycle = panel.GetWindowLifecycle();
32 descriptor.context_scope = panel.GetContextScope();
33 descriptor.presentation = panel.GetPresentationPolicy();
34 descriptor.enabled_condition = [panel_ptr]() {
35 return panel_ptr->IsEnabled();
36 };
37 descriptor.disabled_tooltip = panel.GetDisabledTooltip();
38 descriptor.visibility_flag = nullptr;
39 descriptor.window_title = panel.GetIcon() + " " + panel.GetDisplayName();
40 return descriptor;
41}
42
43} // namespace
44
46 if (!FindSessionWindowIds(session_id)) {
47 session_state_.session_windows[session_id] = std::vector<std::string>();
49 std::unordered_map<std::string, std::string>();
51 std::unordered_map<std::string, std::string>();
52 for (const auto& global_panel_id : global_panel_ids_) {
53 TrackPanelForSession(session_id, global_panel_id, global_panel_id);
54 }
56 LOG_INFO("WorkspaceWindowManager", "Registered session %zu (total: %zu)",
57 session_id, session_count_);
58 }
59}
60
62 if (FindSessionWindowIds(session_id)) {
63 UnregisterSessionPanels(session_id);
64 session_state_.session_windows.erase(session_id);
65 session_state_.session_window_mapping.erase(session_id);
67 session_state_.session_context_keys.erase(session_id);
69
70 if (active_session_ == session_id) {
72 if (!session_state_.session_windows.empty()) {
74 }
75 }
76
77 LOG_INFO("WorkspaceWindowManager", "Unregistered session %zu (total: %zu)",
78 session_id, session_count_);
79 }
80}
81
83 active_session_ = session_id;
84}
85
88 std::string key) {
89 RegisterSession(session_id);
90 auto& session_map = session_state_.session_context_keys[session_id];
91 const std::string old_key =
92 (session_map.find(scope) != session_map.end()) ? session_map[scope] : "";
93 if (old_key == key) {
94 return;
95 }
96 session_map[scope] = std::move(key);
97 ApplyContextPolicy(session_id, scope, old_key, session_map[scope]);
98}
99
101 size_t session_id, WindowContextScope scope) const {
102 auto sit = session_state_.session_context_keys.find(session_id);
103 if (sit == session_state_.session_context_keys.end()) {
104 return "";
105 }
106 const auto& session_map = sit->second;
107 auto it = session_map.find(scope);
108 if (it == session_map.end()) {
109 return "";
110 }
111 return it->second;
112}
113
115 const std::string& legacy_base_id, const std::string& canonical_base_id) {
116 if (legacy_base_id.empty() || canonical_base_id.empty() ||
117 legacy_base_id == canonical_base_id) {
118 return;
119 }
120 panel_id_aliases_[legacy_base_id] = canonical_base_id;
121}
122
124 const std::string& panel_id) const {
125 return ResolveBaseWindowId(panel_id);
126}
127
129 const std::string& panel_id) const {
130 if (panel_id.empty()) {
131 return "";
132 }
133
134 std::string resolved = panel_id;
135 std::unordered_set<std::string> visited;
136 visited.insert(resolved);
137
138 for (int depth = 0; depth < 16; ++depth) {
139 auto alias_it = panel_id_aliases_.find(resolved);
140 if (alias_it == panel_id_aliases_.end() || alias_it->second.empty()) {
141 return resolved;
142 }
143
144 const std::string& next = alias_it->second;
145 if (next == resolved || visited.count(next) > 0) {
146 return resolved;
147 }
148
149 resolved = next;
150 visited.insert(resolved);
151 }
152
153 return resolved;
154}
155
157 WindowContextScope scope,
158 const std::string& old_key,
159 const std::string& new_key) {
160 (void)old_key;
161 if (!new_key.empty()) {
162 return;
163 }
164
165 const auto* session_windows = FindSessionWindowIds(session_id);
166 if (!session_windows) {
167 return;
168 }
169
170 for (const auto& prefixed_id : *session_windows) {
171 const auto* desc = FindDescriptorByPrefixedId(prefixed_id);
172 if (!desc) {
173 continue;
174 }
175 if (desc->context_scope != scope) {
176 continue;
177 }
178 if (!desc->visibility_flag || !*desc->visibility_flag) {
179 continue;
180 }
181 if (IsWindowPinnedImpl(prefixed_id)) {
182 continue;
183 }
184
185 const std::string base_id = GetBaseIdForPrefixedId(session_id, prefixed_id);
186 if (!base_id.empty()) {
187 (void)CloseWindowImpl(session_id, base_id);
188 }
189 }
190}
191
193 size_t session_id, const std::string& prefixed_id) const {
194 const auto* reverse = FindSessionReverseWindowMapping(session_id);
195 if (!reverse) {
196 return "";
197 }
198 auto it = reverse->find(prefixed_id);
199 if (it == reverse->end()) {
200 return "";
201 }
202 return it->second;
203}
204
206 size_t session_id, const std::vector<std::string>& panel_ids) {
207 const auto* window_mapping = FindSessionWindowMapping(session_id);
208 if (!window_mapping) {
209 return;
210 }
211
212 std::unordered_set<std::string> visible_set;
213 visible_set.reserve(panel_ids.size());
214 for (const auto& panel_id : panel_ids) {
215 visible_set.insert(ResolveBaseWindowId(panel_id));
216 }
217
218 for (const auto& [base_id, prefixed_id] : *window_mapping) {
219 if (auto* descriptor = FindDescriptorByPrefixedId(prefixed_id)) {
220 if (!descriptor->visibility_flag) {
221 continue;
222 }
223 *descriptor->visibility_flag = visible_set.count(base_id) > 0;
224 }
225 }
226
227 LOG_INFO("WorkspaceWindowManager", "Set %zu panels visible for session %zu",
228 panel_ids.size(), session_id);
229}
230
231std::unordered_map<std::string, bool>
233 std::unordered_map<std::string, bool> state;
234
235 const auto* session_mapping = FindSessionWindowMapping(session_id);
236 if (!session_mapping) {
237 return state;
238 }
239
240 for (const auto& [base_id, prefixed_id] : *session_mapping) {
241 if (IsTransientPanelVisibilityId(base_id)) {
242 continue;
243 }
244 if (const auto* descriptor = FindDescriptorByPrefixedId(prefixed_id)) {
245 if (descriptor->visibility_flag) {
246 state[base_id] = *descriptor->visibility_flag;
247 }
248 }
249 }
250
251 return state;
252}
253
255 size_t session_id, const std::unordered_map<std::string, bool>& state,
256 bool publish_events) {
257 const auto* session_mapping = FindSessionWindowMapping(session_id);
258 if (!session_mapping) {
259 LOG_WARN("WorkspaceWindowManager",
260 "Cannot restore visibility: session %zu not found", session_id);
261 return;
262 }
263
264 size_t restored = 0;
265 for (const auto& [base_id, visible] : state) {
266 const std::string canonical_base_id = ResolveBaseWindowId(base_id);
267 if (IsTransientPanelVisibilityId(canonical_base_id)) {
268 continue;
269 }
270 auto mapping_it = session_mapping->find(canonical_base_id);
271 if (mapping_it == session_mapping->end()) {
272 continue;
273 }
274 if (auto* descriptor = FindDescriptorByPrefixedId(mapping_it->second)) {
275 if (!descriptor->visibility_flag) {
276 continue;
277 }
278 if (!visible)
279 pending_float_window_ids_.erase(mapping_it->second);
280 *descriptor->visibility_flag = visible;
281 if (publish_events) {
282 PublishWindowVisibilityChanged(session_id, mapping_it->second,
283 canonical_base_id, descriptor->category,
284 visible);
285 }
286 restored++;
287 }
288 }
289
290 LOG_INFO("WorkspaceWindowManager",
291 "Restored visibility for %zu/%zu panels in session %zu", restored,
292 state.size(), session_id);
293}
294
295std::unordered_map<std::string, bool>
297 std::unordered_map<std::string, bool> state;
298
299 for (const auto& [prefixed_id, pinned] : pinned_panels_) {
300 std::string base_id = prefixed_id;
301 if (prefixed_id.size() > 2 && prefixed_id[0] == 's') {
302 size_t dot_pos = prefixed_id.find('.');
303 if (dot_pos != std::string::npos && dot_pos + 1 < prefixed_id.size()) {
304 base_id = prefixed_id.substr(dot_pos + 1);
305 }
306 }
307 state[base_id] = pinned;
308 }
309
310 // Include pins that were restored but never yet matched a live panel — e.g.
311 // the user pinned the emulator panel in a prior session and has not re-opened
312 // that editor yet this run. Without this, a save would drop those intentions.
313 for (const auto& [base_id, pinned] : pending_pinned_base_ids_) {
314 state.try_emplace(base_id, pinned);
315 }
316
317 return state;
318}
319
321 const std::unordered_map<std::string, bool>& state) {
322 std::unordered_map<std::string, bool> canonical_state;
323 canonical_state.reserve(state.size());
324 for (const auto& [base_id, pinned] : state) {
325 canonical_state[ResolveBaseWindowId(base_id)] = pinned;
326 }
327
328 // Seed pending with every restored entry. TrackPanelForSession consumes
329 // them as matching panels register; leftover entries persist so a repeat
330 // Save round-trips them without asking the panel to actually register.
331 pending_pinned_base_ids_ = canonical_state;
332
333 for (const auto& [session_id, card_mapping] : session_card_mapping_) {
334 for (const auto& [base_id, prefixed_id] : card_mapping) {
335 auto state_it = canonical_state.find(base_id);
336 if (state_it != canonical_state.end()) {
337 pinned_panels_[prefixed_id] = state_it->second;
338 pending_pinned_base_ids_.erase(base_id);
339 }
340 }
341 }
342
343 LOG_INFO("WorkspaceWindowManager",
344 "Restored pinned state for %zu panels (%zu still pending "
345 "registration)",
346 canonical_state.size() - pending_pinned_base_ids_.size(),
348}
349
351 size_t session_id, const std::string& base_card_id) const {
352 const WindowDescriptor* descriptor =
353 GetWindowDescriptorImpl(session_id, base_card_id);
354 if (!descriptor) {
355 return "";
356 }
357 return GetWindowNameImpl(*descriptor);
358}
359
361 const WindowDescriptor& descriptor) const {
362 return descriptor.GetImGuiWindowName();
363}
364
366 size_t session_id, const std::vector<WindowDescriptor>& cards) {
368 ImGui::IsWindowHovered(ImGuiHoveredFlags_None) &&
369 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
371 browser_state_.focused_window_index = cards.empty() ? -1 : 0;
372 }
373
374 if (!browser_state_.sidebar_has_focus || cards.empty()) {
375 return;
376 }
377
378 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
381 return;
382 }
383
384 int card_count = static_cast<int>(cards.size());
385 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
386 ImGui::IsKeyPressed(ImGuiKey_J)) {
388 std::min(browser_state_.focused_window_index + 1, card_count - 1);
389 }
390 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
391 ImGui::IsKeyPressed(ImGuiKey_K)) {
393 std::max(browser_state_.focused_window_index - 1, 0);
394 }
395 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
397 }
398 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
399 browser_state_.focused_window_index = card_count - 1;
400 }
401
404 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
405 ImGui::IsKeyPressed(ImGuiKey_Space)) {
406 const auto& card = cards[browser_state_.focused_window_index];
407 ToggleWindowImpl(session_id, card.card_id);
408 }
409 }
410}
411
413 const std::string& card_id) {
414 last_used_at_[card_id] = ++mru_counter_;
415}
416
418 size_t session_id, const std::string& category) const {
419 auto panels = GetWindowsInCategoryImpl(session_id, category);
420
421 std::sort(
422 panels.begin(), panels.end(),
423 [this, session_id](const WindowDescriptor& a, const WindowDescriptor& b) {
424 bool a_pinned = IsWindowPinnedImpl(session_id, a.card_id);
425 bool b_pinned = IsWindowPinnedImpl(session_id, b.card_id);
426 if (a_pinned != b_pinned) {
427 return a_pinned > b_pinned;
428 }
429
430 auto a_it = last_used_at_.find(a.card_id);
431 auto b_it = last_used_at_.find(b.card_id);
432 uint64_t a_time = (a_it != last_used_at_.end()) ? a_it->second : 0;
433 uint64_t b_time = (b_it != last_used_at_.end()) ? b_it->second : 0;
434 if (a_time != b_time) {
435 return a_time > b_time;
436 }
437
438 return a.priority < b.priority;
439 });
440
441 return panels;
442}
443
444size_t WorkspaceWindowManager::GetVisibleWindowCount(size_t session_id) const {
445 size_t count = 0;
446 const auto* session_windows = FindSessionWindowIds(session_id);
447 if (!session_windows) {
448 return count;
449 }
450 for (const auto& prefixed_card_id : *session_windows) {
451 if (const auto* descriptor = FindDescriptorByPrefixedId(prefixed_card_id)) {
452 if (descriptor->visibility_flag && *descriptor->visibility_flag) {
453 count++;
454 }
455 }
456 }
457 return count;
458}
459
460void WorkspaceWindowManager::UpdateSessionCount() {
461 session_count_ = session_cards_.size();
462}
463
464std::string WorkspaceWindowManager::GetPrefixedWindowId(
465 size_t session_id, const std::string& base_id) const {
466 const std::string resolved_base_id = ResolveBaseWindowId(base_id);
467
468 const auto* session_mapping = FindSessionWindowMapping(session_id);
469 if (session_mapping) {
470 auto card_it = session_mapping->find(resolved_base_id);
471 if (card_it != session_mapping->end()) {
472 return card_it->second;
473 }
474 }
475
476 if (cards_.find(resolved_base_id) != cards_.end()) {
477 return resolved_base_id;
478 }
479
480 return "";
481}
482
483void WorkspaceWindowManager::RegisterPanelDescriptorForSession(
484 size_t session_id, const WindowContent& panel) {
485 RegisterSession(session_id);
486 std::string panel_id =
487 MakeWindowId(session_id, panel.GetId(), panel.GetScope());
488 bool already_registered = (cards_.find(panel_id) != cards_.end());
489 WindowDescriptor descriptor = BuildDescriptorFromPanel(panel);
490 RegisterPanel(session_id, descriptor);
491 if (!already_registered && panel.IsVisibleByDefault()) {
492 OpenWindowImpl(session_id, panel.GetId());
493 }
494}
495
496void WorkspaceWindowManager::TrackPanelForSession(size_t session_id,
497 const std::string& base_id,
498 const std::string& panel_id) {
499 const std::string canonical_base_id = ResolveBaseWindowId(base_id);
500
501 auto& card_list = session_cards_[session_id];
502 if (std::find(card_list.begin(), card_list.end(), panel_id) ==
503 card_list.end()) {
504 card_list.push_back(panel_id);
505 }
506 session_card_mapping_[session_id][canonical_base_id] = panel_id;
507 session_reverse_card_mapping_[session_id][panel_id] = canonical_base_id;
508
509 // If RestorePinnedState previously carried a pin for this base id that
510 // couldn't be applied yet (panel hadn't registered), apply it now. User's
511 // live pinned_panels_ wins on unregister/re-register — pending is consumed
512 // exactly once.
513 auto pending_it = pending_pinned_base_ids_.find(canonical_base_id);
514 if (pending_it != pending_pinned_base_ids_.end()) {
515 pinned_panels_[panel_id] = pending_it->second;
516 pending_pinned_base_ids_.erase(pending_it);
517 }
518}
519
520void WorkspaceWindowManager::UnregisterSessionPanels(size_t session_id) {
521 const auto* session_windows = FindSessionWindowIds(session_id);
522 if (!session_windows) {
523 return;
524 }
525
526 for (const auto& prefixed_card_id : *session_windows) {
527 if (global_panel_ids_.find(prefixed_card_id) != global_panel_ids_.end()) {
528 continue;
529 }
530 const std::string base_card_id =
531 GetBaseIdForPrefixedId(session_id, prefixed_card_id);
532 // Registry instances are deliberately shared. Editor-created session
533 // content is owned by this descriptor and must die before its RomSession.
534 if (registry_panel_ids_.find(base_card_id) == registry_panel_ids_.end()) {
535 if (auto instance_it = panel_instances_.find(prefixed_card_id);
536 instance_it != panel_instances_.end()) {
537 instance_it->second->OnClose();
538 gui::GetAnimator().ClearAnimationsForPanel(prefixed_card_id);
539 UntrackResourceWindow(prefixed_card_id);
540 panel_instances_.erase(instance_it);
541 }
542 }
543 if (!base_card_id.empty()) {
544 RememberPinnedStateForRemovedWindow(session_id, base_card_id,
545 prefixed_card_id);
546 }
547 pending_float_window_ids_.erase(prefixed_card_id);
548 cards_.erase(prefixed_card_id);
549 centralized_visibility_.erase(prefixed_card_id);
550 pinned_panels_.erase(prefixed_card_id);
551 }
552}
553
554} // namespace editor
555} // 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 WindowPresentationPolicy GetPresentationPolicy() const
How this content is hosted and advertised by workspace chrome.
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.
std::unordered_set< std::string > & pending_float_window_ids_
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:138
#define LOG_INFO(category, format,...)
Definition log.h:136
WindowContextScope
Optional context binding for a window's behavior within an editor.
bool IsTransientPanelVisibilityId(absl::string_view panel_id)
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