diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index b2f8dc6105ed..33a6d8c34367 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,72 @@ 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 == "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); + if (selectedIndex >= 0 && selectedIndex < control.segmentCount) + control.selectedSegment = selectedIndex; +} + @end 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 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. diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index ee7388f17756..0d428cb0f01f 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 diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index cdca2d47c446..a09e0314c424 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,13 @@ 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: 5 + }), + new TouchBarSegmentedControl({segments: []}) ]) window.setTouchBar(touchBar) label.label = 'baz'