Update new_editor inspector layout and host rendering

This commit is contained in:
2026-04-21 13:24:03 +08:00
parent d388c3994b
commit 5d81a64ef3
62 changed files with 4417 additions and 151 deletions

View File

@@ -41,8 +41,8 @@ inline constexpr float kBreadcrumbItemPaddingX = 4.0f;
inline constexpr float kBreadcrumbItemPaddingY = 1.0f;
inline constexpr float kBreadcrumbSpacing = 3.0f;
inline constexpr float kTreeTopPadding = 0.0f;
inline constexpr float kGridInsetX = 16.0f;
inline constexpr float kGridInsetY = 12.0f;
inline constexpr float kGridInsetX = 0.0f;
inline constexpr float kGridInsetY = 0.0f;
inline constexpr float kGridTileWidth = 92.0f;
inline constexpr float kGridTileHeight = 92.0f;
inline constexpr float kGridTileGapX = 12.0f;
@@ -250,6 +250,87 @@ bool HasValidBounds(const UIRect& bounds) {
constexpr auto kGridDoubleClickInterval = std::chrono::milliseconds(400);
Widgets::UIEditorScrollViewMetrics BuildProjectGridScrollMetrics() {
Widgets::UIEditorScrollViewMetrics metrics = ResolveUIEditorScrollViewMetrics();
metrics.scrollbarWidth = 8.0f;
metrics.scrollbarInset = 3.0f;
metrics.minThumbHeight = 28.0f;
metrics.cornerRounding = 0.0f;
metrics.borderThickness = 0.0f;
metrics.focusedBorderThickness = 0.0f;
return metrics;
}
Widgets::UIEditorScrollViewPalette BuildProjectGridScrollPalette() {
Widgets::UIEditorScrollViewPalette palette = ResolveUIEditorScrollViewPalette();
palette.surfaceColor = kPaneColor;
return palette;
}
int ResolveProjectGridColumnCount(float gridWidth) {
const float effectiveTileWidth = kGridTileWidth + kGridTileGapX;
int columnCount = effectiveTileWidth > 0.0f
? static_cast<int>((ClampNonNegative(gridWidth) + kGridTileGapX) / effectiveTileWidth)
: 1;
if (columnCount < 1) {
columnCount = 1;
}
return columnCount;
}
float MeasureProjectGridContentHeight(
std::size_t itemCount,
int columnCount) {
if (itemCount == 0u || columnCount < 1) {
return 0.0f;
}
const std::size_t resolvedColumnCount = static_cast<std::size_t>(columnCount);
const std::size_t rowCount =
(itemCount + resolvedColumnCount - 1u) / resolvedColumnCount;
return static_cast<float>(rowCount) * kGridTileHeight +
static_cast<float>((std::max)(rowCount, std::size_t(1u)) - 1u) *
kGridTileGapY;
}
Widgets::UIEditorScrollViewLayout BuildProjectGridScrollLayout(
const UIRect& bounds,
std::size_t itemCount,
float verticalOffset,
const Widgets::UIEditorScrollViewMetrics& metrics,
int* resolvedColumnCount = nullptr) {
int columnCount = ResolveProjectGridColumnCount(bounds.width);
float contentHeight = MeasureProjectGridContentHeight(itemCount, columnCount);
Widgets::UIEditorScrollViewLayout scrollLayout = {};
for (int iteration = 0; iteration < 3; ++iteration) {
scrollLayout =
Widgets::BuildUIEditorScrollViewLayout(bounds, contentHeight, verticalOffset, metrics);
verticalOffset = scrollLayout.verticalOffset;
const int nextColumnCount =
ResolveProjectGridColumnCount(scrollLayout.contentRect.width);
const float nextContentHeight =
MeasureProjectGridContentHeight(itemCount, nextColumnCount);
if (nextColumnCount == columnCount &&
std::abs(nextContentHeight - contentHeight) <= 0.01f) {
columnCount = nextColumnCount;
contentHeight = nextContentHeight;
break;
}
columnCount = nextColumnCount;
contentHeight = nextContentHeight;
}
scrollLayout =
Widgets::BuildUIEditorScrollViewLayout(bounds, contentHeight, verticalOffset, metrics);
if (resolvedColumnCount != nullptr) {
*resolvedColumnCount = columnCount;
}
return scrollLayout;
}
Widgets::UIEditorMenuPopupItem BuildContextMenuCommandItem(
std::string itemId,
std::string label,
@@ -350,12 +431,14 @@ void ProjectPanel::SetTextMeasurer(const UIEditorTextMeasurer* textMeasurer) {
void ProjectPanel::ResetInteractionState() {
m_assetDragState = {};
m_treeDragState = {};
m_gridScrollInteractionState = {};
m_treeInteractionState = {};
m_treeFrame = {};
m_contextMenu = {};
ClearRenameState();
m_frameEvents.clear();
m_layout = {};
m_gridVerticalOffset = 0.0f;
m_hoveredAssetItemId.clear();
m_lastPrimaryClickedAssetId.clear();
m_lastPrimaryClickTime = {};
@@ -387,6 +470,7 @@ bool ProjectPanel::WantsHostPointerRelease() const {
bool ProjectPanel::HasActivePointerCapture() const {
return m_splitterDragging ||
m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb ||
GridDrag::HasActivePointerCapture(m_assetDragState) ||
TreeDrag::HasActivePointerCapture(m_treeDragState);
}
@@ -540,6 +624,11 @@ bool ProjectPanel::TryStartQueuedRenameSession() {
initialText = folder->label;
}
if (m_pendingRenameSurface == RenameSurface::Grid &&
HasValidBounds(m_layout.bounds)) {
EnsureAssetVisible(m_pendingRenameItemId, m_layout.bounds);
}
const UIRect bounds =
BuildRenameBounds(m_pendingRenameItemId, m_pendingRenameSurface);
if (!HasValidBounds(bounds)) {
@@ -709,6 +798,10 @@ bool ProjectPanel::NavigateToFolder(std::string_view itemId, EventSource source)
SyncCurrentFolderSelection();
SyncAssetSelectionFromRuntime();
ResetGridScrollPosition();
if (HasValidBounds(m_layout.bounds)) {
RebuildLayout(m_layout.bounds);
}
m_hoveredAssetItemId.clear();
m_lastPrimaryClickedAssetId.clear();
EmitEvent(
@@ -729,7 +822,8 @@ bool ProjectPanel::OpenProjectItem(std::string_view itemId, EventSource source)
if (navigated && HasValidBounds(m_layout.bounds)) {
SyncCurrentFolderSelection();
SyncAssetSelectionFromRuntime();
m_layout = BuildLayout(m_layout.bounds);
ResetGridScrollPosition();
RebuildLayout(m_layout.bounds);
m_hoveredAssetItemId.clear();
EmitEvent(
EventKind::FolderNavigated,
@@ -1199,6 +1293,9 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand(
m_lastPrimaryClickTime = {};
ResolveProjectRuntime()->SetSelection(createdItemId);
SyncAssetSelectionFromRuntime();
if (m_visible && HasValidBounds(m_layout.bounds)) {
EnsureAssetVisible(createdItemId, m_layout.bounds);
}
const AssetEntry* createdAsset = FindAssetEntry(createdItemId);
if (createdAsset == nullptr) {
@@ -1230,7 +1327,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand(
if (target.containerFolder->itemId != GetBrowserModel().GetCurrentFolderId()) {
NavigateToFolder(target.containerFolder->itemId, EventSource::GridSecondary);
if (HasValidBounds(m_layout.bounds)) {
m_layout = BuildLayout(m_layout.bounds);
RebuildLayout(m_layout.bounds);
}
}
@@ -1262,7 +1359,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand(
if (target.containerFolder->itemId != GetBrowserModel().GetCurrentFolderId()) {
NavigateToFolder(target.containerFolder->itemId, EventSource::GridSecondary);
if (HasValidBounds(m_layout.bounds)) {
m_layout = BuildLayout(m_layout.bounds);
RebuildLayout(m_layout.bounds);
}
}
@@ -1489,6 +1586,7 @@ void ProjectPanel::Update(
FindMountedProjectPanel(contentHostFrame);
if (panelState == nullptr) {
if (m_splitterDragging ||
m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb ||
m_assetDragState.dragging ||
m_treeDragState.dragging ||
m_renameState.active) {
@@ -1497,6 +1595,7 @@ void ProjectPanel::Update(
m_visible = false;
m_assetDragState = {};
m_treeDragState = {};
m_gridScrollInteractionState = {};
CloseContextMenu();
ClearRenameState();
ResetTransientFrames();
@@ -1504,7 +1603,11 @@ void ProjectPanel::Update(
}
if (!HasProjectRuntime()) {
if (m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb) {
m_requestPointerRelease = true;
}
m_visible = false;
m_gridScrollInteractionState = {};
CloseContextMenu();
ClearRenameState();
ResetTransientFrames();
@@ -1540,7 +1643,7 @@ void ProjectPanel::Update(
ClaimCommandFocus(filteredEvents, panelState->bounds, inputContext.allowInteraction);
m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width);
m_layout = BuildLayout(panelState->bounds);
RebuildLayout(panelState->bounds);
if (m_contextMenu.open) {
RebuildContextMenu();
}
@@ -1584,7 +1687,7 @@ void ProjectPanel::Update(
m_treeFrame.result.selectedItemId != GetBrowserModel().GetCurrentFolderId()) {
CloseContextMenu();
NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree);
m_layout = BuildLayout(panelState->bounds);
RebuildLayout(panelState->bounds);
}
if (m_treeFrame.result.renameRequested &&
!m_treeFrame.result.renameItemId.empty()) {
@@ -1670,7 +1773,7 @@ void ProjectPanel::Update(
EmitSelectionClearedEvent(EventSource::Tree);
}
SyncCurrentFolderSelection();
m_layout = BuildLayout(panelState->bounds);
RebuildLayout(panelState->bounds);
m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout(
m_layout.treeRect,
GetBrowserModel().GetTreeItems(),
@@ -1679,6 +1782,27 @@ void ProjectPanel::Update(
m_treeInteractionState.verticalOffset);
}
const Widgets::UIEditorScrollViewMetrics gridScrollMetrics =
BuildProjectGridScrollMetrics();
const bool hadGridScrollCapture =
m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb;
UpdateUIEditorScrollViewInteraction(
m_gridScrollInteractionState,
m_gridVerticalOffset,
m_layout.gridRect,
m_layout.gridScrollLayout.contentHeight,
filteredEvents,
gridScrollMetrics);
if (!hadGridScrollCapture &&
m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb) {
m_requestPointerCapture = true;
} else if (
hadGridScrollCapture &&
!m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb) {
m_requestPointerRelease = true;
}
RebuildLayout(panelState->bounds);
struct ProjectAssetDragCallbacks {
::XCEngine::UI::Widgets::UISelectionModel& assetSelection;
::XCEngine::UI::Widgets::UIExpansionModel& folderExpansion;
@@ -1698,6 +1822,10 @@ void ProjectPanel::Update(
}
std::string ResolveDraggableItem(const UIPoint& point) const {
if (!ContainsPoint(layout.gridScrollLayout.contentRect, point)) {
return {};
}
for (const AssetTileLayout& tile : layout.assetTiles) {
if (tile.itemIndex >= assetEntries.size()) {
continue;
@@ -1793,7 +1921,7 @@ void ProjectPanel::Update(
}
}
m_layout = BuildLayout(panelState->bounds);
RebuildLayout(panelState->bounds);
m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout(
m_layout.treeRect,
GetBrowserModel().GetTreeItems(),
@@ -1845,7 +1973,7 @@ void ProjectPanel::Update(
if (m_splitterDragging) {
m_navigationWidth =
ClampNavigationWidth(event.position.x - panelState->bounds.x, panelState->bounds.width);
m_layout = BuildLayout(panelState->bounds);
RebuildLayout(panelState->bounds);
}
m_splitterHovered =
@@ -1880,7 +2008,7 @@ void ProjectPanel::Update(
m_pressedBreadcrumbIndex = HitTestBreadcrumbItem(event.position);
if (!ContainsPoint(m_layout.gridRect, event.position)) {
if (!ContainsPoint(m_layout.gridScrollLayout.contentRect, event.position)) {
break;
}
@@ -1921,7 +2049,7 @@ void ProjectPanel::Update(
}
if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Right &&
ContainsPoint(m_layout.gridRect, event.position)) {
ContainsPoint(m_layout.gridScrollLayout.contentRect, event.position)) {
const auto& assetEntries = GetBrowserModel().GetAssetEntries();
const std::size_t hitIndex = HitTestAssetTile(event.position);
if (hitIndex >= assetEntries.size()) {
@@ -1966,7 +2094,7 @@ void ProjectPanel::Update(
m_layout.breadcrumbItems[releasedBreadcrumbIndex];
if (item.clickable) {
NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb);
m_layout = BuildLayout(panelState->bounds);
RebuildLayout(panelState->bounds);
}
}
m_pressedBreadcrumbIndex = kInvalidLayoutIndex;
@@ -2029,6 +2157,15 @@ ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const {
layout.browserBodyRect.y + kGridInsetY,
ClampNonNegative(layout.browserBodyRect.width - kGridInsetX * 2.0f),
ClampNonNegative(layout.browserBodyRect.height - kGridInsetY * 2.0f));
const Widgets::UIEditorScrollViewMetrics gridScrollMetrics =
BuildProjectGridScrollMetrics();
int columnCount = 1;
layout.gridScrollLayout = BuildProjectGridScrollLayout(
layout.gridRect,
assetEntries.size(),
m_gridVerticalOffset,
gridScrollMetrics,
&columnCount);
const float breadcrumbRowHeight = kHeaderFontSize + kBreadcrumbItemPaddingY * 2.0f;
const float breadcrumbY =
@@ -2079,20 +2216,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 UIPoint gridContentOrigin =
Widgets::ResolveUIEditorScrollViewContentOrigin(layout.gridScrollLayout);
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 tileX =
gridContentOrigin.x + static_cast<float>(column) * (kGridTileWidth + kGridTileGapX);
const float tileY =
gridContentOrigin.y + static_cast<float>(row) * (kGridTileHeight + kGridTileGapY);
AssetTileLayout tile = {};
tile.itemIndex = index;
@@ -2113,6 +2246,59 @@ ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const {
return layout;
}
void ProjectPanel::RebuildLayout(const UIRect& bounds) {
m_layout = BuildLayout(bounds);
m_gridVerticalOffset = m_layout.gridScrollLayout.verticalOffset;
}
void ProjectPanel::ResetGridScrollPosition() {
m_gridVerticalOffset = 0.0f;
m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb = false;
}
void ProjectPanel::EnsureAssetVisible(
std::string_view itemId,
const UIRect& bounds) {
if (itemId.empty() || !HasValidBounds(bounds)) {
return;
}
RebuildLayout(bounds);
const Widgets::UIEditorScrollViewMetrics gridScrollMetrics =
BuildProjectGridScrollMetrics();
const auto& assetEntries = GetBrowserModel().GetAssetEntries();
for (const AssetTileLayout& tile : m_layout.assetTiles) {
if (tile.itemIndex >= assetEntries.size()) {
continue;
}
if (assetEntries[tile.itemIndex].itemId != itemId) {
continue;
}
const UIRect& contentRect = m_layout.gridScrollLayout.contentRect;
float adjustedOffset = m_gridVerticalOffset;
const float tileBottom = tile.tileRect.y + tile.tileRect.height;
const float contentBottom = contentRect.y + contentRect.height;
if (tile.tileRect.y < contentRect.y) {
adjustedOffset -= contentRect.y - tile.tileRect.y;
} else if (tileBottom > contentBottom) {
adjustedOffset += tileBottom - contentBottom;
}
adjustedOffset = Widgets::ClampUIEditorScrollViewOffset(
m_layout.gridScrollLayout.bounds,
m_layout.gridScrollLayout.contentHeight,
adjustedOffset,
gridScrollMetrics);
if (std::abs(adjustedOffset - m_gridVerticalOffset) > 0.01f) {
m_gridVerticalOffset = adjustedOffset;
RebuildLayout(bounds);
}
return;
}
}
std::size_t ProjectPanel::HitTestBreadcrumbItem(const UIPoint& point) const {
for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) {
const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index];
@@ -2125,6 +2311,10 @@ std::size_t ProjectPanel::HitTestBreadcrumbItem(const UIPoint& point) const {
}
std::size_t ProjectPanel::HitTestAssetTile(const UIPoint& point) const {
if (!ContainsPoint(m_layout.gridScrollLayout.contentRect, point)) {
return kInvalidLayoutIndex;
}
for (const AssetTileLayout& tile : m_layout.assetTiles) {
if (ContainsPoint(tile.tileRect, point)) {
return tile.itemIndex;
@@ -2195,6 +2385,16 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
m_layout.browserHeaderRect.width,
kHeaderBottomBorderThickness),
ResolveUIEditorDockHostPalette().splitterColor);
const Widgets::UIEditorScrollViewPalette gridScrollPalette =
BuildProjectGridScrollPalette();
const Widgets::UIEditorScrollViewMetrics gridScrollMetrics =
BuildProjectGridScrollMetrics();
AppendUIEditorScrollViewBackground(
drawList,
m_layout.gridScrollLayout,
m_gridScrollInteractionState.scrollViewState,
gridScrollPalette,
gridScrollMetrics);
const Widgets::UIEditorTreeViewPalette treePalette = ResolveUIEditorTreeViewPalette();
const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics();
@@ -2274,6 +2474,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
}
drawList.PopClipRect();
drawList.PushClipRect(m_layout.gridScrollLayout.contentRect);
for (const AssetTileLayout& tile : m_layout.assetTiles) {
if (tile.itemIndex >= assetEntries.size()) {
continue;
@@ -2339,6 +2540,7 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
break;
}
}
drawList.PopClipRect();
if (m_renameState.active) {
const Widgets::UIEditorTextFieldPalette textFieldPalette =
@@ -2351,19 +2553,30 @@ void ProjectPanel::Append(UIDrawList& drawList) const {
BuildUIEditorPropertyGridTextFieldMetrics(
ResolveUIEditorPropertyGridMetrics(),
ResolveUIEditorTextFieldMetrics()));
AppendUIEditorInlineRenameSession(
drawList,
m_renameFrame,
m_renameState,
textFieldPalette,
textFieldMetrics);
if (m_activeRenameSurface == RenameSurface::Grid) {
drawList.PushClipRect(m_layout.gridScrollLayout.contentRect);
AppendUIEditorInlineRenameSession(
drawList,
m_renameFrame,
m_renameState,
textFieldPalette,
textFieldMetrics);
drawList.PopClipRect();
} else {
AppendUIEditorInlineRenameSession(
drawList,
m_renameFrame,
m_renameState,
textFieldPalette,
textFieldMetrics);
}
}
if (assetEntries.empty()) {
const UIRect messageRect(
m_layout.gridRect.x,
m_layout.gridRect.y,
m_layout.gridRect.width,
m_layout.gridScrollLayout.contentRect.x,
m_layout.gridScrollLayout.contentRect.y,
m_layout.gridScrollLayout.contentRect.width,
18.0f);
drawList.AddText(
UIPoint(messageRect.x, ResolveTextTop(messageRect.y, messageRect.height, kHeaderFontSize)),