| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview |
| * 'settings-display' is the settings subpage for display settings. |
| */ |
| |
| import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js'; |
| import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js'; |
| import 'chrome://resources/cr_elements/cr_tabs/cr_tabs.js'; |
| import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js'; |
| import 'chrome://resources/cr_elements/policy/cr_policy_pref_indicator.js'; |
| import 'chrome://resources/cr_elements/md_select.css.js'; |
| import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js'; |
| import './display_layout.js'; |
| import './display_overscan_dialog.js'; |
| import '../settings_scheduler_slider/settings_scheduler_slider.js'; |
| import '../../controls/settings_slider.js'; |
| import '../../settings_shared.css.js'; |
| import '../../settings_vars.css.js'; |
| import '../../controls/settings_dropdown_menu.js'; |
| import 'chrome://resources/cr_elements/cr_slider/cr_slider.js'; |
| |
| import {assert} from 'chrome://resources/js/assert.m.js'; |
| import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.js'; |
| import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js'; |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; |
| import {flush, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {Setting} from '../../mojom-webui/setting.mojom-webui.js'; |
| import {Route} from '../../router.js'; |
| import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js'; |
| import {routes} from '../os_route.js'; |
| import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js'; |
| import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js'; |
| |
| import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl, getDisplayApi} from './device_page_browser_proxy.js'; |
| |
| /** |
| * @typedef {{ |
| * value: (!{ |
| * recommended: (boolean|undefined), |
| * external_width: (number|undefined), |
| * external_height: (number|undefined), |
| * external_use_native: (boolean|undefined), |
| * external_scale_percentage: (number|undefined), |
| * internal_scale_percentage: (number|undefined) |
| * }|null) |
| * }} |
| */ |
| let DisplayResolutionPrefObject; |
| |
| /** |
| * The types of Night Light automatic schedule. The values of the enum values |
| * are synced with the pref "prefs.ash.night_light.schedule_type". |
| * @enum {number} |
| */ |
| const NightLightScheduleType = { |
| NEVER: 0, |
| SUNSET_TO_SUNRISE: 1, |
| CUSTOM: 2, |
| }; |
| |
| /** |
| * @constructor |
| * @extends {PolymerElement} |
| * @implements {DeepLinkingBehaviorInterface} |
| * @implements {I18nBehaviorInterface} |
| * @implements {PrefsBehaviorInterface} |
| * @implements {RouteObserverBehaviorInterface} |
| */ |
| const SettingsDisplayElementBase = mixinBehaviors( |
| [DeepLinkingBehavior, I18nBehavior, PrefsBehavior, RouteObserverBehavior], |
| PolymerElement); |
| |
| /** @polymer */ |
| class SettingsDisplayElement extends SettingsDisplayElementBase { |
| static get is() { |
| return 'settings-display'; |
| } |
| |
| static get template() { |
| return html`{__html_template__}`; |
| } |
| |
| static get properties() { |
| return { |
| /** |
| * @type {!chrome.settingsPrivate.PrefObject} |
| * @private |
| */ |
| selectedModePref_: { |
| type: Object, |
| value() { |
| return { |
| key: 'fakeDisplaySliderPref', |
| type: chrome.settingsPrivate.PrefType.NUMBER, |
| value: 0, |
| }; |
| }, |
| }, |
| |
| /** |
| * @type {!chrome.settingsPrivate.PrefObject} |
| * @private |
| */ |
| selectedZoomPref_: { |
| type: Object, |
| value() { |
| return { |
| key: 'fakeDisplaySliderZoomPref', |
| type: chrome.settingsPrivate.PrefType.NUMBER, |
| value: 0, |
| }; |
| }, |
| }, |
| |
| /** |
| * Array of displays. |
| * @type {!Array<!chrome.system.display.DisplayUnitInfo>} |
| */ |
| displays: Array, |
| |
| /** |
| * Array of display layouts. |
| * @type {!Array<!chrome.system.display.DisplayLayout>} |
| */ |
| layouts: Array, |
| |
| /** |
| * String listing the ids in displays. Used to observe changes to the |
| * display configuration (i.e. when a display is added or removed). |
| */ |
| displayIds: {type: String, observer: 'onDisplayIdsChanged_'}, |
| |
| /** Primary display id */ |
| primaryDisplayId: String, |
| |
| /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */ |
| selectedDisplay: Object, |
| |
| /** Id passed to the overscan dialog. */ |
| overscanDisplayId: { |
| type: String, |
| notify: true, |
| }, |
| |
| /** Ids for mirroring destination displays. */ |
| mirroringDestinationIds: Array, |
| |
| /** @private {!Array<number>} Mode index values for slider. */ |
| modeValues_: Array, |
| |
| /** |
| * @private {!Array<SliderTick>} Display zoom slider tick |
| * values. |
| */ |
| zoomValues_: Array, |
| |
| /** @private {!DropdownMenuOptionList} */ |
| displayModeList_: { |
| type: Array, |
| value: [], |
| }, |
| |
| /** @private {!DropdownMenuOptionList} */ |
| refreshRateList_: { |
| type: Array, |
| value: [], |
| }, |
| |
| /** @private */ |
| unifiedDesktopAvailable_: { |
| type: Boolean, |
| value() { |
| return loadTimeData.getBoolean('unifiedDesktopAvailable'); |
| }, |
| }, |
| |
| /** @private */ |
| ambientColorAvailable_: { |
| type: Boolean, |
| value() { |
| return loadTimeData.getBoolean('deviceSupportsAmbientColor'); |
| }, |
| }, |
| |
| /** @private */ |
| listAllDisplayModes_: { |
| type: Boolean, |
| value() { |
| return loadTimeData.getBoolean('listAllDisplayModes'); |
| }, |
| }, |
| |
| /** @private */ |
| unifiedDesktopMode_: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| /** |
| * @type {!chrome.settingsPrivate.PrefObject} |
| * @private |
| */ |
| selectedParentModePref_: { |
| type: Object, |
| value: function() { |
| return { |
| key: 'fakeDisplayParentModePref', |
| type: chrome.settingsPrivate.PrefType.NUMBER, |
| value: 0, |
| }; |
| }, |
| }, |
| |
| /** @private */ |
| scheduleTypesList_: { |
| type: Array, |
| value() { |
| return [ |
| { |
| name: loadTimeData.getString('displayNightLightScheduleNever'), |
| value: NightLightScheduleType.NEVER, |
| }, |
| { |
| name: loadTimeData.getString( |
| 'displayNightLightScheduleSunsetToSunRise'), |
| value: NightLightScheduleType.SUNSET_TO_SUNRISE, |
| }, |
| { |
| name: loadTimeData.getString('displayNightLightScheduleCustom'), |
| value: NightLightScheduleType.CUSTOM, |
| }, |
| ]; |
| }, |
| }, |
| |
| /** @private */ |
| shouldOpenCustomScheduleCollapse_: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| /** @private */ |
| nightLightScheduleSubLabel_: String, |
| |
| /** @private */ |
| logicalResolutionText_: String, |
| |
| /** @private {!Array<string>} */ |
| displayTabNames_: Array, |
| |
| /** @private */ |
| selectedTab_: Number, |
| |
| /** |
| * Contains the settingId of any deep link that wasn't able to be shown, |
| * null otherwise. |
| * @private {?Setting} |
| */ |
| pendingSettingId_: { |
| type: Number, |
| value: null, |
| }, |
| |
| /** |
| * Used by DeepLinkingBehavior to focus this page's deep links. |
| * @type {!Set<!Setting>} |
| */ |
| supportedSettingIds: { |
| type: Object, |
| value: () => new Set([ |
| Setting.kDisplaySize, |
| Setting.kNightLight, |
| Setting.kDisplayOrientation, |
| Setting.kDisplayArrangement, |
| Setting.kDisplayResolution, |
| Setting.kDisplayRefreshRate, |
| Setting.kDisplayMirroring, |
| Setting.kAllowWindowsToSpanDisplays, |
| Setting.kAmbientColors, |
| Setting.kTouchscreenCalibration, |
| Setting.kNightLightColorTemperature, |
| Setting.kDisplayOverscan, |
| ]), |
| }, |
| |
| }; |
| } |
| |
| static get observers() { |
| return [ |
| 'updateNightLightScheduleSettings_(prefs.ash.night_light.schedule_type.*,' + |
| ' prefs.ash.night_light.enabled.*)', |
| 'onSelectedModeChange_(selectedModePref_.value)', |
| 'onSelectedParentModeChange_(selectedParentModePref_.value)', |
| 'onSelectedZoomChange_(selectedZoomPref_.value)', |
| 'onDisplaysChanged_(displays.*)', |
| |
| ]; |
| } |
| |
| /** @override */ |
| constructor() { |
| super(); |
| |
| /** |
| * This represents the index of the mode with the highest refresh rate at |
| * the current resolution. |
| * @private {number} |
| */ |
| this.currentSelectedParentModeIndex_ = -1; |
| |
| /** |
| * This is the index of the currently selected mode. |
| * @private {number} Selected mode index received from chrome. |
| */ |
| this.currentSelectedModeIndex_ = -1; |
| |
| /** |
| * Listener for chrome.system.display.onDisplayChanged events. |
| * @type {function(void)|undefined} |
| * @private |
| */ |
| this.displayChangedListener_ = undefined; |
| |
| /** @private {string} */ |
| this.invalidDisplayId_ = loadTimeData.getString('invalidDisplayId'); |
| |
| /** @private {!Route|undefined} */ |
| this.currentRoute_ = undefined; |
| |
| /** @private {!DevicePageBrowserProxy} */ |
| this.browserProxy_ = DevicePageBrowserProxyImpl.getInstance(); |
| |
| /** |
| * Maps a parentModeIndex to the list of possible refresh rates. |
| * All modes have a modeIndex corresponding to the index in the selected |
| * display's mode list. Parent mode indexes represent the mode with the |
| * highest refresh rate at a given resolution. There is 1 and only 1 |
| * parentModeIndex for each possible resolution . |
| * @private {!Map<number, DropdownMenuOptionList>} |
| */ |
| this.parentModeToRefreshRateMap_ = new Map(); |
| |
| /** |
| * Map containing an entry for each display mode mapping its modeIndex to |
| * the corresponding parentModeIndex value. |
| * @private {!Map<number, number>} Mode index values for slider. |
| */ |
| this.modeToParentModeMap_ = new Map(); |
| } |
| |
| /** @override */ |
| connectedCallback() { |
| super.connectedCallback(); |
| |
| this.displayChangedListener_ = |
| this.displayChangedListener_ || (() => this.getDisplayInfo_()); |
| getDisplayApi().onDisplayChanged.addListener(this.displayChangedListener_); |
| |
| this.getDisplayInfo_(); |
| this.$.displaySizeSlider.updateValueInstantly = false; |
| } |
| |
| /** @override */ |
| disconnectedCallback() { |
| super.disconnectedCallback(); |
| |
| getDisplayApi().onDisplayChanged.removeListener( |
| assert(this.displayChangedListener_)); |
| |
| this.currentSelectedModeIndex_ = -1; |
| this.currentSelectedParentModeIndex_ = -1; |
| } |
| |
| /** |
| * Overridden from DeepLinkingBehavior. |
| * @param {!Setting} settingId |
| * @return {boolean} |
| */ |
| beforeDeepLinkAttempt(settingId) { |
| if (!this.displays) { |
| // On initial page load, displays will not be loaded and deep link |
| // attempt will fail. Suppress warnings by exiting early and try again |
| // in updateDisplayInfo_. |
| return false; |
| } |
| |
| // Continue with deep link attempt. |
| return true; |
| } |
| |
| /** |
| * @param {!Route} newRoute |
| * @param {!Route=} opt_oldRoute |
| */ |
| currentRouteChanged(newRoute, opt_oldRoute) { |
| this.currentRoute_ = newRoute; |
| |
| // When navigating away from the page, deselect any selected display. |
| if (newRoute !== routes.DISPLAY && opt_oldRoute === routes.DISPLAY) { |
| this.browserProxy_.highlightDisplay(this.invalidDisplayId_); |
| return; |
| } |
| |
| // Does not apply to this page. |
| if (newRoute !== routes.DISPLAY) { |
| this.pendingSettingId_ = null; |
| return; |
| } |
| |
| this.attemptDeepLink().then(result => { |
| if (!result.deepLinkShown && result.pendingSettingId) { |
| // Store any deep link settingId that wasn't shown so we can try again |
| // in updateDisplayInfo_. |
| this.pendingSettingId_ = result.pendingSettingId; |
| } |
| }); |
| } |
| |
| /** |
| * Shows or hides the overscan dialog. |
| * @param {boolean} showOverscan |
| * @private |
| */ |
| showOverscanDialog_(showOverscan) { |
| if (showOverscan) { |
| this.$.displayOverscan.open(); |
| this.$.displayOverscan.focus(); |
| } else { |
| this.$.displayOverscan.close(); |
| } |
| } |
| |
| /** @private */ |
| onDisplayIdsChanged_() { |
| // Close any overscan dialog (which will cancel any overscan operation) |
| // if displayIds changes. |
| this.showOverscanDialog_(false); |
| } |
| |
| /** @private */ |
| getDisplayInfo_() { |
| /** @type {chrome.system.display.GetInfoFlags} */ const flags = { |
| singleUnified: true, |
| }; |
| getDisplayApi().getInfo( |
| flags, displays => this.displayInfoFetched_(displays)); |
| } |
| |
| /** |
| * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
| * @private |
| */ |
| displayInfoFetched_(displays) { |
| if (!displays.length) { |
| return; |
| } |
| getDisplayApi().getDisplayLayout( |
| layouts => this.displayLayoutFetched_(displays, layouts)); |
| if (this.isMirrored_(displays)) { |
| this.mirroringDestinationIds = displays[0].mirroringDestinationIds; |
| } else { |
| this.mirroringDestinationIds = []; |
| } |
| } |
| |
| /** |
| * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
| * @param {!Array<!chrome.system.display.DisplayLayout>} layouts |
| * @private |
| */ |
| displayLayoutFetched_(displays, layouts) { |
| this.layouts = layouts; |
| this.displays = displays; |
| this.displayTabNames_ = displays.map(({name}) => name); |
| this.updateDisplayInfo_(); |
| } |
| |
| /** |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {number} The index of the currently selected mode of the |
| * |selectedDisplay|. If the display has no modes, returns 0. |
| * @private |
| */ |
| getSelectedModeIndex_(selectedDisplay) { |
| for (let i = 0; i < selectedDisplay.modes.length; ++i) { |
| if (selectedDisplay.modes[i].isSelected) { |
| return i; |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * Checks if the given device policy is enabled. |
| * @param {DisplayResolutionPrefObject} policyPref |
| * @return {boolean} |
| * @private |
| */ |
| isDevicePolicyEnabled_(policyPref) { |
| return policyPref !== undefined && policyPref.value !== null; |
| } |
| |
| /** |
| * Checks if display resolution is managed by device policy. |
| * @param {DisplayResolutionPrefObject} resolutionPref |
| * @return {boolean} |
| * @private |
| */ |
| isDisplayResolutionManagedByPolicy_(resolutionPref) { |
| return this.isDevicePolicyEnabled_(resolutionPref) && |
| (resolutionPref.value.external_use_native !== undefined || |
| (resolutionPref.value.external_width !== undefined && |
| resolutionPref.value.external_height !== undefined)); |
| } |
| |
| /** |
| * Checks if display resolution is managed by policy and the policy |
| * is mandatory. |
| * @param {DisplayResolutionPrefObject} resolutionPref |
| * @return {boolean} |
| * @private |
| */ |
| isDisplayResolutionMandatory_(resolutionPref) { |
| return this.isDisplayResolutionManagedByPolicy_(resolutionPref) && |
| !resolutionPref.value.recommended; |
| } |
| |
| /** |
| * Checks if display scale factor is managed by device policy. |
| * @param {chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @param {DisplayResolutionPrefObject} resolutionPref |
| * @return {boolean} |
| * @private |
| */ |
| isDisplayScaleManagedByPolicy_(selectedDisplay, resolutionPref) { |
| if (!this.isDevicePolicyEnabled_(resolutionPref) || !selectedDisplay) { |
| return false; |
| } |
| if (selectedDisplay.isInternal) { |
| return resolutionPref.value.internal_scale_percentage !== undefined; |
| } |
| return resolutionPref.value.external_scale_percentage !== undefined; |
| } |
| |
| /** |
| * Checks if display scale factor is managed by policy and the policy |
| * is mandatory. |
| * @param {DisplayResolutionPrefObject} resolutionPref |
| * @return {boolean} |
| * @private |
| */ |
| isDisplayScaleMandatory_(selectedDisplay, resolutionPref) { |
| return this.isDisplayScaleManagedByPolicy_( |
| selectedDisplay, resolutionPref) && |
| !resolutionPref.value.recommended; |
| } |
| |
| |
| /** |
| * Parses the display modes for |selectedDisplay|. |displayModeList_| will |
| * contain entries representing a combined resolution + refresh rate. |
| * Only one parse*DisplayModes_ method must be called, depending on the |
| * state of |listAllDisplayModes_|. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @private |
| */ |
| parseCompoundDisplayModes_(selectedDisplay) { |
| assert(!this.listAllDisplayModes_); |
| const optionList = []; |
| for (let i = 0; i < selectedDisplay.modes.length; ++i) { |
| const mode = selectedDisplay.modes[i]; |
| |
| const id = 'displayResolutionMenuItem'; |
| const refreshRate = Math.round(mode.refreshRate * 100) / 100; |
| const resolution = this.i18n( |
| id, mode.width.toString(), mode.height.toString(), |
| refreshRate.toString()); |
| |
| optionList.push({ |
| name: resolution, |
| value: i, |
| }); |
| } |
| this.displayModeList_ = optionList; |
| } |
| |
| /** |
| * Uses the modes of |selectedDisplay| to build a nested map of width => |
| * height => refreshRate => modeIndex. modeIndex is the index of the |
| * resolution + refreshRate combination in |selectedDisplay|'s mode list. |
| * This is used to traverse all possible display modes in ascending order. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {!Map<number, Map<number, Map<number, number>>>} |
| * @private |
| */ |
| createModeMap_(selectedDisplay) { |
| const modes = new Map(); |
| for (let i = 0; i < selectedDisplay.modes.length; ++i) { |
| const mode = selectedDisplay.modes[i]; |
| if (!modes.has(mode.width)) { |
| modes.set(mode.width, new Map()); |
| } |
| |
| if (!modes.get(mode.width).has(mode.height)) { |
| modes.get(mode.width).set(mode.height, new Map()); |
| } |
| |
| // Prefer the first native mode we find, for consistency. |
| if (modes.get(mode.width).get(mode.height).has(mode.refreshRate)) { |
| const existingModeIndex = |
| modes.get(mode.width).get(mode.height).get(mode.refreshRate); |
| const existingMode = selectedDisplay.modes[existingModeIndex]; |
| if (existingMode.isNative || !mode.isNative) { |
| continue; |
| } |
| } |
| modes.get(mode.width).get(mode.height).set(mode.refreshRate, i); |
| } |
| return modes; |
| } |
| |
| /** |
| * Parses the display modes for |selectedDisplay|. |displayModeList_| will |
| * contain entries representing only resolution options. |
| * The 'parentMode' for a resolution is the highest refresh rate. This |
| * method goes through the mode list for a given display creating data |
| * structures so that given a resolution, the default refresh rate is |
| * selected, and other possible refresh rates at that resolution are shown |
| * in a dropdown. Only one parse*DisplayModes_ method must be called, |
| * depending on the state of |listAllDisplayModes_|. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @private |
| */ |
| parseSplitDisplayModes_(selectedDisplay) { |
| assert(this.listAllDisplayModes_); |
| // Clear the mappings before recalculating. |
| this.modeToParentModeMap_ = new Map(); |
| this.parentModeToRefreshRateMap_ = new Map(); |
| this.displayModeList_ = []; |
| |
| // Build the modes into a nested map of width => height => refresh rate. |
| const modes = this.createModeMap_(selectedDisplay); |
| |
| // Traverse the modes ordered by width (asc), height (asc), |
| // refresh rate (desc). |
| const widthsArr = Array.from(modes.keys()).sort(); |
| for (let i = 0; i < widthsArr.length; i++) { |
| const width = widthsArr[i]; |
| const heightsMap = modes.get(width); |
| const heightArr = Array.from(heightsMap.keys()); |
| for (let j = 0; j < heightArr.length; j++) { |
| // The highest/first refresh rate for each width/height pair |
| // (resolution) is the default and therefore the "parent" mode. |
| const height = heightArr[j]; |
| const refreshRates = heightsMap.get(height); |
| const parentModeIndex = this.getParentModeIndex_(refreshRates); |
| this.addResolution_(parentModeIndex, width, height); |
| |
| // For each of the refresh rates at a given resolution, add an entry |
| // to |parentModeToRefreshRateMap_|. This allows us to retrieve a |
| // list of all the possible refresh rates given a resolution's |
| // parentModeIndex. |
| const refreshRatesArr = Array.from(refreshRates.keys()); |
| for (let k = 0; k < refreshRatesArr.length; k++) { |
| const rate = refreshRatesArr[k]; |
| const modeIndex = refreshRates.get(rate); |
| const isInterlaced = selectedDisplay.modes[modeIndex].isInterlaced; |
| |
| this.addRefreshRate_(parentModeIndex, modeIndex, rate, isInterlaced); |
| } |
| } |
| } |
| |
| // Construct mode->parentMode map so we can get parent modes later. |
| for (let i = 0; i < selectedDisplay.modes.length; i++) { |
| const mode = selectedDisplay.modes[i]; |
| const parentModeIndex = |
| this.getParentModeIndex_(modes.get(mode.width).get(mode.height)); |
| this.modeToParentModeMap_.set(i, parentModeIndex); |
| } |
| assert(this.modeToParentModeMap_.size === selectedDisplay.modes.length); |
| |
| // Use the new sort order. |
| this.sortResolutionList_(); |
| } |
| |
| /** |
| * Picks the appropriate parent mode from a refresh rate -> mode index map. |
| * Currently this chooses the mode with the highest refresh rate. |
| * @param {Map<number,number>} refreshRates each possible refresh rate |
| * mapped to the corresponding mode index. |
| * @private |
| */ |
| getParentModeIndex_(refreshRates) { |
| const maxRefreshRate = Math.max(...refreshRates.keys()); |
| return refreshRates.get(maxRefreshRate); |
| } |
| |
| /** |
| * Adds a an entry in |displayModeList_| for the resolution represented by |
| * |width| and |height| and possible |refreshRates|. |
| * @param {number} parentModeIndex |
| * @param {number} width |
| * @param {number} height |
| * @private |
| */ |
| addResolution_(parentModeIndex, width, height) { |
| assert(this.listAllDisplayModes_); |
| |
| // Add an entry in the outer map for |parentModeIndex|. The inner |
| // array (the value at |parentModeIndex|) will be populated with all |
| // possible refresh rates for the given resolution. |
| this.parentModeToRefreshRateMap_.set(parentModeIndex, []); |
| |
| const resolutionOption = |
| this.i18n('displayResolutionOnlyMenuItem', width, height); |
| |
| // Only store one entry in the |resolutionList| per resolution, |
| // mapping it to the parentModeIndex for that resolution. |
| this.push('displayModeList_', { |
| name: resolutionOption, |
| value: parentModeIndex, |
| }); |
| } |
| |
| /** |
| * Adds a an entry in |parentModeToRefreshRateMap_| for the refresh rate |
| * represented by |rate|. |
| * @param {number} parentModeIndex |
| * @param {number} modeIndex |
| * @param {number} rate |
| * @param {boolean|undefined} isInterlaced |
| * @private |
| */ |
| addRefreshRate_(parentModeIndex, modeIndex, rate, isInterlaced) { |
| assert(this.listAllDisplayModes_); |
| |
| // Truncate at two decimal places for display. If the refresh rate |
| // is a whole number, remove the mantissa. |
| let refreshRate = Number(rate).toFixed(2); |
| if (refreshRate.endsWith('.00')) { |
| refreshRate = refreshRate.substring(0, refreshRate.length - 3); |
| } |
| |
| const id = isInterlaced ? 'displayRefreshRateInterlacedMenuItem' : |
| 'displayRefreshRateMenuItem'; |
| |
| const refreshRateOption = this.i18n(id, refreshRate.toString()); |
| |
| this.parentModeToRefreshRateMap_.get(parentModeIndex).push({ |
| name: refreshRateOption, |
| value: modeIndex, |
| }); |
| } |
| |
| /** |
| * Sorts |displayModeList_| in descending order. First order sort is width, |
| * second order sort is height. |
| * @private |
| */ |
| sortResolutionList_() { |
| const getWidthFromResolutionString = function(str) { |
| return Number(str.substr(0, str.indexOf(' '))); |
| }; |
| |
| this.displayModeList_ = |
| this.displayModeList_ |
| .sort((first, second) => { |
| return getWidthFromResolutionString(first.name) - |
| getWidthFromResolutionString(second.name); |
| }) |
| .reverse(); |
| } |
| |
| /** |
| * Parses display modes for |selectedDisplay|. A 'mode' is a resolution + |
| * refresh rate combo. If |listAllDisplayModes_| is on, resolution and |
| * refresh rate are parsed into separate dropdowns and |
| * |parentModeToRefreshRateMap_| + |modeToParentModeMap_| are populated. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @private |
| */ |
| updateDisplayModeStructures_(selectedDisplay) { |
| if (this.listAllDisplayModes_) { |
| this.parseSplitDisplayModes_(selectedDisplay); |
| } else { |
| this.parseCompoundDisplayModes_(selectedDisplay); |
| } |
| } |
| |
| /** |
| * Returns a value from |zoomValues_| that is closest to the display zoom |
| * percentage currently selected for the |selectedDisplay|. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {number} |
| * @private |
| */ |
| getSelectedDisplayZoom_(selectedDisplay) { |
| const selectedZoom = selectedDisplay.displayZoomFactor; |
| let closestMatch = this.zoomValues_[0].value; |
| let minimumDiff = Math.abs(closestMatch - selectedZoom); |
| |
| for (let i = 0; i < this.zoomValues_.length; i++) { |
| const currentDiff = Math.abs(this.zoomValues_[i].value - selectedZoom); |
| if (currentDiff < minimumDiff) { |
| closestMatch = this.zoomValues_[i].value; |
| minimumDiff = currentDiff; |
| } |
| } |
| |
| return /** @type {number} */ (closestMatch); |
| } |
| |
| /** |
| * Given the display with the current display mode, this function lists all |
| * the display zoom values and their labels to be used by the slider. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {!Array<SliderTick>} |
| */ |
| getZoomValues_(selectedDisplay) { |
| return selectedDisplay.availableDisplayZoomFactors.map(value => { |
| const ariaValue = Math.round(value * 100); |
| return { |
| value, |
| ariaValue, |
| label: this.i18n('displayZoomValue', ariaValue.toString()), |
| }; |
| }); |
| } |
| |
| /** |
| * We need to call this explicitly rather than relying on change events |
| * so that we can control the update order. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @private |
| */ |
| setSelectedDisplay_(selectedDisplay) { |
| // |modeValues_| controls the resolution slider's tick values. Changing it |
| // might trigger a change in the |selectedModePref_.value| if the number |
| // of modes differs and the current mode index is out of range of the new |
| // modes indices. Thus, we need to set |currentSelectedModeIndex_| to -1 |
| // to indicate that the |selectedDisplay| and |selectedModePref_.value| |
| // are out of sync, and therefore getResolutionText_() and |
| // onSelectedModeChange_() will be no-ops. |
| this.currentSelectedModeIndex_ = -1; |
| this.currentSelectedParentModeIndex_ = -1; |
| const numModes = selectedDisplay.modes.length; |
| this.modeValues_ = numModes === 0 ? [] : Array.from(Array(numModes).keys()); |
| |
| // Note that the display zoom values has the same number of ticks for all |
| // displays, so the above problem doesn't apply here. |
| this.zoomValues_ = this.getZoomValues_(selectedDisplay); |
| this.set( |
| 'selectedZoomPref_.value', |
| this.getSelectedDisplayZoom_(selectedDisplay)); |
| |
| this.updateDisplayModeStructures_(selectedDisplay); |
| |
| // Set |selectedDisplay| first since only the resolution slider depends |
| // on |selectedModePref_|. |
| this.selectedDisplay = selectedDisplay; |
| this.selectedTab_ = this.displays.indexOf(this.selectedDisplay); |
| |
| const currentModeIndex = this.getSelectedModeIndex_(selectedDisplay); |
| |
| this.currentSelectedModeIndex_ = currentModeIndex; |
| // This will also cause the parent mode to be updated. |
| this.set('selectedModePref_.value', this.currentSelectedModeIndex_); |
| |
| if (this.listAllDisplayModes_) { |
| // Now that everything is in sync, set the selected mode to its correct |
| // value right before updating the pref. |
| this.currentSelectedParentModeIndex_ = |
| this.modeToParentModeMap_.get(currentModeIndex); |
| this.refreshRateList_ = this.parentModeToRefreshRateMap_.get( |
| this.currentSelectedParentModeIndex_); |
| } else { |
| this.currentSelectedParentModeIndex_ = currentModeIndex; |
| } |
| |
| this.set( |
| 'selectedParentModePref_.value', this.currentSelectedParentModeIndex_); |
| |
| this.updateLogicalResolutionText_( |
| /** @type {number} */ (this.selectedZoomPref_.value)); |
| } |
| |
| /** |
| * Returns true if the resolution setting needs to be displayed. |
| * @param {!chrome.system.display.DisplayUnitInfo} display |
| * @return {boolean} |
| * @private |
| */ |
| showDropDownResolutionSetting_(display) { |
| return !display.isInternal; |
| } |
| |
| /** |
| * Returns true if the refresh rate setting needs to be displayed. |
| * @param {!chrome.system.display.DisplayUnitInfo} display |
| * @return {boolean} |
| * @private |
| */ |
| showRefreshRateSetting_(display) { |
| return this.listAllDisplayModes_ && |
| this.showDropDownResolutionSetting_(display); |
| } |
| |
| /** |
| * Returns true if external touch devices are connected and the current |
| * display is not an internal display. If the feature is not enabled via the |
| * switch, this will return false. |
| * @param {!chrome.system.display.DisplayUnitInfo} display Display being |
| * checked for touch support. |
| * @return {boolean} |
| * @private |
| */ |
| showTouchCalibrationSetting_(display) { |
| return !display.isInternal && |
| loadTimeData.getBoolean('enableTouchCalibrationSetting'); |
| } |
| |
| /** |
| * Returns true if the overscan setting should be shown for |display|. |
| * @param {!chrome.system.display.DisplayUnitInfo} display |
| * @return {boolean} |
| * @private |
| */ |
| showOverscanSetting_(display) { |
| return !display.isInternal; |
| } |
| |
| /** |
| * Returns true if the ambient color setting should be shown for |display|. |
| * @param {boolean} ambientColorAvailable |
| * @param {chrome.system.display.DisplayUnitInfo} display |
| * @return {boolean} |
| * @private |
| */ |
| showAmbientColorSetting_(ambientColorAvailable, display) { |
| return ambientColorAvailable && display && display.isInternal; |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| hasMultipleDisplays_() { |
| return this.displays.length > 1; |
| } |
| |
| /** |
| * Returns false if the display select menu has to be hidden. |
| * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {boolean} |
| * @private |
| */ |
| showDisplaySelectMenu_(displays, selectedDisplay) { |
| if (selectedDisplay) { |
| return displays.length > 1 && !selectedDisplay.isPrimary; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the select menu index indicating whether the display currently is |
| * primary or extended. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @param {string} primaryDisplayId |
| * @return {number} Returns 0 if the display is primary else returns 1. |
| * @private |
| */ |
| getDisplaySelectMenuIndex_(selectedDisplay, primaryDisplayId) { |
| if (selectedDisplay && selectedDisplay.id === primaryDisplayId) { |
| return 0; |
| } |
| return 1; |
| } |
| |
| /** |
| * Returns the i18n string for the text to be used for mirroring settings. |
| * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
| * @return {string} i18n string for mirroring settings text. |
| * @private |
| */ |
| getDisplayMirrorText_(displays) { |
| return this.i18n('displayMirror', displays[0].name); |
| } |
| |
| /** |
| * @param {boolean} unifiedDesktopAvailable |
| * @param {boolean} unifiedDesktopMode |
| * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
| * @return {boolean} |
| * @private |
| */ |
| showUnifiedDesktop_(unifiedDesktopAvailable, unifiedDesktopMode, displays) { |
| if (displays === undefined) { |
| return false; |
| } |
| |
| return unifiedDesktopMode || |
| (unifiedDesktopAvailable && displays.length > 1 && |
| !this.isMirrored_(displays)); |
| } |
| |
| /** |
| * @param {boolean} unifiedDesktopMode |
| * @return {string} |
| * @private |
| */ |
| getUnifiedDesktopText_(unifiedDesktopMode) { |
| return this.i18n( |
| unifiedDesktopMode ? 'displayUnifiedDesktopOn' : |
| 'displayUnifiedDesktopOff'); |
| } |
| |
| /** |
| * @param {boolean} unifiedDesktopMode |
| * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
| * @return {boolean} |
| * @private |
| */ |
| showMirror_(unifiedDesktopMode, displays) { |
| if (displays === undefined) { |
| return false; |
| } |
| |
| return this.isMirrored_(displays) || |
| (!unifiedDesktopMode && displays.length > 1); |
| } |
| |
| /** |
| * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
| * @return {boolean} |
| * @private |
| */ |
| isMirrored_(displays) { |
| return displays !== undefined && displays.length > 0 && |
| !!displays[0].mirroringSourceId; |
| } |
| |
| /** |
| * @param {!chrome.system.display.DisplayUnitInfo} display |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {boolean} |
| * @private |
| */ |
| isSelected_(display, selectedDisplay) { |
| return display.id === selectedDisplay.id; |
| } |
| |
| /** |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {boolean} |
| * @private |
| */ |
| enableSetResolution_(selectedDisplay) { |
| return selectedDisplay.modes.length > 1; |
| } |
| |
| /** |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {boolean} |
| * @private |
| */ |
| enableDisplayZoomSlider_(selectedDisplay) { |
| return selectedDisplay.availableDisplayZoomFactors.length > 1; |
| } |
| |
| /** |
| * Returns true if the given mode is the best mode for the |
| * |selectedDisplay|. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @param {!chrome.system.display.DisplayMode} mode |
| * @return {boolean} |
| * @private |
| */ |
| isBestMode_(selectedDisplay, mode) { |
| if (!selectedDisplay.isInternal) { |
| return mode.isNative; |
| } |
| |
| // Things work differently for full HD devices(1080p). The best mode is |
| // the one with 1.25 device scale factor and 0.8 ui scale. |
| if (mode.heightInNativePixels === 1080) { |
| return Math.abs(mode.uiScale - 0.8) < 0.001 && |
| Math.abs(mode.deviceScaleFactor - 1.25) < 0.001; |
| } |
| |
| return mode.uiScale === 1.0; |
| } |
| |
| /** |
| * @return {string} |
| * @private |
| */ |
| getResolutionText_() { |
| if (this.selectedDisplay.modes.length === 0 || |
| this.currentSelectedModeIndex_ === -1) { |
| // If currentSelectedModeIndex_ == -1, selectedDisplay and |
| // |selectedModePref_.value| are not in sync. |
| return this.i18n( |
| 'displayResolutionText', this.selectedDisplay.bounds.width.toString(), |
| this.selectedDisplay.bounds.height.toString()); |
| } |
| const mode = this.selectedDisplay.modes[ |
| /** @type {number} */ (this.selectedModePref_.value)]; |
| assert(mode); |
| const widthStr = mode.width.toString(); |
| const heightStr = mode.height.toString(); |
| if (this.isBestMode_(this.selectedDisplay, mode)) { |
| return this.i18n('displayResolutionTextBest', widthStr, heightStr); |
| } else if (mode.isNative) { |
| return this.i18n('displayResolutionTextNative', widthStr, heightStr); |
| } |
| return this.i18n('displayResolutionText', widthStr, heightStr); |
| } |
| |
| /** |
| * Updates the logical resolution text to be used for the display size |
| * section |
| * @param {number} zoomFactor Current zoom factor applied on the selected |
| * display. |
| * @private |
| */ |
| updateLogicalResolutionText_(zoomFactor) { |
| if (!this.selectedDisplay.isInternal) { |
| this.logicalResolutionText_ = ''; |
| return; |
| } |
| const mode = this.selectedDisplay.modes[this.currentSelectedModeIndex_]; |
| const deviceScaleFactor = mode.deviceScaleFactor; |
| const inverseZoomFactor = 1.0 / zoomFactor; |
| let logicalResolutionStrId = 'displayZoomLogicalResolutionText'; |
| if (Math.abs(deviceScaleFactor - inverseZoomFactor) < 0.001) { |
| logicalResolutionStrId = 'displayZoomNativeLogicalResolutionNativeText'; |
| } else if (Math.abs(inverseZoomFactor - 1.0) < 0.001) { |
| logicalResolutionStrId = 'displayZoomLogicalResolutionDefaultText'; |
| } |
| let widthStr = |
| Math.round(mode.widthInNativePixels / (deviceScaleFactor * zoomFactor)) |
| .toString(); |
| let heightStr = |
| Math.round(mode.heightInNativePixels / (deviceScaleFactor * zoomFactor)) |
| .toString(); |
| if (this.shouldSwapLogicalResolutionText_()) { |
| const temp = widthStr; |
| widthStr = heightStr; |
| heightStr = temp; |
| } |
| this.logicalResolutionText_ = |
| this.i18n(logicalResolutionStrId, widthStr, heightStr); |
| } |
| |
| /** |
| * Determines whether width and height should be swapped in the |
| * Logical Resolution Text. Returns true if the longer edge of the |
| * display's native pixels is different than the longer edge of the |
| * display's current bounds. |
| * @private |
| */ |
| shouldSwapLogicalResolutionText_() { |
| const mode = this.selectedDisplay.modes[this.currentSelectedModeIndex_]; |
| const bounds = this.selectedDisplay.bounds; |
| |
| return bounds.width > bounds.height !== |
| mode.widthInNativePixels > mode.heightInNativePixels; |
| } |
| |
| |
| /** |
| * Handles the event where the display size slider is being dragged, i.e. |
| * the mouse or tap has not been released. |
| * @private |
| */ |
| onDisplaySizeSliderDrag_() { |
| if (!this.selectedDisplay) { |
| return; |
| } |
| |
| const sliderValue = |
| this.$.displaySizeSlider.shadowRoot.querySelector('#slider').value; |
| const zoomFactor = this.$.displaySizeSlider.ticks[sliderValue].value; |
| this.updateLogicalResolutionText_( |
| /** @type {number} */ (zoomFactor)); |
| } |
| |
| /** |
| * @param {!CustomEvent<string>} e |e.detail| is the id of the selected |
| * display. |
| * @private |
| */ |
| onSelectDisplay_(e) { |
| const id = e.detail; |
| for (let i = 0; i < this.displays.length; ++i) { |
| const display = this.displays[i]; |
| if (id === display.id) { |
| if (this.selectedDisplay !== display) { |
| this.setSelectedDisplay_(display); |
| } |
| return; |
| } |
| } |
| } |
| |
| /** @private */ |
| onSelectDisplayTab_() { |
| const {selected} = this.shadowRoot.querySelector('cr-tabs'); |
| if (this.selectedTab_ !== selected) { |
| this.setSelectedDisplay_(this.displays[selected]); |
| } |
| } |
| |
| /** |
| * Handles event when a touch calibration option is selected. |
| * @param {!Event} e |
| * @private |
| */ |
| onTouchCalibrationTap_(e) { |
| getDisplayApi().showNativeTouchCalibration(this.selectedDisplay.id); |
| } |
| |
| /** |
| * Handles the event when an option from display select menu is selected. |
| * @param {!{target: !HTMLSelectElement}} e |
| * @private |
| */ |
| updatePrimaryDisplay_(e) { |
| /** @type {number} */ const PRIMARY_DISP_IDX = 0; |
| if (!this.selectedDisplay) { |
| return; |
| } |
| if (this.selectedDisplay.id === this.primaryDisplayId) { |
| return; |
| } |
| if (!e.target.value) { |
| return; |
| } |
| |
| /** @type {!chrome.system.display.DisplayProperties} */ const properties = { |
| isPrimary: true, |
| }; |
| getDisplayApi().setDisplayProperties( |
| this.selectedDisplay.id, properties, |
| () => this.setPropertiesCallback_()); |
| } |
| |
| /** |
| * Handles a change in the |selectedParentModePref| value triggered via the |
| * observer. |
| * @param {number} newModeIndex The new index value |
| * @private |
| */ |
| onSelectedParentModeChange_(newModeIndex) { |
| if (this.currentSelectedParentModeIndex_ === newModeIndex) { |
| return; |
| } |
| |
| if (!this.hasNewParentModeBeenSet()) { |
| // Don't change the selected display mode until we have received an |
| // update from Chrome and the mode differs from the current mode. |
| return; |
| } |
| |
| // Reset |selectedModePref| to the parentMode. |
| this.set('selectedModePref_.value', this.selectedParentModePref_.value); |
| } |
| |
| /** |
| * Returns True if a new parentMode has been set and we have received an |
| * update from Chrome. |
| * @return {boolean} |
| * @private |
| */ |
| hasNewParentModeBeenSet() { |
| if (this.currentSelectedParentModeIndex_ === -1) { |
| return false; |
| } |
| |
| return this.currentSelectedParentModeIndex_ !== |
| this.selectedParentModePref_.value; |
| } |
| |
| /** |
| * Returns True if a new mode has been set and we have received an update |
| * from Chrome. |
| * @return {boolean} |
| * @private |
| */ |
| hasNewModeBeenSet() { |
| if (this.currentSelectedModeIndex_ === -1) { |
| return false; |
| } |
| |
| if (this.currentSelectedParentModeIndex_ !== |
| this.selectedParentModePref_.value) { |
| return true; |
| } |
| |
| return this.currentSelectedModeIndex_ !== this.selectedModePref_.value; |
| } |
| |
| /** |
| * Handles a change in |selectedModePref| triggered via the observer. |
| * @param {number} newModeIndex The new index value |
| * @private |
| */ |
| onSelectedModeChange_(newModeIndex) { |
| // We want to ignore all value changes to the pref due to the slider being |
| // dragged. See http://crbug/845712 for more info. |
| if (this.currentSelectedModeIndex_ === newModeIndex) { |
| return; |
| } |
| |
| if (!this.hasNewModeBeenSet()) { |
| // Don't change the selected display mode until we have received an |
| // update from Chrome and the mode differs from the current mode. |
| return; |
| } |
| /** @type {!chrome.system.display.DisplayProperties} */ const properties = { |
| displayMode: this.selectedDisplay.modes[ |
| /** @type {number} */ (this.selectedModePref_.value)], |
| }; |
| |
| this.refreshRateList_ = this.parentModeToRefreshRateMap_.get( |
| /** @type {number} */ (this.selectedParentModePref_.value)); |
| getDisplayApi().setDisplayProperties( |
| this.selectedDisplay.id, properties, |
| () => this.setPropertiesCallback_()); |
| } |
| |
| /** |
| * Triggerend when the display size slider changes its value. This only |
| * occurs when the value is committed (i.e. not while the slider is being |
| * dragged). |
| * @private |
| */ |
| onSelectedZoomChange_() { |
| if (this.currentSelectedModeIndex_ === -1 || !this.selectedDisplay) { |
| return; |
| } |
| |
| /** @type {!chrome.system.display.DisplayProperties} */ const properties = { |
| displayZoomFactor: |
| /** @type {number} */ (this.selectedZoomPref_.value), |
| }; |
| |
| getDisplayApi().setDisplayProperties( |
| this.selectedDisplay.id, properties, |
| () => this.setPropertiesCallback_()); |
| } |
| |
| /** |
| * Returns whether the option "Auto-rotate" is one of the shown options in |
| * the rotation drop-down menu. |
| * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| * @return {boolean|undefined} |
| * @private |
| */ |
| showAutoRotateOption_(selectedDisplay) { |
| return selectedDisplay.isAutoRotationAllowed; |
| } |
| |
| /** |
| * @param {!Event} event |
| * @private |
| */ |
| onOrientationChange_(event) { |
| const target = /** @type {!HTMLSelectElement} */ (event.target); |
| const value = /** @type {number} */ (parseInt(target.value, 10)); |
| |
| assert(value !== -1 || this.selectedDisplay.isAutoRotationAllowed); |
| |
| /** @type {!chrome.system.display.DisplayProperties} */ const properties = { |
| rotation: value, |
| }; |
| getDisplayApi().setDisplayProperties( |
| this.selectedDisplay.id, properties, |
| () => this.setPropertiesCallback_()); |
| } |
| |
| /** @private */ |
| onMirroredTap_(event) { |
| // Blur the control so that when the transition animation completes and |
| // the UI is focused, the control does not receive focus. crbug.com/785070 |
| event.target.blur(); |
| |
| /** @type {!chrome.system.display.MirrorModeInfo} */ |
| const mirrorModeInfo = { |
| mode: this.isMirrored_(this.displays) ? |
| chrome.system.display.MirrorMode.OFF : |
| chrome.system.display.MirrorMode.NORMAL, |
| }; |
| getDisplayApi().setMirrorMode(mirrorModeInfo, () => { |
| const error = chrome.runtime.lastError; |
| if (error) { |
| console.error('setMirrorMode Error: ' + error.message); |
| } |
| }); |
| } |
| |
| /** @private */ |
| onUnifiedDesktopTap_() { |
| /** @type {!chrome.system.display.DisplayProperties} */ const properties = { |
| isUnified: !this.unifiedDesktopMode_, |
| }; |
| getDisplayApi().setDisplayProperties( |
| this.primaryDisplayId, properties, () => this.setPropertiesCallback_()); |
| } |
| |
| /** |
| * @param {!Event} e |
| * @private |
| */ |
| onOverscanTap_(e) { |
| e.preventDefault(); |
| this.overscanDisplayId = this.selectedDisplay.id; |
| this.showOverscanDialog_(true); |
| } |
| |
| /** @private */ |
| onCloseOverscanDialog_() { |
| focusWithoutInk(assert(this.shadowRoot.querySelector('#overscan'))); |
| } |
| |
| /** @private */ |
| updateDisplayInfo_() { |
| let displayIds = ''; |
| let primaryDisplay = undefined; |
| let selectedDisplay = undefined; |
| for (let i = 0; i < this.displays.length; ++i) { |
| const display = this.displays[i]; |
| if (displayIds) { |
| displayIds += ','; |
| } |
| displayIds += display.id; |
| if (display.isPrimary && !primaryDisplay) { |
| primaryDisplay = display; |
| } |
| if (this.selectedDisplay && display.id === this.selectedDisplay.id) { |
| selectedDisplay = display; |
| } |
| } |
| this.displayIds = displayIds; |
| this.primaryDisplayId = (primaryDisplay && primaryDisplay.id) || ''; |
| selectedDisplay = selectedDisplay || primaryDisplay || |
| (this.displays && this.displays[0]); |
| this.setSelectedDisplay_(selectedDisplay); |
| |
| this.unifiedDesktopMode_ = !!primaryDisplay && primaryDisplay.isUnified; |
| |
| // Check if we have yet to focus a deep-linked element. |
| if (!this.pendingSettingId_) { |
| return; |
| } |
| |
| this.showDeepLink(this.pendingSettingId_).then(result => { |
| if (result.deepLinkShown) { |
| this.pendingSettingId_ = null; |
| } |
| }); |
| } |
| |
| /** @private */ |
| setPropertiesCallback_() { |
| if (chrome.runtime.lastError) { |
| console.error( |
| 'setDisplayProperties Error: ' + chrome.runtime.lastError.message); |
| } |
| } |
| |
| /** |
| * Invoked when the status of Night Light or its schedule type are changed, |
| * in order to update the schedule settings, such as whether to show the |
| * custom schedule slider, and the schedule sub label. |
| * @private |
| */ |
| updateNightLightScheduleSettings_() { |
| const scheduleType = this.getPref('ash.night_light.schedule_type').value; |
| this.shouldOpenCustomScheduleCollapse_ = |
| scheduleType === NightLightScheduleType.CUSTOM; |
| |
| if (scheduleType === NightLightScheduleType.SUNSET_TO_SUNRISE) { |
| const nightLightStatus = this.getPref('ash.night_light.enabled').value; |
| this.nightLightScheduleSubLabel_ = nightLightStatus ? |
| this.i18n('displayNightLightOffAtSunrise') : |
| this.i18n('displayNightLightOnAtSunset'); |
| } else { |
| this.nightLightScheduleSubLabel_ = ''; |
| } |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| shouldShowArrangementSection_() { |
| if (!this.displays) { |
| return false; |
| } |
| return this.hasMultipleDisplays_() || this.isMirrored_(this.displays); |
| } |
| |
| /** @private */ |
| onDisplaysChanged_() { |
| flush(); |
| const displayLayout = this.shadowRoot.querySelector('#displayLayout'); |
| if (displayLayout) { |
| displayLayout.updateDisplays( |
| this.displays, this.layouts, this.mirroringDestinationIds); |
| } |
| } |
| } |
| |
| customElements.define(SettingsDisplayElement.is, SettingsDisplayElement); |