David PHAM-VAN

Add Stack Widget

@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 * Improve font bounds calculation 3 * Improve font bounds calculation
4 * Add RichText Widget 4 * Add RichText Widget
5 * Fix MultiPage max height 5 * Fix MultiPage max height
  6 +* Add Stack Widget
6 7
7 # 1.3.2 8 # 1.3.2
8 * Update Readme 9 * Update Readme
@@ -34,6 +34,7 @@ part 'widgets/geometry.dart'; @@ -34,6 +34,7 @@ part 'widgets/geometry.dart';
34 part 'widgets/grid_view.dart'; 34 part 'widgets/grid_view.dart';
35 part 'widgets/image.dart'; 35 part 'widgets/image.dart';
36 part 'widgets/placeholders.dart'; 36 part 'widgets/placeholders.dart';
  37 +part 'widgets/stack.dart';
37 part 'widgets/table.dart'; 38 part 'widgets/table.dart';
38 part 'widgets/text.dart'; 39 part 'widgets/text.dart';
39 part 'widgets/theme.dart'; 40 part 'widgets/theme.dart';
@@ -32,6 +32,13 @@ class BoxConstraints { @@ -32,6 +32,13 @@ class BoxConstraints {
32 minHeight = height != null ? height : 0.0, 32 minHeight = height != null ? height : 0.0,
33 maxHeight = height != null ? height : double.infinity; 33 maxHeight = height != null ? height : double.infinity;
34 34
  35 + /// Creates box constraints that is respected only by the given size.
  36 + BoxConstraints.tight(PdfPoint size)
  37 + : minWidth = size.x,
  38 + maxWidth = size.x,
  39 + minHeight = size.y,
  40 + maxHeight = size.y;
  41 +
35 /// Creates box constraints that expand to fill another box constraints. 42 /// Creates box constraints that expand to fill another box constraints.
36 const BoxConstraints.expand({double width, double height}) 43 const BoxConstraints.expand({double width, double height})
37 : minWidth = width != null ? width : double.infinity, 44 : minWidth = width != null ? width : double.infinity,
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +/// How to size the non-positioned children of a [Stack].
  20 +enum StackFit { loose, expand, passthrough }
  21 +
  22 +/// Whether overflowing children should be clipped, or their overflow be
  23 +/// visible.
  24 +enum Overflow { visible, clip }
  25 +
  26 +/// A widget that controls where a child of a [Stack] is positioned.
  27 +class Positioned extends SingleChildWidget {
  28 + Positioned({
  29 + this.left,
  30 + this.top,
  31 + this.right,
  32 + this.bottom,
  33 + @required Widget child,
  34 + }) : super(child: child);
  35 +
  36 + final double left;
  37 +
  38 + final double top;
  39 +
  40 + final double right;
  41 +
  42 + final double bottom;
  43 +
  44 + double get width => box?.width;
  45 +
  46 + double get height => box?.height;
  47 +}
  48 +
  49 +/// A widget that positions its children relative to the edges of its box.
  50 +class Stack extends MultiChildWidget {
  51 + Stack({
  52 + this.alignment = Alignment.topLeft,
  53 + this.fit = StackFit.loose,
  54 + this.overflow = Overflow.clip,
  55 + List<Widget> children = const <Widget>[],
  56 + }) : super(children: children);
  57 +
  58 + /// How to align the non-positioned and partially-positioned children in the
  59 + /// stack.
  60 + final Alignment alignment;
  61 +
  62 + /// How to size the non-positioned children in the stack.
  63 + final StackFit fit;
  64 +
  65 + /// Whether overflowing children should be clipped.
  66 + final Overflow overflow;
  67 +
  68 + @override
  69 + void layout(Context context, BoxConstraints constraints,
  70 + {bool parentUsesSize = false}) {
  71 + final int childCount = children.length;
  72 +
  73 + bool hasNonPositionedChildren = false;
  74 +
  75 + if (childCount == 0) {
  76 + box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest);
  77 + return;
  78 + }
  79 +
  80 + double width = constraints.minWidth;
  81 + double height = constraints.minHeight;
  82 +
  83 + BoxConstraints nonPositionedConstraints;
  84 + assert(fit != null);
  85 + switch (fit) {
  86 + case StackFit.loose:
  87 + nonPositionedConstraints = constraints.loosen();
  88 + break;
  89 + case StackFit.expand:
  90 + nonPositionedConstraints = BoxConstraints.tight(constraints.biggest);
  91 + break;
  92 + case StackFit.passthrough:
  93 + nonPositionedConstraints = constraints;
  94 + break;
  95 + }
  96 + assert(nonPositionedConstraints != null);
  97 +
  98 + for (Widget child in children) {
  99 + if (!(child is Positioned)) {
  100 + hasNonPositionedChildren = true;
  101 +
  102 + child.layout(context, nonPositionedConstraints, parentUsesSize: true);
  103 +
  104 + final PdfRect childSize = child.box;
  105 + width = math.max(width, childSize.width);
  106 + height = math.max(height, childSize.height);
  107 + }
  108 + }
  109 +
  110 + if (hasNonPositionedChildren) {
  111 + box = PdfRect.fromPoints(PdfPoint.zero, PdfPoint(width, height));
  112 + assert(box.width == constraints.constrainWidth(width));
  113 + assert(box.height == constraints.constrainHeight(height));
  114 + } else {
  115 + box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest);
  116 + }
  117 +
  118 + for (Widget child in children) {
  119 + if (!(child is Positioned)) {
  120 + child.box = PdfRect.fromPoints(
  121 + alignment.inscribe(child.box.size, box).offset, child.box.size);
  122 + } else {
  123 + final Positioned positioned = child;
  124 + BoxConstraints childConstraints = const BoxConstraints();
  125 +
  126 + if (positioned.left != null && positioned.right != null)
  127 + childConstraints = childConstraints.tighten(
  128 + width: box.width - positioned.right - positioned.left);
  129 + else if (positioned.width != null)
  130 + childConstraints = childConstraints.tighten(width: positioned.width);
  131 +
  132 + if (positioned.top != null && positioned.bottom != null)
  133 + childConstraints = childConstraints.tighten(
  134 + height: box.height - positioned.bottom - positioned.top);
  135 + else if (positioned.height != null)
  136 + childConstraints =
  137 + childConstraints.tighten(height: positioned.height);
  138 +
  139 + positioned.layout(context, childConstraints, parentUsesSize: true);
  140 +
  141 + double x;
  142 + if (positioned.left != null) {
  143 + x = positioned.left;
  144 + } else if (positioned.right != null) {
  145 + x = box.width - positioned.right - positioned.width;
  146 + } else {
  147 + x = alignment.inscribe(positioned.box.size, box).x;
  148 + }
  149 +
  150 + double y;
  151 + if (positioned.bottom != null) {
  152 + y = positioned.bottom;
  153 + } else if (positioned.top != null) {
  154 + y = box.height - positioned.top - positioned.height;
  155 + } else {
  156 + y = alignment.inscribe(positioned.box.size, box).y;
  157 + }
  158 +
  159 + positioned.box =
  160 + PdfRect.fromPoints(PdfPoint(x, y), positioned.box.size);
  161 + }
  162 + }
  163 + }
  164 +
  165 + @override
  166 + void paint(Context context) {
  167 + super.paint(context);
  168 +
  169 + final Matrix4 mat = Matrix4.identity();
  170 + mat.translate(box.x, box.y);
  171 + context.canvas
  172 + ..saveContext()
  173 + ..setTransform(mat);
  174 + if (overflow == Overflow.clip) {
  175 + context.canvas
  176 + ..drawRect(0, 0, box.width, box.height)
  177 + ..clipPath();
  178 + }
  179 + for (Widget child in children) {
  180 + child.paint(context);
  181 + }
  182 + context.canvas.restoreContext();
  183 + }
  184 +}
