From 703b5738c8df36decee7891be3b9158b9283d095 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sun, 27 Nov 2016 16:57:01 +1100 Subject: [PATCH 01/69] Initial TouchBar Magic * Make the AtomNSWindow also a NSTouchbarDelegate * Implement basic makeTouchBar and makeItemForIdentifier methods * Initial sending of touch / update events through IPC to BrowserWindowObjects TODO: * JS API * JS Object Converters * Generalize methods so that popovers can work --- atom/browser/api/atom_api_window.cc | 9 ++++ atom/browser/api/atom_api_window.h | 2 + atom/browser/native_window.cc | 10 ++++ atom/browser/native_window.h | 4 ++ atom/browser/native_window_mac.h | 1 + atom/browser/native_window_mac.mm | 78 +++++++++++++++++++++++++++ atom/browser/native_window_observer.h | 1 + 7 files changed, 105 insertions(+) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 2f97a88faaf..1107a628a9e 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -282,6 +282,10 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } +void Window::OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) { + Emit("_touch-bar-interaction", item_type, item_id); +} + #if defined(OS_WIN) void Window::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) { if (IsWindowMessageHooked(message)) { @@ -840,6 +844,10 @@ void Window::SetVibrancy(mate::Arguments* args) { window_->SetVibrancy(type); } +void Window::InitTouchBar() { + window_->InitTouchBar(); +} + int32_t Window::ID() const { return weak_map_id(); } @@ -960,6 +968,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setAutoHideCursor", &Window::SetAutoHideCursor) #endif .SetMethod("setVibrancy", &Window::SetVibrancy) + .SetMethod("initTouchBar", &Window::InitTouchBar) #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 80af78a5b03..3325c87fa9c 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -85,6 +85,7 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; + void OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; @@ -203,6 +204,7 @@ class Window : public mate::TrackableObject, void SetAutoHideCursor(bool auto_hide); void SetVibrancy(mate::Arguments* args); + void InitTouchBar(); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index eae68c37f5f..2219a4d595d 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -340,6 +340,9 @@ void NativeWindow::SetAutoHideCursor(bool auto_hide) { void NativeWindow::SetVibrancy(const std::string& filename) { } +void NativeWindow::InitTouchBar() { +} + void NativeWindow::FocusOnWebView() { web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } @@ -565,6 +568,13 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( observer.OnExecuteWindowsCommand(command); } +void NativeWindow::NotifyTouchBarItemInteraction( + const std::string& type, + const std::string& item_id) { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnTouchBarItemResult(type, item_id)); + } + #if defined(OS_WIN) void NativeWindow::NotifyWindowMessage( UINT message, WPARAM w_param, LPARAM l_param) { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index aa5e7e0c715..3338396146c 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -169,6 +169,9 @@ class NativeWindow : public base::SupportsUserData, // Vibrancy API virtual void SetVibrancy(const std::string& type); + // Touchbar API + virtual void InitTouchBar(); + // Webview APIs. virtual void FocusOnWebView(); virtual void BlurWebView(); @@ -228,6 +231,7 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); + void NotifyTouchBarItemInteraction(const std::string& item_type, const std::string& item_id); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 2beb55c0296..fec5eacb3a1 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -100,6 +100,7 @@ class NativeWindowMac : public NativeWindow, void SetAutoHideCursor(bool auto_hide) override; void SetVibrancy(const std::string& type) override; + void InitTouchBar() 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 acbef9906a7..029cf6002da 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -351,9 +351,14 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; +- (void)reloadTouchBar; +@end + +@interface AtomNSWindow () @end @implementation AtomNSWindow + NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -363,6 +368,75 @@ bool ScopedDisableResize::disable_resize_ = false; enable_larger_than_screen_ = enable; } +- (void)reloadTouchBar { + bar_items_ = [[NSMutableArray alloc] init]; + [bar_items_ addObject:@"com.electron.tb.button.1"]; + [bar_items_ addObject:@"com.electron.tb.button.2"]; + [bar_items_ addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + NSLog(@"Reloading Touch Bar --> '%@'", bar_items_[1]); + self.touchBar = nil; +} + +- (NSTouchBar *)makeTouchBar { + NSLog(@"Making Touch Bar"); + NSTouchBar* bar = [[NSTouchBar alloc] init]; + bar.delegate = self; + + // Set the default ordering of items. + + // NSLog(@"%@", bar_items_[1]); + bar.defaultItemIdentifiers = [bar_items_ copy]; + + return bar; +} + +- (void)buttonAction:(id)sender { + NSString* item_id = [NSString stringWithFormat:@"com.electron.tb.button.%d", (int)((NSButton *)sender).tag]; + NSLog(@"Button with ID: '%@' was pressed", item_id); + shell_->NotifyTouchBarItemInteraction("button", std::string([item_id UTF8String])); +} + +- (void)colorPickerAction:(id)sender { + NSString* item_id = ((NSColorPickerTouchBarItem *)sender).identifier; + NSLog(@"ColorPicker with ID: '%@' was updated with color '%@'", item_id, ((NSColorPickerTouchBarItem *)sender).color); + shell_->NotifyTouchBarItemInteraction("color_picker", std::string([item_id UTF8String])); +} + +static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; +static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; +// static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; +static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; +// static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; + +- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + if ([identifier hasPrefix:ButtonIdentifier]) { + NSString *idCopy = [identifier copy]; + idCopy = [identifier substringFromIndex:[ButtonIdentifier length]]; + NSButton *theButton = [NSButton buttonWithTitle:@"Electron Button" target:self action:@selector(buttonAction:)]; + theButton.tag = [idCopy floatValue]; + + NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + customItem.view = theButton; + + return customItem; + } else if ([identifier hasPrefix:LabelIdentifier]) { + NSTextField *theLabel = [NSTextField labelWithString:@"Hello From Electron"]; + + NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + customItem.view = theLabel; + + return customItem; + } else if ([identifier hasPrefix:ColorPickerIdentifier]) { + NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; + colorPickerItem.target = self; + colorPickerItem.action = @selector(colorPickerAction:); + return colorPickerItem; + } + + return nil; +} + + // NSWindow overrides. - (void)swipeWithEvent:(NSEvent *)event { @@ -1346,6 +1420,10 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { [effect_view setMaterial:vibrancyType]; } +void NativeWindowMac::InitTouchBar() { + [window_ reloadTouchBar]; +} + void NativeWindowMac::OnInputEvent(const blink::WebInputEvent& event) { switch (event.type) { case blink::WebInputEvent::GestureScrollBegin: diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 42d6b0287fa..e3d9deaed2e 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -70,6 +70,7 @@ class NativeWindowObserver { virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} + virtual void OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) {} // Called when window message received #if defined(OS_WIN) From 7857c83ea134d0967f7c68d28ad3c7a4e59cf791 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sun, 27 Nov 2016 22:54:12 +1100 Subject: [PATCH 02/69] Make dynamic buttons work along with click events --- atom/browser/api/atom_api_window.cc | 13 +++-- atom/browser/api/atom_api_window.h | 3 +- atom/browser/native_window.cc | 6 +- atom/browser/native_window.h | 4 +- atom/browser/native_window_mac.h | 7 ++- atom/browser/native_window_mac.mm | 65 +++++++++++++++++---- default_app/default_app.js | 11 +++- filenames.gypi | 1 + lib/browser/api/browser-window.js | 18 +++++- lib/browser/api/exports/electron.js | 6 ++ lib/browser/api/touch-bar.js | 89 +++++++++++++++++++++++++++++ 11 files changed, 203 insertions(+), 20 deletions(-) create mode 100644 lib/browser/api/touch-bar.js diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 1107a628a9e..a75df27fce6 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -283,7 +283,7 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { } void Window::OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) { - Emit("_touch-bar-interaction", item_type, item_id); + Emit("-touch-bar-interaction", item_type, item_id); } #if defined(OS_WIN) @@ -844,8 +844,12 @@ void Window::SetVibrancy(mate::Arguments* args) { window_->SetVibrancy(type); } -void Window::InitTouchBar() { - window_->InitTouchBar(); +void Window::DestroyTouchBar() { + window_->DestroyTouchBar(); +} + +void Window::SetTouchBar(mate::Arguments* args) { + window_->SetTouchBar(args); } int32_t Window::ID() const { @@ -968,7 +972,8 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setAutoHideCursor", &Window::SetAutoHideCursor) #endif .SetMethod("setVibrancy", &Window::SetVibrancy) - .SetMethod("initTouchBar", &Window::InitTouchBar) + .SetMethod("_destroyTouchBar", &Window::DestroyTouchBar) + .SetMethod("_setTouchBar", &Window::SetTouchBar) #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 3325c87fa9c..54aa01d3886 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -204,7 +204,8 @@ class Window : public mate::TrackableObject, void SetAutoHideCursor(bool auto_hide); void SetVibrancy(mate::Arguments* args); - void InitTouchBar(); + void DestroyTouchBar(); + void SetTouchBar(mate::Arguments* args); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 2219a4d595d..53068045ea7 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -33,6 +33,7 @@ #include "content/public/browser/render_widget_host_view.h" #include "content/public/common/content_switches.h" #include "ipc/ipc_message_macros.h" +#include "native_mate/constructor.h" #include "native_mate/dictionary.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/gfx/codec/png_codec.h" @@ -340,7 +341,10 @@ void NativeWindow::SetAutoHideCursor(bool auto_hide) { void NativeWindow::SetVibrancy(const std::string& filename) { } -void NativeWindow::InitTouchBar() { +void NativeWindow::DestroyTouchBar() { +} + +void NativeWindow::SetTouchBar(mate::Arguments* args) { } void NativeWindow::FocusOnWebView() { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 3338396146c..bd7584e0d10 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -23,6 +23,7 @@ #include "extensions/browser/app_window/size_constraints.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" +#include "native_mate/constructor.h" class SkRegion; @@ -170,7 +171,8 @@ class NativeWindow : public base::SupportsUserData, virtual void SetVibrancy(const std::string& type); // Touchbar API - virtual void InitTouchBar(); + virtual void DestroyTouchBar(); + virtual void SetTouchBar(mate::Arguments* args); // Webview APIs. virtual void FocusOnWebView(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index fec5eacb3a1..026f86e599c 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -13,6 +13,7 @@ #include "atom/browser/native_window.h" #include "base/mac/scoped_nsobject.h" #include "content/public/browser/render_widget_host.h" +#include "native_mate/constructor.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @@ -100,7 +101,9 @@ class NativeWindowMac : public NativeWindow, void SetAutoHideCursor(bool auto_hide) override; void SetVibrancy(const std::string& type) override; - void InitTouchBar() override; + void DestroyTouchBar() override; + void SetTouchBar(mate::Arguments* args) override; + std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; @@ -155,6 +158,8 @@ class NativeWindowMac : public NativeWindow, base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; + std::vector touch_bar_items_; + // Event monitor for scroll wheel event. id wheel_event_monitor_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 029cf6002da..87a27cae07a 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -352,6 +352,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; - (void)reloadTouchBar; +- (void)resetTouchBar; +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; @end @interface AtomNSWindow () @@ -359,6 +361,7 @@ bool ScopedDisableResize::disable_resize_ = false; @implementation AtomNSWindow NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; + std::map button_labels; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -368,24 +371,52 @@ bool ScopedDisableResize::disable_resize_ = false; enable_larger_than_screen_ = enable; } +- (void)resetTouchBar { + bar_items_ = [[NSMutableArray alloc] init]; + self.touchBar = nil; + NSLog(@"Destroying TouchBar"); +} + - (void)reloadTouchBar { bar_items_ = [[NSMutableArray alloc] init]; - [bar_items_ addObject:@"com.electron.tb.button.1"]; - [bar_items_ addObject:@"com.electron.tb.button.2"]; + std::vector items = shell_->GetTouchBarItems(); + std::map new_button_labels; + button_labels = new_button_labels; + + NSLog(@"reload"); + for (mate::Dictionary &item : items ) { + NSLog(@"reload iter"); + std::string type; + std::string item_id; + if (item.Get("type", &type) && item.Get("id", &item_id)) { + NSLog(@"type: %@", [NSString stringWithUTF8String:type.c_str()]); + NSLog(@"id: %@", [NSString stringWithUTF8String:item_id.c_str()]); + if (type == "button") { + std::string label; + if (item.Get("label", &label)) { + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + button_labels.insert(make_pair(item_id, label)); + } + } + } + } + // [bar_items_ addObject:@"com.electron.tb.button.1"]; + // [bar_items_ addObject:@"com.electron.tb.button.2"]; [bar_items_ addObject:NSTouchBarItemIdentifierOtherItemsProxy]; - NSLog(@"Reloading Touch Bar --> '%@'", bar_items_[1]); + // NSLog(@"Reloading Touch Bar --> '%@'", bar_items_[1]); self.touchBar = nil; } - (NSTouchBar *)makeTouchBar { NSLog(@"Making Touch Bar"); + return [self touchBarFromMutatableArray:bar_items_]; +} + +- (NSTouchBar *)touchBarFromMutatableArray:(NSMutableArray*)items { NSTouchBar* bar = [[NSTouchBar alloc] init]; bar.delegate = self; - // Set the default ordering of items. - - // NSLog(@"%@", bar_items_[1]); - bar.defaultItemIdentifiers = [bar_items_ copy]; + bar.defaultItemIdentifiers = [items copy]; return bar; } @@ -412,7 +443,7 @@ static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; if ([identifier hasPrefix:ButtonIdentifier]) { NSString *idCopy = [identifier copy]; idCopy = [identifier substringFromIndex:[ButtonIdentifier length]]; - NSButton *theButton = [NSButton buttonWithTitle:@"Electron Button" target:self action:@selector(buttonAction:)]; + NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:button_labels[std::string([idCopy UTF8String])].c_str()] target:self action:@selector(buttonAction:)]; theButton.tag = [idCopy floatValue]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; @@ -1420,8 +1451,22 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { [effect_view setMaterial:vibrancyType]; } -void NativeWindowMac::InitTouchBar() { - [window_ reloadTouchBar]; +void NativeWindowMac::DestroyTouchBar() { + [window_ resetTouchBar]; +} + +void NativeWindowMac::SetTouchBar(mate::Arguments* args) { + std::vector items; + LOG(ERROR) << "FOO"; + if (args->GetNext(&items)) { + LOG(ERROR) << "BAR"; + touch_bar_items_ = items; + [window_ reloadTouchBar]; + } +} + +std::vector NativeWindowMac::GetTouchBarItems() { + return touch_bar_items_; } void NativeWindowMac::OnInputEvent(const blink::WebInputEvent& event) { diff --git a/default_app/default_app.js b/default_app/default_app.js index bfb97a9ab08..35e290b4e9d 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -1,4 +1,4 @@ -const {app, BrowserWindow} = require('electron') +const {app, BrowserWindow,TouchBar} = require('electron') const path = require('path') let mainWindow = null @@ -24,5 +24,14 @@ exports.load = (appUrl) => { mainWindow = new BrowserWindow(options) mainWindow.loadURL(appUrl) mainWindow.focus() + + mainWindow.setTouchBar(new TouchBar([ + new (TouchBar.Button)({ + label: 'Hello World!', + click: () => { + console.log('Hello World Clicked') + } + }) + ])) }) } diff --git a/filenames.gypi b/filenames.gypi index 00297becef2..0e4ad206267 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -30,6 +30,7 @@ 'lib/browser/api/session.js', 'lib/browser/api/screen.js', 'lib/browser/api/system-preferences.js', + 'lib/browser/api/touch-bar.js', 'lib/browser/api/tray.js', 'lib/browser/api/web-contents.js', 'lib/browser/chrome-extension.js', diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 1aa34d45a0a..fd72a4cf0f8 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -1,6 +1,6 @@ 'use strict' -const {ipcMain} = require('electron') +const {ipcMain,TouchBar} = require('electron') const {EventEmitter} = require('events') const {BrowserWindow} = process.atomBinding('window') const v8Util = process.atomBinding('v8_util') @@ -131,6 +131,11 @@ BrowserWindow.prototype._init = function () { return this.webContents.devToolsWebContents } }) + + // Proxy TouchBar events + this.on('-touch-bar-interaction', (event, item_type, id, ...args) => { + TouchBar._event(id, ...args) + }) } BrowserWindow.getFocusedWindow = () => { @@ -198,4 +203,15 @@ Object.assign(BrowserWindow.prototype, { } }) +// TouchBar API +BrowserWindow.prototype.setTouchBar = function (touchBar) { + if (touchBar === null || typeof touchBar === 'undefined') { + this._destroyTouchBar(); + } else if (Array.isArray(touchBar)) { + this._setTouchBar((new TouchBar(touchBar)).toJSON()); + } else { + this._setTouchBar(touchBar.toJSON()) + } +} + module.exports = BrowserWindow diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 11698f1df55..3f405952175 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -103,6 +103,12 @@ Object.defineProperties(exports, { return require('../system-preferences') } }, + TouchBar: { + enumerable: true, + get: function () { + return require('../touch-bar') + } + }, Tray: { enumerable: true, get: function () { diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js new file mode 100644 index 00000000000..d8ce377bc86 --- /dev/null +++ b/lib/browser/api/touch-bar.js @@ -0,0 +1,89 @@ +const {EventEmitter} = require('events') +const {app} = require('electron') + +class TouchBar { + constructor (items) { + this.items = items; + if (!Array.isArray(items)) { + throw new Error('The items object provided has to be an array') + } + console.log(items) + items.forEach((item) => { + if (!item.id) { + throw new Error('Each item must be an instance of a TouchBarItem') + } + }) + } + + toJSON () { + return this.items.map((item) => item.toJSON ? item.toJSON() : item); + } +} + +let item_id_incrementor = 1 +const item_event_handlers = {} + +TouchBar._event = (id, ...args) => { + const id_parts = id.split('.') + const item_id = id_parts[id_parts.length - 1] + if (item_event_handlers[item_id]) item_event_handlers[item_id](...args) +} + +class TouchBarItem { + constructor (config) { + this.id = item_id_incrementor++ + const mConfig = Object.assign({}, config || {}) + Object.defineProperty(this, 'config', { + configurable: false, + enumerable: false, + get: () => mConfig + }) + this.config.id = `${this.config.id || this.id}`; + this.config.type = 'button'; + if (typeof this.config !== 'object' || this.config === null) { + throw new Error('Provided config must be a non-null object') + } + } + + toJSON () { + return this.config + } +} + +TouchBar.Button = class TouchBarButton extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'button'; + const click = config.click + if (typeof click === 'function') { + item_event_handlers[`${this.id}`] = click + } + } +} + +TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'colorpicker'; + const change = config.change + if (typeof change === 'function') { + item_event_handlers[`${this.id}`] = change + } + } +} + +TouchBar.Label = class TouchBarLabel extends TouchBarItem {} + +TouchBar.List = class TouchBarList extends TouchBarItem {} + +TouchBar.Slider = class TouchBarSlider extends TouchBarItem { + constructor (config) { + super(config) + const change = config.change + if (typeof change === 'function') { + item_event_handlers[this.id] = change + } + } +} + +module.exports = TouchBar; From 18c7c3ece86014904a5ae85d883cbd813fc09535 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 28 Nov 2016 18:24:48 +1100 Subject: [PATCH 03/69] Make label and colorpicker types work --- atom/browser/api/atom_api_window.cc | 4 +-- atom/browser/api/atom_api_window.h | 2 +- atom/browser/native_window.cc | 4 +-- atom/browser/native_window.h | 2 +- atom/browser/native_window_mac.mm | 48 ++++++++++++++++++++------- atom/browser/native_window_observer.h | 2 +- default_app/default_app.js | 8 +++++ lib/browser/api/touch-bar.js | 21 +++++++----- 8 files changed, 64 insertions(+), 27 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index a75df27fce6..ae52b03f079 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -282,8 +282,8 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } -void Window::OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) { - Emit("-touch-bar-interaction", item_type, item_id); +void Window::OnTouchBarItemResult(const std::string& item_type, const std::vector& args) { + Emit("-touch-bar-interaction", item_type, args); } #if defined(OS_WIN) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 54aa01d3886..55347bb54da 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -85,7 +85,7 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; - void OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) override; + void OnTouchBarItemResult(const std::string& item_type, const std::vector& args) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 53068045ea7..1aead897d99 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -574,9 +574,9 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( void NativeWindow::NotifyTouchBarItemInteraction( const std::string& type, - const std::string& item_id) { + const std::vector& args) { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, - OnTouchBarItemResult(type, item_id)); + OnTouchBarItemResult(type, args)); } #if defined(OS_WIN) diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index bd7584e0d10..38636fa2a5a 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -233,7 +233,7 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); - void NotifyTouchBarItemInteraction(const std::string& item_type, const std::string& item_id); + void NotifyTouchBarItemInteraction(const std::string& item_type, const std::vector& args); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 87a27cae07a..1e83d133cfb 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -361,7 +361,7 @@ bool ScopedDisableResize::disable_resize_ = false; @implementation AtomNSWindow NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; - std::map button_labels; + std::map item_labels; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -380,8 +380,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)reloadTouchBar { bar_items_ = [[NSMutableArray alloc] init]; std::vector items = shell_->GetTouchBarItems(); - std::map new_button_labels; - button_labels = new_button_labels; + std::map new_labels; + item_labels = new_labels; NSLog(@"reload"); for (mate::Dictionary &item : items ) { @@ -395,8 +395,16 @@ bool ScopedDisableResize::disable_resize_ = false; std::string label; if (item.Get("label", &label)) { [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - button_labels.insert(make_pair(item_id, label)); + item_labels.insert(make_pair(item_id, label)); } + } else if (type == "label") { + std::string label; + if (item.Get("label", &label)) { + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + item_labels.insert(make_pair(item_id, label)); + } + } else if (type == "colorpicker") { + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } } } @@ -424,13 +432,17 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)buttonAction:(id)sender { NSString* item_id = [NSString stringWithFormat:@"com.electron.tb.button.%d", (int)((NSButton *)sender).tag]; NSLog(@"Button with ID: '%@' was pressed", item_id); - shell_->NotifyTouchBarItemInteraction("button", std::string([item_id UTF8String])); + shell_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); } - (void)colorPickerAction:(id)sender { NSString* item_id = ((NSColorPickerTouchBarItem *)sender).identifier; - NSLog(@"ColorPicker with ID: '%@' was updated with color '%@'", item_id, ((NSColorPickerTouchBarItem *)sender).color); - shell_->NotifyTouchBarItemInteraction("color_picker", std::string([item_id UTF8String])); + NSColor* color = ((NSColorPickerTouchBarItem *)sender).color; + NSString* colorHexString = [NSString stringWithFormat:@"#%02X%02X%02X", + (int) (color.redComponent * 0xFF), (int) (color.greenComponent * 0xFF), + (int) (color.blueComponent * 0xFF)]; + NSLog(@"ColorPicker with ID: '%@' was updated with color '%@'", item_id, colorHexString); + shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), std::string([colorHexString UTF8String]) }); } static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; @@ -439,19 +451,31 @@ static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorp static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; // static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; +- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { + NSString *idCopy = [identifier copy]; + idCopy = [identifier substringFromIndex:[prefix length]]; + return idCopy; +} + +- (bool)hasLabel:(NSString*)id { + return item_labels.find(std::string([id UTF8String])) != item_labels.end(); +} + - (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { if ([identifier hasPrefix:ButtonIdentifier]) { - NSString *idCopy = [identifier copy]; - idCopy = [identifier substringFromIndex:[ButtonIdentifier length]]; - NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:button_labels[std::string([idCopy UTF8String])].c_str()] target:self action:@selector(buttonAction:)]; - theButton.tag = [idCopy floatValue]; + NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + if (![self hasLabel:id]) return nil; + NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:item_labels[std::string([id UTF8String])].c_str()] target:self action:@selector(buttonAction:)]; + theButton.tag = [id floatValue]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theButton; return customItem; } else if ([identifier hasPrefix:LabelIdentifier]) { - NSTextField *theLabel = [NSTextField labelWithString:@"Hello From Electron"]; + NSString* id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + if (![self hasLabel:id]) return nil; + NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:item_labels[std::string([id UTF8String])].c_str()]]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theLabel; diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index e3d9deaed2e..bd9ff21f56c 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -70,7 +70,7 @@ class NativeWindowObserver { virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} - virtual void OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) {} + virtual void OnTouchBarItemResult(const std::string& item_type, const std::vector& args) {} // Called when window message received #if defined(OS_WIN) diff --git a/default_app/default_app.js b/default_app/default_app.js index 35e290b4e9d..3856c8e90ab 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -31,6 +31,14 @@ exports.load = (appUrl) => { click: () => { console.log('Hello World Clicked') } + }), + new (TouchBar.Label)({ + label: 'This is a Label' + }), + new (TouchBar.ColorPicker)({ + change: (...args) => { + console.log('Color was changed', ...args) + } }) ])) }) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index d8ce377bc86..f62d7c2ff03 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -23,8 +23,9 @@ class TouchBar { let item_id_incrementor = 1 const item_event_handlers = {} -TouchBar._event = (id, ...args) => { - const id_parts = id.split('.') +TouchBar._event = (eventArgs) => { + const args = eventArgs.slice(1) + const id_parts = eventArgs[0].split('.') const item_id = id_parts[id_parts.length - 1] if (item_event_handlers[item_id]) item_event_handlers[item_id](...args) } @@ -39,7 +40,6 @@ class TouchBarItem { get: () => mConfig }) this.config.id = `${this.config.id || this.id}`; - this.config.type = 'button'; if (typeof this.config !== 'object' || this.config === null) { throw new Error('Provided config must be a non-null object') } @@ -53,7 +53,7 @@ class TouchBarItem { TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'button'; + this.config.type = 'button' const click = config.click if (typeof click === 'function') { item_event_handlers[`${this.id}`] = click @@ -64,22 +64,27 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'colorpicker'; - const change = config.change + this.config.type = 'colorpicker' + const change = this.config.change if (typeof change === 'function') { item_event_handlers[`${this.id}`] = change } } } -TouchBar.Label = class TouchBarLabel extends TouchBarItem {} +TouchBar.Label = class TouchBarLabel extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'label' + } +} TouchBar.List = class TouchBarList extends TouchBarItem {} TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) - const change = config.change + const change = this.config.change if (typeof change === 'function') { item_event_handlers[this.id] = change } From c92c4138a8a0d992984c50f9925848e495a696ea Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 28 Nov 2016 21:43:39 +1100 Subject: [PATCH 04/69] Add Slider item type and add options to the button type --- atom/browser/native_window_mac.h | 5 +- atom/browser/native_window_mac.mm | 206 +++++++++++++++++++++++------- default_app/default_app.js | 16 ++- lib/browser/api/browser-window.js | 2 +- lib/browser/api/touch-bar.js | 15 ++- 5 files changed, 191 insertions(+), 53 deletions(-) diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 026f86e599c..b960290c82d 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -14,6 +14,7 @@ #include "base/mac/scoped_nsobject.h" #include "content/public/browser/render_widget_host.h" #include "native_mate/constructor.h" +#include "native_mate/persistent_dictionary.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @@ -103,7 +104,7 @@ class NativeWindowMac : public NativeWindow, void SetVibrancy(const std::string& type) override; void DestroyTouchBar() override; void SetTouchBar(mate::Arguments* args) override; - std::vector GetTouchBarItems(); + std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; @@ -158,7 +159,7 @@ class NativeWindowMac : public NativeWindow, base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; - std::vector touch_bar_items_; + std::vector touch_bar_items_; // Event monitor for scroll wheel event. id wheel_event_monitor_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 1e83d133cfb..b7537ecb742 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -10,6 +10,7 @@ #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" +#include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/options_switches.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_cftyperef.h" @@ -361,7 +362,7 @@ bool ScopedDisableResize::disable_resize_ = false; @implementation AtomNSWindow NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; - std::map item_labels; + std::map item_id_map; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -379,39 +380,29 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)reloadTouchBar { bar_items_ = [[NSMutableArray alloc] init]; - std::vector items = shell_->GetTouchBarItems(); - std::map new_labels; - item_labels = new_labels; + std::vector items = shell_->GetTouchBarItems(); + std::map new_map; + item_id_map = new_map; - NSLog(@"reload"); - for (mate::Dictionary &item : items ) { - NSLog(@"reload iter"); + for (mate::PersistentDictionary &item : items ) { std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { NSLog(@"type: %@", [NSString stringWithUTF8String:type.c_str()]); NSLog(@"id: %@", [NSString stringWithUTF8String:item_id.c_str()]); + item_id_map.insert(make_pair(item_id, item)); if (type == "button") { - std::string label; - if (item.Get("label", &label)) { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - item_labels.insert(make_pair(item_id, label)); - } + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "label") { - std::string label; - if (item.Get("label", &label)) { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - item_labels.insert(make_pair(item_id, label)); - } + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "colorpicker") { [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "slider") { + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } } } - // [bar_items_ addObject:@"com.electron.tb.button.1"]; - // [bar_items_ addObject:@"com.electron.tb.button.2"]; [bar_items_ addObject:NSTouchBarItemIdentifierOtherItemsProxy]; - // NSLog(@"Reloading Touch Bar --> '%@'", bar_items_[1]); self.touchBar = nil; } @@ -445,11 +436,11 @@ bool ScopedDisableResize::disable_resize_ = false; shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), std::string([colorHexString UTF8String]) }); } -static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; -static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; -// static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; -static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; -// static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; +- (void)sliderAction:(id)sender { + NSString* item_id = ((NSSliderTouchBarItem *)sender).identifier; + NSLog(@"Slider with ID: '%@' was changed", item_id); + shell_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem *)sender).slider intValue]) }); +} - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { NSString *idCopy = [identifier copy]; @@ -457,35 +448,164 @@ static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; return idCopy; } -- (bool)hasLabel:(NSString*)id { - return item_labels.find(std::string([id UTF8String])) != item_labels.end(); +- (bool)hasTBDict:(std::string)id { + return item_id_map.find(id) != item_id_map.end(); } -- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - if ([identifier hasPrefix:ButtonIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - if (![self hasLabel:id]) return nil; - NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:item_labels[std::string([id UTF8String])].c_str()] target:self action:@selector(buttonAction:)]; +- (NSColor*)colorFromHexColorString:(NSString*)inColorString { + NSColor* result = nil; + unsigned colorCode = 0; + unsigned char redByte, greenByte, blueByte; + + if (nil != inColorString) + { + NSScanner* scanner = [NSScanner scannerWithString:inColorString]; + (void) [scanner scanHexInt:&colorCode]; // ignore error + } + redByte = (unsigned char)(colorCode >> 16); + greenByte = (unsigned char)(colorCode >> 8); + blueByte = (unsigned char)(colorCode); // masks off high bits + + result = [NSColor + colorWithCalibratedRed:(CGFloat)redByte / 0xff + green:(CGFloat)greenByte / 0xff + blue:(CGFloat)blueByte / 0xff + alpha:1.0]; + return result; +} + +- (nullable NSTouchBarItem *)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + std::string label; + if (item.Get("label", &label)) { + NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; theButton.tag = [id floatValue]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theButton; + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + std::string backgroundColor; + if (item.Get("backgroundColor", &backgroundColor)) { + theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; + } + + std::string labelColor; + if (item.Get("labelColor", &labelColor)) { + NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]]; + NSUInteger len = [attrTitle length]; + NSRange range = NSMakeRange(0, len); + [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; + [attrTitle fixAttributesInRange:range]; + [theButton setAttributedTitle:attrTitle]; + } + + gfx::Image image; + if (item.Get("image", &image)) { + theButton.image = image.AsNSImage(); + } + return customItem; - } else if ([identifier hasPrefix:LabelIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - if (![self hasLabel:id]) return nil; - NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:item_labels[std::string([id UTF8String])].c_str()]]; + } + return nil; +} + +- (nullable NSTouchBarItem*) makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + NSLog(@"Making label: '%@'", id); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + std::string label; + if (item.Get("label", &label)) { + NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:label.c_str()]]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theLabel; + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + return customItem; + } + return nil; +} + +- (nullable NSTouchBarItem*) makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + + NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; + colorPickerItem.target = self; + colorPickerItem.action = @selector(colorPickerAction:); + + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + colorPickerItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + return colorPickerItem; +} + +- (nullable NSTouchBarItem*) makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + + NSSliderTouchBarItem *sliderItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; + sliderItem.target = self; + sliderItem.action = @selector(sliderAction:); + + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + sliderItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + std::string label; + if (item.Get("label", &label)) { + sliderItem.label = [NSString stringWithUTF8String:label.c_str()]; + } + + int maxValue = 100; + int minValue = 0; + int initialValue = 50; + if (item.Get("minValue", &minValue) && item.Get("maxValue", &maxValue)) { + item.Get("initialValue", &initialValue); + } + sliderItem.slider.minValue = minValue; + sliderItem.slider.maxValue = maxValue; + sliderItem.slider.doubleValue = initialValue; + + return sliderItem; +} + +static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; +static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; +// static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; +static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; +static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; + +- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + if ([identifier hasPrefix:ButtonIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + return [self makeButtonForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:LabelIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + return [self makeLabelForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:ColorPickerIdentifier]) { - NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; - colorPickerItem.target = self; - colorPickerItem.action = @selector(colorPickerAction:); - return colorPickerItem; + NSString* id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + return [self makeColorPickerForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:SliderIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + return [self makeSliderForID:id withIdentifier:identifier]; } return nil; @@ -1480,16 +1600,14 @@ void NativeWindowMac::DestroyTouchBar() { } void NativeWindowMac::SetTouchBar(mate::Arguments* args) { - std::vector items; - LOG(ERROR) << "FOO"; + std::vector items; if (args->GetNext(&items)) { - LOG(ERROR) << "BAR"; touch_bar_items_ = items; [window_ reloadTouchBar]; } } -std::vector NativeWindowMac::GetTouchBarItems() { +std::vector NativeWindowMac::GetTouchBarItems() { return touch_bar_items_; } diff --git a/default_app/default_app.js b/default_app/default_app.js index 3856c8e90ab..17f591b48fd 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -28,6 +28,8 @@ exports.load = (appUrl) => { mainWindow.setTouchBar(new TouchBar([ new (TouchBar.Button)({ label: 'Hello World!', + backgroundColor: "DDDDDD", + labelColor: "000000", click: () => { console.log('Hello World Clicked') } @@ -36,10 +38,18 @@ exports.load = (appUrl) => { label: 'This is a Label' }), new (TouchBar.ColorPicker)({ - change: (...args) => { - console.log('Color was changed', ...args) + change: (newColor) => { + console.log('Color was changed', newColor) } - }) + }), + new (TouchBar.Slider)({ + label: 'Slider 123', + minValue: 50, + maxValue: 1000, + change: (newVal) => { + console.log('Slider was changed', newVal, typeof newVal) + } + }), ])) }) } diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index fd72a4cf0f8..ba1fdadcb62 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -134,7 +134,7 @@ BrowserWindow.prototype._init = function () { // Proxy TouchBar events this.on('-touch-bar-interaction', (event, item_type, id, ...args) => { - TouchBar._event(id, ...args) + TouchBar._event(item_type, id, ...args) }) } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index f62d7c2ff03..0709e07f1c9 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -23,8 +23,11 @@ class TouchBar { let item_id_incrementor = 1 const item_event_handlers = {} -TouchBar._event = (eventArgs) => { - const args = eventArgs.slice(1) +TouchBar._event = (itemType, eventArgs) => { + let args = eventArgs.slice(1) + if (itemType === 'slider') { + args = args.map(val => parseInt(val, 10)) + } const id_parts = eventArgs[0].split('.') const item_id = id_parts[id_parts.length - 1] if (item_event_handlers[item_id]) item_event_handlers[item_id](...args) @@ -79,11 +82,17 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } -TouchBar.List = class TouchBarList extends TouchBarItem {} +TouchBar.List = class TouchBarList extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'list' + } +} TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) + this.config.type = 'slider'; const change = this.config.change if (typeof change === 'function') { item_event_handlers[this.id] = change From 2bc45c86653029e68b1b5dfc726cd95f7a72ee3a Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 28 Nov 2016 21:46:32 +1100 Subject: [PATCH 05/69] Change demo touchbar values --- default_app/default_app.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/default_app/default_app.js b/default_app/default_app.js index 17f591b48fd..feeb68871e0 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -28,8 +28,8 @@ exports.load = (appUrl) => { mainWindow.setTouchBar(new TouchBar([ new (TouchBar.Button)({ label: 'Hello World!', - backgroundColor: "DDDDDD", - labelColor: "000000", + backgroundColor: "FF0000", + labelColor: "0000FF", click: () => { console.log('Hello World Clicked') } @@ -46,6 +46,7 @@ exports.load = (appUrl) => { label: 'Slider 123', minValue: 50, maxValue: 1000, + initialValue: 300, change: (newVal) => { console.log('Slider was changed', newVal, typeof newVal) } From 269d899a9939d287579749ea264125c065adb328 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:00:08 +1100 Subject: [PATCH 06/69] Implement popOver item type (woo hoo it worked) --- atom/browser/native_window_mac.mm | 130 ++++++++++++++++++++---------- default_app/default_app.js | 14 ++++ lib/browser/api/touch-bar.js | 15 +++- 3 files changed, 115 insertions(+), 44 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index b7537ecb742..26430ce625f 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -375,39 +375,42 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)resetTouchBar { bar_items_ = [[NSMutableArray alloc] init]; self.touchBar = nil; - NSLog(@"Destroying TouchBar"); } -- (void)reloadTouchBar { - bar_items_ = [[NSMutableArray alloc] init]; - std::vector items = shell_->GetTouchBarItems(); - std::map new_map; - item_id_map = new_map; +- (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { + NSMutableArray* idents = [[NSMutableArray alloc] init]; - for (mate::PersistentDictionary &item : items ) { + for (mate::PersistentDictionary &item : dicts) { std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { - NSLog(@"type: %@", [NSString stringWithUTF8String:type.c_str()]); - NSLog(@"id: %@", [NSString stringWithUTF8String:item_id.c_str()]); item_id_map.insert(make_pair(item_id, item)); if (type == "button") { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "label") { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "colorpicker") { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "slider") { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "popover") { + [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } } } - [bar_items_ addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + [idents addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + + return idents; +} + +- (void)reloadTouchBar { + std::map new_map; + item_id_map = new_map; + bar_items_ = [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; self.touchBar = nil; } - (NSTouchBar *)makeTouchBar { - NSLog(@"Making Touch Bar"); return [self touchBarFromMutatableArray:bar_items_]; } @@ -422,7 +425,6 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)buttonAction:(id)sender { NSString* item_id = [NSString stringWithFormat:@"com.electron.tb.button.%d", (int)((NSButton *)sender).tag]; - NSLog(@"Button with ID: '%@' was pressed", item_id); shell_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); } @@ -432,13 +434,11 @@ bool ScopedDisableResize::disable_resize_ = false; NSString* colorHexString = [NSString stringWithFormat:@"#%02X%02X%02X", (int) (color.redComponent * 0xFF), (int) (color.greenComponent * 0xFF), (int) (color.blueComponent * 0xFF)]; - NSLog(@"ColorPicker with ID: '%@' was updated with color '%@'", item_id, colorHexString); shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), std::string([colorHexString UTF8String]) }); } - (void)sliderAction:(id)sender { NSString* item_id = ((NSSliderTouchBarItem *)sender).identifier; - NSLog(@"Slider with ID: '%@' was changed", item_id); shell_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem *)sender).slider intValue]) }); } @@ -474,13 +474,38 @@ bool ScopedDisableResize::disable_resize_ = false; return result; } +- (NSButton*)makeButtonForDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { + NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; + + std::string backgroundColor; + if (dict.Get("backgroundColor", &backgroundColor)) { + theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; + } + + std::string labelColor; + if (dict.Get("labelColor", &labelColor)) { + NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]]; + NSUInteger len = [attrTitle length]; + NSRange range = NSMakeRange(0, len); + [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; + [attrTitle fixAttributesInRange:range]; + [theButton setAttributedTitle:attrTitle]; + } + + gfx::Image image; + if (dict.Get("image", &image)) { + theButton.image = image.AsNSImage(); + } + return theButton; +} + - (nullable NSTouchBarItem *)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; std::string label; if (item.Get("label", &label)) { - NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; + NSButton* theButton = [self makeButtonForDict:item withLabel:label]; theButton.tag = [id floatValue]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; @@ -491,26 +516,6 @@ bool ScopedDisableResize::disable_resize_ = false; customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; } - std::string backgroundColor; - if (item.Get("backgroundColor", &backgroundColor)) { - theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; - } - - std::string labelColor; - if (item.Get("labelColor", &labelColor)) { - NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]]; - NSUInteger len = [attrTitle length]; - NSRange range = NSMakeRange(0, len); - [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; - [attrTitle fixAttributesInRange:range]; - [theButton setAttributedTitle:attrTitle]; - } - - gfx::Image image; - if (item.Get("image", &image)) { - theButton.image = image.AsNSImage(); - } - return customItem; } return nil; @@ -518,7 +523,6 @@ bool ScopedDisableResize::disable_resize_ = false; - (nullable NSTouchBarItem*) makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id = std::string([id UTF8String]); - NSLog(@"Making label: '%@'", id); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; std::string label; @@ -577,9 +581,10 @@ bool ScopedDisableResize::disable_resize_ = false; int maxValue = 100; int minValue = 0; int initialValue = 50; - if (item.Get("minValue", &minValue) && item.Get("maxValue", &maxValue)) { - item.Get("initialValue", &initialValue); - } + item.Get("minValue", &minValue); + item.Get("maxValue", &maxValue); + item.Get("initialValue", &initialValue); + sliderItem.slider.minValue = minValue; sliderItem.slider.maxValue = maxValue; sliderItem.slider.doubleValue = initialValue; @@ -587,10 +592,46 @@ bool ScopedDisableResize::disable_resize_ = false; return sliderItem; } +- (nullable NSTouchBarItem*) makePopOverForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + + NSPopoverTouchBarItem *popOverItem = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; + + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + popOverItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + std::string label; + gfx::Image image; + if (item.Get("label", &label)) { + popOverItem.collapsedRepresentationLabel = [NSString stringWithUTF8String:label.c_str()]; + } else if (item.Get("image", &image)) { + popOverItem.collapsedRepresentationImage = image.AsNSImage(); + } + + bool showCloseButton; + if (item.Get("showCloseButton", &showCloseButton)) { + popOverItem.showsCloseButton = showCloseButton; + } + + std::vector touchBar; + if (item.Get("touchBar", &touchBar)) { + popOverItem.popoverTouchBar = [self touchBarFromMutatableArray:[self identifierArrayFromDicts:touchBar]]; + } else { + return nil; + } + + return popOverItem; +} + static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; // static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; +static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; - (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { @@ -606,6 +647,9 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; } else if ([identifier hasPrefix:SliderIdentifier]) { NSString* id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; return [self makeSliderForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:PopOverIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + return [self makePopOverForID:id withIdentifier:identifier]; } return nil; diff --git a/default_app/default_app.js b/default_app/default_app.js index feeb68871e0..401703a9fee 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -28,6 +28,7 @@ exports.load = (appUrl) => { mainWindow.setTouchBar(new TouchBar([ new (TouchBar.Button)({ label: 'Hello World!', + // image: '/path/to/image', backgroundColor: "FF0000", labelColor: "0000FF", click: () => { @@ -42,6 +43,19 @@ exports.load = (appUrl) => { console.log('Color was changed', newColor) } }), + new (TouchBar.PopOver)({ + // image: '/path/to/image', + label: 'foo', + showCloseButton: true, + touchBar: new TouchBar([ + new (TouchBar.Button)({ + label: 'Sub Button', + click: () => { + console.log('Sub Button Clicked') + } + }) + ]) + }), new (TouchBar.Slider)({ label: 'Slider 123', minValue: 50, diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 0709e07f1c9..1a397fc573d 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -7,7 +7,6 @@ class TouchBar { if (!Array.isArray(items)) { throw new Error('The items object provided has to be an array') } - console.log(items) items.forEach((item) => { if (!item.id) { throw new Error('Each item must be an instance of a TouchBarItem') @@ -89,6 +88,20 @@ TouchBar.List = class TouchBarList extends TouchBarItem { } } +TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'popover' + } + + toJSON () { + const config = this.config; + return Object.assign({}, config, { + touchBar: config.touchBar && config.touchBar.toJSON ? config.touchBar.toJSON() : [] + }) + } +} + TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) From 43cc5079d80f2abc2fdd0a970a3287d022ecbbd9 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:36:57 +1100 Subject: [PATCH 07/69] Implement group item --- atom/browser/native_window_mac.mm | 40 ++++++++++++++++++++++++++++++- default_app/default_app.js | 14 +++++++---- lib/browser/api/touch-bar.js | 21 ++++++++++------ 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 26430ce625f..81aa690cabf 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -395,6 +395,8 @@ bool ScopedDisableResize::disable_resize_ = false; [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "popover") { [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "group") { + [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } } } @@ -627,14 +629,43 @@ bool ScopedDisableResize::disable_resize_ = false; return popOverItem; } +- (nullable NSTouchBarItem*) makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + + std::vector items; + if (!item.Get("items", &items)) { + return nil; + } + + NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; + NSMutableArray* identList = [self identifierArrayFromDicts:items]; + for (NSUInteger i = 0; i < [identList count]; i++) { + NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; + if (generatedItem) { + [generatedItems addObject:generatedItem]; + } + } + NSGroupTouchBarItem *groupItem = [NSGroupTouchBarItem groupItemWithIdentifier:identifier items:generatedItems]; + + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + groupItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + return groupItem; +} + static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; // static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; +static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.tb.group."; static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; -- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { +- (nullable NSTouchBarItem *)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { if ([identifier hasPrefix:ButtonIdentifier]) { NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; return [self makeButtonForID:id withIdentifier:identifier]; @@ -650,11 +681,18 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; } else if ([identifier hasPrefix:PopOverIdentifier]) { NSString* id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; return [self makePopOverForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:GroupIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + return [self makeGroupForID:id withIdentifier:identifier]; } return nil; } +- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + return [self makeItemForIdentifier:identifier]; +} + // NSWindow overrides. diff --git a/default_app/default_app.js b/default_app/default_app.js index 401703a9fee..9ebc6b0ae94 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -48,11 +48,15 @@ exports.load = (appUrl) => { label: 'foo', showCloseButton: true, touchBar: new TouchBar([ - new (TouchBar.Button)({ - label: 'Sub Button', - click: () => { - console.log('Sub Button Clicked') - } + new (TouchBar.Group)({ + items: new TouchBar( + [1, 2, 3].map((i) => new (TouchBar.Button)({ + label: `Button ${i}`, + click: () => { + console.log(`Button ${i} (group) Clicked`) + } + })) + ) }) ]) }), diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 1a397fc573d..0479e19252c 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -74,6 +74,20 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { } } +TouchBar.Group = class TouchBarGroup extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'group' + } + + toJSON () { + const config = this.config; + return Object.assign({}, config, { + items: config.items && config.items.toJSON ? config.items.toJSON() : [] + }) + } +} + TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { super(config) @@ -81,13 +95,6 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } -TouchBar.List = class TouchBarList extends TouchBarItem { - constructor (config) { - super(config) - this.config.type = 'list' - } -} - TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { constructor (config) { super(config) From 257b32b84bf6c4f8cffcf5f4db3a1329e580b596 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:44:25 +1100 Subject: [PATCH 08/69] Remove unused ident --- atom/browser/native_window_mac.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 81aa690cabf..dfd71555ad9 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -659,7 +659,6 @@ bool ScopedDisableResize::disable_resize_ = false; static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; -// static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.tb.group."; static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; From 4f0caffc3b1362fc8827e1ffb73acf789ead8b48 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:55:07 +1100 Subject: [PATCH 09/69] Fix JS linting --- default_app/default_app.js | 8 +++---- lib/browser/api/browser-window.js | 10 ++++----- lib/browser/api/touch-bar.js | 35 ++++++++++++++----------------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/default_app/default_app.js b/default_app/default_app.js index 9ebc6b0ae94..57110e9c90d 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -1,4 +1,4 @@ -const {app, BrowserWindow,TouchBar} = require('electron') +const {app, BrowserWindow, TouchBar} = require('electron') const path = require('path') let mainWindow = null @@ -29,8 +29,8 @@ exports.load = (appUrl) => { new (TouchBar.Button)({ label: 'Hello World!', // image: '/path/to/image', - backgroundColor: "FF0000", - labelColor: "0000FF", + backgroundColor: 'FF0000', + labelColor: '0000FF', click: () => { console.log('Hello World Clicked') } @@ -68,7 +68,7 @@ exports.load = (appUrl) => { change: (newVal) => { console.log('Slider was changed', newVal, typeof newVal) } - }), + }) ])) }) } diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index ba1fdadcb62..ac6e3b4bb6e 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -1,6 +1,6 @@ 'use strict' -const {ipcMain,TouchBar} = require('electron') +const {ipcMain, TouchBar} = require('electron') const {EventEmitter} = require('events') const {BrowserWindow} = process.atomBinding('window') const v8Util = process.atomBinding('v8_util') @@ -133,8 +133,8 @@ BrowserWindow.prototype._init = function () { }) // Proxy TouchBar events - this.on('-touch-bar-interaction', (event, item_type, id, ...args) => { - TouchBar._event(item_type, id, ...args) + this.on('-touch-bar-interaction', (event, itemType, id, ...args) => { + TouchBar._event(itemType, id, ...args) }) } @@ -206,9 +206,9 @@ Object.assign(BrowserWindow.prototype, { // TouchBar API BrowserWindow.prototype.setTouchBar = function (touchBar) { if (touchBar === null || typeof touchBar === 'undefined') { - this._destroyTouchBar(); + this._destroyTouchBar() } else if (Array.isArray(touchBar)) { - this._setTouchBar((new TouchBar(touchBar)).toJSON()); + this._setTouchBar((new TouchBar(touchBar)).toJSON()) } else { this._setTouchBar(touchBar.toJSON()) } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 0479e19252c..1b18656a697 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -1,9 +1,6 @@ -const {EventEmitter} = require('events') -const {app} = require('electron') - class TouchBar { constructor (items) { - this.items = items; + this.items = items if (!Array.isArray(items)) { throw new Error('The items object provided has to be an array') } @@ -15,33 +12,33 @@ class TouchBar { } toJSON () { - return this.items.map((item) => item.toJSON ? item.toJSON() : item); + return this.items.map((item) => item.toJSON ? item.toJSON() : item) } } -let item_id_incrementor = 1 -const item_event_handlers = {} +let itemIdIncrementor = 1 +const itemEventHandlers = {} TouchBar._event = (itemType, eventArgs) => { let args = eventArgs.slice(1) if (itemType === 'slider') { args = args.map(val => parseInt(val, 10)) } - const id_parts = eventArgs[0].split('.') - const item_id = id_parts[id_parts.length - 1] - if (item_event_handlers[item_id]) item_event_handlers[item_id](...args) + const idParts = eventArgs[0].split('.') + const itemId = idParts[idParts.length - 1] + if (itemEventHandlers[itemId]) itemEventHandlers[itemId](...args) } class TouchBarItem { constructor (config) { - this.id = item_id_incrementor++ + this.id = itemIdIncrementor++ const mConfig = Object.assign({}, config || {}) Object.defineProperty(this, 'config', { configurable: false, enumerable: false, get: () => mConfig }) - this.config.id = `${this.config.id || this.id}`; + this.config.id = `${this.config.id || this.id}` if (typeof this.config !== 'object' || this.config === null) { throw new Error('Provided config must be a non-null object') } @@ -58,7 +55,7 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { this.config.type = 'button' const click = config.click if (typeof click === 'function') { - item_event_handlers[`${this.id}`] = click + itemEventHandlers[`${this.id}`] = click } } } @@ -69,7 +66,7 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { this.config.type = 'colorpicker' const change = this.config.change if (typeof change === 'function') { - item_event_handlers[`${this.id}`] = change + itemEventHandlers[`${this.id}`] = change } } } @@ -81,7 +78,7 @@ TouchBar.Group = class TouchBarGroup extends TouchBarItem { } toJSON () { - const config = this.config; + const config = this.config return Object.assign({}, config, { items: config.items && config.items.toJSON ? config.items.toJSON() : [] }) @@ -102,7 +99,7 @@ TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { } toJSON () { - const config = this.config; + const config = this.config return Object.assign({}, config, { touchBar: config.touchBar && config.touchBar.toJSON ? config.touchBar.toJSON() : [] }) @@ -112,12 +109,12 @@ TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'slider'; + this.config.type = 'slider' const change = this.config.change if (typeof change === 'function') { - item_event_handlers[this.id] = change + itemEventHandlers[this.id] = change } } } -module.exports = TouchBar; +module.exports = TouchBar From d1b3ba39bd6f9e9af2bb6bbfe98b6d57e1e310af Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:57:26 +1100 Subject: [PATCH 10/69] Fix cpp linting --- atom/browser/api/atom_api_window.cc | 3 ++- atom/browser/api/atom_api_window.h | 3 ++- atom/browser/native_window.h | 5 +++-- atom/browser/native_window_observer.h | 4 +++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index ae52b03f079..e031916a0f2 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -282,7 +282,8 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } -void Window::OnTouchBarItemResult(const std::string& item_type, const std::vector& args) { +void Window::OnTouchBarItemResult(const std::string& item_type, + const std::vector& args) { Emit("-touch-bar-interaction", item_type, args); } diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 55347bb54da..7241982f2b7 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -85,7 +85,8 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; - void OnTouchBarItemResult(const std::string& item_type, const std::vector& args) override; + void OnTouchBarItemResult(const std::string& item_type, + const std::vector& args) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 38636fa2a5a..b76a49a061d 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -21,9 +21,9 @@ #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" +#include "native_mate/constructor.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" -#include "native_mate/constructor.h" class SkRegion; @@ -233,7 +233,8 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); - void NotifyTouchBarItemInteraction(const std::string& item_type, const std::vector& args); + void NotifyTouchBarItemInteraction(const std::string& item_type, + const std::vector& args); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index bd9ff21f56c..7fd1b7caebd 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_NATIVE_WINDOW_OBSERVER_H_ #include +#include #include "base/strings/string16.h" #include "ui/base/window_open_disposition.h" @@ -70,7 +71,8 @@ class NativeWindowObserver { virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} - virtual void OnTouchBarItemResult(const std::string& item_type, const std::vector& args) {} + virtual void OnTouchBarItemResult(const std::string& item_type, + const std::vector& args) {} // Called when window message received #if defined(OS_WIN) From 15dcc314d34c3b72b228c4afdeb10864f21166c4 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 2 Dec 2016 23:43:42 +1100 Subject: [PATCH 11/69] Export the TouchBar items as their own props on the electron main export --- lib/browser/api/exports/electron.js | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 3f405952175..0c6ce065b02 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -109,6 +109,42 @@ Object.defineProperties(exports, { return require('../touch-bar') } }, + TouchBarButton: { + enumerable: true, + get: function () { + return require('../touch-bar').Button + } + }, + TouchBarColorPicker: { + enumerable: true, + get: function () { + return require('../touch-bar').ColorPicker + } + }, + TouchBarGroup: { + enumerable: true, + get: function () { + return require('../touch-bar').Group + } + }, + TouchBarLabel: { + enumerable: true, + get: function () { + return require('../touch-bar').Label + } + }, + TouchBarPopOver: { + enumerable: true, + get: function () { + return require('../touch-bar').PopOver + } + }, + TouchBarSlider: { + enumerable: true, + get: function () { + return require('../touch-bar').Slider + } + }, Tray: { enumerable: true, get: function () { From 61949657f0c0591556e63ef10c11bfcce4d9ed45 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 2 Dec 2016 23:58:02 +1100 Subject: [PATCH 12/69] Some docs for touch bar --- docs/api/browser-window.md | 6 ++++++ docs/api/touch-bar.md | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 docs/api/touch-bar.md diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index a8aa5e70c0b..fc1c705b21d 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1266,6 +1266,12 @@ Controls whether to hide cursor when typing. Adds a vibrancy effect to the browser window. Passing `null` or an empty string will remove the vibrancy effect on the window. +#### `win.setTouchBar(touchBar)` _macOS_ + +* `touchBar` TouchBar + +Sets the touchBar layout for the current window. + [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look [vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md new file mode 100644 index 00000000000..68bdaf88a11 --- /dev/null +++ b/docs/api/touch-bar.md @@ -0,0 +1,44 @@ +## Class: TouchBar + +> Create TouchBar layouts for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBar(items)` + +* `items` (TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopOver | TouchBarSlider)[] + +Creates a new touch bar. Note any changes to the TouchBar instance +will not affect the rendered TouchBar. To affect the rendered +TouchBar you **must** use either methods on the TouchBar or methods +on the TouchBar* items + +### Instance Methods + +The `menu` object has the following instance methods: + +#### `touchBar.destroy()` + +Immediately destroys the TouchBar instance and will reset the rendered +touch bar. + +## Examples + +The `TouchBar` class is only available in the main process, it is not currently possible to use in the renderer process **even** through the remote module. + +### Main process + +An example of creating a touch bar in the main process: + +```javascript +const {TouchBar, TouchBarButton} = require('electron') + +const touchBar = new TouchBar([ + new TouchBarButton({ + label: 'Example Button', + click: () => console.log('I was clicked') + }) +]) + +mainWindow.setTouchBar(touchBar) +``` From dd09c91cf29fdd9fc2bf41cf82e3c3a300ac96ba Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 16 Dec 2016 17:24:51 +1100 Subject: [PATCH 13/69] initial work on updating touch bar item config without rerender --- atom/browser/api/atom_api_window.cc | 5 ++ atom/browser/api/atom_api_window.h | 1 + atom/browser/native_window.cc | 3 ++ atom/browser/native_window.h | 1 + atom/browser/native_window_mac.h | 1 + atom/browser/native_window_mac.mm | 76 +++++++++++++++++++++------- default_app/default_app.js | 22 ++++---- default_app/icon.png | Bin 122330 -> 17533 bytes lib/browser/api/browser-window.js | 6 +++ lib/browser/api/touch-bar.js | 10 ++++ 10 files changed, 98 insertions(+), 27 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index e031916a0f2..adafab9173b 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -853,6 +853,10 @@ void Window::SetTouchBar(mate::Arguments* args) { window_->SetTouchBar(args); } +void Window::RefreshTouchBarItem(mate::Arguments* args) { + window_->RefreshTouchBarItem(args); +} + int32_t Window::ID() const { return weak_map_id(); } @@ -975,6 +979,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setVibrancy", &Window::SetVibrancy) .SetMethod("_destroyTouchBar", &Window::DestroyTouchBar) .SetMethod("_setTouchBar", &Window::SetTouchBar) + .SetMethod("_refreshTouchBarItem", &Window::RefreshTouchBarItem) #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 7241982f2b7..2620292b253 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -207,6 +207,7 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); void DestroyTouchBar(); void SetTouchBar(mate::Arguments* args); + void RefreshTouchBarItem(mate::Arguments* args); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 1aead897d99..ab7e1084924 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -347,6 +347,9 @@ void NativeWindow::DestroyTouchBar() { void NativeWindow::SetTouchBar(mate::Arguments* args) { } +void NativeWindow::RefreshTouchBarItem(mate::Arguments* args) { +} + void NativeWindow::FocusOnWebView() { web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index b76a49a061d..23c393e7aee 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -173,6 +173,7 @@ class NativeWindow : public base::SupportsUserData, // Touchbar API virtual void DestroyTouchBar(); virtual void SetTouchBar(mate::Arguments* args); + virtual void RefreshTouchBarItem(mate::Arguments* args); // Webview APIs. virtual void FocusOnWebView(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index b960290c82d..f8ebfec3adc 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -104,6 +104,7 @@ class NativeWindowMac : public NativeWindow, void SetVibrancy(const std::string& type) override; void DestroyTouchBar() override; void SetTouchBar(mate::Arguments* args) override; + void RefreshTouchBarItem(mate::Arguments* args) override; std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index dfd71555ad9..09317221c8a 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -353,6 +353,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; - (void)reloadTouchBar; +- (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)resetTouchBar; - (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; @end @@ -363,6 +364,7 @@ bool ScopedDisableResize::disable_resize_ = false; @implementation AtomNSWindow NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; std::map item_id_map; + std::map item_map; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -405,6 +407,20 @@ bool ScopedDisableResize::disable_resize_ = false; return idents; } +- (void)refreshTouchBarItem:(mate::Arguments*)args { + std::string item_id; + std::string type; + mate::PersistentDictionary dict; + if (args->GetNext(&dict) && dict.Get("type", &type) && dict.Get("id", &item_id)) { + if (item_map.find(item_id) != item_map.end()) { + if (type == "slider") { + NSSliderTouchBarItem* item = (NSSliderTouchBarItem *)item_map[item_id]; + [self updateSlider:item withOpts:dict]; + } + } + } +} + - (void)reloadTouchBar { std::map new_map; item_id_map = new_map; @@ -505,12 +521,16 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; + NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updateButton:customItem withOpts:item withID:id]; +} + +- (nullable NSTouchBarItem *)updateButton:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item withID:(NSString*)id { std::string label; if (item.Get("label", &label)) { NSButton* theButton = [self makeButtonForDict:item withLabel:label]; theButton.tag = [id floatValue]; - NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theButton; std::string customizationLabel; @@ -527,11 +547,15 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; + NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updateLabel:customItem withOpts:item]; +} + +- (nullable NSTouchBarItem*) updateLabel:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item { std::string label; if (item.Get("label", &label)) { NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:label.c_str()]]; - NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theLabel; std::string customizationLabel; @@ -548,8 +572,11 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updateColorPicker:colorPickerItem withOpts:item]; +} + +- (nullable NSTouchBarItem*) updateColorPicker:(NSColorPickerTouchBarItem*)colorPickerItem withOpts:(mate::PersistentDictionary)item { colorPickerItem.target = self; colorPickerItem.action = @selector(colorPickerAction:); @@ -565,8 +592,11 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSSliderTouchBarItem *sliderItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updateSlider:sliderItem withOpts:item]; +} + +- (nullable NSTouchBarItem*) updateSlider:(NSSliderTouchBarItem*)sliderItem withOpts:(mate::PersistentDictionary)item { sliderItem.target = self; sliderItem.action = @selector(sliderAction:); @@ -598,9 +628,11 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSPopoverTouchBarItem *popOverItem = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updatePopOver:popOverItem withOpts:item]; +} +- (nullable NSTouchBarItem*) updatePopOver:(NSPopoverTouchBarItem*)popOverItem withOpts:(mate::PersistentDictionary)item { std::string customizationLabel; if (item.Get("customizationLabel", &customizationLabel)) { popOverItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; @@ -665,27 +697,31 @@ static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; - (nullable NSTouchBarItem *)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + NSTouchBarItem * item = nil; + NSString * id = nil; if ([identifier hasPrefix:ButtonIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - return [self makeButtonForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + item = [self makeButtonForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:LabelIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - return [self makeLabelForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + item = [self makeLabelForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:ColorPickerIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; - return [self makeColorPickerForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + item = [self makeColorPickerForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:SliderIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; - return [self makeSliderForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + item = [self makeSliderForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:PopOverIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; - return [self makePopOverForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + item = [self makePopOverForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:GroupIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; - return [self makeGroupForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + item = [self makeGroupForID:id withIdentifier:identifier]; } - return nil; + item_map.insert(make_pair(std::string([id UTF8String]), item)); + + return item; } - (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { @@ -1688,6 +1724,10 @@ void NativeWindowMac::SetTouchBar(mate::Arguments* args) { } } +void NativeWindowMac::RefreshTouchBarItem(mate::Arguments* args) { + [window_ refreshTouchBarItem:args]; +} + std::vector NativeWindowMac::GetTouchBarItems() { return touch_bar_items_; } diff --git a/default_app/default_app.js b/default_app/default_app.js index 57110e9c90d..42644ac36c4 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -25,6 +25,18 @@ exports.load = (appUrl) => { mainWindow.loadURL(appUrl) mainWindow.focus() + const slider = new (TouchBar.Slider)({ + label: 'Slider 123', + minValue: 50, + maxValue: 1000, + initialValue: 300, + change: (newVal) => { + console.log('Slider was changed', newVal, typeof newVal) + } + }); + + global.slider = slider; + mainWindow.setTouchBar(new TouchBar([ new (TouchBar.Button)({ label: 'Hello World!', @@ -60,15 +72,7 @@ exports.load = (appUrl) => { }) ]) }), - new (TouchBar.Slider)({ - label: 'Slider 123', - minValue: 50, - maxValue: 1000, - initialValue: 300, - change: (newVal) => { - console.log('Slider was changed', newVal, typeof newVal) - } - }) + slider, ])) }) } diff --git a/default_app/icon.png b/default_app/icon.png index ac3a6547d9eccda3c17de4c0d3516b867b86d51b..1e8be4bab538ef93ed6ae35f205f7da69239db9e 100644 GIT binary patch literal 17533 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 literal 122330 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 { item._owner = this; }) +} + +BrowserWindow.prototype._updateTouchBarItem = function (itemID) { + this._refreshTouchBarItem(itemID); } module.exports = BrowserWindow diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 1b18656a697..a008cc7778d 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -44,6 +44,16 @@ class TouchBarItem { } } + updateConfig(newConfig) { + if (!this._owner) { + throw new Error('Cannot call methods on TouchBarItems without assigning to a BrowserWindow'); + } + const dupConfig = Object.assign({}, newConfig); + delete dupConfig.id; + Object.assign(this.config, dupConfig); + this._owner._updateTouchBarItem(this.toJSON()); + } + toJSON () { return this.config } From 28d5c8bbdec6c636b83b23235707496945a29bc6 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 16 Dec 2016 18:01:49 +1100 Subject: [PATCH 14/69] Add updateConfig ability to other items --- atom/browser/native_window_mac.mm | 31 +++++++++++++++++++++++++------ default_app/default_app.js | 19 ++++++++++--------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 09317221c8a..eb2ad60f5d2 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -413,9 +413,18 @@ bool ScopedDisableResize::disable_resize_ = false; mate::PersistentDictionary dict; if (args->GetNext(&dict) && dict.Get("type", &type) && dict.Get("id", &item_id)) { if (item_map.find(item_id) != item_map.end()) { - if (type == "slider") { - NSSliderTouchBarItem* item = (NSSliderTouchBarItem *)item_map[item_id]; - [self updateSlider:item withOpts:dict]; + if (type == "button") { + [self updateButton:(NSCustomTouchBarItem *)item_map[item_id] withOpts:dict withID:[NSString stringWithUTF8String:item_id.c_str()] andCreate:false]; + } else if (type == "label") { + [self updateLabel:(NSCustomTouchBarItem *)item_map[item_id] withOpts:dict]; + } else if (type == "colorpicker") { + [self updateColorPicker:(NSColorPickerTouchBarItem *)item_map[item_id] withOpts:dict]; + } else if (type == "slider") { + [self updateSlider:(NSSliderTouchBarItem *)item_map[item_id] withOpts:dict]; + } else if (type == "popover") { + [self updatePopOver:(NSPopoverTouchBarItem *)item_map[item_id] withOpts:dict]; + } else if (type == "group") { + args->ThrowError("You can not update the config of a group. Update the individual items or replace the group"); } } } @@ -495,6 +504,10 @@ bool ScopedDisableResize::disable_resize_ = false; - (NSButton*)makeButtonForDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; + return [self updateNSButton:theButton forDict:dict withLabel:label]; +} + +- (NSButton*)updateNSButton:(NSButton*)theButton forDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { std::string backgroundColor; if (dict.Get("backgroundColor", &backgroundColor)) { theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; @@ -522,13 +535,19 @@ bool ScopedDisableResize::disable_resize_ = false; if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; - return [self updateButton:customItem withOpts:item withID:id]; + return [self updateButton:customItem withOpts:item withID:id andCreate:true]; } -- (nullable NSTouchBarItem *)updateButton:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item withID:(NSString*)id { +- (nullable NSTouchBarItem *)updateButton:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item withID:(NSString*)id andCreate:(bool)create { std::string label; if (item.Get("label", &label)) { - NSButton* theButton = [self makeButtonForDict:item withLabel:label]; + NSButton* theButton = nil; + if (!create) { + theButton = (NSButton*)customItem.view; + [self updateNSButton:theButton forDict:item withLabel:label]; + } else { + theButton = [self makeButtonForDict:item withLabel:label]; + } theButton.tag = [id floatValue]; customItem.view = theButton; diff --git a/default_app/default_app.js b/default_app/default_app.js index 42644ac36c4..549c6d73114 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -36,17 +36,18 @@ exports.load = (appUrl) => { }); global.slider = slider; + global.button = new (TouchBar.Button)({ + label: 'Hello World!', + // image: '/path/to/image', + backgroundColor: 'FF0000', + labelColor: '0000FF', + click: () => { + console.log('Hello World Clicked') + } + }); mainWindow.setTouchBar(new TouchBar([ - new (TouchBar.Button)({ - label: 'Hello World!', - // image: '/path/to/image', - backgroundColor: 'FF0000', - labelColor: '0000FF', - click: () => { - console.log('Hello World Clicked') - } - }), + button, new (TouchBar.Label)({ label: 'This is a Label' }), From ba3fbc9d1b77b13bfb3de2d8d82cba34cdc6395d Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 16 Dec 2016 19:27:54 +1100 Subject: [PATCH 15/69] Fix Group items --- atom/browser/native_window_mac.mm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index eb2ad60f5d2..fd44588098f 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -693,9 +693,11 @@ bool ScopedDisableResize::disable_resize_ = false; NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; NSMutableArray* identList = [self identifierArrayFromDicts:items]; for (NSUInteger i = 0; i < [identList count]; i++) { - NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; - if (generatedItem) { - [generatedItems addObject:generatedItem]; + if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { + NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; + if (generatedItem) { + [generatedItems addObject:generatedItem]; + } } } NSGroupTouchBarItem *groupItem = [NSGroupTouchBarItem groupItemWithIdentifier:identifier items:generatedItems]; From 2a00bb30c5d0fe576e4fcf7b68058a75a6177829 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 09:20:25 -0800 Subject: [PATCH 16/69] Use new NativeWindowObserver helper --- atom/browser/native_window.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index ab7e1084924..b008739709c 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -576,11 +576,11 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( } void NativeWindow::NotifyTouchBarItemInteraction( - const std::string& type, - const std::vector& args) { - FOR_EACH_OBSERVER(NativeWindowObserver, observers_, - OnTouchBarItemResult(type, args)); - } + const std::string& type, + const std::vector& args) { + for (NativeWindowObserver& observer : observers_) + observer.OnTouchBarItemResult(type, args); +} #if defined(OS_WIN) void NativeWindow::NotifyWindowMessage( From 52905ae9b3c95c62a48db04982859fa2e319e5cd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:30:41 -0800 Subject: [PATCH 17/69] Get compiling against 10.10 SDK --- atom/browser/native_window_mac.mm | 35 ++-- .../ui/cocoa/touch_bar_forward_declarations.h | 169 ++++++++++++++++++ filenames.gypi | 1 + 3 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 atom/browser/ui/cocoa/touch_bar_forward_declarations.h diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index fd44588098f..a9eddb0ca04 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -7,6 +7,7 @@ #include #include +#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" @@ -355,14 +356,14 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)reloadTouchBar; - (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)resetTouchBar; -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; @end @interface AtomNSWindow () @end @implementation AtomNSWindow - NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; + NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; std::map item_id_map; std::map item_map; @@ -376,11 +377,11 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)resetTouchBar { bar_items_ = [[NSMutableArray alloc] init]; - self.touchBar = nil; + // self.touchBar = nil; } -- (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { - NSMutableArray* idents = [[NSMutableArray alloc] init]; +- (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { + NSMutableArray* idents = [[NSMutableArray alloc] init]; for (mate::PersistentDictionary &item : dicts) { std::string type; @@ -434,15 +435,15 @@ bool ScopedDisableResize::disable_resize_ = false; std::map new_map; item_id_map = new_map; bar_items_ = [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; - self.touchBar = nil; + // self.touchBar = nil; } -- (NSTouchBar *)makeTouchBar { +- (NSTouchBar*)makeTouchBar { return [self touchBarFromMutatableArray:bar_items_]; } -- (NSTouchBar *)touchBarFromMutatableArray:(NSMutableArray*)items { - NSTouchBar* bar = [[NSTouchBar alloc] init]; +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { + NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; bar.delegate = self; bar.defaultItemIdentifiers = [items copy]; @@ -534,7 +535,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + NSCustomTouchBarItem *customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updateButton:customItem withOpts:item withID:id andCreate:true]; } @@ -566,7 +567,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + NSCustomTouchBarItem *customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updateLabel:customItem withOpts:item]; } @@ -591,7 +592,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; + NSColorPickerTouchBarItem *colorPickerItem = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updateColorPicker:colorPickerItem withOpts:item]; } @@ -611,7 +612,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSSliderTouchBarItem *sliderItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; + NSSliderTouchBarItem *sliderItem = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updateSlider:sliderItem withOpts:item]; } @@ -647,7 +648,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSPopoverTouchBarItem *popOverItem = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; + NSPopoverTouchBarItem *popOverItem = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updatePopOver:popOverItem withOpts:item]; } @@ -690,8 +691,8 @@ bool ScopedDisableResize::disable_resize_ = false; return nil; } - NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; - NSMutableArray* identList = [self identifierArrayFromDicts:items]; + NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; + NSMutableArray* identList = [self identifierArrayFromDicts:items]; for (NSUInteger i = 0; i < [identList count]; i++) { if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; @@ -700,7 +701,7 @@ bool ScopedDisableResize::disable_resize_ = false; } } } - NSGroupTouchBarItem *groupItem = [NSGroupTouchBarItem groupItemWithIdentifier:identifier items:generatedItems]; + NSGroupTouchBarItem *groupItem = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; std::string customizationLabel; if (item.Get("customizationLabel", &customizationLabel)) { diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h new file mode 100644 index 00000000000..c6fdd1f7913 --- /dev/null +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -0,0 +1,169 @@ +// 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. + +#ifndef ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ +#define ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ + +// Once Chrome no longer supports OSX 10.12.0, this file can be deleted. + +#import + +#if !defined(MAC_OS_X_VERSION_10_12_1) + +#pragma clang assume_nonnull begin + +@class NSTouchBar, NSTouchBarItem; +@protocol NSTouchBarDelegate; + +typedef float NSTouchBarItemPriority; +static const NSTouchBarItemPriority NSTouchBarItemPriorityHigh = 1000; +static const NSTouchBarItemPriority NSTouchBarItemPriorityNormal = 0; +static const NSTouchBarItemPriority NSTouchBarItemPriorityLow = -1000; + +typedef NSString* NSTouchBarItemIdentifier; +typedef NSString* NSTouchBarCustomizationIdentifier; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceSmall = + @"NSTouchBarItemIdentifierFixedSpaceSmall"; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFlexibleSpace = + @"NSTouchBarItemIdentifierFlexibleSpace"; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = + @"NSTouchBarItemIdentifierOtherItemsProxy"; + +@interface NSTouchBar : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder*)aDecoder + NS_DESIGNATED_INITIALIZER; + +@property(copy, nullable) + NSTouchBarCustomizationIdentifier customizationIdentifier; +@property(copy) NSArray* customizationAllowedItemIdentifiers; +@property(copy) NSArray* customizationRequiredItemIdentifiers; +@property(copy) NSArray* defaultItemIdentifiers; +@property(copy, readonly) NSArray* itemIdentifiers; +@property(copy, nullable) NSTouchBarItemIdentifier principalItemIdentifier; +@property(copy) NSSet* templateItems; +@property(nullable, weak) id delegate; + +- (nullable __kindof NSTouchBarItem*)itemForIdentifier: + (NSTouchBarItemIdentifier)identifier; + +@property(readonly, getter=isVisible) BOOL visible; + +@end + +@interface NSTouchBarItem : NSObject + +- (instancetype)initWithIdentifier:(NSTouchBarItemIdentifier)identifier + NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder*)coder + NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +@property(readonly, copy) NSTouchBarItemIdentifier identifier; +@property NSTouchBarItemPriority visibilityPriority; +@property(readonly, nullable) NSView* view; +@property(readonly, nullable) NSViewController* viewController; +@property(readwrite, copy) NSString* customizationLabel; +@property(readonly, getter=isVisible) BOOL visible; + +@end + +@interface NSGroupTouchBarItem : NSTouchBarItem + ++ (NSGroupTouchBarItem*)groupItemWithIdentifier: + (NSTouchBarItemIdentifier)identifier + items:(NSArray*)items; + +@property(strong) NSTouchBar* groupTouchBar; +@property(readwrite, copy, null_resettable) NSString* customizationLabel; + +@end + +@interface NSCustomTouchBarItem : NSTouchBarItem + +@property(readwrite, strong) __kindof NSView* view; +@property(readwrite, strong, nullable) + __kindof NSViewController* viewController; +@property(readwrite, copy, null_resettable) NSString* customizationLabel; + +@end + +@interface NSColorPickerTouchBarItem : NSTouchBarItem + +@property SEL action; +@property(weak) id target; +@property(copy) NSColor *color; + +@end + +@interface NSPopoverTouchBarItem : NSTouchBarItem + +@property BOOL showsCloseButton; +@property(strong) NSImage *collapsedRepresentationImage; +@property(strong) NSString *collapsedRepresentationLabel; +@property(strong) NSTouchBar *popoverTouchBar; + +@end + +@interface NSSliderTouchBarItem : NSTouchBarItem + +@property SEL action; +@property(weak) id target; +@property(copy) NSString *label; +@property(strong) NSSlider *slider; + +@end + +@interface NSWindow (TouchBarSDK) + +@property(strong, readonly) NSTouchBar* touchBar; + +@end + +@interface NSButton (TouchBarSDK) + +@property(copy) NSColor *bezelColor; ++ (instancetype)buttonWithTitle:(NSString *)title + target:(id)target + action:(SEL)action; + +@end + +@interface NSTextField (TouchBarSDK) + ++ (instancetype)labelWithString:(NSString *)stringValue; + +@end + +@protocol NSTouchBarDelegate + +@optional +- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier; +@end + +#pragma clang assume_nonnull end + +#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12_1 + +// When compiling against the 10.12.1 SDK or later, just provide forward +// declarations to suppress the partial availability warnings. + +@class NSCustomTouchBarItem; +@class NSGroupTouchBarItem; +@class NSTouchBar; +@protocol NSTouchBarDelegate; +@class NSTouchBarItem; + +@interface NSWindow (TouchBarSDK) +@property(strong, readonly) NSTouchBar* touchBar; +@end + +#endif // MAC_OS_X_VERSION_10_12_1 + +#endif // ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ diff --git a/filenames.gypi b/filenames.gypi index 0e4ad206267..a1758e2acff 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -281,6 +281,7 @@ 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', + 'atom/browser/ui/cocoa/touch_bar_forward_declarations.h', 'atom/browser/ui/drag_util_mac.mm', 'atom/browser/ui/drag_util_views.cc', 'atom/browser/ui/drag_util.h', From 9272582bd65853d4b27cde882b39f654c45b0136 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:38:57 -0800 Subject: [PATCH 18/69] Revert default_app changes --- default_app/default_app.js | 53 +------------------------------------ default_app/icon.png | Bin 17533 -> 122330 bytes 2 files changed, 1 insertion(+), 52 deletions(-) diff --git a/default_app/default_app.js b/default_app/default_app.js index 549c6d73114..bfb97a9ab08 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -1,4 +1,4 @@ -const {app, BrowserWindow, TouchBar} = require('electron') +const {app, BrowserWindow} = require('electron') const path = require('path') let mainWindow = null @@ -24,56 +24,5 @@ exports.load = (appUrl) => { mainWindow = new BrowserWindow(options) mainWindow.loadURL(appUrl) mainWindow.focus() - - const slider = new (TouchBar.Slider)({ - label: 'Slider 123', - minValue: 50, - maxValue: 1000, - initialValue: 300, - change: (newVal) => { - console.log('Slider was changed', newVal, typeof newVal) - } - }); - - global.slider = slider; - global.button = new (TouchBar.Button)({ - label: 'Hello World!', - // image: '/path/to/image', - backgroundColor: 'FF0000', - labelColor: '0000FF', - click: () => { - console.log('Hello World Clicked') - } - }); - - mainWindow.setTouchBar(new TouchBar([ - button, - new (TouchBar.Label)({ - label: 'This is a Label' - }), - new (TouchBar.ColorPicker)({ - change: (newColor) => { - console.log('Color was changed', newColor) - } - }), - new (TouchBar.PopOver)({ - // image: '/path/to/image', - label: 'foo', - showCloseButton: true, - touchBar: new TouchBar([ - new (TouchBar.Group)({ - items: new TouchBar( - [1, 2, 3].map((i) => new (TouchBar.Button)({ - label: `Button ${i}`, - click: () => { - console.log(`Button ${i} (group) Clicked`) - } - })) - ) - }) - ]) - }), - slider, - ])) }) } diff --git a/default_app/icon.png b/default_app/icon.png index 1e8be4bab538ef93ed6ae35f205f7da69239db9e..ac3a6547d9eccda3c17de4c0d3516b867b86d51b 100644 GIT binary patch literal 122330 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 From 9bdca3bbfb16a99edc92e99e5ed4e453a4c07fa4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:40:00 -0800 Subject: [PATCH 19/69] Remove linter warnings --- lib/browser/api/browser-window.js | 6 ++++-- lib/browser/api/touch-bar.js | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 9f5746980f6..31eac937a6e 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -213,11 +213,13 @@ BrowserWindow.prototype.setTouchBar = function (touchBar) { this._setTouchBar(touchBar.toJSON()) } touchBar._owner = this - touchBar.items.forEach((item) => { item._owner = this; }) + touchBar.items.forEach((item) => { + item._owner = this + }) } BrowserWindow.prototype._updateTouchBarItem = function (itemID) { - this._refreshTouchBarItem(itemID); + this._refreshTouchBarItem(itemID) } module.exports = BrowserWindow diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index a008cc7778d..07b2631a77d 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -44,14 +44,14 @@ class TouchBarItem { } } - updateConfig(newConfig) { + updateConfig (newConfig) { if (!this._owner) { - throw new Error('Cannot call methods on TouchBarItems without assigning to a BrowserWindow'); + throw new Error('Cannot call methods on TouchBarItems without assigning to a BrowserWindow') } - const dupConfig = Object.assign({}, newConfig); - delete dupConfig.id; - Object.assign(this.config, dupConfig); - this._owner._updateTouchBarItem(this.toJSON()); + const dupConfig = Object.assign({}, newConfig) + delete dupConfig.id + Object.assign(this.config, dupConfig) + this._owner._updateTouchBarItem(this.toJSON()) } toJSON () { From d4d3c78701f00ceec05ee14ab1145ffd008e53ad Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:43:44 -0800 Subject: [PATCH 20/69] Ignore lint warnings in Objective-C header --- script/cpplint.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/cpplint.py b/script/cpplint.py index 7ec3537e8da..9d9281fb70a 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -9,6 +9,8 @@ from lib.util import execute IGNORE_FILES = [ os.path.join('atom', 'browser', 'mac', 'atom_application.h'), os.path.join('atom', 'browser', 'mac', 'atom_application_delegate.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', + 'touch_bar_forward_declarations.h'), os.path.join('atom', 'browser', 'resources', 'win', 'resource.h'), os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_menu_controller.h'), os.path.join('atom', 'common', 'api', 'api_messages.h'), From 69638a399cfd5b6fbb429d1774a87f3505f6a18e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:49:45 -0800 Subject: [PATCH 21/69] Make touchBar readwrite/nullable --- atom/browser/native_window_mac.mm | 4 ++-- atom/browser/ui/cocoa/touch_bar_forward_declarations.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index a9eddb0ca04..6c6847b269f 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -377,7 +377,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)resetTouchBar { bar_items_ = [[NSMutableArray alloc] init]; - // self.touchBar = nil; + self.touchBar = nil; } - (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { @@ -435,7 +435,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::map new_map; item_id_map = new_map; bar_items_ = [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; - // self.touchBar = nil; + self.touchBar = nil; } - (NSTouchBar*)makeTouchBar { diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index c6fdd1f7913..e9a6e5525b6 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -121,7 +121,7 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @interface NSWindow (TouchBarSDK) -@property(strong, readonly) NSTouchBar* touchBar; +@property(strong, readwrite, nullable) NSTouchBar* touchBar; @end From eff49ad19ca4e0e616f8fb35a940fae4141bc45d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 11:23:33 -0800 Subject: [PATCH 22/69] Return early when touchBar is null --- lib/browser/api/browser-window.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 31eac937a6e..ecfd9195f43 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -205,13 +205,16 @@ Object.assign(BrowserWindow.prototype, { // TouchBar API BrowserWindow.prototype.setTouchBar = function (touchBar) { - if (touchBar === null || typeof touchBar === 'undefined') { + if (touchBar == null) { this._destroyTouchBar() - } else if (Array.isArray(touchBar)) { - this._setTouchBar((new TouchBar(touchBar)).toJSON()) - } else { - this._setTouchBar(touchBar.toJSON()) + return } + + if (Array.isArray(touchBar)) { + touchBar = new TouchBar(touchBar) + } + + this._setTouchBar(touchBar.toJSON()) touchBar._owner = this touchBar.items.forEach((item) => { item._owner = this From b16d649819359f3abd07f462eeeda7a023be3e60 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 12:05:44 -0800 Subject: [PATCH 23/69] Use skia color helpers --- atom/browser/native_window_mac.mm | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 6c6847b269f..f8001100c2e 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -459,10 +459,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)colorPickerAction:(id)sender { NSString* item_id = ((NSColorPickerTouchBarItem *)sender).identifier; NSColor* color = ((NSColorPickerTouchBarItem *)sender).color; - NSString* colorHexString = [NSString stringWithFormat:@"#%02X%02X%02X", - (int) (color.redComponent * 0xFF), (int) (color.greenComponent * 0xFF), - (int) (color.blueComponent * 0xFF)]; - shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), std::string([colorHexString UTF8String]) }); + std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); + shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), hex_color }); } - (void)sliderAction:(id)sender { @@ -481,25 +479,8 @@ bool ScopedDisableResize::disable_resize_ = false; } - (NSColor*)colorFromHexColorString:(NSString*)inColorString { - NSColor* result = nil; - unsigned colorCode = 0; - unsigned char redByte, greenByte, blueByte; - - if (nil != inColorString) - { - NSScanner* scanner = [NSScanner scannerWithString:inColorString]; - (void) [scanner scanHexInt:&colorCode]; // ignore error - } - redByte = (unsigned char)(colorCode >> 16); - greenByte = (unsigned char)(colorCode >> 8); - blueByte = (unsigned char)(colorCode); // masks off high bits - - result = [NSColor - colorWithCalibratedRed:(CGFloat)redByte / 0xff - green:(CGFloat)greenByte / 0xff - blue:(CGFloat)blueByte / 0xff - alpha:1.0]; - return result; + SkColor color = atom::ParseHexColor([inColorString UTF8String]); + return skia::SkColorToCalibratedNSColor(color); } - (NSButton*)makeButtonForDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { From 1b5149ae7a9ed5965cbfe78f1b2fbfa74f86059e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 16:07:19 -0800 Subject: [PATCH 24/69] Few memory and scope tweaks --- atom/browser/native_window_mac.mm | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index f8001100c2e..4f720ef2a92 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -363,7 +363,6 @@ bool ScopedDisableResize::disable_resize_ = false; @end @implementation AtomNSWindow - NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; std::map item_id_map; std::map item_map; @@ -376,7 +375,6 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)resetTouchBar { - bar_items_ = [[NSMutableArray alloc] init]; self.touchBar = nil; } @@ -432,27 +430,25 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)reloadTouchBar { - std::map new_map; - item_id_map = new_map; - bar_items_ = [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; + item_id_map.clear(); self.touchBar = nil; } - (NSTouchBar*)makeTouchBar { - return [self touchBarFromMutatableArray:bar_items_]; + NSMutableArray* item_identifiers = + [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; + return [self touchBarFromMutatableArray:item_identifiers]; } - (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; bar.delegate = self; - - bar.defaultItemIdentifiers = [items copy]; - + bar.defaultItemIdentifiers = items; return bar; } - (void)buttonAction:(id)sender { - NSString* item_id = [NSString stringWithFormat:@"com.electron.tb.button.%d", (int)((NSButton *)sender).tag]; + NSString* item_id = [NSString stringWithFormat:@"%@.%d", ButtonIdentifier, (int)((NSButton *)sender).tag]; shell_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); } @@ -469,9 +465,7 @@ bool ScopedDisableResize::disable_resize_ = false; } - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { - NSString *idCopy = [identifier copy]; - idCopy = [identifier substringFromIndex:[prefix length]]; - return idCopy; + return [identifier substringFromIndex:[prefix length]]; } - (bool)hasTBDict:(std::string)id { @@ -497,7 +491,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string labelColor; if (dict.Get("labelColor", &labelColor)) { - NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]]; + NSMutableAttributedString *attrTitle = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]] autorelease]; NSUInteger len = [attrTitle length]; NSRange range = NSMakeRange(0, len); [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; From b632cdd37d3e119b7be14379dc8803b3547eb666 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:29:23 -0800 Subject: [PATCH 25/69] Extract helper class to encapsulate touch bar items --- atom/browser/native_window_mac.mm | 356 +---------------------- atom/browser/ui/cocoa/atom_touch_bar.h | 63 +++++ atom/browser/ui/cocoa/atom_touch_bar.mm | 362 ++++++++++++++++++++++++ filenames.gypi | 2 + 4 files changed, 439 insertions(+), 344 deletions(-) create mode 100644 atom/browser/ui/cocoa/atom_touch_bar.h create mode 100644 atom/browser/ui/cocoa/atom_touch_bar.mm diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 4f720ef2a92..9ef09f625df 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -7,7 +7,7 @@ #include #include -#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" +#include "atom/browser/ui/cocoa/atom_touch_bar.h" #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" @@ -356,18 +356,17 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)reloadTouchBar; - (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)resetTouchBar; -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; @end @interface AtomNSWindow () @end @implementation AtomNSWindow - std::map item_id_map; - std::map item_map; + base::scoped_nsobject touch_bar_; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; + touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell]); } - (void)setEnableLargerThanScreen:(bool)enable { @@ -378,357 +377,26 @@ bool ScopedDisableResize::disable_resize_ = false; self.touchBar = nil; } -- (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { - NSMutableArray* idents = [[NSMutableArray alloc] init]; - - for (mate::PersistentDictionary &item : dicts) { - std::string type; - std::string item_id; - if (item.Get("type", &type) && item.Get("id", &item_id)) { - item_id_map.insert(make_pair(item_id, item)); - if (type == "button") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "label") { - [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "colorpicker") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "slider") { - [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "popover") { - [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "group") { - [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } - } - } - [idents addObject:NSTouchBarItemIdentifierOtherItemsProxy]; - - return idents; -} - -- (void)refreshTouchBarItem:(mate::Arguments*)args { - std::string item_id; - std::string type; - mate::PersistentDictionary dict; - if (args->GetNext(&dict) && dict.Get("type", &type) && dict.Get("id", &item_id)) { - if (item_map.find(item_id) != item_map.end()) { - if (type == "button") { - [self updateButton:(NSCustomTouchBarItem *)item_map[item_id] withOpts:dict withID:[NSString stringWithUTF8String:item_id.c_str()] andCreate:false]; - } else if (type == "label") { - [self updateLabel:(NSCustomTouchBarItem *)item_map[item_id] withOpts:dict]; - } else if (type == "colorpicker") { - [self updateColorPicker:(NSColorPickerTouchBarItem *)item_map[item_id] withOpts:dict]; - } else if (type == "slider") { - [self updateSlider:(NSSliderTouchBarItem *)item_map[item_id] withOpts:dict]; - } else if (type == "popover") { - [self updatePopOver:(NSPopoverTouchBarItem *)item_map[item_id] withOpts:dict]; - } else if (type == "group") { - args->ThrowError("You can not update the config of a group. Update the individual items or replace the group"); - } - } - } -} - - (void)reloadTouchBar { - item_id_map.clear(); + [touch_bar_ clear]; self.touchBar = nil; } +- (void)refreshTouchBarItem:(mate::Arguments*)args { + [touch_bar_ refreshTouchBarItem:args]; +} + - (NSTouchBar*)makeTouchBar { - NSMutableArray* item_identifiers = - [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; - return [self touchBarFromMutatableArray:item_identifiers]; + return [touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; } -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { - NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; - bar.delegate = self; - bar.defaultItemIdentifiers = items; - return bar; +- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + return [touch_bar_ makeItemForIdentifier:identifier]; } -- (void)buttonAction:(id)sender { - NSString* item_id = [NSString stringWithFormat:@"%@.%d", ButtonIdentifier, (int)((NSButton *)sender).tag]; - shell_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); -} - -- (void)colorPickerAction:(id)sender { - NSString* item_id = ((NSColorPickerTouchBarItem *)sender).identifier; - NSColor* color = ((NSColorPickerTouchBarItem *)sender).color; - std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); - shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), hex_color }); -} - -- (void)sliderAction:(id)sender { - NSString* item_id = ((NSSliderTouchBarItem *)sender).identifier; - shell_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem *)sender).slider intValue]) }); -} - -- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { - return [identifier substringFromIndex:[prefix length]]; -} - -- (bool)hasTBDict:(std::string)id { - return item_id_map.find(id) != item_id_map.end(); -} - -- (NSColor*)colorFromHexColorString:(NSString*)inColorString { - SkColor color = atom::ParseHexColor([inColorString UTF8String]); - return skia::SkColorToCalibratedNSColor(color); -} - -- (NSButton*)makeButtonForDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { - NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; - - return [self updateNSButton:theButton forDict:dict withLabel:label]; -} - -- (NSButton*)updateNSButton:(NSButton*)theButton forDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { - std::string backgroundColor; - if (dict.Get("backgroundColor", &backgroundColor)) { - theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; - } - - std::string labelColor; - if (dict.Get("labelColor", &labelColor)) { - NSMutableAttributedString *attrTitle = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]] autorelease]; - NSUInteger len = [attrTitle length]; - NSRange range = NSMakeRange(0, len); - [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; - [attrTitle fixAttributesInRange:range]; - [theButton setAttributedTitle:attrTitle]; - } - - gfx::Image image; - if (dict.Get("image", &image)) { - theButton.image = image.AsNSImage(); - } - return theButton; -} - -- (nullable NSTouchBarItem *)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSCustomTouchBarItem *customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updateButton:customItem withOpts:item withID:id andCreate:true]; -} - -- (nullable NSTouchBarItem *)updateButton:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item withID:(NSString*)id andCreate:(bool)create { - std::string label; - if (item.Get("label", &label)) { - NSButton* theButton = nil; - if (!create) { - theButton = (NSButton*)customItem.view; - [self updateNSButton:theButton forDict:item withLabel:label]; - } else { - theButton = [self makeButtonForDict:item withLabel:label]; - } - theButton.tag = [id floatValue]; - - customItem.view = theButton; - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - return customItem; - } - return nil; -} - -- (nullable NSTouchBarItem*) makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSCustomTouchBarItem *customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updateLabel:customItem withOpts:item]; -} - -- (nullable NSTouchBarItem*) updateLabel:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item { - std::string label; - if (item.Get("label", &label)) { - NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:label.c_str()]]; - - customItem.view = theLabel; - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - return customItem; - } - return nil; -} - -- (nullable NSTouchBarItem*) makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSColorPickerTouchBarItem *colorPickerItem = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updateColorPicker:colorPickerItem withOpts:item]; -} - -- (nullable NSTouchBarItem*) updateColorPicker:(NSColorPickerTouchBarItem*)colorPickerItem withOpts:(mate::PersistentDictionary)item { - colorPickerItem.target = self; - colorPickerItem.action = @selector(colorPickerAction:); - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - colorPickerItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - return colorPickerItem; -} - -- (nullable NSTouchBarItem*) makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSSliderTouchBarItem *sliderItem = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updateSlider:sliderItem withOpts:item]; -} - -- (nullable NSTouchBarItem*) updateSlider:(NSSliderTouchBarItem*)sliderItem withOpts:(mate::PersistentDictionary)item { - sliderItem.target = self; - sliderItem.action = @selector(sliderAction:); - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - sliderItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - std::string label; - if (item.Get("label", &label)) { - sliderItem.label = [NSString stringWithUTF8String:label.c_str()]; - } - - int maxValue = 100; - int minValue = 0; - int initialValue = 50; - item.Get("minValue", &minValue); - item.Get("maxValue", &maxValue); - item.Get("initialValue", &initialValue); - - sliderItem.slider.minValue = minValue; - sliderItem.slider.maxValue = maxValue; - sliderItem.slider.doubleValue = initialValue; - - return sliderItem; -} - -- (nullable NSTouchBarItem*) makePopOverForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSPopoverTouchBarItem *popOverItem = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updatePopOver:popOverItem withOpts:item]; -} - -- (nullable NSTouchBarItem*) updatePopOver:(NSPopoverTouchBarItem*)popOverItem withOpts:(mate::PersistentDictionary)item { - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - popOverItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - std::string label; - gfx::Image image; - if (item.Get("label", &label)) { - popOverItem.collapsedRepresentationLabel = [NSString stringWithUTF8String:label.c_str()]; - } else if (item.Get("image", &image)) { - popOverItem.collapsedRepresentationImage = image.AsNSImage(); - } - - bool showCloseButton; - if (item.Get("showCloseButton", &showCloseButton)) { - popOverItem.showsCloseButton = showCloseButton; - } - - std::vector touchBar; - if (item.Get("touchBar", &touchBar)) { - popOverItem.popoverTouchBar = [self touchBarFromMutatableArray:[self identifierArrayFromDicts:touchBar]]; - } else { - return nil; - } - - return popOverItem; -} - -- (nullable NSTouchBarItem*) makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - - std::vector items; - if (!item.Get("items", &items)) { - return nil; - } - - NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; - NSMutableArray* identList = [self identifierArrayFromDicts:items]; - for (NSUInteger i = 0; i < [identList count]; i++) { - if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { - NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; - if (generatedItem) { - [generatedItems addObject:generatedItem]; - } - } - } - NSGroupTouchBarItem *groupItem = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - groupItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - return groupItem; -} - -static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; -static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; -static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.tb.group."; -static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; -static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; -static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; - -- (nullable NSTouchBarItem *)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - NSTouchBarItem * item = nil; - NSString * id = nil; - if ([identifier hasPrefix:ButtonIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - item = [self makeButtonForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:LabelIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - item = [self makeLabelForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:ColorPickerIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; - item = [self makeColorPickerForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:SliderIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; - item = [self makeSliderForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:PopOverIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; - item = [self makePopOverForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:GroupIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; - item = [self makeGroupForID:id withIdentifier:identifier]; - } - - item_map.insert(make_pair(std::string([id UTF8String]), item)); - - return item; -} - -- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - return [self makeItemForIdentifier:identifier]; -} - - // NSWindow overrides. -- (void)swipeWithEvent:(NSEvent *)event { +- (void)swipeWithEvent:(NSEvent*)event { if (event.deltaY == 1.0) { shell_->NotifyWindowSwipe("up"); } else if (event.deltaX == -1.0) { diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h new file mode 100644 index 00000000000..2bd5a736ec1 --- /dev/null +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -0,0 +1,63 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ +#define ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ + +#import + +#include +#include +#include + +#include "atom/browser/native_window.h" +#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" +#include "native_mate/constructor.h" +#include "native_mate/persistent_dictionary.h" + +@interface AtomTouchBar : NSObject { + @protected + std::map item_id_map; + std::map item_map; + id delegate_; + atom::NativeWindow* window_; +} + +- (id)initWithDelegate:(id)delegate + window:(atom::NativeWindow*)window; + +- (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options; +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; +- (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts; +- (void)refreshTouchBarItem:(mate::Arguments*)args; +- (void)clear; + +- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; +- (bool)hasTBDict:(const std::string&)id; +- (NSColor*)colorFromHexColorString:(const std::string&)colorString; + +// Selector actions +- (void)buttonAction:(id)sender; +- (void)colorPickerAction:(id)sender; +- (void)sliderAction:(id)sender; + +// Helpers to create touch bar items +- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier; +- (NSTouchBarItem*)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makePopoverForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier; + +// Helpers to update touch bar items +- (void)updateButton:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; +- (void)updateLabel:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withOptions:(const mate::PersistentDictionary)options; +- (void)updateSlider:(NSSliderTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; +- (void)updatePopover:(NSPopoverTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; + +@end + +#endif // ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm new file mode 100644 index 00000000000..ac5a907d7a2 --- /dev/null +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -0,0 +1,362 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#import "atom/browser/ui/cocoa/atom_touch_bar.h" + +#include "atom/common/color_util.h" +#include "atom/common/native_mate_converters/image_converter.h" +#include "skia/ext/skia_utils_mac.h" +#include "ui/gfx/image/image.h" + +@implementation AtomTouchBar + +static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.touchbar.button."; +static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.touchbar.colorpicker."; +static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.touchbar.group."; +static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.touchbar.label."; +static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.touchbar.popover."; +static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; + +- (id)initWithDelegate:(id)delegate + window:(atom::NativeWindow*)window { + if ((self = [super init])) { + delegate_ = delegate; + window_ = window; + } + return self; +} + +- (void)clear { + item_id_map.clear(); +} + +- (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options { + NSMutableArray* identifiers = [self identifierArrayFromDicts:item_options]; + return [self touchBarFromMutatableArray:identifiers]; +} + +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { + NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; + bar.delegate = delegate_; + bar.defaultItemIdentifiers = items; + return bar; +} + +- (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts { + NSMutableArray* idents = [[NSMutableArray alloc] init]; + + for (const auto& item : dicts) { + std::string type; + std::string item_id; + if (item.Get("type", &type) && item.Get("id", &item_id)) { + item_id_map.insert(make_pair(item_id, item)); + if (type == "button") { + [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "label") { + [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "colorpicker") { + [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "slider") { + [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "popover") { + [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "group") { + [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } + } + } + [idents addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + + return idents; +} + +- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + NSTouchBarItem* item = nil; + NSString* id = nil; + if ([identifier hasPrefix:ButtonIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + item = [self makeButtonForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:LabelIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + item = [self makeLabelForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:ColorPickerIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + item = [self makeColorPickerForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:SliderIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + item = [self makeSliderForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:PopOverIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + item = [self makePopoverForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:GroupIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + item = [self makeGroupForID:id withIdentifier:identifier]; + } + + item_map.insert(make_pair(std::string([id UTF8String]), item)); + + return item; +} + + +- (void)refreshTouchBarItem:(mate::Arguments*)args { + std::string item_id; + std::string type; + mate::PersistentDictionary options; + if (!args->GetNext(&options)) return; + if (!options.Get("type", &type)) return; + if (!options.Get("id", &item_id)) return; + if (item_map.find(item_id) == item_map.end()) return; + + if (type == "button") { + [self updateButton:(NSCustomTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "label") { + [self updateLabel:(NSCustomTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "colorpicker") { + [self updateColorPicker:(NSColorPickerTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "slider") { + [self updateSlider:(NSSliderTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "popover") { + [self updatePopover:(NSPopoverTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "group") { + args->ThrowError("You can not update the config of a group. Update the individual items or replace the group"); + } +} + +- (void)buttonAction:(id)sender { + NSString* item_id = [NSString stringWithFormat:@"%@.%d", ButtonIdentifier, (int)((NSButton*)sender).tag]; + window_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); +} + +- (void)colorPickerAction:(id)sender { + NSString* item_id = ((NSColorPickerTouchBarItem*)sender).identifier; + NSColor* color = ((NSColorPickerTouchBarItem*)sender).color; + std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); + window_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), hex_color }); +} + +- (void)sliderAction:(id)sender { + NSString* item_id = ((NSSliderTouchBarItem*)sender).identifier; + window_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem*)sender).slider intValue]) }); +} + +- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { + return [identifier substringFromIndex:[prefix length]]; +} + +- (bool)hasTBDict:(const std::string&)id { + return item_id_map.find(id) != item_id_map.end(); +} + +- (NSColor*)colorFromHexColorString:(const std::string&)colorString { + SkColor color = atom::ParseHexColor(colorString); + return skia::SkColorToCalibratedNSColor(color); +} + +- (NSTouchBarItem*)makeButtonForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary options = item_id_map[s_id]; + NSCustomTouchBarItem* item = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; + NSButton* button = [NSButton buttonWithTitle:@"" + target:self + action:@selector(buttonAction:)]; + button.tag = [id floatValue]; + item.view = button; + [self updateButton:item withOptions:options]; + return item; +} + +- (void)updateButton:(NSCustomTouchBarItem*)item + withOptions:(const mate::PersistentDictionary&)options { + NSButton* button = (NSButton*)item.view; + + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } + + std::string backgroundColor; + if (options.Get("backgroundColor", &backgroundColor)) { + button.bezelColor = [self colorFromHexColorString:backgroundColor]; + } + + std::string label; + if (options.Get("label", &label)) { + button.title = [NSString stringWithUTF8String:label.data()]; + } + + std::string labelColor; + if (!label.empty() && options.Get("labelColor", &labelColor)) { + NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.data()]] autorelease]; + NSRange range = NSMakeRange(0, [attrTitle length]); + [attrTitle addAttribute:NSForegroundColorAttributeName + value:[self colorFromHexColorString:labelColor] + range:range]; + [attrTitle fixAttributesInRange:range]; + button.attributedTitle = attrTitle; + } + + gfx::Image image; + if (options.Get("image", &image)) { + button.image = image.AsNSImage(); + } +} + +- (NSTouchBarItem*)makeLabelForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary item = item_id_map[s_id]; + NSCustomTouchBarItem* customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; + customItem.view = [NSTextField labelWithString:@""]; + [self updateLabel:customItem withOptions:item]; + + return customItem; +} + +- (void)updateLabel:(NSCustomTouchBarItem*)item + withOptions:(const mate::PersistentDictionary&)options { + std::string label; + options.Get("label", &label); + NSTextField* text_field = (NSTextField*)item.view; + text_field.stringValue = [NSString stringWithUTF8String:label.data()]; + + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } +} + +- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary options = item_id_map[s_id]; + NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; + item.target = self; + item.action = @selector(colorPickerAction:); + [self updateColorPicker:item withOptions:options]; + return item; +} + +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item + withOptions:(const mate::PersistentDictionary)options { + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } +} + +- (NSTouchBarItem*)makeSliderForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary options = item_id_map[s_id]; + NSSliderTouchBarItem* item = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; + item.target = self; + item.action = @selector(sliderAction:); + [self updateSlider:item withOptions:options]; + return item; +} + +- (void)updateSlider:(NSSliderTouchBarItem*)item + withOptions:(const mate::PersistentDictionary&)options { + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } + + std::string label; + options.Get("label", &label); + item.label = [NSString stringWithUTF8String:label.data()]; + + int maxValue = 100; + int minValue = 0; + int initialValue = 50; + options.Get("minValue", &minValue); + options.Get("maxValue", &maxValue); + options.Get("initialValue", &initialValue); + + item.slider.minValue = minValue; + item.slider.maxValue = maxValue; + item.slider.doubleValue = initialValue; +} + +- (NSTouchBarItem*)makePopoverForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary options = item_id_map[s_id]; + NSPopoverTouchBarItem* item = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; + [self updatePopover:item withOptions:options]; + return item; +} + +- (void)updatePopover:(NSPopoverTouchBarItem*)item + withOptions:(const mate::PersistentDictionary&)options { + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } + + std::string label; + gfx::Image image; + if (options.Get("label", &label)) { + item.collapsedRepresentationLabel = [NSString stringWithUTF8String:label.data()]; + } else if (options.Get("image", &image)) { + item.collapsedRepresentationImage = image.AsNSImage(); + } + + bool showCloseButton; + if (options.Get("showCloseButton", &showCloseButton)) { + item.showsCloseButton = showCloseButton; + } + + std::vector touchBar; + if (options.Get("touchBar", &touchBar)) { + item.popoverTouchBar = [self touchBarFromMutatableArray:[self identifierArrayFromDicts:touchBar]]; + } +} + +- (NSTouchBarItem*)makeGroupForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary options = item_id_map[s_id]; + + std::vector items; + if (!options.Get("items", &items)) return nil; + + NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; + NSMutableArray* identList = [self identifierArrayFromDicts:items]; + for (NSUInteger i = 0; i < [identList count]; i++) { + if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { + NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; + if (generatedItem) { + [generatedItems addObject:generatedItem]; + } + } + } + + NSGroupTouchBarItem* item = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } + return item; +} + +@end diff --git a/filenames.gypi b/filenames.gypi index a1758e2acff..6ac732c24a3 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -281,6 +281,8 @@ 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', + 'atom/browser/ui/cocoa/atom_touch_bar.h', + 'atom/browser/ui/cocoa/atom_touch_bar.mm', 'atom/browser/ui/cocoa/touch_bar_forward_declarations.h', 'atom/browser/ui/drag_util_mac.mm', 'atom/browser/ui/drag_util_views.cc', From 48515d9eccbcb9d9c0b765a76e5af021f41be187 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:33:56 -0800 Subject: [PATCH 26/69] Ignore objective-c header lint --- atom/browser/native_window_mac.mm | 3 +-- script/cpplint.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 9ef09f625df..c4f127459d1 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -11,7 +11,6 @@ #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" -#include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/options_switches.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_cftyperef.h" @@ -396,7 +395,7 @@ bool ScopedDisableResize::disable_resize_ = false; // NSWindow overrides. -- (void)swipeWithEvent:(NSEvent*)event { +- (void)swipeWithEvent:(NSEvent *)event { if (event.deltaY == 1.0) { shell_->NotifyWindowSwipe("up"); } else if (event.deltaX == -1.0) { diff --git a/script/cpplint.py b/script/cpplint.py index 9d9281fb70a..6c715522f8c 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -9,10 +9,11 @@ from lib.util import execute IGNORE_FILES = [ os.path.join('atom', 'browser', 'mac', 'atom_application.h'), os.path.join('atom', 'browser', 'mac', 'atom_application_delegate.h'), - os.path.join('atom', 'browser', 'ui', 'cocoa', - 'touch_bar_forward_declarations.h'), os.path.join('atom', 'browser', 'resources', 'win', 'resource.h'), os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_menu_controller.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_touch_bar.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', + 'touch_bar_forward_declarations.h'), os.path.join('atom', 'common', 'api', 'api_messages.h'), os.path.join('atom', 'common', 'common_message_generator.cc'), os.path.join('atom', 'common', 'common_message_generator.h'), From cf7cd1f32b401175123802791185f5b392bf7b21 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:39:07 -0800 Subject: [PATCH 27/69] hasTBDict -> hasItemWithID --- atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 2bd5a736ec1..e15af0946a8 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -34,7 +34,7 @@ - (void)clear; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; -- (bool)hasTBDict:(const std::string&)id; +- (bool)hasItemWithID:(const std::string&)id; - (NSColor*)colorFromHexColorString:(const std::string&)colorString; // Selector actions diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index ac5a907d7a2..6fb98639f74 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -150,7 +150,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return [identifier substringFromIndex:[prefix length]]; } -- (bool)hasTBDict:(const std::string&)id { +- (bool)hasItemWithID:(const std::string&)id { return item_id_map.find(id) != item_id_map.end(); } @@ -162,7 +162,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; NSCustomTouchBarItem* item = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -214,7 +214,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; NSCustomTouchBarItem* customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -240,7 +240,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -261,7 +261,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; NSSliderTouchBarItem* item = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -297,7 +297,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makePopoverForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; NSPopoverTouchBarItem* item = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -334,7 +334,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; std::vector items; From 8500538793a6d6a10004f3e792b55bafa2bcdba4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:43:57 -0800 Subject: [PATCH 28/69] Use base::SysUTF8ToNSString helper --- atom/browser/ui/cocoa/atom_touch_bar.mm | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 6fb98639f74..34d14161214 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -6,6 +6,7 @@ #include "atom/common/color_util.h" #include "atom/common/native_mate_converters/image_converter.h" +#include "base/strings/sys_string_conversions.h" #include "skia/ext/skia_utils_mac.h" #include "ui/gfx/image/image.h" @@ -52,17 +53,17 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (item.Get("type", &type) && item.Get("id", &item_id)) { item_id_map.insert(make_pair(item_id, item)); if (type == "button") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "label") { - [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "colorpicker") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "slider") { - [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "popover") { - [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "group") { - [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; } } } @@ -181,7 +182,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string backgroundColor; @@ -191,12 +192,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string label; if (options.Get("label", &label)) { - button.title = [NSString stringWithUTF8String:label.data()]; + button.title = base::SysUTF8ToNSString(label); } std::string labelColor; if (!label.empty() && options.Get("labelColor", &labelColor)) { - NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.data()]] autorelease]; + NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:base::SysUTF8ToNSString(label)] autorelease]; NSRange range = NSMakeRange(0, [attrTitle length]); [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:labelColor] @@ -229,11 +230,11 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string label; options.Get("label", &label); NSTextField* text_field = (NSTextField*)item.view; - text_field.stringValue = [NSString stringWithUTF8String:label.data()]; + text_field.stringValue = base::SysUTF8ToNSString(label); std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } } @@ -254,7 +255,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withOptions:(const mate::PersistentDictionary)options { std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } } @@ -275,12 +276,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withOptions:(const mate::PersistentDictionary&)options { std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string label; options.Get("label", &label); - item.label = [NSString stringWithUTF8String:label.data()]; + item.label = base::SysUTF8ToNSString(label); int maxValue = 100; int minValue = 0; @@ -309,13 +310,13 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withOptions:(const mate::PersistentDictionary&)options { std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string label; gfx::Image image; if (options.Get("label", &label)) { - item.collapsedRepresentationLabel = [NSString stringWithUTF8String:label.data()]; + item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); } else if (options.Get("image", &image)) { item.collapsedRepresentationImage = image.AsNSImage(); } @@ -354,7 +355,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide NSGroupTouchBarItem* item = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel);; } return item; } From b30f7c3c94e6b1c2a9b92ba61925e500e79b0d46 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:45:21 -0800 Subject: [PATCH 29/69] Use std::string ctor --- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 34d14161214..94d44f3265f 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -297,7 +297,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makePopoverForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); + std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; From f297ba987e1563aafc6574d4b418b8f75b41e0fd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:50:11 -0800 Subject: [PATCH 30/69] touchBarFromMutatableArray -> touchBarFromItemIdentifiers --- atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index e15af0946a8..1010cf5b8a9 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -28,7 +28,7 @@ window:(atom::NativeWindow*)window; - (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options; -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; +- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts; - (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)clear; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 94d44f3265f..48e0dbe691f 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -34,10 +34,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options { NSMutableArray* identifiers = [self identifierArrayFromDicts:item_options]; - return [self touchBarFromMutatableArray:identifiers]; + return [self touchBarFromItemIdentifiers:identifiers]; } -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { +- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items { NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; bar.delegate = delegate_; bar.defaultItemIdentifiers = items; @@ -328,7 +328,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::vector touchBar; if (options.Get("touchBar", &touchBar)) { - item.popoverTouchBar = [self touchBarFromMutatableArray:[self identifierArrayFromDicts:touchBar]]; + item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifierArrayFromDicts:touchBar]]; } } From ca29ec010139adf2bb93be98d68047cc1bf68709 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:54:51 -0800 Subject: [PATCH 31/69] Use reference --- atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 1010cf5b8a9..eb23a6a480d 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -54,7 +54,7 @@ // Helpers to update touch bar items - (void)updateButton:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; - (void)updateLabel:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withOptions:(const mate::PersistentDictionary)options; +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; - (void)updateSlider:(NSSliderTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; - (void)updatePopover:(NSPopoverTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 48e0dbe691f..4865e400c8e 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -252,7 +252,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item - withOptions:(const mate::PersistentDictionary)options { + withOptions:(const mate::PersistentDictionary&)options { std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); From 14ef5c595779c6929cf487d0ebd4bba028db94c4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 13:50:16 -0800 Subject: [PATCH 32/69] Reset helper when makeTouchBar is called --- atom/browser/native_window_mac.mm | 13 ++++--------- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 ++++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index c4f127459d1..6cd42bc64ac 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -352,9 +352,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; -- (void)reloadTouchBar; -- (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)resetTouchBar; +- (void)refreshTouchBarItem:(mate::Arguments*)args; @end @interface AtomNSWindow () @@ -365,7 +364,6 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; - touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell]); } - (void)setEnableLargerThanScreen:(bool)enable { @@ -376,16 +374,12 @@ bool ScopedDisableResize::disable_resize_ = false; self.touchBar = nil; } -- (void)reloadTouchBar { - [touch_bar_ clear]; - self.touchBar = nil; -} - - (void)refreshTouchBarItem:(mate::Arguments*)args { [touch_bar_ refreshTouchBarItem:args]; } - (NSTouchBar*)makeTouchBar { + touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); return [touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; } @@ -1377,6 +1371,7 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { } void NativeWindowMac::DestroyTouchBar() { + touch_bar_items_.clear(); [window_ resetTouchBar]; } @@ -1384,7 +1379,7 @@ void NativeWindowMac::SetTouchBar(mate::Arguments* args) { std::vector items; if (args->GetNext(&items)) { touch_bar_items_ = items; - [window_ reloadTouchBar]; + [window_ resetTouchBar]; } } diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 4865e400c8e..f24baad10aa 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -161,7 +161,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeButtonForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -213,7 +213,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeLabelForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -260,7 +260,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeSliderForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -296,7 +296,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makePopoverForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; From b39b49a15a8808f95075c57a85b6eb86842ebced Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 13:53:00 -0800 Subject: [PATCH 33/69] Rename to touch_bar_helper_ to differentiate from touchBar property --- atom/browser/native_window_mac.mm | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 6cd42bc64ac..f0ac44b63cc 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -336,10 +336,11 @@ bool ScopedDisableResize::disable_resize_ = false; @end -@interface AtomNSWindow : EventDispatchingWindow { +@interface AtomNSWindow : EventDispatchingWindow { @private atom::NativeWindowMac* shell_; bool enable_larger_than_screen_; + base::scoped_nsobject touch_bar_helper_; CGFloat windowButtonsInterButtonSpacing_; } @property BOOL acceptsFirstMouse; @@ -356,11 +357,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)refreshTouchBarItem:(mate::Arguments*)args; @end -@interface AtomNSWindow () -@end - @implementation AtomNSWindow - base::scoped_nsobject touch_bar_; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -375,16 +372,17 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)refreshTouchBarItem:(mate::Arguments*)args { - [touch_bar_ refreshTouchBarItem:args]; + [touch_bar_helper_ refreshTouchBarItem:args]; } - (NSTouchBar*)makeTouchBar { - touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); - return [touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; + touch_bar_helper_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); + return [touch_bar_helper_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; } -- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - return [touch_bar_ makeItemForIdentifier:identifier]; +- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + return [touch_bar_helper_ makeItemForIdentifier:identifier]; } // NSWindow overrides. From 28f2a4951bb72f80161e4498af60c35a66641d65 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 13:54:20 -0800 Subject: [PATCH 34/69] touch_bar_helper_ -> atom_touch_bar_ --- atom/browser/native_window_mac.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index f0ac44b63cc..af65d53368f 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -340,7 +340,7 @@ bool ScopedDisableResize::disable_resize_ = false; @private atom::NativeWindowMac* shell_; bool enable_larger_than_screen_; - base::scoped_nsobject touch_bar_helper_; + base::scoped_nsobject atom_touch_bar_; CGFloat windowButtonsInterButtonSpacing_; } @property BOOL acceptsFirstMouse; @@ -372,12 +372,12 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)refreshTouchBarItem:(mate::Arguments*)args { - [touch_bar_helper_ refreshTouchBarItem:args]; + [atom_touch_bar_ refreshTouchBarItem:args]; } - (NSTouchBar*)makeTouchBar { - touch_bar_helper_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); - return [touch_bar_helper_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; + atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); + return [atom_touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; } - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar From 1972e2eff947c91bd7041ec64a1d82d3e21c2781 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 14:09:22 -0800 Subject: [PATCH 35/69] Update renamed variable --- atom/browser/native_window_mac.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index af65d53368f..1a20ea74d01 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -382,7 +382,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - return [touch_bar_helper_ makeItemForIdentifier:identifier]; + return [atom_touch_bar_ makeItemForIdentifier:identifier]; } // NSWindow overrides. From cbb6f8c33ec4caa82f3bb9cd1f3010bed0c6f89a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 15:37:15 -0800 Subject: [PATCH 36/69] Store event listeners in each TouchBar class --- atom/browser/api/atom_api_window.cc | 6 +- atom/browser/api/atom_api_window.h | 4 +- atom/browser/native_window.cc | 6 +- atom/browser/native_window.h | 4 +- atom/browser/native_window_observer.h | 5 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 33 ++++-- lib/browser/api/browser-window.js | 14 +-- lib/browser/api/touch-bar.js | 134 +++++++++++------------- 8 files changed, 104 insertions(+), 102 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index adafab9173b..6c14d8b3c67 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -282,9 +282,9 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } -void Window::OnTouchBarItemResult(const std::string& item_type, - const std::vector& args) { - Emit("-touch-bar-interaction", item_type, args); +void Window::OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) { + Emit("-touch-bar-interaction", item_id, details); } #if defined(OS_WIN) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 2620292b253..bf3e8cb5e76 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -85,8 +85,8 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; - void OnTouchBarItemResult(const std::string& item_type, - const std::vector& args) override; + void OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index b008739709c..322ca8a0e4f 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -576,10 +576,10 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( } void NativeWindow::NotifyTouchBarItemInteraction( - const std::string& type, - const std::vector& args) { + const std::string& item_id, + const base::DictionaryValue& details) { for (NativeWindowObserver& observer : observers_) - observer.OnTouchBarItemResult(type, args); + observer.OnTouchBarItemResult(item_id, details); } #if defined(OS_WIN) diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 23c393e7aee..77188d84940 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -234,8 +234,8 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); - void NotifyTouchBarItemInteraction(const std::string& item_type, - const std::vector& args); + void NotifyTouchBarItemInteraction(const std::string& item_id, + const base::DictionaryValue& details); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 7fd1b7caebd..7f1f5aace82 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -9,6 +9,7 @@ #include #include "base/strings/string16.h" +#include "base/values.h" #include "ui/base/window_open_disposition.h" #include "url/gurl.h" @@ -71,8 +72,8 @@ class NativeWindowObserver { virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} - virtual void OnTouchBarItemResult(const std::string& item_type, - const std::vector& args) {} + virtual void OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) {} // Called when window message received #if defined(OS_WIN) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index f24baad10aa..194be6f5a80 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -131,20 +131,30 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (void)buttonAction:(id)sender { - NSString* item_id = [NSString stringWithFormat:@"%@.%d", ButtonIdentifier, (int)((NSButton*)sender).tag]; - window_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); + NSString* item_id = [NSString stringWithFormat:@"%ld", ((NSButton*)sender).tag]; + window_->NotifyTouchBarItemInteraction([item_id UTF8String], + base::DictionaryValue()); } - (void)colorPickerAction:(id)sender { - NSString* item_id = ((NSColorPickerTouchBarItem*)sender).identifier; + NSString* identifier = ((NSColorPickerTouchBarItem*)sender).identifier; + NSString* item_id = [self idFromIdentifier:identifier + withPrefix:ColorPickerIdentifier]; NSColor* color = ((NSColorPickerTouchBarItem*)sender).color; std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); - window_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), hex_color }); + base::DictionaryValue details; + details.SetString("color", hex_color); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], details); } - (void)sliderAction:(id)sender { - NSString* item_id = ((NSSliderTouchBarItem*)sender).identifier; - window_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem*)sender).slider intValue]) }); + NSString* identifier = ((NSSliderTouchBarItem*)sender).identifier; + NSString* item_id = [self idFromIdentifier:identifier + withPrefix:SliderIdentifier]; + base::DictionaryValue details; + details.SetInteger("value", + [((NSSliderTouchBarItem*)sender).slider intValue]); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], details); } - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { @@ -326,9 +336,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide item.showsCloseButton = showCloseButton; } - std::vector touchBar; - if (options.Get("touchBar", &touchBar)) { - item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifierArrayFromDicts:touchBar]]; + mate::PersistentDictionary child; + std::vector items; + if (options.Get("child", &child) && child.Get("ordereredItems", &items)) { + item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifierArrayFromDicts:items]]; } } @@ -338,8 +349,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary child; + if (!options.Get("child", &child)) return nil; std::vector items; - if (!options.Get("items", &items)) return nil; + if (!child.Get("ordereredItems", &items)) return nil; NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; NSMutableArray* identList = [self identifierArrayFromDicts:items]; diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index ecfd9195f43..a8a81cbd59d 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -133,8 +133,10 @@ BrowserWindow.prototype._init = function () { }) // Proxy TouchBar events - this.on('-touch-bar-interaction', (event, itemType, id, ...args) => { - TouchBar._event(itemType, id, ...args) + this.on('-touch-bar-interaction', (event, id, details) => { + if (this._touchBar != null) { + this._touchBar.emit('interaction', id, details) + } }) } @@ -207,6 +209,7 @@ Object.assign(BrowserWindow.prototype, { BrowserWindow.prototype.setTouchBar = function (touchBar) { if (touchBar == null) { this._destroyTouchBar() + this._touchBar = null return } @@ -214,11 +217,8 @@ BrowserWindow.prototype.setTouchBar = function (touchBar) { touchBar = new TouchBar(touchBar) } - this._setTouchBar(touchBar.toJSON()) - touchBar._owner = this - touchBar.items.forEach((item) => { - item._owner = this - }) + this._touchBar = touchBar + this._setTouchBar(touchBar.ordereredItems) } BrowserWindow.prototype._updateTouchBarItem = function (itemID) { diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 07b2631a77d..fe75e3ade56 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -1,82 +1,67 @@ -class TouchBar { +const {EventEmitter} = require('events') + +let itemIdIncrementor = 1 + +class TouchBar extends EventEmitter { constructor (items) { - this.items = items + super() + if (!Array.isArray(items)) { throw new Error('The items object provided has to be an array') } + + this.items = {} + this.ordereredItems = [] + const registerItem = (item) => { + this.items[item.id] = item + if (item.child instanceof TouchBar) { + item.child.ordereredItems.forEach(registerItem) + } + } items.forEach((item) => { - if (!item.id) { + this.ordereredItems.push(item) + if (!(item instanceof TouchBarItem)) { throw new Error('Each item must be an instance of a TouchBarItem') } + registerItem(item) + }) + + this.on('interaction', (itemID, details) => { + const item = this.items[itemID] + if (item != null && item.onInteraction != null) { + item.onInteraction(details) + } }) } - - toJSON () { - return this.items.map((item) => item.toJSON ? item.toJSON() : item) - } -} - -let itemIdIncrementor = 1 -const itemEventHandlers = {} - -TouchBar._event = (itemType, eventArgs) => { - let args = eventArgs.slice(1) - if (itemType === 'slider') { - args = args.map(val => parseInt(val, 10)) - } - const idParts = eventArgs[0].split('.') - const itemId = idParts[idParts.length - 1] - if (itemEventHandlers[itemId]) itemEventHandlers[itemId](...args) } class TouchBarItem { constructor (config) { - this.id = itemIdIncrementor++ - const mConfig = Object.assign({}, config || {}) - Object.defineProperty(this, 'config', { - configurable: false, - enumerable: false, - get: () => mConfig - }) - this.config.id = `${this.config.id || this.id}` - if (typeof this.config !== 'object' || this.config === null) { - throw new Error('Provided config must be a non-null object') - } - } - - updateConfig (newConfig) { - if (!this._owner) { - throw new Error('Cannot call methods on TouchBarItems without assigning to a BrowserWindow') - } - const dupConfig = Object.assign({}, newConfig) - delete dupConfig.id - Object.assign(this.config, dupConfig) - this._owner._updateTouchBarItem(this.toJSON()) - } - - toJSON () { - return this.config + this.id = `${itemIdIncrementor++}` } } TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'button' - const click = config.click - if (typeof click === 'function') { - itemEventHandlers[`${this.id}`] = click - } + this.type = 'button' + this.label = config.label + this.backgroundColor = config.backgroundColor + this.labelColor = config.labelColor + this.onInteraction = config.click } } TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'colorpicker' - const change = this.config.change + this.type = 'colorpicker' + + const {change} = config if (typeof change === 'function') { - itemEventHandlers[`${this.id}`] = change + this.onInteraction = (details) => { + change(details.color) + } } } } @@ -84,45 +69,48 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { TouchBar.Group = class TouchBarGroup extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'group' - } - - toJSON () { - const config = this.config - return Object.assign({}, config, { - items: config.items && config.items.toJSON ? config.items.toJSON() : [] - }) + this.type = 'group' + this.child = config.items + if (!(this.child instanceof TouchBar)) { + this.child = new TouchBar(this.items) + } } } TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'label' + this.type = 'label' + this.label = config.label } } TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'popover' - } - - toJSON () { - const config = this.config - return Object.assign({}, config, { - touchBar: config.touchBar && config.touchBar.toJSON ? config.touchBar.toJSON() : [] - }) + this.type = 'popover' + this.label = config.label + this.showCloseButton = config.showCloseButton + this.child = config.items + if (!(this.child instanceof TouchBar)) { + this.child = new TouchBar(this.items) + } } } TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'slider' - const change = this.config.change + this.type = 'slider' + this.minValue = config.minValue + this.maxValue = config.maxValue + this.initialValue = config.initialValue + + const {change} = config if (typeof change === 'function') { - itemEventHandlers[this.id] = change + this.onInteraction = (details) => { + change(details.value) + } } } } From 98f5858b1139783f887b68b2e2d2935a2721423f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 16:08:12 -0800 Subject: [PATCH 37/69] Initial support for dynamic properties --- atom/browser/api/atom_api_window.cc | 4 ++-- atom/browser/api/atom_api_window.h | 2 +- atom/browser/native_window.cc | 2 +- atom/browser/native_window.h | 2 +- atom/browser/native_window_mac.h | 2 +- atom/browser/native_window_mac.mm | 11 ++++++----- atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 25 +++++++++++-------------- lib/browser/api/browser-window.js | 12 +++++++----- lib/browser/api/touch-bar.js | 17 +++++++++++++++-- 10 files changed, 46 insertions(+), 33 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 6c14d8b3c67..3f9b82af59f 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -853,8 +853,8 @@ void Window::SetTouchBar(mate::Arguments* args) { window_->SetTouchBar(args); } -void Window::RefreshTouchBarItem(mate::Arguments* args) { - window_->RefreshTouchBarItem(args); +void Window::RefreshTouchBarItem(const std::string& item_id) { + window_->RefreshTouchBarItem(item_id); } int32_t Window::ID() const { diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index bf3e8cb5e76..697dfa3e10c 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -207,7 +207,7 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); void DestroyTouchBar(); void SetTouchBar(mate::Arguments* args); - void RefreshTouchBarItem(mate::Arguments* args); + void RefreshTouchBarItem(const std::string& item_id); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 322ca8a0e4f..fb34ee021e4 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -347,7 +347,7 @@ void NativeWindow::DestroyTouchBar() { void NativeWindow::SetTouchBar(mate::Arguments* args) { } -void NativeWindow::RefreshTouchBarItem(mate::Arguments* args) { +void NativeWindow::RefreshTouchBarItem(const std::string& item_id) { } void NativeWindow::FocusOnWebView() { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 77188d84940..8e8b6130281 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -173,7 +173,7 @@ class NativeWindow : public base::SupportsUserData, // Touchbar API virtual void DestroyTouchBar(); virtual void SetTouchBar(mate::Arguments* args); - virtual void RefreshTouchBarItem(mate::Arguments* args); + virtual void RefreshTouchBarItem(const std::string& item_id); // Webview APIs. virtual void FocusOnWebView(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index f8ebfec3adc..d80ff42897b 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -104,7 +104,7 @@ class NativeWindowMac : public NativeWindow, void SetVibrancy(const std::string& type) override; void DestroyTouchBar() override; void SetTouchBar(mate::Arguments* args) override; - void RefreshTouchBarItem(mate::Arguments* args) override; + void RefreshTouchBarItem(const std::string& item_id) override; std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 1a20ea74d01..0dc3bd952b3 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -354,7 +354,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; - (void)resetTouchBar; -- (void)refreshTouchBarItem:(mate::Arguments*)args; +- (void)refreshTouchBarItem:(const std::string&)item_id; + @end @implementation AtomNSWindow @@ -371,8 +372,8 @@ bool ScopedDisableResize::disable_resize_ = false; self.touchBar = nil; } -- (void)refreshTouchBarItem:(mate::Arguments*)args { - [atom_touch_bar_ refreshTouchBarItem:args]; +- (void)refreshTouchBarItem:(const std::string&)item_id { + [atom_touch_bar_ refreshTouchBarItem:item_id]; } - (NSTouchBar*)makeTouchBar { @@ -1381,8 +1382,8 @@ void NativeWindowMac::SetTouchBar(mate::Arguments* args) { } } -void NativeWindowMac::RefreshTouchBarItem(mate::Arguments* args) { - [window_ refreshTouchBarItem:args]; +void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) { + [window_ refreshTouchBarItem:item_id]; } std::vector NativeWindowMac::GetTouchBarItems() { diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index eb23a6a480d..2efabe6cf0e 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -30,7 +30,7 @@ - (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts; -- (void)refreshTouchBarItem:(mate::Arguments*)args; +- (void)refreshTouchBarItem:(const std::string&)item_id; - (void)clear; - (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 194be6f5a80..b2053f7df04 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -101,32 +101,29 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } -- (void)refreshTouchBarItem:(mate::Arguments*)args { - std::string item_id; - std::string type; - mate::PersistentDictionary options; - if (!args->GetNext(&options)) return; - if (!options.Get("type", &type)) return; - if (!options.Get("id", &item_id)) return; +- (void)refreshTouchBarItem:(const std::string&)item_id { if (item_map.find(item_id) == item_map.end()) return; + if (![self hasItemWithID:item_id]) return; - if (type == "button") { + mate::PersistentDictionary options = item_id_map[item_id]; + std::string item_type; + options.Get("type", &item_type); + + if (item_type == "button") { [self updateButton:(NSCustomTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "label") { + } else if (item_type == "label") { [self updateLabel:(NSCustomTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "colorpicker") { + } else if (item_type == "colorpicker") { [self updateColorPicker:(NSColorPickerTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "slider") { + } else if (item_type == "slider") { [self updateSlider:(NSSliderTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "popover") { + } else if (item_type == "popover") { [self updatePopover:(NSPopoverTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "group") { - args->ThrowError("You can not update the config of a group. Update the individual items or replace the group"); } } diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index a8a81cbd59d..27f425c7ae9 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -138,6 +138,8 @@ BrowserWindow.prototype._init = function () { this._touchBar.emit('interaction', id, details) } }) + + this._touchBarListener = this._refreshTouchBarItem.bind(this) } BrowserWindow.getFocusedWindow = () => { @@ -208,8 +210,11 @@ Object.assign(BrowserWindow.prototype, { // TouchBar API BrowserWindow.prototype.setTouchBar = function (touchBar) { if (touchBar == null) { + if (this._touchBar != null) { + this._touchBar.removeListener('change', this._touchBarListener) + this._touchBar = null + } this._destroyTouchBar() - this._touchBar = null return } @@ -218,11 +223,8 @@ BrowserWindow.prototype.setTouchBar = function (touchBar) { } this._touchBar = touchBar + this._touchBar.on('change', this._touchBarListener) this._setTouchBar(touchBar.ordereredItems) } -BrowserWindow.prototype._updateTouchBarItem = function (itemID) { - this._refreshTouchBarItem(itemID) -} - module.exports = BrowserWindow diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index fe75e3ade56..237ef716fa9 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -14,6 +14,9 @@ class TouchBar extends EventEmitter { this.ordereredItems = [] const registerItem = (item) => { this.items[item.id] = item + item.on('change', () => { + this.emit('change', item.id, item.type) + }) if (item.child instanceof TouchBar) { item.child.ordereredItems.forEach(registerItem) } @@ -35,8 +38,9 @@ class TouchBar extends EventEmitter { } } -class TouchBarItem { +class TouchBarItem extends EventEmitter { constructor (config) { + super() this.id = `${itemIdIncrementor++}` } } @@ -81,7 +85,16 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { super(config) this.type = 'label' - this.label = config.label + this._label = config.label + } + + set label (newLabel) { + this._label = newLabel + this.emit('change') + } + + get label () { + return this._label } } From 823b3baed020753090c523fd1883d1b6147d2d5f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 16:14:02 -0800 Subject: [PATCH 38/69] Use vector of dictionaries instead of mate::Arguments --- atom/browser/api/atom_api_window.cc | 4 ++-- atom/browser/api/atom_api_window.h | 3 ++- atom/browser/native_window.cc | 3 ++- atom/browser/native_window.h | 4 +++- atom/browser/native_window_mac.h | 5 ++--- atom/browser/native_window_mac.mm | 10 ++++------ 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 3f9b82af59f..fbfba8a9e69 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -849,8 +849,8 @@ void Window::DestroyTouchBar() { window_->DestroyTouchBar(); } -void Window::SetTouchBar(mate::Arguments* args) { - window_->SetTouchBar(args); +void Window::SetTouchBar(const std::vector& items) { + window_->SetTouchBar(items); } void Window::RefreshTouchBarItem(const std::string& item_id) { diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 697dfa3e10c..b8060c3f47c 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -16,6 +16,7 @@ #include "atom/common/api/atom_api_native_image.h" #include "atom/common/key_weak_map.h" #include "native_mate/handle.h" +#include "native_mate/persistent_dictionary.h" #include "ui/gfx/image/image.h" class GURL; @@ -206,7 +207,7 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); void DestroyTouchBar(); - void SetTouchBar(mate::Arguments* args); + void SetTouchBar(const std::vector& items); void RefreshTouchBarItem(const std::string& item_id); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index fb34ee021e4..21d442c9207 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -344,7 +344,8 @@ void NativeWindow::SetVibrancy(const std::string& filename) { void NativeWindow::DestroyTouchBar() { } -void NativeWindow::SetTouchBar(mate::Arguments* args) { +void NativeWindow::SetTouchBar( + const std::vector& items) { } void NativeWindow::RefreshTouchBarItem(const std::string& item_id) { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 8e8b6130281..bb94127b7a3 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -22,6 +22,7 @@ #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" #include "native_mate/constructor.h" +#include "native_mate/persistent_dictionary.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" @@ -172,7 +173,8 @@ class NativeWindow : public base::SupportsUserData, // Touchbar API virtual void DestroyTouchBar(); - virtual void SetTouchBar(mate::Arguments* args); + virtual void SetTouchBar( + const std::vector& items); virtual void RefreshTouchBarItem(const std::string& item_id); // Webview APIs. diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index d80ff42897b..388ea282c07 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -13,8 +13,6 @@ #include "atom/browser/native_window.h" #include "base/mac/scoped_nsobject.h" #include "content/public/browser/render_widget_host.h" -#include "native_mate/constructor.h" -#include "native_mate/persistent_dictionary.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @@ -103,7 +101,8 @@ class NativeWindowMac : public NativeWindow, void SetVibrancy(const std::string& type) override; void DestroyTouchBar() override; - void SetTouchBar(mate::Arguments* args) override; + void SetTouchBar( + const std::vector& items) override; void RefreshTouchBarItem(const std::string& item_id) override; std::vector GetTouchBarItems(); diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 0dc3bd952b3..d93b1364584 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1374,12 +1374,10 @@ void NativeWindowMac::DestroyTouchBar() { [window_ resetTouchBar]; } -void NativeWindowMac::SetTouchBar(mate::Arguments* args) { - std::vector items; - if (args->GetNext(&items)) { - touch_bar_items_ = items; - [window_ resetTouchBar]; - } +void NativeWindowMac::SetTouchBar( + const std::vector& items) { + touch_bar_items_ = items; + [window_ resetTouchBar]; } void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) { From 812beb240ba888bb93f183f335bdc149003a35dd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 16:15:39 -0800 Subject: [PATCH 39/69] Remove unneeded includes --- atom/browser/native_window.cc | 1 - atom/browser/native_window.h | 1 - 2 files changed, 2 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 21d442c9207..49c08f5d4b0 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -33,7 +33,6 @@ #include "content/public/browser/render_widget_host_view.h" #include "content/public/common/content_switches.h" #include "ipc/ipc_message_macros.h" -#include "native_mate/constructor.h" #include "native_mate/dictionary.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/gfx/codec/png_codec.h" diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index bb94127b7a3..69b2f50962d 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -21,7 +21,6 @@ #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" -#include "native_mate/constructor.h" #include "native_mate/persistent_dictionary.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" From ec500b285238dd46cf484e0dfb7499911e23b0ff Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 16:44:03 -0800 Subject: [PATCH 40/69] Use scoped_nsobject to match Chrome implementation --- atom/browser/native_window_mac.mm | 5 ++- atom/browser/ui/cocoa/atom_touch_bar.h | 3 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 45 +++++++++++++------------ 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index d93b1364584..66fc97480c7 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -383,7 +383,10 @@ bool ScopedDisableResize::disable_resize_ = false; - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - return [atom_touch_bar_ makeItemForIdentifier:identifier]; + if (touchBar) + return [atom_touch_bar_ makeItemForIdentifier:identifier]; + else + return nil; } // NSWindow overrides. diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 2efabe6cf0e..50f03cda621 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -13,13 +13,14 @@ #include "atom/browser/native_window.h" #include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" +#include "base/mac/scoped_nsobject.h" #include "native_mate/constructor.h" #include "native_mate/persistent_dictionary.h" @interface AtomTouchBar : NSObject { @protected std::map item_id_map; - std::map item_map; + std::map> item_map; id delegate_; atom::NativeWindow* window_; } diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index b2053f7df04..c7a002081e3 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -38,10 +38,11 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items { - NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; - bar.delegate = delegate_; - bar.defaultItemIdentifiers = items; - return bar; + base::scoped_nsobject bar( + [[NSClassFromString(@"NSTouchBar") alloc] init]); + [bar setDelegate:delegate_]; + [bar setDefaultItemIdentifiers:items]; + return bar.autorelease(); } - (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts { @@ -51,7 +52,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { - item_id_map.insert(make_pair(item_id, item)); + item_id_map[item_id] = item; if (type == "button") { [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "label") { @@ -73,31 +74,33 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - NSTouchBarItem* item = nil; - NSString* id = nil; + base::scoped_nsobject item; + NSString* item_id = nil; + if ([identifier hasPrefix:ButtonIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - item = [self makeButtonForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + item.reset([self makeButtonForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:LabelIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - item = [self makeLabelForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + item.reset([self makeLabelForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:ColorPickerIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; - item = [self makeColorPickerForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + item.reset([self makeColorPickerForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:SliderIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; - item = [self makeSliderForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + item.reset([self makeSliderForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:PopOverIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; - item = [self makePopoverForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + item.reset([self makePopoverForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:GroupIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; - item = [self makeGroupForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + item.reset([self makeGroupForID:item_id withIdentifier:identifier]); } - item_map.insert(make_pair(std::string([id UTF8String]), item)); + if (item_id) + item_map[[item_id UTF8String]] = item; - return item; + return item.autorelease(); } From b959f782f6bf83fc74b2bab40f97243217f0c741 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 17:12:35 -0800 Subject: [PATCH 41/69] Rename instance variable maps --- atom/browser/native_window_mac.mm | 2 +- atom/browser/ui/cocoa/atom_touch_bar.h | 20 ++-- atom/browser/ui/cocoa/atom_touch_bar.mm | 136 ++++++++++++------------ 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 66fc97480c7..71fdef99155 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -378,7 +378,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (NSTouchBar*)makeTouchBar { atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); - return [atom_touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; + return [atom_touch_bar_ makeTouchBarFromSettings:shell_->GetTouchBarItems()]; } - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 50f03cda621..ff70f1274fc 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -19,8 +19,8 @@ @interface AtomTouchBar : NSObject { @protected - std::map item_id_map; - std::map> item_map; + std::map settings_; + std::map> items_; id delegate_; atom::NativeWindow* window_; } @@ -28,14 +28,14 @@ - (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window; -- (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options; +- (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; -- (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts; +- (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; - (void)clear; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; -- (bool)hasItemWithID:(const std::string&)id; +- (bool)hasItemWithID:(const std::string&)item_id; - (NSColor*)colorFromHexColorString:(const std::string&)colorString; // Selector actions @@ -53,11 +53,11 @@ - (NSTouchBarItem*)makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier; // Helpers to update touch bar items -- (void)updateButton:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updateLabel:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updateSlider:(NSSliderTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updatePopover:(NSPopoverTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; +- (void)updateButton:(NSCustomTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateLabel:(NSCustomTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateSlider:(NSSliderTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updatePopover:(NSPopoverTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; @end diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index c7a002081e3..33c5201da0c 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -29,11 +29,11 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (void)clear { - item_id_map.clear(); + settings_.clear(); } -- (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options { - NSMutableArray* identifiers = [self identifierArrayFromDicts:item_options]; +- (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings { + NSMutableArray* identifiers = [self identifiersFromSettings:settings]; return [self touchBarFromItemIdentifiers:identifiers]; } @@ -45,32 +45,32 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return bar.autorelease(); } -- (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts { - NSMutableArray* idents = [[NSMutableArray alloc] init]; +- (NSMutableArray*)identifiersFromSettings:(const std::vector&)dicts { + NSMutableArray* identifiers = [[NSMutableArray alloc] init]; for (const auto& item : dicts) { std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { - item_id_map[item_id] = item; + settings_[item_id] = item; if (type == "button") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "label") { - [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "colorpicker") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "slider") { - [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "popover") { - [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "group") { - [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; } } } - [idents addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + [identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy]; - return idents; + return identifiers; } - (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { @@ -98,35 +98,35 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } if (item_id) - item_map[[item_id UTF8String]] = item; + items_[[item_id UTF8String]] = item; return item.autorelease(); } - (void)refreshTouchBarItem:(const std::string&)item_id { - if (item_map.find(item_id) == item_map.end()) return; + if (items_.find(item_id) == items_.end()) return; if (![self hasItemWithID:item_id]) return; - mate::PersistentDictionary options = item_id_map[item_id]; + mate::PersistentDictionary settings = settings_[item_id]; std::string item_type; - options.Get("type", &item_type); + settings.Get("type", &item_type); if (item_type == "button") { - [self updateButton:(NSCustomTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updateButton:(NSCustomTouchBarItem*)items_[item_id] + withSettings:settings]; } else if (item_type == "label") { - [self updateLabel:(NSCustomTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updateLabel:(NSCustomTouchBarItem*)items_[item_id] + withSettings:settings]; } else if (item_type == "colorpicker") { - [self updateColorPicker:(NSColorPickerTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updateColorPicker:(NSColorPickerTouchBarItem*)items_[item_id] + withSettings:settings]; } else if (item_type == "slider") { - [self updateSlider:(NSSliderTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updateSlider:(NSSliderTouchBarItem*)items_[item_id] + withSettings:settings]; } else if (item_type == "popover") { - [self updatePopover:(NSPopoverTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updatePopover:(NSPopoverTouchBarItem*)items_[item_id] + withSettings:settings]; } } @@ -161,8 +161,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return [identifier substringFromIndex:[prefix length]]; } -- (bool)hasItemWithID:(const std::string&)id { - return item_id_map.find(id) != item_id_map.end(); +- (bool)hasItemWithID:(const std::string&)item_id { + return settings_.find(item_id) != settings_.end(); } - (NSColor*)colorFromHexColorString:(const std::string&)colorString { @@ -175,38 +175,38 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; NSCustomTouchBarItem* item = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; NSButton* button = [NSButton buttonWithTitle:@"" target:self action:@selector(buttonAction:)]; button.tag = [id floatValue]; item.view = button; - [self updateButton:item withOptions:options]; + [self updateButton:item withSettings:settings]; return item; } - (void)updateButton:(NSCustomTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { NSButton* button = (NSButton*)item.view; std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string backgroundColor; - if (options.Get("backgroundColor", &backgroundColor)) { + if (settings.Get("backgroundColor", &backgroundColor)) { button.bezelColor = [self colorFromHexColorString:backgroundColor]; } std::string label; - if (options.Get("label", &label)) { + if (settings.Get("label", &label)) { button.title = base::SysUTF8ToNSString(label); } std::string labelColor; - if (!label.empty() && options.Get("labelColor", &labelColor)) { + if (!label.empty() && settings.Get("labelColor", &labelColor)) { NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:base::SysUTF8ToNSString(label)] autorelease]; NSRange range = NSMakeRange(0, [attrTitle length]); [attrTitle addAttribute:NSForegroundColorAttributeName @@ -217,7 +217,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } gfx::Image image; - if (options.Get("image", &image)) { + if (settings.Get("image", &image)) { button.image = image.AsNSImage(); } } @@ -227,23 +227,23 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; + mate::PersistentDictionary item = settings_[s_id]; NSCustomTouchBarItem* customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; customItem.view = [NSTextField labelWithString:@""]; - [self updateLabel:customItem withOptions:item]; + [self updateLabel:customItem withSettings:item]; return customItem; } - (void)updateLabel:(NSCustomTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { std::string label; - options.Get("label", &label); + settings.Get("label", &label); NSTextField* text_field = (NSTextField*)item.view; text_field.stringValue = base::SysUTF8ToNSString(label); std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } } @@ -253,18 +253,18 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; item.target = self; item.action = @selector(colorPickerAction:); - [self updateColorPicker:item withOptions:options]; + [self updateColorPicker:item withSettings:settings]; return item; } - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } } @@ -274,31 +274,31 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; NSSliderTouchBarItem* item = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; item.target = self; item.action = @selector(sliderAction:); - [self updateSlider:item withOptions:options]; + [self updateSlider:item withSettings:settings]; return item; } - (void)updateSlider:(NSSliderTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string label; - options.Get("label", &label); + settings.Get("label", &label); item.label = base::SysUTF8ToNSString(label); int maxValue = 100; int minValue = 0; int initialValue = 50; - options.Get("minValue", &minValue); - options.Get("maxValue", &maxValue); - options.Get("initialValue", &initialValue); + settings.Get("minValue", &minValue); + settings.Get("maxValue", &maxValue); + settings.Get("initialValue", &initialValue); item.slider.minValue = minValue; item.slider.maxValue = maxValue; @@ -310,36 +310,36 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; NSPopoverTouchBarItem* item = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; - [self updatePopover:item withOptions:options]; + [self updatePopover:item withSettings:settings]; return item; } - (void)updatePopover:(NSPopoverTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string label; gfx::Image image; - if (options.Get("label", &label)) { + if (settings.Get("label", &label)) { item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); - } else if (options.Get("image", &image)) { + } else if (settings.Get("image", &image)) { item.collapsedRepresentationImage = image.AsNSImage(); } bool showCloseButton; - if (options.Get("showCloseButton", &showCloseButton)) { + if (settings.Get("showCloseButton", &showCloseButton)) { item.showsCloseButton = showCloseButton; } mate::PersistentDictionary child; std::vector items; - if (options.Get("child", &child) && child.Get("ordereredItems", &items)) { - item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifierArrayFromDicts:items]]; + if (settings.Get("child", &child) && child.Get("ordereredItems", &items)) { + item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]]; } } @@ -347,15 +347,15 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; mate::PersistentDictionary child; - if (!options.Get("child", &child)) return nil; + if (!settings.Get("child", &child)) return nil; std::vector items; if (!child.Get("ordereredItems", &items)) return nil; NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; - NSMutableArray* identList = [self identifierArrayFromDicts:items]; + NSMutableArray* identList = [self identifiersFromSettings:items]; for (NSUInteger i = 0; i < [identList count]; i++) { if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; @@ -367,7 +367,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide NSGroupTouchBarItem* item = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel);; } return item; From 347d472841f30d48b90c5d63e6bf9f227b0248fc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 17:15:30 -0800 Subject: [PATCH 42/69] Remove unused clear method --- atom/browser/ui/cocoa/atom_touch_bar.h | 4 +--- atom/browser/ui/cocoa/atom_touch_bar.mm | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index ff70f1274fc..d0146ec5f9f 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -25,14 +25,12 @@ atom::NativeWindow* window_; } -- (id)initWithDelegate:(id)delegate - window:(atom::NativeWindow*)window; +- (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window; - (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; -- (void)clear; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; - (bool)hasItemWithID:(const std::string&)item_id; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 33c5201da0c..f7656fa8031 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -28,10 +28,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return self; } -- (void)clear { - settings_.clear(); -} - - (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings { NSMutableArray* identifiers = [self identifiersFromSettings:settings]; return [self touchBarFromItemIdentifiers:identifiers]; From d5dbe3676e993c38e09e6667acc0e46579c12568 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 10:55:28 -0800 Subject: [PATCH 43/69] Add window helpers to TouchBar class --- lib/browser/api/browser-window.js | 47 ++++++++++++------------------- lib/browser/api/touch-bar.js | 37 ++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 27f425c7ae9..aa3872870e8 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -131,15 +131,6 @@ BrowserWindow.prototype._init = function () { return this.webContents.devToolsWebContents } }) - - // Proxy TouchBar events - this.on('-touch-bar-interaction', (event, id, details) => { - if (this._touchBar != null) { - this._touchBar.emit('interaction', id, details) - } - }) - - this._touchBarListener = this._refreshTouchBarItem.bind(this) } BrowserWindow.getFocusedWindow = () => { @@ -204,27 +195,25 @@ Object.assign(BrowserWindow.prototype, { }, capturePage (...args) { return this.webContents.capturePage(...args) + }, + + // TouchBar API + setTouchBar (touchBar) { + if (touchBar == null) { + if (this._touchBar != null) { + this._touchBar._removeFromWindow(this) + } + this._destroyTouchBar() + return + } + + if (Array.isArray(touchBar)) { + touchBar = new TouchBar(touchBar) + } + + this._touchBar = touchBar + this._touchBar._addToWindow(this) } }) -// TouchBar API -BrowserWindow.prototype.setTouchBar = function (touchBar) { - if (touchBar == null) { - if (this._touchBar != null) { - this._touchBar.removeListener('change', this._touchBarListener) - this._touchBar = null - } - this._destroyTouchBar() - return - } - - if (Array.isArray(touchBar)) { - touchBar = new TouchBar(touchBar) - } - - this._touchBar = touchBar - this._touchBar.on('change', this._touchBarListener) - this._setTouchBar(touchBar.ordereredItems) -} - module.exports = BrowserWindow diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 237ef716fa9..20285246c36 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -10,6 +10,7 @@ class TouchBar extends EventEmitter { throw new Error('The items object provided has to be an array') } + this.windowListeners = {} this.items = {} this.ordereredItems = [] const registerItem = (item) => { @@ -28,13 +29,45 @@ class TouchBar extends EventEmitter { } registerItem(item) }) + } - this.on('interaction', (itemID, details) => { + // Called by BrowserWindow.setTouchBar + _addToWindow (window) { + const {id} = window + + // Already added to window + if (this.windowListeners.hasOwnProperty(id)) return + + const changeListener = (itemID) => { + window._refreshTouchBarItem(itemID) + } + this.on('change', changeListener) + + const interactionListener = (event, itemID, details) => { const item = this.items[itemID] if (item != null && item.onInteraction != null) { item.onInteraction(details) } - }) + } + window.on('-touch-bar-interaction', interactionListener) + + const removeListeners = () => { + this.removeListener('change', changeListener) + window.removeListener('-touch-bar-interaction', interactionListener) + window.removeListener('closed', removeListeners) + window._touchBar = null + delete this.windowListeners[id] + } + window.once('closed', removeListeners) + this.windowListeners[id] = removeListeners + + window._setTouchBar(this.ordereredItems) + } + + // Called by BrowserWindow.setTouchBar + _removeFromWindow (window) { + const removeListeners = this.windowListeners[window.id] + if (removeListeners != null) removeListeners() } } From 51f1c5a5576a0bef3f56e7f6d021ba335b1d2941 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 11:05:34 -0800 Subject: [PATCH 44/69] Use SetTouchBar withe empty vector instead of DestroyTouchBar --- atom/browser/api/atom_api_window.cc | 7 +------ atom/browser/api/atom_api_window.h | 1 - atom/browser/native_window.cc | 3 --- atom/browser/native_window.h | 1 - atom/browser/native_window_mac.h | 1 - atom/browser/native_window_mac.mm | 5 ----- lib/browser/api/browser-window.js | 14 +++++++------- lib/browser/api/touch-bar.js | 4 +++- 8 files changed, 11 insertions(+), 25 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index fbfba8a9e69..6748c9c0dd1 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -845,10 +845,6 @@ void Window::SetVibrancy(mate::Arguments* args) { window_->SetVibrancy(type); } -void Window::DestroyTouchBar() { - window_->DestroyTouchBar(); -} - void Window::SetTouchBar(const std::vector& items) { window_->SetTouchBar(items); } @@ -977,8 +973,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setAutoHideCursor", &Window::SetAutoHideCursor) #endif .SetMethod("setVibrancy", &Window::SetVibrancy) - .SetMethod("_destroyTouchBar", &Window::DestroyTouchBar) - .SetMethod("_setTouchBar", &Window::SetTouchBar) + .SetMethod("_setTouchBarItems", &Window::SetTouchBar) .SetMethod("_refreshTouchBarItem", &Window::RefreshTouchBarItem) #if defined(OS_WIN) .SetMethod("hookWindowMessage", &Window::HookWindowMessage) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index b8060c3f47c..f30baf79d4d 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -206,7 +206,6 @@ class Window : public mate::TrackableObject, void SetAutoHideCursor(bool auto_hide); void SetVibrancy(mate::Arguments* args); - void DestroyTouchBar(); void SetTouchBar(const std::vector& items); void RefreshTouchBarItem(const std::string& item_id); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 49c08f5d4b0..90a823f80b6 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -340,9 +340,6 @@ void NativeWindow::SetAutoHideCursor(bool auto_hide) { void NativeWindow::SetVibrancy(const std::string& filename) { } -void NativeWindow::DestroyTouchBar() { -} - void NativeWindow::SetTouchBar( const std::vector& items) { } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 69b2f50962d..941f5849a65 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -171,7 +171,6 @@ class NativeWindow : public base::SupportsUserData, virtual void SetVibrancy(const std::string& type); // Touchbar API - virtual void DestroyTouchBar(); virtual void SetTouchBar( const std::vector& items); virtual void RefreshTouchBarItem(const std::string& item_id); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 388ea282c07..03430af57cc 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -100,7 +100,6 @@ class NativeWindowMac : public NativeWindow, void SetAutoHideCursor(bool auto_hide) override; void SetVibrancy(const std::string& type) override; - void DestroyTouchBar() override; void SetTouchBar( const std::vector& items) override; void RefreshTouchBarItem(const std::string& item_id) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 71fdef99155..b3ab36ed31f 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1372,11 +1372,6 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { [effect_view setMaterial:vibrancyType]; } -void NativeWindowMac::DestroyTouchBar() { - touch_bar_items_.clear(); - [window_ resetTouchBar]; -} - void NativeWindowMac::SetTouchBar( const std::vector& items) { touch_bar_items_ = items; diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index aa3872870e8..3c936d2af50 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -199,20 +199,20 @@ Object.assign(BrowserWindow.prototype, { // TouchBar API setTouchBar (touchBar) { + // This property is set from within TouchBar + if (this._touchBar != null) { + this._touchBar._removeFromWindow(this) + } + if (touchBar == null) { - if (this._touchBar != null) { - this._touchBar._removeFromWindow(this) - } - this._destroyTouchBar() + this._setTouchBarItems([]) return } if (Array.isArray(touchBar)) { touchBar = new TouchBar(touchBar) } - - this._touchBar = touchBar - this._touchBar._addToWindow(this) + touchBar._addToWindow(this) } }) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 20285246c36..6f6a31fd25a 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -38,6 +38,8 @@ class TouchBar extends EventEmitter { // Already added to window if (this.windowListeners.hasOwnProperty(id)) return + window._touchBar = this + const changeListener = (itemID) => { window._refreshTouchBarItem(itemID) } @@ -61,7 +63,7 @@ class TouchBar extends EventEmitter { window.once('closed', removeListeners) this.windowListeners[id] = removeListeners - window._setTouchBar(this.ordereredItems) + window._setTouchBarItems(this.ordereredItems) } // Called by BrowserWindow.setTouchBar From f9dd91d54d29ca6c3866cd8a940dff01c2301163 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 11:12:22 -0800 Subject: [PATCH 45/69] Add static helper to bind touch bar to window --- lib/browser/api/browser-window.js | 20 +++----------------- lib/browser/api/touch-bar.js | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 3c936d2af50..4b3f70139f0 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -1,6 +1,7 @@ 'use strict' -const {ipcMain, TouchBar} = require('electron') +const electron = require('electron') +const {ipcMain} = electron const {EventEmitter} = require('events') const {BrowserWindow} = process.atomBinding('window') const v8Util = process.atomBinding('v8_util') @@ -196,23 +197,8 @@ Object.assign(BrowserWindow.prototype, { capturePage (...args) { return this.webContents.capturePage(...args) }, - - // TouchBar API setTouchBar (touchBar) { - // This property is set from within TouchBar - if (this._touchBar != null) { - this._touchBar._removeFromWindow(this) - } - - if (touchBar == null) { - this._setTouchBarItems([]) - return - } - - if (Array.isArray(touchBar)) { - touchBar = new TouchBar(touchBar) - } - touchBar._addToWindow(this) + electron.TouchBar._setOnWindow(touchBar, this) } }) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 6f6a31fd25a..93cfb8988da 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -3,6 +3,24 @@ const {EventEmitter} = require('events') let itemIdIncrementor = 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 (items) { super() From f153d082975ef3018b08ce7585cd0fa3dea1abfb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 12:50:36 -0800 Subject: [PATCH 46/69] Support setting the initial/available colors --- atom/browser/ui/cocoa/atom_touch_bar.mm | 21 ++++++++++++++++++- .../ui/cocoa/touch_bar_forward_declarations.h | 1 + lib/browser/api/touch-bar.js | 8 ++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index f7656fa8031..0613b2afc3b 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -245,7 +245,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeColorPickerForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -253,6 +253,25 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; item.target = self; item.action = @selector(colorPickerAction:); + + std::string selectedColor; + if (settings.Get("selectedColor", &selectedColor)) { + item.color = [self colorFromHexColorString:selectedColor]; + } + + std::vector colors; + if (settings.Get("availableColors", &colors) && colors.size() > 0) { + NSColorList* color_list = [[[NSColorList alloc] initWithName:identifier] autorelease]; + for (size_t i = 0; i < colors.size(); ++i) { + [color_list insertColor:[self colorFromHexColorString:colors[i]] + key:base::SysUTF8ToNSString(colors[i]) + atIndex:i]; + } + item.colorList = color_list; + } + + item.showsAlpha = NO; + [self updateColorPicker:item withSettings:settings]; return item; } diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index e9a6e5525b6..e4e2bc15a5d 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -98,6 +98,7 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @property SEL action; @property(weak) id target; @property(copy) NSColor *color; +@property(strong) NSColorList *colorList; @end diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 93cfb8988da..62008b70d88 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -1,6 +1,6 @@ const {EventEmitter} = require('events') -let itemIdIncrementor = 1 +let nextItemID = 1 class TouchBar extends EventEmitter { @@ -94,7 +94,7 @@ class TouchBar extends EventEmitter { class TouchBarItem extends EventEmitter { constructor (config) { super() - this.id = `${itemIdIncrementor++}` + this.id = `${nextItemID++}` } } @@ -113,6 +113,8 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super(config) this.type = 'colorpicker' + this.availableColors = config.availableColors + this.selectedColor = config.selectedColor const {change} = config if (typeof change === 'function') { @@ -151,7 +153,7 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } -TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { +TouchBar.Popover = class TouchBarPopover extends TouchBarItem { constructor (config) { super(config) this.type = 'popover' From 61aa9bbff43c4bfcf6ed245ed3b190fd12ee0f74 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 14:10:52 -0800 Subject: [PATCH 47/69] Add support for spacer items --- atom/browser/ui/cocoa/atom_touch_bar.mm | 12 ++++++++++-- .../ui/cocoa/touch_bar_forward_declarations.h | 3 +++ lib/browser/api/touch-bar.js | 8 ++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 0613b2afc3b..469160c5a58 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -61,6 +61,16 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide [identifiers addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "group") { [identifiers addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; + } else if (type == "spacer") { + std::string size; + item.Get("size", &size); + if (size == "large") { + [identifiers addObject:NSTouchBarItemIdentifierFixedSpaceLarge]; + } else if (size == "flexible") { + [identifiers addObject:NSTouchBarItemIdentifierFlexibleSpace]; + } else { + [identifiers addObject:NSTouchBarItemIdentifierFixedSpaceSmall]; + } } } } @@ -270,8 +280,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide item.colorList = color_list; } - item.showsAlpha = NO; - [self updateColorPicker:item withSettings:settings]; return item; } diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index e4e2bc15a5d..68693cb593b 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -27,6 +27,9 @@ typedef NSString* NSTouchBarCustomizationIdentifier; static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceSmall = @"NSTouchBarItemIdentifierFixedSpaceSmall"; +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceLarge = + @"NSTouchBarItemIdentifierFixedSpaceLarge"; + static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFlexibleSpace = @"NSTouchBarItemIdentifierFlexibleSpace"; diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 62008b70d88..dd8e89f4c29 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -153,6 +153,14 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } +TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { + constructor (config) { + super(config) + this.type = 'spacer' + this.size = config.size + } +} + TouchBar.Popover = class TouchBarPopover extends TouchBarItem { constructor (config) { super(config) From 5f9e9d4b368d3750b582c941f10bf7e47186fbdd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 14:54:43 -0800 Subject: [PATCH 48/69] Add move live updating properties --- atom/browser/ui/cocoa/atom_touch_bar.mm | 80 ++++++------------------- lib/browser/api/touch-bar.js | 57 +++++++++++------- 2 files changed, 53 insertions(+), 84 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 469160c5a58..8a746ccbe2f 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -196,11 +196,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withSettings:(const mate::PersistentDictionary&)settings { NSButton* button = (NSButton*)item.view; - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); - } - std::string backgroundColor; if (settings.Get("backgroundColor", &backgroundColor)) { button.bezelColor = [self colorFromHexColorString:backgroundColor]; @@ -211,17 +206,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide button.title = base::SysUTF8ToNSString(label); } - std::string labelColor; - if (!label.empty() && settings.Get("labelColor", &labelColor)) { - NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:base::SysUTF8ToNSString(label)] autorelease]; - NSRange range = NSMakeRange(0, [attrTitle length]); - [attrTitle addAttribute:NSForegroundColorAttributeName - value:[self colorFromHexColorString:labelColor] - range:range]; - [attrTitle fixAttributesInRange:range]; - button.attributedTitle = attrTitle; - } - gfx::Image image; if (settings.Get("image", &image)) { button.image = image.AsNSImage(); @@ -247,11 +231,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide settings.Get("label", &label); NSTextField* text_field = (NSTextField*)item.view; text_field.stringValue = base::SysUTF8ToNSString(label); - - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); - } } - (NSTouchBarItem*)makeColorPickerForID:(NSString*)id @@ -263,32 +242,26 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; item.target = self; item.action = @selector(colorPickerAction:); - - std::string selectedColor; - if (settings.Get("selectedColor", &selectedColor)) { - item.color = [self colorFromHexColorString:selectedColor]; - } - - std::vector colors; - if (settings.Get("availableColors", &colors) && colors.size() > 0) { - NSColorList* color_list = [[[NSColorList alloc] initWithName:identifier] autorelease]; - for (size_t i = 0; i < colors.size(); ++i) { - [color_list insertColor:[self colorFromHexColorString:colors[i]] - key:base::SysUTF8ToNSString(colors[i]) - atIndex:i]; - } - item.colorList = color_list; - } - [self updateColorPicker:item withSettings:settings]; return item; } - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); + std::vector colors; + if (settings.Get("availableColors", &colors) && colors.size() > 0) { + NSColorList* color_list = [[[NSColorList alloc] initWithName:@""] autorelease]; + for (size_t i = 0; i < colors.size(); ++i) { + [color_list insertColor:[self colorFromHexColorString:colors[i]] + key:base::SysUTF8ToNSString(colors[i]) + atIndex:i]; + } + item.colorList = color_list; + } + + std::string selectedColor; + if (settings.Get("selectedColor", &selectedColor)) { + item.color = [self colorFromHexColorString:selectedColor]; } } @@ -307,25 +280,20 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (void)updateSlider:(NSSliderTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); - } - std::string label; settings.Get("label", &label); item.label = base::SysUTF8ToNSString(label); int maxValue = 100; int minValue = 0; - int initialValue = 50; + int value = 50; settings.Get("minValue", &minValue); settings.Get("maxValue", &maxValue); - settings.Get("initialValue", &initialValue); + settings.Get("value", &value); item.slider.minValue = minValue; item.slider.maxValue = maxValue; - item.slider.doubleValue = initialValue; + item.slider.doubleValue = value; } - (NSTouchBarItem*)makePopoverForID:(NSString*)id @@ -341,11 +309,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (void)updatePopover:(NSPopoverTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); - } - std::string label; gfx::Image image; if (settings.Get("label", &label)) { @@ -387,13 +350,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } } } - - NSGroupTouchBarItem* item = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel);; - } - return item; + return [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier + items:generatedItems]; } @end diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index dd8e89f4c29..12138563c4c 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -96,16 +96,33 @@ class TouchBarItem extends EventEmitter { super() this.id = `${nextItemID++}` } + + _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') + }, + enumerable: true + }) + } } TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { super(config) this.type = 'button' - this.label = config.label - this.backgroundColor = config.backgroundColor - this.labelColor = config.labelColor - this.onInteraction = config.click + const {click, label, backgroundColor} = config + this._addLiveProperty('label', label) + this._addLiveProperty('backgroundColor', backgroundColor) + if (typeof click === 'function') { + this.onInteraction = config.click + } } } @@ -113,12 +130,13 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super(config) this.type = 'colorpicker' - this.availableColors = config.availableColors - this.selectedColor = config.selectedColor + const {availableColors, change, selectedColor} = config + this._addLiveProperty('availableColors', availableColors) + this._addLiveProperty('selectedColor', selectedColor) - const {change} = config if (typeof change === 'function') { this.onInteraction = (details) => { + this._selectedColor = details.color change(details.color) } } @@ -140,16 +158,7 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { super(config) this.type = 'label' - this._label = config.label - } - - set label (newLabel) { - this._label = newLabel - this.emit('change') - } - - get label () { - return this._label + this._addLiveProperty('label', config.label) } } @@ -157,7 +166,7 @@ TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { constructor (config) { super(config) this.type = 'spacer' - this.size = config.size + this._addLiveProperty('size', config.size) } } @@ -165,7 +174,7 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { constructor (config) { super(config) this.type = 'popover' - this.label = config.label + this._addLiveProperty('label', config.label) this.showCloseButton = config.showCloseButton this.child = config.items if (!(this.child instanceof TouchBar)) { @@ -178,13 +187,15 @@ TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) this.type = 'slider' - this.minValue = config.minValue - this.maxValue = config.maxValue - this.initialValue = config.initialValue + const {change, label, minValue, maxValue, value} = config + this._addLiveProperty('label', label) + this._addLiveProperty('minValue', minValue) + this._addLiveProperty('maxValue', maxValue) + this._addLiveProperty('value', value) - const {change} = config if (typeof change === 'function') { this.onInteraction = (details) => { + this._value = details.value change(details.value) } } From 708ed9d1cd1c2dc1c3b272169915d4b60cd16125 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 15:26:10 -0800 Subject: [PATCH 49/69] Store ordered settings in AtomTouchBar --- atom/browser/native_window_mac.h | 3 --- atom/browser/native_window_mac.mm | 17 +++++++---------- atom/browser/ui/cocoa/atom_touch_bar.h | 5 +++-- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 +++++--- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 03430af57cc..bd34993fb11 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -103,7 +103,6 @@ class NativeWindowMac : public NativeWindow, void SetTouchBar( const std::vector& items) override; void RefreshTouchBarItem(const std::string& item_id) override; - std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; @@ -158,8 +157,6 @@ class NativeWindowMac : public NativeWindow, base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; - std::vector touch_bar_items_; - // Event monitor for scroll wheel event. id wheel_event_monitor_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index b3ab36ed31f..b59360ec372 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -353,7 +353,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; -- (void)resetTouchBar; +- (void)resetTouchBar:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; @end @@ -368,7 +368,10 @@ bool ScopedDisableResize::disable_resize_ = false; enable_larger_than_screen_ = enable; } -- (void)resetTouchBar { +- (void)resetTouchBar:(const std::vector&)settings { + atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self + window:shell_ + settings:settings]); self.touchBar = nil; } @@ -377,8 +380,7 @@ bool ScopedDisableResize::disable_resize_ = false; } - (NSTouchBar*)makeTouchBar { - atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); - return [atom_touch_bar_ makeTouchBarFromSettings:shell_->GetTouchBarItems()]; + return [atom_touch_bar_ makeTouchBar]; } - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar @@ -1374,18 +1376,13 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { void NativeWindowMac::SetTouchBar( const std::vector& items) { - touch_bar_items_ = items; - [window_ resetTouchBar]; + [window_ resetTouchBar:items]; } void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) { [window_ refreshTouchBarItem:item_id]; } -std::vector NativeWindowMac::GetTouchBarItems() { - return touch_bar_items_; -} - 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 d0146ec5f9f..738536678b8 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -19,15 +19,16 @@ @interface AtomTouchBar : NSObject { @protected + std::vector ordered_settings_; std::map settings_; std::map> items_; id delegate_; atom::NativeWindow* window_; } -- (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window; +- (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window settings:(const std::vector&)settings; -- (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings; +- (NSTouchBar*)makeTouchBar; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 8a746ccbe2f..277123f3bca 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -20,16 +20,18 @@ static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.touchbar.popo static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; - (id)initWithDelegate:(id)delegate - window:(atom::NativeWindow*)window { + window:(atom::NativeWindow*)window + settings:(const std::vector&)settings { if ((self = [super init])) { delegate_ = delegate; window_ = window; + ordered_settings_ = settings; } return self; } -- (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings { - NSMutableArray* identifiers = [self identifiersFromSettings:settings]; +- (NSTouchBar*)makeTouchBar { + NSMutableArray* identifiers = [self identifiersFromSettings:ordered_settings_]; return [self touchBarFromItemIdentifiers:identifiers]; } From 5fe3ac60faaa0400858c863c615d4d3183eaee6c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 15:29:34 -0800 Subject: [PATCH 50/69] Check that atom_touch_bar_ is set --- atom/browser/native_window_mac.mm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index b59360ec372..12315ae255d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -376,16 +376,20 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)refreshTouchBarItem:(const std::string&)item_id { - [atom_touch_bar_ refreshTouchBarItem:item_id]; + if (atom_touch_bar_) + [atom_touch_bar_ refreshTouchBarItem:item_id]; } - (NSTouchBar*)makeTouchBar { - return [atom_touch_bar_ makeTouchBar]; + if (atom_touch_bar_) + return [atom_touch_bar_ makeTouchBar]; + else + return nil; } - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - if (touchBar) + if (touchBar && atom_touch_bar_) return [atom_touch_bar_ makeItemForIdentifier:identifier]; else return nil; From 76f112ffc5391133c8c946529f6735a0841d9886 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 16:12:44 -0800 Subject: [PATCH 51/69] Only export TouchBar to start --- lib/browser/api/exports/electron.js | 36 ----------------------------- 1 file changed, 36 deletions(-) diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 0c6ce065b02..3f405952175 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -109,42 +109,6 @@ Object.defineProperties(exports, { return require('../touch-bar') } }, - TouchBarButton: { - enumerable: true, - get: function () { - return require('../touch-bar').Button - } - }, - TouchBarColorPicker: { - enumerable: true, - get: function () { - return require('../touch-bar').ColorPicker - } - }, - TouchBarGroup: { - enumerable: true, - get: function () { - return require('../touch-bar').Group - } - }, - TouchBarLabel: { - enumerable: true, - get: function () { - return require('../touch-bar').Label - } - }, - TouchBarPopOver: { - enumerable: true, - get: function () { - return require('../touch-bar').PopOver - } - }, - TouchBarSlider: { - enumerable: true, - get: function () { - return require('../touch-bar').Slider - } - }, Tray: { enumerable: true, get: function () { From 70d61869a5626a9a988fe64dcda9105103078ddf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 09:16:29 -0800 Subject: [PATCH 52/69] Use array to create empty NSMutableArray --- atom/browser/ui/cocoa/atom_touch_bar.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 277123f3bca..621de194eaf 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -44,7 +44,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSMutableArray*)identifiersFromSettings:(const std::vector&)dicts { - NSMutableArray* identifiers = [[NSMutableArray alloc] init]; + NSMutableArray* identifiers = [NSMutableArray array]; for (const auto& item : dicts) { std::string type; @@ -342,7 +342,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::vector items; if (!child.Get("ordereredItems", &items)) return nil; - NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; + NSMutableArray* generatedItems = [NSMutableArray array]; NSMutableArray* identList = [self identifiersFromSettings:items]; for (NSUInteger i = 0; i < [identList count]; i++) { if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { From 93bbe8e70b34cff180df861da7e32484025b1d86 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 09:22:57 -0800 Subject: [PATCH 53/69] Use device NS colors consistently --- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 621de194eaf..b93c2cc3f57 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -175,7 +175,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSColor*)colorFromHexColorString:(const std::string&)colorString { SkColor color = atom::ParseHexColor(colorString); - return skia::SkColorToCalibratedNSColor(color); + return skia::SkColorToDeviceNSColor(color); } - (NSTouchBarItem*)makeButtonForID:(NSString*)id From 8d716e8b17e269f85c93b5b54ae5c4d69c6764d2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 09:30:21 -0800 Subject: [PATCH 54/69] Register item after validating --- lib/browser/api/touch-bar.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 12138563c4c..6871c82172a 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -3,7 +3,6 @@ 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) { @@ -31,6 +30,7 @@ class TouchBar extends EventEmitter { this.windowListeners = {} this.items = {} this.ordereredItems = [] + const registerItem = (item) => { this.items[item.id] = item item.on('change', () => { @@ -41,15 +41,14 @@ class TouchBar extends EventEmitter { } } items.forEach((item) => { - this.ordereredItems.push(item) if (!(item instanceof TouchBarItem)) { throw new Error('Each item must be an instance of a TouchBarItem') } + this.ordereredItems.push(item) registerItem(item) }) } - // Called by BrowserWindow.setTouchBar _addToWindow (window) { const {id} = window @@ -84,7 +83,6 @@ class TouchBar extends EventEmitter { window._setTouchBarItems(this.ordereredItems) } - // Called by BrowserWindow.setTouchBar _removeFromWindow (window) { const removeListeners = this.windowListeners[window.id] if (removeListeners != null) removeListeners() From d1edd80ef821c936725a0e444a1d34e8c82453cf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 10:23:24 -0800 Subject: [PATCH 55/69] Use NSTouchBar itemForIdentifier to lookup existing item --- atom/browser/native_window_mac.mm | 4 +- atom/browser/ui/cocoa/atom_touch_bar.h | 5 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 136 ++++++++++++++---------- lib/browser/api/touch-bar.js | 4 +- 4 files changed, 85 insertions(+), 64 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 12315ae255d..34f4028d4a5 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -376,8 +376,8 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)refreshTouchBarItem:(const std::string&)item_id { - if (atom_touch_bar_) - [atom_touch_bar_ refreshTouchBarItem:item_id]; + if (atom_touch_bar_ && self.touchBar) + [atom_touch_bar_ refreshTouchBarItem:self.touchBar id:item_id]; } - (NSTouchBar*)makeTouchBar { diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 738536678b8..d34a1f6d473 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -21,7 +21,6 @@ @protected std::vector ordered_settings_; std::map settings_; - std::map> items_; id delegate_; atom::NativeWindow* window_; } @@ -31,9 +30,11 @@ - (NSTouchBar*)makeTouchBar; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; -- (void)refreshTouchBarItem:(const std::string&)item_id; +- (void)refreshTouchBarItem:(NSTouchBar*)touchBar id:(const std::string&)item_id; + - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; +- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id type:(const std::string&)typere; - (bool)hasItemWithID:(const std::string&)item_id; - (NSColor*)colorFromHexColorString:(const std::string&)colorString; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index b93c2cc3f57..9f76aed5f0c 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -50,29 +50,24 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { - settings_[item_id] = item; - if (type == "button") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "label") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "colorpicker") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "slider") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "popover") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "group") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "spacer") { + NSTouchBarItemIdentifier identifier = nil; + if (type == "spacer") { std::string size; item.Get("size", &size); if (size == "large") { - [identifiers addObject:NSTouchBarItemIdentifierFixedSpaceLarge]; + identifier = NSTouchBarItemIdentifierFixedSpaceLarge; } else if (size == "flexible") { - [identifiers addObject:NSTouchBarItemIdentifierFlexibleSpace]; + identifier = NSTouchBarItemIdentifierFlexibleSpace; } else { - [identifiers addObject:NSTouchBarItemIdentifierFixedSpaceSmall]; + identifier = NSTouchBarItemIdentifierFixedSpaceSmall; } + } else { + identifier = [self identifierFromID:item_id type:type]; + } + + if (identifier) { + settings_[item_id] = item; + [identifiers addObject:identifier]; } } } @@ -82,59 +77,58 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - base::scoped_nsobject item; NSString* item_id = nil; if ([identifier hasPrefix:ButtonIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - item.reset([self makeButtonForID:item_id withIdentifier:identifier]); + return [self makeButtonForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:LabelIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - item.reset([self makeLabelForID:item_id withIdentifier:identifier]); + return [self makeLabelForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:ColorPickerIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; - item.reset([self makeColorPickerForID:item_id withIdentifier:identifier]); + return [self makeColorPickerForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:SliderIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; - item.reset([self makeSliderForID:item_id withIdentifier:identifier]); + return [self makeSliderForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:PopOverIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; - item.reset([self makePopoverForID:item_id withIdentifier:identifier]); + return [self makePopoverForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:GroupIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; - item.reset([self makeGroupForID:item_id withIdentifier:identifier]); + return [self makeGroupForID:item_id withIdentifier:identifier]; } - if (item_id) - items_[[item_id UTF8String]] = item; - - return item.autorelease(); + return nil; } -- (void)refreshTouchBarItem:(const std::string&)item_id { - if (items_.find(item_id) == items_.end()) return; +- (void)refreshTouchBarItem:(NSTouchBar*)touchBar + id:(const std::string&)item_id { if (![self hasItemWithID:item_id]) return; mate::PersistentDictionary settings = settings_[item_id]; std::string item_type; settings.Get("type", &item_type); + NSTouchBarItemIdentifier identifier = [self identifierFromID:item_id + type:item_type]; + if (!identifier) return; + + NSTouchBarItem* item = [touchBar itemForIdentifier:identifier]; + if (!item) return; + if (item_type == "button") { - [self updateButton:(NSCustomTouchBarItem*)items_[item_id] - withSettings:settings]; + [self updateButton:(NSCustomTouchBarItem*)item withSettings:settings]; } else if (item_type == "label") { - [self updateLabel:(NSCustomTouchBarItem*)items_[item_id] - withSettings:settings]; + [self updateLabel:(NSCustomTouchBarItem*)item withSettings:settings]; } else if (item_type == "colorpicker") { - [self updateColorPicker:(NSColorPickerTouchBarItem*)items_[item_id] + [self updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:settings]; } else if (item_type == "slider") { - [self updateSlider:(NSSliderTouchBarItem*)items_[item_id] - withSettings:settings]; + [self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings]; } else if (item_type == "popover") { - [self updatePopover:(NSPopoverTouchBarItem*)items_[item_id] - withSettings:settings]; + [self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings]; } } @@ -169,6 +163,28 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return [identifier substringFromIndex:[prefix length]]; } +- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id + type:(const std::string&)type { + NSTouchBarItemIdentifier base_identifier = nil; + if (type == "button") + base_identifier = ButtonIdentifier; + else if (type == "label") + base_identifier = LabelIdentifier; + else if (type == "colorpicker") + base_identifier = ColorPickerIdentifier; + else if (type == "slider") + base_identifier = SliderIdentifier; + else if (type == "popover") + base_identifier = PopOverIdentifier; + else if (type == "group") + base_identifier = GroupIdentifier; + + if (base_identifier) + return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()]; + else + return nil; +} + - (bool)hasItemWithID:(const std::string&)item_id { return settings_.find(item_id) != settings_.end(); } @@ -184,14 +200,15 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary settings = settings_[s_id]; - NSCustomTouchBarItem* item = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); NSButton* button = [NSButton buttonWithTitle:@"" target:self action:@selector(buttonAction:)]; button.tag = [id floatValue]; - item.view = button; + [item setView:button]; [self updateButton:item withSettings:settings]; - return item; + return item.autorelease(); } - (void)updateButton:(NSCustomTouchBarItem*)item @@ -219,12 +236,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary item = settings_[s_id]; - NSCustomTouchBarItem* customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; - customItem.view = [NSTextField labelWithString:@""]; - [self updateLabel:customItem withSettings:item]; - - return customItem; + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setView:[NSTextField labelWithString:@""]]; + [self updateLabel:item withSettings:settings]; + return item.autorelease(); } - (void)updateLabel:(NSCustomTouchBarItem*)item @@ -241,11 +258,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary settings = settings_[s_id]; - NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; - item.target = self; - item.action = @selector(colorPickerAction:); + base::scoped_nsobject item([[NSClassFromString( + @"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setTarget:self]; + [item setAction:@selector(colorPickerAction:)]; [self updateColorPicker:item withSettings:settings]; - return item; + return item.autorelease(); } - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item @@ -273,11 +291,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary settings = settings_[s_id]; - NSSliderTouchBarItem* item = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; - item.target = self; - item.action = @selector(sliderAction:); + base::scoped_nsobject item([[NSClassFromString( + @"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setTarget:self]; + [item setAction:@selector(sliderAction:)]; [self updateSlider:item withSettings:settings]; - return item; + return item.autorelease(); } - (void)updateSlider:(NSSliderTouchBarItem*)item @@ -304,9 +323,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary settings = settings_[s_id]; - NSPopoverTouchBarItem* item = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; + base::scoped_nsobject item([[NSClassFromString( + @"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]); [self updatePopover:item withSettings:settings]; - return item; + return item.autorelease(); } - (void)updatePopover:(NSPopoverTouchBarItem*)item diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 6871c82172a..e5be1f3b49c 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -147,7 +147,7 @@ TouchBar.Group = class TouchBarGroup extends TouchBarItem { this.type = 'group' this.child = config.items if (!(this.child instanceof TouchBar)) { - this.child = new TouchBar(this.items) + this.child = new TouchBar(this.child) } } } @@ -176,7 +176,7 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { this.showCloseButton = config.showCloseButton this.child = config.items if (!(this.child instanceof TouchBar)) { - this.child = new TouchBar(this.items) + this.child = new TouchBar(this.child) } } } From 79b17c2cd9502c4fbb30034df8b452374f5ac0f6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 10:25:39 -0800 Subject: [PATCH 56/69] Expose TouchBar on remote module --- lib/renderer/api/remote.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index 240aa22d892..e524ecc7208 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -360,6 +360,7 @@ const browserModules = [ 'screen', 'session', 'systemPreferences', + 'TouchBar', 'Tray', 'webContents' ] From ecc0478e3c16e451bd63789429943b74f7e62037 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 10:28:07 -0800 Subject: [PATCH 57/69] Match Popover casing in macOS APIs --- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 9f76aed5f0c..bf0c047ecfc 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -16,7 +16,7 @@ static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.touchbar.butto static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.touchbar.colorpicker."; static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.touchbar.group."; static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.touchbar.label."; -static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.touchbar.popover."; +static NSTouchBarItemIdentifier PopoverIdentifier = @"com.electron.touchbar.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; - (id)initWithDelegate:(id)delegate @@ -91,8 +91,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } else if ([identifier hasPrefix:SliderIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; return [self makeSliderForID:item_id withIdentifier:identifier]; - } else if ([identifier hasPrefix:PopOverIdentifier]) { - item_id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + } else if ([identifier hasPrefix:PopoverIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:PopoverIdentifier]; return [self makePopoverForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:GroupIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; @@ -175,7 +175,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide else if (type == "slider") base_identifier = SliderIdentifier; else if (type == "popover") - base_identifier = PopOverIdentifier; + base_identifier = PopoverIdentifier; else if (type == "group") base_identifier = GroupIdentifier; From 506b42b5633e69babffbe5b079ab658378cb741f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 10:32:55 -0800 Subject: [PATCH 58/69] :art: --- atom/browser/ui/cocoa/atom_touch_bar.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index bf0c047ecfc..c16135c9adc 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -159,7 +159,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide window_->NotifyTouchBarItemInteraction([item_id UTF8String], details); } -- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { +- (NSString*)idFromIdentifier:(NSString*)identifier + withPrefix:(NSString*)prefix { return [identifier substringFromIndex:[prefix length]]; } From a34f9d35836bd4852ae8efee9269616b9c14c9d0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 12:56:23 -0800 Subject: [PATCH 59/69] Support icon property to match MenuItem --- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- lib/browser/api/touch-bar.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index c16135c9adc..9aaa534e425 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -227,7 +227,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } gfx::Image image; - if (settings.Get("image", &image)) { + if (settings.Get("icon", &image)) { button.image = image.AsNSImage(); } } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index e5be1f3b49c..83a8028b82e 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -115,9 +115,10 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { super(config) this.type = 'button' - const {click, label, backgroundColor} = config + const {click, icon, label, backgroundColor} = config this._addLiveProperty('label', label) this._addLiveProperty('backgroundColor', backgroundColor) + this._addLiveProperty('icon', icon) if (typeof click === 'function') { this.onInteraction = config.click } From ce12dcd3b42dbee2a4d6bf2f0e7fbd17a31b67ba Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 13:37:34 -0800 Subject: [PATCH 60/69] Add live popover icon property --- atom/browser/ui/cocoa/atom_touch_bar.mm | 5 +++-- lib/browser/api/touch-bar.js | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 9aaa534e425..9c3ac9eeeeb 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -333,10 +333,11 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (void)updatePopover:(NSPopoverTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { std::string label; - gfx::Image image; if (settings.Get("label", &label)) { item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); - } else if (settings.Get("image", &image)) { + } + gfx::Image image; + if (settings.Get("icon", &image)) { item.collapsedRepresentationImage = image.AsNSImage(); } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 83a8028b82e..abcf8289e62 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -174,6 +174,7 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { super(config) this.type = 'popover' this._addLiveProperty('label', config.label) + this._addLiveProperty('icon', config.icon) this.showCloseButton = config.showCloseButton this.child = config.items if (!(this.child instanceof TouchBar)) { From eb03ab561d6a1edb5eeba36a68822ee5e7df9e85 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 14:29:59 -0800 Subject: [PATCH 61/69] Make config optional --- lib/browser/api/touch-bar.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index abcf8289e62..1698844c207 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -90,7 +90,7 @@ class TouchBar extends EventEmitter { } class TouchBarItem extends EventEmitter { - constructor (config) { + constructor () { super() this.id = `${nextItemID++}` } @@ -113,7 +113,8 @@ class TouchBarItem extends EventEmitter { TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'button' const {click, icon, label, backgroundColor} = config this._addLiveProperty('label', label) @@ -127,7 +128,8 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'colorpicker' const {availableColors, change, selectedColor} = config this._addLiveProperty('availableColors', availableColors) @@ -144,7 +146,8 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { TouchBar.Group = class TouchBarGroup extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'group' this.child = config.items if (!(this.child instanceof TouchBar)) { @@ -155,7 +158,8 @@ TouchBar.Group = class TouchBarGroup extends TouchBarItem { TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'label' this._addLiveProperty('label', config.label) } @@ -163,7 +167,8 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'spacer' this._addLiveProperty('size', config.size) } @@ -171,7 +176,8 @@ TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { TouchBar.Popover = class TouchBarPopover extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'popover' this._addLiveProperty('label', config.label) this._addLiveProperty('icon', config.icon) @@ -185,7 +191,8 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'slider' const {change, label, minValue, maxValue, value} = config this._addLiveProperty('label', label) From 81ecd4499cf02e04b6b8c1f4582e67263d9d7c75 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 14:32:38 -0800 Subject: [PATCH 62/69] Make exported properties match class names --- lib/browser/api/touch-bar.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 1698844c207..4957f248d6c 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -111,7 +111,7 @@ class TouchBarItem extends EventEmitter { } } -TouchBar.Button = class TouchBarButton extends TouchBarItem { +TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -126,7 +126,7 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { } } -TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { +TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -144,7 +144,7 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { } } -TouchBar.Group = class TouchBarGroup extends TouchBarItem { +TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -156,7 +156,7 @@ TouchBar.Group = class TouchBarGroup extends TouchBarItem { } } -TouchBar.Label = class TouchBarLabel extends TouchBarItem { +TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -165,7 +165,7 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } -TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { +TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -174,7 +174,7 @@ TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { } } -TouchBar.Popover = class TouchBarPopover extends TouchBarItem { +TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -189,7 +189,7 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { } } -TouchBar.Slider = class TouchBarSlider extends TouchBarItem { +TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { constructor (config) { super() if (config == null) config = {} From 002369576ff94e3b13a2d55f9191625254e35760 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 09:54:46 -0800 Subject: [PATCH 63/69] Add initial touch bar docs --- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- docs/api/browser-window.md | 3 +- docs/api/touch-bar-button.md | 33 +++++++ docs/api/touch-bar-color-picker.md | 28 ++++++ docs/api/touch-bar-group.md | 10 ++ docs/api/touch-bar-label.md | 19 ++++ docs/api/touch-bar-popover.md | 28 ++++++ docs/api/touch-bar-slider.md | 38 ++++++++ docs/api/touch-bar-spacer.md | 13 +++ docs/api/touch-bar.md | 123 +++++++++++++++++++----- lib/browser/api/touch-bar.js | 18 ++-- 11 files changed, 280 insertions(+), 35 deletions(-) create mode 100644 docs/api/touch-bar-button.md create mode 100644 docs/api/touch-bar-color-picker.md create mode 100644 docs/api/touch-bar-group.md create mode 100644 docs/api/touch-bar-label.md create mode 100644 docs/api/touch-bar-popover.md create mode 100644 docs/api/touch-bar-slider.md create mode 100644 docs/api/touch-bar-spacer.md diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 9c3ac9eeeeb..9f006bf7dfd 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -341,7 +341,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide item.collapsedRepresentationImage = image.AsNSImage(); } - bool showCloseButton; + bool showCloseButton = true; if (settings.Get("showCloseButton", &showCloseButton)) { item.showsCloseButton = showCloseButton; } diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index fc1c705b21d..677e2a773ba 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1270,7 +1270,8 @@ will remove the vibrancy effect on the window. * `touchBar` TouchBar -Sets the touchBar layout for the current window. +Sets the touchBar layout for the current window. Specifying `null` or +`undefined` clears the touch bar. [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look diff --git a/docs/api/touch-bar-button.md b/docs/api/touch-bar-button.md new file mode 100644 index 00000000000..018c267f300 --- /dev/null +++ b/docs/api/touch-bar-button.md @@ -0,0 +1,33 @@ +## Class: TouchBarButton + +> Create a button in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarButton(options)` + +* `options` Object + * `label` String (optional) - Button text. + * `backgroundColor` String (optional) - Button background color in hex format, + i.e `#ABCDEF`. + * `icon` NativeImage (optional) - Button icon. + * `click` Function (optional) - Function to call when the button is clicked. + +### Instance Properties + +The following properties are available on instances of `TouchBarButton`: + +#### `touchBarButton.label` + +The button's current text. Changing this value immediately updates the button +in the touch bar. + +#### `touchBarButton.backgroundColor` + +The button's current background color. Changing this value immediately updates +the button in the touch bar. + +#### `touchBarButton.icon` + +The button's current icon. Changing this value immediately updates the button +in the touch bar. diff --git a/docs/api/touch-bar-color-picker.md b/docs/api/touch-bar-color-picker.md new file mode 100644 index 00000000000..42a2ace22b9 --- /dev/null +++ b/docs/api/touch-bar-color-picker.md @@ -0,0 +1,28 @@ +## Class: TouchBarColorPicker + +> Create a color picker in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarColorPicker(options)` + +* `options` Object + * `availableColors` String[] (optional) - Array of hex color strings to + appear as possible colors to select. + * `selectedColor` String (optional) - The selected hex color in the picker, + i.e `#ABCDEF`. + * `change` Function (optional) - Function to call when a color is selected. + +### Instance Properties + +The following properties are available on instances of `TouchBarColorPicker`: + +#### `touchBarColorPicker.availableColors` + +The color picker's available colors to select. Changing this value immediately +updates the color picker in the touch bar. + +#### `touchBarColorPicker.selectedColor` + +The color picker's currently selected color. Changing this value immediately +updates the color picker in the touch bar. diff --git a/docs/api/touch-bar-group.md b/docs/api/touch-bar-group.md new file mode 100644 index 00000000000..d765effa899 --- /dev/null +++ b/docs/api/touch-bar-group.md @@ -0,0 +1,10 @@ +## Class: TouchBarGroup + +> Create a group in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarGroup(options)` + +* `options` Object + * `items` TouchBar - Items to display as a group. diff --git a/docs/api/touch-bar-label.md b/docs/api/touch-bar-label.md new file mode 100644 index 00000000000..7d0137c35aa --- /dev/null +++ b/docs/api/touch-bar-label.md @@ -0,0 +1,19 @@ +## Class: TouchBarLabel + +> Create a label in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarLabel(options)` + +* `options` Object + * `label` String (optional) - Text to display. + +### Instance Properties + +The following properties are available on instances of `TouchBarLabel`: + +#### `touchBarLabel.label` + +The label's current text. Changing this value immediately updates the label in +the touch bar. diff --git a/docs/api/touch-bar-popover.md b/docs/api/touch-bar-popover.md new file mode 100644 index 00000000000..d065da05e14 --- /dev/null +++ b/docs/api/touch-bar-popover.md @@ -0,0 +1,28 @@ +## Class: TouchBarPopover + +> Create a popover in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarPopover(options)` + +* `options` Object + * `label` String (optional) - Popover button text. + * `icon` NativeImage (optional) - Popover button icon. + * `items` TouchBar (optional) - Items to display in the popover. + * `showCloseButton` Boolean (optional) - `true` to display a close button + on the left of the popover, `false` to not show it. Default is `true`. + +### Instance Properties + +The following properties are available on instances of `TouchBarPopover`: + +#### `touchBarPopover.label` + +The popover's current button text. Changing this value immediately updates the +popover in the touch bar. + +#### `touchBarPopover.icon` + +The popover's current button icon. Changing this value immediately updates the +popover in the touch bar. diff --git a/docs/api/touch-bar-slider.md b/docs/api/touch-bar-slider.md new file mode 100644 index 00000000000..1be9b45716d --- /dev/null +++ b/docs/api/touch-bar-slider.md @@ -0,0 +1,38 @@ +## Class: TouchBarSlider + +> Create a slider in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSlider(options)` + +* `options` Object + * `label` String (optional) - Label text. + * `value` Integer (optional) - Selected value. + * `minValue` Integer (optional) - Minimum value. + * `maxValue` Integer (optional) - Maximum value. + * `change` Function (optional) - Function to call when the slider is changed. + +### Instance Properties + +The following properties are available on instances of `TouchBarSlider`: + +#### `touchBarSlider.label` + +The slider's current text. Changing this value immediately updates the slider +in the touch bar. + +#### `touchBarSlider.value` + +The slider's current value. Changing this value immediately updates the slider +in the touch bar. + +#### `touchBarSlider.minValue` + +The slider's current minimum value. Changing this value immediately updates the +slider in the touch bar. + +#### `touchBarSlider.maxValue` + +The slider's current maximum value. Changing this value immediately updates the +slider in the touch bar. diff --git a/docs/api/touch-bar-spacer.md b/docs/api/touch-bar-spacer.md new file mode 100644 index 00000000000..56148a44e1b --- /dev/null +++ b/docs/api/touch-bar-spacer.md @@ -0,0 +1,13 @@ +## Class: TouchBarSpacer + +> Create a spacer between two items in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSlider(options)` + +* `options` Object + * `size` String (optional) - Size of spacer, possible values are: + * `small` - Small space between items. + * `large` - Large space between items. + * `flexible` - Take up all available space. diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 68bdaf88a11..147087bb02c 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -6,39 +6,114 @@ Process: [Main](../tutorial/quick-start.md#main-process) ### `new TouchBar(items)` -* `items` (TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopOver | TouchBarSlider)[] +* `items` (TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopOver | TouchBarSlider | TouchBarSpacer)[] -Creates a new touch bar. Note any changes to the TouchBar instance -will not affect the rendered TouchBar. To affect the rendered -TouchBar you **must** use either methods on the TouchBar or methods -on the TouchBar* items - -### Instance Methods - -The `menu` object has the following instance methods: - -#### `touchBar.destroy()` - -Immediately destroys the TouchBar instance and will reset the rendered -touch bar. +Creates a new touch bar with the specified items. Use +`BrowserWindow.setTouchBar` to add the `TouchBar` to a window. ## Examples -The `TouchBar` class is only available in the main process, it is not currently possible to use in the renderer process **even** through the remote module. +The `TouchBar` class is only available in the main process, it is not currently +possible to use in the renderer process **even** through the remote module. -### Main process - -An example of creating a touch bar in the main process: +Below is an example of a simple slot machine touch bar game with a button +and some labels. ```javascript -const {TouchBar, TouchBarButton} = require('electron') +const {app, BrowserWindow, TouchBar} = require('electron') + +const {TouchBarLabel, TouchBarButton, TouchBarSpacer} = TouchBar + +let spinning = false + +// Reel labels +const reel1 = new TouchBarLabel() +const reel2 = new TouchBarLabel() +const reel3 = new TouchBarLabel() + +// Spin result label +const result = new TouchBarLabel() + +// Spin button +const spin = new TouchBarButton({ + label: '🎰 Spin', + backgroundColor: '#7851A9', + click: () => { + // Ignore clicks if already spinning + if (spinning) { + return + } + + spinning = true + result.label = '' + + let timeout = 10 + const spinLength = 4 * 1000 // 4 seconds + const startTime = Date.now() + + const spinReels = () => { + updateReels() + + if ((Date.now() - startTime) >= spinLength) { + finishSpin() + } else { + // Slow down a bit on each spin + timeout *= 1.1 + setTimeout(spinReels, timeout) + } + } + + spinReels() + } +}) + +const getRandomValue = () => { + const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇'] + return values[Math.floor(Math.random() * values.length)] +} + +const updateReels = () => { + reel1.label = getRandomValue() + reel2.label = getRandomValue() + reel3.label = getRandomValue() +} + +const finishSpin = () => { + const uniqueValues = new Set([reel1.label, reel2.label, reel3.label]).size + if (uniqueValues === 1) { + // All 3 values are the same + result.label = '💰 Jackpot!' + } else if (uniqueValues === 2) { + // 2 values are the same + result.label = '😍 Winner!' + } else { + // No values are the same + result.label = '🙁 Spin Again' + } + spinning = false +} const touchBar = new TouchBar([ - new TouchBarButton({ - label: 'Example Button', - click: () => console.log('I was clicked') - }) + spin, + new TouchBarSpacer({size: 'large'}), + reel1, + reel2, + reel3, + new TouchBarSpacer({size: 'large'}), + result ]) -mainWindow.setTouchBar(touchBar) +let window + +app.once('ready', () => { + window = new BrowserWindow({ + frame: false, + titleBarStyle: 'hidden-inset', + width: 200, + height: 200, + backgroundColor: '#000' + }) + window.loadURL('about:blank') + window.setTouchBar(touchBar) +}) ``` diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 4957f248d6c..905179d9c57 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -165,15 +165,6 @@ TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { } } -TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { - constructor (config) { - super() - if (config == null) config = {} - this.type = 'spacer' - this._addLiveProperty('size', config.size) - } -} - TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { constructor (config) { super() @@ -209,4 +200,13 @@ TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { } } +TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'spacer' + this.size = config.size + } +} + module.exports = TouchBar From c349aeff80951561dc84bc880bca7595c011e9b8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 10:22:25 -0800 Subject: [PATCH 64/69] Add initial touch bar specs --- docs/api/touch-bar.md | 3 --- lib/browser/api/touch-bar.js | 8 +++--- spec/api-touch-bar-spec.js | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 spec/api-touch-bar-spec.js diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 147087bb02c..a499703fd5d 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -13,9 +13,6 @@ Creates a new touch bar with the specified items. Use ## Examples -The `TouchBar` class is only available in the main process, it is not currently -possible to use in the renderer process **even** through the remote module. - Below is an example of a simple slot machine touch bar game with a button and some labels. diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 905179d9c57..d4c8c5c44c2 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -24,7 +24,7 @@ class TouchBar extends EventEmitter { super() if (!Array.isArray(items)) { - throw new Error('The items object provided has to be an array') + throw new Error('Must specify items array as first argument') } this.windowListeners = {} @@ -42,7 +42,7 @@ class TouchBar extends EventEmitter { } items.forEach((item) => { if (!(item instanceof TouchBarItem)) { - throw new Error('Each item must be an instance of a TouchBarItem') + throw new Error('Each item must be an instance of TouchBarItem') } this.ordereredItems.push(item) registerItem(item) @@ -121,7 +121,9 @@ TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { this._addLiveProperty('backgroundColor', backgroundColor) this._addLiveProperty('icon', icon) if (typeof click === 'function') { - this.onInteraction = config.click + this.onInteraction = () => { + config.click() + } } } } diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js new file mode 100644 index 00000000000..b658c0c2486 --- /dev/null +++ b/spec/api-touch-bar-spec.js @@ -0,0 +1,50 @@ +const assert = require('assert') +const {BrowserWindow, TouchBar} = require('electron').remote +const {closeWindow} = require('./window-helpers') + +const {TouchBarButton, TouchBarColorPicker, TouchBarGroup} = TouchBar +const {TouchBarLabel, TouchBarPopover, TouchBarSlider, TouchBarSpacer} = TouchBar + +describe('TouchBar module', function () { + it('throws an error when created without an items array', function () { + assert.throws(() => { + const touchBar = new TouchBar() + touchBar.toString() + }, /Must specify items array as first argument/) + }) + + it('throws an error when created with invalid items', function () { + assert.throws(() => { + const touchBar = new TouchBar([1, true, {}, []]) + touchBar.toString() + }, /Each item must be an instance of TouchBarItem/) + }) + + describe('BrowserWindow behavior', function () { + let window + + beforeEach(function () { + window = new BrowserWindow() + }) + + afterEach(function () { + window.setTouchBar(null) + return closeWindow(window).then(function () { window = null }) + }) + + it('can be added to and removed from a window', function () { + const touchBar = new TouchBar([ + new TouchBarButton({label: 'foo', backgroundColor: '#F00', click: () => {}}), + new TouchBarColorPicker({selectedColor: '#F00', change: () => {}}), + new TouchBarGroup({items: new TouchBar([new TouchBarLabel({label: 'hello'})])}), + new TouchBarLabel({label: 'bar'}), + new TouchBarPopover({items: new TouchBar([new TouchBarButton({label: 'pop'})])}), + new TouchBarSlider({label: 'slide', value: 5, minValue: 2, maxValue: 75, change: () => {}}), + new TouchBarSpacer({size: 'large'}) + ]) + window.setTouchBar(touchBar) + window.setTouchBar() + window.setTouchBar(new TouchBar([new TouchBarLabel({label: 'two'})])) + }) + }) +}) From ca2898a60e23a40d1a77e6d13710f29c98e53d6e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 10:49:42 -0800 Subject: [PATCH 65/69] Check that window responds to touchBar selector for pre-10.12.1 compat --- atom/browser/native_window_mac.mm | 2 ++ spec/api-touch-bar-spec.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 34f4028d4a5..d2ca6b8621d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -369,6 +369,8 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)resetTouchBar:(const std::vector&)settings { + if (![self respondsToSelector:@selector(touchBar)]) return; + atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_ settings:settings]); diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index b658c0c2486..cdca2d47c44 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -33,16 +33,18 @@ describe('TouchBar module', function () { }) it('can be added to and removed from a window', function () { + const label = new TouchBarLabel({label: 'bar'}) const touchBar = new TouchBar([ new TouchBarButton({label: 'foo', backgroundColor: '#F00', click: () => {}}), new TouchBarColorPicker({selectedColor: '#F00', change: () => {}}), new TouchBarGroup({items: new TouchBar([new TouchBarLabel({label: 'hello'})])}), - new TouchBarLabel({label: 'bar'}), + label, new TouchBarPopover({items: new TouchBar([new TouchBarButton({label: 'pop'})])}), new TouchBarSlider({label: 'slide', value: 5, minValue: 2, maxValue: 75, change: () => {}}), new TouchBarSpacer({size: 'large'}) ]) window.setTouchBar(touchBar) + label.label = 'baz' window.setTouchBar() window.setTouchBar(new TouchBar([new TouchBarLabel({label: 'two'})])) }) From d9a8c15c69907860624e6bbb6468ffa62bb39a35 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 10:54:21 -0800 Subject: [PATCH 66/69] Add clover to possible values --- docs/api/touch-bar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index a499703fd5d..fa62b8dc6cc 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -65,7 +65,7 @@ const spin = new TouchBarButton({ }) const getRandomValue = () => { - const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇'] + const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇', '🍀'] return values[Math.floor(Math.random() * values.length)] } From fde310f50d9a427eaebe6439a35e6a46e995780e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 14:04:55 -0800 Subject: [PATCH 67/69] Mention it only effects 10.12.1 with touch bar --- docs/api/browser-window.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 677e2a773ba..398dd3b7464 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1271,7 +1271,8 @@ will remove the vibrancy effect on the window. * `touchBar` TouchBar Sets the touchBar layout for the current window. Specifying `null` or -`undefined` clears the touch bar. +`undefined` clears the touch bar. This method only has an effect if the +machine has a touch bar and is running on macOS 10.12.1+. [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look From edebb32014b00c7653fac05cb1924a5c245843dc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 14:07:59 -0800 Subject: [PATCH 68/69] Drop nullable for consistency --- atom/browser/native_window_mac.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index d2ca6b8621d..c88469a9c8a 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -389,8 +389,8 @@ bool ScopedDisableResize::disable_resize_ = false; return nil; } -- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar - makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { +- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { if (touchBar && atom_touch_bar_) return [atom_touch_bar_ makeItemForIdentifier:identifier]; else From 9f323104e72ad77dff16f12951fd6d56249162c2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 14:11:09 -0800 Subject: [PATCH 69/69] Remove unused include --- atom/browser/native_window_observer.h | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 7f1f5aace82..3b8d86e6fb0 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -6,7 +6,6 @@ #define ATOM_BROWSER_NATIVE_WINDOW_OBSERVER_H_ #include -#include #include "base/strings/string16.h" #include "base/values.h"