diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index d4d00f5d..6b3a5e8c 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -6,6 +6,7 @@ import 'package:image_picker/image_picker.dart'; import '../models/documents/attribute.dart'; import 'controller.dart'; +import 'toolbar/arrow_indicated_button_list.dart'; import 'toolbar/clear_format_button.dart'; import 'toolbar/color_button.dart'; import 'toolbar/history_button.dart'; @@ -316,21 +317,9 @@ class _QuillToolbarState extends State { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.symmetric(horizontal: 8), constraints: BoxConstraints.tightFor(height: widget.preferredSize.height), color: Theme.of(context).canvasColor, - child: CustomScrollView( - scrollDirection: Axis.horizontal, - slivers: [ - SliverFillRemaining( - hasScrollBody: false, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: widget.children, - ), - ), - ], - ), + child: ArrowIndicatedButtonList(buttons: widget.children), ); } } diff --git a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart new file mode 100644 index 00000000..b17157d4 --- /dev/null +++ b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart @@ -0,0 +1,125 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +/// Scrollable list with arrow indicators. +/// +/// The arrow indicators are automatically hidden if the list is not +/// scrollable in the direction of the respective arrow. +class ArrowIndicatedButtonList extends StatefulWidget { + const ArrowIndicatedButtonList({required this.buttons, Key? key}) + : super(key: key); + + final List buttons; + + @override + _ArrowIndicatedButtonListState createState() => + _ArrowIndicatedButtonListState(); +} + +class _ArrowIndicatedButtonListState extends State + with WidgetsBindingObserver { + final ScrollController _controller = ScrollController(); + bool _showLeftArrow = false; + bool _showRightArrow = false; + + @override + void initState() { + super.initState(); + _controller.addListener(_handleScroll); + + // Listening to the WidgetsBinding instance is necessary so that we can + // hide the arrows when the window gets a new size and thus the toolbar + // becomes scrollable/unscrollable. + WidgetsBinding.instance!.addObserver(this); + + // Workaround to allow the scroll controller attach to our ListView so that + // we can detect if overflow arrows need to be shown on init. + Timer.run(_handleScroll); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + _buildLeftArrow(), + _buildScrollableList(), + _buildRightColor(), + ], + ); + } + + @override + void didChangeMetrics() => _handleScroll(); + + @override + void dispose() { + _controller.dispose(); + WidgetsBinding.instance!.removeObserver(this); + super.dispose(); + } + + void _handleScroll() { + setState(() { + _showLeftArrow = + _controller.position.minScrollExtent != _controller.position.pixels; + _showRightArrow = + _controller.position.maxScrollExtent != _controller.position.pixels; + }); + } + + Widget _buildLeftArrow() { + return SizedBox( + width: 8, + child: Transform.translate( + // Move the icon a few pixels to center it + offset: const Offset(-5, 0), + child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null, + ), + ); + } + + Widget _buildScrollableList() { + return Expanded( + child: ScrollConfiguration( + // Remove the glowing effect, as we already have the arrow indicators + behavior: _NoGlowBehavior(), + // The CustomScrollView is necessary so that the children are not + // stretched to the height of the toolbar, https://bit.ly/3uC3bjI + child: CustomScrollView( + scrollDirection: Axis.horizontal, + controller: _controller, + physics: const ClampingScrollPhysics(), + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: widget.buttons, + ), + ) + ], + ), + ), + ); + } + + Widget _buildRightColor() { + return SizedBox( + width: 8, + child: Transform.translate( + // Move the icon a few pixels to center it + offset: const Offset(-5, 0), + child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null, + ), + ); + } +} + +/// ScrollBehavior without the Material glow effect. +class _NoGlowBehavior extends ScrollBehavior { + @override + Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) { + return child; + } +}