feat: select school terms and smooth refractive Dock motion
This commit is contained in:
@@ -30,6 +30,14 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
value: 0,
|
||||
);
|
||||
bool _reduceMotion = false;
|
||||
late int _targetIndex;
|
||||
bool _elasticMotion = false;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_targetIndex = widget.selectedIndex;
|
||||
}
|
||||
|
||||
double get _pressure => _press.value.clamp(0.0, 1.18);
|
||||
void _pressTo(double target) {
|
||||
if (_reduceMotion) {
|
||||
@@ -38,7 +46,7 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
}
|
||||
_press.animateWith(
|
||||
SpringSimulation(
|
||||
const SpringDescription(mass: 1, stiffness: 440, damping: 20),
|
||||
const SpringDescription(mass: 1, stiffness: 440, damping: 17),
|
||||
_press.value,
|
||||
target,
|
||||
_press.velocity,
|
||||
@@ -55,26 +63,36 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
double? _drag;
|
||||
double _dragSpeed = 0;
|
||||
double get _position => _drag ?? _motion.value.clamp(-0.12, 2.12);
|
||||
void _spring(int index, [double velocity = 0]) {
|
||||
void _moveTo(int index, {bool elastic = false, double velocity = 0}) {
|
||||
_targetIndex = index;
|
||||
_elasticMotion = elastic && !_reduceMotion;
|
||||
if (_reduceMotion) {
|
||||
_motion.value = index.toDouble();
|
||||
return;
|
||||
}
|
||||
_motion.animateWith(
|
||||
SpringSimulation(
|
||||
const SpringDescription(mass: 1, stiffness: 380, damping: 23),
|
||||
_position,
|
||||
if (elastic) {
|
||||
_motion.animateWith(
|
||||
SpringSimulation(
|
||||
const SpringDescription(mass: 1, stiffness: 380, damping: 19),
|
||||
_position,
|
||||
index.toDouble(),
|
||||
velocity.clamp(-8, 8),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
_motion.animateTo(
|
||||
index.toDouble(),
|
||||
velocity.clamp(-8, 8),
|
||||
),
|
||||
);
|
||||
duration: const Duration(milliseconds: 320),
|
||||
curve: Curves.easeOutCubic,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _select(int index, [double velocity = 0]) {
|
||||
void _select(int index, {bool elastic = false, double velocity = 0}) {
|
||||
final position = _position;
|
||||
setState(() => _drag = null);
|
||||
_motion.value = position;
|
||||
_spring(index, velocity);
|
||||
_moveTo(index, elastic: elastic, velocity: velocity);
|
||||
_pressTo(0);
|
||||
widget.onSelected(index);
|
||||
}
|
||||
@@ -82,8 +100,10 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
@override
|
||||
void didUpdateWidget(GlassDock oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.selectedIndex != widget.selectedIndex && _drag == null) {
|
||||
_spring(widget.selectedIndex);
|
||||
// The parent acknowledges onSelected with a rebuild. Restarting here used
|
||||
// to erase the release velocity and start a second spring for the same tab.
|
||||
if (widget.selectedIndex != _targetIndex && _drag == null) {
|
||||
_moveTo(widget.selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,12 +144,10 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
child: lens
|
||||
? GlassRefraction(strength: 0.25 + pressure * 0.95, child: content)
|
||||
: BackdropFilter(
|
||||
filter: ui.ImageFilter.blur(sigmaX: 1.0, sigmaY: 1.0),
|
||||
child: content,
|
||||
),
|
||||
child: GlassRefraction(
|
||||
strength: lens ? 0.25 + pressure * 0.95 : 0.32,
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,12 +156,19 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
animation: Listenable.merge([_motion, _press]),
|
||||
builder: (context, _) {
|
||||
final press = _pressure;
|
||||
final speed = (_drag != null ? _dragSpeed : _motion.velocity).abs();
|
||||
final stretch = (speed / 16).clamp(0.0, 0.12) + press * 0.18;
|
||||
final speed = _reduceMotion
|
||||
? 0.0
|
||||
: (_drag != null
|
||||
? _dragSpeed
|
||||
: _elasticMotion
|
||||
? _motion.velocity
|
||||
: 0.0)
|
||||
.abs();
|
||||
final stretch = (speed / 16).clamp(0.0, 0.14) + press * 0.30;
|
||||
final lensWidth = _travel * (1 + stretch);
|
||||
final lensHeight =
|
||||
(_height - _inset * 2) *
|
||||
(1 + press * 0.19 - (speed / 16).clamp(0.0, 0.07));
|
||||
(1 + press * 0.31 - (speed / 16).clamp(0.0, 0.07));
|
||||
return SizedBox(
|
||||
width: _width,
|
||||
height: _height,
|
||||
@@ -201,7 +226,7 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
child: GestureDetector(
|
||||
key: index == widget.selectedIndex ? const Key('dock-cursor') : null,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTapDown: (_) => _pressTo(1),
|
||||
onTapDown: index == widget.selectedIndex ? (_) => _pressTo(1) : null,
|
||||
onTapCancel: () {
|
||||
if (_drag == null) _pressTo(0);
|
||||
},
|
||||
@@ -229,7 +254,8 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
onHorizontalDragEnd: index == widget.selectedIndex
|
||||
? (details) => _select(
|
||||
_position.round().clamp(0, 2),
|
||||
details.velocity.pixelsPerSecond.dx / _travel,
|
||||
elastic: true,
|
||||
velocity: details.velocity.pixelsPerSecond.dx / _travel,
|
||||
)
|
||||
: null,
|
||||
onHorizontalDragCancel: index == widget.selectedIndex
|
||||
@@ -238,7 +264,7 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
|
||||
final position = _position;
|
||||
setState(() => _drag = null);
|
||||
_motion.value = position;
|
||||
_spring(widget.selectedIndex);
|
||||
_moveTo(widget.selectedIndex, elastic: true);
|
||||
_pressTo(0);
|
||||
}
|
||||
: null,
|
||||
|
||||
Reference in New Issue
Block a user