Refine detached tab drag host behavior
This commit is contained in:
@@ -44,6 +44,8 @@ constexpr UINT kDefaultDpi = 96u;
|
||||
constexpr float kBaseDpiScale = 96.0f;
|
||||
constexpr float kBorderlessTitleBarHeightDips = 28.0f;
|
||||
constexpr float kBorderlessTitleBarFontSize = 12.0f;
|
||||
constexpr LONG kDetachedWindowDragOffsetXPixels = 40;
|
||||
constexpr LONG kDetachedWindowDragOffsetYPixels = 12;
|
||||
const UIColor kShellSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f);
|
||||
const UIColor kShellBorderColor(0.15f, 0.15f, 0.15f, 1.0f);
|
||||
const UIColor kShellTextColor(0.92f, 0.92f, 0.92f, 1.0f);
|
||||
@@ -451,6 +453,18 @@ bool IsPointInsideRect(
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
const Widgets::UIEditorDockHostTabStackLayout* FindDockHostTabStackLayoutByNodeId(
|
||||
const Widgets::UIEditorDockHostLayout& layout,
|
||||
std::string_view nodeId) {
|
||||
for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) {
|
||||
if (tabStack.nodeId == nodeId) {
|
||||
return &tabStack;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::size_t ResolveCrossWindowDropInsertionIndex(
|
||||
const Widgets::UIEditorDockHostTabStackLayout& tabStack,
|
||||
const UIPoint& point) {
|
||||
@@ -1010,10 +1024,10 @@ std::wstring Application::BuildManagedWindowTitle(
|
||||
|
||||
RECT Application::BuildDetachedWindowRect(const POINT& screenPoint) const {
|
||||
RECT rect = {
|
||||
screenPoint.x - 420,
|
||||
screenPoint.y - 24,
|
||||
screenPoint.x - 420 + 960,
|
||||
screenPoint.y - 24 + 720
|
||||
screenPoint.x - kDetachedWindowDragOffsetXPixels,
|
||||
screenPoint.y - kDetachedWindowDragOffsetYPixels,
|
||||
screenPoint.x - kDetachedWindowDragOffsetXPixels + 960,
|
||||
screenPoint.y - kDetachedWindowDragOffsetYPixels + 720
|
||||
};
|
||||
|
||||
const HMONITOR monitor = MonitorFromPoint(screenPoint, MONITOR_DEFAULTTONEAREST);
|
||||
@@ -1056,6 +1070,7 @@ void Application::ResetManagedWindowInteractionState(ManagedWindowState& windowS
|
||||
windowState.pendingGlobalTabDragNodeId.clear();
|
||||
windowState.pendingGlobalTabDragPanelId.clear();
|
||||
windowState.pendingGlobalTabDragScreenPoint = {};
|
||||
windowState.pendingGlobalTabDragWindowOffset = {};
|
||||
}
|
||||
|
||||
bool Application::SynchronizeManagedWindowsFromWindowSet(
|
||||
@@ -1122,12 +1137,14 @@ void Application::BeginGlobalTabDragSession(
|
||||
std::string_view panelWindowId,
|
||||
std::string_view sourceNodeId,
|
||||
std::string_view panelId,
|
||||
const POINT& screenPoint) {
|
||||
const POINT& screenPoint,
|
||||
const POINT& windowDragOffset) {
|
||||
m_globalTabDragSession.active = true;
|
||||
m_globalTabDragSession.panelWindowId = std::string(panelWindowId);
|
||||
m_globalTabDragSession.sourceNodeId = std::string(sourceNodeId);
|
||||
m_globalTabDragSession.panelId = std::string(panelId);
|
||||
m_globalTabDragSession.screenPoint = screenPoint;
|
||||
m_globalTabDragSession.windowDragOffset = windowDragOffset;
|
||||
}
|
||||
|
||||
void Application::EndGlobalTabDragSession() {
|
||||
@@ -1187,6 +1204,112 @@ const Application::ManagedWindowState* Application::FindTopmostWindowStateAtScre
|
||||
excludedWindowId);
|
||||
}
|
||||
|
||||
POINT Application::ConvertClientDipsToScreenPixels(
|
||||
const ManagedWindowState& windowState,
|
||||
const UIPoint& point) const {
|
||||
const float dpiScale = windowState.hostRuntime.GetDpiScale(kBaseDpiScale);
|
||||
POINT clientPoint = {
|
||||
static_cast<LONG>(point.x * dpiScale),
|
||||
static_cast<LONG>(point.y * dpiScale)
|
||||
};
|
||||
if (windowState.hwnd != nullptr) {
|
||||
ClientToScreen(windowState.hwnd, &clientPoint);
|
||||
}
|
||||
return clientPoint;
|
||||
}
|
||||
|
||||
bool Application::TryResolveDraggedTabScreenRect(
|
||||
const ManagedWindowState& windowState,
|
||||
std::string_view nodeId,
|
||||
std::string_view panelId,
|
||||
RECT& outRect) const {
|
||||
outRect = {};
|
||||
const Widgets::UIEditorDockHostLayout& layout =
|
||||
windowState.editorWorkspace
|
||||
.GetShellFrame()
|
||||
.workspaceInteractionFrame
|
||||
.dockHostFrame
|
||||
.layout;
|
||||
const Widgets::UIEditorDockHostTabStackLayout* tabStack =
|
||||
FindDockHostTabStackLayoutByNodeId(layout, nodeId);
|
||||
if (tabStack == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t tabIndex = Widgets::UIEditorTabStripInvalidIndex;
|
||||
for (std::size_t index = 0; index < tabStack->items.size(); ++index) {
|
||||
if (tabStack->items[index].panelId == panelId) {
|
||||
tabIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tabIndex == Widgets::UIEditorTabStripInvalidIndex ||
|
||||
tabIndex >= tabStack->tabStripLayout.tabHeaderRects.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const UIRect& tabRect = tabStack->tabStripLayout.tabHeaderRects[tabIndex];
|
||||
const POINT topLeft = ConvertClientDipsToScreenPixels(
|
||||
windowState,
|
||||
UIPoint(tabRect.x, tabRect.y));
|
||||
const POINT bottomRight = ConvertClientDipsToScreenPixels(
|
||||
windowState,
|
||||
UIPoint(tabRect.x + tabRect.width, tabRect.y + tabRect.height));
|
||||
outRect.left = topLeft.x;
|
||||
outRect.top = topLeft.y;
|
||||
outRect.right = bottomRight.x;
|
||||
outRect.bottom = bottomRight.y;
|
||||
return outRect.right > outRect.left && outRect.bottom > outRect.top;
|
||||
}
|
||||
|
||||
POINT Application::ResolveGlobalTabDragWindowOffset(
|
||||
const ManagedWindowState& windowState,
|
||||
std::string_view nodeId,
|
||||
std::string_view panelId,
|
||||
const POINT& screenPoint) const {
|
||||
RECT tabScreenRect = {};
|
||||
if (TryResolveDraggedTabScreenRect(windowState, nodeId, panelId, tabScreenRect)) {
|
||||
const LONG offsetX =
|
||||
(std::clamp)(screenPoint.x - tabScreenRect.left, 0L, tabScreenRect.right - tabScreenRect.left);
|
||||
const LONG offsetY =
|
||||
(std::clamp)(screenPoint.y - tabScreenRect.top, 0L, tabScreenRect.bottom - tabScreenRect.top);
|
||||
return POINT { offsetX, offsetY };
|
||||
}
|
||||
|
||||
const float dpiScale = windowState.hostRuntime.GetDpiScale(kBaseDpiScale);
|
||||
return POINT {
|
||||
static_cast<LONG>(static_cast<float>(kDetachedWindowDragOffsetXPixels) * dpiScale),
|
||||
static_cast<LONG>(static_cast<float>(kDetachedWindowDragOffsetYPixels) * dpiScale)
|
||||
};
|
||||
}
|
||||
|
||||
void Application::MoveGlobalTabDragWindow(
|
||||
ManagedWindowState& windowState,
|
||||
const POINT& screenPoint) const {
|
||||
if (windowState.hwnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
RECT currentRect = {};
|
||||
if (!GetWindowRect(windowState.hwnd, ¤tRect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const LONG width = currentRect.right - currentRect.left;
|
||||
const LONG height = currentRect.bottom - currentRect.top;
|
||||
const LONG left = screenPoint.x - m_globalTabDragSession.windowDragOffset.x;
|
||||
const LONG top = screenPoint.y - m_globalTabDragSession.windowDragOffset.y;
|
||||
SetWindowPos(
|
||||
windowState.hwnd,
|
||||
nullptr,
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
bool Application::TryStartGlobalTabDrag(ManagedWindowState& sourceWindowState) {
|
||||
if (!sourceWindowState.pendingGlobalTabDragStart ||
|
||||
sourceWindowState.pendingGlobalTabDragNodeId.empty() ||
|
||||
@@ -1197,10 +1320,12 @@ bool Application::TryStartGlobalTabDrag(ManagedWindowState& sourceWindowState) {
|
||||
const std::string sourceNodeId = sourceWindowState.pendingGlobalTabDragNodeId;
|
||||
const std::string panelId = sourceWindowState.pendingGlobalTabDragPanelId;
|
||||
const POINT screenPoint = sourceWindowState.pendingGlobalTabDragScreenPoint;
|
||||
const POINT windowDragOffset = sourceWindowState.pendingGlobalTabDragWindowOffset;
|
||||
sourceWindowState.pendingGlobalTabDragStart = false;
|
||||
sourceWindowState.pendingGlobalTabDragNodeId.clear();
|
||||
sourceWindowState.pendingGlobalTabDragPanelId.clear();
|
||||
sourceWindowState.pendingGlobalTabDragScreenPoint = {};
|
||||
sourceWindowState.pendingGlobalTabDragWindowOffset = {};
|
||||
|
||||
if (sourceWindowState.primary) {
|
||||
UIEditorWindowWorkspaceController windowWorkspaceController(
|
||||
@@ -1236,7 +1361,9 @@ bool Application::TryStartGlobalTabDrag(ManagedWindowState& sourceWindowState) {
|
||||
detachedWindowState->windowId,
|
||||
detachedWindowState->workspaceController.GetWorkspace().root.nodeId,
|
||||
panelId,
|
||||
screenPoint);
|
||||
screenPoint,
|
||||
windowDragOffset);
|
||||
MoveGlobalTabDragWindow(*detachedWindowState, screenPoint);
|
||||
SetCapture(detachedWindowState->hwnd);
|
||||
SetForegroundWindow(detachedWindowState->hwnd);
|
||||
LogRuntimeTrace(
|
||||
@@ -1251,7 +1378,9 @@ bool Application::TryStartGlobalTabDrag(ManagedWindowState& sourceWindowState) {
|
||||
sourceWindowState.windowId,
|
||||
sourceNodeId,
|
||||
panelId,
|
||||
screenPoint);
|
||||
screenPoint,
|
||||
windowDragOffset);
|
||||
MoveGlobalTabDragWindow(sourceWindowState, screenPoint);
|
||||
if (sourceWindowState.hwnd != nullptr) {
|
||||
SetCapture(sourceWindowState.hwnd);
|
||||
}
|
||||
@@ -1352,7 +1481,7 @@ bool Application::HandleGlobalTabDragPointerMove(HWND hwnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ManagedWindowState* ownerWindowState = FindWindowState(m_globalTabDragSession.panelWindowId);
|
||||
ManagedWindowState* ownerWindowState = FindWindowState(m_globalTabDragSession.panelWindowId);
|
||||
if (ownerWindowState == nullptr || ownerWindowState->hwnd != hwnd) {
|
||||
return false;
|
||||
}
|
||||
@@ -1360,6 +1489,7 @@ bool Application::HandleGlobalTabDragPointerMove(HWND hwnd) {
|
||||
POINT screenPoint = {};
|
||||
if (GetCursorPos(&screenPoint)) {
|
||||
m_globalTabDragSession.screenPoint = screenPoint;
|
||||
MoveGlobalTabDragWindow(*ownerWindowState, screenPoint);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1443,6 +1573,59 @@ bool Application::HandleGlobalTabDragPointerButtonUp(HWND hwnd) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::AppendGlobalTabDragDropPreview(UIDrawList& drawList) const {
|
||||
if (!m_globalTabDragSession.active || m_currentWindowState == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_currentWindowState->windowId == m_globalTabDragSession.panelWindowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ManagedWindowState* targetWindowState = FindTopmostWindowStateAtScreenPoint(
|
||||
m_globalTabDragSession.screenPoint,
|
||||
m_globalTabDragSession.panelWindowId);
|
||||
if (targetWindowState == nullptr || targetWindowState != m_currentWindowState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const UIPoint targetPoint =
|
||||
ConvertScreenPixelsToClientDips(*targetWindowState, m_globalTabDragSession.screenPoint);
|
||||
const Widgets::UIEditorDockHostLayout& targetLayout =
|
||||
targetWindowState->editorWorkspace
|
||||
.GetShellFrame()
|
||||
.workspaceInteractionFrame
|
||||
.dockHostFrame
|
||||
.layout;
|
||||
CrossWindowDockDropTarget dropTarget = {};
|
||||
if (!TryResolveCrossWindowDockDropTarget(targetLayout, targetPoint, dropTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Widgets::UIEditorDockHostDropPreviewState previewState = {};
|
||||
previewState.visible = true;
|
||||
previewState.sourceNodeId = m_globalTabDragSession.sourceNodeId;
|
||||
previewState.sourcePanelId = m_globalTabDragSession.panelId;
|
||||
previewState.targetNodeId = dropTarget.nodeId;
|
||||
previewState.placement = dropTarget.placement;
|
||||
previewState.insertionIndex = dropTarget.insertionIndex;
|
||||
const Widgets::UIEditorDockHostDropPreviewLayout previewLayout =
|
||||
Widgets::ResolveUIEditorDockHostDropPreviewLayout(targetLayout, previewState);
|
||||
if (!previewLayout.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Widgets::UIEditorDockHostPalette& dockPalette =
|
||||
ResolveUIEditorShellInteractionPalette().shellPalette.dockHostPalette;
|
||||
drawList.AddFilledRect(
|
||||
previewLayout.previewRect,
|
||||
dockPalette.dropPreviewFillColor);
|
||||
drawList.AddRectOutline(
|
||||
previewLayout.previewRect,
|
||||
dockPalette.dropPreviewBorderColor,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
void Application::HandleDestroyedWindow(HWND hwnd) {
|
||||
if (ManagedWindowState* windowState = FindWindowState(hwnd); windowState != nullptr) {
|
||||
windowState->hwnd = nullptr;
|
||||
@@ -1570,6 +1753,12 @@ void Application::RenderFrame() {
|
||||
RequireCurrentWindowState().pendingGlobalTabDragPanelId =
|
||||
dockHostInteractionState.activeTabDragPanelId;
|
||||
RequireCurrentWindowState().pendingGlobalTabDragScreenPoint = screenPoint;
|
||||
RequireCurrentWindowState().pendingGlobalTabDragWindowOffset =
|
||||
ResolveGlobalTabDragWindowOffset(
|
||||
RequireCurrentWindowState(),
|
||||
dockHostInteractionState.activeTabDragNodeId,
|
||||
dockHostInteractionState.activeTabDragPanelId,
|
||||
screenPoint);
|
||||
}
|
||||
if (shellFrame.result.workspaceResult.dockHostResult.detachRequested) {
|
||||
POINT screenPoint = {};
|
||||
@@ -1588,6 +1777,7 @@ void Application::RenderFrame() {
|
||||
ApplyHostedContentCaptureRequests();
|
||||
ApplyCurrentCursor();
|
||||
m_editorWorkspace.Append(drawList);
|
||||
AppendGlobalTabDragDropPreview(drawList);
|
||||
if (frameContext.canRenderViewports) {
|
||||
m_editorWorkspace.RenderRequestedViewports(frameContext.renderContext);
|
||||
}
|
||||
@@ -1636,6 +1826,10 @@ bool Application::IsBorderlessWindowEnabled() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::HasBorderlessWindowChrome() const {
|
||||
return m_currentWindowState != nullptr && m_currentWindowState->primary;
|
||||
}
|
||||
|
||||
bool Application::IsBorderlessWindowMaximized() const {
|
||||
return m_hostRuntime.IsBorderlessWindowMaximized();
|
||||
}
|
||||
@@ -1888,7 +2082,7 @@ Host::BorderlessWindowChromeLayout Application::ResolveBorderlessWindowChromeLay
|
||||
}
|
||||
|
||||
Host::BorderlessWindowChromeHitTarget Application::HitTestBorderlessWindowChrome(LPARAM lParam) const {
|
||||
if (!IsBorderlessWindowEnabled() || m_hwnd == nullptr) {
|
||||
if (!HasBorderlessWindowChrome() || m_hwnd == nullptr) {
|
||||
return Host::BorderlessWindowChromeHitTarget::None;
|
||||
}
|
||||
|
||||
@@ -1931,6 +2125,10 @@ bool Application::UpdateBorderlessWindowChromeHover(LPARAM lParam) {
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) {
|
||||
if (!HasBorderlessWindowChrome()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
|
||||
m_hostRuntime.IsBorderlessResizeActive()) {
|
||||
return false;
|
||||
@@ -1969,6 +2167,10 @@ bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) {
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowChromeButtonUp(LPARAM lParam) {
|
||||
if (!HasBorderlessWindowChrome()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_hostRuntime.IsBorderlessWindowDragRestoreArmed()) {
|
||||
ClearBorderlessWindowChromeDragRestoreState();
|
||||
return true;
|
||||
@@ -1997,6 +2199,10 @@ bool Application::HandleBorderlessWindowChromeButtonUp(LPARAM lParam) {
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowChromeDoubleClick(LPARAM lParam) {
|
||||
if (!HasBorderlessWindowChrome()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_hostRuntime.IsBorderlessWindowDragRestoreArmed()) {
|
||||
ClearBorderlessWindowChromeDragRestoreState();
|
||||
}
|
||||
@@ -2010,6 +2216,10 @@ bool Application::HandleBorderlessWindowChromeDoubleClick(LPARAM lParam) {
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowChromeDragRestorePointerMove() {
|
||||
if (!HasBorderlessWindowChrome()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_hostRuntime.IsBorderlessWindowDragRestoreArmed() || m_hwnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -2101,7 +2311,7 @@ void Application::ClearBorderlessWindowChromeState() {
|
||||
void Application::AppendBorderlessWindowChrome(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
float clientWidthDips) const {
|
||||
if (!IsBorderlessWindowEnabled()) {
|
||||
if (!HasBorderlessWindowChrome()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2412,7 +2622,7 @@ bool Application::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight)
|
||||
}
|
||||
|
||||
UIRect Application::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const {
|
||||
if (!IsBorderlessWindowEnabled()) {
|
||||
if (!HasBorderlessWindowChrome()) {
|
||||
return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user