9ae772316335142df88aa978e2b72703e10fc89b
[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     NSColor *_defaultTickMarkColor;
40 }
41 @end
42
43 @implementation VLCDefaultValueSliderCell
44
45 #pragma mark -
46 #pragma mark Public interface
47
48 - (instancetype)init
49 {
50     self = [super init];
51     if (self) {
52         [self setupSelf];
53     }
54     return self;
55 }
56
57 - (instancetype)initWithCoder:(NSCoder *)coder
58 {
59     self = [super initWithCoder:coder];
60     if (self) {
61         [self setupSelf];
62     }
63     return self;
64 }
65
66 - (void)setDefaultValue:(double)value
67 {
68     if (value > _maxValue || value < _minValue)
69         value = DBL_MAX;
70
71     if (_defaultValue == DBL_MAX && value != DBL_MAX) {
72         _drawTickMarkForDefault = YES;
73         _snapsToDefault = YES;
74     } else if (value == DBL_MAX) {
75         _drawTickMarkForDefault = NO;
76         _snapsToDefault = NO;
77     }
78     _defaultValue = value;
79     [[self controlView] setNeedsDisplay:YES];
80 }
81
82 - (double)defaultValue
83 {
84     return _defaultValue;
85 }
86
87 - (void)setDefaultTickMarkColor:(NSColor *)color
88 {
89     _defaultTickMarkColor = color;
90     [[self controlView] setNeedsDisplay:YES];
91
92 }
93
94 - (NSColor *)defaultTickMarkColor
95 {
96     return _defaultTickMarkColor;
97 }
98
99 - (void)drawDefaultTickMarkWithFrame:(NSRect)rect
100 {
101     [_defaultTickMarkColor setFill];
102     NSRectFill(rect);
103 }
104
105 #pragma mark -
106 #pragma mark Internal helpers
107
108 - (void)setupSelf
109 {
110     _defaultValue = DBL_MAX;
111     _isRTL = ([self userInterfaceLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft);
112     _isFlipped = [[self controlView] isFlipped];
113     _defaultTickMarkColor = [NSColor grayColor];
114 }
115
116 /*
117  * Adapted from GNUstep NSSliderCell
118  * - (NSRect)knobRectFlipped:(BOOL)flipped
119  *
120  * Calculates the knobRect for a given position
121  * This is later used to draw the default tick mark in the center of
122  * where the knob would be, when it is at the default value.
123  */
124 #pragma clang diagnostic push
125 #pragma clang diagnostic ignored "-Wpartial-availability"
126 - (NSRect)knobRectFlipped:(BOOL)flipped forValue:(double)doubleValue
127 {
128     NSRect superRect = [super knobRectFlipped:flipped];
129     NSPoint     origin = _trackRect.origin;
130     NSSize size = superRect.size;
131
132     if ([self isVertical] && flipped) {
133         doubleValue = _maxValue + _minValue - doubleValue;
134     }
135
136     doubleValue = (doubleValue - _minValue) / (_maxValue - _minValue);
137
138     if ([self isVertical] == YES) {
139         origin.x = superRect.origin.x;
140         origin.y += (_trackRect.size.height - size.height) * doubleValue;
141     } else {
142         origin.x += ((_trackRect.size.width - size.width) * doubleValue);
143         origin.y = superRect.origin.y;
144     }
145
146     return NSMakeRect(origin.x, origin.y, size.width, size.height);
147 }
148
149 #pragma mark -
150 #pragma mark Overwritten super methods
151
152 - (NSRect)knobRectFlipped:(BOOL)flipped
153 {
154     return [self knobRectFlipped:flipped forValue:[self doubleValue]];
155 }
156
157 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
158 {
159     // Do all other drawing
160     [super drawWithFrame:cellFrame inView:controlView];
161
162     // Default tick mark
163     if (_drawTickMarkForDefault && _defaultValue != DBL_MAX) {
164
165         // Calculate rect for default tick mark
166         CGFloat tickThickness = 1.0;
167
168         NSRect tickFrame = [self knobRectFlipped:_isFlipped
169                                         forValue:_defaultValue];
170
171         if ([self isVertical]) {
172             CGFloat mid = NSMidY(tickFrame);
173             tickFrame.origin.x = cellFrame.origin.x;
174             tickFrame.origin.y = mid - tickThickness/2.0;
175             tickFrame.size.width = cellFrame.size.width;
176             tickFrame.size.height = tickThickness;
177         } else {
178             CGFloat mid = NSMidX(tickFrame);
179             // Ugly workaround
180             // Corrects minor alignment issue on non-retina
181             CGFloat scale = [[[self controlView] window] backingScaleFactor];
182             tickFrame.size.height = cellFrame.size.height;
183             tickFrame.origin.y = cellFrame.origin.y;
184             if (scale > 1.0) {
185                 tickFrame.origin.x = mid;
186             } else {
187                 tickFrame.origin.x = mid - tickThickness;
188                 if (OSX_YOSEMITE_AND_HIGHER) {
189                     tickFrame.size.height = cellFrame.size.height - 1;
190                     tickFrame.origin.y = cellFrame.origin.y - 1;
191                 }
192             }
193             tickFrame.size.width = tickThickness;
194         }
195
196         NSAlignmentOptions alignOpts = NSAlignMinXOutward | NSAlignMinYOutward |
197                                        NSAlignWidthOutward | NSAlignMaxYOutward;
198         NSRect alignedRect = [[self controlView] backingAlignedRect:tickFrame options:alignOpts];
199
200         // Draw default tick mark
201         [self drawDefaultTickMarkWithFrame:alignedRect];
202     }
203
204     // Redraw knob
205     [super drawKnob];
206 }
207 #pragma clang diagnostic pop
208
209 - (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView
210 {
211     double oldValue = [self doubleValue];
212     BOOL result = [super continueTracking:lastPoint at:currentPoint inView:controlView];
213     double newValue = [self doubleValue];
214
215     // If no change, nothing to do.
216     if (newValue == oldValue)
217         return result;
218
219     // Determine in which direction slider is moving
220     BOOL sliderMovingForward = (oldValue > newValue) ? NO : YES;
221
222     // Calculate snap-threshhold
223     double range = self.maxValue - self.minValue;
224     double thresh = (range * 0.01) * 7;
225
226     // Snap to default value
227     if (ABS(newValue - _defaultValue) < thresh && _snapsToDefault) {
228         if (sliderMovingForward && newValue > _defaultValue) {
229             [self setDoubleValue:_defaultValue];
230         } else if (!sliderMovingForward && newValue < _defaultValue) {
231             [self setDoubleValue:_defaultValue];
232         }
233     }
234
235     return result;
236 }
237
238 @end