From 1c027c526b6b09cd7091824b4666d7ee3a62ee91 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 10 Mar 2017 17:40:39 +1100 Subject: [PATCH 1/7] Add segmented control implementation --- atom/browser/ui/cocoa/atom_touch_bar.mm | 86 ++++++++++++++++++++++++- lib/browser/api/touch-bar.js | 19 ++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index b2f8dc6105ed..37dc6e7f1874 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -18,6 +18,7 @@ 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."; +static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touchbar.segmentedcontrol."; - (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window @@ -97,6 +98,9 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } else if ([identifier hasPrefix:GroupIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; return [self makeGroupForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:SegmentedControlIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:SegmentedControlIdentifier]; + return [self makeSegmentedControlForID:item_id withIdentifier:identifier]; } return nil; @@ -129,7 +133,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide [self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings]; } else if (item_type == "popover") { [self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings]; - } + } else if (item_type == "segmented_control") + [self updateSegmentedControl:(NSCustomTouchBarItem*)item withSettings:settings]; } - (void)buttonAction:(id)sender { @@ -164,6 +169,14 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return [identifier substringFromIndex:[prefix length]]; } +- (void)segmentedControlAction:(id)sender { + NSString* item_id = [NSString stringWithFormat:@"%ld", ((NSSegmentedControl*)sender).tag]; + base::DictionaryValue details; + details.SetInteger("selectedIndex", ((NSSegmentedControl*)sender).selectedSegment); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], + details); +} + - (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id type:(const std::string&)type { NSTouchBarItemIdentifier base_identifier = nil; @@ -179,6 +192,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide base_identifier = PopoverIdentifier; else if (type == "group") base_identifier = GroupIdentifier; + else if (type == "segmented_control") + base_identifier = SegmentedControlIdentifier; if (base_identifier) return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()]; @@ -384,4 +399,73 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide items:generatedItems]; } +- (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + + NSSegmentedControl* control = [NSSegmentedControl segmentedControlWithLabels:[NSMutableArray array] + trackingMode:NSSegmentSwitchTrackingSelectOne + target:self + action:@selector(segmentedControlAction:)]; + control.tag = [id floatValue]; + [item setView:control]; + + [self updateSegmentedControl:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updateSegmentedControl:(NSCustomTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + + NSSegmentedControl* control = item.view; + + std::string segmentStyle; + settings.Get("segmentStyle", &segmentStyle); + if (segmentStyle == "automatic") + control.segmentStyle = NSSegmentStyleAutomatic; + else if (segmentStyle == "rounded") + control.segmentStyle = NSSegmentStyleRounded; + else if (segmentStyle == "textured-rounded") + control.segmentStyle = NSSegmentStyleTexturedRounded; + else if (segmentStyle == "round-rect") + control.segmentStyle = NSSegmentStyleRoundRect; + else if (segmentStyle == "textured-square") + control.segmentStyle = NSSegmentStyleTexturedSquare; + else if (segmentStyle == "capsule") + control.segmentStyle = NSSegmentStyleCapsule; + else if (segmentStyle == "small-square") + control.segmentStyle = NSSegmentStyleSmallSquare; + else if (segmentStyle == "separated") + control.segmentStyle = NSSegmentStyleSeparated; + else + control.segmentStyle = NSSegmentStyleAutomatic; + + std::vector segments; + settings.Get("segments", &segments); + + control.segmentCount = segments.size(); + for (int i = 0; i < (int)segments.size(); i++) { + std::string label; + gfx::Image image; + bool enabled = true; + segments[i].Get("enabled", &enabled); + if (segments[i].Get("label", &label)) { + [control setLabel:base::SysUTF8ToNSString(label) forSegment:i]; + } else if (segments[i].Get("icon", &image)) { + [control setImage:image.AsNSImage() forSegment:i]; + [control setImageScaling:NSImageScaleProportionallyUpOrDown forSegment:i]; + } + [control setEnabled:enabled forSegment:i]; + } + + int selectedIndex = 0; + settings.Get("selectedIndex", &selectedIndex); + control.selectedSegment = selectedIndex; +} + @end diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index ee7388f17756..a6e2e593ccd5 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -212,4 +212,23 @@ TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { } } +TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + const {segmentStyle, segments, selectedIndex, change} = config + this.type = 'segmented_control' + this._addLiveProperty('segmentStyle', segmentStyle); + this._addLiveProperty('segments', segments || []); + this._addLiveProperty('selectedIndex', selectedIndex) + + if (typeof change === 'function') { + this.onInteraction = (details) => { + this._selectedIndex = details.selectedIndex; + change(details.selectedIndex); + } + } + } +} + module.exports = TouchBar From e6a66b6006103817ff4275df3b793344bb3b5d87 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 10 Mar 2017 17:50:23 +1100 Subject: [PATCH 2/7] Add docs for segmented control touch bar item --- .../structures/segmented-control-segment.md | 5 +++ docs/api/touch-bar-segmented-control.md | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 docs/api/structures/segmented-control-segment.md create mode 100644 docs/api/touch-bar-segmented-control.md diff --git a/docs/api/structures/segmented-control-segment.md b/docs/api/structures/segmented-control-segment.md new file mode 100644 index 000000000000..ae01a07f32d2 --- /dev/null +++ b/docs/api/structures/segmented-control-segment.md @@ -0,0 +1,5 @@ +# SegmentedControlSegment Object + +* `label` String - (Optional) The text to appear in this segment +* `icon` NativeImage - (Optional) The image to appear in this segment +* `enabled` Boolean - (Optional) Whether this segment is selectable. Default: true diff --git a/docs/api/touch-bar-segmented-control.md b/docs/api/touch-bar-segmented-control.md new file mode 100644 index 000000000000..8f30f77169e8 --- /dev/null +++ b/docs/api/touch-bar-segmented-control.md @@ -0,0 +1,41 @@ +## Class: TouchBarSegmentedControl + +> Create a segmented control (a button group) where one button has a selected state + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSegmentedControl(options)` + +* `options` Object + * `segmentStyle` String - (Optional) Style of the segments: + * `automatic` - Default + * `rounded` + * `textured-rounded` + * `round-rect` + * `textured-square` + * `capsule` + * `small-square` + * `separated` + * `segments` [SegmentedControlSegment[]](structures/segmented-control-segment.md) - An array of segments to place in this control + * `selectedIndex` Integer (Optional) - The index of the currently selected segment, will update automatically with user interaction + * `change` Function - Called when the user selects a new segment + * `selectedIndex` - The index of the segment the user selected + +### Instance Properties + +The following properties are available on instances of `TouchBarSegmentedControl`: + +#### `touchBarSegmentedControl.segmentStyle` + +A `String` representing the controls current segment style. Updating this value immediately updates the control +in the touch bar. + +#### `touchBarSegmentedControl.segments` + +A `SegmentedControlSegment[]` array representing the segments in this control. Updating this value immediately +updates the control in the touch bar. Updating deep properties inside this array **does not update the touch bar**. + +#### `touchBarSegmentedControl.selectedIndex` + +An `Integer` representing the currently selected segment. Changing this value immediately updates the control +in the touch bar. User interaction with the touch bar will update this value automatically. From bfe63d7a884ea573c4a979a60cdc857f1a2c1f47 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 10 Mar 2017 17:56:26 +1100 Subject: [PATCH 3/7] Fix linting --- lib/browser/api/touch-bar.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index a6e2e593ccd5..0d428cb0f01f 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -218,14 +218,14 @@ TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends Touch if (config == null) config = {} const {segmentStyle, segments, selectedIndex, change} = config this.type = 'segmented_control' - this._addLiveProperty('segmentStyle', segmentStyle); - this._addLiveProperty('segments', segments || []); + this._addLiveProperty('segmentStyle', segmentStyle) + this._addLiveProperty('segments', segments || []) this._addLiveProperty('selectedIndex', selectedIndex) if (typeof change === 'function') { this.onInteraction = (details) => { - this._selectedIndex = details.selectedIndex; - change(details.selectedIndex); + this._selectedIndex = details.selectedIndex + change(details.selectedIndex) } } } From 458e4be77c9be1d51b00f75444785d90414924d9 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Mar 2017 09:53:47 -0800 Subject: [PATCH 4/7] Add forward declaration for 10.12 API --- atom/browser/ui/cocoa/touch_bar_forward_declarations.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index 68693cb593ba..8c7b85e3a95c 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -144,6 +144,15 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @end +@interface NSSegmentedControl (TouchBarSDK) + ++ (instancetype)segmentedControlWithLabels:(NSArray*)labels + trackingMode:(NSSegmentSwitchTracking)trackingMode + target:(id)target + action:(SEL)action; + +@end + @protocol NSTouchBarDelegate @optional From 3b2faf7b8941f4bf011c28780c5c4658cde150a3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Mar 2017 10:04:22 -0800 Subject: [PATCH 5/7] Add initial TouchBarSegmentedControl spec --- spec/api-touch-bar-spec.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index cdca2d47c446..fa2c2a8eec0a 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -3,7 +3,7 @@ const {BrowserWindow, TouchBar} = require('electron').remote const {closeWindow} = require('./window-helpers') const {TouchBarButton, TouchBarColorPicker, TouchBarGroup} = TouchBar -const {TouchBarLabel, TouchBarPopover, TouchBarSlider, TouchBarSpacer} = TouchBar +const {TouchBarLabel, TouchBarPopover, TouchBarSegmentedControl, TouchBarSlider, TouchBarSpacer} = TouchBar describe('TouchBar module', function () { it('throws an error when created without an items array', function () { @@ -41,7 +41,12 @@ describe('TouchBar module', function () { 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'}) + new TouchBarSpacer({size: 'large'}), + new TouchBarSegmentedControl({ + segmentStyle: 'capsule', + segments: [{label: 'baz', enabled: false}], + selectedIndex: 0 + }) ]) window.setTouchBar(touchBar) label.label = 'baz' From 3349e32196cabc3fb1d2371cab4d8872fbfbdb28 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Mar 2017 10:09:14 -0800 Subject: [PATCH 6/7] Verify selectedIndex to prevent NSRangeException --- atom/browser/ui/cocoa/atom_touch_bar.mm | 3 ++- spec/api-touch-bar-spec.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 37dc6e7f1874..d6f7c1c1d761 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -465,7 +465,8 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc int selectedIndex = 0; settings.Get("selectedIndex", &selectedIndex); - control.selectedSegment = selectedIndex; + if (selectedIndex >= 0 && selectedIndex < control.segmentCount) + control.selectedSegment = selectedIndex; } @end diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index fa2c2a8eec0a..a09e0314c424 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -45,8 +45,9 @@ describe('TouchBar module', function () { new TouchBarSegmentedControl({ segmentStyle: 'capsule', segments: [{label: 'baz', enabled: false}], - selectedIndex: 0 - }) + selectedIndex: 5 + }), + new TouchBarSegmentedControl({segments: []}) ]) window.setTouchBar(touchBar) label.label = 'baz' From 5b1d5b39a2cce41562ade569462b2d5ec0a5464b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Mar 2017 10:11:10 -0800 Subject: [PATCH 7/7] Set automatic via else block --- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index d6f7c1c1d761..33a6d8c34367 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -400,7 +400,7 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc } - (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -420,15 +420,13 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc } - (void)updateSegmentedControl:(NSCustomTouchBarItem*)item - withSettings:(const mate::PersistentDictionary&)settings { + withSettings:(const mate::PersistentDictionary&)settings { NSSegmentedControl* control = item.view; std::string segmentStyle; settings.Get("segmentStyle", &segmentStyle); - if (segmentStyle == "automatic") - control.segmentStyle = NSSegmentStyleAutomatic; - else if (segmentStyle == "rounded") + if (segmentStyle == "rounded") control.segmentStyle = NSSegmentStyleRounded; else if (segmentStyle == "textured-rounded") control.segmentStyle = NSSegmentStyleTexturedRounded;