#include "ProjectPanelSupport.h" #include namespace XCEngine::UI::Editor::App { using namespace ProjectPanelSupport; void ProjectPanel::ResetTransientFrames() { m_treeFrame = {}; m_frameEvents.clear(); m_layout = {}; m_hoveredAssetItemId.clear(); m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; m_pressedBreadcrumbIndex = kInvalidLayoutIndex; m_splitterHovered = false; m_splitterDragging = false; } void ProjectPanel::Update( const UIEditorPanelContentHostFrame& contentHostFrame, const std::vector& inputEvents, bool allowInteraction, bool panelActive) { m_requestPointerCapture = false; m_requestPointerRelease = false; m_frameEvents.clear(); const UIEditorPanelContentHostPanelState* panelState = FindMountedProjectPanel(contentHostFrame); if (panelState == nullptr) { if (m_splitterDragging) { m_requestPointerRelease = true; } m_visible = false; ResetTransientFrames(); return; } if (m_browserModel.GetTreeItems().empty()) { m_browserModel.Refresh(); SyncCurrentFolderSelection(); } m_visible = true; const std::vector filteredEvents = FilterProjectPanelInputEvents( panelState->bounds, inputEvents, allowInteraction, panelActive, m_splitterDragging); m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); m_layout = BuildLayout(panelState->bounds); const Widgets::UIEditorTreeViewMetrics treeMetrics = BuildEditorTreeViewMetrics(); const std::vector treeEvents = FilterTreeInputEvents(filteredEvents, m_splitterDragging); m_treeFrame = UpdateUIEditorTreeViewInteraction( m_treeInteractionState, m_folderSelection, m_folderExpansion, m_layout.treeRect, m_browserModel.GetTreeItems(), treeEvents, treeMetrics); if (m_treeFrame.result.selectionChanged && !m_treeFrame.result.selectedItemId.empty() && m_treeFrame.result.selectedItemId != m_browserModel.GetCurrentFolderId()) { NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree); m_layout = BuildLayout(panelState->bounds); } for (const UIInputEvent& event : filteredEvents) { switch (event.type) { case UIInputEventType::FocusLost: m_hoveredAssetItemId.clear(); m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; m_pressedBreadcrumbIndex = kInvalidLayoutIndex; m_splitterHovered = false; if (m_splitterDragging) { m_splitterDragging = false; m_requestPointerRelease = true; } break; case UIInputEventType::PointerMove: { if (m_splitterDragging) { m_navigationWidth = ClampNavigationWidth(event.position.x - panelState->bounds.x, panelState->bounds.width); m_layout = BuildLayout(panelState->bounds); } m_splitterHovered = m_splitterDragging || ContainsPoint(m_layout.dividerRect, event.position); m_hoveredBreadcrumbIndex = HitTestBreadcrumbItem(event.position); const std::size_t hoveredAssetIndex = HitTestAssetTile(event.position); const auto& assetEntries = m_browserModel.GetAssetEntries(); m_hoveredAssetItemId = hoveredAssetIndex < assetEntries.size() ? assetEntries[hoveredAssetIndex].itemId : std::string(); break; } case UIInputEventType::PointerLeave: if (!m_splitterDragging) { m_splitterHovered = false; } m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; m_hoveredAssetItemId.clear(); break; case UIInputEventType::PointerButtonDown: if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Left) { if (ContainsPoint(m_layout.dividerRect, event.position)) { m_splitterDragging = true; m_splitterHovered = true; m_pressedBreadcrumbIndex = kInvalidLayoutIndex; m_requestPointerCapture = true; break; } m_pressedBreadcrumbIndex = HitTestBreadcrumbItem(event.position); if (!ContainsPoint(m_layout.gridRect, event.position)) { break; } const auto& assetEntries = m_browserModel.GetAssetEntries(); const std::size_t hitIndex = HitTestAssetTile(event.position); if (hitIndex >= assetEntries.size()) { if (m_assetSelection.HasSelection()) { m_assetSelection.ClearSelection(); EmitSelectionClearedEvent(EventSource::Background); } break; } const AssetEntry& assetEntry = assetEntries[hitIndex]; const bool alreadySelected = m_assetSelection.IsSelected(assetEntry.itemId); const bool selectionChanged = m_assetSelection.SetSelection(assetEntry.itemId); if (selectionChanged) { EmitEvent(EventKind::AssetSelected, EventSource::GridPrimary, &assetEntry); } const std::uint64_t nowMs = GetTickCount64(); const std::uint64_t doubleClickThresholdMs = static_cast(GetDoubleClickTime()); const bool doubleClicked = alreadySelected && m_lastPrimaryClickedAssetId == assetEntry.itemId && nowMs >= m_lastPrimaryClickTimeMs && nowMs - m_lastPrimaryClickTimeMs <= doubleClickThresholdMs; m_lastPrimaryClickedAssetId = assetEntry.itemId; m_lastPrimaryClickTimeMs = nowMs; if (!doubleClicked) { break; } if (assetEntry.directory) { NavigateToFolder(assetEntry.itemId, EventSource::GridDoubleClick); m_layout = BuildLayout(panelState->bounds); m_hoveredAssetItemId.clear(); } else { EmitEvent(EventKind::AssetOpened, EventSource::GridDoubleClick, &assetEntry); } break; } if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Right && ContainsPoint(m_layout.gridRect, event.position)) { const auto& assetEntries = m_browserModel.GetAssetEntries(); const std::size_t hitIndex = HitTestAssetTile(event.position); if (hitIndex >= assetEntries.size()) { EmitEvent( EventKind::ContextMenuRequested, EventSource::Background, static_cast(nullptr)); break; } const AssetEntry& assetEntry = assetEntries[hitIndex]; if (!m_assetSelection.IsSelected(assetEntry.itemId)) { m_assetSelection.SetSelection(assetEntry.itemId); EmitEvent(EventKind::AssetSelected, EventSource::GridSecondary, &assetEntry); } EmitEvent(EventKind::ContextMenuRequested, EventSource::GridSecondary, &assetEntry); } break; case UIInputEventType::PointerButtonUp: if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { break; } if (m_splitterDragging) { m_splitterDragging = false; m_splitterHovered = ContainsPoint(m_layout.dividerRect, event.position); m_requestPointerRelease = true; break; } { const std::size_t releasedBreadcrumbIndex = HitTestBreadcrumbItem(event.position); if (m_pressedBreadcrumbIndex != kInvalidLayoutIndex && m_pressedBreadcrumbIndex == releasedBreadcrumbIndex && releasedBreadcrumbIndex < m_layout.breadcrumbItems.size()) { const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[releasedBreadcrumbIndex]; if (item.clickable) { NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb); m_layout = BuildLayout(panelState->bounds); } } m_pressedBreadcrumbIndex = kInvalidLayoutIndex; } break; default: break; } } } } // namespace XCEngine::UI::Editor::App