Commit 6aaaa949 authored by Vladislav Kaznacheev's avatar Vladislav Kaznacheev Committed by Commit Bot

Fix large menu position on a small screen.

Currently excessively large menus are only displayed correctly
with kBubbleAbove alignment.

This CL adds checks and adjustments that ensure the correct menu
size and position when alignment is kBubbleLeft or kBubbleRight.

Bug: 1002879
Test: manual, added MenuControllerTest.TestMenuFitsOnSmallScreen/*
Change-Id: I2dd36432465d4df39f47296d7675f9c65e7ec25e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1807565Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Vladislav Kaznacheev <kaznacheev@chromium.org>
Cr-Commit-Position: refs/heads/master@{#704394}
parent ac357d4e
...@@ -2407,44 +2407,61 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, ...@@ -2407,44 +2407,61 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
y = anchor_bounds.bottom() - border_and_shadow_insets.top() + y = anchor_bounds.bottom() - border_and_shadow_insets.top() +
menu_config.touchable_anchor_offset; menu_config.touchable_anchor_offset;
} }
} else if (state_.anchor == MenuAnchorPosition::kBubbleLeft) { } else if (state_.anchor == MenuAnchorPosition::kBubbleLeft ||
// Align the right of the menu with the left of the anchor, and the top of state_.anchor == MenuAnchorPosition::kBubbleRight) {
// the menu with the top of the anchor. if (state_.anchor == MenuAnchorPosition::kBubbleLeft) {
x = anchor_bounds.x() - menu_size.width() + // Align the right of the menu with the left of the anchor, and the top
border_and_shadow_insets.right() - // of the menu with the top of the anchor.
menu_config.touchable_anchor_offset;
y = anchor_bounds.y() - border_and_shadow_insets.top();
// Align the left of the menu with the right of the anchor.
if (x < monitor_bounds.x()) {
x = anchor_bounds.right() - border_and_shadow_insets.left() +
menu_config.touchable_anchor_offset;
}
// Prefer aligning the bottom of the menu to the bottom of the anchor.
if (y + menu_size.height() > monitor_bounds.bottom()) {
y = anchor_bounds.bottom() - menu_size.height() +
border_and_shadow_insets.bottom();
// For some very tall menus, this may still be off screen.
if (y < monitor_bounds.y())
y = monitor_bounds.y();
}
} else if (state_.anchor == MenuAnchorPosition::kBubbleRight) {
// Align the left of the menu with the right of the anchor, and the top of
// the menu with the top of the anchor.
x = anchor_bounds.right() - border_and_shadow_insets.left() +
menu_config.touchable_anchor_offset;
y = anchor_bounds.y() - border_and_shadow_insets.top();
if (x + menu_size.width() > monitor_bounds.right()) {
// Align the right of the menu with the left of the anchor.
x = anchor_bounds.x() - menu_size.width() + x = anchor_bounds.x() - menu_size.width() +
border_and_shadow_insets.right() - border_and_shadow_insets.right() -
menu_config.touchable_anchor_offset; menu_config.touchable_anchor_offset;
// Align the left of the menu with the right of the anchor.
if (x < monitor_bounds.x()) {
x = anchor_bounds.right() - border_and_shadow_insets.left() +
menu_config.touchable_anchor_offset;
}
} else {
// Align the left of the menu with the right of the anchor, and the top
// of the menu with the top of the anchor.
x = anchor_bounds.right() - border_and_shadow_insets.left() +
menu_config.touchable_anchor_offset;
if (x + menu_size.width() > monitor_bounds.right()) {
// Align the right of the menu with the left of the anchor.
x = anchor_bounds.x() - menu_size.width() +
border_and_shadow_insets.right() -
menu_config.touchable_anchor_offset;
}
} }
if (y + menu_size.height() > monitor_bounds.bottom()) {
// Align the bottom of the menu with the bottom of the anchor. const int y_below = anchor_bounds.y() - border_and_shadow_insets.top();
y = anchor_bounds.bottom() - menu_size.height() + const int y_above = anchor_bounds.bottom() - menu_size.height() +
border_and_shadow_insets.bottom(); border_and_shadow_insets.bottom();
if (y_below + menu_size.height() <= monitor_bounds.bottom()) {
// Show below the anchor. Align the top of the menu with the top of the
// anchor.
y = y_below;
} else if (y_above >= monitor_bounds.y()) {
// No room below, but there is room above. Show above the anchor. Align
// the bottom of the menu with the bottom of the anchor.
y = y_above;
} else {
// No room above or below. Show as low as possible. Align the bottom of
// the menu with the bottom of the screen.
y = monitor_bounds.bottom() - menu_size.height();
} }
} }
// The above adjustments may have shifted a large menu off the screen.
// Clamp the menu origin to the valid range.
const int x_min = monitor_bounds.x() - border_and_shadow_insets.left();
const int x_max = monitor_bounds.right() - menu_size.width() +
border_and_shadow_insets.right();
const int y_min = monitor_bounds.y() - border_and_shadow_insets.top();
const int y_max = monitor_bounds.bottom() - menu_size.height() +
border_and_shadow_insets.bottom();
DCHECK_LE(x_min, x_max);
DCHECK_LE(y_min, y_max);
x = base::ClampToRange(x, x_min, x_max);
y = base::ClampToRange(y, y_min, y_max);
} else { } else {
if (!use_touchable_layout_) { if (!use_touchable_layout_) {
NOTIMPLEMENTED() NOTIMPLEMENTED()
......
...@@ -560,6 +560,62 @@ class MenuControllerTest : public ViewsTestBase, ...@@ -560,6 +560,62 @@ class MenuControllerTest : public ViewsTestBase,
EXPECT_TRUE(options.monitor_bounds.Contains(final_bounds)); EXPECT_TRUE(options.monitor_bounds.Contains(final_bounds));
} }
void TestMenuFitsOnSmallScreen(MenuAnchorPosition menu_anchor_position,
const gfx::Rect& monitor_bounds) {
SCOPED_TRACE(base::StringPrintf(
"MenuAnchorPosition: %d, monitor_bounds: @%s\n", menu_anchor_position,
monitor_bounds.ToString().c_str()));
MenuBoundsOptions options;
options.menu_anchor = menu_anchor_position;
options.monitor_bounds = monitor_bounds;
options.menu_size = monitor_bounds.size();
options.menu_size.Enlarge(100, 100);
const gfx::Size anchor_size(0, 0);
// Adjust the final bounds to not include the shadow and border.
const gfx::Insets border_and_shadow_insets =
BubbleBorder::GetBorderAndShadowInsets(
MenuConfig::instance().touchable_menu_shadow_elevation);
options.anchor_bounds = gfx::Rect(monitor_bounds.origin(), anchor_size);
gfx::Rect final_bounds = CalculateBubbleMenuBounds(options);
final_bounds.Inset(border_and_shadow_insets);
EXPECT_TRUE(options.monitor_bounds.Contains(final_bounds))
<< options.monitor_bounds.ToString() << " does not contain "
<< final_bounds.ToString();
options.anchor_bounds =
gfx::Rect(monitor_bounds.bottom_left(), anchor_size);
final_bounds = CalculateBubbleMenuBounds(options);
final_bounds.Inset(border_and_shadow_insets);
EXPECT_TRUE(options.monitor_bounds.Contains(final_bounds))
<< options.monitor_bounds.ToString() << " does not contain "
<< final_bounds.ToString();
options.anchor_bounds =
gfx::Rect(monitor_bounds.bottom_right(), anchor_size);
final_bounds = CalculateBubbleMenuBounds(options);
final_bounds.Inset(border_and_shadow_insets);
EXPECT_TRUE(options.monitor_bounds.Contains(final_bounds))
<< options.monitor_bounds.ToString() << " does not contain "
<< final_bounds.ToString();
options.anchor_bounds = gfx::Rect(monitor_bounds.top_right(), anchor_size);
final_bounds = CalculateBubbleMenuBounds(options);
final_bounds.Inset(border_and_shadow_insets);
EXPECT_TRUE(options.monitor_bounds.Contains(final_bounds))
<< options.monitor_bounds.ToString() << " does not contain "
<< final_bounds.ToString();
options.anchor_bounds =
gfx::Rect(monitor_bounds.CenterPoint(), anchor_size);
final_bounds = CalculateBubbleMenuBounds(options);
final_bounds.Inset(border_and_shadow_insets);
EXPECT_TRUE(options.monitor_bounds.Contains(final_bounds))
<< options.monitor_bounds.ToString() << " does not contain "
<< final_bounds.ToString();
}
void TestSubmenuFitsOnScreen(MenuItemView* item, void TestSubmenuFitsOnScreen(MenuItemView* item,
const gfx::Rect& monitor_bounds, const gfx::Rect& monitor_bounds,
const gfx::Rect& parent_bounds) { const gfx::Rect& parent_bounds) {
...@@ -1855,6 +1911,24 @@ TEST_P(MenuControllerTest, TestMenuFitsOnScreenSmallAnchor) { ...@@ -1855,6 +1911,24 @@ TEST_P(MenuControllerTest, TestMenuFitsOnScreenSmallAnchor) {
} }
} }
// Test that menus fit a small screen.
TEST_P(MenuControllerTest, TestMenuFitsOnSmallScreen) {
const int display_size = 500;
// Simulate multiple display layouts.
for (int x = -1; x <= 1; x++)
for (int y = -1; y <= 1; y++) {
const gfx::Rect monitor_bounds(x * display_size, y * display_size,
display_size, display_size);
TestMenuFitsOnSmallScreen(MenuAnchorPosition::kBubbleAbove,
monitor_bounds);
TestMenuFitsOnSmallScreen(MenuAnchorPosition::kBubbleLeft,
monitor_bounds);
TestMenuFitsOnSmallScreen(MenuAnchorPosition::kBubbleRight,
monitor_bounds);
}
}
// Test that submenus are displayed within the screen bounds on smaller screens. // Test that submenus are displayed within the screen bounds on smaller screens.
TEST_P(MenuControllerTest, TestSubmenuFitsOnScreen) { TEST_P(MenuControllerTest, TestSubmenuFitsOnScreen) {
menu_controller()->set_use_touchable_layout(true); menu_controller()->set_use_touchable_layout(true);
......
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