Vertical toolbar (#1101)

pull/1102/head
Adil Hanney 2 years ago committed by GitHub
parent 76ea6c0e7e
commit e81ac15976
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CHANGELOG.md
  2. 84
      lib/src/widgets/toolbar.dart
  3. 80
      lib/src/widgets/toolbar/arrow_indicated_button_list.dart
  4. 97
      lib/src/widgets/toolbar/select_header_style_button.dart
  5. 2
      pubspec.yaml

@ -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] # [6.3.5]
* Ability to add custom shortcuts. * Ability to add custom shortcuts.

@ -45,8 +45,10 @@ const double kIconButtonFactor = 1.77;
class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
const QuillToolbar({ const QuillToolbar({
required this.children, required this.children,
this.toolbarHeight = 36, this.axis = Axis.horizontal,
this.toolbarSize = 36,
this.toolbarIconAlignment = WrapAlignment.center, this.toolbarIconAlignment = WrapAlignment.center,
this.toolbarIconCrossAlignment = WrapCrossAlignment.center,
this.toolbarSectionSpacing = 4, this.toolbarSectionSpacing = 4,
this.multiRowsDisplay = true, this.multiRowsDisplay = true,
this.color, this.color,
@ -58,9 +60,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
factory QuillToolbar.basic({ factory QuillToolbar.basic({
required QuillController controller, required QuillController controller,
Axis axis = Axis.horizontal,
double toolbarIconSize = kDefaultIconSize, double toolbarIconSize = kDefaultIconSize,
double toolbarSectionSpacing = 4, double toolbarSectionSpacing = 4,
WrapAlignment toolbarIconAlignment = WrapAlignment.center, WrapAlignment toolbarIconAlignment = WrapAlignment.center,
WrapCrossAlignment toolbarIconCrossAlignment = WrapCrossAlignment.center,
bool showDividers = true, bool showDividers = true,
bool showFontFamily = true, bool showFontFamily = true,
bool showFontSize = true, bool showFontSize = true,
@ -170,10 +174,12 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
return QuillToolbar( return QuillToolbar(
key: key, key: key,
axis: axis,
color: color, color: color,
toolbarHeight: toolbarIconSize * 2, toolbarSize: toolbarIconSize * 2,
toolbarSectionSpacing: toolbarSectionSpacing, toolbarSectionSpacing: toolbarSectionSpacing,
toolbarIconAlignment: toolbarIconAlignment, toolbarIconAlignment: toolbarIconAlignment,
toolbarIconCrossAlignment: toolbarIconCrossAlignment,
multiRowsDisplay: multiRowsDisplay, multiRowsDisplay: multiRowsDisplay,
customButtons: customButtons, customButtons: customButtons,
locale: locale, locale: locale,
@ -334,11 +340,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
isButtonGroupShown[3] || isButtonGroupShown[3] ||
isButtonGroupShown[4] || isButtonGroupShown[4] ||
isButtonGroupShown[5])) isButtonGroupShown[5]))
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showAlignmentButtons) if (showAlignmentButtons)
SelectAlignmentButton( SelectAlignmentButton(
controller: controller, controller: controller,
@ -365,14 +367,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
isButtonGroupShown[3] || isButtonGroupShown[3] ||
isButtonGroupShown[4] || isButtonGroupShown[4] ||
isButtonGroupShown[5])) isButtonGroupShown[5]))
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showHeaderStyle) if (showHeaderStyle)
SelectHeaderStyleButton( SelectHeaderStyleButton(
controller: controller, controller: controller,
axis: axis,
iconSize: toolbarIconSize, iconSize: toolbarIconSize,
iconTheme: iconTheme, iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
@ -383,11 +382,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
(isButtonGroupShown[3] || (isButtonGroupShown[3] ||
isButtonGroupShown[4] || isButtonGroupShown[4] ||
isButtonGroupShown[5])) isButtonGroupShown[5]))
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showListNumbers) if (showListNumbers)
ToggleStyleButton( ToggleStyleButton(
attribute: Attribute.ol, attribute: Attribute.ol,
@ -427,11 +422,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
if (showDividers && if (showDividers &&
isButtonGroupShown[3] && isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5])) (isButtonGroupShown[4] || isButtonGroupShown[5]))
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showQuote) if (showQuote)
ToggleStyleButton( ToggleStyleButton(
attribute: Attribute.blockQuote, attribute: Attribute.blockQuote,
@ -460,11 +451,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
), ),
if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5])
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
if (showLink) if (showLink)
LinkStyleButton( LinkStyleButton(
controller: controller, controller: controller,
@ -484,11 +471,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
), ),
if (customButtons.isNotEmpty) if (customButtons.isNotEmpty)
if (showDividers) if (showDividers)
VerticalDivider( _dividerOnAxis(axis),
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
for (var customButton in customButtons) for (var customButton in customButtons)
QuillIconButton( QuillIconButton(
highlightElevation: 0, 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<Widget> children; final List<Widget> children;
final double toolbarHeight; final Axis axis;
final double toolbarSize;
final double toolbarSectionSpacing; final double toolbarSectionSpacing;
final WrapAlignment toolbarIconAlignment; final WrapAlignment toolbarIconAlignment;
final WrapCrossAlignment toolbarIconCrossAlignment;
final bool multiRowsDisplay; final bool multiRowsDisplay;
/// The color of the toolbar. /// The color of the toolbar.
@ -523,7 +524,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
final List<QuillCustomButton> customButtons; final List<QuillCustomButton> customButtons;
@override @override
Size get preferredSize => Size.fromHeight(toolbarHeight); Size get preferredSize => axis == Axis.horizontal
? Size.fromHeight(toolbarSize)
: Size.fromWidth(toolbarSize);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -531,16 +534,23 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
initialLocale: locale, initialLocale: locale,
child: multiRowsDisplay child: multiRowsDisplay
? Wrap( ? Wrap(
direction: axis,
alignment: toolbarIconAlignment, alignment: toolbarIconAlignment,
crossAxisAlignment: toolbarIconCrossAlignment,
runSpacing: 4, runSpacing: 4,
spacing: toolbarSectionSpacing, spacing: toolbarSectionSpacing,
children: children, children: children,
) )
: Container( : Container(
constraints: constraints: BoxConstraints.tightFor(
BoxConstraints.tightFor(height: preferredSize.height), height: axis == Axis.horizontal ? toolbarSize : null,
width: axis == Axis.vertical ? toolbarSize : null,
),
color: color ?? Theme.of(context).canvasColor, color: color ?? Theme.of(context).canvasColor,
child: ArrowIndicatedButtonList(buttons: children), child: ArrowIndicatedButtonList(
axis: axis,
buttons: children,
),
), ),
); );
} }

