fix(new_editor): stabilize inspector add-component layout and project browser scrolling
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "ProjectPanel.h"
|
||||
#include "Rendering/Assets/BuiltInIcons.h"
|
||||
#include <XCEditor/Collections/UIEditorScrollView.h>
|
||||
#include <XCEditor/Collections/UIEditorTreeView.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <algorithm>
|
||||
@@ -84,6 +85,8 @@ void AppendTilePreview(
|
||||
const UIRect& previewRect,
|
||||
bool directory,
|
||||
const UITextureHandle* texture);
|
||||
Widgets::UIEditorScrollViewPalette BuildProjectBrowserScrollPalette();
|
||||
int ResolveAssetGridColumnCount(float gridWidth);
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
@@ -214,6 +217,23 @@ void AppendTilePreview(
|
||||
drawList.AddRectOutline(sheetRect, kTilePreviewOutlineColor, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
Widgets::UIEditorScrollViewPalette BuildProjectBrowserScrollPalette() {
|
||||
Widgets::UIEditorScrollViewPalette palette =
|
||||
ResolveUIEditorScrollViewPalette();
|
||||
palette.surfaceColor.a = 0.0f;
|
||||
palette.borderColor.a = 0.0f;
|
||||
palette.focusedBorderColor.a = 0.0f;
|
||||
return palette;
|
||||
}
|
||||
|
||||
int ResolveAssetGridColumnCount(float gridWidth) {
|
||||
const float effectiveTileWidth = kGridTileWidth + kGridTileGapX;
|
||||
int columnCount = effectiveTileWidth > 0.0f
|
||||
? static_cast<int>((gridWidth + kGridTileGapX) / effectiveTileWidth)
|
||||
: 1;
|
||||
return (std::max)(columnCount, 1);
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
@@ -358,6 +378,9 @@ void ProjectPanel::SetTextMeasurer(const UIEditorTextMeasurer* textMeasurer) {
|
||||
void ProjectPanel::ResetInteractionState() {
|
||||
m_assetDragState = {};
|
||||
m_treeDragState = {};
|
||||
m_browserScrollInteractionState = {};
|
||||
m_browserScrollFrame = {};
|
||||
m_browserVerticalOffset = 0.0f;
|
||||
m_treeInteractionState = {};
|
||||
m_treeFrame = {};
|
||||
m_contextMenu = {};
|
||||
@@ -383,6 +406,7 @@ ProjectPanel::CursorKind ProjectPanel::GetCursorKind() const {
|
||||
|
||||
bool ProjectPanel::HasActivePointerCapture() const {
|
||||
return m_splitterDragging ||
|
||||
HasActiveUIEditorScrollViewPointerCapture(m_browserScrollInteractionState) ||
|
||||
GridDrag::HasActivePointerCapture(m_assetDragState) ||
|
||||
TreeDrag::HasActivePointerCapture(m_treeDragState) ||
|
||||
HasActiveUIEditorTreeViewPointerCapture(m_treeInteractionState);
|
||||
@@ -668,7 +692,7 @@ void ProjectPanel::SyncAssetSelectionFromRuntime() {
|
||||
|
||||
Widgets::UIEditorTreeViewMetrics ProjectPanel::RebuildPanelLayout(const UIRect& bounds) {
|
||||
const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics();
|
||||
m_layout = BuildLayout(bounds);
|
||||
m_layout = BuildLayout(bounds, {}, 0.0f);
|
||||
m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout(
|
||||
m_layout.treeRect,
|
||||
GetWindowTreeItems(),
|
||||
@@ -678,6 +702,74 @@ Widgets::UIEditorTreeViewMetrics ProjectPanel::RebuildPanelLayout(const UIRect&
|
||||
return treeMetrics;
|
||||
}
|
||||
|
||||
float ProjectPanel::MeasureBrowserContentHeight(const UIRect& browserContentRect) const {
|
||||
if (!HasValidBounds(browserContentRect)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float gridWidth =
|
||||
ClampNonNegative(browserContentRect.width - kGridInsetX * 2.0f);
|
||||
if (gridWidth <= 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const std::size_t assetCount = GetBrowserModel().GetAssetEntries().size();
|
||||
const int columnCount = ResolveAssetGridColumnCount(gridWidth);
|
||||
const std::size_t rowCount =
|
||||
assetCount == 0u
|
||||
? 0u
|
||||
: (assetCount + static_cast<std::size_t>(columnCount) - 1u) /
|
||||
static_cast<std::size_t>(columnCount);
|
||||
|
||||
float contentHeight = kGridInsetY * 2.0f;
|
||||
if (rowCount > 0u) {
|
||||
contentHeight +=
|
||||
static_cast<float>(rowCount) * kGridTileHeight +
|
||||
static_cast<float>(rowCount - 1u) * kGridTileGapY;
|
||||
} else {
|
||||
contentHeight += 18.0f;
|
||||
}
|
||||
|
||||
return contentHeight;
|
||||
}
|
||||
|
||||
void ProjectPanel::RebuildBrowserScrollLayout() {
|
||||
m_browserScrollFrame = {};
|
||||
|
||||
if (!HasValidBounds(m_layout.browserBodyRect)) {
|
||||
m_browserVerticalOffset = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
const Widgets::UIEditorScrollViewMetrics& scrollMetrics =
|
||||
ResolveUIEditorScrollViewMetrics();
|
||||
|
||||
float contentHeight = MeasureBrowserContentHeight(m_layout.browserBodyRect);
|
||||
m_browserVerticalOffset = Widgets::ClampUIEditorScrollViewOffset(
|
||||
m_layout.browserBodyRect,
|
||||
contentHeight,
|
||||
m_browserVerticalOffset,
|
||||
scrollMetrics);
|
||||
m_browserScrollFrame.layout = Widgets::BuildUIEditorScrollViewLayout(
|
||||
m_layout.browserBodyRect,
|
||||
contentHeight,
|
||||
m_browserVerticalOffset,
|
||||
scrollMetrics);
|
||||
|
||||
contentHeight = MeasureBrowserContentHeight(m_browserScrollFrame.layout.contentRect);
|
||||
m_browserVerticalOffset = Widgets::ClampUIEditorScrollViewOffset(
|
||||
m_layout.browserBodyRect,
|
||||
contentHeight,
|
||||
m_browserVerticalOffset,
|
||||
scrollMetrics);
|
||||
m_browserScrollFrame.layout = Widgets::BuildUIEditorScrollViewLayout(
|
||||
m_layout.browserBodyRect,
|
||||
contentHeight,
|
||||
m_browserVerticalOffset,
|
||||
scrollMetrics);
|
||||
m_browserScrollFrame.result.verticalOffset = m_browserVerticalOffset;
|
||||
}
|
||||
|
||||
bool ProjectPanel::NavigateToFolder(std::string_view itemId, EventSource source) {
|
||||
if (!ResolveProjectRuntime()->NavigateToFolder(itemId)) {
|
||||
return false;
|
||||
@@ -704,6 +796,11 @@ bool ProjectPanel::OpenProjectItem(std::string_view itemId, EventSource source)
|
||||
if (navigated && HasValidBounds(m_layout.bounds)) {
|
||||
SyncSelectionsFromRuntime();
|
||||
RebuildPanelLayout(m_layout.bounds);
|
||||
RebuildBrowserScrollLayout();
|
||||
m_layout = BuildLayout(
|
||||
m_layout.bounds,
|
||||
m_browserScrollFrame.layout.contentRect,
|
||||
m_browserVerticalOffset);
|
||||
m_hoveredAssetItemId.clear();
|
||||
EmitEvent(
|
||||
EventKind::FolderNavigated,
|
||||
@@ -1198,6 +1295,11 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand(
|
||||
NavigateToFolder(target.containerFolder->itemId, EventSource::GridSecondary);
|
||||
if (HasValidBounds(m_layout.bounds)) {
|
||||
RebuildPanelLayout(m_layout.bounds);
|
||||
RebuildBrowserScrollLayout();
|
||||
m_layout = BuildLayout(
|
||||
m_layout.bounds,
|
||||
m_browserScrollFrame.layout.contentRect,
|
||||
m_browserVerticalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1230,6 +1332,11 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand(
|
||||
NavigateToFolder(target.containerFolder->itemId, EventSource::GridSecondary);
|
||||
if (HasValidBounds(m_layout.bounds)) {
|
||||
RebuildPanelLayout(m_layout.bounds);
|
||||
RebuildBrowserScrollLayout();
|
||||
m_layout = BuildLayout(
|
||||
m_layout.bounds,
|
||||
m_browserScrollFrame.layout.contentRect,
|
||||
m_browserVerticalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1485,6 +1592,33 @@ void ProjectPanel::Update(
|
||||
m_navigationWidth = ClampNavigationWidth(m_navigationWidth, dispatchEntry.bounds.width);
|
||||
const Widgets::UIEditorTreeViewMetrics treeMetrics =
|
||||
RebuildPanelLayout(dispatchEntry.bounds);
|
||||
const auto refreshBrowserLayout = [&]() {
|
||||
RebuildBrowserScrollLayout();
|
||||
m_layout = BuildLayout(
|
||||
dispatchEntry.bounds,
|
||||
m_browserScrollFrame.layout.contentRect,
|
||||
m_browserVerticalOffset);
|
||||
};
|
||||
const auto updateBrowserScrollInteraction = [&]() {
|
||||
RebuildBrowserScrollLayout();
|
||||
if (HasValidBounds(m_layout.browserBodyRect)) {
|
||||
m_browserScrollFrame = UpdateUIEditorScrollViewInteraction(
|
||||
m_browserScrollInteractionState,
|
||||
m_browserVerticalOffset,
|
||||
m_layout.browserBodyRect,
|
||||
m_browserScrollFrame.layout.contentHeight,
|
||||
filteredEvents,
|
||||
ResolveUIEditorScrollViewMetrics());
|
||||
} else {
|
||||
m_browserScrollFrame = {};
|
||||
m_browserVerticalOffset = 0.0f;
|
||||
}
|
||||
|
||||
m_layout = BuildLayout(
|
||||
dispatchEntry.bounds,
|
||||
m_browserScrollFrame.layout.contentRect,
|
||||
m_browserVerticalOffset);
|
||||
};
|
||||
if (m_contextMenu.open) {
|
||||
RebuildContextMenu();
|
||||
}
|
||||
@@ -1497,6 +1631,7 @@ void ProjectPanel::Update(
|
||||
}
|
||||
|
||||
if (m_renameState.active || !m_pendingRenameItemId.empty()) {
|
||||
updateBrowserScrollInteraction();
|
||||
TryStartQueuedRenameSession();
|
||||
UpdateRenameSession(filteredEvents);
|
||||
return;
|
||||
@@ -1522,6 +1657,7 @@ void ProjectPanel::Update(
|
||||
CloseContextMenu();
|
||||
NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree);
|
||||
RebuildPanelLayout(dispatchEntry.bounds);
|
||||
refreshBrowserLayout();
|
||||
}
|
||||
if (m_treeFrame.result.renameRequested &&
|
||||
!m_treeFrame.result.renameItemId.empty()) {
|
||||
@@ -1607,8 +1743,11 @@ void ProjectPanel::Update(
|
||||
EmitSelectionClearedEvent(EventSource::Tree);
|
||||
}
|
||||
RebuildPanelLayout(dispatchEntry.bounds);
|
||||
refreshBrowserLayout();
|
||||
}
|
||||
|
||||
updateBrowserScrollInteraction();
|
||||
|
||||
struct ProjectAssetDragCallbacks {
|
||||
::XCEngine::UI::Widgets::UISelectionModel& assetSelection;
|
||||
::XCEngine::UI::Widgets::UIExpansionModel& folderExpansion;
|
||||
@@ -1723,6 +1862,7 @@ void ProjectPanel::Update(
|
||||
}
|
||||
|
||||
RebuildPanelLayout(dispatchEntry.bounds);
|
||||
refreshBrowserLayout();
|
||||
}
|
||||
|
||||
const bool suppressPanelPointerEvents =
|
||||
@@ -1770,7 +1910,8 @@ void ProjectPanel::Update(
|
||||
ClampNavigationWidth(
|
||||
event.position.x - dispatchEntry.bounds.x,
|
||||
dispatchEntry.bounds.width);
|
||||
m_layout = BuildLayout(dispatchEntry.bounds);
|
||||
RebuildPanelLayout(dispatchEntry.bounds);
|
||||
refreshBrowserLayout();
|
||||
}
|
||||
|
||||
m_splitterHovered =
|
||||
@@ -1892,6 +2033,7 @@ void ProjectPanel::Update(
|
||||
if (item.clickable) {
|
||||
NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb);
|
||||
RebuildPanelLayout(dispatchEntry.bounds);
|
||||
refreshBrowserLayout();
|
||||
}
|
||||
}
|
||||
m_pressedBreadcrumbIndex = kInvalidLayoutIndex;
|
||||
@@ -1904,7 +2046,10 @@ void ProjectPanel::Update(
|
||||
}
|
||||
}
|
||||
|
||||
ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const {
|
||||
ProjectPanel::Layout ProjectPanel::BuildLayout(
|
||||
const UIRect& bounds,
|
||||
const UIRect& browserContentRect,
|
||||
float browserVerticalOffset) const {
|
||||
Layout layout = {};
|
||||
const auto& assetEntries = GetBrowserModel().GetAssetEntries();
|
||||
const std::vector<ProjectBrowserModel::BreadcrumbSegment> breadcrumbSegments =
|
||||
@@ -1949,11 +2094,15 @@ ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const {
|
||||
layout.browserHeaderRect.y + layout.browserHeaderRect.height,
|
||||
layout.rightPaneRect.width,
|
||||
ClampNonNegative(layout.rightPaneRect.height - layout.browserHeaderRect.height));
|
||||
const UIRect effectiveBrowserContentRect =
|
||||
HasValidBounds(browserContentRect)
|
||||
? browserContentRect
|
||||
: layout.browserBodyRect;
|
||||
layout.gridRect = UIRect(
|
||||
layout.browserBodyRect.x + kGridInsetX,
|
||||
layout.browserBodyRect.y + kGridInsetY,
|
||||
ClampNonNegative(layout.browserBodyRect.width - kGridInsetX * 2.0f),
|
||||
ClampNonNegative(layout.browserBodyRect.height - kGridInsetY * 2.0f));
|
||||
effectiveBrowserContentRect.x + kGridInsetX,
|
||||
effectiveBrowserContentRect.y + kGridInsetY,
|
||||
ClampNonNegative(effectiveBrowserContentRect.width - kGridInsetX * 2.0f),
|
||||
ClampNonNegative(effectiveBrowserContentRect.height - kGridInsetY * 2.0f));
|
||||
|
||||
const float breadcrumbRowHeight = kHeaderFontSize + kBreadcrumbItemPaddingY * 2.0f;
|
||||
const float breadcrumbY =
|
||||
@@ -2004,20 +2153,16 @@ ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const {
|
||||
nextItemX += itemWidth + kBreadcrumbSpacing;
|
||||
}
|
||||
|
||||
const float effectiveTileWidth = kGridTileWidth + kGridTileGapX;
|
||||
int columnCount = effectiveTileWidth > 0.0f
|
||||
? static_cast<int>((layout.gridRect.width + kGridTileGapX) / effectiveTileWidth)
|
||||
: 1;
|
||||
if (columnCount < 1) {
|
||||
columnCount = 1;
|
||||
}
|
||||
const int columnCount = ResolveAssetGridColumnCount(layout.gridRect.width);
|
||||
|
||||
layout.assetTiles.reserve(assetEntries.size());
|
||||
for (std::size_t index = 0; index < assetEntries.size(); ++index) {
|
||||
const int column = static_cast<int>(index % static_cast<std::size_t>(columnCount));
|
||||
const int row = static_cast<int>(index / static_cast<std::size_t>(columnCount));
|
||||
const float tileX = layout.gridRect.x + static_cast<float>(column) * (kGridTileWidth + kGridTileGapX);
|
||||
const float tileY = layout.gridRect.y + static_cast<float>(row) * (kGridTileHeight + kGridTileGapY);
|
||||
const float tileY =
|
||||
layout.gridRect.y - browserVerticalOffset +
|
||||
static_cast<float>(row) * (kGridTileHeight + kGridTileGapY);
|
||||
|
||||
AssetTileLayout tile = {};
|
||||
tile.itemIndex = index;
|
||||
@@ -2183,6 +2328,16 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
|
||||
}
|
||||
drawList.PopClipRect();
|
||||
|
||||
if (HasValidBounds(m_browserScrollFrame.layout.bounds)) {
|
||||
Widgets::AppendUIEditorScrollViewBackground(
|
||||
drawList,
|
||||
m_browserScrollFrame.layout,
|
||||
m_browserScrollInteractionState.scrollViewState,
|
||||
BuildProjectBrowserScrollPalette(),
|
||||
ResolveUIEditorScrollViewMetrics());
|
||||
}
|
||||
|
||||
drawList.PushClipRect(m_browserScrollFrame.layout.contentRect);
|
||||
for (const AssetTileLayout& tile : m_layout.assetTiles) {
|
||||
if (tile.itemIndex >= assetEntries.size()) {
|
||||
continue;
|
||||
@@ -2274,6 +2429,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
|
||||
kTextMuted,
|
||||
kHeaderFontSize);
|
||||
}
|
||||
drawList.PopClipRect();
|
||||
|
||||
AppendContextMenu(drawList);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user