| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | // This example shows how to use the ui.Canvas interface to draw various shapes |
| 6 | // with gradients and transforms. |
| 7 | |
| 8 | import 'dart:math' as math; |
| 9 | import 'dart:typed_data'; |
| 10 | import 'dart:ui' as ui; |
| 11 | |
| 12 | // The FlutterView into which this example will draw; set in the main method. |
| 13 | late final ui.FlutterView view; |
| 14 | |
| 15 | ui.Picture paint(ui.Rect paintBounds) { |
| 16 | // First we create a PictureRecorder to record the commands we're going to |
| 17 | // feed in the canvas. The PictureRecorder will eventually produce a Picture, |
| 18 | // which is an immutable record of those commands. |
| 19 | final ui.PictureRecorder recorder = ui.PictureRecorder(); |
| 20 | |
| 21 | // Next, we create a canvas from the recorder. The canvas is an interface |
| 22 | // which can receive drawing commands. The canvas interface is modeled after |
| 23 | // the SkCanvas interface from Skia. The paintBounds establishes a "cull rect" |
| 24 | // for the canvas, which lets the implementation discard any commands that |
| 25 | // are entirely outside this rectangle. |
| 26 | final ui.Canvas canvas = ui.Canvas(recorder, paintBounds); |
| 27 | |
| 28 | final ui.Paint paint = ui.Paint(); |
| 29 | canvas.drawPaint(ui.Paint()..color = const ui.Color(0xFFFFFFFF)); |
| 30 | |
| 31 | final ui.Size size = paintBounds.size; |
| 32 | final ui.Offset mid = size.center(ui.Offset.zero); |
| 33 | final double radius = size.shortestSide / 2.0; |
| 34 | |
| 35 | final double devicePixelRatio = view.devicePixelRatio; |
| 36 | final ui.Size logicalSize = view.physicalSize / devicePixelRatio; |
| 37 | |
| 38 | // Saves a copy of current transform onto the save stack. |
| 39 | canvas.save(); |
| 40 | |
| 41 | // Transforms that occur after this point apply only to the |
| 42 | // yellow-bluish rectangle. |
| 43 | |
| 44 | // This line will cause the transform to shift entirely outside the paint |
| 45 | // boundaries, which will cause the canvas interface to discard its |
| 46 | // commands. Comment it out to see it on screen. |
| 47 | canvas.translate(-mid.dx / 2.0, logicalSize.height * 2.0); |
| 48 | |
| 49 | // Clips the current transform. |
| 50 | canvas.clipRect( |
| 51 | ui.Rect.fromLTRB(0, radius + 50, logicalSize.width, logicalSize.height), |
| 52 | clipOp: ui.ClipOp.difference, |
| 53 | ); |
| 54 | |
| 55 | // Shifts the coordinate space of and rotates the current transform. |
| 56 | canvas.translate(mid.dx, mid.dy); |
| 57 | canvas.rotate(math.pi / 4); |
| 58 | |
| 59 | final ui.Gradient yellowBlue = ui.Gradient.linear( |
| 60 | ui.Offset(-radius, -radius), |
| 61 | ui.Offset.zero, |
| 62 | <ui.Color>[const ui.Color(0xFFFFFF00), const ui.Color(0xFF0000FF)], |
| 63 | ); |
| 64 | |
| 65 | // Draws a yellow-bluish rectangle. |
| 66 | canvas.drawRect( |
| 67 | ui.Rect.fromLTRB(-radius, -radius, radius, radius), |
| 68 | ui.Paint()..shader = yellowBlue, |
| 69 | ); |
| 70 | |
| 71 | // Transforms that occur after this point apply only to the |
| 72 | // yellow circle. |
| 73 | |
| 74 | // Scale x and y by 0.5. |
| 75 | final Float64List scaleMatrix = Float64List.fromList(<double>[ |
| 76 | 0.5, |
| 77 | 0.0, |
| 78 | 0.0, |
| 79 | 0.0, |
| 80 | 0.0, |
| 81 | 0.5, |
| 82 | 0.0, |
| 83 | 0.0, |
| 84 | 0.0, |
| 85 | 0.0, |
| 86 | 1.0, |
| 87 | 0.0, |
| 88 | 0.0, |
| 89 | 0.0, |
| 90 | 0.0, |
| 91 | 1.0, |
| 92 | ]); |
| 93 | canvas.transform(scaleMatrix); |
| 94 | |
| 95 | // Sets paint to transparent yellow. |
| 96 | paint.color = const ui.Color.fromARGB(128, 0, 255, 0); |
| 97 | |
| 98 | // Draws a transparent yellow circle. |
| 99 | canvas.drawCircle(ui.Offset.zero, radius, paint); |
| 100 | |
| 101 | // Restores the transform from before `save` was called. |
| 102 | canvas.restore(); |
| 103 | |
| 104 | // Sets paint to transparent red. |
| 105 | paint.color = const ui.Color.fromARGB(128, 255, 0, 0); |
| 106 | |
| 107 | // This circle is drawn on top of the previous layer that contains |
| 108 | // the rectangle and smaller circle. |
| 109 | canvas.drawCircle(const ui.Offset(150.0, 300.0), radius, paint); |
| 110 | |
| 111 | // When we're done issuing painting commands, we end the recording and receive |
| 112 | // a Picture, which is an immutable record of the commands we've issued. You |
| 113 | // can draw a Picture into another canvas or include it as part of a |
| 114 | // composited scene. |
| 115 | return recorder.endRecording(); |
| 116 | } |
| 117 | |
| 118 | ui.Scene composite(ui.Picture picture, ui.Rect paintBounds) { |
| 119 | final double devicePixelRatio = view.devicePixelRatio; |
| 120 | final Float64List deviceTransform = Float64List(16) |
| 121 | ..[0] = devicePixelRatio |
| 122 | ..[5] = devicePixelRatio |
| 123 | ..[10] = 1.0 |
| 124 | ..[15] = 1.0; |
| 125 | final ui.SceneBuilder sceneBuilder = ui.SceneBuilder() |
| 126 | ..pushTransform(deviceTransform) |
| 127 | ..addPicture(ui.Offset.zero, picture) |
| 128 | ..pop(); |
| 129 | return sceneBuilder.build(); |
| 130 | } |
| 131 | |
| 132 | void beginFrame(Duration timeStamp) { |
| 133 | final ui.Rect paintBounds = ui.Offset.zero & (view.physicalSize / view.devicePixelRatio); |
| 134 | final ui.Picture picture = paint(paintBounds); |
| 135 | final ui.Scene scene = composite(picture, paintBounds); |
| 136 | view.render(scene); |
| 137 | } |
| 138 | |
| 139 | void main() { |
| 140 | // TODO(goderbauer): Create a window if embedder doesn't provide an implicit view to draw into. |
| 141 | assert(ui.PlatformDispatcher.instance.implicitView != null); |
| 142 | view = ui.PlatformDispatcher.instance.implicitView!; |
| 143 | |
| 144 | ui.PlatformDispatcher.instance |
| 145 | ..onBeginFrame = beginFrame |
| 146 | ..scheduleFrame(); |
| 147 | } |
| 148 | |