From 5b2f8b84ab18f8a0ea4770892624b7101a29bb29 Mon Sep 17 00:00:00 2001 From: seantan22 Date: Tue, 16 Feb 2021 12:21:01 -0500 Subject: [PATCH] Preferences: Support window.zoomLevel Preference: Fixes #8751 What it does - Adds support to specify window zoom level as a preference How to test 1. Open `Preferences` view and locate the `window.zoomLevel` preference 2. Enter in a custom value or change the zoom level using ctrl + / - / 0 3. Observe that the zoom level preference is updated 4. Reload the window and observe that the preferred zoom level is restored Signed-off-by: seantan22 --- .../menu/electron-menu-contribution.ts | 48 ++++++++++++++--- .../window/electron-window-module.ts | 2 + .../window/electron-window-preferences.ts | 52 +++++++++++++++++++ .../window/electron-window-service.ts | 37 ++++++++++++- 4 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 packages/core/src/electron-browser/window/electron-window-preferences.ts diff --git a/packages/core/src/electron-browser/menu/electron-menu-contribution.ts b/packages/core/src/electron-browser/menu/electron-menu-contribution.ts index 6a0e01f9c2b8a..8acec14d30245 100644 --- a/packages/core/src/electron-browser/menu/electron-menu-contribution.ts +++ b/packages/core/src/electron-browser/menu/electron-menu-contribution.ts @@ -20,10 +20,11 @@ import { Command, CommandContribution, CommandRegistry, isOSX, isWindows, MenuModelRegistry, MenuContribution, Disposable } from '../../common'; -import { KeybindingContribution, KeybindingRegistry } from '../../browser'; +import { KeybindingContribution, KeybindingRegistry, PreferenceScope, PreferenceService } from '../../browser'; import { FrontendApplication, FrontendApplicationContribution, CommonMenus } from '../../browser'; import { ElectronMainMenuFactory } from './electron-main-menu-factory'; import { FrontendApplicationStateService, FrontendApplicationState } from '../../browser/frontend-application-state'; +import { WindowPreferences } from '../window/electron-window-preferences'; export namespace ElectronCommands { export const TOGGLE_DEVELOPER_TOOLS: Command = { @@ -68,9 +69,21 @@ export namespace ElectronMenus { @injectable() export class ElectronMenuContribution implements FrontendApplicationContribution, CommandContribution, MenuContribution, KeybindingContribution { + // Specified in window preferences schema + protected ZOOM_MIN = -8; + protected ZOOM_MAX = 9; + // Amount to increment or decrement the window zoom level. + protected ZOOM_VARIATION = 0.5; + @inject(FrontendApplicationStateService) protected readonly stateService: FrontendApplicationStateService; + @inject(PreferenceService) + protected readonly preferenceService: PreferenceService; + + @inject(WindowPreferences) + protected readonly windowPreferences: WindowPreferences; + constructor( @inject(ElectronMainMenuFactory) protected readonly factory: ElectronMainMenuFactory ) { } @@ -152,19 +165,31 @@ export class ElectronMenuContribution implements FrontendApplicationContribution }); registry.registerCommand(ElectronCommands.ZOOM_IN, { - execute: () => { + execute: async () => { const webContents = currentWindow.webContents; - webContents.setZoomLevel(webContents.zoomLevel + 0.5); + let zoomLevel = this.roundZoomLevel(webContents.zoomLevel, this.ZOOM_VARIATION, true) + this.ZOOM_VARIATION; + if (zoomLevel > this.ZOOM_MAX) { + zoomLevel = this.ZOOM_MAX; + return; + }; + this.preferenceService.set('window.zoomLevel', zoomLevel, PreferenceScope.User); } }); registry.registerCommand(ElectronCommands.ZOOM_OUT, { - execute: () => { + execute: async () => { const webContents = currentWindow.webContents; - webContents.setZoomLevel(webContents.zoomLevel - 0.5); + let zoomLevel = this.roundZoomLevel(webContents.zoomLevel, this.ZOOM_VARIATION, false) - this.ZOOM_VARIATION; + if (zoomLevel < this.ZOOM_MIN) { + zoomLevel = this.ZOOM_MIN; + return; + }; + this.preferenceService.set('window.zoomLevel', zoomLevel, PreferenceScope.User); } }); registry.registerCommand(ElectronCommands.RESET_ZOOM, { - execute: () => currentWindow.webContents.setZoomLevel(0) + execute: async () => { + this.preferenceService.set('window.zoomLevel', 0, PreferenceScope.User); + } }); } @@ -224,4 +249,15 @@ export class ElectronMenuContribution implements FrontendApplicationContribution }); } + /** + * Returns the zoom level rounded to the nearest multiple of a specified value. + * + * @param zoomLevel the current window zoom level. + * @param nearestMultiple the nearest multiple to round to. + * @param isIncreasing true, if rounding before increasing zoom; false, if rounding before decreasing zoom. + */ + private roundZoomLevel(zoomLevel: number, nearestMultiple: number, isIncreasing: boolean): number { + return isIncreasing ? Math.floor(zoomLevel / nearestMultiple) * nearestMultiple : Math.ceil(zoomLevel / nearestMultiple) * nearestMultiple; + } + } diff --git a/packages/core/src/electron-browser/window/electron-window-module.ts b/packages/core/src/electron-browser/window/electron-window-module.ts index 31c2dff1a3227..750ac9387ee58 100644 --- a/packages/core/src/electron-browser/window/electron-window-module.ts +++ b/packages/core/src/electron-browser/window/electron-window-module.ts @@ -22,11 +22,13 @@ import { ElectronClipboardService } from '../electron-clipboard-service'; import { ClipboardService } from '../../browser/clipboard-service'; import { ElectronMainWindowService, electronMainWindowServicePath } from '../../electron-common/electron-main-window-service'; import { ElectronIpcConnectionProvider } from '../messaging/electron-ipc-connection-provider'; +import { bindWindowPreferences } from './electron-window-preferences'; export default new ContainerModule(bind => { bind(ElectronMainWindowService).toDynamicValue(context => ElectronIpcConnectionProvider.createProxy(context.container, electronMainWindowServicePath) ).inSingletonScope(); + bindWindowPreferences(bind); bind(WindowService).to(ElectronWindowService).inSingletonScope(); bind(FrontendApplicationContribution).toService(WindowService); bind(ClipboardService).to(ElectronClipboardService).inSingletonScope(); diff --git a/packages/core/src/electron-browser/window/electron-window-preferences.ts b/packages/core/src/electron-browser/window/electron-window-preferences.ts new file mode 100644 index 0000000000000..46ba553849cff --- /dev/null +++ b/packages/core/src/electron-browser/window/electron-window-preferences.ts @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (C) 2021 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { interfaces } from 'inversify'; +import { createPreferenceProxy, PreferenceContribution, PreferenceProxy, PreferenceSchema, PreferenceService } from '../../browser/preferences'; + +export const windowPreferencesSchema: PreferenceSchema = { + type: 'object', + scope: 'application', + properties: { + 'window.zoomLevel': { + 'type': 'number', + 'default': 0, + 'minimum': -8, + 'maximum': 9, + 'description': 'Adjust the zoom level of the window. The original size is 0. Each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller.' + }, + } +}; + +export class WindowConfiguration { + 'window.zoomLevel': number; +} + +export const WindowPreferences = Symbol('WindowPreferences'); +export type WindowPreferences = PreferenceProxy; + +export function createWindowPreferences(preferences: PreferenceService): WindowPreferences { + return createPreferenceProxy(preferences, windowPreferencesSchema); +} + +export function bindWindowPreferences(bind: interfaces.Bind): void { + bind(WindowPreferences).toDynamicValue(ctx => { + const preferences = ctx.container.get(PreferenceService); + return createWindowPreferences(preferences); + }).inSingletonScope(); + + bind(PreferenceContribution).toConstantValue({ schema: windowPreferencesSchema }); +} diff --git a/packages/core/src/electron-browser/window/electron-window-service.ts b/packages/core/src/electron-browser/window/electron-window-service.ts index 75e5f351cb37f..357ae68ec035a 100644 --- a/packages/core/src/electron-browser/window/electron-window-service.ts +++ b/packages/core/src/electron-browser/window/electron-window-service.ts @@ -14,11 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, inject } from 'inversify'; +import { injectable, inject, postConstruct } from 'inversify'; import { remote } from 'electron'; import { NewWindowOptions } from '../../browser/window/window-service'; import { DefaultWindowService } from '../../browser/window/default-window-service'; import { ElectronMainWindowService } from '../../electron-common/electron-main-window-service'; +import { WindowConfiguration, WindowPreferences } from './electron-window-preferences'; +import { PreferenceChangeEvent } from '../../browser'; @injectable() export class ElectronWindowService extends DefaultWindowService { @@ -33,14 +35,32 @@ export class ElectronWindowService extends DefaultWindowService { */ protected closeOnUnload: boolean = false; + /** + * Most recent value set for window.zoomLevel preference + */ + protected prevPreferredZoomLevel: number | undefined; + @inject(ElectronMainWindowService) protected readonly delegate: ElectronMainWindowService; + @inject(WindowPreferences) + protected readonly windowPreferences: WindowPreferences; + openNewWindow(url: string, { external }: NewWindowOptions = {}): undefined { this.delegate.openNewWindow(url, { external }); return undefined; } + @postConstruct() + protected init(): void { + // if window.zoomLevel is not default (0) on startup, `Preferences` will fire event + this.windowPreferences.onPreferenceChanged((e: PreferenceChangeEvent) => { + if (e.preferenceName === 'window.zoomLevel') { + this.updateWindowZoomLevel(); + } + }); + } + registerUnloadListeners(): void { window.addEventListener('beforeunload', event => { if (this.isUnloading) { @@ -85,4 +105,19 @@ export class ElectronWindowService extends DefaultWindowService { }); return response === 0; // 'Yes', close the window. } + + /** + * Updates the window zoom level based on the value set in `Preferences`. + */ + protected updateWindowZoomLevel(): void { + const preferredZoomLevel = this.windowPreferences['window.zoomLevel']; + if (this.prevPreferredZoomLevel === preferredZoomLevel) { + return; + } + this.prevPreferredZoomLevel = preferredZoomLevel; + const webContents = remote.getCurrentWindow().webContents; + if (webContents.getZoomLevel() !== preferredZoomLevel) { + webContents.setZoomLevel(preferredZoomLevel); + } + } }