feat(new_editor): add project panel and polish dock chrome
This commit is contained in:
@@ -147,6 +147,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP)
|
||||
add_executable(XCUIEditorApp WIN32
|
||||
app/main.cpp
|
||||
app/Application.cpp
|
||||
app/Panels/ProductProjectPanel.cpp
|
||||
app/Shell/ProductShellAsset.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -38,6 +38,20 @@ D2D1_RECT_F ToD2DRect(const ::XCEngine::UI::UIRect& rect, float dpiScale) {
|
||||
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
|
||||
|
||||
bool NativeRenderer::Initialize(HWND hwnd) {
|
||||
@@ -458,6 +472,51 @@ void NativeRenderer::RenderCommand(
|
||||
}
|
||||
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: {
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
|
||||
const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale;
|
||||
@@ -472,6 +531,58 @@ void NativeRenderer::RenderCommand(
|
||||
}
|
||||
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: {
|
||||
if (command.text.empty()) {
|
||||
break;
|
||||
|
||||
@@ -41,6 +41,45 @@ constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Edit
|
||||
constexpr UINT kDefaultDpi = 96u;
|
||||
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) {
|
||||
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
|
||||
|
||||
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.shortcutManager = &m_shortcutManager;
|
||||
m_shellServices.textMeasurer = &m_renderer;
|
||||
m_projectPanel.Initialize(ResolveRepoRootPath());
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage = "Old editor shell baseline loaded.";
|
||||
LogRuntimeTrace("app", "workspace initialized: " + DescribeWorkspaceState());
|
||||
@@ -385,6 +446,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
LogRuntimeTrace("app", "renderer initialization failed");
|
||||
return false;
|
||||
}
|
||||
m_projectPanel.SetTextMeasurer(&m_renderer);
|
||||
|
||||
ShowWindow(m_hwnd, nCmdShow);
|
||||
UpdateWindow(m_hwnd);
|
||||
@@ -448,6 +510,11 @@ void Application::RenderFrame() {
|
||||
const UIEditorShellInteractionDefinition definition = BuildShellDefinition();
|
||||
std::vector<UIInputEvent> frameEvents = std::move(m_pendingInputEvents);
|
||||
m_pendingInputEvents.clear();
|
||||
const std::vector<UIInputEvent> hostedContentEvents = frameEvents;
|
||||
const std::vector<UIInputEvent> shellEvents =
|
||||
m_projectPanel.HasActivePointerCapture()
|
||||
? FilterShellInputEventsForHostedContentCapture(frameEvents)
|
||||
: frameEvents;
|
||||
if (!frameEvents.empty()) {
|
||||
LogRuntimeTrace(
|
||||
"input",
|
||||
@@ -459,10 +526,10 @@ void Application::RenderFrame() {
|
||||
m_workspaceController,
|
||||
UIRect(0.0f, 0.0f, width, height),
|
||||
definition,
|
||||
frameEvents,
|
||||
shellEvents,
|
||||
m_shellServices,
|
||||
metrics);
|
||||
if (!frameEvents.empty() ||
|
||||
if (!shellEvents.empty() ||
|
||||
m_shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
|
||||
m_shellFrame.result.workspaceResult.dockHostResult.commandExecuted) {
|
||||
std::ostringstream frameTrace = {};
|
||||
@@ -482,13 +549,24 @@ void Application::RenderFrame() {
|
||||
}
|
||||
ApplyHostCaptureRequests(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();
|
||||
AppendUIEditorShellInteraction(
|
||||
const UIEditorShellComposeModel shellComposeModel =
|
||||
BuildShellComposeModelFromFrame(m_shellFrame);
|
||||
AppendUIEditorShellCompose(
|
||||
drawList,
|
||||
m_shellFrame,
|
||||
m_shellInteractionState,
|
||||
palette,
|
||||
metrics);
|
||||
m_shellFrame.shellFrame,
|
||||
shellComposeModel,
|
||||
m_shellInteractionState.composeState,
|
||||
palette.shellPalette,
|
||||
metrics.shellMetrics);
|
||||
m_projectPanel.Append(drawList);
|
||||
AppendShellPopups(drawList, m_shellFrame, palette, metrics);
|
||||
} else {
|
||||
drawList.AddText(
|
||||
UIPoint(28.0f, 28.0f),
|
||||
@@ -522,6 +600,14 @@ float Application::PixelsToDips(float pixels) 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(
|
||||
m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout)) {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
@@ -657,6 +755,10 @@ bool Application::HasInteractiveCaptureState() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Application::HasInteractiveCaptureState() const {
|
||||
return HasShellInteractiveCaptureState() || m_projectPanel.HasActivePointerCapture();
|
||||
}
|
||||
|
||||
UIEditorShellInteractionDefinition Application::BuildShellDefinition() const {
|
||||
std::string statusText = m_lastStatus;
|
||||
if (!m_lastMessage.empty()) {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <Host/InputModifierTracker.h>
|
||||
#include <Host/NativeRenderer.h>
|
||||
|
||||
#include "Panels/ProductProjectPanel.h"
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
@@ -43,11 +45,15 @@ private:
|
||||
void RenderFrame();
|
||||
void OnResize(UINT width, UINT height);
|
||||
void OnDpiChanged(UINT dpi, const RECT& suggestedRect);
|
||||
bool ApplyCurrentCursor() const;
|
||||
LPCWSTR ResolveCurrentCursorResource() const;
|
||||
float GetDpiScale() const;
|
||||
float PixelsToDips(float pixels) const;
|
||||
::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const;
|
||||
void LogRuntimeTrace(std::string_view channel, std::string_view message) const;
|
||||
void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result);
|
||||
void ApplyHostedContentCaptureRequests();
|
||||
bool HasShellInteractiveCaptureState() const;
|
||||
bool HasInteractiveCaptureState() const;
|
||||
UIEditorShellInteractionDefinition BuildShellDefinition() const;
|
||||
void UpdateLastStatus(const UIEditorShellInteractionResult& result);
|
||||
@@ -77,6 +83,7 @@ private:
|
||||
EditorShellAssetValidationResult m_shellValidation = {};
|
||||
UIEditorWorkspaceController m_workspaceController = {};
|
||||
UIEditorShortcutManager m_shortcutManager = {};
|
||||
App::ProductProjectPanel m_projectPanel = {};
|
||||
UIEditorShellInteractionServices m_shellServices = {};
|
||||
UIEditorShellInteractionState m_shellInteractionState = {};
|
||||
UIEditorShellInteractionFrame m_shellFrame = {};
|
||||
|
||||
1032
new_editor/app/Panels/ProductProjectPanel.cpp
Normal file
1032
new_editor/app/Panels/ProductProjectPanel.cpp
Normal file
File diff suppressed because it is too large
Load Diff
121
new_editor/app/Panels/ProductProjectPanel.h
Normal file
121
new_editor/app/Panels/ProductProjectPanel.h
Normal 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
|
||||
@@ -25,7 +25,7 @@ UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
{ "game", "Game", UIEditorPanelPresentationKind::ViewportShell, false, false, false },
|
||||
{ "inspector", "Inspector", 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;
|
||||
}
|
||||
@@ -81,9 +81,9 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
"project-panel",
|
||||
"project",
|
||||
"Project",
|
||||
true)
|
||||
false)
|
||||
},
|
||||
0u));
|
||||
1u));
|
||||
workspace.activePanelId = "scene";
|
||||
return workspace;
|
||||
}
|
||||
@@ -434,6 +434,14 @@ UIEditorWorkspacePanelPresentationModel BuildViewportPresentation(
|
||||
return presentation;
|
||||
}
|
||||
|
||||
UIEditorWorkspacePanelPresentationModel BuildHostedContentPresentation(
|
||||
std::string panelId) {
|
||||
UIEditorWorkspacePanelPresentationModel presentation = {};
|
||||
presentation.panelId = std::move(panelId);
|
||||
presentation.kind = UIEditorPanelPresentationKind::HostedContent;
|
||||
return presentation;
|
||||
}
|
||||
|
||||
UIEditorShellInteractionDefinition BuildBaseShellDefinition() {
|
||||
UIEditorShellInteractionDefinition definition = {};
|
||||
definition.menuModel = BuildMenuModel();
|
||||
@@ -449,7 +457,7 @@ UIEditorShellInteractionDefinition BuildBaseShellDefinition() {
|
||||
BuildViewportPresentation("game", "Game", "Display 1", "Game preview host ready"),
|
||||
BuildPlaceholderPresentation("inspector"),
|
||||
BuildPlaceholderPresentation("console"),
|
||||
BuildPlaceholderPresentation("project")
|
||||
BuildHostedContentPresentation("project")
|
||||
};
|
||||
return definition;
|
||||
}
|
||||
|
||||
@@ -65,9 +65,9 @@ struct UIEditorTabStripPalette {
|
||||
::XCEngine::UI::UIColor contentBackgroundColor =
|
||||
::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f);
|
||||
::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(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(0.36f, 0.36f, 0.36f, 1.0f);
|
||||
::XCEngine::UI::UIColor tabColor =
|
||||
@@ -77,11 +77,11 @@ struct UIEditorTabStripPalette {
|
||||
::XCEngine::UI::UIColor tabSelectedColor =
|
||||
::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f);
|
||||
::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(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(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(0.86f, 0.86f, 0.86f, 1.0f);
|
||||
::XCEngine::UI::UIColor textSecondary =
|
||||
|
||||
@@ -60,7 +60,7 @@ struct UIEditorDockHostState {
|
||||
|
||||
struct UIEditorDockHostMetrics {
|
||||
::XCEngine::UI::Layout::UISplitterMetrics splitterMetrics =
|
||||
::XCEngine::UI::Layout::UISplitterMetrics{ 1.0f, 10.0f };
|
||||
::XCEngine::UI::Layout::UISplitterMetrics{ 2.0f, 10.0f };
|
||||
UIEditorTabStripMetrics tabStripMetrics = {};
|
||||
UIEditorPanelFrameMetrics panelFrameMetrics = {};
|
||||
::XCEngine::UI::UISize minimumStandalonePanelBodySize =
|
||||
@@ -75,11 +75,11 @@ struct UIEditorDockHostPalette {
|
||||
UIEditorTabStripPalette tabStripPalette = {};
|
||||
UIEditorPanelFramePalette panelFramePalette = {};
|
||||
::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(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(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(0.93f, 0.94f, 0.96f, 1.0f);
|
||||
::XCEngine::UI::UIColor placeholderTextColor =
|
||||
|
||||
@@ -88,20 +88,6 @@ float ResolveTabTextLeft(
|
||||
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(
|
||||
bool selected,
|
||||
bool hovered,
|
||||
@@ -359,11 +345,6 @@ void AppendUIEditorTabStripBackground(
|
||||
if (layout.headerRect.height > 0.0f) {
|
||||
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) {
|
||||
const bool selected = layout.selectedIndex == index;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <XCEngine/UI/Widgets/UIFlatHierarchyHelpers.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
@@ -19,16 +20,42 @@ std::vector<std::size_t> BuildItemOffsets(std::size_t count) {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
constexpr float kTreeFontSize = 12.0f;
|
||||
float ResolveTreeViewRowHeight(
|
||||
const UIEditorTreeViewItem& item,
|
||||
const UIEditorTreeViewMetrics& metrics) {
|
||||
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,
|
||||
float insetY) {
|
||||
return ::XCEngine::UI::UIPoint(rect.x + 2.0f, rect.y + insetY - 1.0f);
|
||||
bool expanded,
|
||||
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
|
||||
@@ -267,23 +294,21 @@ void AppendUIEditorTreeViewForeground(
|
||||
for (std::size_t visibleOffset = 0u; visibleOffset < layout.rowRects.size(); ++visibleOffset) {
|
||||
const UIEditorTreeViewItem& item = items[layout.visibleItemIndices[visibleOffset]];
|
||||
if (layout.itemHasChildren[visibleOffset]) {
|
||||
drawList.AddText(
|
||||
ResolveDisclosureGlyphPosition(
|
||||
layout.disclosureRects[visibleOffset],
|
||||
metrics.labelInsetY),
|
||||
layout.itemExpanded[visibleOffset] ? "v" : ">",
|
||||
palette.disclosureColor,
|
||||
12.0f);
|
||||
AppendDisclosureArrow(
|
||||
drawList,
|
||||
layout.disclosureRects[visibleOffset],
|
||||
layout.itemExpanded[visibleOffset],
|
||||
palette.disclosureColor);
|
||||
}
|
||||
|
||||
drawList.PushClipRect(layout.labelRects[visibleOffset]);
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(
|
||||
layout.labelRects[visibleOffset].x,
|
||||
layout.labelRects[visibleOffset].y + metrics.labelInsetY),
|
||||
ResolveTextTop(layout.labelRects[visibleOffset], kTreeFontSize)),
|
||||
item.label,
|
||||
palette.textColor,
|
||||
12.0f);
|
||||
kTreeFontSize);
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
drawList.PopClipRect();
|
||||
|
||||
Reference in New Issue
Block a user