LCOV - code coverage report
Current view: top level - editing/text_selection - mongol_text_selection_controls.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 99 108 91.7 %
Date: 2021-08-02 17:55:49 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2014 The Flutter Authors.
       2             : // Copyright 2021 Suragch.
       3             : // All rights reserved.
       4             : // Use of this source code is governed by a BSD-style license that can be
       5             : // found in the LICENSE file.
       6             : 
       7             : import 'dart:math' as math;
       8             : 
       9             : import 'package:flutter/material.dart' show Theme, TextSelectionTheme, Icons;
      10             : import 'package:flutter/rendering.dart';
      11             : import 'package:flutter/scheduler.dart';
      12             : import 'package:flutter/services.dart' show TextSelectionDelegate;
      13             : import 'package:flutter/widgets.dart';
      14             : 
      15             : import 'mongol_text_selection_toolbar.dart';
      16             : import 'mongol_text_selection_toolbar_button.dart';
      17             : 
      18             : // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/text_selection.dart
      19             : // This file builds the Copy/Paste toolbar that pops up when you long click, etc.
      20             : // If you want a different style you can replace this class with another one.
      21             : // That's what Flutter does to give a different style for Material, Cupertino
      22             : // and others.
      23             : 
      24             : const double _kHandleSize = 22.0;
      25             : 
      26             : // Padding between the toolbar and the anchor.
      27             : const double _kToolbarContentDistanceRight = _kHandleSize - 2.0;
      28             : const double _kToolbarContentDistance = 8.0;
      29             : 
      30             : /// Mongol styled text selection controls. (Adapted from Android Material version)
      31             : ///
      32             : /// In order to avoid Mongolian Unicode and font issues, the text editing
      33             : /// controls use icons rather than text for the copy/cut/past/select buttons.
      34             : class MongolTextSelectionControls extends TextSelectionControls {
      35             :   /// Returns the size of the handle.
      36           1 :   @override
      37             :   Size getHandleSize(double textLineWidth) =>
      38             :       const Size(_kHandleSize, _kHandleSize);
      39             : 
      40             :   /// Builder for Mongol copy/paste text selection toolbar.
      41           1 :   @override
      42             :   Widget buildToolbar(
      43             :     BuildContext context,
      44             :     Rect globalEditableRegion,
      45             :     double textLineWidth,
      46             :     Offset selectionMidpoint,
      47             :     List<TextSelectionPoint> endpoints,
      48             :     TextSelectionDelegate delegate,
      49             :     ClipboardStatusNotifier clipboardStatus,
      50             :     Offset? lastSecondaryTapDownPosition,
      51             :   ) {
      52           1 :     return _TextSelectionControlsToolbar(
      53             :       globalEditableRegion: globalEditableRegion,
      54             :       textLineWidth: textLineWidth,
      55             :       selectionMidpoint: selectionMidpoint,
      56             :       endpoints: endpoints,
      57             :       delegate: delegate,
      58             :       clipboardStatus: clipboardStatus,
      59           1 :       handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
      60           1 :       handleCopy: canCopy(delegate)
      61           0 :           ? () => handleCopy(delegate, clipboardStatus)
      62             :           : null,
      63           1 :       handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
      64             :       handleSelectAll:
      65           1 :           canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
      66             :     );
      67             :   }
      68             : 
      69             :   /// Builder for material-style text selection handles.
      70           1 :   @override
      71             :   Widget buildHandle(
      72             :       BuildContext context, TextSelectionHandleType type, double textHeight) {
      73           1 :     final theme = Theme.of(context);
      74           2 :     final handleColor = TextSelectionTheme.of(context).selectionHandleColor ??
      75           2 :         theme.colorScheme.primary;
      76           1 :     final Widget handle = SizedBox(
      77             :       width: _kHandleSize,
      78             :       height: _kHandleSize,
      79           1 :       child: CustomPaint(
      80           1 :         painter: _TextSelectionHandlePainter(
      81             :           color: handleColor,
      82             :         ),
      83             :       ),
      84             :     );
      85             : 
      86             :     // [handle] is a circle, with a rectangle in the top left quadrant of that
      87             :     // circle (an onion pointing to 10:30). We rotate [handle] to point
      88             :     // down-right, up-left, or left depending on the handle type.
      89             :     switch (type) {
      90           1 :       case TextSelectionHandleType.left: // points down-right
      91           1 :         return Transform.rotate(
      92             :           angle: math.pi,
      93             :           child: handle,
      94             :         );
      95           1 :       case TextSelectionHandleType.right: // points up-left
      96             :         return handle;
      97           1 :       case TextSelectionHandleType.collapsed: // points left
      98           1 :         return Transform.rotate(
      99           2 :           angle: -math.pi / 4.0,
     100             :           child: handle,
     101             :         );
     102             :     }
     103             :   }
     104             : 
     105             :   /// Gets anchor for material-style text selection handles.
     106             :   ///
     107             :   /// See [TextSelectionControls.getHandleAnchor].
     108           1 :   @override
     109             :   Offset getHandleAnchor(TextSelectionHandleType type, double textLineWidth) {
     110             :     switch (type) {
     111           1 :       case TextSelectionHandleType.left:
     112             :         //return const Offset(0, 0);
     113             :         return const Offset(_kHandleSize, _kHandleSize);
     114           1 :       case TextSelectionHandleType.right:
     115             :         return Offset.zero;
     116             :       default:
     117             :         return const Offset(-4, _kHandleSize / 2);
     118             :     }
     119             :   }
     120             : 
     121           1 :   @override
     122             :   bool canSelectAll(TextSelectionDelegate delegate) {
     123             :     // Android allows SelectAll when selection is not collapsed, unless
     124             :     // everything has already been selected.
     125           1 :     final value = delegate.textEditingValue;
     126           1 :     return delegate.selectAllEnabled &&
     127           2 :         value.text.isNotEmpty &&
     128           3 :         !(value.selection.start == 0 &&
     129           5 :             value.selection.end == value.text.length);
     130             :   }
     131             : }
     132             : 
     133             : // The label and callback for the available default text selection menu buttons.
     134             : class _TextSelectionToolbarItemData {
     135           1 :   const _TextSelectionToolbarItemData({
     136             :     required this.icon,
     137             :     required this.onPressed,
     138             :   });
     139             : 
     140             :   final IconData icon;
     141             :   final VoidCallback onPressed;
     142             : }
     143             : 
     144             : // The highest level toolbar widget, built directly by buildToolbar.
     145             : class _TextSelectionControlsToolbar extends StatefulWidget {
     146           1 :   const _TextSelectionControlsToolbar({
     147             :     Key? key,
     148             :     required this.clipboardStatus,
     149             :     required this.delegate,
     150             :     required this.endpoints,
     151             :     required this.globalEditableRegion,
     152             :     required this.handleCut,
     153             :     required this.handleCopy,
     154             :     required this.handlePaste,
     155             :     required this.handleSelectAll,
     156             :     required this.selectionMidpoint,
     157             :     required this.textLineWidth,
     158           1 :   }) : super(key: key);
     159             : 
     160             :   final ClipboardStatusNotifier clipboardStatus;
     161             :   final TextSelectionDelegate delegate;
     162             :   final List<TextSelectionPoint> endpoints;
     163             :   final Rect globalEditableRegion;
     164             :   final VoidCallback? handleCut;
     165             :   final VoidCallback? handleCopy;
     166             :   final VoidCallback? handlePaste;
     167             :   final VoidCallback? handleSelectAll;
     168             :   final Offset selectionMidpoint;
     169             :   final double textLineWidth;
     170             : 
     171           1 :   @override
     172             :   _TextSelectionControlsToolbarState createState() =>
     173           1 :       _TextSelectionControlsToolbarState();
     174             : }
     175             : 
     176             : class _TextSelectionControlsToolbarState
     177             :     extends State<_TextSelectionControlsToolbar> with TickerProviderStateMixin {
     178           0 :   void _onChangedClipboardStatus() {
     179           0 :     setState(() {
     180             :       // Inform the widget that the value of clipboardStatus has changed.
     181             :     });
     182             :   }
     183             : 
     184           1 :   @override
     185             :   void initState() {
     186           1 :     super.initState();
     187           4 :     widget.clipboardStatus.addListener(_onChangedClipboardStatus);
     188           3 :     widget.clipboardStatus.update();
     189             :   }
     190             : 
     191           0 :   @override
     192             :   void didUpdateWidget(_TextSelectionControlsToolbar oldWidget) {
     193           0 :     super.didUpdateWidget(oldWidget);
     194           0 :     if (widget.clipboardStatus != oldWidget.clipboardStatus) {
     195           0 :       widget.clipboardStatus.addListener(_onChangedClipboardStatus);
     196           0 :       oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
     197             :     }
     198           0 :     widget.clipboardStatus.update();
     199             :   }
     200             : 
     201           1 :   @override
     202             :   void dispose() {
     203           1 :     super.dispose();
     204             :     // When used in an Overlay, it can happen that this is disposed after its
     205             :     // creator has already disposed _clipboardStatus.
     206           3 :     if (!widget.clipboardStatus.disposed) {
     207           4 :       widget.clipboardStatus.removeListener(_onChangedClipboardStatus);
     208             :     }
     209             :   }
     210             : 
     211           1 :   @override
     212             :   Widget build(BuildContext context) {
     213             :     // If there are no buttons to be shown, don't render anything.
     214           2 :     if (widget.handleCut == null &&
     215           2 :         widget.handleCopy == null &&
     216           2 :         widget.handlePaste == null &&
     217           2 :         widget.handleSelectAll == null) {
     218             :       return const SizedBox.shrink();
     219             :     }
     220             :     // If the paste button is desired, don't render anything until the state of
     221             :     // the clipboard is known, since it's used to determine if paste is shown.
     222           2 :     if (widget.handlePaste != null &&
     223           4 :         widget.clipboardStatus.value == ClipboardStatus.unknown) {
     224             :       return const SizedBox.shrink();
     225             :     }
     226             : 
     227             :     // Calculate the positioning of the menu. It is placed to the left of the
     228             :     // selection if there is enough room, or otherwise to the right.
     229           3 :     final startTextSelectionPoint = widget.endpoints[0];
     230             :     final endTextSelectionPoint =
     231          10 :         widget.endpoints.length > 1 ? widget.endpoints[1] : widget.endpoints[0];
     232           1 :     final anchorLeft = Offset(
     233           4 :         widget.globalEditableRegion.left +
     234           3 :             startTextSelectionPoint.point.dx -
     235           3 :             widget.textLineWidth -
     236             :             _kToolbarContentDistance,
     237           7 :         widget.globalEditableRegion.top + widget.selectionMidpoint.dy);
     238           1 :     final anchorRight = Offset(
     239           4 :         widget.globalEditableRegion.left +
     240           3 :             endTextSelectionPoint.point.dx +
     241             :             _kToolbarContentDistanceRight,
     242           7 :         widget.globalEditableRegion.top + widget.selectionMidpoint.dy);
     243             : 
     244             :     // Determine which buttons will appear so that the order and total number is
     245             :     // known.
     246             :     final itemDatas =
     247           1 :         <_TextSelectionToolbarItemData>[
     248           2 :       if (widget.handleCut != null)
     249           1 :         _TextSelectionToolbarItemData(
     250             :           icon: Icons.cut,
     251           2 :           onPressed: widget.handleCut!,
     252             :         ),
     253           2 :       if (widget.handleCopy != null)
     254           1 :         _TextSelectionToolbarItemData(
     255             :           icon: Icons.copy,
     256           2 :           onPressed: widget.handleCopy!,
     257             :         ),
     258           2 :       if (widget.handlePaste != null &&
     259           4 :           widget.clipboardStatus.value == ClipboardStatus.pasteable)
     260           1 :         _TextSelectionToolbarItemData(
     261             :           icon: Icons.paste,
     262           2 :           onPressed: widget.handlePaste!,
     263             :         ),
     264           2 :       if (widget.handleSelectAll != null)
     265           1 :         _TextSelectionToolbarItemData(
     266             :           icon: Icons.select_all,
     267           2 :           onPressed: widget.handleSelectAll!,
     268             :         ),
     269             :     ];
     270             : 
     271             :     // If there is no option available, build an empty widget.
     272           1 :     if (itemDatas.isEmpty) {
     273             :       return const SizedBox(width: 0.0, height: 0.0);
     274             :     }
     275             : 
     276           1 :     return MongolTextSelectionToolbar(
     277             :       anchorLeft: anchorLeft,
     278             :       anchorRight: anchorRight,
     279             :       children: itemDatas
     280           1 :           .asMap()
     281           1 :           .entries
     282           2 :           .map((MapEntry<int, _TextSelectionToolbarItemData> entry) {
     283           1 :         return MongolTextSelectionToolbarButton(
     284           1 :           padding: MongolTextSelectionToolbarButton.getPadding(
     285           2 :               entry.key, itemDatas.length),
     286           2 :           onPressed: entry.value.onPressed,
     287           3 :           child: Icon(entry.value.icon),
     288             :         );
     289           1 :       }).toList(),
     290             :     );
     291             :   }
     292             : }
     293             : 
     294             : /// Draws a single text selection handle which points up and to the left.
     295             : class _TextSelectionHandlePainter extends CustomPainter {
     296           1 :   _TextSelectionHandlePainter({required this.color});
     297             : 
     298             :   final Color color;
     299             : 
     300           1 :   @override
     301             :   void paint(Canvas canvas, Size size) {
     302           3 :     final paint = Paint()..color = color;
     303           2 :     final radius = size.width / 2.0;
     304             :     final circle =
     305           2 :         Rect.fromCircle(center: Offset(radius, radius), radius: radius);
     306           1 :     final point = Rect.fromLTWH(0.0, 0.0, radius, radius);
     307           1 :     final path = Path()
     308           1 :       ..addOval(circle)
     309           1 :       ..addRect(point);
     310           1 :     canvas.drawPath(path, paint);
     311             :   }
     312             : 
     313           1 :   @override
     314             :   bool shouldRepaint(_TextSelectionHandlePainter oldPainter) {
     315           3 :     return color != oldPainter.color;
     316             :   }
     317             : }
     318             : 
     319             : /// Text selection controls that follow the Material Design specification.
     320           2 : final TextSelectionControls mongolTextSelectionControls =
     321           1 :     MongolTextSelectionControls();

Generated by: LCOV version 1.15