b9e817eea42bf8d9290954a37617195b4adbe48d
[vlc.git] / modules / gui / macosx / VLCDefaultValueSliderCell.m
1 /*****************************************************************************
2  * VLCDefaultValueSliderCell.m: SliderCell subclass for VLCDefaultValueSlider
3  *****************************************************************************
4  * Copyright (C) 2016 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Marvin Scholz <epirat07 -at- gmail -dot- com>
8  *
9  * This file uses parts of code from the GNUstep NSSliderCell code:
10  * 
11  *   Copyright (C) 1996,1999 Free Software Foundation, Inc.
12  *   Author: Ovidiu Predescu <ovidiu@net-community.com>
13  *   Date: September 1997
14  *   Rewrite: Richard Frith-Macdonald <richard@brainstorm.co.uk>
15  *   Date: 1999
16  *
17  * This program is free software; you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation; either version 2 of the License, or
20  * (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with this program; if not, write to the Free Software
29  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
30  *****************************************************************************/
31
32 #import "VLCDefaultValueSliderCell.h"
33 #import "CompatibilityFixes.h"
34
35 @interface VLCDefaultValueSliderCell (){
36     BOOL _isRTL;
37     BOOL _isFlipped;
38     double _defaultValue;
39     double _normalizedDefaultValue;
40     NSColor *_defaultTickMarkColor;
41 }
42 @end
43
44 @implementation VLCDefaultValueSliderCell
45
46 #pragma mark -
47 #pragma mark Public interface
48
49 - (instancetype)init
50 {
51     self = [super init];
52     if (self) {
53         [self setupSelf];
54     }
55     return self;
56 }
57
58 - (instancetype)initWithCoder:(NSCoder *)coder
59 {
60     self = [super initWithCoder:coder];
61     if (self) {
62         [self setupSelf];
63     }
64     return self;
65 }
66
67 - (void)setDefaultValue:(double)value
68 {
69     if (value > _maxValue || value < _minValue)
70         value = DBL_MAX;
71
72     if (_defaultValue == DBL_MAX && value != DBL_MAX) {
73         _drawTickMarkForDefault = YES;
74         _snapsToDefault = YES;
75     } else if (value == DBL_MAX) {
76         _drawTickMarkForDefault = NO;
77         _snapsToDefault = NO;
78     }
79     _defaultValue = value;
80     _normalizedDefaultValue = (value == DBL_MAX) ? DBL_MAX : [self normalizedValue:_defaultValue];
81     [[self controlView] setNeedsDisplay:YES];
82 }
83
84 - (double)defaultValue
85 {
86     return _defaultValue;
87 }
88
89 - (void)setDefaultTickMarkColor:(NSColor *)color
90 {
91     _defaultTickMarkColor = color;
92     [[self controlView] setNeedsDisplay:YES];
93
94 }
95
96 - (NSColor *)defaultTickMarkColor
97 {
98     return _defaultTickMarkColor;
99 }
100
101 - (void)drawDefaultTickMarkWithFrame:(NSRect)rect
102 {
103     [_defaultTickMarkColor setFill];
104     NSRectFill(rect);
105 }
106
107 #pragma mark -
108 #pragma mark Internal helpers
109
110 - (void)setupSelf
111 {
112     _defaultValue = DBL_MAX;
113     _normalizedDefaultValue = DBL_MAX;
114     _isRTL = ([self userInterfaceLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft);
115     _isFlipped = [[self controlView] isFlipped];
116     _defaultTickMarkColor = [NSColor grayColor];
117 }
118
119 /*
120  * Adapted from GNUstep NSSliderCell
121  * - (NSRect)knobRectFlipped:(BOOL)flipped
122  *
123  * Calculates the knobRect for a given position
124  * This is later used to draw the default tick mark in the center of
125  * where the knob would be, when it is at the default value.
126  */
127 #pragma clang diagnostic push
128 #pragma clang diagnostic ignored "-Wpartial-availability"
129 - (NSRect)knobRectFlipped:(BOOL)flipped forValue:(double)doubleValue
130 {
131     NSRect superRect = [super knobRectFlipped:flipped];
132     NSPoint     origin = _trackRect.origin;
133     NSSize size = superRect.size;
134
135     if ([self isVertical] && flipped) {
136         doubleValue = _maxValue + _minValue - doubleValue;
137     }
138
139     doubleValue = (doubleValue - _minValue) / (_maxValue - _minValue);
140
141     if ([self isVertical] == YES) {
142         origin.x = superRect.origin.x;
143         origin.y += (_trackRect.size.height - size.height) * doubleValue;
144     } else {
145         origin.x += ((_trackRect.size.width - size.width) * doubleValue);
146         origin.y = superRect.origin.y;
147     }
148
149     return NSMakeRect(origin.x, origin.y, size.width, size.height);
150 }
151
152 #pragma mark -
153 #pragma mark Overwritten super methods
154
155 - (NSRect)knobRectFlipped:(BOOL)flipped
156 {
157     return [self knobRectFlipped:flipped forValue:[self doubleValue]];
158 }
159
160 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
161 {
162     // Do all other drawing
163     [super drawWithFrame:cellFrame inView:controlView];
164
165     // Default tick mark
166     if (_drawTickMarkForDefault && _defaultValue != DBL_MAX) {
167
168         // Calculate rect for default tick mark
169         CGFloat tickThickness = 1.0;
170
171         NSRect tickFrame = [self knobRectFlipped:_isFlipped
172                                         forValue:_defaultValue];
173
174         if ([self isVertical]) {
175             CGFloat mid = NSMidY(tickFrame);
176             tickFrame.origin.x = cellFrame.origin.x;
177             tickFrame.origin.y = mid - tickThickness/2.0;
178             tickFrame.size.width = cellFrame.size.width;
179             tickFrame.size.height = tickThickness;
180         } else {
181             CGFloat mid = NSMidX(tickFrame);
182             // Ugly workaround
183             // Corrects minor alignment issue on non-retina
184             CGFloat scale = [[[self controlView] window] backingScaleFactor];
185             tickFrame.size.height = cellFrame.size.height;
186             tickFrame.origin.y = cellFrame.origin.y;
187             if (scale > 1.0) {
188                 tickFrame.origin.x = mid;
189             } else {
190                 tickFrame.origin.x = mid - tickThickness;
191                 if (OSX_YOSEMITE_AND_HIGHER) {
192                     tickFrame.size.height = cellFrame.size.height - 1;
193                     tickFrame.origin.y = cellFrame.origin.y - 1;
194                 }
195             }
196             tickFrame.size.width = tickThickness;
197         }
198
199         NSAlignmentOptions alignOpts = NSAlignMinXOutward | NSAlignMinYOutward |
200                                        NSAlignWidthOutward | NSAlignMaxYOutward;
201         NSRect alignedRect = [[self controlView] backingAlignedRect:tickFrame options:alignOpts];
202
203         // Draw default tick mark
204         [self drawDefaultTickMarkWithFrame:alignedRect];
205     }
206
207     // Redraw knob
208     [super drawKnob];
209 }
210 #pragma clang diagnostic pop
211
212 - (double)normalizedValue:(double)value
213 {
214     double min = [self minValue];
215     double max = [self maxValue];
216
217     max -= min;
218     value -= min;
219
220     return (value / max) * 100;
221 }
222
223 - (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView
224 {
225     double oldValue = [self normalizedValue:self.doubleValue];
226     BOOL result = [super continueTracking:lastPoint at:currentPoint inView:controlView];
227     double newValue = [self normalizedValue:self.doubleValue];
228
229     // If no change, nothing to do.
230     if (newValue == oldValue)
231         return result;
232
233     // Determine in which direction slider is moving
234     BOOL sliderMovingForward = (oldValue > newValue) ? NO : YES;
235
236     // Claculate snap-threshhold
237     double thresh = 100 * (self.knobThickness/3) / _trackRect.size.width;
238
239     // Snap to default value
240     if (_snapsToDefault && ABS(newValue - _normalizedDefaultValue) < thresh) {
241         if (sliderMovingForward && newValue > _normalizedDefaultValue) {
242             [self setDoubleValue:_defaultValue];
243         } else if (!sliderMovingForward && newValue < _normalizedDefaultValue) {
244             [self setDoubleValue:_defaultValue];
245         }
246     }
247
248     return result;
249 }
250
251 @end