| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/exo/wayland/wayland_positioner.h" |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "xdg-shell-server-protocol.h" |
| |
| namespace exo { |
| namespace wayland { |
| namespace { |
| |
| class WaylandPositionerTest : public testing::Test { |
| protected: |
| // By default the test cases happen on a 5x5 grid with an anchor rect at |
| // (2,2,1x1). |
| struct TestCaseBuilder { |
| WaylandPositioner positioner; |
| gfx::Rect work_area = {0, 0, 5, 5}; |
| |
| explicit TestCaseBuilder() { positioner.SetAnchorRect({2, 2, 1, 1}); } |
| |
| TestCaseBuilder& SetFlipState(bool x, bool y) { |
| return *this; |
| } |
| |
| TestCaseBuilder& SetAnchor(uint32_t anchor) { |
| positioner.SetAnchor(anchor); |
| return *this; |
| } |
| |
| TestCaseBuilder& SetGravity(uint32_t gravity) { |
| positioner.SetGravity(gravity); |
| return *this; |
| } |
| |
| TestCaseBuilder& SetAdjustment(uint32_t adjustment) { |
| positioner.SetAdjustment(adjustment); |
| return *this; |
| } |
| |
| TestCaseBuilder& SetAnchorRect(int x, int y, int w, int h) { |
| positioner.SetAnchorRect({x, y, w, h}); |
| return *this; |
| } |
| |
| TestCaseBuilder& SetWorkArea(const gfx::Rect& rect) { |
| work_area = rect; |
| return *this; |
| } |
| |
| TestCaseBuilder& SetSize(int w, int h) { |
| positioner.SetSize({w, h}); |
| return *this; |
| } |
| |
| WaylandPositioner::Result Solve() const { |
| return positioner.CalculateBounds(work_area); |
| } |
| |
| gfx::Rect SolveToRect() const { |
| WaylandPositioner::Result result = Solve(); |
| return {result.origin.x(), result.origin.y(), result.size.width(), |
| result.size.height()}; |
| } |
| }; |
| }; |
| |
| TEST_F(WaylandPositionerTest, UnconstrainedCases) { |
| // No gravity or anchor. |
| EXPECT_EQ(TestCaseBuilder().SetSize(1, 1).SolveToRect(), |
| gfx::Rect(2, 2, 1, 1)); |
| |
| // Anchor without gravity. |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(2, 1) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_RIGHT) |
| .SolveToRect(), |
| gfx::Rect(2, 2, 2, 1)); |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(2, 1) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_LEFT) |
| .SolveToRect(), |
| gfx::Rect(1, 2, 2, 1)); |
| |
| // Gravity without anchor. |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(1, 2) |
| .SetAnchorRect(2, 2, 0, 0) |
| .SetGravity(XDG_POSITIONER_GRAVITY_TOP) |
| .SolveToRect(), |
| gfx::Rect(2, 0, 1, 2)); |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(1, 2) |
| .SetAnchorRect(2, 2, 0, 0) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM) |
| .SolveToRect(), |
| gfx::Rect(2, 2, 1, 2)); |
| |
| // Gravity + anchor in the same direction. |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(2, 2) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) |
| .SolveToRect(), |
| gfx::Rect(0, 3, 2, 2)); |
| |
| // Gravity + anchor in opposing directions. |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(2, 2) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT) |
| .SolveToRect(), |
| gfx::Rect(1, 2, 2, 2)); |
| } |
| |
| TEST_F(WaylandPositionerTest, FlipSlideResizePriority) { |
| TestCaseBuilder builder; |
| builder.SetAnchorRect(4, 4, 0, 0) |
| .SetSize(2, 2) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); |
| // Flip is enabled, so the result will be at 2,2 (i.e. flipping a 2-wide |
| // square around 4,4). |
| EXPECT_EQ(builder.SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) |
| .SolveToRect(), |
| gfx::Rect(2, 2, 2, 2)); |
| // If we cant flip on an axis, that axis will slide to 3 instead. |
| EXPECT_EQ(builder.SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X) |
| .SolveToRect(), |
| gfx::Rect(3, 2, 2, 2)); |
| EXPECT_EQ(builder.SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y) |
| .SolveToRect(), |
| gfx::Rect(2, 3, 2, 2)); |
| // If we cant flip or slide, we resize. |
| EXPECT_EQ(builder |
| .SetAdjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y) |
| .SolveToRect(), |
| gfx::Rect(4, 4, 1, 1)); |
| } |
| |
| TEST_F(WaylandPositionerTest, TriesToMaximizeArea) { |
| // The size is too large to fit where the anchor is. |
| WaylandPositioner::Result result = |
| TestCaseBuilder() |
| .SetAnchorRect(2, 4, 0, 0) |
| .SetSize(4, 10) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) |
| .SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) |
| .Solve(); |
| // We can slide to 1 on x, but we must resize on y (after sliding to 0). |
| EXPECT_EQ(result.origin, gfx::Point(1, 0)); |
| // The x size will be preserved but y shrinks to the work area. |
| EXPECT_EQ(result.size, gfx::Size(4, 5)); |
| } |
| |
| TEST_F(WaylandPositionerTest, PropagatesAnInitialFlip) { |
| WaylandPositioner::Result result = |
| TestCaseBuilder() |
| .SetAnchorRect(3, 1, 0, 0) |
| .SetSize(2, 2) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) |
| .SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) |
| .SetFlipState(true, true) |
| .Solve(); |
| // With a propagated flip state: |
| // - X and Y remain flipped to be positioned by the client. |
| EXPECT_EQ(result.origin, gfx::Point(3, 1)); |
| EXPECT_EQ(result.size, gfx::Size(2, 2)); |
| } |
| |
| // This is a common case for dropdown menus. In ChromeOS we do not let them |
| // slide if they might occlude the anchor rectangle. For this case, x axis does |
| // slide but the y axis resized instead. |
| TEST_F(WaylandPositionerTest, PreventsSlidingThatOccludesAnchorRect) { |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(3, 3) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) |
| .SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) |
| .SolveToRect(), |
| gfx::Rect(2, 3, 3, 2)); |
| |
| // Here we ensure that the 4x4 popup does slide, which is allowed because |
| // the anchor rect is already occluded. |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(4, 4) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) |
| .SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) |
| .SolveToRect(), |
| gfx::Rect(1, 1, 4, 4)); |
| } |
| |
| // Allowing sliding which will occlude the anchor if there are no other |
| // positioning options which do not result in a constrained view available. |
| TEST_F(WaylandPositionerTest, |
| AllowsSlidingThatOccludesWhenThereAreNoOtherOptions) { |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(4, 4) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) |
| // Disable resizing in both axes which will force sliding. |
| .SetAdjustment(~(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y)) |
| .SolveToRect(), |
| gfx::Rect(1, 1, 4, 4)); |
| } |
| |
| // Make sure that the size should never be an empty even if the constraints |
| // resulted in empty size. |
| TEST_F(WaylandPositionerTest, ResizableShouldNotBeEmpty) { |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(3, 3) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_BOTTOM) |
| .SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) |
| .SetAnchorRect(1, -10, 4, 4) |
| .SolveToRect(), |
| gfx::Rect(2, 0, 3, 1)); |
| EXPECT_EQ(TestCaseBuilder() |
| .SetSize(3, 3) |
| .SetGravity(XDG_POSITIONER_GRAVITY_RIGHT) |
| .SetAnchor(XDG_POSITIONER_ANCHOR_RIGHT) |
| .SetAdjustment(~XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) |
| .SetAnchorRect(-10, 2, 4, 4) |
| .SolveToRect(), |
| gfx::Rect(0, 2, 1, 3)); |
| } |
| |
| TEST_F(WaylandPositionerTest, |
| AllowsAdditionalAdjustmentsIfNoSolutionCanBeFound) { |
| EXPECT_EQ(TestCaseBuilder() |
| .SetWorkArea(gfx::Rect(5, 5)) |
| .SetSize(10, 10) |
| .SetAnchorRect(0, 0, 0, 0) |
| .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) |
| // No solution should forcibly allow resize |
| .SetAdjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y) |
| .SolveToRect(), |
| gfx::Rect(0, 0, 5, 5)); |
| } |
| |
| } // namespace |
| } // namespace wayland |
| } // namespace exo |