Commit 49938bfd authored by Caroline Rising's avatar Caroline Rising Committed by Commit Bot

Mirror or offset bubble arrow if bubble exits parent window.

Prior behavior was for bubbles to stay within the screen bounds. With this change the bubble will also attempt to stay within the parent window bounds (if one exist) while always staying within the screen bounds. Also add ability for any bubble to offset if out of given bounds instead of mirror. This behavior is used for tab hover cards.

Bug: 910739
Change-Id: Ib1137646e818b09fe5192896414be9686d1e01fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1552284Reviewed-by: default avatarDana Fried <dfried@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Commit-Queue: Caroline Rising <corising@chromium.org>
Cr-Commit-Position: refs/heads/master@{#649240}
parent 75430c45
......@@ -192,6 +192,8 @@ TabHoverCardBubbleView::TabHoverCardBubbleView(Tab* tab)
// hide the hovercard on press, touch, and keyboard events.
SetCanActivate(false);
set_adjust_if_offscreen(true);
title_label_ =
new views::Label(base::string16(), CONTEXT_TAB_HOVER_CARD_TITLE,
views::style::STYLE_PRIMARY);
......@@ -234,6 +236,9 @@ TabHoverCardBubbleView::TabHoverCardBubbleView(Tab* tab)
widget_ = views::BubbleDialogDelegateView::CreateBubble(this);
fade_animation_delegate_ =
std::make_unique<WidgetFadeAnimationDelegate>(widget_);
GetBubbleFrameView()->set_preferred_arrow_adjustment(
views::BubbleFrameView::PreferredArrowAdjustment::kOffset);
}
TabHoverCardBubbleView::~TabHoverCardBubbleView() = default;
......
......@@ -174,6 +174,7 @@ class VIEWS_EXPORT BubbleBorder : public Border {
// The arrow will still anchor to the same location but the bubble will shift
// location to place the arrow |offset| pixels from the perpendicular edge.
void set_arrow_offset(int offset) { arrow_offset_ = offset; }
int arrow_offset() const { return arrow_offset_; }
// Sets the shadow elevation for MD shadows. A null |shadow_elevation| will
// yield the default BubbleBorder MD shadow.
......
......@@ -46,9 +46,9 @@ namespace {
// Get the |vertical| or horizontal amount that |available_bounds| overflows
// |window_bounds|.
int GetOffScreenLength(const gfx::Rect& available_bounds,
const gfx::Rect& window_bounds,
bool vertical) {
int GetOverflowLength(const gfx::Rect& available_bounds,
const gfx::Rect& window_bounds,
bool vertical) {
if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
return 0;
......@@ -384,8 +384,8 @@ void BubbleFrameView::OnThemeChanged() {
void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
if (bubble_border_ && bubble_border_->use_theme_background_color()) {
bubble_border_->set_background_color(GetNativeTheme()->
GetSystemColor(ui::NativeTheme::kColorId_DialogBackground));
bubble_border_->set_background_color(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DialogBackground));
SchedulePaint();
}
}
......@@ -461,21 +461,38 @@ void BubbleFrameView::SetFootnoteView(View* view) {
AddChildView(footnote_container_);
}
gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect& anchor_rect,
const gfx::Size& client_size,
bool adjust_if_offscreen) {
gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(
const gfx::Rect& anchor_rect,
const gfx::Size& client_size,
bool adjust_to_fit_available_bounds) {
gfx::Size size(GetFrameSizeForClientSize(client_size));
const BubbleBorder::Arrow arrow = bubble_border_->arrow();
if (adjust_if_offscreen && BubbleBorder::has_arrow(arrow)) {
// Try to mirror the anchoring if the bubble does not fit on the screen.
if (!bubble_border_->is_arrow_at_center(arrow)) {
MirrorArrowIfOffScreen(true, anchor_rect, size);
MirrorArrowIfOffScreen(false, anchor_rect, size);
} else {
if (adjust_to_fit_available_bounds && BubbleBorder::has_arrow(arrow)) {
// Get the desired bubble bounds without adjustment.
bubble_border_->set_arrow_offset(0);
// Try to mirror the anchoring if the bubble does not fit in the available
// bounds.
if (bubble_border_->is_arrow_at_center(arrow) ||
preferred_arrow_adjustment_ == PreferredArrowAdjustment::kOffset) {
const bool mirror_vertical = BubbleBorder::is_arrow_on_horizontal(arrow);
MirrorArrowIfOffScreen(mirror_vertical, anchor_rect, size);
OffsetArrowIfOffScreen(anchor_rect, size);
MirrorArrowIfOutOfBounds(mirror_vertical, anchor_rect, size,
GetAvailableAnchorWindowBounds());
MirrorArrowIfOutOfBounds(mirror_vertical, anchor_rect, size,
GetAvailableScreenBounds(anchor_rect));
OffsetArrowIfOutOfBounds(anchor_rect, size,
GetAvailableAnchorWindowBounds());
OffsetArrowIfOutOfBounds(anchor_rect, size,
GetAvailableScreenBounds(anchor_rect));
} else {
MirrorArrowIfOutOfBounds(true, anchor_rect, size,
GetAvailableAnchorWindowBounds());
MirrorArrowIfOutOfBounds(true, anchor_rect, size,
GetAvailableScreenBounds(anchor_rect));
MirrorArrowIfOutOfBounds(false, anchor_rect, size,
GetAvailableAnchorWindowBounds());
MirrorArrowIfOutOfBounds(false, anchor_rect, size,
GetAvailableScreenBounds(anchor_rect));
}
}
......@@ -495,6 +512,17 @@ gfx::Rect BubbleFrameView::GetAvailableScreenBounds(
.work_area();
}
gfx::Rect BubbleFrameView::GetAvailableAnchorWindowBounds() const {
views::BubbleDialogDelegateView* bubble_delegate_view =
GetWidget()->widget_delegate()->AsBubbleDialogDelegate();
if (bubble_delegate_view) {
views::View* const anchor_view = bubble_delegate_view->GetAnchorView();
if (anchor_view && anchor_view->GetWidget())
return anchor_view->GetWidget()->GetWindowBoundsInScreen();
}
return gfx::Rect();
}
bool BubbleFrameView::ExtendClientIntoTitle() const {
return false;
}
......@@ -507,14 +535,16 @@ gfx::Rect BubbleFrameView::GetCloseButtonMirroredBounds() const {
return close_->GetMirroredBounds();
}
void BubbleFrameView::MirrorArrowIfOffScreen(
void BubbleFrameView::MirrorArrowIfOutOfBounds(
bool vertical,
const gfx::Rect& anchor_rect,
const gfx::Size& client_size) {
// Check if the bounds don't fit on screen.
gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
const gfx::Size& client_size,
const gfx::Rect& available_bounds) {
if (available_bounds.IsEmpty())
return;
// Check if the bounds don't fit in the available bounds.
gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
if (GetOffScreenLength(available_bounds, window_bounds, vertical) > 0) {
if (GetOverflowLength(available_bounds, window_bounds, vertical) > 0) {
BubbleBorder::Arrow arrow = bubble_border()->arrow();
// Mirror the arrow and get the new bounds.
bubble_border_->set_arrow(
......@@ -525,8 +555,8 @@ void BubbleFrameView::MirrorArrowIfOffScreen(
// Restore the original arrow if mirroring doesn't show more of the bubble.
// Otherwise it should invoke parent's Layout() to layout the content based
// on the new bubble border.
if (GetOffScreenLength(available_bounds, mirror_bounds, vertical) >=
GetOffScreenLength(available_bounds, window_bounds, vertical)) {
if (GetOverflowLength(available_bounds, mirror_bounds, vertical) >=
GetOverflowLength(available_bounds, window_bounds, vertical)) {
bubble_border_->set_arrow(arrow);
} else {
InvalidateLayout();
......@@ -535,16 +565,15 @@ void BubbleFrameView::MirrorArrowIfOffScreen(
}
}
void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect,
const gfx::Size& client_size) {
void BubbleFrameView::OffsetArrowIfOutOfBounds(
const gfx::Rect& anchor_rect,
const gfx::Size& client_size,
const gfx::Rect& available_bounds) {
BubbleBorder::Arrow arrow = bubble_border()->arrow();
DCHECK(BubbleBorder::is_arrow_at_center(arrow));
DCHECK(BubbleBorder::is_arrow_at_center(arrow) ||
preferred_arrow_adjustment_ == PreferredArrowAdjustment::kOffset);
// Get the desired bubble bounds without adjustment.
bubble_border_->set_arrow_offset(0);
gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
return;
......@@ -552,22 +581,38 @@ void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect,
const bool is_horizontal = BubbleBorder::is_arrow_on_horizontal(arrow);
int offscreen_adjust = 0;
if (is_horizontal) {
if (window_bounds.x() < available_bounds.x())
// If the window bounds are larger than the available bounds then we want to
// offset the window to fit as much of it in the available bounds as
// possible without exiting the other side of the available bounds.
if (window_bounds.width() > available_bounds.width()) {
if (window_bounds.x() < available_bounds.x())
offscreen_adjust = available_bounds.right() - window_bounds.right();
else
offscreen_adjust = available_bounds.x() - window_bounds.x();
} else if (window_bounds.x() < available_bounds.x()) {
offscreen_adjust = available_bounds.x() - window_bounds.x();
else if (window_bounds.right() > available_bounds.right())
} else if (window_bounds.right() > available_bounds.right()) {
offscreen_adjust = available_bounds.right() - window_bounds.right();
}
} else {
if (window_bounds.y() < available_bounds.y())
if (window_bounds.height() > available_bounds.height()) {
if (window_bounds.y() < available_bounds.y())
offscreen_adjust = available_bounds.bottom() - window_bounds.bottom();
else
offscreen_adjust = available_bounds.y() - window_bounds.y();
} else if (window_bounds.y() < available_bounds.y()) {
offscreen_adjust = available_bounds.y() - window_bounds.y();
else if (window_bounds.bottom() > available_bounds.bottom())
} else if (window_bounds.bottom() > available_bounds.bottom()) {
offscreen_adjust = available_bounds.bottom() - window_bounds.bottom();
}
}
// For center arrows, arrows are moved in the opposite direction of
// |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble
// window needs to be moved to the right and that means we need to move arrow
// to the left, and that means negative offset.
bubble_border_->set_arrow_offset(-offscreen_adjust);
bubble_border_->set_arrow_offset(bubble_border_->arrow_offset() -
offscreen_adjust);
if (offscreen_adjust)
SchedulePaint();
}
......
......@@ -27,6 +27,8 @@ class ImageView;
class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
public ButtonListener {
public:
enum class PreferredArrowAdjustment { kMirror, kOffset };
// Internal class name.
static const char kViewClassName[];
......@@ -92,12 +94,17 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
footnote_margins_ = footnote_margins;
}
void set_preferred_arrow_adjustment(PreferredArrowAdjustment adjustment) {
preferred_arrow_adjustment_ = adjustment;
}
// Given the size of the contents and the rect to point at, returns the bounds
// of the bubble window. The bubble's arrow location may change if the bubble
// does not fit on the monitor and |adjust_if_offscreen| is true.
// does not fit on the monitor or anchor window (if one exists) and
// |adjust_to_fit_available_bounds| is true.
gfx::Rect GetUpdatedWindowBounds(const gfx::Rect& anchor_rect,
const gfx::Size& client_size,
bool adjust_if_offscreen);
bool adjust_to_fit_available_bounds);
Button* GetCloseButtonForTest() { return close_; }
......@@ -110,6 +117,9 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
// Returns the available screen bounds if the frame were to show in |rect|.
virtual gfx::Rect GetAvailableScreenBounds(const gfx::Rect& rect) const;
// Returns the available anchor window bounds in the screen.
virtual gfx::Rect GetAvailableAnchorWindowBounds() const;
// Override and return true to allow client view to overlap into the title
// area when HasTitle() returns false and/or ShouldShowCloseButton() returns
// true. Returns false by default.
......@@ -127,15 +137,17 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
FRIEND_TEST_ALL_PREFIXES(BubbleDialogDelegateViewTest, CloseMethods);
// Mirrors the bubble's arrow location on the |vertical| or horizontal axis,
// if the generated window bounds don't fit in the monitor bounds.
void MirrorArrowIfOffScreen(bool vertical,
const gfx::Rect& anchor_rect,
const gfx::Size& client_size);
// if the generated window bounds don't fit in the given available bounds.
void MirrorArrowIfOutOfBounds(bool vertical,
const gfx::Rect& anchor_rect,
const gfx::Size& client_size,
const gfx::Rect& available_bounds);
// Adjust the bubble's arrow offsets if the generated window bounds don't fit
// in the monitor bounds.
void OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect,
const gfx::Size& client_size);
// in the given available bounds.
void OffsetArrowIfOutOfBounds(const gfx::Rect& anchor_rect,
const gfx::Size& client_size,
const gfx::Rect& available_bounds);
// The width of the frame for the given |client_width|. The result accounts
// for the minimum title bar width and includes all insets and possible
......@@ -188,6 +200,11 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
// Time when view has been shown.
base::TimeTicks view_shown_time_stamp_;
// Set preference for how the arrow will be adjusted if the window is outside
// the available bounds.
PreferredArrowAdjustment preferred_arrow_adjustment_ =
PreferredArrowAdjustment::kMirror;
DISALLOW_COPY_AND_ASSIGN(BubbleFrameView);
};
......
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