| // 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/public/cpp/ash_constants.h" |
| #include "ash/public/cpp/ash_view_ids.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/style/ash_color_provider.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/unfocusable_label.h" |
| #include "chromeos/ui/vector_icons/vector_icons.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/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/square_ink_drop_ripple.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/controls/button/md_text_button.h" |
| #include "ui/views/controls/button/toggle_button.h" |
| #include "ui/views/controls/highlight_path_generator.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::Orientation::kVertical, |
| gfx::Insets(8, kTrayPopupLabelHorizontalPadding)); |
| box_layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| box_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kStretch); |
| 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::Orientation::kHorizontal); |
| box_layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| box_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| 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)); |
| } |
| |
| gfx::Insets GetInkDropInsets(TrayPopupInkDropStyle ink_drop_style) { |
| if (ink_drop_style == TrayPopupInkDropStyle::HOST_CENTERED || |
| ink_drop_style == TrayPopupInkDropStyle::INSET_BOUNDS) { |
| return gfx::Insets(kTrayPopupInkDropInset); |
| } |
| return gfx::Insets(); |
| } |
| |
| class HighlightPathGenerator : public views::HighlightPathGenerator { |
| public: |
| explicit HighlightPathGenerator(TrayPopupInkDropStyle ink_drop_style) |
| : ink_drop_style_(ink_drop_style) {} |
| |
| HighlightPathGenerator(const HighlightPathGenerator&) = delete; |
| HighlightPathGenerator& operator=(const HighlightPathGenerator&) = delete; |
| |
| // views::HighlightPathGenerator: |
| base::Optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect) override { |
| gfx::RectF bounds = rect; |
| bounds.Inset(GetInkDropInsets(ink_drop_style_)); |
| float corner_radius = 0.f; |
| switch (ink_drop_style_) { |
| case TrayPopupInkDropStyle::HOST_CENTERED: |
| corner_radius = std::min(bounds.width(), bounds.height()) / 2.f; |
| bounds.ClampToCenteredSize(gfx::SizeF(corner_radius, corner_radius)); |
| break; |
| case TrayPopupInkDropStyle::INSET_BOUNDS: |
| corner_radius = kTrayPopupInkDropCornerRadius; |
| break; |
| case TrayPopupInkDropStyle::FILL_BOUNDS: |
| break; |
| } |
| |
| return gfx::RRectF(bounds, corner_radius); |
| } |
| |
| private: |
| const TrayPopupInkDropStyle ink_drop_style_; |
| }; |
| |
| } // 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); |
| label->SetSubpixelRenderingEnabled(false); |
| return label; |
| } |
| |
| UnfocusableLabel* TrayPopupUtils::CreateUnfocusableLabel() { |
| UnfocusableLabel* label = new UnfocusableLabel(); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label->SetSubpixelRenderingEnabled(false); |
| return label; |
| } |
| |
| views::ImageView* TrayPopupUtils::CreateMainImageView() { |
| auto* image = new views::ImageView; |
| image->SetPreferredSize( |
| gfx::Size(kTrayPopupItemMinStartWidth, kTrayPopupItemMinHeight)); |
| return image; |
| } |
| |
| views::ToggleButton* TrayPopupUtils::CreateToggleButton( |
| views::Button::PressedCallback callback, |
| int accessible_name_id) { |
| views::ToggleButton* toggle = new views::ToggleButton(std::move(callback)); |
| 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)); |
| UpdateToggleButtonColors(toggle); |
| return toggle; |
| } |
| |
| std::unique_ptr<views::Painter> TrayPopupUtils::CreateFocusPainter() { |
| return views::Painter::CreateSolidFocusPainter( |
| AshColorProvider::Get()->GetControlsLayerColor( |
| AshColorProvider::ControlsLayerType::kFocusRingColor), |
| kFocusBorderThickness, gfx::InsetsF()); |
| } |
| |
| void TrayPopupUtils::ConfigureTrayPopupButton(views::Button* button) { |
| button->SetInstallFocusRingOnFocus(true); |
| button->SetInkDropMode(views::InkDropHostView::InkDropMode::ON); |
| button->SetHasInkDropActionOnClick(true); |
| } |
| |
| void TrayPopupUtils::ConfigureAsStickyHeader(views::View* view) { |
| view->SetID(VIEW_ID_STICKY_HEADER); |
| view->SetBorder( |
| views::CreateEmptyBorder(gfx::Insets(kMenuSeparatorVerticalPadding, 0))); |
| view->SetPaintToLayer(); |
| view->layer()->SetFillsBoundsOpaquely(false); |
| } |
| |
| void TrayPopupUtils::ConfigureContainer(TriView::Container container, |
| views::View* container_view) { |
| container_view->SetLayoutManager(CreateDefaultLayoutManager(container)); |
| } |
| |
| views::LabelButton* TrayPopupUtils::CreateTrayPopupButton( |
| views::Button::PressedCallback callback, |
| const std::u16string& text) { |
| auto button = |
| std::make_unique<views::MdTextButton>(std::move(callback), text); |
| button->SetProminent(true); |
| return button.release(); |
| } |
| |
| views::Separator* TrayPopupUtils::CreateVerticalSeparator() { |
| views::Separator* separator = new views::Separator(); |
| separator->SetPreferredHeight(24); |
| separator->SetColor(AshColorProvider::Get()->GetContentLayerColor( |
| AshColorProvider::ContentLayerType::kSeparatorColor)); |
| return separator; |
| } |
| |
| std::unique_ptr<views::InkDrop> TrayPopupUtils::CreateInkDrop( |
| 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) { |
| const AshColorProvider::RippleAttributes ripple_attributes = |
| AshColorProvider::Get()->GetRippleAttributes(); |
| return std::make_unique<views::FloodFillInkDropRipple>( |
| host->size(), GetInkDropInsets(ink_drop_style), center_point, |
| ripple_attributes.base_color, ripple_attributes.inkdrop_opacity); |
| } |
| |
| std::unique_ptr<views::InkDropHighlight> TrayPopupUtils::CreateInkDropHighlight( |
| const views::View* host) { |
| const AshColorProvider::RippleAttributes ripple_attributes = |
| AshColorProvider::Get()->GetRippleAttributes(); |
| auto highlight = std::make_unique<views::InkDropHighlight>( |
| gfx::SizeF(host->size()), ripple_attributes.base_color); |
| highlight->set_visible_opacity(ripple_attributes.highlight_opacity); |
| return highlight; |
| } |
| |
| void TrayPopupUtils::InstallHighlightPathGenerator( |
| views::View* host, |
| TrayPopupInkDropStyle ink_drop_style) { |
| views::HighlightPathGenerator::Install( |
| host, std::make_unique<HighlightPathGenerator>(ink_drop_style)); |
| } |
| |
| views::Separator* TrayPopupUtils::CreateListItemSeparator(bool left_inset) { |
| views::Separator* separator = new views::Separator(); |
| separator->SetColor(AshColorProvider::Get()->GetContentLayerColor( |
| AshColorProvider::ContentLayerType::kSeparatorColor)); |
| separator->SetBorder(views::CreateEmptyBorder( |
| kMenuSeparatorVerticalPadding - views::Separator::kThickness, |
| left_inset ? kMenuExtraMarginFromLeftEdge + kMenuButtonSize + |
| kTrayPopupLabelHorizontalPadding |
| : 0, |
| kMenuSeparatorVerticalPadding, 0)); |
| return separator; |
| } |
| |
| bool TrayPopupUtils::CanOpenWebUISettings() { |
| return Shell::Get()->session_controller()->ShouldEnableSettings(); |
| } |
| |
| void TrayPopupUtils::InitializeAsCheckableRow(HoverHighlightView* container, |
| bool checked, |
| bool enterprise_managed) { |
| const int dip_size = GetDefaultSizeOfVectorIcon(kCheckCircleIcon); |
| gfx::ImageSkia check_mark = CreateVectorIcon( |
| kHollowCheckCircleIcon, dip_size, |
| AshColorProvider::Get()->GetContentLayerColor( |
| AshColorProvider::ContentLayerType::kIconColorProminent)); |
| if (enterprise_managed) { |
| gfx::ImageSkia enterprise_managed_icon = CreateVectorIcon( |
| chromeos::kEnterpriseIcon, dip_size, gfx::kGoogleGrey100); |
| container->AddRightIcon(enterprise_managed_icon, |
| enterprise_managed_icon.width()); |
| } |
| container->AddRightIcon(check_mark, check_mark.width()); |
| UpdateCheckMarkVisibility(container, checked); |
| } |
| |
| void TrayPopupUtils::UpdateCheckMarkVisibility(HoverHighlightView* container, |
| bool visible) { |
| container->SetRightViewVisible(visible); |
| container->SetAccessibilityState( |
| visible ? HoverHighlightView::AccessibilityState::CHECKED_CHECKBOX |
| : HoverHighlightView::AccessibilityState::UNCHECKED_CHECKBOX); |
| } |
| |
| void TrayPopupUtils::UpdateToggleButtonColors(views::ToggleButton* toggle) { |
| auto* ash_color_provider = AshColorProvider::Get(); |
| toggle->SetThumbOnColor(ash_color_provider->GetContentLayerColor( |
| AshColorProvider::ContentLayerType::kSwitchKnobColorActive)); |
| toggle->SetThumbOffColor(ash_color_provider->GetContentLayerColor( |
| AshColorProvider::ContentLayerType::kSwitchKnobColorInactive)); |
| toggle->SetTrackOnColor(ash_color_provider->GetContentLayerColor( |
| AshColorProvider::ContentLayerType::kSwitchTrackColorActive)); |
| toggle->SetTrackOffColor(ash_color_provider->GetContentLayerColor( |
| AshColorProvider::ContentLayerType::kSwitchTrackColorInactive)); |
| } |
| |
| void TrayPopupUtils::SetLabelFontList(views::Label* label, FontStyle style) { |
| label->SetAutoColorReadabilityEnabled(false); |
| const gfx::FontList google_sans_font_list({"Google Sans"}, gfx::Font::NORMAL, |
| 16, gfx::Font::Weight::MEDIUM); |
| const gfx::FontList roboto_font_list({"Roboto"}, gfx::Font::NORMAL, 16, |
| gfx::Font::Weight::MEDIUM); |
| |
| switch (style) { |
| case FontStyle::kTitle: |
| label->SetFontList(google_sans_font_list); |
| break; |
| case FontStyle::kPodMenuHeader: |
| label->SetFontList(roboto_font_list); |
| break; |
| case FontStyle::kSubHeader: |
| label->SetFontList(roboto_font_list.Derive(-1, gfx::Font::NORMAL, |
| gfx::Font::Weight::MEDIUM)); |
| break; |
| case FontStyle::kSmallTitle: |
| label->SetFontList(roboto_font_list.Derive(-3, gfx::Font::NORMAL, |
| gfx::Font::Weight::MEDIUM)); |
| break; |
| case FontStyle::kDetailedViewLabel: |
| case FontStyle::kSystemInfo: |
| label->SetFontList(roboto_font_list.Derive(-4, gfx::Font::NORMAL, |
| gfx::Font::Weight::NORMAL)); |
| break; |
| } |
| } |
| |
| } // namespace ash |