diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 6748c9c0dd1..9a4ae5850dc 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -853,6 +853,10 @@ void Window::RefreshTouchBarItem(const std::string& item_id) { window_->RefreshTouchBarItem(item_id); } +void Window::SetEscapeTouchBarItem(const mate::PersistentDictionary& item) { + window_->SetEscapeTouchBarItem(item); +} + int32_t Window::ID() const { return weak_map_id(); } @@ -975,6 +979,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setVibrancy", &Window::SetVibrancy) .SetMethod("_setTouchBarItems", &Window::SetTouchBar) .SetMethod("_refreshTouchBarItem", &Window::RefreshTouchBarItem) + .SetMethod("_setEscapeTouchBarItem", &Window::SetEscapeTouchBarItem) #if defined(OS_WIN) .SetMethod("hookWindowMessage", &Window::HookWindowMessage) .SetMethod("isWindowMessageHooked", &Window::IsWindowMessageHooked) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index f30baf79d4d..24afec354df 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -208,6 +208,7 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); void SetTouchBar(const std::vector& items); void RefreshTouchBarItem(const std::string& item_id); + void SetEscapeTouchBarItem(const mate::PersistentDictionary& item); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 90a823f80b6..af32a2e8a85 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -347,6 +347,10 @@ void NativeWindow::SetTouchBar( void NativeWindow::RefreshTouchBarItem(const std::string& item_id) { } +void NativeWindow::SetEscapeTouchBarItem( + const mate::PersistentDictionary& item) { +} + void NativeWindow::FocusOnWebView() { web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 941f5849a65..227e28c1e46 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -174,6 +174,7 @@ class NativeWindow : public base::SupportsUserData, virtual void SetTouchBar( const std::vector& items); virtual void RefreshTouchBarItem(const std::string& item_id); + virtual void SetEscapeTouchBarItem(const mate::PersistentDictionary& item); // Webview APIs. virtual void FocusOnWebView(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index bd34993fb11..a535cdb4bef 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -103,6 +103,7 @@ class NativeWindowMac : public NativeWindow, void SetTouchBar( const std::vector& items) override; void RefreshTouchBarItem(const std::string& item_id) override; + void SetEscapeTouchBarItem(const mate::PersistentDictionary& item) override; // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 621e786ddc0..c3ca24a3d41 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -368,6 +368,7 @@ enum { - (void)enableWindowButtonsOffset; - (void)resetTouchBar:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item; @end @@ -410,6 +411,11 @@ enum { return nil; } +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item { + if (atom_touch_bar_ && self.touchBar) + [atom_touch_bar_ setEscapeTouchBarItem:item forTouchBar:self.touchBar]; +} + // NSWindow overrides. - (void)swipeWithEvent:(NSEvent *)event { @@ -1417,6 +1423,10 @@ void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) { [window_ refreshTouchBarItem:item_id]; } +void NativeWindowMac::SetEscapeTouchBarItem(const mate::PersistentDictionary& item) { + [window_ setEscapeTouchBarItem:item]; +} + void NativeWindowMac::OnInputEvent(const blink::WebInputEvent& event) { switch (event.type) { case blink::WebInputEvent::GestureScrollBegin: diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 29cdf79b255..26662cf4850 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -31,6 +31,8 @@ - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; - (void)refreshTouchBarItem:(NSTouchBar*)touchBar id:(const std::string&)item_id; +- (void)addNonDefaultTouchBarItems:(const std::vector&)items; +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item forTouchBar:(NSTouchBar*)touchBar; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 69926684b67..3a8037b1c5f 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -145,7 +145,25 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; } else if (item_type == "scrubber") { [self updateScrubber:(NSCustomTouchBarItem*)item withSettings:settings]; } +} +- (void)addNonDefaultTouchBarItems:(const std::vector&)items { + [self identifiersFromSettings:items]; +} + +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item forTouchBar:(NSTouchBar*)touchBar { + std::string type; + std::string item_id; + NSTouchBarItemIdentifier identifier = nil; + if (item.Get("type", &type) && item.Get("id", &item_id)) { + identifier = [self identifierFromID:item_id type:type]; + } + if (identifier) { + [self addNonDefaultTouchBarItems:{ item }]; + touchBar.escapeKeyReplacementItemIdentifier = identifier; + } else { + touchBar.escapeKeyReplacementItemIdentifier = nil; + } } - (void)buttonAction:(id)sender { diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index 57d5bd7453b..6fe7c820a1c 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -55,6 +55,7 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @property(copy) NSArray* defaultItemIdentifiers; @property(copy, readonly) NSArray* itemIdentifiers; @property(copy, nullable) NSTouchBarItemIdentifier principalItemIdentifier; +@property(copy, nullable) NSTouchBarItemIdentifier escapeKeyReplacementItemIdentifier; @property(copy) NSSet* templateItems; @property(nullable, weak) id delegate; diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 7eb2eb0209b..7ac578da46d 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -4,9 +4,11 @@ Process: [Main](../tutorial/quick-start.md#main-process) -### `new TouchBar(items)` _Experimental_ +### `new TouchBar(options)` _Experimental_ -* `items` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md))[] +* `options` - Object + * `items` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarScrubber](touch-bar-scrubber.md) | [TouchBarSegmentedControl](touch-bar-segmented-control.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md))[] + * `escapeItem` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarScrubber](touch-bar-scrubber.md) | [TouchBarSegmentedControl](touch-bar-segmented-control.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md)) (optional) Creates a new touch bar with the specified items. Use `BrowserWindow.setTouchBar` to add the `TouchBar` to a window. @@ -14,6 +16,16 @@ Creates a new touch bar with the specified items. Use **Note:** The TouchBar API is currently experimental and may change or be removed in future Electron releases. +### Instance Properties + +The following properties are available on instances of `TouchBar`: + +#### `touchBar.escapeItem` + +The `TouchBarItem` that will replace the "esc" button on the touch bar when set. +Setting to `null` restores the default "esc" button. Changing this value +immediately updates the escape item in the touch bar. + ## Examples Below is an example of a simple slot machine touch bar game with a button diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 74951edbac6..1070398c75c 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -20,22 +20,37 @@ class TouchBar extends EventEmitter { touchBar._addToWindow(window) } - constructor (items) { + constructor (options) { super() + if (options == null) { + throw new Error('Must specify options object as first argument') + } + + let {items, escapeItem} = options + + // FIXME Support array as first argument, remove in 2.0 + if (Array.isArray(options)) { + items = options + escapeItem = null + } + if (!Array.isArray(items)) { - throw new Error('Must specify items array as first argument') + 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.emit('change', item.id, item.type) - }) + item.on('change', this.changeListener) if (item.child instanceof TouchBar) { item.child.ordereredItems.forEach(registerItem) } @@ -49,6 +64,24 @@ class TouchBar extends EventEmitter { }) } + 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 @@ -62,8 +95,16 @@ class TouchBar extends EventEmitter { } this.on('change', changeListener) + const escapeItemListener = (item) => { + window._setEscapeTouchBarItem(item != null ? item : {}) + } + this.on('escape-item-change', escapeItemListener) + const interactionListener = (event, itemID, details) => { - const item = this.items[itemID] + 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) } @@ -72,6 +113,7 @@ class TouchBar extends EventEmitter { 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 @@ -81,6 +123,7 @@ class TouchBar extends EventEmitter { this.windowListeners[id] = removeListeners window._setTouchBarItems(this.ordereredItems) + escapeItemListener(this.escapeItem) } _removeFromWindow (window) { @@ -104,7 +147,7 @@ class TouchBarItem extends EventEmitter { }, set: function (value) { this[privateName] = value - this.emit('change') + this.emit('change', this) }, enumerable: true }) diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index a8e90502f55..420ef3fc7b3 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -6,20 +6,32 @@ const {TouchBarButton, TouchBarColorPicker, TouchBarGroup} = TouchBar const {TouchBarLabel, TouchBarPopover, TouchBarScrubber, TouchBarSegmentedControl, TouchBarSlider, TouchBarSpacer} = TouchBar describe('TouchBar module', function () { - it('throws an error when created without an items array', function () { + it('throws an error when created without an options object', function () { assert.throws(() => { const touchBar = new TouchBar() touchBar.toString() - }, /Must specify items array as first argument/) + }, /Must specify options object as first argument/) }) it('throws an error when created with invalid items', function () { assert.throws(() => { - const touchBar = new TouchBar([1, true, {}, []]) + const touchBar = new TouchBar({items: [1, true, {}, []]}) touchBar.toString() }, /Each item must be an instance of TouchBarItem/) }) + it('throws an error when an invalid escape item is set', function () { + assert.throws(() => { + const touchBar = new TouchBar({items: [], escapeItem: 'esc'}) + touchBar.toString() + }, /Escape item must be an instance of TouchBarItem/) + + assert.throws(() => { + const touchBar = new TouchBar({items: []}) + touchBar.escapeItem = 'esc' + }, /Escape item must be an instance of TouchBarItem/) + }) + describe('BrowserWindow behavior', function () { let window @@ -55,10 +67,40 @@ describe('TouchBar module', function () { showArrowButtons: true }) ]) + const escapeButton = new TouchBarButton({ + label: 'foo' + }) window.setTouchBar(touchBar) + touchBar.escapeItem = escapeButton label.label = 'baz' + escapeButton.label = 'hello' window.setTouchBar() window.setTouchBar(new TouchBar([new TouchBarLabel({label: 'two'})])) + touchBar.escapeItem = null + }) + + it('calls the callback on the items when a window interaction event fires', function (done) { + const button = new TouchBarButton({ + label: 'bar', + click: () => { + done() + } + }) + const touchBar = new TouchBar({items: [button]}) + window.setTouchBar(touchBar) + window.emit('-touch-bar-interaction', {}, button.id) + }) + + it('calls the callback on the escape item when a window interaction event fires', function (done) { + const button = new TouchBarButton({ + label: 'bar', + click: () => { + done() + } + }) + const touchBar = new TouchBar({escapeItem: button}) + window.setTouchBar(touchBar) + window.emit('-touch-bar-interaction', {}, button.id) }) }) })