Widget _buildLayout()

in packages/devtools_app/lib/src/shared/split.dart [105:281]


  Widget _buildLayout(BuildContext context, BoxConstraints constraints) {
    final width = constraints.maxWidth;
    final height = constraints.maxHeight;
    final axisSize = isHorizontal ? width : height;

    final availableSize = axisSize - _totalSplitterSize();

    // Size calculation helpers.
    double _minSizeForIndex(int index) {
      if (widget.minSizes == null) return 0.0;

      double totalMinSize = 0;
      for (var minSize in widget.minSizes) {
        totalMinSize += minSize;
      }

      // Reduce the min sizes gracefully if the total required min size for all
      // children is greater than the available size for children.
      if (totalMinSize > availableSize) {
        return widget.minSizes[index] * availableSize / totalMinSize;
      } else {
        return widget.minSizes[index];
      }
    }

    double _minFractionForIndex(int index) =>
        _minSizeForIndex(index) / availableSize;

    void _clampFraction(int index) {
      fractions[index] =
          fractions[index].clamp(_minFractionForIndex(index), 1.0);
    }

    double _sizeForIndex(int index) => availableSize * fractions[index];

    double fractionDeltaRequired = 0.0;
    double fractionDeltaAvailable = 0.0;

    double deltaFromMinimumSize(int index) =>
        fractions[index] - _minFractionForIndex(index);

    for (int i = 0; i < fractions.length; ++i) {
      final delta = deltaFromMinimumSize(i);
      if (delta < 0) {
        fractionDeltaRequired -= delta;
      } else {
        fractionDeltaAvailable += delta;
      }
    }
    if (fractionDeltaRequired > 0) {
      // Likely due to a change in the available size, the current fractions for
      // the children do not obey the min size constraints.
      // The min size constraints for children are scaled so it is always
      // possible to meet them. A scaleFactor greater than 1 would indicate that
      // it is impossible to meet the constraints.
      double scaleFactor = fractionDeltaRequired / fractionDeltaAvailable;
      assert(scaleFactor <= 1 + defaultEpsilon);
      scaleFactor = math.min(scaleFactor, 1.0);
      for (int i = 0; i < fractions.length; ++i) {
        final delta = deltaFromMinimumSize(i);
        if (delta < 0) {
          // This is equivalent to adding delta but avoids rounding error.
          fractions[i] = _minFractionForIndex(i);
        } else {
          // Reduce all fractions that are above their minimum size by an amount
          // proportional to their ability to reduce their size without
          // violating their minimum size constraints.
          fractions[i] -= delta * scaleFactor;
        }
      }
    }

    // Determine what fraction to give each child, including enough space to
    // display the divider.
    final sizes = List.generate(fractions.length, (i) => _sizeForIndex(i));

    void updateSpacing(DragUpdateDetails dragDetails, int splitterIndex) {
      final dragDelta =
          isHorizontal ? dragDetails.delta.dx : dragDetails.delta.dy;
      final fractionalDelta = dragDelta / axisSize;

      // Returns the actual delta applied to elements before the splitter.
      double updateSpacingBeforeSplitterIndex(double delta) {
        final startingDelta = delta;
        var index = splitterIndex;
        while (index >= 0) {
          fractions[index] += delta;
          final minFractionForIndex = _minFractionForIndex(index);
          if (fractions[index] >= minFractionForIndex) {
            _clampFraction(index);
            return startingDelta;
          }
          delta = fractions[index] - minFractionForIndex;
          _clampFraction(index);
          index--;
        }
        // At this point, we know that both [startingDelta] and [delta] are
        // negative, and that [delta] represents the overflow that did not get
        // applied.
        return startingDelta - delta;
      }

      // Returns the actual delta applied to elements after the splitter.
      double updateSpacingAfterSplitterIndex(double delta) {
        final startingDelta = delta;
        var index = splitterIndex + 1;
        while (index < fractions.length) {
          fractions[index] += delta;
          final minFractionForIndex = _minFractionForIndex(index);
          if (fractions[index] >= minFractionForIndex) {
            _clampFraction(index);
            return startingDelta;
          }
          delta = fractions[index] - minFractionForIndex;
          _clampFraction(index);
          index++;
        }
        // At this point, we know that both [startingDelta] and [delta] are
        // negative, and that [delta] represents the overflow that did not get
        // applied.
        return startingDelta - delta;
      }

      setState(() {
        // Update the fraction of space consumed by the children. Always update
        // the shrinking children first so that we do not over-increase the size
        // of the growing children and cause layout overflow errors.
        if (fractionalDelta <= 0.0) {
          final appliedDelta =
              updateSpacingBeforeSplitterIndex(fractionalDelta);
          updateSpacingAfterSplitterIndex(-appliedDelta);
        } else {
          final appliedDelta =
              updateSpacingAfterSplitterIndex(-fractionalDelta);
          updateSpacingBeforeSplitterIndex(-appliedDelta);
        }
      });
      _verifyFractionsSumTo1(fractions);
    }

    final children = <Widget>[];
    for (int i = 0; i < widget.children.length; i++) {
      children.addAll([
        SizedBox(
          width: isHorizontal ? sizes[i] : width,
          height: isHorizontal ? height : sizes[i],
          child: widget.children[i],
        ),
        if (i < widget.children.length - 1)
          MouseRegion(
            cursor: isHorizontal
                ? SystemMouseCursors.resizeColumn
                : SystemMouseCursors.resizeRow,
            child: GestureDetector(
              key: widget.dividerKey(i),
              behavior: HitTestBehavior.translucent,
              onHorizontalDragUpdate: (details) =>
                  isHorizontal ? updateSpacing(details, i) : null,
              onVerticalDragUpdate: (details) =>
                  isHorizontal ? null : updateSpacing(details, i),
              // DartStartBehavior.down is needed to keep the mouse pointer stuck to
              // the drag bar. There still appears to be a few frame lag before the
              // drag action triggers which is't ideal but isn't a launch blocker.
              dragStartBehavior: DragStartBehavior.down,
              child: widget.splitters != null
                  ? widget.splitters[i]
                  : DefaultSplitter(isHorizontal: isHorizontal),
            ),
          ),
      ]);
    }
    return Flex(
      direction: widget.axis,
      children: children,
      crossAxisAlignment: CrossAxisAlignment.stretch,
    );
  }