text: // 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/wm/core/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"
  #include "ui/wm/core/shadow_types.h"
  
  namespace wm {
  
  namespace {
  
  // Duration for opacity animation in milliseconds.
  const int kShadowAnimationDurationMs = 100;
  
  // Default rounded corner radius. Shadow::SetRoundedCornerRadius can
  // be used to override this for elements with a different rounded
  // corner radius.
  const int kDefaultRoundedCornerRadius = 2;
  
  }  // namespace
  
  Shadow::Shadow()
      : desired_elevation_(ShadowElevation::NONE),
        rounded_corner_radius_(kDefaultRoundedCornerRadius) {}
  
  Shadow::~Shadow() {}
  
  void Shadow::Init(ShadowElevation elevation) {
    DCHECK_NE(ShadowElevation::DEFAULT, elevation);
    desired_elevation_ = elevation;
    layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN));
    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(ShadowElevation elevation) {
    DCHECK_NE(ShadowElevation::DEFAULT, elevation);
    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_);
    if (size_adjusted_elevation != effective_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);
    }
    effective_elevation_ = size_adjusted_elevation;
  
    // 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 wm
  