| 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'; |
| 6 | library; |
| 7 | |
| 8 | import 'dart:math' as math; |
| 9 | |
| 10 | import 'package:flutter/rendering.dart'; |
| 11 | |
| 12 | import 'basic.dart'; |
| 13 | import '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]. |
| 20 | enum 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} |
| 58 | class 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 | |
| 244 | class _OverflowBarParentData extends ContainerBoxParentData<RenderBox> {} |
| 245 | |
| 246 | class _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 | |