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';
6library;
7
8import 'dart:ui_web' as ui_web;
9
10import 'package:flutter/foundation.dart';
11
12import '../painting/_web_image_info_web.dart';
13import '../rendering/box.dart';
14import '../rendering/shifted_box.dart';
15import '../web.dart' as web;
16import 'basic.dart';
17import 'framework.dart';
18import 'platform_view.dart';
19
20/// Displays an `<img>` element with `src` set to [src].
21class 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 element. The browser is able to display the image
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.
59class 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].
122class 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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com