1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import "RNCSlider.h"
9
10@implementation RNCSlider
11{
12  float _unclippedValue;
13  bool _minimumTrackImageSet;
14  bool _maximumTrackImageSet;
15}
16
17- (instancetype)initWithFrame:(CGRect)frame
18{
19    return [super initWithFrame:frame];
20}
21
22- (void)setValue:(float)value
23{
24    value = [self discreteValue:value];
25  _unclippedValue = value;
26  super.value = value;
27  [self setupAccessibility:value];
28}
29
30- (void)setValue:(float)value animated:(BOOL)animated
31{
32    value = [self discreteValue:value];
33  _unclippedValue = value;
34  [super setValue:value animated:animated];
35  [self setupAccessibility:value];
36}
37
38- (void)setupAccessibility:(float)value
39{
40  if (self.accessibilityUnits && self.accessibilityIncrements && [self.accessibilityIncrements count] - 1 == (int)self.maximumValue) {
41    int index = (int)value;
42    NSString *sliderValue = (NSString *)[self.accessibilityIncrements objectAtIndex:index];
43    NSUInteger stringLength = [self.accessibilityUnits length];
44
45    NSString *spokenUnits = [NSString stringWithString:self.accessibilityUnits];
46    if (sliderValue && [sliderValue intValue] == 1) {
47      spokenUnits = [spokenUnits substringToIndex:stringLength-1];
48    }
49
50    self.accessibilityValue = [NSString stringWithFormat:@"%@ %@", sliderValue, spokenUnits];
51  }
52}
53
54- (void)setMinimumValue:(float)minimumValue
55{
56  super.minimumValue = minimumValue;
57  super.value = _unclippedValue;
58}
59
60- (void)setMaximumValue:(float)maximumValue
61{
62  super.maximumValue = maximumValue;
63  super.value = _unclippedValue;
64}
65
66- (void)setTrackImage:(UIImage *)trackImage
67{
68  if (trackImage != _trackImage) {
69    _trackImage = trackImage;
70    CGFloat width = trackImage.size.width / 2;
71    if (!_minimumTrackImageSet) {
72      UIImage *minimumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){
73        0, width, 0, width
74      } resizingMode:UIImageResizingModeStretch];
75      [self setMinimumTrackImage:minimumTrackImage forState:UIControlStateNormal];
76    }
77    if (!_maximumTrackImageSet) {
78      UIImage *maximumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){
79        0, width, 0, width
80      } resizingMode:UIImageResizingModeStretch];
81      [self setMaximumTrackImage:maximumTrackImage forState:UIControlStateNormal];
82    }
83  }
84}
85
86- (void)setMinimumTrackImage:(UIImage *)minimumTrackImage
87{
88  _trackImage = nil;
89  _minimumTrackImageSet = true;
90  minimumTrackImage = [minimumTrackImage resizableImageWithCapInsets:(UIEdgeInsets){
91    0, minimumTrackImage.size.width, 0, 0
92  } resizingMode:UIImageResizingModeStretch];
93  [self setMinimumTrackImage:minimumTrackImage forState:UIControlStateNormal];
94}
95
96- (UIImage *)minimumTrackImage
97{
98  return [self thumbImageForState:UIControlStateNormal];
99}
100
101- (void)setMaximumTrackImage:(UIImage *)maximumTrackImage
102{
103  _trackImage = nil;
104  _maximumTrackImageSet = true;
105  maximumTrackImage = [maximumTrackImage resizableImageWithCapInsets:(UIEdgeInsets){
106    0, 0, 0, maximumTrackImage.size.width
107  } resizingMode:UIImageResizingModeStretch];
108  [self setMaximumTrackImage:maximumTrackImage forState:UIControlStateNormal];
109}
110
111- (UIImage *)maximumTrackImage
112{
113  return [self thumbImageForState:UIControlStateNormal];
114}
115
116- (void)setThumbImage:(UIImage *)thumbImage
117{
118  [self setThumbImage:thumbImage forState:UIControlStateNormal];
119}
120
121- (UIImage *)thumbImage
122{
123  return [self thumbImageForState:UIControlStateNormal];
124}
125
126- (void)setInverted:(BOOL)inverted
127{
128  if (inverted) {
129    self.transform = CGAffineTransformMakeScale(-1, 1);
130  } else {
131    self.transform = CGAffineTransformMakeScale(1, 1);
132  }
133}
134
135- (void)setDisabled:(BOOL)disabled
136{
137    self.enabled = !disabled;
138    [self layoutSubviews];
139}
140
141- (float)discreteValue:(float)value
142{
143    if (self.step > 0 && value >= self.maximumValue) {
144        return self.maximumValue;
145    }
146
147    if (self.step > 0 && self.step <= (self.maximumValue - self.minimumValue)) {
148        double (^_round)(double) = ^(double x) {
149            if (!UIAccessibilityIsVoiceOverRunning()) {
150                return round(x);
151            } else if (self.lastValue > value) {
152                return floor(x);
153            } else {
154                return ceil(x);
155            }
156        };
157
158        return MAX(self.minimumValue,
159            MIN(self.maximumValue, self.minimumValue + _round((value - self.minimumValue) / self.step) * self.step)
160        );
161    }
162
163    return value;
164}
165
166@end
167