1#define IMGUI_DEFINE_MATH_OPERATORS
8#include "imgui/imgui.h"
33 return panel_ptr->IsEnabled();
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) {
48 return std::all_of(panel_id.begin() + kRoomPanelPrefixLength, panel_id.end(),
49 [](
char ch) { return ch >=
'0' && ch <=
'9'; });
58 std::unordered_map<std::string, std::string>();
60 std::unordered_map<std::string, std::string>();
65 LOG_INFO(
"WorkspaceWindowManager",
"Registered session %zu (total: %zu)",
86 LOG_INFO(
"WorkspaceWindowManager",
"Unregistered session %zu (total: %zu)",
100 const std::string old_key =
101 (session_map.find(scope) != session_map.end()) ? session_map[scope] :
"";
102 if (old_key == key) {
105 session_map[scope] = std::move(key);
115 const auto& session_map = sit->second;
116 auto it = session_map.find(scope);
117 if (it == session_map.end()) {
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) {
133 const std::string& panel_id)
const {
138 const std::string& panel_id)
const {
139 if (panel_id.empty()) {
143 std::string resolved = panel_id;
144 std::unordered_set<std::string> visited;
145 visited.insert(resolved);
147 for (
int depth = 0; depth < 16; ++depth) {
153 const std::string& next = alias_it->second;
154 if (next == resolved || visited.count(next) > 0) {
159 visited.insert(resolved);
167 const std::string& old_key,
168 const std::string& new_key) {
170 if (!new_key.empty()) {
175 if (!session_windows) {
179 for (
const auto& prefixed_id : *session_windows) {
184 if (desc->context_scope != scope) {
187 if (!desc->visibility_flag || !*desc->visibility_flag) {
195 if (!base_id.empty()) {
202 size_t session_id,
const std::string& prefixed_id)
const {
207 auto it = reverse->find(prefixed_id);
208 if (it == reverse->end()) {
215 size_t session_id,
const std::vector<std::string>& panel_ids) {
217 if (!window_mapping) {
221 std::unordered_set<std::string> visible_set;
222 visible_set.reserve(panel_ids.size());
223 for (
const auto& panel_id : panel_ids) {
227 for (
const auto& [base_id, prefixed_id] : *window_mapping) {
229 if (!descriptor->visibility_flag) {
232 *descriptor->visibility_flag = visible_set.count(base_id) > 0;
236 LOG_INFO(
"WorkspaceWindowManager",
"Set %zu panels visible for session %zu",
237 panel_ids.size(), session_id);
240std::unordered_map<std::string, bool>
242 std::unordered_map<std::string, bool> state;
245 if (!session_mapping) {
249 for (
const auto& [base_id, prefixed_id] : *session_mapping) {
250 if (IsTransientPanelVisibilityId(base_id)) {
254 if (descriptor->visibility_flag) {
255 state[base_id] = *descriptor->visibility_flag;
264 size_t session_id,
const std::unordered_map<std::string, bool>& state,
265 bool publish_events) {
267 if (!session_mapping) {
269 "Cannot restore visibility: session %zu not found", session_id);
274 for (
const auto& [base_id, visible] : state) {
276 if (IsTransientPanelVisibilityId(canonical_base_id)) {
279 auto mapping_it = session_mapping->find(canonical_base_id);
280 if (mapping_it == session_mapping->end()) {
284 if (!descriptor->visibility_flag) {
287 *descriptor->visibility_flag = visible;
288 if (publish_events) {
290 canonical_base_id, descriptor->category,
298 "Restored visibility for %zu/%zu panels in session %zu", restored,
299 state.size(), session_id);
302std::unordered_map<std::string, bool>
304 std::unordered_map<std::string, bool> state;
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);
314 state[base_id] = pinned;
321 state.try_emplace(base_id, pinned);
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) {
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()) {
351 "Restored pinned state for %zu panels (%zu still pending "
358 size_t session_id,
const std::string& base_card_id)
const {
373 size_t session_id,
const std::vector<WindowDescriptor>& cards) {
375 ImGui::IsWindowHovered(ImGuiHoveredFlags_None) &&
376 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
385 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
391 int card_count =
static_cast<int>(cards.size());
392 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
393 ImGui::IsKeyPressed(ImGuiKey_J)) {
397 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
398 ImGui::IsKeyPressed(ImGuiKey_K)) {
402 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
405 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
411 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
412 ImGui::IsKeyPressed(ImGuiKey_Space)) {
420 const std::string& card_id) {
425 size_t session_id,
const std::string& category)
const {
429 panels.begin(), panels.end(),
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;
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;
451size_t WorkspaceWindowManager::GetVisibleWindowCount(
size_t session_id)
const {
453 const auto* session_windows = FindSessionWindowIds(session_id);
454 if (!session_windows) {
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) {
467void WorkspaceWindowManager::UpdateSessionCount() {
468 session_count_ = session_cards_.size();
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);
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;
483 if (cards_.find(resolved_base_id) != cards_.end()) {
484 return resolved_base_id;
490void WorkspaceWindowManager::RegisterPanelDescriptorForSession(
492 RegisterSession(session_id);
493 std::string panel_id =
495 bool already_registered = (cards_.find(panel_id) != cards_.end());
497 RegisterPanel(session_id, descriptor);
499 OpenWindowImpl(session_id, panel.
GetId());
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);
508 auto& card_list = session_cards_[session_id];
509 if (std::find(card_list.begin(), card_list.end(), panel_id) ==
511 card_list.push_back(panel_id);
513 session_card_mapping_[session_id][canonical_base_id] = panel_id;
514 session_reverse_card_mapping_[session_id][panel_id] = canonical_base_id;
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);
527void WorkspaceWindowManager::UnregisterSessionPanels(
size_t session_id) {
528 const auto* session_windows = FindSessionWindowIds(session_id);
529 if (!session_windows) {
533 for (
const auto& prefixed_card_id : *session_windows) {
534 if (global_panel_ids_.find(prefixed_card_id) != global_panel_ids_.end()) {
537 const std::string base_card_id =
538 GetBaseIdForPrefixedId(session_id, prefixed_card_id);
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();
546 UntrackResourceWindow(prefixed_card_id);
547 panel_instances_.erase(instance_it);
550 if (!base_card_id.empty()) {
551 RememberPinnedStateForRemovedWindow(session_id, base_card_id,
554 cards_.erase(prefixed_card_id);
555 centralized_visibility_.erase(prefixed_card_id);
556 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_
void ClearAnimationsForPanel(const std::string &panel_id)
#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