saminsohag

theme extantions added

## 0.1.11
* `GptMarkdownTheme` and `GptMarkdownThemeData` classes added.
## 0.1.10
* components are now selectable.
... ...
... ... @@ -4,7 +4,9 @@ import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';
import 'package:gpt_markdown/gpt_markdown.dart';
import 'package:flutter_math_fork/flutter_math.dart';
import 'package:gpt_markdown/theme.dart';
import 'package:watcher/watcher.dart';
import 'selectable_adapter.dart';
void main() {
runApp(const MyApp());
... ... @@ -34,7 +36,11 @@ class _MyAppState extends State<MyApp> {
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: Colors.blue,
extensions: [
GptMarkdownThemeData(
highlightColor: Colors.red,
),
]),
home: MyHomePage(
title: 'GptMarkdown',
onPressed: () {
... ... @@ -147,7 +153,11 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
@override
Widget build(BuildContext context) {
return Scaffold(
return GptMarkdownTheme(
gptThemeData: GptMarkdownTheme.of(context).copyWith(
highlightColor: Colors.purple,
),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
... ... @@ -180,8 +190,8 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
onPressed: () => setState(() {
writingMod = !writingMod;
}),
icon:
Icon(writingMod ? Icons.arrow_drop_down : Icons.arrow_drop_up),
icon: Icon(
writingMod ? Icons.arrow_drop_down : Icons.arrow_drop_up),
),
],
),
... ... @@ -211,7 +221,8 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Theme.of(context).colorScheme.outline),
color:
Theme.of(context).colorScheme.outline),
),
child: Theme(
data: Theme.of(context),
... ... @@ -324,6 +335,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
),
);
}
child = SelectableAdapter(
selectedText: tex,
child: Math.tex(tex),
);
child = InkWell(
onTap: () {
debugPrint("Hello world");
... ... @@ -346,13 +361,16 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
borderRadius:
BorderRadius.circular(10),
),
child: Center(child: Text("$value")),
child:
Center(child: Text("$value")),
),
);
},
);
if (selectable) {
child = SelectionArea(child: child);
child = SelectionArea(
child: child,
);
}
return child;
},
... ... @@ -384,6 +402,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
],
),
),
),
);
}
}
... ...
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class SelectableAdapter extends StatelessWidget {
const SelectableAdapter(
{super.key, required this.selectedText, required this.child});
final Widget child;
final String selectedText;
@override
Widget build(BuildContext context) {
final SelectionRegistrar? registrar = SelectionContainer.maybeOf(context);
if (registrar == null) {
return child;
}
return MouseRegion(
cursor: SystemMouseCursors.text,
child: _SelectableAdapter(
registrar: registrar,
selectedText: selectedText,
child: child,
),
);
}
}
class _SelectableAdapter extends SingleChildRenderObjectWidget {
const _SelectableAdapter({
required this.registrar,
required Widget child,
required this.selectedText,
}) : super(child: child);
final SelectionRegistrar registrar;
final String selectedText;
@override
_RenderSelectableAdapter createRenderObject(BuildContext context) {
return _RenderSelectableAdapter(
DefaultSelectionStyle.of(context).selectionColor!,
selectedText,
registrar,
);
}
@override
void updateRenderObject(
BuildContext context, _RenderSelectableAdapter renderObject) {
renderObject
..selectionColor = DefaultSelectionStyle.of(context).selectionColor!
..registrar = registrar;
}
}
class _RenderSelectableAdapter extends RenderProxyBox
with Selectable, SelectionRegistrant {
String selectionText;
_RenderSelectableAdapter(
Color selectionColor,
this.selectionText,
SelectionRegistrar registrar,
) : _selectionColor = selectionColor,
_geometry = ValueNotifier<SelectionGeometry>(_noSelection) {
this.registrar = registrar;
_geometry.addListener(markNeedsPaint);
}
static const SelectionGeometry _noSelection =
SelectionGeometry(status: SelectionStatus.none, hasContent: true);
final ValueNotifier<SelectionGeometry> _geometry;
Color get selectionColor => _selectionColor;
late Color _selectionColor;
set selectionColor(Color value) {
if (_selectionColor == value) {
return;
}
_selectionColor = value;
markNeedsPaint();
}
// ValueListenable APIs
@override
void addListener(VoidCallback listener) => _geometry.addListener(listener);
@override
void removeListener(VoidCallback listener) =>
_geometry.removeListener(listener);
@override
SelectionGeometry get value => _geometry.value;
// Selectable APIs.
@override
List<Rect> get boundingBoxes => <Rect>[paintBounds];
// Adjust this value to enlarge or shrink the selection highlight.
static const double _padding = 0.0;
Rect _getSelectionHighlightRect() {
return Rect.fromLTWH(0 - _padding, 0 - _padding, size.width + _padding * 2,
size.height + _padding * 2);
}
Offset? _start;
Offset? _end;
void _updateGeometry() {
if (_start == null || _end == null) {
_geometry.value = _noSelection;
return;
}
final Rect renderObjectRect = Rect.fromLTWH(0, 0, size.width, size.height);
final Rect selectionRect = Rect.fromPoints(_start!, _end!);
if (renderObjectRect.intersect(selectionRect).isEmpty) {
_geometry.value = _noSelection;
} else {
final Rect selectionRect = _getSelectionHighlightRect();
final SelectionPoint firstSelectionPoint = SelectionPoint(
localPosition: selectionRect.bottomLeft,
lineHeight: selectionRect.size.height,
handleType: TextSelectionHandleType.left,
);
final SelectionPoint secondSelectionPoint = SelectionPoint(
localPosition: selectionRect.bottomRight,
lineHeight: selectionRect.size.height,
handleType: TextSelectionHandleType.right,
);
final bool isReversed;
if (_start!.dy > _end!.dy) {
isReversed = true;
} else if (_start!.dy < _end!.dy) {
isReversed = false;
} else {
isReversed = _start!.dx > _end!.dx;
}
_geometry.value = SelectionGeometry(
status: SelectionStatus.uncollapsed,
hasContent: true,
startSelectionPoint:
isReversed ? secondSelectionPoint : firstSelectionPoint,
endSelectionPoint:
isReversed ? firstSelectionPoint : secondSelectionPoint,
selectionRects: <Rect>[selectionRect],
);
}
}
@override
SelectionResult dispatchSelectionEvent(SelectionEvent event) {
SelectionResult result = SelectionResult.none;
switch (event.type) {
case SelectionEventType.startEdgeUpdate:
case SelectionEventType.endEdgeUpdate:
final Rect renderObjectRect =
Rect.fromLTWH(0, 0, size.width, size.height);
// Normalize offset in case it is out side of the rect.
final Offset point =
globalToLocal((event as SelectionEdgeUpdateEvent).globalPosition);
final Offset adjustedPoint =
SelectionUtils.adjustDragOffset(renderObjectRect, point);
if (event.type == SelectionEventType.startEdgeUpdate) {
_start = adjustedPoint;
} else {
_end = adjustedPoint;
}
result = SelectionUtils.getResultBasedOnRect(renderObjectRect, point);
case SelectionEventType.clear:
_start = _end = null;
case SelectionEventType.selectAll:
case SelectionEventType.selectWord:
case SelectionEventType.selectParagraph:
_start = Offset.zero;
_end = Offset.infinite;
case SelectionEventType.granularlyExtendSelection:
result = SelectionResult.end;
final GranularlyExtendSelectionEvent extendSelectionEvent =
event as GranularlyExtendSelectionEvent;
// Initialize the offset it there is no ongoing selection.
if (_start == null || _end == null) {
if (extendSelectionEvent.forward) {
_start = _end = Offset.zero;
} else {
_start = _end = Offset.infinite;
}
}
// Move the corresponding selection edge.
final Offset newOffset =
extendSelectionEvent.forward ? Offset.infinite : Offset.zero;
if (extendSelectionEvent.isEnd) {
if (newOffset == _end) {
result = extendSelectionEvent.forward
? SelectionResult.next
: SelectionResult.previous;
}
_end = newOffset;
} else {
if (newOffset == _start) {
result = extendSelectionEvent.forward
? SelectionResult.next
: SelectionResult.previous;
}
_start = newOffset;
}
case SelectionEventType.directionallyExtendSelection:
result = SelectionResult.end;
final DirectionallyExtendSelectionEvent extendSelectionEvent =
event as DirectionallyExtendSelectionEvent;
// Convert to local coordinates.
final double horizontalBaseLine = globalToLocal(Offset(event.dx, 0)).dx;
final Offset newOffset;
final bool forward;
switch (extendSelectionEvent.direction) {
case SelectionExtendDirection.backward:
case SelectionExtendDirection.previousLine:
forward = false;
// Initialize the offset it there is no ongoing selection.
if (_start == null || _end == null) {
_start = _end = Offset.infinite;
}
// Move the corresponding selection edge.
if (extendSelectionEvent.direction ==
SelectionExtendDirection.previousLine ||
horizontalBaseLine < 0) {
newOffset = Offset.zero;
} else {
newOffset = Offset.infinite;
}
case SelectionExtendDirection.nextLine:
case SelectionExtendDirection.forward:
forward = true;
// Initialize the offset it there is no ongoing selection.
if (_start == null || _end == null) {
_start = _end = Offset.zero;
}
// Move the corresponding selection edge.
if (extendSelectionEvent.direction ==
SelectionExtendDirection.nextLine ||
horizontalBaseLine > size.width) {
newOffset = Offset.infinite;
} else {
newOffset = Offset.zero;
}
}
if (extendSelectionEvent.isEnd) {
if (newOffset == _end) {
result = forward ? SelectionResult.next : SelectionResult.previous;
}
_end = newOffset;
} else {
if (newOffset == _start) {
result = forward ? SelectionResult.next : SelectionResult.previous;
}
_start = newOffset;
}
}
_updateGeometry();
return result;
}
// This method is called when users want to copy selected content in this
// widget into clipboard.
@override
SelectedContent? getSelectedContent() {
return value.hasSelection
? SelectedContent(plainText: selectionText)
: null;
}
LayerLink? _startHandle;
LayerLink? _endHandle;
@override
void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) {
if (_startHandle == startHandle && _endHandle == endHandle) {
return;
}
_startHandle = startHandle;
_endHandle = endHandle;
markNeedsPaint();
}
@override
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
if (!_geometry.value.hasSelection) {
return;
}
// Draw the selection highlight.
final Paint selectionPaint = Paint()
..style = PaintingStyle.fill
..color = _selectionColor;
context.canvas
.drawRect(_getSelectionHighlightRect().shift(offset), selectionPaint);
// Push the layer links if any.
if (_startHandle != null) {
context.pushLayer(
LeaderLayer(
link: _startHandle!,
offset: offset + value.startSelectionPoint!.localPosition,
),
(PaintingContext context, Offset offset) {},
Offset.zero,
);
}
if (_endHandle != null) {
context.pushLayer(
LeaderLayer(
link: _endHandle!,
offset: offset + value.endSelectionPoint!.localPosition,
),
(PaintingContext context, Offset offset) {},
Offset.zero,
);
}
}
@override
void dispose() {
_geometry.dispose();
super.dispose();
}
}
... ...
... ... @@ -126,7 +126,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.1.10"
version: "0.1.11"
http:
dependency: transitive
description:
... ... @@ -373,5 +373,5 @@ packages:
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.3.0 <4.0.0"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
... ...
... ... @@ -5,7 +5,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=2.18.6 <4.0.0'
sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
... ...
... ... @@ -7,6 +7,7 @@ import 'package:gpt_markdown/custom_widgets/custom_error_image.dart';
import 'package:gpt_markdown/custom_widgets/custom_rb_cb.dart';
import 'package:gpt_markdown/custom_widgets/markdow_config.dart';
import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart';
import 'package:gpt_markdown/theme.dart';
import 'md_widget.dart';
/// Markdown components
... ... @@ -413,14 +414,14 @@ class HighlightedText extends InlineMd {
style: config.style?.copyWith(
fontWeight: FontWeight.bold,
background: Paint()
..color = Theme.of(context).colorScheme.onInverseSurface
..color = GptMarkdownTheme.of(context).highlightColor
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round,
) ??
TextStyle(
fontWeight: FontWeight.bold,
background: Paint()
..color = Theme.of(context).colorScheme.surfaceContainerHighest
..color = GptMarkdownTheme.of(context).highlightColor
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round,
),
... ...
import 'package:flutter/material.dart';
/// Theme defined for `TexMarkdown` widget
class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> {
GptMarkdownThemeData({
required this.highlightColor,
});
/// Define default attributes.
factory GptMarkdownThemeData.from(BuildContext context) {
return GptMarkdownThemeData(
highlightColor: Theme.of(context).colorScheme.onInverseSurface,
);
}
Color highlightColor;
@override
GptMarkdownThemeData copyWith({Color? highlightColor}) {
return GptMarkdownThemeData(
highlightColor: highlightColor ?? this.highlightColor,
);
}
@override
GptMarkdownThemeData lerp(GptMarkdownThemeData? other, double t) {
if (other == null) {
return this;
}
return GptMarkdownThemeData(
highlightColor:
Color.lerp(highlightColor, other.highlightColor, t) ?? highlightColor,
);
}
}
/// Wrap a `Widget` with `GptMarkdownTheme` to provide `GptMarkdownThemeData` in your intiar app.
class GptMarkdownTheme extends InheritedWidget {
const GptMarkdownTheme({
super.key,
required this.gptThemeData,
required super.child,
});
final GptMarkdownThemeData gptThemeData;
static GptMarkdownThemeData of(BuildContext context) {
final provider =
context.dependOnInheritedWidgetOfExactType<GptMarkdownTheme>();
if (provider != null) {
return provider.gptThemeData;
}
final themeData = Theme.of(context).extension<GptMarkdownThemeData>();
if (themeData != null) {
return themeData;
}
return GptMarkdownThemeData.from(context);
}
@override
bool updateShouldNotify(GptMarkdownTheme oldWidget) {
return gptThemeData != oldWidget.gptThemeData;
}
}
... ...
name: gpt_markdown
description: "The purpose of this package is to render the response of ChatGPT into a Flutter app."
version: 0.1.10
version: 0.1.11
homepage: https://github.com/saminsohag/flutter_packages/tree/main/gpt_markdown
environment:
... ...