import 'package:flutter/material.dart'; import 'package:flyinsono/lab/color/lab_colors.dart'; import 'package:flyinsono/lab/components/operate_button/more_operate_button.dart'; import 'package:flyinsono/lab/style/lab_box_decoration.dart'; /// 更多功能扩展菜单 class MoreOperateMenu extends StatefulWidget { const MoreOperateMenu({ super.key, required this.buttons, }) : super(); final List buttons; static const defaultTextStyle = TextStyle( color: LabColors.text200, fontSize: 12, ); static const defaultDecoration = BoxDecoration( color: LabColors.base800, borderRadius: BorderRadius.all(Radius.circular(4)), ); @override State createState() => MoreOperateMenuState(); static MoreOperateMenuState? maybeOf(BuildContext context) { // assert(context); return context.findAncestorStateOfType(); } } class MoreOperateMenuState extends State { OverlayEntry? _entry; bool _isHoverMore = false; /// 显示 Overlay void _showOverlay() { if (_entry != null) { return; } _createNewEntry(); } /// 隐藏 Overlay void _hideOverlay() { _entry?.remove(); _entry = null; } /// 创建新的 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 = _countOffset(targetConatiner.size, target.size, targetOffset); final Offset edgeOffset = _edgeCorrection(targetConatiner.size, target.size, overlayOffset); final Widget overlay = _buildOverlay(edgeOffset); _entry = OverlayEntry(builder: (BuildContext context) => overlay); _listenButtonClicked(); overlayState.insert(_entry!); } /// 监听按钮事件来关闭 Overlay void _listenButtonClicked() { for (final button in widget.buttons) { button.clicked.removeListener(_closeMoreMenu); } for (final button in widget.buttons) { button.clicked.addListener(_closeMoreMenu); } } /// 创建 Overlay 组件 Widget _buildOverlay( Offset offset, ) { return Directionality( textDirection: Directionality.of(context), child: Positioned.directional( top: offset.dy, start: offset.dx, textDirection: Directionality.of(context), child: MouseRegion( onEnter: _handleMouseEnterMenu, onExit: _handleMouseExitMenu, child: Container( padding: EdgeInsets.only(top: 5), child: Container( decoration: LabBoxDecoration.base.copyWith( color: LabColors.base300, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 2, spreadRadius: 1, ), ], ), padding: EdgeInsets.all(5), child: SizedBox( width: 100, child: Column( children: widget.buttons, ), ), ), ), ), ), ); } /// 计算位置布局 Offset _countOffset( Size containerSize, Size targetSize, Offset targetOffset) { final double x = targetOffset.dx; final double y = targetOffset.dy; final double w = targetSize.width; final double h = targetSize.height; return Offset(x - w / 2, y + h / 2); } /// 边缘矫正 Offset _edgeCorrection( Size containerSize, Size targetSize, Offset targetOffset) { final double x = targetOffset.dx; final double y = targetOffset.dy; final double w = targetSize.width; final double h = targetSize.height; final double cw = containerSize.width; final double ch = containerSize.height; if (x < 0) { return Offset(0, y); } if (x + w > cw) { return Offset(cw - w, y); } if (y < 0) { return Offset(x, 0); } if (y + h > ch) { return Offset(x, ch - h); } return targetOffset; } @override Widget build(BuildContext context) { Color _colorMore = _isHoverMore ? LabColors.base400 : Colors.transparent; return MouseRegion( cursor: SystemMouseCursors.click, onEnter: _handleMouseEnterMoreButton, onExit: _handleMouseExitMoreButton, child: Container( decoration: BoxDecoration( color: _colorMore, borderRadius: BorderRadius.circular(5), ), child: Container( width: 80, child: Icon( Icons.keyboard_arrow_down_rounded, size: 20, color: LabColors.base700, ), ), ), ); } void _handleMouseEnterMoreButton(PointerEvent details) { _showOverlay(); setState(() { _isHoverMore = true; }); } void _handleMouseExitMoreButton(PointerEvent details) { setState(() { _isHoverMore = false; }); // 如果下一帧不在菜单内,则隐藏 Overlay WidgetsBinding.instance.addPostFrameCallback((timeStamp) { if (!_isHoverMore) { _hideOverlay(); setState(() { _isHoverMore = false; }); } }); } void _handleMouseEnterMenu(PointerEvent details) { setState(() { _isHoverMore = true; }); } void _handleMouseExitMenu(PointerEvent details) { _hideOverlay(); setState(() { _isHoverMore = false; }); } void _closeMoreMenu() { _hideOverlay(); setState(() { _isHoverMore = false; }); } /// 组件入参更新时,如果 disable 为 true,则隐藏 Overlay @override void didUpdateWidget(MoreOperateMenu oldWidget) { super.didUpdateWidget(oldWidget); } @override void dispose() { super.dispose(); if (_entry != null) { _entry?.remove(); _entry = null; } } }