@ -7,9 +7,13 @@ import 'package:flutter/material.dart';
/// The arrow indicators are automatically hidden if the list is not /// The arrow indicators are automatically hidden if the list is not
/// scrollable in the direction of the respective arrow. /// scrollable in the direction of the respective arrow.
class ArrowIndicatedButtonList extends StatefulWidget { class ArrowIndicatedButtonList extends StatefulWidget {
const ArrowIndicatedButtonList({required this.buttons, Key? key}) const ArrowIndicatedButtonList({
: super(key: key); required this.axis,
required this.buttons,
Key? key,
}) : super(key: key);
final Axis axis;
final List<Widget> buttons; final List<Widget> buttons;
@override @override
@ -20,8 +24,8 @@ class ArrowIndicatedButtonList extends StatefulWidget {
class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList> class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
with WidgetsBindingObserver { with WidgetsBindingObserver {
final ScrollController _controller = ScrollController(); final ScrollController _controller = ScrollController();
bool _showLeftArrow = false; bool _showBackwardArrow = false;
bool _showRightArrow = false; bool _showForwardArrow = false;
@override @override
void initState() { void initState() {
@ -40,13 +44,19 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( final children = <Widget>[
children: <Widget>[ _buildBackwardArrow(),
_buildLeftArrow(), _buildScrollableList(),
_buildScrollableList(), _buildForwardArrow(),
_buildRightColor(), ];
],
); return widget.axis == Axis.horizontal
? Row(
children: children,
)
: Column(
children: children,
);
} }
@override @override
@ -63,20 +73,29 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_showLeftArrow = _showBackwardArrow =
_controller.position.minScrollExtent != _controller.position.pixels; _controller.position.minScrollExtent != _controller.position.pixels;
_showRightArrow = _showForwardArrow =
_controller.position.maxScrollExtent != _controller.position.pixels; _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( return SizedBox(
width: 8, width: 8,
child: Transform.translate( child: Transform.translate(
// Move the icon a few pixels to center it // Move the icon a few pixels to center it
offset: const Offset(-5, 0), 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<ArrowIndicatedButtonList>
// Remove the glowing effect, as we already have the arrow indicators // Remove the glowing effect, as we already have the arrow indicators
behavior: _NoGlowBehavior(), behavior: _NoGlowBehavior(),
// The CustomScrollView is necessary so that the children are not // 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( child: CustomScrollView(
scrollDirection: Axis.horizontal, scrollDirection: widget.axis,
controller: _controller, controller: _controller,
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
slivers: [ slivers: [
SliverFillRemaining( SliverFillRemaining(
hasScrollBody: false, hasScrollBody: false,
child: Row( child: widget.axis == Axis.horizontal
mainAxisAlignment: MainAxisAlignment.spaceEvenly, ? Row(
children: widget.buttons, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
), children: widget.buttons,
)
: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: widget.buttons,
),
) )
], ],
), ),
@ -106,13 +131,22 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
); );
} }
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( return SizedBox(
width: 8, width: 8,
child: Transform.translate( child: Transform.translate(
// Move the icon a few pixels to center it // Move the icon a few pixels to center it
offset: const Offset(-5, 0), offset: const Offset(-5, 0),
child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null, child: icon != null ? Icon(icon, size: 18) : null,
), ),
); );
} }

