mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
504 lines
18 KiB
Dart
504 lines
18 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// ignore_for_file: invalid_use_of_internal_member
|
|
|
|
import 'dart:ui';
|
|
import 'package:flutter/src/widgets/_window_positioner.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
group('WindowPlacementTest', () {
|
|
const clientDisplayArea = Rect.fromLTWH(0, 0, 800, 600);
|
|
const clientParentSize = Size(400, 300);
|
|
const clientChildSize = Size(100, 50);
|
|
final clientParentPosition = Offset(
|
|
(clientDisplayArea.width - clientParentSize.width) / 2,
|
|
(clientDisplayArea.height - clientParentSize.height) / 2,
|
|
);
|
|
|
|
final clientParentRect = Rect.fromLTWH(
|
|
clientParentPosition.dx,
|
|
clientParentPosition.dy,
|
|
clientParentSize.width,
|
|
clientParentSize.height,
|
|
);
|
|
|
|
const displayArea = Rect.fromLTWH(0, 0, 640, 480);
|
|
const parentSize = Size(600, 400);
|
|
const childSize = Size(300, 300);
|
|
const rectangleNearRhs = Rect.fromLTWH(590, 20, 10, 20);
|
|
const rectangleNearLeftSide = Rect.fromLTWH(0, 20, 20, 20);
|
|
const rectangleNearAllSides = Rect.fromLTWH(0, 20, 600, 380);
|
|
const rectangleNearBottom = Rect.fromLTWH(20, 380, 20, 20);
|
|
const rectangleNearBothBottomRight = Rect.fromLTWH(400, 380, 200, 20);
|
|
|
|
final parentPosition = Offset(
|
|
(displayArea.width - parentSize.width) / 2,
|
|
(displayArea.height - parentSize.height) / 2,
|
|
);
|
|
|
|
final parentRect = Rect.fromLTWH(
|
|
parentPosition.dx,
|
|
parentPosition.dy,
|
|
parentSize.width,
|
|
parentSize.height,
|
|
);
|
|
|
|
Rect anchorRectFor(Rect rect) => rect.translate(parentPosition.dx, parentPosition.dy);
|
|
Offset onTopEdge(Rect rect, Size childSize) => rect.topLeft - Offset(0, childSize.height);
|
|
Offset onLeftEdge(Rect rect, Size childSize) => rect.topLeft - Offset(childSize.width, 0);
|
|
|
|
test('Client anchors to parent given anchor rectangle right of parent', () {
|
|
const rectSize = 10.0;
|
|
final overlappingRight = Rect.fromCenter(
|
|
center: clientParentRect.topRight.translate(-rectSize / 2, clientParentRect.height / 2),
|
|
width: rectSize,
|
|
height: rectSize,
|
|
);
|
|
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topRight,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideY: true, resizeX: true),
|
|
);
|
|
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: clientChildSize,
|
|
anchorRect: overlappingRight,
|
|
parentRect: clientParentRect,
|
|
displayRect: clientDisplayArea,
|
|
);
|
|
|
|
final Offset expectedPosition = overlappingRight.topRight;
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
expect(childRect.size, clientChildSize);
|
|
});
|
|
|
|
test('Client anchors to parent given anchor rectangle above parent', () {
|
|
const rectSize = 10.0;
|
|
final overlappingAbove = Rect.fromCenter(
|
|
center: clientParentRect.topCenter.translate(0, -rectSize / 2),
|
|
width: rectSize,
|
|
height: rectSize,
|
|
);
|
|
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topRight,
|
|
childAnchor: WindowPositionerAnchor.bottomRight,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true),
|
|
);
|
|
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: clientChildSize,
|
|
anchorRect: overlappingAbove,
|
|
parentRect: clientParentRect,
|
|
displayRect: clientDisplayArea,
|
|
);
|
|
|
|
final Offset expectedPosition =
|
|
overlappingAbove.bottomRight - Offset(clientChildSize.width, clientChildSize.height);
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
expect(childRect.size, clientChildSize);
|
|
});
|
|
|
|
test('Client anchors to parent given offset right of parent', () {
|
|
const rectSize = 10.0;
|
|
final midRight = Rect.fromLTWH(
|
|
clientParentRect.right - rectSize,
|
|
clientParentRect.center.dy,
|
|
rectSize,
|
|
rectSize,
|
|
);
|
|
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topRight,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
offset: Offset(rectSize, 0),
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideY: true, resizeX: true),
|
|
);
|
|
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: clientChildSize,
|
|
anchorRect: midRight,
|
|
parentRect: clientParentRect,
|
|
displayRect: clientDisplayArea,
|
|
);
|
|
|
|
final Offset expectedPosition = midRight.topRight;
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
expect(childRect.size, clientChildSize);
|
|
});
|
|
|
|
test('Client anchors to parent given offset above parent', () {
|
|
const rectSize = 10.0;
|
|
final midTop = Rect.fromLTWH(
|
|
clientParentRect.center.dx,
|
|
clientParentRect.top,
|
|
rectSize,
|
|
rectSize,
|
|
);
|
|
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topRight,
|
|
childAnchor: WindowPositionerAnchor.bottomRight,
|
|
offset: Offset(0, -rectSize),
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true),
|
|
);
|
|
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: clientChildSize,
|
|
anchorRect: midTop,
|
|
parentRect: clientParentRect,
|
|
displayRect: clientDisplayArea,
|
|
);
|
|
|
|
final Offset expectedPosition =
|
|
clientParentPosition +
|
|
Offset(clientParentSize.width / 2 + rectSize, 0) -
|
|
Offset(clientChildSize.width, clientChildSize.height);
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
expect(childRect.size, clientChildSize);
|
|
});
|
|
|
|
test('Client anchors to parent given anchor rectangle and offset below left parent', () {
|
|
const rectSize = 10.0;
|
|
final belowLeft = Rect.fromLTWH(
|
|
clientParentRect.left - rectSize,
|
|
clientParentRect.bottom,
|
|
rectSize,
|
|
rectSize,
|
|
);
|
|
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.bottomLeft,
|
|
childAnchor: WindowPositionerAnchor.topRight,
|
|
offset: Offset(-rectSize, rectSize),
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeX: true, resizeY: true),
|
|
);
|
|
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: clientChildSize,
|
|
anchorRect: belowLeft,
|
|
parentRect: clientParentRect,
|
|
displayRect: clientDisplayArea,
|
|
);
|
|
|
|
final Offset expectedPosition =
|
|
clientParentRect.bottomLeft - Offset(clientChildSize.width, 0);
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
expect(childRect.size, clientChildSize);
|
|
});
|
|
|
|
group('Can attach by every anchor given no constraint adjustment', () {
|
|
Offset anchorPositionFor(WindowPositionerAnchor anchor, Rect rect) {
|
|
return switch (anchor) {
|
|
WindowPositionerAnchor.center => rect.center,
|
|
WindowPositionerAnchor.top => rect.topCenter,
|
|
WindowPositionerAnchor.bottom => rect.bottomCenter,
|
|
WindowPositionerAnchor.left => rect.centerLeft,
|
|
WindowPositionerAnchor.right => rect.centerRight,
|
|
WindowPositionerAnchor.topLeft => rect.topLeft,
|
|
WindowPositionerAnchor.bottomLeft => rect.bottomLeft,
|
|
WindowPositionerAnchor.topRight => rect.topRight,
|
|
WindowPositionerAnchor.bottomRight => rect.bottomRight,
|
|
};
|
|
}
|
|
|
|
for (final WindowPositionerAnchor parentAnchor in WindowPositionerAnchor.values) {
|
|
for (final WindowPositionerAnchor childAnchor in WindowPositionerAnchor.values) {
|
|
test('parent: $parentAnchor, child: $childAnchor', () {
|
|
final Rect anchorRect = anchorRectFor(const Rect.fromLTWH(100, 50, 20, 20));
|
|
final positioner = WindowPositioner(
|
|
parentAnchor: parentAnchor,
|
|
childAnchor: childAnchor,
|
|
);
|
|
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(
|
|
anchorPositionFor(childAnchor, childRect),
|
|
anchorPositionFor(parentAnchor, anchorRect),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Placement is flipped given anchor rectangle near right side and offset', () {
|
|
const xOffset = 42.0;
|
|
const yOffset = 13.0;
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topRight,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
offset: Offset(xOffset, yOffset),
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(flipX: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearRhs);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
final Offset expectedPosition =
|
|
onLeftEdge(anchorRect, childSize) + const Offset(-xOffset, yOffset);
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
});
|
|
|
|
test('Placement is flipped given anchor rectangle near bottom and offset', () {
|
|
const xOffset = 42.0;
|
|
const yOffset = 13.0;
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.bottomLeft,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
offset: Offset(xOffset, yOffset),
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(flipY: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearBottom);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
final Offset expectedPosition =
|
|
onTopEdge(anchorRect, childSize) + const Offset(xOffset, -yOffset);
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
});
|
|
|
|
test('Placement is flipped both ways given anchor rectangle near bottom right and offset', () {
|
|
const xOffset = 42.0;
|
|
const yOffset = 13.0;
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.bottomRight,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
offset: Offset(xOffset, yOffset),
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(flipX: true, flipY: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearBothBottomRight);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
final Offset expectedPosition =
|
|
anchorRect.topLeft -
|
|
Offset(childSize.width, childSize.height) -
|
|
const Offset(xOffset, yOffset);
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
});
|
|
|
|
test('Placement can slide in X given anchor rectangle near right side', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topRight,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearRhs);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(childRect.topLeft.dx, displayArea.right - childSize.width);
|
|
});
|
|
|
|
test('Placement can slide in X given anchor rectangle near left side', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topLeft,
|
|
childAnchor: WindowPositionerAnchor.topRight,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearLeftSide);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(childRect.topLeft.dx, displayArea.left);
|
|
});
|
|
|
|
test('Placement can slide in Y given anchor rectangle near bottom', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.bottomLeft,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideY: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearBottom);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(childRect.topLeft.dy, displayArea.bottom - childSize.height);
|
|
});
|
|
|
|
test('Placement can slide in Y given anchor rectangle near top', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topLeft,
|
|
childAnchor: WindowPositionerAnchor.bottomLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideY: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearAllSides);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(childRect.topLeft.dy, displayArea.top);
|
|
});
|
|
|
|
test('Placement can slide in X and Y given anchor rectangle near bottom right and offset', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.bottomLeft,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true, slideY: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearBothBottomRight);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
final expectedPosition = Offset(
|
|
displayArea.right - childSize.width,
|
|
displayArea.bottom - childSize.height,
|
|
);
|
|
|
|
expect(childRect.topLeft, expectedPosition);
|
|
});
|
|
|
|
test('Placement can resize in X given anchor rectangle near right side', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topRight,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeX: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearRhs);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(childRect.width, displayArea.right - (anchorRect.left + anchorRect.width));
|
|
});
|
|
|
|
test('Placement can resize in X given anchor rectangle near left side', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topLeft,
|
|
childAnchor: WindowPositionerAnchor.topRight,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeX: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearLeftSide);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(childRect.width, anchorRect.left - displayArea.left);
|
|
});
|
|
|
|
test('Placement can resize in Y given anchor rectangle near bottom', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.bottomLeft,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeY: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearAllSides);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(childRect.height, displayArea.bottom - (anchorRect.top + anchorRect.height));
|
|
});
|
|
|
|
test('Placement can resize in Y given anchor rectangle near top', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.topLeft,
|
|
childAnchor: WindowPositionerAnchor.bottomLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeY: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearAllSides);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
expect(childRect.height, anchorRect.top - displayArea.top);
|
|
});
|
|
|
|
test('Placement can resize in X and Y given anchor rectangle near bottom right and offset', () {
|
|
const positioner = WindowPositioner(
|
|
parentAnchor: WindowPositionerAnchor.bottomRight,
|
|
childAnchor: WindowPositionerAnchor.topLeft,
|
|
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeX: true, resizeY: true),
|
|
);
|
|
|
|
final Rect anchorRect = anchorRectFor(rectangleNearBothBottomRight);
|
|
final Rect childRect = positioner.placeWindow(
|
|
childSize: childSize,
|
|
anchorRect: anchorRect,
|
|
parentRect: parentRect,
|
|
displayRect: displayArea,
|
|
);
|
|
|
|
final expectedSize = Size(
|
|
displayArea.right - (anchorRect.left + anchorRect.width),
|
|
displayArea.bottom - (anchorRect.top + anchorRect.height),
|
|
);
|
|
|
|
expect(childRect.size, expectedSize);
|
|
});
|
|
});
|
|
}
|