@@ -133,15 +133,30 @@ void main() { @@ -133,15 +133,30 @@ void main() {
133 <String>['York Steak House', 'Outi Vuorinen', 'Finland'], 133 <String>['York Steak House', 'Outi Vuorinen', 'Finland'],
134 <String>['Weathervane', 'Else Jeremiassen', 'Iceland'], 134 <String>['Weathervane', 'Else Jeremiassen', 'Iceland'],
135 ]), 135 ]),
136 - CustomPaint( 136 + ]));
  137 +
  138 + pdf.addPage(Page(
  139 + pageFormat: const PdfPageFormat(400.0, 200.0),
  140 + margin: const EdgeInsets.all(10.0),
  141 + build: (Context context) => Stack(overflow: Overflow.visible,
  142 + // fit: StackFit.expand,
  143 + // alignment: Alignment.bottomRight,
  144 + children: <Widget>[
  145 + Positioned(
  146 + right: 10,
  147 + top: 10,
  148 + child: CustomPaint(
137 size: const PdfPoint(50, 50), 149 size: const PdfPoint(50, 50),
138 painter: (PdfGraphics canvas, PdfPoint size) { 150 painter: (PdfGraphics canvas, PdfPoint size) {
139 canvas 151 canvas
140 ..setColor(PdfColor.indigo) 152 ..setColor(PdfColor.indigo)
141 ..drawRRect(0, 0, size.x, size.y, 10, 10) 153 ..drawRRect(0, 0, size.x, size.y, 10, 10)
142 ..fillPath(); 154 ..fillPath();
143 - }),  
144 - RichText( 155 + })),
  156 + Positioned(
  157 + left: 10,
  158 + bottom: 10,
  159 + child: RichText(
145 text: TextSpan( 160 text: TextSpan(
146 text: 'Hello ', 161 text: 'Hello ',
147 style: Theme.of(context).defaultTextStyle, 162 style: Theme.of(context).defaultTextStyle,
@@ -150,14 +165,15 @@ void main() { @@ -150,14 +165,15 @@ void main() {
150 text: 'bold', 165 text: 'bold',
151 style: Theme.of(context) 166 style: Theme.of(context)
152 .defaultTextStyleBold 167 .defaultTextStyleBold
153 - .copyWith(fontSize: 20, color: PdfColor.blue)), 168 + .copyWith(
  169 + fontSize: 20, color: PdfColor.blue)),
154 const TextSpan( 170 const TextSpan(
155 text: ' world!', 171 text: ' world!',
156 ), 172 ),
157 ], 173 ],
158 ), 174 ),
159 - )  
160 - ])); 175 + ))
  176 + ])));
161 177
162 final File file = File('widgets.pdf'); 178 final File file = File('widgets.pdf');
163 file.writeAsBytesSync(pdf.document.save()); 179 file.writeAsBytesSync(pdf.document.save());