1
+ using System ;
2
+ using Unity . UIWidgets . animation ;
3
+ using Unity . UIWidgets . foundation ;
4
+ using Unity . UIWidgets . gestures ;
5
+ using Unity . UIWidgets . rendering ;
6
+ using Unity . UIWidgets . scheduler ;
7
+ using Unity . UIWidgets . ui ;
8
+ using Unity . UIWidgets . widgets ;
9
+ using UnityEngine ;
10
+ using Canvas = Unity . UIWidgets . ui . Canvas ;
11
+ using Color = Unity . UIWidgets . ui . Color ;
12
+ using Rect = Unity . UIWidgets . ui . Rect ;
13
+
14
+ namespace Unity . UIWidgets . cupertino {
15
+ class SliderUtils {
16
+ public const float _kPadding = 8.0f ;
17
+ public static readonly Color _kTrackColor = new Color ( 0xFFB5B5B5 ) ;
18
+ public const float _kSliderHeight = 2.0f * ( CupertinoThumbPainter . radius + _kPadding ) ;
19
+ public const float _kSliderWidth = 176.0f ; // Matches Material Design slider.
20
+ public static readonly TimeSpan _kDiscreteTransitionDuration = new TimeSpan ( 0 , 0 , 0 , 0 , 500 ) ;
21
+ public const float _kAdjustmentUnit = 0.1f ; // Matches iOS implementation of material slider.
22
+ }
23
+
24
+ public class CupertinoSlider : StatefulWidget {
25
+ public CupertinoSlider (
26
+ Key key = null ,
27
+ float ? value = null ,
28
+ ValueChanged < float > onChanged = null ,
29
+ ValueChanged < float > onChangeStart = null ,
30
+ ValueChanged < float > onChangeEnd = null ,
31
+ float min = 0.0f ,
32
+ float max = 1.0f ,
33
+ int ? divisions = null ,
34
+ Color activeColor = null
35
+ ) : base ( key : key ) {
36
+ D . assert ( value != null ) ;
37
+ D . assert ( onChanged != null ) ;
38
+ D . assert ( min != null ) ;
39
+ D . assert ( max != null ) ;
40
+ D . assert ( value >= min && value <= max ) ;
41
+ D . assert ( divisions == null || divisions > 0 ) ;
42
+ this . value = value . Value ;
43
+ this . onChanged = onChanged ;
44
+ this . onChangeStart = onChangeStart ;
45
+ this . onChangeEnd = onChangeEnd ;
46
+ this . min = min ;
47
+ this . max = max ;
48
+ this . divisions = divisions ;
49
+ this . activeColor = activeColor ;
50
+ }
51
+
52
+ public readonly float value ;
53
+
54
+ public readonly ValueChanged < float > onChanged ;
55
+
56
+ public readonly ValueChanged < float > onChangeStart ;
57
+
58
+ public readonly ValueChanged < float > onChangeEnd ;
59
+
60
+ public readonly float min ;
61
+
62
+ public readonly float max ;
63
+
64
+ public readonly int ? divisions ;
65
+
66
+ public readonly Color activeColor ;
67
+
68
+ public override State createState ( ) {
69
+ return new _CupertinoSliderState ( ) ;
70
+ }
71
+
72
+ public override void debugFillProperties ( DiagnosticPropertiesBuilder properties ) {
73
+ base . debugFillProperties ( properties ) ;
74
+ properties . add ( new FloatProperty ( "value" , this . value ) ) ;
75
+ properties . add ( new FloatProperty ( "min" , this . min ) ) ;
76
+ properties . add ( new FloatProperty ( "max" , this . max ) ) ;
77
+ }
78
+ }
79
+
80
+ class _CupertinoSliderState : TickerProviderStateMixin < CupertinoSlider > {
81
+ void _handleChanged ( float value ) {
82
+ D . assert ( this . widget . onChanged != null ) ;
83
+ float lerpValue = MathUtils . lerpFloat ( this . widget . min , this . widget . max , value ) ;
84
+ if ( lerpValue != this . widget . value ) {
85
+ this . widget . onChanged ( lerpValue ) ;
86
+ }
87
+ }
88
+
89
+ void _handleDragStart ( float value ) {
90
+ D . assert ( this . widget . onChangeStart != null ) ;
91
+ this . widget . onChangeStart ( MathUtils . lerpFloat ( this . widget . min , this . widget . max , value ) ) ;
92
+ }
93
+
94
+ void _handleDragEnd ( float value ) {
95
+ D . assert ( this . widget . onChangeEnd != null ) ;
96
+ this . widget . onChangeEnd ( MathUtils . lerpFloat ( this . widget . min , this . widget . max , value ) ) ;
97
+ }
98
+
99
+ public override Widget build ( BuildContext context ) {
100
+ return new _CupertinoSliderRenderObjectWidget (
101
+ value : ( this . widget . value - this . widget . min ) / ( this . widget . max - this . widget . min ) ,
102
+ divisions : this . widget . divisions ,
103
+ activeColor : this . widget . activeColor ?? CupertinoTheme . of ( context ) . primaryColor ,
104
+ onChanged : this . widget . onChanged != null ? ( ValueChanged < float > ) this . _handleChanged : null ,
105
+ onChangeStart : this . widget . onChangeStart != null ? ( ValueChanged < float > ) this . _handleDragStart : null ,
106
+ onChangeEnd : this . widget . onChangeEnd != null ? ( ValueChanged < float > ) this . _handleDragEnd : null ,
107
+ vsync : this
108
+ ) ;
109
+ }
110
+ }
111
+
112
+ class _CupertinoSliderRenderObjectWidget : LeafRenderObjectWidget {
113
+ public _CupertinoSliderRenderObjectWidget (
114
+ Key key = null ,
115
+ float ? value = null ,
116
+ int ? divisions = null ,
117
+ Color activeColor = null ,
118
+ ValueChanged < float > onChanged = null ,
119
+ ValueChanged < float > onChangeStart = null ,
120
+ ValueChanged < float > onChangeEnd = null ,
121
+ TickerProvider vsync = null
122
+ ) : base ( key : key ) {
123
+ this . value = value ;
124
+ this . divisions = divisions ;
125
+ this . activeColor = activeColor ;
126
+ this . onChanged = onChanged ;
127
+ this . onChangeStart = onChangeStart ;
128
+ this . onChangeEnd = onChangeEnd ;
129
+ this . vsync = vsync ;
130
+ }
131
+
132
+ public readonly float ? value ;
133
+ public readonly int ? divisions ;
134
+ public readonly Color activeColor ;
135
+ public readonly ValueChanged < float > onChanged ;
136
+ public readonly ValueChanged < float > onChangeStart ;
137
+ public readonly ValueChanged < float > onChangeEnd ;
138
+ public readonly TickerProvider vsync ;
139
+
140
+ public override RenderObject createRenderObject ( BuildContext context ) {
141
+ return new _RenderCupertinoSlider (
142
+ value : this . value ?? 0.0f ,
143
+ divisions : this . divisions ,
144
+ activeColor : this . activeColor ,
145
+ onChanged : this . onChanged ,
146
+ onChangeStart : this . onChangeStart ,
147
+ onChangeEnd : this . onChangeEnd ,
148
+ vsync : this . vsync
149
+ ) ;
150
+ }
151
+
152
+ public override void updateRenderObject ( BuildContext context , RenderObject _renderObject ) {
153
+ _RenderCupertinoSlider renderObject = _renderObject as _RenderCupertinoSlider ;
154
+ renderObject . value = this . value ?? 0.0f ;
155
+ renderObject . divisions = this . divisions ?? 0 ;
156
+ renderObject . activeColor = this . activeColor ;
157
+ renderObject . onChanged = this . onChanged ;
158
+ renderObject . onChangeStart = this . onChangeStart ;
159
+ renderObject . onChangeEnd = this . onChangeEnd ;
160
+ }
161
+ }
162
+
163
+ class _RenderCupertinoSlider : RenderConstrainedBox {
164
+ public _RenderCupertinoSlider (
165
+ float value ,
166
+ int ? divisions = null ,
167
+ Color activeColor = null ,
168
+ ValueChanged < float > onChanged = null ,
169
+ ValueChanged < float > onChangeStart = null ,
170
+ ValueChanged < float > onChangeEnd = null ,
171
+ TickerProvider vsync = null
172
+ ) : base ( additionalConstraints : BoxConstraints . tightFor ( width : SliderUtils . _kSliderWidth ,
173
+ height : SliderUtils . _kSliderHeight ) ) {
174
+ D . assert ( value != null && value >= 0.0f && value <= 1.0f ) ;
175
+ this . _value = value ;
176
+ this . _divisions = divisions ;
177
+ this . _activeColor = activeColor ;
178
+ this . _onChanged = onChanged ;
179
+ this . _drag = new HorizontalDragGestureRecognizer ( ) ;
180
+ this . _drag . onStart = this . _handleDragStart ;
181
+ this . _drag . onUpdate = this . _handleDragUpdate ;
182
+ this . _drag . onEnd = this . _handleDragEnd ;
183
+ this . _position = new AnimationController (
184
+ value : value ,
185
+ duration : SliderUtils . _kDiscreteTransitionDuration ,
186
+ vsync : vsync
187
+ ) ;
188
+ this . _position . addListener ( this . markNeedsPaint ) ;
189
+ }
190
+
191
+ public float value {
192
+ get { return this . _value ; }
193
+ set {
194
+ D . assert ( value != null && value >= 0.0f && value <= 1.0f ) ;
195
+ if ( value == this . _value ) {
196
+ return ;
197
+ }
198
+
199
+ this . _value = value ;
200
+ if ( this . divisions != null ) {
201
+ this . _position . animateTo ( value , curve : Curves . fastOutSlowIn ) ;
202
+ }
203
+ else {
204
+ this . _position . setValue ( value ) ;
205
+ }
206
+ }
207
+ }
208
+
209
+ float _value ;
210
+
211
+ public int ? divisions {
212
+ get { return this . _divisions ; }
213
+ set {
214
+ if ( value == this . _divisions ) {
215
+ return ;
216
+ }
217
+
218
+ this . _divisions = value ;
219
+ this . markNeedsPaint ( ) ;
220
+ }
221
+ }
222
+
223
+ int ? _divisions ;
224
+
225
+ public Color activeColor {
226
+ get { return this . _activeColor ; }
227
+ set {
228
+ if ( value == this . _activeColor ) {
229
+ return ;
230
+ }
231
+
232
+ this . _activeColor = value ;
233
+ this . markNeedsPaint ( ) ;
234
+ }
235
+ }
236
+
237
+ Color _activeColor ;
238
+
239
+ public ValueChanged < float > onChanged {
240
+ get { return this . _onChanged ; }
241
+ set {
242
+ if ( value == this . _onChanged ) {
243
+ return ;
244
+ }
245
+
246
+ this . _onChanged = value ;
247
+ }
248
+ }
249
+
250
+ ValueChanged < float > _onChanged ;
251
+
252
+ public ValueChanged < float > onChangeStart ;
253
+ public ValueChanged < float > onChangeEnd ;
254
+
255
+
256
+ AnimationController _position ;
257
+
258
+ HorizontalDragGestureRecognizer _drag ;
259
+ float _currentDragValue = 0.0f ;
260
+
261
+ float _discretizedCurrentDragValue {
262
+ get {
263
+ float dragValue = this . _currentDragValue . clamp ( 0.0f , 1.0f ) ;
264
+ if ( this . divisions != null ) {
265
+ dragValue = Mathf . Round ( dragValue * this . divisions . Value ) / this . divisions . Value ;
266
+ }
267
+
268
+ return dragValue ;
269
+ }
270
+ }
271
+
272
+ public float _trackLeft {
273
+ get { return SliderUtils . _kPadding ; }
274
+ }
275
+
276
+ public float _trackRight {
277
+ get { return this . size . width - SliderUtils . _kPadding ; }
278
+ }
279
+
280
+ float _thumbCenter {
281
+ get {
282
+ float visualPosition = this . _value ;
283
+
284
+ return MathUtils . lerpFloat ( this . _trackLeft + CupertinoThumbPainter . radius ,
285
+ this . _trackRight - CupertinoThumbPainter . radius ,
286
+ visualPosition ) ;
287
+ }
288
+ }
289
+
290
+ public bool isInteractive {
291
+ get { return this . onChanged != null ; }
292
+ }
293
+
294
+ void _handleDragStart ( DragStartDetails details ) {
295
+ this . _startInteraction ( details . globalPosition ) ;
296
+ }
297
+
298
+ void _handleDragUpdate ( DragUpdateDetails details ) {
299
+ if ( this . isInteractive ) {
300
+ float extent = Mathf . Max ( SliderUtils . _kPadding ,
301
+ this . size . width - 2.0f * ( SliderUtils . _kPadding + CupertinoThumbPainter . radius ) ) ;
302
+ float ? valueDelta = details . primaryDelta / extent ;
303
+ this . _currentDragValue += valueDelta ?? 0.0f ;
304
+
305
+ this . onChanged ( this . _discretizedCurrentDragValue ) ;
306
+ }
307
+ }
308
+
309
+ void _handleDragEnd ( DragEndDetails details ) {
310
+ this . _endInteraction ( ) ;
311
+ }
312
+
313
+ void _startInteraction ( Offset globalPosition ) {
314
+ if ( this . isInteractive ) {
315
+ if ( this . onChangeStart != null ) {
316
+ this . onChangeStart ( this . _discretizedCurrentDragValue ) ;
317
+ }
318
+
319
+ this . _currentDragValue = this . _value ;
320
+ this . onChanged ( this . _discretizedCurrentDragValue ) ;
321
+ }
322
+ }
323
+
324
+ void _endInteraction ( ) {
325
+ if ( this . onChangeEnd != null ) {
326
+ this . onChangeEnd ( this . _discretizedCurrentDragValue ) ;
327
+ }
328
+
329
+ this . _currentDragValue = 0.0f ;
330
+ }
331
+
332
+ protected override bool hitTestSelf ( Offset position ) {
333
+ return ( position . dx - this . _thumbCenter ) . abs ( ) < CupertinoThumbPainter . radius + SliderUtils . _kPadding ;
334
+ }
335
+
336
+ public override void handleEvent ( PointerEvent e , HitTestEntry entry ) {
337
+ D . assert ( this . debugHandleEvent ( e , entry ) ) ;
338
+ if ( e is PointerDownEvent pointerDownEvent && this . isInteractive ) {
339
+ this . _drag . addPointer ( pointerDownEvent ) ;
340
+ }
341
+ }
342
+
343
+ CupertinoThumbPainter _thumbPainter = new CupertinoThumbPainter ( ) ;
344
+
345
+ public override
346
+ void paint ( PaintingContext context , Offset offset ) {
347
+ float visualPosition ;
348
+ Color leftColor ;
349
+ Color rightColor ;
350
+ visualPosition = this . _position . value ;
351
+ leftColor = SliderUtils . _kTrackColor ;
352
+ rightColor = this . _activeColor ;
353
+
354
+ float trackCenter = offset . dy + this . size . height / 2.0f ;
355
+ float trackLeft = offset . dx + this . _trackLeft ;
356
+ float trackTop = trackCenter - 1.0f ;
357
+ float trackBottom = trackCenter + 1.0f ;
358
+ float trackRight = offset . dx + this . _trackRight ;
359
+ float trackActive = offset . dx + this . _thumbCenter ;
360
+
361
+ Canvas canvas = context . canvas ;
362
+
363
+ if ( visualPosition > 0.0f ) {
364
+ Paint paint = new Paint ( ) ;
365
+ paint . color = rightColor ;
366
+ canvas . drawRRect ( RRect . fromLTRBXY ( trackLeft , trackTop , trackActive , trackBottom , 1.0f , 1.0f ) , paint ) ;
367
+ }
368
+
369
+ if ( visualPosition < 1.0f ) {
370
+ Paint paint = new Paint ( ) ;
371
+ paint . color = leftColor ;
372
+ canvas . drawRRect ( RRect . fromLTRBXY ( trackActive , trackTop , trackRight , trackBottom , 1.0f , 1.0f ) , paint ) ;
373
+ }
374
+
375
+ Offset thumbCenter = new Offset ( trackActive , trackCenter ) ;
376
+ this . _thumbPainter . paint ( canvas ,
377
+ Rect . fromCircle ( center : thumbCenter , radius : CupertinoThumbPainter . radius ) ) ;
378
+ }
379
+ }
380
+ }
0 commit comments