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 {
key: key,
initialScrollOffset: 0.0,
scrollDirection: ScrollDirection.horizontal
);
) {
assert(key != null);
}
final String initialValue;
final KeyboardType keyboardType;
......@@ -78,7 +80,7 @@ class InputState extends ScrollableState<Input> {
Widget buildContent(BuildContext context) {
assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context);
bool focused = Focus.at(context, config);
bool focused = Focus.at(context);
if (focused && !_keyboardHandle.attached) {
_keyboardHandle = keyboard.show(_editableValue.stub, config.keyboardType);
......@@ -118,7 +120,17 @@ class InputState extends ScrollableState<Input> {
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(
onSizeChanged: _handleContainerSizeChanged,
child: new Container(
......@@ -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 {
final bool autofocus;
final Widget child;
static bool at(BuildContext context, Widget widget, { bool autofocus: true }) {
assert(widget != null);
assert(widget.key is GlobalKey);
static GlobalKey debugOnlyFocusedKey;
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);
if (focusScope != null) {
if (autofocus)
focusScope._setFocusedWidgetIfUnset(widget.key);
focusScope._setFocusedWidgetIfUnset(context.widget.key);
return focusScope.scopeFocused &&
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;
}
static bool _atScope(BuildContext context, Widget widget, { bool autofocus: true }) {
assert(widget != null);
static bool _atScope(BuildContext context, { bool autofocus: true }) {
assert(context != null);
assert(context.widget != null);
assert(context.widget is Focus);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope);
if (focusScope != null) {
assert(context.widget.key != null);
if (autofocus)
focusScope._setFocusedScopeIfUnset(widget.key);
assert(widget.key != null);
focusScope._setFocusedScopeIfUnset(context.widget.key);
return focusScope.scopeFocused &&
focusScope.focusedScope == widget.key;
focusScope.focusedScope == context.widget.key;
}
return true;
}
// Don't call moveTo() and 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 moveTo(BuildContext context, Widget widget) {
assert(widget != null);
assert(widget.key is GlobalKey);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope);
/// Focuses a particular widget, identified by its GlobalKey.
/// The widget must be in the widget tree.
///
/// Don't call moveTo() 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 moveTo(GlobalKey key) {
assert(key.currentContext != null);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfType(_FocusScope);
if (focusScope != null)
focusScope.focusState._setFocusedWidget(widget.key);
}
static void moveScopeTo(BuildContext context, Focus component) {
assert(component != null);
assert(component.key != null);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope);
focusScope.focusState._setFocusedWidget(key);
}
/// Focuses a particular focus scope, identified by its GlobalKey. The widget
/// must be in the widget tree.
///
/// 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)
focusScope.focusState._setFocusedScope(component.key);
focusScope.focusState._setFocusedScope(key);
}
FocusState createState() => new FocusState();
......@@ -222,7 +242,7 @@ class FocusState extends State<Focus> {
Widget build(BuildContext context) {
return new _FocusScope(
focusState: this,
scopeFocused: Focus._atScope(context, config),
scopeFocused: Focus._atScope(context),
focusedScope: _focusedScope == _noFocusedScope ? null : _focusedScope,
focusedWidget: _focusedWidget,
child: config.child
......
......@@ -11,9 +11,9 @@ class TestFocusable extends StatelessComponent {
final String no;
final String yes;
Widget build(BuildContext context) {
bool focused = Focus.at(context, this);
bool focused = Focus.at(context);
return new GestureDetector(
onTap: () { Focus.moveTo(context, this); },
onTap: () { Focus.moveTo(key); },
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