saminsohag

texts are selectable now

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
enum CustomRbSlot {
rb,
child,
}
class CustomRb
extends SlottedMultiChildRenderObjectWidget<CustomRbSlot, RenderBox> {
class CustomRb extends StatelessWidget {
const CustomRb({
super.key,
this.spacing = 5,
... ... @@ -23,268 +14,73 @@ class CustomRb
final TextDirection textDirection;
@override
Widget? childForSlot(CustomRbSlot slot) {
switch (slot) {
case CustomRbSlot.rb:
return Radio(
value: value,
groupValue: true,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) {},
);
case CustomRbSlot.child:
return child;
}
}
@override
SlottedContainerRenderObjectMixin<CustomRbSlot, RenderBox> createRenderObject(
BuildContext context) {
return RenderCustomRb(spacing, textDirection);
}
@override
void updateRenderObject(
BuildContext context, covariant RenderCustomRb renderObject) {
renderObject.spacing = spacing;
renderObject.textDirection = textDirection;
Widget build(BuildContext context) {
return Directionality(
textDirection: textDirection,
child: Row(
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Text.rich(
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding:
EdgeInsetsDirectional.only(start: spacing, end: spacing),
child: Radio(
value: value,
groupValue: true,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) {},
),
),
),
),
Expanded(
child: child,
)
],
),
);
}
@override
Iterable<CustomRbSlot> get slots => CustomRbSlot.values;
}
class RenderCustomRb extends RenderBox
with SlottedContainerRenderObjectMixin<CustomRbSlot, RenderBox> {
RenderCustomRb(this._spacing, this._textDirection);
double _spacing;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value) {
return;
}
_textDirection = value;
markNeedsLayout();
}
set spacing(double value) {
if (_spacing == value) {
return;
}
_spacing = value;
markNeedsLayout();
}
RenderBox? get rb => childForSlot(CustomRbSlot.rb);
RenderBox? get body => childForSlot(CustomRbSlot.child);
Size _layoutBox(RenderBox box, BoxConstraints constraints) {
box.layout(constraints, parentUsesSize: true);
return box.size;
}
@override
void performLayout() {
if (rb == null || body == null) {
size = constraints.constrain(const Size(50, 10));
return;
}
rb;
Size rbSize =
_layoutBox(rb!, const BoxConstraints(maxWidth: 50, maxHeight: 20));
Size bodySize = _layoutBox(
body!,
BoxConstraints(
maxWidth: constraints.maxWidth - rbSize.width - _spacing));
if (_textDirection == TextDirection.ltr) {
body!.parentData = BoxParentData()
..offset = Offset(rbSize.width + _spacing, 0);
rb!.parentData = BoxParentData()
..offset = Offset(
0,
body!.computeDistanceToActualBaseline(TextBaseline.alphabetic)! -
rb!.size.height / 1.5,
);
} else {
body!.parentData = BoxParentData()..offset = Offset(-_spacing, 0);
rb!.parentData = BoxParentData()
..offset = Offset(
bodySize.width,
body!.computeDistanceToActualBaseline(TextBaseline.alphabetic)! -
rb!.size.height / 1.5,
);
}
size = constraints.constrain(Size(bodySize.width + rbSize.width + _spacing,
max(rbSize.height, bodySize.height)));
}
@override
void paint(PaintingContext context, Offset offset) {
context.paintChild(
body!, offset + (body!.parentData as BoxParentData).offset);
context.paintChild(rb!, offset + (rb!.parentData as BoxParentData).offset);
}
@override
bool hitTestSelf(Offset position) {
return true;
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
for (final RenderBox child in children) {
final BoxParentData parentData = child.parentData! as BoxParentData;
final bool isHit = result.addWithPaintOffset(
offset: parentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - parentData.offset);
return child.hitTest(result, position: transformed);
},
);
if (isHit) {
return true;
}
}
return false;
}
}
enum CustomCbSlot {
cb,
child,
}
class CustomCb
extends SlottedMultiChildRenderObjectWidget<CustomCbSlot, RenderBox> {
const CustomCb(
{super.key,
this.spacing = 5,
this.textDirection = TextDirection.ltr,
required this.child,
required this.value});
class CustomCb extends StatelessWidget {
const CustomCb({
super.key,
this.spacing = 5,
required this.child,
this.textDirection = TextDirection.ltr,
required this.value,
});
final Widget child;
final TextDirection textDirection;
final bool value;
final double spacing;
final TextDirection textDirection;
@override
Widget? childForSlot(CustomCbSlot slot) {
switch (slot) {
case CustomCbSlot.cb:
return Checkbox(value: value, onChanged: (value) {});
case CustomCbSlot.child:
return child;
}
}
@override
SlottedContainerRenderObjectMixin<CustomCbSlot, RenderBox> createRenderObject(
BuildContext context) {
return RenderCustomCb(spacing, textDirection);
}
@override
void updateRenderObject(
BuildContext context, covariant RenderCustomCb renderObject) {
renderObject.spacing = spacing;
renderObject.textDirection = textDirection;
}
@override
Iterable<CustomCbSlot> get slots => CustomCbSlot.values;
}
class RenderCustomCb extends RenderBox
with SlottedContainerRenderObjectMixin<CustomCbSlot, RenderBox> {
RenderCustomCb(this._spacing, this._textDirection);
double _spacing;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value) {
return;
}
_textDirection = value;
markNeedsLayout();
}
set spacing(double value) {
if (_spacing == value) {
return;
}
_spacing = value;
markNeedsLayout();
}
RenderBox? get rb => childForSlot(CustomCbSlot.cb);
RenderBox? get body => childForSlot(CustomCbSlot.child);
Size _layoutBox(RenderBox box, BoxConstraints constraints) {
box.layout(constraints, parentUsesSize: true);
return box.size;
}
@override
void performLayout() {
if (rb == null || body == null) {
size = constraints.constrain(const Size(50, 10));
return;
}
rb;
Size rbSize =
_layoutBox(rb!, const BoxConstraints(maxWidth: 50, maxHeight: 20));
Size bodySize = _layoutBox(
body!,
BoxConstraints(
maxWidth: constraints.maxWidth - rbSize.width - _spacing));
if (_textDirection == TextDirection.ltr) {
body!.parentData = BoxParentData()
..offset = Offset(rbSize.width + _spacing, 0);
rb!.parentData = BoxParentData()
..offset = Offset(
0,
body!.computeDistanceToActualBaseline(TextBaseline.alphabetic)! -
rb!.size.height / 1.5);
} else {
body!.parentData = BoxParentData()..offset = Offset(-_spacing, 0);
rb!.parentData = BoxParentData()
..offset = Offset(
bodySize.width,
body!.computeDistanceToActualBaseline(TextBaseline.alphabetic)! -
rb!.size.height / 1.5);
}
size = constraints.constrain(Size(bodySize.width + rbSize.width + _spacing,
max(rbSize.height, bodySize.height)));
}
@override
void paint(PaintingContext context, Offset offset) {
context.paintChild(
body!, offset + (body!.parentData as BoxParentData).offset);
context.paintChild(rb!, offset + (rb!.parentData as BoxParentData).offset);
}
@override
bool hitTestSelf(Offset position) {
return true;
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
for (final RenderBox child in children) {
final BoxParentData parentData = child.parentData! as BoxParentData;
final bool isHit = result.addWithPaintOffset(
offset: parentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - parentData.offset);
return child.hitTest(result, position: transformed);
},
);
if (isHit) {
return true;
}
}
return false;
Widget build(BuildContext context) {
return Directionality(
textDirection: textDirection,
child: Row(
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Text.rich(
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding:
EdgeInsetsDirectional.only(start: spacing, end: spacing),
child: Checkbox(value: value, onChanged: (value) {}),
),
),
),
Expanded(
child: child,
)
],
),
);
}
}
... ...
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class UnorderedListView extends SingleChildRenderObjectWidget {
const UnorderedListView(
{super.key,
this.spacing = 6,
this.padding = 10,
this.bulletColor,
this.bulletSize = 4,
this.textDirection = TextDirection.ltr,
required super.child});
class UnorderedListView extends StatelessWidget {
const UnorderedListView({
super.key,
this.spacing = 8,
this.padding = 12,
this.bulletColor,
this.bulletSize = 4,
this.textDirection = TextDirection.ltr,
required this.child,
});
final double bulletSize;
final double spacing;
final double padding;
final TextDirection textDirection;
final Color? bulletColor;
@override
RenderObject createRenderObject(BuildContext context) {
return UnorderedListRenderObject(
spacing,
padding,
bulletColor ?? Theme.of(context).colorScheme.onSurface,
textDirection,
bulletSize,
final Widget child;
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: textDirection,
child: Row(
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
if (bulletSize == 0)
SizedBox(
width: spacing + padding,
)
else
Text.rich(
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding:
EdgeInsetsDirectional.only(start: padding, end: spacing),
child: Container(
width: bulletSize,
height: bulletSize,
decoration: BoxDecoration(
color: bulletColor,
shape: BoxShape.circle,
),
),
),
),
),
Expanded(
child: child,
)
],
),
);
}
@override
void updateRenderObject(
BuildContext context, covariant UnorderedListRenderObject renderObject) {
renderObject.bulletColor =
bulletColor ?? Theme.of(context).colorScheme.onSurface;
renderObject.bulletSize = bulletSize;
renderObject.spacing = spacing;
renderObject.padding = padding;
renderObject.textDirection = textDirection;
}
}
class UnorderedListRenderObject extends RenderProxyBox {
UnorderedListRenderObject(
double spacing,
double padding,
Color bulletColor,
TextDirection textDirection,
this._bulletSize, {
RenderBox? child,
}) : _bulletColor = bulletColor,
_spacing = spacing,
_padding = padding,
_textDirection = textDirection,
super(child);
double _spacing;
double _padding;
Offset _bulletOffset = Offset.zero;
TextDirection _textDirection;
set spacing(double value) {
if (_spacing == value) {
return;
}
_spacing = value;
markNeedsLayout();
}
set padding(double value) {
if (_padding == value) {
return;
}
_padding = value;
markNeedsLayout();
}
set textDirection(TextDirection value) {
if (_textDirection == value) {
return;
}
_textDirection = value;
markNeedsLayout();
markNeedsPaint();
}
Color _bulletColor;
double _bulletSize;
set bulletSize(double value) {
if (_bulletSize == value) {
return;
}
_bulletSize = value;
markNeedsLayout();
}
set bulletColor(Color value) {
if (_bulletColor == value) {
return;
}
_bulletColor = value;
markNeedsLayout();
}
@override
double computeMinIntrinsicWidth(double height) {
child!.layout(
BoxConstraints(
maxWidth:
constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
),
parentUsesSize: true);
return child!.size.width;
}
@override
double computeMaxIntrinsicWidth(double height) {
child!.layout(
BoxConstraints(
maxWidth:
constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
),
parentUsesSize: true);
return child!.size.width;
}
@override
double computeMinIntrinsicHeight(double width) {
child!.layout(
BoxConstraints(
maxWidth:
constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
),
parentUsesSize: true);
return child!.size.height;
}
@override
double computeMaxIntrinsicHeight(double width) {
child!.layout(
BoxConstraints(
maxWidth:
constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
),
parentUsesSize: true);
return child!.size.height;
}
@override
void performLayout() {
super.performLayout();
if (child == null) {
return;
}
child!.layout(
BoxConstraints(
maxWidth:
constraints.maxWidth - _spacing - 6 - _bulletSize - _padding,
),
parentUsesSize: true);
if (_textDirection == TextDirection.ltr) {
child!.parentData = BoxParentData()
..offset = Offset(_spacing + _padding + 6 + _bulletSize, 0);
var value =
child!.computeDistanceToActualBaseline(TextBaseline.alphabetic);
_bulletOffset = Offset(4 + _padding, value! - _bulletSize);
} else {
child!.parentData = BoxParentData()
..offset = Offset(-_spacing - _padding + 6 + _bulletSize, 0);
var value =
child!.computeDistanceToActualBaseline(TextBaseline.alphabetic);
_bulletOffset =
Offset(child!.size.width - 4 + _padding, value! - _bulletSize);
}
size = constraints.constrain(Size(
child!.size.width + _spacing + _padding + 6 + _bulletSize,
child!.size.height));
}
@override
void paint(PaintingContext context, Offset offset) {
if (child == null) {
return;
}
if (_textDirection == TextDirection.ltr) {
context.paintChild(
child!, offset + (child!.parentData as BoxParentData).offset);
context.canvas.drawCircle(
offset + _bulletOffset, _bulletSize, Paint()..color = _bulletColor);
} else {
context.paintChild(
child!, offset + (child!.parentData as BoxParentData).offset);
context.canvas.drawCircle(
offset + _bulletOffset, _bulletSize, Paint()..color = _bulletColor);
}
}
@override
bool hitTestSelf(Offset position) {
return false;
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
Offset offset = (child!.parentData as BoxParentData).offset;
return result.addWithPaintOffset(
offset: offset,
position: position,
hitTest: (result, newOffset) {
return child?.hitTest(result, position: newOffset) ?? false;
},
);
}
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
return hitTestChildren(result, position: position);
}
}
class OrderedListView extends SingleChildRenderObjectWidget {
class OrderedListView extends StatelessWidget {
final String no;
final double spacing;
final double padding;
... ... @@ -224,196 +65,36 @@ class OrderedListView extends SingleChildRenderObjectWidget {
this.spacing = 6,
this.padding = 10,
TextStyle? style,
required super.child,
required this.child,
this.textDirection = TextDirection.ltr,
required this.no})
: _style = style;
final TextStyle? _style;
final TextDirection textDirection;
TextStyle getStyle(BuildContext context) {
if (_style == null || _style!.inherit) {
return DefaultTextStyle.of(context).style.merge(_style);
}
return _style!;
}
@override
RenderObject createRenderObject(BuildContext context) {
return OrderedListRenderObject(
no,
spacing,
padding,
textDirection,
getStyle(context),
);
}
@override
void updateRenderObject(
BuildContext context, covariant OrderedListRenderObject renderObject) {
renderObject.no = no;
renderObject.spacing = spacing;
renderObject.padding = padding;
renderObject.style = getStyle(context);
renderObject.textDirection = textDirection;
}
}
class OrderedListRenderObject extends RenderProxyBox {
OrderedListRenderObject(
String no,
double spacing,
double padding,
TextDirection textDirection,
TextStyle style, {
RenderBox? child,
}) : _no = no,
_style = style,
_spacing = spacing,
_padding = padding,
_textDirection = textDirection,
super(child);
double _spacing;
double _padding;
TextDirection _textDirection;
Offset _ptOffset = Offset.zero;
set spacing(double value) {
if (_spacing == value) {
return;
}
_spacing = value;
markNeedsLayout();
}
set padding(double value) {
if (_padding == value) {
return;
}
_padding = value;
markNeedsLayout();
}
set textDirection(TextDirection value) {
if (_textDirection == value) {
return;
}
_textDirection = value;
markNeedsLayout();
markNeedsPaint();
}
TextStyle _style;
set style(TextStyle value) {
_style = value;
markNeedsLayout();
}
String _no;
set no(String value) {
if (_no == value) {
return;
}
_no = value;
markNeedsLayout();
}
@override
double computeMinIntrinsicHeight(double width) {
pt = TextPainter(
text: TextSpan(
text: _no,
style: _style,
),
textDirection: TextDirection.ltr);
pt.layout(maxWidth: constraints.maxWidth - 50 - _spacing - _padding);
return child!
.computeMinIntrinsicHeight(width - pt.width - _spacing - _padding);
}
@override
double computeMaxIntrinsicHeight(double width) {
pt = TextPainter(
text: TextSpan(
text: _no,
style: _style,
),
textDirection: TextDirection.ltr);
pt.layout(maxWidth: constraints.maxWidth - 50 - _spacing - _padding);
return child!
.computeMaxIntrinsicHeight(width - pt.width - _spacing - _padding);
}
late TextPainter pt;
@override
void performLayout() {
super.performLayout();
if (child == null) {
return;
}
pt = TextPainter(
text: TextSpan(
text: _no,
style: _style,
),
textDirection: TextDirection.ltr);
pt.layout(maxWidth: constraints.maxWidth - 50 - _spacing - _padding);
child!.layout(
BoxConstraints(
maxWidth: constraints.maxWidth - pt.width - _spacing - _padding,
),
parentUsesSize: true);
if (_textDirection == TextDirection.ltr) {
child!.parentData = BoxParentData()
..offset = Offset(_spacing + _padding + pt.width, 0);
var value =
child!.computeDistanceToActualBaseline(TextBaseline.alphabetic);
_ptOffset = Offset(_padding,
value! - pt.computeDistanceToActualBaseline(TextBaseline.alphabetic));
} else {
child!.parentData = BoxParentData()
..offset = Offset(-_spacing - _padding + pt.width, 0);
var value =
child!.computeDistanceToActualBaseline(TextBaseline.alphabetic);
_ptOffset = Offset(child!.size.width + _padding - 4,
value! - pt.computeDistanceToActualBaseline(TextBaseline.alphabetic));
}
size = constraints.constrain(Size(
child!.size.width + _spacing + _padding + pt.width,
child!.size.height));
}
@override
void paint(PaintingContext context, Offset offset) {
if (child == null) {
return;
}
context.paintChild(
child!,
offset + (child!.parentData as BoxParentData).offset,
final Widget child;
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: textDirection,
child: Row(
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Padding(
padding: EdgeInsetsDirectional.only(start: padding, end: spacing),
child: Text.rich(
TextSpan(
text: no,
),
style: _style,
),
),
Expanded(
child: child,
)
],
),
);
pt.paint(context.canvas, offset + _ptOffset);
}
@override
bool hitTestSelf(Offset position) {
return false;
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
Offset offset = (child!.parentData as BoxParentData).offset;
return result.addWithPaintOffset(
offset: offset,
position: position,
hitTest: (result, newOffset) {
return child?.hitTest(result, position: newOffset) ?? false;
},
);
}
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
return hitTestChildren(result, position: position);
}
}
... ...
... ... @@ -12,25 +12,26 @@ import 'md_widget.dart';
/// Markdown components
abstract class MarkdownComponent {
static List<MarkdownComponent> get components => [
CodeBlockMd(),
NewLines(),
TableMd(),
HTag(),
IndentMd(),
UnOrderedList(),
OrderedList(),
RadioButtonMd(),
CheckBoxMd(),
HrLine(),
ImageMd(),
HighlightedText(),
BoldMd(),
ItalicMd(),
LatexMathMultyLine(),
LatexMath(),
ATagMd(),
SourceTag(),
];
CodeBlockMd(),
NewLines(),
TableMd(),
HTag(),
IndentMd(),
UnOrderedList(),
OrderedList(),
RadioButtonMd(),
CheckBoxMd(),
HrLine(),
ImageMd(),
HighlightedText(),
StrikeMd(),
BoldMd(),
ItalicMd(),
LatexMathMultyLine(),
LatexMath(),
ATagMd(),
SourceTag(),
];
/// Generate widget for markdown widget
static List<InlineSpan> generate(
... ... @@ -329,8 +330,8 @@ class IndentMd extends BlockMd {
padding: spaces * 5,
bulletSize: 0,
textDirection: config.textDirection,
child: RichText(
text: TextSpan(
child: Text.rich(
TextSpan(
children: MarkdownComponent.generate(
context,
"${match?[2]}",
... ... @@ -358,7 +359,7 @@ class UnOrderedList extends BlockMd {
bulletColor:
config.style?.color ?? DefaultTextStyle.of(context).style.color,
padding: 10.0,
bulletSize: 0.2 *
bulletSize: 0.4 *
(config.style?.fontSize ??
DefaultTextStyle.of(context).style.fontSize ??
kDefaultFontSize),
... ... @@ -457,11 +458,40 @@ class BoldMd extends InlineMd {
);
}
}
class StrikeMd extends InlineMd {
@override
RegExp get exp => RegExp(r"(?<!\*)\~\~(?<!\s)(.+?)(?<!\s)\~\~(?!\*)");
@override
InlineSpan span(
BuildContext context,
String text,
final GptMarkdownConfig config,
) {
var match = exp.firstMatch(text.trim());
var conf = config.copyWith(
style: config.style?.copyWith(
decoration: TextDecoration.lineThrough,
decorationColor: config.style?.color,
) ??
const TextStyle(decoration: TextDecoration.lineThrough,
));
return TextSpan(
children: MarkdownComponent.generate(
context,
"${match?[1]}",
conf,
),
style: conf.style,
);
}
}
/// Italic text component
class ItalicMd extends InlineMd {
@override
RegExp get exp => RegExp(r"(?<!\*)\*(?<!\s)(.+?)(?<!\s)\*(?!\*)", dotAll: true);
RegExp get exp =>
RegExp(r"(?<!\*)\*(?<!\s)(.+?)(?<!\s)\*(?!\*)", dotAll: true);
@override
InlineSpan span(
... ...