blob: 25e2cfce4999bdf6a94d61aae4558470d18f89ed [file] [log] [blame]
// Copyright 2016 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 "chrome/browser/ui/autofill/popup_view_common.h"
#include <algorithm>
#include <utility>
#include "build/build_config.h"
#include "chrome/browser/platform_util.h"
#include "ui/gfx/geometry/vector2d.h"
#if defined(OS_ANDROID)
#include "ui/android/view_android.h"
#include "ui/android/window_android.h"
#else // defined(OS_ANDROID)
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "ui/views/widget/widget.h"
#endif // !defined(OS_ANDROID)
namespace autofill {
namespace {
// CalculateWidthValues below ultimately determines the maximum popup width
// given the constraints of the window and the element's position within it.
// Returning a struct allows callers to reuse intermediary values produced
// during this calculation which are also relevant to positioning and sizing.
struct WidthCalculationResults {
int right_growth_start; // Popup start coordinate when growing to the right.
int left_growth_end; // Popup end coordinate when growing to the left.
int right_available; // Amount of space available if drawing to the right.
int left_available; // Amount of space available if drawing to the left.
int max_popup_width; // The max of |right_available| and |left_available|.
};
WidthCalculationResults CalculateWidthValues(int leftmost_available_x,
int rightmost_available_x,
const gfx::Rect& element_bounds) {
WidthCalculationResults result;
result.right_growth_start =
std::max(leftmost_available_x,
std::min(rightmost_available_x, element_bounds.x()));
result.left_growth_end =
std::max(leftmost_available_x,
std::min(rightmost_available_x, element_bounds.right()));
result.right_available = rightmost_available_x - result.right_growth_start;
result.left_available = result.left_growth_end - leftmost_available_x;
result.max_popup_width =
std::max(result.right_available, result.left_available);
return result;
}
// Sets the |x| and |width| components of |popup_bounds| as the x-coordinate of
// the starting point and the width of the popup, taking into account the
// direction it's supposed to grow (either to the left or to the right).
// Components |y| and |height| of |popup_bounds| are not changed.
void CalculatePopupXAndWidth(int leftmost_available_x,
int rightmost_available_x,
int popup_required_width,
const gfx::Rect& element_bounds,
bool is_rtl,
gfx::Rect* popup_bounds) {
WidthCalculationResults result = CalculateWidthValues(
leftmost_available_x, rightmost_available_x, element_bounds);
int popup_width = std::min(popup_required_width, result.max_popup_width);
// Prefer to grow towards the end (right for LTR, left for RTL). But if there
// is not enough space available in the desired direction and more space in
// the other direction, reverse it.
bool grow_left = false;
if (is_rtl) {
grow_left = result.left_available >= popup_width ||
result.left_available >= result.right_available;
} else {
grow_left = result.right_available < popup_width &&
result.right_available < result.left_available;
}
popup_bounds->set_width(popup_width);
popup_bounds->set_x(grow_left ? result.left_growth_end - popup_width
: result.right_growth_start);
}
// Sets the |y| and |height| components of |popup_bounds| as the y-coordinate of
// the starting point and the height of the popup, taking into account the
// direction it's supposed to grow (either up or down). Components |x| and
// |width| of |popup_bounds| are not changed.
void CalculatePopupYAndHeight(int topmost_available_y,
int bottommost_available_y,
int popup_required_height,
const gfx::Rect& element_bounds,
gfx::Rect* popup_bounds) {
// Calculate the start coordinates for the popup if it is growing down or
// the end position if it is growing up, capped to screen space.
int top_growth_end =
std::max(topmost_available_y,
std::min(bottommost_available_y, element_bounds.y()));
int bottom_growth_start =
std::max(topmost_available_y,
std::min(bottommost_available_y, element_bounds.bottom()));
int top_available = top_growth_end - topmost_available_y;
int bottom_available = bottommost_available_y - bottom_growth_start;
if (bottom_available >= popup_required_height ||
bottom_available >= top_available) {
// The popup can appear below the field.
popup_bounds->set_height(std::min(bottom_available, popup_required_height));
popup_bounds->set_y(bottom_growth_start);
} else {
// The popup must appear above the field.
popup_bounds->set_height(std::min(top_available, popup_required_height));
popup_bounds->set_y(top_growth_end - popup_bounds->height());
}
}
} // namespace
void PopupViewCommon::CalculatePopupHorizontalBounds(
int desired_width,
const gfx::Rect& element_bounds,
gfx::NativeView container_view,
bool is_rtl,
gfx::Rect* popup_bounds) {
const gfx::Rect bounds = GetWindowBounds(container_view);
CalculatePopupXAndWidth(/*leftmost_available_x=*/bounds.x(),
/*rightmost_available_x=*/bounds.x() + bounds.width(),
desired_width, element_bounds, is_rtl, popup_bounds);
}
void PopupViewCommon::CalculatePopupVerticalBounds(
int desired_height,
const gfx::Rect& element_bounds,
gfx::NativeView container_view,
gfx::Rect* popup_bounds) {
const gfx::Rect window_bounds = GetWindowBounds(container_view);
CalculatePopupYAndHeight(
/*topmost_available_y=*/window_bounds.y(),
/*bottommost_available_y=*/window_bounds.y() + window_bounds.height(),
desired_height, element_bounds, popup_bounds);
}
gfx::Rect PopupViewCommon::CalculatePopupBounds(int desired_width,
int desired_height,
const gfx::Rect& element_bounds,
gfx::NativeView container_view,
bool is_rtl) {
const gfx::Rect window_bounds = GetWindowBounds(container_view);
gfx::Rect popup_bounds;
CalculatePopupXAndWidth(
/*leftmost_available_x=*/window_bounds.x(),
/*rightmost_available_x=*/window_bounds.x() + window_bounds.width(),
desired_width, element_bounds, is_rtl, &popup_bounds);
CalculatePopupYAndHeight(
/*topmost_available_y=*/window_bounds.y(),
/*bottommost_available_y=*/window_bounds.y() + window_bounds.height(),
desired_height, element_bounds, &popup_bounds);
return popup_bounds;
}
gfx::Rect PopupViewCommon::GetWindowBounds(gfx::NativeView container_view) {
// The call to FindBrowserWithWindow will fail on Android, so we use
// platform-specific calls.
#if defined(OS_ANDROID)
return container_view->GetWindowAndroid()->bounds();
#else
views::Widget* widget =
views::Widget::GetTopLevelWidgetForNativeView(container_view);
if (widget)
return widget->GetWindowBoundsInScreen();
// If the widget is null, try to get these bounds from a browser window. This
// is common on Mac when the window is drawn using Cocoa.
gfx::NativeWindow window = platform_util::GetTopLevel(container_view);
Browser* browser = chrome::FindBrowserWithWindow(window);
if (browser)
return browser->window()->GetBounds();
// If the browser is null, simply return an empty rect. The most common reason
// to end up here is that the NativeView has been destroyed externally, which
// can happen at any time. This happens fairly commonly on Windows (e.g., at
// shutdown) in particular.
return gfx::Rect();
#endif
}
int PopupViewCommon::CalculateMaxWidth(const gfx::Rect& element_bounds,
gfx::NativeView container_view) {
const gfx::Rect window_bounds = GetWindowBounds(container_view);
int leftmost_available_x = window_bounds.x();
int rightmost_available_x = window_bounds.x() + window_bounds.width();
return CalculateWidthValues(leftmost_available_x, rightmost_available_x,
element_bounds)
.max_popup_width;
}
} // namespace autofill