#include "ui/wm/core/shadow_controller.h"
#include <utility>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/scoped_multi_source_observation.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/env.h"
#include "ui/aura/env_observer.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/base/class_property.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/wm/core/shadow_controller_delegate.h"
#include "ui/wm/core/shadow_types.h"
#include "ui/wm/core/window_util.h"
#include "ui/wm/public/activation_client.h"
using std::make_pair;
DEFINE_UI_CLASS_PROPERTY_TYPE(ui::Shadow*)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ui::Shadow, kShadowLayerKey)
namespace wm {
namespace {
int GetShadowElevationForActiveState(aura::Window* window) {
int elevation = window->GetProperty(kShadowElevationKey);
if (elevation != kShadowElevationDefault)
return elevation;
if (IsActiveWindow(window))
return kShadowElevationActiveWindow;
return GetDefaultShadowElevationForWindow(window);
}
int GetShadowElevationForWindowLosingActive(aura::Window* losing_active,
aura::Window* gaining_active) {
if (gaining_active && GetHideOnDeactivate(gaining_active)) {
if (base::Contains(GetTransientChildren(losing_active), gaining_active))
return kShadowElevationActiveWindow;
}
return kShadowElevationInactiveWindow;
}
}
class ShadowController::Impl :
public aura::EnvObserver,
public aura::WindowObserver,
public base::RefCounted<Impl> {
public:
static Impl* GetInstance(aura::Env* env);
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
void set_delegate(std::unique_ptr<ShadowControllerDelegate> delegate) {
delegate_ = std::move(delegate);
}
bool IsShadowVisibleForWindow(aura::Window* window);
void UpdateShadowForWindow(aura::Window* window);
void OnWindowInitialized(aura::Window* window) override;
void OnWindowHierarchyChanged(const HierarchyChangeParams& params) override;
void OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) override;
void OnWindowVisibilityChanging(aura::Window* window, bool visible) override;
void OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) override;
void OnWindowDestroyed(aura::Window* window) override;
void OnWindowOcclusionChanged(aura::Window* window) override;
private:
friend class base::RefCounted<Impl>;
friend class ShadowController;
explicit Impl(aura::Env* env);
~Impl() override;
static base::flat_set<Impl*>* GetInstances();
void OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active);
bool ShouldShowShadowForWindow(aura::Window* window) const;
void MaybeSetShadowRadiusForWindow(aura::Window* window) const;
void HandleWindowActivationChange(aura::Window* gaining_active,
aura::Window* losing_active);
void HandlePossibleShadowVisibilityChange(aura::Window* window);
void CreateShadowForWindow(aura::Window* window);
bool IsObservingWindowForTest(aura::Window* window) const;
const raw_ptr<aura::Env> env_;
base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
observation_manager_{this};
std::unique_ptr<ShadowControllerDelegate> delegate_;
};
ShadowController::Impl* ShadowController::Impl::GetInstance(aura::Env* env) {
for (Impl* impl : *GetInstances()) {
if (impl->env_ == env)
return impl;
}
return new Impl(env);
}
bool ShadowController::Impl::IsShadowVisibleForWindow(aura::Window* window) {
if (!observation_manager_.IsObservingSource(window))
return false;
ui::Shadow* shadow = GetShadowForWindow(window);
return shadow && shadow->layer()->visible();
}
void ShadowController::Impl::UpdateShadowForWindow(aura::Window* window) {
DCHECK(observation_manager_.IsObservingSource(window));
HandlePossibleShadowVisibilityChange(window);
}
void ShadowController::Impl::OnWindowInitialized(aura::Window* window) {
if (delegate_ && !delegate_->ShouldObserveWindow(window)) {
return;
}
DCHECK(!window->parent());
DCHECK(!window->TargetVisibility());
observation_manager_.AddObservation(window);
}
void ShadowController::Impl::OnWindowHierarchyChanged(
const HierarchyChangeParams& params) {
if (!params.new_parent) {
return;
}
const bool parent_changed = params.target == params.receiver &&
params.new_parent != params.old_parent;
if (parent_changed && params.target->IsVisible()) {
HandlePossibleShadowVisibilityChange(params.target);
}
}
void ShadowController::Impl::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
bool shadow_will_change = key == kShadowElevationKey &&
window->GetProperty(kShadowElevationKey) != old;
if (key == aura::client::kShowStateKey) {
shadow_will_change = window->GetProperty(aura::client::kShowStateKey) !=
static_cast<ui::mojom::WindowShowState>(old);
}
if (key == aura::client::kWindowRoundedCornersKey) {
shadow_will_change =
*window->GetProperty(aura::client::kWindowRoundedCornersKey) !=
static_cast<gfx::RoundedCornersF>(old);
}
shadow_will_change |=
delegate_ &&
delegate_->ShouldUpdateShadowOnWindowPropertyChange(window, key, old);
if (shadow_will_change && window->TargetVisibility()) {
HandlePossibleShadowVisibilityChange(window);
}
}
void ShadowController::Impl::OnWindowVisibilityChanging(aura::Window* window,
bool visible) {
if (window->IsRootWindow()) {
observation_manager_.RemoveObservation(window);
return;
}
HandlePossibleShadowVisibilityChange(window);
}
void ShadowController::Impl::OnWindowBoundsChanged(
aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) {
ui::Shadow* shadow = GetShadowForWindow(window);
if (shadow && window->GetProperty(aura::client::kUseWindowBoundsForShadow))
shadow->SetContentBounds(gfx::Rect(new_bounds.size()));
}
void ShadowController::Impl::OnWindowDestroyed(aura::Window* window) {
window->ClearProperty(kShadowLayerKey);
observation_manager_.RemoveObservation(window);
}
void ShadowController::Impl::OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (gained_active) {
ui::Shadow* shadow = GetShadowForWindow(gained_active);
if (shadow)
shadow->SetElevation(GetShadowElevationForActiveState(gained_active));
}
if (lost_active) {
ui::Shadow* shadow = GetShadowForWindow(lost_active);
if (shadow && GetShadowElevationConvertDefault(lost_active) ==
kShadowElevationInactiveWindow) {
shadow->SetElevation(
GetShadowElevationForWindowLosingActive(lost_active, gained_active));
}
}
}
bool ShadowController::Impl::ShouldShowShadowForWindow(
aura::Window* window) const {
if (window->GetOcclusionState() == aura::Window::OcclusionState::OCCLUDED) {
return false;
}
if (delegate_) {
const bool should_show = delegate_->ShouldShowShadowForWindow(window);
if (should_show)
DCHECK(GetShadowElevationConvertDefault(window) > 0);
return should_show;
}
ui::mojom::WindowShowState show_state =
window->GetProperty(aura::client::kShowStateKey);
if (show_state == ui::mojom::WindowShowState::kFullscreen ||
show_state == ui::mojom::WindowShowState::kMaximized) {
return false;
}
return GetShadowElevationConvertDefault(window) > 0;
}
void ShadowController::Impl::OnWindowOcclusionChanged(aura::Window* window) {
ui::Shadow* shadow = GetShadowForWindow(window);
if (!shadow) {
return;
}
HandlePossibleShadowVisibilityChange(window);
}
void ShadowController::Impl::MaybeSetShadowRadiusForWindow(
aura::Window* window) const {
ui::Shadow* shadow = GetShadowForWindow(window);
CHECK(shadow);
if (delegate_ && !delegate_->ShouldRoundShadowForWindow(window)) {
shadow->SetRoundedCornerRadius(0);
return;
}
gfx::RoundedCornersF* rounded_corners =
window->GetProperty(aura::client::kWindowRoundedCornersKey);
if (rounded_corners) {
shadow->SetRoundedCornerRadius(rounded_corners->upper_left());
}
}
void ShadowController::Impl::HandlePossibleShadowVisibilityChange(
aura::Window* window) {
const bool should_show = ShouldShowShadowForWindow(window);
ui::Shadow* shadow = GetShadowForWindow(window);
if (shadow) {
shadow->SetElevation(GetShadowElevationForActiveState(window));
MaybeSetShadowRadiusForWindow(window);
if (shadow->layer()->GetTargetVisibility() != should_show) {
shadow->layer()->SetVisible(should_show);
}
} else if (should_show) {
CreateShadowForWindow(window);
}
}
void ShadowController::Impl::CreateShadowForWindow(aura::Window* window) {
DCHECK(!window->IsRootWindow());
ui::Shadow* shadow =
window->SetProperty(kShadowLayerKey, std::make_unique<ui::Shadow>());
MaybeSetShadowRadiusForWindow(window);
shadow->Init(GetShadowElevationForActiveState(window));
#if BUILDFLAG(IS_CHROMEOS)
shadow->SetShadowStyle(gfx::ShadowStyle::kChromeOSSystemUI);
#endif
shadow->SetContentBounds(gfx::Rect(window->bounds().size()));
shadow->layer()->SetVisible(ShouldShowShadowForWindow(window));
window->layer()->Add(shadow->layer());
window->layer()->StackAtBottom(shadow->layer());
window->TrackOcclusionState();
if (delegate_) {
delegate_->ApplyColorThemeToWindowShadow(window);
}
}
bool ShadowController::Impl::IsObservingWindowForTest(
aura::Window* window) const {
return observation_manager_.IsObservingSource(window);
}
ShadowController::Impl::Impl(aura::Env* env)
: env_(env), observation_manager_(this) {
GetInstances()->insert(this);
env_->AddObserver(this);
}
ShadowController::Impl::~Impl() {
env_->RemoveObserver(this);
GetInstances()->erase(this);
}
base::flat_set<ShadowController::Impl*>*
ShadowController::Impl::GetInstances() {
static base::NoDestructor<base::flat_set<Impl*>> impls;
return impls.get();
}
ui::Shadow* ShadowController::GetShadowForWindow(aura::Window* window) {
return window->GetProperty(kShadowLayerKey);
}
ui::Shadow::ElevationToColorsMap ShadowController::GenerateShadowColorsMap(
const ui::ColorProvider* color_provider) {
ui::Shadow::ElevationToColorsMap color_map;
color_map[kShadowElevationPopup] = std::make_pair(
color_provider->GetColor(ui::kColorShadowValueKeyShadowElevationFour),
color_provider->GetColor(
ui::kColorShadowValueAmbientShadowElevationFour));
color_map[kShadowElevationInactiveWindow] = std::make_pair(
color_provider->GetColor(ui::kColorShadowValueKeyShadowElevationTwelve),
color_provider->GetColor(
ui::kColorShadowValueAmbientShadowElevationTwelve));
color_map[kShadowElevationActiveWindow] = std::make_pair(
color_provider->GetColor(
ui::kColorShadowValueKeyShadowElevationTwentyFour),
color_provider->GetColor(
ui::kColorShadowValueAmbientShadowElevationTwentyFour));
return color_map;
}
ShadowController::ShadowController(
ActivationClient* activation_client,
std::unique_ptr<ShadowControllerDelegate> delegate,
aura::Env* env)
: activation_client_(activation_client),
impl_(Impl::GetInstance(env ? env : aura::Env::GetInstance())) {
activation_client_->AddObserver(this);
if (delegate)
impl_->set_delegate(std::move(delegate));
}
ShadowController::~ShadowController() {
activation_client_->RemoveObserver(this);
}
bool ShadowController::IsShadowVisibleForWindow(aura::Window* window) {
return impl_->IsShadowVisibleForWindow(window);
}
void ShadowController::UpdateShadowForWindow(aura::Window* window) {
impl_->UpdateShadowForWindow(window);
}
void ShadowController::OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
impl_->OnWindowActivated(reason, gained_active, lost_active);
}
bool ShadowController::IsObservingWindowForTest(aura::Window* window) const {
return impl_->IsObservingWindowForTest(window);
}
}