Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set initial size and position of secondary windows #13201

Merged
merged 13 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/core/src/browser/core-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,22 @@ export const corePreferenceSchema: PreferenceSchema = {
scope: 'application',
markdownDescription: nls.localizeByDefault('Separator used by {0}.', '`#window.title#`')
},
'window.secondaryWindowPlacement': {
type: 'string',
enum: ['originalSize', 'halfWidth', 'fullSize'],
enumDescriptions: [
nls.localize('theia/core/secondaryWindow/originalSize', 'The position and size of the extracted widget will be the same as the original widget.'),
nls.localize('theia/core/secondaryWindow/halfWidth', 'The position and size of the extracted widget will be half the width of the running Theia application.'),
nls.localize('theia/core/secondaryWindow/fullSize', 'The position and size of the extracted widget will be the same as the running Theia application.'),
],
default: 'originalSize',
description: nls.localize('theia/core/secondaryWindow/description', 'Sets the initial position and size of the extracted secondary window.'),
},
'window.secondaryWindowAlwaysOnTop': {
type: 'boolean',
default: false,
description: nls.localize('theia/core/secondaryWindow/alwaysOnTop', 'When enabled, the secondary window stays above all other windows, including those of different applications.'),
},
'http.proxy': {
type: 'string',
pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+|\\[[:0-9a-fA-F]+\\])(:\\d+)?/?$|^$',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { WindowService } from './window-service';
import { ExtractableWidget } from '../widgets';
import { ApplicationShell } from '../shell';
import { Saveable } from '../saveable';
import { PreferenceService } from '../preferences';
import { environment } from '../../common';

@injectable()
export class DefaultSecondaryWindowService implements SecondaryWindowService {
Expand All @@ -38,6 +40,9 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
@inject(WindowService)
protected readonly windowService: WindowService;

@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;

@postConstruct()
init(): void {
// Set up messaging with secondary windows
Expand Down Expand Up @@ -100,7 +105,13 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
}

protected doCreateSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined {
const newWindow = window.open(DefaultSecondaryWindowService.SECONDARY_WINDOW_URL, this.nextWindowId(), 'popup') ?? undefined;
let options;
const [height, width, left, top] = this.findSecondaryWindowCoordinates(widget);
options = `popup=1,width=${width},height=${height},left=${left},top=${top}`;
if (this.preferenceService.get('window.secondaryWindowAlwaysOnTop')) {
options += ',alwaysOnTop=true';
vladarama marked this conversation as resolved.
Show resolved Hide resolved
}
const newWindow = window.open(DefaultSecondaryWindowService.SECONDARY_WINDOW_URL, this.nextWindowId(), options) ?? undefined;
if (newWindow) {
newWindow.addEventListener('DOMContentLoaded', () => {
newWindow.addEventListener('beforeunload', evt => {
Expand All @@ -124,6 +135,48 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
return newWindow;
}

protected findSecondaryWindowCoordinates(widget: ExtractableWidget): (number | undefined)[] {
const clientBounds = widget.node.getBoundingClientRect();
const preference = this.preferenceService.get('window.secondaryWindowPlacement');

let height; let width; let left; let top;
const offsetY = 20; // Offset to avoid the window title bar

switch (preference) {
case 'originalSize': {
height = widget.node.clientHeight;
width = widget.node.clientWidth;
left = window.screenLeft + clientBounds.x;
top = window.screenTop + (window.outerHeight - window.innerHeight) + offsetY;
if (environment.electron.is()) {
top = window.screenTop + clientBounds.y;
}
break;
}
case 'halfWidth': {
height = window.innerHeight - (window.outerHeight - window.innerHeight);
vladarama marked this conversation as resolved.
Show resolved Hide resolved
width = window.innerWidth / 2;
left = window.screenLeft;
top = window.screenTop;
if (!environment.electron.is()) {
height = window.innerHeight + clientBounds.y - offsetY;
}
break;
}
case 'fullSize': {
height = window.innerHeight - (window.outerHeight - window.innerHeight);
width = window.innerWidth;
left = window.screenLeft;
top = window.screenTop;
if (!environment.electron.is()) {
height = window.innerHeight + clientBounds.y - offsetY;
}
break;
}
}
return [height, width, left, top];
}

focus(win: Window): void {
win.focus();
}
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/electron-main/electron-main-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ export class ElectronMainApplication {
electronWindow.webContents.setWindowOpenHandler(() => {
const { minWidth, minHeight } = this.getDefaultOptions();
const options: BrowserWindowConstructorOptions = {
...this.getDefaultTheiaWindowBounds(),
...this.getDefaultTheiaSecondaryWindowBounds(),
// We always need the native window frame for now because the secondary window does not have Theia's title bar by default.
// In 'custom' title bar mode this would leave the window without any window controls (close, min, max)
// TODO set to this.useNativeWindowFrame when secondary windows support a custom title bar.
Expand Down Expand Up @@ -461,6 +461,10 @@ export class ElectronMainApplication {
};
}

protected getDefaultTheiaSecondaryWindowBounds(): TheiaBrowserWindowOptions {
return {};
}

protected getDefaultTheiaWindowBounds(): TheiaBrowserWindowOptions {
// The `screen` API must be required when the application is ready.
// See: https://electronjs.org/docs/api/screen#screen
Expand Down
Loading