import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'markdown_editing.dart'; /// Highlighted markdown source that is either read-only or editable. class MarkdownView extends StatefulWidget { final MarkdownController controller; final bool readOnly; final double fontSize; final FocusNode? focusNode; final ScrollController? scrollController; const MarkdownView({ super.key, required this.controller, this.readOnly = true, this.fontSize = 16, this.focusNode, this.scrollController, }); @override MarkdownViewState createState() => MarkdownViewState(); } class MarkdownViewState extends State { final _fieldKey = GlobalKey(); final _formatters = [MarkdownInputFormatter()]; /// Scrolls [offset] into view without focusing the field, so read mode /// doesn't bring up the keyboard. void revealOffset(int offset) { final editable = _findEditable(_fieldKey.currentContext?.findRenderObject()); if (editable == null) return; final caret = editable.getLocalRectForCaret(TextPosition(offset: offset)); editable.showOnScreen( rect: Rect.fromLTRB( caret.left, caret.top - 80, caret.right, caret.bottom + 80), duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); } /// Text offset at the top-left of the visible area. int firstVisibleOffset() { final box = _fieldKey.currentContext?.findRenderObject() as RenderBox?; final editable = _findEditable(box); if (box == null || editable == null) return 0; return editable .getPositionForPoint(box.localToGlobal(const Offset(1, 1))) .offset; } RenderEditable? _findEditable(RenderObject? node) { if (node is RenderEditable) return node; RenderEditable? found; node?.visitChildren((child) => found ??= _findEditable(child)); return found; } void _indent({required bool outdent}) { final controller = widget.controller; controller.value = indentLines(controller.value, outdent: outdent); } @override Widget build(BuildContext context) { // Tab would otherwise move focus out of the editor on desktop. return CallbackShortcuts( bindings: widget.readOnly ? const {} : { const SingleActivator(LogicalKeyboardKey.tab): () => _indent(outdent: false), const SingleActivator(LogicalKeyboardKey.tab, shift: true): () => _indent(outdent: true), }, child: TextField( key: _fieldKey, controller: widget.controller, focusNode: widget.focusNode, scrollController: widget.scrollController, readOnly: widget.readOnly, showCursor: !widget.readOnly, expands: true, maxLines: null, decoration: null, inputFormatters: widget.readOnly ? null : _formatters, style: TextStyle(fontSize: widget.fontSize), ), ); } }