diff --git a/examples/layers/raw/canvas.dart b/examples/layers/raw/canvas.dart index 4e046e68cc..3f955c8156 100644 --- a/examples/layers/raw/canvas.dart +++ b/examples/layers/raw/canvas.dart @@ -2,19 +2,32 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This example shows how to use the ui.Canvas interface to draw various shapes +// with gradients and transforms. + import 'dart:ui' as ui; import 'dart:math' as math; import 'dart:typed_data'; ui.Picture paint(ui.Rect paintBounds) { + // First we create a PictureRecorder to record the commands we're going to + // feed in the canvas. The PictureRecorder will eventually produce a Picture, + // which is an immutable record of those commands. ui.PictureRecorder recorder = new ui.PictureRecorder(); + + // Next, we create a canvas from the recorder. The canvas is an interface + // which can receive drawing commands. The canvas interface is modeled after + // the SkCanvas interface from Skia. The paintBounds establishes a "cull rect" + // for the canvas, which lets the implementation discard any commands that + // are entirely outside this rectangle. ui.Canvas canvas = new ui.Canvas(recorder, paintBounds); - ui.Size size = paintBounds.size; ui.Paint paint = new ui.Paint(); + canvas.drawPaint(new ui.Paint()..color = const ui.Color(0xFFFFFFFF)); + + ui.Size size = paintBounds.size; ui.Point mid = size.center(ui.Point.origin); double radius = size.shortestSide / 2.0; - canvas.drawPaint(new ui.Paint()..color = const ui.Color(0xFFFFFFFF)); canvas.save(); canvas.translate(-mid.x/2.0, ui.window.size.height*2.0); @@ -46,6 +59,11 @@ ui.Picture paint(ui.Rect paintBounds) { canvas.restore(); canvas.translate(0.0, 50.0); + + // A DrawLooper is a powerful painting primitive that lets you apply several + // blending and filtering passes over a sequence of commands "in a loop". For + // example, the looper below draws the circle tree times, once with a blur, + // then with a gradient blend, and finally with just an offset. ui.LayerDrawLooperBuilder builder = new ui.LayerDrawLooperBuilder() ..addLayerOnTop( new ui.DrawLooperLayerInfo() @@ -90,6 +108,10 @@ ui.Picture paint(ui.Rect paintBounds) { paint.drawLooper = builder.build(); canvas.drawCircle(ui.Point.origin, radius, paint); + // When we're done issuing painting commands, we end the recording an receive + // a Picture, which is an immutable record of the commands we've issued. You + // can draw a Picture into another canvas or include it as part of a + // composited scene. return recorder.endRecording(); } diff --git a/examples/layers/raw/hello_world.dart b/examples/layers/raw/hello_world.dart index 1c7f83e80e..0583943413 100644 --- a/examples/layers/raw/hello_world.dart +++ b/examples/layers/raw/hello_world.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This example shows how to put some pixels on the screen using the raw +// interface to the engine. + import 'dart:ui' as ui; import 'dart:typed_data'; @@ -12,18 +15,38 @@ import 'package:sky_services/pointer/pointer.mojom.dart'; ui.Color color; ui.Picture paint(ui.Rect paintBounds) { + // First we create a PictureRecorder to record the commands we're going to + // feed in the canvas. The PictureRecorder will eventually produce a Picture, + // which is an immutable record of those commands. ui.PictureRecorder recorder = new ui.PictureRecorder(); + + // Next, we create a canvas from the recorder. The canvas is an interface + // which can receive drawing commands. The canvas interface is modeled after + // the SkCanvas interface from Skia. The paintBounds establishes a "cull rect" + // for the canvas, which lets the implementation discard any commands that + // are entirely outside this rectangle. ui.Canvas canvas = new ui.Canvas(recorder, paintBounds); + + // The commands draw a circle in the center of the screen. ui.Size size = paintBounds.size; + canvas.drawCircle( + size.center(ui.Point.origin), + size.shortestSide * 0.45, + new ui.Paint()..color = color + ); - double radius = size.shortestSide * 0.45; - ui.Paint paint = new ui.Paint()..color = color; - canvas.drawCircle(size.center(ui.Point.origin), radius, paint); - + // When we're done issuing painting commands, we end the recording an receive + // a Picture, which is an immutable record of the commands we've issued. You + // can draw a Picture into another canvas or include it as part of a + // composited scene. return recorder.endRecording(); } ui.Scene composite(ui.Picture picture, ui.Rect paintBounds) { + // The device pixel ratio gives an approximate ratio of the size of pixels on + // the device's screen to "normal" sized pixels. We commonly work in logical + // pixels, which are then scalled by the device pixel ratio before being drawn + // on the screen. final double devicePixelRatio = ui.window.devicePixelRatio; ui.Rect sceneBounds = new ui.Rect.fromLTWH( 0.0, @@ -31,49 +54,83 @@ ui.Scene composite(ui.Picture picture, ui.Rect paintBounds) { ui.window.size.width * devicePixelRatio, ui.window.size.height * devicePixelRatio ); + + // This transform scales the x and y coordinates by the devicePixelRatio. Float64List deviceTransform = new Float64List(16) ..[0] = devicePixelRatio ..[5] = devicePixelRatio ..[10] = 1.0 ..[15] = 1.0; + + // We build a very simple scene graph with two nodes. The root node is a + // transform that scale its children by the device pixel ratio. This transform + // lets us paint in "logical" pixels which are converted to device pixels by + // this scaling operation. ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(sceneBounds) ..pushTransform(deviceTransform) ..addPicture(ui.Offset.zero, picture) ..pop(); + + // When we're done recording the scene, we call build() to obtain an immutable + // record of the scene we've recorded. return sceneBuilder.build(); } void beginFrame(Duration timeStamp) { ui.Rect paintBounds = ui.Point.origin & ui.window.size; + // First, record a picture with our painting commands. ui.Picture picture = paint(paintBounds); + // Second, include that picture in a scene graph. ui.Scene scene = composite(picture, paintBounds); + // Third, instruct the engine to render that scene graph. ui.window.render(scene); } -void handlePopRoute() { - print('Pressed back button.'); -} - +// Pointer input arrives as an array of bytes. The format for the data is +// defined by pointer.mojom, which generates serializes and parsers for a +// number of languages, including Dart, C++, Java, and Go. void handlePointerPacket(ByteData serializedPacket) { + // We wrap the byte data up into a Mojo Message object, which we then + // deserialize according to the mojom definition. bindings.Message message = new bindings.Message(serializedPacket, [], serializedPacket.lengthInBytes, 0); PointerPacket packet = PointerPacket.deserialize(message); + // The deserialized pointer packet contains a number of pointer movements, + // which we iterate through and process. for (Pointer pointer in packet.pointers) { if (pointer.type == PointerType.down) { - color = new ui.Color.fromARGB(255, 0, 0, 255); + // If the pointer went down, we change the color of the circle to blue. + color = const ui.Color(0xFF0000FF); + // Rather than calling paint() synchronously, we ask the engine to + // schedule a frame. The engine will call onBeginFrame when it is actually + // time to produce the frame. ui.window.scheduleFrame(); } else if (pointer.type == PointerType.up) { - color = new ui.Color.fromARGB(255, 0, 255, 0); + // Similarly, if the pointer went up, we change the color of the circle to + // green and schedule a frame. It's harmless to call scheduleFrame many + // times because the engine will ignore redundant requests up until the + // point where the engine calls onBeginFrame, which signals the boundary + // between one frame and another. + color = const ui.Color(0xFF00FF00); ui.window.scheduleFrame(); } } } +// This function is the primary entry point to your application. The engine +// calls main() as soon as it has loaded your code. void main() { + // Print statements go either go to stdout or to the system log, as + // appropriate for the operating system. print('Hello, world'); - color = new ui.Color.fromARGB(255, 0, 255, 0); + color = const ui.Color(0xFF00FF00); + // The engine calls onBeginFrame whenever it wants us to produce a frame. ui.window.onBeginFrame = beginFrame; - ui.window.onPopRoute = handlePopRoute; + // The engine calls onPointerPacket whenever it had updated information about + // the pointers directed at our app. ui.window.onPointerPacket = handlePointerPacket; + // Here we kick off the whole process by asking the engine to schedule a new + // frame. The engine will eventually call onBeginFrame when it is time for us + // to actually produce the frame. ui.window.scheduleFrame(); } diff --git a/examples/layers/raw/spinning_square.dart b/examples/layers/raw/spinning_square.dart index 7ebbac22b3..c4f464a880 100644 --- a/examples/layers/raw/spinning_square.dart +++ b/examples/layers/raw/spinning_square.dart @@ -2,49 +2,60 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:developer'; +// This example shows how to perform a simple animation using the raw interface +// to the engine. + import 'dart:math' as math; import 'dart:typed_data'; import 'dart:ui' as ui; -Duration timeBase = null; - void beginFrame(Duration timeStamp) { - Timeline.timeSync('beginFrame', () { - if (timeBase == null) - timeBase = timeStamp; - double delta = (timeStamp - timeBase).inMicroseconds / Duration.MICROSECONDS_PER_MILLISECOND; + // The timeStamp argument to beginFrame indicates the timing information we + // should use to clock our animations. It's important to use timeStamp rather + // than reading the system time because we want all the parts of the system to + // coordinate the timings of their animations. If each component read the + // system clock independently, the animations that we processed later would be + // slightly ahead of the animations we processed earlier. - // paint - ui.Rect paintBounds = ui.Point.origin & ui.window.size; - ui.PictureRecorder recorder = new ui.PictureRecorder(); - ui.Canvas canvas = new ui.Canvas(recorder, paintBounds); - canvas.translate(paintBounds.width / 2.0, paintBounds.height / 2.0); - canvas.rotate(math.PI * delta / 1800); - canvas.drawRect(new ui.Rect.fromLTRB(-100.0, -100.0, 100.0, 100.0), - new ui.Paint()..color = const ui.Color.fromARGB(255, 0, 255, 0)); - ui.Picture picture = recorder.endRecording(); + // PAINT - // composite - final double devicePixelRatio = ui.window.devicePixelRatio; - ui.Rect sceneBounds = new ui.Rect.fromLTWH( - 0.0, - 0.0, - ui.window.size.width * devicePixelRatio, - ui.window.size.height * devicePixelRatio - ); - Float64List deviceTransform = new Float64List(16) - ..[0] = devicePixelRatio - ..[5] = devicePixelRatio - ..[10] = 1.0 - ..[15] = 1.0; - ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(sceneBounds) - ..pushTransform(deviceTransform) - ..addPicture(ui.Offset.zero, picture) - ..pop(); - ui.window.render(sceneBuilder.build()); - }); + ui.Rect paintBounds = ui.Point.origin & ui.window.size; + ui.PictureRecorder recorder = new ui.PictureRecorder(); + ui.Canvas canvas = new ui.Canvas(recorder, paintBounds); + canvas.translate(paintBounds.width / 2.0, paintBounds.height / 2.0); + // Here we determine the rotation according to the timeStamp given to us by + // the engine. + double t = timeStamp.inMicroseconds / Duration.MICROSECONDS_PER_MILLISECOND / 1800.0; + canvas.rotate(math.PI * (t % 1.0)); + + canvas.drawRect(new ui.Rect.fromLTRB(-100.0, -100.0, 100.0, 100.0), + new ui.Paint()..color = const ui.Color.fromARGB(255, 0, 255, 0)); + ui.Picture picture = recorder.endRecording(); + + // COMPOSITE + + final double devicePixelRatio = ui.window.devicePixelRatio; + ui.Rect sceneBounds = new ui.Rect.fromLTWH( + 0.0, + 0.0, + ui.window.size.width * devicePixelRatio, + ui.window.size.height * devicePixelRatio + ); + Float64List deviceTransform = new Float64List(16) + ..[0] = devicePixelRatio + ..[5] = devicePixelRatio + ..[10] = 1.0 + ..[15] = 1.0; + ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(sceneBounds) + ..pushTransform(deviceTransform) + ..addPicture(ui.Offset.zero, picture) + ..pop(); + ui.window.render(sceneBuilder.build()); + + // After rendering the current frame of the animation, we ask the engine to + // schedule another frame. The engine will call beginFrame again when its time + // to produce the next frame. ui.window.scheduleFrame(); } diff --git a/examples/layers/raw/text.dart b/examples/layers/raw/text.dart index ce9ddc8d7e..24e90fbc9d 100644 --- a/examples/layers/raw/text.dart +++ b/examples/layers/raw/text.dart @@ -2,9 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This example shows how to draw some bi-directional text using the raw +// interface to the engine. + import 'dart:ui' as ui; import 'dart:typed_data'; +// A paragraph represents a rectangular region that contains some text. ui.Paragraph paragraph; ui.Picture paint(ui.Rect paintBounds) { @@ -15,8 +19,9 @@ ui.Picture paint(ui.Rect paintBounds) { canvas.drawRect(new ui.Rect.fromLTRB(-100.0, -100.0, 100.0, 100.0), new ui.Paint()..color = const ui.Color.fromARGB(255, 0, 255, 0)); - canvas.translate(paragraph.maxWidth / -2.0, (paragraph.maxWidth / 2.0) - 125); - paragraph.paint(canvas, ui.Offset.zero); + // The paint method of Pargraph draws the contents of the paragraph unto the + // given canvas. + paragraph.paint(canvas, new ui.Offset(paragraph.maxWidth / -2.0, (paragraph.maxWidth / 2.0) - 125)); return recorder.endRecording(); } @@ -49,19 +54,37 @@ void beginFrame(Duration timeStamp) { } void main() { + // To create a paragraph of text, we use ParagraphBuilder. ui.ParagraphBuilder builder = new ui.ParagraphBuilder() + // We first push a style that turns the text blue. ..pushStyle(new ui.TextStyle(color: const ui.Color(0xFF0000FF))) - ..addText("Hello, ") + ..addText('Hello, ') + // The next run of text will be bold. ..pushStyle(new ui.TextStyle(fontWeight: ui.FontWeight.bold)) - ..addText("world. ") + ..addText('world. ') + // The pop() command signals the end of the bold styling. ..pop() - ..addText("هذا هو قليلا طويلة من النص الذي يجب التفاف .") + // We add text to the paragraph in logical order. The paragraph object + // understands bi-directional text and will compute the visual ordering + // during layout. + ..addText('هذا هو قليلا طويلة من النص الذي يجب التفاف .') + // The second pop() removes the blue color. ..pop() - ..addText(" و أكثر قليلا لجعله أطول. "); + // We can add more text with the default styling. + ..addText(' و أكثر قليلا لجعله أطول. ') + ..addText('สวัสดี'); + // When we're done adding styles and text, we build the Paragraph object, at + // which time we can apply styling that affects the entire paragraph, such as + // left, right, or center alignment. Once built, the contents of the paragraph + // cannot be altered, but sizing and positioning information can be updated. paragraph = builder.build(new ui.ParagraphStyle()) + // Next, we supply a maximum width that the text is permitted to occupy. ..maxWidth = 180.0 + // ... and we ask the paragraph to the visual position of each its glyphs as + // well as its overall size, subject to its sizing constraints. ..layout(); + // Finally, we register our beginFrame callback and kick off the first frame. ui.window.onBeginFrame = beginFrame; ui.window.scheduleFrame(); } diff --git a/examples/layers/rendering/autolayout.dart b/examples/layers/rendering/autolayout.dart index 17a90c35b5..c2c30c540b 100644 --- a/examples/layers/rendering/autolayout.dart +++ b/examples/layers/rendering/autolayout.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 'dart:ui'; +// This example shows how to use the Cassowary autolayout system directly in the +// underlying render tree. import 'package:cassowary/cassowary.dart' as al; import 'package:flutter/rendering.dart'; diff --git a/examples/layers/rendering/custom_coordinate_systems.dart b/examples/layers/rendering/custom_coordinate_systems.dart index e28dfbc164..8b5d295da3 100644 --- a/examples/layers/rendering/custom_coordinate_systems.dart +++ b/examples/layers/rendering/custom_coordinate_systems.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This example shows how to build a render tree with a non-cartesian coordinate +// system. Most of the guts of this examples are in src/sector_layout.dart. + import 'package:flutter/rendering.dart'; import 'src/sector_layout.dart'; diff --git a/examples/layers/rendering/flex_layout.dart b/examples/layers/rendering/flex_layout.dart index 9066762127..945c83ea34 100644 --- a/examples/layers/rendering/flex_layout.dart +++ b/examples/layers/rendering/flex_layout.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This example shows how to use flex layout directly in the underlying render +// tree. + import 'package:flutter/rendering.dart'; import 'src/solid_color_box.dart'; diff --git a/examples/layers/rendering/hello_world.dart b/examples/layers/rendering/hello_world.dart index 904414a73b..84f39a6424 100644 --- a/examples/layers/rendering/hello_world.dart +++ b/examples/layers/rendering/hello_world.dart @@ -2,12 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This example shows how to show the text 'Hello, world.' using the underlying +// render tree. + import 'package:flutter/rendering.dart'; void main() { + // We use RenderingFlutterBinding to attach the render tree to the window. new RenderingFlutterBinding( + // The root of our render tree is a RenderPositionedBox, which centers its + // child both vertically and horizontally. root: new RenderPositionedBox( alignment: const FractionalOffset(0.5, 0.5), + // We use a RenderParagraph to display the text "Hello, world." without + // any explicit styling. child: new RenderParagraph(new PlainTextSpan('Hello, world.')) ) ); diff --git a/examples/layers/rendering/spinning_square.dart b/examples/layers/rendering/spinning_square.dart index 04df327c99..5ed6ffd44c 100644 --- a/examples/layers/rendering/spinning_square.dart +++ b/examples/layers/rendering/spinning_square.dart @@ -2,43 +2,59 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This example shows how to perform a simple animation using the underlying +// render tree. + import 'dart:math' as math; import 'package:flutter/animation.dart'; import 'package:flutter/rendering.dart'; void main() { - // A green box... + // We first create a render object that represents a green box. RenderBox green = new RenderDecoratedBox( decoration: const BoxDecoration(backgroundColor: const Color(0xFF00FF00)) ); - // of a certain size... + // Second, we wrap that green box in a render object that forces the green box + // to have a specific size. RenderBox square = new RenderConstrainedBox( additionalConstraints: const BoxConstraints.tightFor(width: 200.0, height: 200.0), child: green ); - // With a given rotation (starts off as the identity transform)... + // Third, we wrap the sized green square in a render object that applies rotation + // transform before painting its child. Each frame of the animation, we'll + // update the transform of this render object to cause the green square to + // spin. RenderTransform spin = new RenderTransform( transform: new Matrix4.identity(), alignment: const FractionalOffset(0.5, 0.5), child: square ); - // centered... + // Finally, we center the spinning green square... RenderBox root = new RenderPositionedBox( alignment: const FractionalOffset(0.5, 0.5), child: spin ); - // on the screen. + // and attach it to the window. new RenderingFlutterBinding(root: root); - // A repeating animation every 1800 milliseconds... + // To make the square spin, we use an animation that repeats every 1800 + // milliseconds. AnimationController animation = new AnimationController( duration: const Duration(milliseconds: 1800) )..repeat(); - // From 0.0 to math.PI. + // The animation will produce a value between 0.0 and 1.0 each frame, but we + // want to rotate the square using a value between 0.0 and math.PI. To change + // the range of the animation, we use a Tween. Tween tween = new Tween(begin: 0.0, end: math.PI); + // We add a listener to the animation, which will be called every time the + // animation ticks. animation.addListener(() { - // Each frame of the animation, set the rotation of the square. + // This code runs every tick of the animation and sets a new transform on + // the "spin" render object by evaluating the tween on the current value + // of the animation. Setting this value will mark a number of dirty bits + // inside the render tree, which cause the render tree to repaint with the + // new transform value this frame. spin.transform = new Matrix4.rotationZ(tween.evaluate(animation)); }); } diff --git a/examples/layers/rendering/touch_input.dart b/examples/layers/rendering/touch_input.dart index 54304b1bad..68f442122e 100644 --- a/examples/layers/rendering/touch_input.dart +++ b/examples/layers/rendering/touch_input.dart @@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/material.dart'; +// This example shows how to use process input events in the underlying render +// tree. + +import 'package:flutter/material.dart'; // Imported just for its color palette. import 'package:flutter/rendering.dart'; // Material design colors. :p @@ -34,12 +37,23 @@ class Dot { } /// A render object that draws dots under each pointer. -class RenderDots extends RenderConstrainedBox { - RenderDots() : super(additionalConstraints: const BoxConstraints.expand()); +class RenderDots extends RenderBox { + RenderDots(); /// State to remember which dots to paint. final Map _dots = {}; + /// Indicates that the size of this render object depends only on the + /// layout constraints provided by the parent. + bool get sizedByParent => true; + + /// By selecting the biggest value permitted by the incomming constraints + /// during layout, this function makes this render object as large as + /// possible (i.e., fills the entire screen). + void performResize() { + size = constraints.biggest; + } + /// Makes this render object hittable so that it receives pointer events. bool hitTestSelf(Point position) => true; @@ -49,6 +63,10 @@ class RenderDots extends RenderConstrainedBox { if (event is PointerDownEvent) { Color color = _kColors[event.pointer.remainder(_kColors.length)]; _dots[event.pointer] = new Dot(color: color)..update(event); + // We call markNeedsPaint to indicate that our painting commands have + // changed and that paint needs to be called before displaying a new frame + // to the user. It's harmless to call markNeedsPaint multiple times + // because the render tree will ignore redundant calls. markNeedsPaint(); } else if (event is PointerUpEvent || event is PointerCancelEvent) { _dots.remove(event.pointer); @@ -62,31 +80,50 @@ class RenderDots extends RenderConstrainedBox { /// Issues new painting commands. void paint(PaintingContext context, Offset offset) { final Canvas canvas = context.canvas; + // The "size" property indicates the size of that this render box was + // alotted during layout. Here we paint our bounds white. Notice that we're + // located at "offset" from the origin of the canvas' coordinate system. + // Passing offset during the render tree's paint walk is an optimization to + // avoid having to change the origin of the canvas's coordinate system too + // often. canvas.drawRect(offset & size, new Paint()..color = const Color(0xFFFFFFFF)); + + // We iterate through our model and paint each dot. for (Dot dot in _dots.values) dot.paint(canvas, offset); - super.paint(context, offset); } } void main() { + // Create some styled text to tell the user to interact with the app. RenderParagraph paragraph = new RenderParagraph( new StyledTextSpan( new TextStyle(color: Colors.black87), - [ new PlainTextSpan("Touch me!") ] + [ new PlainTextSpan("Touch me!") ] ) ); + // A stack is a render object that layers its children on top of each other. + // The bottom later is our RenderDots object, and on top of that we show the + // text. RenderStack stack = new RenderStack( children: [ new RenderDots(), paragraph, ] ); - // Prevent the RenderParagraph from filling the whole screen so - // that it doesn't eat events. + // The "parentData" field of a render object is controlled by the render + // object's parent render object. Now that we've added the paragraph as a + // child of the RenderStack, the paragraph's parentData field has been + // populated with a StackParentData, which we can use to provide input to the + // stack's layout algorithm. + // + // We use the StackParentData of the paragraph to position the text in the top + // left corner of the screen. final StackParentData paragraphParentData = paragraph.parentData; paragraphParentData ..top = 40.0 ..left = 20.0; + + // Finally, we attach the render tree we've built to the screen. new RenderingFlutterBinding(root: stack); }