diff --git a/CHANGELOG.md b/CHANGELOG.md index 439304c0..17eed562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# [6.4.0] +* Use `axis` to make the toolbar vertical. +* Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis. +* Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`. + # [6.3.5] * Ability to add custom shortcuts. diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 5ea2ad18..390317c2 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -45,8 +45,10 @@ const double kIconButtonFactor = 1.77; class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { const QuillToolbar({ required this.children, - this.toolbarHeight = 36, + this.axis = Axis.horizontal, + this.toolbarSize = 36, this.toolbarIconAlignment = WrapAlignment.center, + this.toolbarIconCrossAlignment = WrapCrossAlignment.center, this.toolbarSectionSpacing = 4, this.multiRowsDisplay = true, this.color, @@ -58,9 +60,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { factory QuillToolbar.basic({ required QuillController controller, + Axis axis = Axis.horizontal, double toolbarIconSize = kDefaultIconSize, double toolbarSectionSpacing = 4, WrapAlignment toolbarIconAlignment = WrapAlignment.center, + WrapCrossAlignment toolbarIconCrossAlignment = WrapCrossAlignment.center, bool showDividers = true, bool showFontFamily = true, bool showFontSize = true, @@ -170,10 +174,12 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { return QuillToolbar( key: key, + axis: axis, color: color, - toolbarHeight: toolbarIconSize * 2, + toolbarSize: toolbarIconSize * 2, toolbarSectionSpacing: toolbarSectionSpacing, toolbarIconAlignment: toolbarIconAlignment, + toolbarIconCrossAlignment: toolbarIconCrossAlignment, multiRowsDisplay: multiRowsDisplay, customButtons: customButtons, locale: locale, @@ -334,11 +340,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showAlignmentButtons) SelectAlignmentButton( controller: controller, @@ -365,14 +367,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showHeaderStyle) SelectHeaderStyleButton( controller: controller, + axis: axis, iconSize: toolbarIconSize, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -383,11 +382,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showListNumbers) ToggleStyleButton( attribute: Attribute.ol, @@ -427,11 +422,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showDividers && isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5])) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showQuote) ToggleStyleButton( attribute: Attribute.blockQuote, @@ -460,11 +451,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { afterButtonPressed: afterButtonPressed, ), if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showLink) LinkStyleButton( controller: controller, @@ -484,11 +471,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ), if (customButtons.isNotEmpty) if (showDividers) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), for (var customButton in customButtons) QuillIconButton( highlightElevation: 0, @@ -503,10 +486,28 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ); } + static Widget _dividerOnAxis(Axis axis) { + if (axis == Axis.horizontal) { + return const VerticalDivider( + indent: 12, + endIndent: 12, + color: Colors.grey, + ); + } else { + return const Divider( + indent: 12, + endIndent: 12, + color: Colors.grey, + ); + } + } + final List children; - final double toolbarHeight; + final Axis axis; + final double toolbarSize; final double toolbarSectionSpacing; final WrapAlignment toolbarIconAlignment; + final WrapCrossAlignment toolbarIconCrossAlignment; final bool multiRowsDisplay; /// The color of the toolbar. @@ -523,7 +524,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { final List customButtons; @override - Size get preferredSize => Size.fromHeight(toolbarHeight); + Size get preferredSize => axis == Axis.horizontal + ? Size.fromHeight(toolbarSize) + : Size.fromWidth(toolbarSize); @override Widget build(BuildContext context) { @@ -531,16 +534,23 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { initialLocale: locale, child: multiRowsDisplay ? Wrap( + direction: axis, alignment: toolbarIconAlignment, + crossAxisAlignment: toolbarIconCrossAlignment, runSpacing: 4, spacing: toolbarSectionSpacing, children: children, ) : Container( - constraints: - BoxConstraints.tightFor(height: preferredSize.height), + constraints: BoxConstraints.tightFor( + height: axis == Axis.horizontal ? toolbarSize : null, + width: axis == Axis.vertical ? toolbarSize : null, + ), color: color ?? Theme.of(context).canvasColor, - child: ArrowIndicatedButtonList(buttons: children), + child: ArrowIndicatedButtonList( + axis: axis, + buttons: children, + ), ), ); } diff --git a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart index 1c6917a1..94e500ff 100644 --- a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart +++ b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart @@ -7,9 +7,13 @@ import 'package:flutter/material.dart'; /// 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); + const ArrowIndicatedButtonList({ + required this.axis, + required this.buttons, + Key? key, + }) : super(key: key); + final Axis axis; final List buttons; @override @@ -20,8 +24,8 @@ class ArrowIndicatedButtonList extends StatefulWidget { class _ArrowIndicatedButtonListState extends State with WidgetsBindingObserver { final ScrollController _controller = ScrollController(); - bool _showLeftArrow = false; - bool _showRightArrow = false; + bool _showBackwardArrow = false; + bool _showForwardArrow = false; @override void initState() { @@ -40,13 +44,19 @@ class _ArrowIndicatedButtonListState extends State @override Widget build(BuildContext context) { - return Row( - children: [ - _buildLeftArrow(), - _buildScrollableList(), - _buildRightColor(), - ], - ); + final children = [ + _buildBackwardArrow(), + _buildScrollableList(), + _buildForwardArrow(), + ]; + + return widget.axis == Axis.horizontal + ? Row( + children: children, + ) + : Column( + children: children, + ); } @override @@ -63,20 +73,29 @@ class _ArrowIndicatedButtonListState extends State if (!mounted) return; setState(() { - _showLeftArrow = + _showBackwardArrow = _controller.position.minScrollExtent != _controller.position.pixels; - _showRightArrow = + _showForwardArrow = _controller.position.maxScrollExtent != _controller.position.pixels; }); } - Widget _buildLeftArrow() { + Widget _buildBackwardArrow() { + IconData? icon; + if (_showBackwardArrow) { + if (widget.axis == Axis.horizontal) { + icon = Icons.arrow_left; + } else { + icon = Icons.arrow_drop_up; + } + } + 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, + child: icon != null ? Icon(icon, size: 18) : null, ), ); } @@ -87,18 +106,24 @@ class _ArrowIndicatedButtonListState extends State // 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 + // stretched to the height of the toolbar: + // https://stackoverflow.com/a/65998731/7091839 child: CustomScrollView( - scrollDirection: Axis.horizontal, + scrollDirection: widget.axis, controller: _controller, physics: const ClampingScrollPhysics(), slivers: [ SliverFillRemaining( hasScrollBody: false, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: widget.buttons, - ), + child: widget.axis == Axis.horizontal + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: widget.buttons, + ) + : Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: widget.buttons, + ), ) ], ), @@ -106,13 +131,22 @@ class _ArrowIndicatedButtonListState extends State ); } - Widget _buildRightColor() { + Widget _buildForwardArrow() { + IconData? icon; + if (_showForwardArrow) { + if (widget.axis == Axis.horizontal) { + icon = Icons.arrow_right; + } else { + icon = Icons.arrow_drop_down; + } + } + 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, + child: icon != null ? Icon(icon, size: 18) : null, ), ); } diff --git a/lib/src/widgets/toolbar/select_header_style_button.dart b/lib/src/widgets/toolbar/select_header_style_button.dart index 72192398..5e1dee9f 100644 --- a/lib/src/widgets/toolbar/select_header_style_button.dart +++ b/lib/src/widgets/toolbar/select_header_style_button.dart @@ -10,6 +10,7 @@ import '../toolbar.dart'; class SelectHeaderStyleButton extends StatefulWidget { const SelectHeaderStyleButton({ required this.controller, + this.axis = Axis.horizontal, this.iconSize = kDefaultIconSize, this.iconTheme, this.attributes = const [ @@ -23,6 +24,7 @@ class SelectHeaderStyleButton extends StatefulWidget { }) : super(key: key); final QuillController controller; + final Axis axis; final double iconSize; final QuillIconTheme? iconTheme; final List attributes; @@ -67,53 +69,60 @@ class _SelectHeaderStyleButtonState extends State { fontSize: widget.iconSize * 0.7, ); - return Row( - mainAxisSize: MainAxisSize.min, - children: widget.attributes.map((attribute) { - final isSelected = _selectedAttribute == attribute; - return Padding( - // ignore: prefer_const_constructors - padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), - child: ConstrainedBox( - constraints: BoxConstraints.tightFor( - width: widget.iconSize * kIconButtonFactor, - height: widget.iconSize * kIconButtonFactor, - ), - child: RawMaterialButton( - hoverElevation: 0, - highlightElevation: 0, - elevation: 0, - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - widget.iconTheme?.borderRadius ?? 2)), - fillColor: isSelected - ? (widget.iconTheme?.iconSelectedFillColor ?? - Theme.of(context).primaryColor) - : (widget.iconTheme?.iconUnselectedFillColor ?? - theme.canvasColor), - onPressed: () { - final _attribute = _selectedAttribute == attribute - ? Attribute.header - : attribute; - widget.controller.formatSelection(_attribute); - widget.afterButtonPressed?.call(); - }, - child: Text( - _valueToText[attribute] ?? '', - style: style.copyWith( - color: isSelected - ? (widget.iconTheme?.iconSelectedColor ?? - theme.primaryIconTheme.color) - : (widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), - ), + final children = widget.attributes.map((attribute) { + final isSelected = _selectedAttribute == attribute; + return Padding( + // ignore: prefer_const_constructors + padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), + child: ConstrainedBox( + constraints: BoxConstraints.tightFor( + width: widget.iconSize * kIconButtonFactor, + height: widget.iconSize * kIconButtonFactor, + ), + child: RawMaterialButton( + hoverElevation: 0, + highlightElevation: 0, + elevation: 0, + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + widget.iconTheme?.borderRadius ?? 2)), + fillColor: isSelected + ? (widget.iconTheme?.iconSelectedFillColor ?? + Theme.of(context).primaryColor) + : (widget.iconTheme?.iconUnselectedFillColor ?? + theme.canvasColor), + onPressed: () { + final _attribute = _selectedAttribute == attribute + ? Attribute.header + : attribute; + widget.controller.formatSelection(_attribute); + widget.afterButtonPressed?.call(); + }, + child: Text( + _valueToText[attribute] ?? '', + style: style.copyWith( + color: isSelected + ? (widget.iconTheme?.iconSelectedColor ?? + theme.primaryIconTheme.color) + : (widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color), ), ), ), - ); - }).toList(), - ); + ), + ); + }).toList(); + + return widget.axis == Axis.horizontal + ? Row( + mainAxisSize: MainAxisSize.min, + children: children, + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: children, + ); } void _didChangeEditingValue() { diff --git a/pubspec.yaml b/pubspec.yaml index 1dc93bef..951881cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.3.5 +version: 6.4.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill