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

@ -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<Widget> 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<QuillCustomButton> 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,
),
),
);
}

@ -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<Widget> buttons;
@override
@ -20,8 +24,8 @@ class ArrowIndicatedButtonList extends StatefulWidget {
class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
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<ArrowIndicatedButtonList>
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
_buildLeftArrow(),
_buildScrollableList(),
_buildRightColor(),
],
);
final children = <Widget>[
_buildBackwardArrow(),
_buildScrollableList(),
_buildForwardArrow(),
];
return widget.axis == Axis.horizontal
? Row(
children: children,
)
: Column(
children: children,
);
}
@override
@ -63,20 +73,29 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
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<ArrowIndicatedButtonList>
// 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<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(
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,
),
);
}

@ -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<Attribute> attributes;
@ -67,53 +69,60 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
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() {

@ -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

Loading…
Cancel
Save