Commit b89935e5 authored by Adam Barth's avatar Adam Barth

Merge pull request #1496 from abarth/move_caret

Adds the ability to move the caret by tapping
parents 4125f386 36099383
...@@ -121,6 +121,16 @@ class _InputState extends State<Input> { ...@@ -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() { void _handleTextUpdated() {
if (_value != _editableString.text) { if (_value != _editableString.text) {
setState(() { setState(() {
...@@ -137,6 +147,15 @@ class _InputState extends State<Input> { ...@@ -137,6 +147,15 @@ class _InputState extends State<Input> {
config.onSubmitted(_value); config.onSubmitted(_value);
} }
void _handleSelectionChanged(TextSelection selection) {
if (_isAttachedToKeyboard) {
_keyboardHandle.setSelection(selection.start, selection.end);
} else {
_editableString.setSelection(selection);
_requestKeyboard();
}
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
...@@ -221,7 +240,8 @@ class _InputState extends State<Input> { ...@@ -221,7 +240,8 @@ class _InputState extends State<Input> {
style: textStyle, style: textStyle,
hideText: config.hideText, hideText: config.hideText,
cursorColor: cursorColor, cursorColor: cursorColor,
selectionColor: cursorColor selectionColor: cursorColor,
onSelectionChanged: _handleSelectionChanged
) )
)); ));
...@@ -258,15 +278,7 @@ class _InputState extends State<Input> { ...@@ -258,15 +278,7 @@ class _InputState extends State<Input> {
return new GestureDetector( return new GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: _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
}
},
child: new Padding( child: new Padding(
padding: const EdgeDims.symmetric(horizontal: 16.0), padding: const EdgeDims.symmetric(horizontal: 16.0),
child: child child: child
......
...@@ -2,38 +2,9 @@ ...@@ -2,38 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
/// Whether a [TextPosition] is visually upstream or downstream of its offset. import 'dart:ui' show TextAffinity, TextPosition;
///
/// 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,
/// 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. export 'dart:ui' show TextAffinity, TextPosition;
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;
}
/// A range of characters in a string of text. /// A range of characters in a string of text.
class TextRange { class TextRange {
...@@ -97,9 +68,15 @@ class TextSelection extends TextRange { ...@@ -97,9 +68,15 @@ class TextSelection extends TextRange {
const TextSelection.collapsed({ const TextSelection.collapsed({
int offset, int offset,
this.affinity: TextAffinity.downstream, this.affinity: TextAffinity.downstream
this.isDirectional: false }) : baseOffset = offset, extentOffset = offset, isDirectional = false, super.collapsed(offset);
}) : baseOffset = offset, extentOffset = offset, 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. /// The offset at which the selection originates.
/// ///
...@@ -141,4 +118,8 @@ class TextSelection extends TextRange { ...@@ -141,4 +118,8 @@ class TextSelection extends TextRange {
/// ///
/// Might be larger than, smaller than, or equal to base. /// Might be larger than, smaller than, or equal to base.
TextPosition get extent => new TextPosition(offset: extentOffset, affinity: affinity); 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 { ...@@ -265,4 +265,9 @@ class TextPainter {
return _paragraph.getBoxesForRange(selection.start, selection.end); return _paragraph.getBoxesForRange(selection.start, selection.end);
} }
TextPosition getPositionForOffset(Offset offset) {
assert(!_needsLayout);
return _paragraph.getPositionForOffset(offset);
}
} }
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
import 'paragraph.dart'; import 'paragraph.dart';
import 'proxy_box.dart' show SizeChangedCallback;
const _kCaretGap = 1.0; // pixels const _kCaretGap = 1.0; // pixels
const _kCaretHeightOffset = 2.0; // pixels const _kCaretHeightOffset = 2.0; // pixels
...@@ -26,22 +26,28 @@ class RenderEditableLine extends RenderBox { ...@@ -26,22 +26,28 @@ class RenderEditableLine extends RenderBox {
Color selectionColor, Color selectionColor,
TextSelection selection, TextSelection selection,
Offset paintOffset: Offset.zero, Offset paintOffset: Offset.zero,
this.onSelectionChanged,
this.onContentSizeChanged this.onContentSizeChanged
}) : _textPainter = new TextPainter(text), }) : _textPainter = new TextPainter(text),
_cursorColor = cursorColor, _cursorColor = cursorColor,
_showCursor = showCursor, _showCursor = showCursor,
_selection = selection, _selection = selection,
_paintOffset = paintOffset { _paintOffset = paintOffset {
assert(!showCursor || cursorColor != null); assert(!showCursor || cursorColor != null);
// TODO(abarth): These min/max values should be the default for TextPainter. // TODO(abarth): These min/max values should be the default for TextPainter.
_textPainter _textPainter
..minWidth = 0.0 ..minWidth = 0.0
..maxWidth = double.INFINITY ..maxWidth = double.INFINITY
..minHeight = 0.0 ..minHeight = 0.0
..maxHeight = double.INFINITY; ..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 /// The text to display
StyledTextSpan get text => _textPainter.text; StyledTextSpan get text => _textPainter.text;
...@@ -147,6 +153,31 @@ class RenderEditableLine extends RenderBox { ...@@ -147,6 +153,31 @@ class RenderEditableLine extends RenderBox {
bool hitTestSelf(Point position) => true; 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 BoxConstraints _constraintsForCurrentLayout; // when null, we don't have a current layout
// TODO(abarth): This logic should live in TextPainter and be shared with RenderParagraph. // TODO(abarth): This logic should live in TextPainter and be shared with RenderParagraph.
......
...@@ -1115,9 +1115,6 @@ class RenderFractionalTranslation extends RenderProxyBox { ...@@ -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 /// Calls [onSizeChanged] whenever the child's layout size changes
/// ///
/// Because size observer calls its callback during layout, you cannot modify /// Because size observer calls its callback during layout, you cannot modify
...@@ -1131,7 +1128,7 @@ class RenderSizeObserver extends RenderProxyBox { ...@@ -1131,7 +1128,7 @@ class RenderSizeObserver extends RenderProxyBox {
} }
/// The callback to call whenever the child's layout size changes /// The callback to call whenever the child's layout size changes
SizeChangedCallback onSizeChanged; ValueChanged<Size> onSizeChanged;
void performLayout() { void performLayout() {
Size oldSize = hasSize ? size : null; Size oldSize = hasSize ? size : null;
...@@ -1540,7 +1537,7 @@ class RenderSemanticAnnotations extends RenderProxyBox { ...@@ -1540,7 +1537,7 @@ class RenderSemanticAnnotations extends RenderProxyBox {
/// If 'container' is true, this RenderObject will introduce a new /// If 'container' is true, this RenderObject will introduce a new
/// node in the semantics tree. Otherwise, the semantics will be /// node in the semantics tree. Otherwise, the semantics will be
/// merged with the semantics of any ancestors. /// merged with the semantics of any ancestors.
/// ///
/// The 'container' flag is implicitly set to true on the immediate /// The 'container' flag is implicitly set to true on the immediate
/// semantics-providing descendants of a node where multiple /// semantics-providing descendants of a node where multiple
/// children have semantics or have descendants providing semantics. /// children have semantics or have descendants providing semantics.
......
...@@ -819,7 +819,7 @@ class SizeObserver extends OneChildRenderObjectWidget { ...@@ -819,7 +819,7 @@ class SizeObserver extends OneChildRenderObjectWidget {
} }
/// The callback to call whenever the child's layout size changes /// The callback to call whenever the child's layout size changes
final SizeChangedCallback onSizeChanged; final ValueChanged<Size> onSizeChanged;
RenderSizeObserver createRenderObject() => new RenderSizeObserver(onSizeChanged: onSizeChanged); RenderSizeObserver createRenderObject() => new RenderSizeObserver(onSizeChanged: onSizeChanged);
......
...@@ -158,6 +158,10 @@ class EditableString { ...@@ -158,6 +158,10 @@ class EditableString {
/// The range of text that is currently selected. /// The range of text that is currently selected.
TextSelection get selection => _client.selection; TextSelection get selection => _client.selection;
void setSelection(TextSelection selection) {
_client.selection = selection;
}
/// A keyboard client stub that can be attached to a keyboard service. /// A keyboard client stub that can be attached to a keyboard service.
/// ///
/// See [Keyboard]. /// See [Keyboard].
...@@ -180,7 +184,8 @@ class RawEditableLine extends Scrollable { ...@@ -180,7 +184,8 @@ class RawEditableLine extends Scrollable {
this.hideText: false, this.hideText: false,
this.style, this.style,
this.cursorColor, this.cursorColor,
this.selectionColor this.selectionColor,
this.onSelectionChanged
}) : super( }) : super(
key: key, key: key,
initialScrollOffset: 0.0, initialScrollOffset: 0.0,
...@@ -205,6 +210,9 @@ class RawEditableLine extends Scrollable { ...@@ -205,6 +210,9 @@ class RawEditableLine extends Scrollable {
/// The color to use when painting the selection. /// The color to use when painting the selection.
final Color selectionColor; final Color selectionColor;
/// Called when the user requests a change to the selection.
final ValueChanged<TextSelection> onSelectionChanged;
RawEditableTextState createState() => new RawEditableTextState(); RawEditableTextState createState() => new RawEditableTextState();
} }
...@@ -290,6 +298,7 @@ class RawEditableTextState extends ScrollableState<RawEditableLine> { ...@@ -290,6 +298,7 @@ class RawEditableTextState extends ScrollableState<RawEditableLine> {
selectionColor: config.selectionColor, selectionColor: config.selectionColor,
hideText: config.hideText, hideText: config.hideText,
onContentSizeChanged: _handleContentSizeChanged, onContentSizeChanged: _handleContentSizeChanged,
onSelectionChanged: config.onSelectionChanged,
paintOffset: new Offset(-scrollOffset, 0.0) paintOffset: new Offset(-scrollOffset, 0.0)
) )
); );
...@@ -306,6 +315,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget { ...@@ -306,6 +315,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
this.selectionColor, this.selectionColor,
this.hideText, this.hideText,
this.onContentSizeChanged, this.onContentSizeChanged,
this.onSelectionChanged,
this.paintOffset this.paintOffset
}) : super(key: key); }) : super(key: key);
...@@ -315,7 +325,8 @@ class _EditableLineWidget extends LeafRenderObjectWidget { ...@@ -315,7 +325,8 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
final bool showCursor; final bool showCursor;
final Color selectionColor; final Color selectionColor;
final bool hideText; final bool hideText;
final SizeChangedCallback onContentSizeChanged; final ValueChanged<Size> onContentSizeChanged;
final ValueChanged<TextSelection> onSelectionChanged;
final Offset paintOffset; final Offset paintOffset;
RenderEditableLine createRenderObject() { RenderEditableLine createRenderObject() {
...@@ -326,19 +337,22 @@ class _EditableLineWidget extends LeafRenderObjectWidget { ...@@ -326,19 +337,22 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
selectionColor: selectionColor, selectionColor: selectionColor,
selection: value.selection, selection: value.selection,
onContentSizeChanged: onContentSizeChanged, onContentSizeChanged: onContentSizeChanged,
onSelectionChanged: onSelectionChanged,
paintOffset: paintOffset paintOffset: paintOffset
); );
} }
void updateRenderObject(RenderEditableLine renderObject, void updateRenderObject(RenderEditableLine renderObject,
_EditableLineWidget oldWidget) { _EditableLineWidget oldWidget) {
renderObject.text = _styledTextSpan; renderObject
renderObject.cursorColor = cursorColor; ..text = _styledTextSpan
renderObject.showCursor = showCursor; ..cursorColor = cursorColor
renderObject.selectionColor = selectionColor; ..showCursor = showCursor
renderObject.selection = value.selection; ..selectionColor = selectionColor
renderObject.onContentSizeChanged = onContentSizeChanged; ..selection = value.selection
renderObject.paintOffset = paintOffset; ..onContentSizeChanged = onContentSizeChanged
..onSelectionChanged = onSelectionChanged
..paintOffset = paintOffset;
} }
StyledTextSpan get _styledTextSpan { StyledTextSpan get _styledTextSpan {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment