277 lines
7.4 KiB
Dart
277 lines
7.4 KiB
Dart
// Copyright 2015 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:ui' as ui show Image;
|
|
|
|
import 'box.dart';
|
|
import 'object.dart';
|
|
|
|
export 'package:flutter/painting.dart' show
|
|
ImageFit,
|
|
ImageRepeat;
|
|
|
|
/// An image in the render tree.
|
|
///
|
|
/// The render image attempts to find a size for itself that fits in the given
|
|
/// constraints and preserves the image's intrinisc aspect ratio.
|
|
class RenderImage extends RenderBox {
|
|
/// Creates a render box that displays an image.
|
|
RenderImage({
|
|
ui.Image image,
|
|
double width,
|
|
double height,
|
|
double scale: 1.0,
|
|
Color color,
|
|
ImageFit fit,
|
|
FractionalOffset alignment,
|
|
ImageRepeat repeat: ImageRepeat.noRepeat,
|
|
Rect centerSlice
|
|
}) : _image = image,
|
|
_width = width,
|
|
_height = height,
|
|
_scale = scale,
|
|
_color = color,
|
|
_fit = fit,
|
|
_alignment = alignment,
|
|
_repeat = repeat,
|
|
_centerSlice = centerSlice {
|
|
_updateColorFilter();
|
|
}
|
|
|
|
/// The image to display.
|
|
ui.Image get image => _image;
|
|
ui.Image _image;
|
|
set image (ui.Image value) {
|
|
if (value == _image)
|
|
return;
|
|
_image = value;
|
|
markNeedsPaint();
|
|
if (_width == null || _height == null)
|
|
markNeedsLayout();
|
|
}
|
|
|
|
/// If non-null, requires the image to have this width.
|
|
///
|
|
/// If null, the image will pick a size that best preserves its intrinsic
|
|
/// aspect ratio.
|
|
double get width => _width;
|
|
double _width;
|
|
set width (double value) {
|
|
if (value == _width)
|
|
return;
|
|
_width = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
/// If non-null, require the image to have this height.
|
|
///
|
|
/// If null, the image will pick a size that best preserves its intrinsic
|
|
/// aspect ratio.
|
|
double get height => _height;
|
|
double _height;
|
|
set height (double value) {
|
|
if (value == _height)
|
|
return;
|
|
_height = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
/// Specifies the image's scale.
|
|
///
|
|
/// Used when determining the best display size for the image.
|
|
double get scale => _scale;
|
|
double _scale;
|
|
set scale (double value) {
|
|
assert(value != null);
|
|
if (value == _scale)
|
|
return;
|
|
_scale = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
ColorFilter _colorFilter;
|
|
|
|
// Should we make the transfer mode configurable?
|
|
void _updateColorFilter() {
|
|
if (_color == null)
|
|
_colorFilter = null;
|
|
else
|
|
_colorFilter = new ColorFilter.mode(_color, TransferMode.srcIn);
|
|
}
|
|
|
|
/// If non-null, apply this color filter to the image before painting.
|
|
Color get color => _color;
|
|
Color _color;
|
|
set color (Color value) {
|
|
if (value == _color)
|
|
return;
|
|
_color = value;
|
|
_updateColorFilter();
|
|
markNeedsPaint();
|
|
}
|
|
|
|
/// How to inscribe the image into the place allocated during layout.
|
|
ImageFit get fit => _fit;
|
|
ImageFit _fit;
|
|
set fit (ImageFit value) {
|
|
if (value == _fit)
|
|
return;
|
|
_fit = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
/// How to align the image within its bounds.
|
|
FractionalOffset get alignment => _alignment;
|
|
FractionalOffset _alignment;
|
|
set alignment (FractionalOffset value) {
|
|
if (value == _alignment)
|
|
return;
|
|
_alignment = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
/// How to repeat this image if it doesn't fill its layout bounds.
|
|
ImageRepeat get repeat => _repeat;
|
|
ImageRepeat _repeat;
|
|
set repeat (ImageRepeat value) {
|
|
if (value == _repeat)
|
|
return;
|
|
_repeat = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
/// The center slice for a nine-patch image.
|
|
///
|
|
/// The region of the image inside the center slice will be stretched both
|
|
/// horizontally and vertically to fit the image into its destination. The
|
|
/// region of the image above and below the center slice will be stretched
|
|
/// only horizontally and the region of the image to the left and right of
|
|
/// the center slice will be stretched only vertically.
|
|
Rect get centerSlice => _centerSlice;
|
|
Rect _centerSlice;
|
|
set centerSlice (Rect value) {
|
|
if (value == _centerSlice)
|
|
return;
|
|
_centerSlice = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
/// Find a size for the render image within the given constraints.
|
|
///
|
|
/// - The dimensions of the RenderImage must fit within the constraints.
|
|
/// - The aspect ratio of the RenderImage matches the instrinsic aspect
|
|
/// ratio of the image.
|
|
/// - The RenderImage's dimension are maximal subject to being smaller than
|
|
/// the intrinsic size of the image.
|
|
Size _sizeForConstraints(BoxConstraints constraints) {
|
|
// Folds the given |width| and |height| into |constraints| so they can all
|
|
// be treated uniformly.
|
|
constraints = new BoxConstraints.tightFor(
|
|
width: _width,
|
|
height: _height
|
|
).enforce(constraints);
|
|
|
|
if (constraints.isTight || _image == null)
|
|
return constraints.smallest;
|
|
|
|
double width = _image.width.toDouble() / _scale;
|
|
double height = _image.height.toDouble() / _scale;
|
|
assert(width > 0.0);
|
|
assert(height > 0.0);
|
|
double aspectRatio = width / height;
|
|
|
|
if (width > constraints.maxWidth) {
|
|
width = constraints.maxWidth;
|
|
height = width / aspectRatio;
|
|
}
|
|
|
|
if (height > constraints.maxHeight) {
|
|
height = constraints.maxHeight;
|
|
width = height * aspectRatio;
|
|
}
|
|
|
|
if (width < constraints.minWidth) {
|
|
width = constraints.minWidth;
|
|
height = width / aspectRatio;
|
|
}
|
|
|
|
if (height < constraints.minHeight) {
|
|
height = constraints.minHeight;
|
|
width = height * aspectRatio;
|
|
}
|
|
|
|
return constraints.constrain(new Size(width, height));
|
|
}
|
|
|
|
@override
|
|
double getMinIntrinsicWidth(double height) {
|
|
if (_width == null && _height == null)
|
|
return 0.0;
|
|
return _sizeForConstraints(new BoxConstraints.tightForFinite(height: height)).width;
|
|
}
|
|
|
|
@override
|
|
double getMaxIntrinsicWidth(double height) {
|
|
return _sizeForConstraints(new BoxConstraints.tightForFinite(height: height)).width;
|
|
}
|
|
|
|
@override
|
|
double getMinIntrinsicHeight(double width) {
|
|
if (_width == null && _height == null)
|
|
return 0.0;
|
|
return _sizeForConstraints(new BoxConstraints.tightForFinite(width: width)).height;
|
|
}
|
|
|
|
@override
|
|
double getMaxIntrinsicHeight(double width) {
|
|
return _sizeForConstraints(new BoxConstraints.tightForFinite(width: width)).height;
|
|
}
|
|
|
|
@override
|
|
bool hitTestSelf(Point position) => true;
|
|
|
|
@override
|
|
void performLayout() {
|
|
size = _sizeForConstraints(constraints);
|
|
}
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (_image == null)
|
|
return;
|
|
paintImage(
|
|
canvas: context.canvas,
|
|
rect: offset & size,
|
|
image: _image,
|
|
colorFilter: _colorFilter,
|
|
fit: _fit,
|
|
alignment: _alignment,
|
|
centerSlice: _centerSlice,
|
|
repeat: _repeat
|
|
);
|
|
}
|
|
|
|
@override
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
description.add('image: $image');
|
|
if (width != null)
|
|
description.add('width: $width');
|
|
if (height != null)
|
|
description.add('height: $height');
|
|
if (scale != 1.0)
|
|
description.add('scale: $scale');
|
|
if (color != null)
|
|
description.add('color: $color');
|
|
if (fit != null)
|
|
description.add('fit: $fit');
|
|
if (alignment != null)
|
|
description.add('alignment: $alignment');
|
|
if (repeat != ImageRepeat.noRepeat)
|
|
description.add('repeat: $repeat');
|
|
if (centerSlice != null)
|
|
description.add('centerSlice: $centerSlice');
|
|
}
|
|
}
|