Commit b1dae4c4 authored by Ian Hickson's avatar Ian Hickson

Various Input and Focus fixes

Require a Key on Input.

Simplify the API for Focus.at() and Focus.moveTo().
Fixes #236.
This will require an e-mail to flutter-dev.

Make Input grab focus onTap not onPointerDown.
Fixes #189.

Complain when you use Focus.at() with two different GlobalKeys that
are both in the tree at the same time.
Fixes #181.

Add dartdocs for Focus.moveTo() and Focus.moveScopeTo().
parent c8126288
...@@ -27,7 +27,9 @@ class Input extends Scrollable { ...@@ -27,7 +27,9 @@ class Input extends Scrollable {
key: key, key: key,
initialScrollOffset: 0.0, initialScrollOffset: 0.0,
scrollDirection: ScrollDirection.horizontal scrollDirection: ScrollDirection.horizontal
); ) {
assert(key != null);
}
final String initialValue; final String initialValue;
final KeyboardType keyboardType; final KeyboardType keyboardType;
...@@ -78,7 +80,7 @@ class InputState extends ScrollableState<Input> { ...@@ -78,7 +80,7 @@ class InputState extends ScrollableState<Input> {
Widget buildContent(BuildContext context) { Widget buildContent(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
bool focused = Focus.at(context, config); bool focused = Focus.at(context);
if (focused && !_keyboardHandle.attached) { if (focused && !_keyboardHandle.attached) {
_keyboardHandle = keyboard.show(_editableValue.stub, config.keyboardType); _keyboardHandle = keyboard.show(_editableValue.stub, config.keyboardType);
...@@ -118,7 +120,17 @@ class InputState extends ScrollableState<Input> { ...@@ -118,7 +120,17 @@ class InputState extends ScrollableState<Input> {
scrollOffset: scrollOffsetVector scrollOffset: scrollOffsetVector
)); ));
return new Listener( return new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (Focus.at(context)) {
assert(_keyboardHandle.attached);
_keyboardHandle.showByRequest();
} else {
Focus.moveTo(config.key);
// we'll get told to rebuild and we'll take care of the keyboard then
}
},
child: new SizeObserver( child: new SizeObserver(
onSizeChanged: _handleContainerSizeChanged, onSizeChanged: _handleContainerSizeChanged,
child: new Container( child: new Container(
...@@ -136,18 +148,7 @@ class InputState extends ScrollableState<Input> { ...@@ -136,18 +148,7 @@ class InputState extends ScrollableState<Input> {
) )
) )
) )
), )
behavior: HitTestBehavior.opaque,
onPointerDown: (_) {
// TODO(ianh): https://github.com/flutter/engine/issues/1530
if (Focus.at(context, config)) {
assert(_keyboardHandle.attached);
_keyboardHandle.showByRequest();
} else {
Focus.moveTo(context, config);
// we'll get told to rebuild and we'll take care of the keyboard then
}
}
); );
} }
......
...@@ -82,51 +82,71 @@ class Focus extends StatefulComponent { ...@@ -82,51 +82,71 @@ class Focus extends StatefulComponent {
final bool autofocus; final bool autofocus;
final Widget child; final Widget child;
static bool at(BuildContext context, Widget widget, { bool autofocus: true }) { static GlobalKey debugOnlyFocusedKey;
assert(widget != null);
assert(widget.key is GlobalKey); static bool at(BuildContext context, { bool autofocus: true }) {
assert(context != null);
assert(context.widget != null);
assert(context.widget.key != null);
assert(context.widget.key is GlobalKey);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope); _FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope);
if (focusScope != null) { if (focusScope != null) {
if (autofocus) if (autofocus)
focusScope._setFocusedWidgetIfUnset(widget.key); focusScope._setFocusedWidgetIfUnset(context.widget.key);
return focusScope.scopeFocused && return focusScope.scopeFocused &&
focusScope.focusedScope == null && focusScope.focusedScope == null &&
focusScope.focusedWidget == widget.key; focusScope.focusedWidget == context.widget.key;
} }
assert(() {
if (debugOnlyFocusedKey?.currentContext == null)
debugOnlyFocusedKey = context.widget.key;
if (debugOnlyFocusedKey != context.widget.key) {
debugPrint('Tried to focus widgets with two different keys: $debugOnlyFocusedKey and ${context.widget.key}');
assert('If you have more than one focusable widget, then you should put them inside a Focus.' == true);
}
return true;
});
return true; return true;
} }
static bool _atScope(BuildContext context, Widget widget, { bool autofocus: true }) { static bool _atScope(BuildContext context, { bool autofocus: true }) {
assert(widget != null); assert(context != null);
assert(context.widget != null);
assert(context.widget is Focus);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope); _FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope);
if (focusScope != null) { if (focusScope != null) {
assert(context.widget.key != null);
if (autofocus) if (autofocus)
focusScope._setFocusedScopeIfUnset(widget.key); focusScope._setFocusedScopeIfUnset(context.widget.key);
assert(widget.key != null);
return focusScope.scopeFocused && return focusScope.scopeFocused &&
focusScope.focusedScope == widget.key; focusScope.focusedScope == context.widget.key;
} }
return true; return true;
} }
// Don't call moveTo() and moveScopeTo() from your build() /// Focuses a particular widget, identified by its GlobalKey.
// functions, it's intended to be called from event listeners, e.g. /// The widget must be in the widget tree.
// in response to a finger tap or tab key. ///
/// Don't call moveTo() from your build() functions, it's intended to be
static void moveTo(BuildContext context, Widget widget) { /// called from event listeners, e.g. in response to a finger tap or tab key.
assert(widget != null); static void moveTo(GlobalKey key) {
assert(widget.key is GlobalKey); assert(key.currentContext != null);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope); _FocusScope focusScope = key.currentContext.ancestorWidgetOfType(_FocusScope);
if (focusScope != null) if (focusScope != null)
focusScope.focusState._setFocusedWidget(widget.key); focusScope.focusState._setFocusedWidget(key);
} }
static void moveScopeTo(BuildContext context, Focus component) { /// Focuses a particular focus scope, identified by its GlobalKey. The widget
assert(component != null); /// must be in the widget tree.
assert(component.key != null); ///
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope); /// Don't call moveScopeTo() from your build() functions, it's intended to be
/// called from event listeners, e.g. in response to a finger tap or tab key.
static void moveScopeTo(GlobalKey key) {
assert(key.currentWidget is Focus);
assert(key.currentContext != null);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfType(_FocusScope);
if (focusScope != null) if (focusScope != null)
focusScope.focusState._setFocusedScope(component.key); focusScope.focusState._setFocusedScope(key);
} }
FocusState createState() => new FocusState(); FocusState createState() => new FocusState();
...@@ -222,7 +242,7 @@ class FocusState extends State<Focus> { ...@@ -222,7 +242,7 @@ class FocusState extends State<Focus> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new _FocusScope( return new _FocusScope(
focusState: this, focusState: this,
scopeFocused: Focus._atScope(context, config), scopeFocused: Focus._atScope(context),
focusedScope: _focusedScope == _noFocusedScope ? null : _focusedScope, focusedScope: _focusedScope == _noFocusedScope ? null : _focusedScope,
focusedWidget: _focusedWidget, focusedWidget: _focusedWidget,
child: config.child child: config.child
......
...@@ -11,9 +11,9 @@ class TestFocusable extends StatelessComponent { ...@@ -11,9 +11,9 @@ class TestFocusable extends StatelessComponent {
final String no; final String no;
final String yes; final String yes;
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool focused = Focus.at(context, this); bool focused = Focus.at(context);
return new GestureDetector( return new GestureDetector(
onTap: () { Focus.moveTo(context, this); }, onTap: () { Focus.moveTo(key); },
child: new Text(focused ? yes : no) child: new Text(focused ? yes : no)
); );
} }
......
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