Commit 6b22924a authored by Maksim Sisov's avatar Maksim Sisov Committed by Commit Bot

[ozone/wayland] Let Wayland compositor decide how to place menu windows

This patch fixes disposition/misplacement of menu windows and let the
Wayland compositor decide how to place the windows if their pixels
cannot be shown on one display or if they are partly going to be hidden
because of being shown too close to some edges of a display.

First of all, Chromium positions windows using screen coordinates.
As long as Wayland does not provide global coordinates, the client
does not know the position of windows in screen coordinates and
always assumes that they are located on 0,0. What's left for us to do
is to translate bounds from screen coordinates to relative to parent
coordinates, which is easy to do.

Secondly, WaylandWindow re-positions nested menus back to the right side
of parent menus (if the size of a top level window is close or equal to the
work area of a display) and let Wayland compositor decide how to position
them based on calculated anchor rect, anchor and gravity. The exception
for this is based on the maximized state of a top level window.
If it's maximized, then the nested windows are not re positioned
and left on the left side of the parent menu windows.

Also, WaylandWindow must be able to recalculate origin back from
relative to parent to be relative to top level window aka on screen
location.

The unittests exercises all those mentioned cases and make sure
WaylandWindow is able to calculated bounds correctly (the values
are taken by manual usage of the Chromium browser and logs).

PS XdgVersionV5Test/WaylandWindowTest.AdjustPopupBounds/0 is skipped
as long as it does not support xdg positioner.

