blob: c7eb04e1f45b1ad90f79a0e7f9f2243c1cfab764 [file] [log] [blame]
// 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/shelf/shelf_widget.h"
#include <memory>
#include <utility>
#include "ash/animation/animation_change_type.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/controls/contextual_tooltip.h"
#include "ash/focus_cycler.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/drag_handle.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/hotseat_transition_animator.h"
#include "ash/shelf/hotseat_widget.h"
#include "ash/shelf/login_shelf_view.h"
#include "ash/shelf/login_shelf_widget.h"
#include "ash/shelf/scrollable_shelf_view.h"
#include "ash/shelf/shelf_background_animator_observer.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shell.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/style_util.h"
#include "ash/system/status_area_widget.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/work_area_insets.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_delegate.h"
#include "ui/compositor/layer_owner.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/views/accessible_pane_view.h"
#include "ui/views/focus/focus_search.h"
#include "ui/views/highlight_border.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
constexpr int kShelfBlurRadius = 30;
// The maximum size of the opaque layer during an "overshoot" (drag away from
// the screen edge).
constexpr int kShelfMaxOvershootHeight = 60;
constexpr int kDragHandleCornerRadius = 2;
// Return the first or last focusable child of |root|.
views::View* FindFirstOrLastFocusableChild(views::View* root,
bool find_last_child) {
views::FocusSearch search(root, find_last_child /*cycle*/,
false /*accessibility_mode*/);
views::FocusTraversable* dummy_focus_traversable;
views::View* dummy_focus_traversable_view;
return search.FindNextFocusableView(
root,
find_last_child ? views::FocusSearch::SearchDirection::kBackwards
: views::FocusSearch::SearchDirection::kForwards,
views::FocusSearch::TraversalDirection::kDown,
views::FocusSearch::StartingViewPolicy::kSkipStartingView,
views::FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog,
&dummy_focus_traversable, &dummy_focus_traversable_view);
}
// Sets the shelf opacity to 0 when the shelf is done hiding to avoid getting
// rid of blur.
class HideAnimationObserver : public ui::ImplicitAnimationObserver {
public:
explicit HideAnimationObserver(ui::Layer* layer) : layer_(layer) {}
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsScheduled() override {}
void OnImplicitAnimationsCompleted() override { layer_->SetOpacity(0); }
private:
// Unowned.
ui::Layer* const layer_;
};
class ShelfBackgroundLayerDelegate : public ui::LayerOwner,
public ui::LayerDelegate {
public:
ShelfBackgroundLayerDelegate(Shelf* shelf,
views::View* owner_view,
bool draw_highlight_border)
: shelf_(shelf),
owner_view_(owner_view),
draw_highlight_border_(draw_highlight_border) {}
ShelfBackgroundLayerDelegate(const ShelfBackgroundLayerDelegate&) = delete;
ShelfBackgroundLayerDelegate& operator=(const ShelfBackgroundLayerDelegate&) =
delete;
~ShelfBackgroundLayerDelegate() override {}
void Initialize() {
// If the shelf does not have highlight border, it will be monochromatic, so
// it can use a solid color layer.
auto layer = std::make_unique<ui::Layer>(
draw_highlight_border_ ? ui::LAYER_TEXTURED : ui::LAYER_SOLID_COLOR);
layer->SetName("shelf/Background");
if (draw_highlight_border_) {
layer->set_delegate(this);
layer->SetFillsBoundsOpaquely(false);
}
SetLayer(std::move(layer));
}
// Sets the shelf background color.
void SetBackgroundColor(SkColor color) {
background_color_ = color;
if (draw_highlight_border_) {
layer()->SchedulePaint(gfx::Rect(layer()->size()));
} else {
layer()->SetColor(color);
}
}
// Sets the highlight border type to use if shelf uses highlight border.
// No-op if `draw_highlight_border_` is false.
void SetBorderType(views::HighlightBorder::Type type) {
if (!draw_highlight_border_)
return;
highlight_border_type_ = type;
layer()->SchedulePaint(gfx::Rect(layer()->size()));
}
// Sets the rounded corners used by the shelf.
void SetRoundedCornerRadius(float radius) {
corner_radius_ = radius;
layer()->SetRoundedCornerRadius({
shelf_->SelectValueForShelfAlignment(radius, 0.0f, radius),
shelf_->SelectValueForShelfAlignment(radius, radius, 0.0f),
shelf_->SelectValueForShelfAlignment(0.0f, radius, 0.0f),
shelf_->SelectValueForShelfAlignment(0.0f, 0.0f, radius),
});
// Schedule paint to repaint the highlight border.
if (draw_highlight_border_)
layer()->SchedulePaint(gfx::Rect(layer()->size()));
}
void SetLoginShelfFromShelfWidget(LoginShelfView* view) {
login_shelf_from_shelf_widget_ = view;
}
SkColor background_color() const { return background_color_; }
private:
// views::LayerDelegate:
void OnPaintLayer(const ui::PaintContext& context) override {
ui::PaintRecorder recorder(context, layer()->size());
gfx::Canvas* canvas = recorder.canvas();
// cc::PaintFlags flags for the background.
cc::PaintFlags flags;
flags.setColor(background_color_);
flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kFill_Style);
canvas->DrawRoundRect(gfx::Rect(layer()->size()), corner_radius_, flags);
// Don't draw highlight border in login screen.
LoginShelfView* login_shelf_view =
features::IsUseLoginShelfWidgetEnabled()
? shelf_->login_shelf_widget()->login_shelf_view()
: login_shelf_from_shelf_widget_;
if (login_shelf_view && login_shelf_view->GetVisible())
return;
if (corner_radius_ > 0) {
views::HighlightBorder::PaintBorderToCanvas(
canvas, *owner_view_, gfx::Rect(layer()->size()),
gfx::RoundedCornersF(corner_radius_), highlight_border_type_, false);
} else {
// If the shelf corners are not rounded, only paint the highlight border
// on the inner edge of the shelf to separate the shelf and the work area.
PaintEdgeToCanvas(canvas);
}
}
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {
layer()->SchedulePaint(gfx::Rect(layer()->size()));
}
void PaintEdgeToCanvas(gfx::Canvas* canvas) {
SkColor inner_color = views::HighlightBorder::GetHighlightColor(
*owner_view_, highlight_border_type_, /*use_light_colors=*/false);
SkColor outer_color = views::HighlightBorder::GetBorderColor(
*owner_view_, highlight_border_type_, /*use_light_colors=*/false);
const int border_thickness = views::kHighlightBorderThickness;
const float half_thickness = border_thickness / 2.0f;
cc::PaintFlags flags;
flags.setStrokeWidth(border_thickness);
flags.setColor(outer_color);
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setAntiAlias(true);
// Scale bounds and corner radius with device scale factor to make sure
// border bounds match content bounds but keep border stroke width the same.
gfx::ScopedCanvas scoped_canvas(canvas);
const float dsf = canvas->UndoDeviceScaleFactor();
const gfx::RectF pixel_bounds =
gfx::ConvertRectToPixels(gfx::Rect(layer()->size()), dsf);
// The points that are used to draw the highlighted edge.
gfx::PointF start_point, end_point;
switch (shelf_->alignment()) {
case ShelfAlignment::kBottom:
case ShelfAlignment::kBottomLocked:
start_point = gfx::PointF(pixel_bounds.origin());
end_point = gfx::PointF(pixel_bounds.top_right());
start_point.Offset(0, half_thickness);
end_point.Offset(0, half_thickness);
break;
case ShelfAlignment::kLeft:
start_point = gfx::PointF(pixel_bounds.top_right());
end_point = gfx::PointF(pixel_bounds.bottom_right());
start_point.Offset(-half_thickness, 0);
end_point.Offset(-half_thickness, 0);
break;
case ShelfAlignment::kRight:
start_point = gfx::PointF(pixel_bounds.origin());
end_point = gfx::PointF(pixel_bounds.bottom_left());
start_point.Offset(half_thickness, 0);
end_point.Offset(half_thickness, 0);
break;
}
// Draw the outer line.
canvas->DrawLine(start_point, end_point, flags);
switch (shelf_->alignment()) {
case ShelfAlignment::kBottom:
case ShelfAlignment::kBottomLocked:
start_point.Offset(0, border_thickness);
end_point.Offset(0, border_thickness);
break;
case ShelfAlignment::kLeft:
start_point.Offset(-border_thickness, 0);
end_point.Offset(-border_thickness, 0);
break;
case ShelfAlignment::kRight:
start_point.Offset(border_thickness, 0);
end_point.Offset(border_thickness, 0);
break;
}
// Draw the inner line.
flags.setColor(inner_color);
canvas->DrawLine(start_point, end_point, flags);
}
Shelf* const shelf_;
views::View* const owner_view_;
const bool draw_highlight_border_;
// The pointer to the login shelf view that resides in the shelf widget. Set
// only when the login shelf widget is not in use.
// TODO(https://crbug.com/1343114): remove this data member and its related
// code after the login shelf widget is ready.
LoginShelfView* login_shelf_from_shelf_widget_ = nullptr;
SkColor background_color_;
float corner_radius_ = 0.0f;
views::HighlightBorder::Type highlight_border_type_ =
views::HighlightBorder::Type::kHighlightBorder1;
};
} // namespace
// The contents view of the Shelf. In an active session, this is used to
// display a semi-opaque background behind the shelf. Outside of an active
// session, this also contains the login shelf view.
class ShelfWidget::DelegateView : public views::WidgetDelegate,
public views::AccessiblePaneView,
public ShelfBackgroundAnimatorObserver,
public HotseatTransitionAnimator::Observer {
public:
DelegateView(ShelfWidget* shelf_widget, Shelf* shelf);
DelegateView(const DelegateView&) = delete;
DelegateView& operator=(const DelegateView&) = delete;
~DelegateView() override;
void set_focus_cycler(FocusCycler* focus_cycler) {
focus_cycler_ = focus_cycler;
}
FocusCycler* focus_cycler() { return focus_cycler_; }
void SetParentLayer(ui::Layer* layer);
// Adds the login shelf view as a child of this view.
// Returns a pointer to the login shelf view passed in as an argument.
LoginShelfView* AddLoginShelfView(
std::unique_ptr<LoginShelfView> login_shelf_view) {
DCHECK(!features::IsUseLoginShelfWidgetEnabled());
login_shelf_view_ = AddChildView(std::move(login_shelf_view));
opaque_background_.SetLoginShelfFromShelfWidget(login_shelf_view_);
return login_shelf_view_;
}
void set_default_last_focusable_child(bool default_last_focusable_child) {
default_last_focusable_child_ = default_last_focusable_child;
}
// Immediately hides the layer used to draw the shelf background.
void HideOpaqueBackground();
// Immediately shows the layer used to draw the shelf background.
void ShowOpaqueBackground();
// views::WidgetDelegate:
views::Widget* GetWidget() override { return View::GetWidget(); }
const views::Widget* GetWidget() const override { return View::GetWidget(); }
// views::View:
void OnThemeChanged() override;
bool CanActivate() const override;
void ReorderChildLayers(ui::Layer* parent_layer) override;
void OnWidgetInitialized() override;
void UpdateBackgroundBlur();
void UpdateOpaqueBackground();
void UpdateDragHandle();
// This will be called when the parent local bounds change.
void OnBoundsChanged(const gfx::Rect& old_bounds) override;
// views::AccessiblePaneView:
views::View* GetDefaultFocusableChild() override;
void Layout() override;
// ShelfBackgroundAnimatorObserver:
void UpdateShelfBackground(SkColor color) override;
// HotseatBackgroundAnimator::Observer:
void OnHotseatTransitionAnimationWillStart(HotseatState from_state,
HotseatState to_state) override;
void OnHotseatTransitionAnimationEnded(HotseatState from_state,
HotseatState to_state) override;
// Hide or show the the |animating_background_| layer.
void ShowAnimatingBackground(bool show);
SkColor GetShelfBackgroundColor() const;
ui::Layer* opaque_background_layer() { return opaque_background_.layer(); }
ShelfBackgroundLayerDelegate* opaque_background() {
return &opaque_background_;
}
ui::Layer* animating_background() { return &animating_background_; }
ui::Layer* animating_drag_handle() { return &animating_drag_handle_; }
DragHandle* drag_handle() { return drag_handle_; }
private:
// Whether |opaque_background_| is explicitly hidden during an animation.
// Prevents calls to UpdateOpaqueBackground from inadvertently showing
// |opaque_background_| during animations.
bool hide_background_for_transitions_ = false;
ShelfWidget* const shelf_widget_;
FocusCycler* focus_cycler_ = nullptr;
// Pointer to the login shelf view - visible only when the session is
// inactive. The view is owned by this view's hierarchy.
// Set only when the login shelf widget is not in use.
// TODO(https://crbug.com/1343114): remove this data member when the login
// shelf widget is in use.
LoginShelfView* login_shelf_view_ = nullptr;
// A background layer that may be visible depending on a
// ShelfBackgroundAnimator.
ShelfBackgroundLayerDelegate opaque_background_;
// A background layer used to animate hotseat transitions.
ui::Layer animating_background_;
// A layer to animate the drag handle during hotseat transitions.
ui::Layer animating_drag_handle_;
// A drag handle shown in tablet mode when we are not on the home screen.
// Owned by the view hierarchy.
DragHandle* drag_handle_ = nullptr;
// When true, the default focus of the shelf is the last focusable child.
bool default_last_focusable_child_ = false;
// Cache the state of the background blur so that it can be updated only
// when necessary.
bool background_is_currently_blurred_ = false;
};
ShelfWidget::DelegateView::DelegateView(ShelfWidget* shelf_widget, Shelf* shelf)
: shelf_widget_(shelf_widget),
opaque_background_(
shelf,
this,
/*draw_highlight_border=*/features::IsDarkLightModeEnabled()),
animating_background_(ui::LAYER_SOLID_COLOR),
animating_drag_handle_(ui::LAYER_SOLID_COLOR) {
animating_background_.SetName("shelf/Animation");
animating_background_.Add(&animating_drag_handle_);
opaque_background_.Initialize();
DCHECK(shelf_widget_);
SetOwnedByWidget(true);
set_allow_deactivate_on_esc(true);
// |animating_background_| will be made visible during hotseat animations.
ShowAnimatingBackground(false);
drag_handle_ = AddChildView(
std::make_unique<DragHandle>(kDragHandleCornerRadius, shelf));
animating_drag_handle_.SetRoundedCornerRadius(
{kDragHandleCornerRadius, kDragHandleCornerRadius,
kDragHandleCornerRadius, kDragHandleCornerRadius});
}
ShelfWidget::DelegateView::~DelegateView() = default;
void ShelfWidget::DelegateView::SetParentLayer(ui::Layer* layer) {
layer->Add(opaque_background_layer());
ReorderLayers();
// Animating background is only shown during hotseat state transitions to
// animate the background from below the shelf. At the same time the shelf
// widget may be animating between in-app and system shelf. Make animating
// background the sibling of the shelf widget to avoid shelf widget animation
// from interfering with the animating background animation.
layer->parent()->Add(&animating_background_);
layer->parent()->StackAtBottom(&animating_background_);
}
void ShelfWidget::DelegateView::HideOpaqueBackground() {
hide_background_for_transitions_ = true;
opaque_background_layer()->SetVisible(false);
drag_handle_->SetVisible(false);
}
void ShelfWidget::DelegateView::ShowOpaqueBackground() {
hide_background_for_transitions_ = false;
UpdateOpaqueBackground();
UpdateDragHandle();
UpdateBackgroundBlur();
}
void ShelfWidget::DelegateView::OnThemeChanged() {
views::AccessiblePaneView::OnThemeChanged();
animating_background_.SetColor(
ShelfConfig::Get()->GetMaximizedShelfColor(GetWidget()));
shelf_widget_->background_animator_.PaintBackground(
shelf_widget_->shelf_layout_manager()->GetShelfBackgroundType(),
AnimationChangeType::IMMEDIATE);
animating_drag_handle_.SetColor(
GetColorProvider()->GetColor(kColorAshShelfHandleColor));
}
bool ShelfWidget::DelegateView::CanActivate() const {
if (features::IsUseLoginShelfWidgetEnabled())
return false;
// This widget only contains anything interesting to activate in login/lock
// screen mode. Only allow activation from the focus cycler, not from mouse
// events, etc.
return login_shelf_view_->GetVisible() && focus_cycler_ &&
focus_cycler_->widget_activating() == GetWidget();
}
void ShelfWidget::DelegateView::ReorderChildLayers(ui::Layer* parent_layer) {
views::View::ReorderChildLayers(parent_layer);
parent_layer->StackAtBottom(opaque_background_layer());
}
void ShelfWidget::DelegateView::OnWidgetInitialized() {
UpdateOpaqueBackground();
}
void ShelfWidget::DelegateView::UpdateBackgroundBlur() {
if (hide_background_for_transitions_)
return;
// Blur only if the background is visible.
const bool should_blur_background =
opaque_background_layer()->visible() &&
shelf_widget_->shelf_layout_manager()->ShouldBlurShelfBackground();
if (should_blur_background == background_is_currently_blurred_)
return;
opaque_background_layer()->SetBackgroundBlur(
should_blur_background ? kShelfBlurRadius : 0);
opaque_background_layer()->SetBackdropFilterQuality(
ColorProvider::kBackgroundBlurQuality);
background_is_currently_blurred_ = should_blur_background;
}
void ShelfWidget::DelegateView::UpdateOpaqueBackground() {
if (hide_background_for_transitions_)
return;
// Shell could be destroying.
if (!Shell::Get()->tablet_mode_controller())
return;
gfx::Rect opaque_background_bounds = GetLocalBounds();
const Shelf* shelf = shelf_widget_->shelf();
const ShelfBackgroundType background_type =
shelf_widget_->GetBackgroundType();
const bool tablet_mode = Shell::Get()->IsInTabletMode();
const bool in_app = ShelfConfig::Get()->is_in_app();
const bool split_view = ShelfConfig::Get()->in_split_view_with_overview();
bool show_opaque_background = !tablet_mode || in_app || split_view;
if (show_opaque_background != opaque_background_layer()->visible())
opaque_background_layer()->SetVisible(show_opaque_background);
// Extend the opaque layer a little bit to handle "overshoot" gestures
// gracefully (the user drags the shelf further than it can actually go).
// That way:
// 1) When the shelf has rounded corners, only two of them are visible,
// 2) Even when the shelf is squared, it doesn't tear off the screen edge
// when dragged away.
// To achieve this, we extend the layer in the same direction where the shelf
// is aligned (downwards for a bottom shelf, etc.).
const float radius = ShelfConfig::Get()->shelf_size() / 2.0f;
// We can easily round only 2 corners out of 4 which means we don't need as
// much extra shelf height.
const int safety_margin = kShelfMaxOvershootHeight;
opaque_background_bounds.Inset(gfx::Insets::TLBR(
0, -shelf->SelectValueForShelfAlignment(0, safety_margin, 0),
-shelf->SelectValueForShelfAlignment(safety_margin, 0, 0),
-shelf->SelectValueForShelfAlignment(0, 0, safety_margin)));
const bool is_vertical_alignment_in_overview =
!shelf->IsHorizontalAlignment() && ShelfConfig::Get()->in_overview_mode();
// Show rounded corners except in maximized (which includes split view) mode,
// or whenever we are "in app", or the shelf is on the vertical alignment in
// overview mode.
if (background_type == ShelfBackgroundType::kMaximized ||
background_type == ShelfBackgroundType::kInApp ||
(tablet_mode && (in_app || split_view)) ||
is_vertical_alignment_in_overview) {
opaque_background_.SetRoundedCornerRadius(0);
} else {
opaque_background_.SetRoundedCornerRadius(radius);
}
opaque_background_layer()->SetBounds(opaque_background_bounds);
UpdateDragHandle();
UpdateBackgroundBlur();
SchedulePaint();
}
void ShelfWidget::DelegateView::UpdateDragHandle() {
if (!Shell::Get()->IsInTabletMode()) {
drag_handle_->SetVisible(false);
return;
}
if ((!ShelfConfig::Get()->in_split_view_with_overview() &&
!ShelfConfig::Get()->is_in_app()) ||
hide_background_for_transitions_) {
drag_handle_->SetVisible(false);
return;
}
drag_handle_->SetVisible(true);
}
void ShelfWidget::DelegateView::OnBoundsChanged(const gfx::Rect& old_bounds) {
UpdateOpaqueBackground();
// Layout the animating background layer below the shelf bounds (the layer
// will be transformed up as needed during hotseat state transitions).
const gfx::Rect widget_bounds = GetWidget()->GetLayer()->bounds();
animating_background_.SetBounds(
gfx::Rect(gfx::Point(widget_bounds.x(), widget_bounds.bottom()),
gfx::Size(widget_bounds.width(),
ShelfConfig::Get()->in_app_shelf_size())));
// The StatusAreaWidget could be gone before this is called during display
// tear down.
if (shelf_widget_->status_area_widget())
shelf_widget_->status_area_widget()->UpdateCollapseState();
}
views::View* ShelfWidget::DelegateView::GetDefaultFocusableChild() {
DCHECK(!features::IsUseLoginShelfWidgetEnabled());
if (login_shelf_view_->GetVisible()) {
return FindFirstOrLastFocusableChild(login_shelf_view_,
default_last_focusable_child_);
}
// If the login shelf view is not visible, there is nothing else to focus
// in this view.
return nullptr;
}
void ShelfWidget::DelegateView::Layout() {
if (!features::IsUseLoginShelfWidgetEnabled())
login_shelf_view_->SetBoundsRect(GetLocalBounds());
// Center drag handle within the expected in-app shelf bounds - it's safe to
// assume bottom shelf, given that the drag handle is only shown within the
// bottom shelf (either in tablet mode, or on login/lock screen)
gfx::Rect drag_handle_bounds = GetLocalBounds();
drag_handle_bounds.Inset(gfx::Insets().set_top(
drag_handle_bounds.height() -
ShelfConfig::Get()->shelf_drag_handle_centering_size()));
drag_handle_bounds.ClampToCenteredSize(ShelfConfig::Get()->DragHandleSize());
drag_handle_->SetBoundsRect(drag_handle_bounds);
}
void ShelfWidget::DelegateView::UpdateShelfBackground(SkColor color) {
opaque_background_.SetBackgroundColor(color);
UpdateOpaqueBackground();
}
void ShelfWidget::DelegateView::OnHotseatTransitionAnimationWillStart(
HotseatState from_state,
HotseatState to_state) {
ShowAnimatingBackground(true);
// If animating from a kShownHomeLauncher hotseat, the animating background
// will animate from the hotseat background into the in-app shelf, so hide the
// real shelf background until the animation is complete.
if (from_state == HotseatState::kShownHomeLauncher)
HideOpaqueBackground();
}
void ShelfWidget::DelegateView::OnHotseatTransitionAnimationEnded(
HotseatState from_state,
HotseatState to_state) {
ShowAnimatingBackground(false);
// NOTE: The from and to state may not match the transition states for which
// the background was hidden (if the original animation got interrupted by
// another transition, only the later animation end will be reported).
if (hide_background_for_transitions_)
ShowOpaqueBackground();
}
void ShelfWidget::DelegateView::ShowAnimatingBackground(bool show) {
animating_background_.SetVisible(show);
}
SkColor ShelfWidget::DelegateView::GetShelfBackgroundColor() const {
return opaque_background_.background_color();
}
bool ShelfWidget::GetHitTestRects(aura::Window* target,
gfx::Rect* hit_test_rect_mouse,
gfx::Rect* hit_test_rect_touch) {
// TODO(https://crbug.com/1343114): refactor the code below after the login
// shelf widget is ready.
DCHECK(!features::IsUseLoginShelfWidgetEnabled());
// This should only get called when the login shelf is visible, i.e. not
// during an active session. In an active session, hit test rects should be
// calculated higher up in the class hierarchy by |EasyResizeWindowTargeter|.
// When in OOBE or locked/login screen, let events pass through empty parts of
// the shelf.
DCHECK(login_shelf_view_->GetVisible());
gfx::Rect login_view_button_bounds =
login_shelf_view_->ConvertRectToWidget(login_shelf_view_->GetMirroredRect(
login_shelf_view_->get_button_union_bounds()));
aura::Window* source = login_shelf_view_->GetWidget()->GetNativeWindow();
aura::Window::ConvertRectToTarget(source, target->parent(),
&login_view_button_bounds);
*hit_test_rect_mouse = login_view_button_bounds;
*hit_test_rect_touch = login_view_button_bounds;
return true;
}
base::ScopedClosureRunner ShelfWidget::ForceShowHotseatInTabletMode() {
++force_show_hotseat_count_;
if (force_show_hotseat_count_ == 1)
shelf_layout_manager_->UpdateVisibilityState();
return base::ScopedClosureRunner(base::BindOnce(
&ShelfWidget::ResetForceShowHotseat, weak_ptr_factory_.GetWeakPtr()));
}
bool ShelfWidget::IsHotseatForcedShowInTabletMode() const {
return force_show_hotseat_count_ > 0;
}
ui::Layer* ShelfWidget::GetOpaqueBackground() {
return delegate_view_->opaque_background_layer();
}
ui::Layer* ShelfWidget::GetAnimatingBackground() {
return delegate_view_->animating_background();
}
ui::Layer* ShelfWidget::GetAnimatingDragHandle() {
return delegate_view_->animating_drag_handle();
}
DragHandle* ShelfWidget::GetDragHandle() {
return delegate_view_->drag_handle();
}
void ShelfWidget::ScheduleShowDragHandleNudge() {
delegate_view_->drag_handle()->ScheduleShowDragHandleNudge();
}
void ShelfWidget::HideDragHandleNudge(
contextual_tooltip::DismissNudgeReason context) {
delegate_view_->drag_handle()->HideDragHandleNudge(context);
}
void ShelfWidget::SetLoginShelfButtonOpacity(float target_opacity) {
// TODO(https://crbug.com/1343114): remove this function after the login shelf
// widget is ready.
DCHECK(!features::IsUseLoginShelfWidgetEnabled());
if (login_shelf_view_->GetVisible())
login_shelf_view_->SetButtonOpacity(target_opacity);
}
ShelfWidget::ShelfWidget(Shelf* shelf)
: shelf_(shelf),
background_animator_(shelf_, Shell::Get()->wallpaper_controller()),
shelf_layout_manager_owned_(
std::make_unique<ShelfLayoutManager>(this, shelf)),
shelf_layout_manager_(shelf_layout_manager_owned_.get()),
delegate_view_(new DelegateView(this, shelf_)),
scoped_session_observer_(this) {
DCHECK(shelf_);
}
ShelfWidget::~ShelfWidget() {
// Must call Shutdown() before destruction.
DCHECK(!status_area_widget());
}
void ShelfWidget::Initialize(aura::Window* shelf_container) {
DCHECK(shelf_container);
// TODO(https://crbug.com/1343114): remove it after the login shelf view is
// moved to its own widget.
if (!features::IsUseLoginShelfWidgetEnabled()) {
login_shelf_view_ =
delegate_view_->AddLoginShelfView(std::make_unique<LoginShelfView>(
RootWindowController::ForWindow(shelf_container)
->lock_screen_action_background_controller()));
}
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.name = "ShelfWidget";
params.layer_type = ui::LAYER_NOT_DRAWN;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.delegate = delegate_view_;
params.parent = shelf_container;
Init(std::move(params));
// The shelf should not take focus when initially shown.
set_focus_on_creation(false);
delegate_view_->SetParentLayer(GetLayer());
SetContentsView(delegate_view_);
shelf_layout_manager_->AddObserver(this);
shelf_container->SetLayoutManager(std::move(shelf_layout_manager_owned_));
shelf_layout_manager_->InitObservers();
background_animator_.Init(ShelfBackgroundType::kDefaultBg);
background_animator_.PaintBackground(
shelf_layout_manager_->GetShelfBackgroundType(),
AnimationChangeType::IMMEDIATE);
background_animator_.AddObserver(delegate_view_);
shelf_->AddObserver(this);
// Sets initial session state to make sure the UI is properly shown.
OnSessionStateChanged(Shell::Get()->session_controller()->GetSessionState());
delegate_view_->SetEnableArrowKeyTraversal(true);
}
void ShelfWidget::Shutdown() {
hotseat_transition_animator_->RemoveObserver(delegate_view_);
// Shutting down the status area widget may cause some widgets (e.g. bubbles)
// to close, so uninstall the ShelfLayoutManager event filters first. Don't
// reset the pointer until later because other widgets (e.g. app list) may
// access it later in shutdown.
shelf_layout_manager_->PrepareForShutdown();
Shell::Get()->focus_cycler()->RemoveWidget(shelf_->status_area_widget());
Shell::Get()->focus_cycler()->RemoveWidget(navigation_widget());
Shell::Get()->focus_cycler()->RemoveWidget(hotseat_widget());
// Don't need to update the shelf background during shutdown.
background_animator_.RemoveObserver(delegate_view_);
shelf_->RemoveObserver(this);
// Don't need to observe focus/activation during shutdown.
Shell::Get()->focus_cycler()->RemoveWidget(this);
SetFocusCycler(nullptr);
}
ShelfBackgroundType ShelfWidget::GetBackgroundType() const {
return background_animator_.target_background_type();
}
void ShelfWidget::RegisterHotseatWidget(HotseatWidget* hotseat_widget) {
// Show a context menu for right clicks anywhere on the shelf widget.
delegate_view_->set_context_menu_controller(hotseat_widget->GetShelfView());
hotseat_transition_animator_ =
std::make_unique<HotseatTransitionAnimator>(this);
hotseat_transition_animator_->AddObserver(delegate_view_);
shelf_->hotseat_widget()->OnHotseatTransitionAnimatorCreated(
hotseat_transition_animator());
}
void ShelfWidget::PostCreateShelf() {
ash::FocusCycler* focus_cycler = Shell::Get()->focus_cycler();
SetFocusCycler(focus_cycler);
// Add widgets to |focus_cycler| in the desired focus order in LTR.
focus_cycler->AddWidget(navigation_widget());
hotseat_widget()->SetFocusCycler(focus_cycler);
focus_cycler->AddWidget(status_area_widget());
shelf_layout_manager_->LayoutShelf();
shelf_layout_manager_->UpdateAutoHideState();
ShowIfHidden();
}
bool ShelfWidget::IsShowingMenu() const {
return hotseat_widget()->GetShelfView()->IsShowingMenu();
}
void ShelfWidget::SetFocusCycler(FocusCycler* focus_cycler) {
delegate_view_->set_focus_cycler(focus_cycler);
if (focus_cycler)
focus_cycler->AddWidget(this);
}
FocusCycler* ShelfWidget::GetFocusCycler() {
return delegate_view_->focus_cycler();
}
gfx::Rect ShelfWidget::GetScreenBoundsOfItemIconForWindow(
aura::Window* window) {
ShelfID id = ShelfID::Deserialize(window->GetProperty(kShelfIDKey));
if (id.IsNull())
return gfx::Rect();
return hotseat_widget()
->scrollable_shelf_view()
->GetTargetScreenBoundsOfItemIcon(id);
}
gfx::Rect ShelfWidget::GetVisibleShelfBounds() const {
gfx::Rect shelf_region = GetWindowBoundsInScreen();
const display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(GetNativeWindow());
DCHECK(!display.bounds().IsEmpty());
shelf_region.Intersect(display.bounds());
return screen_util::SnapBoundsToDisplayEdge(shelf_region, GetNativeWindow());
}
ApplicationDragAndDropHost* ShelfWidget::GetDragAndDropHostForAppList() {
return hotseat_widget()->GetShelfView();
}
void ShelfWidget::set_default_last_focusable_child(
bool default_last_focusable_child) {
delegate_view_->set_default_last_focusable_child(
default_last_focusable_child);
}
LoginShelfView* ShelfWidget::GetLoginShelfView() {
if (features::IsUseLoginShelfWidgetEnabled())
return shelf_->login_shelf_widget()->login_shelf_view();
return login_shelf_view_;
}
bool ShelfWidget::OnNativeWidgetActivationChanged(bool active) {
// TODO(https://crbug.com/1343114): remove this function after the login shelf
// widget is ready.
if (features::IsUseLoginShelfWidgetEnabled())
return false;
if (!Widget::OnNativeWidgetActivationChanged(active))
return false;
if (active) {
// This widget should not get activated in an active session.
DCHECK(login_shelf_view_->GetVisible());
delegate_view_->SetPaneFocusAndFocusDefault();
}
return true;
}
void ShelfWidget::WillDeleteShelfLayoutManager() {
shelf_layout_manager_->RemoveObserver(this);
shelf_layout_manager_ = nullptr;
}
void ShelfWidget::OnHotseatStateChanged(HotseatState old_state,
HotseatState new_state) {
// |hotseat_transition_animator_| could be released when this is
// called during shutdown.
if (!hotseat_transition_animator_)
return;
hotseat_transition_animator_->OnHotseatStateChanged(old_state, new_state);
if (new_state == HotseatState::kExtended) {
delegate_view_->opaque_background()->SetBorderType(
views::HighlightBorder::Type::kHighlightBorder2);
} else {
delegate_view_->opaque_background()->SetBorderType(
views::HighlightBorder::Type::kHighlightBorder1);
}
}
void ShelfWidget::OnBackgroundTypeChanged(ShelfBackgroundType background_type,
AnimationChangeType change_type) {
delegate_view_->UpdateOpaqueBackground();
}
void ShelfWidget::CalculateTargetBounds() {
const ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager();
const int shelf_size = ShelfConfig::Get()->shelf_size();
// By default, show the whole shelf on the screen.
int shelf_in_screen_portion = shelf_size;
const WorkAreaInsets* const work_area =
WorkAreaInsets::ForWindow(GetNativeWindow());
if (layout_manager->is_shelf_auto_hidden()) {
const display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(
GetNativeWindow());
shelf_in_screen_portion =
Shell::Get()->app_list_controller()->GetTargetVisibility(display.id())
? shelf_size
: ShelfConfig::Get()->hidden_shelf_in_screen_portion();
} else if (layout_manager->visibility_state() == SHELF_HIDDEN ||
work_area->IsKeyboardShown()) {
shelf_in_screen_portion = 0;
}
gfx::Rect available_bounds =
screen_util::GetDisplayBoundsWithShelf(GetNativeWindow());
available_bounds.Inset(work_area->GetAccessibilityInsets());
int shelf_width =
shelf_->PrimaryAxisValue(available_bounds.width(), shelf_size);
int shelf_height =
shelf_->PrimaryAxisValue(shelf_size, available_bounds.height());
const int shelf_primary_position = shelf_->SelectValueForShelfAlignment(
available_bounds.bottom() - shelf_in_screen_portion,
available_bounds.x() - shelf_size + shelf_in_screen_portion,
available_bounds.right() - shelf_in_screen_portion);
gfx::Point shelf_origin = shelf_->SelectValueForShelfAlignment(
gfx::Point(available_bounds.x(), shelf_primary_position),
gfx::Point(shelf_primary_position, available_bounds.y()),
gfx::Point(shelf_primary_position, available_bounds.y()));
target_bounds_ =
gfx::Rect(shelf_origin.x(), shelf_origin.y(), shelf_width, shelf_height);
}
void ShelfWidget::UpdateLayout(bool animate) {
const ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager();
hide_animation_observer_.reset();
gfx::Rect current_shelf_bounds = GetWindowBoundsInScreen();
const float target_opacity = layout_manager->GetOpacity();
if (GetLayer()->opacity() != target_opacity) {
if (target_opacity == 0) {
if (animate) {
// On hide, set the opacity after the animation completes if |animate|
// is true.
hide_animation_observer_ =
std::make_unique<HideAnimationObserver>(GetLayer());
} else {
// Otherwise, directly set the opacity to 0.
GetLayer()->SetOpacity(0.0f);
}
} else {
// On show, set the opacity before the animation begins to ensure the blur
// is shown while the shelf moves.
GetLayer()->SetOpacity(1.0f);
}
}
if (GetNativeView()->layer()->GetAnimator()->is_animating()) {
// When the |shelf_widget_| needs to reverse the direction of the current
// animation, we must take into account the transform when calculating the
// current shelf widget bounds.
current_shelf_bounds =
GetLayer()->transform().MapRect(current_shelf_bounds);
}
gfx::Transform shelf_widget_target_transform;
shelf_widget_target_transform.Translate(current_shelf_bounds.origin() -
GetTargetBounds().origin());
GetLayer()->SetTransform(shelf_widget_target_transform);
SetBounds(
screen_util::SnapBoundsToDisplayEdge(target_bounds_, GetNativeWindow()));
// There is no need to animate if the shelf already has the desired transform.
if (!shelf_widget_target_transform.IsIdentity()) {
ui::ScopedLayerAnimationSettings shelf_animation_setter(
GetLayer()->GetAnimator());
if (hide_animation_observer_)
shelf_animation_setter.AddObserver(hide_animation_observer_.get());
const base::TimeDelta animation_duration =
animate ? ShelfConfig::Get()->shelf_animation_duration()
: base::TimeDelta();
if (!animate) {
GetLayer()->GetAnimator()->StopAnimating();
shelf_->status_area_widget()->GetLayer()->GetAnimator()->StopAnimating();
}
shelf_animation_setter.SetTransitionDuration(animation_duration);
if (animate) {
shelf_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
shelf_animation_setter.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
}
GetLayer()->SetTransform(gfx::Transform());
}
delegate_view_->UpdateOpaqueBackground();
}
void ShelfWidget::UpdateTargetBoundsForGesture(int shelf_position) {
if (shelf_->IsHorizontalAlignment()) {
if (!Shell::Get()->IsInTabletMode())
target_bounds_.set_y(shelf_position);
} else {
target_bounds_.set_x(shelf_position);
}
}
void ShelfWidget::HandleLocaleChange() {
// TODO(https://crbug.com/1343114): remove this function when the login shelf
// widget is ready.
DCHECK(!features::IsUseLoginShelfWidgetEnabled());
login_shelf_view_->HandleLocaleChange();
}
gfx::Rect ShelfWidget::GetTargetBounds() const {
return target_bounds_;
}
void ShelfWidget::OnSessionStateChanged(session_manager::SessionState state) {
// Do not show shelf widget:
// 1. when views based shelf is disabled; or
// 2. in UNKNOWN state - it might be called before shelf was initialized; or
// 3. in RMA state - shelf should be hidden to avoid blocking the RMA app
// controls or intercepting UI events; or
// 4. on secondary screens in states other than ACTIVE.
bool hide_for_session_state =
state == session_manager::SessionState::UNKNOWN ||
state == session_manager::SessionState::RMA;
bool hide_on_secondary_screen = shelf_->ShouldHideOnSecondaryDisplay(state);
if (hide_for_session_state || hide_on_secondary_screen) {
HideIfShown();
} else {
bool show_hotseat = (state == session_manager::SessionState::ACTIVE);
hotseat_widget()->GetShelfView()->SetVisible(show_hotseat);
hotseat_transition_animator_->SetAnimationsEnabledInSessionState(
show_hotseat);
// Shelf widget should only be active if login shelf view is visible.
aura::Window* const shelf_window = GetNativeWindow();
if (show_hotseat && IsActive())
wm::DeactivateWindow(shelf_window);
// TODO(https://crbug.com/1343114): remove it when the login shelf widget is
// used as default.
if (!features::IsUseLoginShelfWidgetEnabled())
GetLoginShelfView()->SetVisible(!show_hotseat);
ShowIfHidden();
// The shelf widget can get activated when login shelf view is shown, which
// would stack it above other widgets in the shelf container, which is an
// undesirable state for active session shelf (as the shelf background would
// be painted over the hotseat/navigation buttons/status area). Make sure
// the shelf widget is restacked at the bottom of the shelf container when
// the session state changes.
// TODO(https://crbug.com/1057207): Ideally, the shelf widget position at
// the bottom of window stack would be maintained using a "stacked at
// bottom" window property - switch to that approach once it's ready for
// usage.
if (show_hotseat)
shelf_window->parent()->StackChildAtBottom(shelf_window);
}
shelf_layout_manager_->SetDimmed(false);
delegate_view_->UpdateDragHandle();
// Update drag handle's color on session state changes since the color mode
// might change on session state changes.
delegate_view_->drag_handle()->UpdateColor();
// TODO(https://crbug.com/1343114): remove it when the login shelf widget is
// ready.
if (!features::IsUseLoginShelfWidgetEnabled())
login_shelf_view_->UpdateAfterSessionChange();
}
void ShelfWidget::OnUserSessionAdded(const AccountId& account_id) {
shelf_layout_manager_->SetDimmed(false);
// TODO(https://crbug.com/1343114): remove it when the login shelf widget is
// ready.
if (!features::IsUseLoginShelfWidgetEnabled())
login_shelf_view_->UpdateAfterSessionChange();
}
SkColor ShelfWidget::GetShelfBackgroundColor() const {
return delegate_view_->GetShelfBackgroundColor();
}
void ShelfWidget::HideIfShown() {
if (IsVisible())
Hide();
}
void ShelfWidget::ShowIfHidden() {
if (!IsVisible())
Show();
}
ui::Layer* ShelfWidget::GetDelegateViewOpaqueBackgroundLayerForTesting() {
return delegate_view_->opaque_background_layer();
}
void ShelfWidget::OnMouseEvent(ui::MouseEvent* event) {
if (event->IsMouseWheelEvent()) {
ui::MouseWheelEvent* mouse_wheel_event = event->AsMouseWheelEvent();
shelf_->ProcessMouseWheelEvent(mouse_wheel_event);
return;
}
if (event->type() == ui::ET_MOUSE_PRESSED) {
keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser();
// If the shelf receives the mouse pressing event, the RootView of the shelf
// will reset the gesture handler. As a result, if the shelf is in drag
// progress when the mouse is pressed, shelf will not receive the gesture
// end event. So explicitly cancel the drag in this scenario.
shelf_layout_manager_->CancelDragOnShelfIfInProgress();
}
views::Widget::OnMouseEvent(event);
}
void ShelfWidget::OnGestureEvent(ui::GestureEvent* event) {
if (event->type() == ui::ET_GESTURE_TAP_DOWN)
keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser();
ui::GestureEvent event_in_screen(*event);
gfx::Point location_in_screen(event->location());
::wm::ConvertPointToScreen(GetNativeWindow(), &location_in_screen);
event_in_screen.set_location(location_in_screen);
// Tap on in-app shelf should show a contextual nudge for in-app to home
// gesture.
if (event->type() == ui::ET_GESTURE_TAP && ShelfConfig::Get()->is_in_app() &&
features::AreContextualNudgesEnabled()) {
if (delegate_view_->drag_handle()->MaybeShowDragHandleNudge()) {
event->StopPropagation();
return;
}
}
shelf_layout_manager()->ProcessGestureEventFromShelfWidget(&event_in_screen);
if (!event->handled())
views::Widget::OnGestureEvent(event);
}
void ShelfWidget::OnScrollEvent(ui::ScrollEvent* event) {
shelf_->ProcessScrollEvent(event);
if (!event->handled())
views::Widget::OnScrollEvent(event);
}
void ShelfWidget::ResetForceShowHotseat() {
if (force_show_hotseat_count_ == 0)
return;
--force_show_hotseat_count_;
if (force_show_hotseat_count_ == 0)
shelf_layout_manager_->UpdateVisibilityState();
}
} // namespace ash