feat(new_editor): add project panel and polish dock chrome

This commit is contained in:
2026-04-11 20:20:30 +08:00
parent 030230eb1f
commit 0a015b52ca
12 changed files with 1455 additions and 52 deletions

View File

@@ -31,6 +31,7 @@ enum class UIDrawCommandType : std::uint8_t {
RectOutline, RectOutline,
FilledRectLinearGradient, FilledRectLinearGradient,
Line, Line,
FilledTriangle,
FilledCircle, FilledCircle,
CircleOutline, CircleOutline,
Text, Text,
@@ -149,6 +150,20 @@ public:
return AddCommand(std::move(command)); return AddCommand(std::move(command));
} }
UIDrawCommand& AddFilledTriangle(
const UIPoint& a,
const UIPoint& b,
const UIPoint& c,
const UIColor& color) {
UIDrawCommand command = {};
command.type = UIDrawCommandType::FilledTriangle;
command.position = a;
command.uvMin = b;
command.uvMax = c;
command.color = color;
return AddCommand(std::move(command));
}
UIDrawCommand& AddFilledCircle( UIDrawCommand& AddFilledCircle(
const UIPoint& center, const UIPoint& center,
float radius, float radius,

View File

@@ -147,6 +147,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP)
add_executable(XCUIEditorApp WIN32 add_executable(XCUIEditorApp WIN32
app/main.cpp app/main.cpp
app/Application.cpp app/Application.cpp
app/Panels/ProductProjectPanel.cpp
app/Shell/ProductShellAsset.cpp app/Shell/ProductShellAsset.cpp
) )

View File

@@ -38,6 +38,20 @@ D2D1_RECT_F ToD2DRect(const ::XCEngine::UI::UIRect& rect, float dpiScale) {
return D2D1::RectF(left, top, right, bottom); return D2D1::RectF(left, top, right, bottom);
} }
D2D1_POINT_2F ToD2DPoint(
const ::XCEngine::UI::UIPoint& point,
float dpiScale,
float pixelOffset = 0.0f) {
return D2D1::Point2F(
SnapToPixel(point.x, dpiScale) + pixelOffset,
SnapToPixel(point.y, dpiScale) + pixelOffset);
}
float ResolveStrokePixelOffset(float thickness) {
const float roundedThickness = std::round(thickness);
return std::fmod(roundedThickness, 2.0f) == 1.0f ? 0.5f : 0.0f;
}
} // namespace } // namespace
bool NativeRenderer::Initialize(HWND hwnd) { bool NativeRenderer::Initialize(HWND hwnd) {
@@ -458,6 +472,51 @@ void NativeRenderer::RenderCommand(
} }
break; break;
} }
case ::XCEngine::UI::UIDrawCommandType::FilledRectLinearGradient: {
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f;
const D2D1_GRADIENT_STOP stops[2] = {
D2D1::GradientStop(0.0f, ToD2DColor(command.color)),
D2D1::GradientStop(1.0f, ToD2DColor(command.secondaryColor))
};
Microsoft::WRL::ComPtr<ID2D1GradientStopCollection> stopCollection;
HRESULT hr = renderTarget.CreateGradientStopCollection(
stops,
2u,
stopCollection.ReleaseAndGetAddressOf());
if (FAILED(hr) || !stopCollection) {
break;
}
const D2D1_POINT_2F startPoint =
command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical
? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.top)
: D2D1::Point2F(rect.left, (rect.top + rect.bottom) * 0.5f);
const D2D1_POINT_2F endPoint =
command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical
? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.bottom)
: D2D1::Point2F(rect.right, (rect.top + rect.bottom) * 0.5f);
Microsoft::WRL::ComPtr<ID2D1LinearGradientBrush> gradientBrush;
hr = renderTarget.CreateLinearGradientBrush(
D2D1::LinearGradientBrushProperties(startPoint, endPoint),
stopCollection.Get(),
gradientBrush.ReleaseAndGetAddressOf());
if (FAILED(hr) || !gradientBrush) {
break;
}
if (command.rounding > 0.0f) {
renderTarget.FillRoundedRectangle(
D2D1::RoundedRect(rect, rounding, rounding),
gradientBrush.Get());
} else {
renderTarget.FillRectangle(rect, gradientBrush.Get());
}
break;
}
case ::XCEngine::UI::UIDrawCommandType::RectOutline: { case ::XCEngine::UI::UIDrawCommandType::RectOutline: {
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale;
@@ -472,6 +531,58 @@ void NativeRenderer::RenderCommand(
} }
break; break;
} }
case ::XCEngine::UI::UIDrawCommandType::Line: {
const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale;
const float pixelOffset = ResolveStrokePixelOffset(thickness);
const D2D1_POINT_2F start = ToD2DPoint(command.position, dpiScale, pixelOffset);
const D2D1_POINT_2F end = ToD2DPoint(command.uvMin, dpiScale, pixelOffset);
renderTarget.DrawLine(start, end, &solidBrush, thickness);
break;
}
case ::XCEngine::UI::UIDrawCommandType::FilledTriangle: {
Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry;
HRESULT hr = m_d2dFactory->CreatePathGeometry(geometry.ReleaseAndGetAddressOf());
if (FAILED(hr) || !geometry) {
break;
}
Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink;
hr = geometry->Open(sink.ReleaseAndGetAddressOf());
if (FAILED(hr) || !sink) {
break;
}
const D2D1_POINT_2F a = ToD2DPoint(command.position, dpiScale);
const D2D1_POINT_2F b = ToD2DPoint(command.uvMin, dpiScale);
const D2D1_POINT_2F c = ToD2DPoint(command.uvMax, dpiScale);
const D2D1_POINT_2F points[2] = { b, c };
sink->BeginFigure(a, D2D1_FIGURE_BEGIN_FILLED);
sink->AddLines(points, 2u);
sink->EndFigure(D2D1_FIGURE_END_CLOSED);
hr = sink->Close();
if (FAILED(hr)) {
break;
}
renderTarget.FillGeometry(geometry.Get(), &solidBrush);
break;
}
case ::XCEngine::UI::UIDrawCommandType::FilledCircle: {
const float radius = command.radius * dpiScale;
renderTarget.FillEllipse(
D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius),
&solidBrush);
break;
}
case ::XCEngine::UI::UIDrawCommandType::CircleOutline: {
const float radius = command.radius * dpiScale;
const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale;
renderTarget.DrawEllipse(
D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius),
&solidBrush,
thickness);
break;
}
case ::XCEngine::UI::UIDrawCommandType::Text: { case ::XCEngine::UI::UIDrawCommandType::Text: {
if (command.text.empty()) { if (command.text.empty()) {
break; break;

View File

@@ -41,6 +41,45 @@ constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Edit
constexpr UINT kDefaultDpi = 96u; constexpr UINT kDefaultDpi = 96u;
constexpr float kBaseDpiScale = 96.0f; constexpr float kBaseDpiScale = 96.0f;
UIEditorShellComposeModel BuildShellComposeModelFromFrame(
const UIEditorShellInteractionFrame& frame) {
UIEditorShellComposeModel model = {};
model.menuBarItems = frame.request.menuBarItems;
model.toolbarButtons = frame.model.toolbarButtons;
model.statusSegments = frame.model.statusSegments;
model.workspacePresentations = frame.model.workspacePresentations;
return model;
}
void AppendShellPopups(
UIDrawList& drawList,
const UIEditorShellInteractionFrame& frame,
const UIEditorShellInteractionPalette& palette,
const UIEditorShellInteractionMetrics& metrics) {
const std::size_t popupCount =
(std::min)(frame.request.popupRequests.size(), frame.popupFrames.size());
for (std::size_t index = 0; index < popupCount; ++index) {
const UIEditorShellInteractionPopupRequest& popupRequest =
frame.request.popupRequests[index];
const UIEditorShellInteractionPopupFrame& popupFrame =
frame.popupFrames[index];
Widgets::AppendUIEditorMenuPopupBackground(
drawList,
popupRequest.layout,
popupRequest.widgetItems,
popupFrame.popupState,
palette.popupPalette,
metrics.popupMetrics);
Widgets::AppendUIEditorMenuPopupForeground(
drawList,
popupRequest.layout,
popupRequest.widgetItems,
popupFrame.popupState,
palette.popupPalette,
metrics.popupMetrics);
}
}
Application* GetApplicationFromWindow(HWND hwnd) { Application* GetApplicationFromWindow(HWND hwnd) {
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
} }
@@ -289,6 +328,27 @@ std::string DescribeInputEventType(const UIInputEvent& event) {
} }
} }
std::vector<UIInputEvent> FilterShellInputEventsForHostedContentCapture(
const std::vector<UIInputEvent>& inputEvents) {
std::vector<UIInputEvent> filteredEvents = {};
filteredEvents.reserve(inputEvents.size());
for (const UIInputEvent& event : inputEvents) {
switch (event.type) {
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerLeave:
case UIInputEventType::PointerButtonDown:
case UIInputEventType::PointerButtonUp:
case UIInputEventType::PointerWheel:
break;
default:
filteredEvents.push_back(event);
break;
}
}
return filteredEvents;
}
} // namespace } // namespace
int Application::Run(HINSTANCE hInstance, int nCmdShow) { int Application::Run(HINSTANCE hInstance, int nCmdShow) {
@@ -339,6 +399,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher(); m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
m_shellServices.shortcutManager = &m_shortcutManager; m_shellServices.shortcutManager = &m_shortcutManager;
m_shellServices.textMeasurer = &m_renderer; m_shellServices.textMeasurer = &m_renderer;
m_projectPanel.Initialize(ResolveRepoRootPath());
m_lastStatus = "Ready"; m_lastStatus = "Ready";
m_lastMessage = "Old editor shell baseline loaded."; m_lastMessage = "Old editor shell baseline loaded.";
LogRuntimeTrace("app", "workspace initialized: " + DescribeWorkspaceState()); LogRuntimeTrace("app", "workspace initialized: " + DescribeWorkspaceState());
@@ -385,6 +446,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
LogRuntimeTrace("app", "renderer initialization failed"); LogRuntimeTrace("app", "renderer initialization failed");
return false; return false;
} }
m_projectPanel.SetTextMeasurer(&m_renderer);
ShowWindow(m_hwnd, nCmdShow); ShowWindow(m_hwnd, nCmdShow);
UpdateWindow(m_hwnd); UpdateWindow(m_hwnd);
@@ -448,6 +510,11 @@ void Application::RenderFrame() {
const UIEditorShellInteractionDefinition definition = BuildShellDefinition(); const UIEditorShellInteractionDefinition definition = BuildShellDefinition();
std::vector<UIInputEvent> frameEvents = std::move(m_pendingInputEvents); std::vector<UIInputEvent> frameEvents = std::move(m_pendingInputEvents);
m_pendingInputEvents.clear(); m_pendingInputEvents.clear();
const std::vector<UIInputEvent> hostedContentEvents = frameEvents;
const std::vector<UIInputEvent> shellEvents =
m_projectPanel.HasActivePointerCapture()
? FilterShellInputEventsForHostedContentCapture(frameEvents)
: frameEvents;
if (!frameEvents.empty()) { if (!frameEvents.empty()) {
LogRuntimeTrace( LogRuntimeTrace(
"input", "input",
@@ -459,10 +526,10 @@ void Application::RenderFrame() {
m_workspaceController, m_workspaceController,
UIRect(0.0f, 0.0f, width, height), UIRect(0.0f, 0.0f, width, height),
definition, definition,
frameEvents, shellEvents,
m_shellServices, m_shellServices,
metrics); metrics);
if (!frameEvents.empty() || if (!shellEvents.empty() ||
m_shellFrame.result.workspaceResult.dockHostResult.layoutChanged || m_shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
m_shellFrame.result.workspaceResult.dockHostResult.commandExecuted) { m_shellFrame.result.workspaceResult.dockHostResult.commandExecuted) {
std::ostringstream frameTrace = {}; std::ostringstream frameTrace = {};
@@ -482,13 +549,24 @@ void Application::RenderFrame() {
} }
ApplyHostCaptureRequests(m_shellFrame.result); ApplyHostCaptureRequests(m_shellFrame.result);
UpdateLastStatus(m_shellFrame.result); UpdateLastStatus(m_shellFrame.result);
m_projectPanel.Update(
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
hostedContentEvents,
!m_shellFrame.result.workspaceInputSuppressed,
m_workspaceController.GetWorkspace().activePanelId == "project");
ApplyHostedContentCaptureRequests();
ApplyCurrentCursor(); ApplyCurrentCursor();
AppendUIEditorShellInteraction( const UIEditorShellComposeModel shellComposeModel =
BuildShellComposeModelFromFrame(m_shellFrame);
AppendUIEditorShellCompose(
drawList, drawList,
m_shellFrame, m_shellFrame.shellFrame,
m_shellInteractionState, shellComposeModel,
palette, m_shellInteractionState.composeState,
metrics); palette.shellPalette,
metrics.shellMetrics);
m_projectPanel.Append(drawList);
AppendShellPopups(drawList, m_shellFrame, palette, metrics);
} else { } else {
drawList.AddText( drawList.AddText(
UIPoint(28.0f, 28.0f), UIPoint(28.0f, 28.0f),
@@ -522,6 +600,14 @@ float Application::PixelsToDips(float pixels) const {
} }
LPCWSTR Application::ResolveCurrentCursorResource() const { LPCWSTR Application::ResolveCurrentCursorResource() const {
switch (m_projectPanel.GetCursorKind()) {
case App::ProductProjectPanel::CursorKind::ResizeEW:
return IDC_SIZEWE;
case App::ProductProjectPanel::CursorKind::Arrow:
default:
break;
}
switch (Widgets::ResolveUIEditorDockHostCursorKind( switch (Widgets::ResolveUIEditorDockHostCursorKind(
m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout)) { m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout)) {
case Widgets::UIEditorDockHostCursorKind::ResizeEW: case Widgets::UIEditorDockHostCursorKind::ResizeEW:
@@ -639,7 +725,19 @@ void Application::ApplyHostCaptureRequests(const UIEditorShellInteractionResult&
} }
} }
bool Application::HasInteractiveCaptureState() const { void Application::ApplyHostedContentCaptureRequests() {
if (m_projectPanel.WantsHostPointerCapture() && GetCapture() != m_hwnd) {
SetCapture(m_hwnd);
}
if (m_projectPanel.WantsHostPointerRelease() &&
GetCapture() == m_hwnd &&
!HasShellInteractiveCaptureState()) {
ReleaseCapture();
}
}
bool Application::HasShellInteractiveCaptureState() const {
if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) { if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) {
return true; return true;
} }
@@ -657,6 +755,10 @@ bool Application::HasInteractiveCaptureState() const {
return false; return false;
} }
bool Application::HasInteractiveCaptureState() const {
return HasShellInteractiveCaptureState() || m_projectPanel.HasActivePointerCapture();
}
UIEditorShellInteractionDefinition Application::BuildShellDefinition() const { UIEditorShellInteractionDefinition Application::BuildShellDefinition() const {
std::string statusText = m_lastStatus; std::string statusText = m_lastStatus;
if (!m_lastMessage.empty()) { if (!m_lastMessage.empty()) {

View File

@@ -8,6 +8,8 @@
#include <Host/InputModifierTracker.h> #include <Host/InputModifierTracker.h>
#include <Host/NativeRenderer.h> #include <Host/NativeRenderer.h>
#include "Panels/ProductProjectPanel.h"
#include <XCEditor/Foundation/UIEditorShortcutManager.h> #include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEditor/Shell/UIEditorShellAsset.h> #include <XCEditor/Shell/UIEditorShellAsset.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h> #include <XCEditor/Shell/UIEditorShellInteraction.h>
@@ -43,11 +45,15 @@ private:
void RenderFrame(); void RenderFrame();
void OnResize(UINT width, UINT height); void OnResize(UINT width, UINT height);
void OnDpiChanged(UINT dpi, const RECT& suggestedRect); void OnDpiChanged(UINT dpi, const RECT& suggestedRect);
bool ApplyCurrentCursor() const;
LPCWSTR ResolveCurrentCursorResource() const;
float GetDpiScale() const; float GetDpiScale() const;
float PixelsToDips(float pixels) const; float PixelsToDips(float pixels) const;
::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const; ::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const;
void LogRuntimeTrace(std::string_view channel, std::string_view message) const; void LogRuntimeTrace(std::string_view channel, std::string_view message) const;
void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result); void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result);
void ApplyHostedContentCaptureRequests();
bool HasShellInteractiveCaptureState() const;
bool HasInteractiveCaptureState() const; bool HasInteractiveCaptureState() const;
UIEditorShellInteractionDefinition BuildShellDefinition() const; UIEditorShellInteractionDefinition BuildShellDefinition() const;
void UpdateLastStatus(const UIEditorShellInteractionResult& result); void UpdateLastStatus(const UIEditorShellInteractionResult& result);
@@ -77,6 +83,7 @@ private:
EditorShellAssetValidationResult m_shellValidation = {}; EditorShellAssetValidationResult m_shellValidation = {};
UIEditorWorkspaceController m_workspaceController = {}; UIEditorWorkspaceController m_workspaceController = {};
UIEditorShortcutManager m_shortcutManager = {}; UIEditorShortcutManager m_shortcutManager = {};
App::ProductProjectPanel m_projectPanel = {};
UIEditorShellInteractionServices m_shellServices = {}; UIEditorShellInteractionServices m_shellServices = {};
UIEditorShellInteractionState m_shellInteractionState = {}; UIEditorShellInteractionState m_shellInteractionState = {};
UIEditorShellInteractionFrame m_shellFrame = {}; UIEditorShellInteractionFrame m_shellFrame = {};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
#pragma once
#include <XCEditor/Collections/UIEditorTreeViewInteraction.h>
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
#include <XCEditor/Shell/UIEditorPanelContentHost.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <cstdint>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor::App {
class ProductProjectPanel {
public:
enum class CursorKind : std::uint8_t {
Arrow = 0,
ResizeEW
};
void Initialize(const std::filesystem::path& repoRoot);
void SetTextMeasurer(const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer);
void Update(
const UIEditorPanelContentHostFrame& contentHostFrame,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
bool allowInteraction,
bool panelActive);
void Append(::XCEngine::UI::UIDrawList& drawList) const;
CursorKind GetCursorKind() const;
bool WantsHostPointerCapture() const;
bool WantsHostPointerRelease() const;
bool HasActivePointerCapture() const;
private:
struct FolderEntry {
std::string itemId = {};
std::filesystem::path absolutePath = {};
};
struct AssetEntry {
std::string itemId = {};
std::filesystem::path absolutePath = {};
std::string displayName = {};
bool directory = false;
};
struct BreadcrumbItemLayout {
std::string label = {};
std::string targetFolderId = {};
::XCEngine::UI::UIRect rect = {};
bool separator = false;
bool clickable = false;
bool current = false;
};
struct AssetTileLayout {
std::size_t itemIndex = static_cast<std::size_t>(-1);
::XCEngine::UI::UIRect tileRect = {};
::XCEngine::UI::UIRect previewRect = {};
::XCEngine::UI::UIRect labelRect = {};
};
struct Layout {
::XCEngine::UI::UIRect bounds = {};
::XCEngine::UI::UIRect leftPaneRect = {};
::XCEngine::UI::UIRect treeRect = {};
::XCEngine::UI::UIRect dividerRect = {};
::XCEngine::UI::UIRect rightPaneRect = {};
::XCEngine::UI::UIRect browserHeaderRect = {};
::XCEngine::UI::UIRect browserBodyRect = {};
::XCEngine::UI::UIRect gridRect = {};
std::vector<BreadcrumbItemLayout> breadcrumbItems = {};
std::vector<AssetTileLayout> assetTiles = {};
};
const FolderEntry* FindFolderEntry(std::string_view itemId) const;
const UIEditorPanelContentHostPanelState* FindMountedProjectPanel(
const UIEditorPanelContentHostFrame& contentHostFrame) const;
Layout BuildLayout(const ::XCEngine::UI::UIRect& bounds) const;
std::size_t HitTestBreadcrumbItem(const ::XCEngine::UI::UIPoint& point) const;
std::size_t HitTestAssetTile(const ::XCEngine::UI::UIPoint& point) const;
void RefreshFolderTree();
void RefreshAssetList();
void EnsureValidCurrentFolder();
void ExpandFolderAncestors(std::string_view itemId);
void SyncCurrentFolderSelection();
void NavigateToFolder(std::string_view itemId);
void ResetTransientFrames();
std::filesystem::path m_assetsRootPath = {};
std::vector<FolderEntry> m_folderEntries = {};
std::vector<Widgets::UIEditorTreeViewItem> m_treeItems = {};
std::vector<AssetEntry> m_assetEntries = {};
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* m_textMeasurer = nullptr;
::XCEngine::UI::Widgets::UISelectionModel m_folderSelection = {};
::XCEngine::UI::Widgets::UIExpansionModel m_folderExpansion = {};
::XCEngine::UI::Widgets::UISelectionModel m_assetSelection = {};
UIEditorTreeViewInteractionState m_treeInteractionState = {};
UIEditorTreeViewInteractionFrame m_treeFrame = {};
Layout m_layout = {};
std::string m_currentFolderId = {};
std::string m_hoveredAssetItemId = {};
std::string m_lastPrimaryClickedAssetId = {};
float m_navigationWidth = 248.0f;
std::uint64_t m_lastPrimaryClickTimeMs = 0u;
std::size_t m_hoveredBreadcrumbIndex = static_cast<std::size_t>(-1);
std::size_t m_pressedBreadcrumbIndex = static_cast<std::size_t>(-1);
bool m_visible = false;
bool m_splitterHovered = false;
bool m_splitterDragging = false;
bool m_requestPointerCapture = false;
bool m_requestPointerRelease = false;
};
} // namespace XCEngine::UI::Editor::App

View File

@@ -25,7 +25,7 @@ UIEditorPanelRegistry BuildPanelRegistry() {
{ "game", "Game", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, { "game", "Game", UIEditorPanelPresentationKind::ViewportShell, false, false, false },
{ "inspector", "Inspector", UIEditorPanelPresentationKind::Placeholder, true, false, false }, { "inspector", "Inspector", UIEditorPanelPresentationKind::Placeholder, true, false, false },
{ "console", "Console", UIEditorPanelPresentationKind::Placeholder, true, false, false }, { "console", "Console", UIEditorPanelPresentationKind::Placeholder, true, false, false },
{ "project", "Project", UIEditorPanelPresentationKind::Placeholder, true, false, false } { "project", "Project", UIEditorPanelPresentationKind::HostedContent, false, false, false }
}; };
return registry; return registry;
} }
@@ -81,9 +81,9 @@ UIEditorWorkspaceModel BuildWorkspace() {
"project-panel", "project-panel",
"project", "project",
"Project", "Project",
true) false)
}, },
0u)); 1u));
workspace.activePanelId = "scene"; workspace.activePanelId = "scene";
return workspace; return workspace;
} }
@@ -434,6 +434,14 @@ UIEditorWorkspacePanelPresentationModel BuildViewportPresentation(
return presentation; return presentation;
} }
UIEditorWorkspacePanelPresentationModel BuildHostedContentPresentation(
std::string panelId) {
UIEditorWorkspacePanelPresentationModel presentation = {};
presentation.panelId = std::move(panelId);
presentation.kind = UIEditorPanelPresentationKind::HostedContent;
return presentation;
}
UIEditorShellInteractionDefinition BuildBaseShellDefinition() { UIEditorShellInteractionDefinition BuildBaseShellDefinition() {
UIEditorShellInteractionDefinition definition = {}; UIEditorShellInteractionDefinition definition = {};
definition.menuModel = BuildMenuModel(); definition.menuModel = BuildMenuModel();
@@ -449,7 +457,7 @@ UIEditorShellInteractionDefinition BuildBaseShellDefinition() {
BuildViewportPresentation("game", "Game", "Display 1", "Game preview host ready"), BuildViewportPresentation("game", "Game", "Display 1", "Game preview host ready"),
BuildPlaceholderPresentation("inspector"), BuildPlaceholderPresentation("inspector"),
BuildPlaceholderPresentation("console"), BuildPlaceholderPresentation("console"),
BuildPlaceholderPresentation("project") BuildHostedContentPresentation("project")
}; };
return definition; return definition;
} }

