blob: 7880b240c9f702c98c262b662f8a9c694330d375 [file] [log] [blame]
// Copyright 2011 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/infobars/infobar_container_view.h"
#include <numeric>
#include "cc/paint/paint_flags.h"
#include "cc/paint/paint_shader.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/infobars/infobar_view.h"
#include "chrome/grit/generated_resources.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/skia_paint_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/cascading_property.h"
#include "ui/views/controls/focus_ring.h"
namespace {
class ContentShadow : public views::View {
METADATA_HEADER(ContentShadow, views::View)
public:
ContentShadow();
protected:
// views::View:
gfx::Size CalculatePreferredSize(
const views::SizeBounds& available_size) const override;
void OnPaint(gfx::Canvas* canvas) override;
void OnThemeChanged() override;
};
ContentShadow::ContentShadow() {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
}
gfx::Size ContentShadow::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
return gfx::Size(0, views::BubbleBorder::GetBorderAndShadowInsets().height());
}
void ContentShadow::OnPaint(gfx::Canvas* canvas) {
// Outdent the sides to make the shadow appear uniform in the corners.
gfx::RectF container_bounds(parent()->GetLocalBounds());
View::ConvertRectToTarget(parent(), this, &container_bounds);
container_bounds.Inset(
gfx::InsetsF::VH(0, -views::BubbleBorder::kShadowBlur));
views::BubbleBorder::DrawBorderAndShadow(gfx::RectFToSkRect(container_bounds),
canvas, GetColorProvider());
}
void ContentShadow::OnThemeChanged() {
views::View::OnThemeChanged();
// ContentShadow is a layer, it needs to be manually marked for redraw when
// the theme changes.
SchedulePaint();
}
BEGIN_METADATA(ContentShadow)
END_METADATA
} // namespace
constexpr int kSeparatorHeightDip = 1;
InfoBarContainerView::InfoBarContainerView(Delegate* delegate)
: infobars::InfoBarContainer(delegate),
content_shadow_(new ContentShadow()) {
SetID(VIEW_ID_INFO_BAR_CONTAINER);
AddChildViewRaw(content_shadow_.get());
views::SetCascadingColorProviderColor(this, views::kCascadingBackgroundColor,
kColorToolbar);
SetBackground(
views::CreateSolidBackground(kColorInfoBarContentAreaSeparator));
GetViewAccessibility().SetRole(ax::mojom::Role::kGroup);
GetViewAccessibility().SetName(
l10n_util::GetStringUTF8(IDS_ACCNAME_INFOBAR_CONTAINER));
}
InfoBarContainerView::~InfoBarContainerView() {
RemoveAllInfoBarsForDestruction();
}
bool InfoBarContainerView::IsEmpty() const {
// NOTE: Can't check if the size IsEmpty() since it's always 0-width.
return GetPreferredSize().height() == 0;
}
void InfoBarContainerView::Layout(PassKey) {
const auto set_bounds = [this](int top, View* child) {
const int height = static_cast<InfoBarView*>(child)->computed_height();
// Do not add separator dip if it's the first infobar. The first infobar
// should be flush with the top of InfoBarContainerView.
int add_separator_height =
(child == children().front()) ? 0 : kSeparatorHeightDip;
child->SetBounds(0, top + add_separator_height, width(), height);
return child->bounds().bottom();
};
DCHECK_EQ(content_shadow_, children().back());
const int top = std::accumulate(children().begin(),
std::prev(children().end()), 0, set_bounds);
// The shadow is positioned flush with the bottom infobar, with the separator
// there drawn by the shadow code (so we don't have to extend our bounds out
// to be able to draw it; see comments in CalculatePreferredSize() on why the
// shadow is drawn outside the container bounds).
content_shadow_->SetBounds(0, top, width(),
content_shadow_->GetPreferredSize().height());
}
gfx::Size InfoBarContainerView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
const auto enlarge_size = [this](const gfx::Size& size, const View* child) {
const gfx::Size child_size = child->GetPreferredSize();
int add_separator_height =
(child == children().front()) ? 0 : kSeparatorHeightDip;
return gfx::Size(
std::max(size.width(), child_size.width()),
size.height() + child_size.height() + add_separator_height);
};
// Don't reserve space for the bottom shadow here. Because the shadow paints
// to its own layer and this class doesn't, it can paint outside the size
// computed here. Not including the shadow bounds means the browser will
// automatically lay out web content beginning below the bottom infobar
// (instead of below the shadow), and clicks in the shadow region will go to
// the web content instead of the infobars; both of these effects are
// desirable. On the other hand, it also means the browser doesn't know the
// shadow is there and could lay out something atop it or size the window too
// small for it; but these are unlikely.
DCHECK_EQ(content_shadow_, children().back());
return std::accumulate(children().cbegin(), std::prev(children().cend()),
gfx::Size(), enlarge_size);
}
void InfoBarContainerView::PlatformSpecificAddInfoBar(
infobars::InfoBar* infobar,
size_t position) {
AddChildViewAt(static_cast<InfoBarView*>(infobar),
static_cast<int>(position));
}
void InfoBarContainerView::PlatformSpecificRemoveInfoBar(
infobars::InfoBar* infobar) {
RemoveChildView(static_cast<InfoBarView*>(infobar));
}
BEGIN_METADATA(InfoBarContainerView)
END_METADATA