1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
// Copyright 2012 The Chromium Authors
// 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>(ui::LAYER_NOT_DRAWN));
layer()->SetName("Shadow Parent Container");
RecreateShadowLayer();
}
void Shadow::SetContentBounds(const gfx::Rect& content_bounds) {
// The layer's bounds should change with the content bounds accordingly. Need
// to recalculate the layer bounds if the layer bounds were modified after the
// content bounds were last set. 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_ &&
layer()->bounds() == last_layer_bounds_) {
return;
}
content_bounds_ = content_bounds;
UpdateShadowAppearance();
}
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;
UpdateShadowAppearance();
}
void Shadow::SetShadowStyle(gfx::ShadowStyle style) {
if (style_ == style)
return;
style_ = style;
UpdateShadowAppearance();
}
void Shadow::SetElevationToColorsMap(const ElevationToColorsMap& color_map) {
color_map_ = color_map;
UpdateShadowAppearance();
}
void Shadow::OnImplicitAnimationsCompleted() {
std::unique_ptr<ui::Layer> to_be_deleted = fading_layer_owner_.ReleaseLayer();
// The size needed for layer() may be smaller now that |fading_layer()| is
// removed.
UpdateShadowAppearance();
}
// -----------------------------------------------------------------------------
// Shadow::ShadowLayerOwner:
Shadow::ShadowLayerOwner::ShadowLayerOwner(Shadow* owner,
std::unique_ptr<Layer> layer)
: LayerOwner(std::move(layer)), owner_shadow_(owner) {}
Shadow::ShadowLayerOwner::~ShadowLayerOwner() = default;
std::unique_ptr<Layer> 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_->UpdateShadowAppearance();
return result;
}
// -----------------------------------------------------------------------------
// Shadow:
void Shadow::RecreateShadowLayer() {
shadow_layer_owner_.Reset(std::make_unique<ui::Layer>(ui::LAYER_NINE_PATCH));
shadow_layer()->SetName("Shadow");
shadow_layer()->SetVisible(true);
shadow_layer()->SetFillsBoundsOpaquely(false);
layer()->Add(shadow_layer());
details_ = nullptr;
UpdateShadowAppearance();
}
void Shadow::UpdateShadowAppearance() {
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_));
auto iter = color_map_.find(desired_elevation_);
const auto& details =
(iter == color_map_.end())
? gfx::ShadowDetails::Get(size_adjusted_elevation,
rounded_corner_radius_, style_)
: gfx::ShadowDetails::Get(
size_adjusted_elevation, rounded_corner_radius_,
/*key_color=*/iter->second.first,
/*ambient_color=*/iter->second.second, 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.nine_patch_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.nine_patch_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);
}
last_layer_bounds_ = 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
|