diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index d0643bd67e84f..baf14d9368dff 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -79,7 +79,7 @@ export interface ICommonNativeHostService { handleTitleDoubleClick(): Promise; - getCursorScreenPoint(): Promise; + getCursorScreenPoint(): Promise<{ readonly point: IPoint; readonly display: IRectangle }>; isMaximized(): Promise; maximizeWindow(): Promise; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 0929f47ea05d5..88ec38a27879c 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -208,8 +208,11 @@ export class NativeHostMainService extends Disposable implements INativeHostMain window?.handleTitleDoubleClick(); } - async getCursorScreenPoint(windowId: number | undefined): Promise { - return screen.getCursorScreenPoint(); + async getCursorScreenPoint(windowId: number | undefined): Promise<{ readonly point: IPoint; readonly display: IRectangle }> { + const point = screen.getCursorScreenPoint(); + const display = screen.getDisplayNearestPoint(point); + + return { point, display: display.bounds }; } async isMaximized(windowId: number | undefined): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index e0aa447bc61ba..62c3b1e19aaea 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -331,27 +331,32 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC targetGroup.focus(); } - protected async maybeCreateAuxiliaryEditorPartAt(e: DragEvent, offsetElement?: HTMLElement): Promise { - const cursorLocation = await this.hostService.getCursorScreenPoint() ?? { x: e.screenX, y: e.screenY }; + protected async maybeCreateAuxiliaryEditorPartAt(e: DragEvent, offsetElement: HTMLElement): Promise { + const { point, display } = await this.hostService.getCursorScreenPoint() ?? { point: { x: e.screenX, y: e.screenY } }; const window = getWindow(e); - if (cursorLocation.x >= window.screenX && cursorLocation.x <= window.screenX + window.outerWidth && cursorLocation.y >= window.screenY && cursorLocation.y <= window.screenY + window.outerHeight) { + if (point.x >= window.screenX && point.x <= window.screenX + window.outerWidth && point.y >= window.screenY && point.y <= window.screenY + window.outerHeight) { return; // refuse to create as long as the mouse was released over main window to reduce chance of opening by accident } - let offsetX = 0; - let offsetY = 30; // take title bar height into account (approximation) + const offsetX = offsetElement.offsetWidth / 2; + const offsetY = 30/* take title bar height into account (approximation) */ + offsetElement.offsetHeight / 2; - if (offsetElement) { - offsetX += offsetElement.offsetWidth / 2; - offsetY += offsetElement.offsetHeight / 2; - } + const bounds = { + x: point.x - offsetX, + y: point.y - offsetY + }; - return this.editorGroupService.createAuxiliaryEditorPart({ - bounds: { - x: cursorLocation.x - offsetX, - y: cursorLocation.y - offsetY + if (display) { + if (bounds.x < display.x) { + bounds.x = display.x; // prevent overflow to the left } - }); + + if (bounds.y < display.y) { + bounds.y = display.y; // prevent overflow to the top + } + } + + return this.editorGroupService.createAuxiliaryEditorPart({ bounds }); } protected isNewWindowOperation(e: DragEvent): boolean { diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index e2a1c27421189..e06a85028a0ce 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -202,8 +202,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili const height = options?.bounds?.height ?? BrowserAuxiliaryWindowService.DEFAULT_SIZE.height; const bounds: IRectangle = { - x: Math.max(options?.bounds?.x ?? (activeWindow.screen.availWidth / 2 - width / 2), 0), - y: Math.max(options?.bounds?.y ?? (activeWindow.screen.availHeight / 2 - height / 2), 0), + x: options?.bounds?.x ?? (activeWindow.screen.availWidth / 2 - width / 2), + y: options?.bounds?.y ?? (activeWindow.screen.availHeight / 2 - height / 2), width: Math.max(width, WindowMinimumSize.WIDTH), height: Math.max(height, WindowMinimumSize.HEIGHT) }; diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 6e198baa33e1d..f2d402899fc35 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -9,7 +9,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions, IPathData, IFileToOpen, IPoint } from 'vs/platform/window/common/window'; +import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions, IPathData, IFileToOpen } from 'vs/platform/window/common/window'; import { isResourceEditorInput, pathsToEditors } from 'vs/workbench/common/editor'; import { whenEditorClosed } from 'vs/workbench/browser/editor'; import { IWorkspace, IWorkspaceProvider } from 'vs/workbench/browser/web.api'; @@ -502,7 +502,7 @@ export class BrowserHostService extends Disposable implements IHostService { // There seems to be no API to bring a window to front in browsers } - async getCursorScreenPoint(): Promise { + async getCursorScreenPoint(): Promise { return undefined; } diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index a84d70ab0dca7..2b13e2aa3f754 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions, IPoint } from 'vs/platform/window/common/window'; +import { IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions, IPoint, IRectangle } from 'vs/platform/window/common/window'; export const IHostService = createDecorator('hostService'); @@ -87,9 +87,9 @@ export interface IHostService { moveTop(targetWindow: Window): Promise; /** - * Get the location of the mouse cursor or `undefined` if unavailable. + * Get the location of the mouse cursor and its display bounds or `undefined` if unavailable. */ - getCursorScreenPoint(): Promise; + getCursorScreenPoint(): Promise<{ readonly point: IPoint; readonly display: IRectangle } | undefined>; //#endregion diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts index 77cadf301b426..c8e8e38af3240 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts @@ -9,7 +9,7 @@ import { INativeHostService } from 'vs/platform/native/common/native'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILabelService, Verbosity } from 'vs/platform/label/common/label'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions, IPoint } from 'vs/platform/window/common/window'; +import { IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions, IPoint, IRectangle } from 'vs/platform/window/common/window'; import { Disposable } from 'vs/base/common/lifecycle'; import { NativeHostService } from 'vs/platform/native/common/nativeHostService'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; @@ -153,7 +153,7 @@ class WorkbenchHostService extends Disposable implements IHostService { return this.nativeHostService.moveWindowTop(isAuxiliaryWindow(targetWindow) ? { targetWindowId: targetWindow.vscodeWindowId } : undefined); } - getCursorScreenPoint(): Promise { + getCursorScreenPoint(): Promise<{ readonly point: IPoint; readonly display: IRectangle }> { return this.nativeHostService.getCursorScreenPoint(); } diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index 346b4d36cdc09..5d8f3ada4d36a 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -96,7 +96,7 @@ export class TestNativeHostService implements INativeHostService { async unmaximizeWindow(): Promise { } async minimizeWindow(): Promise { } async moveWindowTop(options?: INativeOptions): Promise { } - getCursorScreenPoint(): Promise { throw new Error('Method not implemented.'); } + getCursorScreenPoint(): Promise<{ readonly point: IPoint; readonly display: IRectangle }> { throw new Error('Method not implemented.'); } async positionWindow(position: IRectangle, options?: INativeOptions): Promise { } async updateWindowControls(options: { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise { } async setMinimumSize(width: number | undefined, height: number | undefined): Promise { }