// 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); }); }); }