macosx: Add VLCSlider(Cell)
[vlc.git] / modules / gui / macosx / VLCSliderCell.m
1 /*****************************************************************************
2  * VLCSliderCell.m
3  *****************************************************************************
4  * Copyright (C) 2017 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Marvin Scholz <epirat07 at gmail dot com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #import "VLCSliderCell.h"
25 #import "CompatibilityFixes.h"
26
27 @interface VLCSliderCell () {
28     NSInteger _animationPosition;
29     double _lastTime;
30     double _deltaToLastFrame;
31     NSRect _lastKnobRect;
32     CVDisplayLinkRef _displayLink;
33 }
34 @end
35
36 @implementation VLCSliderCell
37
38 - (instancetype)initWithCoder:(NSCoder *)coder
39 {
40     self = [super initWithCoder:coder];
41     if (self) {
42         // Color Declarations
43         _gradientColor = [NSColor colorWithCalibratedRed: 0.663 green: 0.663 blue: 0.663 alpha: 1];
44         _gradientColor2 = [NSColor colorWithCalibratedRed: 0.749 green: 0.749 blue: 0.753 alpha: 1];
45         _trackStrokeColor = [NSColor colorWithCalibratedRed: 0.619 green: 0.624 blue: 0.623 alpha: 1];
46         _filledTrackColor = [NSColor colorWithCalibratedRed: 0.596 green: 0.596 blue: 0.596 alpha: 1];
47         _knobFillColor = [NSColor colorWithCalibratedRed: 1 green: 1 blue: 1 alpha: 1];
48         _activeKnobFillColor = [NSColor colorWithCalibratedRed: 0.95 green: 0.95 blue: 0.95 alpha: 1];
49         _shadowColor = [NSColor colorWithCalibratedRed: 0.32 green: 0.32 blue: 0.32 alpha: 1];
50         _knobStrokeColor = [NSColor colorWithCalibratedRed: 0.592 green: 0.596 blue: 0.596 alpha: 1];
51
52         // Gradient Declarations
53         _trackGradient = [[NSGradient alloc] initWithColorsAndLocations:
54                           _gradientColor, 0.0,
55                           [_gradientColor blendedColorWithFraction:0.5 ofColor:_gradientColor2], 0.60,
56                           _gradientColor2, 1.0, nil];
57
58         // Shadow Declarations
59         _knobShadow = [[NSShadow alloc] init];
60         _knobShadow.shadowColor = _shadowColor;
61         _knobShadow.shadowOffset = NSMakeSize(0, 0);
62         _knobShadow.shadowBlurRadius = 2;
63
64         _highlightBackground = [NSColor colorWithCalibratedRed:0.20 green:0.55 blue:0.91 alpha:1.0];
65         NSColor *highlightAccent = [NSColor colorWithCalibratedRed:0.4588235294 green:0.7254901961 blue:0.9882352941 alpha:1.0];
66         _highlightGradient = [[NSGradient alloc] initWithColors:@[
67                                                                   _highlightBackground,
68                                                                   highlightAccent,
69                                                                   _highlightBackground
70                                                                   ]];
71         _animationWidth = [[self controlView] bounds].size.width;
72
73         [self initDisplayLink];
74     }
75     return self;
76 }
77
78 - (void)dealloc
79 {
80     CVDisplayLinkRelease(_displayLink);
81 }
82
83 static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext)
84 {
85     CVTimeStamp inNowCopy = *inNow;
86     dispatch_async(dispatch_get_main_queue(), ^{
87         VLCSliderCell *sliderCell = (__bridge VLCSliderCell*)displayLinkContext;
88         [sliderCell displayLink:displayLink tickWithTime:&inNowCopy];
89     });
90     return kCVReturnSuccess;
91 }
92
93 - (void)displayLink:(CVDisplayLinkRef)displayLink tickWithTime:(const CVTimeStamp*)inNow
94 {
95     if (_lastTime == 0) {
96         _deltaToLastFrame = 0;
97     } else {
98         _deltaToLastFrame = (double)(inNow->videoTime - _lastTime) / inNow->videoTimeScale;
99     }
100     _lastTime = inNow->videoTime;
101
102     [self.controlView setNeedsDisplay:YES];
103 }
104
105 - (void)initDisplayLink
106 {
107     CVReturn ret = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
108     if (ret != kCVReturnSuccess) {
109         // TODO: Handle error
110         return;
111     }
112     CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void*) self);
113 }
114
115 #pragma mark -
116 #pragma mark Normal slider drawing
117
118 - (void)drawKnob:(NSRect)knobRect
119 {
120     _lastKnobRect = knobRect;
121     // 10.7 - 10.10 hack
122     if (!OSX_YOSEMITE_AND_HIGHER)
123         knobRect.size.height += 1.5;
124
125     // Draw knob
126     NSBezierPath* knobPath = [NSBezierPath bezierPathWithOvalInRect:NSInsetRect(knobRect, 2.0, 2.0)];
127     if (self.isHighlighted) {
128         [_activeKnobFillColor setFill];
129     } else {
130         [_knobFillColor setFill];
131     }
132
133     [knobPath fill];
134
135     [_knobStrokeColor setStroke];
136     knobPath.lineWidth = 0.5;
137
138     [NSGraphicsContext saveGraphicsState];
139     if (self.isHighlighted)
140         [_knobShadow set];
141     [knobPath stroke];
142     [NSGraphicsContext restoreGraphicsState];
143 }
144
145 - (NSRect)barRectFlipped:(BOOL)flipped
146 {
147     return [[self controlView] bounds];
148 }
149
150 - (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped
151 {
152     // Inset rect
153     rect = NSInsetRect(rect, 1.0, 1.0);
154
155     // Empty Track Drawing
156     NSBezierPath* emptyTrackPath = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:3 yRadius:3];
157     [_trackGradient drawInBezierPath:emptyTrackPath angle:-90];
158     [_trackStrokeColor setStroke];
159     emptyTrackPath.lineWidth = 1;
160     [emptyTrackPath stroke];
161
162     // Calculate filled track
163     NSRect filledTrackRect = rect;
164     NSRect knobRect = [self knobRectFlipped:NO];
165     filledTrackRect.size.width = knobRect.origin.x + (self.knobThickness / 2);
166
167     // Filled Track Drawing
168     CGFloat filledTrackCornerRadius = 2;
169     NSRect filledTrackInnerRect = NSInsetRect(filledTrackRect, filledTrackCornerRadius, filledTrackCornerRadius);
170     NSBezierPath* filledTrackPath = [NSBezierPath bezierPathWithRoundedRect:filledTrackInnerRect
171                                                                     xRadius:filledTrackCornerRadius
172                                                                     yRadius:filledTrackCornerRadius];
173
174     [_filledTrackColor setFill];
175     [filledTrackPath fill];
176 }
177
178 #pragma mark -
179 #pragma mark Indefinite slider drawing
180
181
182 - (void)drawHighlightInRect:(NSRect)rect
183 {
184     [_highlightGradient drawInRect:rect angle:0];
185 }
186
187
188 - (void)drawHighlightBackgroundInRect:(NSRect)rect
189 {
190     rect = NSInsetRect(rect, 1.0, 1.0);
191     NSBezierPath *fullPath = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:2.0 yRadius:2.0];
192     [_highlightBackground setFill];
193     [fullPath fill];
194 }
195
196 - (void)drawAnimationInRect:(NSRect)rect
197 {
198     [self drawHighlightBackgroundInRect:rect];
199
200     [NSGraphicsContext saveGraphicsState];
201     rect = NSInsetRect(rect, 1.0, 1.0);
202     NSBezierPath *fullPath = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:2.0 yRadius:2.0];
203     [fullPath setClip];
204
205     // Use previously calculated position
206     rect.origin.x = _animationPosition;
207
208     // Calculate new position for next Frame
209     if (_animationPosition < (rect.size.width + _animationWidth)) {
210         _animationPosition += (rect.size.width + _animationWidth) * _deltaToLastFrame;
211     } else {
212         _animationPosition = -(_animationWidth);
213     }
214
215     rect.size.width = _animationWidth;
216
217
218     [self drawHighlightInRect:rect];
219     [NSGraphicsContext restoreGraphicsState];
220     _deltaToLastFrame = 0;
221 }
222
223
224 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
225 {
226     if (_indefinite)
227         return [self drawAnimationInRect:cellFrame];
228
229     [super drawWithFrame:cellFrame inView:controlView];
230
231 }
232
233 #pragma mark -
234 #pragma mark Animation handling
235
236 - (void)beginAnimating
237 {
238     CVReturn err = CVDisplayLinkStart(_displayLink);
239     if (err != kCVReturnSuccess) {
240         // TODO: Handle error
241     }
242     _animationPosition = -(_animationWidth);
243     [self setEnabled:NO];
244 }
245
246 - (void)endAnimating
247 {
248     CVDisplayLinkStop(_displayLink);
249     [self setEnabled:YES];
250
251 }
252
253 - (void)setIndefinite:(BOOL)indefinite
254 {
255     if (_indefinite == indefinite)
256         return;
257
258     if (indefinite)
259         [self beginAnimating];
260     else
261         [self endAnimating];
262     _indefinite = indefinite;
263 }
264
265
266 @end