Commit 90e2e300 authored by Nicholas Hollingum's avatar Nicholas Hollingum Committed by Commit Bot

Fixed some limitations in Exo's popup placement.

Exo could not handle some cases of popup placement, particularly when
the new window would be too large to fit on screen.

This CL reworks the implementation in some ways:
 - We break the placement problem down into two 1D problems, which
   minimises code duplication.
 - We respect the transformation order specified in the wayland spec,
   i.e. flip > slide > resize.
 - We maximize the visibility of the popup subject to the above, so
   when no valid transformations result in an unconstrained window, we
   choose the transformations that make the popup most visible.
 - We handle the case of resize, where previously only the top-left
   corner of the popup could be chosen.
 - We properly send the configuration callback, to inform the popup
   surface of its actual placement.

Bug: 942859
Change-Id: I892678492047ac2849afa84b1004731b7decab92
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1588058
Commit-Queue: Nic Hollingum <hollingum@google.com>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#659400}
parent fe92fb46
......@@ -81,6 +81,8 @@ source_set("wayland") {
"wayland_keyboard_delegate.h",
"wayland_pointer_delegate.cc",
"wayland_pointer_delegate.h",
"wayland_positioner.cc",
"wayland_positioner.h",
"wl_shell.cc",
"wl_shell.h",
"zaura_shell.cc",
......@@ -205,6 +207,7 @@ source_set("unit_tests") {
if (is_chromeos) {
sources += [
"wayland_positioner_unittest.cc",
"zaura_shell_unittest.cc",
"zcr_remote_shell_unittest.cc",
]
......@@ -212,6 +215,7 @@ source_set("unit_tests") {
"//ash",
"//ash:test_support",
"//ash/public/cpp",
"//third_party/wayland-protocols:xdg_shell_protocol",
"//ui/compositor",
"//ui/compositor:test_support",
"//ui/display",
......
// 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"
namespace exo {
namespace wayland {
namespace {
// Represents the 1-dimensional projection of the gravity/anchor values.
enum Direction { kNegative = -1, kNeutral = 0, kPositive = 1 };
static Direction Flip(Direction d) {
return (Direction)-d;
}
// Decodes a masked anchor/gravity bitfield to the direction.
Direction MaskToDirection(uint32_t field,
uint32_t negative_mask,
uint32_t positive_mask) {
DCHECK(!((field & negative_mask) && (field & positive_mask)));
if (field & negative_mask)
return kNegative;
if (field & positive_mask)
return kPositive;
return kNeutral;
}
// Represents the possible/actual positioner adjustments for this window.
struct ConstraintAdjustment {
bool flip;
bool slide;
bool resize;
};
// Decodes an adjustment bit field into the structure.
ConstraintAdjustment MaskToConstraintAdjustment(uint32_t field,
uint32_t flip_mask,
uint32_t slide_mask,
uint32_t resize_mask) {
return {field & flip_mask, field & slide_mask, field & resize_mask};
}
// A 1-dimensional projection of a range (a.k.a. a segment), used to solve the
// positioning problem in 1D.
struct Range1D {
int32_t start;
int32_t end;
Range1D GetTranspose(int32_t offset) const {
return {start + offset, end + offset};
}
int32_t center() const { return (start + end) / 2; }
};
// Works out the range's position that results from using exactly the
// adjustments specified by |adjustments|.
Range1D Calculate(const ConstraintAdjustment& adjustments,
int32_t work_size,
Range1D anchor_range,
uint32_t size,
int32_t offset,
Direction anchor,
Direction gravity) {
if (adjustments.flip) {
return Calculate({/*flip=*/false, adjustments.slide, adjustments.resize},
work_size, anchor_range, size, -offset, Flip(anchor),
Flip(gravity));
}
if (adjustments.resize) {
Range1D unresized =
Calculate({/*flip=*/false, adjustments.slide, /*resize=*/false},
work_size, anchor_range, size, offset, anchor, gravity);
return {std::max(unresized.start, 0), std::min(unresized.end, work_size)};
}
if (adjustments.slide) {
// Either the slide unconstrains the window, or the window is constrained
// in the positive direction
Range1D unslid =
Calculate({/*flip=*/false, /*slide=*/false, /*resize=*/false},
work_size, anchor_range, size, offset, anchor, gravity);
if (unslid.end > work_size)
unslid = unslid.GetTranspose(work_size - unslid.end);
if (unslid.start < 0)
return unslid.GetTranspose(-unslid.start);
return unslid;
}
int32_t start = offset;
switch (anchor) {
case Direction::kNegative:
start += anchor_range.start;
break;
case Direction::kNeutral:
start += anchor_range.center();
break;
case Direction::kPositive:
start += anchor_range.end;
break;
}
switch (gravity) {
case Direction::kNegative:
start -= size;
break;
case Direction::kNeutral:
start -= size / 2;
break;
case Direction::kPositive:
break;
}
return {start, start + size};
}
// Determines which adjustments (subject to them being a subset of the allowed
// adjustments) result in the best range position.
//
// Note: this is a 1-dimensional projection of the window-positioning problem.
std::pair<Range1D, ConstraintAdjustment> DetermineBestConstraintAdjustment(
const Range1D& work_area,
const Range1D& anchor_range,
uint32_t size,
int32_t offset,
Direction anchor,
Direction gravity,
const ConstraintAdjustment& valid_adjustments) {
if (work_area.start != 0) {
int32_t shift = -work_area.start;
std::pair<Range1D, ConstraintAdjustment> shifted_result =
DetermineBestConstraintAdjustment(
work_area.GetTranspose(shift), anchor_range.GetTranspose(shift),
size, offset, anchor, gravity, valid_adjustments);
return {shifted_result.first.GetTranspose(-shift), shifted_result.second};
}
// To determine the position, cycle through the available combinations of
// adjustments and choose the first one that maximizes the amount of the
// window that is visible on screen.
Range1D best_position{0, 0};
ConstraintAdjustment best_adjustments;
bool best_constrained = true;
int32_t best_visibility = 0;
for (uint32_t adjustment_bit_field = 0; adjustment_bit_field < 8;
++adjustment_bit_field) {
// When several options tie for visibility, we preference based on the
// ordering flip > slide > resize, which is defined in the positioner
// specification.
ConstraintAdjustment adjustment =
MaskToConstraintAdjustment(adjustment_bit_field, /*flip_mask=*/1,
/*slide_mask=*/2, /*resize_mask=*/4);
if ((adjustment.flip && !valid_adjustments.flip) ||
(adjustment.slide && !valid_adjustments.slide) ||
(adjustment.resize && !valid_adjustments.resize))
continue;
Range1D position = Calculate(adjustment, work_area.end, anchor_range, size,
offset, anchor, gravity);
bool constrained = position.start < 0 || position.end > work_area.end;
int32_t visibility = std::abs(std::min(position.end, work_area.end) -
std::max(position.start, 0));
if (visibility > best_visibility || ((!constrained) && best_constrained)) {
best_position = position;
best_constrained = constrained;
best_visibility = visibility;
best_adjustments = adjustment;
}
}
return {best_position, best_adjustments};
}
} // namespace
WaylandPositioner::Result WaylandPositioner::CalculatePosition(
const gfx::Rect& work_area,
bool flip_x,
bool flip_y) const {
Direction anchor_x = MaskToDirection(anchor_, ZXDG_POSITIONER_V6_ANCHOR_LEFT,
ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
Direction anchor_y = MaskToDirection(anchor_, ZXDG_POSITIONER_V6_ANCHOR_TOP,
ZXDG_POSITIONER_V6_ANCHOR_BOTTOM);
Direction gravity_x =
MaskToDirection(gravity_, ZXDG_POSITIONER_V6_GRAVITY_LEFT,
ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
Direction gravity_y =
MaskToDirection(gravity_, ZXDG_POSITIONER_V6_GRAVITY_TOP,
ZXDG_POSITIONER_V6_GRAVITY_BOTTOM);
ConstraintAdjustment adjustments_x = MaskToConstraintAdjustment(
adjustment_, ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X,
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X,
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X);
ConstraintAdjustment adjustments_y = MaskToConstraintAdjustment(
adjustment_, ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y,
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y,
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y);
int32_t offset_x = offset_.x();
int32_t offset_y = offset_.y();
// Chrome windows have the behaviour that if a menu needs to be flipped,
// its children will be flipped by default. That is not part of the normal
// wayland spec but we are doing it here for consistency.
if (flip_x) {
offset_x = -offset_x;
anchor_x = Flip(anchor_x);
gravity_x = Flip(gravity_x);
}
if (flip_y) {
offset_y = -offset_y;
anchor_y = Flip(anchor_y);
gravity_y = Flip(gravity_y);
}
// Exo overrides the ability to slide in cases when the orthogonal
// anchor+gravity would mean the slide can occlude |anchor_rect_|.
//
// We are doing this in order to stop a common case of clients allowing
// dropdown menus to occlude the menu header. Whilst this may cause some
// popups to avoid sliding where they could, for UX reasons we'd rather that
// than allowing menus to be occluded.
if (!(anchor_x == gravity_x && anchor_x != kNeutral))
adjustments_y.slide = false;
if (!(anchor_y == gravity_y && anchor_y != kNeutral))
adjustments_x.slide = false;
std::pair<Range1D, ConstraintAdjustment> x =
DetermineBestConstraintAdjustment(
{work_area.x(), work_area.right()},
{anchor_rect_.x(), anchor_rect_.right()}, size_.width(), offset_x,
anchor_x, gravity_x, adjustments_x);
std::pair<Range1D, ConstraintAdjustment> y =
DetermineBestConstraintAdjustment(
{work_area.y(), work_area.bottom()},
{anchor_rect_.y(), anchor_rect_.bottom()}, size_.height(), offset_y,
anchor_y, gravity_y, adjustments_y);
return {{x.first.start, y.first.start},
{x.first.end - x.first.start, y.first.end - y.first.start},
x.second.flip ? !flip_x : flip_x,
y.second.flip ? !flip_y : flip_y};
}
} // namespace wayland
} // namespace exo
// 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.
#ifndef COMPONENTS_EXO_WAYLAND_WAYLAND_POSITIONER_H_
#define COMPONENTS_EXO_WAYLAND_WAYLAND_POSITIONER_H_
#include <xdg-shell-unstable-v6-server-protocol.h>
#include "base/macros.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"
namespace exo {
namespace wayland {
class WaylandPositioner {
public:
// Holds the result of window positioning.
struct Result {
gfx::Point origin;
gfx::Size size;
bool x_flipped;
bool y_flipped;
};
WaylandPositioner() = default;
// Calculate and return position from current state.
Result CalculatePosition(const gfx::Rect& work_area,
bool flip_x,
bool flip_y) const;
void SetSize(gfx::Size size) { size_ = std::move(size); }
void SetAnchorRect(gfx::Rect anchor_rect) {
anchor_rect_ = std::move(anchor_rect);
}
void SetAnchor(uint32_t anchor) { anchor_ = anchor; }
void SetGravity(uint32_t gravity) { gravity_ = gravity; }
void SetAdjustment(uint32_t adjustment) { adjustment_ = adjustment; }
void SetOffset(gfx::Vector2d offset) { offset_ = std::move(offset); }
private:
gfx::Size size_;
gfx::Rect anchor_rect_;
uint32_t anchor_ = ZXDG_POSITIONER_V6_ANCHOR_NONE;
uint32_t gravity_ = ZXDG_POSITIONER_V6_GRAVITY_NONE;
// A bitmask that defines the subset of modifications to the position/size
// that are allowed, see zxdg_positioner.constraint_adjustment() for more
// details.
uint32_t adjustment_ = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE;
// Defines an absolute translation (i.e. unaffected by flipping, scaling or
// resizing) for the placement of the window relative to the |anchor_rect_|.
// See zxdg_positioner.set_offset() for more details.
gfx::Vector2d offset_;
DISALLOW_COPY_AND_ASSIGN(WaylandPositioner);
};
} // namespace wayland
} // namespace exo
#endif // COMPONENTS_EXO_WAYLAND_WAYLAND_POSITIONER_H_
// 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));
}
} // namespace
} // namespace wayland
} // namespace exo
......@@ -15,6 +15,7 @@
#include "base/strings/utf_string_conversions.h"
#include "components/exo/display.h"
#include "components/exo/wayland/server_util.h"
#include "components/exo/wayland/wayland_positioner.h"
#include "components/exo/xdg_shell_surface.h"
#include "ui/aura/window_observer.h"
#include "ui/base/hit_test.h"
......@@ -29,139 +30,6 @@ namespace {
////////////////////////////////////////////////////////////////////////////////
// xdg_positioner_interface:
uint32_t InvertBitfield(uint32_t bitfield, uint32_t mask) {
return (bitfield & ~mask) | ((bitfield & mask) ^ mask);
}
// TODO(oshima): propagate x/y flip state to children.
struct WaylandPositioner {
static constexpr uint32_t kHorizontalAnchors =
ZXDG_POSITIONER_V6_ANCHOR_LEFT | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
static constexpr uint32_t kVerticalAnchors =
ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
static constexpr uint32_t kHorizontalGravities =
ZXDG_POSITIONER_V6_GRAVITY_LEFT | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
static constexpr uint32_t kVerticalGravities =
ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
static int CalculateX(const gfx::Size& size,
const gfx::Rect& anchor_rect,
uint32_t anchor,
uint32_t gravity,
int offset,
bool flipped) {
if (flipped) {
anchor = InvertBitfield(anchor, kHorizontalAnchors);
gravity = InvertBitfield(gravity, kHorizontalGravities);
offset = -offset;
}
int x = offset;
if (anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT)
x += anchor_rect.x();
else if (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT)
x += anchor_rect.right();
else
x += anchor_rect.CenterPoint().x();
if (gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT)
return x - size.width();
if (gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT)
return x;
return x - size.width() / 2;
}
static int CalculateY(const gfx::Size& size,
const gfx::Rect& anchor_rect,
uint32_t anchor,
uint32_t gravity,
int offset,
bool flipped) {
if (flipped) {
anchor = InvertBitfield(anchor, kVerticalAnchors);
gravity = InvertBitfield(gravity, kVerticalGravities);
offset = -offset;
}
int y = offset;
if (anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP)
y += anchor_rect.y();
else if (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM)
y += anchor_rect.bottom();
else
y += anchor_rect.CenterPoint().y();
if (gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP)
return y - size.height();
if (gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)
return y;
return y - size.height() / 2;
}
// Calculate and return position from current state.
gfx::Point CalculatePosition(const gfx::Rect& work_area) {
// TODO(oshima): The size must be smaller than work area.
gfx::Rect bounds(gfx::Point(CalculateX(size, anchor_rect, anchor, gravity,
offset.x(), x_flipped),
CalculateY(size, anchor_rect, anchor, gravity,
offset.y(), y_flipped)),
size);
// Adjust x position if the bounds are not fully contained by the work area.
if (work_area.x() > bounds.x() || work_area.right() < bounds.right()) {
// Allow sliding horizontally if the surface is attached below
// or above the parent surface.
bool can_slide_x = (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM &&
gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM) ||
(anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP &&
gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP);
if (adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X &&
can_slide_x) {
if (bounds.x() < work_area.x())
bounds.set_x(work_area.x());
else if (bounds.right() > work_area.right())
bounds.set_x(work_area.right() - size.width());
} else if (adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X) {
x_flipped = !x_flipped;
bounds.set_x(CalculateX(size, anchor_rect, anchor, gravity, offset.x(),
x_flipped));
}
}
// Adjust y position if the bounds are not fully contained by the work area.
if (work_area.y() > bounds.y() || work_area.bottom() < bounds.bottom()) {
// Allow sliding vertically if the surface is attached left or
// right of the parent surface.
bool can_slide_y = (anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT &&
gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) ||
(anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT &&
gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
if (adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y &&
can_slide_y) {
if (bounds.y() < work_area.y())
bounds.set_y(work_area.y());
else if (bounds.bottom() > work_area.bottom())
bounds.set_y(work_area.bottom() - size.height());
} else if (adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y) {
y_flipped = !y_flipped;
bounds.set_y(CalculateY(size, anchor_rect, anchor, gravity, offset.y(),
y_flipped));
}
}
return bounds.origin();
}
gfx::Size size;
gfx::Rect anchor_rect;
uint32_t anchor = ZXDG_POSITIONER_V6_ANCHOR_NONE;
uint32_t gravity = ZXDG_POSITIONER_V6_GRAVITY_NONE;
uint32_t adjustment = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE;
gfx::Vector2d offset;
bool y_flipped = false;
bool x_flipped = false;
};
void xdg_positioner_v6_destroy(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
......@@ -176,7 +44,7 @@ void xdg_positioner_v6_set_size(wl_client* client,
return;
}
GetUserDataAs<WaylandPositioner>(resource)->size = gfx::Size(width, height);
GetUserDataAs<WaylandPositioner>(resource)->SetSize(gfx::Size(width, height));
}
void xdg_positioner_v6_set_anchor_rect(wl_client* client,
......@@ -191,8 +59,8 @@ void xdg_positioner_v6_set_anchor_rect(wl_client* client,
return;
}
GetUserDataAs<WaylandPositioner>(resource)->anchor_rect =
gfx::Rect(x, y, width, height);
GetUserDataAs<WaylandPositioner>(resource)->SetAnchorRect(
gfx::Rect(x, y, width, height));
}
void xdg_positioner_v6_set_anchor(wl_client* client,
......@@ -207,7 +75,7 @@ void xdg_positioner_v6_set_anchor(wl_client* client,
return;
}
GetUserDataAs<WaylandPositioner>(resource)->anchor = anchor;
GetUserDataAs<WaylandPositioner>(resource)->SetAnchor(anchor);
}
void xdg_positioner_v6_set_gravity(wl_client* client,
......@@ -222,20 +90,20 @@ void xdg_positioner_v6_set_gravity(wl_client* client,
return;
}
GetUserDataAs<WaylandPositioner>(resource)->gravity = gravity;
GetUserDataAs<WaylandPositioner>(resource)->SetGravity(gravity);
}
void xdg_positioner_v6_set_constraint_adjustment(wl_client* client,
wl_resource* resource,
uint32_t adjustment) {
GetUserDataAs<WaylandPositioner>(resource)->adjustment = adjustment;
GetUserDataAs<WaylandPositioner>(resource)->SetAdjustment(adjustment);
}
void xdg_positioner_v6_set_offset(wl_client* client,
wl_resource* resource,
int32_t x,
int32_t y) {
GetUserDataAs<WaylandPositioner>(resource)->offset = gfx::Vector2d(x, y);
GetUserDataAs<WaylandPositioner>(resource)->SetOffset(gfx::Vector2d(x, y));
}
const struct zxdg_positioner_v6_interface xdg_positioner_v6_implementation = {
......@@ -663,24 +531,23 @@ void xdg_surface_v6_get_popup(wl_client* client,
gfx::Rect work_area = display.work_area();
wm::ConvertRectFromScreen(parent->GetWidget()->GetNativeWindow(), &work_area);
// Try layout using parent's flip state.
WaylandPositioner* positioner =
GetUserDataAs<WaylandPositioner>(positioner_resource);
// Try layout using parent's flip state.
positioner->x_flipped = parent->x_flipped();
positioner->y_flipped = parent->y_flipped();
gfx::Point position = positioner->CalculatePosition(work_area);
WaylandPositioner::Result position = positioner->CalculatePosition(
work_area, parent->x_flipped(), parent->y_flipped());
// Remember the new flip state for its child popups.
shell_surface->set_x_flipped(positioner->x_flipped);
shell_surface->set_y_flipped(positioner->y_flipped);
shell_surface->set_x_flipped(position.x_flipped);
shell_surface->set_y_flipped(position.y_flipped);
// |position| is relative to the parent's contents view origin, and |origin|
// is in screen coordinates.
gfx::Point origin = position;
gfx::Point origin = position.origin;
views::View::ConvertPointToScreen(
parent->GetWidget()->widget_delegate()->GetContentsView(), &origin);
shell_surface->SetOrigin(origin);
shell_surface->SetSize(position.size);
shell_surface->SetContainer(ash::kShellWindowId_MenuContainer);
shell_surface->DisableMovement();
shell_surface->SetActivatable(false);
......@@ -695,6 +562,12 @@ void xdg_surface_v6_get_popup(wl_client* client,
SetImplementation(
xdg_popup_resource, &xdg_popup_v6_implementation,
std::make_unique<WaylandPopup>(xdg_popup_resource, resource));
// We send the configure event here as this event needs x,y coordinates
// relative to the parent window.
zxdg_popup_v6_send_configure(xdg_popup_resource, position.origin.x(),
position.origin.y(), position.size.width(),
position.size.height());
}
void xdg_surface_v6_set_window_geometry(wl_client* client,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment