Committed by
David PHAM-VAN
support stack's Positioned directional
Showing
3 changed files
with
151 additions
and
54 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 | ||
| @@ -188,7 +187,7 @@ class Transform extends SingleChildWidget { | @@ -188,7 +187,7 @@ class Transform extends SingleChildWidget { | ||
| 188 | 187 | ||
| 189 | final bool unconstrained; | 188 | final bool unconstrained; |
| 190 | 189 | ||
| 191 | - Matrix4 _effectiveTransform(Context context) { | 190 | + Matrix4 _effectiveTransform(Context context) { |
| 192 | final result = Matrix4.identity(); | 191 | final result = Matrix4.identity(); |
| 193 | if (origin != null) { | 192 | if (origin != null) { |
| 194 | result.translate(origin!.x, origin!.y); | 193 | result.translate(origin!.x, origin!.y); |
| @@ -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