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 | /// @docImport 'package:flutter/rendering.dart'; |
6 | library; |
7 | |
8 | import 'dart:ui_web' as ui_web; |
9 | |
10 | import 'package:flutter/foundation.dart'; |
11 | |
12 | import '../painting/_web_image_info_web.dart'; |
13 | import '../rendering/box.dart'; |
14 | import '../rendering/shifted_box.dart'; |
15 | import '../web.dart' as web; |
16 | import 'basic.dart'; |
17 | import 'framework.dart'; |
18 | import 'platform_view.dart'; |
19 | |
20 | /// Displays an `<img>` element with `src` set to [src]. |
21 | class ImgElementPlatformView extends StatelessWidget { |
22 | /// Creates a platform view backed with an `<img>` element. |
23 | ImgElementPlatformView(this.src, {super.key}) { |
24 | if (!_registered) { |
25 | _register(); |
26 | } |
27 | } |
28 | |
29 | static const String _viewType = 'Flutter__ImgElementImage__'; |
30 | static bool _registered = false; |
31 | |
32 | static void _register() { |
33 | assert(!_registered); |
34 | _registered = true; |
35 | ui_web.platformViewRegistry.registerViewFactory(_viewType, (int viewId, {Object? params}) { |
36 | final Map<Object?, Object?> paramsMap = params! as Map<Object?, Object?>; |
37 | // Create a new |
38 | // without fetching it over the network again. |
39 | final web.HTMLImageElement img = web.document.createElement('img') as web.HTMLImageElement; |
40 | img.src = paramsMap['src']! as String; |
41 | return img; |
42 | }); |
43 | } |
44 | |
45 | /// The `src` URL for the `<img>` tag. |
46 | final String? src; |
47 | |
48 | @override |
49 | Widget build(BuildContext context) { |
50 | if (src == null) { |
51 | return const SizedBox.expand(); |
52 | } |
53 | return HtmlElementView(viewType: _viewType, creationParams: <String, String?>{'src': src}); |
54 | } |
55 | } |
56 | |
57 | /// A widget which displays and lays out an underlying HTML element in a |
58 | /// platform view. |
59 | class RawWebImage extends SingleChildRenderObjectWidget { |
60 | /// Creates a [RawWebImage]. |
61 | RawWebImage({ |
62 | super.key, |
63 | required this.image, |
64 | this.debugImageLabel, |
65 | this.width, |
66 | this.height, |
67 | this.fit, |
68 | this.alignment = Alignment.center, |
69 | this.matchTextDirection = false, |
70 | }) : super(child: ImgElementPlatformView(image.htmlImage.src)); |
71 | |
72 | /// The underlying HTML element to be displayed. |
73 | final WebImageInfo image; |
74 | |
75 | /// A debug label explaining the image. |
76 | final String? debugImageLabel; |
77 | |
78 | /// The requested width for this widget. |
79 | final double? width; |
80 | |
81 | /// The requested height for this widget. |
82 | final double? height; |
83 | |
84 | /// How the HTML element should be inscribed in the box constraining it. |
85 | final BoxFit? fit; |
86 | |
87 | /// How the image should be aligned in the box constraining it. |
88 | final AlignmentGeometry alignment; |
89 | |
90 | /// Whether or not the alignment of the image should match the text direction. |
91 | final bool matchTextDirection; |
92 | |
93 | @override |
94 | RenderObject createRenderObject(BuildContext context) { |
95 | return RenderWebImage( |
96 | image: image.htmlImage, |
97 | width: width, |
98 | height: height, |
99 | fit: fit, |
100 | alignment: alignment, |
101 | matchTextDirection: matchTextDirection, |
102 | textDirection: |
103 | matchTextDirection || alignment is! Alignment ? Directionality.of(context) : null, |
104 | ); |
105 | } |
106 | |
107 | @override |
108 | void updateRenderObject(BuildContext context, RenderWebImage renderObject) { |
109 | renderObject |
110 | ..image = image.htmlImage |
111 | ..width = width |
112 | ..height = height |
113 | ..fit = fit |
114 | ..alignment = alignment |
115 | ..matchTextDirection = matchTextDirection |
116 | ..textDirection = |
117 | matchTextDirection || alignment is! Alignment ? Directionality.of(context) : null; |
118 | } |
119 | } |
120 | |
121 | /// Lays out and positions the child HTML element similarly to [RenderImage]. |
122 | class RenderWebImage extends RenderShiftedBox { |
123 | /// Creates a new [RenderWebImage]. |
124 | RenderWebImage({ |
125 | RenderBox? child, |
126 | required web.HTMLImageElement image, |
127 | double? width, |
128 | double? height, |
129 | BoxFit? fit, |
130 | AlignmentGeometry alignment = Alignment.center, |
131 | bool matchTextDirection = false, |
132 | TextDirection? textDirection, |
133 | }) : _image = image, |
134 | _width = width, |
135 | _height = height, |
136 | _fit = fit, |
137 | _alignment = alignment, |
138 | _matchTextDirection = matchTextDirection, |
139 | _textDirection = textDirection, |
140 | super(child); |
141 | |
142 | Alignment? _resolvedAlignment; |
143 | bool? _flipHorizontally; |
144 | |
145 | void _resolve() { |
146 | if (_resolvedAlignment != null) { |
147 | return; |
148 | } |
149 | _resolvedAlignment = alignment.resolve(textDirection); |
150 | _flipHorizontally = matchTextDirection && textDirection == TextDirection.rtl; |
151 | } |
152 | |
153 | void _markNeedResolution() { |
154 | _resolvedAlignment = null; |
155 | _flipHorizontally = null; |
156 | markNeedsPaint(); |
157 | } |
158 | |
159 | /// Whether to paint the image in the direction of the [TextDirection]. |
160 | /// |
161 | /// If this is true, then in [TextDirection.ltr] contexts, the image will be |
162 | /// drawn with its origin in the top left (the "normal" painting direction for |
163 | /// images); and in [TextDirection.rtl] contexts, the image will be drawn with |
164 | /// a scaling factor of -1 in the horizontal direction so that the origin is |
165 | /// in the top right. |
166 | /// |
167 | /// This is occasionally used with images in right-to-left environments, for |
168 | /// images that were designed for left-to-right locales. Be careful, when |
169 | /// using this, to not flip images with integral shadows, text, or other |
170 | /// effects that will look incorrect when flipped. |
171 | /// |
172 | /// If this is set to true, [textDirection] must not be null. |
173 | bool get matchTextDirection => _matchTextDirection; |
174 | bool _matchTextDirection; |
175 | set matchTextDirection(bool value) { |
176 | if (value == _matchTextDirection) { |
177 | return; |
178 | } |
179 | _matchTextDirection = value; |
180 | _markNeedResolution(); |
181 | } |
182 | |
183 | /// The text direction with which to resolve [alignment]. |
184 | /// |
185 | /// This may be changed to null, but only after the [alignment] and |
186 | /// [matchTextDirection] properties have been changed to values that do not |
187 | /// depend on the direction. |
188 | TextDirection? get textDirection => _textDirection; |
189 | TextDirection? _textDirection; |
190 | set textDirection(TextDirection? value) { |
191 | if (_textDirection == value) { |
192 | return; |
193 | } |
194 | _textDirection = value; |
195 | _markNeedResolution(); |
196 | } |
197 | |
198 | /// The image to display. |
199 | web.HTMLImageElement get image => _image; |
200 | web.HTMLImageElement _image; |
201 | set image(web.HTMLImageElement value) { |
202 | if (value == _image) { |
203 | return; |
204 | } |
205 | // If we get a clone of our image, it's the same underlying native data - |
206 | // return early. |
207 | if (value.src == _image.src) { |
208 | return; |
209 | } |
210 | final bool sizeChanged = |
211 | _image.naturalWidth != value.naturalWidth || _image.naturalHeight != value.naturalHeight; |
212 | _image = value; |
213 | markNeedsPaint(); |
214 | if (sizeChanged && (_width == null || _height == null)) { |
215 | markNeedsLayout(); |
216 | } |
217 | } |
218 | |
219 | /// If non-null, requires the image to have this width. |
220 | /// |
221 | /// If null, the image will pick a size that best preserves its intrinsic |
222 | /// aspect ratio. |
223 | double? get width => _width; |
224 | double? _width; |
225 | set width(double? value) { |
226 | if (value == _width) { |
227 | return; |
228 | } |
229 | _width = value; |
230 | markNeedsLayout(); |
231 | } |
232 | |
233 | /// If non-null, require the image to have this height. |
234 | /// |
235 | /// If null, the image will pick a size that best preserves its intrinsic |
236 | /// aspect ratio. |
237 | double? get height => _height; |
238 | double? _height; |
239 | set height(double? value) { |
240 | if (value == _height) { |
241 | return; |
242 | } |
243 | _height = value; |
244 | markNeedsLayout(); |
245 | } |
246 | |
247 | /// How to inscribe the image into the space allocated during layout. |
248 | /// |
249 | /// The default varies based on the other fields. See the discussion at |
250 | /// [paintImage]. |
251 | BoxFit? get fit => _fit; |
252 | BoxFit? _fit; |
253 | set fit(BoxFit? value) { |
254 | if (value == _fit) { |
255 | return; |
256 | } |
257 | _fit = value; |
258 | markNeedsPaint(); |
259 | } |
260 | |
261 | /// How to align the image within its bounds. |
262 | /// |
263 | /// If this is set to a text-direction-dependent value, [textDirection] must |
264 | /// not be null. |
265 | AlignmentGeometry get alignment => _alignment; |
266 | AlignmentGeometry _alignment; |
267 | set alignment(AlignmentGeometry value) { |
268 | if (value == _alignment) { |
269 | return; |
270 | } |
271 | _alignment = value; |
272 | _markNeedResolution(); |
273 | } |
274 | |
275 | /// Find a size for the render image within the given constraints. |
276 | /// |
277 | /// - The dimensions of the RenderImage must fit within the constraints. |
278 | /// - The aspect ratio of the RenderImage matches the intrinsic aspect |
279 | /// ratio of the image. |
280 | /// - The RenderImage's dimension are maximal subject to being smaller than |
281 | /// the intrinsic size of the image. |
282 | Size _sizeForConstraints(BoxConstraints constraints) { |
283 | // Folds the given |width| and |height| into |constraints| so they can all |
284 | // be treated uniformly. |
285 | constraints = BoxConstraints.tightFor(width: _width, height: _height).enforce(constraints); |
286 | |
287 | return constraints.constrainSizeAndAttemptToPreserveAspectRatio( |
288 | Size(_image.naturalWidth.toDouble(), _image.naturalHeight.toDouble()), |
289 | ); |
290 | } |
291 | |
292 | @override |
293 | double computeMinIntrinsicWidth(double height) { |
294 | assert(height >= 0.0); |
295 | if (_width == null && _height == null) { |
296 | return 0.0; |
297 | } |
298 | return _sizeForConstraints(BoxConstraints.tightForFinite(height: height)).width; |
299 | } |
300 | |
301 | @override |
302 | double computeMaxIntrinsicWidth(double height) { |
303 | assert(height >= 0.0); |
304 | return _sizeForConstraints(BoxConstraints.tightForFinite(height: height)).width; |
305 | } |
306 | |
307 | @override |
308 | double computeMinIntrinsicHeight(double width) { |
309 | assert(width >= 0.0); |
310 | if (_width == null && _height == null) { |
311 | return 0.0; |
312 | } |
313 | return _sizeForConstraints(BoxConstraints.tightForFinite(width: width)).height; |
314 | } |
315 | |
316 | @override |
317 | double computeMaxIntrinsicHeight(double width) { |
318 | assert(width >= 0.0); |
319 | return _sizeForConstraints(BoxConstraints.tightForFinite(width: width)).height; |
320 | } |
321 | |
322 | @override |
323 | bool hitTestSelf(Offset position) => true; |
324 | |
325 | @override |
326 | @protected |
327 | Size computeDryLayout(covariant BoxConstraints constraints) { |
328 | return _sizeForConstraints(constraints); |
329 | } |
330 | |
331 | @override |
332 | void performLayout() { |
333 | _resolve(); |
334 | assert(_resolvedAlignment != null); |
335 | assert(_flipHorizontally != null); |
336 | size = _sizeForConstraints(constraints); |
337 | |
338 | if (child == null) { |
339 | return; |
340 | } |
341 | |
342 | final Size inputSize = Size(image.naturalWidth.toDouble(), image.naturalHeight.toDouble()); |
343 | fit ??= BoxFit.scaleDown; |
344 | final FittedSizes fittedSizes = applyBoxFit(fit!, inputSize, size); |
345 | final Size childSize = fittedSizes.destination; |
346 | child!.layout(BoxConstraints.tight(childSize)); |
347 | final double halfWidthDelta = (size.width - childSize.width) / 2.0; |
348 | final double halfHeightDelta = (size.height - childSize.height) / 2.0; |
349 | final double dx = |
350 | halfWidthDelta + |
351 | (_flipHorizontally! ? -_resolvedAlignment!.x : _resolvedAlignment!.x) * halfWidthDelta; |
352 | final double dy = halfHeightDelta + _resolvedAlignment!.y * halfHeightDelta; |
353 | final BoxParentData childParentData = child!.parentData! as BoxParentData; |
354 | childParentData.offset = Offset(dx, dy); |
355 | } |
356 | |
357 | @override |
358 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
359 | super.debugFillProperties(properties); |
360 | properties.add(DiagnosticsProperty<web.HTMLImageElement>('image', image)); |
361 | properties.add(DoubleProperty('width', width, defaultValue: null)); |
362 | properties.add(DoubleProperty('height', height, defaultValue: null)); |
363 | properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null)); |
364 | properties.add( |
365 | DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null), |
366 | ); |
367 | } |
368 | } |
369 |
Definitions
- ImgElementPlatformView
- ImgElementPlatformView
- _register
- build
- RawWebImage
- RawWebImage
- createRenderObject
- updateRenderObject
- RenderWebImage
- RenderWebImage
- _resolve
- _markNeedResolution
- matchTextDirection
- matchTextDirection
- textDirection
- textDirection
- image
- image
- width
- width
- height
- height
- fit
- fit
- alignment
- alignment
- _sizeForConstraints
- computeMinIntrinsicWidth
- computeMaxIntrinsicWidth
- computeMinIntrinsicHeight
- computeMaxIntrinsicHeight
- hitTestSelf
- computeDryLayout
- performLayout
Learn more about Flutter for embedded and desktop on industrialflutter.com