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();
25 descriptor.workflow_description = panel.GetWorkflowDescription();
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
42void WorkspaceWindowManager::RegisterSession(size_t session_id) {
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
58void WorkspaceWindowManager::UnregisterSession(size_t session_id) {
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
79void WorkspaceWindowManager::SetActiveSession(size_t session_id) {
80 active_session_ = session_id;
81}
82
83void WorkspaceWindowManager::SetContextKey(size_t session_id,
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
153void WorkspaceWindowManager::ApplyContextPolicy(size_t session_id,
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
406std::vector<WindowDescriptor> WorkspaceWindowManager::GetWindowsSortedByMRUImpl(
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
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.
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