David PHAM-VAN

Convert Wrap to SpanningWidget

@@ -55,9 +55,11 @@ class MultiPage extends Page { @@ -55,9 +55,11 @@ class MultiPage extends Page {
55 this.header, 55 this.header,
56 this.footer, 56 this.footer,
57 Theme theme, 57 Theme theme,
  58 + this.maxPages = 20,
58 PageOrientation orientation = PageOrientation.natural, 59 PageOrientation orientation = PageOrientation.natural,
59 EdgeInsets margin}) 60 EdgeInsets margin})
60 : _buildList = build, 61 : _buildList = build,
  62 + assert(maxPages != null && maxPages > 0),
61 super( 63 super(
62 pageFormat: pageFormat, 64 pageFormat: pageFormat,
63 margin: margin, 65 margin: margin,
@@ -74,6 +76,8 @@ class MultiPage extends Page { @@ -74,6 +76,8 @@ class MultiPage extends Page {
74 76
75 final List<_MultiPageInstance> _pages = <_MultiPageInstance>[]; 77 final List<_MultiPageInstance> _pages = <_MultiPageInstance>[];
76 78
  79 + final int maxPages;
  80 +
77 void _paintChild( 81 void _paintChild(
78 Context context, Widget child, double x, double y, double pageHeight) { 82 Context context, Widget child, double x, double y, double pageHeight) {
79 if (mustRotate) { 83 if (mustRotate) {
@@ -126,9 +130,9 @@ class MultiPage extends Page { @@ -126,9 +130,9 @@ class MultiPage extends Page {
126 130
127 assert(() { 131 assert(() {
128 // Detect too big widgets 132 // Detect too big widgets
129 - if (sameCount++ > 20) { 133 + if (sameCount++ > maxPages) {
130 throw Exception( 134 throw Exception(
131 - 'This widget created more than 20 pages. This may be an issue in the widget or the document.'); 135 + 'This widget created more than $maxPages pages. This may be an issue in the widget or the document.');
132 } 136 }
133 return true; 137 return true;
134 }()); 138 }());
@@ -188,7 +192,7 @@ class MultiPage extends Page { @@ -188,7 +192,7 @@ class MultiPage extends Page {
188 192
189 // What to do if the widget is too big for the page? 193 // What to do if the widget is too big for the page?
190 if (offsetStart - child.box.height < offsetEnd) { 194 if (offsetStart - child.box.height < offsetEnd) {
191 - // If it is not a multi=page widget and its height 195 + // If it is not a multi-page widget and its height
192 // is smaller than a full new page, we schedule a new page creation 196 // is smaller than a full new page, we schedule a new page creation
193 if (child.box.height <= pageHeight - pageHeightMargin && 197 if (child.box.height <= pageHeight - pageHeightMargin &&
194 !(child is SpanningWidget)) { 198 !(child is SpanningWidget)) {
@@ -37,8 +37,16 @@ class _RunMetrics { @@ -37,8 +37,16 @@ class _RunMetrics {
37 final int childCount; 37 final int childCount;
38 } 38 }
39 39
  40 +class _WrapContext extends WidgetContext {
  41 + int firstChild = 0;
  42 + int lastChild = 0;
  43 +
  44 + @override
  45 + String toString() => 'WrapContext first:$firstChild last:$lastChild';
  46 +}
  47 +
40 /// A widget that displays its children in multiple horizontal or vertical runs. 48 /// A widget that displays its children in multiple horizontal or vertical runs.
41 -class Wrap extends MultiChildWidget { 49 +class Wrap extends MultiChildWidget implements SpanningWidget {
42 /// Creates a wrap layout. 50 /// Creates a wrap layout.
43 51
44 Wrap({ 52 Wrap({
@@ -83,7 +91,10 @@ class Wrap extends MultiChildWidget { @@ -83,7 +91,10 @@ class Wrap extends MultiChildWidget {
83 91
84 bool get textDirection => false; 92 bool get textDirection => false;
85 93
86 - bool _hasVisualOverflow = false; 94 + @override
  95 + bool get canSpan => _context.lastChild < children.length;
  96 +
  97 + final _WrapContext _context = _WrapContext();
87 98
88 bool get _debugHasNecessaryDirections { 99 bool get _debugHasNecessaryDirections {
89 assert(direction != null); 100 assert(direction != null);
@@ -192,9 +203,8 @@ class Wrap extends MultiChildWidget { @@ -192,9 +203,8 @@ class Wrap extends MultiChildWidget {
192 void layout(Context context, BoxConstraints constraints, 203 void layout(Context context, BoxConstraints constraints,
193 {bool parentUsesSize = false}) { 204 {bool parentUsesSize = false}) {
194 assert(_debugHasNecessaryDirections); 205 assert(_debugHasNecessaryDirections);
195 - _hasVisualOverflow = false;  
196 206
197 - if (children.isEmpty) { 207 + if (children.isEmpty || _context.firstChild >= children.length) {
198 box = PdfRect.fromPoints(PdfPoint.zero, constraints.smallest); 208 box = PdfRect.fromPoints(PdfPoint.zero, constraints.smallest);
199 return; 209 return;
200 } 210 }
@@ -234,7 +244,7 @@ class Wrap extends MultiChildWidget { @@ -234,7 +244,7 @@ class Wrap extends MultiChildWidget {
234 double runCrossAxisExtent = 0.0; 244 double runCrossAxisExtent = 0.0;
235 int childCount = 0; 245 int childCount = 0;
236 246
237 - for (Widget child in children) { 247 + for (Widget child in children.sublist(_context.firstChild)) {
238 child.layout(context, childConstraints, parentUsesSize: true); 248 child.layout(context, childConstraints, parentUsesSize: true);
239 249
240 final double childMainAxisExtent = _getMainAxisExtent(child); 250 final double childMainAxisExtent = _getMainAxisExtent(child);
@@ -297,9 +307,6 @@ class Wrap extends MultiChildWidget { @@ -297,9 +307,6 @@ class Wrap extends MultiChildWidget {
297 break; 307 break;
298 } 308 }
299 309
300 - _hasVisualOverflow = containerMainAxisExtent < mainAxisExtent ||  
301 - containerCrossAxisExtent < crossAxisExtent;  
302 -  
303 final double crossAxisFreeSpace = 310 final double crossAxisFreeSpace =
304 math.max(0.0, containerCrossAxisExtent - crossAxisExtent); 311 math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
305 double runLeadingSpace = 0.0; 312 double runLeadingSpace = 0.0;
@@ -333,7 +340,7 @@ class Wrap extends MultiChildWidget { @@ -333,7 +340,7 @@ class Wrap extends MultiChildWidget {
333 ? containerCrossAxisExtent - runLeadingSpace 340 ? containerCrossAxisExtent - runLeadingSpace
334 : runLeadingSpace; 341 : runLeadingSpace;
335 342
336 - int currentWidget = 0; 343 + _context.lastChild = _context.firstChild;
337 for (int i = 0; i < runCount; ++i) { 344 for (int i = 0; i < runCount; ++i) {
338 final _RunMetrics metrics = runMetrics[i]; 345 final _RunMetrics metrics = runMetrics[i];
339 final double runMainAxisExtent = metrics.mainAxisExtent; 346 final double runMainAxisExtent = metrics.mainAxisExtent;
@@ -377,6 +384,12 @@ class Wrap extends MultiChildWidget { @@ -377,6 +384,12 @@ class Wrap extends MultiChildWidget {
377 crossAxisOffset -= runCrossAxisExtent; 384 crossAxisOffset -= runCrossAxisExtent;
378 } 385 }
379 386
  387 + if (crossAxisOffset < -.01 ||
  388 + crossAxisOffset + runCrossAxisExtent > containerCrossAxisExtent) {
  389 + break;
  390 + }
  391 +
  392 + int currentWidget = _context.lastChild;
380 for (Widget child in children.sublist(currentWidget)) { 393 for (Widget child in children.sublist(currentWidget)) {
381 final int runIndex = childRunMetrics[child]; 394 final int runIndex = childRunMetrics[child];
382 if (runIndex != i) { 395 if (runIndex != i) {
@@ -407,6 +420,8 @@ class Wrap extends MultiChildWidget { @@ -407,6 +420,8 @@ class Wrap extends MultiChildWidget {
407 } else { 420 } else {
408 crossAxisOffset += runCrossAxisExtent + runBetweenSpace; 421 crossAxisOffset += runCrossAxisExtent + runBetweenSpace;
409 } 422 }
  423 +
  424 + _context.lastChild = currentWidget;
410 } 425 }
411 } 426 }
412 427
@@ -416,19 +431,26 @@ class Wrap extends MultiChildWidget { @@ -416,19 +431,26 @@ class Wrap extends MultiChildWidget {
416 431
417 context.canvas.saveContext(); 432 context.canvas.saveContext();
418 433
419 - if (_hasVisualOverflow) {  
420 - context.canvas  
421 - ..drawRect(box.left, box.bottom, box.width, box.height)  
422 - ..clipPath();  
423 - }  
424 -  
425 final Matrix4 mat = Matrix4.identity(); 434 final Matrix4 mat = Matrix4.identity();
426 mat.translate(box.x, box.y); 435 mat.translate(box.x, box.y);
427 context.canvas.setTransform(mat); 436 context.canvas.setTransform(mat);
428 - for (Widget child in children) { 437 + for (Widget child
  438 + in children.sublist(_context.firstChild, _context.lastChild)) {
429 child.paint(context); 439 child.paint(context);
430 } 440 }
431 441
432 context.canvas.restoreContext(); 442 context.canvas.restoreContext();
433 } 443 }
  444 +
  445 + @override
  446 + void restoreContext(WidgetContext context) {
  447 + if (context is _WrapContext) {
  448 + _context.firstChild = context.lastChild;
  449 + }
  450 + }
  451 +
  452 + @override
  453 + WidgetContext saveContext() {
  454 + return _context;
  455 + }
434 } 456 }
@@ -254,6 +254,36 @@ void main() { @@ -254,6 +254,36 @@ void main() {
254 ); 254 );
255 }); 255 });
256 256
  257 + test('Wrap Widget Multipage', () {
  258 + final math.Random rnd = math.Random(42);
  259 + pdf.addPage(
  260 + MultiPage(
  261 + pageFormat: const PdfPageFormat(200, 200),
  262 + margin: const EdgeInsets.all(10),
  263 + build: (Context context) => <Widget>[
  264 + Wrap(
  265 + direction: Axis.vertical,
  266 + verticalDirection: VerticalDirection.up,
  267 + alignment: WrapAlignment.center,
  268 + runAlignment: WrapAlignment.center,
  269 + crossAxisAlignment: WrapCrossAlignment.center,
  270 + spacing: 10,
  271 + runSpacing: 10,
  272 + children: List<Widget>.generate(
  273 + 17,
  274 + (int n) => Container(
  275 + width: rnd.nextDouble() * 100,
  276 + height: rnd.nextDouble() * 100,
  277 + alignment: Alignment.center,
  278 + color: PdfColors.blue800,
  279 + child: Text('$n'),
  280 + )),
  281 + )
  282 + ],
  283 + ),
  284 + );
  285 + });
  286 +
257 test('Wrap Widget Empty', () { 287 test('Wrap Widget Empty', () {
258 pdf.addPage(Page(build: (Context context) => Wrap())); 288 pdf.addPage(Page(build: (Context context) => Wrap()));
259 }); 289 });