| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 import 'dart:math' as math; |
| 5 import 'dart:sky' as sky; | 6 import 'dart:sky' as sky; |
| 6 import 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path; | 7 import 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path; |
| 7 | 8 |
| 8 import 'shadows.dart'; | 9 import 'shadows.dart'; |
| 10 import 'package:sky/mojo/net/image_cache.dart' as image_cache; |
| 9 | 11 |
| 10 class BorderSide { | 12 class BorderSide { |
| 11 const BorderSide({ | 13 const BorderSide({ |
| 12 this.color: const Color(0xFF000000), | 14 this.color: const Color(0xFF000000), |
| 13 this.width: 1.0 | 15 this.width: 1.0 |
| 14 }); | 16 }); |
| 15 final Color color; | 17 final Color color; |
| 16 final double width; | 18 final double width; |
| 17 | 19 |
| 18 static const none = const BorderSide(width: 0.0); | 20 static const none = const BorderSide(width: 0.0); |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 113 this.colorStops, this.tileMode); | 115 this.colorStops, this.tileMode); |
| 114 } | 116 } |
| 115 | 117 |
| 116 final Point center; | 118 final Point center; |
| 117 final double radius; | 119 final double radius; |
| 118 final List<Color> colors; | 120 final List<Color> colors; |
| 119 final List<double> colorStops; | 121 final List<double> colorStops; |
| 120 final sky.TileMode tileMode; | 122 final sky.TileMode tileMode; |
| 121 } | 123 } |
| 122 | 124 |
| 125 enum BackgroundFit { fill, contain, cover, none, scaleDown } |
| 126 |
| 127 enum BackgroundRepeat { repeat, repeatX, repeatY, noRepeat } |
| 128 |
| 129 // TODO(jackson): We should abstract this out into a separate class |
| 130 // that handles the image caching and so forth, which has callbacks |
| 131 // for "size changed" and "image changed". This would also enable us |
| 132 // to do animated images. |
| 133 |
| 134 class BackgroundImage { |
| 135 final String src; |
| 136 final BackgroundFit fit; |
| 137 final BackgroundRepeat repeat; |
| 138 BackgroundImage({ |
| 139 this.src, |
| 140 this.fit: BackgroundFit.scaleDown, |
| 141 this.repeat: BackgroundRepeat.noRepeat |
| 142 }) { |
| 143 image_cache.load(src, (image) { |
| 144 if (image == null) |
| 145 return; |
| 146 _image = image; |
| 147 _size = new Size(image.width.toDouble(), image.height.toDouble()); |
| 148 for (Function listener in _listeners) { |
| 149 listener(); |
| 150 } |
| 151 }); |
| 152 } |
| 153 |
| 154 sky.Image _image; |
| 155 sky.Image get image => _image; |
| 156 |
| 157 Size _size; |
| 158 |
| 159 final List<Function> _listeners = new List<Function>(); |
| 160 |
| 161 void addChangeListener(Function listener) { |
| 162 _listeners.add(listener); |
| 163 } |
| 164 |
| 165 void removeChangeListener(Function listener) { |
| 166 _listeners.remove(listener); |
| 167 } |
| 168 |
| 169 String toString() => 'BackgroundImage($src, $fit, $repeat)'; |
| 170 } |
| 171 |
| 123 enum Shape { rectangle, circle } | 172 enum Shape { rectangle, circle } |
| 124 | 173 |
| 125 // This must be immutable, because we won't notice when it changes | 174 // This must be immutable, because we won't notice when it changes |
| 126 class BoxDecoration { | 175 class BoxDecoration { |
| 127 const BoxDecoration({ | 176 const BoxDecoration({ |
| 128 this.backgroundColor, // null = don't draw background | 177 this.backgroundColor, // null = don't draw background color |
| 178 this.backgroundImage, // null = don't draw background image |
| 129 this.border, // null = don't draw border | 179 this.border, // null = don't draw border |
| 130 this.borderRadius, // null = use more efficient background drawing; note tha
t this must be null for circles | 180 this.borderRadius, // null = use more efficient background drawing; note tha
t this must be null for circles |
| 131 this.boxShadow, // null = don't draw shadows | 181 this.boxShadow, // null = don't draw shadows |
| 132 this.gradient, // null = don't allocate gradient objects | 182 this.gradient, // null = don't allocate gradient objects |
| 133 this.shape: Shape.rectangle | 183 this.shape: Shape.rectangle |
| 134 }); | 184 }); |
| 135 | 185 |
| 136 final Color backgroundColor; | 186 final Color backgroundColor; |
| 187 final BackgroundImage backgroundImage; |
| 137 final double borderRadius; | 188 final double borderRadius; |
| 138 final Border border; | 189 final Border border; |
| 139 final List<BoxShadow> boxShadow; | 190 final List<BoxShadow> boxShadow; |
| 140 final Gradient gradient; | 191 final Gradient gradient; |
| 141 final Shape shape; | 192 final Shape shape; |
| 142 | 193 |
| 143 String toString([String prefix = '']) { | 194 String toString([String prefix = '']) { |
| 144 List<String> result = []; | 195 List<String> result = []; |
| 145 if (backgroundColor != null) | 196 if (backgroundColor != null) |
| 146 result.add('${prefix}backgroundColor: $backgroundColor'); | 197 result.add('${prefix}backgroundColor: $backgroundColor'); |
| 198 if (backgroundImage != null) |
| 199 result.add('${prefix}backgroundImage: $backgroundImage'); |
| 147 if (border != null) | 200 if (border != null) |
| 148 result.add('${prefix}border: $border'); | 201 result.add('${prefix}border: $border'); |
| 149 if (borderRadius != null) | 202 if (borderRadius != null) |
| 150 result.add('${prefix}borderRadius: $borderRadius'); | 203 result.add('${prefix}borderRadius: $borderRadius'); |
| 151 if (boxShadow != null) | 204 if (boxShadow != null) |
| 152 result.add('${prefix}boxShadow: ${boxShadow.map((shadow) => shadow.toStrin
g())}'); | 205 result.add('${prefix}boxShadow: ${boxShadow.map((shadow) => shadow.toStrin
g())}'); |
| 153 if (gradient != null) | 206 if (gradient != null) |
| 154 result.add('${prefix}gradient: $gradient'); | 207 result.add('${prefix}gradient: $gradient'); |
| 155 if (shape != Shape.rectangle) | 208 if (shape != Shape.rectangle) |
| 156 result.add('${prefix}shape: $shape'); | 209 result.add('${prefix}shape: $shape'); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 192 | 245 |
| 193 if (_decoration.gradient != null) | 246 if (_decoration.gradient != null) |
| 194 paint.setShader(_decoration.gradient.createShader()); | 247 paint.setShader(_decoration.gradient.createShader()); |
| 195 | 248 |
| 196 _cachedBackgroundPaint = paint; | 249 _cachedBackgroundPaint = paint; |
| 197 } | 250 } |
| 198 | 251 |
| 199 return _cachedBackgroundPaint; | 252 return _cachedBackgroundPaint; |
| 200 } | 253 } |
| 201 | 254 |
| 202 void paint(sky.Canvas canvas, Rect rect) { | 255 void _paintBackgroundColor(sky.Canvas canvas, Rect rect) { |
| 203 if (_decoration.backgroundColor != null || _decoration.boxShadow != null || | 256 if (_decoration.backgroundColor != null || _decoration.boxShadow != null || |
| 204 _decoration.gradient != null) { | 257 _decoration.gradient != null) { |
| 205 switch (_decoration.shape) { | 258 switch (_decoration.shape) { |
| 206 case Shape.circle: | 259 case Shape.circle: |
| 207 assert(_decoration.borderRadius == null); | 260 assert(_decoration.borderRadius == null); |
| 208 Point center = rect.center; | 261 Point center = rect.center; |
| 209 double radius = rect.shortestSide / 2.0; | 262 double radius = rect.shortestSide / 2.0; |
| 210 canvas.drawCircle(center, radius, _backgroundPaint); | 263 canvas.drawCircle(center, radius, _backgroundPaint); |
| 211 break; | 264 break; |
| 212 case Shape.rectangle: | 265 case Shape.rectangle: |
| 213 if (_decoration.borderRadius == null) | 266 if (_decoration.borderRadius == null) |
| 214 canvas.drawRect(rect, _backgroundPaint); | 267 canvas.drawRect(rect, _backgroundPaint); |
| 215 else | 268 else |
| 216 canvas.drawRRect(new sky.RRect()..setRectXY(rect, _decoration.border
Radius, _decoration.borderRadius), _backgroundPaint); | 269 canvas.drawRRect(new sky.RRect()..setRectXY(rect, _decoration.border
Radius, _decoration.borderRadius), _backgroundPaint); |
| 217 break; | 270 break; |
| 218 } | 271 } |
| 219 } | 272 } |
| 273 } |
| 220 | 274 |
| 221 if (_decoration.border != null) { | 275 void _paintBackgroundImage(sky.Canvas canvas, Rect rect) { |
| 222 assert(_decoration.borderRadius == null); // TODO(abarth): Implement borde
rs with border radius. | 276 if (_decoration.backgroundImage == null) |
| 223 assert(_decoration.shape == Shape.rectangle); // TODO(ianh): Implement bor
ders on circles. | 277 return; |
| 224 | 278 sky.Image image = _decoration.backgroundImage.image; |
| 225 assert(_decoration.border.top != null); | 279 if (image != null) { |
| 226 assert(_decoration.border.right != null); | 280 Size bounds = rect.size; |
| 227 assert(_decoration.border.bottom != null); | 281 Size imageSize = _decoration.backgroundImage._size; |
| 228 assert(_decoration.border.left != null); | 282 Size src; |
| 229 | 283 Size dst; |
| 230 Paint paint = new Paint(); | 284 switch(_decoration.backgroundImage.fit) { |
| 231 Path path; | 285 case BackgroundFit.fill: |
| 232 | 286 src = imageSize; |
| 233 paint.color = _decoration.border.top.color; | 287 dst = bounds; |
| 234 path = new Path(); | 288 break; |
| 235 path.moveTo(rect.left, rect.top); | 289 case BackgroundFit.contain: |
| 236 path.lineTo(rect.left + _decoration.border.left.width, rect.top + _decorat
ion.border.top.width); | 290 src = imageSize; |
| 237 path.lineTo(rect.right - _decoration.border.right.width, rect.top + _decor
ation.border.top.width); | 291 if (bounds.width / bounds.height > src.width / src.height) { |
| 238 path.lineTo(rect.right, rect.top); | 292 dst = new Size(bounds.width, src.height * bounds.width / src.width); |
| 239 path.close(); | 293 } else { |
| 240 canvas.drawPath(path, paint); | 294 dst = new Size(src.width * bounds.height / src.height, bounds.height
); |
| 241 | 295 } |
| 242 paint.color = _decoration.border.right.color; | 296 break; |
| 243 path = new Path(); | 297 case BackgroundFit.cover: |
| 244 path.moveTo(rect.right, rect.top); | 298 if (bounds.width / bounds.height > imageSize.width / imageSize.height)
{ |
| 245 path.lineTo(rect.right - _decoration.border.right.width, rect.top + _decor
ation.border.top.width); | 299 src = new Size(imageSize.width, imageSize.width * bounds.height / bo
unds.width); |
| 246 path.lineTo(rect.right - _decoration.border.right.width, rect.bottom - _de
coration.border.bottom.width); | 300 } else { |
| 247 path.lineTo(rect.right, rect.bottom); | 301 src = new Size(imageSize.height * bounds.width / bounds.height, imag
eSize.height); |
| 248 path.close(); | 302 } |
| 249 canvas.drawPath(path, paint); | 303 dst = bounds; |
| 250 | 304 break; |
| 251 paint.color = _decoration.border.bottom.color; | 305 case BackgroundFit.none: |
| 252 path = new Path(); | 306 src = new Size(math.min(imageSize.width, bounds.width), |
| 253 path.moveTo(rect.right, rect.bottom); | 307 math.min(imageSize.height, bounds.height)); |
| 254 path.lineTo(rect.right - _decoration.border.right.width, rect.bottom - _de
coration.border.bottom.width); | 308 dst = src; |
| 255 path.lineTo(rect.left + _decoration.border.left.width, rect.bottom - _deco
ration.border.bottom.width); | 309 break; |
| 256 path.lineTo(rect.left, rect.bottom); | 310 case BackgroundFit.scaleDown: |
| 257 path.close(); | 311 src = imageSize; |
| 258 canvas.drawPath(path, paint); | 312 dst = bounds; |
| 259 | 313 if (src.height > dst.height) { |
| 260 paint.color = _decoration.border.left.color; | 314 dst = new Size(src.width * dst.height / src.height, src.height); |
| 261 path = new Path(); | 315 } |
| 262 path.moveTo(rect.left, rect.bottom); | 316 if (src.width > dst.width) { |
| 263 path.lineTo(rect.left + _decoration.border.left.width, rect.bottom - _deco
ration.border.bottom.width); | 317 dst = new Size(dst.width, src.height * dst.width / src.width); |
| 264 path.lineTo(rect.left + _decoration.border.left.width, rect.top + _decorat
ion.border.top.width); | 318 } |
| 265 path.lineTo(rect.left, rect.top); | 319 break; |
| 266 path.close(); | 320 } |
| 267 canvas.drawPath(path, paint); | 321 canvas.drawImageRect(image, Point.origin & src, rect.topLeft & dst, new Pa
int()); |
| 268 } | 322 } |
| 269 } | 323 } |
| 324 |
| 325 void _paintBorder(sky.Canvas canvas, Rect rect) { |
| 326 if (_decoration.border == null) |
| 327 return; |
| 328 |
| 329 assert(_decoration.borderRadius == null); // TODO(abarth): Implement borders
with border radius. |
| 330 assert(_decoration.shape == Shape.rectangle); // TODO(ianh): Implement borde
rs on circles. |
| 331 |
| 332 assert(_decoration.border.top != null); |
| 333 assert(_decoration.border.right != null); |
| 334 assert(_decoration.border.bottom != null); |
| 335 assert(_decoration.border.left != null); |
| 336 |
| 337 Paint paint = new Paint(); |
| 338 Path path; |
| 339 |
| 340 paint.color = _decoration.border.top.color; |
| 341 path = new Path(); |
| 342 path.moveTo(rect.left, rect.top); |
| 343 path.lineTo(rect.left + _decoration.border.left.width, rect.top + _decoratio
n.border.top.width); |
| 344 path.lineTo(rect.right - _decoration.border.right.width, rect.top + _decorat
ion.border.top.width); |
| 345 path.lineTo(rect.right, rect.top); |
| 346 path.close(); |
| 347 canvas.drawPath(path, paint); |
| 348 |
| 349 paint.color = _decoration.border.right.color; |
| 350 path = new Path(); |
| 351 path.moveTo(rect.right, rect.top); |
| 352 path.lineTo(rect.right - _decoration.border.right.width, rect.top + _decorat
ion.border.top.width); |
| 353 path.lineTo(rect.right - _decoration.border.right.width, rect.bottom - _deco
ration.border.bottom.width); |
| 354 path.lineTo(rect.right, rect.bottom); |
| 355 path.close(); |
| 356 canvas.drawPath(path, paint); |
| 357 |
| 358 paint.color = _decoration.border.bottom.color; |
| 359 path = new Path(); |
| 360 path.moveTo(rect.right, rect.bottom); |
| 361 path.lineTo(rect.right - _decoration.border.right.width, rect.bottom - _deco
ration.border.bottom.width); |
| 362 path.lineTo(rect.left + _decoration.border.left.width, rect.bottom - _decora
tion.border.bottom.width); |
| 363 path.lineTo(rect.left, rect.bottom); |
| 364 path.close(); |
| 365 canvas.drawPath(path, paint); |
| 366 |
| 367 paint.color = _decoration.border.left.color; |
| 368 path = new Path(); |
| 369 path.moveTo(rect.left, rect.bottom); |
| 370 path.lineTo(rect.left + _decoration.border.left.width, rect.bottom - _decora
tion.border.bottom.width); |
| 371 path.lineTo(rect.left + _decoration.border.left.width, rect.top + _decoratio
n.border.top.width); |
| 372 path.lineTo(rect.left, rect.top); |
| 373 path.close(); |
| 374 canvas.drawPath(path, paint); |
| 375 } |
| 376 |
| 377 void paint(sky.Canvas canvas, Rect rect) { |
| 378 _paintBackgroundColor(canvas, rect); |
| 379 _paintBackgroundImage(canvas, rect); |
| 380 _paintBorder(canvas, rect); |
| 381 } |
| 270 } | 382 } |
| OLD | NEW |