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 'single_child_scroll_view.dart';
6library;
7
8import 'dart:math' as math;
9
10import 'package:flutter/rendering.dart';
11
12import 'basic.dart';
13import 'framework.dart';
14
15/// Defines the horizontal alignment of [OverflowBar] children
16/// when they're laid out in an overflow column.
17///
18/// This value must be interpreted relative to the ambient
19/// [TextDirection].
20enum OverflowBarAlignment {
21 /// Each child is left-aligned for [TextDirection.ltr],
22 /// right-aligned for [TextDirection.rtl].
23 start,
24
25 /// Each child is right-aligned for [TextDirection.ltr],
26 /// left-aligned for [TextDirection.rtl].
27 end,
28
29 /// Each child is horizontally centered.
30 center,
31}
32
33/// A widget that lays out its [children] in a row unless they
34/// "overflow" the available horizontal space, in which case it lays
35/// them out in a column instead.
36///
37/// This widget's width will expand to contain its children and the
38/// specified [spacing] until it overflows. The overflow column will
39/// consume all of the available width. The [overflowAlignment]
40/// defines how each child will be aligned within the overflow column
41/// and the [overflowSpacing] defines the gap between each child.
42///
43/// The order that the children appear in the horizontal layout
44/// is defined by the [textDirection], just like the [Row] widget.
45/// If the layout overflows, then children's order within their
46/// column is specified by [overflowDirection] instead.
47///
48/// {@tool dartpad}
49/// This example defines a simple approximation of a dialog
50/// layout, where the layout of the dialog's action buttons are
51/// defined by an [OverflowBar]. The content is wrapped in a
52/// [SingleChildScrollView], so that if overflow occurs, the
53/// action buttons will still be accessible by scrolling,
54/// no matter how much vertical space is available.
55///
56/// ** See code in examples/api/lib/widgets/overflow_bar/overflow_bar.0.dart **
57/// {@end-tool}
58class OverflowBar extends MultiChildRenderObjectWidget {
59 /// Constructs an OverflowBar.
60 const OverflowBar({
61 super.key,
62 this.spacing = 0.0,
63 this.alignment,
64 this.overflowSpacing = 0.0,
65 this.overflowAlignment = OverflowBarAlignment.start,
66 this.overflowDirection = VerticalDirection.down,
67 this.textDirection,
68 super.children,
69 });
70
71 /// The width of the gap between [children] for the default
72 /// horizontal layout.
73 ///
74 /// If the horizontal layout overflows, then [overflowSpacing] is
75 /// used instead.
76 ///
77 /// Defaults to 0.0.
78 final double spacing;
79
80 /// Defines the [children]'s horizontal layout according to the same
81 /// rules as for [Row.mainAxisAlignment].
82 ///
83 /// If this property is non-null, and the [children], separated by
84 /// [spacing], fit within the available width, then the overflow
85 /// bar will be as wide as possible. If the children do not fit
86 /// within the available width, then this property is ignored and
87 /// [overflowAlignment] applies instead.
88 ///
89 /// If this property is null (the default) then the overflow bar
90 /// will be no wider than needed to layout the [children] separated
91 /// by [spacing], modulo the incoming constraints.
92 ///
93 /// If [alignment] is one of [MainAxisAlignment.spaceAround],
94 /// [MainAxisAlignment.spaceBetween], or
95 /// [MainAxisAlignment.spaceEvenly], then the [spacing] parameter is
96 /// only used to see if the horizontal layout will overflow.
97 ///
98 /// Defaults to null.
99 ///
100 /// See also:
101 ///
102 /// * [overflowAlignment], the horizontal alignment of the [children] within
103 /// the vertical "overflow" layout.
104 ///
105 final MainAxisAlignment? alignment;
106
107 /// The height of the gap between [children] in the vertical
108 /// "overflow" layout.
109 ///
110 /// This parameter is only used if the horizontal layout overflows, i.e.
111 /// if there isn't enough horizontal room for the [children] and [spacing].
112 ///
113 /// Defaults to 0.0.
114 ///
115 /// See also:
116 ///
117 /// * [spacing], The width of the gap between each pair of children
118 /// for the default horizontal layout.
119 final double overflowSpacing;
120
121 /// The horizontal alignment of the [children] within the vertical
122 /// "overflow" layout.
123 ///
124 /// This parameter is only used if the horizontal layout overflows, i.e.
125 /// if there isn't enough horizontal room for the [children] and [spacing].
126 /// In that case the overflow bar will expand to fill the available
127 /// width and it will layout its [children] in a column. The
128 /// horizontal alignment of each child within that column is
129 /// defined by this parameter and the [textDirection]. If the
130 /// [textDirection] is [TextDirection.ltr] then each child will be
131 /// aligned with the left edge of the available space for
132 /// [OverflowBarAlignment.start], with the right edge of the
133 /// available space for [OverflowBarAlignment.end]. Similarly, if the
134 /// [textDirection] is [TextDirection.rtl] then each child will
135 /// be aligned with the right edge of the available space for
136 /// [OverflowBarAlignment.start], and with the left edge of the
137 /// available space for [OverflowBarAlignment.end]. For
138 /// [OverflowBarAlignment.center] each child is horizontally
139 /// centered within the available space.
140 ///
141 /// Defaults to [OverflowBarAlignment.start].
142 ///
143 /// See also:
144 ///
145 /// * [alignment], which defines the [children]'s horizontal layout
146 /// (according to the same rules as for [Row.mainAxisAlignment]) when
147 /// the children, separated by [spacing], fit within the available space.
148 /// * [overflowDirection], which defines the order that the
149 /// [OverflowBar]'s children appear in, if the horizontal layout
150 /// overflows.
151 final OverflowBarAlignment overflowAlignment;
152
153 /// Defines the order that the [children] appear in, if
154 /// the horizontal layout overflows.
155 ///
156 /// This parameter is only used if the horizontal layout overflows, i.e.
157 /// if there isn't enough horizontal room for the [children] and [spacing].
158 ///
159 /// If the children do not fit into a single row, then they
160 /// are arranged in a column. The first child is at the top of the
161 /// column if this property is set to [VerticalDirection.down], since it
162 /// "starts" at the top and "ends" at the bottom. On the other hand,
163 /// the first child will be at the bottom of the column if this
164 /// property is set to [VerticalDirection.up], since it "starts" at the
165 /// bottom and "ends" at the top.
166 ///
167 /// Defaults to [VerticalDirection.down].
168 ///
169 /// See also:
170 ///
171 /// * [overflowAlignment], which defines the horizontal alignment
172 /// of the children within the vertical "overflow" layout.
173 final VerticalDirection overflowDirection;
174
175 /// Determines the order that the [children] appear in for the default
176 /// horizontal layout, and the interpretation of
177 /// [OverflowBarAlignment.start] and [OverflowBarAlignment.end] for
178 /// the vertical overflow layout.
179 ///
180 /// For the default horizontal layout, if [textDirection] is
181 /// [TextDirection.rtl] then the last child is laid out first. If
182 /// [textDirection] is [TextDirection.ltr] then the first child is
183 /// laid out first.
184 ///
185 /// If this parameter is null, then the value of
186 /// `Directionality.of(context)` is used.
187 ///
188 /// See also:
189 ///
190 /// * [overflowDirection], which defines the order that the
191 /// [OverflowBar]'s children appear in, if the horizontal layout
192 /// overflows.
193 /// * [Directionality], which defines the ambient directionality of
194 /// text and text-direction-sensitive render objects.
195 final TextDirection? textDirection;
196
197 @override
198 RenderObject createRenderObject(BuildContext context) {
199 return _RenderOverflowBar(
200 spacing: spacing,
201 alignment: alignment,
202 overflowSpacing: overflowSpacing,
203 overflowAlignment: overflowAlignment,
204 overflowDirection: overflowDirection,
205 textDirection: textDirection ?? Directionality.of(context),
206 );
207 }
208
209 @override
210 void updateRenderObject(BuildContext context, RenderObject renderObject) {
211 (renderObject as _RenderOverflowBar)
212 ..spacing = spacing
213 ..alignment = alignment
214 ..overflowSpacing = overflowSpacing
215 ..overflowAlignment = overflowAlignment
216 ..overflowDirection = overflowDirection
217 ..textDirection = textDirection ?? Directionality.of(context);
218 }
219
220 @override
221 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
222 super.debugFillProperties(properties);
223 properties.add(DoubleProperty('spacing', spacing, defaultValue: 0));
224 properties.add(EnumProperty<MainAxisAlignment>('alignment', alignment, defaultValue: null));
225 properties.add(DoubleProperty('overflowSpacing', overflowSpacing, defaultValue: 0));
226 properties.add(
227 EnumProperty<OverflowBarAlignment>(
228 'overflowAlignment',
229 overflowAlignment,
230 defaultValue: OverflowBarAlignment.start,
231 ),
232 );
233 properties.add(
234 EnumProperty<VerticalDirection>(
235 'overflowDirection',
236 overflowDirection,
237 defaultValue: VerticalDirection.down,
238 ),
239 );
240 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
241 }
242}
243
244class _OverflowBarParentData extends ContainerBoxParentData<RenderBox> {}
245
246class _RenderOverflowBar extends RenderBox
247 with
248 ContainerRenderObjectMixin<RenderBox, _OverflowBarParentData>,
249 RenderBoxContainerDefaultsMixin<RenderBox, _OverflowBarParentData> {
250 _RenderOverflowBar({
251 List<RenderBox>? children,
252 double spacing = 0.0,
253 MainAxisAlignment? alignment,
254 double overflowSpacing = 0.0,
255 OverflowBarAlignment overflowAlignment = OverflowBarAlignment.start,
256 VerticalDirection overflowDirection = VerticalDirection.down,
257 required TextDirection textDirection,
258 }) : _spacing = spacing,
259 _alignment = alignment,
260 _overflowSpacing = overflowSpacing,
261 _overflowAlignment = overflowAlignment,
262 _overflowDirection = overflowDirection,
263 _textDirection = textDirection {
264 addAll(children);
265 }
266
267 double get spacing => _spacing;
268 double _spacing;
269 set spacing(double value) {
270 if (_spacing == value) {
271 return;
272 }
273 _spacing = value;
274 markNeedsLayout();
275 }
276
277 MainAxisAlignment? get alignment => _alignment;
278 MainAxisAlignment? _alignment;
279 set alignment(MainAxisAlignment? value) {
280 if (_alignment == value) {
281 return;
282 }
283 _alignment = value;
284 markNeedsLayout();
285 }
286
287 double get overflowSpacing => _overflowSpacing;
288 double _overflowSpacing;
289 set overflowSpacing(double value) {
290 if (_overflowSpacing == value) {
291 return;
292 }
293 _overflowSpacing = value;
294 markNeedsLayout();
295 }
296
297 OverflowBarAlignment get overflowAlignment => _overflowAlignment;
298 OverflowBarAlignment _overflowAlignment;
299 set overflowAlignment(OverflowBarAlignment value) {
300 if (_overflowAlignment == value) {
301 return;
302 }
303 _overflowAlignment = value;
304 markNeedsLayout();
305 }
306
307 VerticalDirection get overflowDirection => _overflowDirection;
308 VerticalDirection _overflowDirection;
309 set overflowDirection(VerticalDirection value) {
310 if (_overflowDirection == value) {
311 return;
312 }
313 _overflowDirection = value;
314 markNeedsLayout();
315 }
316
317 TextDirection get textDirection => _textDirection;
318 TextDirection _textDirection;
319 set textDirection(TextDirection value) {
320 if (_textDirection == value) {
321 return;
322 }
323 _textDirection = value;
324 markNeedsLayout();
325 }
326
327 @override
328 void setupParentData(RenderBox child) {
329 if (child.parentData is! _OverflowBarParentData) {
330 child.parentData = _OverflowBarParentData();
331 }
332 }
333
334 @override
335 double computeMinIntrinsicHeight(double width) {
336 RenderBox? child = firstChild;
337 if (child == null) {
338 return 0;
339 }
340 double barWidth = 0.0;
341 while (child != null) {
342 barWidth += child.getMinIntrinsicWidth(double.infinity);
343 child = childAfter(child);
344 }
345 barWidth += spacing * (childCount - 1);
346
347 double height = 0.0;
348 if (barWidth > width) {
349 child = firstChild;
350 while (child != null) {
351 height += child.getMinIntrinsicHeight(width);
352 child = childAfter(child);
353 }
354 return height + overflowSpacing * (childCount - 1);
355 } else {
356 child = firstChild;
357 while (child != null) {
358 height = math.max(height, child.getMinIntrinsicHeight(width));
359 child = childAfter(child);
360 }
361 return height;
362 }
363 }
364
365 @override
366 double computeMaxIntrinsicHeight(double width) {
367 RenderBox? child = firstChild;
368 if (child == null) {
369 return 0;
370 }
371 double barWidth = 0.0;
372 while (child != null) {
373 barWidth += child.getMinIntrinsicWidth(double.infinity);
374 child = childAfter(child);
375 }
376 barWidth += spacing * (childCount - 1);
377
378 double height = 0.0;
379 if (barWidth > width) {
380 child = firstChild;
381 while (child != null) {
382 height += child.getMaxIntrinsicHeight(width);
383 child = childAfter(child);
384 }
385 return height + overflowSpacing * (childCount - 1);
386 } else {
387 child = firstChild;
388 while (child != null) {
389 height = math.max(height, child.getMaxIntrinsicHeight(width));
390 child = childAfter(child);
391 }
392 return height;
393 }
394 }
395
396 @override
397 double computeMinIntrinsicWidth(double height) {
398 RenderBox? child = firstChild;
399 if (child == null) {
400 return 0;
401 }
402 double width = 0.0;
403 while (child != null) {
404 width += child.getMinIntrinsicWidth(double.infinity);
405 child = childAfter(child);
406 }
407 return width + spacing * (childCount - 1);
408 }
409
410 @override
411 double computeMaxIntrinsicWidth(double height) {
412 RenderBox? child = firstChild;
413 if (child == null) {
414 return 0;
415 }
416 double width = 0.0;
417 while (child != null) {
418 width += child.getMaxIntrinsicWidth(double.infinity);
419 child = childAfter(child);
420 }
421 return width + spacing * (childCount - 1);
422 }
423
424 @override
425 double? computeDistanceToActualBaseline(TextBaseline baseline) {
426 return defaultComputeDistanceToHighestActualBaseline(baseline);
427 }
428
429 @override
430 double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
431 final BoxConstraints childConstraints = constraints.loosen();
432
433 final (
434 RenderBox? Function(RenderBox) next,
435 RenderBox? startChild,
436 ) = switch (overflowDirection) {
437 VerticalDirection.down => (childAfter, firstChild),
438 VerticalDirection.up => (childBefore, lastChild),
439 };
440
441 double maxChildHeight = 0.0;
442 double y = 0.0;
443 double childrenWidth = 0.0;
444 BaselineOffset minHorizontalBaseline = BaselineOffset.noBaseline;
445 BaselineOffset verticalBaseline = BaselineOffset.noBaseline;
446
447 for (RenderBox? child = startChild; child != null; child = next(child)) {
448 final Size childSize = child.getDryLayout(childConstraints);
449 final double heightDiff = childSize.height - maxChildHeight;
450 if (heightDiff > 0) {
451 minHorizontalBaseline += heightDiff / 2;
452 maxChildHeight = childSize.height;
453 }
454
455 final BaselineOffset baselineOffset = BaselineOffset(
456 child.getDryBaseline(childConstraints, baseline),
457 );
458 if (baselineOffset != null) {
459 verticalBaseline ??= baselineOffset + y;
460 minHorizontalBaseline = minHorizontalBaseline.minOf(
461 baselineOffset + (maxChildHeight - childSize.height),
462 );
463 }
464 y += childSize.height + overflowSpacing;
465 childrenWidth += childSize.width;
466 }
467
468 assert((verticalBaseline == null) == (minHorizontalBaseline == null));
469 return childrenWidth + spacing * (childCount - 1) > constraints.maxWidth
470 ? verticalBaseline.offset
471 : minHorizontalBaseline.offset;
472 }
473
474 @override
475 Size computeDryLayout(BoxConstraints constraints) {
476 RenderBox? child = firstChild;
477 if (child == null) {
478 return constraints.smallest;
479 }
480 final BoxConstraints childConstraints = constraints.loosen();
481 double childrenWidth = 0.0;
482 double maxChildHeight = 0.0;
483 double y = 0.0;
484 while (child != null) {
485 final Size childSize = child.getDryLayout(childConstraints);
486 childrenWidth += childSize.width;
487 maxChildHeight = math.max(maxChildHeight, childSize.height);
488 y += childSize.height + overflowSpacing;
489 child = childAfter(child);
490 }
491 final double actualWidth = childrenWidth + spacing * (childCount - 1);
492 if (actualWidth > constraints.maxWidth) {
493 return constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing));
494 } else {
495 final double overallWidth = alignment == null ? actualWidth : constraints.maxWidth;
496 return constraints.constrain(Size(overallWidth, maxChildHeight));
497 }
498 }
499
500 @override
501 void performLayout() {
502 RenderBox? child = firstChild;
503 if (child == null) {
504 size = constraints.smallest;
505 return;
506 }
507
508 final BoxConstraints childConstraints = constraints.loosen();
509 double childrenWidth = 0;
510 double maxChildHeight = 0;
511 double maxChildWidth = 0;
512
513 while (child != null) {
514 child.layout(childConstraints, parentUsesSize: true);
515 childrenWidth += child.size.width;
516 maxChildHeight = math.max(maxChildHeight, child.size.height);
517 maxChildWidth = math.max(maxChildWidth, child.size.width);
518 child = childAfter(child);
519 }
520
521 final bool rtl = textDirection == TextDirection.rtl;
522 final double actualWidth = childrenWidth + spacing * (childCount - 1);
523
524 if (actualWidth > constraints.maxWidth) {
525 // Overflow vertical layout
526 child = overflowDirection == VerticalDirection.down ? firstChild : lastChild;
527 RenderBox? nextChild() =>
528 overflowDirection == VerticalDirection.down ? childAfter(child!) : childBefore(child!);
529 double y = 0;
530 while (child != null) {
531 final _OverflowBarParentData childParentData = child.parentData! as _OverflowBarParentData;
532 final double x = switch (overflowAlignment) {
533 OverflowBarAlignment.center => (constraints.maxWidth - child.size.width) / 2,
534 OverflowBarAlignment.start => rtl ? constraints.maxWidth - child.size.width : 0,
535 OverflowBarAlignment.end => rtl ? 0 : constraints.maxWidth - child.size.width,
536 };
537 childParentData.offset = Offset(x, y);
538 y += child.size.height + overflowSpacing;
539 child = nextChild();
540 }
541 size = constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing));
542 } else {
543 // Default horizontal layout
544 child = firstChild;
545 final double firstChildWidth = child!.size.width;
546 final double overallWidth = alignment == null ? actualWidth : constraints.maxWidth;
547 size = constraints.constrain(Size(overallWidth, maxChildHeight));
548
549 late double x; // initial value: origin of the first child
550 double layoutSpacing = spacing; // space between children
551 switch (alignment) {
552 case null:
553 x = rtl ? size.width - firstChildWidth : 0;
554 case MainAxisAlignment.start:
555 x = rtl ? size.width - firstChildWidth : 0;
556 case MainAxisAlignment.center:
557 final double halfRemainingWidth = (size.width - actualWidth) / 2;
558 x = rtl ? size.width - halfRemainingWidth - firstChildWidth : halfRemainingWidth;
559 case MainAxisAlignment.end:
560 x = rtl ? actualWidth - firstChildWidth : size.width - actualWidth;
561 case MainAxisAlignment.spaceBetween:
562 layoutSpacing = (size.width - childrenWidth) / (childCount - 1);
563 x = rtl ? size.width - firstChildWidth : 0;
564 case MainAxisAlignment.spaceAround:
565 layoutSpacing = childCount > 0 ? (size.width - childrenWidth) / childCount : 0;
566 x = rtl ? size.width - layoutSpacing / 2 - firstChildWidth : layoutSpacing / 2;
567 case MainAxisAlignment.spaceEvenly:
568 layoutSpacing = (size.width - childrenWidth) / (childCount + 1);
569 x = rtl ? size.width - layoutSpacing - firstChildWidth : layoutSpacing;
570 }
571
572 while (child != null) {
573 final _OverflowBarParentData childParentData = child.parentData! as _OverflowBarParentData;
574 childParentData.offset = Offset(x, (maxChildHeight - child.size.height) / 2);
575 // x is the horizontal origin of child. To advance x to the next child's
576 // origin for LTR: add the width of the current child. To advance x to
577 // the origin of the next child for RTL: subtract the width of the next
578 // child (if there is one).
579 if (!rtl) {
580 x += child.size.width + layoutSpacing;
581 }
582 child = childAfter(child);
583 if (rtl && child != null) {
584 x -= child.size.width + layoutSpacing;
585 }
586 }
587 }
588 }
589
590 @override
591 bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
592 return defaultHitTestChildren(result, position: position);
593 }
594
595 @override
596 void paint(PaintingContext context, Offset offset) {
597 defaultPaint(context, offset);
598 }
599
600 @override
601 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
602 super.debugFillProperties(properties);
603 properties.add(DoubleProperty('spacing', spacing, defaultValue: 0));
604 properties.add(DoubleProperty('overflowSpacing', overflowSpacing, defaultValue: 0));
605 properties.add(
606 EnumProperty<OverflowBarAlignment>(
607 'overflowAlignment',
608 overflowAlignment,
609 defaultValue: OverflowBarAlignment.start,
610 ),
611 );
612 properties.add(
613 EnumProperty<VerticalDirection>(
614 'overflowDirection',
615 overflowDirection,
616 defaultValue: VerticalDirection.down,
617 ),
618 );
619 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
620 }
621}
622

Provided by KDAB

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