Skip to content

Commit

Permalink
Set initial size and position of secondary windows (#13201)
Browse files Browse the repository at this point in the history
This implements the initial size and positions of secondary windows. It also adds a preference, called `Secondary Window Placement` allowing the user to choose the placement of the secondary windows between:
- originalSize: same size as the widget.
- halfWidth: half the size of the Theia application, positioned to the side.
- fullScreen: the secondary window will take up the full screen.
Also allows to display secondary windows on top.

Signed-off-by: Marc Dumais <marc.dumais@ericsson.com>
Signed-off-by: Vlad Arama <vlad.arama@ericsson.com>
Co-authored-by: Marc Dumais <marc.dumais@ericsson.com>
Co-authored-by: Vlad Arama <vlad.arama@ericsson.com>
  • Loading branch information
3 people committed Dec 21, 2023
1 parent 0055c8c commit 94103a2
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 2 deletions.
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';
}
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);
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 @@ -397,7 +397,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 @@ -463,6 +463,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

0 comments on commit 94103a2

Please sign in to comment.