This introduces the notion of event disposition and allows event
targets (widgets and render objects) to consume events that should not
be processed further. This is needed by the Switch component in the
Drawer in the stocks example. The Switch is embedded in a DrawerItem.
The Switch handles the gesture tap event to toggle its state and should
handle pointer events to allow swiping and draw its own radial
reaction. The DrawerItem also handles gesture taps to allow toggling
the switch value when tapping anywhere on the drawer and to draw its
own ink splash. When tapping on the switch, both the switch's render
object and the DrawerItem's listener are in the event dispatch path.
The Switch needs to signal in some fashion that it consumed the event
so the DrawerItem does not also try to toggle the switch's state.
This changes Switch from being a subclass of the Toggleable widget to
being a standalone Component with a custom RenderObject. This is to
enable adding Switch-specific radial reaction animations in a
subsequent patch. The CustomPaint logic that Toggleable was using does
not provide a nice way for a class and its subclass to both participate
in deciding to repaint.
This introduces a GlobalKey registry so that you can tell when a key
has gone away (so you can unfocus dead dialogs).
Also I added an assert that you're not calling setState() during a
build. It turns out that doing so means you have a bug, because since
you're dirty already (you're building), you won't get rebuilt.
The focus code itself is gnarly. It uses a Component and an internal
Inherited TagNode to manage the focus state, and does crazy things
like updating its state during build to pretend retroactively that it
was built with some other state, once someone asks for focus the first
time (i.e. the first time it's examined, so you can't tell that it was
in a different state before). It does this so that it can autofocus
controls which otherwise wouldn't be focused.
This moves all the focus management into Navigator and showDialog(),
so the complexity is all buried deep and not visible to apps,
hopefully.
To do something like two tabs that each have an Input widget that
needs to be focused when you switch panes, you'd need to have two
Focus objects, one in each tab, and you need to set their autofocus to
true (maybe that should be the default?).
This removes the need to manually include the dialog builder in the main window's build() function.
It also removes the need to track if a dialog is visible.
Other changes:
- I made dialog.dart a bit more readable.
- I renamed transitionFinished to fullyOpaque since that's what actually matters.
- I made Routes track if they're opaque. Eventually this should probably be more configurable when creating the route.
Directions for Future Research:
- Use this for focus management somehow.
- The popup menu should use something like this.
- We should factor the following out into a showDialog() function that returns a future for the dialog's exit result:
navigator.push(new DialogRoute(builder: (navigator, route) { ... }));
- Maybe navigator.pop() should take a value to return to that Future.