@ -10,6 +10,7 @@ import '../toolbar.dart';
class SelectHeaderStyleButton extends StatefulWidget { class SelectHeaderStyleButton extends StatefulWidget {
const SelectHeaderStyleButton({ const SelectHeaderStyleButton({
required this.controller, required this.controller,
this.axis = Axis.horizontal,
this.iconSize = kDefaultIconSize, this.iconSize = kDefaultIconSize,
this.iconTheme, this.iconTheme,
this.attributes = const [ this.attributes = const [
@ -23,6 +24,7 @@ class SelectHeaderStyleButton extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
final QuillController controller; final QuillController controller;
final Axis axis;
final double iconSize; final double iconSize;
final QuillIconTheme? iconTheme; final QuillIconTheme? iconTheme;
final List<Attribute> attributes; final List<Attribute> attributes;
@ -67,53 +69,60 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
fontSize: widget.iconSize * 0.7, fontSize: widget.iconSize * 0.7,
); );
return Row( final children = widget.attributes.map((attribute) {
mainAxisSize: MainAxisSize.min, final isSelected = _selectedAttribute == attribute;
children: widget.attributes.map((attribute) { return Padding(
final isSelected = _selectedAttribute == attribute; // ignore: prefer_const_constructors
return Padding( padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0),
// ignore: prefer_const_constructors child: ConstrainedBox(
padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), constraints: BoxConstraints.tightFor(
child: ConstrainedBox( width: widget.iconSize * kIconButtonFactor,
constraints: BoxConstraints.tightFor( height: widget.iconSize * kIconButtonFactor,
width: widget.iconSize * kIconButtonFactor, ),
height: widget.iconSize * kIconButtonFactor, child: RawMaterialButton(
), hoverElevation: 0,
child: RawMaterialButton( highlightElevation: 0,
hoverElevation: 0, elevation: 0,
highlightElevation: 0, visualDensity: VisualDensity.compact,
elevation: 0, shape: RoundedRectangleBorder(
visualDensity: VisualDensity.compact, borderRadius: BorderRadius.circular(
shape: RoundedRectangleBorder( widget.iconTheme?.borderRadius ?? 2)),
borderRadius: BorderRadius.circular( fillColor: isSelected
widget.iconTheme?.borderRadius ?? 2)), ? (widget.iconTheme?.iconSelectedFillColor ??
fillColor: isSelected Theme.of(context).primaryColor)
? (widget.iconTheme?.iconSelectedFillColor ?? : (widget.iconTheme?.iconUnselectedFillColor ??
Theme.of(context).primaryColor) theme.canvasColor),
: (widget.iconTheme?.iconUnselectedFillColor ?? onPressed: () {
theme.canvasColor), final _attribute = _selectedAttribute == attribute
onPressed: () { ? Attribute.header
final _attribute = _selectedAttribute == attribute : attribute;
? Attribute.header widget.controller.formatSelection(_attribute);
: attribute; widget.afterButtonPressed?.call();
widget.controller.formatSelection(_attribute); },
widget.afterButtonPressed?.call(); child: Text(
}, _valueToText[attribute] ?? '',
child: Text( style: style.copyWith(
_valueToText[attribute] ?? '', color: isSelected
style: style.copyWith( ? (widget.iconTheme?.iconSelectedColor ??
color: isSelected theme.primaryIconTheme.color)
? (widget.iconTheme?.iconSelectedColor ?? : (widget.iconTheme?.iconUnselectedColor ??
theme.primaryIconTheme.color) theme.iconTheme.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() { void _didChangeEditingValue() {

@ -1,6 +1,6 @@
name: flutter_quill name: flutter_quill
description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us)
version: 6.3.5 version: 6.4.0
#author: bulletjournal #author: bulletjournal
homepage: https://bulletjournal.us/home/index.html homepage: https://bulletjournal.us/home/index.html
repository: https://github.com/singerdmx/flutter-quill repository: https://github.com/singerdmx/flutter-quill

Loading…
Cancel
Save