• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 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
5part of engine;
6
7/// Generic callback signature, used by [_futurize].
8typedef Callback<T> = void Function(T result);
9
10/// Signature for a method that receives a [_Callback].
11///
12/// Return value should be null on success, and a string error message on
13/// failure.
14typedef Callbacker<T> = String Function(Callback<T> callback);
15
16/// Converts a method that receives a value-returning callback to a method that
17/// returns a Future.
18///
19/// Return a [String] to cause an [Exception] to be synchronously thrown with
20/// that string as a message.
21///
22/// If the callback is called with null, the future completes with an error.
23///
24/// Example usage:
25///
26/// ```dart
27/// typedef IntCallback = void Function(int result);
28///
29/// String _doSomethingAndCallback(IntCallback callback) {
30///   new Timer(new Duration(seconds: 1), () { callback(1); });
31/// }
32///
33/// Future<int> doSomething() {
34///   return _futurize(_doSomethingAndCallback);
35/// }
36/// ```
37Future<T> futurize<T>(Callbacker<T> callbacker) {
38  final Completer<T> completer = Completer<T>.sync();
39  final String error = callbacker((T t) {
40    if (t == null) {
41      completer.completeError(Exception('operation failed'));
42    } else {
43      completer.complete(t);
44    }
45  });
46  if (error != null) {
47    throw Exception(error);
48  }
49  return completer.future;
50}
51
52/// Converts [matrix] to CSS transform value.
53String matrix4ToCssTransform(Matrix4 matrix) {
54  return float64ListToCssTransform(matrix.storage);
55}
56
57/// Returns `true` is the [matrix] describes an identity transformation.
58bool isIdentityFloat64ListTransform(Float64List matrix) {
59  assert(matrix.length == 16);
60  final Float64List m = matrix;
61  return m[0] == 1.0 &&
62      m[1] == 0.0 &&
63      m[2] == 0.0 &&
64      m[3] == 0.0 &&
65      m[4] == 0.0 &&
66      m[5] == 1.0 &&
67      m[6] == 0.0 &&
68      m[7] == 0.0 &&
69      m[8] == 0.0 &&
70      m[9] == 0.0 &&
71      m[10] == 1.0 &&
72      m[11] == 0.0 &&
73      m[12] == 0.0 &&
74      m[13] == 0.0 &&
75      m[14] == 0.0 &&
76      m[15] == 1.0;
77}
78
79/// Converts [matrix] to CSS transform value.
80String float64ListToCssTransform(Float64List matrix) {
81  assert(matrix.length == 16);
82  final Float64List m = matrix;
83  if (m[0] == 1.0 &&
84      m[1] == 0.0 &&
85      m[2] == 0.0 &&
86      m[3] == 0.0 &&
87      m[4] == 0.0 &&
88      m[5] == 1.0 &&
89      m[6] == 0.0 &&
90      m[7] == 0.0 &&
91      m[8] == 0.0 &&
92      m[9] == 0.0 &&
93      m[10] == 1.0 &&
94      m[11] == 0.0 &&
95      // 12 can be anything
96      // 13 can be anything
97      m[14] == 0.0 &&
98      m[15] == 1.0) {
99    final double tx = m[12];
100    final double ty = m[13];
101    return 'translate(${tx}px, ${ty}px)';
102  } else {
103    return 'matrix3d(${m[0]},${m[1]},${m[2]},${m[3]},${m[4]},${m[5]},${m[6]},${m[7]},${m[8]},${m[9]},${m[10]},${m[11]},${m[12]},${m[13]},${m[14]},${m[15]})';
104  }
105}
106
107bool get assertionsEnabled {
108  bool k = false;
109  assert(k = true);
110  return k;
111}
112
113/// Converts a rectangular clip specified in local coordinates to screen
114/// coordinates given the effective [transform].
115///
116/// The resulting clip is a rectangle aligned to the pixel grid, i.e. two of
117/// its sides are vertical and two are horizontal. In the presence of rotations
118/// the rectangle is inflated such that it fits the rotated rectangle.
119ui.Rect localClipRectToGlobalClip({ui.Rect localClip, Matrix4 transform}) {
120  return localClipToGlobalClip(
121    localLeft: localClip.left,
122    localTop: localClip.top,
123    localRight: localClip.right,
124    localBottom: localClip.bottom,
125    transform: transform,
126  );
127}
128
129/// Converts a rectangular clip specified in local coordinates to screen
130/// coordinates given the effective [transform].
131///
132/// This is the same as [localClipRectToGlobalClip], except that the local clip
133/// rect is specified in terms of left, top, right, and bottom edge offsets.
134ui.Rect localClipToGlobalClip({
135  double localLeft,
136  double localTop,
137  double localRight,
138  double localBottom,
139  Matrix4 transform,
140}) {
141  assert(localLeft != null);
142  assert(localTop != null);
143  assert(localRight != null);
144  assert(localBottom != null);
145
146  // Construct a matrix where each row represents a vector pointing at
147  // one of the four corners of the (left, top, right, bottom) rectangle.
148  // Using the row-major order allows us to multiply the matrix in-place
149  // by the transposed current transformation matrix. The vector_math
150  // library has a convenience function `multiplyTranspose` that performs
151  // the multiplication without copying. This way we compute the positions
152  // of all four points in a single matrix-by-matrix multiplication at the
153  // cost of one `Matrix4` instance and one `Float64List` instance.
154  //
155  // The rejected alternative was to use `Vector3` for each point and
156  // multiply by the current transform. However, that would cost us four
157  // `Vector3` instances, four `Float64List` instances, and four
158  // matrix-by-vector multiplications.
159  //
160  // `Float64List` initializes the array with zeros, so we do not have to
161  // fill in every single element.
162  final Float64List pointData = Float64List(16);
163
164  // Row 0: top-left
165  pointData[0] = localLeft;
166  pointData[4] = localTop;
167  pointData[12] = 1;
168
169  // Row 1: top-right
170  pointData[1] = localRight;
171  pointData[5] = localTop;
172  pointData[13] = 1;
173
174  // Row 2: bottom-left
175  pointData[2] = localLeft;
176  pointData[6] = localBottom;
177  pointData[14] = 1;
178
179  // Row 3: bottom-right
180  pointData[3] = localRight;
181  pointData[7] = localBottom;
182  pointData[15] = 1;
183
184  final Matrix4 pointMatrix = Matrix4.fromFloat64List(pointData);
185  pointMatrix.multiplyTranspose(transform);
186
187  return ui.Rect.fromLTRB(
188    math.min(math.min(math.min(pointData[0], pointData[1]), pointData[2]),
189        pointData[3]),
190    math.min(math.min(math.min(pointData[4], pointData[5]), pointData[6]),
191        pointData[7]),
192    math.max(math.max(math.max(pointData[0], pointData[1]), pointData[2]),
193        pointData[3]),
194    math.max(math.max(math.max(pointData[4], pointData[5]), pointData[6]),
195        pointData[7]),
196  );
197}
198
199/// Returns true if [rect] contains every point that is also contained by the
200/// [other] rect.
201///
202/// Points on the edges of both rectangles are also considered. For example,
203/// this returns true when the two rects are equal to each other.
204bool rectContainsOther(ui.Rect rect, ui.Rect other) {
205  return rect.left <= other.left &&
206      rect.top <= other.top &&
207      rect.right >= other.right &&
208      rect.bottom >= other.bottom;
209}
210
211/// Counter used for generating clip path id inside an svg <defs> tag.
212int _clipIdCounter = 0;
213
214/// Converts Path to svg element that contains a clip-path definition.
215///
216/// Calling this method updates [_clipIdCounter]. The HTML id of the generated
217/// clip is set to "svgClip${_clipIdCounter}", e.g. "svgClip123".
218String _pathToSvgClipPath(ui.Path path,
219    {double offsetX = 0, double offsetY = 0}) {
220  _clipIdCounter += 1;
221  final ui.Rect bounds = path.getBounds();
222  final StringBuffer sb = StringBuffer();
223  sb.write('<svg width="${bounds.right}" height="${bounds.bottom}" '
224      'style="position:absolute">');
225  sb.write('<defs>');
226
227  final String clipId = 'svgClip$_clipIdCounter';
228  sb.write('<clipPath id=$clipId>');
229
230  sb.write('<path fill="#FFFFFF" d="');
231  pathToSvg(path, sb, offsetX: offsetX, offsetY: offsetY);
232  sb.write('"></path></clipPath></defs></svg');
233  return sb.toString();
234}
235