import 'package:flutter/material.dart'; import 'package:flyinsonolite/controls/text/fistext.dart'; import 'package:flyinsonolite/infrastructure/scale.dart'; typedef void ValueCallback(T value); /// 音量调节控件 0-100 class VolumeSlider extends StatefulWidget { final void Function(int) onVolumeChanged; final Widget child; final int initVolume; final double extraOffsetX; const VolumeSlider( {super.key, this.initVolume = 100, this.extraOffsetX = 0, required this.child, required this.onVolumeChanged}); String get pageName => "VolumeSlider"; @override State createState() { return _VolumeSliderState(); } } class _VolumeSliderState extends State { /// 音量调节控件高度 double get _height => 180.s; /// 音量调节控件宽度 double get _width => 30.s; OverlayEntry? _entry; int _volume = 100; /// 滑动时最后一次的音量值 int _lastChangedVolume = 100; /// 当前是否移入target bool _isEnterTarget = false; @override Widget build(BuildContext context) { return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: onTap, child: widget.child, )); } @override void dispose() { super.dispose(); if (_entry != null) { _entry?.remove(); _entry = null; } } @override void initState() { super.initState(); _volume = widget.initVolume; _lastChangedVolume = widget.initVolume; } /// 创建 Overlay 组件 Widget _buildOverlay(Offset offset) { return Directionality( textDirection: Directionality.of(context), child: Positioned( left: offset.dx, bottom: offset.dy, child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: BorderRadius.circular(8.s), ), margin: EdgeInsets.only(bottom: 5.s), width: _width * 2, height: _height, child: StatefulSlider( initVolume: _volume, onVolumeChanged: (value) { setState(() { _volume = value; }); widget.onVolumeChanged.call(_volume); }, onVolumeChanging: (value) { if (value == _lastChangedVolume) return; _lastChangedVolume = value; }, ), ), ), ); } /// 创建新的 OverlayEntry void _createNewEntry() { final OverlayState overlayState = Overlay.of( context, debugRequiredFor: widget, ); final RenderBox target = context.findRenderObject()! as RenderBox; final RenderBox targetConatiner = overlayState.context.findRenderObject() as RenderBox; final Offset targetOffset = target.localToGlobal( target.size.center(Offset.zero), ancestor: overlayState.context.findRenderObject(), ); final Offset overlayOffset = Offset( targetOffset.dx - target.size.width + widget.extraOffsetX, targetConatiner.size.height - targetOffset.dy + target.size.height / 2, ); final Widget overlay = _buildOverlay(overlayOffset); _entry = OverlayEntry(builder: (BuildContext context) => overlay); overlayState.insert(_entry!); } /// 隐藏 Tooltip void _hideTooltip() { _isEnterTarget = false; _hideTooltipLater(); } /// 立即隐藏 void _hideTooltipImmediately() { if (_entry == null) return; if (_entry!.mounted) { _entry?.remove(); _entry = null; } } /// 稍后隐藏 void _hideTooltipLater() { Future.delayed(const Duration(milliseconds: 500), () { if (_isEnterTarget) { _hideTooltipLater(); return; } _preHandleWhenRemove(); _hideTooltipImmediately(); }); } /// 提前移除的情况下预处理 void _preHandleWhenRemove() { if (_lastChangedVolume != _volume) { setState(() { _volume = _lastChangedVolume; }); widget.onVolumeChanged.call(_volume); } } /// 显示 Tooltip void _showTooltip() { _isEnterTarget = true; if (_entry != null) return; _createNewEntry(); } void onTap() { if (_isEnterTarget) { _hideTooltip(); } else { _showTooltip(); } } } class StatefulSlider extends StatefulWidget { final ValueCallback onVolumeChanged; final ValueCallback onVolumeChanging; final int initVolume; const StatefulSlider( {super.key, required this.onVolumeChanged, required this.onVolumeChanging, required this.initVolume}); @override _StatefulSliderState createState() => _StatefulSliderState(); } class _StatefulSliderState extends State { int _volume = 100; @override Widget build(BuildContext context) { /// 音量调节文字高度 double height = 30.s; double width = 30.s; return Column( children: [ Expanded( child: RotatedBox( quarterTurns: 3, child: SliderTheme( data: SliderThemeData( trackHeight: 5.s, thumbShape: RoundSliderThumbShape( enabledThumbRadius: 10.s, ), overlayShape: RoundSliderOverlayShape(overlayRadius: 24.s)), child: Slider( min: 0, max: 100, divisions: 100, value: _volume.toDouble(), onChanged: (value) { widget.onVolumeChanging(value.toInt()); setState(() { _volume = value.toInt(); }); }, onChangeEnd: (value) { widget.onVolumeChanged.call(value.toInt()); }, )), )), SizedBox( width: width, height: height, child: Center( child: FISText( "$_volume", style: TextStyle(color: Colors.white, fontSize: 14.s), )), ), ], ); } @override void initState() { super.initState(); _volume = widget.initVolume; } @override void didUpdateWidget(StatefulSlider oldWidget) { super.didUpdateWidget(oldWidget); _volume = widget.initVolume; } }