1/***************************************************************************** 2 * VLCDefaultValueSliderCell.m: SliderCell subclass for VLCDefaultValueSlider 3 ***************************************************************************** 4 * Copyright (C) 2016 VLC authors and VideoLAN 5 * $Id: 4bec1b52b50632e314cbe3d3faa6da20a070a0c4 $ 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 > self.maxValue || value < self.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 * 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 127- (NSRect)knobRectFlipped:(BOOL)flipped forValue:(double)doubleValue 128 { 129 NSRect resultRect; 130 NSRect trackRect = self.trackRect; 131 double val = [self normalizedValue:doubleValue] / 100; 132 133 if (self.isVertical) { 134 resultRect.origin.x = -1; 135 resultRect.origin.y = (NSHeight(trackRect) - self.knobThickness) * val; 136 if (_isRTL) 137 resultRect.origin.y = (NSHeight(trackRect) - self.knobThickness) - resultRect.origin.y; 138 } else { 139 resultRect.origin.x = (NSWidth(trackRect) - self.knobThickness) * val; 140 resultRect.origin.y = -1; 141 if (_isRTL) 142 resultRect.origin.x = (NSWidth(trackRect) - self.knobThickness) - resultRect.origin.x; 143 } 144 145 resultRect.size.height = self.knobThickness; 146 resultRect.size.width = self.knobThickness; 147 148 return [self.controlView backingAlignedRect:resultRect options:NSAlignAllEdgesNearest]; 149 } 150 151#pragma mark - 152#pragma mark Overwritten super methods 153 154- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView 155{ 156 // Do all other drawing 157 [super drawWithFrame:cellFrame inView:controlView]; 158 159 // Default tick mark 160 if (_drawTickMarkForDefault && _defaultValue != DBL_MAX) { 161 162 // Calculate rect for default tick mark 163 CGFloat tickThickness = 1.0; 164 165 NSRect tickFrame = [self knobRectFlipped:_isFlipped 166 forValue:_defaultValue]; 167 168 if ([self isVertical]) { 169 CGFloat mid = NSMidY(tickFrame); 170 tickFrame.origin.x = cellFrame.origin.x; 171 tickFrame.origin.y = mid - tickThickness/2.0; 172 tickFrame.size.width = cellFrame.size.width; 173 tickFrame.size.height = tickThickness; 174 } else { 175 CGFloat mid = NSMidX(tickFrame); 176 // Ugly workaround 177 // Corrects minor alignment issue on non-retina 178 CGFloat scale = [[[self controlView] window] backingScaleFactor]; 179 tickFrame.size.height = cellFrame.size.height; 180 tickFrame.origin.y = cellFrame.origin.y; 181 if (scale > 1.0) { 182 tickFrame.origin.x = mid; 183 } else { 184 tickFrame.origin.x = mid - tickThickness; 185 if (OSX_YOSEMITE_AND_HIGHER) { 186 tickFrame.size.height = cellFrame.size.height - 1; 187 tickFrame.origin.y = cellFrame.origin.y - 1; 188 } 189 } 190 tickFrame.size.width = tickThickness; 191 } 192 193 NSAlignmentOptions alignOpts = NSAlignMinXOutward | NSAlignMinYOutward | 194 NSAlignWidthOutward | NSAlignMaxYOutward; 195 NSRect alignedRect = [[self controlView] backingAlignedRect:tickFrame options:alignOpts]; 196 197 // Draw default tick mark 198 [self drawDefaultTickMarkWithFrame:alignedRect]; 199 } 200 201 // Redraw knob 202 [super drawKnob]; 203} 204#pragma clang diagnostic pop 205 206- (double)normalizedValue:(double)value 207{ 208 double min = [self minValue]; 209 double max = [self maxValue]; 210 211 max -= min; 212 value -= min; 213 214 return (value / max) * 100; 215} 216 217- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView 218{ 219 double oldValue = [self normalizedValue:self.doubleValue]; 220 BOOL result = [super continueTracking:lastPoint at:currentPoint inView:controlView]; 221 double newValue = [self normalizedValue:self.doubleValue]; 222 223 // If no change, nothing to do. 224 if (newValue == oldValue) 225 return result; 226 227 // Determine in which direction slider is moving 228 BOOL sliderMovingForward = (oldValue > newValue) ? NO : YES; 229 230 // Claculate snap-threshhold 231 double thresh = 100 * (self.knobThickness/3) / self.trackRect.size.width; 232 233 // Snap to default value 234 if (_snapsToDefault && ABS(newValue - _normalizedDefaultValue) < thresh) { 235 if (sliderMovingForward && newValue > _normalizedDefaultValue) { 236 [self setDoubleValue:_defaultValue]; 237 } else if (!sliderMovingForward && newValue < _normalizedDefaultValue) { 238 [self setDoubleValue:_defaultValue]; 239 } 240 } 241 242 return result; 243} 244 245@end 246