diff --git a/docs/api/touch-bar-button.md b/docs/api/touch-bar-button.md index df06d236d409b..20e325c2650a7 100644 --- a/docs/api/touch-bar-button.md +++ b/docs/api/touch-bar-button.md @@ -41,6 +41,10 @@ the button in the touch bar. A `NativeImage` representing the button's current icon. Changing this value immediately updates the button in the touch bar. +#### `touchBarButton.iconPosition` + +A `String` - Can be `left`, `right` or `overlay`. Defaults to `overlay`. + #### `touchBarButton.enabled` A `Boolean` representing whether the button is in an enabled state. diff --git a/docs/api/touch-bar-segmented-control.md b/docs/api/touch-bar-segmented-control.md index 0622f1ce7a09f..caf969a174f31 100644 --- a/docs/api/touch-bar-segmented-control.md +++ b/docs/api/touch-bar-segmented-control.md @@ -49,3 +49,7 @@ updates the control in the touch bar. Updating deep properties inside this array An `Integer` representing the currently selected segment. Changing this value immediately updates the control in the touch bar. User interaction with the touch bar will update this value automatically. + +#### `touchBarSegmentedControl.mode` + +A `String` representing the current selection mode of the control. Can be `single`, `multiple` or `buttons`. diff --git a/docs/api/touch-bar-spacer.md b/docs/api/touch-bar-spacer.md index 65a19f4ffd342..76a58f6188115 100644 --- a/docs/api/touch-bar-spacer.md +++ b/docs/api/touch-bar-spacer.md @@ -11,3 +11,11 @@ Process: [Main](../tutorial/application-architecture.md#main-and-renderer-proces * `small` - Small space between items. Maps to `NSTouchBarItemIdentifierFixedSpaceSmall`. This is the default. * `large` - Large space between items. Maps to `NSTouchBarItemIdentifierFixedSpaceLarge`. * `flexible` - Take up all available space. Maps to `NSTouchBarItemIdentifierFlexibleSpace`. + +### Instance Properties + +The following properties are available on instances of `TouchBarSpacer`: + +#### `touchBarSpacer.size` + +A `String` representing the size of the spacer. Can be `small`, `large` or `flexible`. diff --git a/filenames.auto.gni b/filenames.auto.gni index 8eb36da3ce629..4fbe40cb00849 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -217,7 +217,7 @@ auto_filenames = { "lib/browser/api/screen.ts", "lib/browser/api/session.ts", "lib/browser/api/system-preferences.ts", - "lib/browser/api/touch-bar.js", + "lib/browser/api/touch-bar.ts", "lib/browser/api/tray.ts", "lib/browser/api/view.ts", "lib/browser/api/views/image-view.ts", diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js deleted file mode 100644 index d92a85782622e..0000000000000 --- a/lib/browser/api/touch-bar.js +++ /dev/null @@ -1,365 +0,0 @@ -'use strict'; - -const { EventEmitter } = require('events'); - -let nextItemID = 1; - -class TouchBar extends EventEmitter { - // Bind a touch bar to a window - static _setOnWindow (touchBar, window) { - if (window._touchBar != null) { - window._touchBar._removeFromWindow(window); - } - - if (touchBar == null) { - window._setTouchBarItems([]); - return; - } - - if (Array.isArray(touchBar)) { - touchBar = new TouchBar(touchBar); - } - touchBar._addToWindow(window); - } - - constructor (options) { - super(); - - if (options == null) { - throw new Error('Must specify options object as first argument'); - } - - let { items, escapeItem } = options; - - if (!Array.isArray(items)) { - items = []; - } - - this.changeListener = (item) => { - this.emit('change', item.id, item.type); - }; - - this.windowListeners = {}; - this.items = {}; - this.ordereredItems = []; - this.escapeItem = escapeItem; - - const registerItem = (item) => { - this.items[item.id] = item; - item.on('change', this.changeListener); - if (item.child instanceof TouchBar) { - item.child.ordereredItems.forEach(registerItem); - } - }; - - let hasOtherItemsProxy = false; - const idSet = new Set(); - items.forEach((item) => { - if (!(item instanceof TouchBarItem)) { - throw new Error('Each item must be an instance of TouchBarItem'); - } - - if (item.type === 'other_items_proxy') { - if (!hasOtherItemsProxy) { - hasOtherItemsProxy = true; - } else { - throw new Error('Must only have one OtherItemsProxy per TouchBar'); - } - } - - if (!idSet.has(item.id)) { - idSet.add(item.id); - } else { - throw new Error('Cannot add a single instance of TouchBarItem multiple times in a TouchBar'); - } - }); - - // register in separate loop after all items are validated - for (const item of items) { - this.ordereredItems.push(item); - registerItem(item); - } - } - - set escapeItem (item) { - if (item != null && !(item instanceof TouchBarItem)) { - throw new Error('Escape item must be an instance of TouchBarItem'); - } - if (this.escapeItem != null) { - this.escapeItem.removeListener('change', this.changeListener); - } - this._escapeItem = item; - if (this.escapeItem != null) { - this.escapeItem.on('change', this.changeListener); - } - this.emit('escape-item-change', item); - } - - get escapeItem () { - return this._escapeItem; - } - - _addToWindow (window) { - const { id } = window; - - // Already added to window - if (Object.prototype.hasOwnProperty.call(this.windowListeners, id)) return; - - window._touchBar = this; - - const changeListener = (itemID) => { - window._refreshTouchBarItem(itemID); - }; - this.on('change', changeListener); - - const escapeItemListener = (item) => { - window._setEscapeTouchBarItem(item != null ? item : {}); - }; - this.on('escape-item-change', escapeItemListener); - - const interactionListener = (event, itemID, details) => { - let item = this.items[itemID]; - if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) { - item = this.escapeItem; - } - if (item != null && item.onInteraction != null) { - item.onInteraction(details); - } - }; - window.on('-touch-bar-interaction', interactionListener); - - const removeListeners = () => { - this.removeListener('change', changeListener); - this.removeListener('escape-item-change', escapeItemListener); - window.removeListener('-touch-bar-interaction', interactionListener); - window.removeListener('closed', removeListeners); - window._touchBar = null; - delete this.windowListeners[id]; - const unregisterItems = (items) => { - for (const item of items) { - item.removeListener('change', this.changeListener); - if (item.child instanceof TouchBar) { - unregisterItems(item.child.ordereredItems); - } - } - }; - unregisterItems(this.ordereredItems); - if (this.escapeItem) { - this.escapeItem.removeListener('change', this.changeListener); - } - }; - window.once('closed', removeListeners); - this.windowListeners[id] = removeListeners; - - window._setTouchBarItems(this.ordereredItems); - escapeItemListener(this.escapeItem); - } - - _removeFromWindow (window) { - const removeListeners = this.windowListeners[window.id]; - if (removeListeners != null) removeListeners(); - } -} - -class TouchBarItem extends EventEmitter { - constructor () { - super(); - this._addImmutableProperty('id', `${nextItemID++}`); - this._parents = []; - } - - _addImmutableProperty (name, value) { - Object.defineProperty(this, name, { - get: function () { - return value; - }, - set: function () { - throw new Error(`Cannot override property ${name}`); - }, - enumerable: true, - configurable: false - }); - } - - _addLiveProperty (name, initialValue) { - const privateName = `_${name}`; - this[privateName] = initialValue; - Object.defineProperty(this, name, { - get: function () { - return this[privateName]; - }, - set: function (value) { - this[privateName] = value; - this.emit('change', this); - }, - enumerable: true - }); - } - - _addParent (item) { - const existing = this._parents.some(test => test.id === item.id); - if (!existing) { - this._parents.push({ - id: item.id, - type: item.type - }); - } - } -} - -TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - this._addImmutableProperty('type', 'button'); - this._addLiveProperty('label', config.label); - this._addLiveProperty('accessibilityLabel', config.accessibilityLabel); - this._addLiveProperty('backgroundColor', config.backgroundColor); - this._addLiveProperty('icon', config.icon); - this._addLiveProperty('iconPosition', config.iconPosition); - this._addLiveProperty('enabled', typeof config.enabled !== 'boolean' ? true : config.enabled); - if (typeof config.click === 'function') { - this._addImmutableProperty('onInteraction', () => { - config.click(); - }); - } - } -}; - -TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - this._addImmutableProperty('type', 'colorpicker'); - this._addLiveProperty('availableColors', config.availableColors); - this._addLiveProperty('selectedColor', config.selectedColor); - - if (typeof config.change === 'function') { - this._addImmutableProperty('onInteraction', (details) => { - this._selectedColor = details.color; - config.change(details.color); - }); - } - } -}; - -TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - this._addImmutableProperty('type', 'group'); - const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items); - this._addLiveProperty('child', defaultChild); - this.child.ordereredItems.forEach((item) => item._addParent(this)); - } -}; - -TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - this._addImmutableProperty('type', 'label'); - this._addLiveProperty('label', config.label); - this._addLiveProperty('accessibilityLabel', config.accessibilityLabel); - this._addLiveProperty('textColor', config.textColor); - } -}; - -TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - this._addImmutableProperty('type', 'popover'); - this._addLiveProperty('label', config.label); - this._addLiveProperty('icon', config.icon); - this._addLiveProperty('showCloseButton', config.showCloseButton); - const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items); - this._addLiveProperty('child', defaultChild); - this.child.ordereredItems.forEach((item) => item._addParent(this)); - } -}; - -TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - this._addImmutableProperty('type', 'slider'); - this._addLiveProperty('label', config.label); - this._addLiveProperty('minValue', config.minValue); - this._addLiveProperty('maxValue', config.maxValue); - this._addLiveProperty('value', config.value); - - if (typeof config.change === 'function') { - this._addImmutableProperty('onInteraction', (details) => { - this._value = details.value; - config.change(details.value); - }); - } - } -}; - -TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - this._addImmutableProperty('type', 'spacer'); - this._addImmutableProperty('size', config.size); - } -}; - -TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - this._addImmutableProperty('type', 'segmented_control'); - this._addLiveProperty('segmentStyle', config.segmentStyle); - this._addLiveProperty('segments', config.segments || []); - this._addLiveProperty('selectedIndex', config.selectedIndex); - this._addLiveProperty('mode', config.mode); - - if (typeof config.change === 'function') { - this._addImmutableProperty('onInteraction', (details) => { - this._selectedIndex = details.selectedIndex; - config.change(details.selectedIndex, details.isSelected); - }); - } - } -}; - -TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { - constructor (config) { - super(); - if (config == null) config = {}; - let { select, highlight } = config; - this._addImmutableProperty('type', 'scrubber'); - this._addLiveProperty('items', config.items); - this._addLiveProperty('selectedStyle', config.selectedStyle || null); - this._addLiveProperty('overlayStyle', config.overlayStyle || null); - this._addLiveProperty('showArrowButtons', config.showArrowButtons || false); - this._addLiveProperty('mode', config.mode || 'free'); - - const cont = typeof config.continuous === 'undefined' ? true : config.continuous; - this._addLiveProperty('continuous', cont); - - if (typeof select === 'function' || typeof highlight === 'function') { - if (select == null) select = () => {}; - if (highlight == null) highlight = () => {}; - this._addImmutableProperty('onInteraction', (details) => { - if (details.type === 'select' && typeof select === 'function') { - select(details.selectedIndex); - } else if (details.type === 'highlight' && typeof highlight === 'function') { - highlight(details.highlightedIndex); - } - }); - } - } -}; - -TouchBar.TouchBarOtherItemsProxy = class TouchBarOtherItemsProxy extends TouchBarItem { - constructor (config) { - super(); - this._addImmutableProperty('type', 'other_items_proxy'); - } -}; - -module.exports = TouchBar; diff --git a/lib/browser/api/touch-bar.ts b/lib/browser/api/touch-bar.ts new file mode 100644 index 0000000000000..1ce8b272f97ef --- /dev/null +++ b/lib/browser/api/touch-bar.ts @@ -0,0 +1,459 @@ +import { EventEmitter } from 'events'; + +let nextItemID = 1; + +const hiddenProperties = Symbol('hidden touch bar props'); + +const extendConstructHook = (target: any, hook: Function) => { + const existingHook = target._hook; + target._hook = function () { + hook.call(this); + if (existingHook) existingHook.call(this); + }; +}; + +const ImmutableProperty = >(def: (config: T extends TouchBarItem ? C : never, setInternalProp: (k: K, v: T[K]) => void) => any) => (target: T, propertyKey: keyof T) => { + extendConstructHook(target as any, function (this: T) { + (this as any)[hiddenProperties][propertyKey] = def((this as any)._config, (k, v) => { + (this as any)[hiddenProperties][k] = v; + }); + }); + Object.defineProperty(target, propertyKey, { + get: function () { + return (this as any)[hiddenProperties][propertyKey]; + }, + set: function () { + throw new Error(`Cannot override property ${name}`); + }, + enumerable: true, + configurable: false + }); +}; + +const LiveProperty = >(def: (config: T extends TouchBarItem ? C : never) => any, onMutate?: (self: T, newValue: any) => void) => (target: T, propertyKey: keyof T) => { + extendConstructHook(target as any, function (this: T) { + (this as any)[hiddenProperties][propertyKey] = def((this as any)._config); + if (onMutate) onMutate((this as any), (this as any)[hiddenProperties][propertyKey]); + }); + Object.defineProperty(target, propertyKey, { + get: function () { + return this[hiddenProperties][propertyKey]; + }, + set: function (value) { + if (onMutate) onMutate((this as any), value); + this[hiddenProperties][propertyKey] = value; + this.emit('change', this); + }, + enumerable: true + }); +}; + +abstract class TouchBarItem extends EventEmitter { + @ImmutableProperty(() => `${nextItemID++}`) id!: string; + abstract type: string; + abstract onInteraction: Function | null; + child?: TouchBar; + + private _parents: { id: string; type: string }[] = []; + private _config!: ConfigType; + + constructor (config: ConfigType) { + super(); + this._config = this._config || config || {} as any; + (this as any)[hiddenProperties] = {}; + const hook = (this as any)._hook; + if (hook) hook.call(this); + delete (this as any)._hook; + } + + public _addParent (item: TouchBarItem) { + const existing = this._parents.some(test => test.id === item.id); + if (!existing) { + this._parents.push({ + id: item.id, + type: item.type + }); + } + } + + public _removeParent (item: TouchBarItem) { + this._parents = this._parents.filter(test => test.id !== item.id); + } +} + +class TouchBarButton extends TouchBarItem implements Electron.TouchBarButton { + @ImmutableProperty(() => 'button') + type!: string; + + @LiveProperty(config => config.label) + label!: string; + + @LiveProperty(config => config.accessibilityLabel) + accessibilityLabel!: string; + + @LiveProperty(config => config.backgroundColor) + backgroundColor!: string; + + @LiveProperty(config => config.icon) + icon!: Electron.NativeImage; + + @LiveProperty(config => config.iconPosition) + iconPosition!: Electron.TouchBarButton['iconPosition']; + + @LiveProperty(config => typeof config.enabled !== 'boolean' ? true : config.enabled) + enabled!: boolean; + + @ImmutableProperty(({ click: onClick }) => typeof onClick === 'function' ? () => onClick() : null) + onInteraction!: Function | null; +} + +class TouchBarColorPicker extends TouchBarItem implements Electron.TouchBarColorPicker { + @ImmutableProperty(() => 'colorpicker') + type!: string; + + @LiveProperty(config => config.availableColors) + availableColors!: string[]; + + @LiveProperty(config => config.selectedColor) + selectedColor!: string; + + @ImmutableProperty(({ change: onChange }, setInternalProp) => typeof onChange === 'function' ? (details: { color: string }) => { + setInternalProp('selectedColor', details.color); + onChange(details.color); + } : null) + onInteraction!: Function | null; +} + +class TouchBarGroup extends TouchBarItem implements Electron.TouchBarGroup { + @ImmutableProperty(() => 'group') + type!: string; + + @LiveProperty(config => config.items instanceof TouchBar ? config.items : new TouchBar(config.items), (self, newChild: TouchBar) => { + if (self.child) { + for (const item of self.child.orderedItems) { + item._removeParent(self); + } + } + for (const item of newChild.orderedItems) { + item._addParent(item); + } + }) + child!: TouchBar; + + onInteraction = null; +} + +class TouchBarLabel extends TouchBarItem implements Electron.TouchBarLabel { + @ImmutableProperty(() => 'label') + type!: string; + + @LiveProperty(config => config.label) + label!: string; + + @LiveProperty(config => config.accessibilityLabel) + accessibilityLabel!: string; + + @LiveProperty(config => config.textColor) + textColor!: string; + + onInteraction = null; +} + +class TouchBarPopover extends TouchBarItem implements Electron.TouchBarPopover { + @ImmutableProperty(() => 'popover') + type!: string; + + @LiveProperty(config => config.label) + label!: string; + + @LiveProperty(config => config.icon) + icon!: Electron.NativeImage; + + @LiveProperty(config => config.showCloseButton) + showCloseButton!: boolean; + + @LiveProperty(config => config.items instanceof TouchBar ? config.items : new TouchBar(config.items), (self, newChild: TouchBar) => { + if (self.child) { + for (const item of self.child.orderedItems) { + item._removeParent(self); + } + } + for (const item of newChild.orderedItems) { + item._addParent(item); + } + }) + child!: TouchBar; + + onInteraction = null; +} + +class TouchBarSlider extends TouchBarItem implements Electron.TouchBarSlider { + @ImmutableProperty(() => 'slider') + type!: string; + + @LiveProperty(config => config.label) + label!: string; + + @LiveProperty(config => config.minValue) + minValue!: number; + + @LiveProperty(config => config.maxValue) + maxValue!: number; + + @LiveProperty(config => config.value) + value!: number; + + @ImmutableProperty(({ change: onChange }, setInternalProp) => typeof onChange === 'function' ? (details: { value: number }) => { + setInternalProp('value', details.value); + onChange(details.value); + } : null) + onInteraction!: Function | null; +} + +class TouchBarSpacer extends TouchBarItem implements Electron.TouchBarSpacer { + @ImmutableProperty(() => 'spacer') + type!: string; + + @ImmutableProperty(config => config.size) + size!: Electron.TouchBarSpacer['size']; + + onInteraction = null; +} + +class TouchBarSegmentedControl extends TouchBarItem implements Electron.TouchBarSegmentedControl { + @ImmutableProperty(() => 'segmented_control') + type!: string; + + @LiveProperty(config => config.segmentStyle) + segmentStyle!: Electron.TouchBarSegmentedControl['segmentStyle']; + + @LiveProperty(config => config.segments || []) + segments!: Electron.SegmentedControlSegment[]; + + @LiveProperty(config => config.selectedIndex) + selectedIndex!: number; + + @LiveProperty(config => config.mode) + mode!: Electron.TouchBarSegmentedControl['mode']; + + @ImmutableProperty(({ change: onChange }, setInternalProp) => typeof onChange === 'function' ? (details: { selectedIndex: number, isSelected: boolean }) => { + setInternalProp('selectedIndex', details.selectedIndex); + onChange(details.selectedIndex, details.isSelected); + } : null) + onInteraction!: Function | null; +} + +class TouchBarScrubber extends TouchBarItem implements Electron.TouchBarScrubber { + @ImmutableProperty(() => 'scrubber') + type!: string; + + @LiveProperty(config => config.items) + items!: Electron.ScrubberItem[]; + + @LiveProperty(config => config.selectedStyle || null) + selectedStyle!: Electron.TouchBarScrubber['selectedStyle']; + + @LiveProperty(config => config.overlayStyle || null) + overlayStyle!: Electron.TouchBarScrubber['overlayStyle']; + + @LiveProperty(config => config.showArrowButtons || false) + showArrowButtons!: boolean; + + @LiveProperty(config => config.mode || 'free') + mode!: Electron.TouchBarScrubber['mode']; + + @LiveProperty(config => typeof config.continuous === 'undefined' ? true : config.continuous) + continuous!: boolean; + + @ImmutableProperty(({ select: onSelect, highlight: onHighlight }) => typeof onSelect === 'function' || typeof onHighlight === 'function' ? (details: { type: 'select'; selectedIndex: number } | { type: 'highlight'; highlightedIndex: number }) => { + if (details.type === 'select') { + if (onSelect) onSelect(details.selectedIndex); + } else { + if (onHighlight) onHighlight(details.highlightedIndex); + } + } : null) + onInteraction!: Function | null; +} + +class TouchBarOtherItemsProxy extends TouchBarItem implements Electron.TouchBarOtherItemsProxy { + @ImmutableProperty(() => 'other_items_proxy') type!: string; + onInteraction = null; +} + +const escapeItemSymbol = Symbol('escape item'); + +class TouchBar extends EventEmitter implements Electron.TouchBar { + // Bind a touch bar to a window + static _setOnWindow (touchBar: TouchBar | Electron.TouchBarConstructorOptions['items'], window: Electron.BrowserWindow) { + if (window._touchBar != null) { + window._touchBar._removeFromWindow(window); + } + + if (!touchBar) { + window._setTouchBarItems([]); + return; + } + + if (Array.isArray(touchBar)) { + touchBar = new TouchBar({ items: touchBar }); + } + touchBar._addToWindow(window); + } + + private windowListeners: Record = {}; + private items: Record> = {}; + orderedItems: TouchBarItem[] = []; + + constructor (options: Electron.TouchBarConstructorOptions) { + super(); + + if (options == null) { + throw new Error('Must specify options object as first argument'); + } + + let { items, escapeItem } = options; + + if (!Array.isArray(items)) { + items = []; + } + + this.windowListeners = {}; + this.items = {}; + this.escapeItem = (escapeItem as any) || null; + + const registerItem = (item: TouchBarItem) => { + this.items[item.id] = item; + item.on('change', this.changeListener); + if (item.child instanceof TouchBar) { + item.child.orderedItems.forEach(registerItem); + } + }; + + let hasOtherItemsProxy = false; + const idSet = new Set(); + items.forEach((item) => { + if (!(item instanceof TouchBarItem)) { + throw new Error('Each item must be an instance of TouchBarItem'); + } + + if (item.type === 'other_items_proxy') { + if (!hasOtherItemsProxy) { + hasOtherItemsProxy = true; + } else { + throw new Error('Must only have one OtherItemsProxy per TouchBar'); + } + } + + if (!idSet.has(item.id)) { + idSet.add(item.id); + } else { + throw new Error('Cannot add a single instance of TouchBarItem multiple times in a TouchBar'); + } + }); + + // register in separate loop after all items are validated + for (const item of (items as TouchBarItem[])) { + this.orderedItems.push(item); + registerItem(item); + } + } + + private changeListener = (item: TouchBarItem) => { + this.emit('change', item.id, item.type); + }; + + private [escapeItemSymbol]: TouchBarItem | null = null; + + set escapeItem (item: TouchBarItem | null) { + if (item != null && !(item instanceof TouchBarItem)) { + throw new Error('Escape item must be an instance of TouchBarItem'); + } + const escapeItem = this.escapeItem; + if (escapeItem) { + escapeItem.removeListener('change', this.changeListener); + } + this[escapeItemSymbol] = item; + if (this.escapeItem != null) { + this.escapeItem.on('change', this.changeListener); + } + this.emit('escape-item-change', item); + } + + get escapeItem (): TouchBarItem | null { + return this[escapeItemSymbol]; + } + + _addToWindow (window: Electron.BrowserWindow) { + const { id } = window; + + // Already added to window + if (Object.prototype.hasOwnProperty.call(this.windowListeners, id)) return; + + window._touchBar = this; + + const changeListener = (itemID: string) => { + window._refreshTouchBarItem(itemID); + }; + this.on('change', changeListener); + + const escapeItemListener = (item: Electron.TouchBarItemType | null) => { + window._setEscapeTouchBarItem(item != null ? item : {}); + }; + this.on('escape-item-change', escapeItemListener); + + const interactionListener = (_: any, itemID: string, details: any) => { + let item = this.items[itemID]; + if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) { + item = this.escapeItem; + } + if (item != null && item.onInteraction != null) { + item.onInteraction(details); + } + }; + window.on('-touch-bar-interaction', interactionListener); + + const removeListeners = () => { + this.removeListener('change', changeListener); + this.removeListener('escape-item-change', escapeItemListener); + window.removeListener('-touch-bar-interaction', interactionListener); + window.removeListener('closed', removeListeners); + window._touchBar = null; + delete this.windowListeners[id]; + const unregisterItems = (items: TouchBarItem[]) => { + for (const item of items) { + item.removeListener('change', this.changeListener); + if (item.child instanceof TouchBar) { + unregisterItems(item.child.orderedItems); + } + } + }; + unregisterItems(this.orderedItems); + if (this.escapeItem) { + this.escapeItem.removeListener('change', this.changeListener); + } + }; + window.once('closed', removeListeners); + this.windowListeners[id] = removeListeners; + + window._setTouchBarItems(this.orderedItems); + escapeItemListener(this.escapeItem); + } + + _removeFromWindow (window: Electron.BrowserWindow) { + const removeListeners = this.windowListeners[window.id]; + if (removeListeners != null) removeListeners(); + } + + static TouchBarButton = TouchBarButton; + static TouchBarColorPicker = TouchBarColorPicker; + static TouchBarGroup = TouchBarGroup; + static TouchBarLabel = TouchBarLabel; + static TouchBarPopover = TouchBarPopover; + static TouchBarSlider = TouchBarSlider; + static TouchBarSpacer = TouchBarSpacer; + static TouchBarSegmentedControl = TouchBarSegmentedControl; + static TouchBarScrubber = TouchBarScrubber; + static TouchBarOtherItemsProxy = TouchBarOtherItemsProxy; +} + +export default TouchBar; diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 699ebbe5f82cd..ab16ebdf6959e 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -19,6 +19,17 @@ declare namespace Electron { setAppPath(path: string | null): void; } + type TouchBarItemType = NonNullable[0]; + + interface BrowserWindow { + _touchBar: Electron.TouchBar | null; + _setTouchBarItems: (items: TouchBarItemType[]) => void; + _setEscapeTouchBarItem: (item: TouchBarItemType | {}) => void; + _refreshTouchBarItem: (itemID: string) => void; + on(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this; + removeListener(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this; + } + interface ContextBridge { internalContextBridge: { contextIsolationEnabled: boolean; @@ -29,6 +40,10 @@ declare namespace Electron { } } + interface TouchBar { + _removeFromWindow: (win: BrowserWindow) => void; + } + interface WebContents { _getURL(): string; _loadURL(url: string, options: Electron.LoadURLOptions): void;