Commit cf889934 authored by Adam Barth's avatar Adam Barth

RenderInkWell should use gestures

After this patch, InkWell is driven by gesture recognizers, which lets us
cleanly cancel splashes when the user actually scrolls.

I've also refactored all the clients of InkWell to use InkWell to detect
gestures instead of wrapping InkWell in a GestureDetector.

Fixes #1271
parent 445c512d
...@@ -42,8 +42,8 @@ void launch(String relativeUrl, String bundle) { ...@@ -42,8 +42,8 @@ void launch(String relativeUrl, String bundle) {
activity.startActivity(intent); activity.startActivity(intent);
} }
class SkyDemo { class FlutterDemo {
SkyDemo({ FlutterDemo({
name, name,
this.href, this.href,
this.bundle, this.bundle,
...@@ -60,8 +60,8 @@ class SkyDemo { ...@@ -60,8 +60,8 @@ class SkyDemo {
final BoxDecoration decoration; final BoxDecoration decoration;
} }
List<SkyDemo> demos = [ List<FlutterDemo> demos = [
new SkyDemo( new FlutterDemo(
name: 'Stocks', name: 'Stocks',
href: '../../stocks/lib/main.dart', href: '../../stocks/lib/main.dart',
bundle: 'stocks.skyx', bundle: 'stocks.skyx',
...@@ -74,7 +74,7 @@ List<SkyDemo> demos = [ ...@@ -74,7 +74,7 @@ List<SkyDemo> demos = [
) )
) )
), ),
new SkyDemo( new FlutterDemo(
name: 'Asteroids', name: 'Asteroids',
href: '../../game/lib/main.dart', href: '../../game/lib/main.dart',
bundle: 'game.skyx', bundle: 'game.skyx',
...@@ -87,7 +87,7 @@ List<SkyDemo> demos = [ ...@@ -87,7 +87,7 @@ List<SkyDemo> demos = [
) )
) )
), ),
new SkyDemo( new FlutterDemo(
name: 'Fitness', name: 'Fitness',
href: '../../fitness/lib/main.dart', href: '../../fitness/lib/main.dart',
bundle: 'fitness.skyx', bundle: 'fitness.skyx',
...@@ -97,7 +97,7 @@ List<SkyDemo> demos = [ ...@@ -97,7 +97,7 @@ List<SkyDemo> demos = [
backgroundColor: Colors.indigo[500] backgroundColor: Colors.indigo[500]
) )
), ),
new SkyDemo( new FlutterDemo(
name: 'Swipe Away', name: 'Swipe Away',
href: '../../widgets/card_collection.dart', href: '../../widgets/card_collection.dart',
bundle: 'cards.skyx', bundle: 'cards.skyx',
...@@ -107,7 +107,7 @@ List<SkyDemo> demos = [ ...@@ -107,7 +107,7 @@ List<SkyDemo> demos = [
backgroundColor: Colors.redAccent[200] backgroundColor: Colors.redAccent[200]
) )
), ),
new SkyDemo( new FlutterDemo(
name: 'Interactive Text', name: 'Interactive Text',
href: '../../rendering/interactive_flex.dart', href: '../../rendering/interactive_flex.dart',
bundle: 'interactive_flex.skyx', bundle: 'interactive_flex.skyx',
...@@ -120,7 +120,7 @@ List<SkyDemo> demos = [ ...@@ -120,7 +120,7 @@ List<SkyDemo> demos = [
// new SkyDemo( // new SkyDemo(
// 'Touch Demo', '../../rendering/touch_demo.dart', 'Simple example showing handling of touch events at a low level'), // 'Touch Demo', '../../rendering/touch_demo.dart', 'Simple example showing handling of touch events at a low level'),
new SkyDemo( new FlutterDemo(
name: 'Minedigger Game', name: 'Minedigger Game',
href: '../../mine_digger/lib/main.dart', href: '../../mine_digger/lib/main.dart',
bundle: 'mine_digger.skyx', bundle: 'mine_digger.skyx',
...@@ -138,44 +138,47 @@ List<SkyDemo> demos = [ ...@@ -138,44 +138,47 @@ List<SkyDemo> demos = [
const double kCardHeight = 120.0; const double kCardHeight = 120.0;
const EdgeDims kListPadding = const EdgeDims.all(4.0); const EdgeDims kListPadding = const EdgeDims.all(4.0);
class DemoList extends StatelessComponent { class DemoCard extends StatelessComponent {
Widget buildCardContents(SkyDemo demo) { DemoCard({ Key key, this.demo }) : super(key: key);
return new Container(
decoration: demo.decoration, final FlutterDemo demo;
child: new InkWell(
child: new Container( Widget build(BuildContext context) {
margin: const EdgeDims.only(top: 24.0, left: 24.0), return new Container(
child: new Column([ height: kCardHeight,
new Text(demo.name, style: demo.textTheme.title), child: new Card(
new Flexible( child: new Container(
child: new Text(demo.description, style: demo.textTheme.subhead) decoration: demo.decoration,
) child: new InkWell(
], onTap: () => launch(demo.href, demo.bundle),
alignItems: FlexAlignItems.start child: new Container(
margin: const EdgeDims.only(top: 24.0, left: 24.0),
child: new Column([
new Text(demo.name, style: demo.textTheme.title),
new Flexible(
child: new Text(demo.description, style: demo.textTheme.subhead)
)
],
alignItems: FlexAlignItems.start
)
) )
) )
) )
)
); );
} }
}
Widget buildDemo(BuildContext context, SkyDemo demo) { class DemoList extends StatelessComponent {
return new GestureDetector( Widget _buildDemoCard(BuildContext context, FlutterDemo demo) {
key: demo.key, return new DemoCard(key: demo.key, demo: demo);
onTap: () => launch(demo.href, demo.bundle),
child: new Container(
height: kCardHeight,
child: new Card(
child: buildCardContents(demo)
)
)
);
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new ScrollableList<SkyDemo>( return new ScrollableList<FlutterDemo>(
items: demos, items: demos,
itemExtent: kCardHeight, itemExtent: kCardHeight,
itemBuilder: buildDemo, itemBuilder: _buildDemoCard,
padding: kListPadding padding: kListPadding
); );
} }
...@@ -200,7 +203,7 @@ class DemoHome extends StatelessComponent { ...@@ -200,7 +203,7 @@ class DemoHome extends StatelessComponent {
void main() { void main() {
runApp(new App( runApp(new App(
title: 'Sky Demos', title: 'Flutter Demos',
theme: _theme, theme: _theme,
routes: { routes: {
'/': (NavigatorState navigator, Route route) => new DemoHome() '/': (NavigatorState navigator, Route route) => new DemoHome()
......
...@@ -33,15 +33,13 @@ class DialogMenuItem extends StatelessComponent { ...@@ -33,15 +33,13 @@ class DialogMenuItem extends StatelessComponent {
Function onPressed; Function onPressed;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new GestureDetector( return new Container(
onTap: onPressed, height: 48.0,
child: new Container( child: new InkWell(
height: 48.0, onTap: onPressed,
child: new InkWell( child: new Padding(
child: new Padding( padding: const EdgeDims.symmetric(horizontal: 16.0),
padding: const EdgeDims.symmetric(horizontal: 16.0), child: new Row(children)
child: new Row(children)
)
) )
) )
); );
......
...@@ -65,12 +65,13 @@ class MealFragmentState extends State<MealFragment> { ...@@ -65,12 +65,13 @@ class MealFragmentState extends State<MealFragment> {
icon: "navigation/close", icon: "navigation/close",
onPressed: config.navigator.pop), onPressed: config.navigator.pop),
center: new Text('New Meal'), center: new Text('New Meal'),
right: [new InkWell( right: [
child: new GestureDetector( // TODO(abarth): Should this be a FlatButton?
new InkWell(
onTap: _handleSave, onTap: _handleSave,
child: new Text('SAVE') child: new Text('SAVE')
) )
)] ]
); );
} }
......
...@@ -136,12 +136,13 @@ class MeasurementFragmentState extends State<MeasurementFragment> { ...@@ -136,12 +136,13 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
icon: "navigation/close", icon: "navigation/close",
onPressed: config.navigator.pop), onPressed: config.navigator.pop),
center: new Text('New Measurement'), center: new Text('New Measurement'),
right: [new InkWell( right: [
child: new GestureDetector( // TODO(abarth): Should this be a FlatButton?
new InkWell(
onTap: _handleSave, onTap: _handleSave,
child: new Text('SAVE') child: new Text('SAVE')
) )
)] ]
); );
} }
......
...@@ -55,52 +55,50 @@ class StockRow extends StatelessComponent { ...@@ -55,52 +55,50 @@ class StockRow extends StatelessComponent {
String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%"; String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%";
if (stock.percentChange > 0) changeInPrice = "+" + changeInPrice; if (stock.percentChange > 0) changeInPrice = "+" + changeInPrice;
return new GestureDetector( return new InkWell(
onTap: _getTapHandler(onPressed), onTap: _getTapHandler(onPressed),
onLongPress: _getLongPressHandler(onLongPressed), onLongPress: _getLongPressHandler(onLongPressed),
child: new InkWell( child: new Container(
child: new Container( padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0),
padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0), decoration: new BoxDecoration(
decoration: new BoxDecoration( border: new Border(
border: new Border( bottom: new BorderSide(color: Theme.of(context).dividerColor)
bottom: new BorderSide(color: Theme.of(context).dividerColor) )
) ),
child: new Row([
new Container(
key: arrowKey,
child: new StockArrow(percentChange: stock.percentChange),
margin: const EdgeDims.only(right: 5.0)
), ),
child: new Row([ new Flexible(
new Container( child: new Row([
key: arrowKey, new Flexible(
child: new StockArrow(percentChange: stock.percentChange), flex: 2,
margin: const EdgeDims.only(right: 5.0) child: new Text(
), stock.symbol,
new Flexible( key: symbolKey
child: new Row([ )
new Flexible( ),
flex: 2, new Flexible(
child: new Text( child: new Text(
stock.symbol, lastSale,
key: symbolKey style: const TextStyle(textAlign: TextAlign.right),
) key: priceKey
), )
new Flexible( ),
child: new Text( new Flexible(
lastSale, child: new Text(
style: const TextStyle(textAlign: TextAlign.right), changeInPrice,
key: priceKey style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
) )
), ),
new Flexible( ],
child: new Text( alignItems: FlexAlignItems.baseline,
changeInPrice, textBaseline: DefaultTextStyle.of(context).textBaseline
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
)
),
],
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(context).textBaseline
)
) )
]) )
) ])
) )
); );
} }
......
...@@ -14,11 +14,26 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -14,11 +14,26 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
: super(router: router); : super(router: router);
GestureTapCallback onTap; GestureTapCallback onTap;
GestureTapCallback onTapDown;
GestureTapCallback onTapCancel;
void handlePrimaryPointer(sky.PointerEvent event) { void handlePrimaryPointer(sky.PointerEvent event) {
if (event.type == 'pointerup') { if (event.type == 'pointerdown') {
if (onTapDown != null)
onTapDown();
} else if (event.type == 'pointerup') {
resolve(GestureDisposition.accepted); resolve(GestureDisposition.accepted);
onTap(); if (onTap != null)
onTap();
}
}
void rejectGesture(int pointer) {
super.rejectGesture(pointer);
if (pointer == primaryPointer) {
assert(state == GestureRecognizerState.defunct);
if (onTapCancel != null)
onTapCancel();
} }
} }
} }
...@@ -51,7 +51,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -51,7 +51,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
); );
} }
void detatch() { void detach() {
_tap.dispose(); _tap.dispose();
_tap = null; _tap = null;
super.detach(); super.detach();
......
...@@ -375,22 +375,20 @@ class YearPickerState extends ScrollableWidgetListState<YearPicker> { ...@@ -375,22 +375,20 @@ class YearPickerState extends ScrollableWidgetListState<YearPicker> {
for(int i = start; i < start + count; i++) { for(int i = start; i < start + count; i++) {
int year = config.firstDate.year + i; int year = config.firstDate.year + i;
String label = year.toString(); String label = year.toString();
Widget item = new GestureDetector( Widget item = new InkWell(
key: new Key(label), key: new Key(label),
onTap: () { onTap: () {
DateTime result = new DateTime(year, config.selectedDate.month, config.selectedDate.day); DateTime result = new DateTime(year, config.selectedDate.month, config.selectedDate.day);
config.onChanged(result); config.onChanged(result);
}, },
child: new InkWell( child: new Container(
child: new Container( height: config.itemExtent,
height: config.itemExtent, decoration: year == config.selectedDate.year ? new BoxDecoration(
decoration: year == config.selectedDate.year ? new BoxDecoration( backgroundColor: Theme.of(context).primarySwatch[100],
backgroundColor: Theme.of(context).primarySwatch[100], shape: Shape.circle
shape: Shape.circle ) : null,
) : null, child: new Center(
child: new Center( child: new Text(label, style: style)
child: new Text(label, style: style)
)
) )
) )
); );
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/focus.dart'; import 'package:sky/src/widgets/focus.dart';
...@@ -52,7 +53,7 @@ class Dialog extends StatelessComponent { ...@@ -52,7 +53,7 @@ class Dialog extends StatelessComponent {
final List<Widget> actions; final List<Widget> actions;
/// An (optional) callback that is called when the dialog is dismissed. /// An (optional) callback that is called when the dialog is dismissed.
final Function onDismiss; final GestureTapCallback onDismiss;
Color _getColor(BuildContext context) { Color _getColor(BuildContext context) {
switch (Theme.of(context).brightness) { switch (Theme.of(context).brightness) {
......
...@@ -10,7 +10,6 @@ import 'package:sky/painting.dart'; ...@@ -10,7 +10,6 @@ import 'package:sky/painting.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/button_state.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/gesture_detector.dart';
import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/icon.dart';
import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/theme.dart';
...@@ -76,14 +75,12 @@ class DrawerItemState extends ButtonState<DrawerItem> { ...@@ -76,14 +75,12 @@ class DrawerItemState extends ButtonState<DrawerItem> {
) )
); );
return new GestureDetector( return new Container(
onTap: config.onPressed, height: 48.0,
child: new Container( decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)),
height: 48.0, child: new InkWell(
decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)), onTap: config.onPressed,
child: new InkWell( child: new Row(flexChildren)
child: new Row(flexChildren)
)
) )
); );
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// 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.
import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
...@@ -13,7 +14,7 @@ class FlatButton extends MaterialButton { ...@@ -13,7 +14,7 @@ class FlatButton extends MaterialButton {
Key key, Key key,
Widget child, Widget child,
bool enabled: true, bool enabled: true,
Function onPressed GestureTapCallback onPressed
}) : super(key: key, }) : super(key: key,
child: child, child: child,
enabled: enabled, enabled: enabled,
......
...@@ -6,7 +6,6 @@ import 'package:sky/gestures.dart'; ...@@ -6,7 +6,6 @@ import 'package:sky/gestures.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/button_state.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/gesture_detector.dart';
import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/icon.dart';
import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/material.dart'; import 'package:sky/src/widgets/material.dart';
...@@ -46,17 +45,15 @@ class FloatingActionButtonState extends ButtonState<FloatingActionButton> { ...@@ -46,17 +45,15 @@ class FloatingActionButtonState extends ButtonState<FloatingActionButton> {
type: MaterialType.circle, type: MaterialType.circle,
level: highlight ? 3 : 2, level: highlight ? 3 : 2,
child: new ClipOval( child: new ClipOval(
child: new GestureDetector( child: new Container(
onTap: config.onPressed, width: _kSize,
child: new Container( height: _kSize,
width: _kSize, child: new InkWell(
height: _kSize, onTap: config.onPressed,
child: new InkWell( child: new Center(
child: new Center( child: new IconTheme(
child: new IconTheme( data: new IconThemeData(color: iconThemeColor),
data: new IconThemeData(color: iconThemeColor), child: config.child
child: config.child
)
) )
) )
) )
......
...@@ -14,6 +14,8 @@ class GestureDetector extends StatefulComponent { ...@@ -14,6 +14,8 @@ class GestureDetector extends StatefulComponent {
Key key, Key key,
this.child, this.child,
this.onTap, this.onTap,
this.onTapDown,
this.onTapCancel,
this.onShowPress, this.onShowPress,
this.onLongPress, this.onLongPress,
this.onVerticalDragStart, this.onVerticalDragStart,
...@@ -33,6 +35,9 @@ class GestureDetector extends StatefulComponent { ...@@ -33,6 +35,9 @@ class GestureDetector extends StatefulComponent {
final Widget child; final Widget child;
final GestureTapCallback onTap; final GestureTapCallback onTap;
final GestureTapCallback onTapDown;
final GestureTapCallback onTapCancel;
final GestureShowPressCallback onShowPress; final GestureShowPressCallback onShowPress;
final GestureLongPressCallback onLongPress; final GestureLongPressCallback onLongPress;
...@@ -97,11 +102,14 @@ class GestureDetectorState extends State<GestureDetector> { ...@@ -97,11 +102,14 @@ class GestureDetectorState extends State<GestureDetector> {
} }
void _syncTap() { void _syncTap() {
if (config.onTap == null) { if (config.onTap == null && config.onTapDown == null && config.onTapCancel == null) {
_tap = _ensureDisposed(_tap); _tap = _ensureDisposed(_tap);
} else { } else {
_tap ??= new TapGestureRecognizer(router: _router); _tap ??= new TapGestureRecognizer(router: _router);
_tap.onTap = config.onTap; _tap
..onTap = config.onTap
..onTapDown = config.onTapDown
..onTapCancel = config.onTapCancel;
} }
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/gestures.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/icon.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
...@@ -13,8 +14,8 @@ class IconButton extends StatelessComponent { ...@@ -13,8 +14,8 @@ class IconButton extends StatelessComponent {
const IconButton({ Key key, this.icon, this.onPressed, this.color }) : super(key: key); const IconButton({ Key key, this.icon, this.onPressed, this.color }) : super(key: key);
final String icon; final String icon;
final Function onPressed;
final Color color; final Color color;
final GestureTapCallback onPressed;
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget child = new Icon(type: icon, size: 24); Widget child = new Icon(type: icon, size: 24);
......
...@@ -2,16 +2,18 @@ ...@@ -2,16 +2,18 @@
// 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.
import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/gestures.dart';
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
const int _kSplashInitialOpacity = 0x30; const int _kSplashInitialOpacity = 0x30;
const double _kSplashCancelledVelocity = 0.7; const double _kSplashCanceledVelocity = 0.7;
const double _kSplashConfirmedVelocity = 0.7; const double _kSplashConfirmedVelocity = 0.7;
const double _kSplashInitialSize = 0.0; const double _kSplashInitialSize = 0.0;
const double _kSplashUnconfirmedVelocity = 0.2; const double _kSplashUnconfirmedVelocity = 0.2;
...@@ -25,7 +27,7 @@ double _getSplashTargetSize(Size bounds, Point position) { ...@@ -25,7 +27,7 @@ double _getSplashTargetSize(Size bounds, Point position) {
} }
class InkSplash { class InkSplash {
InkSplash(this.pointer, this.position, this.well) { InkSplash(this.position, this.well) {
_targetRadius = _getSplashTargetSize(well.size, position); _targetRadius = _getSplashTargetSize(well.size, position);
_radius = new AnimatedValue<double>( _radius = new AnimatedValue<double>(
_kSplashInitialSize, end: _targetRadius, curve: easeOut); _kSplashInitialSize, end: _targetRadius, curve: easeOut);
...@@ -33,11 +35,12 @@ class InkSplash { ...@@ -33,11 +35,12 @@ class InkSplash {
_performance = new ValueAnimation<double>( _performance = new ValueAnimation<double>(
variable: _radius, variable: _radius,
duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor()) duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor())
)..addListener(_handleRadiusChange) )..addListener(_handleRadiusChange);
..play();
// Wait kTapTimeout to avoid creating tiny splashes during scrolls.
_startTimer = new Timer(kTapTimeout, _play);
} }
final int pointer;
final Point position; final Point position;
final RenderInkWell well; final RenderInkWell well;
...@@ -45,20 +48,39 @@ class InkSplash { ...@@ -45,20 +48,39 @@ class InkSplash {
double _pinnedRadius; double _pinnedRadius;
AnimatedValue<double> _radius; AnimatedValue<double> _radius;
AnimationPerformance _performance; AnimationPerformance _performance;
Timer _startTimer;
bool _cancelStartTimer() {
if (_startTimer != null) {
_startTimer.cancel();
_startTimer = null;
return true;
}
return false;
}
void _play() {
_cancelStartTimer();
_performance.play();
}
void _updateVelocity(double velocity) { void _updateVelocity(double velocity) {
int duration = (_targetRadius / velocity).floor(); int duration = (_targetRadius / velocity).floor();
_performance _performance.duration = new Duration(milliseconds: duration);
..duration = new Duration(milliseconds: duration) _play();
..play();
} }
void confirm() { void confirm() {
if (_cancelStartTimer())
return;
_updateVelocity(_kSplashConfirmedVelocity); _updateVelocity(_kSplashConfirmedVelocity);
_pinnedRadius = null;
} }
void cancel() { void cancel() {
_updateVelocity(_kSplashCancelledVelocity); if (_cancelStartTimer())
return;
_updateVelocity(_kSplashCanceledVelocity);
_pinnedRadius = _radius.value; _pinnedRadius = _radius.value;
} }
...@@ -77,38 +99,95 @@ class InkSplash { ...@@ -77,38 +99,95 @@ class InkSplash {
} }
class RenderInkWell extends RenderProxyBox { class RenderInkWell extends RenderProxyBox {
RenderInkWell({ RenderBox child }) : super(child); RenderInkWell({
RenderBox child,
GestureTapCallback onTap,
GestureLongPressCallback onLongPress
}) : super(child) {
this.onTap = onTap;
this.onLongPress = onLongPress;
}
GestureTapCallback get onTap => _onTap;
GestureTapCallback _onTap;
void set onTap (GestureTapCallback value) {
_onTap = value;
_syncTapRecognizer();
}
GestureTapCallback get onLongPress => _onLongPress;
GestureTapCallback _onLongPress;
void set onLongPress (GestureTapCallback value) {
_onLongPress = value;
_syncLongPressRecognizer();
}
final List<InkSplash> _splashes = new List<InkSplash>(); final List<InkSplash> _splashes = new List<InkSplash>();
TapGestureRecognizer _tap;
LongPressGestureRecognizer _longPress;
void handleEvent(sky.Event event, BoxHitTestEntry entry) { void handleEvent(sky.Event event, BoxHitTestEntry entry) {
// TODO(abarth): We should trigger these effects based on gestures. if (event.type == 'pointerdown' && (_tap != null || _longPress != null)) {
// https://github.com/flutter/engine/issues/1271 _tap?.addPointer(event);
if (event is sky.PointerEvent) { _longPress?.addPointer(event);
switch (event.type) { _splashes.add(new InkSplash(entry.localPosition, this));
case 'pointerdown':
_startSplash(event.pointer, entry.localPosition);
break;
case 'pointerup':
_confirmSplash(event.pointer);
break;
}
} }
} }
void _startSplash(int pointer, Point position) { void attach() {
_splashes.add(new InkSplash(pointer, position, this)); super.attach();
markNeedsPaint(); _syncTapRecognizer();
_syncLongPressRecognizer();
} }
void _forEachSplash(int pointer, Function callback) { void detach() {
_splashes.where((splash) => splash.pointer == pointer) _disposeTapRecognizer();
.forEach(callback); _disposeLongPressRecognizer();
super.detach();
}
void _syncTapRecognizer() {
if (onTap == null) {
_disposeTapRecognizer();
} else {
_tap ??= new TapGestureRecognizer(router: FlutterBinding.instance.pointerRouter)
..onTap = _handleTap
..onTapCancel = _handleTapCancel;
}
} }
void _confirmSplash(int pointer) { void _disposeTapRecognizer() {
_forEachSplash(pointer, (splash) { splash.confirm(); }); _tap?.dispose();
markNeedsPaint(); _tap = null;
}
void _syncLongPressRecognizer() {
if (onLongPress == null) {
_disposeLongPressRecognizer();
} else {
_longPress ??= new LongPressGestureRecognizer(router: FlutterBinding.instance.pointerRouter)
..onLongPress = _handleLongPress;
}
}
void _disposeLongPressRecognizer() {
_longPress?.dispose();
_longPress = null;
}
void _handleTap() {
_splashes.last?.confirm();
onTap();
}
void _handleTapCancel() {
_splashes.last?.cancel();
}
void _handleLongPress() {
_splashes.last?.confirm();
onLongPress();
} }
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
...@@ -126,7 +205,20 @@ class RenderInkWell extends RenderProxyBox { ...@@ -126,7 +205,20 @@ class RenderInkWell extends RenderProxyBox {
} }
class InkWell extends OneChildRenderObjectWidget { class InkWell extends OneChildRenderObjectWidget {
InkWell({ Key key, Widget child }) : super(key: key, child: child); InkWell({
Key key,
Widget child,
this.onTap,
this.onLongPress
}) : super(key: key, child: child);
RenderInkWell createRenderObject() => new RenderInkWell(); final GestureTapCallback onTap;
final GestureLongPressCallback onLongPress;
RenderInkWell createRenderObject() => new RenderInkWell(onTap: onTap, onLongPress: onLongPress);
void updateRenderObject(RenderInkWell renderObject, InkWell oldWidget) {
renderObject.onTap = onTap;
renderObject.onLongPress = onLongPress;
}
} }
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
// 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.
import 'package:sky/gestures.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/button_state.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/gesture_detector.dart';
import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/material.dart'; import 'package:sky/src/widgets/material.dart';
...@@ -22,7 +22,7 @@ abstract class MaterialButton extends StatefulComponent { ...@@ -22,7 +22,7 @@ abstract class MaterialButton extends StatefulComponent {
final Widget child; final Widget child;
final bool enabled; final bool enabled;
final Function onPressed; final GestureTapCallback onPressed;
} }
abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState<T> { abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState<T> {
...@@ -37,17 +37,17 @@ abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState ...@@ -37,17 +37,17 @@ abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState
child: config.child // TODO(ianh): figure out a way to compell the child to have gray text when disabled... child: config.child // TODO(ianh): figure out a way to compell the child to have gray text when disabled...
) )
); );
return new GestureDetector( return new Container(
onTap: config.enabled ? config.onPressed : null, height: 36.0,
child: new Container( constraints: new BoxConstraints(minWidth: 88.0),
height: 36.0, margin: new EdgeDims.all(8.0),
constraints: new BoxConstraints(minWidth: 88.0), child: new Material(
margin: new EdgeDims.all(8.0), type: MaterialType.button,
child: new Material( level: level,
type: MaterialType.button, color: getColor(context),
child: config.enabled ? new InkWell(child: contents) : contents, child: new InkWell(
level: level, onTap: config.enabled ? config.onPressed : null,
color: getColor(context) child: contents
) )
) )
); );
......
...@@ -11,7 +11,7 @@ import 'package:sky/painting.dart'; ...@@ -11,7 +11,7 @@ import 'package:sky/painting.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/focus.dart'; import 'package:sky/src/widgets/focus.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/navigator.dart'; import 'package:sky/src/widgets/navigator.dart';
import 'package:sky/src/widgets/popup_menu_item.dart'; import 'package:sky/src/widgets/popup_menu_item.dart';
import 'package:sky/src/widgets/scrollable.dart'; import 'package:sky/src/widgets/scrollable.dart';
...@@ -94,7 +94,7 @@ class PopupMenuState extends State<PopupMenu> { ...@@ -94,7 +94,7 @@ class PopupMenuState extends State<PopupMenu> {
children.add(new FadeTransition( children.add(new FadeTransition(
performance: config.performance, performance: config.performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)), opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)),
child: new GestureDetector( child: new InkWell(
onTap: () { config.navigator.pop(config.items[i].value); }, onTap: () { config.navigator.pop(config.items[i].value); },
child: config.items[i] child: config.items[i]
)) ))
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/theme.dart';
const double _kMenuItemHeight = 48.0; const double _kMenuItemHeight = 48.0;
...@@ -21,15 +20,13 @@ class PopupMenuItem extends StatelessComponent { ...@@ -21,15 +20,13 @@ class PopupMenuItem extends StatelessComponent {
final dynamic value; final dynamic value;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new InkWell( return new Container(
child: new Container( height: _kMenuItemHeight,
height: _kMenuItemHeight, child: new DefaultTextStyle(
child: new DefaultTextStyle( style: Theme.of(context).text.subhead,
style: Theme.of(context).text.subhead, child: new Baseline(
child: new Baseline( baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom, child: child
child: child
)
) )
) )
); );
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// 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.
import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
...@@ -13,7 +14,7 @@ class RaisedButton extends MaterialButton { ...@@ -13,7 +14,7 @@ class RaisedButton extends MaterialButton {
Key key, Key key,
Widget child, Widget child,
bool enabled: true, bool enabled: true,
Function onPressed GestureTapCallback onPressed
}) : super(key: key, }) : super(key: key,
child: child, child: child,
enabled: enabled, enabled: enabled,
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
// 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.
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/painting.dart'; import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/painting.dart';
import 'package:sky/src/widgets/animated_component.dart'; import 'package:sky/src/widgets/animated_component.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
...@@ -28,7 +28,7 @@ class SnackBarAction extends StatelessComponent { ...@@ -28,7 +28,7 @@ class SnackBarAction extends StatelessComponent {
} }
final String label; final String label;
final Function onPressed; final GestureTapCallback onPressed;
Widget build(BuildContext) { Widget build(BuildContext) {
return new GestureDetector( return new GestureDetector(
......
...@@ -7,9 +7,10 @@ import 'dart:sky' as sky; ...@@ -7,9 +7,10 @@ import 'dart:sky' as sky;
import 'package:newton/newton.dart'; import 'package:newton/newton.dart';
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/gestures.dart';
import 'package:sky/material.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
import 'package:sky/material.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/gesture_detector.dart';
...@@ -307,6 +308,7 @@ class TabLabel { ...@@ -307,6 +308,7 @@ class TabLabel {
class Tab extends StatelessComponent { class Tab extends StatelessComponent {
Tab({ Tab({
Key key, Key key,
this.onSelected,
this.label, this.label,
this.color, this.color,
this.selected: false, this.selected: false,
...@@ -315,6 +317,7 @@ class Tab extends StatelessComponent { ...@@ -315,6 +317,7 @@ class Tab extends StatelessComponent {
assert(label.text != null || label.icon != null); assert(label.text != null || label.icon != null);
} }
final GestureTapCallback onSelected;
final TabLabel label; final TabLabel label;
final Color color; final Color color;
final bool selected; final bool selected;
...@@ -359,7 +362,10 @@ class Tab extends StatelessComponent { ...@@ -359,7 +362,10 @@ class Tab extends StatelessComponent {
padding: _kTabLabelPadding padding: _kTabLabelPadding
); );
return new InkWell(child: centeredLabel); return new InkWell(
onTap: onSelected,
child: centeredLabel
);
} }
} }
...@@ -458,7 +464,7 @@ class TabBarState extends ScrollableState<TabBar> { ...@@ -458,7 +464,7 @@ class TabBarState extends ScrollableState<TabBar> {
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset); .clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
} }
void _handleTap(int tabIndex) { void _handleTabSelected(int tabIndex) {
if (tabIndex != config.selectedIndex) { if (tabIndex != config.selectedIndex) {
if (_tabWidths != null) { if (_tabWidths != null) {
if (config.isScrollable) if (config.isScrollable)
...@@ -471,14 +477,12 @@ class TabBarState extends ScrollableState<TabBar> { ...@@ -471,14 +477,12 @@ class TabBarState extends ScrollableState<TabBar> {
} }
Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) { Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) {
return new GestureDetector( return new Tab(
onTap: () => _handleTap(tabIndex), onSelected: () => _handleTabSelected(tabIndex),
child: new Tab( label: label,
label: label, color: color,
color: color, selected: tabIndex == config.selectedIndex,
selected: tabIndex == config.selectedIndex, selectedColor: selectedColor
selectedColor: selectedColor
)
); );
} }
......
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