blob: 035f3b91f906087fb0603d23e281c4843fe967c9 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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 "xdg-shell-unstable-v6-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};
bool flip_x = false;
bool flip_y = false;
TestCaseBuilder() { positioner.SetAnchorRect({2, 2, 1, 1}); }
TestCaseBuilder& SetFlipState(bool x, bool y) {
flip_x = x;
flip_y = 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& SetSize(uint32_t w, uint32_t h) {
positioner.SetSize({w, h});
return *this;
}
WaylandPositioner::Result Solve() const {
return positioner.CalculatePosition(work_area, flip_x, flip_y);
}
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(ZXDG_POSITIONER_V6_ANCHOR_RIGHT)
.SolveToRect(),
gfx::Rect(2, 2, 2, 1));
EXPECT_EQ(TestCaseBuilder()
.SetSize(2, 1)
.SetAnchor(ZXDG_POSITIONER_V6_ANCHOR_LEFT)
.SolveToRect(),
gfx::Rect(1, 2, 2, 1));
// Gravity without anchor.
EXPECT_EQ(TestCaseBuilder()
.SetSize(1, 2)
.SetAnchorRect(2, 2, 0, 0)
.SetGravity(ZXDG_POSITIONER_V6_GRAVITY_TOP)
.SolveToRect(),
gfx::Rect(2, 0, 1, 2));
EXPECT_EQ(TestCaseBuilder()
.SetSize(1, 2)
.SetAnchorRect(2, 2, 0, 0)
.SetGravity(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)
.SolveToRect(),
gfx::Rect(2, 2, 1, 2));
// Gravity + anchor in the same direction.
EXPECT_EQ(TestCaseBuilder()
.SetSize(2, 2)
.SetGravity(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_LEFT)
.SetAnchor(ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
ZXDG_POSITIONER_V6_ANCHOR_LEFT)
.SolveToRect(),
gfx::Rect(0, 3, 2, 2));
// Gravity + anchor in opposing directions.
EXPECT_EQ(TestCaseBuilder()
.SetSize(2, 2)
.SetGravity(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_LEFT)
.SetAnchor(ZXDG_POSITIONER_V6_ANCHOR_TOP |
ZXDG_POSITIONER_V6_ANCHOR_RIGHT)
.SolveToRect(),
gfx::Rect(1, 2, 2, 2));
}
TEST_F(WaylandPositionerTest, FlipSlideResizePriority) {
TestCaseBuilder builder;
builder.SetAnchorRect(4, 4, 0, 0)
.SetSize(2, 2)
.SetGravity(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT)
.SetAnchor(ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
ZXDG_POSITIONER_V6_ANCHOR_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(~ZXDG_POSITIONER_V6_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(~ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X)
.SolveToRect(),
gfx::Rect(3, 2, 2, 2));
EXPECT_EQ(
builder.SetAdjustment(~ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y)
.SolveToRect(),
gfx::Rect(2, 3, 2, 2));
// If we cant flip or slide, we resize.
EXPECT_EQ(
builder
.SetAdjustment(ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X |
ZXDG_POSITIONER_V6_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(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT)
.SetAnchor(ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
ZXDG_POSITIONER_V6_ANCHOR_RIGHT)
.SetAdjustment(~ZXDG_POSITIONER_V6_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));
// Neither axis will be flipped.
EXPECT_FALSE(result.x_flipped);
EXPECT_FALSE(result.y_flipped);
}
TEST_F(WaylandPositionerTest, PropagatesAnInitialFlip) {
WaylandPositioner::Result result =
TestCaseBuilder()
.SetAnchorRect(3, 1, 0, 0)
.SetSize(2, 2)
.SetGravity(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT)
.SetAnchor(ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
ZXDG_POSITIONER_V6_ANCHOR_RIGHT)
.SetAdjustment(~ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE)
.SetFlipState(true, true)
.Solve();
// With a propagated flip state:
// - Normally the x would not need to flip, but it propagates the flip.
// - Y also propagates, but that makes it constrained so it flips back.
EXPECT_EQ(result.origin, gfx::Point(1, 1));
EXPECT_EQ(result.size, gfx::Size(2, 2));
EXPECT_TRUE(result.x_flipped);
EXPECT_FALSE(result.y_flipped);
}
// 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(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT)
.SetAnchor(ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
ZXDG_POSITIONER_V6_ANCHOR_LEFT)
.SetAdjustment(~ZXDG_POSITIONER_V6_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(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT)
.SetAnchor(ZXDG_POSITIONER_V6_ANCHOR_TOP |
ZXDG_POSITIONER_V6_ANCHOR_LEFT)
.SetAdjustment(~ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE)
.SolveToRect(),
gfx::Rect(1, 1, 4, 4));
}
} // namespace
} // namespace wayland
} // namespace exo