Files
XCEngine/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp

862 lines
29 KiB
C++

#include "Platform/Win32/EditorWindowChromeController.h"
#include "Platform/Win32/EditorWindow.h"
#include "Platform/Win32/EditorWindowConstants.h"
#include "Platform/Win32/EditorWindowInternalState.h"
#include "Platform/Win32/EditorWindowRuntimeController.h"
#include "Platform/Win32/EditorWindowStyle.h"
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Panels/UIEditorPanelRegistry.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Layout/UITabStripLayout.h>
#include <algorithm>
#include <cmath>
#include <string_view>
#include <windowsx.h>
namespace XCEngine::UI::Editor::App::Internal {
using namespace EditorWindowInternal;
using ::XCEngine::UI::Layout::MeasureUITabStripHeaderWidth;
using ::XCEngine::UI::UIColor;
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
namespace {
constexpr float kTitleBarLogoExtent = 16.0f;
constexpr float kTitleBarLogoInsetLeft = 8.0f;
constexpr float kTitleBarLogoTextGap = 8.0f;
std::string ResolveDetachedTitleTabText(const EditorWindow& window) {
const auto& workspaceController = window.GetWorkspaceController();
const std::string_view activePanelId = workspaceController.GetWorkspace().activePanelId;
if (!activePanelId.empty()) {
if (const UIEditorPanelDescriptor* descriptor =
FindUIEditorPanelDescriptor(
workspaceController.GetPanelRegistry(),
activePanelId);
descriptor != nullptr &&
!descriptor->defaultTitle.empty()) {
return descriptor->defaultTitle;
}
}
return std::string("Panel");
}
float ResolveDetachedTabWidth(std::string_view text) {
const Widgets::UIEditorTabStripMetrics& metrics = ResolveUIEditorTabStripMetrics();
Widgets::UIEditorTabStripItem item = {};
item.title = std::string(text);
const float desiredLabelWidth =
Widgets::ResolveUIEditorTabStripDesiredHeaderLabelWidth(item, metrics);
return MeasureUITabStripHeaderWidth(desiredLabelWidth, metrics.layoutMetrics);
}
bool IsRootPanelVisible(
const UIEditorWorkspaceController& controller,
std::string_view panelId) {
const UIEditorPanelSessionState* panelState =
FindUIEditorPanelSessionState(controller.GetSession(), panelId);
return panelState != nullptr && panelState->open && panelState->visible;
}
bool HasSingleVisibleRootTab(const UIEditorWorkspaceController& controller) {
const UIEditorWorkspaceNode& root = controller.GetWorkspace().root;
if (root.kind != UIEditorWorkspaceNodeKind::TabStack) {
return false;
}
std::size_t visiblePanelCount = 0u;
for (const UIEditorWorkspaceNode& child : root.children) {
if (child.kind != UIEditorWorkspaceNodeKind::Panel ||
!IsRootPanelVisible(controller, child.panel.panelId)) {
continue;
}
++visiblePanelCount;
if (visiblePanelCount > 1u) {
return false;
}
}
return visiblePanelCount == 1u;
}
UIRect BuildDetachedTitleLogoRect(const Host::BorderlessWindowChromeLayout& layout) {
const float availableLeft = layout.titleBarRect.x;
const float availableRight = layout.minimizeButtonRect.x;
const float centeredX = std::floor(
layout.titleBarRect.x + layout.titleBarRect.width * 0.5f - kTitleBarLogoExtent * 0.5f);
const float clampedX = (std::max)(
availableLeft,
(std::min)(centeredX, availableRight - kTitleBarLogoExtent));
const float logoY =
layout.titleBarRect.y +
(std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f);
return UIRect(clampedX, logoY, kTitleBarLogoExtent, kTitleBarLogoExtent);
}
} // namespace
void EditorWindowChromeController::Reset() {
m_chromeState = {};
m_runtimeState.Reset();
}
void EditorWindowChromeController::SetWindowDpi(UINT dpi) {
m_runtimeState.SetWindowDpi(dpi);
}
UINT EditorWindowChromeController::GetWindowDpi() const {
return m_runtimeState.GetWindowDpi();
}
float EditorWindowChromeController::GetDpiScale(float baseDpiScale) const {
return m_runtimeState.GetDpiScale(baseDpiScale);
}
void EditorWindowChromeController::BeginInteractiveResize() {
m_runtimeState.BeginInteractiveResize();
}
void EditorWindowChromeController::EndInteractiveResize() {
m_runtimeState.EndInteractiveResize();
}
void EditorWindowChromeController::BeginBorderlessResize(
Host::BorderlessWindowResizeEdge edge,
const POINT& initialScreenPoint,
const RECT& initialWindowRect) {
m_runtimeState.BeginBorderlessResize(edge, initialScreenPoint, initialWindowRect);
}
void EditorWindowChromeController::EndBorderlessResize() {
m_runtimeState.EndBorderlessResize();
}
bool EditorWindowChromeController::IsBorderlessResizeActive() const {
return m_runtimeState.IsBorderlessResizeActive();
}
Host::BorderlessWindowResizeEdge EditorWindowChromeController::GetBorderlessResizeEdge() const {
return m_runtimeState.GetBorderlessResizeEdge();
}
const POINT& EditorWindowChromeController::GetBorderlessResizeInitialScreenPoint() const {
return m_runtimeState.GetBorderlessResizeInitialScreenPoint();
}
const RECT& EditorWindowChromeController::GetBorderlessResizeInitialWindowRect() const {
return m_runtimeState.GetBorderlessResizeInitialWindowRect();
}
void EditorWindowChromeController::SetHoveredBorderlessResizeEdge(
Host::BorderlessWindowResizeEdge edge) {
m_runtimeState.SetHoveredBorderlessResizeEdge(edge);
}
Host::BorderlessWindowResizeEdge EditorWindowChromeController::GetHoveredBorderlessResizeEdge()
const {
return m_runtimeState.GetHoveredBorderlessResizeEdge();
}
void EditorWindowChromeController::SetPredictedClientPixelSize(UINT width, UINT height) {
m_runtimeState.SetPredictedClientPixelSize(width, height);
}
void EditorWindowChromeController::ClearPredictedClientPixelSize() {
m_runtimeState.ClearPredictedClientPixelSize();
}
bool EditorWindowChromeController::TryGetPredictedClientPixelSize(
UINT& outWidth,
UINT& outHeight) const {
return m_runtimeState.TryGetPredictedClientPixelSize(outWidth, outHeight);
}
void EditorWindowChromeController::SetBorderlessWindowMaximized(bool maximized) {
m_runtimeState.SetBorderlessWindowMaximized(maximized);
}
bool EditorWindowChromeController::IsBorderlessWindowMaximized() const {
return m_runtimeState.IsBorderlessWindowMaximized();
}
void EditorWindowChromeController::SetBorderlessWindowRestoreRect(const RECT& rect) {
m_runtimeState.SetBorderlessWindowRestoreRect(rect);
}
bool EditorWindowChromeController::TryGetBorderlessWindowRestoreRect(RECT& outRect) const {
return m_runtimeState.TryGetBorderlessWindowRestoreRect(outRect);
}
void EditorWindowChromeController::BeginBorderlessWindowDragRestore(
const POINT& initialScreenPoint) {
m_runtimeState.BeginBorderlessWindowDragRestore(initialScreenPoint);
}
void EditorWindowChromeController::EndBorderlessWindowDragRestore() {
m_runtimeState.EndBorderlessWindowDragRestore();
}
bool EditorWindowChromeController::IsBorderlessWindowDragRestoreArmed() const {
return m_runtimeState.IsBorderlessWindowDragRestoreArmed();
}
const POINT& EditorWindowChromeController::GetBorderlessWindowDragRestoreInitialScreenPoint()
const {
return m_runtimeState.GetBorderlessWindowDragRestoreInitialScreenPoint();
}
Host::BorderlessWindowChromeHitTarget EditorWindowChromeController::GetHoveredChromeTarget() const {
return m_chromeState.hoveredTarget;
}
void EditorWindowChromeController::SetHoveredChromeTarget(
Host::BorderlessWindowChromeHitTarget target) {
m_chromeState.hoveredTarget = target;
}
Host::BorderlessWindowChromeHitTarget EditorWindowChromeController::GetPressedChromeTarget() const {
return m_chromeState.pressedTarget;
}
void EditorWindowChromeController::SetPressedChromeTarget(
Host::BorderlessWindowChromeHitTarget target) {
m_chromeState.pressedTarget = target;
}
void EditorWindowChromeController::ResetChromeState() {
m_chromeState = {};
}
bool EditorWindowChromeController::IsChromeStateClear() const {
return m_chromeState.hoveredTarget == Host::BorderlessWindowChromeHitTarget::None &&
m_chromeState.pressedTarget == Host::BorderlessWindowChromeHitTarget::None;
}
const Host::BorderlessWindowChromeState& EditorWindowChromeController::GetChromeState() const {
return m_chromeState;
}
bool EditorWindowChromeController::HandleSystemCommand(
EditorWindow& window,
EditorContext& editorContext,
bool globalTabDragActive,
WPARAM wParam) {
if (!window.IsBorderlessWindowEnabled()) {
return false;
}
switch (wParam & 0xFFF0u) {
case SC_MAXIMIZE:
ToggleMaximizeRestore(window, editorContext, globalTabDragActive);
return true;
case SC_RESTORE:
if (!IsIconic(window.m_state->window.hwnd)) {
ToggleMaximizeRestore(window, editorContext, globalTabDragActive);
return true;
}
return false;
default:
return false;
}
}
bool EditorWindowChromeController::HandleGetMinMaxInfo(
const EditorWindow& window,
LPARAM lParam) const {
return Host::HandleBorderlessWindowGetMinMaxInfo(window.m_state->window.hwnd, lParam);
}
LRESULT EditorWindowChromeController::HandleNcCalcSize(
const EditorWindow& window,
WPARAM wParam,
LPARAM lParam) const {
return Host::HandleBorderlessWindowNcCalcSize(
window.m_state->window.hwnd,
wParam,
lParam,
GetWindowDpi());
}
bool EditorWindowChromeController::UpdateResizeHover(EditorWindow& window, LPARAM lParam) {
const Host::BorderlessWindowResizeEdge hoveredEdge = HitTestResizeEdge(window, lParam);
if (GetHoveredBorderlessResizeEdge() == hoveredEdge) {
return false;
}
SetHoveredBorderlessResizeEdge(hoveredEdge);
ApplyResizeCursorHoverPriority();
return true;
}
bool EditorWindowChromeController::HandleResizeButtonDown(EditorWindow& window, LPARAM lParam) {
const Host::BorderlessWindowResizeEdge edge = HitTestResizeEdge(window, lParam);
if (edge == Host::BorderlessWindowResizeEdge::None ||
window.m_state->window.hwnd == nullptr) {
return false;
}
POINT screenPoint = {};
if (!GetCursorPos(&screenPoint)) {
return false;
}
RECT windowRect = {};
if (!GetWindowRect(window.m_state->window.hwnd, &windowRect)) {
return false;
}
BeginBorderlessResize(edge, screenPoint, windowRect);
window.AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize);
window.InvalidateHostWindow();
return true;
}
bool EditorWindowChromeController::HandleResizeButtonUp(EditorWindow& window) {
if (!IsBorderlessResizeActive()) {
return false;
}
EndBorderlessResize();
window.ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize);
window.InvalidateHostWindow();
return true;
}
bool EditorWindowChromeController::HandleResizePointerMove(
EditorWindow& window,
EditorContext& editorContext,
bool globalTabDragActive) {
if (!IsBorderlessResizeActive() ||
window.m_state->window.hwnd == nullptr) {
return false;
}
POINT currentScreenPoint = {};
if (!GetCursorPos(&currentScreenPoint)) {
return false;
}
RECT targetRect = Host::ComputeBorderlessWindowResizeRect(
GetBorderlessResizeInitialWindowRect(),
GetBorderlessResizeInitialScreenPoint(),
currentScreenPoint,
GetBorderlessResizeEdge(),
640,
360);
const int width = targetRect.right - targetRect.left;
const int height = targetRect.bottom - targetRect.top;
if (width <= 0 || height <= 0) {
return true;
}
SetPredictedClientPixelSize(
static_cast<UINT>(width),
static_cast<UINT>(height));
window.ApplyWindowResize(static_cast<UINT>(width), static_cast<UINT>(height));
(void)window.RenderFrame(editorContext, globalTabDragActive);
SetWindowPos(
window.m_state->window.hwnd,
nullptr,
targetRect.left,
targetRect.top,
width,
height,
SWP_NOZORDER | SWP_NOACTIVATE);
return true;
}
void EditorWindowChromeController::ClearResizeState(EditorWindow& window) {
if (IsBorderlessResizeActive()) {
return;
}
if (GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None) {
return;
}
SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None);
window.InvalidateHostWindow();
}
void EditorWindowChromeController::ForceClearResizeState(EditorWindow& window) {
if (GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None &&
!IsBorderlessResizeActive()) {
return;
}
SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None);
EndBorderlessResize();
window.ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize);
window.InvalidateHostWindow();
}
Host::BorderlessWindowResizeEdge EditorWindowChromeController::HitTestResizeEdge(
const EditorWindow& window,
LPARAM lParam) const {
if (!window.IsBorderlessWindowEnabled() ||
window.m_state->window.hwnd == nullptr ||
window.IsBorderlessWindowMaximized()) {
return Host::BorderlessWindowResizeEdge::None;
}
RECT clientRect = {};
if (!GetClientRect(window.m_state->window.hwnd, &clientRect)) {
return Host::BorderlessWindowResizeEdge::None;
}
const float clientWidthDips =
window.PixelsToDips(static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L)));
const float clientHeightDips =
window.PixelsToDips(static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L)));
return Host::HitTestBorderlessWindowResizeEdge(
UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips),
window.ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
}
bool EditorWindowChromeController::UpdateChromeHover(EditorWindow& window, LPARAM lParam) {
if (GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
IsBorderlessResizeActive()) {
const bool changed =
GetHoveredChromeTarget() != Host::BorderlessWindowChromeHitTarget::None;
SetHoveredChromeTarget(Host::BorderlessWindowChromeHitTarget::None);
return changed;
}
const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestChrome(window, lParam);
const Host::BorderlessWindowChromeHitTarget buttonTarget =
hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton ||
hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton ||
hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton
? hitTarget
: Host::BorderlessWindowChromeHitTarget::None;
if (GetHoveredChromeTarget() == buttonTarget) {
return false;
}
SetHoveredChromeTarget(buttonTarget);
return true;
}
bool EditorWindowChromeController::HandleChromeButtonDown(EditorWindow& window, LPARAM lParam) {
if (GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
IsBorderlessResizeActive()) {
return false;
}
const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestChrome(window, lParam);
switch (hitTarget) {
case Host::BorderlessWindowChromeHitTarget::MinimizeButton:
case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
case Host::BorderlessWindowChromeHitTarget::CloseButton:
SetPressedChromeTarget(hitTarget);
window.AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome);
window.InvalidateHostWindow();
return true;
case Host::BorderlessWindowChromeHitTarget::DragRegion:
if (window.m_state->window.hwnd != nullptr) {
if (window.IsBorderlessWindowMaximized()) {
POINT screenPoint = {};
if (GetCursorPos(&screenPoint)) {
BeginBorderlessWindowDragRestore(screenPoint);
window.AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome);
return true;
}
}
window.ForceReleasePointerCapture();
SendMessageW(window.m_state->window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
}
return true;
case Host::BorderlessWindowChromeHitTarget::None:
default:
return false;
}
}
bool EditorWindowChromeController::HandleChromeButtonUp(
EditorWindow& window,
EditorContext& editorContext,
bool globalTabDragActive,
LPARAM lParam) {
if (IsBorderlessWindowDragRestoreArmed()) {
ClearChromeDragRestoreState(window);
return true;
}
const Host::BorderlessWindowChromeHitTarget pressedTarget = GetPressedChromeTarget();
if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton &&
pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton &&
pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) {
return false;
}
const Host::BorderlessWindowChromeHitTarget releasedTarget = HitTestChrome(window, lParam);
SetPressedChromeTarget(Host::BorderlessWindowChromeHitTarget::None);
window.ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome);
window.InvalidateHostWindow();
if (pressedTarget == releasedTarget) {
ExecuteChromeAction(window, editorContext, globalTabDragActive, pressedTarget);
}
return true;
}
bool EditorWindowChromeController::HandleChromeDoubleClick(
EditorWindow& window,
EditorContext& editorContext,
bool globalTabDragActive,
LPARAM lParam) {
if (IsBorderlessWindowDragRestoreArmed()) {
ClearChromeDragRestoreState(window);
}
if (HitTestChrome(window, lParam) != Host::BorderlessWindowChromeHitTarget::DragRegion) {
return false;
}
ExecuteChromeAction(
window,
editorContext,
globalTabDragActive,
Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton);
return true;
}
bool EditorWindowChromeController::HandleChromeDragRestorePointerMove(
EditorWindow& window,
EditorContext& editorContext,
bool globalTabDragActive) {
if (!IsBorderlessWindowDragRestoreArmed() ||
window.m_state->window.hwnd == nullptr) {
return false;
}
POINT currentScreenPoint = {};
if (!GetCursorPos(&currentScreenPoint)) {
return true;
}
const POINT initialScreenPoint = GetBorderlessWindowDragRestoreInitialScreenPoint();
const int dragThresholdX = (std::max)(GetSystemMetrics(SM_CXDRAG), 1);
const int dragThresholdY = (std::max)(GetSystemMetrics(SM_CYDRAG), 1);
const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x;
const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y;
if (std::abs(deltaX) < dragThresholdX &&
std::abs(deltaY) < dragThresholdY) {
return true;
}
RECT restoreRect = {};
RECT currentRect = {};
RECT workAreaRect = {};
if (!TryGetBorderlessWindowRestoreRect(restoreRect) ||
!QueryCurrentWindowRect(window, currentRect) ||
!QueryBorderlessWindowWorkAreaRect(window, workAreaRect)) {
ClearChromeDragRestoreState(window);
return true;
}
const int restoreWidth = restoreRect.right - restoreRect.left;
const int restoreHeight = restoreRect.bottom - restoreRect.top;
const int currentWidth = currentRect.right - currentRect.left;
if (restoreWidth <= 0 || restoreHeight <= 0 || currentWidth <= 0) {
ClearChromeDragRestoreState(window);
return true;
}
const float pointerRatio =
static_cast<float>(currentScreenPoint.x - currentRect.left) /
static_cast<float>(currentWidth);
const float clampedPointerRatio = (std::clamp)(pointerRatio, 0.0f, 1.0f);
const int newLeft =
(std::clamp)(
currentScreenPoint.x -
static_cast<int>(clampedPointerRatio * static_cast<float>(restoreWidth)),
workAreaRect.left,
workAreaRect.right - restoreWidth);
const int titleBarHeightPixels =
static_cast<int>(kBorderlessTitleBarHeightDips * window.GetDpiScale());
const int newTop =
(std::clamp)(
currentScreenPoint.y - (std::max)(titleBarHeightPixels / 2, 1),
workAreaRect.top,
workAreaRect.bottom - restoreHeight);
const RECT targetRect = {
newLeft,
newTop,
newLeft + restoreWidth,
newTop + restoreHeight
};
SetBorderlessWindowMaximized(false);
ApplyPredictedWindowRectTransition(window, editorContext, globalTabDragActive, targetRect);
ClearChromeDragRestoreState(window);
SendMessageW(window.m_state->window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
return true;
}
void EditorWindowChromeController::ClearChromeDragRestoreState(EditorWindow& window) {
if (!IsBorderlessWindowDragRestoreArmed()) {
return;
}
EndBorderlessWindowDragRestore();
window.ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome);
}
void EditorWindowChromeController::ClearChromeState(EditorWindow& window) {
if (IsChromeStateClear()) {
return;
}
ResetChromeState();
window.InvalidateHostWindow();
}
Host::BorderlessWindowChromeHitTarget EditorWindowChromeController::HitTestChrome(
const EditorWindow& window,
LPARAM lParam) const {
if (!window.IsBorderlessWindowEnabled() || window.m_state->window.hwnd == nullptr) {
return Host::BorderlessWindowChromeHitTarget::None;
}
RECT clientRect = {};
if (!GetClientRect(window.m_state->window.hwnd, &clientRect)) {
return Host::BorderlessWindowChromeHitTarget::None;
}
const float clientWidthDips =
window.PixelsToDips(static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L)));
const Host::BorderlessWindowChromeLayout layout =
ResolveChromeLayout(window, clientWidthDips);
return Host::HitTestBorderlessWindowChrome(
layout,
window.ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
}
Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLayout(
const EditorWindow& window,
float clientWidthDips) const {
float leadingOccupiedRight = 0.0f;
if (ShouldUseDetachedTitleBarTabStrip(window)) {
leadingOccupiedRight = ResolveDetachedTabWidth(ResolveDetachedTitleTabText(window));
}
return Host::BuildBorderlessWindowChromeLayout(
UIRect(0.0f, 0.0f, clientWidthDips, kBorderlessTitleBarHeightDips),
leadingOccupiedRight);
}
bool EditorWindowChromeController::ShouldUseDetachedTitleBarTabStrip(
const EditorWindow& window) const {
return !window.m_state->window.primary &&
HasSingleVisibleRootTab(window.m_runtime->GetWorkspaceController());
}
void EditorWindowChromeController::AppendChrome(
const EditorWindow& window,
UIDrawList& drawList,
float clientWidthDips) const {
if (!window.IsBorderlessWindowEnabled()) {
return;
}
const Host::BorderlessWindowChromeLayout layout =
ResolveChromeLayout(window, clientWidthDips);
const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(window);
if (!useDetachedTitleBarTabStrip) {
drawList.AddFilledRect(layout.titleBarRect, kShellSurfaceColor);
drawList.AddLine(
UIPoint(layout.titleBarRect.x, layout.titleBarRect.y + layout.titleBarRect.height),
UIPoint(
layout.titleBarRect.x + layout.titleBarRect.width,
layout.titleBarRect.y + layout.titleBarRect.height),
kShellBorderColor,
1.0f);
}
if (!window.m_state->window.primary) {
if (window.m_runtime->GetTitleBarLogoIcon().IsValid()) {
drawList.AddImage(
BuildDetachedTitleLogoRect(layout),
window.m_runtime->GetTitleBarLogoIcon(),
UIColor(1.0f, 1.0f, 1.0f, 1.0f));
}
} else {
const float iconX = layout.titleBarRect.x + kTitleBarLogoInsetLeft;
const float iconY =
layout.titleBarRect.y +
(std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f);
if (window.m_runtime->GetTitleBarLogoIcon().IsValid()) {
drawList.AddImage(
UIRect(iconX, iconY, kTitleBarLogoExtent, kTitleBarLogoExtent),
window.m_runtime->GetTitleBarLogoIcon(),
UIColor(1.0f, 1.0f, 1.0f, 1.0f));
}
const std::string titleText =
window.m_state->window.titleText.empty()
? std::string("XCEngine Editor")
: window.m_state->window.titleText;
drawList.AddText(
UIPoint(
iconX +
(window.m_runtime->GetTitleBarLogoIcon().IsValid()
? (kTitleBarLogoExtent + kTitleBarLogoTextGap)
: 4.0f),
layout.titleBarRect.y +
(std::max)(
0.0f,
(layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)),
titleText,
kShellTextColor,
kBorderlessTitleBarFontSize);
}
Host::AppendBorderlessWindowChrome(
drawList,
layout,
GetChromeState(),
window.IsBorderlessWindowMaximized());
}
void EditorWindowChromeController::ApplyResizeCursorHoverPriority() {
if (GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
IsBorderlessResizeActive()) {
SetHoveredChromeTarget(Host::BorderlessWindowChromeHitTarget::None);
}
}
bool EditorWindowChromeController::QueryCurrentWindowRect(
const EditorWindow& window,
RECT& outRect) const {
outRect = {};
return window.m_state->window.hwnd != nullptr &&
GetWindowRect(window.m_state->window.hwnd, &outRect) != FALSE;
}
bool EditorWindowChromeController::QueryBorderlessWindowWorkAreaRect(
const EditorWindow& window,
RECT& outRect) const {
outRect = {};
if (window.m_state->window.hwnd == nullptr) {
return false;
}
const HMONITOR monitor =
MonitorFromWindow(window.m_state->window.hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor == nullptr) {
return false;
}
MONITORINFO monitorInfo = {};
monitorInfo.cbSize = sizeof(monitorInfo);
if (!GetMonitorInfoW(monitor, &monitorInfo)) {
return false;
}
outRect = monitorInfo.rcWork;
return true;
}
bool EditorWindowChromeController::ApplyPredictedWindowRectTransition(
EditorWindow& window,
EditorContext& editorContext,
bool globalTabDragActive,
const RECT& targetRect) {
if (window.m_state->window.hwnd == nullptr) {
return false;
}
const int width = targetRect.right - targetRect.left;
const int height = targetRect.bottom - targetRect.top;
if (width <= 0 || height <= 0) {
return false;
}
SetPredictedClientPixelSize(static_cast<UINT>(width), static_cast<UINT>(height));
window.ApplyWindowResize(static_cast<UINT>(width), static_cast<UINT>(height));
(void)window.RenderFrame(editorContext, globalTabDragActive);
SetWindowPos(
window.m_state->window.hwnd,
nullptr,
targetRect.left,
targetRect.top,
width,
height,
SWP_NOZORDER | SWP_NOACTIVATE);
window.InvalidateHostWindow();
return true;
}
void EditorWindowChromeController::ToggleMaximizeRestore(
EditorWindow& window,
EditorContext& editorContext,
bool globalTabDragActive) {
if (window.m_state->window.hwnd == nullptr) {
return;
}
if (!window.IsBorderlessWindowMaximized()) {
RECT currentRect = {};
RECT workAreaRect = {};
if (!QueryCurrentWindowRect(window, currentRect) ||
!QueryBorderlessWindowWorkAreaRect(window, workAreaRect)) {
return;
}
SetBorderlessWindowRestoreRect(currentRect);
SetBorderlessWindowMaximized(true);
ApplyPredictedWindowRectTransition(window, editorContext, globalTabDragActive, workAreaRect);
return;
}
RECT restoreRect = {};
if (!TryGetBorderlessWindowRestoreRect(restoreRect)) {
return;
}
SetBorderlessWindowMaximized(false);
ApplyPredictedWindowRectTransition(window, editorContext, globalTabDragActive, restoreRect);
}
void EditorWindowChromeController::ExecuteChromeAction(
EditorWindow& window,
EditorContext& editorContext,
bool globalTabDragActive,
Host::BorderlessWindowChromeHitTarget target) {
if (window.m_state->window.hwnd == nullptr) {
return;
}
switch (target) {
case Host::BorderlessWindowChromeHitTarget::MinimizeButton:
ShowWindow(window.m_state->window.hwnd, SW_MINIMIZE);
break;
case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
ToggleMaximizeRestore(window, editorContext, globalTabDragActive);
break;
case Host::BorderlessWindowChromeHitTarget::CloseButton:
PostMessageW(window.m_state->window.hwnd, WM_CLOSE, 0, 0);
break;
case Host::BorderlessWindowChromeHitTarget::DragRegion:
case Host::BorderlessWindowChromeHitTarget::None:
default:
break;
}
window.InvalidateHostWindow();
}
} // namespace XCEngine::UI::Editor::App::Internal