macOS: Draw focus ring for VLCHUDSegmentedCell
[vlc.git] / modules / gui / macosx / VLCHUDSegmentedCell.m
1 //
2 //  VLCHUDSegmentedCell.m
3 //  BGHUDAppKit
4 //
5 //  Created by BinaryGod on 7/1/08.
6 //
7 //  Copyright (c) 2008, Tim Davis (BinaryMethod.com, binary.god@gmail.com)
8 //  All rights reserved.
9 //
10 //  Redistribution and use in source and binary forms, with or without modification,
11 //  are permitted provided that the following conditions are met:
12 //
13 //              Redistributions of source code must retain the above copyright notice, this
14 //      list of conditions and the following disclaimer.
15 //
16 //              Redistributions in binary form must reproduce the above copyright notice,
17 //      this list of conditions and the following disclaimer in the documentation and/or
18 //      other materials provided with the distribution.
19 //
20 //              Neither the name of the BinaryMethod.com nor the names of its contributors
21 //      may be used to endorse or promote products derived from this software without
22 //      specific prior written permission.
23 //
24 //      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND
25 //      ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26 //      WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27 //      IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28 //      INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 //      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
30 //      OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31 //      WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 //      ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 //      POSSIBILITY OF SUCH DAMAGE.
34
35 #import "VLCHUDSegmentedCell.h"
36 #import "CompatibilityFixes.h"
37
38 @interface NSSegmentedCell (Private)
39
40 - (NSRect)rectForSegment:(NSInteger)segment inFrame:(NSRect)frame;
41 - (NSInteger)_keySegment;
42
43 @end
44
45
46 @implementation VLCHUDSegmentedCell
47
48 - (instancetype)initWithCoder:(NSCoder *)decoder
49 {
50
51     self = [super initWithCoder:decoder];
52
53     if (self) {
54         _strokeColor = [NSColor colorWithDeviceRed:0.749f green:0.761f blue:0.788f alpha:1.0f];
55         _highlightGradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceRed:0.451f green:0.451f blue:0.455f alpha:0.5f]
56                                                            endingColor:[NSColor colorWithDeviceRed:0.318f green:0.318f blue:0.318f alpha:0.5f]];
57         _normalGradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceRed:0.251f green:0.251f blue:0.255f alpha:0.5f]
58                                                         endingColor:[NSColor colorWithDeviceRed:0.118f green:0.118f blue:0.118f alpha:0.5f]];
59         _disabledNormalGradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceRed:0.251f green:0.251f blue:0.255f alpha:0.5f]
60                                                                 endingColor:[NSColor colorWithDeviceRed:0.118f green:0.118f blue:0.118f alpha:0.5f]];
61         _cellTextColor = [NSColor whiteColor];
62         _disabledCellTextColor = [NSColor colorWithDeviceRed:1 green:1 blue:1 alpha:0.5f];
63     }
64
65     return self;
66 }
67
68 - (void)drawWithFrame:(NSRect)frame inView:(NSView *)view
69 {
70     if (OSX_YOSEMITE_AND_HIGHER) {
71         return [super drawWithFrame:frame inView:view];
72     }
73
74     for (NSInteger segment = 0; segment < self.segmentCount; segment++) {
75         NSRect segmentRect = [self rectForSegment:segment inFrame:frame];
76         [self drawBackgroundForSegment:segment inFrame:segmentRect];
77         [self drawSegment:segment inFrame:segmentRect withView:view];
78         [self drawDividerForSegment:segment inFrame:segmentRect];
79     }
80
81     NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 0.55, 0.55) xRadius:3.0 yRadius:3.0];
82     [_strokeColor setStroke];
83     [rectanglePath setLineWidth:1.0];
84     [rectanglePath stroke];
85 }
86
87 - (void)drawSegment:(NSInteger)segment inFrame:(NSRect)frame withView:(NSView *)view
88 {
89     if (OSX_YOSEMITE_AND_HIGHER) {
90         return [super drawSegment:segment inFrame:frame withView:view];
91     }
92
93     NSString *segmentText = [self labelForSegment:segment];
94
95     NSSize textSize = [segmentText sizeWithAttributes:nil];
96
97     frame.origin.y += (frame.size.height - textSize.height) / 2;
98     frame.size.height -= (frame.size.height - textSize.height) / 2;
99
100     NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init] ;
101     [paragraphStyle setAlignment:NSTextAlignmentCenter];
102
103     NSDictionary *attributes = @{ NSForegroundColorAttributeName : (self.isEnabled) ? _cellTextColor : _disabledCellTextColor,
104                                   NSParagraphStyleAttributeName  : paragraphStyle };
105     [segmentText drawInRect:frame withAttributes:attributes];
106 }
107
108 - (void)drawBackgroundForSegment:(NSInteger)segment inFrame:(NSRect)frame
109 {
110     NSGradient *gradient;
111
112     if (self.isEnabled) {
113         gradient = (segment == self.selectedSegment) ? _highlightGradient : _normalGradient;
114     } else {
115         gradient = _disabledNormalGradient;
116     }
117
118     if (segment > 0 && segment < (self.segmentCount - 1)) {
119         // Middle segments
120         [NSGraphicsContext saveGraphicsState];
121         if ([super showsFirstResponder] && [[[self controlView] window] isKeyWindow] &&
122             ([self focusRingType] == NSFocusRingTypeDefault ||
123              [self focusRingType] == NSFocusRingTypeExterior) &&
124             [self respondsToSelector:@selector(_keySegment)] && self._keySegment == segment) {
125             NSSetFocusRingStyle(NSFocusRingOnly);
126             NSRectFill(frame);
127         }
128         [NSGraphicsContext restoreGraphicsState];
129         [gradient drawInRect:frame angle:90];
130         return;
131     }
132
133     CGFloat radius = 3.0;
134     NSBezierPath* fillPath = [NSBezierPath bezierPath];
135
136     if (segment == 0) {
137         // First segment
138         [fillPath appendBezierPathWithArcWithCenter: NSMakePoint(NSMinX(frame) + radius, NSMinY(frame) + radius) radius:radius startAngle:180 endAngle:270];
139         [fillPath lineToPoint: NSMakePoint(NSMaxX(frame), NSMinY(frame))];
140         [fillPath lineToPoint: NSMakePoint(NSMaxX(frame), NSMaxY(frame))];
141         [fillPath appendBezierPathWithArcWithCenter: NSMakePoint(NSMinX(frame) + radius, NSMaxY(frame) - radius) radius:radius startAngle:90 endAngle:180];
142     } else {
143         // Last segment
144         [fillPath moveToPoint: NSMakePoint(NSMinX(frame), NSMinY(frame))];
145         [fillPath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(frame) - radius, NSMinY(frame) + radius) radius:radius startAngle:270 endAngle:360];
146         [fillPath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(frame) - radius, NSMaxY(frame) - radius) radius:radius startAngle:0 endAngle:90];
147         [fillPath lineToPoint: NSMakePoint(NSMinX(frame), NSMaxY(frame))];
148     }
149     [fillPath closePath];
150     [NSGraphicsContext saveGraphicsState];
151     if ([super showsFirstResponder] && [[[self controlView] window] isKeyWindow] &&
152         ([self focusRingType] == NSFocusRingTypeDefault ||
153          [self focusRingType] == NSFocusRingTypeExterior) &&
154         [self respondsToSelector:@selector(_keySegment)] && self._keySegment == segment) {
155         NSSetFocusRingStyle(NSFocusRingOnly);
156         [fillPath fill];
157     }
158     [NSGraphicsContext restoreGraphicsState];
159     [gradient drawInBezierPath:fillPath angle:90];
160 }
161
162 - (void)drawDividerForSegment:(NSInteger)segment inFrame:(NSRect)frame
163 {
164     if (segment == 0) {
165         // Do not draw for first segment
166         return;
167     }
168
169     // Draw divider on the left of the segment
170     NSBezierPath* dividerPath = [NSBezierPath bezierPath];
171     [dividerPath moveToPoint:NSMakePoint(NSMinX(frame), NSMinY(frame))];
172     [dividerPath lineToPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
173     [_strokeColor setStroke];
174     [dividerPath setLineWidth:1.0];
175     [dividerPath stroke];
176 }
177
178 - (BOOL)_canAnimate {
179     return NO;
180 }
181
182 - (BOOL)_isSliderStyle {
183     return NO;
184 }
185
186 @end