// Copyright (c) 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 "ui/compositor_extra/shadow.h" #include "ui/base/resource/resource_bundle.h" #include "ui/compositor/layer.h" #include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/shadow_util.h" namespace ui { namespace { // Duration for opacity animation in milliseconds. constexpr int kShadowAnimationDurationMs = 100; } // namespace Shadow::Shadow() : shadow_layer_owner_(this) {} Shadow::~Shadow() = default; void Shadow::Init(int elevation) { DCHECK_GE(elevation, 0); desired_elevation_ = elevation; SetLayer(std::make_unique(ui::LAYER_NOT_DRAWN)); layer()->SetName("Shadow Parent Container"); RecreateShadowLayer(); } void Shadow::SetContentBounds(const gfx::Rect& content_bounds) { // When the window moves but doesn't change size, this is a no-op. (The // origin stays the same in this case.) if (content_bounds == content_bounds_) return; content_bounds_ = content_bounds; UpdateLayerBounds(); } void Shadow::SetElevation(int elevation) { DCHECK_GE(elevation, 0); if (desired_elevation_ == elevation) return; desired_elevation_ = elevation; // Stop waiting for any as yet unfinished implicit animations. StopObservingImplicitAnimations(); // The old shadow layer is the new fading out layer. DCHECK(shadow_layer()); fading_layer_owner_.Reset(shadow_layer_owner_.ReleaseLayer()); RecreateShadowLayer(); shadow_layer()->SetOpacity(0.f); { // Observe the fade out animation so we can clean up the layer when done. ui::ScopedLayerAnimationSettings settings(fading_layer()->GetAnimator()); settings.AddObserver(this); settings.SetTransitionDuration( base::Milliseconds(kShadowAnimationDurationMs)); fading_layer()->SetOpacity(0.f); } { // We don't care to observe this one. ui::ScopedLayerAnimationSettings settings(shadow_layer()->GetAnimator()); settings.SetTransitionDuration( base::Milliseconds(kShadowAnimationDurationMs)); shadow_layer()->SetOpacity(1.f); } } void Shadow::SetRoundedCornerRadius(int rounded_corner_radius) { DCHECK_GE(rounded_corner_radius, 0); if (rounded_corner_radius_ == rounded_corner_radius) return; rounded_corner_radius_ = rounded_corner_radius; UpdateLayerBounds(); } void Shadow::SetShadowStyle(gfx::ShadowStyle style) { if (style_ == style) return; style_ = style; RecreateShadowLayer(); } void Shadow::OnImplicitAnimationsCompleted() { std::unique_ptr to_be_deleted = fading_layer_owner_.ReleaseLayer(); // The size needed for layer() may be smaller now that |fading_layer()| is // removed. UpdateLayerBounds(); } // ----------------------------------------------------------------------------- // Shadow::ShadowLayerOwner: Shadow::ShadowLayerOwner::ShadowLayerOwner(Shadow* owner, std::unique_ptr layer) : LayerOwner(std::move(layer)), owner_shadow_(owner) {} Shadow::ShadowLayerOwner::~ShadowLayerOwner() = default; std::unique_ptr Shadow::ShadowLayerOwner::RecreateLayer() { auto result = ui::LayerOwner::RecreateLayer(); // Now update the newly recreated shadow layer with the correct nine patch // image details. owner_shadow_->details_ = nullptr; owner_shadow_->UpdateLayerBounds(); return result; } // ----------------------------------------------------------------------------- // Shadow: void Shadow::RecreateShadowLayer() { shadow_layer_owner_.Reset(std::make_unique(ui::LAYER_NINE_PATCH)); shadow_layer()->SetName("Shadow"); shadow_layer()->SetVisible(true); shadow_layer()->SetFillsBoundsOpaquely(false); layer()->Add(shadow_layer()); UpdateLayerBounds(); } void Shadow::UpdateLayerBounds() { if (content_bounds_.IsEmpty()) return; // The ninebox assumption breaks down when the window is too small for the // desired elevation. The height/width of |blur_region| will be 4 * elevation // (see ShadowDetails::Get), so cap elevation at the most we can handle. const int smaller_dimension = std::min(content_bounds_.width(), content_bounds_.height()); const int size_adjusted_elevation = std::min((smaller_dimension - 2 * rounded_corner_radius_) / 4, static_cast(desired_elevation_)); const auto& details = gfx::ShadowDetails::Get(size_adjusted_elevation, rounded_corner_radius_, style_); gfx::Insets blur_region = gfx::ShadowValue::GetBlurRegion(details.values) + gfx::Insets(rounded_corner_radius_); // Update |shadow_layer()| if details changed and it has been updated in // the past (|details_| is set), or elevation is non-zero. if ((&details != details_) && (details_ || size_adjusted_elevation)) { shadow_layer()->UpdateNinePatchLayerImage(details.ninebox_image); // The ninebox grid is defined in terms of the image size. The shadow blurs // in both inward and outward directions from the edge of the contents, so // the aperture goes further inside the image than the shadow margins (which // represent exterior blur). gfx::Rect aperture(details.ninebox_image.size()); aperture.Inset(blur_region); shadow_layer()->UpdateNinePatchLayerAperture(aperture); details_ = &details; } // Shadow margins are negative, so this expands outwards from // |content_bounds_|. const gfx::Insets margins = gfx::ShadowValue::GetMargin(details.values); gfx::Rect new_layer_bounds = content_bounds_; new_layer_bounds.Inset(margins); gfx::Rect shadow_layer_bounds(new_layer_bounds.size()); // When there's an old shadow fading out, the bounds of layer() have to be // big enough to encompass both shadows. if (fading_layer()) { const gfx::Rect old_layer_bounds = layer()->bounds(); gfx::Rect combined_layer_bounds = old_layer_bounds; combined_layer_bounds.Union(new_layer_bounds); layer()->SetBounds(combined_layer_bounds); // If this is reached via SetContentBounds, we might hypothetically need // to change the size of the fading layer, but the fade is so fast it's // not really an issue. gfx::Rect fading_layer_bounds(fading_layer()->bounds()); fading_layer_bounds.Offset(old_layer_bounds.origin() - combined_layer_bounds.origin()); fading_layer()->SetBounds(fading_layer_bounds); shadow_layer_bounds.Offset(new_layer_bounds.origin() - combined_layer_bounds.origin()); } else { layer()->SetBounds(new_layer_bounds); } shadow_layer()->SetBounds(shadow_layer_bounds); // Occlude the region inside the bounding box. Occlusion uses shadow layer // space. See nine_patch_layer.h for more context on what's going on here. gfx::Rect occlusion_bounds(shadow_layer_bounds.size()); occlusion_bounds.Inset(-margins + gfx::Insets(rounded_corner_radius_)); shadow_layer()->UpdateNinePatchOcclusion(occlusion_bounds); // The border is the same inset as the aperture. shadow_layer()->UpdateNinePatchLayerBorder( gfx::Rect(blur_region.left(), blur_region.top(), blur_region.width(), blur_region.height())); } } // namespace ui