@@ -0,0 +1,866 @@
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <XCEditor/Core/UIEditorViewportInputBridge.h>
# include <XCEditor/Widgets/UIEditorViewportSlot.h>
# include "Host/AutoScreenshot.h"
# include "Host/InputModifierTracker.h"
# include "Host/NativeRenderer.h"
# include <XCEngine/Input/InputTypes.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 : : Input : : KeyCode ;
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 : : Editor : : Host : : AutoScreenshotController ;
using XCEngine : : UI : : Editor : : Host : : InputModifierTracker ;
using XCEngine : : UI : : Editor : : Host : : NativeRenderer ;
using XCEngine : : UI : : Editor : : IsUIEditorViewportInputBridgeKeyDown ;
using XCEngine : : UI : : Editor : : IsUIEditorViewportInputBridgePointerButtonDown ;
using XCEngine : : UI : : Editor : : UIEditorViewportInputBridgeFrame ;
using XCEngine : : UI : : Editor : : UIEditorViewportInputBridgeState ;
using XCEngine : : UI : : Editor : : UpdateUIEditorViewportInputBridge ;
using XCEngine : : UI : : Editor : : Widgets : : AppendUIEditorViewportSlotBackground ;
using XCEngine : : UI : : Editor : : Widgets : : AppendUIEditorViewportSlotForeground ;
using XCEngine : : UI : : Editor : : Widgets : : BuildUIEditorViewportSlotLayout ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorStatusBarSegment ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorStatusBarSlot ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotChrome ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotFrame ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotLayout ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotState ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotToolItem ;
using XCEngine : : UI : : Editor : : Widgets : : UIEditorViewportSlotToolSlot ;
constexpr const wchar_t * kWindowClassName = L " XCUIEditorViewportInputBridgeBasicValidation " ;
constexpr const wchar_t * kWindowTitle = L " XCUI Editor | Viewport Input Bridge " ;
constexpr UIColor kWindowBg ( 0.12f , 0.12f , 0.12f , 1.0f ) ;
constexpr UIColor kCardBg ( 0.18f , 0.18f , 0.18f , 1.0f ) ;
constexpr UIColor kCardBorder ( 0.29f , 0.29f , 0.29f , 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.25f , 0.25f , 0.25f , 1.0f ) ;
constexpr UIColor kButtonBorder ( 0.47f , 0.47f , 0.47f , 1.0f ) ;
constexpr UIColor kPreviewBg ( 0.15f , 0.15f , 0.15f , 1.0f ) ;
enum class ActionId : unsigned char {
Reset = 0 ,
Capture
} ;
struct ButtonState {
ActionId action = ActionId : : Reset ;
std : : string label = { } ;
UIRect rect = { } ;
} ;
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 FormatPoint ( const UIPoint & point ) {
return " ( " + FormatFloat ( point . x ) + " , " + FormatFloat ( point . y ) + " ) " ;
}
std : : string DescribeKeyCode ( std : : int32_t keyCode ) {
switch ( static_cast < KeyCode > ( keyCode ) ) {
case KeyCode : : A : return " A " ;
case KeyCode : : D : return " D " ;
case KeyCode : : F : return " F " ;
case KeyCode : : Q : return " Q " ;
case KeyCode : : S : return " S " ;
case KeyCode : : W : return " W " ;
case KeyCode : : Space : return " Space " ;
case KeyCode : : LeftShift : return " LeftShift " ;
case KeyCode : : LeftCtrl : return " LeftCtrl " ;
case KeyCode : : LeftAlt : return " LeftAlt " ;
case KeyCode : : Escape : return " Escape " ;
case KeyCode : : Enter : return " Enter " ;
case KeyCode : : Left : return " Left " ;
case KeyCode : : Right : return " Right " ;
case KeyCode : : Up : return " Up " ;
case KeyCode : : Down : return " Down " ;
case KeyCode : : F12 : return " F12 " ;
case KeyCode : : None :
default :
return std : : to_string ( keyCode ) ;
}
}
std : : string FormatKeyCodes ( const std : : vector < std : : int32_t > & keyCodes ) {
if ( keyCodes . empty ( ) ) {
return " (none) " ;
}
std : : string result = { } ;
for ( std : : size_t index = 0u ; index < keyCodes . size ( ) ; + + index ) {
if ( index > 0u ) {
result + = " , " ;
}
result + = DescribeKeyCode ( keyCodes [ index ] ) ;
}
return result ;
}
std : : string DescribeButtonStates ( const UIEditorViewportInputBridgeState & state ) {
return std : : string ( " L= " ) +
BoolText ( IsUIEditorViewportInputBridgePointerButtonDown ( state , UIPointerButton : : Left ) ) +
" R= " +
BoolText ( IsUIEditorViewportInputBridgePointerButtonDown ( state , UIPointerButton : : Right ) ) +
" M= " +
BoolText ( IsUIEditorViewportInputBridgePointerButtonDown ( state , UIPointerButton : : Middle ) ) ;
}
std : : string DescribeKeyStates ( const UIEditorViewportInputBridgeState & state ) {
return std : : string ( " W= " ) +
BoolText ( IsUIEditorViewportInputBridgeKeyDown ( state , static_cast < std : : int32_t > ( KeyCode : : W ) ) ) +
" A= " +
BoolText ( IsUIEditorViewportInputBridgeKeyDown ( state , static_cast < std : : int32_t > ( KeyCode : : A ) ) ) +
" F= " +
BoolText ( IsUIEditorViewportInputBridgeKeyDown ( state , static_cast < std : : int32_t > ( KeyCode : : F ) ) ) +
" Space= " +
BoolText ( IsUIEditorViewportInputBridgeKeyDown ( state , static_cast < std : : int32_t > ( KeyCode : : Space ) ) ) ;
}
std : : string DescribeCharacters ( const UIEditorViewportInputBridgeFrame & frame ) {
if ( frame . characters . empty ( ) ) {
return " (none) " ;
}
std : : string result = { } ;
for ( std : : size_t index = 0u ; index < frame . characters . size ( ) ; + + index ) {
if ( index > 0u ) {
result + = " , " ;
}
const std : : uint32_t character = frame . characters [ index ] ;
if ( character > = 32u & & character < 127u ) {
result . push_back ( static_cast < char > ( character ) ) ;
} else {
result + = " U+ " + std : : to_string ( character ) ;
}
}
return result ;
}
std : : int32_t MapVirtualKeyToUIKeyCode ( WPARAM wParam ) {
switch ( wParam ) {
case ' A ' : return static_cast < std : : int32_t > ( KeyCode : : A ) ;
case ' D ' : return static_cast < std : : int32_t > ( KeyCode : : D ) ;
case ' F ' : return static_cast < std : : int32_t > ( KeyCode : : F ) ;
case ' Q ' : return static_cast < std : : int32_t > ( KeyCode : : Q ) ;
case ' S ' : return static_cast < std : : int32_t > ( KeyCode : : S ) ;
case ' W ' : return static_cast < std : : int32_t > ( KeyCode : : W ) ;
case VK_SPACE : return static_cast < std : : int32_t > ( KeyCode : : Space ) ;
case VK_SHIFT : return static_cast < std : : int32_t > ( KeyCode : : LeftShift ) ;
case VK_CONTROL : return static_cast < std : : int32_t > ( KeyCode : : LeftCtrl ) ;
case VK_MENU : return static_cast < std : : int32_t > ( KeyCode : : LeftAlt ) ;
case VK_ESCAPE : return static_cast < std : : int32_t > ( KeyCode : : Escape ) ;
case VK_RETURN : return static_cast < std : : int32_t > ( KeyCode : : Enter ) ;
case VK_UP : return static_cast < std : : int32_t > ( KeyCode : : Up ) ;
case VK_DOWN : return static_cast < std : : int32_t > ( KeyCode : : Down ) ;
case VK_LEFT : return static_cast < std : : int32_t > ( KeyCode : : Left ) ;
case VK_RIGHT : return static_cast < std : : int32_t > ( KeyCode : : Right ) ;
case VK_F12 : return static_cast < std : : int32_t > ( KeyCode : : F12 ) ;
default : return static_cast < std : : int32_t > ( KeyCode : : 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 , 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 ) ;
}
class ScenarioApp {
public :
int 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 ) ;
}
private :
static LRESULT CALLBACK 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 - > QueuePointerEvent ( UIInputEventType : : PointerMove , UIPointerButton : : None , wParam , lParam ) ;
TRACKMOUSEEVENT event = { } ;
event . cbSize = sizeof ( event ) ;
event . dwFlags = TME_LEAVE ;
event . hwndTrack = hwnd ;
TrackMouseEvent ( & event ) ;
return 0 ;
}
break ;
case WM_MOUSELEAVE :
if ( app ! = nullptr ) {
app - > QueuePointerLeaveEvent ( ) ;
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_RBUTTONDOWN :
if ( app ! = nullptr ) {
SetFocus ( hwnd ) ;
app - > HandlePointerDown ( UIPointerButton : : Right , wParam , lParam ) ;
return 0 ;
}
break ;
case WM_RBUTTONUP :
if ( app ! = nullptr ) {
app - > HandlePointerUp ( UIPointerButton : : Right , wParam , lParam ) ;
return 0 ;
}
break ;
case WM_MBUTTONDOWN :
if ( app ! = nullptr ) {
SetFocus ( hwnd ) ;
app - > HandlePointerDown ( UIPointerButton : : Middle , wParam , lParam ) ;
return 0 ;
}
break ;
case WM_MBUTTONUP :
if ( app ! = nullptr ) {
app - > HandlePointerUp ( UIPointerButton : : Middle , wParam , lParam ) ;
return 0 ;
}
break ;
case WM_MOUSEWHEEL :
if ( app ! = nullptr ) {
app - > QueuePointerWheelEvent ( GET_WHEEL_DELTA_WPARAM ( wParam ) , wParam , lParam ) ;
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 ) {
if ( wParam = = VK_F12 ) {
app - > m_autoScreenshot . RequestCapture ( " manual_f12 " ) ;
InvalidateRect ( hwnd , nullptr , FALSE ) ;
UpdateWindow ( hwnd ) ;
return 0 ;
}
app - > QueueKeyEvent ( UIInputEventType : : KeyDown , wParam , lParam ) ;
return 0 ;
}
break ;
case WM_KEYUP :
case WM_SYSKEYUP :
if ( app ! = nullptr ) {
app - > QueueKeyEvent ( UIInputEventType : : KeyUp , wParam , lParam ) ;
return 0 ;
}
break ;
case WM_CHAR :
if ( app ! = nullptr ) {
app - > QueueCharacterEvent ( wParam ) ;
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 Initialize ( HINSTANCE hInstance , int nCmdShow ) {
m_captureRoot =
ResolveRepoRootPath ( ) / " tests/UI/Editor/integration/state/viewport_input_bridge_basic/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 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 ResetScenario ( ) {
m_inputModifierTracker . Reset ( ) ;
m_bridgeState = { } ;
m_bridgeFrame = { } ;
m_pendingEvents . clear ( ) ;
m_lastResult = " Ready " ;
}
void 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_pendingEvents . push_back ( event ) ;
}
void 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_pendingEvents . push_back ( event ) ;
}
void 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_pendingEvents . push_back ( event ) ;
}
void QueueKeyEvent ( UIInputEventType type , WPARAM wParam , LPARAM lParam ) {
UIInputEvent event = { } ;
event . type = type ;
event . keyCode = MapVirtualKeyToUIKeyCode ( wParam ) ;
event . modifiers = m_inputModifierTracker . ApplyKeyMessage ( type , wParam , lParam ) ;
event . repeat = ( static_cast < std : : uint32_t > ( lParam ) & 0x40000000u ) ! = 0u ;
m_pendingEvents . push_back ( event ) ;
}
void QueueCharacterEvent ( WPARAM wParam ) {
UIInputEvent event = { } ;
event . type = UIInputEventType : : Character ;
event . character = static_cast < std : : uint32_t > ( wParam ) ;
event . modifiers = m_inputModifierTracker . GetCurrentModifiers ( ) ;
m_pendingEvents . push_back ( event ) ;
}
void QueueFocusEvent ( UIInputEventType type ) {
UIInputEvent event = { } ;
event . type = type ;
event . modifiers = m_inputModifierTracker . GetCurrentModifiers ( ) ;
m_pendingEvents . push_back ( event ) ;
}
void 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 ) ) ) ;
if ( ContainsPoint ( m_layout . inputRect , point . x , point . y ) ) {
SetCapture ( m_hwnd ) ;
}
}
void 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 ) ) ) ;
for ( const ButtonState & buttonState : m_buttons ) {
if ( ! ContainsPoint ( buttonState . rect , point . x , point . y ) ) {
continue ;
}
if ( buttonState . action = = ActionId : : Reset ) {
ResetScenario ( ) ;
m_lastResult = " 状态已重置 " ;
} else {
m_autoScreenshot . RequestCapture ( " manual_button " ) ;
InvalidateRect ( m_hwnd , nullptr , FALSE ) ;
UpdateWindow ( m_hwnd ) ;
m_lastResult = " 截图已排队 " ;
}
break ;
}
if ( GetCapture ( ) = = m_hwnd ) {
ReleaseCapture ( ) ;
}
}
UIEditorViewportSlotChrome BuildChrome ( ) const {
UIEditorViewportSlotChrome chrome = { } ;
chrome . title = " Scene View " ;
chrome . subtitle = " ViewportInputBridge 验证面板 " ;
chrome . showTopBar = true ;
chrome . showBottomBar = true ;
chrome . topBarHeight = 40.0f ;
chrome . bottomBarHeight = 28.0f ;
return chrome ;
}
UIEditorViewportSlotFrame BuildFrame ( ) const {
UIEditorViewportSlotFrame frame = { } ;
frame . hasTexture = true ;
frame . texture = { 1u , 1280u , 720u } ;
frame . presentedSize = { 1280.0f , 720.0f } ;
frame . statusText = " Fake viewport frame " ;
return frame ;
}
std : : vector < UIEditorViewportSlotToolItem > BuildToolItems ( ) const {
return {
{ " mode " , " Perspective " , UIEditorViewportSlotToolSlot : : Leading , true , true , 98.0f } ,
{ " input " , " Input " , UIEditorViewportSlotToolSlot : : Trailing , true , true , 58.0f }
} ;
}
std : : vector < UIEditorStatusBarSegment > BuildStatusSegments ( ) const {
return {
{ " hover " , std : : string ( " Hover " ) + BoolText ( m_bridgeFrame . hovered ) , UIEditorStatusBarSlot : : Leading , { } , true , true , 96.0f } ,
{ " focus " , std : : string ( " Focus " ) + BoolText ( m_bridgeFrame . focused ) , UIEditorStatusBarSlot : : Leading , { } , true , false , 92.0f } ,
{ " capture " , std : : string ( " Capture " ) + BoolText ( m_bridgeFrame . captured ) , UIEditorStatusBarSlot : : Trailing , { } , true , true , 110.0f } ,
{ " wheel " , " Wheel " + FormatFloat ( m_bridgeFrame . wheelDelta ) , UIEditorStatusBarSlot : : Trailing , { } , true , false , 86.0f }
} ;
}
void 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 ) ) ;
const float leftColumnWidth = 440.0f ;
const float outerPadding = 20.0f ;
m_introRect = UIRect ( outerPadding , outerPadding , leftColumnWidth , 246.0f ) ;
m_controlsRect = UIRect ( outerPadding , 286.0f , leftColumnWidth , 144.0f ) ;
m_stateRect = UIRect ( outerPadding , 450.0f , leftColumnWidth , height - 470.0f ) ;
m_previewRect = UIRect (
leftColumnWidth + outerPadding * 2.0f ,
outerPadding ,
width - leftColumnWidth - outerPadding * 3.0f ,
height - outerPadding * 2.0f ) ;
m_slotRect = UIRect (
m_previewRect . x + 18.0f ,
m_previewRect . y + 18.0f ,
m_previewRect . width - 36.0f ,
m_previewRect . height - 36.0f ) ;
const float buttonHeight = 34.0f ;
m_buttons = {
{ ActionId : : Reset , " Reset " , UIRect ( m_controlsRect . x + 16.0f , m_controlsRect . y + 54.0f , m_controlsRect . width - 32.0f , buttonHeight ) } ,
{ ActionId : : Capture , " 截图 " , UIRect ( m_controlsRect . x + 16.0f , m_controlsRect . y + 98.0f , m_controlsRect . width - 32.0f , buttonHeight ) }
} ;
m_layout = BuildUIEditorViewportSlotLayout (
m_slotRect ,
BuildChrome ( ) ,
BuildFrame ( ) ,
BuildToolItems ( ) ,
BuildStatusSegments ( ) ) ;
}
void 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 ( ) ;
std : : vector < UIInputEvent > frameEvents = std : : move ( m_pendingEvents ) ;
m_pendingEvents . clear ( ) ;
m_bridgeFrame = UpdateUIEditorViewportInputBridge (
m_bridgeState ,
m_layout . inputRect ,
frameEvents ) ;
if ( m_bridgeFrame . focusLost ) {
m_lastResult = " FocusLost " ;
} else if ( m_bridgeFrame . focusGained ) {
m_lastResult = " FocusGained " ;
} else if ( m_bridgeFrame . captureStarted ) {
m_lastResult = " CaptureStarted " ;
} else if ( m_bridgeFrame . captureEnded ) {
m_lastResult = " CaptureEnded " ;
} else if ( ! m_bridgeFrame . characters . empty ( ) ) {
m_lastResult = " Char " + DescribeCharacters ( m_bridgeFrame ) ;
} else if ( ! m_bridgeFrame . pressedKeyCodes . empty ( ) ) {
m_lastResult = " KeyDown " + FormatKeyCodes ( m_bridgeFrame . pressedKeyCodes ) ;
} else if ( ! m_bridgeFrame . releasedKeyCodes . empty ( ) ) {
m_lastResult = " KeyUp " + FormatKeyCodes ( m_bridgeFrame . releasedKeyCodes ) ;
} else if ( m_bridgeFrame . wheelDelta ! = 0.0f ) {
m_lastResult = " Wheel " + std : : to_string ( static_cast < int > ( m_bridgeFrame . wheelDelta ) ) ;
} else if ( m_bridgeFrame . pointerPressedInside ) {
m_lastResult = " PointerDownInside " ;
} else if ( m_bridgeFrame . pointerReleasedInside ) {
m_lastResult = " PointerUpInside " ;
}
UIEditorViewportSlotState slotState = { } ;
slotState . focused = m_bridgeFrame . focused ;
slotState . surfaceHovered = m_bridgeFrame . hovered ;
slotState . surfaceActive = m_bridgeFrame . focused | | m_bridgeFrame . captured ;
slotState . inputCaptured = m_bridgeFrame . captured ;
UIDrawData drawData = { } ;
UIDrawList & drawList = drawData . EmplaceDrawList ( " ViewportInputBridgeBasic " ) ;
drawList . AddFilledRect ( UIRect ( 0.0f , 0.0f , width , height ) , kWindowBg ) ;
DrawCard (
drawList ,
m_introRect ,
" 测试功能: ViewportInputBridge " ,
" 只验证 Editor viewport 输入桥,不混入 Scene/Game 业务。 " ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 66.0f ) ,
" 检查1: hover / focus / capture / local 坐标。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 86.0f ) ,
" 检查2: surface 内左键按下后, Focus + Capture 进入。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 106.0f ) ,
" 检查3: drag 出 surface 后, Capture 保留, Local Pos 继续更新。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 126.0f ) ,
" 检查4: release 后, Capture 必须释放。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 146.0f ) ,
" 检查5: 滚轮 / 按键 / 字符只在 Focus 下进入 frame。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 166.0f ) ,
" 操作: hover, click, drag, wheel, A/W/F/Space, 输入字符。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 186.0f ) ,
" 操作:点击 surface 外部,验证 clear focus。 " ,
kTextMuted ,
11.0f ) ;
drawList . AddText (
UIPoint ( m_introRect . x + 16.0f , m_introRect . y + 210.0f ) ,
" 结果: 左侧状态、底部条、surface 边框必须同步。 " ,
kTextWeak ,
11.0f ) ;
DrawCard ( drawList , m_controlsRect , " 操作 " , " 只保留 Reset / 截图两个辅助操作。 " ) ;
for ( const ButtonState & button : m_buttons ) {
DrawButton ( drawList , button ) ;
}
DrawCard ( drawList , m_stateRect , " 状态 " , " 重点看桥接 frame、按键边沿、字符与 modifiers。 " ) ;
float stateY = m_stateRect . y + 66.0f ;
auto addStateLine = [ & ] ( std : : string text , const UIColor & color = kTextPrimary ) {
drawList . AddText ( UIPoint ( m_stateRect . x + 16.0f , stateY ) , std : : move ( text ) , color , 12.0f ) ;
stateY + = 22.0f ;
} ;
addStateLine ( " Hover: " + BoolText ( m_bridgeFrame . hovered ) ) ;
addStateLine ( " Focus: " + BoolText ( m_bridgeFrame . focused ) ) ;
addStateLine ( " Capture: " + BoolText ( m_bridgeFrame . captured ) ) ;
addStateLine ( " Pointer Inside: " + BoolText ( m_bridgeFrame . pointerInside ) ) ;
addStateLine ( " Screen Pos: " + FormatPoint ( m_bridgeFrame . screenPointerPosition ) ) ;
addStateLine ( " Local Pos: " + FormatPoint ( m_bridgeFrame . localPointerPosition ) ) ;
addStateLine ( " Delta: " + FormatPoint ( m_bridgeFrame . pointerDelta ) ) ;
addStateLine ( " Wheel: " + FormatFloat ( m_bridgeFrame . wheelDelta ) ) ;
addStateLine ( " Buttons Down: " + DescribeButtonStates ( m_bridgeState ) ) ;
addStateLine ( " Keys Down: " + DescribeKeyStates ( m_bridgeState ) ) ;
addStateLine (
" Edges: pressIn " + BoolText ( m_bridgeFrame . pointerPressedInside ) +
" | releaseIn " + BoolText ( m_bridgeFrame . pointerReleasedInside ) ) ;
addStateLine (
" Focus/Capture Edges: gain " + BoolText ( m_bridgeFrame . focusGained ) +
" | lost " + BoolText ( m_bridgeFrame . focusLost ) +
" | cap+ " + BoolText ( m_bridgeFrame . captureStarted ) +
" | cap- " + BoolText ( m_bridgeFrame . captureEnded ) ) ;
addStateLine (
std : : string ( " Modifiers: Ctrl " ) + BoolText ( m_bridgeFrame . modifiers . control ) +
" Shift " + BoolText ( m_bridgeFrame . modifiers . shift ) +
" Alt " + BoolText ( m_bridgeFrame . modifiers . alt ) ) ;
addStateLine ( " Characters: " + DescribeCharacters ( m_bridgeFrame ) , kTextMuted ) ;
const std : : string captureSummary =
m_autoScreenshot . HasPendingCapture ( )
? " 截图排队中... "
: ( m_autoScreenshot . GetLastCaptureSummary ( ) . empty ( )
? std : : string ( " 截图: F12 或按钮 -> viewport_input_bridge_basic/captures/ " )
: m_autoScreenshot . GetLastCaptureSummary ( ) ) ;
addStateLine ( " Result: " + m_lastResult , kTextMuted ) ;
addStateLine ( captureSummary , kTextWeak ) ;
DrawCard ( drawList , m_previewRect , " Preview " , " 这里只放一个 ViewportSlot, 用它承载输入边界。 " ) ;
drawList . AddFilledRect (
UIRect (
m_previewRect . x + 12.0f ,
m_previewRect . y + 44.0f ,
m_previewRect . width - 24.0f ,
m_previewRect . height - 56.0f ) ,
kPreviewBg ,
10.0f ) ;
const auto chrome = BuildChrome ( ) ;
const auto frame = BuildFrame ( ) ;
const auto toolItems = BuildToolItems ( ) ;
const auto statusSegments = BuildStatusSegments ( ) ;
m_layout = BuildUIEditorViewportSlotLayout (
m_slotRect ,
chrome ,
frame ,
toolItems ,
statusSegments ) ;
AppendUIEditorViewportSlotBackground (
drawList ,
m_layout ,
toolItems ,
statusSegments ,
slotState ) ;
AppendUIEditorViewportSlotForeground (
drawList ,
m_layout ,
chrome ,
frame ,
toolItems ,
statusSegments ,
slotState ) ;
const bool framePresented = m_renderer . Render ( drawData ) ;
m_autoScreenshot . CaptureIfRequested (
m_renderer ,
drawData ,
static_cast < unsigned int > ( width ) ,
static_cast < unsigned int > ( height ) ,
framePresented ) ;
}
HWND m_hwnd = nullptr ;
ATOM m_windowClassAtom = 0 ;
NativeRenderer m_renderer = { } ;
AutoScreenshotController m_autoScreenshot = { } ;
InputModifierTracker m_inputModifierTracker = { } ;
std : : filesystem : : path m_captureRoot = { } ;
std : : vector < UIInputEvent > m_pendingEvents = { } ;
std : : vector < ButtonState > m_buttons = { } ;
UIEditorViewportInputBridgeState m_bridgeState = { } ;
UIEditorViewportInputBridgeFrame m_bridgeFrame = { } ;
UIRect m_introRect = { } ;
UIRect m_controlsRect = { } ;
UIRect m_stateRect = { } ;
UIRect m_previewRect = { } ;
UIRect m_slotRect = { } ;
UIEditorViewportSlotLayout m_layout = { } ;
std : : string m_lastResult = { } ;
} ;
} // namespace
int WINAPI wWinMain ( HINSTANCE hInstance , HINSTANCE , LPWSTR , int nCmdShow ) {
return ScenarioApp ( ) . Run ( hInstance , nCmdShow ) ;
}