Add comments to cursor class

pull/236/head
Till Friebe 4 years ago
parent 3b429840b6
commit 1fedaf0d13
  1. 135
      lib/src/widgets/cursor.dart
  2. 2
      lib/src/widgets/text_line.dart

@ -5,8 +5,7 @@ import 'package:flutter/widgets.dart';
import 'box.dart';
const Duration _FADE_DURATION = Duration(milliseconds: 250);
/// Style properties of editing cursor.
class CursorStyle {
const CursorStyle({
required this.color,
@ -19,13 +18,52 @@ class CursorStyle {
this.paintAboveText = false,
});
/// The color to use when painting the cursor.
final Color color;
/// The color to use when painting the background cursor aligned with the text
/// while rendering the floating cursor.
final Color backgroundColor;
/// How thick the cursor will be.
///
/// The cursor will draw under the text. The cursor width will extend
/// to the right of the boundary between characters for left-to-right text
/// and to the left for right-to-left text. This corresponds to extending
/// downstream relative to the selected position. Negative values may be used
/// to reverse this behavior.
final double width;
/// How tall the cursor will be.
///
/// By default, the cursor height is set to the preferred line height of the
/// text.
final double? height;
/// How rounded the corners of the cursor should be.
///
/// By default, the cursor has no radius.
final Radius? radius;
/// The offset that is used, in pixels, when painting the cursor on screen.
///
/// By default, the cursor position should be set to an offset of
/// (-[cursorWidth] * 0.5, 0.0) on iOS platforms and (0, 0) on Android
/// platforms. The origin from where the offset is applied to is the arbitrary
/// location where the cursor ends up being rendered from by default.
final Offset? offset;
/// Whether the cursor will animate from fully transparent to fully opaque
/// during each cursor blink.
///
/// By default, the cursor opacity will animate on iOS platforms and will not
/// animate on Android platforms.
final bool opacityAnimates;
/// If the cursor should be painted on top of the text or underneath it.
///
/// By default, the cursor should be painted on top for iOS platforms and
/// underneath for Android platforms.
final bool paintAboveText;
@override
@ -54,6 +92,10 @@ class CursorStyle {
paintAboveText.hashCode;
}
/// Controls the cursor of an editable widget.
///
/// This class is a [ChangeNotifier] and allows to listen for updates on the
/// cursor [style].
class CursorCont extends ChangeNotifier {
CursorCont({
required this.show,
@ -62,21 +104,35 @@ class CursorCont extends ChangeNotifier {
}) : _style = style,
blink = ValueNotifier(false),
color = ValueNotifier(style.color) {
_blinkOpacityCont =
AnimationController(vsync: tickerProvider, duration: _FADE_DURATION);
_blinkOpacityCont.addListener(_onColorTick);
_blinkOpacityController =
AnimationController(vsync: tickerProvider, duration: _fadeDuration);
_blinkOpacityController.addListener(_onColorTick);
}
// The time it takes for the cursor to fade from fully opaque to fully
// transparent and vice versa. A full cursor blink, from transparent to opaque
// to transparent, is twice this duration.
static const Duration _blinkHalfPeriod = Duration(milliseconds: 500);
// The time the cursor is static in opacity before animating to become
// transparent.
static const Duration _blinkWaitForStart = Duration(milliseconds: 150);
// This value is an eyeball estimation of the time it takes for the iOS cursor
// to ease in and out.
static const Duration _fadeDuration = Duration(milliseconds: 250);
final ValueNotifier<bool> show;
final ValueNotifier<bool> blink;
final ValueNotifier<Color> color;
late AnimationController _blinkOpacityCont;
final ValueNotifier<bool> blink;
late final AnimationController _blinkOpacityController;
Timer? _cursorTimer;
bool _targetCursorVisibility = false;
CursorStyle _style;
CursorStyle _style;
CursorStyle get style => _style;
set style(CursorStyle value) {
if (_style == value) return;
_style = value;
@ -85,9 +141,9 @@ class CursorCont extends ChangeNotifier {
@override
void dispose() {
_blinkOpacityCont.removeListener(_onColorTick);
_blinkOpacityController.removeListener(_onColorTick);
stopCursorTimer();
_blinkOpacityCont.dispose();
_blinkOpacityController.dispose();
show.dispose();
blink.dispose();
color.dispose();
@ -99,28 +155,32 @@ class CursorCont extends ChangeNotifier {
_targetCursorVisibility = !_targetCursorVisibility;
final targetOpacity = _targetCursorVisibility ? 1.0 : 0.0;
if (style.opacityAnimates) {
_blinkOpacityCont.animateTo(targetOpacity, curve: Curves.easeOut);
// If we want to show the cursor, we will animate the opacity to the value
// of 1.0, and likewise if we want to make it disappear, to 0.0. An easing
// curve is used for the animation to mimic the aesthetics of the native
// iOS cursor.
//
// These values and curves have been obtained through eyeballing, so are
// likely not exactly the same as the values for native iOS.
_blinkOpacityController.animateTo(targetOpacity, curve: Curves.easeOut);
} else {
_blinkOpacityCont.value = targetOpacity;
_blinkOpacityController.value = targetOpacity;
}
}
void _cursorWaitForStart(Timer timer) {
void _waitForStart(Timer timer) {
_cursorTimer?.cancel();
_cursorTimer =
Timer.periodic(const Duration(milliseconds: 500), _cursorTick);
_cursorTimer = Timer.periodic(_blinkHalfPeriod, _cursorTick);
}
void startCursorTimer() {
_targetCursorVisibility = true;
_blinkOpacityCont.value = 1.0;
_blinkOpacityController.value = 1.0;
if (style.opacityAnimates) {
_cursorTimer = Timer.periodic(
const Duration(milliseconds: 150), _cursorWaitForStart);
_cursorTimer = Timer.periodic(_blinkWaitForStart, _waitForStart);
} else {
_cursorTimer =
Timer.periodic(const Duration(milliseconds: 500), _cursorTick);
_cursorTimer = Timer.periodic(_blinkHalfPeriod, _cursorTick);
}
}
@ -128,10 +188,10 @@ class CursorCont extends ChangeNotifier {
_cursorTimer?.cancel();
_cursorTimer = null;
_targetCursorVisibility = false;
_blinkOpacityCont.value = 0.0;
_blinkOpacityController.value = 0.0;
if (style.opacityAnimates) {
_blinkOpacityCont
_blinkOpacityController
..stop()
..value = 0.0;
}
@ -149,32 +209,42 @@ class CursorCont extends ChangeNotifier {
}
void _onColorTick() {
color.value = _style.color.withOpacity(_blinkOpacityCont.value);
blink.value = show.value && _blinkOpacityCont.value > 0;
color.value = _style.color.withOpacity(_blinkOpacityController.value);
blink.value = show.value && _blinkOpacityController.value > 0;
}
}
/// Paints the editing cursor.
class CursorPainter {
CursorPainter(this.editable, this.style, this.prototype, this.color,
this.devicePixelRatio);
CursorPainter(
this.editable,
this.style,
this.prototype,
this.color,
this.devicePixelRatio,
);
final RenderContentProxyBox? editable;
final CursorStyle style;
final Rect? prototype;
final Rect prototype;
final Color color;
final double devicePixelRatio;
/// Paints cursor on [canvas] at specified [position].
void paint(Canvas canvas, Offset offset, TextPosition position) {
assert(prototype != null);
final caretOffset =
editable!.getOffsetForCaret(position, prototype) + offset;
var caretRect = prototype!.shift(caretOffset);
var caretRect = prototype.shift(caretOffset);
if (style.offset != null) {
caretRect = caretRect.shift(style.offset!);
}
if (caretRect.left < 0.0) {
// For iOS the cursor may get clipped by the scroll view when
// it's located at a beginning of a line. We ensure that this
// does not happen here. This may result in the cursor being painted
// closer to the character on the right, but it's arguably better
// then painting clipped cursor (or even cursor completely hidden).
caretRect = caretRect.shift(Offset(-caretRect.left, 0));
}
@ -185,6 +255,8 @@ class CursorPainter {
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
// Override the height to take the full height of the glyph at the TextPosition
// when not on iOS. iOS has special handling that creates a taller caret.
caretRect = Rect.fromLTWH(
caretRect.left,
caretRect.top - 2.0,
@ -194,6 +266,7 @@ class CursorPainter {
break;
case TargetPlatform.iOS:
case TargetPlatform.macOS:
// Center the caret vertically along the text.
caretRect = Rect.fromLTWH(
caretRect.left,
caretRect.top + (caretHeight - caretRect.height) / 2,

@ -727,7 +727,7 @@ class RenderEditableTextLine extends RenderEditableBox {
CursorPainter get _cursorPainter => CursorPainter(
_body,
cursorCont.style,
_caretPrototype,
_caretPrototype!,
cursorCont.color.value,
devicePixelRatio,
);

Loading…
Cancel
Save