diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index ed6b696e40c..eef6c3f49d0 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; class WindowManager { @@ -43,18 +43,6 @@ class WindowManager { this._zoomFactor = zoomFactor; } - // --- Pixel Ratio - public getPixelRatio(): number { - let ctx: any = document.createElement('canvas').getContext('2d'); - let dpr = window.devicePixelRatio || 1; - let bsr = ctx.webkitBackingStorePixelRatio || - ctx.mozBackingStorePixelRatio || - ctx.msBackingStorePixelRatio || - ctx.oBackingStorePixelRatio || - ctx.backingStorePixelRatio || 1; - return dpr / bsr; - } - // --- Fullscreen private _fullscreen: boolean = false; private readonly _onDidChangeFullscreen = new Emitter(); @@ -73,6 +61,92 @@ class WindowManager { } } +class PixelRatioImpl extends Disposable { + + private readonly _onDidChange = this._register(new Emitter()); + public readonly onDidChange = this._onDidChange.event; + + private _value: number; + private _removeListener: () => void; + + public get value(): number { + return this._value; + } + + constructor() { + super(); + + this._value = this._getPixelRatio(); + this._removeListener = this._installResolutionListener(); + } + + public override dispose() { + this._removeListener(); + super.dispose(); + } + + private _installResolutionListener(): () => void { + // See https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes + const mediaQueryList = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); + const listener = () => this._updateValue(); + mediaQueryList.addEventListener('change', listener, { once: true }); + return () => { + mediaQueryList.removeEventListener('change', listener); + }; + } + + private _updateValue(): void { + this._value = this._getPixelRatio(); + this._onDidChange.fire(this._value); + this._removeListener = this._installResolutionListener(); + } + + private _getPixelRatio(): number { + const ctx: any = document.createElement('canvas').getContext('2d'); + const dpr = window.devicePixelRatio || 1; + const bsr = ctx.webkitBackingStorePixelRatio || + ctx.mozBackingStorePixelRatio || + ctx.msBackingStorePixelRatio || + ctx.oBackingStorePixelRatio || + ctx.backingStorePixelRatio || 1; + return dpr / bsr; + } +} + +class PixelRatioFacade { + + private _pixelRatioMonitor: PixelRatioImpl | null = null; + private _getOrCreatePixelRatioMonitor(): PixelRatioImpl { + if (!this._pixelRatioMonitor) { + this._pixelRatioMonitor = new PixelRatioImpl(); + } + return this._pixelRatioMonitor; + } + + /** + * Get the current value. + */ + public get value(): number { + return this._getOrCreatePixelRatioMonitor().value; + } + + /** + * Listen for changes. + */ + public get onDidChange(): Event { + return this._getOrCreatePixelRatioMonitor().onDidChange; + } +} + +/** + * Returns the pixel ratio. + * + * This is useful for rendering elements at native screen resolution or for being used as + * a cache key when storing font measurements. Fonts might render differently depending on resolution + * and any measurements need to be discarded for example when a window is moved from a monitor to another. + */ +export const PixelRatio = new PixelRatioFacade(); + /** A zoom index, e.g. 1, 2, 3 */ export function setZoomLevel(zoomLevel: number, isTrusted: boolean): void { WindowManager.INSTANCE.setZoomLevel(zoomLevel, isTrusted); @@ -96,10 +170,6 @@ export function setZoomFactor(zoomFactor: number): void { WindowManager.INSTANCE.setZoomFactor(zoomFactor); } -export function getPixelRatio(): number { - return WindowManager.INSTANCE.getPixelRatio(); -} - export function setFullscreen(fullscreen: boolean): void { WindowManager.INSTANCE.setFullscreen(fullscreen); } diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index 8a3d930b59f..8c45cb8515d 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -74,6 +74,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat this._register(this._containerObserver.onDidChange(() => this._recomputeOptions())); this._register(FontMeasurements.onDidChange(() => this._recomputeOptions())); this._register(browser.onDidChangeZoomLevel(() => this._recomputeOptions())); + this._register(browser.PixelRatio.onDidChange(() => this._recomputeOptions())); this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions())); } @@ -117,7 +118,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat outerWidth: this._containerObserver.getWidth(), outerHeight: this._containerObserver.getHeight(), emptySelectionClipboard: browser.isWebKit || browser.isFirefox, - pixelRatio: browser.getPixelRatio(), + pixelRatio: browser.PixelRatio.value, zoomLevel: browser.getZoomLevel(), accessibilitySupport: ( this._accessibilityService.isScreenReaderOptimized() @@ -151,10 +152,6 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat this._containerObserver.observe(dimension); } - public observePixelRatio(): void { - this._recomputeOptions(); - } - public setIsDominatedByLongLines(isDominatedByLongLines: boolean): void { if (this._isDominatedByLongLines === isDominatedByLongLines) { return; diff --git a/src/vs/editor/browser/config/fontMeasurements.ts b/src/vs/editor/browser/config/fontMeasurements.ts index e52dacd9c3d..6c7a6e72571 100644 --- a/src/vs/editor/browser/config/fontMeasurements.ts +++ b/src/vs/editor/browser/config/fontMeasurements.ts @@ -125,7 +125,7 @@ class FontMeasurementsImpl extends Disposable { // Hey, it's Bug 14341 ... we couldn't read readConfig = new FontInfo({ zoomLevel: browser.getZoomLevel(), - pixelRatio: browser.getPixelRatio(), + pixelRatio: browser.PixelRatio.value, fontFamily: readConfig.fontFamily, fontWeight: readConfig.fontWeight, fontSize: readConfig.fontSize, @@ -221,7 +221,7 @@ class FontMeasurementsImpl extends Disposable { const canTrustBrowserZoomLevel = (browser.getTimeSinceLastZoomLevelChanged() > 2000); return new FontInfo({ zoomLevel: browser.getZoomLevel(), - pixelRatio: browser.getPixelRatio(), + pixelRatio: browser.PixelRatio.value, fontFamily: bareFontInfo.fontFamily, fontWeight: bareFontInfo.fontWeight, fontSize: bareFontInfo.fontSize, diff --git a/src/vs/editor/browser/view/view.ts b/src/vs/editor/browser/view/view.ts index 525c25e147a..4bd12c7a8c5 100644 --- a/src/vs/editor/browser/view/view.ts +++ b/src/vs/editor/browser/view/view.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import * as browser from 'vs/base/browser/browser'; import { Selection } from 'vs/editor/common/core/selection'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -67,7 +66,6 @@ export class View extends ViewEventHandler { private readonly _scrollbar: EditorScrollbar; private readonly _context: ViewContext; - private _configPixelRatio: number; private _selections: Selection[]; // The view lines @@ -107,7 +105,6 @@ export class View extends ViewEventHandler { // The view context is passed on to most classes (basically to reduce param. counts in ctors) this._context = new ViewContext(configuration, themeService.getColorTheme(), model); - this._configPixelRatio = this._context.configuration.options.get(EditorOption.pixelRatio); // Ensure the view is the first event handler in order to update the layout this._context.addEventHandler(this); @@ -306,7 +303,6 @@ export class View extends ViewEventHandler { this._scheduleRender(); } public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - this._configPixelRatio = this._context.configuration.options.get(EditorOption.pixelRatio); this.domNode.setClassName(this._getEditorClassName()); this._applyLayout(); return false; @@ -418,12 +414,6 @@ export class View extends ViewEventHandler { viewPart.render(renderingContext); viewPart.onDidRender(); } - - // Try to detect browser zooming and paint again if necessary - if (Math.abs(browser.getPixelRatio() - this._configPixelRatio) > 0.001) { - // looks like the pixel ratio has changed - this._context.configuration.observePixelRatio(); - } } // --- BEGIN CodeEditor helpers diff --git a/src/vs/editor/common/config/editorConfiguration.ts b/src/vs/editor/common/config/editorConfiguration.ts index abb2296688d..74859b2d519 100644 --- a/src/vs/editor/common/config/editorConfiguration.ts +++ b/src/vs/editor/common/config/editorConfiguration.ts @@ -39,10 +39,6 @@ export interface IEditorConfiguration extends IDisposable { * Recompute options with new reference element dimensions. */ observeContainer(dimension?: IDimension): void; - /** - * Recompute options with new pixel ratio. - */ - observePixelRatio(): void; /** * Set if the current model is dominated by long lines. */ diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 7bef4148416..526c839e9fd 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -7,7 +7,7 @@ import 'vs/workbench/browser/style'; import { localize } from 'vs/nls'; import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { RunOnceScheduler, runWhenIdle, timeout } from 'vs/base/common/async'; -import { getZoomLevel, isFirefox, isSafari, isChrome, getPixelRatio } from 'vs/base/browser/browser'; +import { getZoomLevel, isFirefox, isSafari, isChrome, PixelRatio } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -296,7 +296,7 @@ export class Workbench extends Layout { } } - FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel(), getPixelRatio())); + FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel(), PixelRatio.value)); } private storeFontInfo(storageService: IStorageService): void { diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index f7a7aeb6057..055eccfc23e 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; +import { PixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import { Dimension, append, $, addStandardDisposableListener } from 'vs/base/browser/dom'; import { ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; @@ -85,10 +85,10 @@ export class DisassemblyView extends EditorPane { this._disassembledInstructions = undefined; this._onDidChangeStackFrame = new Emitter(); this._previousDebuggingState = _debugService.state; - this._fontInfo = BareFontInfo.createFromRawSettings(_configurationService.getValue('editor'), getZoomLevel(), getPixelRatio()); + this._fontInfo = BareFontInfo.createFromRawSettings(_configurationService.getValue('editor'), getZoomLevel(), PixelRatio.value); this._register(_configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('editor')) { - this._fontInfo = BareFontInfo.createFromRawSettings(_configurationService.getValue('editor'), getZoomLevel(), getPixelRatio()); + this._fontInfo = BareFontInfo.createFromRawSettings(_configurationService.getValue('editor'), getZoomLevel(), PixelRatio.value); } if (e.affectsConfiguration('debug')) { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 2b10ae26572..c799fdf4296 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -22,7 +22,7 @@ import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/comm import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; -import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; +import { PixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import { CellEditState, ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, INotebookEditorCreationOptions, INotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_DIFF_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { Emitter, Event } from 'vs/base/common/event'; @@ -105,7 +105,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._notebookOptions = new NotebookOptions(this.configurationService, notebookExecutionStateService); this._register(this._notebookOptions); const editorOptions = this.configurationService.getValue('editor'); - this._fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio())); + this._fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), PixelRatio.value)); this._revealFirst = true; this._outputRenderer = this.instantiationService.createInstance(OutputRenderer, this); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index 6b3b5f56da3..2f49f164971 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -28,7 +28,7 @@ import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/vie import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; +import { PixelRatio, getZoomLevel } from 'vs/base/browser/browser'; export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { private readonly lineHeight: number; @@ -37,7 +37,7 @@ export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate('editor'); - this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio()).lineHeight; + this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), PixelRatio.value).lineHeight; } getHeight(element: DiffElementViewModelBase): number { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 03b15081b51..133665b1995 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; +import { PixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import * as aria from 'vs/base/browser/ui/aria/aria'; @@ -637,7 +637,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _generateFontInfo(): void { const editorOptions = this.configurationService.getValue('editor'); - this._fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio())); + this._fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), PixelRatio.value)); } private _createBody(parent: HTMLElement): void { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 167fc7fb683..129938c4c42 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; +import { PixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import { Emitter, Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { Iterable } from 'vs/base/common/iterator'; @@ -508,7 +508,7 @@ export class NotebookService extends Disposable implements INotebookService { // there is a `::before` or `::after` text decoration whose position is above or below current line // we at least make sure that the editor top padding is at least one line const editorOptions = this.configurationService.getValue('editor'); - updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio()).lineHeight + 2); + updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), PixelRatio.value).lineHeight + 2); decorationTriggeredAdjustment = true; break; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 69baca44a1d..358354dc445 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; +import { PixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -60,7 +60,7 @@ export class NotebookCellListDelegate extends Disposable implements IListVirtual super(); const editorOptions = this.configurationService.getValue('editor'); - this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio()).lineHeight; + this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), PixelRatio.value).lineHeight; } getHeight(element: CellViewModel): number {