1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ash/system/tray/tray_popup_utils.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 
11 #include "ash/public/cpp/ash_constants.h"
12 #include "ash/public/cpp/ash_view_ids.h"
13 #include "ash/resources/vector_icons/vector_icons.h"
14 #include "ash/session/session_controller_impl.h"
15 #include "ash/shell.h"
16 #include "ash/style/ash_color_provider.h"
17 #include "ash/system/tray/hover_highlight_view.h"
18 #include "ash/system/tray/size_range_layout.h"
19 #include "ash/system/tray/tray_constants.h"
20 #include "ash/system/tray/unfocusable_label.h"
21 #include "chromeos/ui/vector_icons/vector_icons.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/gfx/color_palette.h"
24 #include "ui/gfx/geometry/insets.h"
25 #include "ui/gfx/paint_vector_icon.h"
26 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
27 #include "ui/views/animation/ink_drop_highlight.h"
28 #include "ui/views/animation/ink_drop_impl.h"
29 #include "ui/views/animation/square_ink_drop_ripple.h"
30 #include "ui/views/controls/button/button.h"
31 #include "ui/views/controls/button/md_text_button.h"
32 #include "ui/views/controls/button/toggle_button.h"
33 #include "ui/views/controls/highlight_path_generator.h"
34 #include "ui/views/controls/image_view.h"
35 #include "ui/views/controls/label.h"
36 #include "ui/views/controls/separator.h"
37 #include "ui/views/controls/slider.h"
38 #include "ui/views/layout/box_layout.h"
39 #include "ui/views/layout/fill_layout.h"
40 #include "ui/views/painter.h"
41 
42 namespace ash {
43 
44 namespace {
45 
46 // Creates a layout manager that positions Views vertically. The Views will be
47 // stretched horizontally and centered vertically.
CreateDefaultCenterLayoutManager()48 std::unique_ptr<views::LayoutManager> CreateDefaultCenterLayoutManager() {
49   // TODO(bruthig): Use constants instead of magic numbers.
50   auto box_layout = std::make_unique<views::BoxLayout>(
51       views::BoxLayout::Orientation::kVertical,
52       gfx::Insets(8, kTrayPopupLabelHorizontalPadding));
53   box_layout->set_main_axis_alignment(
54       views::BoxLayout::MainAxisAlignment::kCenter);
55   box_layout->set_cross_axis_alignment(
56       views::BoxLayout::CrossAxisAlignment::kStretch);
57   return std::move(box_layout);
58 }
59 
60 // Creates a layout manager that positions Views horizontally. The Views will be
61 // centered along the horizontal and vertical axis.
CreateDefaultEndsLayoutManager()62 std::unique_ptr<views::LayoutManager> CreateDefaultEndsLayoutManager() {
63   auto box_layout = std::make_unique<views::BoxLayout>(
64       views::BoxLayout::Orientation::kHorizontal);
65   box_layout->set_main_axis_alignment(
66       views::BoxLayout::MainAxisAlignment::kCenter);
67   box_layout->set_cross_axis_alignment(
68       views::BoxLayout::CrossAxisAlignment::kCenter);
69   return std::move(box_layout);
70 }
71 
CreateDefaultLayoutManager(TriView::Container container)72 std::unique_ptr<views::LayoutManager> CreateDefaultLayoutManager(
73     TriView::Container container) {
74   switch (container) {
75     case TriView::Container::START:
76     case TriView::Container::END:
77       return CreateDefaultEndsLayoutManager();
78     case TriView::Container::CENTER:
79       return CreateDefaultCenterLayoutManager();
80   }
81   // Required by some compilers.
82   NOTREACHED();
83   return nullptr;
84 }
85 
86 // Configures the default size and flex value for the specified |container|
87 // of the given |tri_view|. Used by CreateDefaultRowView().
ConfigureDefaultSizeAndFlex(TriView * tri_view,TriView::Container container)88 void ConfigureDefaultSizeAndFlex(TriView* tri_view,
89                                  TriView::Container container) {
90   int min_width = 0;
91   switch (container) {
92     case TriView::Container::START:
93       min_width = kTrayPopupItemMinStartWidth;
94       break;
95     case TriView::Container::CENTER:
96       tri_view->SetFlexForContainer(TriView::Container::CENTER, 1.f);
97       break;
98     case TriView::Container::END:
99       min_width = kTrayPopupItemMinEndWidth;
100       break;
101   }
102 
103   tri_view->SetMinSize(container,
104                        gfx::Size(min_width, kTrayPopupItemMinHeight));
105   constexpr int kTrayPopupItemMaxHeight = 144;
106   tri_view->SetMaxSize(container, gfx::Size(SizeRangeLayout::kAbsoluteMaxSize,
107                                             kTrayPopupItemMaxHeight));
108 }
109 
GetInkDropInsets(TrayPopupInkDropStyle ink_drop_style)110 gfx::Insets GetInkDropInsets(TrayPopupInkDropStyle ink_drop_style) {
111   if (ink_drop_style == TrayPopupInkDropStyle::HOST_CENTERED ||
112       ink_drop_style == TrayPopupInkDropStyle::INSET_BOUNDS) {
113     return gfx::Insets(kTrayPopupInkDropInset);
114   }
115   return gfx::Insets();
116 }
117 
118 class HighlightPathGenerator : public views::HighlightPathGenerator {
119  public:
HighlightPathGenerator(TrayPopupInkDropStyle ink_drop_style)120   explicit HighlightPathGenerator(TrayPopupInkDropStyle ink_drop_style)
121       : ink_drop_style_(ink_drop_style) {}
122 
123   HighlightPathGenerator(const HighlightPathGenerator&) = delete;
124   HighlightPathGenerator& operator=(const HighlightPathGenerator&) = delete;
125 
126   // views::HighlightPathGenerator:
GetRoundRect(const gfx::RectF & rect)127   base::Optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect) override {
128     gfx::RectF bounds = rect;
129     bounds.Inset(GetInkDropInsets(ink_drop_style_));
130     float corner_radius = 0.f;
131     switch (ink_drop_style_) {
132       case TrayPopupInkDropStyle::HOST_CENTERED:
133         corner_radius = std::min(bounds.width(), bounds.height()) / 2.f;
134         bounds.ClampToCenteredSize(gfx::SizeF(corner_radius, corner_radius));
135         break;
136       case TrayPopupInkDropStyle::INSET_BOUNDS:
137         corner_radius = kTrayPopupInkDropCornerRadius;
138         break;
139       case TrayPopupInkDropStyle::FILL_BOUNDS:
140         break;
141     }
142 
143     return gfx::RRectF(bounds, corner_radius);
144   }
145 
146  private:
147   const TrayPopupInkDropStyle ink_drop_style_;
148 };
149 
150 }  // namespace
151 
CreateDefaultRowView()152 TriView* TrayPopupUtils::CreateDefaultRowView() {
153   TriView* tri_view = CreateMultiTargetRowView();
154 
155   tri_view->SetContainerLayout(
156       TriView::Container::START,
157       CreateDefaultLayoutManager(TriView::Container::START));
158   tri_view->SetContainerLayout(
159       TriView::Container::CENTER,
160       CreateDefaultLayoutManager(TriView::Container::CENTER));
161   tri_view->SetContainerLayout(
162       TriView::Container::END,
163       CreateDefaultLayoutManager(TriView::Container::END));
164 
165   return tri_view;
166 }
167 
CreateSubHeaderRowView(bool start_visible)168 TriView* TrayPopupUtils::CreateSubHeaderRowView(bool start_visible) {
169   TriView* tri_view = CreateDefaultRowView();
170   if (!start_visible) {
171     tri_view->SetInsets(gfx::Insets(
172         0, kTrayPopupPaddingHorizontal - kTrayPopupLabelHorizontalPadding, 0,
173         0));
174     tri_view->SetContainerVisible(TriView::Container::START, false);
175   }
176   return tri_view;
177 }
178 
CreateMultiTargetRowView()179 TriView* TrayPopupUtils::CreateMultiTargetRowView() {
180   TriView* tri_view = new TriView(0 /* padding_between_items */);
181 
182   tri_view->SetInsets(gfx::Insets(0, kMenuExtraMarginFromLeftEdge, 0, 0));
183 
184   ConfigureDefaultSizeAndFlex(tri_view, TriView::Container::START);
185   ConfigureDefaultSizeAndFlex(tri_view, TriView::Container::CENTER);
186   ConfigureDefaultSizeAndFlex(tri_view, TriView::Container::END);
187 
188   tri_view->SetContainerLayout(TriView::Container::START,
189                                std::make_unique<views::FillLayout>());
190   tri_view->SetContainerLayout(TriView::Container::CENTER,
191                                std::make_unique<views::FillLayout>());
192   tri_view->SetContainerLayout(TriView::Container::END,
193                                std::make_unique<views::FillLayout>());
194 
195   return tri_view;
196 }
197 
CreateDefaultLabel()198 views::Label* TrayPopupUtils::CreateDefaultLabel() {
199   views::Label* label = new views::Label();
200   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
201   label->SetSubpixelRenderingEnabled(false);
202   return label;
203 }
204 
CreateUnfocusableLabel()205 UnfocusableLabel* TrayPopupUtils::CreateUnfocusableLabel() {
206   UnfocusableLabel* label = new UnfocusableLabel();
207   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
208   label->SetSubpixelRenderingEnabled(false);
209   return label;
210 }
211 
CreateMainImageView()212 views::ImageView* TrayPopupUtils::CreateMainImageView() {
213   auto* image = new views::ImageView;
214   image->SetPreferredSize(
215       gfx::Size(kTrayPopupItemMinStartWidth, kTrayPopupItemMinHeight));
216   return image;
217 }
218 
CreateToggleButton(views::Button::PressedCallback callback,int accessible_name_id)219 views::ToggleButton* TrayPopupUtils::CreateToggleButton(
220     views::Button::PressedCallback callback,
221     int accessible_name_id) {
222   constexpr SkColor kTrackAlpha = 0x66;
223   auto GetColor = [](bool is_on, SkAlpha alpha = SK_AlphaOPAQUE) {
224     AshColorProvider::ContentLayerType type =
225         is_on ? AshColorProvider::ContentLayerType::kIconColorProminent
226               : AshColorProvider::ContentLayerType::kTextColorPrimary;
227 
228     return SkColorSetA(AshColorProvider::Get()->GetContentLayerColor(type),
229                        alpha);
230   };
231   views::ToggleButton* toggle = new views::ToggleButton(std::move(callback));
232   const gfx::Size toggle_size(toggle->GetPreferredSize());
233   const int vertical_padding = (kMenuButtonSize - toggle_size.height()) / 2;
234   const int horizontal_padding =
235       (kTrayToggleButtonWidth - toggle_size.width()) / 2;
236   toggle->SetBorder(views::CreateEmptyBorder(
237       gfx::Insets(vertical_padding, horizontal_padding)));
238   toggle->SetAccessibleName(l10n_util::GetStringUTF16(accessible_name_id));
239   toggle->SetThumbOnColor(GetColor(true));
240   toggle->SetThumbOffColor(GetColor(false));
241   toggle->SetTrackOnColor(GetColor(true, kTrackAlpha));
242   toggle->SetTrackOffColor(GetColor(false, kTrackAlpha));
243   return toggle;
244 }
245 
CreateFocusPainter()246 std::unique_ptr<views::Painter> TrayPopupUtils::CreateFocusPainter() {
247   return views::Painter::CreateSolidFocusPainter(
248       AshColorProvider::Get()->GetControlsLayerColor(
249           AshColorProvider::ControlsLayerType::kFocusRingColor),
250       kFocusBorderThickness, gfx::InsetsF());
251 }
252 
ConfigureTrayPopupButton(views::Button * button)253 void TrayPopupUtils::ConfigureTrayPopupButton(views::Button* button) {
254   button->SetInstallFocusRingOnFocus(true);
255   button->SetInkDropMode(views::InkDropHostView::InkDropMode::ON);
256   button->SetHasInkDropActionOnClick(true);
257 }
258 
ConfigureAsStickyHeader(views::View * view)259 void TrayPopupUtils::ConfigureAsStickyHeader(views::View* view) {
260   view->SetID(VIEW_ID_STICKY_HEADER);
261   view->SetBorder(
262       views::CreateEmptyBorder(gfx::Insets(kMenuSeparatorVerticalPadding, 0)));
263   view->SetPaintToLayer();
264   view->layer()->SetFillsBoundsOpaquely(false);
265 }
266 
ConfigureContainer(TriView::Container container,views::View * container_view)267 void TrayPopupUtils::ConfigureContainer(TriView::Container container,
268                                         views::View* container_view) {
269   container_view->SetLayoutManager(CreateDefaultLayoutManager(container));
270 }
271 
CreateTrayPopupButton(views::Button::PressedCallback callback,const base::string16 & text)272 views::LabelButton* TrayPopupUtils::CreateTrayPopupButton(
273     views::Button::PressedCallback callback,
274     const base::string16& text) {
275   auto button =
276       std::make_unique<views::MdTextButton>(std::move(callback), text);
277   button->SetProminent(true);
278   return button.release();
279 }
280 
CreateVerticalSeparator()281 views::Separator* TrayPopupUtils::CreateVerticalSeparator() {
282   views::Separator* separator = new views::Separator();
283   separator->SetPreferredHeight(24);
284   separator->SetColor(AshColorProvider::Get()->GetContentLayerColor(
285       AshColorProvider::ContentLayerType::kSeparatorColor));
286   return separator;
287 }
288 
CreateInkDrop(views::InkDropHostView * host)289 std::unique_ptr<views::InkDrop> TrayPopupUtils::CreateInkDrop(
290     views::InkDropHostView* host) {
291   std::unique_ptr<views::InkDropImpl> ink_drop =
292       std::make_unique<views::InkDropImpl>(host, host->size());
293   ink_drop->SetAutoHighlightMode(
294       views::InkDropImpl::AutoHighlightMode::SHOW_ON_RIPPLE);
295   ink_drop->SetShowHighlightOnHover(false);
296   return std::move(ink_drop);
297 }
298 
CreateInkDropRipple(TrayPopupInkDropStyle ink_drop_style,const views::View * host,const gfx::Point & center_point)299 std::unique_ptr<views::InkDropRipple> TrayPopupUtils::CreateInkDropRipple(
300     TrayPopupInkDropStyle ink_drop_style,
301     const views::View* host,
302     const gfx::Point& center_point) {
303   const AshColorProvider::RippleAttributes ripple_attributes =
304       AshColorProvider::Get()->GetRippleAttributes();
305   return std::make_unique<views::FloodFillInkDropRipple>(
306       host->size(), GetInkDropInsets(ink_drop_style), center_point,
307       ripple_attributes.base_color, ripple_attributes.inkdrop_opacity);
308 }
309 
CreateInkDropHighlight(const views::View * host)310 std::unique_ptr<views::InkDropHighlight> TrayPopupUtils::CreateInkDropHighlight(
311     const views::View* host) {
312   const AshColorProvider::RippleAttributes ripple_attributes =
313       AshColorProvider::Get()->GetRippleAttributes();
314   auto highlight = std::make_unique<views::InkDropHighlight>(
315       gfx::SizeF(host->size()), ripple_attributes.base_color);
316   highlight->set_visible_opacity(ripple_attributes.highlight_opacity);
317   return highlight;
318 }
319 
InstallHighlightPathGenerator(views::View * host,TrayPopupInkDropStyle ink_drop_style)320 void TrayPopupUtils::InstallHighlightPathGenerator(
321     views::View* host,
322     TrayPopupInkDropStyle ink_drop_style) {
323   views::HighlightPathGenerator::Install(
324       host, std::make_unique<HighlightPathGenerator>(ink_drop_style));
325 }
326 
CreateListItemSeparator(bool left_inset)327 views::Separator* TrayPopupUtils::CreateListItemSeparator(bool left_inset) {
328   views::Separator* separator = new views::Separator();
329   separator->SetColor(AshColorProvider::Get()->GetContentLayerColor(
330       AshColorProvider::ContentLayerType::kSeparatorColor));
331   separator->SetBorder(views::CreateEmptyBorder(
332       kMenuSeparatorVerticalPadding - views::Separator::kThickness,
333       left_inset ? kMenuExtraMarginFromLeftEdge + kMenuButtonSize +
334                        kTrayPopupLabelHorizontalPadding
335                  : 0,
336       kMenuSeparatorVerticalPadding, 0));
337   return separator;
338 }
339 
CanOpenWebUISettings()340 bool TrayPopupUtils::CanOpenWebUISettings() {
341   return Shell::Get()->session_controller()->ShouldEnableSettings();
342 }
343 
InitializeAsCheckableRow(HoverHighlightView * container,bool checked,bool enterprise_managed)344 void TrayPopupUtils::InitializeAsCheckableRow(HoverHighlightView* container,
345                                               bool checked,
346                                               bool enterprise_managed) {
347   const int dip_size = GetDefaultSizeOfVectorIcon(kCheckCircleIcon);
348   gfx::ImageSkia check_mark = CreateVectorIcon(
349       kHollowCheckCircleIcon, dip_size,
350       AshColorProvider::Get()->GetContentLayerColor(
351           AshColorProvider::ContentLayerType::kIconColorProminent));
352   if (enterprise_managed) {
353     gfx::ImageSkia enterprise_managed_icon = CreateVectorIcon(
354         chromeos::kEnterpriseIcon, dip_size, gfx::kGoogleGrey100);
355     container->AddRightIcon(enterprise_managed_icon,
356                             enterprise_managed_icon.width());
357   }
358   container->AddRightIcon(check_mark, check_mark.width());
359   UpdateCheckMarkVisibility(container, checked);
360 }
361 
UpdateCheckMarkVisibility(HoverHighlightView * container,bool visible)362 void TrayPopupUtils::UpdateCheckMarkVisibility(HoverHighlightView* container,
363                                                bool visible) {
364   container->SetRightViewVisible(visible);
365   container->SetAccessibilityState(
366       visible ? HoverHighlightView::AccessibilityState::CHECKED_CHECKBOX
367               : HoverHighlightView::AccessibilityState::UNCHECKED_CHECKBOX);
368 }
369 
SetLabelFontList(views::Label * label,FontStyle style)370 void TrayPopupUtils::SetLabelFontList(views::Label* label, FontStyle style) {
371   label->SetAutoColorReadabilityEnabled(false);
372   const gfx::FontList& base_font_list = views::Label::GetDefaultFontList();
373   switch (style) {
374     case FontStyle::kTitle:
375       label->SetFontList(base_font_list.Derive(8, gfx::Font::NORMAL,
376                                                gfx::Font::Weight::MEDIUM));
377       break;
378     case FontStyle::kSubHeader:
379       label->SetFontList(base_font_list.Derive(4, gfx::Font::NORMAL,
380                                                gfx::Font::Weight::MEDIUM));
381       break;
382     case FontStyle::kSmallTitle:
383     case FontStyle::kDetailedViewLabel:
384     case FontStyle::kSystemInfo:
385       label->SetFontList(base_font_list.Derive(1, gfx::Font::NORMAL,
386                                                gfx::Font::Weight::NORMAL));
387       break;
388   }
389 }
390 
391 }  // namespace ash
392