1#define IMGUI_DEFINE_MATH_OPERATORS
7#include "imgui/imgui.h"
32 return panel_ptr->IsEnabled();
46 std::unordered_map<std::string, std::string>();
48 std::unordered_map<std::string, std::string>();
53 LOG_INFO(
"WorkspaceWindowManager",
"Registered session %zu (total: %zu)",
74 LOG_INFO(
"WorkspaceWindowManager",
"Unregistered session %zu (total: %zu)",
88 const std::string old_key =
89 (session_map.find(scope) != session_map.end()) ? session_map[scope] :
"";
93 session_map[scope] = std::move(key);
103 const auto& session_map = sit->second;
104 auto it = session_map.find(scope);
105 if (it == session_map.end()) {
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) {
121 const std::string& panel_id)
const {
126 const std::string& panel_id)
const {
127 if (panel_id.empty()) {
131 std::string resolved = panel_id;
132 std::unordered_set<std::string> visited;
133 visited.insert(resolved);
135 for (
int depth = 0; depth < 16; ++depth) {
141 const std::string& next = alias_it->second;
142 if (next == resolved || visited.count(next) > 0) {
147 visited.insert(resolved);
155 const std::string& old_key,
156 const std::string& new_key) {
158 if (!new_key.empty()) {
163 if (!session_windows) {
167 for (
const auto& prefixed_id : *session_windows) {
172 if (desc->context_scope != scope) {
175 if (!desc->visibility_flag || !*desc->visibility_flag) {
183 if (!base_id.empty()) {
190 size_t session_id,
const std::string& prefixed_id)
const {
195 auto it = reverse->find(prefixed_id);
196 if (it == reverse->end()) {
203 size_t session_id,
const std::vector<std::string>& panel_ids) {
205 if (!window_mapping) {
209 std::unordered_set<std::string> visible_set;
210 visible_set.reserve(panel_ids.size());
211 for (
const auto& panel_id : panel_ids) {
215 for (
const auto& [base_id, prefixed_id] : *window_mapping) {
217 if (!descriptor->visibility_flag) {
220 *descriptor->visibility_flag = visible_set.count(base_id) > 0;
224 LOG_INFO(
"WorkspaceWindowManager",
"Set %zu panels visible for session %zu",
225 panel_ids.size(), session_id);
228std::unordered_map<std::string, bool>
230 std::unordered_map<std::string, bool> state;
233 if (!session_mapping) {
237 for (
const auto& [base_id, prefixed_id] : *session_mapping) {
239 if (descriptor->visibility_flag) {
240 state[base_id] = *descriptor->visibility_flag;
249 size_t session_id,
const std::unordered_map<std::string, bool>& state,
250 bool publish_events) {
252 if (!session_mapping) {
254 "Cannot restore visibility: session %zu not found", session_id);
259 for (
const auto& [base_id, visible] : state) {
261 auto mapping_it = session_mapping->find(canonical_base_id);
262 if (mapping_it == session_mapping->end()) {
266 if (!descriptor->visibility_flag) {
269 *descriptor->visibility_flag = visible;
270 if (publish_events) {
272 canonical_base_id, descriptor->category,
280 "Restored visibility for %zu/%zu panels in session %zu", restored,
281 state.size(), session_id);
284std::unordered_map<std::string, bool>
286 std::unordered_map<std::string, bool> state;
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);
296 state[base_id] = pinned;
303 state.try_emplace(base_id, pinned);
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) {
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()) {
333 "Restored pinned state for %zu panels (%zu still pending "
340 size_t session_id,
const std::string& base_card_id)
const {
355 size_t session_id,
const std::vector<WindowDescriptor>& cards) {
357 ImGui::IsWindowHovered(ImGuiHoveredFlags_None) &&
358 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
367 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
373 int card_count =
static_cast<int>(cards.size());
374 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
375 ImGui::IsKeyPressed(ImGuiKey_J)) {
379 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
380 ImGui::IsKeyPressed(ImGuiKey_K)) {
384 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
387 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
393 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
394 ImGui::IsKeyPressed(ImGuiKey_Space)) {
402 const std::string& card_id) {
407 size_t session_id,
const std::string& category)
const {
411 panels.begin(), panels.end(),
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;
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;
433size_t WorkspaceWindowManager::GetVisibleWindowCount(
size_t session_id)
const {
435 const auto* session_windows = FindSessionWindowIds(session_id);
436 if (!session_windows) {
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) {
449void WorkspaceWindowManager::UpdateSessionCount() {
450 session_count_ = session_cards_.size();
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);
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;
465 if (cards_.find(resolved_base_id) != cards_.end()) {
466 return resolved_base_id;
472void WorkspaceWindowManager::RegisterPanelDescriptorForSession(
474 RegisterSession(session_id);
475 std::string panel_id =
477 bool already_registered = (cards_.find(panel_id) != cards_.end());
479 RegisterPanel(session_id, descriptor);
481 OpenWindowImpl(session_id, panel.
GetId());
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);
490 auto& card_list = session_cards_[session_id];
491 if (std::find(card_list.begin(), card_list.end(), panel_id) ==
493 card_list.push_back(panel_id);
495 session_card_mapping_[session_id][canonical_base_id] = panel_id;
496 session_reverse_card_mapping_[session_id][panel_id] = canonical_base_id;
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);
509void WorkspaceWindowManager::UnregisterSessionPanels(
size_t session_id) {
510 const auto* session_windows = FindSessionWindowIds(session_id);
511 if (!session_windows) {
515 for (
const auto& prefixed_card_id : *session_windows) {
516 if (global_panel_ids_.find(prefixed_card_id) != global_panel_ids_.end()) {
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,
525 cards_.erase(prefixed_card_id);
526 centralized_visibility_.erase(prefixed_card_id);
527 pinned_panels_.erase(prefixed_card_id);
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
WindowBrowserState browser_state_
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)
WindowSessionState session_state_
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)
void SetActiveSession(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)
void UnregisterSession(size_t session_id)
void UpdateSessionCount()
std::unordered_map< std::string, std::string > & panel_id_aliases_
std::unordered_map< std::string, bool > & pinned_panels_
void RegisterSession(size_t session_id)
std::unordered_map< std::string, bool > SerializePinnedState() const
Serialize pinned panel state for persistence.
void UnregisterSessionPanels(size_t session_id)
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,...)
#define LOG_INFO(category, format,...)
WindowContextScope
Optional context binding for a window's behavior within an editor.
Metadata for a dockable editor window (formerly PanelInfo)
std::string shortcut_hint
std::string workflow_group
WindowContextScope context_scope
std::string disabled_tooltip
std::string workflow_description
std::string workflow_label
std::string GetImGuiWindowName() const
Build the exact ImGui window name used by PanelWindow::Begin.
std::function< bool()> enabled_condition
WindowLifecycle window_lifecycle
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