Showing
4 changed files
with
130 additions
and
366 deletions
| 1 | # 📦 GPT Markdown & LaTeX for Flutter | 1 | # 📦 GPT Markdown & LaTeX for Flutter |
| 2 | 2 | ||
| 3 | - | 3 | +[](https://pub.dev/packages/gpt_markdown) [](https://pub.dev/packages/gpt_markdown) [](https://pub.dev/packages/gpt_markdown) [](https://github.com/Infinitix-LLC/gpt_markdown) |
| 4 | 4 | ||
| 5 | A comprehensive Flutter package for rendering rich Markdown and LaTeX content in your apps, designed for seamless integration with AI outputs like ChatGPT and Gemini. | 5 | A comprehensive Flutter package for rendering rich Markdown and LaTeX content in your apps, designed for seamless integration with AI outputs like ChatGPT and Gemini. |
| 6 | 6 | ||
| @@ -8,71 +8,106 @@ A comprehensive Flutter package for rendering rich Markdown and LaTeX content in | @@ -8,71 +8,106 @@ A comprehensive Flutter package for rendering rich Markdown and LaTeX content in | ||
| 8 | 8 | ||
| 9 | --- | 9 | --- |
| 10 | 10 | ||
| 11 | +## Supported Markdown Features | ||
| 12 | +| ✨ Feature | ✅ Supported | 🔜 Upcoming | | ||
| 13 | +| --- | --- | --- | | ||
| 14 | +| 💻 Code Block | ✅ | | | ||
| 15 | +| 📊 Table | ✅ | | | ||
| 16 | +| 📝 Heading | ✅ | | | ||
| 17 | +| 📌 Unordered List | ✅ | | | ||
| 18 | +| 📋 Ordered List | ✅ | | | ||
| 19 | +| 🔘 Radio Button | ✅ | | | ||
| 20 | +| ☑️ Check Box | ✅ | | | ||
| 21 | +| ➖ Horizontal Line | ✅ | | | ||
| 22 | +| 🔢 Latex Math | ✅ | | | ||
| 23 | +| ↩️ Indent | ✅ | | ||
| 24 | +| 🖼️ Image | ✅ | | ||
| 25 | +| ✨ Highlighted Text | ✅ | | ||
| 26 | +| ✂️ Striked Text | ✅ | | ||
| 27 | +| 🔵 Bold Text | ✅ | | ||
| 28 | +| 📜 Italic Text | ✅ | | ||
| 29 | +| 🔗 Links | ✅ | | ||
| 30 | +| 📎 Underline | | 🔜 | | ||
| 31 | +| 🧩 Custom components | | 🔜 | | ||
| 32 | + | ||
| 11 | ## ✨ Key Features | 33 | ## ✨ Key Features |
| 12 | 34 | ||
| 13 | Render a wide variety of content with full Markdown and LaTeX support, including: | 35 | Render a wide variety of content with full Markdown and LaTeX support, including: |
| 14 | 36 | ||
| 15 | - List | 37 | - List |
| 16 | - | ||
| 17 | - - Unordered list item | ||
| 18 | - 1. Ordered list item | 38 | +``` |
| 39 | +- Unordered list item | ||
| 40 | +1. Ordered list item | ||
| 41 | +``` | ||
| 19 | 42 | ||
| 20 | - Horizontal line | 43 | - Horizontal line |
| 21 | - | ||
| 22 | - --- | 44 | +``` |
| 45 | +--- | ||
| 46 | +``` | ||
| 23 | 47 | ||
| 24 | - Links | 48 | - Links |
| 25 | - | ||
| 26 | - [<text here>](<href>) | 49 | +``` |
| 50 | +[<text here>](<href>) | ||
| 51 | +``` | ||
| 27 | 52 | ||
| 28 | - Images with size | 53 | - Images with size |
| 29 | - | ||
| 30 | -  | 54 | +``` |
| 55 | + | ||
| 56 | +``` | ||
| 31 | - Table | 57 | - Table |
| 32 | 58 | ||
| 33 | - ``` | ||
| 34 | - | Name | Roll | | ||
| 35 | - |-------------|-------------| | ||
| 36 | - | sohag | 1 | | ||
| 37 | - | ||
| 38 | - ``` | ||
| 39 | - | Name | Roll | | ||
| 40 | - |-------------|-------------| | ||
| 41 | - | sohag | 1 | | ||
| 42 | - | ||
| 43 | -- Striked text | 59 | +``` |
| 60 | +| Name | Roll | | ||
| 61 | +|-------|------| | ||
| 62 | +| sohag | 1 | | ||
| 44 | 63 | ||
| 45 | - ~~striked text~~ | 64 | +``` |
| 46 | 65 | ||
| 47 | -- Bold text | 66 | +| Name | Roll | |
| 67 | +|-------|------| | ||
| 68 | +| sohag | 1 | | ||
| 48 | 69 | ||
| 49 | - **Bold text** | 70 | +- ~~Striked text~~ |
| 71 | +``` | ||
| 72 | +~~striked text~~ | ||
| 73 | +``` | ||
| 50 | 74 | ||
| 51 | -- Italic text | 75 | +- **Bold text** |
| 76 | +``` | ||
| 77 | +**Bold text** | ||
| 78 | +``` | ||
| 52 | 79 | ||
| 53 | - *Italic text* | 80 | +- *Italic text* |
| 81 | +``` | ||
| 82 | +*Italic text* | ||
| 83 | +``` | ||
| 54 | 84 | ||
| 55 | - heading texts | 85 | - heading texts |
| 56 | 86 | ||
| 57 | - # Heading 1 | ||
| 58 | - ## Heading 2 | ||
| 59 | - ### Heading 3 | ||
| 60 | - #### Heading 4 | ||
| 61 | - ##### Heading 5 | ||
| 62 | - ###### Heading 6 | 87 | +``` |
| 88 | +# Heading 1 | ||
| 89 | +## Heading 2 | ||
| 90 | +### Heading 3 | ||
| 91 | +#### Heading 4 | ||
| 92 | +##### Heading 5 | ||
| 93 | +###### Heading 6 | ||
| 94 | +``` | ||
| 63 | 95 | ||
| 64 | - Latex formula `\(\frac a b\)` or `\[\frac ab\]` | 96 | - Latex formula `\(\frac a b\)` or `\[\frac ab\]` |
| 65 | - | ||
| 66 | - \(\frac a b\) | 97 | +``` |
| 98 | +\(\frac a b\) | ||
| 99 | +``` | ||
| 67 | 100 | ||
| 68 | - Radio button and checkbox | 101 | - Radio button and checkbox |
| 69 | 102 | ||
| 70 | - () Unchecked radio | ||
| 71 | - (x) Checked radio | ||
| 72 | - [] Unchecked checkbox | ||
| 73 | - [x] Checked checkbox | 103 | +``` |
| 104 | +() Unchecked radio | ||
| 105 | +(x) Checked radio | ||
| 106 | +[] Unchecked checkbox | ||
| 107 | +[x] Checked checkbox | ||
| 108 | +``` | ||
| 74 | 109 | ||
| 75 | -- You can also make the content selectable using `SelectiionArea` widget. | 110 | +- You can also make the content selectable using `SelectionArea` widget. |
| 76 | 111 | ||
| 77 | ## 🚀 Why Use GPT Markdown? | 112 | ## 🚀 Why Use GPT Markdown? |
| 78 | 113 | ||
| @@ -90,7 +125,7 @@ flutter pub add gpt_markdown | @@ -90,7 +125,7 @@ flutter pub add gpt_markdown | ||
| 90 | 125 | ||
| 91 | ## 📖 Usage | 126 | ## 📖 Usage |
| 92 | 127 | ||
| 93 | -Check the documentation [here.](https://github.com/saminsohag/flutter_packages/tree/main/gpt_markdown/example) | 128 | +Check the documentation [here.](https://github.com/Infinitix-LLC/gpt_markdown/tree/main/example) |
| 94 | 129 | ||
| 95 | ```dart | 130 | ```dart |
| 96 | import 'package:flutter/material.dart'; | 131 | import 'package:flutter/material.dart'; |
| @@ -145,6 +180,8 @@ You can also use LaTeX for mathematical expressions. Here's an example: | @@ -145,6 +180,8 @@ You can also use LaTeX for mathematical expressions. Here's an example: | ||
| 145 | 180 | ||
| 146 | Markdown and LaTeX can be powerful tools for formatting text and mathematical expressions in your Flutter app. If you have any questions or need further assistance, feel free to ask! | 181 | Markdown and LaTeX can be powerful tools for formatting text and mathematical expressions in your Flutter app. If you have any questions or need further assistance, feel free to ask! |
| 147 | ``` | 182 | ``` |
| 183 | +### Output from gpt_markdown | ||
| 184 | + | ||
| 148 | <img width="614" alt="Screenshot 2024-02-15 at 4 13 59 AM" src="https://github.com/saminsohag/flutter_packages/assets/59507062/8f4a4068-a12c-45d1-a954-ebaf3822e754"> | 185 | <img width="614" alt="Screenshot 2024-02-15 at 4 13 59 AM" src="https://github.com/saminsohag/flutter_packages/assets/59507062/8f4a4068-a12c-45d1-a954-ebaf3822e754"> |
| 149 | 186 | ||
| 150 | 187 |
| @@ -73,3 +73,55 @@ class _MyHomePageState extends State<MyHomePage> { | @@ -73,3 +73,55 @@ class _MyHomePageState extends State<MyHomePage> { | ||
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | ``` | 75 | ``` |
| 76 | + | ||
| 77 | +Use `SelectableAdapter` to make any non selectable widget selectable. | ||
| 78 | + | ||
| 79 | +```dart | ||
| 80 | +SelectableAdapter( | ||
| 81 | + selectedText: 'sin(x^2)', | ||
| 82 | + child: Math.tex('sin(x^2)'), | ||
| 83 | +); | ||
| 84 | +``` | ||
| 85 | + | ||
| 86 | +Use `GptMarkdownTheme` widget and `GptMarkdownThemeData` to customize the GptMarkdown. | ||
| 87 | + | ||
| 88 | +```dart | ||
| 89 | +GptMarkdownTheme( | ||
| 90 | + data: GptMarkdownThemeData.of(context).copyWith( | ||
| 91 | + highlightColor: Colors.red, | ||
| 92 | + ), | ||
| 93 | + child: GptMarkdown( | ||
| 94 | + text, | ||
| 95 | + ), | ||
| 96 | +); | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +In theme extension you can use `GptMarkdownThemeData` to customize the GptMarkdown. | ||
| 100 | + | ||
| 101 | +```dart | ||
| 102 | +theme: ThemeData( | ||
| 103 | + useMaterial3: true, | ||
| 104 | + brightness: Brightness.light, | ||
| 105 | + colorSchemeSeed: Colors.blue, | ||
| 106 | + extensions: [ | ||
| 107 | + GptMarkdownThemeData( | ||
| 108 | + brightness: Brightness.light, | ||
| 109 | + highlightColor: Colors.red, | ||
| 110 | + ), | ||
| 111 | + ], | ||
| 112 | +), | ||
| 113 | +darkTheme: ThemeData( | ||
| 114 | + useMaterial3: true, | ||
| 115 | + brightness: Brightness.dark, | ||
| 116 | + colorSchemeSeed: Colors.blue, | ||
| 117 | + extensions: [ | ||
| 118 | + GptMarkdownThemeData( | ||
| 119 | + brightness: Brightness.dark, | ||
| 120 | + highlightColor: Colors.red, | ||
| 121 | + ), | ||
| 122 | + ], | ||
| 123 | +), | ||
| 124 | +``` | ||
| 125 | + | ||
| 126 | +Please see the [README.md](https://github.com/Infinitix-LLC/gpt_markdown) and also [example](https://github.com/Infinitix-LLC/gpt_markdown/tree/main/example/lib/main.dart) app for more details. | ||
| 127 | + |
| @@ -2,10 +2,10 @@ import 'dart:io'; | @@ -2,10 +2,10 @@ import 'dart:io'; | ||
| 2 | 2 | ||
| 3 | import 'package:desktop_drop/desktop_drop.dart'; | 3 | import 'package:desktop_drop/desktop_drop.dart'; |
| 4 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
| 5 | +import 'package:gpt_markdown/custom_widgets/selectable_adapter.dart'; | ||
| 5 | import 'package:gpt_markdown/gpt_markdown.dart'; | 6 | import 'package:gpt_markdown/gpt_markdown.dart'; |
| 6 | import 'package:flutter_math_fork/flutter_math.dart'; | 7 | import 'package:flutter_math_fork/flutter_math.dart'; |
| 7 | import 'package:watcher/watcher.dart'; | 8 | import 'package:watcher/watcher.dart'; |
| 8 | -import 'selectable_adapter.dart'; | ||
| 9 | 9 | ||
| 10 | void main() { | 10 | void main() { |
| 11 | runApp(const MyApp()); | 11 | runApp(const MyApp()); |
example/lib/selectable_adapter.dart
deleted
100644 → 0
| 1 | -import 'package:flutter/material.dart'; | ||
| 2 | -import 'package:flutter/rendering.dart'; | ||
| 3 | - | ||
| 4 | -class SelectableAdapter extends StatelessWidget { | ||
| 5 | - const SelectableAdapter( | ||
| 6 | - {super.key, required this.selectedText, required this.child}); | ||
| 7 | - | ||
| 8 | - final Widget child; | ||
| 9 | - final String selectedText; | ||
| 10 | - | ||
| 11 | - @override | ||
| 12 | - Widget build(BuildContext context) { | ||
| 13 | - final SelectionRegistrar? registrar = SelectionContainer.maybeOf(context); | ||
| 14 | - if (registrar == null) { | ||
| 15 | - return child; | ||
| 16 | - } | ||
| 17 | - return MouseRegion( | ||
| 18 | - cursor: SystemMouseCursors.text, | ||
| 19 | - child: _SelectableAdapter( | ||
| 20 | - registrar: registrar, | ||
| 21 | - selectedText: selectedText, | ||
| 22 | - child: child, | ||
| 23 | - ), | ||
| 24 | - ); | ||
| 25 | - } | ||
| 26 | -} | ||
| 27 | - | ||
| 28 | -class _SelectableAdapter extends SingleChildRenderObjectWidget { | ||
| 29 | - const _SelectableAdapter({ | ||
| 30 | - required this.registrar, | ||
| 31 | - required Widget child, | ||
| 32 | - required this.selectedText, | ||
| 33 | - }) : super(child: child); | ||
| 34 | - | ||
| 35 | - final SelectionRegistrar registrar; | ||
| 36 | - final String selectedText; | ||
| 37 | - | ||
| 38 | - @override | ||
| 39 | - _RenderSelectableAdapter createRenderObject(BuildContext context) { | ||
| 40 | - return _RenderSelectableAdapter( | ||
| 41 | - DefaultSelectionStyle.of(context).selectionColor!, | ||
| 42 | - selectedText, | ||
| 43 | - registrar, | ||
| 44 | - ); | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - @override | ||
| 48 | - void updateRenderObject( | ||
| 49 | - BuildContext context, _RenderSelectableAdapter renderObject) { | ||
| 50 | - renderObject | ||
| 51 | - ..selectionColor = DefaultSelectionStyle.of(context).selectionColor! | ||
| 52 | - ..registrar = registrar; | ||
| 53 | - } | ||
| 54 | -} | ||
| 55 | - | ||
| 56 | -class _RenderSelectableAdapter extends RenderProxyBox | ||
| 57 | - with Selectable, SelectionRegistrant { | ||
| 58 | - String selectionText; | ||
| 59 | - _RenderSelectableAdapter( | ||
| 60 | - Color selectionColor, | ||
| 61 | - this.selectionText, | ||
| 62 | - SelectionRegistrar registrar, | ||
| 63 | - ) : _selectionColor = selectionColor, | ||
| 64 | - _geometry = ValueNotifier<SelectionGeometry>(_noSelection) { | ||
| 65 | - this.registrar = registrar; | ||
| 66 | - _geometry.addListener(markNeedsPaint); | ||
| 67 | - } | ||
| 68 | - | ||
| 69 | - static const SelectionGeometry _noSelection = | ||
| 70 | - SelectionGeometry(status: SelectionStatus.none, hasContent: true); | ||
| 71 | - final ValueNotifier<SelectionGeometry> _geometry; | ||
| 72 | - | ||
| 73 | - Color get selectionColor => _selectionColor; | ||
| 74 | - late Color _selectionColor; | ||
| 75 | - set selectionColor(Color value) { | ||
| 76 | - if (_selectionColor == value) { | ||
| 77 | - return; | ||
| 78 | - } | ||
| 79 | - _selectionColor = value; | ||
| 80 | - markNeedsPaint(); | ||
| 81 | - } | ||
| 82 | - | ||
| 83 | - // ValueListenable APIs | ||
| 84 | - | ||
| 85 | - @override | ||
| 86 | - void addListener(VoidCallback listener) => _geometry.addListener(listener); | ||
| 87 | - | ||
| 88 | - @override | ||
| 89 | - void removeListener(VoidCallback listener) => | ||
| 90 | - _geometry.removeListener(listener); | ||
| 91 | - | ||
| 92 | - @override | ||
| 93 | - SelectionGeometry get value => _geometry.value; | ||
| 94 | - | ||
| 95 | - // Selectable APIs. | ||
| 96 | - | ||
| 97 | - @override | ||
| 98 | - List<Rect> get boundingBoxes => <Rect>[paintBounds]; | ||
| 99 | - | ||
| 100 | - // Adjust this value to enlarge or shrink the selection highlight. | ||
| 101 | - static const double _padding = 0.0; | ||
| 102 | - Rect _getSelectionHighlightRect() { | ||
| 103 | - return Rect.fromLTWH(0 - _padding, 0 - _padding, size.width + _padding * 2, | ||
| 104 | - size.height + _padding * 2); | ||
| 105 | - } | ||
| 106 | - | ||
| 107 | - Offset? _start; | ||
| 108 | - Offset? _end; | ||
| 109 | - void _updateGeometry() { | ||
| 110 | - if (_start == null || _end == null) { | ||
| 111 | - _geometry.value = _noSelection; | ||
| 112 | - return; | ||
| 113 | - } | ||
| 114 | - final Rect renderObjectRect = Rect.fromLTWH(0, 0, size.width, size.height); | ||
| 115 | - final Rect selectionRect = Rect.fromPoints(_start!, _end!); | ||
| 116 | - if (renderObjectRect.intersect(selectionRect).isEmpty) { | ||
| 117 | - _geometry.value = _noSelection; | ||
| 118 | - } else { | ||
| 119 | - final Rect selectionRect = _getSelectionHighlightRect(); | ||
| 120 | - final SelectionPoint firstSelectionPoint = SelectionPoint( | ||
| 121 | - localPosition: selectionRect.bottomLeft, | ||
| 122 | - lineHeight: selectionRect.size.height, | ||
| 123 | - handleType: TextSelectionHandleType.left, | ||
| 124 | - ); | ||
| 125 | - final SelectionPoint secondSelectionPoint = SelectionPoint( | ||
| 126 | - localPosition: selectionRect.bottomRight, | ||
| 127 | - lineHeight: selectionRect.size.height, | ||
| 128 | - handleType: TextSelectionHandleType.right, | ||
| 129 | - ); | ||
| 130 | - final bool isReversed; | ||
| 131 | - if (_start!.dy > _end!.dy) { | ||
| 132 | - isReversed = true; | ||
| 133 | - } else if (_start!.dy < _end!.dy) { | ||
| 134 | - isReversed = false; | ||
| 135 | - } else { | ||
| 136 | - isReversed = _start!.dx > _end!.dx; | ||
| 137 | - } | ||
| 138 | - _geometry.value = SelectionGeometry( | ||
| 139 | - status: SelectionStatus.uncollapsed, | ||
| 140 | - hasContent: true, | ||
| 141 | - startSelectionPoint: | ||
| 142 | - isReversed ? secondSelectionPoint : firstSelectionPoint, | ||
| 143 | - endSelectionPoint: | ||
| 144 | - isReversed ? firstSelectionPoint : secondSelectionPoint, | ||
| 145 | - selectionRects: <Rect>[selectionRect], | ||
| 146 | - ); | ||
| 147 | - } | ||
| 148 | - } | ||
| 149 | - | ||
| 150 | - @override | ||
| 151 | - SelectionResult dispatchSelectionEvent(SelectionEvent event) { | ||
| 152 | - SelectionResult result = SelectionResult.none; | ||
| 153 | - switch (event.type) { | ||
| 154 | - case SelectionEventType.startEdgeUpdate: | ||
| 155 | - case SelectionEventType.endEdgeUpdate: | ||
| 156 | - final Rect renderObjectRect = | ||
| 157 | - Rect.fromLTWH(0, 0, size.width, size.height); | ||
| 158 | - // Normalize offset in case it is out side of the rect. | ||
| 159 | - final Offset point = | ||
| 160 | - globalToLocal((event as SelectionEdgeUpdateEvent).globalPosition); | ||
| 161 | - final Offset adjustedPoint = | ||
| 162 | - SelectionUtils.adjustDragOffset(renderObjectRect, point); | ||
| 163 | - if (event.type == SelectionEventType.startEdgeUpdate) { | ||
| 164 | - _start = adjustedPoint; | ||
| 165 | - } else { | ||
| 166 | - _end = adjustedPoint; | ||
| 167 | - } | ||
| 168 | - result = SelectionUtils.getResultBasedOnRect(renderObjectRect, point); | ||
| 169 | - case SelectionEventType.clear: | ||
| 170 | - _start = _end = null; | ||
| 171 | - case SelectionEventType.selectAll: | ||
| 172 | - case SelectionEventType.selectWord: | ||
| 173 | - case SelectionEventType.selectParagraph: | ||
| 174 | - _start = Offset.zero; | ||
| 175 | - _end = Offset.infinite; | ||
| 176 | - case SelectionEventType.granularlyExtendSelection: | ||
| 177 | - result = SelectionResult.end; | ||
| 178 | - final GranularlyExtendSelectionEvent extendSelectionEvent = | ||
| 179 | - event as GranularlyExtendSelectionEvent; | ||
| 180 | - // Initialize the offset it there is no ongoing selection. | ||
| 181 | - if (_start == null || _end == null) { | ||
| 182 | - if (extendSelectionEvent.forward) { | ||
| 183 | - _start = _end = Offset.zero; | ||
| 184 | - } else { | ||
| 185 | - _start = _end = Offset.infinite; | ||
| 186 | - } | ||
| 187 | - } | ||
| 188 | - // Move the corresponding selection edge. | ||
| 189 | - final Offset newOffset = | ||
| 190 | - extendSelectionEvent.forward ? Offset.infinite : Offset.zero; | ||
| 191 | - if (extendSelectionEvent.isEnd) { | ||
| 192 | - if (newOffset == _end) { | ||
| 193 | - result = extendSelectionEvent.forward | ||
| 194 | - ? SelectionResult.next | ||
| 195 | - : SelectionResult.previous; | ||
| 196 | - } | ||
| 197 | - _end = newOffset; | ||
| 198 | - } else { | ||
| 199 | - if (newOffset == _start) { | ||
| 200 | - result = extendSelectionEvent.forward | ||
| 201 | - ? SelectionResult.next | ||
| 202 | - : SelectionResult.previous; | ||
| 203 | - } | ||
| 204 | - _start = newOffset; | ||
| 205 | - } | ||
| 206 | - case SelectionEventType.directionallyExtendSelection: | ||
| 207 | - result = SelectionResult.end; | ||
| 208 | - final DirectionallyExtendSelectionEvent extendSelectionEvent = | ||
| 209 | - event as DirectionallyExtendSelectionEvent; | ||
| 210 | - // Convert to local coordinates. | ||
| 211 | - final double horizontalBaseLine = globalToLocal(Offset(event.dx, 0)).dx; | ||
| 212 | - final Offset newOffset; | ||
| 213 | - final bool forward; | ||
| 214 | - switch (extendSelectionEvent.direction) { | ||
| 215 | - case SelectionExtendDirection.backward: | ||
| 216 | - case SelectionExtendDirection.previousLine: | ||
| 217 | - forward = false; | ||
| 218 | - // Initialize the offset it there is no ongoing selection. | ||
| 219 | - if (_start == null || _end == null) { | ||
| 220 | - _start = _end = Offset.infinite; | ||
| 221 | - } | ||
| 222 | - // Move the corresponding selection edge. | ||
| 223 | - if (extendSelectionEvent.direction == | ||
| 224 | - SelectionExtendDirection.previousLine || | ||
| 225 | - horizontalBaseLine < 0) { | ||
| 226 | - newOffset = Offset.zero; | ||
| 227 | - } else { | ||
| 228 | - newOffset = Offset.infinite; | ||
| 229 | - } | ||
| 230 | - case SelectionExtendDirection.nextLine: | ||
| 231 | - case SelectionExtendDirection.forward: | ||
| 232 | - forward = true; | ||
| 233 | - // Initialize the offset it there is no ongoing selection. | ||
| 234 | - if (_start == null || _end == null) { | ||
| 235 | - _start = _end = Offset.zero; | ||
| 236 | - } | ||
| 237 | - // Move the corresponding selection edge. | ||
| 238 | - if (extendSelectionEvent.direction == | ||
| 239 | - SelectionExtendDirection.nextLine || | ||
| 240 | - horizontalBaseLine > size.width) { | ||
| 241 | - newOffset = Offset.infinite; | ||
| 242 | - } else { | ||
| 243 | - newOffset = Offset.zero; | ||
| 244 | - } | ||
| 245 | - } | ||
| 246 | - if (extendSelectionEvent.isEnd) { | ||
| 247 | - if (newOffset == _end) { | ||
| 248 | - result = forward ? SelectionResult.next : SelectionResult.previous; | ||
| 249 | - } | ||
| 250 | - _end = newOffset; | ||
| 251 | - } else { | ||
| 252 | - if (newOffset == _start) { | ||
| 253 | - result = forward ? SelectionResult.next : SelectionResult.previous; | ||
| 254 | - } | ||
| 255 | - _start = newOffset; | ||
| 256 | - } | ||
| 257 | - } | ||
| 258 | - _updateGeometry(); | ||
| 259 | - return result; | ||
| 260 | - } | ||
| 261 | - | ||
| 262 | - // This method is called when users want to copy selected content in this | ||
| 263 | - // widget into clipboard. | ||
| 264 | - @override | ||
| 265 | - SelectedContent? getSelectedContent() { | ||
| 266 | - return value.hasSelection | ||
| 267 | - ? SelectedContent(plainText: selectionText) | ||
| 268 | - : null; | ||
| 269 | - } | ||
| 270 | - | ||
| 271 | - LayerLink? _startHandle; | ||
| 272 | - LayerLink? _endHandle; | ||
| 273 | - | ||
| 274 | - @override | ||
| 275 | - void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) { | ||
| 276 | - if (_startHandle == startHandle && _endHandle == endHandle) { | ||
| 277 | - return; | ||
| 278 | - } | ||
| 279 | - _startHandle = startHandle; | ||
| 280 | - _endHandle = endHandle; | ||
| 281 | - markNeedsPaint(); | ||
| 282 | - } | ||
| 283 | - | ||
| 284 | - @override | ||
| 285 | - void paint(PaintingContext context, Offset offset) { | ||
| 286 | - super.paint(context, offset); | ||
| 287 | - if (!_geometry.value.hasSelection) { | ||
| 288 | - return; | ||
| 289 | - } | ||
| 290 | - // Draw the selection highlight. | ||
| 291 | - final Paint selectionPaint = Paint() | ||
| 292 | - ..style = PaintingStyle.fill | ||
| 293 | - ..color = _selectionColor; | ||
| 294 | - context.canvas | ||
| 295 | - .drawRect(_getSelectionHighlightRect().shift(offset), selectionPaint); | ||
| 296 | - | ||
| 297 | - // Push the layer links if any. | ||
| 298 | - if (_startHandle != null) { | ||
| 299 | - context.pushLayer( | ||
| 300 | - LeaderLayer( | ||
| 301 | - link: _startHandle!, | ||
| 302 | - offset: offset + value.startSelectionPoint!.localPosition, | ||
| 303 | - ), | ||
| 304 | - (PaintingContext context, Offset offset) {}, | ||
| 305 | - Offset.zero, | ||
| 306 | - ); | ||
| 307 | - } | ||
| 308 | - if (_endHandle != null) { | ||
| 309 | - context.pushLayer( | ||
| 310 | - LeaderLayer( | ||
| 311 | - link: _endHandle!, | ||
| 312 | - offset: offset + value.endSelectionPoint!.localPosition, | ||
| 313 | - ), | ||
| 314 | - (PaintingContext context, Offset offset) {}, | ||
| 315 | - Offset.zero, | ||
| 316 | - ); | ||
| 317 | - } | ||
| 318 | - } | ||
| 319 | - | ||
| 320 | - @override | ||
| 321 | - void dispose() { | ||
| 322 | - _geometry.dispose(); | ||
| 323 | - super.dispose(); | ||
| 324 | - } | ||
| 325 | -} |
-
Please register or login to post a comment