123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- 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 () {
- if (existingHook) existingHook.call(this);
- hook.call(this);
- };
- };
- const ImmutableProperty = <T extends TouchBarItem<any>>(def: (config: T extends TouchBarItem<infer C> ? C : never, setInternalProp: <K extends keyof T>(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 = <T extends TouchBarItem<any>>(def: (config: T extends TouchBarItem<infer C> ? 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<ConfigType> 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<any>) {
- 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<any>) {
- this._parents = this._parents.filter(test => test.id !== item.id);
- }
- }
- class TouchBarButton extends TouchBarItem<Electron.TouchBarButtonConstructorOptions> implements Electron.TouchBarButton {
- @ImmutableProperty(() => 'button')
- type!: string;
- @LiveProperty<TouchBarButton>(config => config.label)
- label!: string;
- @LiveProperty<TouchBarButton>(config => config.accessibilityLabel)
- accessibilityLabel!: string;
- @LiveProperty<TouchBarButton>(config => config.backgroundColor)
- backgroundColor!: string;
- @LiveProperty<TouchBarButton>(config => config.icon)
- icon!: Electron.NativeImage;
- @LiveProperty<TouchBarButton>(config => config.iconPosition)
- iconPosition!: Electron.TouchBarButton['iconPosition'];
- @LiveProperty<TouchBarButton>(config => typeof config.enabled !== 'boolean' ? true : config.enabled)
- enabled!: boolean;
- @ImmutableProperty<TouchBarButton>(({ click: onClick }) => typeof onClick === 'function' ? () => onClick() : null)
- onInteraction!: Function | null;
- }
- class TouchBarColorPicker extends TouchBarItem<Electron.TouchBarColorPickerConstructorOptions> implements Electron.TouchBarColorPicker {
- @ImmutableProperty(() => 'colorpicker')
- type!: string;
- @LiveProperty<TouchBarColorPicker>(config => config.availableColors)
- availableColors!: string[];
- @LiveProperty<TouchBarColorPicker>(config => config.selectedColor)
- selectedColor!: string;
- @ImmutableProperty<TouchBarColorPicker>(({ change: onChange }, setInternalProp) => typeof onChange === 'function' ? (details: { color: string }) => {
- setInternalProp('selectedColor', details.color);
- onChange(details.color);
- } : null)
- onInteraction!: Function | null;
- }
- class TouchBarGroup extends TouchBarItem<Electron.TouchBarGroupConstructorOptions> implements Electron.TouchBarGroup {
- @ImmutableProperty(() => 'group')
- type!: string;
- @LiveProperty<TouchBarGroup>(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(self);
- }
- })
- child!: TouchBar;
- onInteraction = null;
- }
- class TouchBarLabel extends TouchBarItem<Electron.TouchBarLabelConstructorOptions> implements Electron.TouchBarLabel {
- @ImmutableProperty(() => 'label')
- type!: string;
- @LiveProperty<TouchBarLabel>(config => config.label)
- label!: string;
- @LiveProperty<TouchBarLabel>(config => config.accessibilityLabel)
- accessibilityLabel!: string;
- @LiveProperty<TouchBarLabel>(config => config.textColor)
- textColor!: string;
- onInteraction = null;
- }
- class TouchBarPopover extends TouchBarItem<Electron.TouchBarPopoverConstructorOptions> implements Electron.TouchBarPopover {
- @ImmutableProperty(() => 'popover')
- type!: string;
- @LiveProperty<TouchBarPopover>(config => config.label)
- label!: string;
- @LiveProperty<TouchBarPopover>(config => config.icon)
- icon!: Electron.NativeImage;
- @LiveProperty<TouchBarPopover>(config => config.showCloseButton)
- showCloseButton!: boolean;
- @LiveProperty<TouchBarPopover>(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(self);
- }
- })
- child!: TouchBar;
- onInteraction = null;
- }
- class TouchBarSlider extends TouchBarItem<Electron.TouchBarSliderConstructorOptions> implements Electron.TouchBarSlider {
- @ImmutableProperty(() => 'slider')
- type!: string;
- @LiveProperty<TouchBarSlider>(config => config.label)
- label!: string;
- @LiveProperty<TouchBarSlider>(config => config.minValue)
- minValue!: number;
- @LiveProperty<TouchBarSlider>(config => config.maxValue)
- maxValue!: number;
- @LiveProperty<TouchBarSlider>(config => config.value)
- value!: number;
- @ImmutableProperty<TouchBarSlider>(({ change: onChange }, setInternalProp) => typeof onChange === 'function' ? (details: { value: number }) => {
- setInternalProp('value', details.value);
- onChange(details.value);
- } : null)
- onInteraction!: Function | null;
- }
- class TouchBarSpacer extends TouchBarItem<Electron.TouchBarSpacerConstructorOptions> implements Electron.TouchBarSpacer {
- @ImmutableProperty(() => 'spacer')
- type!: string;
- @ImmutableProperty<TouchBarSpacer>(config => config.size)
- size!: Electron.TouchBarSpacer['size'];
- onInteraction = null;
- }
- class TouchBarSegmentedControl extends TouchBarItem<Electron.TouchBarSegmentedControlConstructorOptions> implements Electron.TouchBarSegmentedControl {
- @ImmutableProperty(() => 'segmented_control')
- type!: string;
- @LiveProperty<TouchBarSegmentedControl>(config => config.segmentStyle)
- segmentStyle!: Electron.TouchBarSegmentedControl['segmentStyle'];
- @LiveProperty<TouchBarSegmentedControl>(config => config.segments || [])
- segments!: Electron.SegmentedControlSegment[];
- @LiveProperty<TouchBarSegmentedControl>(config => config.selectedIndex)
- selectedIndex!: number;
- @LiveProperty<TouchBarSegmentedControl>(config => config.mode)
- mode!: Electron.TouchBarSegmentedControl['mode'];
- @ImmutableProperty<TouchBarSegmentedControl>(({ 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<Electron.TouchBarScrubberConstructorOptions> implements Electron.TouchBarScrubber {
- @ImmutableProperty(() => 'scrubber')
- type!: string;
- @LiveProperty<TouchBarScrubber>(config => config.items)
- items!: Electron.ScrubberItem[];
- @LiveProperty<TouchBarScrubber>(config => config.selectedStyle || null)
- selectedStyle!: Electron.TouchBarScrubber['selectedStyle'];
- @LiveProperty<TouchBarScrubber>(config => config.overlayStyle || null)
- overlayStyle!: Electron.TouchBarScrubber['overlayStyle'];
- @LiveProperty<TouchBarScrubber>(config => config.showArrowButtons || false)
- showArrowButtons!: boolean;
- @LiveProperty<TouchBarScrubber>(config => config.mode || 'free')
- mode!: Electron.TouchBarScrubber['mode'];
- @LiveProperty<TouchBarScrubber>(config => typeof config.continuous === 'undefined' ? true : config.continuous)
- continuous!: boolean;
- @ImmutableProperty<TouchBarScrubber>(({ 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<null> 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 = new Map<number, Function>();
- private items = new Map<string, TouchBarItem<any>>();
- orderedItems: TouchBarItem<any>[] = [];
- 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.escapeItem = (escapeItem as any) || null;
- const registerItem = (item: TouchBarItem<any>) => {
- this.items.set(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<any>[])) {
- this.orderedItems.push(item);
- registerItem(item);
- }
- }
- private changeListener = (item: TouchBarItem<any>) => {
- this.emit('change', item.id, item.type);
- };
- private [escapeItemSymbol]: TouchBarItem<unknown> | null = null;
- set escapeItem (item: TouchBarItem<unknown> | 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<unknown> | null {
- return this[escapeItemSymbol];
- }
- _addToWindow (window: Electron.BrowserWindow) {
- const { id } = window;
- // Already added to window
- if (this.windowListeners.has(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.get(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;
- this.windowListeners.delete(id);
- const unregisterItems = (items: TouchBarItem<any>[]) => {
- 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.set(id, removeListeners);
- window._setTouchBarItems(this.orderedItems);
- escapeItemListener(this.escapeItem);
- }
- _removeFromWindow (window: Electron.BrowserWindow) {
- const removeListeners = this.windowListeners.get(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;
|