blob: 05174d28be59caeff717ce7a93f8964792951710 [file] [log] [blame]
// 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 "base/lazy_instance.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/image/image_skia_operations.h"
namespace wm {
namespace {
// Rounded corners are overdrawn on top of the window's content layer,
// we need to exclude them from the occlusion area.
const int kRoundedCornerRadius = 2;
// Duration for opacity animation in milliseconds.
const int kShadowAnimationDurationMs = 100;
struct ShadowDetails {
// Description of the shadows.
gfx::ShadowValues values;
// Cached ninebox image based on |values|.
gfx::ImageSkia ninebox_image;
};
// Map from elevation to a cached shadow.
using ShadowDetailsMap = std::map<int, ShadowDetails>;
base::LazyInstance<ShadowDetailsMap> g_shadow_cache = LAZY_INSTANCE_INITIALIZER;
const ShadowDetails& GetDetailsForElevation(int elevation) {
auto iter = g_shadow_cache.Get().find(elevation);
if (iter != g_shadow_cache.Get().end())
return iter->second;
auto insertion =
g_shadow_cache.Get().insert(std::make_pair(elevation, ShadowDetails()));
DCHECK(insertion.second);
ShadowDetails* shadow = &insertion.first->second;
// To match the CSS notion of blur (spread outside the bounding box) to the
// Skia notion of blur (spread outside and inside the bounding box), we have
// to double the designer-provided blur values.
const int kBlurCorrection = 2;
// "Key shadow": y offset is elevation and blur is twice the elevation.
shadow->values.emplace_back(gfx::Vector2d(0, elevation),
kBlurCorrection * elevation * 2,
SkColorSetA(SK_ColorBLACK, 0x3d));
// "Ambient shadow": no offset and blur matches the elevation.
shadow->values.emplace_back(gfx::Vector2d(), kBlurCorrection * elevation,
SkColorSetA(SK_ColorBLACK, 0x1f));
// To see what this looks like for elevation 24, try this CSS:
// box-shadow: 0 24px 48px rgba(0, 0, 0, .24),
// 0 0 24px rgba(0, 0, 0, .12);
shadow->ninebox_image = gfx::ImageSkiaOperations::CreateShadowNinebox(
shadow->values, kRoundedCornerRadius);
return *shadow;
}
} // namespace
Shadow::Shadow() {}
Shadow::~Shadow() {}
void Shadow::Init(Style style) {
style_ = style;
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::SetStyle(Style style) {
if (style_ == style)
return;
style_ = style;
// 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::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());
const ShadowDetails& details = GetDetailsForElevation(ElevationForStyle());
shadow_layer_->UpdateNinePatchLayerImage(details.ninebox_image);
UpdateLayerBounds();
}
void Shadow::UpdateLayerBounds() {
const ShadowDetails& details = GetDetailsForElevation(ElevationForStyle());
// 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(kRoundedCornerRadius));
shadow_layer_->UpdateNinePatchOcclusion(occlusion_bounds);
// The border is more or less the same inset as the aperture, but can be no
// larger than the shadow layer. When the shadow layer is too small, shrink
// the dimensions proportionally.
gfx::Insets blur_region = gfx::ShadowValue::GetBlurRegion(details.values) +
gfx::Insets(kRoundedCornerRadius);
int border_w = std::min(blur_region.width(), shadow_layer_bounds.width());
int border_x = border_w * blur_region.left() / blur_region.width();
int border_h = std::min(blur_region.height(), shadow_layer_bounds.height());
int border_y = border_h * blur_region.top() / blur_region.height();
shadow_layer_->UpdateNinePatchLayerBorder(
gfx::Rect(border_x, border_y, border_w, border_h));
// 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());
// The insets for the aperture are nominally |blur_region| but we need to
// resize them if the contents are too small.
// TODO(estade): by cutting out parts of ninebox, we lose the smooth
// horizontal or vertical transition. This isn't very noticeable, but we may
// need to address it by using a separate shadow layer for each ShadowValue,
// by adjusting the shadow for very small windows, or other means.
aperture.Inset(gfx::Insets(border_y, border_x, border_h - border_y,
border_w - border_x));
shadow_layer_->UpdateNinePatchLayerAperture(aperture);
}
int Shadow::ElevationForStyle() {
switch (style_) {
case STYLE_ACTIVE:
return 24;
case STYLE_INACTIVE:
return 8;
case STYLE_SMALL:
return 6;
}
NOTREACHED();
return 0;
}
} // namespace wm