feat: increase line spacing

This commit is contained in:
Lucas.Xu 2022-08-09 17:50:32 +08:00
parent 255cb47876
commit e9d8dc9657
9 changed files with 198 additions and 135 deletions

View File

@ -8,15 +8,24 @@ class FlowySvg extends StatelessWidget {
this.size = const Size(20, 20),
this.color,
this.number,
this.padding,
}) : super(key: key);
final String? name;
final Size size;
final Color? color;
final int? number;
final EdgeInsets? padding;
@override
Widget build(BuildContext context) {
return Padding(
padding: padding ?? const EdgeInsets.all(0),
child: _buildSvg(),
);
}
Widget _buildSvg() {
if (name != null) {
return SizedBox.fromSize(
size: size,

View File

@ -43,27 +43,33 @@ class BulletedListTextNodeWidget extends StatefulWidget {
class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
with Selectable, DefaultSelectable {
@override
final iconKey = GlobalKey();
final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text');
final leftPadding = 20.0;
final _iconSize = 20.0;
final _iconRightPadding = 5.0;
@override
Selectable<StatefulWidget> get forward =>
_richTextKey.currentState as Selectable;
@override
Offset get baseOffset {
return Offset(leftPadding, 0);
}
@override
Widget build(BuildContext context) {
final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding;
return SizedBox(
width: maxTextNodeWidth,
width: defaultMaxTextNodeWidth,
child: Padding(
padding: EdgeInsets.only(bottom: defaultLinePadding),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowySvg(
size: Size.square(leftPadding),
key: iconKey,
size: Size.square(_iconSize),
padding:
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
name: 'point',
),
Expanded(
@ -76,6 +82,7 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
),
],
),
),
);
}
}

View File

@ -41,19 +41,17 @@ class CheckboxNodeWidget extends StatefulWidget {
class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
with Selectable, DefaultSelectable {
final _richTextKey = GlobalKey(debugLabel: 'checkbox_text');
@override
final iconKey = GlobalKey();
final leftPadding = 20.0;
final _richTextKey = GlobalKey(debugLabel: 'checkbox_text');
final _iconSize = 20.0;
final _iconRightPadding = 5.0;
@override
Selectable<StatefulWidget> get forward =>
_richTextKey.currentState as Selectable;
@override
Offset get baseOffset {
return Offset(leftPadding, 0);
}
@override
Widget build(BuildContext context) {
if (widget.textNode.children.isEmpty) {
@ -65,14 +63,20 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
Widget _buildWithSingle(BuildContext context) {
final check = widget.textNode.attributes.check;
final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding;
return SizedBox(
width: maxTextNodeWidth,
width: defaultMaxTextNodeWidth,
child: Padding(
padding: EdgeInsets.only(bottom: defaultLinePadding),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
child: FlowySvg(
size: Size.square(leftPadding),
key: iconKey,
size: Size.square(_iconSize),
padding: EdgeInsets.only(
top: topPadding, right: _iconRightPadding),
name: check ? 'check' : 'uncheck',
),
onTap: () {
@ -95,7 +99,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
),
],
),
);
));
}
Widget _buildWithChildren(BuildContext context) {

View File

@ -6,7 +6,17 @@ import 'package:flutter/material.dart';
mixin DefaultSelectable {
Selectable get forward;
Offset get baseOffset;
GlobalKey? get iconKey;
Offset get baseOffset {
if (iconKey != null) {
final renderBox = iconKey!.currentContext?.findRenderObject();
if (renderBox is RenderBox) {
return Offset(renderBox.size.width, 0);
}
}
return Offset.zero;
}
Position getPositionInOffset(Offset start) =>
forward.getPositionInOffset(start);

View File

@ -41,9 +41,11 @@ class HeadingTextNodeWidget extends StatefulWidget {
class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
with Selectable, DefaultSelectable {
@override
GlobalKey? get iconKey => null;
final _richTextKey = GlobalKey(debugLabel: 'heading_text');
final topPadding = 5.0;
final bottomPadding = 2.0;
final _topPadding = 5.0;
@override
Selectable<StatefulWidget> get forward =>
@ -51,18 +53,18 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
@override
Offset get baseOffset {
return Offset(0, topPadding);
return Offset(0, _topPadding);
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: maxTextNodeWidth,
child: Padding(
return Padding(
padding: EdgeInsets.only(
top: topPadding,
bottom: bottomPadding,
top: _topPadding,
bottom: defaultLinePadding,
),
child: SizedBox(
width: defaultMaxTextNodeWidth,
child: FlowyRichText(
key: _richTextKey,
placeholderText: 'Heading',

View File

@ -43,27 +43,32 @@ class NumberListTextNodeWidget extends StatefulWidget {
class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
with Selectable, DefaultSelectable {
@override
final iconKey = GlobalKey();
final _richTextKey = GlobalKey(debugLabel: 'number_list_text');
final leftPadding = 20.0;
final _iconSize = 20.0;
final _iconRightPadding = 5.0;
@override
Selectable<StatefulWidget> get forward =>
_richTextKey.currentState as Selectable;
@override
Offset get baseOffset {
return Offset(leftPadding, 0);
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: maxTextNodeWidth,
final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding;
return Padding(
padding: EdgeInsets.only(bottom: defaultLinePadding),
child: SizedBox(
width: defaultMaxTextNodeWidth,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowySvg(
size: Size.square(leftPadding),
key: iconKey,
size: Size.square(_iconSize),
padding:
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
number: widget.textNode.attributes.number,
),
Expanded(
@ -76,6 +81,6 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
),
],
),
);
));
}
}

View File

@ -42,30 +42,32 @@ class QuotedTextNodeWidget extends StatefulWidget {
class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
with Selectable, DefaultSelectable {
@override
final iconKey = GlobalKey();
final _richTextKey = GlobalKey(debugLabel: 'quoted_text');
final leftPadding = 20.0;
final _iconSize = 20.0;
final _iconRightPadding = 5.0;
@override
Selectable<StatefulWidget> get forward =>
_richTextKey.currentState as Selectable;
@override
Offset get baseOffset {
return Offset(leftPadding, 0);
}
@override
Widget build(BuildContext context) {
final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding;
return SizedBox(
width: maxTextNodeWidth,
width: defaultMaxTextNodeWidth,
child: Padding(
padding: EdgeInsets.only(bottom: defaultLinePadding),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowySvg(
size: Size(
leftPadding,
_quoteHeight,
),
key: iconKey,
size: Size(_iconSize, _quoteHeight),
padding:
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
name: 'quote',
),
Expanded(
@ -78,12 +80,12 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
),
],
),
);
));
}
double get _quoteHeight {
final lines =
widget.textNode.toRawString().characters.where((c) => c == '\n').length;
return (lines + 1) * leftPadding;
return (lines + 1) * _iconSize;
}
}

View File

@ -42,27 +42,27 @@ class RichTextNodeWidget extends StatefulWidget {
class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
with Selectable, DefaultSelectable {
@override
GlobalKey? get iconKey => null;
final _richTextKey = GlobalKey(debugLabel: 'rich_text');
final leftPadding = 20.0;
@override
Selectable<StatefulWidget> get forward =>
_richTextKey.currentState as Selectable;
@override
Offset get baseOffset {
return Offset.zero;
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: maxTextNodeWidth,
width: defaultMaxTextNodeWidth,
child: Padding(
padding: EdgeInsets.only(bottom: defaultLinePadding),
child: FlowyRichText(
key: _richTextKey,
textNode: widget.textNode,
editorState: widget.editorState,
),
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flowy_editor/document/attributes.dart';
import 'package:flowy_editor/document/node.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@ -60,7 +61,8 @@ class StyleKey {
}
// TODO: customize
double maxTextNodeWidth = 780.0;
double defaultMaxTextNodeWidth = 780.0;
double defaultLinePadding = 8.0;
double baseFontSize = 16.0;
// TODO: customize.
Map<String, double> headingToFontSize = {
@ -176,12 +178,33 @@ class RichTextStyle {
RichTextStyle({
required this.attributes,
required this.text,
this.height = 1.5,
});
RichTextStyle.fromTextNode(TextNode textNode)
: this(attributes: textNode.attributes, text: textNode.toRawString());
final Attributes attributes;
final String text;
final double height;
TextSpan toTextSpan() {
TextSpan toTextSpan() => _toTextSpan(height);
double get topPadding {
if (height == 1.0) {
return 0;
}
// TODO: Need to be optimized.
final painter =
TextPainter(text: _toTextSpan(height), textDirection: TextDirection.ltr)
..layout();
final basePainter =
TextPainter(text: _toTextSpan(null), textDirection: TextDirection.ltr)
..layout();
return painter.height - basePainter.height;
}
TextSpan _toTextSpan(double? height) {
return TextSpan(
text: text,
style: TextStyle(
@ -191,6 +214,7 @@ class RichTextStyle {
color: _textColor,
decoration: _textDecoration,
background: _background,
height: height,
),
recognizer: _recognizer,
);