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