Commit ea3864b1 authored by Ian Hickson's avatar Ian Hickson

Fix Scrollable.of()

Make ancestorStateOfType() and ancestorRenderObjectOfType() support
subclassing.

Fixes https://github.com/flutter/flutter/issues/1087
parent 19fb068d
......@@ -8,7 +8,7 @@ import 'material.dart';
bool debugCheckHasMaterial(BuildContext context) {
assert(() {
if (context.widget is Material || context.ancestorWidgetOfType(Material) != null)
if (context.widget is Material || context.ancestorWidgetOfExactType(Material) != null)
return true;
Element element = context;
debugPrint('${context.widget} needs to be placed inside a Material widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}');
......
......@@ -20,7 +20,7 @@ class IconTheme extends InheritedWidget {
/// The data from the closest instance of this class that encloses the given context.
static IconThemeData of(BuildContext context) {
IconTheme result = context.inheritFromWidgetOfType(IconTheme);
IconTheme result = context.inheritFromWidgetOfExactType(IconTheme);
return result?.data;
}
......
......@@ -95,7 +95,7 @@ class Material extends StatefulComponent {
/// The ink controller from the closest instance of this class that encloses the given context.
static MaterialInkController of(BuildContext context) {
final RenderInkFeatures result = context.ancestorRenderObjectOfType(RenderInkFeatures);
final RenderInkFeatures result = context.ancestorRenderObjectOfType(const TypeMatcher<RenderInkFeatures>());
return result;
}
......
......@@ -27,7 +27,7 @@ class ButtonTheme extends InheritedWidget {
///
/// Defaults to [ButtonColor.normal] if none exists.
static ButtonColor of(BuildContext context) {
ButtonTheme result = context.inheritFromWidgetOfType(ButtonTheme);
ButtonTheme result = context.inheritFromWidgetOfExactType(ButtonTheme);
return result?.color ?? ButtonColor.normal;
}
......
......@@ -110,7 +110,7 @@ class Scaffold extends StatefulComponent {
final Widget drawer;
/// The state from the closest instance of this class that encloses the given context.
static ScaffoldState of(BuildContext context) => context.ancestorStateOfType(ScaffoldState);
static ScaffoldState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<ScaffoldState>());
ScaffoldState createState() => new ScaffoldState();
}
......
......@@ -26,7 +26,7 @@ class Theme extends InheritedWidget {
///
/// Defaults to the fallback theme data if none exists.
static ThemeData of(BuildContext context) {
Theme theme = context.inheritFromWidgetOfType(Theme);
Theme theme = context.inheritFromWidgetOfExactType(Theme);
return theme?.data ?? _kFallbackTheme;
}
......
......@@ -1411,7 +1411,7 @@ class DefaultTextStyle extends InheritedWidget {
/// The style from the closest instance of this class that encloses the given context.
static TextStyle of(BuildContext context) {
DefaultTextStyle result = context.inheritFromWidgetOfType(DefaultTextStyle);
DefaultTextStyle result = context.inheritFromWidgetOfExactType(DefaultTextStyle);
return result?.style;
}
......@@ -1737,7 +1737,7 @@ class DefaultAssetBundle extends InheritedWidget {
/// The bundle from the closest instance of this class that encloses the given context.
static AssetBundle of(BuildContext context) {
DefaultAssetBundle result = context.inheritFromWidgetOfType(DefaultAssetBundle);
DefaultAssetBundle result = context.inheritFromWidgetOfExactType(DefaultAssetBundle);
return result?.bundle;
}
......
......@@ -89,7 +89,7 @@ class Focus extends StatefulComponent {
assert(context.widget != null);
assert(context.widget.key != null);
assert(context.widget.key is GlobalKey);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope);
_FocusScope focusScope = context.inheritFromWidgetOfExactType(_FocusScope);
if (focusScope != null) {
if (autofocus)
focusScope._setFocusedWidgetIfUnset(context.widget.key);
......@@ -113,7 +113,7 @@ class Focus extends StatefulComponent {
assert(context != null);
assert(context.widget != null);
assert(context.widget is Focus);
_FocusScope focusScope = context.inheritFromWidgetOfType(_FocusScope);
_FocusScope focusScope = context.inheritFromWidgetOfExactType(_FocusScope);
if (focusScope != null) {
assert(context.widget.key != null);
if (autofocus)
......@@ -131,7 +131,7 @@ class Focus extends StatefulComponent {
/// 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);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfExactType(_FocusScope);
if (focusScope != null)
focusScope.focusState._setFocusedWidget(key);
}
......@@ -144,7 +144,7 @@ class Focus extends StatefulComponent {
static void moveScopeTo(GlobalKey key) {
assert(key.currentWidget is Focus);
assert(key.currentContext != null);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfType(_FocusScope);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfExactType(_FocusScope);
if (focusScope != null)
focusScope.focusState._setFocusedScope(key);
}
......
......@@ -198,6 +198,11 @@ class GlobalObjectKey extends GlobalKey {
String toString() => '[$runtimeType ${value.runtimeType}(${value.hashCode})]';
}
/// This class is a work-around for the "is" operator not accepting a variable value as its right operand
class TypeMatcher<T> {
const TypeMatcher();
bool check(dynamic object) => object is T;
}
// WIDGETS
......@@ -585,10 +590,10 @@ typedef void ElementVisitor(Element element);
abstract class BuildContext {
Widget get widget;
RenderObject findRenderObject();
InheritedWidget inheritFromWidgetOfType(Type targetType);
Widget ancestorWidgetOfType(Type targetType);
State ancestorStateOfType(Type targetType);
RenderObject ancestorRenderObjectOfType(Type targetType);
InheritedWidget inheritFromWidgetOfExactType(Type targetType);
Widget ancestorWidgetOfExactType(Type targetType);
State ancestorStateOfType(TypeMatcher matcher);
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher);
void visitAncestorElements(bool visitor(Element element));
void visitChildElements(void visitor(Element element));
}
......@@ -876,24 +881,24 @@ abstract class Element<T extends Widget> implements BuildContext {
RenderObject findRenderObject() => renderObject;
Set<Type> _dependencies;
InheritedWidget inheritFromWidgetOfType(Type targetType) {
InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
if (_dependencies == null)
_dependencies = new Set<Type>();
_dependencies.add(targetType);
return ancestorWidgetOfType(targetType);
return ancestorWidgetOfExactType(targetType);
}
Widget ancestorWidgetOfType(Type targetType) {
Widget ancestorWidgetOfExactType(Type targetType) {
Element ancestor = _parent;
while (ancestor != null && ancestor.widget.runtimeType != targetType)
ancestor = ancestor._parent;
return ancestor?.widget;
}
State ancestorStateOfType(Type targetType) {
State ancestorStateOfType(TypeMatcher matcher) {
Element ancestor = _parent;
while (ancestor != null) {
if (ancestor is StatefulComponentElement && ancestor.state.runtimeType == targetType)
if (ancestor is StatefulComponentElement && matcher.check(ancestor.state))
break;
ancestor = ancestor._parent;
}
......@@ -901,10 +906,10 @@ abstract class Element<T extends Widget> implements BuildContext {
return statefulAncestor?.state;
}
RenderObject ancestorRenderObjectOfType(Type targetType) {
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
Element ancestor = _parent;
while (ancestor != null) {
if (ancestor is RenderObjectElement && ancestor.renderObject.runtimeType == targetType)
if (ancestor is RenderObjectElement && matcher.check(ancestor.renderObject))
break;
ancestor = ancestor._parent;
}
......
......@@ -20,7 +20,7 @@ class LocaleQuery<T extends LocaleQueryData> extends InheritedWidget {
/// The data from the closest instance of this class that encloses the given context.
static LocaleQueryData of(BuildContext context) {
LocaleQuery query = context.inheritFromWidgetOfType(LocaleQuery);
LocaleQuery query = context.inheritFromWidgetOfExactType(LocaleQuery);
return query == null ? null : query.data;
}
......
......@@ -58,7 +58,7 @@ class MediaQuery extends InheritedWidget {
/// When that information changes, your widget will be scheduled to be rebuilt,
/// keeping your widget up-to-date.
static MediaQueryData of(BuildContext context) {
MediaQuery query = context.inheritFromWidgetOfType(MediaQuery);
MediaQuery query = context.inheritFromWidgetOfExactType(MediaQuery);
return query == null ? null : query.data;
}
......
......@@ -111,7 +111,7 @@ class MimicOverlayEntry {
}
}
RenderBox stack = context.ancestorRenderObjectOfType(RenderStack);
RenderBox stack = context.ancestorRenderObjectOfType(const TypeMatcher<RenderStack>());
// TODO(abarth): Handle the case where the transform here isn't just a translation.
Point localPosition = stack == null ? globalPosition: stack.globalToLocal(globalPosition);
return new Positioned(
......
......@@ -144,7 +144,7 @@ class Navigator extends StatefulComponent {
}
static bool canPop(BuildContext context) {
NavigatorState navigator = context.ancestorStateOfType(NavigatorState);
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
return navigator != null && navigator.canPop();
}
......@@ -156,7 +156,7 @@ class Navigator extends StatefulComponent {
}
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
NavigatorState navigator = context.ancestorStateOfType(NavigatorState);
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
navigator.openTransaction(callback);
}
......
......@@ -69,7 +69,7 @@ class Overlay extends StatefulComponent {
final List<OverlayEntry> initialEntries;
/// The state from the closest instance of this class that encloses the given context.
static OverlayState of(BuildContext context) => context.ancestorStateOfType(OverlayState);
static OverlayState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<OverlayState>());
OverlayState createState() => new OverlayState();
}
......
......@@ -79,7 +79,7 @@ class PageStorage extends StatelessComponent {
///
/// Returns null if none exists.
static PageStorageBucket of(BuildContext context) {
PageStorage widget = context.ancestorWidgetOfType(PageStorage);
PageStorage widget = context.ancestorWidgetOfExactType(PageStorage);
return widget?.bucket;
}
......
......@@ -388,7 +388,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
///
/// Returns null if the given context is not associated with a modal route.
static ModalRoute of(BuildContext context) {
_ModalScopeStatus widget = context.inheritFromWidgetOfType(_ModalScopeStatus);
_ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
return widget?.route;
}
......
......@@ -59,7 +59,7 @@ abstract class Scrollable extends StatefulComponent {
/// The state from the closest instance of this class that encloses the given context.
static ScrollableState of(BuildContext context) {
return context.ancestorStateOfType(ScrollableState);
return context.ancestorStateOfType(const TypeMatcher<ScrollableState>());
}
/// Scrolls the closest enclosing scrollable to make the given context visible.
......
......@@ -5,7 +5,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
import 'package:test/test.dart';
import 'package:test/test.dart' hide TypeMatcher;
class TestTransition extends TransitionComponent {
TestTransition({
......@@ -99,7 +99,7 @@ void main() {
)
);
NavigatorState navigator = insideKey.currentContext.ancestorStateOfType(NavigatorState);
NavigatorState navigator = insideKey.currentContext.ancestorStateOfType(const TypeMatcher<NavigatorState>());
expect(state(), equals('BC')); // transition ->1 is at 1.0
......
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