View File

@@ -65,9 +65,9 @@ struct UIEditorTabStripPalette {
::XCEngine::UI::UIColor contentBackgroundColor = ::XCEngine::UI::UIColor contentBackgroundColor =
::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f);
::XCEngine::UI::UIColor stripBorderColor = ::XCEngine::UI::UIColor stripBorderColor =
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor headerContentSeparatorColor = ::XCEngine::UI::UIColor headerContentSeparatorColor =
::XCEngine::UI::UIColor(0.27f, 0.27f, 0.27f, 1.0f); ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor focusedBorderColor = ::XCEngine::UI::UIColor focusedBorderColor =
::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f); ::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f);
::XCEngine::UI::UIColor tabColor = ::XCEngine::UI::UIColor tabColor =
@@ -77,11 +77,11 @@ struct UIEditorTabStripPalette {
::XCEngine::UI::UIColor tabSelectedColor = ::XCEngine::UI::UIColor tabSelectedColor =
::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f);
::XCEngine::UI::UIColor tabBorderColor = ::XCEngine::UI::UIColor tabBorderColor =
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor tabHoveredBorderColor = ::XCEngine::UI::UIColor tabHoveredBorderColor =
::XCEngine::UI::UIColor(0.31f, 0.31f, 0.31f, 1.0f); ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor tabSelectedBorderColor = ::XCEngine::UI::UIColor tabSelectedBorderColor =
::XCEngine::UI::UIColor(0.34f, 0.34f, 0.34f, 1.0f); ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor textPrimary = ::XCEngine::UI::UIColor textPrimary =
::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f);
::XCEngine::UI::UIColor textSecondary = ::XCEngine::UI::UIColor textSecondary =

