blob: 962e32f421f09a922363af18d338fd724544c42b [file] [log] [blame]
// Copyright 2019 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/shelf/shelf_navigation_widget.h"
#include "ash/focus_cycler.h"
#include "ash/public/cpp/metrics_util.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/shelf/back_button.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_focus_cycler.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_layout_manager_observer.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/status_area_widget.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "chromeos/constants/chromeos_switches.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/compositor/throughput_tracker.h"
#include "ui/gfx/transform_util.h"
#include "ui/views/animation/bounds_animator.h"
#include "ui/views/background.h"
#include "ui/views/view.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
// The duration of the back button opacity animation.
constexpr base::TimeDelta kButtonOpacityAnimationDuration =
base::TimeDelta::FromMilliseconds(50);
// Returns the bounds for the first button shown in this view (the back
// button in tablet mode, the home button otherwise).
gfx::Rect GetFirstButtonBounds(bool is_shelf_horizontal) {
// ShelfNavigationWidget is larger than the buttons in order to enable child
// views to capture events nearby.
return gfx::Rect(
ShelfConfig::Get()->control_button_edge_spacing(is_shelf_horizontal),
ShelfConfig::Get()->control_button_edge_spacing(!is_shelf_horizontal),
ShelfConfig::Get()->control_size(), ShelfConfig::Get()->control_size());
}
// Returns the bounds for the second button shown in this view (which is
// always the home button and only in tablet mode, which implies a horizontal
// shelf).
gfx::Rect GetSecondButtonBounds() {
// Second button only shows for horizontal shelf.
return gfx::Rect(ShelfConfig::Get()->control_button_edge_spacing(
true /* is_primary_axis_edge */) +
ShelfConfig::Get()->control_size() +
ShelfConfig::Get()->button_spacing(),
ShelfConfig::Get()->control_button_edge_spacing(
false /* is_primary_axis_edge */),
ShelfConfig::Get()->control_size(),
ShelfConfig::Get()->control_size());
}
bool IsBackButtonShown(bool horizontal_alignment) {
// Back button should only be shown in horizontal shelf.
// TODO(https://crbug.com/1102648): Horizontal shelf should be implied by
// tablet mode, but this may not be the case during tablet mode transition as
// shelf layout may get updated before the correct shelf alignment is set.
// Remove this when the linked bug is resolved.
if (!horizontal_alignment)
return false;
// TODO(https://crbug.com/1058205): Test this behavior.
if (ShelfConfig::Get()->is_virtual_keyboard_shown())
return true;
if (!ShelfConfig::Get()->shelf_controls_shown())
return false;
return chromeos::switches::ShouldShowShelfHotseat()
? Shell::Get()->IsInTabletMode() && ShelfConfig::Get()->is_in_app()
: Shell::Get()->IsInTabletMode();
}
bool IsHomeButtonShown() {
return ShelfConfig::Get()->shelf_controls_shown();
}
bool IsHotseatEnabled() {
return Shell::Get()->IsInTabletMode() &&
chromeos::switches::ShouldShowShelfHotseat();
}
// An implicit animation observer that hides a view once the view's opacity
// animation finishes.
// It deletes itself when the animation is done.
class AnimationObserverToHideView : public ui::ImplicitAnimationObserver {
public:
explicit AnimationObserverToHideView(views::View* view) : view_(view) {}
~AnimationObserverToHideView() override = default;
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override {
if (view_->layer()->GetTargetOpacity() == 0.0f)
view_->SetVisible(false);
delete this;
}
private:
views::View* const view_;
};
// Tracks the animation smoothness of a view's bounds animation using
// ui::ThroughputTracker.
class BoundsAnimationReporter : public gfx::AnimationDelegate {
public:
BoundsAnimationReporter(views::View* view,
metrics_util::ReportCallback report_callback)
: tracker_(
view->GetWidget()->GetCompositor()->RequestNewThroughputTracker()) {
tracker_.Start(std::move(report_callback));
}
BoundsAnimationReporter(const BoundsAnimationReporter& other) = delete;
BoundsAnimationReporter& operator=(const BoundsAnimationReporter& other) =
delete;
~BoundsAnimationReporter() override = default;
// gfx::AnimationDelegate:
void AnimationEnded(const gfx::Animation* animation) override {
tracker_.Stop();
}
void AnimationCanceled(const gfx::Animation* animation) override {
tracker_.Cancel();
}
private:
ui::ThroughputTracker tracker_;
};
} // namespace
// An animation metrics reporter for the shelf navigation buttons.
class ASH_EXPORT NavigationButtonAnimationMetricsReporter {
public:
// The different kinds of navigation buttons.
enum class NavigationButtonType {
// The Navigation Widget's back button.
kBackButton,
// The Navigation Widget's home button.
kHomeButton
};
explicit NavigationButtonAnimationMetricsReporter(
NavigationButtonType navigation_button_type)
: navigation_button_type_(navigation_button_type) {}
~NavigationButtonAnimationMetricsReporter() = default;
NavigationButtonAnimationMetricsReporter(
const NavigationButtonAnimationMetricsReporter&) = delete;
NavigationButtonAnimationMetricsReporter& operator=(
const NavigationButtonAnimationMetricsReporter&) = delete;
void ReportSmoothness(HotseatState target_hotseat_state, int smoothness) {
switch (target_hotseat_state) {
case HotseatState::kShownClamshell:
case HotseatState::kShownHomeLauncher:
switch (navigation_button_type_) {
case NavigationButtonType::kBackButton:
UMA_HISTOGRAM_PERCENTAGE(
"Ash.NavigationWidget.BackButton.AnimationSmoothness."
"TransitionToShownHotseat",
smoothness);
break;
case NavigationButtonType::kHomeButton:
UMA_HISTOGRAM_PERCENTAGE(
"Ash.NavigationWidget.HomeButton.AnimationSmoothness."
"TransitionToShownHotseat",
smoothness);
break;
default:
NOTREACHED();
break;
}
break;
case HotseatState::kExtended:
switch (navigation_button_type_) {
case NavigationButtonType::kBackButton:
UMA_HISTOGRAM_PERCENTAGE(
"Ash.NavigationWidget.BackButton.AnimationSmoothness."
"TransitionToExtendedHotseat",
smoothness);
break;
case NavigationButtonType::kHomeButton:
UMA_HISTOGRAM_PERCENTAGE(
"Ash.NavigationWidget.HomeButton.AnimationSmoothness."
"TransitionToExtendedHotseat",
smoothness);
break;
default:
NOTREACHED();
break;
}
break;
case HotseatState::kHidden:
switch (navigation_button_type_) {
case NavigationButtonType::kBackButton:
UMA_HISTOGRAM_PERCENTAGE(
"Ash.NavigationWidget.BackButton.AnimationSmoothness."
"TransitionToHiddenHotseat",
smoothness);
break;
case NavigationButtonType::kHomeButton:
UMA_HISTOGRAM_PERCENTAGE(
"Ash.NavigationWidget.HomeButton.AnimationSmoothness."
"TransitionToHiddenHotseat",
smoothness);
break;
default:
NOTREACHED();
break;
}
break;
default:
NOTREACHED();
break;
}
}
metrics_util::ReportCallback GetReportCallback(
HotseatState target_hotseat_state) {
DCHECK_NE(target_hotseat_state, HotseatState::kNone);
return metrics_util::ForSmoothness(base::BindRepeating(
&NavigationButtonAnimationMetricsReporter::ReportSmoothness,
weak_ptr_factory_.GetWeakPtr(), target_hotseat_state));
}
private:
// The type of navigation button that is animated.
const NavigationButtonType navigation_button_type_;
base::WeakPtrFactory<NavigationButtonAnimationMetricsReporter>
weak_ptr_factory_{this};
};
class ShelfNavigationWidget::Delegate : public views::AccessiblePaneView,
public views::WidgetDelegate {
public:
Delegate(Shelf* shelf, ShelfView* shelf_view);
~Delegate() override;
// Initializes the view.
void Init(ui::Layer* parent_layer);
void UpdateOpaqueBackground();
// views::View:
FocusTraversable* GetPaneFocusTraversable() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
void ReorderChildLayers(ui::Layer* parent_layer) override;
void OnBoundsChanged(const gfx::Rect& old_bounds) override;
// views::AccessiblePaneView:
View* GetDefaultFocusableChild() override;
// views::WidgetDelegate:
void DeleteDelegate() override;
bool CanActivate() const override;
views::Widget* GetWidget() override { return View::GetWidget(); }
const views::Widget* GetWidget() const override { return View::GetWidget(); }
BackButton* back_button() const { return back_button_; }
HomeButton* home_button() const { return home_button_; }
void set_default_last_focusable_child(bool default_last_focusable_child) {
default_last_focusable_child_ = default_last_focusable_child;
}
private:
void SetParentLayer(ui::Layer* layer);
BackButton* back_button_ = nullptr;
HomeButton* home_button_ = nullptr;
// When true, the default focus of the navigation widget is the last
// focusable child.
bool default_last_focusable_child_ = false;
// A background layer that may be visible depending on shelf state.
ui::Layer opaque_background_;
Shelf* shelf_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(Delegate);
};
ShelfNavigationWidget::Delegate::Delegate(Shelf* shelf, ShelfView* shelf_view)
: opaque_background_(ui::LAYER_SOLID_COLOR), shelf_(shelf) {
set_owned_by_client(); // Deleted by DeleteDelegate().
set_allow_deactivate_on_esc(true);
const int control_size = ShelfConfig::Get()->control_size();
std::unique_ptr<BackButton> back_button_ptr =
std::make_unique<BackButton>(shelf);
back_button_ = AddChildView(std::move(back_button_ptr));
back_button_->SetSize(gfx::Size(control_size, control_size));
std::unique_ptr<HomeButton> home_button_ptr =
std::make_unique<HomeButton>(shelf);
home_button_ = AddChildView(std::move(home_button_ptr));
home_button_->set_context_menu_controller(shelf_view);
home_button_->SetSize(gfx::Size(control_size, control_size));
GetViewAccessibility().OverrideNextFocus(shelf->hotseat_widget());
GetViewAccessibility().OverridePreviousFocus(shelf->GetStatusAreaWidget());
opaque_background_.SetName("shelfNavigation/Background");
}
ShelfNavigationWidget::Delegate::~Delegate() = default;
void ShelfNavigationWidget::Delegate::Init(ui::Layer* parent_layer) {
SetParentLayer(parent_layer);
UpdateOpaqueBackground();
}
void ShelfNavigationWidget::Delegate::UpdateOpaqueBackground() {
opaque_background_.SetColor(ShelfConfig::Get()->GetShelfControlButtonColor());
// Hide background if no buttons should be shown.
if (!IsHomeButtonShown() &&
!IsBackButtonShown(shelf_->IsHorizontalAlignment())) {
opaque_background_.SetVisible(false);
return;
}
if (chromeos::switches::ShouldShowShelfHotseat() &&
Shell::Get()->IsInTabletMode() && ShelfConfig::Get()->is_in_app()) {
opaque_background_.SetVisible(false);
return;
}
opaque_background_.SetVisible(true);
int radius = ShelfConfig::Get()->control_border_radius();
gfx::RoundedCornersF rounded_corners = {radius, radius, radius, radius};
if (opaque_background_.rounded_corner_radii() != rounded_corners)
opaque_background_.SetRoundedCornerRadius(rounded_corners);
// The opaque background does not show up when there are two buttons.
gfx::Rect opaque_background_bounds =
GetFirstButtonBounds(shelf_->IsHorizontalAlignment());
opaque_background_.SetBounds(opaque_background_bounds);
opaque_background_.SetBackgroundBlur(
ShelfConfig::Get()->GetShelfControlButtonBlurRadius());
}
bool ShelfNavigationWidget::Delegate::CanActivate() const {
// We don't want mouse clicks to activate us, but we need to allow
// activation when the user is using the keyboard (FocusCycler).
return Shell::Get()->focus_cycler()->widget_activating() == GetWidget();
}
void ShelfNavigationWidget::Delegate::DeleteDelegate() {
delete this;
}
views::FocusTraversable*
ShelfNavigationWidget::Delegate::GetPaneFocusTraversable() {
return this;
}
void ShelfNavigationWidget::Delegate::GetAccessibleNodeData(
ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kToolbar;
node_data->SetName(l10n_util::GetStringUTF8(IDS_ASH_SHELF_ACCESSIBLE_NAME));
ShelfWidget* shelf_widget =
Shelf::ForWindow(GetWidget()->GetNativeWindow())->shelf_widget();
GetViewAccessibility().OverrideNextFocus(shelf_widget->hotseat_widget());
GetViewAccessibility().OverridePreviousFocus(
shelf_widget->status_area_widget());
}
void ShelfNavigationWidget::Delegate::ReorderChildLayers(
ui::Layer* parent_layer) {
views::View::ReorderChildLayers(parent_layer);
parent_layer->StackAtBottom(&opaque_background_);
}
void ShelfNavigationWidget::Delegate::OnBoundsChanged(
const gfx::Rect& old_bounds) {
UpdateOpaqueBackground();
}
views::View* ShelfNavigationWidget::Delegate::GetDefaultFocusableChild() {
return default_last_focusable_child_ ? GetLastFocusableChild()
: GetFirstFocusableChild();
}
void ShelfNavigationWidget::Delegate::SetParentLayer(ui::Layer* layer) {
layer->Add(&opaque_background_);
ReorderLayers();
}
ShelfNavigationWidget::TestApi::TestApi(ShelfNavigationWidget* widget)
: navigation_widget_(widget) {}
ShelfNavigationWidget::TestApi::~TestApi() = default;
bool ShelfNavigationWidget::TestApi::IsHomeButtonVisible() const {
const HomeButton* button = navigation_widget_->delegate_->home_button();
const float opacity = button->layer()->GetTargetOpacity();
DCHECK(opacity == 0.0f || opacity == 1.0f)
<< "Unexpected opacity " << opacity;
return opacity > 0.0f && button->GetVisible();
}
bool ShelfNavigationWidget::TestApi::IsBackButtonVisible() const {
const BackButton* button = navigation_widget_->delegate_->back_button();
const float opacity = button->layer()->GetTargetOpacity();
DCHECK(opacity == 0.0f || opacity == 1.0f)
<< "Unexpected opacity " << opacity;
return opacity > 0.0f && button->GetVisible();
}
views::BoundsAnimator* ShelfNavigationWidget::TestApi::GetBoundsAnimator() {
return navigation_widget_->bounds_animator_.get();
}
ShelfNavigationWidget::ShelfNavigationWidget(Shelf* shelf,
ShelfView* shelf_view)
: shelf_(shelf),
delegate_(new ShelfNavigationWidget::Delegate(shelf, shelf_view)),
bounds_animator_(
std::make_unique<views::BoundsAnimator>(delegate_,
/*use_transforms=*/true)),
back_button_metrics_reporter_(
std::make_unique<NavigationButtonAnimationMetricsReporter>(
NavigationButtonAnimationMetricsReporter::NavigationButtonType::
kBackButton)),
home_button_metrics_reporter_(
std::make_unique<NavigationButtonAnimationMetricsReporter>(
NavigationButtonAnimationMetricsReporter::NavigationButtonType::
kHomeButton)) {
DCHECK(shelf_);
}
ShelfNavigationWidget::~ShelfNavigationWidget() {
// Cancel animations now so the BoundsAnimator doesn't outlive the metrics
// reporter associated to it.
bounds_animator_->Cancel();
}
void ShelfNavigationWidget::Initialize(aura::Window* container) {
DCHECK(container);
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.name = "ShelfNavigationWidget";
params.delegate = delegate_;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.parent = container;
Init(std::move(params));
delegate_->Init(GetLayer());
set_focus_on_creation(false);
GetFocusManager()->set_arrow_key_traversal_enabled_for_widget(true);
SetContentsView(delegate_);
SetSize(CalculateIdealSize(/*only_visible_area=*/false));
UpdateLayout(/*animate=*/false);
}
void ShelfNavigationWidget::OnMouseEvent(ui::MouseEvent* event) {
if (event->IsMouseWheelEvent()) {
ui::MouseWheelEvent* mouse_wheel_event = event->AsMouseWheelEvent();
shelf_->ProcessMouseWheelEvent(mouse_wheel_event, /*from_touchpad=*/false);
return;
}
views::Widget::OnMouseEvent(event);
}
void ShelfNavigationWidget::OnScrollEvent(ui::ScrollEvent* event) {
shelf_->ProcessScrollEvent(event);
if (!event->handled())
views::Widget::OnScrollEvent(event);
}
bool ShelfNavigationWidget::OnNativeWidgetActivationChanged(bool active) {
if (!Widget::OnNativeWidgetActivationChanged(active))
return false;
if (active)
delegate_->SetPaneFocusAndFocusDefault();
return true;
}
void ShelfNavigationWidget::OnGestureEvent(ui::GestureEvent* event) {
// Shelf::ProcessGestureEvent expects an event whose location is in screen
// coordinates - create a copy of the event with the location in screen
// coordinate system.
ui::GestureEvent copy_event(*event);
gfx::Point location_in_screen(copy_event.location());
wm::ConvertPointToScreen(GetNativeWindow(), &location_in_screen);
copy_event.set_location(location_in_screen);
if (shelf_->ProcessGestureEvent(copy_event)) {
event->StopPropagation();
return;
}
views::Widget::OnGestureEvent(event);
}
BackButton* ShelfNavigationWidget::GetBackButton() const {
return IsBackButtonShown(shelf_->IsHorizontalAlignment())
? delegate_->back_button()
: nullptr;
}
HomeButton* ShelfNavigationWidget::GetHomeButton() const {
return IsHomeButtonShown() ? delegate_->home_button() : nullptr;
}
void ShelfNavigationWidget::SetDefaultLastFocusableChild(
bool default_last_focusable_child) {
delegate_->set_default_last_focusable_child(default_last_focusable_child);
}
void ShelfNavigationWidget::CalculateTargetBounds() {
const gfx::Point shelf_origin =
shelf_->shelf_widget()->GetTargetBounds().origin();
gfx::Point nav_origin = gfx::Point(shelf_origin.x(), shelf_origin.y());
gfx::Size nav_size = CalculateIdealSize(/*only_visible_area=*/false);
if (shelf_->IsHorizontalAlignment() && base::i18n::IsRTL()) {
nav_origin.set_x(shelf_->shelf_widget()->GetTargetBounds().size().width() -
nav_size.width());
}
target_bounds_ = gfx::Rect(nav_origin, nav_size);
clip_rect_ = CalculateClipRect();
}
gfx::Rect ShelfNavigationWidget::GetTargetBounds() const {
return target_bounds_;
}
void ShelfNavigationWidget::UpdateLayout(bool animate) {
const bool back_button_shown =
IsBackButtonShown(shelf_->IsHorizontalAlignment());
const bool home_button_shown = IsHomeButtonShown();
const ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager();
// Having a window which is visible but does not have an opacity is an
// illegal state. Also, never show this widget outside of an active session.
if (layout_manager->GetOpacity() &&
layout_manager->is_active_session_state()) {
ShowInactive();
} else {
Hide();
}
// If the widget is currently active, and all the buttons will be hidden,
// focus out to the status area (the widget's focus manager does not properly
// handle the case where the widget does not have another view to focus - it
// would clear the focus, and hit a DCHECK trying to cycle focus within the
// widget).
if (IsActive() && !back_button_shown && !home_button_shown) {
Shelf::ForWindow(GetNativeWindow())
->shelf_focus_cycler()
->FocusOut(true /*reverse*/, SourceView::kShelfNavigationView);
}
// Use the same duration for all parts of the upcoming animation.
const base::TimeDelta animation_duration =
animate ? ShelfConfig::Get()->shelf_animation_duration()
: base::TimeDelta::FromMilliseconds(0);
const HotseatState target_hotseat_state =
layout_manager->CalculateHotseatState(layout_manager->visibility_state(),
layout_manager->auto_hide_state());
const bool update_opacity = !animate || GetLayer()->GetTargetOpacity() !=
layout_manager->GetOpacity();
const bool update_bounds =
!animate || GetLayer()->GetTargetBounds() != target_bounds_;
if (update_opacity || update_bounds) {
ui::ScopedLayerAnimationSettings nav_animation_setter(
GetNativeView()->layer()->GetAnimator());
nav_animation_setter.SetTransitionDuration(animation_duration);
nav_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
nav_animation_setter.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
base::Optional<ui::AnimationThroughputReporter> reporter;
if (animate) {
reporter.emplace(nav_animation_setter.GetAnimator(),
shelf_->GetNavigationWidgetAnimationReportCallback(
target_hotseat_state));
}
if (update_opacity)
GetLayer()->SetOpacity(layout_manager->GetOpacity());
if (update_bounds)
SetBounds(target_bounds_);
}
if (update_bounds && IsHotseatEnabled())
GetLayer()->SetClipRect(clip_rect_);
views::View* const back_button = delegate_->back_button();
UpdateButtonVisibility(back_button, back_button_shown, animate,
back_button_metrics_reporter_.get(),
target_hotseat_state);
views::View* const home_button = delegate_->home_button();
UpdateButtonVisibility(home_button, home_button_shown, animate,
home_button_metrics_reporter_.get(),
target_hotseat_state);
if (back_button_shown) {
// TODO(https://crbug.com/1058205): Test this behavior.
gfx::Transform rotation;
// If the IME virtual keyboard is visible, rotate the back button downwards,
// this indicates it can be used to close the keyboard.
if (ShelfConfig::Get()->is_virtual_keyboard_shown())
rotation.Rotate(270.0);
delegate_->back_button()->layer()->SetTransform(TransformAboutPivot(
delegate_->back_button()->GetCenterPoint(), rotation));
}
gfx::Rect home_button_bounds =
back_button_shown ? GetSecondButtonBounds()
: GetFirstButtonBounds(shelf_->IsHorizontalAlignment());
if (animate) {
if (bounds_animator_->GetTargetBounds(home_button) != home_button_bounds) {
bounds_animator_->SetAnimationDuration(animation_duration);
bounds_animator_->AnimateViewTo(
home_button, home_button_bounds,
std::make_unique<BoundsAnimationReporter>(
home_button, home_button_metrics_reporter_->GetReportCallback(
target_hotseat_state)));
}
} else {
bounds_animator_->Cancel();
home_button->SetBoundsRect(home_button_bounds);
}
back_button->SetBoundsRect(
GetFirstButtonBounds(shelf_->IsHorizontalAlignment()));
delegate_->UpdateOpaqueBackground();
}
void ShelfNavigationWidget::UpdateTargetBoundsForGesture(int shelf_position) {
if (shelf_->IsHorizontalAlignment())
target_bounds_.set_y(shelf_position);
else
target_bounds_.set_x(shelf_position);
}
gfx::Rect ShelfNavigationWidget::GetVisibleBounds() const {
return gfx::Rect(target_bounds_.origin(), clip_rect_.size());
}
void ShelfNavigationWidget::HandleLocaleChange() {
delegate_->home_button()->HandleLocaleChange();
delegate_->back_button()->HandleLocaleChange();
}
void ShelfNavigationWidget::UpdateButtonVisibility(
views::View* button,
bool visible,
bool animate,
NavigationButtonAnimationMetricsReporter* metrics_reporter,
HotseatState target_hotseat_state) {
if (animate && button->layer()->GetTargetOpacity() == visible)
return;
// Update visibility immediately only if making the button visible. When
// hiding the button, the visibility will be updated when the animations
// complete (by AnimationObserverToHideView).
if (visible)
button->SetVisible(true);
button->SetFocusBehavior(visible ? views::View::FocusBehavior::ALWAYS
: views::View::FocusBehavior::NEVER);
ui::ScopedLayerAnimationSettings opacity_settings(
button->layer()->GetAnimator());
opacity_settings.SetTransitionDuration(
animate ? kButtonOpacityAnimationDuration : base::TimeDelta());
opacity_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
base::Optional<ui::AnimationThroughputReporter> reporter;
if (animate) {
reporter.emplace(opacity_settings.GetAnimator(),
metrics_reporter->GetReportCallback(target_hotseat_state));
}
if (!visible)
opacity_settings.AddObserver(new AnimationObserverToHideView(button));
button->layer()->SetOpacity(visible ? 1.0f : 0.0f);
}
gfx::Rect ShelfNavigationWidget::CalculateClipRect() const {
if (IsHotseatEnabled())
return gfx::Rect(CalculateIdealSize(/*only_visible_area=*/true));
return gfx::Rect(target_bounds_.size());
}
gfx::Size ShelfNavigationWidget::CalculateIdealSize(
bool only_visible_area) const {
if (!ShelfConfig::Get()->shelf_controls_shown())
return gfx::Size();
int control_button_number;
if (IsHotseatEnabled() && !only_visible_area) {
// There are home button and back button. So the maximum is 2.
control_button_number = 2;
} else {
control_button_number = CalculateButtonCount();
}
const int control_size = ShelfConfig::Get()->control_size();
int controls_space =
control_button_number * control_size +
(control_button_number - 1) * ShelfConfig::Get()->button_spacing();
const int major_axis_spacing =
2 * ShelfConfig::Get()->control_button_edge_spacing(
shelf_->IsHorizontalAlignment());
const int major_axis_length = controls_space + major_axis_spacing;
// Calculate |minor_axis_spacing|.
int minor_axis_spacing;
if (only_visible_area) {
minor_axis_spacing = 2 * ShelfConfig::Get()->control_button_edge_spacing(
!shelf_->IsHorizontalAlignment());
} else {
// When calculating the minor axis length of the navigation widget, use
// the larger edge spacing between the home launcher state and the in-app
// state. It ensures that the widget' size is constant during the transition
// between these two states.
DCHECK_GT(ShelfConfig::Get()->system_shelf_size(),
ShelfConfig::Get()->in_app_shelf_size());
minor_axis_spacing = ShelfConfig::Get()->system_shelf_size() - control_size;
}
const int minor_axis_length = control_size + minor_axis_spacing;
return shelf_->IsHorizontalAlignment()
? gfx::Size(major_axis_length, minor_axis_length)
: gfx::Size(minor_axis_length, major_axis_length);
}
int ShelfNavigationWidget::CalculateButtonCount() const {
return (IsBackButtonShown(shelf_->IsHorizontalAlignment()) ? 1 : 0) +
(IsHomeButtonShown() ? 1 : 0);
}
} // namespace ash