Skip to content

2D paint offset is off in reverse #128723

@Piinks

Description

@Piinks

While writing tests for ensureVisible in 2D, found a bug in how the 2D viewport calculates the paint offset of the children when reversed.

Expected:

image

Actual:

Screenshot 2023-06-12 at 2 42 21 PM

import 'dart:math' as math;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(const SimpleTest());
}

class SimpleTest extends StatelessWidget {
  const SimpleTest({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SimpleTableView(
          mainAxis: Axis.vertical,
          primary: true,
          verticalDetails: const ScrollableDetails.vertical(reverse: true),
          horizontalDetails: const ScrollableDetails.horizontal(),
          delegate: SimpleTableDelegate(builder: (context, vicinity) {
            return Container(
              decoration: BoxDecoration(
                border: Border.all(),
                color: vicinity.xIndex.isEven && vicinity.yIndex.isEven
                    ? Colors.amber[100]
                    : (vicinity.xIndex.isOdd && vicinity.yIndex.isOdd
                        ? Colors.blueAccent[100]
                        : null),
              ),
              height: 200.0,
              width: 200.0,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Checkbox(value: false, onChanged: (value) {}),
                  Text(vicinity.toString()),
                ],
              ),
            );
          }),
        ),
      ),
    );
  }
}

class SimpleTableDelegate extends TwoDimensionalChildBuilderDelegate {
  SimpleTableDelegate({required super.builder});
}

class SimpleTableView extends TwoDimensionalScrollView {
  const SimpleTableView({
    super.key,
    super.primary,
    super.mainAxis = Axis.vertical,
    super.verticalDetails = const ScrollableDetails.vertical(),
    super.horizontalDetails = const ScrollableDetails.horizontal(),
    required super.delegate,
    super.cacheExtent,
    super.diagonalDragBehavior = DiagonalDragBehavior.none,
    super.dragStartBehavior = DragStartBehavior.start,
    super.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    super.clipBehavior = Clip.hardEdge,
  });

  @override
  Widget buildViewport(BuildContext context, ViewportOffset verticalOffset,
      ViewportOffset horizontalOffset) {
    return SimpleTableViewport(
      horizontalOffset: horizontalOffset,
      horizontalAxisDirection: horizontalDetails.direction,
      verticalOffset: verticalOffset,
      verticalAxisDirection: verticalDetails.direction,
      mainAxis: mainAxis,
      delegate: delegate,
      cacheExtent: cacheExtent,
      clipBehavior: clipBehavior,
    );
  }
}

class SimpleTableViewport extends TwoDimensionalViewport {
  const SimpleTableViewport({
    super.key,
    required super.verticalOffset,
    required super.verticalAxisDirection,
    required super.horizontalOffset,
    required super.horizontalAxisDirection,
    required super.delegate,
    required super.mainAxis,
    super.cacheExtent,
    super.clipBehavior = Clip.hardEdge,
  });

  @override
  SimpleTableDelegate get delegate => super.delegate as SimpleTableDelegate;

  @override
  RenderTwoDimensionalViewport createRenderObject(BuildContext context) {
    return RenderSimpleTableViewport(
      horizontalOffset: horizontalOffset,
      horizontalAxisDirection: horizontalAxisDirection,
      verticalOffset: verticalOffset,
      verticalAxisDirection: verticalAxisDirection,
      mainAxis: mainAxis,
      delegate: delegate,
      childManager: context as TwoDimensionalChildManager,
      cacheExtent: cacheExtent,
      clipBehavior: clipBehavior,
    );
  }

  @override
  void updateRenderObject(
      BuildContext context, RenderSimpleTableViewport renderObject) {
    renderObject
      ..horizontalOffset = horizontalOffset
      ..horizontalAxisDirection = horizontalAxisDirection
      ..verticalOffset = verticalOffset
      ..verticalAxisDirection = verticalAxisDirection
      ..mainAxis = mainAxis
      ..delegate = delegate
      ..cacheExtent = cacheExtent
      ..clipBehavior = clipBehavior;
  }
}

class RenderSimpleTableViewport extends RenderTwoDimensionalViewport {
  RenderSimpleTableViewport({
    required super.horizontalOffset,
    required super.horizontalAxisDirection,
    required super.verticalOffset,
    required super.verticalAxisDirection,
    required super.delegate,
    required super.mainAxis,
    required super.childManager,
    super.cacheExtent,
    super.clipBehavior = Clip.hardEdge,
  });

  @override
  void layoutChildSequence() {
    // Every child is 200x200 square
    final double horizontalPixels = horizontalOffset.pixels;
    final double verticalPixels = verticalOffset.pixels;
    final int leadingColumn = math.max((horizontalPixels / 200).floor(), 0);
    final int trailingColumn = math.min(
      ((horizontalPixels + viewportDimension.width + cacheExtent) / 200).ceil(),
      99,
    );
    final int leadingRow = math.max((verticalPixels / 200).floor(), 0);
    final int trailingRow = math.min(
      ((verticalPixels + viewportDimension.height + cacheExtent) / 200).ceil(),
      99,
    );

    double xLayoutOffset = (leadingColumn * 200) - horizontalOffset.pixels;
    for (int column = leadingColumn; column <= trailingColumn; column++) {
      double yLayoutOffset = (leadingRow * 200) - verticalOffset.pixels;
      for (int row = leadingRow; row <= trailingRow; row++) {
        final ChildVicinity vicinity = ChildVicinity(xIndex: column, yIndex: row);
        final RenderBox child = buildOrObtainChildFor(vicinity)!;
        child.layout(constraints, parentUsesSize: true);
        parentDataOf(child).layoutOffset = Offset(xLayoutOffset, yLayoutOffset);
        yLayoutOffset += 200;
      }
      xLayoutOffset += 200;
    }
    verticalOffset.applyContentDimensions(
      0,
      200 * 100 - viewportDimension.height,
    );
    horizontalOffset.applyContentDimensions(
      0,
      200 * 100 - viewportDimension.width,
    );
  }
}

I have a fix on the way...

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listf: scrollingViewports, list views, slivers, etc.frameworkflutter/packages/flutter repository. See also f: labels.waiting for PR to land (fixed)A fix is in flight

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions