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