Committed by
David PHAM-VAN
support stack's Positioned directional
Showing
3 changed files
with
150 additions
and
53 deletions
@@ -74,32 +74,31 @@ class Padding extends SingleChildWidget { | @@ -74,32 +74,31 @@ class Padding extends SingleChildWidget { | ||
74 | 74 | ||
75 | @override | 75 | @override |
76 | void layout(Context context, BoxConstraints constraints, {bool parentUsesSize = false}) { | 76 | void layout(Context context, BoxConstraints constraints, {bool parentUsesSize = false}) { |
77 | - final effectivePadding = padding.resolve(Directionality.of(context)); | 77 | + final resolvedPadding = padding.resolve(Directionality.of(context)); |
78 | if (child != null) { | 78 | if (child != null) { |
79 | - final childConstraints = constraints.deflate(effectivePadding); | 79 | + final childConstraints = constraints.deflate(resolvedPadding); |
80 | child!.layout(context, childConstraints, parentUsesSize: parentUsesSize); | 80 | child!.layout(context, childConstraints, parentUsesSize: parentUsesSize); |
81 | assert(child!.box != null); | 81 | assert(child!.box != null); |
82 | box = constraints.constrainRect( | 82 | box = constraints.constrainRect( |
83 | - width: child!.box!.width + effectivePadding.horizontal, | ||
84 | - height: child!.box!.height + effectivePadding.vertical); | 83 | + width: child!.box!.width + resolvedPadding.horizontal, height: child!.box!.height + resolvedPadding.vertical); |
85 | } else { | 84 | } else { |
86 | - box = constraints.constrainRect(width: effectivePadding.horizontal, height: effectivePadding.vertical); | 85 | + box = constraints.constrainRect(width: resolvedPadding.horizontal, height: resolvedPadding.vertical); |
87 | } | 86 | } |
88 | } | 87 | } |
89 | 88 | ||
90 | @override | 89 | @override |
91 | void debugPaint(Context context) { | 90 | void debugPaint(Context context) { |
92 | - final effectivePadding = padding.resolve(Directionality.of(context)); | 91 | + final resolvedPadding = padding.resolve(Directionality.of(context)); |
93 | context.canvas | 92 | context.canvas |
94 | ..setFillColor(PdfColors.lime) | 93 | ..setFillColor(PdfColors.lime) |
95 | ..moveTo(box!.x, box!.y) | 94 | ..moveTo(box!.x, box!.y) |
96 | ..lineTo(box!.right, box!.y) | 95 | ..lineTo(box!.right, box!.y) |
97 | ..lineTo(box!.right, box!.top) | 96 | ..lineTo(box!.right, box!.top) |
98 | ..lineTo(box!.x, box!.top) | 97 | ..lineTo(box!.x, box!.top) |
99 | - ..moveTo(box!.x + effectivePadding.left, box!.y + effectivePadding.bottom) | ||
100 | - ..lineTo(box!.x + effectivePadding.left, box!.top - effectivePadding.top) | ||
101 | - ..lineTo(box!.right - effectivePadding.right, box!.top - effectivePadding.top) | ||
102 | - ..lineTo(box!.right - effectivePadding.right, box!.y + effectivePadding.bottom) | 98 | + ..moveTo(box!.x + resolvedPadding.left, box!.y + resolvedPadding.bottom) |
99 | + ..lineTo(box!.x + resolvedPadding.left, box!.top - resolvedPadding.top) | ||
100 | + ..lineTo(box!.right - resolvedPadding.right, box!.top - resolvedPadding.top) | ||
101 | + ..lineTo(box!.right - resolvedPadding.right, box!.y + resolvedPadding.bottom) | ||
103 | ..fillPath(); | 102 | ..fillPath(); |
104 | } | 103 | } |
105 | 104 |
@@ -19,9 +19,7 @@ import 'dart:math' as math; | @@ -19,9 +19,7 @@ import 'dart:math' as math; | ||
19 | import 'package:vector_math/vector_math_64.dart'; | 19 | import 'package:vector_math/vector_math_64.dart'; |
20 | 20 | ||
21 | import '../../pdf.dart'; | 21 | import '../../pdf.dart'; |
22 | -import 'geometry.dart'; | ||
23 | -import 'text.dart'; | ||
24 | -import 'widget.dart'; | 22 | +import '../../widgets.dart'; |
25 | 23 | ||
26 | /// How to size the non-positioned children of a [Stack]. | 24 | /// How to size the non-positioned children of a [Stack]. |
27 | enum StackFit { loose, expand, passthrough } | 25 | enum StackFit { loose, expand, passthrough } |
@@ -33,22 +31,26 @@ enum Overflow { visible, clip } | @@ -33,22 +31,26 @@ enum Overflow { visible, clip } | ||
33 | /// A widget that controls where a child of a [Stack] is positioned. | 31 | /// A widget that controls where a child of a [Stack] is positioned. |
34 | class Positioned extends SingleChildWidget { | 32 | class Positioned extends SingleChildWidget { |
35 | Positioned({ | 33 | Positioned({ |
36 | - this.left, | 34 | + double? left, |
37 | this.top, | 35 | this.top, |
38 | - this.right, | 36 | + double? right, |
39 | this.bottom, | 37 | this.bottom, |
40 | required Widget child, | 38 | required Widget child, |
41 | - }) : super(child: child); | 39 | + }) : _left = left, |
40 | + _right = right, | ||
41 | + super(child: child); | ||
42 | 42 | ||
43 | /// Creates a Positioned object with left, top, right, and bottom set to 0.0 | 43 | /// Creates a Positioned object with left, top, right, and bottom set to 0.0 |
44 | /// unless a value for them is passed. | 44 | /// unless a value for them is passed. |
45 | Positioned.fill({ | 45 | Positioned.fill({ |
46 | - this.left = 0.0, | 46 | + double? left = 0.0, |
47 | this.top = 0.0, | 47 | this.top = 0.0, |
48 | - this.right = 0.0, | 48 | + double? right = 0.0, |
49 | this.bottom = 0.0, | 49 | this.bottom = 0.0, |
50 | required Widget child, | 50 | required Widget child, |
51 | - }) : super(child: child); | 51 | + }) : _left = left, |
52 | + _right = right, | ||
53 | + super(child: child); | ||
52 | 54 | ||
53 | /// Creates a widget that controls where a child of a [Stack] is positioned. | 55 | /// Creates a widget that controls where a child of a [Stack] is positioned. |
54 | factory Positioned.directional({ | 56 | factory Positioned.directional({ |
@@ -80,11 +82,14 @@ class Positioned extends SingleChildWidget { | @@ -80,11 +82,14 @@ class Positioned extends SingleChildWidget { | ||
80 | ); | 82 | ); |
81 | } | 83 | } |
82 | 84 | ||
83 | - final double? left; | 85 | + double? get left => _left; |
84 | 86 | ||
85 | - final double? top; | 87 | + double? get right => _right; |
88 | + | ||
89 | + final double? _left; | ||
90 | + final double? _right; | ||
86 | 91 | ||
87 | - final double? right; | 92 | + final double? top; |
88 | 93 | ||
89 | final double? bottom; | 94 | final double? bottom; |
90 | 95 | ||
@@ -99,6 +104,63 @@ class Positioned extends SingleChildWidget { | @@ -99,6 +104,63 @@ class Positioned extends SingleChildWidget { | ||
99 | } | 104 | } |
100 | } | 105 | } |
101 | 106 | ||
107 | +/// A widget that controls where a child of a [Stack] is positioned without | ||
108 | +/// committing to a specific [TextDirection]. | ||
109 | +class PositionedDirectional extends Positioned { | ||
110 | + PositionedDirectional({ | ||
111 | + this.start, | ||
112 | + this.end, | ||
113 | + double? top, | ||
114 | + double? bottom, | ||
115 | + required Widget child, | ||
116 | + }) : super( | ||
117 | + child: child, | ||
118 | + top: top, | ||
119 | + bottom: bottom, | ||
120 | + ); | ||
121 | + | ||
122 | + PositionedDirectional.fill({ | ||
123 | + this.start = 0.0, | ||
124 | + this.end = 0.0, | ||
125 | + double? top = 0.0, | ||
126 | + double? bottom = 0.0, | ||
127 | + required Widget child, | ||
128 | + }) : super( | ||
129 | + child: child, | ||
130 | + top: top, | ||
131 | + bottom: bottom, | ||
132 | + ); | ||
133 | + | ||
134 | + final double? start; | ||
135 | + | ||
136 | + double? _resolvedLeft; | ||
137 | + | ||
138 | + double? _resolvedRight; | ||
139 | + | ||
140 | + @override | ||
141 | + double? get left => _resolvedLeft; | ||
142 | + | ||
143 | + @override | ||
144 | + double? get right => _resolvedRight; | ||
145 | + | ||
146 | + final double? end; | ||
147 | + | ||
148 | + @override | ||
149 | + void layout(Context context, BoxConstraints constraints, {bool parentUsesSize = false}) { | ||
150 | + super.layout(context, constraints, parentUsesSize: parentUsesSize); | ||
151 | + switch (Directionality.of(context)) { | ||
152 | + case TextDirection.rtl: | ||
153 | + _resolvedLeft = end; | ||
154 | + _resolvedRight = start; | ||
155 | + break; | ||
156 | + case TextDirection.ltr: | ||
157 | + _resolvedLeft = start; | ||
158 | + _resolvedRight = end; | ||
159 | + break; | ||
160 | + } | ||
161 | + } | ||
162 | +} | ||
163 | + | ||
102 | /// A widget that positions its children relative to the edges of its box. | 164 | /// A widget that positions its children relative to the edges of its box. |
103 | class Stack extends MultiChildWidget { | 165 | class Stack extends MultiChildWidget { |
104 | Stack({ | 166 | Stack({ |
@@ -110,7 +172,7 @@ class Stack extends MultiChildWidget { | @@ -110,7 +172,7 @@ class Stack extends MultiChildWidget { | ||
110 | 172 | ||
111 | /// How to align the non-positioned and partially-positioned children in the | 173 | /// How to align the non-positioned and partially-positioned children in the |
112 | /// stack. | 174 | /// stack. |
113 | - final Alignment alignment; | 175 | + final AlignmentGeometry alignment; |
114 | 176 | ||
115 | /// How to size the non-positioned children in the stack. | 177 | /// How to size the non-positioned children in the stack. |
116 | final StackFit fit; | 178 | final StackFit fit; |
@@ -119,8 +181,7 @@ class Stack extends MultiChildWidget { | @@ -119,8 +181,7 @@ class Stack extends MultiChildWidget { | ||
119 | final Overflow overflow; | 181 | final Overflow overflow; |
120 | 182 | ||
121 | @override | 183 | @override |
122 | - void layout(Context context, BoxConstraints constraints, | ||
123 | - {bool parentUsesSize = false}) { | 184 | + void layout(Context context, BoxConstraints constraints, {bool parentUsesSize = false}) { |
124 | final childCount = children.length; | 185 | final childCount = children.length; |
125 | 186 | ||
126 | var hasNonPositionedChildren = false; | 187 | var hasNonPositionedChildren = false; |
@@ -150,7 +211,6 @@ class Stack extends MultiChildWidget { | @@ -150,7 +211,6 @@ class Stack extends MultiChildWidget { | ||
150 | for (final child in children) { | 211 | for (final child in children) { |
151 | if (child is! Positioned) { | 212 | if (child is! Positioned) { |
152 | hasNonPositionedChildren = true; | 213 | hasNonPositionedChildren = true; |
153 | - | ||
154 | child.layout(context, nonPositionedConstraints, parentUsesSize: true); | 214 | child.layout(context, nonPositionedConstraints, parentUsesSize: true); |
155 | assert(child.box != null); | 215 | assert(child.box != null); |
156 | 216 | ||
@@ -167,28 +227,25 @@ class Stack extends MultiChildWidget { | @@ -167,28 +227,25 @@ class Stack extends MultiChildWidget { | ||
167 | } else { | 227 | } else { |
168 | box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest); | 228 | box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest); |
169 | } | 229 | } |
170 | - | 230 | + final resolvedAlignment = alignment.resolve(Directionality.of(context)); |
171 | for (final child in children) { | 231 | for (final child in children) { |
172 | if (child is! Positioned) { | 232 | if (child is! Positioned) { |
173 | - child.box = PdfRect.fromPoints( | ||
174 | - alignment.inscribe(child.box!.size, box!).offset, child.box!.size); | 233 | + child.box = PdfRect.fromPoints(resolvedAlignment.inscribe(child.box!.size, box!).offset, child.box!.size); |
175 | } else { | 234 | } else { |
176 | final positioned = child; | 235 | final positioned = child; |
236 | + | ||
177 | var childConstraints = const BoxConstraints(); | 237 | var childConstraints = const BoxConstraints(); |
178 | 238 | ||
179 | if (positioned.left != null && positioned.right != null) { | 239 | if (positioned.left != null && positioned.right != null) { |
180 | - childConstraints = childConstraints.tighten( | ||
181 | - width: box!.width - positioned.right! - positioned.left!); | 240 | + childConstraints = childConstraints.tighten(width: box!.width - positioned.right! - positioned.left!); |
182 | } else if (positioned.width != null) { | 241 | } else if (positioned.width != null) { |
183 | childConstraints = childConstraints.tighten(width: positioned.width); | 242 | childConstraints = childConstraints.tighten(width: positioned.width); |
184 | } | 243 | } |
185 | 244 | ||
186 | if (positioned.top != null && positioned.bottom != null) { | 245 | if (positioned.top != null && positioned.bottom != null) { |
187 | - childConstraints = childConstraints.tighten( | ||
188 | - height: box!.height - positioned.bottom! - positioned.top!); | 246 | + childConstraints = childConstraints.tighten(height: box!.height - positioned.bottom! - positioned.top!); |
189 | } else if (positioned.height != null) { | 247 | } else if (positioned.height != null) { |
190 | - childConstraints = | ||
191 | - childConstraints.tighten(height: positioned.height); | 248 | + childConstraints = childConstraints.tighten(height: positioned.height); |
192 | } | 249 | } |
193 | 250 | ||
194 | positioned.layout(context, childConstraints, parentUsesSize: true); | 251 | positioned.layout(context, childConstraints, parentUsesSize: true); |
@@ -200,7 +257,7 @@ class Stack extends MultiChildWidget { | @@ -200,7 +257,7 @@ class Stack extends MultiChildWidget { | ||
200 | } else if (positioned.right != null) { | 257 | } else if (positioned.right != null) { |
201 | x = box!.width - positioned.right! - positioned.width!; | 258 | x = box!.width - positioned.right! - positioned.width!; |
202 | } else { | 259 | } else { |
203 | - x = alignment.inscribe(positioned.box!.size, box!).x; | 260 | + x = resolvedAlignment.inscribe(positioned.box!.size, box!).x; |
204 | } | 261 | } |
205 | 262 | ||
206 | double? y; | 263 | double? y; |
@@ -209,11 +266,10 @@ class Stack extends MultiChildWidget { | @@ -209,11 +266,10 @@ class Stack extends MultiChildWidget { | ||
209 | } else if (positioned.top != null) { | 266 | } else if (positioned.top != null) { |
210 | y = box!.height - positioned.top! - positioned.height!; | 267 | y = box!.height - positioned.top! - positioned.height!; |
211 | } else { | 268 | } else { |
212 | - y = alignment.inscribe(positioned.box!.size, box!).y; | 269 | + y = resolvedAlignment.inscribe(positioned.box!.size, box!).y; |
213 | } | 270 | } |
214 | 271 | ||
215 | - positioned.box = | ||
216 | - PdfRect.fromPoints(PdfPoint(x!, y!), positioned.box!.size); | 272 | + positioned.box = PdfRect.fromPoints(PdfPoint(x!, y!), positioned.box!.size); |
217 | } | 273 | } |
218 | } | 274 | } |
219 | } | 275 | } |
@@ -46,20 +46,7 @@ void main() { | @@ -46,20 +46,7 @@ void main() { | ||
46 | pdf = Document(); | 46 | pdf = Document(); |
47 | }); | 47 | }); |
48 | 48 | ||
49 | - test('Should render a blue box followed by a red box ordered RTL aligned right', () { | ||
50 | - pdf.addPage( | ||
51 | - Page( | ||
52 | - textDirection: TextDirection.rtl, | ||
53 | - pageFormat: const PdfPageFormat(150, 50), | ||
54 | - build: (Context context) => TestAnnotation( | ||
55 | - anno: 'RTL Row', | ||
56 | - child: Row( | ||
57 | - children: [_blueBox, _redBox], | ||
58 | - ), | ||
59 | - ), | ||
60 | - ), | ||
61 | - ); | ||
62 | - }); | 49 | + |
63 | 50 | ||
64 | test('RTL Text', () { | 51 | test('RTL Text', () { |
65 | pdf.addPage( | 52 | pdf.addPage( |
@@ -147,6 +134,21 @@ void main() { | @@ -147,6 +134,21 @@ void main() { | ||
147 | ); | 134 | ); |
148 | }); | 135 | }); |
149 | 136 | ||
137 | + test('Should render a blue box followed by a red box ordered RTL aligned right', () { | ||
138 | + pdf.addPage( | ||
139 | + Page( | ||
140 | + textDirection: TextDirection.rtl, | ||
141 | + pageFormat: const PdfPageFormat(150, 50), | ||
142 | + build: (Context context) => TestAnnotation( | ||
143 | + anno: 'RTL Row', | ||
144 | + child: Row( | ||
145 | + children: [_blueBox, _redBox], | ||
146 | + ), | ||
147 | + ), | ||
148 | + ), | ||
149 | + ); | ||
150 | + }); | ||
151 | + | ||
150 | test('Should render a blue box followed by a red box ordered RTL with aligned center', () { | 152 | test('Should render a blue box followed by a red box ordered RTL with aligned center', () { |
151 | pdf.addPage( | 153 | pdf.addPage( |
152 | Page( | 154 | Page( |
@@ -651,6 +653,46 @@ void main() { | @@ -651,6 +653,46 @@ void main() { | ||
651 | ); | 653 | ); |
652 | }); | 654 | }); |
653 | 655 | ||
656 | + test('RTL Stack, should directional child to right44', () { | ||
657 | + pdf.addPage( | ||
658 | + Page( | ||
659 | + textDirection: TextDirection.rtl, | ||
660 | + pageFormat: const PdfPageFormat(150, 150), | ||
661 | + build: (Context context) { | ||
662 | + return TestAnnotation( | ||
663 | + anno: 'RTL Stack PositionDirectional.start', | ||
664 | + child: Stack(children: [ | ||
665 | + PositionedDirectional( | ||
666 | + start: 0, | ||
667 | + child: _blueBox, | ||
668 | + ) | ||
669 | + ]), | ||
670 | + ); | ||
671 | + }, | ||
672 | + ), | ||
673 | + ); | ||
674 | + }); | ||
675 | + | ||
676 | + test('LTR Stack, should directional child to right44', () { | ||
677 | + pdf.addPage( | ||
678 | + Page( | ||
679 | + textDirection: TextDirection.ltr, | ||
680 | + pageFormat: const PdfPageFormat(150, 150), | ||
681 | + build: (Context context) { | ||
682 | + return TestAnnotation( | ||
683 | + anno: 'LTR Stack PositionDirectional.start', | ||
684 | + child: Stack(children: [ | ||
685 | + PositionedDirectional( | ||
686 | + start: 0, | ||
687 | + child: _blueBox, | ||
688 | + ) | ||
689 | + ]), | ||
690 | + ); | ||
691 | + }, | ||
692 | + ), | ||
693 | + ); | ||
694 | + }); | ||
695 | + | ||
654 | tearDownAll(() async { | 696 | tearDownAll(() async { |
655 | final file = File('rtl-layout.pdf'); | 697 | final file = File('rtl-layout.pdf'); |
656 | await file.writeAsBytes(await pdf.save()); | 698 | await file.writeAsBytes(await pdf.save()); |
-
Please register or login to post a comment