| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/status_area_widget.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "ash/capture_mode/stop_recording_button_tray.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_switches.h" |
| #include "ash/constants/tray_background_view_catalog.h" |
| #include "ash/keyboard/ui/keyboard_ui_controller.h" |
| #include "ash/projector/projector_annotation_tray.h" |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/drag_handle.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_layout_manager.h" |
| #include "ash/shelf/shelf_widget.h" |
| #include "ash/shell.h" |
| #include "ash/system/accessibility/dictation_button_tray.h" |
| #include "ash/system/accessibility/select_to_speak/select_to_speak_tray.h" |
| #include "ash/system/eche/eche_tray.h" |
| #include "ash/system/focus_mode/focus_mode_tray.h" |
| #include "ash/system/holding_space/holding_space_tray.h" |
| #include "ash/system/ime_menu/ime_menu_tray.h" |
| #include "ash/system/media/media_tray.h" |
| #include "ash/system/notification_center/notification_center_tray.h" |
| #include "ash/system/overview/overview_button_tray.h" |
| #include "ash/system/palette/palette_tray.h" |
| #include "ash/system/phonehub/phone_hub_tray.h" |
| #include "ash/system/session/logout_button_tray.h" |
| #include "ash/system/status_area_animation_controller.h" |
| #include "ash/system/status_area_widget_delegate.h" |
| #include "ash/system/tray/status_area_overflow_button_tray.h" |
| #include "ash/system/tray/tray_background_view.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_container.h" |
| #include "ash/system/unified/date_tray.h" |
| #include "ash/system/unified/unified_system_tray.h" |
| #include "ash/system/unified/unified_system_tray_bubble.h" |
| #include "ash/system/video_conference/video_conference_tray.h" |
| #include "ash/system/virtual_keyboard/virtual_keyboard_tray.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "ash/wm_mode/wm_mode_button_tray.h" |
| #include "base/command_line.h" |
| #include "base/containers/adapters.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "chromeos/ash/services/assistant/public/cpp/features.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/message_center_types.h" |
| |
| namespace ash { |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // StatusAreaWidget |
| |
| StatusAreaWidget::StatusAreaWidget(aura::Window* status_container, Shelf* shelf) |
| : status_area_widget_delegate_(new StatusAreaWidgetDelegate(shelf)), |
| shelf_(shelf) { |
| DCHECK(status_container); |
| DCHECK(shelf); |
| views::Widget::InitParams params( |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| params.delegate = status_area_widget_delegate_.get(); |
| params.name = "StatusAreaWidget"; |
| params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.parent = status_container; |
| Init(std::move(params)); |
| set_focus_on_creation(false); |
| SetContentsView(status_area_widget_delegate_); |
| } |
| |
| void StatusAreaWidget::Initialize() { |
| DCHECK(!initialized_); |
| |
| // Create the child views, left to right. |
| overflow_button_tray_ = |
| AddTrayButton(std::make_unique<StatusAreaOverflowButtonTray>(shelf_)); |
| if (features::IsVideoConferenceEnabled()) { |
| video_conference_tray_ = |
| AddTrayButton(std::make_unique<VideoConferenceTray>(shelf_)); |
| } |
| if (features::IsFocusModeEnabled()) { |
| focus_mode_tray_ = AddTrayButton(std::make_unique<FocusModeTray>(shelf_)); |
| } |
| holding_space_tray_ = |
| AddTrayButton(std::make_unique<HoldingSpaceTray>(shelf_)); |
| logout_button_tray_ = |
| AddTrayButton(std::make_unique<LogoutButtonTray>(shelf_)); |
| dictation_button_tray_ = AddTrayButton(std::make_unique<DictationButtonTray>( |
| shelf_, TrayBackgroundViewCatalogName::kDictationStatusArea)); |
| select_to_speak_tray_ = AddTrayButton(std::make_unique<SelectToSpeakTray>( |
| shelf_, TrayBackgroundViewCatalogName::kSelectToSpeakStatusArea)); |
| ime_menu_tray_ = AddTrayButton(std::make_unique<ImeMenuTray>(shelf_)); |
| virtual_keyboard_tray_ = AddTrayButton(std::make_unique<VirtualKeyboardTray>( |
| shelf_, TrayBackgroundViewCatalogName::kVirtualKeyboardStatusArea)); |
| |
| stop_recording_button_tray_ = |
| AddTrayButton(std::make_unique<StopRecordingButtonTray>(shelf_)); |
| |
| projector_annotation_tray_ = |
| AddTrayButton(std::make_unique<ProjectorAnnotationTray>(shelf_)); |
| |
| palette_tray_ = AddTrayButton(std::make_unique<PaletteTray>(shelf_)); |
| |
| media_tray_ = AddTrayButton(std::make_unique<MediaTray>(shelf_)); |
| |
| if (features::IsEcheSWAEnabled()) { |
| eche_tray_ = AddTrayButton(std::make_unique<EcheTray>(shelf_)); |
| } |
| |
| if (features::IsPhoneHubEnabled()) { |
| phone_hub_tray_ = AddTrayButton(std::make_unique<PhoneHubTray>(shelf_)); |
| } |
| |
| if (features::IsWmModeEnabled()) { |
| wm_mode_button_tray_ = |
| AddTrayButton(std::make_unique<WmModeButtonTray>(shelf_)); |
| } |
| |
| notification_center_tray_ = |
| AddTrayButton(std::make_unique<NotificationCenterTray>(shelf_)); |
| notification_center_tray_->AddObserver(this); |
| animation_controller_ = std::make_unique<StatusAreaAnimationController>( |
| notification_center_tray()); |
| auto unified_system_tray = std::make_unique<UnifiedSystemTray>(shelf_); |
| unified_system_tray_ = unified_system_tray.get(); |
| date_tray_ = |
| AddTrayButton(std::make_unique<DateTray>(shelf_, unified_system_tray_)); |
| AddTrayButton(std::move(unified_system_tray)); |
| |
| overview_button_tray_ = |
| AddTrayButton(std::make_unique<OverviewButtonTray>(shelf_)); |
| |
| // Each tray_button's animation will be disabled for the life time of this |
| // local `animation_disablers`, which means the closures to enable the |
| // animation will be executed when it's out of this method (Initialize()) |
| // scope. |
| std::list<base::ScopedClosureRunner> animation_disablers; |
| |
| // Initialize after all trays have been created. |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->Initialize(); |
| animation_disablers.push_back(tray_button->DisableShowAnimation()); |
| } |
| |
| EnsureTrayOrder(); |
| |
| UpdateAfterLoginStatusChange( |
| Shell::Get()->session_controller()->login_status()); |
| UpdateLayout(/*animate=*/false); |
| |
| Shell::Get()->session_controller()->AddObserver(this); |
| |
| // NOTE: Container may be hidden depending on login/display state. |
| Show(); |
| |
| initialized_ = true; |
| } |
| |
| StatusAreaWidget::~StatusAreaWidget() { |
| Shell::Get()->session_controller()->RemoveObserver(this); |
| |
| // Resets `animation_controller_` before destroying |
| // `notification_center_tray_` so that we don't run into a UaF. |
| animation_controller_.reset(nullptr); |
| |
| // `TrayBubbleView` might be deleted after `StatusAreaWidget`, so we reset the |
| // pointer here to avoid dangling pointer. |
| open_shelf_pod_bubble_ = nullptr; |
| |
| status_area_widget_delegate_->Shutdown(); |
| } |
| |
| // static |
| StatusAreaWidget* StatusAreaWidget::ForWindow(aura::Window* window) { |
| return Shelf::ForWindow(window)->status_area_widget(); |
| } |
| |
| void StatusAreaWidget::UpdateAfterLoginStatusChange(LoginStatus login_status) { |
| if (login_status_ == login_status) { |
| return; |
| } |
| login_status_ = login_status; |
| |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->UpdateAfterLoginStatusChange(); |
| } |
| } |
| |
| void StatusAreaWidget::SetSystemTrayVisibility(bool visible) { |
| unified_system_tray_->SetVisiblePreferred(visible); |
| date_tray_->SetVisiblePreferred(visible); |
| notification_center_tray_->OnSystemTrayVisibilityChanged(visible); |
| |
| if (visible) { |
| Show(); |
| } else { |
| unified_system_tray_->CloseBubble(); |
| Hide(); |
| } |
| } |
| |
| void StatusAreaWidget::OnSessionStateChanged( |
| session_manager::SessionState state) { |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->UpdateBackground(); |
| } |
| } |
| |
| void StatusAreaWidget::UpdateCollapseState() { |
| collapse_state_ = CalculateCollapseState(); |
| |
| if (collapse_state_ == CollapseState::COLLAPSED) { |
| CalculateButtonVisibilityForCollapsedState(); |
| } else { |
| // All tray buttons are visible when the status area is not collapsible. |
| // This is the most common state. They are also all visible when the status |
| // area is expanded by the user. |
| overflow_button_tray_->SetVisiblePreferred(collapse_state_ == |
| CollapseState::EXPANDED); |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->set_show_when_collapsed(true); |
| tray_button->UpdateAfterStatusAreaCollapseChange(); |
| } |
| } |
| |
| status_area_widget_delegate_->OnStatusAreaCollapseStateChanged( |
| collapse_state_); |
| |
| bool overlap = |
| shelf_->shelf_widget()->GetDragHandle()->GetBoundsInScreen().Intersects( |
| status_area_widget_delegate_->GetBoundsInScreen()); |
| |
| if (collapse_state_ == CollapseState::EXPANDED && overlap) { |
| // Hide the drag handle if the status_area_widget_delegate_ overlaps |
| // expected drag handle bounds. Otherwise show the drag handle. |
| shelf_->shelf_widget()->GetDragHandle()->HideDragHandleNudge( |
| contextual_tooltip::DismissNudgeReason::kOther, |
| /*animate*/ false); |
| shelf_->shelf_widget()->GetDragHandle()->SetVisible(false); |
| } else if (collapse_state_ == CollapseState::COLLAPSED) { |
| shelf_->shelf_widget()->GetDragHandle()->SetVisible(true); |
| } |
| } |
| |
| void StatusAreaWidget::LogVisiblePodCountMetric() { |
| int visible_pod_count = 0; |
| for (ash::TrayBackgroundView* tray_button : tray_buttons_) { |
| switch (tray_button->catalog_name()) { |
| case TrayBackgroundViewCatalogName::kUnifiedSystem: |
| case TrayBackgroundViewCatalogName::kStatusAreaOverflowButton: |
| case TrayBackgroundViewCatalogName::kDateTray: |
| case TrayBackgroundViewCatalogName::kNotificationCenter: |
| // These pods always show, ignore them. |
| continue; |
| |
| case TrayBackgroundViewCatalogName::kSelectToSpeakAccessibilityWindow: |
| case TrayBackgroundViewCatalogName::kDictationAccesibilityWindow: |
| case TrayBackgroundViewCatalogName::kVirtualKeyboardAccessibilityWindow: |
| // These pods show in an unrelated menu. |
| continue; |
| |
| case TrayBackgroundViewCatalogName::kOverview: |
| case TrayBackgroundViewCatalogName::kTestCatalogName: |
| case TrayBackgroundViewCatalogName::kImeMenu: |
| case TrayBackgroundViewCatalogName::kHoldingSpace: |
| case TrayBackgroundViewCatalogName::kScreenCaptureStopRecording: |
| case TrayBackgroundViewCatalogName::kProjectorAnnotation: |
| case TrayBackgroundViewCatalogName::kDictationStatusArea: |
| case TrayBackgroundViewCatalogName::kSelectToSpeakStatusArea: |
| case TrayBackgroundViewCatalogName::kEche: |
| case TrayBackgroundViewCatalogName::kMediaPlayer: |
| case TrayBackgroundViewCatalogName::kPalette: |
| case TrayBackgroundViewCatalogName::kPhoneHub: |
| case TrayBackgroundViewCatalogName::kLogoutButton: |
| case TrayBackgroundViewCatalogName::kVirtualKeyboardStatusArea: |
| case TrayBackgroundViewCatalogName::kWmMode: |
| case TrayBackgroundViewCatalogName::kVideoConferenceTray: |
| case TrayBackgroundViewCatalogName::kFocusMode: |
| if (!tray_button->GetVisible()) { |
| continue; |
| } |
| visible_pod_count += 1; |
| break; |
| } |
| } |
| |
| if (display::Screen::GetScreen()->InTabletMode()) { |
| UMA_HISTOGRAM_COUNTS_100("ChromeOS.SystemTray.Tablet.ShelfPodCount", |
| visible_pod_count); |
| } else { |
| UMA_HISTOGRAM_COUNTS_100("ChromeOS.SystemTray.ShelfPodCount", |
| visible_pod_count); |
| } |
| } |
| |
| void StatusAreaWidget::CalculateTargetBounds() { |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->CalculateTargetBounds(); |
| } |
| status_area_widget_delegate_->CalculateTargetBounds(); |
| |
| gfx::Size status_size(status_area_widget_delegate_->GetTargetBounds().size()); |
| const gfx::Size shelf_size = shelf_->shelf_widget()->GetTargetBounds().size(); |
| const gfx::Point shelf_origin = |
| shelf_->shelf_widget()->GetTargetBounds().origin(); |
| |
| if (shelf_->IsHorizontalAlignment()) { |
| status_size.set_height(shelf_size.height()); |
| } else { |
| status_size.set_width(shelf_size.width()); |
| } |
| |
| gfx::Point status_origin = shelf_->SelectValueForShelfAlignment( |
| gfx::Point(0, 0), |
| gfx::Point(shelf_size.width() - status_size.width(), |
| shelf_size.height() - status_size.height()), |
| gfx::Point(0, shelf_size.height() - status_size.height())); |
| if (shelf_->IsHorizontalAlignment() && !base::i18n::IsRTL()) { |
| status_origin.set_x(shelf_size.width() - status_size.width()); |
| } |
| status_origin.Offset(shelf_origin.x(), shelf_origin.y()); |
| target_bounds_ = gfx::Rect(status_origin, status_size); |
| } |
| |
| gfx::Rect StatusAreaWidget::GetTargetBounds() const { |
| return target_bounds_; |
| } |
| |
| void StatusAreaWidget::UpdateLayout(bool animate) { |
| const LayoutInputs new_layout_inputs = GetLayoutInputs(); |
| if (layout_inputs_ == new_layout_inputs) { |
| return; |
| } |
| |
| if (!new_layout_inputs.should_animate) { |
| animate = false; |
| } |
| |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->UpdateLayout(); |
| } |
| status_area_widget_delegate_->UpdateLayout(animate); |
| |
| // Having a window which is visible but does not have an opacity is an |
| // illegal state. |
| ui::Layer* layer = GetNativeView()->layer(); |
| layer->SetOpacity(new_layout_inputs.opacity); |
| if (new_layout_inputs.opacity) { |
| ShowInactive(); |
| } else { |
| Hide(); |
| } |
| |
| ui::ScopedLayerAnimationSettings animation_setter(layer->GetAnimator()); |
| animation_setter.SetTransitionDuration( |
| animate ? ShelfConfig::Get()->shelf_animation_duration() |
| : base::Milliseconds(0)); |
| animation_setter.SetTweenType(gfx::Tween::EASE_OUT); |
| animation_setter.SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| SetBounds(new_layout_inputs.bounds); |
| layout_inputs_ = new_layout_inputs; |
| } |
| |
| void StatusAreaWidget::UpdateTargetBoundsForGesture(int shelf_position) { |
| if (shelf_->IsHorizontalAlignment()) { |
| target_bounds_.set_y(shelf_position); |
| } else { |
| target_bounds_.set_x(shelf_position); |
| } |
| } |
| |
| void StatusAreaWidget::HandleLocaleChange() { |
| // Here we force the layer's bounds to be updated for text direction (if |
| // needed). |
| status_area_widget_delegate_->RemoveAllChildViewsWithoutDeleting(); |
| |
| for (ash::TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->HandleLocaleChange(); |
| status_area_widget_delegate_->AddChildView(tray_button); |
| } |
| EnsureTrayOrder(); |
| } |
| |
| void StatusAreaWidget::CalculateButtonVisibilityForCollapsedState() { |
| if (!initialized_) { |
| return; |
| } |
| |
| DCHECK(collapse_state_ == CollapseState::COLLAPSED); |
| |
| bool force_collapsible = base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kAshForceStatusAreaCollapsible); |
| |
| // If |stop_recording_button_tray_| is visible, make some space in tray for |
| // it. |
| const int stop_recording_button_width = |
| stop_recording_button_tray_->visible_preferred() |
| ? stop_recording_button_tray_->GetPreferredSize().width() |
| : 0; |
| |
| // We update visibility of each tray button based on the available width. |
| const int available_width = GetCollapseAvailableWidth(force_collapsible) - |
| stop_recording_button_width; |
| |
| // First, reset all tray button to be hidden. |
| overflow_button_tray_->ResetStateToCollapsed(); |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->set_show_when_collapsed(false); |
| } |
| |
| // Iterate backwards making tray buttons visible until |available_width| is |
| // exceeded. |
| TrayBackgroundView* previous_tray = nullptr; |
| bool show_overflow_button = false; |
| int used_width = 0; |
| for (TrayBackgroundView* tray : base::Reversed(tray_buttons_)) { |
| // Skip non-enabled tray buttons. |
| if (!tray->visible_preferred()) { |
| continue; |
| } |
| // Skip |stop_recording_button_tray_| since it's always visible. |
| if (tray == stop_recording_button_tray_) { |
| continue; |
| } |
| |
| // Show overflow button once available width is exceeded. |
| int tray_width = tray->tray_container()->GetPreferredSize().width(); |
| if (used_width + tray_width > available_width) { |
| show_overflow_button = true; |
| |
| // Maybe remove the last tray button to make room for the overflow tray. |
| int overflow_button_width = |
| overflow_button_tray_->GetPreferredSize().width(); |
| if (previous_tray && |
| used_width + overflow_button_width > available_width) { |
| previous_tray->set_show_when_collapsed(false); |
| } |
| break; |
| } |
| |
| tray->set_show_when_collapsed(true); |
| previous_tray = tray; |
| used_width += tray_width; |
| } |
| |
| // Skip |stop_recording_button_tray_| so it's always visible. |
| if (stop_recording_button_tray_->visible_preferred()) { |
| stop_recording_button_tray_->set_show_when_collapsed(true); |
| } |
| |
| overflow_button_tray_->SetVisiblePreferred(show_overflow_button); |
| overflow_button_tray_->UpdateAfterStatusAreaCollapseChange(); |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->UpdateAfterStatusAreaCollapseChange(); |
| } |
| } |
| |
| void StatusAreaWidget::EnsureTrayOrder() { |
| if (projector_annotation_tray_) { |
| status_area_widget_delegate_->ReorderChildView(projector_annotation_tray_, |
| 1); |
| } |
| status_area_widget_delegate_->ReorderChildView( |
| stop_recording_button_tray_, projector_annotation_tray_ ? 2 : 1); |
| } |
| |
| StatusAreaWidget::CollapseState StatusAreaWidget::CalculateCollapseState() |
| const { |
| // The status area is only collapsible in tablet mode. Otherwise, we just show |
| // all trays. |
| if (!Shell::Get()->tablet_mode_controller()) { |
| return CollapseState::NOT_COLLAPSIBLE; |
| } |
| |
| // An update may occur during initialization of the shelf, so just skip it. |
| if (!initialized_) { |
| return CollapseState::NOT_COLLAPSIBLE; |
| } |
| |
| bool is_collapsible = display::Screen::GetScreen()->InTabletMode() && |
| ShelfConfig::Get()->is_in_app(); |
| |
| bool force_collapsible = base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kAshForceStatusAreaCollapsible); |
| |
| is_collapsible |= force_collapsible; |
| CollapseState state = CollapseState::NOT_COLLAPSIBLE; |
| if (is_collapsible) { |
| // Update the collapse state based on the previous overflow button state. |
| state = overflow_button_tray_->state() == |
| StatusAreaOverflowButtonTray::CLICK_TO_EXPAND |
| ? CollapseState::COLLAPSED |
| : CollapseState::EXPANDED; |
| } else { |
| state = CollapseState::NOT_COLLAPSIBLE; |
| } |
| |
| if (state == CollapseState::COLLAPSED) { |
| // We might not need to be collapsed, if there is enough space for all the |
| // buttons. |
| const int available_width = GetCollapseAvailableWidth(force_collapsible); |
| |
| int used_width = 0; |
| for (TrayBackgroundView* tray : base::Reversed(tray_buttons_)) { |
| // If we reach the final overflow tray button, then all the tray buttons |
| // fit and there is no need for a collapse state. |
| if (tray == overflow_button_tray_) { |
| return CollapseState::NOT_COLLAPSIBLE; |
| } |
| |
| // Skip non-enabled tray buttons. |
| if (!tray->visible_preferred()) { |
| continue; |
| } |
| int tray_width = tray->tray_container()->GetPreferredSize().width(); |
| if (used_width + tray_width > available_width) { |
| break; |
| } |
| |
| used_width += tray_width; |
| } |
| } |
| return state; |
| } |
| |
| TrayBackgroundView* StatusAreaWidget::GetSystemTrayAnchor() const { |
| // Use the target visibility of the layer instead of the visibility of the |
| // view because the view is still visible when fading away, but we do not want |
| // to anchor to this element in that case. |
| if (overview_button_tray_->layer()->GetTargetVisibility()) { |
| return overview_button_tray_; |
| } |
| |
| return unified_system_tray_; |
| } |
| |
| bool StatusAreaWidget::ShouldShowShelf() const { |
| // If it has main bubble, return true. |
| if (unified_system_tray_->IsBubbleShown()) { |
| return true; |
| } |
| |
| // If any tray is showing a context menu, the shelf should be visible. |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| if (tray_button->IsShowingMenu()) { |
| return true; |
| } |
| } |
| |
| // If it has a slider bubble, return false. |
| if (unified_system_tray_->IsSliderBubbleShown()) { |
| return false; |
| } |
| |
| // Some TrayBackgroundViews' cache their bubble, the shelf should only be |
| // forced to show if the bubble is visible, and we should not show the shelf |
| // for cached, hidden bubbles. |
| if (open_shelf_pod_bubble_) { |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| if (!tray_button->GetBubbleView()) { |
| continue; |
| } |
| |
| // Any tray bubble is showing, show shelf. |
| if (tray_button->GetBubbleView()->GetVisible()) { |
| return true; |
| } |
| |
| // Tray bubble view is not null and not visible, tray bubble is cached |
| // for hidden case. If the tray caches the view for hidden, we should |
| // hide self otherwise show shelf. |
| if (!tray_button->GetBubbleView()->GetVisible() && |
| !tray_button->CacheBubbleViewForHide()) { |
| return true; |
| } |
| } |
| } |
| |
| // No cases to show shelf, returns false to hide shelf. |
| return false; |
| } |
| |
| bool StatusAreaWidget::IsMessageBubbleShown() const { |
| return unified_system_tray_->IsBubbleShown(); |
| } |
| |
| void StatusAreaWidget::SchedulePaint() { |
| for (TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->SchedulePaint(); |
| } |
| } |
| |
| bool StatusAreaWidget::OnNativeWidgetActivationChanged(bool active) { |
| if (!Widget::OnNativeWidgetActivationChanged(active)) { |
| return false; |
| } |
| if (active) { |
| status_area_widget_delegate_->SetPaneFocusAndFocusDefault(); |
| } |
| return true; |
| } |
| |
| void StatusAreaWidget::SetOpenShelfPodBubble( |
| TrayBubbleView* open_shelf_pod_bubble) { |
| if (open_shelf_pod_bubble_ == open_shelf_pod_bubble) { |
| return; |
| } |
| |
| DCHECK(unified_system_tray_); |
| |
| if (open_shelf_pod_bubble) { |
| DCHECK(open_shelf_pod_bubble->GetBubbleType() == |
| TrayBubbleView::TrayBubbleType::kShelfPodBubble); |
| |
| // We only keep track of bubbles that are anchored to the status area |
| // widget. |
| DCHECK(open_shelf_pod_bubble->IsAnchoredToStatusArea()); |
| |
| // There should be only one shelf pod bubble open at a time, so we will |
| // close the current bubble for the new one to come in. |
| if (open_shelf_pod_bubble_) { |
| open_shelf_pod_bubble_->CloseBubbleView(); |
| open_shelf_pod_bubble_ = nullptr; |
| } |
| } |
| |
| open_shelf_pod_bubble_ = open_shelf_pod_bubble; |
| shelf()->shelf_layout_manager()->OnShelfTrayBubbleVisibilityChanged( |
| /*bubble_shown=*/open_shelf_pod_bubble_); |
| } |
| |
| void StatusAreaWidget::OnViewIsDeleting(views::View* observed_view) { |
| CHECK(observed_view == notification_center_tray_); |
| notification_center_tray_->RemoveObserver(this); |
| } |
| |
| void StatusAreaWidget::OnViewVisibilityChanged(views::View* observed_view, |
| views::View* starting_view) { |
| CHECK(observed_view == notification_center_tray_); |
| UpdateDateTrayRoundedCorners(); |
| } |
| |
| void StatusAreaWidget::OnMouseEvent(ui::MouseEvent* event) { |
| if (event->IsMouseWheelEvent()) { |
| ui::MouseWheelEvent* mouse_wheel_event = event->AsMouseWheelEvent(); |
| shelf_->ProcessMouseWheelEvent(mouse_wheel_event); |
| return; |
| } |
| |
| // Clicking anywhere except the virtual keyboard tray icon should hide the |
| // virtual keyboard. |
| gfx::Point location = event->location(); |
| views::View::ConvertPointFromWidget(virtual_keyboard_tray_, &location); |
| if (event->type() == ui::ET_MOUSE_PRESSED && |
| !virtual_keyboard_tray_->HitTestPoint(location)) { |
| keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser(); |
| } |
| views::Widget::OnMouseEvent(event); |
| } |
| |
| void StatusAreaWidget::OnGestureEvent(ui::GestureEvent* event) { |
| // Tapping anywhere except the virtual keyboard tray icon should hide the |
| // virtual keyboard. |
| gfx::Point location = event->location(); |
| views::View::ConvertPointFromWidget(virtual_keyboard_tray_, &location); |
| if (event->type() == ui::ET_GESTURE_TAP_DOWN && |
| !virtual_keyboard_tray_->HitTestPoint(location)) { |
| keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser(); |
| } |
| views::Widget::OnGestureEvent(event); |
| } |
| |
| void StatusAreaWidget::OnScrollEvent(ui::ScrollEvent* event) { |
| shelf_->ProcessScrollEvent(event); |
| if (!event->handled()) { |
| views::Widget::OnScrollEvent(event); |
| } |
| } |
| |
| template <typename TrayButtonT> |
| TrayButtonT* StatusAreaWidget::AddTrayButton( |
| std::unique_ptr<TrayButtonT> tray_button) { |
| tray_buttons_.push_back(tray_button.get()); |
| return status_area_widget_delegate_->AddChildView(std::move(tray_button)); |
| } |
| // Specialization declared here for use in tests. |
| template TrayBackgroundView* StatusAreaWidget::AddTrayButton< |
| TrayBackgroundView>(std::unique_ptr<TrayBackgroundView> tray_button); |
| |
| StatusAreaWidget::LayoutInputs StatusAreaWidget::GetLayoutInputs() const { |
| unsigned int child_visibility_bitmask = 0; |
| DCHECK(tray_buttons_.size() < |
| std::numeric_limits<decltype(child_visibility_bitmask)>::digits); |
| for (unsigned int i = 0; i < tray_buttons_.size(); ++i) { |
| if (tray_buttons_[i]->GetVisible()) { |
| child_visibility_bitmask |= 1 << i; |
| } |
| } |
| |
| bool should_animate = true; |
| |
| // Do not animate when tray items are added and removed (See |
| // crbug.com/1067199). |
| if (layout_inputs_) { |
| const bool is_horizontal_alignment = shelf_->IsHorizontalAlignment(); |
| const gfx::Rect current_bounds = layout_inputs_->bounds; |
| if ((is_horizontal_alignment && |
| current_bounds.width() != target_bounds_.width()) || |
| (!is_horizontal_alignment && |
| current_bounds.height() != target_bounds_.height())) { |
| should_animate = false; |
| } |
| } |
| |
| return {target_bounds_, CalculateCollapseState(), |
| shelf_->shelf_layout_manager()->GetOpacity(), |
| child_visibility_bitmask, should_animate}; |
| } |
| |
| void StatusAreaWidget::UpdateDateTrayRoundedCorners() { |
| if (!date_tray_) { |
| return; |
| } |
| |
| date_tray_->SetRoundedCornerBehavior( |
| notification_center_tray_->GetVisible() |
| ? TrayBackgroundView::RoundedCornerBehavior::kNotRounded |
| : TrayBackgroundView::RoundedCornerBehavior::kStartRounded); |
| } |
| |
| int StatusAreaWidget::GetCollapseAvailableWidth(bool force_collapsible) const { |
| const int shelf_width = |
| shelf_->shelf_widget()->GetClientAreaBoundsInScreen().width(); |
| |
| if (!force_collapsible) { |
| return shelf_width / 2 - kStatusAreaLeftPaddingForOverflow; |
| } |
| |
| int available_width = kStatusAreaForceCollapseAvailableWidth; |
| // Add the date tray width to the collapse available width. |
| DCHECK(date_tray_); |
| available_width += date_tray_->tray_container()->GetPreferredSize().width(); |
| |
| return available_width; |
| } |
| |
| void StatusAreaWidget::OnLockStateChanged(bool locked) { |
| for (ash::TrayBackgroundView* tray_button : tray_buttons_) { |
| tray_button->UpdateAfterLockStateChange(locked); |
| } |
| } |
| |
| } // namespace ash |