diff --git a/packages/flutter/lib/src/material/input.dart b/packages/flutter/lib/src/material/input.dart
index 9c551b0de14..e1092dbb922 100644
--- a/packages/flutter/lib/src/material/input.dart
+++ b/packages/flutter/lib/src/material/input.dart
@@ -121,6 +121,16 @@ class _InputState extends State {
}
}
+ 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 {
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 {
style: textStyle,
hideText: config.hideText,
cursorColor: cursorColor,
- selectionColor: cursorColor
+ selectionColor: cursorColor,
+ onSelectionChanged: _handleSelectionChanged
)
));
@@ -258,15 +278,7 @@ class _InputState extends State {
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
diff --git a/packages/flutter/lib/src/painting/text_editing.dart b/packages/flutter/lib/src/painting/text_editing.dart
index 00a0835c713..736582b51af 100644
--- a/packages/flutter/lib/src/painting/text_editing.dart
+++ b/packages/flutter/lib/src/painting/text_editing.dart
@@ -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)';
+ }
}
diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart
index 4325c70d0b3..14bf7f1ba6d 100644
--- a/packages/flutter/lib/src/painting/text_painter.dart
+++ b/packages/flutter/lib/src/painting/text_painter.dart
@@ -265,4 +265,9 @@ class TextPainter {
return _paragraph.getBoxesForRange(selection.start, selection.end);
}
+ TextPosition getPositionForOffset(Offset offset) {
+ assert(!_needsLayout);
+ return _paragraph.getPositionForOffset(offset);
+ }
+
}
diff --git a/packages/flutter/lib/src/rendering/editable_line.dart b/packages/flutter/lib/src/rendering/editable_line.dart
index 4b8c47b67cd..4d346af84a8 100644
--- a/packages/flutter/lib/src/rendering/editable_line.dart
+++ b/packages/flutter/lib/src/rendering/editable_line.dart
@@ -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 onContentSizeChanged;
+ ValueChanged 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.
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index 44c1f4847d5..c65f2967954 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -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 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.
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 06be46988f5..29552b691d1 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -819,7 +819,7 @@ class SizeObserver extends OneChildRenderObjectWidget {
}
/// The callback to call whenever the child's layout size changes
- final SizeChangedCallback onSizeChanged;
+ final ValueChanged onSizeChanged;
RenderSizeObserver createRenderObject() => new RenderSizeObserver(onSizeChanged: onSizeChanged);
diff --git a/packages/flutter/lib/src/widgets/editable.dart b/packages/flutter/lib/src/widgets/editable.dart
index e2222bf4ba0..361db24cd6a 100644
--- a/packages/flutter/lib/src/widgets/editable.dart
+++ b/packages/flutter/lib/src/widgets/editable.dart
@@ -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 onSelectionChanged;
+
RawEditableTextState createState() => new RawEditableTextState();
}
@@ -290,6 +298,7 @@ class RawEditableTextState extends ScrollableState {
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 onContentSizeChanged;
+ final ValueChanged 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 {