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, 0.0, 0.0, 0.0, |
77 | 0.0, 0.5, 0.0, 0.0, |
78 | 0.0, 0.0, 1.0, 0.0, |
79 | 0.0, 0.0, 0.0, 1.0, |
80 | ]); |
81 | canvas.transform(scaleMatrix); |
82 | |
83 | // Sets paint to transparent yellow. |
84 | paint.color = const ui.Color.fromARGB(128, 0, 255, 0); |
85 | |
86 | // Draws a transparent yellow circle. |
87 | canvas.drawCircle(ui.Offset.zero, radius, paint); |
88 | |
89 | // Restores the transform from before `save` was called. |
90 | canvas.restore(); |
91 | |
92 | // Sets paint to transparent red. |
93 | paint.color = const ui.Color.fromARGB(128, 255, 0, 0); |
94 | |
95 | // This circle is drawn on top of the previous layer that contains |
96 | // the rectangle and smaller circle. |
97 | canvas.drawCircle(const ui.Offset(150.0, 300.0), radius, paint); |
98 | |
99 | // When we're done issuing painting commands, we end the recording and receive |
100 | // a Picture, which is an immutable record of the commands we've issued. You |
101 | // can draw a Picture into another canvas or include it as part of a |
102 | // composited scene. |
103 | return recorder.endRecording(); |
104 | } |
105 | |
106 | ui.Scene composite(ui.Picture picture, ui.Rect paintBounds) { |
107 | final double devicePixelRatio = view.devicePixelRatio; |
108 | final Float64List deviceTransform = Float64List(16) |
109 | ..[0] = devicePixelRatio |
110 | ..[5] = devicePixelRatio |
111 | ..[10] = 1.0 |
112 | ..[15] = 1.0; |
113 | final ui.SceneBuilder sceneBuilder = ui.SceneBuilder() |
114 | ..pushTransform(deviceTransform) |
115 | ..addPicture(ui.Offset.zero, picture) |
116 | ..pop(); |
117 | return sceneBuilder.build(); |
118 | } |
119 | |
120 | void beginFrame(Duration timeStamp) { |
121 | final ui.Rect paintBounds = ui.Offset.zero & (view.physicalSize / view.devicePixelRatio); |
122 | final ui.Picture picture = paint(paintBounds); |
123 | final ui.Scene scene = composite(picture, paintBounds); |
124 | view.render(scene); |
125 | } |
126 | |
127 | void main() { |
128 | // TODO(goderbauer): Create a window if embedder doesn't provide an implicit view to draw into. |
129 | assert(ui.PlatformDispatcher.instance.implicitView != null); |
130 | view = ui.PlatformDispatcher.instance.implicitView!; |
131 | |
132 | ui.PlatformDispatcher.instance |
133 | ..onBeginFrame = beginFrame |
134 | ..scheduleFrame(); |
135 | } |
136 | |