Test: XdgVersionV6Test/WaylandWindowTest.AdjustPopupBounds/0
Bug: 927280
Change-Id: I30573620562e7bc5dfd3a69487326bcda49fc6ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1474654Reviewed-by: default avatarRobert Kroeger <rjkroege@chromium.org>
Commit-Queue: Maksim Sisov <msisov@igalia.com>
Cr-Commit-Position: refs/heads/master@{#638616}
parent 89f296b3
......@@ -5,10 +5,14 @@
#ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_SURFACE_H_
#define UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_SURFACE_H_
#include <memory>
#include <utility>
#include <wayland-server-protocol-core.h>
#include "base/macros.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/ozone/platform/wayland/test/mock_xdg_popup.h"
#include "ui/ozone/platform/wayland/test/mock_xdg_surface.h"
#include "ui/ozone/platform/wayland/test/server_object.h"
......@@ -38,8 +42,14 @@ class MockSurface : public ServerObject {
}
MockXdgSurface* xdg_surface() const { return xdg_surface_.get(); }
void set_xdg_popup(std::unique_ptr<MockXdgPopup> xdg_popup) {
xdg_popup_ = std::move(xdg_popup);
}
MockXdgPopup* xdg_popup() const { return xdg_popup_.get(); }
private:
std::unique_ptr<MockXdgSurface> xdg_surface_;
std::unique_ptr<MockXdgPopup> xdg_popup_;
DISALLOW_COPY_AND_ASSIGN(MockSurface);
};
......
......@@ -26,7 +26,10 @@ const struct zxdg_popup_v6_interface kZxdgPopupV6Impl = {
&Grab, // grab
};
MockXdgPopup::MockXdgPopup(wl_resource* resource) : ServerObject(resource) {}
MockXdgPopup::MockXdgPopup(wl_resource* resource, const void* implementation)
: ServerObject(resource) {
SetImplementationUnretained(resource, implementation, this);
}
MockXdgPopup::~MockXdgPopup() {}
......
......@@ -5,12 +5,15 @@
#ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_XDG_POPUP_H_
#define UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_XDG_POPUP_H_
#include <utility>
#include <xdg-shell-unstable-v5-server-protocol.h>
#include <xdg-shell-unstable-v6-server-protocol.h>
#include "base/macros.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/ozone/platform/wayland/test/server_object.h"
#include "ui/ozone/platform/wayland/test/test_positioner.h"
struct wl_resource;
......@@ -21,12 +24,27 @@ extern const struct zxdg_popup_v6_interface kZxdgPopupV6Impl;
class MockXdgPopup : public ServerObject {
public:
MockXdgPopup(wl_resource* resource);
MockXdgPopup(wl_resource* resource, const void* implementation);
~MockXdgPopup() override;
MOCK_METHOD1(Grab, void(uint32_t serial));
void set_position(struct TestPositioner::PopupPosition position) {
position_ = std::move(position);
}
gfx::Rect anchor_rect() const { return position_.anchor_rect; }
gfx::Size size() const { return position_.size; }
uint32_t anchor() const { return position_.anchor; }
uint32_t gravity() const { return position_.gravity; }
uint32_t constraint_adjustment() const {
return position_.constraint_adjustment;
}
private:
// Position of the popup. Used only with V6.
struct TestPositioner::PopupPosition position_;
DISALLOW_COPY_AND_ASSIGN(MockXdgPopup);
};
......
......@@ -72,9 +72,13 @@ void GetXdgPopup(struct wl_client* client,
return;
}
CreateResourceWithImpl<MockXdgPopup>(client, &xdg_popup_interface,
wl_resource_get_version(resource),
&kXdgPopupImpl, id);
wl_resource* xdg_popup_resource = wl_resource_create(
client, &xdg_popup_interface, wl_resource_get_version(resource), id);
auto mock_xdg_popup =
std::make_unique<MockXdgPopup>(xdg_popup_resource, &kXdgPopupImpl);
mock_surface->set_xdg_popup(std::move(mock_xdg_popup));
}
void Pong(wl_client* client, wl_resource* resource, uint32_t serial) {
......
......@@ -104,17 +104,22 @@ void GetZXdgPopupV6(struct wl_client* client,
return;
}
wl_resource* xdg_popup_resource = wl_resource_create(
client, &zxdg_popup_v6_interface, wl_resource_get_version(resource), id);
auto* positioner = GetUserDataAs<TestPositioner>(positioner_resource);
if (positioner->size().width() == 0 ||
positioner->anchor_rect().width() == 0) {
DCHECK(positioner);
auto mock_xdg_popup =
std::make_unique<MockXdgPopup>(xdg_popup_resource, &kZxdgPopupV6Impl);
mock_xdg_popup->set_position(positioner->position());
if (mock_xdg_popup->size().IsEmpty() ||
mock_xdg_popup->anchor_rect().IsEmpty()) {
wl_resource_post_error(resource, ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER,
"Positioner object is not complete");
return;
}
CreateResourceWithImpl<MockXdgPopup>(client, &zxdg_popup_v6_interface,
wl_resource_get_version(resource),
&kZxdgPopupV6Impl, id);
mock_xdg_surface->set_xdg_popup(std::move(mock_xdg_popup));
}
const struct xdg_surface_interface kMockXdgSurfaceImpl = {
......
......@@ -5,10 +5,14 @@
#ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_XDG_SURFACE_H_
#define UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_XDG_SURFACE_H_
#include <memory>
#include <utility>
#include <xdg-shell-unstable-v5-server-protocol.h>
#include <xdg-shell-unstable-v6-server-protocol.h>
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/ozone/platform/wayland/test/mock_xdg_popup.h"
#include "ui/ozone/platform/wayland/test/server_object.h"
struct wl_resource;
......@@ -49,10 +53,17 @@ class MockXdgSurface : public ServerObject {
}
MockXdgTopLevel* xdg_toplevel() const { return xdg_toplevel_.get(); }
void set_xdg_popup(std::unique_ptr<MockXdgPopup> xdg_popup) {
xdg_popup_ = std::move(xdg_popup);
}
MockXdgPopup* xdg_popup() const { return xdg_popup_.get(); }
private:
// Used when xdg v6 is used.
std::unique_ptr<MockXdgTopLevel> xdg_toplevel_;
std::unique_ptr<MockXdgPopup> xdg_popup_;
DISALLOW_COPY_AND_ASSIGN(MockXdgSurface);
};
......
......@@ -69,16 +69,23 @@ void SetGravity(struct wl_client* client,
GetUserDataAs<TestPositioner>(resource)->set_gravity(gravity);
}
void SetConstraintAdjustment(struct wl_client* client,
struct wl_resource* resource,
uint32_t constraint_adjustment) {
GetUserDataAs<TestPositioner>(resource)->set_constraint_adjustment(
constraint_adjustment);
}
} // namespace
const struct zxdg_positioner_v6_interface kTestZxdgPositionerV6Impl = {
&DestroyResource, // destroy
&SetSize, // set_size
&SetAnchorRect, // set_anchor_rect
&SetAnchor, // set_anchor
&SetGravity, // set_gravity
nullptr, // set_constraint_adjustment
nullptr, // set_offset
&DestroyResource, // destroy
&SetSize, // set_size
&SetAnchorRect, // set_anchor_rect
&SetAnchor, // set_anchor
&SetGravity, // set_gravity
&SetConstraintAdjustment, // set_constraint_adjustment
nullptr, // set_offset
};
TestPositioner::TestPositioner(wl_resource* resource)
......
......@@ -5,6 +5,8 @@
#ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_POSITIONER_H_
#define UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_POSITIONER_H_
#include <utility>
#include <xdg-shell-unstable-v6-server-protocol.h>
#include "base/macros.h"
......@@ -22,24 +24,30 @@ extern const struct zxdg_positioner_v6_interface kTestZxdgPositionerV6Impl;
// surface relative to a parent surface.
class TestPositioner : public ServerObject {
public:
struct PopupPosition {
gfx::Rect anchor_rect;
gfx::Size size;
uint32_t anchor = 0;
uint32_t gravity = 0;
uint32_t constraint_adjustment = 0;
};
explicit TestPositioner(wl_resource* resource);
~TestPositioner() override;
void set_size(gfx::Size size) { size_ = size; }
gfx::Size size() const { return size_; }
void set_anchor_rect(gfx::Rect anchor_rect) { anchor_rect_ = anchor_rect; }
gfx::Rect anchor_rect() const { return anchor_rect_; }
void set_anchor(uint32_t anchor) { anchor_ = anchor; }
void set_gravity(uint32_t gravity) { gravity_ = gravity; }
PopupPosition position() { return std::move(position_); }
void set_size(gfx::Size size) { position_.size = size; }
void set_anchor_rect(gfx::Rect anchor_rect) {
position_.anchor_rect = anchor_rect;
}
void set_anchor(uint32_t anchor) { position_.anchor = anchor; }
void set_gravity(uint32_t gravity) { position_.gravity = gravity; }
void set_constraint_adjustment(uint32_t constraint_adjustment) {
position_.constraint_adjustment = constraint_adjustment;
}
private:
gfx::Rect anchor_rect_;
gfx::Size size_;
uint32_t anchor_;
uint32_t gravity_;
PopupPosition position_;
DISALLOW_COPY_AND_ASSIGN(TestPositioner);
};
......
......@@ -66,11 +66,20 @@ class XDGShellObjectFactory {
DISALLOW_COPY_AND_ASSIGN(XDGShellObjectFactory);
};
// Translates bounds relative to top level window to specified parent.
gfx::Rect TranslateBoundsToParentCoordinates(const gfx::Rect& child_bounds,
const gfx::Rect& parent_bounds) {
int x = child_bounds.x() - parent_bounds.x();
int y = child_bounds.y() - parent_bounds.y();
return gfx::Rect(gfx::Point(x, y), child_bounds.size());
return gfx::Rect(gfx::Point(child_bounds.x() - parent_bounds.x(),
child_bounds.y() - parent_bounds.y()),
child_bounds.size());
}
// Translates bounds relative to parent window to top level window.
gfx::Rect TranslateBoundsToTopLevelCoordinates(const gfx::Rect& child_bounds,
const gfx::Rect& parent_bounds) {
return gfx::Rect(gfx::Point(child_bounds.x() + parent_bounds.x(),
child_bounds.y() + parent_bounds.y()),
child_bounds.size());
}
} // namespace
......@@ -183,8 +192,7 @@ void WaylandWindow::CreateXdgPopup() {
DCHECK(parent_window_ && !xdg_popup_);
gfx::Rect bounds =
TranslateBoundsToParentCoordinates(bounds_, parent_window_->GetBounds());
auto bounds = AdjustPopupWindowPosition();
xdg_popup_ = xdg_shell_objects_factory_->CreateXDGPopup(connection_, this);
if (!xdg_popup_ ||
......@@ -235,9 +243,9 @@ void WaylandWindow::CreateAndShowTooltipSubSurface() {
void WaylandWindow::ApplyPendingBounds() {
if (pending_bounds_.IsEmpty())
return;
DCHECK(xdg_surface_);
SetBounds(pending_bounds_);
DCHECK(xdg_surface_);
xdg_surface_->SetWindowGeometry(bounds_);
xdg_surface_->AckConfigure();
pending_bounds_ = gfx::Rect();
......@@ -514,6 +522,8 @@ void WaylandWindow::HandleSurfaceConfigure(int32_t width,
bool is_maximized,
bool is_fullscreen,
bool is_activated) {
DCHECK(!xdg_popup());
// Propagate the window state information to the client.
PlatformWindowState old_state = state_;
......@@ -591,6 +601,48 @@ void WaylandWindow::HandleSurfaceConfigure(int32_t width,
MaybeTriggerPendingStateChange();
}
void WaylandWindow::HandlePopupConfigure(const gfx::Rect& bounds) {
DCHECK(xdg_popup());
gfx::Rect new_bounds = bounds;
// It's not enough to just set new bounds. If it is a menu window, whose
// parent is a top level window aka browser window, it can be flipped
// vertically along y-axis and have negative values set. Chromium cannot
// understand that and starts to position nested menu windows incorrectly. To
// fix that, we have to bear in mind that Wayland compositor does not share
// global coordinates for any surfaces, and Chromium assumes the top level
// window is always located at 0,0 origin. What is more, child windows must
// always be positioned relative to parent window local surface coordinates.
// Thus, if the menu window is flipped along y-axis by Wayland and its origin
// is above the top level parent window, the origin of the top level window
// has to be shifted by that value on y-axis so that the origin of the menu
// becomes x,0, and events can be handled normally.
if (!parent_window_->xdg_popup()) {
gfx::Rect parent_bounds = parent_window_->GetBounds();
// The menu window is flipped along y-axis and have x,-y origin. Shift the
// parent top level window instead.
if (new_bounds.y() < 0) {
// Move parent bounds along y-axis.
parent_bounds.set_y(-(new_bounds.y()));
new_bounds.set_y(0);
} else {
// If the menu window is located at correct origin from the browser point
// of view, return the top level window back to 0,0.
parent_bounds.set_y(0);
}
parent_window_->SetBounds(parent_bounds);
} else {
// The nested menu windows are located relative to the parent menu windows.
// Thus, the location must be translated to be relative to the top level
// window, which automatically becomes the same as relative to an origin of
// a display.
new_bounds = TranslateBoundsToTopLevelCoordinates(
new_bounds, parent_window_->GetBounds());
DCHECK(new_bounds.y() >= 0);
}
SetBounds(new_bounds);
}
void WaylandWindow::OnCloseRequest() {
// Before calling OnCloseRequest, the |xdg_popup_| must become hidden and
// only then call OnCloseRequest().
......@@ -753,6 +805,50 @@ void WaylandWindow::UpdateCursorPositionFromEvent(
}
}
gfx::Rect WaylandWindow::AdjustPopupWindowPosition() const {
auto* parent_window = parent_window_->xdg_popup()
? parent_window_->parent_window_
: parent_window_;
DCHECK(parent_window);
// Chromium positions windows in screen coordinates, but Wayland requires them
// to be in local surface coordinates aka relative to parent window.
const gfx::Rect parent_bounds = parent_window_->GetBounds();
gfx::Rect new_bounds =
TranslateBoundsToParentCoordinates(bounds_, parent_bounds);
// Chromium may decide to position nested menu windows on the left side
// instead of the right side of parent menu windows when the size of the
// window becomes larger than the display it is shown on. It's correct when
// the window is located on one display and occupies the whole work area, but
// as soon as it's moved and there is space on the right side, Chromium
// continues positioning the nested menus on the left side relative to the
// parent menu (Wayland does not provide clients with global coordinates).
// Instead, reposition that window to be on the right side of the parent menu
// window and let the compositor decide how to position it if it does not fit
// a single display. However, there is one exception - if the window is
// maximized, let Chromium position it on the left side as long as the Wayland
// compositor may decide to position the nested window on the right side of
// the parent menu window, which results in showing it on a second display if
// more than one display is used.
if (parent_window_->xdg_popup() && parent_window_->parent_window_ &&
!parent_window_->parent_window_->IsMaximized()) {
auto* top_level_window = parent_window_->parent_window_;
DCHECK(top_level_window && !top_level_window->xdg_popup());
if (new_bounds.x() <= 0 && !top_level_window->IsMaximized()) {
// Position the child menu window on the right side of the parent window
// and let the Wayland compositor decide how to do constraint
// adjustements.
int new_x = parent_bounds.width() - (new_bounds.width() + new_bounds.x());
new_bounds.set_x(new_x);
}
}
return new_bounds;
}
WaylandWindow* WaylandWindow::GetTopLevelWindow() {
return parent_window_ ? parent_window_->GetTopLevelWindow() : this;
}
// static
void WaylandWindow::Enter(void* data,
struct wl_surface* wl_surface,
......
......@@ -134,6 +134,7 @@ class WaylandWindow : public PlatformWindow,
bool is_maximized,
bool is_fullscreen,
bool is_activated);
void HandlePopupConfigure(const gfx::Rect& bounds);
void OnCloseRequest();
......@@ -173,6 +174,11 @@ class WaylandWindow : public PlatformWindow,
void UpdateCursorPositionFromEvent(std::unique_ptr<Event> event);
// Returns bounds with origin relative to parent window's origin.
gfx::Rect AdjustPopupWindowPosition() const;
WaylandWindow* GetTopLevelWindow();
// wl_surface_listener
static void Enter(void* data,
struct wl_surface* wl_surface,
......
......@@ -37,6 +37,10 @@ bool XDGPopupWrapperV5::Initialize(WaylandConnection* connection,
xdg_popup_add_listener(xdg_popup_.get(), &xdg_popup_listener, this);
// xdg_popup_v5 does not support configure events. Thus, manually call it to
// propagate final bounds.
wayland_window_->HandlePopupConfigure(bounds);
return true;
}
......
......@@ -6,13 +6,170 @@
#include <xdg-shell-unstable-v6-client-protocol.h>
#include "ui/events/event_constants.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/ozone/platform/wayland/wayland_connection.h"
#include "ui/ozone/platform/wayland/wayland_pointer.h"
#include "ui/ozone/platform/wayland/wayland_window.h"
#include "ui/ozone/platform/wayland/xdg_surface_wrapper_v6.h"
namespace ui {
namespace {
constexpr uint32_t kAnchorDefaultWidth = 1;
constexpr uint32_t kAnchorDefaultHeight = 1;
constexpr uint32_t kAnchorHeightParentMenu = 30;
enum class MenuType {
TYPE_RIGHT_CLICK,
TYPE_3DOT_PARENT_MENU,
TYPE_3DOT_CHILD_MENU,
TYPE_UNKNOWN,
};
uint32_t GetAnchor(MenuType menu_type, const gfx::Rect& bounds) {
uint32_t anchor = 0;
switch (menu_type) {
case MenuType::TYPE_RIGHT_CLICK:
anchor = ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
break;
case MenuType::TYPE_3DOT_PARENT_MENU:
anchor =
ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
break;
case MenuType::TYPE_3DOT_CHILD_MENU:
anchor = ZXDG_POSITIONER_V6_ANCHOR_TOP;
// Chromium may want to manually position a child menu on the left side of
// its parent menu. Thus, react accordingly. Positive x means the child is
// located on the right side of the parent and negative - on the left
// side.
if (bounds.x() >= 0)
anchor |= ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
else
anchor |= ZXDG_POSITIONER_V6_ANCHOR_LEFT;
break;
case MenuType::TYPE_UNKNOWN:
NOTREACHED() << "Unsupported menu type";
break;
}
return anchor;
}
uint32_t GetGravity(MenuType menu_type, const gfx::Rect& bounds) {
uint32_t gravity = 0;
switch (menu_type) {
case MenuType::TYPE_RIGHT_CLICK:
gravity =
ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
break;
case MenuType::TYPE_3DOT_PARENT_MENU:
gravity =
ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
break;
case MenuType::TYPE_3DOT_CHILD_MENU:
gravity = ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
// Chromium may want to manually position a child menu on the left side of
// its parent menu. Thus, react accordingly. Positive x means the child is
// located on the right side of the parent and negative - on the left
// side.
if (bounds.x() >= 0)
gravity |= ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
else
gravity |= ZXDG_POSITIONER_V6_GRAVITY_LEFT;
break;
case MenuType::TYPE_UNKNOWN:
NOTREACHED() << "Unsupported menu type";
break;
}
return gravity;
}
uint32_t GetConstraintAdjustment(MenuType menu_type) {
uint32_t constraint = 0;
switch (menu_type) {
case MenuType::TYPE_RIGHT_CLICK:
constraint = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X |
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y |
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y;
break;
case MenuType::TYPE_3DOT_PARENT_MENU:
constraint = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X |
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y;
break;
case MenuType::TYPE_3DOT_CHILD_MENU:
constraint = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X |
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
break;
case MenuType::TYPE_UNKNOWN:
NOTREACHED() << "Unsupported menu type";
break;
}
return constraint;
}
gfx::Rect GetAnchorRect(MenuType menu_type,
const gfx::Rect& menu_bounds,
const gfx::Rect& parent_window_bounds) {
gfx::Rect anchor_rect;
switch (menu_type) {
case MenuType::TYPE_RIGHT_CLICK:
// Place anchor for right click menus normally.
anchor_rect = gfx::Rect(menu_bounds.x(), menu_bounds.y(),
kAnchorDefaultWidth, kAnchorDefaultHeight);
break;
case MenuType::TYPE_3DOT_PARENT_MENU:
// The anchor for parent menu windows is positioned slightly above the
// specified bounds to ensure flipped window along y-axis won't hide 3-dot
// menu button.
anchor_rect = gfx::Rect(menu_bounds.x() - kAnchorDefaultWidth,
menu_bounds.y() - kAnchorHeightParentMenu,
kAnchorDefaultWidth, kAnchorHeightParentMenu);
break;
case MenuType::TYPE_3DOT_CHILD_MENU:
// The child menu's anchor must meet the following requirements: at some
// point, the Wayland compositor can flip it along x-axis. To make sure
// it's positioned correctly, place it closer to the beginning of the
// parent menu shifted by the same value along x-axis. The width of anchor
// must correspond the width between two points - specified origin by the
// Chromium and calculated point shifted by the same value along x-axis
// from the beginning of the parent menu width.
//
// We also have to bear in mind that Chromium may decide to flip the
// position of the menu window along the x-axis and show it on the other
// side of the parent menu window (normally, the Wayland compositor does
// it). Thus, check which side the child menu window is going to be
// presented on and create right anchor.
if (menu_bounds.x() >= 0) {
auto anchor_width =
parent_window_bounds.width() -
(parent_window_bounds.width() - menu_bounds.x()) * 2;
anchor_rect =
gfx::Rect(parent_window_bounds.width() - menu_bounds.x(),
menu_bounds.y(), anchor_width, kAnchorDefaultHeight);
} else {
DCHECK_LE(menu_bounds.x(), 0);
auto position = menu_bounds.width() + menu_bounds.x();
DCHECK(position > 0 && position < parent_window_bounds.width());
auto anchor_width = parent_window_bounds.width() - position * 2;
anchor_rect = gfx::Rect(position, menu_bounds.y(), anchor_width,
kAnchorDefaultHeight);
}
break;
case MenuType::TYPE_UNKNOWN:
NOTREACHED() << "Unsupported menu type";
break;
}
return anchor_rect;
}
} // namespace
XDGPopupWrapperV6::XDGPopupWrapperV6(std::unique_ptr<XDGSurfaceWrapper> surface,
WaylandWindow* wayland_window)
: wayland_window_(wayland_window), zxdg_surface_v6_(std::move(surface)) {
......@@ -51,7 +208,8 @@ bool XDGPopupWrapperV6::Initialize(WaylandConnection* connection,
if (!parent_xdg_surface)
return false;
zxdg_positioner_v6* positioner = CreatePositioner(connection, bounds);
zxdg_positioner_v6* positioner =
CreatePositioner(connection, parent_window, bounds);
if (!positioner)
return false;
......@@ -73,20 +231,41 @@ bool XDGPopupWrapperV6::Initialize(WaylandConnection* connection,
zxdg_positioner_v6* XDGPopupWrapperV6::CreatePositioner(
WaylandConnection* connection,
WaylandWindow* parent_window,
const gfx::Rect& bounds) {
struct zxdg_positioner_v6* positioner;
positioner = zxdg_shell_v6_create_positioner(connection->shell_v6());
if (!positioner)
return nullptr;
zxdg_positioner_v6_set_anchor_rect(positioner, bounds.x(), bounds.y(), 1, 1);
auto* pointer = connection->pointer();
uint32_t flags = 0;
if (pointer)
flags = pointer->GetFlagsWithKeyboardModifiers();
bool is_right_click_menu = flags & EF_RIGHT_MOUSE_BUTTON;
// Different types of menu require different anchors, constraint adjustments,
// gravity and etc.
MenuType menu_type = MenuType::TYPE_UNKNOWN;
if (is_right_click_menu)
menu_type = MenuType::TYPE_RIGHT_CLICK;
else if (parent_window->xdg_popup())
menu_type = MenuType::TYPE_3DOT_CHILD_MENU;
else
menu_type = MenuType::TYPE_3DOT_PARENT_MENU;
// Place anchor to the end of the possible position.
gfx::Rect anchor_rect =
GetAnchorRect(menu_type, bounds, parent_window->GetBounds());
zxdg_positioner_v6_set_anchor_rect(positioner, anchor_rect.x(),
anchor_rect.y(), anchor_rect.width(),
anchor_rect.height());
zxdg_positioner_v6_set_size(positioner, bounds.width(), bounds.height());
zxdg_positioner_v6_set_anchor(
positioner,
ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
zxdg_positioner_v6_set_gravity(
positioner,
ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
zxdg_positioner_v6_set_anchor(positioner, GetAnchor(menu_type, bounds));
zxdg_positioner_v6_set_gravity(positioner, GetGravity(menu_type, bounds));
zxdg_positioner_v6_set_constraint_adjustment(
positioner, GetConstraintAdjustment(menu_type));
return positioner;
}
......@@ -96,7 +275,19 @@ void XDGPopupWrapperV6::Configure(void* data,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {}
int32_t height) {
// As long as the Wayland compositor repositions/requires to position windows
// relative to their parents, do not propagate final bounds information to
// Chromium. The browser places windows in respect to screen origin, but
// Wayland requires doing so in respect to parent window's origin. To properly
// place windows, the bounds are translated and adjusted according to the
// Wayland compositor needs during WaylandWindow::CreateXdgPopup call.
gfx::Rect new_bounds(x, y, width, height);
WaylandWindow* window =
static_cast<XDGPopupWrapperV6*>(data)->wayland_window_;
DCHECK(window);
window->HandlePopupConfigure(new_bounds);
}
// static
void XDGPopupWrapperV6::PopupDone(void* data,
......
......@@ -26,6 +26,7 @@ class XDGPopupWrapperV6 : public XDGPopupWrapper {
const gfx::Rect& bounds) override;
zxdg_positioner_v6* CreatePositioner(WaylandConnection* connection,
WaylandWindow* parent_window,
const gfx::Rect& bounds);
// xdg_popup_listener
......
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