Files
UnrealEngine/Engine/Source/Developer/DesktopPlatform/Private/Windows/WindowsNativeFeedbackContext.cpp
2025-05-18 13:04:45 +08:00

372 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "WindowsNativeFeedbackContext.h"
#include "HAL/ThreadHeartBeat.h"
#include "Logging/StructuredLog.h"
#include "Misc/CoreMisc.h"
#include "Misc/OutputDeviceHelper.h"
#include "Misc/OutputDeviceConsole.h"
#include "Misc/OutputDeviceRedirector.h"
#include "Misc/App.h"
#include "Internationalization/Internationalization.h"
#include "Windows/WindowsPlatformApplicationMisc.h"
#include "Windows/AllowWindowsPlatformTypes.h"
FWindowsNativeFeedbackContext::FWindowsNativeFeedbackContext()
: FFeedbackContext()
, Context( NULL )
, hThread( NULL )
, hCloseEvent( NULL )
, hUpdateEvent( NULL )
, Progress( 0.0f )
, bReceivedUserCancel( false )
, bShowCancelButton( false )
{
}
FWindowsNativeFeedbackContext::~FWindowsNativeFeedbackContext()
{
DestroySlowTaskWindow();
}
void FWindowsNativeFeedbackContext::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category)
{
Serialize(V, Verbosity, Category, -1.0);
}
void FWindowsNativeFeedbackContext::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category, double Time)
{
FFeedbackContext::Serialize(V, Verbosity, Category, Time);
// Buffer up the output during a slow task so that we can dump it all to the log console if the show log button is clicked
if (GIsSlowTask)
{
FScopeLock Lock(&CriticalSection);
if (hThread)
{
LogOutput += V;
LogOutput += TEXT("\r\n");
SetEvent(hUpdateEvent);
}
}
}
void FWindowsNativeFeedbackContext::SerializeRecord(const UE::FLogRecord& Record)
{
FFeedbackContext::SerializeRecord(Record);
// Buffer up the output during a slow task so that we can dump it all to the log console if the show log button is clicked
if (GIsSlowTask)
{
TStringBuilder<512> Message;
Record.FormatMessageTo(Message);
Message.Append(TEXTVIEW("\r\n"));
FScopeLock Lock(&CriticalSection);
if (hThread)
{
LogOutput += Message.ToView();
SetEvent(hUpdateEvent);
}
}
}
bool FWindowsNativeFeedbackContext::ReceivedUserCancel()
{
FScopeLock Lock(&CriticalSection);
return bReceivedUserCancel;
}
void FWindowsNativeFeedbackContext::StartSlowTask( const FText& Task, bool bShouldShowCancelButton )
{
FFeedbackContext::StartSlowTask( Task, bShouldShowCancelButton );
CreateSlowTaskWindow(Task, bShouldShowCancelButton);
}
void FWindowsNativeFeedbackContext::FinalizeSlowTask( )
{
FFeedbackContext::FinalizeSlowTask( );
DestroySlowTaskWindow();
}
void FWindowsNativeFeedbackContext::ProgressReported( const float TotalProgressInterp, FText DisplayMessage )
{
FScopeLock Lock(&CriticalSection);
if(hThread != NULL)
{
Progress = TotalProgressInterp;
Status = DisplayMessage.ToString();
SetEvent(hUpdateEvent);
}
}
FContextSupplier* FWindowsNativeFeedbackContext::GetContext() const
{
return Context;
}
void FWindowsNativeFeedbackContext::SetContext( FContextSupplier* InSupplier )
{
Context = InSupplier;
}
void FWindowsNativeFeedbackContext::CreateSlowTaskWindow(const FText &InStatus, bool bInShowCancelButton)
{
FScopeLock Lock(&CriticalSection);
if(hThread == NULL && !GIsSilent && !FApp::IsUnattended() && !IsRunningCommandlet())
{
Status = InStatus.ToString();
Progress = 0.0f;
LogOutput.Empty();
bReceivedUserCancel = false;
bShowCancelButton = bInShowCancelButton;
hCloseEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
hUpdateEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
hThread = CreateThread(NULL, 0, &SlowTaskThreadProc, this, 0, NULL);
}
}
void FWindowsNativeFeedbackContext::DestroySlowTaskWindow()
{
FScopeLock Lock(&CriticalSection);
if(hThread != NULL)
{
SetEvent(hCloseEvent);
CriticalSection.Unlock();
WaitForSingleObject(hThread, INFINITE);
CriticalSection.Lock();
CloseHandle(hThread);
hThread = NULL;
CloseHandle(hCloseEvent);
hCloseEvent = NULL;
CloseHandle(hUpdateEvent);
hUpdateEvent = NULL;
LogOutput.Empty();
}
}
DWORD FWindowsNativeFeedbackContext::SlowTaskThreadProc(void* ThreadParam)
{
FWindowsNativeFeedbackContext* Context = (FWindowsNativeFeedbackContext*)ThreadParam;
HINSTANCE HInstance = (HINSTANCE)GetModuleHandle(NULL);
extern APPLICATIONCORE_API HWND GetSplashScreenWindowHandle();
HWND ParentWindowHandle = GetSplashScreenWindowHandle();
WNDCLASSEX WndClassEx;
ZeroMemory(&WndClassEx, sizeof(WndClassEx));
WndClassEx.cbSize = sizeof(WndClassEx);
WndClassEx.style = CS_HREDRAW | CS_VREDRAW | (Context->bShowCancelButton? 0 : CS_NOCLOSE);
WndClassEx.lpfnWndProc = &SlowTaskWindowProc;
WndClassEx.hIcon = LoadIcon(HInstance, MAKEINTRESOURCE(FWindowsPlatformApplicationMisc::GetAppIcon()));
WndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClassEx.hInstance = HInstance;
WndClassEx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
WndClassEx.lpszClassName = TEXT("FFeedbackContextWindows");
ATOM WndClassAtom = RegisterClassEx(&WndClassEx);
NONCLIENTMETRICS NonClientMetrics;
NonClientMetrics.cbSize = sizeof(NonClientMetrics);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NonClientMetrics), &NonClientMetrics, 0);
HANDLE hFont = CreateFontIndirect(&NonClientMetrics.lfMessageFont);
int FontHeight = -MulDiv(8, GetDeviceCaps(GetDC(NULL), LOGPIXELSY), 72);
HANDLE hLogFont = CreateFontW(FontHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FIXED_PITCH | FF_MODERN, TEXT("Courier New"));
TEXTMETRIC TextMetric;
HDC hDC = CreateCompatibleDC(NULL);
HGDIOBJ hPrevObj = SelectObject(hDC, hFont);
GetTextMetrics(hDC, &TextMetric);
SelectObject(hDC, hPrevObj);
DeleteDC(hDC);
FWindowParams Params;
Params.Context = Context;
Params.ScaleX = TextMetric.tmAveCharWidth;
Params.ScaleY = TextMetric.tmHeight;
Params.StandardW = Params.ScaleX * 80;
Params.StandardH = Params.ScaleY * 4;
Params.bLogVisible = false;
DWORD WindowStyle = WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME;
RECT WindowRect;
ZeroMemory(&WindowRect, sizeof(WindowRect));
WindowRect.left = (GetSystemMetrics(SM_CXSCREEN) - Params.StandardW) / 2;
WindowRect.top = (GetSystemMetrics(SM_CYSCREEN) - Params.StandardH) / 2;
WindowRect.right = WindowRect.left + Params.StandardW;
WindowRect.bottom = WindowRect.top + Params.StandardH;
AdjustWindowRectEx(&WindowRect, WindowStyle, 0, 0);
const TCHAR* WindowClassName = MAKEINTATOM( WndClassAtom );
HWND hWnd = CreateWindow(WindowClassName, TEXT("Unreal Engine"), WindowStyle, WindowRect.left, WindowRect.top, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, ParentWindowHandle, NULL, HInstance, NULL);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)&Params);
SendMessageW(hWnd, WM_SETFONT, (WPARAM)hFont, 0);
HWND hWndOpenLog = CreateWindow(WC_BUTTON, TEXT("Show log"), BS_CENTER | BS_VCENTER | BS_PUSHBUTTON | BS_TEXT | WS_CHILD | WS_VISIBLE, 10, 10, 10, 10, hWnd, (HMENU)ShowLogCtlId, HInstance, NULL);
SendMessageW(hWndOpenLog, WM_SETFONT, (WPARAM)hFont, 0);
HWND hWndStatus = CreateWindow(WC_STATIC, TEXT(""), SS_CENTER | WS_CHILD | WS_VISIBLE, 10, 10, 10, 10, hWnd, (HMENU)StatusCtlId, HInstance, NULL);
SendMessageW(hWndStatus, WM_SETFONT, (WPARAM)hFont, 0);
HWND hWndProgress = CreateWindowEx(0, PROGRESS_CLASS, TEXT(""), WS_CHILD | WS_VISIBLE, 10, 10, 10, 10, hWnd, (HMENU)ProgressCtlId, HInstance, NULL);
SendMessageW(hWndProgress, PBM_SETRANGE32, 0, 1000);
HWND hWndLogOutput = CreateWindowEx(WS_EX_STATICEDGE, WC_EDIT, TEXT(""), ES_MULTILINE | ES_READONLY | WS_HSCROLL | WS_VSCROLL | WS_CHILD | WS_VISIBLE, 10, 10, 10, 10, hWnd, (HMENU)LogOutputCtlId, HInstance, NULL);
SendMessageW(hWndLogOutput, WM_SETFONT, (WPARAM)hLogFont, 0);
LayoutControls(hWnd, &Params);
SetEvent(Context->hUpdateEvent);
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
SetForegroundWindow(hWnd);
FString PrevStatus;
float PrevProgress = 0.0f;
int32 PrevLogOutputLength = 0;
for(;;)
{
HANDLE Handles[] = { Context->hCloseEvent, Context->hUpdateEvent };
DWORD Result = MsgWaitForMultipleObjects(2, Handles, 0, INFINITE, QS_ALLEVENTS);
if(Result == WAIT_OBJECT_0)
{
break;
}
else if(Result == WAIT_OBJECT_0 + 1)
{
FScopeLock Lock(&Context->CriticalSection);
if(Context->Status != PrevStatus)
{
SetWindowText(hWndStatus, *Context->Status);
PrevStatus = Context->Status;
}
if(Context->Progress != PrevProgress)
{
SendMessageW(hWndProgress, PBM_SETPOS, (int32)(Context->Progress * 1000.0f), 0);
PrevProgress = Context->Progress;
}
if(Context->LogOutput.Len() > PrevLogOutputLength)
{
SendMessageW(hWndLogOutput, EM_SETSEL, PrevLogOutputLength, PrevLogOutputLength);
SendMessageW(hWndLogOutput, EM_REPLACESEL, FALSE, (LPARAM)(*Context->LogOutput + PrevLogOutputLength));
SendMessageW(hWndLogOutput, EM_SCROLLCARET, 0, 0);
PrevLogOutputLength = Context->LogOutput.Len();
}
}
MSG Msg;
while(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
DestroyWindow(hWnd);
DeleteObject(hLogFont);
DeleteObject(hFont);
UnregisterClass(WindowClassName, HInstance);
return 0;
}
LRESULT CALLBACK FWindowsNativeFeedbackContext::SlowTaskWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_COMMAND:
if(wParam == ShowLogCtlId)
{
FWindowParams *Params = (FWindowParams*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
Params->bLogVisible ^= true;
RECT WindowRect;
GetClientRect(hWnd, &WindowRect);
WindowRect.bottom = Params->StandardH + (Params->bLogVisible? Params->ScaleY * 10 : 0);
AdjustWindowRectEx(&WindowRect, GetWindowLong(hWnd, GWL_STYLE), 0, 0);
SetWindowPos(hWnd, NULL, 0, 0, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, SWP_NOZORDER | SWP_NOMOVE);
SetDlgItemText(hWnd, ShowLogCtlId, Params->bLogVisible? TEXT("Hide log") : TEXT("Show log"));
ShowWindow(GetDlgItem(hWnd, LogOutputCtlId), Params->bLogVisible? SW_SHOW : SW_HIDE);
LayoutControls(hWnd, Params);
}
return 0;
case WM_SIZE:
{
FWindowParams *Params = (FWindowParams*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if(Params != NULL)
{
LayoutControls(hWnd, Params);
}
}
return 0;
case WM_GETMINMAXINFO:
{
FWindowParams *Params = (FWindowParams*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if(Params != NULL)
{
RECT WindowRect;
SetRect(&WindowRect, 0, 0, Params->StandardW, Params->StandardH + (Params->bLogVisible? (Params->ScaleY * 5) : 0));
AdjustWindowRectEx(&WindowRect, WS_CAPTION, 0, 0);
MINMAXINFO *MinMaxInfo = (MINMAXINFO*)lParam;
MinMaxInfo->ptMinTrackSize.x = WindowRect.right - WindowRect.left;
MinMaxInfo->ptMinTrackSize.y = WindowRect.bottom - WindowRect.top;
if(!Params->bLogVisible)
{
MinMaxInfo->ptMaxTrackSize.y = MinMaxInfo->ptMinTrackSize.y;
}
}
}
return 0;
case WM_CLOSE:
{
FWindowParams *Params = (FWindowParams*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
FScopeLock Lock(&Params->Context->CriticalSection);
Params->Context->bReceivedUserCancel = true;
}
return 0;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
void FWindowsNativeFeedbackContext::LayoutControls(HWND hWnd, const FWindowParams* Params)
{
RECT ClientRect;
GetClientRect(hWnd, &ClientRect);
int32 MarginW = Params->ScaleX * 2;
int32 MarginH = Params->ScaleY;
int32 SplitX = ClientRect.right - (Params->ScaleX * 15);
int32 SplitY = Params->ScaleY * 4;
int32 ButtonH = (Params->ScaleY * 7) / 4;
HWND hWndOpenLog = GetDlgItem(hWnd, ShowLogCtlId);
MoveWindow(hWndOpenLog, SplitX, (SplitY - ButtonH) / 2, ClientRect.right - SplitX - MarginW, ButtonH, TRUE);
HWND hWndStatus = GetDlgItem(hWnd, StatusCtlId);
MoveWindow(hWndStatus, MarginW, MarginH, SplitX - (MarginW * 2), Params->ScaleY, TRUE);
HWND hWndProgress = GetDlgItem(hWnd, ProgressCtlId);
MoveWindow(hWndProgress, MarginW, MarginH + (Params->ScaleY * 3) / 2, SplitX - (MarginW * 2), (Params->ScaleY + 1) / 2, TRUE);
HWND hWndLogOutput = GetDlgItem(hWnd, LogOutputCtlId);
MoveWindow(hWndLogOutput, MarginW, SplitY, ClientRect.right - MarginW * 2, ClientRect.bottom - SplitY - MarginH, TRUE);
}
#include "Windows/HideWindowsPlatformTypes.h"