View File

@@ -60,7 +60,7 @@ struct UIEditorDockHostState {
struct UIEditorDockHostMetrics { struct UIEditorDockHostMetrics {
::XCEngine::UI::Layout::UISplitterMetrics splitterMetrics = ::XCEngine::UI::Layout::UISplitterMetrics splitterMetrics =
::XCEngine::UI::Layout::UISplitterMetrics{ 1.0f, 10.0f }; ::XCEngine::UI::Layout::UISplitterMetrics{ 2.0f, 10.0f };
UIEditorTabStripMetrics tabStripMetrics = {}; UIEditorTabStripMetrics tabStripMetrics = {};
UIEditorPanelFrameMetrics panelFrameMetrics = {}; UIEditorPanelFrameMetrics panelFrameMetrics = {};
::XCEngine::UI::UISize minimumStandalonePanelBodySize = ::XCEngine::UI::UISize minimumStandalonePanelBodySize =
@@ -75,11 +75,11 @@ struct UIEditorDockHostPalette {
UIEditorTabStripPalette tabStripPalette = {}; UIEditorTabStripPalette tabStripPalette = {};
UIEditorPanelFramePalette panelFramePalette = {}; UIEditorPanelFramePalette panelFramePalette = {};
::XCEngine::UI::UIColor splitterColor = ::XCEngine::UI::UIColor splitterColor =
::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f); ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor splitterHoveredColor = ::XCEngine::UI::UIColor splitterHoveredColor =
::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor splitterActiveColor = ::XCEngine::UI::UIColor splitterActiveColor =
::XCEngine::UI::UIColor(0.35f, 0.35f, 0.35f, 1.0f); ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor placeholderTitleColor = ::XCEngine::UI::UIColor placeholderTitleColor =
::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f);
::XCEngine::UI::UIColor placeholderTextColor = ::XCEngine::UI::UIColor placeholderTextColor =

View File

@@ -88,20 +88,6 @@ float ResolveTabTextLeft(
return availableLeft + (std::max)(0.0f, (availableWidth - labelWidth) * 0.5f); return availableLeft + (std::max)(0.0f, (availableWidth - labelWidth) * 0.5f);
} }
UIColor ResolveStripBorderColor(
const UIEditorTabStripState& state,
const UIEditorTabStripPalette& palette) {
(void)state;
return palette.stripBorderColor;
}
float ResolveStripBorderThickness(
const UIEditorTabStripState& state,
const UIEditorTabStripMetrics& metrics) {
(void)state;
return metrics.baseBorderThickness;
}
UIColor ResolveTabFillColor( UIColor ResolveTabFillColor(
bool selected, bool selected,
bool hovered, bool hovered,
@@ -359,11 +345,6 @@ void AppendUIEditorTabStripBackground(
if (layout.headerRect.height > 0.0f) { if (layout.headerRect.height > 0.0f) {
drawList.AddFilledRect(layout.headerRect, palette.headerBackgroundColor, stripRounding); drawList.AddFilledRect(layout.headerRect, palette.headerBackgroundColor, stripRounding);
} }
drawList.AddRectOutline(
layout.bounds,
ResolveStripBorderColor(state, palette),
ResolveStripBorderThickness(state, metrics),
stripRounding);
for (std::size_t index = 0; index < layout.tabHeaderRects.size(); ++index) { for (std::size_t index = 0; index < layout.tabHeaderRects.size(); ++index) {
const bool selected = layout.selectedIndex == index; const bool selected = layout.selectedIndex == index;

View File

@@ -3,6 +3,7 @@
#include <XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h> #include <XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h>
#include <algorithm> #include <algorithm>
#include <cmath>
#include <numeric> #include <numeric>
namespace XCEngine::UI::Editor::Widgets { namespace XCEngine::UI::Editor::Widgets {
@@ -19,16 +20,42 @@ std::vector<std::size_t> BuildItemOffsets(std::size_t count) {
return offsets; return offsets;
} }
constexpr float kTreeFontSize = 12.0f;
float ResolveTreeViewRowHeight( float ResolveTreeViewRowHeight(
const UIEditorTreeViewItem& item, const UIEditorTreeViewItem& item,
const UIEditorTreeViewMetrics& metrics) { const UIEditorTreeViewMetrics& metrics) {
return item.desiredHeight > 0.0f ? item.desiredHeight : metrics.rowHeight; return item.desiredHeight > 0.0f ? item.desiredHeight : metrics.rowHeight;
} }
::XCEngine::UI::UIPoint ResolveDisclosureGlyphPosition( float ResolveTextTop(const ::XCEngine::UI::UIRect& rect, float fontSize) {
const float lineHeight = fontSize * 1.6f;
return rect.y + std::floor((rect.height - lineHeight) * 0.5f);
}
void AppendDisclosureArrow(
::XCEngine::UI::UIDrawList& drawList,
const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIRect& rect,
float insetY) { bool expanded,
return ::XCEngine::UI::UIPoint(rect.x + 2.0f, rect.y + insetY - 1.0f); const ::XCEngine::UI::UIColor& color) {
constexpr float kOpticalCenterYOffset = -0.5f;
const float centerX = std::floor(rect.x + rect.width * 0.5f) + 0.5f;
const float centerY =
std::floor(rect.y + rect.height * 0.5f + kOpticalCenterYOffset) + 0.5f;
const float halfExtent = (std::max)(2.5f, std::floor((std::min)(rect.width, rect.height) * 0.24f));
const float triangleHeight = halfExtent * 1.55f;
::XCEngine::UI::UIPoint points[3] = {};
if (expanded) {
points[0] = ::XCEngine::UI::UIPoint(centerX - halfExtent, centerY - triangleHeight * 0.5f);
points[1] = ::XCEngine::UI::UIPoint(centerX + halfExtent, centerY - triangleHeight * 0.5f);
points[2] = ::XCEngine::UI::UIPoint(centerX, centerY + triangleHeight * 0.5f);
} else {
points[0] = ::XCEngine::UI::UIPoint(centerX - triangleHeight * 0.5f, centerY - halfExtent);
points[1] = ::XCEngine::UI::UIPoint(centerX - triangleHeight * 0.5f, centerY + halfExtent);
points[2] = ::XCEngine::UI::UIPoint(centerX + triangleHeight * 0.5f, centerY);
}
drawList.AddFilledTriangle(points[0], points[1], points[2], color);
} }
} // namespace } // namespace
@@ -267,23 +294,21 @@ void AppendUIEditorTreeViewForeground(
for (std::size_t visibleOffset = 0u; visibleOffset < layout.rowRects.size(); ++visibleOffset) { for (std::size_t visibleOffset = 0u; visibleOffset < layout.rowRects.size(); ++visibleOffset) {
const UIEditorTreeViewItem& item = items[layout.visibleItemIndices[visibleOffset]]; const UIEditorTreeViewItem& item = items[layout.visibleItemIndices[visibleOffset]];
if (layout.itemHasChildren[visibleOffset]) { if (layout.itemHasChildren[visibleOffset]) {
drawList.AddText( AppendDisclosureArrow(
ResolveDisclosureGlyphPosition( drawList,
layout.disclosureRects[visibleOffset], layout.disclosureRects[visibleOffset],
metrics.labelInsetY), layout.itemExpanded[visibleOffset],
layout.itemExpanded[visibleOffset] ? "v" : ">", palette.disclosureColor);
palette.disclosureColor,
12.0f);
} }
drawList.PushClipRect(layout.labelRects[visibleOffset]); drawList.PushClipRect(layout.labelRects[visibleOffset]);
drawList.AddText( drawList.AddText(
::XCEngine::UI::UIPoint( ::XCEngine::UI::UIPoint(
layout.labelRects[visibleOffset].x, layout.labelRects[visibleOffset].x,
layout.labelRects[visibleOffset].y + metrics.labelInsetY), ResolveTextTop(layout.labelRects[visibleOffset], kTreeFontSize)),
item.label, item.label,
palette.textColor, palette.textColor,
12.0f); kTreeFontSize);
drawList.PopClipRect(); drawList.PopClipRect();
} }
drawList.PopClipRect(); drawList.PopClipRect();