Milad akarie
Committed by David PHAM-VAN

support stack's Positioned directional

@@ -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());