chromium/ash/wallpaper/views/wallpaper_widget_controller.cc

// 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/wallpaper/views/wallpaper_widget_controller.h"

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/style/color_util.h"
#include "ash/wallpaper/views/wallpaper_view.h"
#include "ui/aura/window.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/screen.h"
#include "ui/gfx/animation/tween.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
#include "ui/wm/core/window_util.h"

namespace ash {

WallpaperWidgetController::WallpaperWidgetController(aura::Window* root_window)
    : root_window_(root_window) {
  Observe(ColorUtil::GetColorProviderSourceForWindow(root_window_));
}

WallpaperWidgetController::~WallpaperWidgetController() {
  widget_->CloseNow();
}

void WallpaperWidgetController::Init(bool locked) {
  widget_ = CreateWallpaperWidget(root_window_, wallpaper_constants::kClear,
                                  locked, &wallpaper_view_);
  CreateWallpaperUnderlayLayer();
}

views::Widget* WallpaperWidgetController::GetWidget() {
  return widget_.get();
}

bool WallpaperWidgetController::IsAnimating() const {
  return old_layer_tree_owner_ &&
         old_layer_tree_owner_->root()->GetAnimator()->is_animating();
}

void WallpaperWidgetController::StopAnimating() {
  if (old_layer_tree_owner_) {
    old_layer_tree_owner_->root()->GetAnimator()->StopAnimating();
    old_layer_tree_owner_.reset();
  }
}

void WallpaperWidgetController::AddAnimationEndCallback(
    base::OnceClosure callback) {
  animation_end_callbacks_.emplace_back(std::move(callback));
}

bool WallpaperWidgetController::Reparent(int container) {
  auto* parent = GetWidget()->GetNativeWindow()->parent();
  auto* root_window = parent->GetRootWindow();
  aura::Window* new_parent = root_window->GetChildById(container);

  if (parent == new_parent) {
    return false;
  }
  new_parent->AddChild(GetWidget()->GetNativeWindow());
  return true;
}

bool WallpaperWidgetController::SetWallpaperBlur(
    float blur,
    const base::TimeDelta& animation_duration) {
  if (!widget_->GetNativeWindow()) {
    return false;
  }

  StopAnimating();
  bool blur_changed = wallpaper_view_->blur_sigma() != blur;

  wallpaper_view_->set_blur_sigma(blur);
  // Show the widget when we have something to show.
  if (!widget_->IsVisible()) {
    widget_->Show();
  }
  if (!animation_duration.is_zero()) {
    ApplyCrossFadeAnimation(animation_duration);
  } else {
    wallpaper_view_->SchedulePaint();
    // Since there is no actual animation scheduled, just call completed method.
    OnImplicitAnimationsCompleted();
  }
  return blur_changed;
}

float WallpaperWidgetController::GetWallpaperBlur() const {
  return wallpaper_view_->blur_sigma();
}

void WallpaperWidgetController::OnImplicitAnimationsCompleted() {
  StopAnimating();
  wallpaper_view_->SetLockShieldEnabled(
      wallpaper_view_->GetWidget()->GetNativeWindow()->parent()->GetId() ==
      kShellWindowId_LockScreenWallpaperContainer);
  RunAnimationEndCallbacks();
}

void WallpaperWidgetController::OnDisplayMetricsChanged(
    const display::Display& display,
    uint32_t metrics) {
  if (!wallpaper_underlay_layer_) {
    return;
  }

  if (!(metrics &
        (DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION |
         DISPLAY_METRIC_DEVICE_SCALE_FACTOR | DISPLAY_METRIC_WORK_AREA))) {
    return;
  }

  if (root_window_ != Shell::GetRootWindowForDisplayId(display.id())) {
    return;
  }

  // Bounds have to be in parent. Since these are set on the layer directly, and
  // layer bounds are relative to the layer's parent.
  wallpaper_underlay_layer_->SetBounds(root_window_->bounds());
}

void WallpaperWidgetController::OnColorProviderChanged() {
  if (wallpaper_underlay_layer_) {
    wallpaper_underlay_layer_->SetColor(
        GetColorProviderSource()->GetColorProvider()->GetColor(
            cros_tokens::kCrosSysSystemBase));
  }
}

void WallpaperWidgetController::CreateWallpaperUnderlayLayer() {
  if (!features::IsForestFeatureEnabled()) {
    return;
  }

  wallpaper_underlay_layer_ =
      std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
  wallpaper_underlay_layer_->SetName("WallpaperUnderlayLayer");
  auto* wallpaper_view_layer = wallpaper_view_->layer();
  auto* wallpaper_view_layer_parent = wallpaper_view_layer->parent();
  wallpaper_view_layer_parent->Add(wallpaper_underlay_layer_.get());
  wallpaper_view_layer_parent->StackBelow(wallpaper_underlay_layer_.get(),
                                          wallpaper_view_layer);
  wallpaper_underlay_layer_->SetBounds(root_window_->bounds());

  OnColorProviderChanged();

  // The `wallpaper_underlay_layer_` should be invisible by default. This
  // prevents the compositor from unnecessarily considering it during occlusion
  // calculations, potentially improving performance. The layer should only
  // become visible when needed (i.e. when entering overview).
  wallpaper_underlay_layer_->SetVisible(false);
}

void WallpaperWidgetController::RunAnimationEndCallbacks() {
  std::list<base::OnceClosure> callbacks;
  animation_end_callbacks_.swap(callbacks);
  for (auto& callback : callbacks) {
    std::move(callback).Run();
  }
}

void WallpaperWidgetController::ApplyCrossFadeAnimation(
    base::TimeDelta duration) {
  DCHECK(wallpaper_view_);

  old_layer_tree_owner_ = wm::RecreateLayers(wallpaper_view_);

  ui::Layer* old_layer = old_layer_tree_owner_->root();
  auto* old_layer_parent = old_layer->parent();
  ui::Layer* new_layer = wallpaper_view_->layer();
  DCHECK_EQ(old_layer_parent, new_layer->parent());
  old_layer_parent->StackAbove(old_layer, new_layer);
  if (wallpaper_underlay_layer_) {
    old_layer_parent->StackBelow(wallpaper_underlay_layer_.get(), new_layer);
  }

  old_layer->SetOpacity(1.f);
  new_layer->SetOpacity(1.f);

  // Fade out the old layer. When clearing the blur, use the opposite tween so
  // that the animations are mirrors of each other.
  const bool clearing =
      wallpaper_view_->blur_sigma() == wallpaper_constants::kClear;
  ui::ScopedLayerAnimationSettings settings(old_layer->GetAnimator());
  settings.SetTransitionDuration(duration);
  settings.SetTweenType(clearing ? gfx::Tween::EASE_IN : gfx::Tween::EASE_OUT);
  settings.AddObserver(this);

  old_layer->SetOpacity(0.f);
}

}  // namespace ash