mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: increase line spacing
This commit is contained in:
parent
255cb47876
commit
e9d8dc9657
@ -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,
|
||||
|
@ -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>
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user