Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions boxy/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## [2.3.0]
* Added `BoxyDelegate.childrenForSemantics` for customizing the semantics order, thanks Albert221 for the PR!

## [2.2.2]
* Fixed bug in CustomBoxy that prevented multiple Slivers from being used

Expand Down
1 change: 1 addition & 0 deletions boxy/example/ios/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
Flutter/ephemeral
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*

Expand Down
23 changes: 20 additions & 3 deletions boxy/lib/src/boxy/custom_boxy_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,11 @@ mixin RenderBoxyMixin<

@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
for (final child in childHandleMap.values) {
if (!child._ignore) {
wrapPhase(BoxyDelegatePhase.semantics, () {
for (final child in delegate.childrenForSemantics) {
visitor(child.render);
}
}
});
}

@override
Expand Down Expand Up @@ -293,6 +293,9 @@ enum BoxyDelegatePhase {

/// The boxy is currently being hit test.
hitTest,

/// The boxy is currently being visited for semantics.
semantics,
}

/// Cache for [Layer] objects, used by [BoxyLayerContext] methods.
Expand Down Expand Up @@ -963,6 +966,20 @@ abstract class BaseBoxyDelegate<LayoutData extends Object,
return out;
}

/// An iterable of children to be visited for semantics, in paint order,
/// skipping children that are not semantically relevant.
///
/// Override this to customize the semantics traversal order.
/// By default, returns [children] in paint order, filtering out
/// ignored children.
Iterable<ChildHandleType> get childrenForSemantics sync* {
for (final child in children) {
if (!child._ignore) {
yield child;
}
}
}

/// The most recent constraints given to this boxy by its parent.
Constraints get constraints;

Expand Down
2 changes: 1 addition & 1 deletion boxy/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: boxy
description: Overcome limitations of built-in layouts, advanced flex, custom multi-child layouts, slivers, and more!
version: 2.2.2
version: 2.3.0
homepage: https://github.com/pingbird/boxy
documentation: https://boxy.wiki

Expand Down
224 changes: 224 additions & 0 deletions boxy/test/boxy_semantics_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import 'package:boxy/boxy.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import 'common.dart';

/// A simple delegate that lays out children in a column.
class SimpleDelegate extends BoxyDelegate {
@override
Size layout() {
double y = 0;
double maxWidth = 0;
for (final child in children) {
final size = child.layout(constraints);
child.position(Offset(0, y));
y += size.height;
if (size.width > maxWidth) {
maxWidth = size.width;
}
}
return Size(maxWidth, y);
}
}

/// A delegate that reverses the children order for paint/hit-test.
class ReversedChildrenDelegate extends BoxyDelegate {
@override
Size layout() {
double y = 0;
double maxWidth = 0;
for (final child in children) {
final size = child.layout(constraints);
child.position(Offset(0, y));
y += size.height;
if (size.width > maxWidth) {
maxWidth = size.width;
}
}
return Size(maxWidth, y);
}

@override
List<BoxyChild> get children => super.children.reversed.toList();
}

/// A delegate that provides custom semantics order independent of children.
class CustomSemanticsDelegate extends BoxyDelegate {
@override
Size layout() {
double y = 0;
double maxWidth = 0;
for (final child in children) {
final size = child.layout(constraints);
child.position(Offset(0, y));
y += size.height;
if (size.width > maxWidth) {
maxWidth = size.width;
}
}
return Size(maxWidth, y);
}

@override
Iterable<BoxyChild> get childrenForSemantics sync* {
// Return only the second child for semantics
final allChildren = super.children;
if (allChildren.length > 1) {
yield allChildren[1];
}
}
}

/// Collects children visited by visitChildrenForSemantics.
List<RenderObject> getSemanticsChildren(RenderObject renderObject) {
final children = <RenderObject>[];
renderObject.visitChildrenForSemantics(children.add);
return children;
}

void main() {
testWidgets(
'Default childrenForSemantics returns children in paint order',
(tester) async {
await tester.pumpWidget(
TestFrame(
child: CustomBoxy(
key: const GlobalObjectKey(#boxy),
delegate: SimpleDelegate(),
children: const [
BoxyId(
id: #first,
child: SizedBox(
key: GlobalObjectKey(#first),
width: 100,
height: 50,
),
),
BoxyId(
id: #second,
child: SizedBox(
key: GlobalObjectKey(#second),
width: 100,
height: 50,
),
),
BoxyId(
id: #third,
child: SizedBox(
key: GlobalObjectKey(#third),
width: 100,
height: 50,
),
),
],
),
),
);

final boxyRender = keyBox(#boxy);
final semanticsChildren = getSemanticsChildren(boxyRender);

expect(semanticsChildren.length, 3);
expect(semanticsChildren[0], keyBox(#first));
expect(semanticsChildren[1], keyBox(#second));
expect(semanticsChildren[2], keyBox(#third));
},
);

testWidgets(
'Overriding children affects childrenForSemantics',
(tester) async {
await tester.pumpWidget(
TestFrame(
child: CustomBoxy(
key: const GlobalObjectKey(#boxy),
delegate: ReversedChildrenDelegate(),
children: const [
BoxyId(
id: #first,
child: SizedBox(
key: GlobalObjectKey(#first),
width: 100,
height: 50,
),
),
BoxyId(
id: #second,
child: SizedBox(
key: GlobalObjectKey(#second),
width: 100,
height: 50,
),
),
BoxyId(
id: #third,
child: SizedBox(
key: GlobalObjectKey(#third),
width: 100,
height: 50,
),
),
],
),
),
);

final boxyRender = keyBox(#boxy);
final semanticsChildren = getSemanticsChildren(boxyRender);

// Semantics should follow the reversed children order
expect(semanticsChildren.length, 3);
expect(semanticsChildren[0], keyBox(#third));
expect(semanticsChildren[1], keyBox(#second));
expect(semanticsChildren[2], keyBox(#first));
},
);

testWidgets(
'Overriding childrenForSemantics customizes semantics order',
(tester) async {
await tester.pumpWidget(
TestFrame(
child: CustomBoxy(
key: const GlobalObjectKey(#boxy),
delegate: CustomSemanticsDelegate(),
children: const [
BoxyId(
id: #first,
child: SizedBox(
key: GlobalObjectKey(#first),
width: 100,
height: 50,
),
),
BoxyId(
id: #second,
child: SizedBox(
key: GlobalObjectKey(#second),
width: 100,
height: 50,
),
),
BoxyId(
id: #third,
child: SizedBox(
key: GlobalObjectKey(#third),
width: 100,
height: 50,
),
),
],
),
),
);

final boxyRender = keyBox(#boxy);
final semanticsChildren = getSemanticsChildren(boxyRender);

// Only the second child should be in semantics
expect(semanticsChildren.length, 1);
expect(semanticsChildren[0], keyBox(#second));
},
);
}