mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #1496 from abarth/move_caret
Adds the ability to move the caret by tapping
This commit is contained in:
commit
b89935e597
@ -121,6 +121,16 @@ class _InputState extends State<Input> {
|
||||
}
|
||||
}
|
||||
|
||||
void _requestKeyboard() {
|
||||
if (Focus.at(context)) {
|
||||
assert(_isAttachedToKeyboard);
|
||||
_keyboardHandle.showByRequest();
|
||||
} else {
|
||||
Focus.moveTo(config.key);
|
||||
// we'll get told to rebuild and we'll take care of the keyboard then
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTextUpdated() {
|
||||
if (_value != _editableString.text) {
|
||||
setState(() {
|
||||
@ -137,6 +147,15 @@ class _InputState extends State<Input> {
|
||||
config.onSubmitted(_value);
|
||||
}
|
||||
|
||||
void _handleSelectionChanged(TextSelection selection) {
|
||||
if (_isAttachedToKeyboard) {
|
||||
_keyboardHandle.setSelection(selection.start, selection.end);
|
||||
} else {
|
||||
_editableString.setSelection(selection);
|
||||
_requestKeyboard();
|
||||
}
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
ThemeData themeData = Theme.of(context);
|
||||
@ -221,7 +240,8 @@ class _InputState extends State<Input> {
|
||||
style: textStyle,
|
||||
hideText: config.hideText,
|
||||
cursorColor: cursorColor,
|
||||
selectionColor: cursorColor
|
||||
selectionColor: cursorColor,
|
||||
onSelectionChanged: _handleSelectionChanged
|
||||
)
|
||||
));
|
||||
|
||||
@ -258,15 +278,7 @@ class _InputState extends State<Input> {
|
||||
|
||||
return new GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (Focus.at(context)) {
|
||||
assert(_isAttachedToKeyboard);
|
||||
_keyboardHandle.showByRequest();
|
||||
} else {
|
||||
Focus.moveTo(config.key);
|
||||
// we'll get told to rebuild and we'll take care of the keyboard then
|
||||
}
|
||||
},
|
||||
onTap: _requestKeyboard,
|
||||
child: new Padding(
|
||||
padding: const EdgeDims.symmetric(horizontal: 16.0),
|
||||
child: child
|
||||
|
||||
@ -2,38 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/// Whether a [TextPosition] is visually upstream or downstream of its offset.
|
||||
///
|
||||
/// For example, when a text position exists at a line break, a single offset has
|
||||
/// two visual positions, one prior to the line break (at the end of the first
|
||||
/// line) and one after the line break (at the start of the second line). A text
|
||||
/// affinity disambiguates between those cases. (Something similar happens with
|
||||
/// between runs of bidirectional text.)
|
||||
enum TextAffinity {
|
||||
/// The position has affinity for the upstream side of the text position.
|
||||
///
|
||||
/// For example, if the offset of the text position is a line break, the
|
||||
/// position represents the end of the first line.
|
||||
upstream,
|
||||
import 'dart:ui' show TextAffinity, TextPosition;
|
||||
|
||||
/// The position has affinity for the downstream side of the text position.
|
||||
///
|
||||
/// For example, if the offset of the text position is a line break, the
|
||||
/// position represents the start of the second line.
|
||||
downstream
|
||||
}
|
||||
|
||||
/// A visual position in a string of text.
|
||||
class TextPosition {
|
||||
const TextPosition({ this.offset, this.affinity: TextAffinity.downstream });
|
||||
|
||||
/// The index of the character just prior to the position.
|
||||
final int offset;
|
||||
|
||||
/// If the offset has more than one visual location (e.g., occurs at a line
|
||||
/// break), which of the two locations is represented by this position.
|
||||
final TextAffinity affinity;
|
||||
}
|
||||
export 'dart:ui' show TextAffinity, TextPosition;
|
||||
|
||||
/// A range of characters in a string of text.
|
||||
class TextRange {
|
||||
@ -97,9 +68,15 @@ class TextSelection extends TextRange {
|
||||
|
||||
const TextSelection.collapsed({
|
||||
int offset,
|
||||
this.affinity: TextAffinity.downstream,
|
||||
this.isDirectional: false
|
||||
}) : baseOffset = offset, extentOffset = offset, super.collapsed(offset);
|
||||
this.affinity: TextAffinity.downstream
|
||||
}) : baseOffset = offset, extentOffset = offset, isDirectional = false, super.collapsed(offset);
|
||||
|
||||
TextSelection.fromPosition(TextPosition position)
|
||||
: baseOffset = position.offset,
|
||||
extentOffset = position.offset,
|
||||
affinity = position.affinity,
|
||||
isDirectional = false,
|
||||
super.collapsed(position.offset);
|
||||
|
||||
/// The offset at which the selection originates.
|
||||
///
|
||||
@ -141,4 +118,8 @@ class TextSelection extends TextRange {
|
||||
///
|
||||
/// Might be larger than, smaller than, or equal to base.
|
||||
TextPosition get extent => new TextPosition(offset: extentOffset, affinity: affinity);
|
||||
|
||||
String toString() {
|
||||
return '$runtimeType(baseOffset: $baseOffset, extentOffset: $extentOffset, affinity: $affinity, isDirectional: $isDirectional)';
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,4 +265,9 @@ class TextPainter {
|
||||
return _paragraph.getBoxesForRange(selection.start, selection.end);
|
||||
}
|
||||
|
||||
TextPosition getPositionForOffset(Offset offset) {
|
||||
assert(!_needsLayout);
|
||||
return _paragraph.getPositionForOffset(offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,12 +4,12 @@
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
|
||||
import 'box.dart';
|
||||
import 'object.dart';
|
||||
import 'paragraph.dart';
|
||||
import 'proxy_box.dart' show SizeChangedCallback;
|
||||
|
||||
const _kCaretGap = 1.0; // pixels
|
||||
const _kCaretHeightOffset = 2.0; // pixels
|
||||
@ -26,22 +26,28 @@ class RenderEditableLine extends RenderBox {
|
||||
Color selectionColor,
|
||||
TextSelection selection,
|
||||
Offset paintOffset: Offset.zero,
|
||||
this.onSelectionChanged,
|
||||
this.onContentSizeChanged
|
||||
}) : _textPainter = new TextPainter(text),
|
||||
_cursorColor = cursorColor,
|
||||
_showCursor = showCursor,
|
||||
_selection = selection,
|
||||
_paintOffset = paintOffset {
|
||||
assert(!showCursor || cursorColor != null);
|
||||
// TODO(abarth): These min/max values should be the default for TextPainter.
|
||||
_textPainter
|
||||
..minWidth = 0.0
|
||||
..maxWidth = double.INFINITY
|
||||
..minHeight = 0.0
|
||||
..maxHeight = double.INFINITY;
|
||||
assert(!showCursor || cursorColor != null);
|
||||
// TODO(abarth): These min/max values should be the default for TextPainter.
|
||||
_textPainter
|
||||
..minWidth = 0.0
|
||||
..maxWidth = double.INFINITY
|
||||
..minHeight = 0.0
|
||||
..maxHeight = double.INFINITY;
|
||||
_tap = new TapGestureRecognizer(router: Gesturer.instance.pointerRouter, gestureArena: Gesturer.instance.gestureArena)
|
||||
..onTapDown = _handleTapDown
|
||||
..onTap = _handleTap
|
||||
..onTapCancel = _handleTapCancel;
|
||||
}
|
||||
|
||||
SizeChangedCallback onContentSizeChanged;
|
||||
ValueChanged<Size> onContentSizeChanged;
|
||||
ValueChanged<TextSelection> onSelectionChanged;
|
||||
|
||||
/// The text to display
|
||||
StyledTextSpan get text => _textPainter.text;
|
||||
@ -147,6 +153,31 @@ class RenderEditableLine extends RenderBox {
|
||||
|
||||
bool hitTestSelf(Point position) => true;
|
||||
|
||||
TapGestureRecognizer _tap;
|
||||
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
|
||||
if (event is PointerDownEvent && onSelectionChanged != null)
|
||||
_tap.addPointer(event);
|
||||
}
|
||||
|
||||
Point _lastTapDownPosition;
|
||||
void _handleTapDown(Point globalPosition) {
|
||||
_lastTapDownPosition = globalPosition;
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
assert(_lastTapDownPosition != null);
|
||||
final Point global = _lastTapDownPosition;
|
||||
_lastTapDownPosition = null;
|
||||
if (onSelectionChanged != null) {
|
||||
TextPosition position = _textPainter.getPositionForOffset(globalToLocal(global).toOffset());
|
||||
onSelectionChanged(new TextSelection.fromPosition(position));
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTapCancel() {
|
||||
_lastTapDownPosition = null;
|
||||
}
|
||||
|
||||
BoxConstraints _constraintsForCurrentLayout; // when null, we don't have a current layout
|
||||
|
||||
// TODO(abarth): This logic should live in TextPainter and be shared with RenderParagraph.
|
||||
|
||||
@ -1115,9 +1115,6 @@ class RenderFractionalTranslation extends RenderProxyBox {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when a size changes.
|
||||
typedef void SizeChangedCallback(Size newSize);
|
||||
|
||||
/// Calls [onSizeChanged] whenever the child's layout size changes
|
||||
///
|
||||
/// Because size observer calls its callback during layout, you cannot modify
|
||||
@ -1131,7 +1128,7 @@ class RenderSizeObserver extends RenderProxyBox {
|
||||
}
|
||||
|
||||
/// The callback to call whenever the child's layout size changes
|
||||
SizeChangedCallback onSizeChanged;
|
||||
ValueChanged<Size> onSizeChanged;
|
||||
|
||||
void performLayout() {
|
||||
Size oldSize = hasSize ? size : null;
|
||||
@ -1540,7 +1537,7 @@ class RenderSemanticAnnotations extends RenderProxyBox {
|
||||
/// If 'container' is true, this RenderObject will introduce a new
|
||||
/// node in the semantics tree. Otherwise, the semantics will be
|
||||
/// merged with the semantics of any ancestors.
|
||||
///
|
||||
///
|
||||
/// The 'container' flag is implicitly set to true on the immediate
|
||||
/// semantics-providing descendants of a node where multiple
|
||||
/// children have semantics or have descendants providing semantics.
|
||||
|
||||
@ -819,7 +819,7 @@ class SizeObserver extends OneChildRenderObjectWidget {
|
||||
}
|
||||
|
||||
/// The callback to call whenever the child's layout size changes
|
||||
final SizeChangedCallback onSizeChanged;
|
||||
final ValueChanged<Size> onSizeChanged;
|
||||
|
||||
RenderSizeObserver createRenderObject() => new RenderSizeObserver(onSizeChanged: onSizeChanged);
|
||||
|
||||
|
||||
@ -158,6 +158,10 @@ class EditableString {
|
||||
/// The range of text that is currently selected.
|
||||
TextSelection get selection => _client.selection;
|
||||
|
||||
void setSelection(TextSelection selection) {
|
||||
_client.selection = selection;
|
||||
}
|
||||
|
||||
/// A keyboard client stub that can be attached to a keyboard service.
|
||||
///
|
||||
/// See [Keyboard].
|
||||
@ -180,7 +184,8 @@ class RawEditableLine extends Scrollable {
|
||||
this.hideText: false,
|
||||
this.style,
|
||||
this.cursorColor,
|
||||
this.selectionColor
|
||||
this.selectionColor,
|
||||
this.onSelectionChanged
|
||||
}) : super(
|
||||
key: key,
|
||||
initialScrollOffset: 0.0,
|
||||
@ -205,6 +210,9 @@ class RawEditableLine extends Scrollable {
|
||||
/// The color to use when painting the selection.
|
||||
final Color selectionColor;
|
||||
|
||||
/// Called when the user requests a change to the selection.
|
||||
final ValueChanged<TextSelection> onSelectionChanged;
|
||||
|
||||
RawEditableTextState createState() => new RawEditableTextState();
|
||||
}
|
||||
|
||||
@ -290,6 +298,7 @@ class RawEditableTextState extends ScrollableState<RawEditableLine> {
|
||||
selectionColor: config.selectionColor,
|
||||
hideText: config.hideText,
|
||||
onContentSizeChanged: _handleContentSizeChanged,
|
||||
onSelectionChanged: config.onSelectionChanged,
|
||||
paintOffset: new Offset(-scrollOffset, 0.0)
|
||||
)
|
||||
);
|
||||
@ -306,6 +315,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
|
||||
this.selectionColor,
|
||||
this.hideText,
|
||||
this.onContentSizeChanged,
|
||||
this.onSelectionChanged,
|
||||
this.paintOffset
|
||||
}) : super(key: key);
|
||||
|
||||
@ -315,7 +325,8 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
|
||||
final bool showCursor;
|
||||
final Color selectionColor;
|
||||
final bool hideText;
|
||||
final SizeChangedCallback onContentSizeChanged;
|
||||
final ValueChanged<Size> onContentSizeChanged;
|
||||
final ValueChanged<TextSelection> onSelectionChanged;
|
||||
final Offset paintOffset;
|
||||
|
||||
RenderEditableLine createRenderObject() {
|
||||
@ -326,19 +337,22 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
|
||||
selectionColor: selectionColor,
|
||||
selection: value.selection,
|
||||
onContentSizeChanged: onContentSizeChanged,
|
||||
onSelectionChanged: onSelectionChanged,
|
||||
paintOffset: paintOffset
|
||||
);
|
||||
}
|
||||
|
||||
void updateRenderObject(RenderEditableLine renderObject,
|
||||
_EditableLineWidget oldWidget) {
|
||||
renderObject.text = _styledTextSpan;
|
||||
renderObject.cursorColor = cursorColor;
|
||||
renderObject.showCursor = showCursor;
|
||||
renderObject.selectionColor = selectionColor;
|
||||
renderObject.selection = value.selection;
|
||||
renderObject.onContentSizeChanged = onContentSizeChanged;
|
||||
renderObject.paintOffset = paintOffset;
|
||||
renderObject
|
||||
..text = _styledTextSpan
|
||||
..cursorColor = cursorColor
|
||||
..showCursor = showCursor
|
||||
..selectionColor = selectionColor
|
||||
..selection = value.selection
|
||||
..onContentSizeChanged = onContentSizeChanged
|
||||
..onSelectionChanged = onSelectionChanged
|
||||
..paintOffset = paintOffset;
|
||||
}
|
||||
|
||||
StyledTextSpan get _styledTextSpan {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user