| // Copyright 2018 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/unified/unified_system_tray_view.h" |
| |
| #include "ash/public/cpp/app_list/app_list_features.h" |
| #include "ash/session/session_controller.h" |
| #include "ash/shell.h" |
| #include "ash/system/message_center/ash_message_center_lock_screen_controller.h" |
| #include "ash/system/message_center/unified_message_center_view.h" |
| #include "ash/system/tray/interacted_by_tap_recorder.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/unified/feature_pod_button.h" |
| #include "ash/system/unified/feature_pods_container_view.h" |
| #include "ash/system/unified/notification_hidden_view.h" |
| #include "ash/system/unified/top_shortcuts_view.h" |
| #include "ash/system/unified/unified_system_info_view.h" |
| #include "ash/system/unified/unified_system_tray_controller.h" |
| #include "ash/system/unified/unified_system_tray_model.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/scoped_canvas.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/public/cpp/message_center_constants.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/focus/focus_search.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/painter.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Border applied to SystemTrayContainer and DetailedViewContainer to iminate |
| // notification list scrolling under SystemTray part of UnifiedSystemTray. |
| // The border paints mock notification frame behind the top corners based on |
| // |rect_below_scroll|. |
| class TopCornerBorder : public views::Border { |
| public: |
| TopCornerBorder() = default; |
| |
| // views::Border: |
| void Paint(const views::View& view, gfx::Canvas* canvas) override { |
| if (rect_below_scroll_.IsEmpty()) |
| return; |
| |
| gfx::ScopedCanvas scoped(canvas); |
| |
| SkPath path; |
| path.addRoundRect(gfx::RectToSkRect(view.GetLocalBounds()), |
| SkIntToScalar(kUnifiedTrayCornerRadius), |
| SkIntToScalar(kUnifiedTrayCornerRadius)); |
| canvas->sk_canvas()->clipPath(path, SkClipOp::kDifference, true); |
| |
| cc::PaintFlags flags; |
| flags.setColor(message_center::kNotificationBackgroundColor); |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| flags.setAntiAlias(true); |
| |
| gfx::Rect rect = rect_below_scroll_; |
| rect.set_height(std::min(rect.height(), kUnifiedTrayCornerRadius * 2)); |
| rect.Inset(gfx::Insets(-kUnifiedTrayCornerRadius * 4, 0, 0, 0)); |
| canvas->DrawRoundRect(gfx::RectF(rect), kUnifiedTrayCornerRadius, flags); |
| } |
| |
| gfx::Insets GetInsets() const override { return gfx::Insets(); } |
| |
| gfx::Size GetMinimumSize() const override { return gfx::Size(); } |
| |
| void set_rect_below_scroll(const gfx::Rect& rect_below_scroll) { |
| rect_below_scroll_ = rect_below_scroll; |
| } |
| |
| private: |
| gfx::Rect rect_below_scroll_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TopCornerBorder); |
| }; |
| |
| class SystemTrayContainer : public views::View { |
| public: |
| SystemTrayContainer() { |
| SetLayoutManager( |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical)); |
| SetBackground(UnifiedSystemTrayView::CreateBackground()); |
| SetBorder(std::make_unique<TopCornerBorder>()); |
| } |
| |
| ~SystemTrayContainer() override = default; |
| |
| // views::View: |
| void ChildPreferredSizeChanged(views::View* child) override { |
| PreferredSizeChanged(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SystemTrayContainer); |
| }; |
| |
| class DetailedViewContainer : public views::View { |
| public: |
| DetailedViewContainer() { |
| SetBackground(UnifiedSystemTrayView::CreateBackground()); |
| SetBorder(std::make_unique<TopCornerBorder>()); |
| } |
| |
| ~DetailedViewContainer() override = default; |
| |
| // views::View: |
| void Layout() override { |
| for (int i = 0; i < child_count(); ++i) |
| child_at(i)->SetBoundsRect(GetContentsBounds()); |
| views::View::Layout(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(DetailedViewContainer); |
| }; |
| |
| } // namespace |
| |
| UnifiedSlidersContainerView::UnifiedSlidersContainerView( |
| bool initially_expanded) |
| : expanded_amount_(initially_expanded ? 1.0 : 0.0) { |
| SetVisible(initially_expanded); |
| } |
| |
| UnifiedSlidersContainerView::~UnifiedSlidersContainerView() = default; |
| |
| void UnifiedSlidersContainerView::SetExpandedAmount(double expanded_amount) { |
| DCHECK(0.0 <= expanded_amount && expanded_amount <= 1.0); |
| SetVisible(expanded_amount > 0.0); |
| expanded_amount_ = expanded_amount; |
| InvalidateLayout(); |
| UpdateOpacity(); |
| } |
| |
| int UnifiedSlidersContainerView::GetExpandedHeight() const { |
| int height = 0; |
| for (int i = 0; i < child_count(); ++i) |
| height += child_at(i)->GetHeightForWidth(kTrayMenuWidth); |
| return height; |
| } |
| |
| void UnifiedSlidersContainerView::Layout() { |
| int y = 0; |
| for (int i = 0; i < child_count(); ++i) { |
| views::View* child = child_at(i); |
| int height = child->GetHeightForWidth(kTrayMenuWidth); |
| child->SetBounds(0, y, kTrayMenuWidth, height); |
| y += height; |
| } |
| } |
| |
| gfx::Size UnifiedSlidersContainerView::CalculatePreferredSize() const { |
| return gfx::Size(kTrayMenuWidth, GetExpandedHeight() * expanded_amount_); |
| } |
| |
| void UnifiedSlidersContainerView::UpdateOpacity() { |
| const int height = GetPreferredSize().height(); |
| for (int i = 0; i < child_count(); ++i) { |
| views::View* child = child_at(i); |
| double opacity = 1.0; |
| if (child->y() > height) { |
| opacity = 0.0; |
| } else if (child->bounds().bottom() < height) { |
| opacity = 1.0; |
| } else { |
| const double ratio = |
| static_cast<double>(height - child->y()) / child->height(); |
| // TODO(tetsui): Confirm the animation curve with UX. |
| opacity = std::max(0., 2. * ratio - 1.); |
| } |
| child->layer()->SetOpacity(opacity); |
| } |
| } |
| |
| // FocusSearch whose purpose is to start focus traversal from the top of |
| // SystemTrayContainer. |
| class UnifiedSystemTrayView::FocusSearch : public views::FocusSearch { |
| public: |
| explicit FocusSearch(UnifiedSystemTrayView* view) |
| : views::FocusSearch(view, false, false), view_(view) {} |
| ~FocusSearch() override = default; |
| |
| views::View* FindNextFocusableView( |
| views::View* starting_view, |
| FocusSearch::SearchDirection search_direction, |
| FocusSearch::TraversalDirection traversal_direction, |
| FocusSearch::StartingViewPolicy check_starting_view, |
| FocusSearch::AnchoredDialogPolicy can_go_into_anchored_dialog, |
| views::FocusTraversable** focus_traversable, |
| views::View** focus_traversable_view) override { |
| // Initial view that is focused when first time Tab or Shift-Tab is pressed. |
| views::View* default_start_view = |
| search_direction == FocusSearch::SearchDirection::kForwards |
| ? view_->system_tray_container_ |
| : view_->notification_hidden_view_; |
| return views::FocusSearch::FindNextFocusableView( |
| starting_view ? starting_view : default_start_view, search_direction, |
| traversal_direction, |
| starting_view ? check_starting_view |
| : StartingViewPolicy::kCheckStartingView, |
| can_go_into_anchored_dialog, focus_traversable, focus_traversable_view); |
| } |
| |
| private: |
| UnifiedSystemTrayView* const view_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FocusSearch); |
| }; |
| |
| UnifiedSystemTrayView::UnifiedSystemTrayView( |
| UnifiedSystemTrayController* controller, |
| bool initially_expanded) |
| : expanded_amount_(initially_expanded ? 1.0 : 0.0), |
| controller_(controller), |
| notification_hidden_view_(new NotificationHiddenView()), |
| top_shortcuts_view_(new TopShortcutsView(controller_)), |
| feature_pods_container_(new FeaturePodsContainerView(initially_expanded)), |
| sliders_container_(new UnifiedSlidersContainerView(initially_expanded)), |
| system_info_view_(new UnifiedSystemInfoView(controller_)), |
| system_tray_container_(new SystemTrayContainer()), |
| detailed_view_container_(new DetailedViewContainer()), |
| message_center_view_( |
| new UnifiedMessageCenterView(this, controller->model())), |
| focus_search_(std::make_unique<FocusSearch>(this)), |
| interacted_by_tap_recorder_( |
| std::make_unique<InteractedByTapRecorder>(this)) { |
| DCHECK(controller_); |
| |
| auto* layout = SetLayoutManager( |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical)); |
| |
| SetBackground(CreateBackground()); |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| |
| SessionController* session_controller = Shell::Get()->session_controller(); |
| |
| AddChildView(message_center_view_); |
| layout->SetFlexForView(message_center_view_, 1); |
| |
| notification_hidden_view_->SetVisible( |
| session_controller->GetUserSession(0) && |
| session_controller->IsScreenLocked() && |
| !AshMessageCenterLockScreenController::IsEnabled()); |
| AddChildView(notification_hidden_view_); |
| |
| AddChildView(system_tray_container_); |
| |
| system_tray_container_->AddChildView(top_shortcuts_view_); |
| system_tray_container_->AddChildView(feature_pods_container_); |
| system_tray_container_->AddChildView(sliders_container_); |
| system_tray_container_->AddChildView(system_info_view_); |
| |
| detailed_view_container_->SetVisible(false); |
| AddChildView(detailed_view_container_); |
| |
| // UnifiedSystemTrayView::FocusSearch makes focus traversal start from |
| // |system_tray_container_|, but we have to complete the cycle by setting |
| // |message_center_view_| next to |detailed_view_container_|. |
| // Also, SetNextFocusableView does not support loop as mentioned in the doc, |
| // we have to set null to |notification_hidden_view_|. |
| notification_hidden_view_->SetNextFocusableView(nullptr); |
| detailed_view_container_->SetNextFocusableView(message_center_view_); |
| |
| top_shortcuts_view_->SetExpandedAmount(expanded_amount_); |
| } |
| |
| UnifiedSystemTrayView::~UnifiedSystemTrayView() = default; |
| |
| void UnifiedSystemTrayView::SetMaxHeight(int max_height) { |
| message_center_view_->SetMaxHeight(max_height); |
| } |
| |
| void UnifiedSystemTrayView::AddFeaturePodButton(FeaturePodButton* button) { |
| feature_pods_container_->AddChildView(button); |
| } |
| |
| void UnifiedSystemTrayView::AddSliderView(views::View* slider_view) { |
| slider_view->SetPaintToLayer(); |
| slider_view->layer()->SetFillsBoundsOpaquely(false); |
| sliders_container_->AddChildView(slider_view); |
| } |
| |
| void UnifiedSystemTrayView::SetDetailedView(views::View* detailed_view) { |
| auto system_tray_size = system_tray_container_->GetPreferredSize(); |
| system_tray_container_->SetVisible(false); |
| |
| detailed_view_container_->RemoveAllChildViews(true /* delete_children */); |
| detailed_view_container_->AddChildView(detailed_view); |
| detailed_view_container_->SetVisible(true); |
| detailed_view_container_->SetPreferredSize(system_tray_size); |
| detailed_view->InvalidateLayout(); |
| Layout(); |
| } |
| |
| void UnifiedSystemTrayView::ResetDetailedView() { |
| detailed_view_container_->RemoveAllChildViews(true /* delete_children */); |
| detailed_view_container_->SetVisible(false); |
| system_tray_container_->SetVisible(true); |
| sliders_container_->UpdateOpacity(); |
| PreferredSizeChanged(); |
| Layout(); |
| } |
| |
| void UnifiedSystemTrayView::SaveFeaturePodFocus() { |
| feature_pods_container_->SaveFocus(); |
| } |
| |
| void UnifiedSystemTrayView::RestoreFeaturePodFocus() { |
| feature_pods_container_->RestoreFocus(); |
| } |
| |
| void UnifiedSystemTrayView::SetExpandedAmount(double expanded_amount) { |
| DCHECK(0.0 <= expanded_amount && expanded_amount <= 1.0); |
| expanded_amount_ = expanded_amount; |
| |
| top_shortcuts_view_->SetExpandedAmount(expanded_amount); |
| feature_pods_container_->SetExpandedAmount(expanded_amount); |
| sliders_container_->SetExpandedAmount(expanded_amount); |
| |
| if (!IsTransformEnabled()) { |
| PreferredSizeChanged(); |
| // It is possible that the ratio between |message_center_view_| and others |
| // can change while the bubble size remain unchanged. |
| Layout(); |
| return; |
| } |
| |
| if (height() != GetExpandedHeight()) |
| PreferredSizeChanged(); |
| Layout(); |
| } |
| |
| int UnifiedSystemTrayView::GetExpandedHeight() const { |
| return (notification_hidden_view_->visible() |
| ? notification_hidden_view_->GetPreferredSize().height() |
| : 0) + |
| top_shortcuts_view_->GetPreferredSize().height() + |
| feature_pods_container_->GetExpandedHeight() + |
| sliders_container_->GetExpandedHeight() + |
| system_info_view_->GetPreferredSize().height(); |
| } |
| |
| int UnifiedSystemTrayView::GetCurrentHeight() const { |
| return GetPreferredSize().height(); |
| } |
| |
| bool UnifiedSystemTrayView::IsTransformEnabled() const { |
| // TODO(tetsui): Support animation by transform even when |
| // UnifiedMessageCenterview is visible. |
| return expanded_amount_ != 0.0 && expanded_amount_ != 1.0 && |
| !message_center_view_->visible(); |
| } |
| |
| void UnifiedSystemTrayView::SetNotificationRectBelowScroll( |
| const gfx::Rect& rect_below_scroll) { |
| static_cast<TopCornerBorder*>(system_tray_container_->border()) |
| ->set_rect_below_scroll(rect_below_scroll); |
| static_cast<TopCornerBorder*>(detailed_view_container_->border()) |
| ->set_rect_below_scroll(rect_below_scroll); |
| SchedulePaint(); |
| } |
| |
| // static |
| std::unique_ptr<views::Background> UnifiedSystemTrayView::CreateBackground() { |
| return views::CreateBackgroundFromPainter( |
| views::Painter::CreateSolidRoundRectPainter( |
| app_list_features::IsBackgroundBlurEnabled() |
| ? kUnifiedMenuBackgroundColorWithBlur |
| : kUnifiedMenuBackgroundColor, |
| kUnifiedTrayCornerRadius)); |
| } |
| |
| void UnifiedSystemTrayView::OnGestureEvent(ui::GestureEvent* event) { |
| gfx::Point screen_location = event->location(); |
| ConvertPointToScreen(this, &screen_location); |
| |
| switch (event->type()) { |
| case ui::ET_GESTURE_SCROLL_BEGIN: |
| controller_->BeginDrag(screen_location); |
| event->SetHandled(); |
| break; |
| case ui::ET_GESTURE_SCROLL_UPDATE: |
| controller_->UpdateDrag(screen_location); |
| event->SetHandled(); |
| break; |
| case ui::ET_GESTURE_END: |
| controller_->EndDrag(screen_location); |
| event->SetHandled(); |
| break; |
| case ui::ET_SCROLL_FLING_START: |
| controller_->Fling(event->details().velocity_y()); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void UnifiedSystemTrayView::ChildPreferredSizeChanged(views::View* child) { |
| // The size change is not caused by SetExpandedAmount(), because they don't |
| // trigger PreferredSizeChanged(). |
| PreferredSizeChanged(); |
| } |
| |
| views::FocusTraversable* UnifiedSystemTrayView::GetFocusTraversable() { |
| return this; |
| } |
| |
| views::FocusSearch* UnifiedSystemTrayView::GetFocusSearch() { |
| return focus_search_.get(); |
| } |
| |
| views::FocusTraversable* UnifiedSystemTrayView::GetFocusTraversableParent() { |
| return nullptr; |
| } |
| |
| views::View* UnifiedSystemTrayView::GetFocusTraversableParentView() { |
| return this; |
| } |
| |
| } // namespace ash |