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