| // Copyright 2014 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/shelf/home_button.h" |
| |
| #include <math.h> // std::ceil |
| |
| #include <memory> |
| |
| #include "ash/app_list/app_list_controller_impl.h" |
| #include "ash/app_list/app_list_model_provider.h" |
| #include "ash/app_list/model/app_list_item.h" |
| #include "ash/app_list/model/app_list_model.h" |
| #include "ash/app_list/quick_app_access_model.h" |
| #include "ash/ash_element_identifiers.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_switches.h" |
| #include "ash/public/cpp/ash_typography.h" |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ash/public/cpp/shelf_types.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_control_button.h" |
| #include "ash/shelf/shelf_focus_cycler.h" |
| #include "ash/shelf/shelf_navigation_widget.h" |
| #include "ash/shelf/shelf_view.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/ash_color_id.h" |
| #include "ash/style/ash_color_provider.h" |
| #include "ash/style/typography.h" |
| #include "ash/user_education/user_education_class_properties.h" |
| #include "base/check_op.h" |
| #include "base/i18n/rtl.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/time/time.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "chromeos/strings/grit/chromeos_strings.h" |
| #include "home_button.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/chromeos/styles/cros_tokens_color_mappings.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/ash/keyboard_capability.h" |
| #include "ui/events/devices/device_data_manager.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/transform_util.h" |
| #include "ui/gfx/scoped_canvas.h" |
| #include "ui/views/animation/animation_builder.h" |
| #include "ui/views/animation/flood_fill_ink_drop_ripple.h" |
| #include "ui/views/animation/ink_drop.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/button/button_controller.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/highlight_border.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/style/typography.h" |
| #include "ui/views/view.h" |
| #include "ui/views/view_class_properties.h" |
| |
| namespace ash { |
| namespace { |
| |
| // The space between the home button and quick app. |
| constexpr int kQuickAppStartMargin = 8; |
| |
| constexpr uint8_t kAssistantInvisibleAlpha = 138; // 54% alpha |
| |
| // Nudge animation constants |
| |
| // The offsets that the home button moves up/down from the original home button |
| // position at each stage of nudge animation. |
| constexpr int kAnimationBounceUpOffset = 12; |
| constexpr int kAnimationBounceDownOffset = 3; |
| |
| // Constants used on `nudge_ripple_layer_` animation. |
| constexpr base::TimeDelta kHomeButtonAnimationDuration = |
| base::Milliseconds(250); |
| constexpr base::TimeDelta kRippleAnimationDuration = base::Milliseconds(2000); |
| |
| // Constants used on `nudge_label_` animation. |
| // |
| // The duration of the showing/hiding animation for nudge label. |
| constexpr base::TimeDelta kNudgeLabelTransitionOnDuration = |
| base::Milliseconds(300); |
| constexpr base::TimeDelta kNudgeLabelTransitionOffDuration = |
| base::Milliseconds(500); |
| |
| // The duration of the fade out animation that animates `nudge_label_` when |
| // users click on the home button while `nudge_label_` is showing. |
| constexpr base::TimeDelta kNudgeLabelFadeOutDuration = base::Milliseconds(100); |
| |
| // The duration that the nudge label is shown. |
| constexpr base::TimeDelta kNudgeLabelShowingDuration = base::Seconds(6); |
| |
| // The minimum space we want to keep between the `nudge_label_` and the first |
| // app in hotseat. Used to determine if `nudge_label_` should be shown. |
| constexpr int kMinSpaceBetweenNudgeLabelAndHotseat = 24; |
| |
| // The durations and delay used for animating in the `quick_app_button_` and |
| // `expandable_container_`. |
| constexpr base::TimeDelta kQuickAppSlideSlideInDuration = |
| base::Milliseconds(200); |
| constexpr base::TimeDelta kQuickAppButtonFadeInDelay = base::Milliseconds(50); |
| constexpr base::TimeDelta kQuickAppButtonFadeInDuration = |
| base::Milliseconds(50); |
| constexpr base::TimeDelta kQuickAppContainerFadeInDuration = |
| base::Milliseconds(100); |
| |
| // The durations used for animating out the `quick_app_button_` and |
| // `expandable_container_`. |
| constexpr base::TimeDelta kQuickAppSlideOutDuration = base::Milliseconds(200); |
| constexpr base::TimeDelta kQuickAppFadeOutDuration = base::Milliseconds(100); |
| |
| } // namespace |
| |
| class HomeButton::ButtonImageView : public views::View { |
| METADATA_HEADER(ButtonImageView, views::View) |
| |
| public: |
| explicit ButtonImageView(HomeButtonController* button_controller) |
| : button_controller_(button_controller) { |
| SetCanProcessEventsWithinSubtree(false); |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| UpdateBackground(); |
| UpdateIconImageModel(); |
| } |
| |
| ButtonImageView(const ButtonImageView&) = delete; |
| ButtonImageView& operator=(const ButtonImageView&) = delete; |
| |
| ~ButtonImageView() override = default; |
| |
| // views::View: |
| void OnPaint(gfx::Canvas* canvas) override { |
| views::View::OnPaint(canvas); |
| |
| if (!image_.isNull()) { |
| canvas->DrawImageInt(image_, (width() - image_.width()) / 2, |
| (height() - image_.height()) / 2, cc::PaintFlags()); |
| return; |
| } |
| |
| gfx::PointF circle_center(gfx::Rect(size()).CenterPoint()); |
| |
| const bool is_long_press_action_available = |
| button_controller_->IsLongPressActionAvailable(); |
| // Paint a white ring as the foreground for the app list circle. The |
| // ceil/dsf math assures that the ring draws sharply and is centered at all |
| // scale factors. |
| const float ring_outer_radius_dp = |
| is_long_press_action_available ? 8.0f : 7.0f; |
| const float ring_thickness_dp = |
| is_long_press_action_available ? 1.0f : 1.5f; |
| { |
| gfx::ScopedCanvas scoped_canvas(canvas); |
| const float dsf = canvas->UndoDeviceScaleFactor(); |
| circle_center.Scale(dsf); |
| cc::PaintFlags fg_flags; |
| fg_flags.setAntiAlias(true); |
| fg_flags.setStyle(cc::PaintFlags::kStroke_Style); |
| fg_flags.setColor(GetColorProvider()->GetColor(GetIconColorId())); |
| |
| if (is_long_press_action_available) { |
| fg_flags.setAlphaf(kAssistantInvisibleAlpha / 255.0f); |
| } |
| |
| const float thickness = std::ceil(ring_thickness_dp * dsf); |
| const float radius = |
| std::ceil(ring_outer_radius_dp * dsf) - thickness / 2; |
| fg_flags.setStrokeWidth(thickness); |
| // Make sure the center of the circle lands on pixel centers. |
| canvas->DrawCircle(circle_center, radius, fg_flags); |
| |
| if (is_long_press_action_available) { |
| fg_flags.setAlphaf(1.0f); |
| const float kCircleRadiusDp = 5.f; |
| fg_flags.setStyle(cc::PaintFlags::kFill_Style); |
| canvas->DrawCircle(circle_center, std::ceil(kCircleRadiusDp * dsf), |
| fg_flags); |
| } |
| } |
| } |
| |
| void OnThemeChanged() override { |
| views::View::OnThemeChanged(); |
| if (image_model_) { |
| image_ = image_model_->Rasterize(GetColorProvider()); |
| } |
| SchedulePaint(); |
| } |
| |
| // Updates the button image view for the new shelf config. |
| void UpdateForShelfConfigChange() { |
| layer()->SetBackgroundBlur( |
| ShelfConfig::Get()->GetShelfControlButtonBlurRadius()); |
| layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality); |
| UpdateBackground(); |
| UpdateIconImageModel(); |
| } |
| |
| void SetToggled(bool toggled) { |
| if (toggled_ == toggled) { |
| return; |
| } |
| |
| toggled_ = toggled; |
| UpdateBackground(); |
| UpdateIconImageModel(); |
| SchedulePaint(); |
| } |
| |
| private: |
| // Updates the view background to match the current shelf config. |
| void UpdateBackground() { |
| auto* const shelf_config = ShelfConfig::Get(); |
| |
| if (shelf_config->in_tablet_mode() && shelf_config->is_in_app()) { |
| SetBackground(nullptr); |
| SetBorder(nullptr); |
| return; |
| } |
| |
| SetBackground(views::CreateRoundedRectBackground( |
| GetBackgroundColorId(), shelf_config->control_border_radius())); |
| |
| if (shelf_config->in_tablet_mode() && !shelf_config->is_in_app()) { |
| SetBorder(std::make_unique<views::HighlightBorder>( |
| shelf_config->control_border_radius(), |
| views::HighlightBorder::Type::kHighlightBorderOnShadow)); |
| } else { |
| SetBorder(nullptr); |
| } |
| } |
| |
| ui::ColorId GetIconColorId() { |
| return toggled_ && !ShelfConfig::Get()->in_tablet_mode() |
| ? cros_tokens::kCrosSysSystemOnPrimaryContainer |
| : cros_tokens::kCrosSysOnSurface; |
| } |
| |
| ui::ColorId GetBackgroundColorId() { |
| if (ShelfConfig::Get()->in_tablet_mode()) { |
| return cros_tokens::kCrosSysSystemBaseElevated; |
| } |
| |
| return toggled_ ? cros_tokens::kCrosSysSystemPrimaryContainer |
| : cros_tokens::kCrosSysSystemOnBase; |
| } |
| |
| void UpdateIconImageModel() { |
| const std::string campbell_config = base::GetFieldTrialParamValueByFeature( |
| features::kCampbellGlyph, "icon"); |
| |
| if (!campbell_config.empty() && switches::IsCampbellSecretKeyMatched()) { |
| if (campbell_config == "hero") { |
| image_model_ = |
| ui::ImageModel::FromVectorIcon(kCampbellHeroIcon, GetIconColorId()); |
| } else if (campbell_config == "action") { |
| image_model_ = ui::ImageModel::FromVectorIcon(kCampbellActionIcon, |
| GetIconColorId()); |
| } else if (campbell_config == "text") { |
| image_model_ = |
| ui::ImageModel::FromVectorIcon(kCampbellTextIcon, GetIconColorId()); |
| } else if (campbell_config == "9dot") { |
| image_model_ = |
| ui::ImageModel::FromVectorIcon(kCampbell9dotIcon, GetIconColorId()); |
| } |
| } else if (Shell::Get()->keyboard_capability()->GetMetaKeyToDisplay() == |
| ui::mojom::MetaKey::kLauncherRefresh) { |
| image_model_ = |
| ui::ImageModel::FromVectorIcon(kCampbellHeroIcon, GetIconColorId()); |
| } else { |
| image_model_ = std::nullopt; |
| image_ = gfx::ImageSkia(); |
| return; |
| } |
| |
| if (image_model_ && GetColorProvider()) { |
| image_ = image_model_->Rasterize(GetColorProvider()); |
| } else { |
| image_ = gfx::ImageSkia(); |
| } |
| } |
| |
| const raw_ptr<HomeButtonController> button_controller_; |
| |
| gfx::ImageSkia image_; |
| std::optional<ui::ImageModel> image_model_; |
| |
| bool toggled_ = false; |
| }; |
| |
| BEGIN_METADATA(HomeButton, ButtonImageView) |
| END_METADATA |
| |
| // HomeButton::ScopedNoClipRect ------------------------------------------------ |
| |
| HomeButton::ScopedNoClipRect::ScopedNoClipRect( |
| ShelfNavigationWidget* shelf_navigation_widget) |
| : shelf_navigation_widget_(shelf_navigation_widget), |
| clip_rect_(shelf_navigation_widget_->GetLayer()->clip_rect()) { |
| shelf_navigation_widget_->GetLayer()->SetClipRect(gfx::Rect()); |
| } |
| |
| HomeButton::ScopedNoClipRect::~ScopedNoClipRect() { |
| // The shelf_navigation_widget_ may be destructed before this dtor is |
| // called. |
| if (shelf_navigation_widget_->GetLayer()) |
| shelf_navigation_widget_->GetLayer()->SetClipRect(clip_rect_); |
| } |
| |
| // HomeButton::ScopedNoClipRect ------------------------------------------------ |
| |
| HomeButton::HomeButton(Shelf* shelf) |
| : ShelfControlButton(shelf, this), |
| shelf_(shelf), |
| controller_(this) { |
| GetViewAccessibility().SetName( |
| l10n_util::GetStringUTF16(IDS_ASH_SHELF_APP_LIST_LAUNCHER_TITLE)); |
| button_controller()->set_notify_action( |
| views::ButtonController::NotifyAction::kOnPress); |
| |
| // When Jelly is disabled, the toggled state is achieved by activating ink |
| // drop from the home button controller. Given that the controller manages ink |
| // drop on gesture events itself, disable the default on-gesture ink drop |
| // behavior. |
| views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON); |
| |
| SetEventTargeter(std::make_unique<views::ViewTargeter>(this)); |
| layer()->SetName("shelf/Homebutton"); |
| |
| // Added at 0 index to ensure it's painted below focus ring view. |
| button_image_view_ = |
| AddChildViewAt(std::make_unique<ButtonImageView>(&controller_), 0); |
| |
| if (features::IsHomeButtonWithTextEnabled()) { |
| // Directly shows the nudge label if the text-in-shelf feature is enabled. |
| CreateNudgeLabel(); |
| expandable_container_->SetVisible(true); |
| shelf_->shelf_layout_manager()->LayoutShelf(false); |
| } |
| |
| if (features::IsHomeButtonQuickAppAccessEnabled() && |
| !features::IsHomeButtonWithTextEnabled()) { |
| shell_observation_.Observe(Shell::Get()); |
| app_list_model_observation_.Observe(AppListModelProvider::Get()); |
| quick_app_model_observation_.Observe( |
| AppListModelProvider::Get()->quick_app_access_model()); |
| } |
| |
| if (features::IsUserEducationEnabled()) { |
| // NOTE: Set `kHelpBubbleContextKey` before `views::kElementIdentifierKey` |
| // in case registration causes a help bubble to be created synchronously. |
| SetProperty(kHelpBubbleContextKey, HelpBubbleContext::kAsh); |
| } |
| SetProperty(views::kElementIdentifierKey, kHomeButtonElementId); |
| |
| ui::DeviceDataManager::GetInstance()->AddObserver(this); |
| ShelfConfig::Get()->AddObserver(this); |
| } |
| |
| HomeButton::~HomeButton() { |
| ui::DeviceDataManager::GetInstance()->RemoveObserver(this); |
| ShelfConfig::Get()->RemoveObserver(this); |
| } |
| |
| gfx::Size HomeButton::CalculatePreferredSize( |
| const views::SizeBounds& available_size) const { |
| const gfx::Size control_button_size = |
| ShelfControlButton::CalculatePreferredSize(available_size); |
| |
| // Take the preferred size of the expandable container into consideration when |
| // it is visible. Note that the button width is already included in the label |
| // width. |
| if (expandable_container_ && expandable_container_->GetVisible()) { |
| const gfx::Size container_size = expandable_container_->GetPreferredSize(); |
| return gfx::Size( |
| std::max(control_button_size.width(), container_size.width()), |
| std::max(control_button_size.height(), container_size.height())); |
| } |
| |
| return control_button_size; |
| } |
| |
| void HomeButton::Layout(PassKey) { |
| LayoutSuperclass<ShelfControlButton>(this); |
| |
| button_image_view_->SetBoundsRect( |
| gfx::Rect(ShelfControlButton::CalculatePreferredSize({}))); |
| |
| if (expandable_container_) { |
| if (shelf_->IsHorizontalAlignment()) { |
| expandable_container_->SetSize(gfx::Size( |
| expandable_container_->GetPreferredSize().width(), height())); |
| } else { |
| expandable_container_->SetSize(gfx::Size( |
| width(), expandable_container_->GetPreferredSize().height())); |
| } |
| |
| if (quick_app_button_) { |
| if (shelf_->IsHorizontalAlignment()) { |
| expandable_container_->SetBorder( |
| views::CreateEmptyBorder(gfx::Insets::TLBR( |
| 0, |
| ShelfControlButton::CalculatePreferredSize({}).width() + |
| kQuickAppStartMargin, |
| 0, 0))); |
| } else { |
| expandable_container_->SetBorder( |
| views::CreateEmptyBorder(gfx::Insets::TLBR( |
| ShelfControlButton::CalculatePreferredSize({}).height() + |
| kQuickAppStartMargin, |
| 0, 0, 0))); |
| } |
| expandable_container_->layer()->SetClipRect( |
| gfx::Rect(expandable_container_->size())); |
| } |
| } |
| } |
| |
| void HomeButton::AddedToWidget() { |
| UpdateTooltipText(); |
| } |
| |
| void HomeButton::OnGestureEvent(ui::GestureEvent* event) { |
| if (!controller_.MaybeHandleGestureEvent(event)) |
| Button::OnGestureEvent(event); |
| } |
| |
| void HomeButton::OnShelfButtonAboutToRequestFocusFromTabTraversal( |
| ShelfButton* button, |
| bool reverse) { |
| DCHECK_EQ(button, this); |
| const bool quick_app_focused = |
| quick_app_button_ && |
| (GetFocusManager()->GetFocusedView() == quick_app_button_); |
| |
| // Focus out if: |
| // * The currently focused view is already this button, so focus out to |
| // ensure traversal to a different button. |
| // * Going forward with the quick app button currently focused, implies that |
| // the widget is trying to traverse forward to the next widget. |
| // * Going in reverse when the shelf has a back button, which implies that |
| // the widget is trying to loop back from the back button. |
| if (GetFocusManager()->GetFocusedView() == this || |
| (quick_app_focused && !reverse) || |
| (reverse && shelf()->navigation_widget()->GetBackButton())) { |
| shelf()->shelf_focus_cycler()->FocusOut(reverse, |
| SourceView::kShelfNavigationView); |
| } |
| } |
| |
| void HomeButton::ButtonPressed(views::Button* sender, |
| const ui::Event& event, |
| views::InkDrop* ink_drop) { |
| if (display::Screen::Get()->InTabletMode()) { |
| base::RecordAction( |
| base::UserMetricsAction("AppList_HomeButtonPressedTablet")); |
| } else { |
| base::RecordAction( |
| base::UserMetricsAction("AppList_HomeButtonPressedClamshell")); |
| } |
| |
| Shell::Get()->app_list_controller()->ToggleAppList( |
| GetDisplayId(), AppListShowSource::kShelfButton, event.time_stamp()); |
| |
| // If the home button is pressed, fade out the nudge label if it is showing. |
| if (expandable_container_ && !quick_app_button_) { |
| // The label shouldn't be removed if the text-in-shelf feature is enabled. |
| if (features::IsHomeButtonWithTextEnabled()) |
| return; |
| |
| if (!expandable_container_->GetVisible()) { |
| // If the nudge label is not visible and will not be animating, directly |
| // remove them as the nudge won't be showing anymore. |
| RemoveNudgeLabel(); |
| return; |
| } |
| |
| if (label_nudge_timer_.IsRunning()) |
| label_nudge_timer_.Stop(); |
| AnimateNudgeLabelFadeOut(); |
| } |
| } |
| |
| void HomeButton::OnShelfConfigUpdated() { |
| button_image_view_->UpdateForShelfConfigChange(); |
| } |
| |
| void HomeButton::OnIconUpdated() { |
| // `button_image_view_` may not be set during `HomeButton` construction - |
| // `button_image_view_` is created after `controller_`, which can end up |
| // calling this method in response to registering assistant state observer. |
| if (button_image_view_) { |
| button_image_view_->SchedulePaint(); |
| } |
| } |
| |
| bool HomeButton::IsShowingAppList() const { |
| auto* controller = Shell::Get()->app_list_controller(); |
| return controller && controller->GetTargetVisibility(GetDisplayId()); |
| } |
| |
| void HomeButton::HandleLocaleChange() { |
| GetViewAccessibility().SetName( |
| l10n_util::GetStringUTF16(IDS_ASH_SHELF_APP_LIST_LAUNCHER_TITLE)); |
| TooltipTextChanged(); |
| UpdateTooltipText(); |
| // Reset the bounds rect so the child layer bounds get updated on next shelf |
| // layout if the RTL changed. |
| SetBoundsRect(gfx::Rect()); |
| } |
| |
| int64_t HomeButton::GetDisplayId() const { |
| aura::Window* window = GetWidget()->GetNativeWindow(); |
| return display::Screen::Get()->GetDisplayNearestWindow(window).id(); |
| } |
| |
| std::unique_ptr<HomeButton::ScopedNoClipRect> |
| HomeButton::CreateScopedNoClipRect() { |
| return std::make_unique<HomeButton::ScopedNoClipRect>( |
| shelf()->navigation_widget()); |
| } |
| |
| bool HomeButton::CanShowNudgeLabel() const { |
| if (!shelf_->IsHorizontalAlignment()) |
| return false; |
| |
| // Avoid showing the text nudge label when a quick app button is shown. |
| if (quick_app_button_) { |
| return false; |
| } |
| |
| // If there's no pinned app in shelf, shows the nudge label for the launcher |
| // nudge. |
| ShelfView* shelf_view = shelf_->hotseat_widget()->GetShelfView(); |
| int view_size = shelf_view->view_model()->view_size(); |
| if (view_size == 0) |
| return true; |
| |
| // Need to have nudge_label_ existing to calculate the space for itself. |
| DCHECK(nudge_label_); |
| |
| // For the calculation below, convert all points and rects to the root window |
| // coordinate to make sure they are under the same coordinate. |
| gfx::Rect first_app_bounds = |
| shelf_view->view_model()->view_at(0)->GetMirroredBounds(); |
| first_app_bounds = shelf_view->ConvertRectToWidget(first_app_bounds); |
| aura::Window* shelf_native_window = |
| shelf_view->GetWidget()->GetNativeWindow(); |
| aura::Window::ConvertRectToTarget(shelf_native_window, |
| shelf_native_window->GetRootWindow(), |
| &first_app_bounds); |
| |
| gfx::Rect label_rect = |
| ConvertRectToWidget(expandable_container_->GetMirroredBounds()); |
| aura::Window* native_window = GetWidget()->GetNativeWindow(); |
| DCHECK_EQ(shelf_native_window->GetRootWindow(), |
| native_window->GetRootWindow()); |
| aura::Window::ConvertRectToTarget( |
| native_window, native_window->GetRootWindow(), &label_rect); |
| |
| // Horizontal space between the `label_rect` and the first app in shelf, which |
| // is also the app that is closest to the home button, is calculated here to |
| // check if there's enough space to show the `nudge_label_`. |
| int space = label_rect.ManhattanInternalDistance(first_app_bounds); |
| return space >= kMinSpaceBetweenNudgeLabelAndHotseat; |
| } |
| |
| void HomeButton::StartNudgeAnimation() { |
| // Don't animate the label as it is already visible when text-in-shelf is |
| // enabled. |
| if (features::IsHomeButtonWithTextEnabled()) |
| return; |
| |
| // Ensure any in-progress nudge animations are completed before initializing |
| // a new nudge animation, and creating a rippler layer. Nudge animation |
| // callbacks may otherwise delete ripple layer mid new animation set up (and |
| // delete the newly created ripple layer just before the layer animation is |
| // set up by animation builder). |
| nudge_ripple_layer_.ReleaseLayer(); |
| if (nudge_label_) |
| nudge_label_->layer()->GetAnimator()->AbortAllAnimations(); |
| if (expandable_container_) { |
| expandable_container_->layer()->GetAnimator()->AbortAllAnimations(); |
| } |
| |
| // Create the nudge label first to check if there is enough space to show it. |
| if (!nudge_label_) |
| CreateNudgeLabel(); |
| |
| const bool can_show_nudge_label = CanShowNudgeLabel(); |
| |
| views::AnimationBuilder builder; |
| builder |
| .SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .OnStarted(base::BindOnce(&HomeButton::OnNudgeAnimationStarted, |
| weak_ptr_factory_.GetWeakPtr())) |
| .OnEnded(base::BindOnce(can_show_nudge_label |
| ? &HomeButton::OnLabelSlideInAnimationEnded |
| : &HomeButton::OnNudgeAnimationEnded, |
| weak_ptr_factory_.GetWeakPtr())) |
| .OnAborted(base::BindOnce(&HomeButton::OnNudgeAnimationEnded, |
| weak_ptr_factory_.GetWeakPtr())) |
| .Once(); |
| |
| if (can_show_nudge_label) { |
| AnimateNudgeLabelSlideIn(builder); |
| } else { |
| AnimateNudgeBounce(builder); |
| } |
| |
| // Remove clip_rect from the home button and its ancestors as the animation |
| // goes beyond its bounds. The object is deleted once the animation ends. |
| scoped_no_clip_rect_ = CreateScopedNoClipRect(); |
| AnimateNudgeRipple(builder); |
| } |
| |
| void HomeButton::SetToggled(bool toggled) { |
| button_image_view_->SetToggled(toggled); |
| } |
| |
| void HomeButton::AddNudgeAnimationObserverForTest( |
| NudgeAnimationObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| void HomeButton::RemoveNudgeAnimationObserverForTest( |
| NudgeAnimationObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void HomeButton::OnThemeChanged() { |
| ShelfControlButton::OnThemeChanged(); |
| |
| if (ripple_layer_delegate_) { |
| ripple_layer_delegate_->set_color(GetColorProvider()->GetColor( |
| cros_tokens::kCrosSysRippleNeutralOnSubtle)); |
| } |
| if (expandable_container_) { |
| expandable_container_->layer()->SetColor( |
| GetColorProvider()->GetColor(cros_tokens::kCrosSysSystemOnBase)); |
| } |
| } |
| |
| void HomeButton::CreateExpandableContainer() { |
| const int home_button_width = |
| ShelfControlButton::CalculatePreferredSize({}).width(); |
| |
| // Add container at 0 index so it's stacked under other views (e.g. |
| // `button_image_view_`, and focus ring). |
| expandable_container_ = AddChildViewAt(std::make_unique<views::View>(), 0); |
| expandable_container_->SetLayoutManager( |
| std::make_unique<views::FillLayout>()); |
| expandable_container_->SetPaintToLayer(ui::LAYER_SOLID_COLOR); |
| expandable_container_->layer()->SetMasksToBounds(true); |
| if (GetColorProvider()) { |
| expandable_container_->layer()->SetColor( |
| GetColorProvider()->GetColor(cros_tokens::kCrosSysSystemOnBase)); |
| } |
| expandable_container_->layer()->SetRoundedCornerRadius( |
| gfx::RoundedCornersF(home_button_width / 2.f)); |
| expandable_container_->layer()->SetName("NudgeLabelContainer"); |
| } |
| |
| void HomeButton::UpdateTooltipText() { |
| // Don't show a tooltip if we're already showing the app list. |
| SetTooltipText(IsShowingAppList() ? std::u16string() |
| : GetViewAccessibility().GetCachedName()); |
| } |
| |
| void HomeButton::CreateNudgeLabel() { |
| DCHECK(!expandable_container_); |
| |
| CreateExpandableContainer(); |
| expandable_container_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR( |
| 0, ShelfControlButton::CalculatePreferredSize({}).width(), 0, 16))); |
| |
| // Create a view to clip the `nudge_label_` to the area right of the home |
| // button during nudge label animation. |
| auto* label_mask = |
| expandable_container_->AddChildView(std::make_unique<views::View>()); |
| label_mask->SetLayoutManager(std::make_unique<views::FillLayout>()); |
| label_mask->SetBorder( |
| views::CreateEmptyBorder(gfx::Insets::TLBR(0, 12, 0, 0))); |
| label_mask->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| label_mask->layer()->SetMasksToBounds(true); |
| label_mask->layer()->SetName("NudgeLabelMask"); |
| |
| nudge_label_ = label_mask->AddChildView(std::make_unique<views::Label>( |
| l10n_util::GetStringUTF16(IDS_SHELF_LAUNCHER_NUDGE_TEXT))); |
| nudge_label_->SetAutoColorReadabilityEnabled(false); |
| nudge_label_->SetPaintToLayer(); |
| nudge_label_->layer()->SetFillsBoundsOpaquely(false); |
| nudge_label_->SetTextContext(CONTEXT_LAUNCHER_NUDGE_LABEL); |
| nudge_label_->SetTextStyle(views::style::STYLE_EMPHASIZED); |
| nudge_label_->SetEnabledColor(cros_tokens::kCrosSysOnSurface); |
| TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2, |
| *nudge_label_); |
| expandable_container_->SetVisible(false); |
| DeprecatedLayoutImmediately(); |
| } |
| |
| void HomeButton::CreateQuickAppButton() { |
| CreateExpandableContainer(); |
| if (shelf_->IsHorizontalAlignment()) { |
| expandable_container_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR( |
| 0, |
| ShelfControlButton::CalculatePreferredSize({}).width() + |
| kQuickAppStartMargin, |
| 0, 0))); |
| } else { |
| expandable_container_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR( |
| ShelfControlButton::CalculatePreferredSize({}).height() + |
| kQuickAppStartMargin, |
| 0, 0, 0))); |
| } |
| |
| quick_app_button_ = expandable_container_->AddChildView( |
| std::make_unique<views::ImageButton>(base::BindRepeating( |
| &HomeButton::QuickAppButtonPressed, base::Unretained(this)))); |
| quick_app_button_->GetViewAccessibility().SetName( |
| AppListModelProvider::Get()->quick_app_access_model()->GetAppName()); |
| |
| const int control_size = |
| ShelfControlButton::CalculatePreferredSize({}).width(); |
| |
| const gfx::Size preferred_size = gfx::Size(control_size, control_size); |
| |
| quick_app_button_->SetPaintToLayer(); |
| quick_app_button_->layer()->SetFillsBoundsOpaquely(false); |
| quick_app_button_->SetImageModel( |
| views::Button::STATE_NORMAL, |
| ui::ImageModel::FromImageSkia( |
| AppListModelProvider::Get()->quick_app_access_model()->GetAppIcon( |
| preferred_size))); |
| views::HighlightPathGenerator::Install( |
| quick_app_button_, |
| std::make_unique<views::RoundRectHighlightPathGenerator>( |
| gfx::Insets(views::FocusRing::kDefaultHaloThickness / 2), |
| ShelfConfig::Get()->control_border_radius())); |
| views::FocusRing::Get(quick_app_button_) |
| ->SetColorId(cros_tokens::kCrosSysFocusRing); |
| quick_app_button_->SetSize(preferred_size); |
| |
| shelf_->shelf_layout_manager()->LayoutShelf(false); |
| } |
| |
| void HomeButton::QuickAppButtonPressed() { |
| ash::Shell::Get()->app_list_controller()->ActivateItem( |
| AppListModelProvider::Get()->quick_app_access_model()->quick_app_id(), |
| /*event_flags=*/0, ash::AppListLaunchedFrom::kLaunchedFromQuickAppAccess, |
| /*is_above_the_fold=*/false); |
| AppListModelProvider::Get()->quick_app_access_model()->SetQuickAppActivated(); |
| } |
| |
| void HomeButton::AnimateNudgeRipple(views::AnimationBuilder& builder) { |
| // Create the ripple layer and its delegate for the nudge animation. |
| nudge_ripple_layer_.Reset(std::make_unique<ui::Layer>()); |
| ui::Layer* ripple_layer = nudge_ripple_layer_.layer(); |
| |
| float ripple_diameter = |
| ShelfControlButton::CalculatePreferredSize({}).width(); |
| auto* color_provider = GetColorProvider(); |
| DCHECK(color_provider); |
| ripple_layer_delegate_ = std::make_unique<views::CircleLayerDelegate>( |
| color_provider->GetColor(cros_tokens::kCrosSysRippleNeutralOnSubtle), |
| /*radius=*/ripple_diameter / 2); |
| |
| // The bounds are set with respect to |shelf_container_layer| stated below. |
| ripple_layer->SetBounds( |
| gfx::Rect(layer()->parent()->bounds().x() + layer()->bounds().x(), |
| layer()->parent()->bounds().y() + layer()->bounds().y(), |
| ripple_diameter, ripple_diameter)); |
| |
| ripple_layer->set_delegate(ripple_layer_delegate_.get()); |
| ripple_layer->SetMasksToBounds(true); |
| ripple_layer->SetFillsBoundsOpaquely(false); |
| |
| // The position of the ripple layer is independent to the home button and its |
| // parent shelf navigation widget. Therefore the ripple layer is added to the |
| // shelf container layer, which is the parent layer of the shelf navigation |
| // widget. |
| ui::Layer* shelf_container_layer = GetWidget()->GetLayer()->parent(); |
| shelf_container_layer->Add(ripple_layer); |
| shelf_container_layer->StackBelow(ripple_layer, layer()->parent()); |
| |
| // The point of the center of the round button. |
| const gfx::PointF ripple_center = |
| gfx::RectF(gfx::SizeF(ripple_layer->size())).CenterPoint(); |
| |
| gfx::Transform initial_disc_scale; |
| initial_disc_scale.Scale(0.1f, 0.1f); |
| gfx::Transform initial_state = |
| gfx::TransformAboutPivot(ripple_center, initial_disc_scale); |
| |
| gfx::Transform final_disc_scale; |
| final_disc_scale.Scale(3.0f, 3.0f); |
| gfx::Transform scale_about_pivot = |
| gfx::TransformAboutPivot(ripple_center, final_disc_scale); |
| |
| builder.GetCurrentSequence() |
| .At(base::TimeDelta()) |
| // Set up the animation of the `nudge_ripple_layer_` |
| .SetDuration(base::TimeDelta()) |
| .SetTransform(ripple_layer, initial_state) |
| .SetOpacity(ripple_layer, 0.5f) |
| .Then() |
| .SetDuration(kRippleAnimationDuration) |
| .SetTransform(ripple_layer, scale_about_pivot, |
| gfx::Tween::ACCEL_0_40_DECEL_100) |
| .SetOpacity(ripple_layer, 0.0f, gfx::Tween::ACCEL_0_80_DECEL_80); |
| } |
| |
| void HomeButton::AnimateNudgeBounce(views::AnimationBuilder& builder) { |
| gfx::PointF bounce_up_point = shelf()->SelectValueForShelfAlignment( |
| gfx::PointF(0, -kAnimationBounceUpOffset), |
| gfx::PointF(kAnimationBounceUpOffset, 0), |
| gfx::PointF(-kAnimationBounceUpOffset, 0)); |
| gfx::PointF bounce_down_point = shelf()->SelectValueForShelfAlignment( |
| gfx::PointF(0, kAnimationBounceDownOffset), |
| gfx::PointF(-kAnimationBounceDownOffset, 0), |
| gfx::PointF(kAnimationBounceDownOffset, 0)); |
| |
| gfx::Transform move_up; |
| move_up.Translate(bounce_up_point.x(), bounce_up_point.y()); |
| gfx::Transform move_down; |
| move_down.Translate(bounce_down_point.x(), bounce_down_point.y()); |
| |
| // Home button movement settings. Note that the navigation widget layer |
| // contains the non-opaque part of the home button and is also animated along |
| // with the home button. |
| ui::Layer* widget_layer = GetWidget()->GetLayer(); |
| |
| // Set up the animation of the `widget_layer`, which bounce up and down during |
| // the animation. |
| builder.GetCurrentSequence() |
| .At(base::TimeDelta()) |
| .SetDuration(kHomeButtonAnimationDuration) |
| .SetTransform(widget_layer, move_up, gfx::Tween::FAST_OUT_SLOW_IN_3) |
| .Then() |
| .SetDuration(kHomeButtonAnimationDuration) |
| .SetTransform(widget_layer, move_down, gfx::Tween::ACCEL_80_DECEL_20) |
| .Then() |
| .SetDuration(kHomeButtonAnimationDuration) |
| .SetTransform(widget_layer, gfx::Transform(), |
| gfx::Tween::FAST_OUT_SLOW_IN_3); |
| } |
| |
| void HomeButton::AnimateNudgeLabelSlideIn(views::AnimationBuilder& builder) { |
| // Make sure the label is created. |
| DCHECK(expandable_container_ && nudge_label_); |
| |
| // Update the shelf layout to provide space for the navigation widget. |
| expandable_container_->SetVisible(true); |
| shelf_->shelf_layout_manager()->LayoutShelf(false); |
| |
| const gfx::Rect initial_container_clip_rect = |
| GetExpandableContainerClipRectToHomeButton(); |
| const gfx::Transform initial_transform = |
| GetTransformForContainerChildBehindHomeButton(); |
| |
| // Calculate the target clip rect on `expandable_container_`. |
| const gfx::Rect container_target_clip_rect = |
| gfx::Rect(expandable_container_->size()); |
| |
| // Set up the animation of the `nudge_label_` |
| builder.GetCurrentSequence() |
| .At(base::TimeDelta()) |
| .SetDuration(base::TimeDelta()) |
| .SetTransform(nudge_label_->layer(), initial_transform) |
| .SetClipRect(expandable_container_->layer(), initial_container_clip_rect) |
| .SetOpacity(expandable_container_->layer(), 0) |
| .Then() |
| .SetDuration(kNudgeLabelTransitionOnDuration) |
| .SetTransform(nudge_label_->layer(), gfx::Transform(), |
| gfx::Tween::ACCEL_5_70_DECEL_90) |
| .SetClipRect(expandable_container_->layer(), container_target_clip_rect, |
| gfx::Tween::ACCEL_5_70_DECEL_90) |
| .SetOpacity(expandable_container_->layer(), 1, |
| gfx::Tween::ACCEL_5_70_DECEL_90); |
| } |
| |
| void HomeButton::AnimateNudgeLabelSlideOut() { |
| const gfx::Transform target_transform = |
| GetTransformForContainerChildBehindHomeButton(); |
| const gfx::Rect container_target_clip_rect = |
| GetExpandableContainerClipRectToHomeButton(); |
| |
| views::AnimationBuilder() |
| .SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .OnEnded(base::BindOnce(&HomeButton::OnNudgeAnimationEnded, |
| weak_ptr_factory_.GetWeakPtr())) |
| .OnAborted(base::BindOnce(&HomeButton::OnNudgeAnimationEnded, |
| weak_ptr_factory_.GetWeakPtr())) |
| .Once() |
| .SetDuration(kNudgeLabelTransitionOffDuration) |
| .SetTransform(nudge_label_->layer(), target_transform, |
| gfx::Tween::ACCEL_40_DECEL_100_3) |
| .SetClipRect(expandable_container_->layer(), container_target_clip_rect, |
| gfx::Tween::ACCEL_40_DECEL_100_3) |
| .SetOpacity(expandable_container_->layer(), 0, |
| gfx::Tween::ACCEL_40_DECEL_100_3); |
| } |
| |
| void HomeButton::AnimateNudgeLabelFadeOut() { |
| views::AnimationBuilder() |
| .SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .OnEnded(base::BindOnce(&HomeButton::OnLabelFadeOutAnimationEnded, |
| weak_ptr_factory_.GetWeakPtr())) |
| .OnAborted(base::BindOnce(&HomeButton::OnLabelFadeOutAnimationEnded, |
| weak_ptr_factory_.GetWeakPtr())) |
| .Once() |
| .SetDuration(kNudgeLabelFadeOutDuration) |
| .SetOpacity(expandable_container_->layer(), 0, gfx::Tween::LINEAR); |
| } |
| |
| void HomeButton::OnNudgeAnimationStarted() { |
| for (auto& observer : observers_) |
| observer.NudgeAnimationStarted(this); |
| } |
| |
| void HomeButton::OnNudgeAnimationEnded() { |
| // Delete the ripple layer and its delegate after the launcher nudge animation |
| // is completed. |
| nudge_ripple_layer_.ReleaseLayer(); |
| ripple_layer_delegate_.reset(); |
| |
| if (expandable_container_) { |
| expandable_container_->SetVisible(false); |
| shelf_->shelf_layout_manager()->LayoutShelf(false); |
| } |
| |
| // Reset the clip rect after the animation is completed. |
| scoped_no_clip_rect_.reset(); |
| |
| for (auto& observer : observers_) |
| observer.NudgeAnimationEnded(this); |
| } |
| |
| void HomeButton::OnLabelSlideInAnimationEnded() { |
| for (auto& observer : observers_) |
| observer.NudgeLabelShown(this); |
| |
| // After the label is shown for `kNudgeLabelShowingDuration` amount of time, |
| // move the label back to its original position. |
| label_nudge_timer_.Start( |
| FROM_HERE, kNudgeLabelShowingDuration, |
| base::BindOnce(&HomeButton::AnimateNudgeLabelSlideOut, |
| base::Unretained(this))); |
| } |
| |
| void HomeButton::OnLabelFadeOutAnimationEnded() { |
| OnNudgeAnimationEnded(); |
| |
| // If the label is faded out by clicking on it, remove the label as it is |
| // assumed that the nudge won't be shown again. |
| RemoveNudgeLabel(); |
| } |
| |
| void HomeButton::RemoveNudgeLabel() { |
| nudge_label_ = nullptr; |
| RemoveChildViewT(expandable_container_.ExtractAsDangling()); |
| } |
| |
| void HomeButton::RemoveQuickAppButton() { |
| quick_app_button_ = nullptr; |
| RemoveChildViewT(expandable_container_.ExtractAsDangling()); |
| } |
| |
| bool HomeButton::DoesIntersectRect(const views::View* target, |
| const gfx::Rect& rect) const { |
| DCHECK_EQ(target, this); |
| gfx::Rect button_bounds = target->GetLocalBounds(); |
| |
| // If the `expandable_container_` is visible, set all the area within the |
| // label bounds clickable. |
| if (expandable_container_ && expandable_container_->GetVisible()) { |
| button_bounds = expandable_container_->layer()->bounds(); |
| } |
| |
| // Increase clickable area for the button to account for clicks around the |
| // spacing. This will not intercept events outside of the parent widget. |
| button_bounds.Inset( |
| gfx::Insets::VH(-ShelfConfig::Get()->control_button_edge_spacing( |
| !shelf()->IsHorizontalAlignment()), |
| -ShelfConfig::Get()->control_button_edge_spacing( |
| shelf()->IsHorizontalAlignment()))); |
| return button_bounds.Intersects(rect); |
| } |
| |
| void HomeButton::OnInputDeviceConfigurationChanged(uint8_t input_device_types) { |
| if (input_device_types & InputDeviceEventObserver::kKeyboard) { |
| button_image_view_->UpdateForShelfConfigChange(); |
| } |
| } |
| |
| void HomeButton::OnDeviceListsComplete() { |
| button_image_view_->UpdateForShelfConfigChange(); |
| } |
| |
| void HomeButton::OnShellDestroying() { |
| shell_observation_.Reset(); |
| app_list_model_observation_.Reset(); |
| quick_app_model_observation_.Reset(); |
| } |
| |
| void HomeButton::OnActiveAppListModelsChanged(AppListModel* model, |
| SearchModel* search_model) { |
| QuickAppAccessModel* quick_model = |
| AppListModelProvider::Get()->quick_app_access_model(); |
| quick_app_model_observation_.Reset(); |
| quick_app_model_observation_.Observe(quick_model); |
| |
| OnQuickAppShouldShowChanged(quick_model->quick_app_should_show_state()); |
| } |
| |
| void HomeButton::OnQuickAppShouldShowChanged(bool show_quick_app) { |
| if (!show_quick_app && quick_app_button_) { |
| AnimateQuickAppButtonOut(); |
| } else if (show_quick_app && !quick_app_button_) { |
| if (nudge_label_) { |
| RemoveNudgeLabel(); |
| } |
| AnimateQuickAppButtonIn(); |
| } |
| } |
| |
| void HomeButton::OnQuickAppIconChanged() { |
| if (!quick_app_button_) { |
| return; |
| } |
| |
| const int control_size = |
| ShelfControlButton::CalculatePreferredSize({}).width(); |
| quick_app_button_->SetImageModel( |
| views::Button::STATE_NORMAL, |
| ui::ImageModel::FromImageSkia( |
| AppListModelProvider::Get()->quick_app_access_model()->GetAppIcon( |
| gfx::Size(control_size, control_size)))); |
| } |
| |
| void HomeButton::AnimateQuickAppButtonIn() { |
| CreateQuickAppButton(); |
| |
| CHECK(quick_app_button_ && expandable_container_ && !nudge_label_); |
| |
| const gfx::Rect initial_container_clip_rect = |
| GetExpandableContainerClipRectToHomeButton(); |
| const gfx::Transform initial_transform = |
| GetTransformForContainerChildBehindHomeButton(); |
| |
| // Calculate the target clip rect on `expandable_container_`. |
| const gfx::Rect container_target_clip_rect = |
| gfx::Rect(expandable_container_->size()); |
| |
| views::AnimationBuilder() |
| .SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .Once() |
| .SetDuration(base::TimeDelta()) |
| .SetTransform(quick_app_button_->layer(), initial_transform) |
| .SetClipRect(expandable_container_->layer(), initial_container_clip_rect) |
| .SetOpacity(quick_app_button_->layer(), 0) |
| .SetOpacity(expandable_container_->layer(), 0) |
| .Then() |
| .SetDuration(kQuickAppSlideSlideInDuration) |
| .SetClipRect(expandable_container_->layer(), container_target_clip_rect, |
| gfx::Tween::ACCEL_20_DECEL_100) |
| .SetTransform(quick_app_button_->layer(), gfx::Transform(), |
| gfx::Tween::ACCEL_20_DECEL_100) |
| .At(kQuickAppButtonFadeInDelay) |
| .SetDuration(kQuickAppButtonFadeInDuration) |
| .SetOpacity(quick_app_button_->layer(), 1) |
| .At(base::TimeDelta()) |
| .SetDuration(kQuickAppContainerFadeInDuration) |
| .SetOpacity(expandable_container_->layer(), 1); |
| } |
| |
| void HomeButton::AnimateQuickAppButtonOut() { |
| const gfx::Transform target_transform = |
| GetTransformForContainerChildBehindHomeButton(); |
| const gfx::Rect container_target_clip_rect = |
| GetExpandableContainerClipRectToHomeButton(); |
| |
| views::AnimationBuilder() |
| .SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .OnEnded(base::BindOnce(&HomeButton::OnQuickAppButtonSlideOutDone, |
| weak_ptr_factory_.GetWeakPtr())) |
| .OnAborted(base::BindOnce(&HomeButton::OnQuickAppButtonSlideOutDone, |
| weak_ptr_factory_.GetWeakPtr())) |
| .Once() |
| .SetDuration(kQuickAppSlideOutDuration) |
| .SetTransform(quick_app_button_->layer(), target_transform, |
| gfx::Tween::ACCEL_20_DECEL_100) |
| .SetClipRect(expandable_container_->layer(), container_target_clip_rect, |
| gfx::Tween::ACCEL_20_DECEL_100) |
| .At(base::TimeDelta()) |
| .SetDuration(kQuickAppFadeOutDuration) |
| .SetOpacity(expandable_container_->layer(), 0) |
| .SetOpacity(quick_app_button_->layer(), 0); |
| } |
| |
| void HomeButton::OnQuickAppButtonSlideOutDone() { |
| RemoveQuickAppButton(); |
| shelf_->shelf_layout_manager()->LayoutShelf(true); |
| } |
| |
| gfx::Transform HomeButton::GetTransformForContainerChildBehindHomeButton() { |
| const int home_button_width = |
| ShelfControlButton::CalculatePreferredSize({}).width(); |
| |
| const int container_visible_width = |
| expandable_container_->width() - home_button_width; |
| |
| gfx::Transform target_transform; |
| if (shelf_->IsHorizontalAlignment()) { |
| target_transform.Translate(base::i18n::IsRTL() ? container_visible_width |
| : -container_visible_width, |
| 0); |
| } else { |
| target_transform.Translate( |
| 0, home_button_width - expandable_container_->height()); |
| } |
| return target_transform; |
| } |
| |
| gfx::Rect HomeButton::GetExpandableContainerClipRectToHomeButton() { |
| const int home_button_width = |
| ShelfControlButton::CalculatePreferredSize({}).width(); |
| const int container_visible_width = |
| expandable_container_->width() - home_button_width; |
| |
| gfx::Rect clip_rect = |
| gfx::Rect(base::i18n::IsRTL() ? container_visible_width : 0, 0, |
| home_button_width, home_button_width); |
| |
| return clip_rect; |
| } |
| |
| BEGIN_METADATA(HomeButton) |
| END_METADATA |
| |
| } // namespace ash |