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.size = const Size(20, 20),
|
||||||
this.color,
|
this.color,
|
||||||
this.number,
|
this.number,
|
||||||
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String? name;
|
final String? name;
|
||||||
final Size size;
|
final Size size;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
final int? number;
|
final int? number;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: padding ?? const EdgeInsets.all(0),
|
||||||
|
child: _buildSvg(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSvg() {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
return SizedBox.fromSize(
|
return SizedBox.fromSize(
|
||||||
size: size,
|
size: size,
|
||||||
|
@ -43,27 +43,33 @@ class BulletedListTextNodeWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
|
class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
|
||||||
with Selectable, DefaultSelectable {
|
with Selectable, DefaultSelectable {
|
||||||
|
@override
|
||||||
|
final iconKey = GlobalKey();
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text');
|
final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text');
|
||||||
final leftPadding = 20.0;
|
final _iconSize = 20.0;
|
||||||
|
final _iconRightPadding = 5.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Selectable<StatefulWidget> get forward =>
|
Selectable<StatefulWidget> get forward =>
|
||||||
_richTextKey.currentState as Selectable;
|
_richTextKey.currentState as Selectable;
|
||||||
|
|
||||||
@override
|
|
||||||
Offset get baseOffset {
|
|
||||||
return Offset(leftPadding, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: maxTextNodeWidth,
|
width: defaultMaxTextNodeWidth,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
FlowySvg(
|
FlowySvg(
|
||||||
size: Size.square(leftPadding),
|
key: iconKey,
|
||||||
|
size: Size.square(_iconSize),
|
||||||
|
padding:
|
||||||
|
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
|
||||||
name: 'point',
|
name: 'point',
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -76,6 +82,7 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,19 +41,17 @@ class CheckboxNodeWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
||||||
with Selectable, DefaultSelectable {
|
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
|
@override
|
||||||
Selectable<StatefulWidget> get forward =>
|
Selectable<StatefulWidget> get forward =>
|
||||||
_richTextKey.currentState as Selectable;
|
_richTextKey.currentState as Selectable;
|
||||||
|
|
||||||
@override
|
|
||||||
Offset get baseOffset {
|
|
||||||
return Offset(leftPadding, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.textNode.children.isEmpty) {
|
if (widget.textNode.children.isEmpty) {
|
||||||
@ -65,14 +63,20 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
|||||||
|
|
||||||
Widget _buildWithSingle(BuildContext context) {
|
Widget _buildWithSingle(BuildContext context) {
|
||||||
final check = widget.textNode.attributes.check;
|
final check = widget.textNode.attributes.check;
|
||||||
|
final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: maxTextNodeWidth,
|
width: defaultMaxTextNodeWidth,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: FlowySvg(
|
child: FlowySvg(
|
||||||
size: Size.square(leftPadding),
|
key: iconKey,
|
||||||
|
size: Size.square(_iconSize),
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: topPadding, right: _iconRightPadding),
|
||||||
name: check ? 'check' : 'uncheck',
|
name: check ? 'check' : 'uncheck',
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -95,7 +99,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildWithChildren(BuildContext context) {
|
Widget _buildWithChildren(BuildContext context) {
|
||||||
|
@ -6,7 +6,17 @@ import 'package:flutter/material.dart';
|
|||||||
mixin DefaultSelectable {
|
mixin DefaultSelectable {
|
||||||
Selectable get forward;
|
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) =>
|
Position getPositionInOffset(Offset start) =>
|
||||||
forward.getPositionInOffset(start);
|
forward.getPositionInOffset(start);
|
||||||
|
@ -41,9 +41,11 @@ class HeadingTextNodeWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
|
class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
|
||||||
with Selectable, DefaultSelectable {
|
with Selectable, DefaultSelectable {
|
||||||
|
@override
|
||||||
|
GlobalKey? get iconKey => null;
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'heading_text');
|
final _richTextKey = GlobalKey(debugLabel: 'heading_text');
|
||||||
final topPadding = 5.0;
|
final _topPadding = 5.0;
|
||||||
final bottomPadding = 2.0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Selectable<StatefulWidget> get forward =>
|
Selectable<StatefulWidget> get forward =>
|
||||||
@ -51,18 +53,18 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Offset get baseOffset {
|
Offset get baseOffset {
|
||||||
return Offset(0, topPadding);
|
return Offset(0, _topPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return Padding(
|
||||||
width: maxTextNodeWidth,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
top: topPadding,
|
top: _topPadding,
|
||||||
bottom: bottomPadding,
|
bottom: defaultLinePadding,
|
||||||
),
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
width: defaultMaxTextNodeWidth,
|
||||||
child: FlowyRichText(
|
child: FlowyRichText(
|
||||||
key: _richTextKey,
|
key: _richTextKey,
|
||||||
placeholderText: 'Heading',
|
placeholderText: 'Heading',
|
||||||
|
@ -43,27 +43,32 @@ class NumberListTextNodeWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
|
class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
|
||||||
with Selectable, DefaultSelectable {
|
with Selectable, DefaultSelectable {
|
||||||
|
@override
|
||||||
|
final iconKey = GlobalKey();
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'number_list_text');
|
final _richTextKey = GlobalKey(debugLabel: 'number_list_text');
|
||||||
final leftPadding = 20.0;
|
final _iconSize = 20.0;
|
||||||
|
final _iconRightPadding = 5.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Selectable<StatefulWidget> get forward =>
|
Selectable<StatefulWidget> get forward =>
|
||||||
_richTextKey.currentState as Selectable;
|
_richTextKey.currentState as Selectable;
|
||||||
|
|
||||||
@override
|
|
||||||
Offset get baseOffset {
|
|
||||||
return Offset(leftPadding, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding;
|
||||||
width: maxTextNodeWidth,
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||||
|
child: SizedBox(
|
||||||
|
width: defaultMaxTextNodeWidth,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
FlowySvg(
|
FlowySvg(
|
||||||
size: Size.square(leftPadding),
|
key: iconKey,
|
||||||
|
size: Size.square(_iconSize),
|
||||||
|
padding:
|
||||||
|
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
|
||||||
number: widget.textNode.attributes.number,
|
number: widget.textNode.attributes.number,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -76,6 +81,6 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,30 +42,32 @@ class QuotedTextNodeWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
||||||
with Selectable, DefaultSelectable {
|
with Selectable, DefaultSelectable {
|
||||||
|
@override
|
||||||
|
final iconKey = GlobalKey();
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'quoted_text');
|
final _richTextKey = GlobalKey(debugLabel: 'quoted_text');
|
||||||
final leftPadding = 20.0;
|
final _iconSize = 20.0;
|
||||||
|
final _iconRightPadding = 5.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Selectable<StatefulWidget> get forward =>
|
Selectable<StatefulWidget> get forward =>
|
||||||
_richTextKey.currentState as Selectable;
|
_richTextKey.currentState as Selectable;
|
||||||
|
|
||||||
@override
|
|
||||||
Offset get baseOffset {
|
|
||||||
return Offset(leftPadding, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: maxTextNodeWidth,
|
width: defaultMaxTextNodeWidth,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
FlowySvg(
|
FlowySvg(
|
||||||
size: Size(
|
key: iconKey,
|
||||||
leftPadding,
|
size: Size(_iconSize, _quoteHeight),
|
||||||
_quoteHeight,
|
padding:
|
||||||
),
|
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
|
||||||
name: 'quote',
|
name: 'quote',
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -78,12 +80,12 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
double get _quoteHeight {
|
double get _quoteHeight {
|
||||||
final lines =
|
final lines =
|
||||||
widget.textNode.toRawString().characters.where((c) => c == '\n').length;
|
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>
|
class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
|
||||||
with Selectable, DefaultSelectable {
|
with Selectable, DefaultSelectable {
|
||||||
|
@override
|
||||||
|
GlobalKey? get iconKey => null;
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'rich_text');
|
final _richTextKey = GlobalKey(debugLabel: 'rich_text');
|
||||||
final leftPadding = 20.0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Selectable<StatefulWidget> get forward =>
|
Selectable<StatefulWidget> get forward =>
|
||||||
_richTextKey.currentState as Selectable;
|
_richTextKey.currentState as Selectable;
|
||||||
|
|
||||||
@override
|
|
||||||
Offset get baseOffset {
|
|
||||||
return Offset.zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: maxTextNodeWidth,
|
width: defaultMaxTextNodeWidth,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||||
child: FlowyRichText(
|
child: FlowyRichText(
|
||||||
key: _richTextKey,
|
key: _richTextKey,
|
||||||
textNode: widget.textNode,
|
textNode: widget.textNode,
|
||||||
editorState: widget.editorState,
|
editorState: widget.editorState,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flowy_editor/document/attributes.dart';
|
import 'package:flowy_editor/document/attributes.dart';
|
||||||
|
import 'package:flowy_editor/document/node.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -60,7 +61,8 @@ class StyleKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: customize
|
// TODO: customize
|
||||||
double maxTextNodeWidth = 780.0;
|
double defaultMaxTextNodeWidth = 780.0;
|
||||||
|
double defaultLinePadding = 8.0;
|
||||||
double baseFontSize = 16.0;
|
double baseFontSize = 16.0;
|
||||||
// TODO: customize.
|
// TODO: customize.
|
||||||
Map<String, double> headingToFontSize = {
|
Map<String, double> headingToFontSize = {
|
||||||
@ -176,12 +178,33 @@ class RichTextStyle {
|
|||||||
RichTextStyle({
|
RichTextStyle({
|
||||||
required this.attributes,
|
required this.attributes,
|
||||||
required this.text,
|
required this.text,
|
||||||
|
this.height = 1.5,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
RichTextStyle.fromTextNode(TextNode textNode)
|
||||||
|
: this(attributes: textNode.attributes, text: textNode.toRawString());
|
||||||
|
|
||||||
final Attributes attributes;
|
final Attributes attributes;
|
||||||
final String text;
|
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(
|
return TextSpan(
|
||||||
text: text,
|
text: text,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -191,6 +214,7 @@ class RichTextStyle {
|
|||||||
color: _textColor,
|
color: _textColor,
|
||||||
decoration: _textDecoration,
|
decoration: _textDecoration,
|
||||||
background: _background,
|
background: _background,
|
||||||
|
height: height,
|
||||||
),
|
),
|
||||||
recognizer: _recognizer,
|
recognizer: _recognizer,
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user