blob: 3a2acc36a393b04ce3ab369687ab415ed573f2b4 [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 "ash/system/tray/tray_popup_utils.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "ash/ash_constants.h"
#include "ash/ash_view_ids.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller.h"
#include "ash/shell.h"
#include "ash/system/tray/hover_highlight_view.h"
#include "ash/system/tray/size_range_layout.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_popup_item_style.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/animation/square_ink_drop_ripple.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/button/toggle_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/slider.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/painter.h"
namespace ash {
namespace {
// Creates a layout manager that positions Views vertically. The Views will be
// stretched horizontally and centered vertically.
std::unique_ptr<views::LayoutManager> CreateDefaultCenterLayoutManager() {
// TODO(bruthig): Use constants instead of magic numbers.
auto box_layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical,
gfx::Insets(8, kTrayPopupLabelHorizontalPadding));
box_layout->set_main_axis_alignment(
views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
box_layout->set_cross_axis_alignment(
views::BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH);
return std::move(box_layout);
}
// Creates a layout manager that positions Views horizontally. The Views will be
// centered along the horizontal and vertical axis.
std::unique_ptr<views::LayoutManager> CreateDefaultEndsLayoutManager() {
auto box_layout =
std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal);
box_layout->set_main_axis_alignment(
views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
box_layout->set_cross_axis_alignment(
views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
return std::move(box_layout);
}
std::unique_ptr<views::LayoutManager> CreateDefaultLayoutManager(
TriView::Container container) {
switch (container) {
case TriView::Container::START:
case TriView::Container::END:
return CreateDefaultEndsLayoutManager();
case TriView::Container::CENTER:
return CreateDefaultCenterLayoutManager();
}
// Required by some compilers.
NOTREACHED();
return nullptr;
}
// Configures the default size and flex value for the specified |container|
// of the given |tri_view|. Used by CreateDefaultRowView().
void ConfigureDefaultSizeAndFlex(TriView* tri_view,
TriView::Container container) {
int min_width = 0;
switch (container) {
case TriView::Container::START:
min_width = kTrayPopupItemMinStartWidth;
break;
case TriView::Container::CENTER:
tri_view->SetFlexForContainer(TriView::Container::CENTER, 1.f);
break;
case TriView::Container::END:
min_width = kTrayPopupItemMinEndWidth;
break;
}
tri_view->SetMinSize(container,
gfx::Size(min_width, kTrayPopupItemMinHeight));
constexpr int kTrayPopupItemMaxHeight = 144;
tri_view->SetMaxSize(
container,
gfx::Size(SizeRangeLayout::kAbsoluteMaxSize, kTrayPopupItemMaxHeight));
}
class BorderlessLabelButton : public views::LabelButton {
public:
BorderlessLabelButton(views::ButtonListener* listener,
const base::string16& text)
: LabelButton(listener, text) {
const int kHorizontalPadding = 20;
SetBorder(views::CreateEmptyBorder(gfx::Insets(0, kHorizontalPadding)));
TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::BUTTON);
style.SetupLabel(label());
SetHorizontalAlignment(gfx::ALIGN_CENTER);
SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
TrayPopupUtils::ConfigureTrayPopupButton(this);
}
~BorderlessLabelButton() override = default;
// views::LabelButton:
int GetHeightForWidth(int width) const override { return kMenuButtonSize; }
private:
// TODO(estade,bruthig): there's a lot in common here with ActionableView.
// Find a way to share. See related TODO on InkDropHostView::SetInkDropMode().
std::unique_ptr<views::InkDrop> CreateInkDrop() override {
return TrayPopupUtils::CreateInkDrop(TrayPopupInkDropStyle::INSET_BOUNDS,
this);
}
std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override {
return TrayPopupUtils::CreateInkDropRipple(
TrayPopupInkDropStyle::INSET_BOUNDS, this,
GetInkDropCenterBasedOnLastEvent());
}
std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
const override {
return TrayPopupUtils::CreateInkDropHighlight(
TrayPopupInkDropStyle::INSET_BOUNDS, this);
}
std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override {
return TrayPopupUtils::CreateInkDropMask(
TrayPopupInkDropStyle::INSET_BOUNDS, this);
}
DISALLOW_COPY_AND_ASSIGN(BorderlessLabelButton);
};
} // namespace
TriView* TrayPopupUtils::CreateDefaultRowView() {
TriView* tri_view = CreateMultiTargetRowView();
tri_view->SetContainerLayout(
TriView::Container::START,
CreateDefaultLayoutManager(TriView::Container::START));
tri_view->SetContainerLayout(
TriView::Container::CENTER,
CreateDefaultLayoutManager(TriView::Container::CENTER));
tri_view->SetContainerLayout(
TriView::Container::END,
CreateDefaultLayoutManager(TriView::Container::END));
return tri_view;
}
TriView* TrayPopupUtils::CreateSubHeaderRowView(bool start_visible) {
TriView* tri_view = CreateDefaultRowView();
if (!start_visible) {
tri_view->SetInsets(gfx::Insets(
0, kTrayPopupPaddingHorizontal - kTrayPopupLabelHorizontalPadding, 0,
0));
tri_view->SetContainerVisible(TriView::Container::START, false);
}
return tri_view;
}
TriView* TrayPopupUtils::CreateMultiTargetRowView() {
TriView* tri_view = new TriView(0 /* padding_between_items */);
tri_view->SetInsets(gfx::Insets(0, kMenuExtraMarginFromLeftEdge, 0, 0));
ConfigureDefaultSizeAndFlex(tri_view, TriView::Container::START);
ConfigureDefaultSizeAndFlex(tri_view, TriView::Container::CENTER);
ConfigureDefaultSizeAndFlex(tri_view, TriView::Container::END);
tri_view->SetContainerLayout(TriView::Container::START,
std::make_unique<views::FillLayout>());
tri_view->SetContainerLayout(TriView::Container::CENTER,
std::make_unique<views::FillLayout>());
tri_view->SetContainerLayout(TriView::Container::END,
std::make_unique<views::FillLayout>());
return tri_view;
}
views::Label* TrayPopupUtils::CreateDefaultLabel() {
views::Label* label = new views::Label();
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
// Frequently the label will paint to a layer that's non-opaque, so subpixel
// rendering won't work unless we explicitly set a background. See
// crbug.com/686363
label->SetBackground(views::CreateThemedSolidBackground(
label, ui::NativeTheme::kColorId_BubbleBackground));
return label;
}
views::ImageView* TrayPopupUtils::CreateMainImageView() {
auto* image = new views::ImageView;
image->SetPreferredSize(
gfx::Size(kTrayPopupItemMinStartWidth, kTrayPopupItemMinHeight));
return image;
}
views::ImageView* TrayPopupUtils::CreateMoreImageView() {
auto* image = new views::ImageView;
image->SetPreferredSize(gfx::Size(gfx::Size(kMenuIconSize, kMenuIconSize)));
image->EnableCanvasFlippingForRTLUI(true);
image->SetImage(
gfx::CreateVectorIcon(kSystemMenuArrowRightIcon, kMenuIconColor));
return image;
}
views::Slider* TrayPopupUtils::CreateSlider(views::SliderListener* listener) {
views::Slider* slider = new views::Slider(listener);
slider->SetBorder(views::CreateEmptyBorder(
gfx::Insets(0, kTrayPopupSliderHorizontalPadding)));
return slider;
}
views::ToggleButton* TrayPopupUtils::CreateToggleButton(
views::ButtonListener* listener,
int accessible_name_id) {
views::ToggleButton* toggle = new views::ToggleButton(listener);
const gfx::Size toggle_size(toggle->GetPreferredSize());
const int vertical_padding = (kMenuButtonSize - toggle_size.height()) / 2;
const int horizontal_padding =
(kTrayToggleButtonWidth - toggle_size.width()) / 2;
toggle->SetBorder(views::CreateEmptyBorder(
gfx::Insets(vertical_padding, horizontal_padding)));
toggle->SetAccessibleName(l10n_util::GetStringUTF16(accessible_name_id));
return toggle;
}
std::unique_ptr<views::Painter> TrayPopupUtils::CreateFocusPainter() {
return views::Painter::CreateSolidFocusPainter(
kFocusBorderColor, kFocusBorderThickness, gfx::InsetsF());
}
void TrayPopupUtils::ConfigureTrayPopupButton(views::Button* button) {
button->SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
button->SetFocusForPlatform();
button->SetInkDropMode(views::InkDropHostView::InkDropMode::ON);
button->set_has_ink_drop_action_on_click(true);
button->set_ink_drop_base_color(kTrayPopupInkDropBaseColor);
button->set_ink_drop_visible_opacity(kTrayPopupInkDropRippleOpacity);
}
void TrayPopupUtils::ConfigureAsStickyHeader(views::View* view) {
view->set_id(VIEW_ID_STICKY_HEADER);
view->SetBackground(views::CreateThemedSolidBackground(
view, ui::NativeTheme::kColorId_BubbleBackground));
view->SetBorder(
views::CreateEmptyBorder(gfx::Insets(kMenuSeparatorVerticalPadding, 0)));
view->SetPaintToLayer();
view->layer()->SetFillsBoundsOpaquely(false);
}
void TrayPopupUtils::ShowStickyHeaderSeparator(views::View* view,
bool show_separator) {
if (show_separator) {
view->SetBorder(views::CreatePaddedBorder(
views::CreateSolidSidedBorder(0, 0, kSeparatorWidth, 0,
kMenuSeparatorColor),
gfx::Insets(kMenuSeparatorVerticalPadding, 0,
kMenuSeparatorVerticalPadding - kSeparatorWidth, 0)));
} else {
view->SetBorder(views::CreateEmptyBorder(
gfx::Insets(kMenuSeparatorVerticalPadding, 0)));
}
view->SchedulePaint();
}
void TrayPopupUtils::ConfigureContainer(TriView::Container container,
views::View* container_view) {
container_view->SetLayoutManager(CreateDefaultLayoutManager(container));
}
views::LabelButton* TrayPopupUtils::CreateTrayPopupBorderlessButton(
views::ButtonListener* listener,
const base::string16& text) {
return new BorderlessLabelButton(listener, text);
}
views::LabelButton* TrayPopupUtils::CreateTrayPopupButton(
views::ButtonListener* listener,
const base::string16& text) {
auto* button = views::MdTextButton::Create(listener, text);
button->SetProminent(true);
return button;
}
views::Separator* TrayPopupUtils::CreateVerticalSeparator() {
views::Separator* separator = new views::Separator();
separator->SetPreferredHeight(24);
separator->SetColor(kMenuSeparatorColor);
return separator;
}
std::unique_ptr<views::InkDrop> TrayPopupUtils::CreateInkDrop(
TrayPopupInkDropStyle ink_drop_style,
views::InkDropHostView* host) {
std::unique_ptr<views::InkDropImpl> ink_drop =
std::make_unique<views::InkDropImpl>(host, host->size());
ink_drop->SetAutoHighlightMode(
views::InkDropImpl::AutoHighlightMode::SHOW_ON_RIPPLE);
ink_drop->SetShowHighlightOnHover(false);
return std::move(ink_drop);
}
std::unique_ptr<views::InkDropRipple> TrayPopupUtils::CreateInkDropRipple(
TrayPopupInkDropStyle ink_drop_style,
const views::View* host,
const gfx::Point& center_point,
SkColor color) {
return std::make_unique<views::FloodFillInkDropRipple>(
host->size(), TrayPopupUtils::GetInkDropInsets(ink_drop_style),
center_point, color, kTrayPopupInkDropRippleOpacity);
}
std::unique_ptr<views::InkDropHighlight> TrayPopupUtils::CreateInkDropHighlight(
TrayPopupInkDropStyle ink_drop_style,
const views::View* host,
SkColor color) {
const gfx::Rect bounds =
TrayPopupUtils::GetInkDropBounds(ink_drop_style, host);
std::unique_ptr<views::InkDropHighlight> highlight(
new views::InkDropHighlight(bounds.size(), 0,
gfx::PointF(bounds.CenterPoint()), color));
highlight->set_visible_opacity(kTrayPopupInkDropHighlightOpacity);
return highlight;
}
std::unique_ptr<views::InkDropMask> TrayPopupUtils::CreateInkDropMask(
TrayPopupInkDropStyle ink_drop_style,
const views::View* host) {
if (ink_drop_style == TrayPopupInkDropStyle::FILL_BOUNDS)
return nullptr;
const gfx::Size layer_size = host->size();
switch (ink_drop_style) {
case TrayPopupInkDropStyle::HOST_CENTERED: {
const gfx::Rect mask_bounds =
GetInkDropBounds(TrayPopupInkDropStyle::HOST_CENTERED, host);
const int radius =
std::min(mask_bounds.width(), mask_bounds.height()) / 2;
return std::make_unique<views::CircleInkDropMask>(
layer_size, mask_bounds.CenterPoint(), radius);
}
case TrayPopupInkDropStyle::INSET_BOUNDS: {
const gfx::Insets mask_insets =
GetInkDropInsets(TrayPopupInkDropStyle::INSET_BOUNDS);
return std::make_unique<views::RoundRectInkDropMask>(
layer_size, mask_insets, kTrayPopupInkDropCornerRadius);
}
case TrayPopupInkDropStyle::FILL_BOUNDS:
// Handled by quick return above.
break;
}
// Required by some compilers.
NOTREACHED();
return nullptr;
}
gfx::Insets TrayPopupUtils::GetInkDropInsets(
TrayPopupInkDropStyle ink_drop_style) {
gfx::Insets insets;
if (ink_drop_style == TrayPopupInkDropStyle::HOST_CENTERED ||
ink_drop_style == TrayPopupInkDropStyle::INSET_BOUNDS) {
insets.Set(kTrayPopupInkDropInset, kTrayPopupInkDropInset,
kTrayPopupInkDropInset, kTrayPopupInkDropInset);
}
return insets;
}
gfx::Rect TrayPopupUtils::GetInkDropBounds(TrayPopupInkDropStyle ink_drop_style,
const views::View* host) {
gfx::Rect bounds = host->GetLocalBounds();
bounds.Inset(GetInkDropInsets(ink_drop_style));
return bounds;
}
views::Separator* TrayPopupUtils::CreateListItemSeparator(bool left_inset) {
views::Separator* separator = new views::Separator();
separator->SetColor(kMenuSeparatorColor);
separator->SetBorder(views::CreateEmptyBorder(
kMenuSeparatorVerticalPadding - views::Separator::kThickness,
left_inset
? kMenuExtraMarginFromLeftEdge + kMenuButtonSize +
kTrayPopupLabelHorizontalPadding
: 0,
kMenuSeparatorVerticalPadding, 0));
return separator;
}
views::Separator* TrayPopupUtils::CreateListSubHeaderSeparator() {
views::Separator* separator = new views::Separator();
separator->SetColor(kMenuSeparatorColor);
separator->SetBorder(views::CreateEmptyBorder(
kMenuSeparatorVerticalPadding - views::Separator::kThickness, 0, 0, 0));
return separator;
}
bool TrayPopupUtils::CanOpenWebUISettings() {
return Shell::Get()->session_controller()->ShouldEnableSettings();
}
void TrayPopupUtils::InitializeAsCheckableRow(HoverHighlightView* container,
bool checked) {
gfx::ImageSkia check_mark =
CreateVectorIcon(kCheckCircleIcon, gfx::kGoogleGreen700);
container->AddRightIcon(check_mark, check_mark.width());
UpdateCheckMarkVisibility(container, checked);
}
void TrayPopupUtils::UpdateCheckMarkVisibility(HoverHighlightView* container,
bool visible) {
container->SetRightViewVisible(visible);
container->SetAccessiblityState(
visible ? HoverHighlightView::AccessibilityState::CHECKED_CHECKBOX
: HoverHighlightView::AccessibilityState::UNCHECKED_CHECKBOX);
}
} // namespace ash