@@ -0,0 +1,957 @@
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <XCEditor/Core/UIEditorPanelRegistry.h>
# include <XCEditor/Core/UIEditorWorkspaceCompose.h>
# include <XCEditor/Core/UIEditorWorkspaceController.h>
# include <XCEditor/Core/UIEditorWorkspaceModel.h>
# include <XCEditor/Widgets/UIEditorViewportSlot.h>
# include "Host/AutoScreenshot.h"
# include "Host/InputModifierTracker.h"
# include "Host/NativeRenderer.h"
# include <XCEngine/UI/DrawData.h>
# include <windows.h>
# include <windowsx.h>
# include <algorithm>
# include <filesystem>
# include <sstream>
# include <string>
# include <string_view>
# include <utility>
# include <vector>
# ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT
# define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
# endif
namespace {
using XCEngine : : UI : : UIColor ;
using XCEngine : : UI : : UIDrawData ;
using XCEngine : : UI : : UIDrawList ;
using XCEngine : : UI : : UIInputEvent ;
using XCEngine : : UI : : UIInputEventType ;
using XCEngine : : UI : : UIPoint ;
using XCEngine : : UI : : UIPointerButton ;
using XCEngine : : UI : : UIRect ;
using XCEngine : : UI : : UISize ;
using XCEngine : : UI : : Editor : : AppendUIEditorWorkspaceCompose ;
using XCEngine : : UI : : Editor : : BuildDefaultUIEditorWorkspaceController ;
using XCEngine : : UI : : Editor : : BuildUIEditorWorkspacePanel ;
using XCEngine : : UI : : Editor : : BuildUIEditorWorkspaceSplit ;
using XCEngine : : UI : : Editor : : BuildUIEditorWorkspaceTabStack ;
using XCEngine : : UI : : Editor : : FindUIEditorWorkspaceViewportPresentationFrame ;
using XCEngine : : UI : : Editor : : FindUIEditorWorkspaceViewportPresentationRequest ;
using XCEngine : : UI : : Editor : : GetUIEditorWorkspaceCommandStatusName ;
using XCEngine : : UI : : Editor : : ResolveUIEditorWorkspaceComposeRequest ;
using XCEngine : : UI : : Editor : : UIEditorPanelPresentationKind ;
using XCEngine : : UI : : Editor : : UIEditorPanelRegistry ;
using XCEngine : : UI : : Editor : : UIEditorViewportShellModel ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceCommand ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceCommandKind ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceCommandResult ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceComposeFrame ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceComposeRequest ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceComposeState ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceController ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceModel ;
using XCEngine : : UI : : Editor : : UIEditorWorkspacePanelPresentationModel ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceSplitAxis ;
using XCEngine : : UI : : Editor : : UpdateUIEditorWorkspaceCompose ;
using XCEngine : : UI : : Editor : : Host : : AutoScreenshotController ;
using XCEngine : : UI : : Editor : : Host : : InputModifierTracker ;
using XCEngine : : UI : : Editor : : Host : : NativeRenderer ;
using XCEngine : : UI : : Editor : : Widgets : : HitTestUIEditorViewportSlot ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorStatusBarSegment ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorStatusBarSlot ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotHitTarget ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotHitTargetKind ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotToolItem ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotToolSlot ;
constexpr const wchar_t * kWindowClassName = L " XCUIEditorWorkspaceViewportComposeValidation " ;
constexpr const wchar_t * kWindowTitle = L " XCUI Editor | Workspace Viewport Compose " ;
constexpr UIColor kWindowBg ( 0.11f , 0.11f , 0.11f , 1.0f ) ;
constexpr UIColor kCardBg ( 0.17f , 0.17f , 0.17f , 1.0f ) ;
constexpr UIColor kCardBorder ( 0.28f , 0.28f , 0.28f , 1.0f ) ;
constexpr UIColor kPreviewBg ( 0.14f , 0.14f , 0.14f , 1.0f ) ;
constexpr UIColor kTextPrimary ( 0.94f , 0.94f , 0.94f , 1.0f ) ;
constexpr UIColor kTextMuted ( 0.72f , 0.72f , 0.72f , 1.0f ) ;
constexpr UIColor kTextWeak ( 0.56f , 0.56f , 0.56f , 1.0f ) ;
constexpr UIColor kButtonBg ( 0.24f , 0.24f , 0.24f , 1.0f ) ;
constexpr UIColor kButtonOnBg ( 0.36f , 0.36f , 0.36f , 1.0f ) ;
constexpr UIColor kButtonBorder ( 0.46f , 0.46f , 0.46f , 1.0f ) ;
constexpr UIColor kSuccess ( 0.46f , 0.72f , 0.50f , 1.0f ) ;
constexpr UIColor kDanger ( 0.78f , 0.35f , 0.35f , 1.0f ) ;
enum class ActionId : unsigned char {
ActivateScene = 0 ,
ActivateDocument ,
ToggleTopBar ,
ToggleBottomBar ,
ToggleTexture ,
Reset ,
Capture
} ;
struct ButtonState {
ActionId action = ActionId : : ActivateScene ;
std : : string label = { } ;
UIRect rect = { } ;
bool selected = false ;
} ;
std : : filesystem : : path ResolveRepoRootPath ( ) ;
bool ContainsPoint ( const UIRect & rect , float x , float y ) ;
std : : string BoolText ( bool value ) ;
std : : string FormatFloat ( float value ) ;
std : : string FormatSize ( const UISize & size ) ;
std : : string FormatRect ( const UIRect & rect ) ;
std : : string DescribeHitTarget ( const UIEditorViewportSlotHitTarget & hit ) ;
void DrawCard ( UIDrawList & drawList , const UIRect & rect , std : : string_view title , std : : string_view subtitle = { } ) ;
void DrawButton ( UIDrawList & drawList , const ButtonState & button ) ;
UIEditorPanelRegistry BuildPanelRegistry ( ) ;
UIEditorWorkspaceModel BuildWorkspace ( ) ;
class ScenarioApp {
public :
int Run ( HINSTANCE hInstance , int nCmdShow ) ;
private :
static LRESULT CALLBACK WndProc ( HWND hwnd , UINT message , WPARAM wParam , LPARAM lParam ) ;
bool Initialize ( HINSTANCE hInstance , int nCmdShow ) ;
void Shutdown ( ) ;
void ResetScenario ( ) ;
void UpdateLayoutForCurrentWindow ( ) ;
std : : vector < UIEditorWorkspacePanelPresentationModel > BuildPresentationModels ( ) const ;
void QueuePointerEvent ( UIInputEventType type , UIPointerButton button , WPARAM wParam , LPARAM lParam ) ;
void QueuePointerLeaveEvent ( ) ;
void QueuePointerWheelEvent ( short wheelDelta , WPARAM wParam , LPARAM lParam ) ;
void QueueFocusEvent ( UIInputEventType type ) ;
void HandleMouseMove ( WPARAM wParam , LPARAM lParam ) ;
void HandlePointerDown ( UIPointerButton button , WPARAM wParam , LPARAM lParam ) ;
void HandlePointerUp ( UIPointerButton button , WPARAM wParam , LPARAM lParam ) ;
void ExecuteAction ( ActionId action ) ;
void DispatchWorkspaceCommand ( UIEditorWorkspaceCommandKind kind , std : : string_view panelId , std : : string_view label ) ;
std : : string GetSelectedTabPanelId ( ) const ;
void UpdateComposeFrame ( ) ;
void UpdateLastResult ( const XCEngine : : UI : : Editor : : UIEditorWorkspaceViewportComposeFrame * viewportFrame ) ;
void RenderFrame ( ) ;
HWND m_hwnd = nullptr ;
ATOM m_windowClassAtom = 0 ;
NativeRenderer m_renderer = { } ;
AutoScreenshotController m_autoScreenshot = { } ;
InputModifierTracker m_inputModifierTracker = { } ;
std : : filesystem : : path m_captureRoot = { } ;
UIEditorWorkspaceController m_controller = { } ;
UIEditorWorkspaceComposeState m_composeState = { } ;
UIEditorWorkspaceComposeRequest m_composeRequest = { } ;
UIEditorWorkspaceComposeFrame m_composeFrame = { } ;
std : : vector < UIInputEvent > m_pendingInputEvents = { } ;
std : : vector < ButtonState > m_buttons = { } ;
UIRect m_introRect = { } ;
UIRect m_controlsRect = { } ;
UIRect m_stateRect = { } ;
UIRect m_previewRect = { } ;
UIRect m_workspaceRect = { } ;
UIPoint m_mousePosition = UIPoint ( - 1000.0f , - 1000.0f ) ;
UIEditorViewportSlotHitTarget m_hoverHit = { } ;
bool m_showTopBar = true ;
bool m_showBottomBar = true ;
bool m_textureEnabled = true ;
bool m_previousHovered = false ;
bool m_previousFocused = false ;
bool m_previousCaptured = false ;
bool m_previousViewportVisible = false ;
bool m_hasSnapshot = false ;
std : : string m_lastResult = { } ;
} ;
std : : filesystem : : path ResolveRepoRootPath ( ) {
std : : string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT ;
if ( root . size ( ) > = 2u & & root . front ( ) = = ' " ' & & root . back ( ) = = ' " ' ) {
root = root . substr ( 1u , root . size ( ) - 2u ) ;
}
return std : : filesystem : : path ( root ) . lexically_normal ( ) ;
}
bool ContainsPoint ( const UIRect & rect , float x , float y ) {
return x > = rect . x & &
x < = rect . x + rect . width & &
y > = rect . y & &
y < = rect . y + rect . height ;
}
std : : string BoolText ( bool value ) {
return value ? " On " : " Off " ;
}
std : : string FormatFloat ( float value ) {
std : : ostringstream stream = { } ;
stream . setf ( std : : ios : : fixed , std : : ios : : floatfield ) ;
stream . precision ( 1 ) ;
stream < < value ;
return stream . str ( ) ;
}
std : : string FormatSize ( const UISize & size ) {
return FormatFloat ( size . width ) + " x " + FormatFloat ( size . height ) ;
}
std : : string FormatRect ( const UIRect & rect ) {
return " x= " + FormatFloat ( rect . x ) +
" y= " + FormatFloat ( rect . y ) +
" w= " + FormatFloat ( rect . width ) +
" h= " + FormatFloat ( rect . height ) ;
}
std : : string DescribeHitTarget ( const UIEditorViewportSlotHitTarget & hit ) {
switch ( hit . kind ) {
case UIEditorViewportSlotHitTargetKind : : TopBar :
return " TopBar " ;
case UIEditorViewportSlotHitTargetKind : : Title :
return " Title " ;
case UIEditorViewportSlotHitTargetKind : : ToolItem :
return " ToolItem[ " + std : : to_string ( hit . index ) + " ] " ;
case UIEditorViewportSlotHitTargetKind : : Surface :
return " Surface " ;
case UIEditorViewportSlotHitTargetKind : : BottomBar :
return " BottomBar " ;
case UIEditorViewportSlotHitTargetKind : : StatusSegment :
return " StatusSegment[ " + std : : to_string ( hit . index ) + " ] " ;
case UIEditorViewportSlotHitTargetKind : : StatusSeparator :
return " StatusSeparator[ " + std : : to_string ( hit . index ) + " ] " ;
case UIEditorViewportSlotHitTargetKind : : None :
default :
return " None " ;
}
}
void DrawCard (
UIDrawList & drawList ,
const UIRect & rect ,
std : : string_view title ,
std : : string_view subtitle ) {
drawList . AddFilledRect ( rect , kCardBg , 10.0f ) ;
drawList . AddRectOutline ( rect , kCardBorder , 1.0f , 10.0f ) ;
drawList . AddText (
UIPoint ( rect . x + 16.0f , rect . y + 14.0f ) ,
std : : string ( title ) ,
kTextPrimary ,
17.0f ) ;
if ( ! subtitle . empty ( ) ) {
drawList . AddText (
UIPoint ( rect . x + 16.0f , rect . y + 38.0f ) ,
std : : string ( subtitle ) ,
kTextMuted ,
12.0f ) ;
}
}
void DrawButton ( UIDrawList & drawList , const ButtonState & button ) {
drawList . AddFilledRect (
button . rect ,
button . selected ? kButtonOnBg : kButtonBg ,
8.0f ) ;
drawList . AddRectOutline ( button . rect , kButtonBorder , 1.0f , 8.0f ) ;
drawList . AddText (
UIPoint ( button . rect . x + 12.0f , button . rect . y + 10.0f ) ,
button . label ,
kTextPrimary ,
12.0f ) ;
}
UIEditorPanelRegistry BuildPanelRegistry ( ) {
UIEditorPanelRegistry registry = { } ;
registry . panels = {
{ " hierarchy " , " Hierarchy " , UIEditorPanelPresentationKind : : Placeholder , true , true , false } ,
{ " scene " , " Scene " , UIEditorPanelPresentationKind : : ViewportShell , false , true , false } ,
{ " document " , " Document " , UIEditorPanelPresentationKind : : Placeholder , true , true , true } ,
{ " inspector " , " Inspector " , UIEditorPanelPresentationKind : : Placeholder , true , true , true }
} ;
return registry ;
}
UIEditorWorkspaceModel BuildWorkspace ( ) {
UIEditorWorkspaceModel workspace = { } ;
workspace . root = BuildUIEditorWorkspaceSplit (
" root-left-main " ,
UIEditorWorkspaceSplitAxis : : Horizontal ,
0.23f ,
BuildUIEditorWorkspacePanel ( " hierarchy-node " , " hierarchy " , " Hierarchy " , true ) ,
BuildUIEditorWorkspaceSplit (
" main-center-right " ,
UIEditorWorkspaceSplitAxis : : Horizontal ,
0.72f ,
BuildUIEditorWorkspaceTabStack (
" center-tabs " ,
{
BuildUIEditorWorkspacePanel ( " scene-node " , " scene " , " Scene " ) ,
BuildUIEditorWorkspacePanel ( " document-node " , " document " , " Document " , true )
} ,
0u ) ,
BuildUIEditorWorkspacePanel ( " inspector-node " , " inspector " , " Inspector " , true ) ) ) ;
workspace . activePanelId = " scene " ;
return workspace ;
}
int ScenarioApp : : Run ( HINSTANCE hInstance , int nCmdShow ) {
if ( ! Initialize ( hInstance , nCmdShow ) ) {
Shutdown ( ) ;
return 1 ;
}
MSG message = { } ;
while ( message . message ! = WM_QUIT ) {
if ( PeekMessageW ( & message , nullptr , 0U , 0U , PM_REMOVE ) ) {
TranslateMessage ( & message ) ;
DispatchMessageW ( & message ) ;
continue ;
}
RenderFrame ( ) ;
Sleep ( 8 ) ;
}
Shutdown ( ) ;
return static_cast < int > ( message . wParam ) ;
}
LRESULT CALLBACK ScenarioApp : : WndProc ( HWND hwnd , UINT message , WPARAM wParam , LPARAM lParam ) {
if ( message = = WM_NCCREATE ) {
const auto * createStruct = reinterpret_cast < CREATESTRUCTW * > ( lParam ) ;
auto * app = reinterpret_cast < ScenarioApp * > ( createStruct - > lpCreateParams ) ;
SetWindowLongPtrW ( hwnd , GWLP_USERDATA , reinterpret_cast < LONG_PTR > ( app ) ) ;
return TRUE ;
}
auto * app = reinterpret_cast < ScenarioApp * > ( GetWindowLongPtrW ( hwnd , GWLP_USERDATA ) ) ;
switch ( message ) {
case WM_SIZE :
if ( app ! = nullptr & & wParam ! = SIZE_MINIMIZED ) {
app - > m_renderer . Resize ( static_cast < UINT > ( LOWORD ( lParam ) ) , static_cast < UINT > ( HIWORD ( lParam ) ) ) ;
}
return 0 ;
case WM_MOUSEMOVE :
if ( app ! = nullptr ) {
app - > HandleMouseMove ( wParam , lParam ) ;
return 0 ;
}
break ;
case WM_MOUSELEAVE :
if ( app ! = nullptr ) {
app - > QueuePointerLeaveEvent ( ) ;
InvalidateRect ( hwnd , nullptr , FALSE ) ;
return 0 ;
}
break ;
case WM_LBUTTONDOWN :
if ( app ! = nullptr ) {
SetFocus ( hwnd ) ;
app - > HandlePointerDown ( UIPointerButton : : Left , wParam , lParam ) ;
return 0 ;
}
break ;
case WM_LBUTTONUP :
if ( app ! = nullptr ) {
app - > HandlePointerUp ( UIPointerButton : : Left , wParam , lParam ) ;
return 0 ;
}
break ;
case WM_MOUSEWHEEL :
if ( app ! = nullptr ) {
app - > QueuePointerWheelEvent ( GET_WHEEL_DELTA_WPARAM ( wParam ) , wParam , lParam ) ;
InvalidateRect ( hwnd , nullptr , FALSE ) ;
return 0 ;
}
break ;
case WM_SETFOCUS :
if ( app ! = nullptr ) {
app - > m_inputModifierTracker . SyncFromSystemState ( ) ;
app - > QueueFocusEvent ( UIInputEventType : : FocusGained ) ;
return 0 ;
}
break ;
case WM_KILLFOCUS :
if ( app ! = nullptr ) {
if ( GetCapture ( ) = = hwnd ) {
ReleaseCapture ( ) ;
}
app - > m_inputModifierTracker . Reset ( ) ;
app - > QueueFocusEvent ( UIInputEventType : : FocusLost ) ;
return 0 ;
}
break ;
case WM_KEYDOWN :
case WM_SYSKEYDOWN :
if ( app ! = nullptr & & wParam = = VK_F12 ) {
app - > m_autoScreenshot . RequestCapture ( " manual_f12 " ) ;
InvalidateRect ( hwnd , nullptr , FALSE ) ;
UpdateWindow ( hwnd ) ;
return 0 ;
}
break ;
case WM_PAINT :
if ( app ! = nullptr ) {
PAINTSTRUCT paintStruct = { } ;
BeginPaint ( hwnd , & paintStruct ) ;
app - > RenderFrame ( ) ;
EndPaint ( hwnd , & paintStruct ) ;
return 0 ;
}
break ;
case WM_ERASEBKGND :
return 1 ;
case WM_DESTROY :
PostQuitMessage ( 0 ) ;
return 0 ;
default :
break ;
}
return DefWindowProcW ( hwnd , message , wParam , lParam ) ;
}
bool ScenarioApp : : Initialize ( HINSTANCE hInstance , int nCmdShow ) {
m_captureRoot =
ResolveRepoRootPath ( ) / " tests/UI/Editor/integration/shell/workspace_viewport_compose/captures " ;
m_autoScreenshot . Initialize ( m_captureRoot ) ;
WNDCLASSEXW windowClass = { } ;
windowClass . cbSize = sizeof ( windowClass ) ;
windowClass . style = CS_HREDRAW | CS_VREDRAW ;
windowClass . lpfnWndProc = & ScenarioApp : : WndProc ;
windowClass . hInstance = hInstance ;
windowClass . hCursor = LoadCursorW ( nullptr , IDC_ARROW ) ;
windowClass . lpszClassName = kWindowClassName ;
m_windowClassAtom = RegisterClassExW ( & windowClass ) ;
if ( m_windowClassAtom = = 0 ) {
return false ;
}
m_hwnd = CreateWindowExW (
0 ,
kWindowClassName ,
kWindowTitle ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT ,
CW_USEDEFAULT ,
1520 ,
940 ,
nullptr ,
nullptr ,
hInstance ,
this ) ;
if ( m_hwnd = = nullptr ) {
return false ;
}
if ( ! m_renderer . Initialize ( m_hwnd ) ) {
return false ;
}
ShowWindow ( m_hwnd , nCmdShow ) ;
ResetScenario ( ) ;
return true ;
}
void ScenarioApp : : Shutdown ( ) {
m_autoScreenshot . Shutdown ( ) ;
m_renderer . Shutdown ( ) ;
if ( m_hwnd ! = nullptr & & IsWindow ( m_hwnd ) ) {
DestroyWindow ( m_hwnd ) ;
}
m_hwnd = nullptr ;
if ( m_windowClassAtom ! = 0 ) {
UnregisterClassW ( kWindowClassName , GetModuleHandleW ( nullptr ) ) ;
m_windowClassAtom = 0 ;
}
}
void ScenarioApp : : ResetScenario ( ) {
m_controller = BuildDefaultUIEditorWorkspaceController ( BuildPanelRegistry ( ) , BuildWorkspace ( ) ) ;
m_composeState = { } ;
m_composeRequest = { } ;
m_composeFrame = { } ;
m_pendingInputEvents . clear ( ) ;
m_inputModifierTracker . Reset ( ) ;
m_buttons . clear ( ) ;
m_mousePosition = UIPoint ( - 1000.0f , - 1000.0f ) ;
m_hoverHit = { } ;
m_showTopBar = true ;
m_showBottomBar = true ;
m_textureEnabled = true ;
m_lastResult = " Ready " ;
m_previousHovered = false ;
m_previousFocused = false ;
m_previousCaptured = false ;
m_previousViewportVisible = false ;
m_hasSnapshot = false ;
}
void ScenarioApp : : UpdateLayoutForCurrentWindow ( ) {
if ( m_hwnd = = nullptr ) {
return ;
}
RECT clientRect = { } ;
GetClientRect ( m_hwnd , & clientRect ) ;
const float width = static_cast < float > ( std : : max ( 1L , clientRect . right - clientRect . left ) ) ;
const float height = static_cast < float > ( std : : max ( 1L , clientRect . bottom - clientRect . top ) ) ;
constexpr float outerPadding = 20.0f ;
constexpr float leftColumnWidth = 460.0f ;
m_introRect = UIRect ( outerPadding , outerPadding , leftColumnWidth , 184.0f ) ;
m_controlsRect = UIRect ( outerPadding , 220.0f , leftColumnWidth , 340.0f ) ;
m_stateRect = UIRect ( outerPadding , 576.0f , leftColumnWidth , height - 596.0f ) ;
m_previewRect = UIRect (
leftColumnWidth + outerPadding * 2.0f ,
outerPadding ,
width - leftColumnWidth - outerPadding * 3.0f ,
height - outerPadding * 2.0f ) ;
m_workspaceRect = UIRect (
m_previewRect . x + 18.0f ,
m_previewRect . y + 54.0f ,
m_previewRect . width - 36.0f ,
m_previewRect . height - 72.0f ) ;
const float buttonHeight = 32.0f ;
const float gap = 8.0f ;
const float left = m_controlsRect . x + 16.0f ;
const float top = m_controlsRect . y + 64.0f ;
const float widthAvailable = m_controlsRect . width - 32.0f ;
const bool sceneSelected = GetSelectedTabPanelId ( ) = = " scene " ;
m_buttons = {
{ ActionId : : ActivateScene , " 切到 Scene " , UIRect ( left , top , widthAvailable , buttonHeight ) , sceneSelected } ,
{ ActionId : : ActivateDocument , " 切到 Document " , UIRect ( left , top + ( buttonHeight + gap ) , widthAvailable , buttonHeight ) , ! sceneSelected } ,
{ ActionId : : ToggleTopBar , std : : string ( " TopBar: " ) + ( m_showTopBar ? " 开 " : " 关 " ) , UIRect ( left , top + ( buttonHeight + gap ) * 2.0f , widthAvailable , buttonHeight ) , m_showTopBar } ,
{ ActionId : : ToggleBottomBar , std : : string ( " BottomBar: " ) + ( m_showBottomBar ? " 开 " : " 关 " ) , UIRect ( left , top + ( buttonHeight + gap ) * 3.0f , widthAvailable , buttonHeight ) , m_showBottomBar } ,
{ ActionId : : ToggleTexture , std : : string ( " Texture: " ) + ( m_textureEnabled ? " 开 " : " 关 " ) , UIRect ( left , top + ( buttonHeight + gap ) * 4.0f , widthAvailable , buttonHeight ) , m_textureEnabled } ,
{ ActionId : : Reset , " Reset " , UIRect ( left , top + ( buttonHeight + gap ) * 5.0f , widthAvailable , buttonHeight ) , false } ,
{ ActionId : : Capture , " 截图 " , UIRect ( left , top + ( buttonHeight + gap ) * 6.0f , widthAvailable , buttonHeight ) , false }
} ;
}
std : : vector < UIEditorWorkspacePanelPresentationModel > ScenarioApp : : BuildPresentationModels ( ) const {
UIEditorViewportShellModel viewportModel = { } ;
viewportModel . spec . chrome . title = " Scene " ;
viewportModel . spec . chrome . subtitle = " WorkspaceCompose 基础层 " ;
viewportModel . spec . chrome . showTopBar = m_showTopBar ;
viewportModel . spec . chrome . showBottomBar = m_showBottomBar ;
viewportModel . spec . chrome . topBarHeight = 40.0f ;
viewportModel . spec . chrome . bottomBarHeight = 28.0f ;
viewportModel . spec . toolItems = {
{ " mode " , " Perspective " , UIEditorViewportSlotToolSlot : : Leading , true , true , 98.0f } ,
{ " focus " , " WorkspaceCompose " , UIEditorViewportSlotToolSlot : : Trailing , true , false , 132.0f }
} ;
viewportModel . spec . statusSegments = {
{ " chrome-top " , std : : string ( " TopBar " ) + BoolText ( m_showTopBar ) , UIEditorStatusBarSlot : : Leading , { } , true , true , 96.0f } ,
{ " chrome-bottom " , std : : string ( " BottomBar " ) + BoolText ( m_showBottomBar ) , UIEditorStatusBarSlot : : Leading , { } , true , false , 126.0f } ,
{ " branch " , m_textureEnabled ? " Texture " : " Fallback " , UIEditorStatusBarSlot : : Trailing , { } , true , true , 100.0f }
} ;
if ( m_textureEnabled ) {
viewportModel . frame . hasTexture = true ;
viewportModel . frame . texture = { 1u , 1280u , 720u } ;
viewportModel . frame . presentedSize = UISize ( 1280.0f , 720.0f ) ;
viewportModel . frame . statusText = " Fake viewport frame " ;
} else {
viewportModel . frame . hasTexture = false ;
viewportModel . frame . statusText = " 这里只验证 WorkspaceCompose contract, 不接 Scene/Game 业务。 " ;
}
UIEditorWorkspacePanelPresentationModel model = { } ;
model . panelId = " scene " ;
model . kind = UIEditorPanelPresentationKind : : ViewportShell ;
model . viewportShellModel = std : : move ( viewportModel ) ;
return { model } ;
}
void ScenarioApp : : QueuePointerEvent ( UIInputEventType type , UIPointerButton button , WPARAM wParam , LPARAM lParam ) {
UIInputEvent event = { } ;
event . type = type ;
event . pointerButton = button ;
event . position = UIPoint (
static_cast < float > ( GET_X_LPARAM ( lParam ) ) ,
static_cast < float > ( GET_Y_LPARAM ( lParam ) ) ) ;
event . modifiers = m_inputModifierTracker . BuildPointerModifiers ( static_cast < std : : size_t > ( wParam ) ) ;
m_pendingInputEvents . push_back ( event ) ;
m_mousePosition = event . position ;
}
void ScenarioApp : : QueuePointerLeaveEvent ( ) {
UIInputEvent event = { } ;
event . type = UIInputEventType : : PointerLeave ;
if ( m_hwnd ! = nullptr ) {
POINT clientPoint = { } ;
GetCursorPos ( & clientPoint ) ;
ScreenToClient ( m_hwnd , & clientPoint ) ;
event . position = UIPoint ( static_cast < float > ( clientPoint . x ) , static_cast < float > ( clientPoint . y ) ) ;
m_mousePosition = event . position ;
}
m_pendingInputEvents . push_back ( event ) ;
}
void ScenarioApp : : QueuePointerWheelEvent ( short wheelDelta , WPARAM wParam , LPARAM lParam ) {
if ( m_hwnd = = nullptr ) {
return ;
}
POINT screenPoint = { GET_X_LPARAM ( lParam ) , GET_Y_LPARAM ( lParam ) } ;
ScreenToClient ( m_hwnd , & screenPoint ) ;
UIInputEvent event = { } ;
event . type = UIInputEventType : : PointerWheel ;
event . position = UIPoint ( static_cast < float > ( screenPoint . x ) , static_cast < float > ( screenPoint . y ) ) ;
event . wheelDelta = static_cast < float > ( wheelDelta ) ;
event . modifiers = m_inputModifierTracker . BuildPointerModifiers ( static_cast < std : : size_t > ( wParam ) ) ;
m_pendingInputEvents . push_back ( event ) ;
m_mousePosition = event . position ;
}
void ScenarioApp : : QueueFocusEvent ( UIInputEventType type ) {
UIInputEvent event = { } ;
event . type = type ;
event . modifiers = m_inputModifierTracker . GetCurrentModifiers ( ) ;
m_pendingInputEvents . push_back ( event ) ;
}
void ScenarioApp : : HandleMouseMove ( WPARAM wParam , LPARAM lParam ) {
QueuePointerEvent ( UIInputEventType : : PointerMove , UIPointerButton : : None , wParam , lParam ) ;
TRACKMOUSEEVENT event = { } ;
event . cbSize = sizeof ( event ) ;
event . dwFlags = TME_LEAVE ;
event . hwndTrack = m_hwnd ;
TrackMouseEvent ( & event ) ;
InvalidateRect ( m_hwnd , nullptr , FALSE ) ;
}
void ScenarioApp : : HandlePointerDown ( UIPointerButton button , WPARAM wParam , LPARAM lParam ) {
QueuePointerEvent ( UIInputEventType : : PointerButtonDown , button , wParam , lParam ) ;
const UIPoint point (
static_cast < float > ( GET_X_LPARAM ( lParam ) ) ,
static_cast < float > ( GET_Y_LPARAM ( lParam ) ) ) ;
const auto * viewportFrame = FindUIEditorWorkspaceViewportPresentationFrame ( m_composeFrame , " scene " ) ;
if ( viewportFrame ! = nullptr & &
ContainsPoint ( viewportFrame - > viewportShellFrame . slotLayout . inputRect , point . x , point . y ) ) {
SetCapture ( m_hwnd ) ;
}
InvalidateRect ( m_hwnd , nullptr , FALSE ) ;
}
void ScenarioApp : : HandlePointerUp ( UIPointerButton button , WPARAM wParam , LPARAM lParam ) {
QueuePointerEvent ( UIInputEventType : : PointerButtonUp , button , wParam , lParam ) ;
const UIPoint point (
static_cast < float > ( GET_X_LPARAM ( lParam ) ) ,
static_cast < float > ( GET_Y_LPARAM ( lParam ) ) ) ;
if ( GetCapture ( ) = = m_hwnd ) {
ReleaseCapture ( ) ;
}
for ( const ButtonState & buttonState : m_buttons ) {
if ( ! ContainsPoint ( buttonState . rect , point . x , point . y ) ) {
continue ;
}
ExecuteAction ( buttonState . action ) ;
break ;
}
InvalidateRect ( m_hwnd , nullptr , FALSE ) ;
}
void ScenarioApp : : ExecuteAction ( ActionId action ) {
switch ( action ) {
case ActionId : : ActivateScene :
DispatchWorkspaceCommand ( UIEditorWorkspaceCommandKind : : ActivatePanel , " scene " , " Activate Scene " ) ;
break ;
case ActionId : : ActivateDocument :
DispatchWorkspaceCommand ( UIEditorWorkspaceCommandKind : : ActivatePanel , " document " , " Activate Document " ) ;
break ;
case ActionId : : ToggleTopBar :
m_showTopBar = ! m_showTopBar ;
m_lastResult = m_showTopBar ? " TopBar 已打开 " : " TopBar 已关闭 " ;
break ;
case ActionId : : ToggleBottomBar :
m_showBottomBar = ! m_showBottomBar ;
m_lastResult = m_showBottomBar ? " BottomBar 已打开 " : " BottomBar 已关闭 " ;
break ;
case ActionId : : ToggleTexture :
m_textureEnabled = ! m_textureEnabled ;
m_lastResult = m_textureEnabled ? " 切到 Texture 分支 " : " 切到 Fallback 分支 " ;
break ;
case ActionId : : Reset :
ResetScenario ( ) ;
m_lastResult = " 状态已重置 " ;
break ;
case ActionId : : Capture :
m_autoScreenshot . RequestCapture ( " manual_button " ) ;
m_lastResult = " 截图已排队 " ;
InvalidateRect ( m_hwnd , nullptr , FALSE ) ;
UpdateWindow ( m_hwnd ) ;
break ;
}
}
void ScenarioApp : : DispatchWorkspaceCommand (
UIEditorWorkspaceCommandKind kind ,
std : : string_view panelId ,
std : : string_view label ) {
UIEditorWorkspaceCommand command = { } ;
command . kind = kind ;
command . panelId = std : : string ( panelId ) ;
const UIEditorWorkspaceCommandResult result = m_controller . Dispatch ( command ) ;
m_lastResult =
std : : string ( label ) + " -> " +
std : : string ( GetUIEditorWorkspaceCommandStatusName ( result . status ) ) +
" | " +
result . message ;
}
std : : string ScenarioApp : : GetSelectedTabPanelId ( ) const {
if ( ! m_composeFrame . dockHostLayout . tabStacks . empty ( ) ) {
return m_composeFrame . dockHostLayout . tabStacks . front ( ) . selectedPanelId ;
}
return m_controller . GetWorkspace ( ) . activePanelId ;
}
void ScenarioApp : : UpdateComposeFrame ( ) {
const auto presentations = BuildPresentationModels ( ) ;
m_composeRequest = ResolveUIEditorWorkspaceComposeRequest (
m_workspaceRect ,
m_controller . GetPanelRegistry ( ) ,
m_controller . GetWorkspace ( ) ,
m_controller . GetSession ( ) ,
presentations ) ;
std : : vector < UIInputEvent > frameEvents = std : : move ( m_pendingInputEvents ) ;
m_pendingInputEvents . clear ( ) ;
m_composeFrame = UpdateUIEditorWorkspaceCompose (
m_composeState ,
m_workspaceRect ,
m_controller . GetPanelRegistry ( ) ,
m_controller . GetWorkspace ( ) ,
m_controller . GetSession ( ) ,
presentations ,
frameEvents ) ;
const auto * viewportFrame =
FindUIEditorWorkspaceViewportPresentationFrame ( m_composeFrame , " scene " ) ;
if ( viewportFrame ! = nullptr ) {
m_hoverHit = HitTestUIEditorViewportSlot (
viewportFrame - > viewportShellFrame . slotLayout ,
m_mousePosition ) ;
} else {
m_hoverHit = { } ;
}
UpdateLastResult ( viewportFrame ) ;
}
void ScenarioApp : : UpdateLastResult (
const XCEngine : : UI : : Editor : : UIEditorWorkspaceViewportComposeFrame * viewportFrame ) {
const bool viewportVisible = viewportFrame ! = nullptr ;
if ( ! viewportVisible ) {
if ( m_hasSnapshot & & m_previousViewportVisible ) {
m_lastResult = " Viewport body 已切回 DockHost placeholder " ;
}
m_previousViewportVisible = false ;
m_previousHovered = false ;
m_previousFocused = false ;
m_previousCaptured = false ;
m_hasSnapshot = true ;
return ;
}
const auto & inputFrame = viewportFrame - > viewportShellFrame . inputFrame ;
if ( inputFrame . captureStarted ) {
m_lastResult = " CaptureStarted " ;
} else if ( inputFrame . captureEnded ) {
m_lastResult = " CaptureEnded " ;
} else if ( inputFrame . focusLost ) {
m_lastResult = " FocusLost " ;
} else if ( inputFrame . focusGained ) {
m_lastResult = " FocusGained " ;
} else if ( inputFrame . pointerPressedInside ) {
m_lastResult = " PointerDownInside " ;
} else if ( inputFrame . pointerReleasedInside ) {
m_lastResult = " PointerUpInside " ;
} else if ( inputFrame . wheelDelta ! = 0.0f ) {
m_lastResult = " Wheel " + FormatFloat ( inputFrame . wheelDelta ) ;
} else if ( m_hasSnapshot & & ! m_previousViewportVisible ) {
m_lastResult = " Viewport body 接管成功 " ;
} else if ( m_hasSnapshot & & m_previousHovered ! = inputFrame . hovered ) {
m_lastResult = std : : string ( " Hover " ) + BoolText ( inputFrame . hovered ) ;
}
m_previousViewportVisible = true ;
m_previousHovered = inputFrame . hovered ;
m_previousFocused = inputFrame . focused ;
m_previousCaptured = inputFrame . captured ;
m_hasSnapshot = true ;
}
void ScenarioApp : : RenderFrame ( ) {
if ( m_hwnd = = nullptr ) {
return ;
}
RECT clientRect = { } ;
GetClientRect ( m_hwnd , & clientRect ) ;
const float width = static_cast < float > ( std : : max ( 1L , clientRect . right - clientRect . left ) ) ;
const float height = static_cast < float > ( std : : max ( 1L , clientRect . bottom - clientRect . top ) ) ;
UpdateLayoutForCurrentWindow ( ) ;
UpdateComposeFrame ( ) ;
UpdateLayoutForCurrentWindow ( ) ;
const auto * viewportRequest =
FindUIEditorWorkspaceViewportPresentationRequest ( m_composeRequest , " scene " ) ;
const auto * viewportFrame =
FindUIEditorWorkspaceViewportPresentationFrame ( m_composeFrame , " scene " ) ;
const std : : string selectedTabId = GetSelectedTabPanelId ( ) ;
const std : : string selectedPresentation =
selectedTabId = = " scene " & & viewportFrame ! = nullptr ? " ViewportShell " : " Placeholder " ;
const auto validation = m_controller . ValidateState ( ) ;
UIDrawData drawData = { } ;
UIDrawList & drawList = drawData . EmplaceDrawList ( " WorkspaceViewportCompose " ) ;
drawList . AddFilledRect ( UIRect ( 0.0f , 0.0f , width , height ) , kWindowBg ) ;
DrawCard (
drawList ,
m_introRect ,
" 测试功能: WorkspaceCompose body 接管 " ,
" 只验证 DockHost body 被 ViewportShell 接管这条基础 contract。 " ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 66.0f ) ,
" 检查 1: 切到 Scene 时,中间 tab body 必须由 ViewportShell 接管;切到 Document 时,必须退回 DockHost placeholder。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 86.0f ) ,
" 检查 2: TopBar / BottomBar 只改变 viewport request size, 不应破坏 workspace shell 与左右占位面板。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 106.0f ) ,
" 检查 3: hover / focus / capture 只在 Scene 可见时生效,切回 Document 后不能残留脏状态。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 126.0f ) ,
" 操作:先切 Scene, hover / click / drag 右侧中间 viewport; 再切 Document, 看 body 是否恢复 placeholder。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 146.0f ) ,
" 操作:切换 TopBar / BottomBar / Texture, 观察左侧 Request Size、右侧 viewport chrome、以及 workspace 外壳是否同步。 " ,
kTextMuted ,
11.0f ) ;
DrawCard ( drawList , m_controlsRect , " 操作 " , " 这里只放当前这个 compose contract 真正需要检查的 7 个控件。 " ) ;
for ( const ButtonState & button : m_buttons ) {
DrawButton ( drawList , button ) ;
}
DrawCard ( drawList , m_stateRect , " 状态 " , " 左侧直接回显当前 compose 结果,便于人工检查。 " ) ;
float stateY = m_stateRect . y + 66.0f ;
auto addStateLine = [ & ] ( std : : string text , const UIColor & color , float fontSize = 12.0f ) {
drawList . AddText ( UIPoint ( m_stateRect . x + 16.0f , stateY ) , std : : move ( text ) , color , fontSize ) ;
stateY + = 18.0f ;
} ;
addStateLine ( " Active Panel: " + m_controller . GetWorkspace ( ) . activePanelId , kTextPrimary ) ;
addStateLine ( " Selected Tab: " + selectedTabId , kTextPrimary ) ;
addStateLine ( " Selected Presentation: " + selectedPresentation , kTextPrimary ) ;
addStateLine ( " Viewport Visible: " + BoolText ( viewportFrame ! = nullptr ) , kTextPrimary ) ;
addStateLine (
" Texture Branch: " + std : : string ( m_textureEnabled ? " Texture " : " Fallback " ) ,
kTextPrimary ) ;
addStateLine (
" Request Size: " +
( viewportRequest ! = nullptr
? FormatSize ( viewportRequest - > viewportShellRequest . requestedViewportSize )
: std : : string ( " n/a " ) ) ,
kTextPrimary ) ;
addStateLine (
" Input Rect: " +
( viewportFrame ! = nullptr
? FormatRect ( viewportFrame - > viewportShellFrame . slotLayout . inputRect )
: std : : string ( " n/a " ) ) ,
kTextMuted ,
11.0f ) ;
addStateLine (
" Hover Hit: " +
( viewportFrame ! = nullptr ? DescribeHitTarget ( m_hoverHit ) : std : : string ( " n/a " ) ) ,
kTextPrimary ) ;
addStateLine (
" Hover: " +
( viewportFrame ! = nullptr
? BoolText ( viewportFrame - > viewportShellFrame . inputFrame . hovered )
: std : : string ( " n/a " ) ) ,
kTextPrimary ) ;
addStateLine (
" Focus: " +
( viewportFrame ! = nullptr
? BoolText ( viewportFrame - > viewportShellFrame . inputFrame . focused )
: std : : string ( " n/a " ) ) ,
kTextPrimary ) ;
addStateLine (
" Capture: " +
( viewportFrame ! = nullptr
? BoolText ( viewportFrame - > viewportShellFrame . inputFrame . captured )
: std : : string ( " n/a " ) ) ,
kTextPrimary ) ;
addStateLine ( " Result: " + m_lastResult , kTextMuted ) ;
addStateLine (
validation . IsValid ( ) ? " Workspace Validation: OK " : " Workspace Validation: " + validation . message ,
validation . IsValid ( ) ? kSuccess : kDanger ,
11.0f ) ;
const std : : string captureSummary =
m_autoScreenshot . HasPendingCapture ( )
? " 截图排队中... "
: ( m_autoScreenshot . GetLastCaptureSummary ( ) . empty ( )
? std : : string ( " 截图: F12 或按钮 -> workspace_viewport_compose/captures/ " )
: m_autoScreenshot . GetLastCaptureSummary ( ) ) ;
addStateLine ( captureSummary , kTextWeak , 11.0f ) ;
DrawCard (
drawList ,
m_previewRect ,
" Preview " ,
" 这里只有一个 WorkspaceCompose 试验场,不混入任何 editor 业务面板。 " ) ;
drawList . AddFilledRect ( m_workspaceRect , kPreviewBg , 10.0f ) ;
AppendUIEditorWorkspaceCompose ( drawList , m_composeFrame ) ;
const bool framePresented = m_renderer . Render ( drawData ) ;
m_autoScreenshot . CaptureIfRequested (
m_renderer ,
drawData ,
static_cast < unsigned int > ( width ) ,
static_cast < unsigned int > ( height ) ,
framePresented ) ;
}
} // namespace
int WINAPI wWinMain ( HINSTANCE hInstance , HINSTANCE , LPWSTR , int nCmdShow ) {
return ScenarioApp ( ) . Run ( hInstance , nCmdShow ) ;
}