| // 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::~Shadow() {} |
| |
| void Shadow::Init(int elevation) { |
| DCHECK_GE(elevation, 0); |
| desired_elevation_ = elevation; |
| layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN)); |
| layer_->set_name("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_ = std::move(shadow_layer_); |
| 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::TimeDelta::FromMilliseconds(kShadowAnimationDurationMs)); |
| fading_layer_->SetOpacity(0.f); |
| } |
| |
| { |
| // We don't care to observe this one. |
| ui::ScopedLayerAnimationSettings settings(shadow_layer_->GetAnimator()); |
| settings.SetTransitionDuration( |
| base::TimeDelta::FromMilliseconds(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::OnImplicitAnimationsCompleted() { |
| fading_layer_.reset(); |
| // The size needed for layer() may be smaller now that |fading_layer_| is |
| // removed. |
| UpdateLayerBounds(); |
| } |
| |
| void Shadow::RecreateShadowLayer() { |
| shadow_layer_.reset(new ui::Layer(ui::LAYER_NINE_PATCH)); |
| shadow_layer_->set_name("Shadow"); |
| shadow_layer_->SetVisible(true); |
| shadow_layer_->SetFillsBoundsOpaquely(false); |
| layer()->Add(shadow_layer_.get()); |
| |
| 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<int>(desired_elevation_)); |
| const auto& details = |
| gfx::ShadowDetails::Get(size_adjusted_elevation, rounded_corner_radius_); |
| 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 |