From a0d940ce66b94f64c560fab6afa28e509be59655 Mon Sep 17 00:00:00 2001 From: Yestay Tastanov Date: Tue, 18 Apr 2023 15:30:49 +0600 Subject: [PATCH 1/2] WIP --- .../03.change_language_theme/home_screen.dart | 22 ++- lib/flutter_code_editor.dart | 1 + lib/src/code_field/code_field.dart | 28 +-- lib/src/code_field/code_field_overlay.dart | 173 ++++++++++++++++++ 4 files changed, 200 insertions(+), 24 deletions(-) create mode 100644 lib/src/code_field/code_field_overlay.dart diff --git a/example/lib/03.change_language_theme/home_screen.dart b/example/lib/03.change_language_theme/home_screen.dart index 8df0e97e..fa50eb9c 100644 --- a/example/lib/03.change_language_theme/home_screen.dart +++ b/example/lib/03.change_language_theme/home_screen.dart @@ -104,17 +104,19 @@ class _HomeScreenState extends State { children: [ CodeTheme( data: CodeThemeData(styles: themes[_theme]), - child: CodeField( - focusNode: _codeFieldFocusNode, - controller: _codeController, - textStyle: const TextStyle(fontFamily: 'SourceCode'), - gutterStyle: GutterStyle( - textStyle: const TextStyle( - color: Colors.purple, + child: CodeFieldOverlay( + child: CodeField( + focusNode: _codeFieldFocusNode, + controller: _codeController, + textStyle: const TextStyle(fontFamily: 'SourceCode'), + gutterStyle: GutterStyle( + textStyle: const TextStyle( + color: Colors.purple, + ), + showLineNumbers: _showNumbers, + showErrors: _showErrors, + showFoldingHandles: _showFoldingHandles, ), - showLineNumbers: _showNumbers, - showErrors: _showErrors, - showFoldingHandles: _showFoldingHandles, ), ), ), diff --git a/lib/flutter_code_editor.dart b/lib/flutter_code_editor.dart index ea77c5ff..7b35ca2a 100644 --- a/lib/flutter_code_editor.dart +++ b/lib/flutter_code_editor.dart @@ -13,6 +13,7 @@ export 'src/code/tokens.dart'; export 'src/code_field/code_controller.dart'; export 'src/code_field/code_field.dart'; +export 'src/code_field/code_field_overlay.dart'; export 'src/code_field/editor_params.dart'; export 'src/code_field/text_editing_value.dart'; diff --git a/lib/src/code_field/code_field.dart b/lib/src/code_field/code_field.dart index efe11148..1d6241c8 100644 --- a/lib/src/code_field/code_field.dart +++ b/lib/src/code_field/code_field.dart @@ -493,20 +493,20 @@ class _CodeFieldState extends State { } void _onPopupStateChanged() { - final shouldShow = - widget.controller.popupController.shouldShow && windowSize != null; - if (!shouldShow) { - _suggestionsPopup?.remove(); - _suggestionsPopup = null; - return; - } - - if (_suggestionsPopup == null) { - _suggestionsPopup = _buildSuggestionOverlay(); - Overlay.of(context).insert(_suggestionsPopup!); - } - - _suggestionsPopup!.markNeedsBuild(); + // final shouldShow = + // widget.controller.popupController.shouldShow && windowSize != null; + // if (!shouldShow) { + // _suggestionsPopup?.remove(); + // _suggestionsPopup = null; + // return; + // } + + // if (_suggestionsPopup == null) { + // _suggestionsPopup = _buildSuggestionOverlay(); + // Overlay.of(context).insert(_suggestionsPopup!); + // } + + // _suggestionsPopup!.markNeedsBuild(); } OverlayEntry _buildSuggestionOverlay() { diff --git a/lib/src/code_field/code_field_overlay.dart b/lib/src/code_field/code_field_overlay.dart new file mode 100644 index 00000000..db187bcc --- /dev/null +++ b/lib/src/code_field/code_field_overlay.dart @@ -0,0 +1,173 @@ +import 'dart:math'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter/src/widgets/placeholder.dart'; + +import '../../flutter_code_editor.dart'; +import '../sizes.dart'; +import '../wip/autocomplete/popup.dart'; +import '../wip/autocomplete/popup_controller.dart'; +import 'default_styles.dart'; + +class CodeFieldOverlay extends StatefulWidget { + final CodeField child; + late final CodeController controller; + + CodeFieldOverlay({ + super.key, + required this.child, + }) { + controller = child.controller; + } + + @override + State createState() => _CodeFieldOverlayState(); +} + +class _CodeFieldOverlayState extends State { + late TextStyle textStyle; + OverlayEntry? _suggestionsPopup; + Offset _normalPopupOffset = Offset.zero; + Offset _flippedPopupOffset = Offset.zero; + Offset? _editorOffset; + Size? windowSize; + Color _backgroundCol = Colors.black; + + @override + void initState() { + widget.controller.addListener(_onTextChanged); + widget.controller.addListener(_updatePopupOffset); + widget.controller.popupController.addListener(_onPopupStateChanged); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + const rootKey = 'root'; + + final themeData = Theme.of(context); + final styles = CodeTheme.of(context)?.styles; + _backgroundCol = + styles?[rootKey]?.backgroundColor ?? DefaultStyles.backgroundColor; + + final defaultTextStyle = TextStyle( + color: styles?[rootKey]?.color ?? DefaultStyles.textColor, + fontSize: themeData.textTheme.titleMedium?.fontSize, + ); + + textStyle = defaultTextStyle.merge(widget.child.textStyle); + + return widget.child; + } + + void _updatePopupOffset() { + final textPainter = _getTextPainter(widget.controller.text); + final caretHeight = _getCaretHeight(textPainter); + + final leftOffset = _getPopupLeftOffset(textPainter); + final normalTopOffset = _getPopupTopOffset(textPainter, caretHeight); + final flippedTopOffset = normalTopOffset - + (Sizes.autocompletePopupMaxHeight + caretHeight + Sizes.caretPadding); + + setState(() { + _normalPopupOffset = Offset(leftOffset, normalTopOffset); + _flippedPopupOffset = Offset(leftOffset, flippedTopOffset); + }); + } + + TextPainter _getTextPainter(String text) { + return TextPainter( + textDirection: TextDirection.ltr, + text: TextSpan(text: text, style: textStyle), + )..layout(); + } + + Offset _getCaretOffset(TextPainter textPainter) { + return textPainter.getOffsetForCaret( + widget.controller.selection.base, + Rect.zero, + ); + } + + double _getCaretHeight(TextPainter textPainter) { + final double? caretFullHeight = textPainter.getFullHeightForCaret( + widget.controller.selection.base, + Rect.zero, + ); + return (widget.controller.selection.base.offset > 0) ? caretFullHeight! : 0; + } + + double _getPopupLeftOffset(TextPainter textPainter) { + return max( + _getCaretOffset(textPainter).dx + + widget.child.padding.left - + (_editorOffset?.dx ?? 0), + 0, + ); + } + + double _getPopupTopOffset(TextPainter textPainter, double caretHeight) { + return max( + _getCaretOffset(textPainter).dy + + caretHeight + + 16 + + widget.child.padding.top - + (_editorOffset?.dy ?? 0), + 0, + ); + } + + void _onPopupStateChanged() { + final shouldShow = + widget.controller.popupController.shouldShow && windowSize != null; + if (!shouldShow) { + _suggestionsPopup?.remove(); + _suggestionsPopup = null; + return; + } + + if (_suggestionsPopup == null) { + _suggestionsPopup = _buildSuggestionOverlay(); + Overlay.of(context).insert(_suggestionsPopup!); + } + + _suggestionsPopup!.markNeedsBuild(); + } + + OverlayEntry _buildSuggestionOverlay() { + return OverlayEntry( + builder: (context) { + return Popup( + normalOffset: _normalPopupOffset, + flippedOffset: _flippedPopupOffset, + controller: widget.controller.popupController, + editingWindowSize: windowSize!, + style: textStyle, + backgroundColor: Colors.black, + parentFocusNode: widget.child.focusNode!, + editorOffset: _editorOffset, + ); + }, + ); + } + + void _onTextChanged() { + final box = context.findRenderObject() as RenderBox?; + _editorOffset = box?.localToGlobal(Offset.zero); + + rebuild(); + } + + void rebuild() { + setState(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + final double width = context.size!.width; + final double height = context.size!.height; + windowSize = Size(width, height); + }); + }); + } +} From 1f2d7985d6cc0adf14fb73acbc30c6667210fd06 Mon Sep 17 00:00:00 2001 From: Yestay Tastanov Date: Tue, 18 Apr 2023 18:15:10 +0600 Subject: [PATCH 2/2] base changes --- lib/src/code_field/code_field_overlay.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/code_field/code_field_overlay.dart b/lib/src/code_field/code_field_overlay.dart index db187bcc..d9321f69 100644 --- a/lib/src/code_field/code_field_overlay.dart +++ b/lib/src/code_field/code_field_overlay.dart @@ -103,7 +103,8 @@ class _CodeFieldOverlayState extends State { double _getPopupLeftOffset(TextPainter textPainter) { return max( _getCaretOffset(textPainter).dx + - widget.child.padding.left - + widget.child.padding.left + + (widget.child.focusNode?.offset.dx ?? 0) + (_editorOffset?.dx ?? 0), 0, ); @@ -114,7 +115,7 @@ class _CodeFieldOverlayState extends State { _getCaretOffset(textPainter).dy + caretHeight + 16 + - widget.child.padding.top - + widget.child.padding.top + (_editorOffset?.dy ?? 0), 0, );