@@ -0,0 +1,659 @@
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <XCEditor/Core/UIEditorPanelHostLifecycle.h>
# include <XCEditor/Core/UIEditorWorkspaceController.h>
# include "Host/AutoScreenshot.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 : : UIPoint ;
using XCEngine : : UI : : UIRect ;
using XCEngine : : UI : : Editor : : BuildDefaultUIEditorWorkspaceController ;
using XCEngine : : UI : : Editor : : BuildUIEditorWorkspacePanel ;
using XCEngine : : UI : : Editor : : BuildUIEditorWorkspaceSplit ;
using XCEngine : : UI : : Editor : : BuildUIEditorWorkspaceTabStack ;
using XCEngine : : UI : : Editor : : CollectUIEditorWorkspaceVisiblePanels ;
using XCEngine : : UI : : Editor : : FindUIEditorPanelHostState ;
using XCEngine : : UI : : Editor : : GetUIEditorPanelHostLifecycleEventKindName ;
using XCEngine : : UI : : Editor : : GetUIEditorWorkspaceCommandStatusName ;
using XCEngine : : UI : : Editor : : Host : : AutoScreenshotController ;
using XCEngine : : UI : : Editor : : Host : : NativeRenderer ;
using XCEngine : : UI : : Editor : : UIEditorPanelHostLifecycleFrame ;
using XCEngine : : UI : : Editor : : UIEditorPanelHostLifecycleRequest ;
using XCEngine : : UI : : Editor : : UIEditorPanelHostLifecycleState ;
using XCEngine : : UI : : Editor : : UIEditorPanelRegistry ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceCommand ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceCommandKind ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceController ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceModel ;
using XCEngine : : UI : : Editor : : UIEditorWorkspaceSplitAxis ;
using XCEngine : : UI : : Editor : : UpdateUIEditorPanelHostLifecycle ;
constexpr const wchar_t * kWindowClassName = L " XCUIEditorPanelHostLifecycleValidation " ;
constexpr const wchar_t * kWindowTitle = L " XCUI Editor | Panel Host Lifecycle " ;
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 kSuccess ( 0.45f , 0.72f , 0.49f , 1.0f ) ;
constexpr UIColor kWarning ( 0.81f , 0.67f , 0.35f , 1.0f ) ;
constexpr UIColor kDanger ( 0.78f , 0.36f , 0.36f , 1.0f ) ;
constexpr UIColor kAccent ( 0.68f , 0.72f , 0.78f , 1.0f ) ;
constexpr UIColor kButtonBg ( 0.25f , 0.25f , 0.25f , 1.0f ) ;
constexpr UIColor kButtonHover ( 0.33f , 0.33f , 0.33f , 1.0f ) ;
constexpr UIColor kButtonBorder ( 0.46f , 0.46f , 0.46f , 1.0f ) ;
constexpr UIColor kPreviewCard ( 0.15f , 0.15f , 0.15f , 1.0f ) ;
constexpr UIColor kAttachedBorder ( 0.54f , 0.64f , 0.74f , 1.0f ) ;
constexpr UIColor kVisibleBorder ( 0.46f , 0.72f , 0.50f , 1.0f ) ;
constexpr UIColor kActiveBorder ( 0.86f , 0.76f , 0.46f , 1.0f ) ;
enum class ActionId : unsigned char {
HideActive = 0 ,
CloseDocB ,
OpenDocB ,
ActivateDetails ,
FocusActive ,
ClearFocus ,
Reset ,
Capture
} ;
struct ButtonState {
ActionId action = ActionId : : HideActive ;
std : : string label = { } ;
UIRect rect = { } ;
bool hovered = false ;
} ;
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 ( ) ;
}
UIEditorPanelRegistry BuildPanelRegistry ( ) {
UIEditorPanelRegistry registry = { } ;
registry . panels = {
{ " doc-a " , " Document A " , { } , true , true , true } ,
{ " doc-b " , " Document B " , { } , true , true , true } ,
{ " details " , " Details " , { } , true , true , true }
} ;
return registry ;
}
UIEditorWorkspaceModel BuildWorkspace ( ) {
UIEditorWorkspaceModel workspace = { } ;
workspace . root = BuildUIEditorWorkspaceSplit (
" root-split " ,
UIEditorWorkspaceSplitAxis : : Horizontal ,
0.66f ,
BuildUIEditorWorkspaceTabStack (
" document-tabs " ,
{
BuildUIEditorWorkspacePanel ( " doc-a-node " , " doc-a " , " Document A " , true ) ,
BuildUIEditorWorkspacePanel ( " doc-b-node " , " doc-b " , " Document B " , true )
} ,
0u ) ,
BuildUIEditorWorkspacePanel ( " details-node " , " details " , " Details " , true ) ) ;
workspace . activePanelId = " doc-a " ;
return workspace ;
}
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 JoinVisiblePanelIds ( const UIEditorWorkspaceController & controller ) {
const auto panels =
CollectUIEditorWorkspaceVisiblePanels ( controller . GetWorkspace ( ) , controller . GetSession ( ) ) ;
if ( panels . empty ( ) ) {
return " (none) " ;
}
std : : ostringstream stream = { } ;
for ( std : : size_t index = 0 ; index < panels . size ( ) ; + + index ) {
if ( index > 0u ) {
stream < < " , " ;
}
stream < < panels [ index ] . panelId ;
}
return stream . str ( ) ;
}
std : : string FormatBool ( bool value ) {
return value ? " true " : " false " ;
}
std : : string FormatEvents ( const UIEditorPanelHostLifecycleFrame & frame ) {
if ( frame . events . empty ( ) ) {
return " (none) " ;
}
std : : ostringstream stream = { } ;
for ( std : : size_t index = 0 ; index < frame . events . size ( ) ; + + index ) {
if ( index > 0u ) {
stream < < " | " ;
}
stream < < GetUIEditorPanelHostLifecycleEventKindName ( frame . events [ index ] . kind )
< < " : " < < frame . events [ index ] . panelId ;
}
return stream . str ( ) ;
}
UIColor ResolveFlagColor ( bool value , const UIColor & onColor ) {
return value ? onColor : kTextWeak ;
}
UIColor ResolveHostBorderColor ( bool attached , bool visible , bool active , bool focused ) {
if ( focused ) {
return kActiveBorder ;
}
if ( active ) {
return kActiveBorder ;
}
if ( visible ) {
return kVisibleBorder ;
}
if ( attached ) {
return kAttachedBorder ;
}
return kCardBorder ;
}
void DrawCard (
UIDrawList & drawList ,
const UIRect & rect ,
std : : string_view title ,
std : : string_view subtitle = { } ) {
drawList . AddFilledRect ( rect , kCardBg , 12.0f ) ;
drawList . AddRectOutline ( rect , kCardBorder , 1.0f , 12.0f ) ;
drawList . AddText ( UIPoint ( rect . x + 18.0f , rect . y + 16.0f ) , std : : string ( title ) , kTextPrimary , 17.0f ) ;
if ( ! subtitle . empty ( ) ) {
drawList . AddText ( UIPoint ( rect . x + 18.0f , rect . y + 42.0f ) , std : : string ( subtitle ) , kTextMuted , 12.0f ) ;
}
}
void DrawButton ( UIDrawList & drawList , const ButtonState & button ) {
drawList . AddFilledRect ( button . rect , button . hovered ? kButtonHover : 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 ) ;
}
void DrawHostStateCard (
UIDrawList & drawList ,
const UIRect & rect ,
std : : string_view title ,
const XCEngine : : UI : : Editor : : UIEditorPanelHostState * state ) {
const bool attached = state ! = nullptr & & state - > attached ;
const bool visible = state ! = nullptr & & state - > visible ;
const bool active = state ! = nullptr & & state - > active ;
const bool focused = state ! = nullptr & & state - > focused ;
drawList . AddFilledRect ( rect , kPreviewCard , 10.0f ) ;
drawList . AddRectOutline (
rect ,
ResolveHostBorderColor ( attached , visible , active , focused ) ,
active | | focused ? 2.0f : 1.0f ,
10.0f ) ;
drawList . AddText ( UIPoint ( rect . x + 16.0f , rect . y + 14.0f ) , std : : string ( title ) , kTextPrimary , 16.0f ) ;
float lineY = rect . y + 48.0f ;
auto addFlag = [ & ] ( std : : string_view label , bool value , const UIColor & color ) {
drawList . AddText (
UIPoint ( rect . x + 16.0f , lineY ) ,
std : : string ( label ) + " : " + ( value ? " true " : " false " ) ,
ResolveFlagColor ( value , color ) ,
12.0f ) ;
lineY + = 22.0f ;
} ;
addFlag ( " Attached " , attached , kAccent ) ;
addFlag ( " Visible " , visible , kSuccess ) ;
addFlag ( " Active " , active , kWarning ) ;
addFlag ( " Focused " , focused , kActiveBorder ) ;
std : : string footer = { } ;
if ( ! attached ) {
footer = " host 已 detached " ;
} else if ( ! visible ) {
footer = " host 保留 attach, 但当前不在屏幕上 " ;
} else if ( focused ) {
footer = " 当前 host 已 visible + active + focused " ;
} else if ( active ) {
footer = " 当前 host 已 active, 但还没 focus " ;
} else {
footer = " 当前 host 只处于 attached/visible " ;
}
drawList . AddText ( UIPoint ( rect . x + 16.0f , rect . y + rect . height - 26.0f ) , footer , kTextMuted , 11.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 - > OnResize ( static_cast < UINT > ( LOWORD ( lParam ) ) , static_cast < UINT > ( HIWORD ( lParam ) ) ) ;
}
return 0 ;
case WM_PAINT :
if ( app ! = nullptr ) {
PAINTSTRUCT paintStruct = { } ;
BeginPaint ( hwnd , & paintStruct ) ;
app - > RenderFrame ( ) ;
EndPaint ( hwnd , & paintStruct ) ;
return 0 ;
}
break ;
case WM_MOUSEMOVE :
if ( app ! = nullptr ) {
app - > HandleMouseMove (
static_cast < float > ( GET_X_LPARAM ( lParam ) ) ,
static_cast < float > ( GET_Y_LPARAM ( lParam ) ) ) ;
return 0 ;
}
break ;
case WM_LBUTTONUP :
if ( app ! = nullptr ) {
app - > HandleClick (
static_cast < float > ( GET_X_LPARAM ( lParam ) ) ,
static_cast < float > ( GET_Y_LPARAM ( lParam ) ) ) ;
return 0 ;
}
break ;
case WM_KEYDOWN :
case WM_SYSKEYDOWN :
if ( app ! = nullptr & & wParam = = VK_F12 ) {
app - > m_autoScreenshot . RequestCapture ( " manual_f12 " ) ;
return 0 ;
}
break ;
case WM_ERASEBKGND :
return 1 ;
case WM_DESTROY :
if ( app ! = nullptr ) {
app - > m_hwnd = nullptr ;
}
PostQuitMessage ( 0 ) ;
return 0 ;
default :
break ;
}
return DefWindowProcW ( hwnd , message , wParam , lParam ) ;
}
bool Initialize ( HINSTANCE hInstance , int nCmdShow ) {
m_hInstance = hInstance ;
ResetScenario ( ) ;
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 ,
1440 ,
920 ,
nullptr ,
nullptr ,
hInstance ,
this ) ;
if ( m_hwnd = = nullptr ) {
return false ;
}
ShowWindow ( m_hwnd , nCmdShow ) ;
UpdateWindow ( m_hwnd ) ;
if ( ! m_renderer . Initialize ( m_hwnd ) ) {
return false ;
}
m_captureRoot =
ResolveRepoRootPath ( ) /
" tests/UI/Editor/integration/state/panel_host_lifecycle/captures " ;
m_autoScreenshot . Initialize ( m_captureRoot ) ;
return true ;
}
void Shutdown ( ) {
m_autoScreenshot . Shutdown ( ) ;
m_renderer . Shutdown ( ) ;
if ( m_hwnd ! = nullptr ) {
DestroyWindow ( m_hwnd ) ;
m_hwnd = nullptr ;
}
if ( m_windowClassAtom ! = 0 ) {
UnregisterClassW ( kWindowClassName , m_hInstance ) ;
m_windowClassAtom = 0 ;
}
}
void ResetScenario ( ) {
m_controller =
BuildDefaultUIEditorWorkspaceController ( BuildPanelRegistry ( ) , BuildWorkspace ( ) ) ;
m_lifecycleState = { } ;
m_focusRequestPanelId . clear ( ) ;
RefreshLifecycle (
" Ready " ,
" 已重置。先看 doc-b: 它默认是 attached, 但因为不是选中 tab, 所以 visible=false。 " ) ;
}
void RefreshLifecycle ( std : : string status , std : : string message ) {
m_lifecycleFrame = UpdateUIEditorPanelHostLifecycle (
m_lifecycleState ,
m_controller . GetPanelRegistry ( ) ,
m_controller . GetWorkspace ( ) ,
m_controller . GetSession ( ) ,
UIEditorPanelHostLifecycleRequest { m_focusRequestPanelId } ) ;
m_lastStatus = std : : move ( status ) ;
m_lastMessage = std : : move ( message ) ;
}
void UpdateLayout ( ) {
RECT clientRect = { } ;
GetClientRect ( m_hwnd , & clientRect ) ;
const float width = static_cast < float > ( ( std : : max ) ( clientRect . right - clientRect . left , 1L ) ) ;
const float height = static_cast < float > ( ( std : : max ) ( clientRect . bottom - clientRect . top , 1L ) ) ;
constexpr float padding = 20.0f ;
constexpr float leftWidth = 440.0f ;
m_introRect = UIRect ( padding , padding , leftWidth , 208.0f ) ;
m_controlsRect = UIRect ( padding , 244.0f , leftWidth , 244.0f ) ;
m_stateRect = UIRect ( padding , 504.0f , leftWidth , height - 524.0f ) ;
m_previewRect = UIRect ( leftWidth + padding * 2.0f , padding , width - leftWidth - padding * 3.0f , height - padding * 2.0f ) ;
const float buttonLeft = m_controlsRect . x + 16.0f ;
const float buttonTop = m_controlsRect . y + 62.0f ;
const float buttonWidth = ( m_controlsRect . width - 32.0f - 16.0f ) * 0.5f ;
const float buttonHeight = 34.0f ;
const float rowGap = 10.0f ;
m_buttons = {
{ ActionId : : HideActive , " Hide Active " , UIRect ( buttonLeft , buttonTop , buttonWidth , buttonHeight ) , false } ,
{ ActionId : : CloseDocB , " Close Doc B " , UIRect ( buttonLeft + buttonWidth + 16.0f , buttonTop , buttonWidth , buttonHeight ) , false } ,
{ ActionId : : OpenDocB , " Open Doc B " , UIRect ( buttonLeft , buttonTop + buttonHeight + rowGap , buttonWidth , buttonHeight ) , false } ,
{ ActionId : : ActivateDetails , " Activate Details " , UIRect ( buttonLeft + buttonWidth + 16.0f , buttonTop + buttonHeight + rowGap , buttonWidth , buttonHeight ) , false } ,
{ ActionId : : FocusActive , " Focus Active " , UIRect ( buttonLeft , buttonTop + ( buttonHeight + rowGap ) * 2.0f , buttonWidth , buttonHeight ) , false } ,
{ ActionId : : ClearFocus , " Clear Focus " , UIRect ( buttonLeft + buttonWidth + 16.0f , buttonTop + ( buttonHeight + rowGap ) * 2.0f , buttonWidth , buttonHeight ) , false } ,
{ ActionId : : Reset , " Reset " , UIRect ( buttonLeft , buttonTop + ( buttonHeight + rowGap ) * 3.0f , buttonWidth , buttonHeight ) , false } ,
{ ActionId : : Capture , " Capture(F12) " , UIRect ( buttonLeft + buttonWidth + 16.0f , buttonTop + ( buttonHeight + rowGap ) * 3.0f , buttonWidth , buttonHeight ) , false }
} ;
const float previewPadding = 24.0f ;
const float hostCardGap = 18.0f ;
const float docWidth = ( m_previewRect . width - previewPadding * 2.0f - hostCardGap ) * 0.56f ;
const float sideWidth = m_previewRect . width - previewPadding * 2.0f - hostCardGap - docWidth ;
const float cardTop = m_previewRect . y + 76.0f ;
const float cardHeight = ( m_previewRect . height - 116.0f - hostCardGap ) * 0.5f ;
m_docARect = UIRect ( m_previewRect . x + previewPadding , cardTop , docWidth , cardHeight ) ;
m_docBRect = UIRect ( m_previewRect . x + previewPadding , cardTop + cardHeight + hostCardGap , docWidth , cardHeight ) ;
m_detailsRect = UIRect ( m_docARect . x + docWidth + hostCardGap , cardTop , sideWidth , cardHeight * 2.0f + hostCardGap ) ;
}
void OnResize ( UINT width , UINT height ) {
m_renderer . Resize ( width , height ) ;
InvalidateRect ( m_hwnd , nullptr , FALSE ) ;
}
void HandleMouseMove ( float x , float y ) {
UpdateLayout ( ) ;
for ( ButtonState & button : m_buttons ) {
button . hovered = ContainsPoint ( button . rect , x , y ) ;
}
}
void HandleClick ( float x , float y ) {
UpdateLayout ( ) ;
for ( const ButtonState & button : m_buttons ) {
if ( ContainsPoint ( button . rect , x , y ) ) {
ExecuteAction ( button . action ) ;
return ;
}
}
}
void ExecuteAction ( ActionId action ) {
if ( action = = ActionId : : Reset ) {
ResetScenario ( ) ;
return ;
}
if ( action = = ActionId : : Capture ) {
m_autoScreenshot . RequestCapture ( " manual_button " ) ;
m_lastStatus = " Ready " ;
m_lastMessage = " 截图已排队,输出到 tests/UI/Editor/integration/state/panel_host_lifecycle/captures/。 " ;
return ;
}
if ( action = = ActionId : : FocusActive ) {
m_focusRequestPanelId = m_controller . GetWorkspace ( ) . activePanelId ;
RefreshLifecycle (
" Focus " ,
m_focusRequestPanelId . empty ( )
? " 当前没有 active panel 可 focus。 "
: " 已把 focus request 指向当前 active panel。 " ) ;
return ;
}
if ( action = = ActionId : : ClearFocus ) {
m_focusRequestPanelId . clear ( ) ;
RefreshLifecycle ( " Focus " , " 已清空 focus request。 " ) ;
return ;
}
UIEditorWorkspaceCommand command = { } ;
switch ( action ) {
case ActionId : : HideActive :
command . kind = UIEditorWorkspaceCommandKind : : HidePanel ;
command . panelId = m_controller . GetWorkspace ( ) . activePanelId ;
break ;
case ActionId : : CloseDocB :
command . kind = UIEditorWorkspaceCommandKind : : ClosePanel ;
command . panelId = " doc-b " ;
break ;
case ActionId : : OpenDocB :
command . kind = UIEditorWorkspaceCommandKind : : OpenPanel ;
command . panelId = " doc-b " ;
break ;
case ActionId : : ActivateDetails :
command . kind = UIEditorWorkspaceCommandKind : : ActivatePanel ;
command . panelId = " details " ;
break ;
default :
return ;
}
const auto result = m_controller . Dispatch ( command ) ;
RefreshLifecycle (
std : : string ( GetUIEditorWorkspaceCommandStatusName ( result . status ) ) ,
result . message ) ;
}
void RenderFrame ( ) {
UpdateLayout ( ) ;
RECT clientRect = { } ;
GetClientRect ( m_hwnd , & clientRect ) ;
const float width = static_cast < float > ( ( std : : max ) ( clientRect . right - clientRect . left , 1L ) ) ;
const float height = static_cast < float > ( ( std : : max ) ( clientRect . bottom - clientRect . top , 1L ) ) ;
UIDrawData drawData = { } ;
UIDrawList & drawList = drawData . EmplaceDrawList ( " PanelHostLifecycle " ) ;
drawList . AddFilledRect ( UIRect ( 0.0f , 0.0f , width , height ) , kWindowBg ) ;
DrawCard ( drawList , m_introRect , " 这个测试验证什么功能 " , " 只验证 Editor panel host lifecycle contract, 不做业务面板。 " ) ;
drawList . AddText ( UIPoint ( m_introRect . x + 18.0f , m_introRect . y + 72.0f ) , " 1. 验证 attach/detach 由 panel open/close 驱动,而不是由 visible 驱动。 " , kTextPrimary , 12.0f ) ;
drawList . AddText ( UIPoint ( m_introRect . x + 18.0f , m_introRect . y + 94.0f ) , " 2. 验证非选中 tab 依然 attached, 但 visible=false。 " , kTextPrimary , 12.0f ) ;
drawList . AddText ( UIPoint ( m_introRect . x + 18.0f , m_introRect . y + 116.0f ) , " 3. 验证 active 与 focused 是两层不同状态, focus 由外部 request 决定。 " , kTextPrimary , 12.0f ) ;
drawList . AddText ( UIPoint ( m_introRect . x + 18.0f , m_introRect . y + 138.0f ) , " 4. 验证 hide/close/reopen/activate 会输出稳定的 lifecycle events。 " , kTextPrimary , 12.0f ) ;
drawList . AddText ( UIPoint ( m_introRect . x + 18.0f , m_introRect . y + 164.0f ) , " 建议操作:先看 doc-b 默认 attached 但 hidden; 再点 Hide Active / Focus Active / Close Doc B / Open Doc B。 " , kTextWeak , 11.0f ) ;
DrawCard ( drawList , m_controlsRect , " 操作 " , " 只保留这组 contract 必要的按钮。 " ) ;
for ( const ButtonState & button : m_buttons ) {
DrawButton ( drawList , button ) ;
}
DrawCard ( drawList , m_stateRect , " 状态 " , " 重点检查 host flags 和本帧 lifecycle events。 " ) ;
float stateY = m_stateRect . y + 66.0f ;
auto addStateLine = [ & ] ( std : : string label , std : : string value , const UIColor & color , float fontSize = 12.0f ) {
drawList . AddText ( UIPoint ( m_stateRect . x + 18.0f , stateY ) , std : : move ( label ) + " : " + std : : move ( value ) , color , fontSize ) ;
stateY + = 20.0f ;
} ;
addStateLine ( " Active Panel " , m_controller . GetWorkspace ( ) . activePanelId . empty ( ) ? " (none) " : m_controller . GetWorkspace ( ) . activePanelId , kTextPrimary , 11.0f ) ;
addStateLine ( " Focus Request " , m_focusRequestPanelId . empty ( ) ? " (none) " : m_focusRequestPanelId , m_focusRequestPanelId . empty ( ) ? kTextMuted : kWarning , 11.0f ) ;
addStateLine ( " Visible Panels " , JoinVisiblePanelIds ( m_controller ) , kTextPrimary , 11.0f ) ;
const UIColor resultColor =
m_lastStatus = = " Rejected " ? kDanger :
( m_lastStatus = = " Ready " ? kWarning : kSuccess ) ;
addStateLine ( " Result " , m_lastStatus , resultColor ) ;
drawList . AddText ( UIPoint ( m_stateRect . x + 18.0f , stateY + 4.0f ) , m_lastMessage , kTextMuted , 11.0f ) ;
stateY + = 32.0f ;
addStateLine ( " Events " , FormatEvents ( m_lifecycleFrame ) , kAccent , 11.0f ) ;
const auto * docA = FindUIEditorPanelHostState ( m_lifecycleFrame , " doc-a " ) ;
const auto * docB = FindUIEditorPanelHostState ( m_lifecycleFrame , " doc-b " ) ;
const auto * details = FindUIEditorPanelHostState ( m_lifecycleFrame , " details " ) ;
auto formatHostLine = [ ] ( std : : string_view panelId , const XCEngine : : UI : : Editor : : UIEditorPanelHostState * state ) {
if ( state = = nullptr ) {
return std : : string ( panelId ) + " missing " ;
}
return std : : string ( panelId ) +
" a= " + ( state - > attached ? " 1 " : " 0 " ) +
" v= " + ( state - > visible ? " 1 " : " 0 " ) +
" act= " + ( state - > active ? " 1 " : " 0 " ) +
" f= " + ( state - > focused ? " 1 " : " 0 " ) ;
} ;
addStateLine ( " doc-a " , formatHostLine ( " doc-a " , docA ) , kTextWeak , 11.0f ) ;
addStateLine ( " doc-b " , formatHostLine ( " doc-b " , docB ) , kTextWeak , 11.0f ) ;
addStateLine ( " details " , formatHostLine ( " details " , details ) , kTextWeak , 11.0f ) ;
addStateLine (
" Screenshot " ,
m_autoScreenshot . HasPendingCapture ( )
? " 截图排队中... "
: ( m_autoScreenshot . GetLastCaptureSummary ( ) . empty ( )
? std : : string ( " F12 或 Capture -> captures/ " )
: m_autoScreenshot . GetLastCaptureSummary ( ) ) ,
kTextWeak ,
11.0f ) ;
DrawCard ( drawList , m_previewRect , " Preview " , " 这里不是业务面板,只把 host state 直接可视化。 " ) ;
DrawHostStateCard ( drawList , m_docARect , " Document A " , docA ) ;
DrawHostStateCard ( drawList , m_docBRect , " Document B " , docB ) ;
DrawHostStateCard ( drawList , m_detailsRect , " Details " , details ) ;
const bool framePresented = m_renderer . Render ( drawData ) ;
m_autoScreenshot . CaptureIfRequested (
m_renderer ,
drawData ,
static_cast < unsigned int > ( width ) ,
static_cast < unsigned int > ( height ) ,
framePresented ) ;
}
HINSTANCE m_hInstance = nullptr ;
HWND m_hwnd = nullptr ;
ATOM m_windowClassAtom = 0 ;
NativeRenderer m_renderer = { } ;
AutoScreenshotController m_autoScreenshot = { } ;
std : : filesystem : : path m_captureRoot = { } ;
UIEditorWorkspaceController m_controller = { } ;
UIEditorPanelHostLifecycleState m_lifecycleState = { } ;
UIEditorPanelHostLifecycleFrame m_lifecycleFrame = { } ;
std : : string m_focusRequestPanelId = { } ;
std : : string m_lastStatus = { } ;
std : : string m_lastMessage = { } ;
UIRect m_introRect = { } ;
UIRect m_controlsRect = { } ;
UIRect m_stateRect = { } ;
UIRect m_previewRect = { } ;
UIRect m_docARect = { } ;
UIRect m_docBRect = { } ;
UIRect m_detailsRect = { } ;
std : : vector < ButtonState > m_buttons = { } ;
} ;
} // namespace
int WINAPI wWinMain ( HINSTANCE hInstance , HINSTANCE , LPWSTR , int nCmdShow ) {
return ScenarioApp ( ) . Run ( hInstance , nCmdShow ) ;
}