New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Touch Bar Support #987
Conversation
Thank you for the pull request. This is a good addition, we've been discussing it in issue #922 |
if ([control isKindOfClass:[NSButton class]]) { | ||
NSButton *button = (NSButton *)control; | ||
if (button.action == @selector(installNow:)) { | ||
installButton = button; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you use outlets, like this?
https://github.com/sparkle-project/Sparkle/blob/1.17.x/Sparkle/SUUpdateAlert.m#L47,L49
In the 1.17.x branch I've added one for the automatic update alert as well:
https://github.com/sparkle-project/Sparkle/blob/1.17.x/Sparkle/SUUpdateAlert.m#L47,L49
you could add the other two (it's OK if you change only the English xib, I'll update the rest).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did use outlets in my initial draft, but I was horrified by the xibs...
So I decided to use a 'just working' way by iterating through subviews, and change them to outlets in the future.
I think I can update them later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a script that copies changes between xibs, so it's enough if you just modify English one.
|
||
#import "SUTouchBar.h" | ||
|
||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101202 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if that will work on older systems. This checks for the SDK version, rather than the OS version Sparkle is running on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is a SDK version check.
If SDK version is lower than 10.12.2, there are not definitions for NSTouchBar and it's related APIs. So I added fake implementation to make sure the file can be compiled
That means, if Sparkle compile with SDK < 10.12.2, there will be no Touch Bar support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the latest SDK link the NSTouchBar
symbol weakly? I wonder if some class-from-string trickery is needed for gracefully failing on older systems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last looking at the code, I don't think we need class-from-string trickery because of hooking into the responder chain. The SDK check is more about compiling with older Xcode versions which shouldn't be the highest priority right now (In fact I'd be more interested currently in seeing if Travis can compile with touch bar support after upgrading Xcode 8 image).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NSTouchBar
is marked NS_CLASS_AVAILABLE_MAC(10_12_2)
, so it will be weakly linked.
To support touch bar with compiling with old SDK, we need a forward declare and NSClassFromString
trick.
Thanks for the pull request! Few observations:
Thanks for testing the UI tests! |
We may want and need to change |
|
I've set the delegate before in
I don't think so. You have two different subclass implementations based on a SDK check. Instead, you could surround the SDK check around -makeTouchBar (as long as you avoid subclassing NSTouchBar..) I use a macro like this in one of my projects for obtaining strings from property names (uses the comma operator, and sizeof operator doesn't evaluate the expression but makes sure the symbol exists). We could stick it in a new header file.
|
For old SDK, it is lacking the definition of NSTouchBar and NSTouchBarDelegate. A complete forward declaration is required, e.g.,
For binding keypath compile time check, I found a solution in ReactiveObjC: |
I think we should focus on writing the code correctly first before worrying about older SDKs. Maybe it's reasonable to not support the touch bar when compiling with an old SDK. My macro above seems to be similar and simpler than the one ReactiveCocoa uses. |
I've add some updates. But currently:
There is an issue in current commit, the default button (rightmost one) is not colored blue in touch bar. This issue happens in If so, I have to move the |
Yeah, this is kind of weird. I can reproduce this defect with the update permission prompt 100% of the time, but only reproduced the defect with the automatic update alert the first couple times I tried. |
I've found the reason, here is the stack trace:
The default button's I think we need to set the touch bar's default button's |
if (!(self = [super init])) | ||
return self; | ||
|
||
indentifier = [NSString stringWithFormat:@"%@.%@", [NSBundle mainBundle].bundleIdentifier, anIdentifier]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel easy about using the main bundle identifier. Maybe SPARKLE_BUNDLE_IDENTIFIER could be used instead if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Main bundle identifier means system level unique. SPARKLE_BUNDLE_IDENTIFIER means application level unique. I'm not sure which is "globally-unique", but I think it is not really matters here.
return self; | ||
|
||
indentifier = [NSString stringWithFormat:@"%@.%@", [NSBundle mainBundle].bundleIdentifier, anIdentifier]; | ||
touchBarItems = [NSMutableArray arrayWithCapacity:6]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't hardcode a capacity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A macro or a const integer?
} | ||
|
||
-(void)addSpace { | ||
return nil; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't return nil here.
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:itemId]; | ||
|
||
NSButton *buttonCopy = [NSButton buttonWithTitle:button.title target:button.target action:button.action]; | ||
buttonCopy.keyEquivalent = button.keyEquivalent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need this anymore, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to keep it, because the keyEquivalent
set on returned NSButton
is a fix for modal window.
I'd like to change the API to -addButtonWithButton:isDefault:
, to avoid the keyEquivalent
issue.
Setting the Could we, at least temporarily, remove the SDK check, and see if the current Travis builds or not with the touch bar? Also we would want to update the travis file as I mentioned in a comment above. I'm not keen yet on having a mutable touch bar provider (and ivar) as another level of indirection, but I can come back later to formulate my thoughts on that. |
I submitted a PR for upgrading the continuous integration files. (#991) |
So as I mentioned before we should not have an intermediate touch bar provider:
In the grand scheme of things, the automatic and regular update alert's will be merged together (2.0) so don't worry about there being similar code among those. With these changes, we'd get rid of a strong reference to a variable we don't need, a mutable array we don't need, and it wouldn't feel like we are wrapping around an API that wasn't intended to be wrapped around. |
@zorgiepoo, I think there is a different in design pattern between yours and mine. My idea came from In another word, from the UI appearance, I think all Sparkle dialogs are subclasses of And for my implementation, all May be the name of As the centering of the buttons, I tried several combination. And I think the center position looks like |
Yeah, I feel a bit strongly about this :). The current approach is creating another layer I'm not comfortable with having, and it has side effects (strong reference, mutable object). NSAlert is fine because Apple provides it. The way Apple provides support for the touch bar is such that a controller implements its own logic for it. The automatic alert will be removed in the near future, and we'll have two dialogs not that similar to each other. And I do not want a SDK check to drive a design decision like this, which is why I repeat saying to not worry about it initially (in fact I'm curious if Travis will fail with it off after the recent changes I pushed). |
I don't like strong reference either, so in the first commit I used subclassing. When I changed to delegate, I must have a strong reference to the delegate. I don't like the mutable array either. And in my very early draft, I used Maybe rename I also hate the SDK checking. But Sparkle minimal building environment is Xcode7. I don't want to push it to 8.2 right now. If |
I agree with you on subclassing. I don't want to use subclassing for removing the ivar either. So the only dispute is whether it is acceptable to use a helper as an indirect implementation. Apple's documents and sample code can't list every cases. Even it is very common to set delegate to self, but it is not a must. If multiple window share same touch bar template, a helper or delegate is needed for creating touch bar from template. It is very common to reuse customized controls. Writing redundant setup codes in every window controller is much worse. I don't know why you think the touch bar is different from each Sparkle dialog. As I see it, they are buttons grouped in center, and almost will not change in the future. If no such high similarity exists, I definitely go with your approach. This helper is used to reduce the redundancy. I think you might be accept if it is only a helper function like |
Where we seem to differ is that I think writing and keeping an additional layer of builder code is worse than the little bit of redundant code (that can be put in re-usable functions where appropriate..). Possibly a different case if we were reusing the same view across the windows. [edit]: I'd be a little more accepting to your approach if you could do it without setting the touch bar delegate, i.e., without mixing the builder type of code so much with the declarative API. It wasn't ever the naming of the class I was that concerned about. |
Other things regarding the current code:
This applies less so if we create a new module/class/file (SUTouchBarProvider) Pad a space after the "addButtonWithButton" doesn't sound properly named. Perhaps "addTouchBarButtonUsingButton:" or "addTouchBarButtonFromButton:" I think you are right your class should be renamed. It's not a New headers should use A comment should be added why passing a separate flag for setting the key equivalent is necessary. I'd prefer SDK checking to be inlined in the methods and have multiple checks, so you don't make a mistake like returning nil from a void method again, or rename one method in one place and forget to rename in other, etc. |
[self.touchBarProvider addButtonWithButton:self.laterButton isDefault:NO]; | ||
[self.touchBarProvider addButtonWithButton:self.installButton isDefault:YES]; | ||
[self.touchBarProvider addSpace]; | ||
[self.touchBarProvider addSpace]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think two spaces as extra padding should be added. It seems kind of hacky/maybe fragile to me, and I think it looks visually fine without them.
I have more new questions now...
If only three button is supported, just like NSAlert. I may write a new helper to create a SUButtonGroupTouchBarItem. And the rest part will just follow @zorgiepoo 's approach naturally. And the ivar holding the builder can be removed. It might be a 'good' design. |
In the next step, I might change the primary item into a NSCustomTouchBarItem with buttons auto layout in it's view. |
Are you talking about the touch bar buttons, or the buttons on the window? If the latter, then that should belong and be discussed in a separate pull request (and changes shouldn't be made here). Injecting state via The issues I see with the current code:
|
Agree with all you mentioned above. I just have no time yesterday to complete it all. So I decided to change it step by step without breaking it's usability. As I mentioned in last comment, I will drop the NSGroupTouchBarItem and the subclassing, by replacing it with a view or view controller to layout the buttons. It cost too much to build a helper to handle touch bar completely, with delegate patten. |
Two more things to do:
|
The thing about a new view is that we still add another dependency. I feel the payoff has to be really worth it to justify doing that. Haven't taken look at the current code yet though. |
Sometimes it is hard to tell which implementation is better. They both have pros and cons. I found NSAlert is using a private NSButtonGroupTouchBarItem as it's touch bar item. I think NSButtonGroupTouchBarItem may be also used in Open/Save panel, or other similar dialogs. If NSButtonGroupTouchBarItem is public, I think we will all go with it. But sadly, it is private. What we should do if we want to add touch bar for our own application's alert/save dialog? There will be three level that can be reused, TouchBar, TouchBarItem and TouchBarItemView. For my own project, I prefer to reuse TouchBarItem, i.e., write a similar MyButtonGroupTouchBarItem, and wish the original one can be public in the future SDK. I think reusing view is a compromise to avoid subclassing TouchBarItem, and it fits the SDK. Interface Builder treats NSTouchBar in xib more like a control, which made me want to subclass NSTouchBar in the beginning. It builds touch bar by a private NSTouchBarStaticConfiguration. But for programming, only delegate is available. |
So I typed this without looking at the current code and made a bad guess of what the code was going to look like. Having this being written such that it can be used easily by other projects should not be a concern for us; that should not be our aim. Discussing about an SDK seems to be kind of a red flag to me :/.
I don't see why the status window controller uses this class (as an aside: I don't consider the status window to be very much like an alert). One concern I have now is we're now writing a custom layout instead of letting the touch bar handle the layout for us. The approach I suggested is still the simplest (less dependencies / creating additional layers), not to be confused by highest code-reuse. |
I plan to adjust all touch bar buttons with extra 70pt padding on width, including the single button in Status window. Using a layout is the only idea currently that I can set the button width equally. If layout is not involved. The only code left in SUTouchBarButtonGroup is only button copy. Surely it should be downgraded to a helper function. But if layout is involved, I'd like to keep it as a helper class. SUTouchBarButtonGroup may be renamed to SUTouchBarButtonGroupViewController but it seems too long. It is a general view for button group in touch bar, designed for all four dialogs. |
I don't think the widths should be equal. They aren't equal in our windows, and they aren't even equal in standard NSAlert's, right?
I'm not even sure about this; not even sure it'll look better, and somewhat doubting that will be worth the cost for having a custom layout. I had the feeling |
OK, I see they are equal in the touch bar for NSAlert's (interesting), and am now understanding your private mention of I'll try to come back later to review other changes. |
A guidance from 'Design for the Touch Bar' of macOS Human Interface Guidelines:
Equal width make the button more like a physical key. I think it still looks good without extra padding currently. We may add padding in another pull request. |
if (!(self = [super init])) | ||
return self; | ||
|
||
NSView *buttonGroup = [NSView new]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+new is the same as [[NSView alloc] init]. In general, objc programmers don't use +new. An actual issue though is that -init is not documented for NSView so I think I'd be hesitant with invoking it. NSView's designated initializer is '-initWithFrame:' -- you can try using that, even if it means supplying an empty rect, I suppose.
|
||
@synthesize buttons = _buttons; | ||
|
||
- (instancetype)initByReferencingButtons:(NSArray<NSButton *> *)buttons |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the API may be more readable / sensible if it took up to three button parameters (a couple being nullable) instead of an array of buttons. We only need up to 3 in the project, and it'd give actual names instead of relying on button indices. The code currently applies very specific logic for buttons at certain indices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be looks like
- (instancetype)initByReferencingDefaultButton:(NSButton *)defaultButton alternateButton:(nullable NSButton *)alternateButton otherButton:(nullable NSButton *)otherButton;
But it conflicts with the property buttons
. It may need to three properties for holding three buttons.
The view places buttons from right to left, just like NSAlert
. It is designed to rely on indices. Also the view does not care about the purpose of the buttons, it cares about layout only. I think it is good to keep the button array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be looks like
I think that's good.
But it conflicts with the property buttons. It may need to three properties for holding three buttons.
Indeed. Although, there is no need to add properties if they're currently being unused by Sparkle, so you may be able to get away with one property.
I think that the layout cares is a good enough reason. It's not as simple as "right to left" -- padding differs in a certain spot, and one button has a return mapping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know whether you still want to change the API. If needed, I prefer to make it looks like NSAlert's API:
- (instancetype)init;
- (NSButton *)addButtonWithTitle:(NSString *)title;
@property (readonly, copy) NSArray<NSButton *> *buttons;
Named button API is deprecated in a different reason:
+ (NSAlert *)alertWithMessageText:(nullable NSString *)message defaultButton:(nullable NSString *)defaultButton alternateButton:(nullable NSString *)alternateButton otherButton:(nullable NSString *)otherButton informativeTextWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(5,6) NS_DEPRECATED_MAC(10_3, 10_10, "Use -init instead");
And no similar API was add back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't imitate NSAlert exactly (for that reason alone). NSAlert is also not a view controller. I'd stick with the single initializer and not go for more mutability (which should be avoided when possible). This is also a private API for us, not one that we are exposing, so we don't have to worry about certain future implications. This is also why we don't need to expose properties for alternative and other buttons, or support more than three buttons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean
- (instancetype)initByReferencingDefaultButton:(NSButton *)defaultButton alternateButton:(nullable NSButton *)alternateButton otherButton:(nullable NSButton *)otherButton;
with one property defaultButton
only?
So the SUTouchBarButtonGroup
will not be the correct name, since ButtonGroup mean any number of buttons. It may need to change to SUTouchBarAlertView
as you suggest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm very sorry than I still can't accept your suggestion now. Could you please merge this pull request and change the API later if you insist?
I am fine with the API as you suggest, but I don't want to change it in my commit because it is against my will. I hope you can change it in your commit, to keep the fact that we have different designs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is OK :). Are you planning on creating more PR's for this? I think the SDK checking was all left out. If you do and if @pornel doesn't, I'd appreciate it if you added the comments/docs I requested.
I don't think this is the right justification. Physicals key don't all have equal widths. But I presume that they should be equal for some other reason, else they wouldn't be for NSAlert. I would add comments/documentation on why we need to set the key equivalent instead of referencing the button, and why we have a custom layout (mentioning equal widths and mimicking the private NSButtonGroupTouchBarItem behavior that NSAlert uses). Just a measure preventing someone in the future for removing such code unknowingly. |
To my understands, physical keys layout is symmetry, which implies equal width. |
I don't have any objections to merging it. We can see if the current implementation works for us, and tweak the API later if it turns out necessary. |
Thank you @Bi11! |
Please add the comments/docs then @pornel Too important to forget and delay later. Should be easier for you since you've direct commit access as well. |
Thank you @pornel and @zorgiepoo . And sorry for the delay, I am really busy with my work these days. I'm downloading the old versions of Xcode (8, 8.1 and 8.2). I'll create a pull request once I've confirmed the API availability and added the forward declarations. |
Add Touch Bar support by mirroring buttons for each dialog.