Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p><strong>Edit</strong>: Since I wrote this, <a href="https://github.com/indragiek/INAppStoreWindow" rel="noreferrer">INAppStore</a> has implemented a pretty nice way to do this with <code>INWindowButton</code>. If you're looking for a drag and drop solution check there, but the code below will still help you implement your own.</p> <hr> <p>So I couldn't find a way to alter the <code>standardWindowButton</code>s. Here is a walkthrough of how I created my own buttons.</p> <p><strong>Note:</strong> There are 4 states the buttons can be in</p> <ul> <li>Window inactive <img src="https://i.stack.imgur.com/EYJ4z.png" alt="Window Inactive Controls"></li> <li>Window active - normal <img src="https://i.stack.imgur.com/rtjeV.png" alt="Window Active Normal Controls"></li> <li>Window active - hover <img src="https://i.stack.imgur.com/mQznv.png" alt="Window Active Hover Controls"></li> <li>Window active - press <img src="https://i.stack.imgur.com/2ua3O.png" alt="Window Active Press Controls"></li> </ul> <p>On to the walkthrough!</p> <p><strong>Step 1: Hide the pre-existing buttons</strong></p> <pre><code>NSButton *windowButton = [self standardWindowButton:NSWindowCloseButton]; [windowButton setHidden:YES]; windowButton = [self standardWindowButton:NSWindowMiniaturizeButton]; [windowButton setHidden:YES]; windowButton = [self standardWindowButton:NSWindowZoomButton]; [windowButton setHidden:YES]; </code></pre> <p><strong>Step 2: Setup the view in Interface Builder</strong></p> <p>You'll notice on hover the buttons all change to their hover state, so we need a container view to pick up the hover.</p> <ul> <li>Create a container view to be 54px wide x 16px tall.</li> <li>Create 3 Square style <code>NSButton</code>s, each 14px wide x 16px tall inside the container view.</li> <li>Space out the buttons so there is are 6px gaps in-between.</li> </ul> <p>Setup the buttons</p> <ul> <li>In the attributes inspector, set the <code>Image</code> property for each button to the window-active-normal image.</li> <li>Set the <code>Alternate</code> image property to the window-active-press image.</li> <li>Turn <code>Bordered</code> off.</li> <li>Set the <code>Type</code> to <code>Momentary Change</code>.</li> <li>For each button set the identifier to <code>close</code>,<code>minimize</code> or <code>zoom</code> (Below you'll see how you can use this to make the NSButton subclass simpler)</li> </ul> <p><strong>Step 3: Subclass the container view &amp; buttons</strong></p> <p><strong>Container:</strong></p> <p>Create a new file, subclass NSView. Here we are going to use Notification Center to tell the buttons when they should switch to their hover state.</p> <p><em>HMTrafficLightButtonsContainer.m</em></p> <pre><code>// Tells the view to pick up the hover event - (void)viewDidMoveToWindow { [self addTrackingRect:[self bounds] owner:self userData:nil assumeInside:NO]; } // When the mouse enters/exits we send out these notifications - (void)mouseEntered:(NSEvent *)theEvent { [[NSNotificationCenter defaultCenter] postNotificationName:@"HMTrafficButtonMouseEnter" object:self]; } - (void)mouseExited:(NSEvent *)theEvent { [[NSNotificationCenter defaultCenter] postNotificationName:@"HMTrafficButtonMouseExit" object:self]; } </code></pre> <p><strong>Buttons:</strong></p> <p>Create a new file, this time subclass NSButton. This one's a bit more to explain so I'll just post all the code.</p> <p><em>HMTrafficLightButton.m</em></p> <pre><code>@implementation HMTrafficLightButton { NSImage *inactive; NSImage *active; NSImage *hover; NSImage *press; BOOL activeState; BOOL hoverState; BOOL pressedState; } -(id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self setup]; } return self; } - (id)initWithFrame:(NSRect)frameRect { self = [super initWithFrame:frameRect]; if (self) { [self setup]; } return self; } - (void)setup { // Setup images, we use the identifier to chose which image to load active = [NSImage imageNamed:[NSString stringWithFormat:@"window-button-%@-active",self.identifier]]; hover = [NSImage imageNamed:[NSString stringWithFormat:@"window-button-%@-hover",self.identifier]]; press = [NSImage imageNamed:[NSString stringWithFormat:@"window-button-%@-press",self.identifier]]; inactive = [NSImage imageNamed:@"window-button-all-inactive"]; // Checks to see if window is active or inactive when the `init` is called if ([self.window isMainWindow] &amp;&amp; [[NSApplication sharedApplication] isActive]) { [self setActiveState]; } else { [self setInactiveState]; } // Watch for hover notifications from the container view // Also watches for notifications for when the window // becomes/resigns main [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setActiveState) name:NSWindowDidBecomeMainNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setInactiveState) name:NSWindowDidResignMainNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hoverIn) name:@"HMTrafficButtonMouseEnter" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hoverOut) name:@"HMTrafficButtonMouseExit" object:nil]; } - (void)mouseDown:(NSEvent *)theEvent { pressedState = YES; hoverState = NO; [super mouseDown:theEvent]; } - (void)mouseUp:(NSEvent *)theEvent { pressedState = NO; hoverState = YES; [super mouseUp:theEvent]; } - (void)setActiveState { activeState = YES; if (hoverState) { [self setImage:hover]; } else { [self setImage:active]; } } - (void)setInactiveState { activeState = NO; [self setImage:inactive]; } - (void)hoverIn { hoverState = YES; [self setImage:hover]; } - (void)hoverOut { hoverState = NO; if (activeState) { [self setImage:active]; } else { [self setImage:inactive]; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end </code></pre> <p>In IB set the Custom Class of the container view and all 3 buttons to their respective classes that we just created.</p> <p><strong>Step 4: Set the button actions</strong></p> <p>These methods, called from the view controller, are the same as the <code>standardWindowButton</code>s'. Link them to the buttons in IB.</p> <pre><code>- (IBAction)clickCloseButton:(id)sender { [self.view.window close]; } - (IBAction)clickMinimizeButton:(id)sender { [self.view.window miniaturize:sender]; } - (IBAction)clickZoomButton:(id)sender { [self.view.window zoom:sender]; } </code></pre> <p><strong>Step 5: Add the view to the window</strong></p> <p><em>I have a separate xib and view controller setup specifically for the window controls. The view controller is called HMWindowControlsController</em></p> <pre><code>(HMWindowControlsController*) windowControlsController = [[HMWindowControlsController alloc] initWithNibName:@"WindowControls" bundle:nil]; NSView *windowControlsView = windowControlsController.view; // Set the position of the window controls, the x is 7 px, the y will // depend on your titlebar height. windowControlsView.frame = NSMakeRect(7.0, 10.0, 54.0, 16.0); // Add to target view [targetView addSubview:windowControlsView]; </code></pre> <p>Hope this helps. This is a pretty lengthy post, if you think I've made a mistake or left something out please let me know.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload