Showing
5 changed files
with
215 additions
and
6 deletions
@@ -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, |
pdf/lib/widgets/stack.dart
0 → 100644
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()); |
-
Please register or login to post a comment