// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:vector_math/vector_math_64.dart' show Quad, Vector3; /// Flutter code sample for [InteractiveViewer.builder]. void main() => runApp(const IVBuilderExampleApp()); class IVBuilderExampleApp extends StatelessWidget { const IVBuilderExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('IV Builder Example'), ), body: const _IVBuilderExample(), ), ); } } class _IVBuilderExample extends StatefulWidget { const _IVBuilderExample(); @override State<_IVBuilderExample> createState() => _IVBuilderExampleState(); } class _IVBuilderExampleState extends State<_IVBuilderExample> { static const double _cellWidth = 160.0; static const double _cellHeight = 80.0; // Returns the axis aligned bounding box for the given Quad, which might not // be axis aligned. Rect axisAlignedBoundingBox(Quad quad) { double xMin = quad.point0.x; double xMax = quad.point0.x; double yMin = quad.point0.y; double yMax = quad.point0.y; for (final Vector3 point in <Vector3>[ quad.point1, quad.point2, quad.point3, ]) { if (point.x < xMin) { xMin = point.x; } else if (point.x > xMax) { xMax = point.x; } if (point.y < yMin) { yMin = point.y; } else if (point.y > yMax) { yMax = point.y; } } return Rect.fromLTRB(xMin, yMin, xMax, yMax); } @override Widget build(BuildContext context) { return Center( child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return InteractiveViewer.builder( boundaryMargin: const EdgeInsets.all(double.infinity), builder: (BuildContext context, Quad viewport) { return _TableBuilder( cellWidth: _cellWidth, cellHeight: _cellHeight, viewport: axisAlignedBoundingBox(viewport), builder: (BuildContext context, int row, int column) { return Container( height: _cellHeight, width: _cellWidth, color: row % 2 + column % 2 == 1 ? Colors.white : Colors.grey.withOpacity(0.1), child: Align( child: Text('$row x $column'), ), ); }, ); }, ); }, ), ); } } typedef _CellBuilder = Widget Function(BuildContext context, int row, int column); class _TableBuilder extends StatelessWidget { const _TableBuilder({ required this.cellWidth, required this.cellHeight, required this.viewport, required this.builder, }); final double cellWidth; final double cellHeight; final Rect viewport; final _CellBuilder builder; @override Widget build(BuildContext context) { final int firstRow = (viewport.top / cellHeight).floor(); final int lastRow = (viewport.bottom / cellHeight).ceil(); final int firstCol = (viewport.left / cellWidth).floor(); final int lastCol = (viewport.right / cellWidth).ceil(); // This will create and render exactly (lastRow - firstRow) * (lastCol - firstCol) cells return SizedBox( // Stack needs constraints, even though we then Clip.none outside of them. // InteractiveViewer.builder always sets constrained to false, giving infinite constraints to the child. // See: https://master-api.flutter.dev/flutter/widgets/InteractiveViewer/constrained.html width: 1, height: 1, child: Stack( clipBehavior: Clip.none, children: <Widget>[ for (int row = firstRow; row < lastRow; row++) for (int col = firstCol; col < lastCol; col++) Positioned( left: col * cellWidth, top: row * cellHeight, child: builder(context, row, col), ), ], ), ); } }