blob: 5aaae78c476f293623a48e639ad5637d30cf1215 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h"
#include <memory>
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
#include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_controller.h"
#include "chrome/browser/ui/views/toolbar/back_forward_button.h"
#include "chrome/browser/ui/views/toolbar/reload_button.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_content_settings_container.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_menu_button.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_navigation_button_container.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_origin_text.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/window_controls_overlay_toggle_button.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "ui/base/hit_test.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/view_utils.h"
#include "ui/views/window/hit_test_utils.h"
WebAppFrameToolbarView::WebAppFrameToolbarView(views::Widget* widget,
BrowserView* browser_view)
: browser_view_(browser_view) {
DCHECK(browser_view_);
DCHECK(web_app::AppBrowserController::IsWebApp(browser_view_->browser()));
SetID(VIEW_ID_WEB_APP_FRAME_TOOLBAR);
SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
{
// TODO(tluk) fix the need for both LayoutInContainer() and a layout
// manager for frame layout.
views::FlexLayout* layout =
SetLayoutManager(std::make_unique<views::FlexLayout>());
layout->SetOrientation(views::LayoutOrientation::kHorizontal);
layout->SetMainAxisAlignment(views::LayoutAlignment::kEnd);
layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
}
const auto* app_controller = browser_view_->browser()->app_controller();
if (app_controller->HasMinimalUiButtons()) {
left_container_ =
AddChildView(std::make_unique<WebAppNavigationButtonContainer>(
browser_view_, /*toolbar_button_provider=*/this));
left_container_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(
views::LayoutOrientation::kHorizontal,
views::MinimumFlexSizeRule::kScaleToMinimumSnapToZero)
.WithOrder(2));
}
center_container_ = AddChildView(std::make_unique<views::View>());
center_container_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::LayoutOrientation::kHorizontal,
views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded)
.WithOrder(3));
right_container_ =
AddChildView(std::make_unique<WebAppToolbarButtonContainer>(
widget, browser_view, this));
right_container_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(right_container_->GetFlexRule()).WithOrder(1));
UpdateStatusIconsVisibility();
DCHECK(
!browser_view_->toolbar_button_provider() ||
views::IsViewClass<WebAppFrameToolbarView>(
browser_view_->toolbar_button_provider()->GetAsAccessiblePaneView()))
<< "This should be the first ToolbarButtorProvider or a replacement for "
"an existing instance of this class during a window frame refresh.";
browser_view_->SetToolbarButtonProvider(this);
if (browser_view_->IsWindowControlsOverlayEnabled())
OnWindowControlsOverlayEnabledChanged();
if (browser_view_->AppUsesBorderlessMode())
UpdateBorderlessModeEnabled();
}
WebAppFrameToolbarView::~WebAppFrameToolbarView() = default;
void WebAppFrameToolbarView::UpdateStatusIconsVisibility() {
right_container_->UpdateStatusIconsVisibility();
}
void WebAppFrameToolbarView::UpdateCaptionColors() {
// We want to behave differently if this is an update to the color (as opposed
// to the first time it's being set). Specifically, updates should pulse the
// origin into view.
bool color_previously_set = active_background_color_.has_value();
if (color_previously_set) {
SkColor old_active_background_color = *active_background_color_;
SkColor old_active_foreground_color = *active_foreground_color_;
SkColor old_inactive_background_color = *inactive_background_color_;
SkColor old_inactive_foreground_color = *inactive_foreground_color_;
UpdateCachedColors();
if (old_active_background_color == active_background_color_ &&
old_active_foreground_color == active_foreground_color_ &&
old_inactive_background_color == inactive_background_color_ &&
old_inactive_foreground_color == inactive_foreground_color_) {
return;
}
} else {
UpdateCachedColors();
}
UpdateChildrenColor(/*color_changed=*/color_previously_set);
}
void WebAppFrameToolbarView::SetPaintAsActive(bool active) {
if (paint_as_active_ == active)
return;
paint_as_active_ = active;
UpdateChildrenColor(/*color_changed=*/false);
OnPropertyChanged(&paint_as_active_, views::kPropertyEffectsNone);
}
bool WebAppFrameToolbarView::GetPaintAsActive() const {
return paint_as_active_;
}
std::pair<int, int> WebAppFrameToolbarView::LayoutInContainer(
int leading_x,
int trailing_x,
int y,
int available_height) {
DCHECK(!browser_view_->IsWindowControlsOverlayEnabled());
SetVisible(available_height > 0);
if (available_height == 0) {
SetSize(gfx::Size());
return std::pair<int, int>(0, 0);
}
gfx::Size preferred_size = GetPreferredSize();
const int width = std::max(trailing_x - leading_x, 0);
const int height = preferred_size.height();
DCHECK_LE(height, available_height);
SetBounds(leading_x, y, width, available_height);
Layout();
if (!center_container_->GetVisible())
return std::pair<int, int>(0, 0);
// Bounds for remaining inner space, in parent container coordinates.
gfx::Rect center_bounds = center_container_->bounds();
DCHECK(center_bounds.x() == 0 || left_container_);
center_bounds.Offset(bounds().OffsetFromOrigin());
return std::pair<int, int>(center_bounds.x(), center_bounds.right());
}
void WebAppFrameToolbarView::LayoutForWindowControlsOverlay(
gfx::Rect available_space) {
DCHECK(!left_container_);
// The center_container_ might have been laid out by the frame view such that
// it interferes with hit testing in the ToolbarButtonContainer. Ensure that
// its bounds are cleared when laying out WCO.
center_container_->SetBounds(0, 0, 0, 0);
const int width = std::min(available_space.width(),
right_container_->GetPreferredSize().width());
const int x = available_space.right() - width;
SetBounds(x, available_space.y(), width, available_space.height());
}
ExtensionsToolbarContainer*
WebAppFrameToolbarView::GetExtensionsToolbarContainer() {
return right_container_->extensions_container();
}
gfx::Size WebAppFrameToolbarView::GetToolbarButtonSize() const {
const int size = GetLayoutConstant(WEB_APP_MENU_BUTTON_SIZE);
return gfx::Size(size, size);
}
views::View* WebAppFrameToolbarView::GetDefaultExtensionDialogAnchorView() {
return right_container_->extensions_container()->GetExtensionsButton();
}
PageActionIconView* WebAppFrameToolbarView::GetPageActionIconView(
PageActionIconType type) {
return right_container_->page_action_icon_controller()->GetIconView(type);
}
AppMenuButton* WebAppFrameToolbarView::GetAppMenuButton() {
return right_container_->web_app_menu_button();
}
gfx::Rect WebAppFrameToolbarView::GetFindBarBoundingBox(int contents_bottom) {
if (!IsDrawn())
return gfx::Rect();
// If LTR find bar will be right aligned so align to right edge of app menu
// button. Otherwise it will be left aligned so align to the left edge of the
// app menu button.
views::View* anchor_view = GetAnchorView(PageActionIconType::kFind);
gfx::Rect anchor_bounds =
anchor_view->ConvertRectToWidget(anchor_view->GetLocalBounds());
int x_pos = 0;
int width = anchor_bounds.right();
if (base::i18n::IsRTL()) {
x_pos = anchor_bounds.x();
width = GetWidget()->GetRootView()->width() - anchor_bounds.x();
}
return gfx::Rect(x_pos, anchor_bounds.bottom(), width,
contents_bottom - anchor_bounds.bottom());
}
void WebAppFrameToolbarView::FocusToolbar() {
SetPaneFocus(nullptr);
}
views::AccessiblePaneView* WebAppFrameToolbarView::GetAsAccessiblePaneView() {
return this;
}
views::View* WebAppFrameToolbarView::GetAnchorView(PageActionIconType type) {
views::View* anchor = GetAppMenuButton();
return anchor ? anchor : this;
}
void WebAppFrameToolbarView::ZoomChangedForActiveTab(bool can_show_bubble) {
right_container_->page_action_icon_controller()->ZoomChangedForActiveTab(
can_show_bubble);
}
SidePanelToolbarButton* WebAppFrameToolbarView::GetSidePanelButton() {
return nullptr;
}
AvatarToolbarButton* WebAppFrameToolbarView::GetAvatarToolbarButton() {
return nullptr;
}
ToolbarButton* WebAppFrameToolbarView::GetBackButton() {
return left_container_ ? left_container_->back_button() : nullptr;
}
ReloadButton* WebAppFrameToolbarView::GetReloadButton() {
return left_container_ ? left_container_->reload_button() : nullptr;
}
IntentChipButton* WebAppFrameToolbarView::GetIntentChipButton() {
return nullptr;
}
DownloadToolbarButtonView* WebAppFrameToolbarView::GetDownloadButton() {
return right_container_ ? right_container_->download_button() : nullptr;
}
bool WebAppFrameToolbarView::DoesIntersectRect(const View* target,
const gfx::Rect& rect) const {
DCHECK_EQ(target, this);
if (!views::ViewTargeterDelegate::DoesIntersectRect(this, rect))
return false;
// If the rect is inside the bounds of the center_container, do not claim it.
// There is no actionable content in the center_container, and it overlaps
// tabs in tabbed PWA windows.
gfx::RectF rect_in_center_container_coords_f(rect);
View::ConvertRectToTarget(this, center_container_,
&rect_in_center_container_coords_f);
gfx::Rect rect_in_client_view_coords =
gfx::ToEnclosingRect(rect_in_center_container_coords_f);
return !center_container_->HitTestRect(rect_in_client_view_coords);
}
void WebAppFrameToolbarView::OnWindowControlsOverlayEnabledChanged() {
if (browser_view_->IsWindowControlsOverlayEnabled()) {
// The color is not set until the view is added to a widget.
if (active_background_color_) {
SetBackground(views::CreateSolidBackground(
paint_as_active_ ? *active_background_color_
: *inactive_background_color_));
}
// BrowserView paints to a layer, so this view must do the same to ensure
// that it paints on top of the BrowserView.
SetPaintToLayer();
views::SetHitTestComponent(this, static_cast<int>(HTCAPTION));
} else {
SetBackground(nullptr);
DestroyLayer();
views::SetHitTestComponent(this, static_cast<int>(HTNOWHERE));
}
right_container_->extensions_container()->WindowControlsOverlayEnabledChanged(
browser_view_->IsWindowControlsOverlayEnabled());
}
void WebAppFrameToolbarView::UpdateBorderlessModeEnabled() {
bool is_borderless_mode_enabled = browser_view_->IsBorderlessModeEnabled();
// The toolbar and menu button are hidden and not set to nullptrs,
// because there are many features that depend on the toolbar and would not
// work without it. For example all the shortcut commands (e.g. Ctrl+F, zoom)
// rely on the menu button and toolbar so when these are hidden, the shortcuts
// will still work.
SetVisible(!is_borderless_mode_enabled);
GetAppMenuButton()->SetVisible(!is_borderless_mode_enabled);
}
void WebAppFrameToolbarView::SetWindowControlsOverlayToggleVisible(
bool visible) {
right_container_->window_controls_overlay_toggle_button()->SetVisible(
visible);
}
PageActionIconController*
WebAppFrameToolbarView::GetPageActionIconControllerForTesting() {
return right_container_->page_action_icon_controller();
}
void WebAppFrameToolbarView::ChildPreferredSizeChanged(views::View* child) {
PreferredSizeChanged();
}
void WebAppFrameToolbarView::OnThemeChanged() {
views::AccessiblePaneView::OnThemeChanged();
UpdateCaptionColors();
}
views::View* WebAppFrameToolbarView::GetContentSettingContainerForTesting() {
return right_container_->content_settings_container();
}
const std::vector<ContentSettingImageView*>&
WebAppFrameToolbarView::GetContentSettingViewsForTesting() const {
return right_container_->content_settings_container()
->get_content_setting_views();
}
void WebAppFrameToolbarView::UpdateCachedColors() {
const BrowserNonClientFrameView* frame_view =
browser_view_->frame()->GetFrameView();
DCHECK(frame_view);
active_background_color_ =
frame_view->GetFrameColor(BrowserFrameActiveState::kActive);
active_foreground_color_ =
frame_view->GetCaptionColor(BrowserFrameActiveState::kActive);
inactive_background_color_ =
frame_view->GetFrameColor(BrowserFrameActiveState::kInactive);
inactive_foreground_color_ =
frame_view->GetCaptionColor(BrowserFrameActiveState::kInactive);
}
void WebAppFrameToolbarView::UpdateChildrenColor(bool color_changed) {
const SkColor foreground_color = paint_as_active_
? *active_foreground_color_
: *inactive_foreground_color_;
if (left_container_)
left_container_->SetIconColor(foreground_color);
const SkColor background_color = paint_as_active_
? *active_background_color_
: *inactive_background_color_;
right_container_->SetColors(foreground_color, background_color,
color_changed);
if (browser_view_->IsWindowControlsOverlayEnabled())
SetBackground(views::CreateSolidBackground(background_color));
}
BEGIN_METADATA(WebAppFrameToolbarView, views::AccessiblePaneView)
ADD_PROPERTY_METADATA(bool, PaintAsActive)
END_METADATA