tray_detailed_view.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. // Copyright (c) 2012 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. #include "ash/system/tray/tray_detailed_view.h"
  5. #include <cstring>
  6. #include <string>
  7. #include <utility>
  8. #include "ash/public/cpp/ash_view_ids.h"
  9. #include "ash/strings/grit/ash_strings.h"
  10. #include "ash/style/ash_color_provider.h"
  11. #include "ash/system/time/calendar_view.h"
  12. #include "ash/system/tray/detailed_view_delegate.h"
  13. #include "ash/system/tray/hover_highlight_view.h"
  14. #include "ash/system/tray/system_menu_button.h"
  15. #include "ash/system/tray/tray_constants.h"
  16. #include "ash/system/tray/tray_popup_utils.h"
  17. #include "ash/system/tray/tri_view.h"
  18. #include "base/bind.h"
  19. #include "base/containers/adapters.h"
  20. #include "third_party/skia/include/core/SkDrawLooper.h"
  21. #include "ui/base/l10n/l10n_util.h"
  22. #include "ui/base/resource/resource_bundle.h"
  23. #include "ui/compositor/clip_recorder.h"
  24. #include "ui/compositor/paint_context.h"
  25. #include "ui/compositor/paint_recorder.h"
  26. #include "ui/gfx/canvas.h"
  27. #include "ui/gfx/geometry/insets.h"
  28. #include "ui/gfx/geometry/size.h"
  29. #include "ui/gfx/image/image_skia.h"
  30. #include "ui/gfx/paint_vector_icon.h"
  31. #include "ui/gfx/skia_paint_util.h"
  32. #include "ui/gfx/vector_icon_types.h"
  33. #include "ui/views/accessibility/view_accessibility.h"
  34. #include "ui/views/background.h"
  35. #include "ui/views/border.h"
  36. #include "ui/views/controls/image_view.h"
  37. #include "ui/views/controls/label.h"
  38. #include "ui/views/controls/progress_bar.h"
  39. #include "ui/views/controls/scroll_view.h"
  40. #include "ui/views/layout/box_layout.h"
  41. #include "ui/views/layout/fill_layout.h"
  42. #include "ui/views/view_targeter.h"
  43. #include "ui/views/view_targeter_delegate.h"
  44. #include "ui/views/widget/widget.h"
  45. namespace ash {
  46. namespace {
  47. // The index of the horizontal rule below the title row.
  48. const int kTitleRowSeparatorIndex = 1;
  49. // A view that is used as ScrollView contents. It supports designating some of
  50. // the children as sticky header rows. The sticky header rows are not scrolled
  51. // above the top of the visible viewport until the next one "pushes" it up and
  52. // are painted above other children. To indicate that a child is a sticky header
  53. // row use SetID(VIEW_ID_STICKY_HEADER).
  54. class ScrollContentsView : public views::View {
  55. public:
  56. explicit ScrollContentsView(DetailedViewDelegate* delegate)
  57. : delegate_(delegate) {
  58. box_layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
  59. views::BoxLayout::Orientation::kVertical));
  60. }
  61. ScrollContentsView(const ScrollContentsView&) = delete;
  62. ScrollContentsView& operator=(const ScrollContentsView&) = delete;
  63. ~ScrollContentsView() override = default;
  64. protected:
  65. // views::View:
  66. void OnBoundsChanged(const gfx::Rect& previous_bounds) override {
  67. PositionHeaderRows();
  68. }
  69. void PaintChildren(const views::PaintInfo& paint_info) override {
  70. int sticky_header_height = 0;
  71. for (const auto& header : headers_) {
  72. // Sticky header is at the top.
  73. if (header.view->y() != header.natural_offset) {
  74. sticky_header_height = header.view->bounds().height();
  75. DCHECK_EQ(VIEW_ID_STICKY_HEADER, header.view->GetID());
  76. break;
  77. }
  78. }
  79. // Paint contents other than sticky headers. If sticky header is at the top,
  80. // it clips the header's height so that nothing is shown behind the header.
  81. {
  82. ui::ClipRecorder clip_recorder(paint_info.context());
  83. gfx::Rect clip_rect = gfx::Rect(paint_info.paint_recording_size()) -
  84. paint_info.offset_from_parent();
  85. auto clip_insets = gfx::Insets::TLBR(sticky_header_height, 0, 0, 0);
  86. clip_rect.Inset(gfx::ScaleToFlooredInsets(
  87. clip_insets, paint_info.paint_recording_scale_x(),
  88. paint_info.paint_recording_scale_y()));
  89. clip_recorder.ClipRect(clip_rect);
  90. for (auto* child : children()) {
  91. if (child->GetID() != VIEW_ID_STICKY_HEADER && !child->layer())
  92. child->Paint(paint_info);
  93. }
  94. }
  95. // Paint sticky headers.
  96. for (auto* child : children()) {
  97. if (child->GetID() == VIEW_ID_STICKY_HEADER && !child->layer())
  98. child->Paint(paint_info);
  99. }
  100. bool did_draw_shadow = false;
  101. // Paint header row separators.
  102. for (auto& header : headers_)
  103. did_draw_shadow =
  104. PaintDelineation(header, paint_info.context()) || did_draw_shadow;
  105. // Draw a shadow at the top of the viewport when scrolled, but only if a
  106. // header didn't already draw one. Overlap the shadow with the separator
  107. // that's below the header view so we don't get both a separator and a full
  108. // shadow.
  109. if (y() != 0 && !did_draw_shadow)
  110. DrawShadow(paint_info.context(),
  111. gfx::Rect(0, 0, width(), -y() - kTraySeparatorWidth));
  112. }
  113. void Layout() override {
  114. views::View::Layout();
  115. headers_.clear();
  116. for (auto* child : children()) {
  117. if (child->GetID() == VIEW_ID_STICKY_HEADER)
  118. headers_.emplace_back(child);
  119. }
  120. PositionHeaderRows();
  121. }
  122. const char* GetClassName() const override { return "ScrollContentsView"; }
  123. View::Views GetChildrenInZOrder() override {
  124. // Place sticky headers last in the child order so that they wind up on top
  125. // in Z order.
  126. View::Views children_in_z_order = children();
  127. std::stable_partition(children_in_z_order.begin(),
  128. children_in_z_order.end(), [](const View* child) {
  129. return child->GetID() != VIEW_ID_STICKY_HEADER;
  130. });
  131. return children_in_z_order;
  132. }
  133. void ViewHierarchyChanged(
  134. const views::ViewHierarchyChangedDetails& details) override {
  135. if (!details.is_add && details.parent == this) {
  136. headers_.erase(std::remove_if(headers_.begin(), headers_.end(),
  137. [details](const Header& header) {
  138. return header.view == details.child;
  139. }),
  140. headers_.end());
  141. } else if (details.is_add && details.parent == this &&
  142. details.child == children().front()) {
  143. // We always want padding on the bottom of the scroll contents.
  144. // We only want padding on the top of the scroll contents if the first
  145. // child is not a header (in that case, the padding is built into the
  146. // header).
  147. DCHECK_EQ(box_layout_, GetLayoutManager());
  148. box_layout_->set_inside_border_insets(
  149. gfx::Insets::TLBR(details.child->GetID() == VIEW_ID_STICKY_HEADER
  150. ? 0
  151. : kMenuSeparatorVerticalPadding,
  152. 0, kMenuSeparatorVerticalPadding, 0));
  153. }
  154. }
  155. private:
  156. const int kShadowOffsetY = 2;
  157. const int kShadowBlur = 2;
  158. // A structure that keeps the original offset of each header between the
  159. // calls to Layout() to allow keeping track of which view should be sticky.
  160. struct Header {
  161. explicit Header(views::View* view)
  162. : view(view), natural_offset(view->y()), draw_separator_below(false) {}
  163. // A header View that can be decorated as sticky.
  164. views::View* view;
  165. // Offset from the top of ScrollContentsView to |view|'s original vertical
  166. // position.
  167. int natural_offset;
  168. // True when a separator needs to be painted below the header when another
  169. // header is pushing |this| header up.
  170. bool draw_separator_below;
  171. };
  172. // Adjusts y-position of header rows allowing one or two rows to stick to the
  173. // top of the visible viewport.
  174. void PositionHeaderRows() {
  175. const int scroll_offset = -y();
  176. Header* previous_header = nullptr;
  177. for (auto& header : base::Reversed(headers_)) {
  178. views::View* header_view = header.view;
  179. bool draw_separator_below = false;
  180. if (header.natural_offset >= scroll_offset) {
  181. previous_header = &header;
  182. header_view->SetY(header.natural_offset);
  183. } else {
  184. if (previous_header && previous_header->view->y() <=
  185. scroll_offset + header_view->height()) {
  186. // Lower header displacing the header above.
  187. draw_separator_below = true;
  188. header_view->SetY(previous_header->view->y() - header_view->height());
  189. } else {
  190. // A header becomes sticky.
  191. header_view->SetY(scroll_offset);
  192. header_view->Layout();
  193. header_view->SchedulePaint();
  194. }
  195. }
  196. if (header.draw_separator_below != draw_separator_below) {
  197. header.draw_separator_below = draw_separator_below;
  198. delegate_->ShowStickyHeaderSeparator(header_view, draw_separator_below);
  199. }
  200. if (header.natural_offset < scroll_offset)
  201. break;
  202. }
  203. }
  204. // Paints a separator for a header view. The separator can be a horizontal
  205. // rule or a horizontal shadow, depending on whether the header is sticking to
  206. // the top of the scroll viewport. The return value indicates whether a shadow
  207. // was drawn.
  208. bool PaintDelineation(const Header& header, const ui::PaintContext& context) {
  209. const View* view = header.view;
  210. // If the header is where it normally belongs or If the header is pushed by
  211. // a header directly below it, draw nothing.
  212. if (view->y() == header.natural_offset || header.draw_separator_below)
  213. return false;
  214. // Otherwise, draw a shadow below.
  215. DrawShadow(context,
  216. gfx::Rect(0, 0, view->width(), view->bounds().bottom()));
  217. return true;
  218. }
  219. // Draws a drop shadow below |shadowed_area|.
  220. void DrawShadow(const ui::PaintContext& context,
  221. const gfx::Rect& shadowed_area) {
  222. ui::PaintRecorder recorder(context, size());
  223. gfx::Canvas* canvas = recorder.canvas();
  224. cc::PaintFlags flags;
  225. gfx::ShadowValues shadow;
  226. shadow.emplace_back(
  227. gfx::Vector2d(0, kShadowOffsetY), kShadowBlur,
  228. AshColorProvider::Get()->GetContentLayerColor(
  229. AshColorProvider::ContentLayerType::kSeparatorColor));
  230. flags.setLooper(gfx::CreateShadowDrawLooper(shadow));
  231. flags.setAntiAlias(true);
  232. canvas->ClipRect(shadowed_area, SkClipOp::kDifference);
  233. canvas->DrawRect(shadowed_area, flags);
  234. }
  235. DetailedViewDelegate* const delegate_;
  236. views::BoxLayout* box_layout_ = nullptr;
  237. // Header child views that stick to the top of visible viewport when scrolled.
  238. std::vector<Header> headers_;
  239. };
  240. } // namespace
  241. ////////////////////////////////////////////////////////////////////////////////
  242. // TrayDetailedView:
  243. TrayDetailedView::TrayDetailedView(DetailedViewDelegate* delegate)
  244. : delegate_(delegate) {
  245. box_layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
  246. views::BoxLayout::Orientation::kVertical));
  247. SetBackground(views::CreateSolidBackground(
  248. delegate_->GetBackgroundColor().value_or(SK_ColorTRANSPARENT)));
  249. }
  250. TrayDetailedView::~TrayDetailedView() = default;
  251. void TrayDetailedView::OnViewClicked(views::View* sender) {
  252. HandleViewClicked(sender);
  253. }
  254. void TrayDetailedView::OverrideProgressBarAccessibleName(
  255. const std::u16string& name) {
  256. progress_bar_accessible_name_ = name;
  257. }
  258. void TrayDetailedView::CreateTitleRow(int string_id) {
  259. DCHECK(!tri_view_);
  260. tri_view_ = delegate_->CreateTitleRow(string_id);
  261. back_button_ = delegate_->CreateBackButton(base::BindRepeating(
  262. &TrayDetailedView::TransitionToMainView, base::Unretained(this)));
  263. tri_view_->AddView(TriView::Container::START, back_button_);
  264. AddChildViewAt(tri_view_, 0);
  265. // If this view doesn't have a separator, adds an empty view as a placeholder
  266. // so that the views below won't move up when the `progress_bar_` becomes
  267. // invisible.
  268. if (!has_separator_) {
  269. auto buffer_view = std::make_unique<views::View>();
  270. buffer_view->SetPreferredSize(gfx::Size(1, kTitleRowProgressBarHeight));
  271. AddChildViewAt(std::move(buffer_view), kTitleRowSeparatorIndex);
  272. } else {
  273. AddChildViewAt(delegate_->CreateTitleSeparator(), kTitleRowSeparatorIndex);
  274. }
  275. CreateExtraTitleRowButtons();
  276. Layout();
  277. }
  278. void TrayDetailedView::CreateScrollableList() {
  279. DCHECK(!scroller_);
  280. auto scroll_content = std::make_unique<ScrollContentsView>(delegate_);
  281. scroller_ = AddChildView(std::make_unique<views::ScrollView>());
  282. scroller_->SetDrawOverflowIndicator(delegate_->IsOverflowIndicatorEnabled());
  283. scroll_content_ = scroller_->SetContents(std::move(scroll_content));
  284. // TODO(varkha): Make the sticky rows work with EnableViewPortLayer().
  285. scroller_->SetBackgroundColor(delegate_->GetBackgroundColor());
  286. box_layout_->SetFlexForView(scroller_, 1);
  287. }
  288. void TrayDetailedView::AddScrollListChild(std::unique_ptr<views::View> child) {
  289. scroll_content_->AddChildView(std::move(child));
  290. }
  291. HoverHighlightView* TrayDetailedView::AddScrollListItem(
  292. const gfx::VectorIcon& icon,
  293. const std::u16string& text) {
  294. HoverHighlightView* item = delegate_->CreateScrollListItem(this, icon, text);
  295. scroll_content_->AddChildView(item);
  296. return item;
  297. }
  298. HoverHighlightView* TrayDetailedView::AddScrollListCheckableItem(
  299. const gfx::VectorIcon& icon,
  300. const std::u16string& text,
  301. bool checked,
  302. bool enterprise_managed) {
  303. HoverHighlightView* item = AddScrollListItem(icon, text);
  304. if (enterprise_managed) {
  305. item->SetAccessibleName(l10n_util::GetStringFUTF16(
  306. IDS_ASH_ACCESSIBILITY_FEATURE_MANAGED, text));
  307. }
  308. TrayPopupUtils::InitializeAsCheckableRow(item, checked, enterprise_managed);
  309. return item;
  310. }
  311. HoverHighlightView* TrayDetailedView::AddScrollListCheckableItem(
  312. const std::u16string& text,
  313. bool checked,
  314. bool enterprise_managed) {
  315. return AddScrollListCheckableItem(gfx::kNoneIcon, text, checked,
  316. enterprise_managed);
  317. }
  318. TriView* TrayDetailedView::AddScrollListSubHeader(const gfx::VectorIcon& icon,
  319. int text_id) {
  320. TriView* header = TrayPopupUtils::CreateSubHeaderRowView(true);
  321. TrayPopupUtils::ConfigureAsStickyHeader(header);
  322. auto* color_provider = AshColorProvider::Get();
  323. sub_header_label_ = TrayPopupUtils::CreateDefaultLabel();
  324. sub_header_label_->SetText(l10n_util::GetStringUTF16(text_id));
  325. sub_header_label_->SetEnabledColor(color_provider->GetContentLayerColor(
  326. AshColorProvider::ContentLayerType::kTextColorPrimary));
  327. TrayPopupUtils::SetLabelFontList(sub_header_label_,
  328. TrayPopupUtils::FontStyle::kSubHeader);
  329. header->AddView(TriView::Container::CENTER, sub_header_label_);
  330. sub_header_image_view_ = TrayPopupUtils::CreateMainImageView();
  331. sub_header_icon_ = &icon;
  332. sub_header_image_view_->SetImage(gfx::CreateVectorIcon(
  333. icon, color_provider->GetContentLayerColor(
  334. AshColorProvider::ContentLayerType::kIconColorPrimary)));
  335. header->AddView(TriView::Container::START, sub_header_image_view_);
  336. scroll_content_->AddChildView(header);
  337. return header;
  338. }
  339. TriView* TrayDetailedView::AddScrollListSubHeader(int text_id) {
  340. return AddScrollListSubHeader(gfx::kNoneIcon, text_id);
  341. }
  342. void TrayDetailedView::Reset() {
  343. RemoveAllChildViews();
  344. scroller_ = nullptr;
  345. scroll_content_ = nullptr;
  346. progress_bar_ = nullptr;
  347. back_button_ = nullptr;
  348. tri_view_ = nullptr;
  349. }
  350. void TrayDetailedView::ShowProgress(double value, bool visible) {
  351. DCHECK(tri_view_);
  352. if (!progress_bar_) {
  353. progress_bar_ = AddChildViewAt(
  354. std::make_unique<views::ProgressBar>(kTitleRowProgressBarHeight),
  355. kTitleRowSeparatorIndex + 1);
  356. progress_bar_->GetViewAccessibility().OverrideName(
  357. progress_bar_accessible_name_.value_or(l10n_util::GetStringUTF16(
  358. IDS_ASH_STATUS_TRAY_PROGRESS_BAR_ACCESSIBLE_NAME)));
  359. progress_bar_->SetVisible(false);
  360. progress_bar_->SetForegroundColor(
  361. AshColorProvider::Get()->GetContentLayerColor(
  362. AshColorProvider::ContentLayerType::kIconColorProminent));
  363. }
  364. progress_bar_->SetValue(value);
  365. progress_bar_->SetVisible(visible);
  366. children()[size_t{kTitleRowSeparatorIndex}]->SetVisible(!visible);
  367. }
  368. views::Button* TrayDetailedView::CreateInfoButton(
  369. views::Button::PressedCallback callback,
  370. int info_accessible_name_id) {
  371. return delegate_->CreateInfoButton(std::move(callback),
  372. info_accessible_name_id);
  373. }
  374. views::Button* TrayDetailedView::CreateSettingsButton(
  375. views::Button::PressedCallback callback,
  376. int setting_accessible_name_id) {
  377. return delegate_->CreateSettingsButton(std::move(callback),
  378. setting_accessible_name_id);
  379. }
  380. views::Button* TrayDetailedView::CreateHelpButton(
  381. views::Button::PressedCallback callback) {
  382. return delegate_->CreateHelpButton(std::move(callback));
  383. }
  384. void TrayDetailedView::HandleViewClicked(views::View* view) {
  385. NOTREACHED();
  386. }
  387. void TrayDetailedView::CreateExtraTitleRowButtons() {}
  388. void TrayDetailedView::TransitionToMainView() {
  389. delegate_->TransitionToMainView(back_button_ && back_button_->HasFocus());
  390. }
  391. void TrayDetailedView::CloseBubble() {
  392. // widget may be null in tests, in this case we do not need to do anything.
  393. views::Widget* widget = GetWidget();
  394. if (!widget)
  395. return;
  396. // Don't close again if we're already closing.
  397. if (widget->IsClosed())
  398. return;
  399. delegate_->CloseBubble();
  400. }
  401. void TrayDetailedView::IgnoreSeparator() {
  402. has_separator_ = false;
  403. }
  404. void TrayDetailedView::Layout() {
  405. views::View::Layout();
  406. if (scroller_ && !scroller_->is_bounded())
  407. scroller_->ClipHeightTo(0, scroller_->height());
  408. }
  409. int TrayDetailedView::GetHeightForWidth(int width) const {
  410. if (bounds().IsEmpty())
  411. return views::View::GetHeightForWidth(width);
  412. // The height of the bubble that contains this detailed view is set to
  413. // the preferred height of the default view, and that determines the
  414. // initial height of |this|. Always request to stay the same height.
  415. return height();
  416. }
  417. const char* TrayDetailedView::GetClassName() const {
  418. return "TrayDetailedView";
  419. }
  420. void TrayDetailedView::OnThemeChanged() {
  421. views::View::OnThemeChanged();
  422. delegate_->UpdateColors();
  423. auto* color_provider = AshColorProvider::Get();
  424. if (sub_header_label_) {
  425. sub_header_label_->SetEnabledColor(color_provider->GetContentLayerColor(
  426. AshColorProvider::ContentLayerType::kTextColorPrimary));
  427. }
  428. if (sub_header_image_view_) {
  429. sub_header_image_view_->SetImage(gfx::CreateVectorIcon(
  430. *sub_header_icon_,
  431. color_provider->GetContentLayerColor(
  432. AshColorProvider::ContentLayerType::kIconColorPrimary)));
  433. }
  434. }
  435. } // namespace ash