Skip to content

Commit 45eeef2

Browse files
authored
Fx FHeader vertical and RTL alignment (#706)
* Fx FHeader vertical and RTL alignment * Prepare Forui for review * Update macos-latest goldens * Update windows-latest goldens --------- Co-authored-by: Pante <[email protected]>
1 parent 1e8f186 commit 45eeef2

File tree

82 files changed

+243
-51
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+243
-51
lines changed

forui/CHANGELOG.md

Lines changed: 8 additions & 0 deletions

forui/lib/src/widgets/header/header.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import 'dart:math';
12
import 'dart:ui';
23

34
import 'package:flutter/foundation.dart';
5+
import 'package:flutter/rendering.dart';
46
import 'package:flutter/services.dart';
57
import 'package:flutter/widgets.dart';
68

79
import 'package:meta/meta.dart';
8-
import 'package:sugar/collection.dart';
910

1011
import 'package:forui/forui.dart';
1112
import 'package:forui/src/foundation/debug.dart';
13+
import 'package:forui/src/foundation/rendering.dart';
1214

1315
part 'header.design.dart';
1416

forui/lib/src/widgets/header/nested_header.dart

Lines changed: 125 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -40,50 +40,6 @@ class _FNestedHeader extends FHeader {
4040
final style = this.style?.call(context.theme.headerStyles.nestedStyle) ?? context.theme.headerStyles.nestedStyle;
4141
final alignment = titleAlignment.resolve(Directionality.maybeOf(context) ?? TextDirection.ltr);
4242

43-
Widget title = Align(
44-
alignment: alignment,
45-
child: Padding(
46-
padding: const EdgeInsets.symmetric(horizontal: 10.0),
47-
child: DefaultTextStyle.merge(
48-
overflow: TextOverflow.fade,
49-
maxLines: 1,
50-
softWrap: false,
51-
style: style.titleTextStyle,
52-
child: this.title,
53-
),
54-
),
55-
);
56-
57-
if (prefixes.isNotEmpty || suffixes.isNotEmpty) {
58-
final spacing = SizedBox(width: style.actionSpacing);
59-
final prefixes = Row(
60-
mainAxisSize: MainAxisSize.min,
61-
children: separate(this.prefixes, by: [spacing]),
62-
);
63-
final suffixes = Row(
64-
mainAxisSize: MainAxisSize.min,
65-
children: separate(this.suffixes, by: [spacing]),
66-
);
67-
68-
// We use a stack as a row could result in the title being off centered if the icon on the left or right is
69-
// missing/different sizes.
70-
title = alignment.x == 0
71-
? Stack(
72-
children: [
73-
title,
74-
Align(alignment: Alignment.centerLeft, child: prefixes),
75-
Align(alignment: Alignment.centerRight, child: suffixes),
76-
],
77-
)
78-
: Row(
79-
children: [
80-
prefixes,
81-
Expanded(child: title),
82-
suffixes,
83-
],
84-
);
85-
}
86-
8743
Widget header = SafeArea(
8844
bottom: false,
8945
child: Semantics(
@@ -92,7 +48,28 @@ class _FNestedHeader extends FHeader {
9248
decoration: style.decoration,
9349
child: Padding(
9450
padding: style.padding,
95-
child: FHeaderData(actionStyle: style.actionStyle, child: title),
51+
child: FHeaderData(
52+
actionStyle: style.actionStyle,
53+
child: _NestedHeader(
54+
alignment: alignment,
55+
prefixes: Row(mainAxisSize: MainAxisSize.min, spacing: style.actionSpacing, children: prefixes),
56+
title: Padding(
57+
padding: const EdgeInsets.symmetric(horizontal: 10.0),
58+
child: DefaultTextStyle.merge(
59+
overflow: TextOverflow.ellipsis,
60+
maxLines: 1,
61+
softWrap: false,
62+
style: style.titleTextStyle,
63+
textHeightBehavior: const TextHeightBehavior(
64+
applyHeightToFirstAscent: false,
65+
applyHeightToLastDescent: false,
66+
),
67+
child: title,
68+
),
69+
),
70+
suffixes: Row(mainAxisSize: MainAxisSize.min, spacing: style.actionSpacing, children: suffixes),
71+
),
72+
),
9673
),
9774
),
9875
),
@@ -122,3 +99,106 @@ class _FNestedHeader extends FHeader {
12299
..add(DiagnosticsProperty('titleAlignment', titleAlignment));
123100
}
124101
}
102+
103+
class _NestedHeader extends MultiChildRenderObjectWidget {
104+
final Alignment alignment;
105+
106+
_NestedHeader({required this.alignment, required Widget prefixes, required Widget title, required Widget suffixes})
107+
: super(children: [prefixes, title, suffixes]);
108+
109+
@override
110+
RenderObject createRenderObject(BuildContext context) =>
111+
_RenderNestedHeader(alignment: alignment, textDirection: Directionality.of(context));
112+
113+
@override
114+
void updateRenderObject(BuildContext context, covariant _RenderNestedHeader renderObject) => renderObject
115+
..alignment = alignment
116+
..direction = Directionality.of(context);
117+
118+
@override
119+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
120+
super.debugFillProperties(properties);
121+
properties.add(DiagnosticsProperty('alignment', alignment));
122+
}
123+
}
124+
125+
class _RenderNestedHeader extends RenderBox
126+
with ContainerRenderObjectMixin<RenderBox, DefaultData>, RenderBoxContainerDefaultsMixin<RenderBox, DefaultData> {
127+
Alignment _alignment;
128+
TextDirection _direction;
129+
130+
_RenderNestedHeader({required Alignment alignment, required TextDirection textDirection})
131+
: _alignment = alignment,
132+
_direction = textDirection;
133+
134+
@override
135+
void setupParentData(RenderBox child) => child.parentData = DefaultData();
136+
137+
@override
138+
void performLayout() {
139+
if (childCount == 0) {
140+
size = constraints.smallest;
141+
return;
142+
}
143+
144+
final prefixes = firstChild!;
145+
final title = childAfter(prefixes)!;
146+
final suffixes = childAfter(title)!;
147+
148+
// We prioritize the prefixes and suffixes since they are interactive.
149+
prefixes.layout(constraints, parentUsesSize: true);
150+
suffixes.layout(constraints, parentUsesSize: true);
151+
title.layout(
152+
constraints.copyWith(maxWidth: constraints.maxWidth - prefixes.size.width - suffixes.size.width),
153+
parentUsesSize: true,
154+
);
155+
156+
final height = [title.size.height, prefixes.size.height, suffixes.size.height].reduce(max);
157+
size = constraints.constrain(Size(constraints.maxWidth, height));
158+
159+
final (left, right) = _direction == TextDirection.ltr ? (prefixes, suffixes) : (suffixes, prefixes);
160+
left.data.offset = Offset(0, (size.height - left.size.height) / 2);
161+
right.data.offset = Offset(size.width - right.size.width, (size.height - right.size.height) / 2);
162+
163+
// Position title based on Alignment (-1 to 1) on each axis where 0 is center.
164+
// Title x-axis is relative to the center of the NestedHeader container
165+
final titleX = (size.width - title.size.width) / 2 * (alignment.x + 1);
166+
final titleY = (size.height - title.size.height) * (alignment.y + 1) / 2;
167+
title.data.offset = Offset(titleX.clamp(left.size.width, size.width - right.size.width - title.size.width), titleY);
168+
}
169+
170+
@override
171+
void paint(PaintingContext context, Offset offset) => defaultPaint(context, offset);
172+
173+
@override
174+
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) =>
175+
defaultHitTestChildren(result, position: position);
176+
177+
Alignment get alignment => _alignment;
178+
179+
set alignment(Alignment value) {
180+
if (_alignment == value) {
181+
return;
182+
}
183+
_alignment = value;
184+
markNeedsLayout();
185+
}
186+
187+
TextDirection get direction => _direction;
188+
189+
set direction(TextDirection value) {
190+
if (_direction == value) {
191+
return;
192+
}
193+
_direction = value;
194+
markNeedsLayout();
195+
}
196+
197+
@override
198+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
199+
super.debugFillProperties(properties);
200+
properties
201+
..add(DiagnosticsProperty('alignment', alignment))
202+
..add(EnumProperty('direction', direction));
203+
}
204+
}

forui/lib/src/widgets/header/root_header.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,14 @@ class _FRootHeader extends FHeader {
3333
children: [
3434
Expanded(
3535
child: DefaultTextStyle.merge(
36-
overflow: TextOverflow.fade,
36+
overflow: TextOverflow.ellipsis,
3737
maxLines: 1,
3838
softWrap: false,
3939
style: style.titleTextStyle,
40+
textHeightBehavior: const TextHeightBehavior(
41+
applyHeightToFirstAscent: false,
42+
applyHeightToLastDescent: false,
43+
),
4044
child: title,
4145
),
4246
),
-34 Bytes
-239 Bytes
24.3 KB
-68 Bytes
-58 Bytes
-49 Bytes

0 commit comments

Comments
 (0)