Skip to content
This repository was archived by the owner on Apr 29, 2021. It is now read-only.

Commit 5b383f5

Browse files
authored
Merge pull request #282 from UnityTech/yczhang1.5.4
Implement slider.
2 parents 437cc30 + f3c4253 commit 5b383f5

File tree

2 files changed

+391
-0
lines changed

2 files changed

+391
-0
lines changed

Runtime/cupertino/slider.cs

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
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+
}

Runtime/cupertino/slider.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)