Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(511)

Side by Side Diff: ash/common/system/tray/tray_details_view.cc

Issue 2732813002: chromeos: Move files in //ash/common to //ash, part 1 (Closed)
Patch Set: rebase Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
5 #include "ash/common/system/tray/tray_details_view.h"
6
7 #include "ash/common/ash_view_ids.h"
8 #include "ash/common/material_design/material_design_controller.h"
9 #include "ash/common/system/tray/system_menu_button.h"
10 #include "ash/common/system/tray/system_tray.h"
11 #include "ash/common/system/tray/system_tray_item.h"
12 #include "ash/common/system/tray/tray_constants.h"
13 #include "ash/common/system/tray/tray_popup_item_style.h"
14 #include "ash/common/system/tray/tray_popup_utils.h"
15 #include "ash/common/system/tray/tri_view.h"
16 #include "ash/strings/grit/ash_strings.h"
17 #include "base/containers/adapters.h"
18 #include "base/memory/ptr_util.h"
19 #include "third_party/skia/include/core/SkDrawLooper.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/compositor/paint_context.h"
22 #include "ui/compositor/paint_recorder.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/skia_paint_util.h"
25 #include "ui/views/background.h"
26 #include "ui/views/border.h"
27 #include "ui/views/controls/label.h"
28 #include "ui/views/controls/progress_bar.h"
29 #include "ui/views/controls/scroll_view.h"
30 #include "ui/views/controls/separator.h"
31 #include "ui/views/layout/box_layout.h"
32 #include "ui/views/view_targeter.h"
33 #include "ui/views/view_targeter_delegate.h"
34
35 namespace ash {
36 namespace {
37
38 bool UseMd() {
39 return MaterialDesignController::IsSystemTrayMenuMaterial();
40 }
41
42 // The index of the horizontal rule below the title row.
43 const int kTitleRowSeparatorIndex = 1;
44
45 // A view that is used as ScrollView contents. It supports designating some of
46 // the children as sticky header rows. The sticky header rows are not scrolled
47 // above the top of the visible viewport until the next one "pushes" it up and
48 // are painted above other children. To indicate that a child is a sticky header
49 // row use set_id(VIEW_ID_STICKY_HEADER).
50 class ScrollContentsView : public views::View {
51 public:
52 ScrollContentsView()
53 : box_layout_(new views::BoxLayout(
54 views::BoxLayout::kVertical,
55 0,
56 0,
57 UseMd() ? 0 : kContentsBetweenChildSpacingNonMd)) {
58 SetLayoutManager(box_layout_);
59 }
60 ~ScrollContentsView() override {}
61
62 protected:
63 // views::View:
64 void OnBoundsChanged(const gfx::Rect& previous_bounds) override {
65 PositionHeaderRows();
66 }
67
68 void PaintChildren(const ui::PaintContext& context) override {
69 views::View::PaintChildren(context);
70 bool did_draw_shadow = false;
71 // Paint header row separators.
72 for (auto& header : headers_)
73 did_draw_shadow = PaintDelineation(header, context) || did_draw_shadow;
74
75 // Draw a shadow at the top of the viewport when scrolled, but only if a
76 // header didn't already draw one. Overlap the shadow with the separator
77 // that's below the header view so we don't get both a separator and a full
78 // shadow.
79 if (y() != 0 && !did_draw_shadow)
80 DrawShadow(context, gfx::Rect(0, 0, width(), -y() - kSeparatorWidth));
81 }
82
83 void Layout() override {
84 views::View::Layout();
85 headers_.clear();
86 for (int i = 0; i < child_count(); ++i) {
87 views::View* view = child_at(i);
88 if (view->id() == VIEW_ID_STICKY_HEADER)
89 headers_.emplace_back(view);
90 }
91 PositionHeaderRows();
92 }
93
94 View::Views GetChildrenInZOrder() override {
95 View::Views children;
96 // Iterate over regular children and later over the sticky headers to keep
97 // the sticky headers above in Z-order.
98 for (int i = 0; i < child_count(); ++i) {
99 if (child_at(i)->id() != VIEW_ID_STICKY_HEADER)
100 children.push_back(child_at(i));
101 }
102 for (int i = 0; i < child_count(); ++i) {
103 if (child_at(i)->id() == VIEW_ID_STICKY_HEADER)
104 children.push_back(child_at(i));
105 }
106 DCHECK_EQ(child_count(), static_cast<int>(children.size()));
107 return children;
108 }
109
110 void ViewHierarchyChanged(
111 const ViewHierarchyChangedDetails& details) override {
112 if (!details.is_add && details.parent == this) {
113 headers_.erase(std::remove_if(headers_.begin(), headers_.end(),
114 [details](const Header& header) {
115 return header.view == details.child;
116 }),
117 headers_.end());
118 } else if (details.is_add && details.parent == this &&
119 details.child == child_at(0)) {
120 // We always want padding on the bottom of the scroll contents.
121 // We only want padding on the top of the scroll contents if the first
122 // child is not a header (in that case, the padding is built into the
123 // header).
124 DCHECK_EQ(box_layout_, GetLayoutManager());
125 box_layout_->set_inside_border_insets(
126 gfx::Insets(details.child->id() == VIEW_ID_STICKY_HEADER
127 ? 0
128 : kMenuSeparatorVerticalPadding,
129 0, kMenuSeparatorVerticalPadding, 0));
130 }
131 }
132
133 private:
134 const int kShadowOffsetY = 2;
135 const int kShadowBlur = 2;
136 // TODO(fukino): Remove this constant once we stop maintaining pre-MD design.
137 // crbug.com/614453.
138 const int kContentsBetweenChildSpacingNonMd = 1;
139
140 // A structure that keeps the original offset of each header between the
141 // calls to Layout() to allow keeping track of which view should be sticky.
142 struct Header {
143 explicit Header(views::View* view)
144 : view(view), natural_offset(view->y()), draw_separator_below(false) {}
145
146 // A header View that can be decorated as sticky.
147 views::View* view;
148
149 // Offset from the top of ScrollContentsView to |view|'s original vertical
150 // position.
151 int natural_offset;
152
153 // True when a separator needs to be painted below the header when another
154 // header is pushing |this| header up.
155 bool draw_separator_below;
156 };
157
158 // Adjusts y-position of header rows allowing one or two rows to stick to the
159 // top of the visible viewport.
160 void PositionHeaderRows() {
161 const int scroll_offset = -y();
162 Header* previous_header = nullptr;
163 for (auto& header : base::Reversed(headers_)) {
164 views::View* header_view = header.view;
165 bool draw_separator_below = false;
166 if (header.natural_offset >= scroll_offset) {
167 previous_header = &header;
168 header_view->SetY(header.natural_offset);
169 } else {
170 if (previous_header &&
171 previous_header->view->y() <=
172 scroll_offset + header_view->height()) {
173 // Lower header displacing the header above.
174 draw_separator_below = true;
175 header_view->SetY(previous_header->view->y() - header_view->height());
176 } else {
177 // A header becomes sticky.
178 header_view->SetY(scroll_offset);
179 header_view->Layout();
180 header_view->SchedulePaint();
181 }
182 }
183 if (header.draw_separator_below != draw_separator_below) {
184 header.draw_separator_below = draw_separator_below;
185 TrayPopupUtils::ShowStickyHeaderSeparator(header_view,
186 draw_separator_below);
187 }
188 if (header.natural_offset < scroll_offset)
189 break;
190 }
191 }
192
193 // Paints a separator for a header view. The separator can be a horizontal
194 // rule or a horizontal shadow, depending on whether the header is sticking to
195 // the top of the scroll viewport. The return value indicates whether a shadow
196 // was drawn.
197 bool PaintDelineation(const Header& header, const ui::PaintContext& context) {
198 const View* view = header.view;
199
200 // If the header is where it normally belongs or If the header is pushed by
201 // a header directly below it, draw nothing.
202 if (view->y() == header.natural_offset || header.draw_separator_below)
203 return false;
204
205 // Otherwise, draw a shadow below.
206 DrawShadow(context,
207 gfx::Rect(0, 0, view->width(), view->bounds().bottom()));
208 return true;
209 }
210
211 // Draws a drop shadow below |shadowed_area|.
212 void DrawShadow(const ui::PaintContext& context,
213 const gfx::Rect& shadowed_area) {
214 ui::PaintRecorder recorder(context, size());
215 gfx::Canvas* canvas = recorder.canvas();
216 cc::PaintFlags flags;
217 gfx::ShadowValues shadow;
218 shadow.emplace_back(gfx::Vector2d(0, kShadowOffsetY), kShadowBlur,
219 kMenuSeparatorColor);
220 flags.setLooper(gfx::CreateShadowDrawLooperCorrectBlur(shadow));
221 flags.setAntiAlias(true);
222 canvas->ClipRect(shadowed_area, SkClipOp::kDifference);
223 canvas->DrawRect(shadowed_area, flags);
224 }
225
226 views::BoxLayout* box_layout_;
227
228 // Header child views that stick to the top of visible viewport when scrolled.
229 std::vector<Header> headers_;
230
231 DISALLOW_COPY_AND_ASSIGN(ScrollContentsView);
232 };
233
234 // Constants for the title row in material design.
235 const int kTitleRowVerticalPadding = 4;
236 const int kTitleRowProgressBarHeight = 2;
237 const int kTitleRowPaddingTop = kTitleRowVerticalPadding;
238 const int kTitleRowPaddingBottom =
239 kTitleRowVerticalPadding - kTitleRowProgressBarHeight;
240
241 class ScrollSeparator : public views::View {
242 public:
243 ScrollSeparator() {}
244
245 ~ScrollSeparator() override {}
246
247 private:
248 // views::View:
249 void OnPaint(gfx::Canvas* canvas) override {
250 canvas->FillRect(gfx::Rect(0, height() / 2, width(), 1), kBorderLightColor);
251 }
252 gfx::Size GetPreferredSize() const override {
253 return gfx::Size(1, kTrayPopupScrollSeparatorHeight);
254 }
255
256 DISALLOW_COPY_AND_ASSIGN(ScrollSeparator);
257 };
258
259 } // namespace
260
261 class ScrollBorder : public views::Border {
262 public:
263 ScrollBorder() {}
264 ~ScrollBorder() override {}
265
266 void set_visible(bool visible) { visible_ = visible; }
267
268 private:
269 // views::Border:
270 void Paint(const views::View& view, gfx::Canvas* canvas) override {
271 if (!visible_)
272 return;
273 canvas->FillRect(gfx::Rect(0, view.height() - 1, view.width(), 1),
274 kBorderLightColor);
275 }
276
277 gfx::Insets GetInsets() const override { return gfx::Insets(0, 0, 1, 0); }
278
279 gfx::Size GetMinimumSize() const override { return gfx::Size(0, 1); }
280
281 bool visible_ = false;
282
283 DISALLOW_COPY_AND_ASSIGN(ScrollBorder);
284 };
285
286 TrayDetailsView::TrayDetailsView(SystemTrayItem* owner)
287 : owner_(owner),
288 box_layout_(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)),
289 title_row_(nullptr),
290 scroller_(nullptr),
291 scroll_content_(nullptr),
292 progress_bar_(nullptr),
293 scroll_border_(nullptr),
294 tri_view_(nullptr),
295 back_button_(nullptr) {
296 SetLayoutManager(box_layout_);
297 set_background(views::Background::CreateSolidBackground(kBackgroundColor));
298 }
299
300 TrayDetailsView::~TrayDetailsView() {}
301
302 void TrayDetailsView::OnViewClicked(views::View* sender) {
303 if (!UseMd() && title_row_ && sender == title_row_->content()) {
304 TransitionToDefaultView();
305 return;
306 }
307
308 HandleViewClicked(sender);
309 }
310
311 void TrayDetailsView::ButtonPressed(views::Button* sender,
312 const ui::Event& event) {
313 if (UseMd() && sender == back_button_) {
314 TransitionToDefaultView();
315 return;
316 }
317
318 HandleButtonPressed(sender, event);
319 }
320
321 void TrayDetailsView::CreateTitleRow(int string_id) {
322 DCHECK(!tri_view_);
323 DCHECK(!title_row_);
324
325 if (UseMd()) {
326 tri_view_ = TrayPopupUtils::CreateDefaultRowView();
327
328 back_button_ = CreateBackButton();
329 tri_view_->AddView(TriView::Container::START, back_button_);
330
331 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
332 auto* label = TrayPopupUtils::CreateDefaultLabel();
333 label->SetText(rb.GetLocalizedString(string_id));
334 TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::TITLE);
335 style.SetupLabel(label);
336 tri_view_->AddView(TriView::Container::CENTER, label);
337
338 tri_view_->SetContainerVisible(TriView::Container::END, false);
339
340 tri_view_->SetBorder(views::CreateEmptyBorder(kTitleRowPaddingTop, 0,
341 kTitleRowPaddingBottom, 0));
342 AddChildViewAt(tri_view_, 0);
343 views::Separator* separator = new views::Separator();
344 separator->SetColor(kMenuSeparatorColor);
345 separator->SetBorder(views::CreateEmptyBorder(
346 kTitleRowProgressBarHeight - views::Separator::kThickness, 0, 0, 0));
347 AddChildViewAt(separator, kTitleRowSeparatorIndex);
348 } else {
349 title_row_ = new SpecialPopupRow();
350 title_row_->SetTextLabel(string_id, this);
351 AddChildViewAt(title_row_, child_count());
352 }
353
354 CreateExtraTitleRowButtons();
355 Layout();
356 }
357
358 void TrayDetailsView::CreateScrollableList() {
359 DCHECK(!scroller_);
360 scroll_content_ = new ScrollContentsView();
361 scroller_ = new views::ScrollView;
362 scroller_->SetContents(scroll_content_);
363 // Make the |scroller_| have a layer to clip |scroll_content_|'s children.
364 // TODO(varkha): Make the sticky rows work with EnableViewPortLayer().
365 scroller_->SetPaintToLayer();
366 scroller_->set_background(
367 views::Background::CreateSolidBackground(kBackgroundColor));
368 scroller_->layer()->SetMasksToBounds(true);
369
370 // Note: |scroller_| takes ownership of |scroll_border_|.
371 if (!UseMd()) {
372 // In MD, the scroller is always the last thing, so this border is
373 // unnecessary and reserves extra space we don't want.
374 scroll_border_ = new ScrollBorder;
375 scroller_->SetBorder(std::unique_ptr<views::Border>(scroll_border_));
376 }
377
378 AddChildView(scroller_);
379 box_layout_->SetFlexForView(scroller_, 1);
380 }
381
382 void TrayDetailsView::AddScrollSeparator() {
383 DCHECK(scroll_content_);
384 // Do not draw the separator if it is the very first item
385 // in the scrollable list.
386 if (scroll_content_->has_children())
387 scroll_content_->AddChildView(new ScrollSeparator);
388 }
389
390 void TrayDetailsView::Reset() {
391 RemoveAllChildViews(true);
392 title_row_ = nullptr;
393 scroller_ = nullptr;
394 scroll_content_ = nullptr;
395 progress_bar_ = nullptr;
396 back_button_ = nullptr;
397 tri_view_ = nullptr;
398 }
399
400 void TrayDetailsView::ShowProgress(double value, bool visible) {
401 DCHECK(UseMd());
402 DCHECK(tri_view_);
403 if (!progress_bar_) {
404 progress_bar_ = new views::ProgressBar(kTitleRowProgressBarHeight);
405 progress_bar_->SetVisible(false);
406 AddChildViewAt(progress_bar_, kTitleRowSeparatorIndex + 1);
407 }
408
409 progress_bar_->SetValue(value);
410 progress_bar_->SetVisible(visible);
411 child_at(kTitleRowSeparatorIndex)->SetVisible(!visible);
412 }
413
414 views::CustomButton* TrayDetailsView::CreateSettingsButton(
415 LoginStatus status,
416 int setting_accessible_name_id) {
417 DCHECK(UseMd());
418 SystemMenuButton* button =
419 new SystemMenuButton(this, TrayPopupInkDropStyle::HOST_CENTERED,
420 kSystemMenuSettingsIcon, setting_accessible_name_id);
421 if (!TrayPopupUtils::CanOpenWebUISettings(status))
422 button->SetEnabled(false);
423 return button;
424 }
425
426 views::CustomButton* TrayDetailsView::CreateHelpButton(LoginStatus status) {
427 DCHECK(UseMd());
428 SystemMenuButton* button =
429 new SystemMenuButton(this, TrayPopupInkDropStyle::HOST_CENTERED,
430 kSystemMenuHelpIcon, IDS_ASH_STATUS_TRAY_HELP);
431 if (!TrayPopupUtils::CanOpenWebUISettings(status))
432 button->SetEnabled(false);
433 return button;
434 }
435
436 void TrayDetailsView::HandleViewClicked(views::View* view) {
437 NOTREACHED();
438 }
439
440 void TrayDetailsView::HandleButtonPressed(views::Button* sender,
441 const ui::Event& event) {
442 NOTREACHED();
443 }
444
445 void TrayDetailsView::CreateExtraTitleRowButtons() {}
446
447 void TrayDetailsView::TransitionToDefaultView() {
448 if (UseMd()) {
449 if (back_button_ && back_button_->HasFocus())
450 owner_->set_restore_focus(true);
451 } else {
452 if (title_row_ && title_row_->content() &&
453 title_row_->content()->HasFocus()) {
454 owner_->set_restore_focus(true);
455 }
456 DoTransitionToDefaultView();
457 return;
458 }
459
460 transition_delay_timer_.Start(
461 FROM_HERE,
462 base::TimeDelta::FromMilliseconds(kTrayDetailedViewTransitionDelayMs),
463 this, &TrayDetailsView::DoTransitionToDefaultView);
464 }
465
466 void TrayDetailsView::DoTransitionToDefaultView() {
467 // Cache pointer to owner in this function scope. TrayDetailsView will be
468 // deleted after called ShowDefaultView.
469 SystemTrayItem* owner = owner_;
470 owner->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
471 owner->set_restore_focus(false);
472 }
473
474 views::Button* TrayDetailsView::CreateBackButton() {
475 DCHECK(UseMd());
476 SystemMenuButton* button = new SystemMenuButton(
477 this, TrayPopupInkDropStyle::HOST_CENTERED, kSystemMenuArrowBackIcon,
478 IDS_ASH_STATUS_TRAY_PREVIOUS_MENU);
479 return button;
480 }
481
482 void TrayDetailsView::Layout() {
483 views::View::Layout();
484 if (scroller_ && !scroller_->is_bounded())
485 scroller_->ClipHeightTo(0, scroller_->height());
486 }
487
488 int TrayDetailsView::GetHeightForWidth(int width) const {
489 if (!UseMd() || bounds().IsEmpty())
490 return views::View::GetHeightForWidth(width);
491
492 // The height of the bubble that contains this detailed view is set to
493 // the preferred height of the default view, and that determines the
494 // initial height of |this|. Always request to stay the same height.
495 return height();
496 }
497
498 void TrayDetailsView::OnPaintBorder(gfx::Canvas* canvas) {
499 if (scroll_border_) {
500 int index = GetIndexOf(scroller_);
501 if (index < child_count() - 1 && child_at(index + 1) != title_row_)
502 scroll_border_->set_visible(true);
503 else
504 scroll_border_->set_visible(false);
505 }
506
507 views::View::OnPaintBorder(canvas);
508 }
509
510 } // namespace ash
OLDNEW
« no previous file with comments | « ash/common/system/tray/tray_details_view.h ('k') | ash/common/system/tray/tray_details_view_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698