diff --git a/examples/stocks2/lib/stock_app.dart b/examples/stocks2/lib/stock_app.dart index fcd95d663f851264dd5c77e44a8f249f7c6d3963..4e29072c8720f8b072f3bb4c130a0baaaf4aa4c4 100644 --- a/examples/stocks2/lib/stock_app.dart +++ b/examples/stocks2/lib/stock_app.dart @@ -2,30 +2,26 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:sky/framework/components2/tool_bar.dart'; -import 'package:sky/framework/components2/drawer.dart'; -import 'package:sky/framework/components2/drawer_header.dart'; -import 'package:sky/framework/components2/floating_action_button.dart'; -import 'package:sky/framework/components2/icon.dart'; -import 'package:sky/framework/components2/icon_button.dart'; -import 'package:sky/framework/components2/menu_divider.dart'; -import 'package:sky/framework/components2/menu_item.dart'; -import 'package:sky/framework/components2/input.dart'; -import 'package:sky/framework/components2/modal_overlay.dart'; -import 'package:sky/framework/components2/popup_menu.dart'; -import 'package:sky/framework/components2/radio.dart'; -import 'package:sky/framework/components2/scaffold.dart'; -import 'package:sky/framework/fn2.dart'; -import 'package:sky/framework/theme2/typography.dart' as typography; +import 'package:sky/framework/editing2/input.dart'; import 'package:sky/framework/theme2/colors.dart' as colors; +import 'package:sky/framework/widgets/drawer.dart'; +import 'package:sky/framework/widgets/drawer_header.dart'; +import 'package:sky/framework/widgets/floating_action_button.dart'; +import 'package:sky/framework/widgets/icon.dart'; +import 'package:sky/framework/widgets/icon_button.dart'; +import 'package:sky/framework/widgets/menu_divider.dart'; +import 'package:sky/framework/widgets/menu_item.dart'; +import 'package:sky/framework/widgets/modal_overlay.dart'; +import 'package:sky/framework/widgets/popup_menu.dart'; +import 'package:sky/framework/widgets/radio.dart'; +import 'package:sky/framework/widgets/scaffold.dart'; +import 'package:sky/framework/widgets/tool_bar.dart'; +import 'package:sky/framework/widgets/wrappers.dart'; + import 'stock_data.dart'; -import 'package:sky/framework/rendering/box.dart'; import 'stock_list.dart'; import 'stock_menu.dart'; -import 'dart:async'; -import 'dart:sky' as sky; - enum StockMode { optimistic, pessimistic } class StocksApp extends App { diff --git a/examples/stocks2/lib/stock_arrow.dart b/examples/stocks2/lib/stock_arrow.dart index f78b253dc6655346aed4c9b9e6884c37cf7471fc..da585fd1b4cdb2c5cad883e442c862ad29e56d1a 100644 --- a/examples/stocks2/lib/stock_arrow.dart +++ b/examples/stocks2/lib/stock_arrow.dart @@ -2,14 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:sky/framework/fn2.dart'; -import 'package:vector_math/vector_math.dart'; +import 'dart:math' as math; +import 'dart:sky' as sky; + import 'package:sky/framework/rendering/box.dart'; import 'package:sky/framework/rendering/object.dart'; import 'package:sky/framework/theme2/colors.dart' as colors; - -import 'dart:math' as math; -import 'dart:sky' as sky; +import 'package:sky/framework/widgets/wrappers.dart'; class StockArrow extends Component { diff --git a/examples/stocks2/lib/stock_data.dart b/examples/stocks2/lib/stock_data.dart index a77ca50a678c4c1b7196cf767e7d792d64aab008..075add267579eaf5a14c2ed254517dd96f3c3331 100644 --- a/examples/stocks2/lib/stock_data.dart +++ b/examples/stocks2/lib/stock_data.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'dart:math'; + import 'package:sky/framework/net/fetch.dart'; // Snapshot from http://www.nasdaq.com/screening/company-list.aspx diff --git a/examples/stocks2/lib/stock_list.dart b/examples/stocks2/lib/stock_list.dart index 7ff6fe8cb75f4d4809cee722c800992885b3fa7f..1c3ac12a026fa5293a6aaa4be0dd47c15c47ec86 100644 --- a/examples/stocks2/lib/stock_list.dart +++ b/examples/stocks2/lib/stock_list.dart @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:sky/framework/components2/fixed_height_scrollable.dart'; -import 'package:sky/framework/fn2.dart'; +import 'package:sky/framework/widgets/fixed_height_scrollable.dart'; +import 'package:sky/framework/widgets/wrappers.dart'; + import 'stock_data.dart'; import 'stock_row.dart'; diff --git a/examples/stocks2/lib/stock_menu.dart b/examples/stocks2/lib/stock_menu.dart index 1ac20ccc201c3bd339ef00125427d62b688a485e..6312b1e32ac90f63b00614b690400b09b80d303b 100644 --- a/examples/stocks2/lib/stock_menu.dart +++ b/examples/stocks2/lib/stock_menu.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:sky/framework/fn2.dart'; -import 'package:sky/framework/components2/popup_menu.dart'; -import 'package:sky/framework/components2/checkbox.dart'; +import 'package:sky/framework/widgets/checkbox.dart'; +import 'package:sky/framework/widgets/popup_menu.dart'; +import 'package:sky/framework/widgets/wrappers.dart'; import 'package:sky/framework/theme/view_configuration.dart'; class StockMenu extends Component { diff --git a/examples/stocks2/lib/stock_row.dart b/examples/stocks2/lib/stock_row.dart index 1ecb06846962e7171176f677dd5d42a7bc3d49a1..fc0555bce72131ab34788224bd9ef536902fa699 100644 --- a/examples/stocks2/lib/stock_row.dart +++ b/examples/stocks2/lib/stock_row.dart @@ -3,11 +3,11 @@ // found in the LICENSE file. import 'dart:sky' as sky; -import 'package:sky/framework/components2/ink_well.dart'; -import 'package:sky/framework/fn2.dart'; -import 'package:sky/framework/rendering/flex.dart'; + import 'package:sky/framework/rendering/box.dart'; -import 'package:sky/framework/theme/typography.dart' as typography; +import 'package:sky/framework/widgets/wrappers.dart'; +import 'package:sky/framework/widgets/ink_well.dart'; + import 'stock_arrow.dart'; import 'stock_data.dart'; diff --git a/examples/fn2/container.dart b/examples/widgets/container.dart similarity index 91% rename from examples/fn2/container.dart rename to examples/widgets/container.dart index f1eec6bbea4558b79b488c8fed36e20411c37a09..437784078ade73ed332a35772e5d65e328441091 100644 --- a/examples/fn2/container.dart +++ b/examples/widgets/container.dart @@ -3,8 +3,11 @@ // found in the LICENSE file. import 'dart:sky' as sky; -import 'package:sky/framework/fn2.dart'; + import 'package:sky/framework/rendering/box.dart'; +import 'package:sky/framework/widgets/ui_node.dart'; +import 'package:sky/framework/widgets/wrappers.dart'; + import '../lib/solid_color_box.dart'; class Rectangle extends RenderObjectWrapper { @@ -29,7 +32,7 @@ class ContainerApp extends App { child: new Block([ new Container( decoration: new BoxDecoration(backgroundColor: const sky.Color(0xFFFFFF00)), - height: 20.0, + height: 20.0 ), new Image(src: "https://www.dartlang.org/logos/dart-logo.png", size: new sky.Size(300.0, 300.0), diff --git a/examples/fn2/hello_fn2.dart b/examples/widgets/hello_widgets.dart similarity index 84% rename from examples/fn2/hello_fn2.dart rename to examples/widgets/hello_widgets.dart index 1aeda5d2ebc12d0dc5ab9d17586b64f0e99118af..127e26fa9e749708ea411180523440a8c3909fc5 100644 --- a/examples/fn2/hello_fn2.dart +++ b/examples/widgets/hello_widgets.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:sky/framework/fn2.dart'; +import 'package:sky/framework/widgets/wrappers.dart'; class HelloWorldApp extends App { UINode build() { @@ -12,4 +12,4 @@ class HelloWorldApp extends App { void main() { new HelloWorldApp(); -} \ No newline at end of file +} diff --git a/examples/widgets/main.sky b/examples/widgets/main.sky deleted file mode 100644 index 506b0ff837521d1945a72af0108950f539768c38..0000000000000000000000000000000000000000 --- a/examples/widgets/main.sky +++ /dev/null @@ -1,15 +0,0 @@ -#!mojo mojo:sky_viewer - - - - - diff --git a/examples/widgets/pubspec.yaml b/examples/widgets/pubspec.yaml deleted file mode 100644 index 1f45fe870a60af58ed8e39ccf141acfb1ffc0a94..0000000000000000000000000000000000000000 --- a/examples/widgets/pubspec.yaml +++ /dev/null @@ -1,3 +0,0 @@ -name: widgets -dependencies: - sky: any diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index aee3614c76d9b2eeec52e2c275442410d639cd4d..e975ac75fb56c13ab6d8aa0048d4125e4b5f3433 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -43,28 +43,6 @@ dart_pkg("sdk") { "lib/framework/components/scaffold.dart", "lib/framework/components/scrollable.dart", "lib/framework/components/tool_bar.dart", - "lib/framework/components2/animated_component.dart", - "lib/framework/components2/button.dart", - "lib/framework/components2/button_base.dart", - "lib/framework/components2/checkbox.dart", - "lib/framework/components2/drawer.dart", - "lib/framework/components2/drawer_header.dart", - "lib/framework/components2/fixed_height_scrollable.dart", - "lib/framework/components2/floating_action_button.dart", - "lib/framework/components2/icon.dart", - "lib/framework/components2/icon_button.dart", - "lib/framework/components2/ink_well.dart", - "lib/framework/components2/input.dart", - "lib/framework/components2/material.dart", - "lib/framework/components2/menu_divider.dart", - "lib/framework/components2/menu_item.dart", - "lib/framework/components2/modal_overlay.dart", - "lib/framework/components2/popup_menu.dart", - "lib/framework/components2/popup_menu_item.dart", - "lib/framework/components2/radio.dart", - "lib/framework/components2/scaffold.dart", - "lib/framework/components2/scrollable.dart", - "lib/framework/components2/tool_bar.dart", "lib/framework/debug/shake-to-reload.sky", "lib/framework/debug/tracing.dart", "lib/framework/editing/editable_string.dart", @@ -72,6 +50,7 @@ dart_pkg("sdk") { "lib/framework/editing/keyboard.dart", "lib/framework/editing2/editable_string.dart", "lib/framework/editing2/editable_text.dart", + "lib/framework/editing2/input.dart", "lib/framework/editing2/keyboard.dart", "lib/framework/elements/animation/controller.dart", "lib/framework/elements/animation/timer.dart", @@ -93,7 +72,6 @@ dart_pkg("sdk") { "lib/framework/elements/sky-toolbar.sky", "lib/framework/embedder.dart", "lib/framework/fn.dart", - "lib/framework/fn2.dart", "lib/framework/layout.dart", "lib/framework/net/fetch.dart", "lib/framework/net/image_cache.dart", @@ -117,6 +95,29 @@ dart_pkg("sdk") { "lib/framework/theme2/shadows.dart", "lib/framework/theme2/typography.dart", "lib/framework/theme2/view_configuration.dart", + "lib/framework/widgets/animated_component.dart", + "lib/framework/widgets/button.dart", + "lib/framework/widgets/button_base.dart", + "lib/framework/widgets/checkbox.dart", + "lib/framework/widgets/drawer.dart", + "lib/framework/widgets/drawer_header.dart", + "lib/framework/widgets/fixed_height_scrollable.dart", + "lib/framework/widgets/floating_action_button.dart", + "lib/framework/widgets/icon.dart", + "lib/framework/widgets/icon_button.dart", + "lib/framework/widgets/ink_well.dart", + "lib/framework/widgets/material.dart", + "lib/framework/widgets/menu_divider.dart", + "lib/framework/widgets/menu_item.dart", + "lib/framework/widgets/modal_overlay.dart", + "lib/framework/widgets/popup_menu.dart", + "lib/framework/widgets/popup_menu_item.dart", + "lib/framework/widgets/radio.dart", + "lib/framework/widgets/scaffold.dart", + "lib/framework/widgets/scrollable.dart", + "lib/framework/widgets/tool_bar.dart", + "lib/framework/widgets/ui_node.dart", + "lib/framework/widgets/wrappers.dart", "lib/sky_tool", "pubspec.yaml", ] diff --git a/sdk/lib/framework/README.md b/sdk/lib/framework/README.md index a15a276002266ab10f658e339af2029febf87648..b60665f4547c3a1a0fbbd988c0e7ebeb34c28121 100644 --- a/sdk/lib/framework/README.md +++ b/sdk/lib/framework/README.md @@ -1,219 +1,22 @@ -Sky Framework -============= +SKY SDK +======== -Effen is a functional-reactive framework for Sky which takes inspiration from -[React](http://facebook.github.io/react/). Effen is comprised of three main -parts: a virtual-dom and diffing engine, a component mechanism and a very early -set of widgets for use in creating applications. +Sky and Sky's SDK are designed as layered frameworks, where each layer +depends on the ones below it but could be replaced wholesale. -The central idea is that you build your UI out of components. Components -describe what their view should look like given their current configuration & -state. The diffing engine ensures that the DOM looks how the component describes -by applying minimal diffs to transition it from one state to the next. +The bottom-most layer is the Sky Platform, which is exposed to Dart +code as the ```dart:sky``` package. -If you just want to dive into code, see the [stocks example](../../../../examples/stocks). +Above this are the files in the [painting/](painting/) directory, +which provide APIs related to drawing graphics. -Hello World ------------ +Layout primitives are provided in the next layer, found in the +[rendering/](rendering/) directory. They use ```dart:sky``` and the +APIs exposed in painting/ to provide a retained-mode layout and +rendering model for applications or documents. -To build an application, create a subclass of App and instantiate it. +Widgets are provided by the files in the [widgets/](widgets/) +directory, using a reactive framework. -```HTML - -``` - -```dart -// In hello_world.dart -import 'package:sky/framework/fn.dart'; - -class HelloWorldApp extends App { - UINode build() { - return new Text('Hello, world!'); - } -} -``` - -An app is comprised of (and is, itself, a) components. A component's main job is -to implement `UINode build()`. The idea here is that the `build` method describes -the DOM of a component at any given point during its lifetime. In this case, our -`HelloWorldApp`'s `build` method just returns a `Text` node which displays the -obligatory line of text. - -Nodes ------ - -A component's `build` method must return a single `UINode` which *may* have -children (and so on, forming a *subtree*). Effen comes with a few built-in nodes -which mirror the built-in nodes/elements of sky: `Text`, `Anchor` (``, -`Image` (``) and `Container` (`
`). `build` can return a tree of -Nodes comprised of any of these nodes and plus any other imported object which -extends `Component`. - -How to structure you app ------------------------- - -If you're familiar with React, the basic idea is the same: Application data -flows *down* from components which have data to components & nodes which they -construct via construction parameters. Generally speaking, View-Model data (data -which is derived from *model* data, but exists only because the view needs it), -is computed during the course of `build` and is short-lived, being handed into -nodes & components as configuration data. - -What does "data flowing down the tree" mean? --------------------------------------------- - -Consider the case of a checkbox. (i.e. `widgets/checkbox.dart`). The `Checkbox` -constructor looks like this: - -```dart - ValueChanged onChanged; - bool checked; - - Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key); -``` - -What this means is that the `Checkbox` component *never* "owns" the state of -the checkbox. It's current state is handed into the `checked` parameter, and -when a click occurs, the checkbox invokes its `onChanged` callback with the -value it thinks it should be changed to -- but it never directly changes the -value itself. This is a bit odd at first look, but if you think about it: a -control isn't very useful unless it gets its value out to someone and if you -think about databinding, the same thing happens: databinding basically tells a -control to *treat some remote variable as its storage*. That's all that is -happening here. In this case, some owning component probably has a set of values -which describe a form. - -Stateful vs. Stateless components ---------------------------------- - -All components have access to two kinds of state: (1) configuration data -(constructor arguments) and (2) private data (data they mutate themselves). -While react components have explicit property bags for these two kinds of state -(`this.prop` and `this.state`), Effen maps these ideas to the public and private -fields of the component. Constructor arguments should (by convention) be -reflected as public fields of the component and state should only be set on -private (with a leading underbar `_`) fields. - -All (non-component) Effen nodes are stateless. Some components will be stateful. -This state will likely encapsulate transient states of the UI, such as scroll -position, animation state, uncommitted form values, etc... - -A component can become stateful in two ways: (1) by passing `super(stateful: -true)` to its call to the superclass's constructor, or by calling -`setState(Function fn)`. The former is a way to have a component start its life -stateful, and the latter results in the component becoming statefull *as well -as* scheduling the component to re-build at the end of the current animation -frame. - -What does it mean to be stateful? It means that the diffing mechanism retains -the specific *instance* of the component as long as the component which builds -it continues to require its presence. The component which constructed it may -have provided new configuration in form of different values for the constructor -parameters, but these values (public fields) will be copied (using reflection) -onto the retained instance whose privates fields are left unmodified. - -Rendering ---------- - -At the end of each animation frame, all components (including the root `App`) -which have `setState` on themselves will be rebuilt and the resulting changes -will be minimally applied to the DOM. Note that components of lower "order" -(those near the root of the tree) will build first because their building may -require rebuilding of higher order (those near the leaves), thus avoiding the -possibility that a component which is dirty build more than once during a single -cycle. - -Keys ----- - -In order to efficiently apply changes to the DOM and to ensure that stateful -components are correctly identified, Effen requires that `no two nodes (except -Text) or components of the same type may exist as children of another element -without being distinguished by unique keys`. [`Text` is excused from this rule]. -In many cases, nodes don't require a key because there is only one type amongst -its siblings -- but if there is more one, you must assign each a key. This is -why most nodes will take `({ Object key })` as an optional constructor -parameter. In development mode (i.e. when sky is built `Debug`) Effen will throw -an error if you forget to do this. - -Event Handling --------------- - -Events logically fire through the Effen node tree. If want to handle an event as -it bubbles from the target to the root, create an `EventListenerNode`. `EventListenerNode` -has named (typed) parameters for a small set of events that we've hit so far, as -well as a 'custom' argument which is a `Map`. If -you'd like to add a type argument for an event, just post a patch. - -```dart -class MyComp extends Component { - MyComp({ - Object key - }) : super(key: key); - - void _handleTap(sky.GestureEvent e) { - // do stuff - } - - void _customEventCallback(sky.Event e) { - // do other stuff - } - - UINode build() { - new EventListenerNode( - new Container( - children: // ... - ), - onGestureTap: _handleTap, - custom: { - 'myCustomEvent': _customEventCallback - } - ); - } - - _handleScroll(sky.Event e) { - setState(() { - // update the scroll position - }); - } -} -``` - -Styling -------- - -Styling is the part of Effen which is least designed and is likely to change. -There are three ways to specify styles: - - * `Style` objects which are interned and can be applied to WrapperNodes via the - ``style` constructor parameter. Use `Style` objects for styles which are - `*not* animated. - - * An `inlineStyle` string which can be applied to Elements via the - `inlineStyle` constructor parameter. Use `inlineStyle` for styles which - *are* animated. - -If you need to apply a Style to a Component or UINode which you didn't construct -(i.e. one that was handed into your constructor), you can wrap it in a -`StyleNode` which also takes a `Style` constructor in it's `style` constructor -parameter. - -Animation ---------- - -Animation is still an area of exploration. Have a look at -[AnimatedComponent](components/animated_component.dart) and -[Drawer](components/drawer.dart) for an example of this this currently works. - -Performance ------------ - -It is a design goal that it should be *possible* to arrange that all "build" -cycles which happen during animations can complete in less than one milliesecond -on a Nexus 5. +Text input widgets are layered on this mechanism and can be found in +the [editing2/](editing2/) directory. diff --git a/sdk/lib/framework/editing2/editable_text.dart b/sdk/lib/framework/editing2/editable_text.dart index 430933bf997d0d2f117bf3c76fa46ac95797911c..59569b04eb412e924b21c5c0692c90c5e659c011 100644 --- a/sdk/lib/framework/editing2/editable_text.dart +++ b/sdk/lib/framework/editing2/editable_text.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; -import '../theme/colors.dart'; import 'dart:async'; + +import '../widgets/wrappers.dart'; import 'editable_string.dart'; class EditableText extends Component { diff --git a/sdk/lib/framework/components2/input.dart b/sdk/lib/framework/editing2/input.dart similarity index 91% rename from sdk/lib/framework/components2/input.dart rename to sdk/lib/framework/editing2/input.dart index 0f7d4816398cc64dd69bf11c6bd798d3d7971c53..4a97f575b1623e36fe8969245a47bfb6aa66c436 100644 --- a/sdk/lib/framework/components2/input.dart +++ b/sdk/lib/framework/editing2/input.dart @@ -2,15 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../editing2/editable_string.dart'; -import '../editing2/editable_text.dart'; -import '../editing2/keyboard.dart'; -import '../fn2.dart'; -import '../theme2/colors.dart'; -import '../theme2/typography.dart' as typography; -import '../rendering/flex.dart'; import 'dart:sky' as sky; +import '../rendering/flex.dart'; +import '../widgets/wrappers.dart'; +import 'editable_string.dart'; +import 'editable_text.dart'; +import 'keyboard.dart'; + typedef void ValueChanged(value); class Input extends Component { diff --git a/sdk/lib/framework/fn.md b/sdk/lib/framework/fn.md new file mode 100644 index 0000000000000000000000000000000000000000..40d964a8799d43bfe193e04286df49e1d9953050 --- /dev/null +++ b/sdk/lib/framework/fn.md @@ -0,0 +1,222 @@ +Sky Framework +============= + +(This file applies to fn.dart, which we are in the process of porting +to a new architecture.) + +Effen is a functional-reactive framework for Sky which takes inspiration from +[React](http://facebook.github.io/react/). Effen is comprised of three main +parts: a virtual-dom and diffing engine, a component mechanism and a very early +set of widgets for use in creating applications. + +The central idea is that you build your UI out of components. Components +describe what their view should look like given their current configuration & +state. The diffing engine ensures that the DOM looks how the component describes +by applying minimal diffs to transition it from one state to the next. + +If you just want to dive into code, see the [stocks example](../../../../examples/stocks). + +Hello World +----------- + +To build an application, create a subclass of App and instantiate it. + +```HTML + +``` + +```dart +// In hello_world.dart +import 'package:sky/framework/fn.dart'; + +class HelloWorldApp extends App { + UINode build() { + return new Text('Hello, world!'); + } +} +``` + +An app is comprised of (and is, itself, a) components. A component's main job is +to implement `UINode build()`. The idea here is that the `build` method describes +the DOM of a component at any given point during its lifetime. In this case, our +`HelloWorldApp`'s `build` method just returns a `Text` node which displays the +obligatory line of text. + +Nodes +----- + +A component's `build` method must return a single `UINode` which *may* have +children (and so on, forming a *subtree*). Effen comes with a few built-in nodes +which mirror the built-in nodes/elements of sky: `Text`, `Anchor` (``, +`Image` (``) and `Container` (`
`). `build` can return a tree of +Nodes comprised of any of these nodes and plus any other imported object which +extends `Component`. + +How to structure you app +------------------------ + +If you're familiar with React, the basic idea is the same: Application data +flows *down* from components which have data to components & nodes which they +construct via construction parameters. Generally speaking, View-Model data (data +which is derived from *model* data, but exists only because the view needs it), +is computed during the course of `build` and is short-lived, being handed into +nodes & components as configuration data. + +What does "data flowing down the tree" mean? +-------------------------------------------- + +Consider the case of a checkbox. (i.e. `widgets/checkbox.dart`). The `Checkbox` +constructor looks like this: + +```dart + ValueChanged onChanged; + bool checked; + + Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key); +``` + +What this means is that the `Checkbox` component *never* "owns" the state of +the checkbox. It's current state is handed into the `checked` parameter, and +when a click occurs, the checkbox invokes its `onChanged` callback with the +value it thinks it should be changed to -- but it never directly changes the +value itself. This is a bit odd at first look, but if you think about it: a +control isn't very useful unless it gets its value out to someone and if you +think about databinding, the same thing happens: databinding basically tells a +control to *treat some remote variable as its storage*. That's all that is +happening here. In this case, some owning component probably has a set of values +which describe a form. + +Stateful vs. Stateless components +--------------------------------- + +All components have access to two kinds of state: (1) configuration data +(constructor arguments) and (2) private data (data they mutate themselves). +While react components have explicit property bags for these two kinds of state +(`this.prop` and `this.state`), Effen maps these ideas to the public and private +fields of the component. Constructor arguments should (by convention) be +reflected as public fields of the component and state should only be set on +private (with a leading underbar `_`) fields. + +All (non-component) Effen nodes are stateless. Some components will be stateful. +This state will likely encapsulate transient states of the UI, such as scroll +position, animation state, uncommitted form values, etc... + +A component can become stateful in two ways: (1) by passing `super(stateful: +true)` to its call to the superclass's constructor, or by calling +`setState(Function fn)`. The former is a way to have a component start its life +stateful, and the latter results in the component becoming statefull *as well +as* scheduling the component to re-build at the end of the current animation +frame. + +What does it mean to be stateful? It means that the diffing mechanism retains +the specific *instance* of the component as long as the component which builds +it continues to require its presence. The component which constructed it may +have provided new configuration in form of different values for the constructor +parameters, but these values (public fields) will be copied (using reflection) +onto the retained instance whose privates fields are left unmodified. + +Rendering +--------- + +At the end of each animation frame, all components (including the root `App`) +which have `setState` on themselves will be rebuilt and the resulting changes +will be minimally applied to the DOM. Note that components of lower "order" +(those near the root of the tree) will build first because their building may +require rebuilding of higher order (those near the leaves), thus avoiding the +possibility that a component which is dirty build more than once during a single +cycle. + +Keys +---- + +In order to efficiently apply changes to the DOM and to ensure that stateful +components are correctly identified, Effen requires that `no two nodes (except +Text) or components of the same type may exist as children of another element +without being distinguished by unique keys`. [`Text` is excused from this rule]. +In many cases, nodes don't require a key because there is only one type amongst +its siblings -- but if there is more one, you must assign each a key. This is +why most nodes will take `({ Object key })` as an optional constructor +parameter. In development mode (i.e. when sky is built `Debug`) Effen will throw +an error if you forget to do this. + +Event Handling +-------------- + +Events logically fire through the Effen node tree. If want to handle an event as +it bubbles from the target to the root, create an `EventListenerNode`. `EventListenerNode` +has named (typed) parameters for a small set of events that we've hit so far, as +well as a 'custom' argument which is a `Map`. If +you'd like to add a type argument for an event, just post a patch. + +```dart +class MyComp extends Component { + MyComp({ + Object key + }) : super(key: key); + + void _handleTap(sky.GestureEvent e) { + // do stuff + } + + void _customEventCallback(sky.Event e) { + // do other stuff + } + + UINode build() { + new EventListenerNode( + new Container( + children: // ... + ), + onGestureTap: _handleTap, + custom: { + 'myCustomEvent': _customEventCallback + } + ); + } + + _handleScroll(sky.Event e) { + setState(() { + // update the scroll position + }); + } +} +``` + +Styling +------- + +Styling is the part of Effen which is least designed and is likely to change. +There are three ways to specify styles: + + * `Style` objects which are interned and can be applied to WrapperNodes via the + ``style` constructor parameter. Use `Style` objects for styles which are + `*not* animated. + + * An `inlineStyle` string which can be applied to Elements via the + `inlineStyle` constructor parameter. Use `inlineStyle` for styles which + *are* animated. + +If you need to apply a Style to a Component or UINode which you didn't construct +(i.e. one that was handed into your constructor), you can wrap it in a +`StyleNode` which also takes a `Style` constructor in it's `style` constructor +parameter. + +Animation +--------- + +Animation is still an area of exploration. Have a look at +[AnimatedComponent](components/animated_component.dart) and +[Drawer](components/drawer.dart) for an example of this this currently works. + +Performance +----------- + +It is a design goal that it should be *possible* to arrange that all "build" +cycles which happen during animations can complete in less than one milliesecond +on a Nexus 5. diff --git a/sdk/lib/framework/rendering/box.dart b/sdk/lib/framework/rendering/box.dart index 6a8968676da24626bc4a01f09d150adaac4a6bf5..f6bf0da0977043b5f84b285908bdd0ee37d90250 100644 --- a/sdk/lib/framework/rendering/box.dart +++ b/sdk/lib/framework/rendering/box.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; import 'dart:sky' as sky; -import 'dart:typed_data'; import 'object.dart'; import '../painting/box_painter.dart'; import 'package:vector_math/vector_math.dart'; diff --git a/sdk/lib/framework/rendering/object.dart b/sdk/lib/framework/rendering/object.dart index ace0aed7cdc13d0b8030f5a7c576a6fba14e3daf..c3dc2f094936ebba5789c670adfa2314868f6731 100644 --- a/sdk/lib/framework/rendering/object.dart +++ b/sdk/lib/framework/rendering/object.dart @@ -4,7 +4,6 @@ import '../node.dart'; import '../scheduler.dart' as scheduler; -import 'dart:math' as math; import 'dart:sky' as sky; import 'dart:sky' show Point, Size, Rect, Color, Paint, Path; export 'dart:sky' show Point, Size, Rect, Color, Paint, Path; diff --git a/sdk/lib/framework/scheduler.dart b/sdk/lib/framework/scheduler.dart index 9c2f181c5c754d4a24ec70d7ae9949bc60b9be5d..96dbfd09f0219461dc9eecf2eab35752b31c2f5f 100644 --- a/sdk/lib/framework/scheduler.dart +++ b/sdk/lib/framework/scheduler.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; import 'dart:sky' as sky; typedef void Callback(double timeStamp); diff --git a/sdk/lib/framework/components2/animated_component.dart b/sdk/lib/framework/widgets/animated_component.dart similarity index 98% rename from sdk/lib/framework/components2/animated_component.dart rename to sdk/lib/framework/widgets/animated_component.dart index 9a02fd531dddd9e5b4ea10c6fea0e09382df625e..b4727da031644dcfe99baa718a9313613fa89329 100644 --- a/sdk/lib/framework/components2/animated_component.dart +++ b/sdk/lib/framework/widgets/animated_component.dart @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../animation/animated_value.dart'; -import '../fn2.dart'; import 'dart:async'; +import '../animation/animated_value.dart'; +import 'wrappers.dart'; + typedef void SetterFunction(double value); class _AnimationEntry { diff --git a/sdk/lib/framework/components2/button.dart b/sdk/lib/framework/widgets/button.dart similarity index 96% rename from sdk/lib/framework/components2/button.dart rename to sdk/lib/framework/widgets/button.dart index 59add1743f0c86a658603b904e9e0148d59b2b60..b41eadbd93e26dcabdc4a0ec40c1e83e794ec12c 100644 --- a/sdk/lib/framework/components2/button.dart +++ b/sdk/lib/framework/widgets/button.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; import 'ink_well.dart'; import 'material.dart'; +import 'wrappers.dart'; class Button extends Component { diff --git a/sdk/lib/framework/components2/button_base.dart b/sdk/lib/framework/widgets/button_base.dart similarity index 97% rename from sdk/lib/framework/components2/button_base.dart rename to sdk/lib/framework/widgets/button_base.dart index c8a9c37e62909d516bb4652330169cd13a9ff3e3..5ae273c1a11063d9783125ebeb735596912553c5 100644 --- a/sdk/lib/framework/components2/button_base.dart +++ b/sdk/lib/framework/widgets/button_base.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; +import 'wrappers.dart'; abstract class ButtonBase extends Component { diff --git a/sdk/lib/framework/components2/checkbox.dart b/sdk/lib/framework/widgets/checkbox.dart similarity index 97% rename from sdk/lib/framework/components2/checkbox.dart rename to sdk/lib/framework/widgets/checkbox.dart index 36fd278dbdbe9bfe133dc2d80b550616bc1a35c7..1d2630c48487e9f77f3d34126202361c8a9c698e 100644 --- a/sdk/lib/framework/components2/checkbox.dart +++ b/sdk/lib/framework/widgets/checkbox.dart @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:sky' as sky; + import 'package:sky/framework/theme2/colors.dart' as colors; -import 'dart:sky' as sky; -import '../fn2.dart'; import '../rendering/box.dart'; -import '../rendering/object.dart'; import 'button_base.dart'; +import 'wrappers.dart'; typedef void ValueChanged(value); diff --git a/sdk/lib/framework/components2/drawer.dart b/sdk/lib/framework/widgets/drawer.dart similarity index 99% rename from sdk/lib/framework/components2/drawer.dart rename to sdk/lib/framework/widgets/drawer.dart index 8ae1cab97acb4aeba1bd73fcad54bb3bd8900e47..71596f9aeba83246ea59862b01765a1d5e646098 100644 --- a/sdk/lib/framework/components2/drawer.dart +++ b/sdk/lib/framework/widgets/drawer.dart @@ -2,16 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; +import 'dart:sky' as sky; + +import 'package:vector_math/vector_math.dart'; + import '../animation/animated_value.dart'; import '../animation/curves.dart'; -import '../fn2.dart'; import '../theme2/colors.dart'; import '../theme2/shadows.dart'; import 'animated_component.dart'; -import 'dart:math' as math; -import 'dart:sky' as sky; -import 'material.dart'; -import 'package:vector_math/vector_math.dart'; +import 'wrappers.dart'; // TODO(eseidel): Draw width should vary based on device size: // http://www.google.com/design/spec/layout/structure.html#structure-side-nav diff --git a/sdk/lib/framework/components2/drawer_header.dart b/sdk/lib/framework/widgets/drawer_header.dart similarity index 98% rename from sdk/lib/framework/components2/drawer_header.dart rename to sdk/lib/framework/widgets/drawer_header.dart index 652596d559c7899e91ba8985d87370d4ced242df..88fb881705c0bbcbf725f9a8de2edb2491f68614 100644 --- a/sdk/lib/framework/components2/drawer_header.dart +++ b/sdk/lib/framework/widgets/drawer_header.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; import '../theme2/colors.dart'; import '../theme2/view_configuration.dart'; +import 'wrappers.dart'; class DrawerHeader extends Component { diff --git a/sdk/lib/framework/components2/fixed_height_scrollable.dart b/sdk/lib/framework/widgets/fixed_height_scrollable.dart similarity index 98% rename from sdk/lib/framework/components2/fixed_height_scrollable.dart rename to sdk/lib/framework/widgets/fixed_height_scrollable.dart index 95392802c7dfdb8d0624ffa08a62e3d90c0cc1d3..70df8523d8d28039a319935c18c28f2f3ec2909c 100644 --- a/sdk/lib/framework/components2/fixed_height_scrollable.dart +++ b/sdk/lib/framework/widgets/fixed_height_scrollable.dart @@ -2,12 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../animation/scroll_behavior.dart'; -import '../fn2.dart'; -import 'dart:async'; import 'dart:math' as math; + import 'package:vector_math/vector_math.dart'; + +import '../animation/scroll_behavior.dart'; import 'scrollable.dart'; +import 'wrappers.dart'; abstract class FixedHeightScrollable extends Scrollable { diff --git a/sdk/lib/framework/components2/floating_action_button.dart b/sdk/lib/framework/widgets/floating_action_button.dart similarity index 96% rename from sdk/lib/framework/components2/floating_action_button.dart rename to sdk/lib/framework/widgets/floating_action_button.dart index b5555f1cf451c152528ba5307c9b5bee9ef453c0..3aa9124665683f3ff021c194f5345bc45f4a81d4 100644 --- a/sdk/lib/framework/components2/floating_action_button.dart +++ b/sdk/lib/framework/widgets/floating_action_button.dart @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; -import '../rendering/box.dart'; +import 'dart:sky' as sky; + import '../painting/shadows.dart'; import '../theme2/colors.dart'; -import 'dart:sky' as sky; import 'ink_well.dart'; import 'material.dart'; +import 'wrappers.dart'; // TODO(eseidel): This needs to change based on device size? // http://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing diff --git a/sdk/lib/framework/components2/icon.dart b/sdk/lib/framework/widgets/icon.dart similarity index 97% rename from sdk/lib/framework/components2/icon.dart rename to sdk/lib/framework/widgets/icon.dart index 7ab1e6acc0c70a4b8bf7c194c7eff4c7ebf2aad8..abd7b6cefd7a4ae5b51864781b397d8006c6403c 100644 --- a/sdk/lib/framework/components2/icon.dart +++ b/sdk/lib/framework/widgets/icon.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; +import 'wrappers.dart'; // TODO(eseidel): This should use package:. const String kAssetBase = '/packages/sky/assets/material-design-icons'; diff --git a/sdk/lib/framework/components2/icon_button.dart b/sdk/lib/framework/widgets/icon_button.dart similarity index 92% rename from sdk/lib/framework/components2/icon_button.dart rename to sdk/lib/framework/widgets/icon_button.dart index d404be632cea8b016a2344aaaebc74ddca73036b..56af188b6714df7f85024501073b7adc67935a0a 100644 --- a/sdk/lib/framework/components2/icon_button.dart +++ b/sdk/lib/framework/widgets/icon_button.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; import '../rendering/box.dart'; import 'icon.dart'; +import 'ui_node.dart'; +import 'wrappers.dart'; class IconButton extends Component { diff --git a/sdk/lib/framework/components2/ink_well.dart b/sdk/lib/framework/widgets/ink_well.dart similarity index 98% rename from sdk/lib/framework/components2/ink_well.dart rename to sdk/lib/framework/widgets/ink_well.dart index a67a63533c3ac60a644040496eb0bd2bd37950f2..48188f52a93897b1827a7a7cb3224e805dfb5489 100644 --- a/sdk/lib/framework/components2/ink_well.dart +++ b/sdk/lib/framework/widgets/ink_well.dart @@ -2,16 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; +import 'dart:sky' as sky; + import '../animation/animated_value.dart'; import '../animation/curves.dart'; -import '../fn2.dart'; import '../rendering/box.dart'; import '../rendering/flex.dart'; import '../rendering/object.dart'; -import 'dart:async'; -import 'dart:collection'; -import 'dart:math' as math; -import 'dart:sky' as sky; +import 'ui_node.dart'; +import 'wrappers.dart'; const int _kSplashInitialOpacity = 0x80; const double _kSplashInitialDelay = 0.0; // we could delay initially in case the user scrolls diff --git a/sdk/lib/framework/components2/material.dart b/sdk/lib/framework/widgets/material.dart similarity index 93% rename from sdk/lib/framework/components2/material.dart rename to sdk/lib/framework/widgets/material.dart index 1ee295a5a13b1366a37dd3cd2a9d4119c695b71a..89f3a56a198dcc2ed058a26b6bd5bd8a7bb7d021 100644 --- a/sdk/lib/framework/components2/material.dart +++ b/sdk/lib/framework/widgets/material.dart @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; -import '../theme/shadows.dart'; +import 'wrappers.dart'; class Material extends Component { diff --git a/sdk/lib/framework/components2/menu_divider.dart b/sdk/lib/framework/widgets/menu_divider.dart similarity index 95% rename from sdk/lib/framework/components2/menu_divider.dart rename to sdk/lib/framework/widgets/menu_divider.dart index 253e213a23b77491c1bea8f2fc7158a34e16e5c4..89faa11dd308cbcdd03a552ee4e181059958f4bf 100644 --- a/sdk/lib/framework/components2/menu_divider.dart +++ b/sdk/lib/framework/widgets/menu_divider.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; +import 'wrappers.dart'; class MenuDivider extends Component { MenuDivider({ Object key }) : super(key: key); diff --git a/sdk/lib/framework/components2/menu_item.dart b/sdk/lib/framework/widgets/menu_item.dart similarity index 97% rename from sdk/lib/framework/components2/menu_item.dart rename to sdk/lib/framework/widgets/menu_item.dart index 4f4bd79a73893249352b8b3a3237040ad45ae0df..52d1c5310e0a6061001faa78a8221e5b272cf9e3 100644 --- a/sdk/lib/framework/components2/menu_item.dart +++ b/sdk/lib/framework/widgets/menu_item.dart @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; import 'button_base.dart'; import 'icon.dart'; import 'ink_well.dart'; +import 'ui_node.dart'; +import 'wrappers.dart'; const BoxDecoration _kHighlightDecoration = const BoxDecoration( backgroundColor: const Color.fromARGB(102, 153, 153, 153) diff --git a/sdk/lib/framework/components2/modal_overlay.dart b/sdk/lib/framework/widgets/modal_overlay.dart similarity index 93% rename from sdk/lib/framework/components2/modal_overlay.dart rename to sdk/lib/framework/widgets/modal_overlay.dart index 84951e051f572d71a768aeed8aed716fc8ec61c2..e53891be50a458a0ec440b0014435614bdd389ac 100644 --- a/sdk/lib/framework/components2/modal_overlay.dart +++ b/sdk/lib/framework/widgets/modal_overlay.dart @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; +import 'ui_node.dart'; +import 'wrappers.dart'; class ModalOverlay extends Component { diff --git a/sdk/lib/framework/components2/popup_menu.dart b/sdk/lib/framework/widgets/popup_menu.dart similarity index 99% rename from sdk/lib/framework/components2/popup_menu.dart rename to sdk/lib/framework/widgets/popup_menu.dart index 62e93f8d4901d000fe146dc19fa4b096d6451895..8307996a575d3ff27bad8154bb6a123b1a9f580a 100644 --- a/sdk/lib/framework/components2/popup_menu.dart +++ b/sdk/lib/framework/widgets/popup_menu.dart @@ -7,13 +7,13 @@ import 'dart:math' as math; import 'dart:sky' as sky; import '../animation/animated_value.dart'; -import '../fn2.dart'; import '../painting/box_painter.dart'; import '../theme2/colors.dart'; import '../theme2/shadows.dart'; import 'animated_component.dart'; import 'material.dart'; import 'popup_menu_item.dart'; +import 'wrappers.dart'; const double _kMenuOpenDuration = 300.0; const double _kMenuCloseDuration = 200.0; diff --git a/sdk/lib/framework/components2/popup_menu_item.dart b/sdk/lib/framework/widgets/popup_menu_item.dart similarity index 96% rename from sdk/lib/framework/components2/popup_menu_item.dart rename to sdk/lib/framework/widgets/popup_menu_item.dart index 78bb37f47bdc1f064a6f89d321c8f78258dfbd60..b0e3453baa6162ccae4f507bc6f71acd1c703e21 100644 --- a/sdk/lib/framework/components2/popup_menu_item.dart +++ b/sdk/lib/framework/widgets/popup_menu_item.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; +import 'wrappers.dart'; import 'ink_well.dart'; class PopupMenuItem extends Component { diff --git a/sdk/lib/framework/components2/radio.dart b/sdk/lib/framework/widgets/radio.dart similarity index 97% rename from sdk/lib/framework/components2/radio.dart rename to sdk/lib/framework/widgets/radio.dart index 36134ed5d645b43c0a5fffc1727f98a587c32c7f..b119d7fe41a805db48e40ff72df511d1a4978c95 100644 --- a/sdk/lib/framework/components2/radio.dart +++ b/sdk/lib/framework/widgets/radio.dart @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:sky' as sky; + import 'package:sky/framework/theme2/colors.dart' as colors; -import '../fn2.dart'; import '../rendering/object.dart'; import 'button_base.dart'; -import 'ink_well.dart'; -import 'dart:sky' as sky; +import 'wrappers.dart'; typedef void ValueChanged(value); diff --git a/sdk/lib/framework/components2/scaffold.dart b/sdk/lib/framework/widgets/scaffold.dart similarity index 99% rename from sdk/lib/framework/components2/scaffold.dart rename to sdk/lib/framework/widgets/scaffold.dart index 1ba9107400e92f4239ceccaa472cceb6e808dac6..5d95f6306467b6c9c5e2d00f2032b5f8324de22d 100644 --- a/sdk/lib/framework/components2/scaffold.dart +++ b/sdk/lib/framework/widgets/scaffold.dart @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; import '../rendering/box.dart'; import '../rendering/object.dart'; import '../theme2/view_configuration.dart'; +import 'ui_node.dart'; enum ScaffoldSlots { toolbar, diff --git a/sdk/lib/framework/components2/scrollable.dart b/sdk/lib/framework/widgets/scrollable.dart similarity index 99% rename from sdk/lib/framework/components2/scrollable.dart rename to sdk/lib/framework/widgets/scrollable.dart index dee2c137335f2e4647d5b4a3700459085341f169..16478f4652d6164ec227d41d3568a0b5bac6180a 100644 --- a/sdk/lib/framework/components2/scrollable.dart +++ b/sdk/lib/framework/widgets/scrollable.dart @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; +import 'dart:sky' as sky; + import '../animation/generators.dart'; import '../animation/mechanics.dart'; import '../animation/scroll_behavior.dart'; -import '../fn2.dart'; import '../theme/view_configuration.dart' as config; -import 'dart:math' as math; -import 'dart:sky' as sky; +import 'wrappers.dart'; const double _kMillisecondsPerSecond = 1000.0; diff --git a/sdk/lib/framework/components2/tool_bar.dart b/sdk/lib/framework/widgets/tool_bar.dart similarity index 97% rename from sdk/lib/framework/components2/tool_bar.dart rename to sdk/lib/framework/widgets/tool_bar.dart index a4df2eb8a32c7bcde727e6fc44740c6343e82b57..620f85285633cbdee1d84c8f24bcbeea2bdd6852 100644 --- a/sdk/lib/framework/components2/tool_bar.dart +++ b/sdk/lib/framework/widgets/tool_bar.dart @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../fn2.dart'; import '../rendering/flex.dart'; -import '../theme2/view_configuration.dart'; import '../theme2/shadows.dart'; +import '../theme2/view_configuration.dart'; +import 'wrappers.dart'; class ToolBar extends Component { diff --git a/sdk/lib/framework/fn2.dart b/sdk/lib/framework/widgets/ui_node.dart similarity index 70% rename from sdk/lib/framework/fn2.dart rename to sdk/lib/framework/widgets/ui_node.dart index 227d04cb3db9fad33f1765917c845acee8358cb9..2d1cd2497b73444203874a6e78180409b87536df 100644 --- a/sdk/lib/framework/fn2.dart +++ b/sdk/lib/framework/widgets/ui_node.dart @@ -2,23 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -library fn; - -import 'app.dart'; import 'dart:async'; import 'dart:collection'; import 'dart:mirrors'; import 'dart:sky' as sky; -import 'package:vector_math/vector_math.dart'; -import 'rendering/block.dart'; -import 'rendering/box.dart'; -import 'rendering/flex.dart'; -import 'rendering/object.dart'; -import 'rendering/paragraph.dart'; -import 'rendering/stack.dart'; -export 'rendering/object.dart' show Point, Size, Rect, Color, Paint, Path; -export 'rendering/box.dart' show BoxConstraints, BoxDecoration, Border, BorderSide, EdgeDims; -export 'rendering/flex.dart' show FlexDirection; + +import '../app.dart'; +import '../rendering/box.dart'; +import '../rendering/object.dart'; + +export '../rendering/box.dart' show BoxConstraints, BoxDecoration, Border, BorderSide, EdgeDims; +export '../rendering/flex.dart' show FlexDirection; +export '../rendering/object.dart' show Point, Size, Rect, Color, Paint, Path; + // final sky.Tracing _tracing = sky.window.tracing; @@ -155,6 +151,7 @@ abstract class UINode { } } + // Descendants of TagNode provide a way to tag RenderObjectWrapper and // Component nodes with annotations, such as event listeners, // stylistic information, etc. @@ -275,6 +272,190 @@ class EventListenerNode extends TagNode { } + +abstract class Component extends UINode { + + Component({ Object key, bool stateful }) + : _stateful = stateful != null ? stateful : false, + _order = _currentOrder + 1, + super(key: key); + + Component.fromArgs(Object key, bool stateful) + : this(key: key, stateful: stateful); + + static Component _currentlyBuilding; + bool get _isBuilding => _currentlyBuilding == this; + + bool _stateful; + bool _dirty = true; + bool _disqualifiedFromEverAppearingAgain = false; + + UINode _built; + dynamic _slot; // cached slot from the last time we were synced + + void didMount() { + assert(!_disqualifiedFromEverAppearingAgain); + super.didMount(); + } + + void remove() { + assert(_built != null); + assert(root != null); + removeChild(_built); + _built = null; + super.remove(); + } + + bool _retainStatefulNodeIfPossible(UINode old) { + assert(!_disqualifiedFromEverAppearingAgain); + + Component oldComponent = old as Component; + if (oldComponent == null || !oldComponent._stateful) + return false; + + assert(key == oldComponent.key); + + // Make |this|, the newly-created object, into the "old" Component, and kill it + _stateful = false; + _built = oldComponent._built; + assert(_built != null); + _disqualifiedFromEverAppearingAgain = true; + + // Make |oldComponent| the "new" component + oldComponent._built = null; + oldComponent._dirty = true; + oldComponent.syncFields(this); + return true; + } + + // This is called by _retainStatefulNodeIfPossible(), during + // syncChild(), just before _sync() is called. + // This must be implemented on any subclass that can become stateful + // (but don't call super.syncFields() if you inherit directly from + // Component, since that'll fire an assert). + // If you don't ever become stateful, then don't override this. + void syncFields(Component source) { + assert(false); + } + + final int _order; + static int _currentOrder = 0; + + /* There are three cases here: + * 1) Building for the first time: + * assert(_built == null && old == null) + * 2) Re-building (because a dirty flag got set): + * assert(_built != null && old == null) + * 3) Syncing against an old version + * assert(_built == null && old != null) + */ + void _sync(UINode old, dynamic slot) { + assert(_built == null || old == null); + assert(!_disqualifiedFromEverAppearingAgain); + + Component oldComponent = old as Component; + + _slot = slot; + + var oldBuilt; + if (oldComponent == null) { + oldBuilt = _built; + } else { + assert(_built == null); + oldBuilt = oldComponent._built; + } + + int lastOrder = _currentOrder; + _currentOrder = _order; + _currentlyBuilding = this; + _built = build(); + assert(_built != null); + _currentlyBuilding = null; + _currentOrder = lastOrder; + + _built = syncChild(_built, oldBuilt, slot); + assert(_built != null); + _dirty = false; + _root = _built.root; + assert(_root == root); // in case a subclass reintroduces it + assert(root != null); + } + + void _buildIfDirty() { + assert(!_disqualifiedFromEverAppearingAgain); + if (!_dirty || !_mounted) + return; + + assert(root != null); + _sync(null, _slot); + } + + void scheduleBuild() { + setState(() {}); + } + + void setState(Function fn()) { + assert(!_disqualifiedFromEverAppearingAgain); + _stateful = true; + fn(); + if (_isBuilding || _dirty || !_mounted) + return; + + _dirty = true; + _scheduleComponentForRender(this); + } + + UINode build(); + +} + +Set _dirtyComponents = new Set(); +bool _buildScheduled = false; +bool _inRenderDirtyComponents = false; + +void _buildDirtyComponents() { + //_tracing.begin('fn::_buildDirtyComponents'); + + Stopwatch sw; + if (_shouldLogRenderDuration) + sw = new Stopwatch()..start(); + + try { + _inRenderDirtyComponents = true; + + List sortedDirtyComponents = _dirtyComponents.toList(); + sortedDirtyComponents.sort((Component a, Component b) => a._order - b._order); + for (var comp in sortedDirtyComponents) { + comp._buildIfDirty(); + } + + _dirtyComponents.clear(); + _buildScheduled = false; + } finally { + _inRenderDirtyComponents = false; + } + + UINode._notifyMountStatusChanged(); + + if (_shouldLogRenderDuration) { + sw.stop(); + print('Render took ${sw.elapsedMicroseconds} microseconds'); + } + + //_tracing.end('fn::_buildDirtyComponents'); +} + +void _scheduleComponentForRender(Component c) { + assert(!_inRenderDirtyComponents); + _dirtyComponents.add(c); + + if (!_buildScheduled) { + _buildScheduled = true; + new Future.microtask(_buildDirtyComponents); + } +} + + /* * RenderObjectWrappers correspond to a desired state of a RenderObject. * They are fully immutable, with one exception: A UINode which is a @@ -378,253 +559,76 @@ abstract class OneChildRenderObjectWrapper extends RenderObjectWrapper { } -class Opacity extends OneChildRenderObjectWrapper { - Opacity({ this.opacity, UINode child, Object key }) - : super(child: child, key: key); - - RenderOpacity get root { RenderOpacity result = super.root; return result; } - final double opacity; +abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper { - RenderOpacity createNode() => new RenderOpacity(opacity: opacity); + // In MultiChildRenderObjectWrapper subclasses, slots are RenderObject nodes + // to use as the "insert before" sibling in ContainerRenderObjectMixin.add() calls - void syncRenderObject(Opacity old) { - super.syncRenderObject(old); - root.opacity = opacity; + MultiChildRenderObjectWrapper({ + Object key, + List children + }) : this.children = children == null ? const [] : children, + super(key: key) { + assert(!_debugHasDuplicateIds()); } -} - -class ClipRect extends OneChildRenderObjectWrapper { - - ClipRect({ UINode child, Object key }) - : super(child: child, key: key); - - RenderClipRect get root { RenderClipRect result = super.root; return result; } - RenderClipRect createNode() => new RenderClipRect(); -} - -class ClipOval extends OneChildRenderObjectWrapper { - ClipOval({ UINode child, Object key }) - : super(child: child, key: key); + final List children; - RenderClipOval get root { RenderClipOval result = super.root; return result; } - RenderClipOval createNode() => new RenderClipOval(); -} + void insert(RenderObjectWrapper child, dynamic slot) { + final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer + assert(slot == null || slot is RenderObject); + assert(root is ContainerRenderObjectMixin); + root.add(child.root, before: slot); + assert(root == this.root); // TODO(ianh): Remove this once the analyzer is cleverer + } -class Padding extends OneChildRenderObjectWrapper { + void removeChild(UINode node) { + final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer + assert(root is ContainerRenderObjectMixin); + assert(node.root.parent == root); + root.remove(node.root); + super.removeChild(node); + assert(root == this.root); // TODO(ianh): Remove this once the analyzer is cleverer + } - Padding({ this.padding, UINode child, Object key }) - : super(child: child, key: key); + void remove() { + assert(children != null); + for (var child in children) { + assert(child != null); + removeChild(child); + } + super.remove(); + } - RenderPadding get root { RenderPadding result = super.root; return result; } - final EdgeDims padding; + bool _debugHasDuplicateIds() { + var idSet = new HashSet(); + for (var child in children) { + assert(child != null); + if (child.interchangeable) + continue; // when these nodes are reordered, we just reassign the data - RenderPadding createNode() => new RenderPadding(padding: padding); + if (!idSet.add(child._key)) { + throw '''If multiple non-interchangeable nodes of the same type exist as children + of another node, they must have unique keys. + Duplicate: "${child._key}"'''; + } + } + return false; + } - void syncRenderObject(Padding old) { + void syncRenderObject(MultiChildRenderObjectWrapper old) { super.syncRenderObject(old); - root.padding = padding; - } -} + final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer + if (root is! ContainerRenderObjectMixin) + return; -class DecoratedBox extends OneChildRenderObjectWrapper { + var startIndex = 0; + var endIndex = children.length; - DecoratedBox({ this.decoration, UINode child, Object key }) - : super(child: child, key: key); - - RenderDecoratedBox get root { RenderDecoratedBox result = super.root; return result; } - final BoxDecoration decoration; - - RenderDecoratedBox createNode() => new RenderDecoratedBox(decoration: decoration); - - void syncRenderObject(DecoratedBox old) { - super.syncRenderObject(old); - root.decoration = decoration; - } - -} - -class SizedBox extends OneChildRenderObjectWrapper { - - SizedBox({ - double width: double.INFINITY, - double height: double.INFINITY, - UINode child, - Object key - }) : desiredSize = new Size(width, height), super(child: child, key: key); - - RenderSizedBox get root { RenderSizedBox result = super.root; return result; } - final Size desiredSize; - - RenderSizedBox createNode() => new RenderSizedBox(desiredSize: desiredSize); - - void syncRenderObject(SizedBox old) { - super.syncRenderObject(old); - root.desiredSize = desiredSize; - } - -} - -class ConstrainedBox extends OneChildRenderObjectWrapper { - - ConstrainedBox({ this.constraints, UINode child, Object key }) - : super(child: child, key: key); - - RenderConstrainedBox get root { RenderConstrainedBox result = super.root; return result; } - final BoxConstraints constraints; - - RenderConstrainedBox createNode() => new RenderConstrainedBox(additionalConstraints: constraints); - - void syncRenderObject(ConstrainedBox old) { - super.syncRenderObject(old); - root.additionalConstraints = constraints; - } - -} - -class ShrinkWrapWidth extends OneChildRenderObjectWrapper { - - ShrinkWrapWidth({ UINode child, Object key }) : super(child: child, key: key); - - RenderShrinkWrapWidth get root { RenderShrinkWrapWidth result = super.root; return result; } - - RenderShrinkWrapWidth createNode() => new RenderShrinkWrapWidth(); - -} - -class Transform extends OneChildRenderObjectWrapper { - - Transform({ this.transform, UINode child, Object key }) - : super(child: child, key: key); - - RenderTransform get root { RenderTransform result = super.root; return result; } - final Matrix4 transform; - - RenderTransform createNode() => new RenderTransform(transform: transform); - - void syncRenderObject(Transform old) { - super.syncRenderObject(old); - root.transform = transform; - } - -} - -class SizeObserver extends OneChildRenderObjectWrapper { - - SizeObserver({ this.callback, UINode child, Object key }) - : super(child: child, key: key); - - RenderSizeObserver get root { RenderSizeObserver result = super.root; return result; } - final SizeChangedCallback callback; - - RenderSizeObserver createNode() => new RenderSizeObserver(callback: callback); - - void syncRenderObject(SizeObserver old) { - super.syncRenderObject(old); - root.callback = callback; - } - - void remove() { - root.callback = null; - super.remove(); - } - -} - -// TODO(jackson) need a mechanism for marking the RenderCustomPaint as needing paint -class CustomPaint extends OneChildRenderObjectWrapper { - - CustomPaint({ this.callback, UINode child, Object key }) - : super(child: child, key: key); - - RenderCustomPaint get root { RenderCustomPaint result = super.root; return result; } - final CustomPaintCallback callback; - - RenderCustomPaint createNode() => new RenderCustomPaint(callback: callback); - - void syncRenderObject(CustomPaint old) { - super.syncRenderObject(old); - root.callback = callback; - } - - void remove() { - root.callback = null; - super.remove(); - } - -} - -abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper { - - // In MultiChildRenderObjectWrapper subclasses, slots are RenderObject nodes - // to use as the "insert before" sibling in ContainerRenderObjectMixin.add() calls - - MultiChildRenderObjectWrapper({ - Object key, - List children - }) : this.children = children == null ? const [] : children, - super(key: key) { - assert(!_debugHasDuplicateIds()); - } - - final List children; - - void insert(RenderObjectWrapper child, dynamic slot) { - final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer - assert(slot == null || slot is RenderObject); - assert(root is ContainerRenderObjectMixin); - root.add(child.root, before: slot); - assert(root == this.root); // TODO(ianh): Remove this once the analyzer is cleverer - } - - void removeChild(UINode node) { - final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer - assert(root is ContainerRenderObjectMixin); - assert(node.root.parent == root); - root.remove(node.root); - super.removeChild(node); - assert(root == this.root); // TODO(ianh): Remove this once the analyzer is cleverer - } - - void remove() { - assert(children != null); - for (var child in children) { - assert(child != null); - removeChild(child); - } - super.remove(); - } - - bool _debugHasDuplicateIds() { - var idSet = new HashSet(); - for (var child in children) { - assert(child != null); - if (child.interchangeable) - continue; // when these nodes are reordered, we just reassign the data - - if (!idSet.add(child._key)) { - throw '''If multiple non-interchangeable nodes of the same type exist as children - of another node, they must have unique keys. - Duplicate: "${child._key}"'''; - } - } - return false; - } - - void syncRenderObject(MultiChildRenderObjectWrapper old) { - super.syncRenderObject(old); - - final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer - if (root is! ContainerRenderObjectMixin) - return; - - var startIndex = 0; - var endIndex = children.length; - - var oldChildren = old == null ? [] : old.children; - var oldStartIndex = 0; - var oldEndIndex = oldChildren.length; + var oldChildren = old == null ? [] : old.children; + var oldStartIndex = 0; + var oldEndIndex = oldChildren.length; RenderObject nextSibling = null; UINode currentNode = null; @@ -740,357 +744,6 @@ abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper { } -class Block extends MultiChildRenderObjectWrapper { - - Block(List children, { Object key }) - : super(key: key, children: children); - - RenderBlock get root { RenderBlock result = super.root; return result; } - RenderBlock createNode() => new RenderBlock(); - -} - -class Stack extends MultiChildRenderObjectWrapper { - - Stack(List children, { Object key }) - : super(key: key, children: children); - - RenderStack get root { RenderStack result = super.root; return result; } - RenderStack createNode() => new RenderStack(); - -} - -class StackPositionedChild extends ParentDataNode { - StackPositionedChild(UINode content, { - double top, double right, double bottom, double left - }) : super(content, new StackParentData()..top = top - ..right = right - ..bottom = bottom - ..left = left); -} - -class Paragraph extends RenderObjectWrapper { - - Paragraph({ Object key, this.text }) : super(key: key); - - RenderParagraph get root { RenderParagraph result = super.root; return result; } - RenderParagraph createNode() => new RenderParagraph(text: text); - - final String text; - - void syncRenderObject(UINode old) { - super.syncRenderObject(old); - root.text = text; - } - - void insert(RenderObjectWrapper child, dynamic slot) { - assert(false); - // Paragraph does not support having children currently - } - -} - -class Text extends Component { - Text(this.data) : super(key: '*text*'); - final String data; - bool get interchangeable => true; - UINode build() => new Paragraph(text: data); -} - -class Flex extends MultiChildRenderObjectWrapper { - - Flex(List children, { - Object key, - this.direction: FlexDirection.horizontal, - this.justifyContent: FlexJustifyContent.flexStart, - this.alignItems: FlexAlignItems.center - }) : super(key: key, children: children); - - RenderFlex get root { RenderFlex result = super.root; return result; } - RenderFlex createNode() => new RenderFlex(direction: this.direction); - - final FlexDirection direction; - final FlexJustifyContent justifyContent; - final FlexAlignItems alignItems; - - void syncRenderObject(UINode old) { - super.syncRenderObject(old); - root.direction = direction; - root.justifyContent = justifyContent; - root.alignItems = alignItems; - } - -} - -class FlexExpandingChild extends ParentDataNode { - FlexExpandingChild(UINode content, { int flex: 1, Object key }) - : super(content, new FlexBoxParentData()..flex = flex, key: key); -} - -class Image extends RenderObjectWrapper { - - Image({ - Object key, - this.src, - this.size - }) : super(key: key); - - RenderImage get root { RenderImage result = super.root; return result; } - RenderImage createNode() => new RenderImage(this.src, this.size); - - final String src; - final Size size; - - void syncRenderObject(UINode old) { - super.syncRenderObject(old); - root.src = src; - root.requestedSize = size; - } - - void insert(RenderObjectWrapper child, dynamic slot) { - assert(false); - // Image does not support having children currently - } - -} - -Set _dirtyComponents = new Set(); -bool _buildScheduled = false; -bool _inRenderDirtyComponents = false; - -void _buildDirtyComponents() { - //_tracing.begin('fn::_buildDirtyComponents'); - - Stopwatch sw; - if (_shouldLogRenderDuration) - sw = new Stopwatch()..start(); - - try { - _inRenderDirtyComponents = true; - - List sortedDirtyComponents = _dirtyComponents.toList(); - sortedDirtyComponents.sort((Component a, Component b) => a._order - b._order); - for (var comp in sortedDirtyComponents) { - comp._buildIfDirty(); - } - - _dirtyComponents.clear(); - _buildScheduled = false; - } finally { - _inRenderDirtyComponents = false; - } - - UINode._notifyMountStatusChanged(); - - if (_shouldLogRenderDuration) { - sw.stop(); - print('Render took ${sw.elapsedMicroseconds} microseconds'); - } - - //_tracing.end('fn::_buildDirtyComponents'); -} - -void _scheduleComponentForRender(Component c) { - assert(!_inRenderDirtyComponents); - _dirtyComponents.add(c); - - if (!_buildScheduled) { - _buildScheduled = true; - new Future.microtask(_buildDirtyComponents); - } -} - -abstract class Component extends UINode { - - Component({ Object key, bool stateful }) - : _stateful = stateful != null ? stateful : false, - _order = _currentOrder + 1, - super(key: key); - - Component.fromArgs(Object key, bool stateful) - : this(key: key, stateful: stateful); - - static Component _currentlyBuilding; - bool get _isBuilding => _currentlyBuilding == this; - - bool _stateful; - bool _dirty = true; - bool _disqualifiedFromEverAppearingAgain = false; - - UINode _built; - dynamic _slot; // cached slot from the last time we were synced - - void didMount() { - assert(!_disqualifiedFromEverAppearingAgain); - super.didMount(); - } - - void remove() { - assert(_built != null); - assert(root != null); - removeChild(_built); - _built = null; - super.remove(); - } - - bool _retainStatefulNodeIfPossible(UINode old) { - assert(!_disqualifiedFromEverAppearingAgain); - - Component oldComponent = old as Component; - if (oldComponent == null || !oldComponent._stateful) - return false; - - assert(key == oldComponent.key); - - // Make |this|, the newly-created object, into the "old" Component, and kill it - _stateful = false; - _built = oldComponent._built; - assert(_built != null); - _disqualifiedFromEverAppearingAgain = true; - - // Make |oldComponent| the "new" component - oldComponent._built = null; - oldComponent._dirty = true; - oldComponent.syncFields(this); - return true; - } - - // This is called by _retainStatefulNodeIfPossible(), during - // syncChild(), just before _sync() is called. - // This must be implemented on any subclass that can become stateful - // (but don't call super.syncFields() if you inherit directly from - // Component, since that'll fire an assert). - // If you don't ever become stateful, then don't override this. - void syncFields(Component source) { - assert(false); - } - - final int _order; - static int _currentOrder = 0; - - /* There are three cases here: - * 1) Building for the first time: - * assert(_built == null && old == null) - * 2) Re-building (because a dirty flag got set): - * assert(_built != null && old == null) - * 3) Syncing against an old version - * assert(_built == null && old != null) - */ - void _sync(UINode old, dynamic slot) { - assert(_built == null || old == null); - assert(!_disqualifiedFromEverAppearingAgain); - - Component oldComponent = old as Component; - - _slot = slot; - - var oldBuilt; - if (oldComponent == null) { - oldBuilt = _built; - } else { - assert(_built == null); - oldBuilt = oldComponent._built; - } - - int lastOrder = _currentOrder; - _currentOrder = _order; - _currentlyBuilding = this; - _built = build(); - assert(_built != null); - _currentlyBuilding = null; - _currentOrder = lastOrder; - - _built = syncChild(_built, oldBuilt, slot); - assert(_built != null); - _dirty = false; - _root = _built.root; - assert(_root == root); // in case a subclass reintroduces it - assert(root != null); - } - - void _buildIfDirty() { - assert(!_disqualifiedFromEverAppearingAgain); - if (!_dirty || !_mounted) - return; - - assert(root != null); - _sync(null, _slot); - } - - void scheduleBuild() { - setState(() {}); - } - - void setState(Function fn()) { - assert(!_disqualifiedFromEverAppearingAgain); - _stateful = true; - fn(); - if (_isBuilding || _dirty || !_mounted) - return; - - _dirty = true; - _scheduleComponentForRender(this); - } - - UINode build(); - -} - -class Container extends Component { - - Container({ - Object key, - this.child, - this.constraints, - this.decoration, - this.width, - this.height, - this.margin, - this.padding, - this.transform - }) : super(key: key); - - final UINode child; - final BoxConstraints constraints; - final BoxDecoration decoration; - final EdgeDims margin; - final EdgeDims padding; - final Matrix4 transform; - final double width; - final double height; - - UINode build() { - UINode current = child; - - if (child == null && width == null && height == null) - current = new SizedBox(); - - if (padding != null) - current = new Padding(padding: padding, child: current); - - if (decoration != null) - current = new DecoratedBox(decoration: decoration, child: current); - - if (width != null || height != null) - current = new SizedBox( - width: width == null ? double.INFINITY : width, - height: height == null ? double.INFINITY : height, - child: current - ); - - if (constraints != null) - current = new ConstrainedBox(constraints: constraints, child: current); - - if (margin != null) - current = new Padding(padding: margin, child: current); - - if (transform != null) - current = new Transform(transform: transform, child: current); - - return current; - } - -} class UINodeAppView extends AppView { @@ -1164,9 +817,9 @@ abstract class App extends AbstractUINodeRoot { typedef UINode Builder(); -class RenderNodeToUINodeAdapter extends AbstractUINodeRoot { +class RenderObjectToUINodeAdapter extends AbstractUINodeRoot { - RenderNodeToUINodeAdapter( + RenderObjectToUINodeAdapter( RenderObjectWithChildMixin container, this.builder ) : _container = container { diff --git a/sdk/lib/framework/widgets/wrappers.dart b/sdk/lib/framework/widgets/wrappers.dart new file mode 100644 index 0000000000000000000000000000000000000000..75eeb03fe06d4be7632f2292bc622bd718ea7c72 --- /dev/null +++ b/sdk/lib/framework/widgets/wrappers.dart @@ -0,0 +1,374 @@ + +import 'package:vector_math/vector_math.dart'; + +import '../rendering/block.dart'; +import '../rendering/box.dart'; +import '../rendering/flex.dart'; +import '../rendering/object.dart'; +import '../rendering/paragraph.dart'; +import '../rendering/stack.dart'; +import 'ui_node.dart'; + +export '../rendering/box.dart' show BoxConstraints, BoxDecoration, Border, BorderSide, EdgeDims; +export '../rendering/flex.dart' show FlexDirection; +export '../rendering/object.dart' show Point, Size, Rect, Color, Paint, Path; +export 'ui_node.dart' show UINode, Component, App, EventListenerNode, ParentDataNode; + + +// PAINTING NODES + +class Opacity extends OneChildRenderObjectWrapper { + Opacity({ this.opacity, UINode child, Object key }) + : super(child: child, key: key); + + RenderOpacity get root { RenderOpacity result = super.root; return result; } + final double opacity; + + RenderOpacity createNode() => new RenderOpacity(opacity: opacity); + + void syncRenderObject(Opacity old) { + super.syncRenderObject(old); + root.opacity = opacity; + } +} + +class DecoratedBox extends OneChildRenderObjectWrapper { + + DecoratedBox({ this.decoration, UINode child, Object key }) + : super(child: child, key: key); + + RenderDecoratedBox get root { RenderDecoratedBox result = super.root; return result; } + final BoxDecoration decoration; + + RenderDecoratedBox createNode() => new RenderDecoratedBox(decoration: decoration); + + void syncRenderObject(DecoratedBox old) { + super.syncRenderObject(old); + root.decoration = decoration; + } + +} + +// TODO(jackson) need a mechanism for marking the RenderCustomPaint as needing paint +class CustomPaint extends OneChildRenderObjectWrapper { + + CustomPaint({ this.callback, UINode child, Object key }) + : super(child: child, key: key); + + RenderCustomPaint get root { RenderCustomPaint result = super.root; return result; } + final CustomPaintCallback callback; + + RenderCustomPaint createNode() => new RenderCustomPaint(callback: callback); + + void syncRenderObject(CustomPaint old) { + super.syncRenderObject(old); + root.callback = callback; + } + + void remove() { + root.callback = null; + super.remove(); + } + +} + +class ClipRect extends OneChildRenderObjectWrapper { + + ClipRect({ UINode child, Object key }) + : super(child: child, key: key); + + RenderClipRect get root { RenderClipRect result = super.root; return result; } + RenderClipRect createNode() => new RenderClipRect(); +} + +class ClipOval extends OneChildRenderObjectWrapper { + + ClipOval({ UINode child, Object key }) + : super(child: child, key: key); + + RenderClipOval get root { RenderClipOval result = super.root; return result; } + RenderClipOval createNode() => new RenderClipOval(); +} + + +// POSITIONING AND SIZING NODES + +class Transform extends OneChildRenderObjectWrapper { + + Transform({ this.transform, UINode child, Object key }) + : super(child: child, key: key); + + RenderTransform get root { RenderTransform result = super.root; return result; } + final Matrix4 transform; + + RenderTransform createNode() => new RenderTransform(transform: transform); + + void syncRenderObject(Transform old) { + super.syncRenderObject(old); + root.transform = transform; + } + +} + +class Padding extends OneChildRenderObjectWrapper { + + Padding({ this.padding, UINode child, Object key }) + : super(child: child, key: key); + + RenderPadding get root { RenderPadding result = super.root; return result; } + final EdgeDims padding; + + RenderPadding createNode() => new RenderPadding(padding: padding); + + void syncRenderObject(Padding old) { + super.syncRenderObject(old); + root.padding = padding; + } + +} + +class SizedBox extends OneChildRenderObjectWrapper { + + SizedBox({ + double width: double.INFINITY, + double height: double.INFINITY, + UINode child, + Object key + }) : desiredSize = new Size(width, height), super(child: child, key: key); + + RenderSizedBox get root { RenderSizedBox result = super.root; return result; } + final Size desiredSize; + + RenderSizedBox createNode() => new RenderSizedBox(desiredSize: desiredSize); + + void syncRenderObject(SizedBox old) { + super.syncRenderObject(old); + root.desiredSize = desiredSize; + } + +} + +class ConstrainedBox extends OneChildRenderObjectWrapper { + + ConstrainedBox({ this.constraints, UINode child, Object key }) + : super(child: child, key: key); + + RenderConstrainedBox get root { RenderConstrainedBox result = super.root; return result; } + final BoxConstraints constraints; + + RenderConstrainedBox createNode() => new RenderConstrainedBox(additionalConstraints: constraints); + + void syncRenderObject(ConstrainedBox old) { + super.syncRenderObject(old); + root.additionalConstraints = constraints; + } + +} + +class ShrinkWrapWidth extends OneChildRenderObjectWrapper { + + ShrinkWrapWidth({ UINode child, Object key }) : super(child: child, key: key); + + RenderShrinkWrapWidth get root { RenderShrinkWrapWidth result = super.root; return result; } + + RenderShrinkWrapWidth createNode() => new RenderShrinkWrapWidth(); + +} + +class SizeObserver extends OneChildRenderObjectWrapper { + + SizeObserver({ this.callback, UINode child, Object key }) + : super(child: child, key: key); + + RenderSizeObserver get root { RenderSizeObserver result = super.root; return result; } + final SizeChangedCallback callback; + + RenderSizeObserver createNode() => new RenderSizeObserver(callback: callback); + + void syncRenderObject(SizeObserver old) { + super.syncRenderObject(old); + root.callback = callback; + } + + void remove() { + root.callback = null; + super.remove(); + } + +} + + +// CONVENIENCE CLASS TO COMBINE COMMON PAINTING, POSITIONING, AND SIZING NODES + +class Container extends Component { + + Container({ + Object key, + this.child, + this.constraints, + this.decoration, + this.width, + this.height, + this.margin, + this.padding, + this.transform + }) : super(key: key); + + final UINode child; + final BoxConstraints constraints; + final BoxDecoration decoration; + final EdgeDims margin; + final EdgeDims padding; + final Matrix4 transform; + final double width; + final double height; + + UINode build() { + UINode current = child; + + if (child == null && width == null && height == null) + current = new SizedBox(); + + if (padding != null) + current = new Padding(padding: padding, child: current); + + if (decoration != null) + current = new DecoratedBox(decoration: decoration, child: current); + + if (width != null || height != null) + current = new SizedBox( + width: width == null ? double.INFINITY : width, + height: height == null ? double.INFINITY : height, + child: current + ); + + if (constraints != null) + current = new ConstrainedBox(constraints: constraints, child: current); + + if (margin != null) + current = new Padding(padding: margin, child: current); + + if (transform != null) + current = new Transform(transform: transform, child: current); + + return current; + } + +} + + +// LAYOUT NODES + +class Block extends MultiChildRenderObjectWrapper { + + Block(List children, { Object key }) + : super(key: key, children: children); + + RenderBlock get root { RenderBlock result = super.root; return result; } + RenderBlock createNode() => new RenderBlock(); + +} + +class Stack extends MultiChildRenderObjectWrapper { + + Stack(List children, { Object key }) + : super(key: key, children: children); + + RenderStack get root { RenderStack result = super.root; return result; } + RenderStack createNode() => new RenderStack(); + +} + +class StackPositionedChild extends ParentDataNode { + StackPositionedChild(UINode content, { + double top, double right, double bottom, double left + }) : super(content, new StackParentData()..top = top + ..right = right + ..bottom = bottom + ..left = left); +} + +class Flex extends MultiChildRenderObjectWrapper { + + Flex(List children, { + Object key, + this.direction: FlexDirection.horizontal, + this.justifyContent: FlexJustifyContent.flexStart, + this.alignItems: FlexAlignItems.center + }) : super(key: key, children: children); + + RenderFlex get root { RenderFlex result = super.root; return result; } + RenderFlex createNode() => new RenderFlex(direction: this.direction); + + final FlexDirection direction; + final FlexJustifyContent justifyContent; + final FlexAlignItems alignItems; + + void syncRenderObject(UINode old) { + super.syncRenderObject(old); + root.direction = direction; + root.justifyContent = justifyContent; + root.alignItems = alignItems; + } + +} + +class FlexExpandingChild extends ParentDataNode { + FlexExpandingChild(UINode content, { int flex: 1, Object key }) + : super(content, new FlexBoxParentData()..flex = flex, key: key); +} + +class Paragraph extends RenderObjectWrapper { + + Paragraph({ Object key, this.text }) : super(key: key); + + RenderParagraph get root { RenderParagraph result = super.root; return result; } + RenderParagraph createNode() => new RenderParagraph(text: text); + + final String text; + + void syncRenderObject(UINode old) { + super.syncRenderObject(old); + root.text = text; + } + + void insert(RenderObjectWrapper child, dynamic slot) { + assert(false); + // Paragraph does not support having children currently + } + +} + +class Text extends Component { + Text(this.data) : super(key: '*text*'); + final String data; + bool get interchangeable => true; + UINode build() => new Paragraph(text: data); +} + +class Image extends RenderObjectWrapper { + + Image({ + Object key, + this.src, + this.size + }) : super(key: key); + + RenderImage get root { RenderImage result = super.root; return result; } + RenderImage createNode() => new RenderImage(this.src, this.size); + + final String src; + final Size size; + + void syncRenderObject(UINode old) { + super.syncRenderObject(old); + root.src = src; + root.requestedSize = size; + } + + void insert(RenderObjectWrapper child, dynamic slot) { + assert(false); + // Image does not support having children currently + } + +}