blob: 187e3d20f67f23683df32e9b91192e33f6f4f81e [file]
// Copyright 2012 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/common/shelf/shelf_widget.h"
#include "ash/common/focus_cycler.h"
#include "ash/common/material_design/material_design_controller.h"
#include "ash/common/session/session_state_delegate.h"
#include "ash/common/shelf/shelf_background_animator_observer.h"
#include "ash/common/shelf/shelf_constants.h"
#include "ash/common/shelf/shelf_delegate.h"
#include "ash/common/shelf/shelf_layout_manager.h"
#include "ash/common/shelf/shelf_view.h"
#include "ash/common/shelf/wm_dimmer_view.h"
#include "ash/common/shelf/wm_shelf.h"
#include "ash/common/shelf/wm_shelf_util.h"
#include "ash/common/system/status_area_layout_manager.h"
#include "ash/common/system/status_area_widget.h"
#include "ash/common/wm_lookup.h"
#include "ash/common/wm_root_window_controller.h"
#include "ash/common/wm_shell.h"
#include "ash/common/wm_window.h"
#include "ash/common/wm_window_property.h"
#include "base/memory/ptr_util.h"
#include "grit/ash_resources.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/views/accessible_pane_view.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace ash {
namespace {
// Size of black border at bottom (or side) of shelf.
const int kNumBlackPixels = 3;
} // namespace
// The contents view of the Shelf. This view contains ShelfView and
// sizes it to the width of the shelf minus the size of the status area.
class ShelfWidget::DelegateView : public views::WidgetDelegate,
public views::AccessiblePaneView,
public ShelfBackgroundAnimatorObserver {
public:
DelegateView(WmShelf* wm_shelf, ShelfWidget* shelf);
~DelegateView() override;
void set_focus_cycler(FocusCycler* focus_cycler) {
focus_cycler_ = focus_cycler;
}
FocusCycler* focus_cycler() { return focus_cycler_; }
ui::Layer* opaque_background() { return &opaque_background_; }
ui::Layer* opaque_foreground() { return &opaque_foreground_; }
// Set if the shelf area is dimmed (eg when a window is maximized).
void SetDimmed(bool dimmed);
bool GetDimmed() const;
void SetParentLayer(ui::Layer* layer);
// views::View overrides:
void OnPaintBackground(gfx::Canvas* canvas) override;
// views::WidgetDelegateView overrides:
views::Widget* GetWidget() override { return View::GetWidget(); }
const views::Widget* GetWidget() const override { return View::GetWidget(); }
bool CanActivate() const override;
void ReorderChildLayers(ui::Layer* parent_layer) override;
// This will be called when the parent local bounds change.
void OnBoundsChanged(const gfx::Rect& old_bounds) override;
// ShelfBackgroundAnimatorObserver:
void UpdateShelfOpaqueBackground(int alpha) override;
void UpdateShelfAssetBackground(int alpha) override;
// Force the shelf to be presented in an undimmed state.
void ForceUndimming(bool force);
// A function to test the current alpha used by the dimming bar. If there is
// no dimmer active, the function will return -1.
int GetDimmingAlphaForTest();
// A function to test the bounds of the dimming bar. Returns gfx::Rect() if
// the dimmer is inactive.
gfx::Rect GetDimmerBoundsForTest();
// Disable dimming animations for running tests. This needs to be called
// prior to the creation of of the dimmer.
void disable_dimming_animations_for_test() {
disable_dimming_animations_for_test_ = true;
}
private:
WmShelf* wm_shelf_;
ShelfWidget* shelf_widget_;
FocusCycler* focus_cycler_;
int asset_background_alpha_;
// TODO(bruthig): Remove opaque_background_ (see https://crbug.com/621551).
// A black background layer which is shown when a maximized window is visible.
ui::Layer opaque_background_;
// A black foreground layer which is shown while transitioning between users.
// Note: Since the back- and foreground layers have different functions they
// can be used simultaneously - so no repurposing possible.
ui::Layer opaque_foreground_;
// The interface for the view which does the dimming. Null if the shelf is not
// being dimmed, or if dimming is not supported (e.g. for mus).
WmDimmerView* dimmer_view_;
// True if dimming animations should be turned off.
bool disable_dimming_animations_for_test_;
DISALLOW_COPY_AND_ASSIGN(DelegateView);
};
ShelfWidget::DelegateView::DelegateView(WmShelf* wm_shelf,
ShelfWidget* shelf_widget)
: wm_shelf_(wm_shelf),
shelf_widget_(shelf_widget),
focus_cycler_(nullptr),
asset_background_alpha_(0),
opaque_background_(ui::LAYER_SOLID_COLOR),
opaque_foreground_(ui::LAYER_SOLID_COLOR),
dimmer_view_(nullptr),
disable_dimming_animations_for_test_(false) {
DCHECK(wm_shelf_);
DCHECK(shelf_widget_);
SetLayoutManager(new views::FillLayout());
set_allow_deactivate_on_esc(true);
opaque_background_.SetColor(SK_ColorBLACK);
opaque_background_.SetBounds(GetLocalBounds());
opaque_background_.SetOpacity(0.0f);
opaque_foreground_.SetColor(SK_ColorBLACK);
opaque_foreground_.SetBounds(GetLocalBounds());
opaque_foreground_.SetOpacity(0.0f);
}
ShelfWidget::DelegateView::~DelegateView() {
// Make sure that the dimmer goes away since it might have set an observer.
SetDimmed(false);
}
void ShelfWidget::DelegateView::SetDimmed(bool dimmed) {
// When starting dimming, attempt to create a dimmer view.
if (dimmed) {
if (!dimmer_view_) {
dimmer_view_ =
wm_shelf_->CreateDimmerView(disable_dimming_animations_for_test_);
}
return;
}
// Close the dimmer widget when stopping dimming.
if (dimmer_view_) {
dimmer_view_->GetDimmerWidget()->CloseNow();
dimmer_view_ = nullptr;
}
}
bool ShelfWidget::DelegateView::GetDimmed() const {
return dimmer_view_ && dimmer_view_->GetDimmerWidget()->IsVisible();
}
void ShelfWidget::DelegateView::SetParentLayer(ui::Layer* layer) {
layer->Add(&opaque_background_);
layer->Add(&opaque_foreground_);
ReorderLayers();
}
void ShelfWidget::DelegateView::OnPaintBackground(gfx::Canvas* canvas) {
if (MaterialDesignController::IsShelfMaterial())
return;
ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
gfx::ImageSkia shelf_background =
*rb->GetImageSkiaNamed(IDR_ASH_SHELF_BACKGROUND);
const bool horizontal = wm_shelf_->IsHorizontalAlignment();
if (!horizontal) {
shelf_background = gfx::ImageSkiaOperations::CreateRotatedImage(
shelf_background, wm_shelf_->GetAlignment() == SHELF_ALIGNMENT_LEFT
? SkBitmapOperations::ROTATION_90_CW
: SkBitmapOperations::ROTATION_270_CW);
}
const gfx::Rect dock_bounds(
shelf_widget_->shelf_layout_manager()->dock_bounds());
SkPaint paint;
paint.setAlpha(asset_background_alpha_);
canvas->DrawImageInt(
shelf_background, 0, 0, shelf_background.width(),
shelf_background.height(),
(horizontal && dock_bounds.x() == 0 && dock_bounds.width() > 0)
? dock_bounds.width()
: 0,
0, horizontal ? width() - dock_bounds.width() : width(), height(), false,
paint);
if (horizontal && dock_bounds.width() > 0) {
// The part of the shelf background that is in the corner below the docked
// windows close to the work area is an arched gradient that blends
// vertically oriented docked background and horizontal shelf.
gfx::ImageSkia shelf_corner = *rb->GetImageSkiaNamed(IDR_ASH_SHELF_CORNER);
if (dock_bounds.x() == 0) {
shelf_corner = gfx::ImageSkiaOperations::CreateRotatedImage(
shelf_corner, SkBitmapOperations::ROTATION_90_CW);
}
canvas->DrawImageInt(
shelf_corner, 0, 0, shelf_corner.width(), shelf_corner.height(),
dock_bounds.x() > 0 ? dock_bounds.x() : dock_bounds.width() - height(),
0, height(), height(), false, paint);
// The part of the shelf background that is just below the docked windows
// is drawn using the last (lowest) 1-pixel tall strip of the image asset.
// This avoids showing the border 3D shadow between the shelf and the
// dock.
canvas->DrawImageInt(shelf_background, 0, shelf_background.height() - 1,
shelf_background.width(), 1,
dock_bounds.x() > 0 ? dock_bounds.x() + height() : 0,
0, dock_bounds.width() - height(), height(), false,
paint);
}
gfx::Rect black_rect =
shelf_widget_->shelf_layout_manager()->SelectValueForShelfAlignment(
gfx::Rect(0, height() - kNumBlackPixels, width(), kNumBlackPixels),
gfx::Rect(0, 0, kNumBlackPixels, height()),
gfx::Rect(width() - kNumBlackPixels, 0, kNumBlackPixels, height()));
canvas->FillRect(black_rect, SK_ColorBLACK);
}
bool ShelfWidget::DelegateView::CanActivate() const {
// Allow to activate as fallback.
if (shelf_widget_->activating_as_fallback_)
return true;
// Allow to activate from the focus cycler.
if (focus_cycler_ && focus_cycler_->widget_activating() == GetWidget())
return true;
// Disallow activating in other cases, especially when using mouse.
return false;
}
void ShelfWidget::DelegateView::ReorderChildLayers(ui::Layer* parent_layer) {
views::View::ReorderChildLayers(parent_layer);
parent_layer->StackAtBottom(&opaque_background_);
parent_layer->StackAtTop(&opaque_foreground_);
}
void ShelfWidget::DelegateView::OnBoundsChanged(const gfx::Rect& old_bounds) {
opaque_background_.SetBounds(GetLocalBounds());
opaque_foreground_.SetBounds(GetLocalBounds());
if (dimmer_view_)
dimmer_view_->GetDimmerWidget()->SetBounds(GetBoundsInScreen());
}
void ShelfWidget::DelegateView::ForceUndimming(bool force) {
if (GetDimmed())
dimmer_view_->ForceUndimming(force);
}
int ShelfWidget::DelegateView::GetDimmingAlphaForTest() {
if (GetDimmed())
return dimmer_view_->GetDimmingAlphaForTest();
return -1;
}
gfx::Rect ShelfWidget::DelegateView::GetDimmerBoundsForTest() {
if (GetDimmed())
return dimmer_view_->GetDimmerWidget()->GetWindowBoundsInScreen();
return gfx::Rect();
}
void ShelfWidget::DelegateView::UpdateShelfOpaqueBackground(int alpha) {
const float kMaxAlpha = 255.0f;
opaque_background_.SetOpacity(alpha / kMaxAlpha);
}
void ShelfWidget::DelegateView::UpdateShelfAssetBackground(int alpha) {
asset_background_alpha_ = alpha;
SchedulePaint();
}
ShelfWidget::ShelfWidget(WmWindow* shelf_container, WmShelf* wm_shelf)
: wm_shelf_(wm_shelf),
status_area_widget_(nullptr),
delegate_view_(new DelegateView(wm_shelf, this)),
shelf_view_(nullptr),
background_animator_(SHELF_BACKGROUND_DEFAULT, wm_shelf_),
activating_as_fallback_(false) {
background_animator_.AddObserver(this);
background_animator_.AddObserver(delegate_view_);
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.name = "ShelfWidget";
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.delegate = delegate_view_;
shelf_container->GetRootWindowController()
->ConfigureWidgetInitParamsForContainer(
this, shelf_container->GetShellWindowId(), &params);
Init(params);
// The shelf should not take focus when initially shown.
set_focus_on_creation(false);
SetContentsView(delegate_view_);
delegate_view_->SetParentLayer(GetLayer());
shelf_layout_manager_ = new ShelfLayoutManager(this, wm_shelf_);
shelf_layout_manager_->AddObserver(this);
shelf_container->SetLayoutManager(base::WrapUnique(shelf_layout_manager_));
background_animator_.PaintBackground(
shelf_layout_manager_->GetShelfBackgroundType(),
BACKGROUND_CHANGE_IMMEDIATE);
views::Widget::AddObserver(this);
}
ShelfWidget::~ShelfWidget() {
// Must call Shutdown() before destruction.
DCHECK(!status_area_widget_);
WmShell::Get()->focus_cycler()->RemoveWidget(this);
SetFocusCycler(nullptr);
RemoveObserver(this);
background_animator_.RemoveObserver(delegate_view_);
background_animator_.RemoveObserver(this);
}
void ShelfWidget::CreateStatusAreaWidget(WmWindow* status_container) {
DCHECK(status_container);
DCHECK(!status_area_widget_);
// TODO(jamescook): Move ownership to RootWindowController.
status_area_widget_ = new StatusAreaWidget(status_container, wm_shelf_);
status_area_widget_->CreateTrayViews();
if (WmShell::Get()->GetSessionStateDelegate()->IsActiveUserSessionStarted())
status_area_widget_->Show();
WmShell::Get()->focus_cycler()->AddWidget(status_area_widget_);
background_animator_.AddObserver(status_area_widget_);
status_container->SetLayoutManager(
base::MakeUnique<StatusAreaLayoutManager>(this));
}
void ShelfWidget::SetPaintsBackground(
ShelfBackgroundType background_type,
BackgroundAnimatorChangeType change_type) {
background_animator_.PaintBackground(background_type, change_type);
}
ShelfBackgroundType ShelfWidget::GetBackgroundType() const {
return background_animator_.target_background_type();
}
void ShelfWidget::HideShelfBehindBlackBar(bool hide, int animation_time_ms) {
if (IsShelfHiddenBehindBlackBar() == hide)
return;
ui::Layer* opaque_foreground = delegate_view_->opaque_foreground();
float target_opacity = hide ? 1.0f : 0.0f;
std::unique_ptr<ui::ScopedLayerAnimationSettings> opaque_foreground_animation;
opaque_foreground_animation.reset(
new ui::ScopedLayerAnimationSettings(opaque_foreground->GetAnimator()));
opaque_foreground_animation->SetTransitionDuration(
base::TimeDelta::FromMilliseconds(animation_time_ms));
opaque_foreground_animation->SetPreemptionStrategy(
ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
opaque_foreground->SetOpacity(target_opacity);
}
bool ShelfWidget::IsShelfHiddenBehindBlackBar() const {
return delegate_view_->opaque_foreground()->GetTargetOpacity() != 0.0f;
}
ShelfAlignment ShelfWidget::GetAlignment() const {
return wm_shelf_->GetAlignment();
}
void ShelfWidget::OnShelfAlignmentChanged() {
shelf_view_->OnShelfAlignmentChanged();
status_area_widget_->SetShelfAlignment(GetAlignment());
delegate_view_->SchedulePaint();
}
void ShelfWidget::SetDimsShelf(bool dimming) {
delegate_view_->SetDimmed(dimming);
// Repaint all children, allowing updates to reflect dimmed state eg:
// status area background, app list button and overflow button.
if (shelf_view_)
shelf_view_->SchedulePaintForAllButtons();
status_area_widget_->SchedulePaint();
}
bool ShelfWidget::GetDimsShelf() const {
return delegate_view_->GetDimmed();
}
ShelfView* ShelfWidget::CreateShelfView() {
DCHECK(!shelf_view_);
shelf_view_ =
new ShelfView(WmShell::Get()->shelf_model(),
WmShell::Get()->shelf_delegate(), wm_shelf_, this);
shelf_view_->Init();
GetContentsView()->AddChildView(shelf_view_);
return shelf_view_;
}
void ShelfWidget::PostCreateShelf() {
SetFocusCycler(WmShell::Get()->focus_cycler());
// Ensure the newly created |shelf_| gets current values.
background_animator_.Initialize(this);
shelf_view_->SetVisible(
WmShell::Get()->GetSessionStateDelegate()->IsActiveUserSessionStarted());
shelf_layout_manager_->LayoutShelf();
shelf_layout_manager_->UpdateAutoHideState();
Show();
}
bool ShelfWidget::IsShelfVisible() const {
return shelf_view_ && shelf_view_->visible();
}
void ShelfWidget::SetShelfVisibility(bool visible) {
if (shelf_view_)
shelf_view_->SetVisible(visible);
}
bool ShelfWidget::IsShowingContextMenu() const {
return shelf_view_ && shelf_view_->IsShowingMenu();
}
bool ShelfWidget::IsShowingOverflowBubble() const {
return shelf_view_ && shelf_view_->IsShowingOverflowBubble();
}
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();
}
void ShelfWidget::Shutdown() {
// Tear down the dimmer before |shelf_layout_manager_|, since the dimmer uses
// |shelf_layout_manager_| to get the shelf's WmWindow, via WmShelf.
delegate_view_->SetDimmed(false);
// 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.
if (shelf_layout_manager_)
shelf_layout_manager_->PrepareForShutdown();
if (status_area_widget_) {
background_animator_.RemoveObserver(status_area_widget_);
WmShell::Get()->focus_cycler()->RemoveWidget(status_area_widget_);
status_area_widget_->Shutdown();
status_area_widget_ = nullptr;
}
CloseNow();
}
void ShelfWidget::ForceUndimming(bool force) {
delegate_view_->ForceUndimming(force);
}
void ShelfWidget::UpdateIconPositionForPanel(WmWindow* panel) {
WmWindow* shelf_window = WmLookup::Get()->GetWindowForWidget(this);
shelf_view_->UpdatePanelIconPosition(
panel->GetIntProperty(WmWindowProperty::SHELF_ID),
shelf_window->ConvertRectFromScreen(panel->GetBoundsInScreen())
.CenterPoint());
}
gfx::Rect ShelfWidget::GetScreenBoundsOfItemIconForWindow(WmWindow* window) {
ShelfID id = window->GetIntProperty(WmWindowProperty::SHELF_ID);
gfx::Rect bounds(shelf_view_->GetIdealBoundsOfItemIcon(id));
gfx::Point screen_origin;
views::View::ConvertPointToScreen(shelf_view_, &screen_origin);
return gfx::Rect(screen_origin.x() + bounds.x(),
screen_origin.y() + bounds.y(), bounds.width(),
bounds.height());
}
AppListButton* ShelfWidget::GetAppListButton() const {
return shelf_view_->GetAppListButton();
}
app_list::ApplicationDragAndDropHost*
ShelfWidget::GetDragAndDropHostForAppList() {
return shelf_view_;
}
void ShelfWidget::OnWidgetActivationChanged(views::Widget* widget,
bool active) {
activating_as_fallback_ = false;
if (active)
delegate_view_->SetPaneFocusAndFocusDefault();
else
delegate_view_->GetFocusManager()->ClearFocus();
}
int ShelfWidget::GetDimmingAlphaForTest() {
if (delegate_view_)
return delegate_view_->GetDimmingAlphaForTest();
return -1;
}
gfx::Rect ShelfWidget::GetDimmerBoundsForTest() {
if (delegate_view_)
return delegate_view_->GetDimmerBoundsForTest();
return gfx::Rect();
}
void ShelfWidget::DisableDimmingAnimationsForTest() {
DCHECK(delegate_view_);
delegate_view_->disable_dimming_animations_for_test();
}
void ShelfWidget::UpdateShelfItemBackground(int alpha) {
if (shelf_view_)
shelf_view_->UpdateShelfItemBackground(alpha);
}
void ShelfWidget::WillDeleteShelfLayoutManager() {
shelf_layout_manager_->RemoveObserver(this);
shelf_layout_manager_ = nullptr;
}
} // namespace ash