Refactor new editor boundaries and test ownership
This commit is contained in:
861
new_editor/app/Platform/Win32/EditorWindowChromeController.cpp
Normal file
861
new_editor/app/Platform/Win32/EditorWindowChromeController.cpp
Normal file
@@ -0,0 +1,861 @@
|
||||
#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(¤tScreenPoint)) {
|
||||
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(¤tScreenPoint)) {
|
||||
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
|
||||
Reference in New Issue
Block a user