| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/wm/core/shadow.h" | 5 #include "ui/wm/core/shadow.h" |
| 6 | 6 |
| 7 #include "third_party/skia/include/core/SkBitmap.h" | 7 #include "base/lazy_instance.h" |
| 8 #include "ui/base/resource/resource_bundle.h" | 8 #include "ui/base/resource/resource_bundle.h" |
| 9 #include "ui/compositor/layer.h" | 9 #include "ui/compositor/layer.h" |
| 10 #include "ui/compositor/scoped_layer_animation_settings.h" | 10 #include "ui/compositor/scoped_layer_animation_settings.h" |
| 11 #include "ui/resources/grit/ui_resources.h" | 11 #include "ui/gfx/geometry/insets.h" |
| 12 #include "ui/gfx/image/image_skia_operations.h" |
| 13 |
| 14 namespace wm { |
| 12 | 15 |
| 13 namespace { | 16 namespace { |
| 14 | 17 |
| 15 // The opacity used for active shadow when animating between | 18 // The opacity used for active shadow when animating between |
| 16 // inactive/active shadow. | 19 // inactive/active shadow. |
| 17 const float kInactiveShadowAnimationOpacity = 0.2f; | 20 const float kInactiveShadowAnimationOpacity = 0.2f; |
| 18 | 21 |
| 19 // Shadow aperture for different styles. | |
| 20 // Note that this may be greater than interior inset to allow shadows with | |
| 21 // curved corners that extend inwards beyond a window's borders. | |
| 22 const int kActiveInteriorAperture = 134; | |
| 23 const int kInactiveInteriorAperture = 134; | |
| 24 const int kSmallInteriorAperture = 9; | |
| 25 | |
| 26 // Interior inset for different styles. | |
| 27 const int kActiveInteriorInset = 64; | |
| 28 const int kInactiveInteriorInset = 64; | |
| 29 const int kSmallInteriorInset = 4; | |
| 30 | |
| 31 // Rounded corners are overdrawn on top of the window's content layer, | 22 // Rounded corners are overdrawn on top of the window's content layer, |
| 32 // we need to exclude them from the occlusion area. | 23 // we need to exclude them from the occlusion area. |
| 33 const int kRoundedCornerRadius = 2; | 24 const int kRoundedCornerRadius = 2; |
| 34 | 25 |
| 35 // Duration for opacity animation in milliseconds. | 26 // Duration for opacity animation in milliseconds. |
| 36 const int kShadowAnimationDurationMs = 100; | 27 const int kShadowAnimationDurationMs = 100; |
| 37 | 28 |
| 38 int GetShadowApertureForStyle(wm::Shadow::Style style) { | 29 struct ShadowDetails { |
| 39 switch (style) { | 30 // Description of the shadows. |
| 40 case wm::Shadow::STYLE_ACTIVE: | 31 gfx::ShadowValues values; |
| 41 return kActiveInteriorAperture; | 32 // Cached ninebox image based on |values|. |
| 42 case wm::Shadow::STYLE_INACTIVE: | 33 gfx::ImageSkia ninebox_image; |
| 43 return kInactiveInteriorAperture; | 34 }; |
| 44 case wm::Shadow::STYLE_SMALL: | |
| 45 return kSmallInteriorAperture; | |
| 46 } | |
| 47 return 0; | |
| 48 } | |
| 49 | 35 |
| 50 int GetInteriorInsetForStyle(wm::Shadow::Style style) { | 36 // Map from elevation to a cached shadow. |
| 51 switch (style) { | 37 using ShadowDetailsMap = std::map<int, ShadowDetails>; |
| 52 case wm::Shadow::STYLE_ACTIVE: | 38 base::LazyInstance<ShadowDetailsMap> g_shadow_cache = LAZY_INSTANCE_INITIALIZER; |
| 53 return kActiveInteriorInset; | 39 |
| 54 case wm::Shadow::STYLE_INACTIVE: | 40 const ShadowDetails& GetDetailsForElevation(int elevation) { |
| 55 return kInactiveInteriorInset; | 41 auto iter = g_shadow_cache.Get().find(elevation); |
| 56 case wm::Shadow::STYLE_SMALL: | 42 if (iter != g_shadow_cache.Get().end()) |
| 57 return kSmallInteriorInset; | 43 return iter->second; |
| 58 } | 44 |
| 59 return 0; | 45 auto insertion = |
| 46 g_shadow_cache.Get().insert(std::make_pair(elevation, ShadowDetails())); |
| 47 DCHECK(insertion.second); |
| 48 ShadowDetails* shadow = &insertion.first->second; |
| 49 // To match the CSS notion of blur (spread outside the bounding box) to the |
| 50 // Skia notion of blur (spread outside and inside the bounding box), we have |
| 51 // to double the designer-provided blur values. |
| 52 const int kBlurCorrection = 2; |
| 53 // "Key shadow": y offset is elevation and blur is twice the elevation. |
| 54 shadow->values.emplace_back(gfx::Vector2d(0, elevation), |
| 55 kBlurCorrection * elevation * 2, |
| 56 SkColorSetA(SK_ColorBLACK, 0x3d)); |
| 57 // "Ambient shadow": no offset and blur matches the elevation. |
| 58 shadow->values.emplace_back(gfx::Vector2d(), kBlurCorrection * elevation, |
| 59 SkColorSetA(SK_ColorBLACK, 0x1f)); |
| 60 // To see what this looks like for elevation 24, try this CSS: |
| 61 // box-shadow: 0 24px 48px rgba(0, 0, 0, .24), |
| 62 // 0 0 24px rgba(0, 0, 0, .12); |
| 63 shadow->ninebox_image = gfx::ImageSkiaOperations::CreateShadowNinebox( |
| 64 shadow->values, kRoundedCornerRadius); |
| 65 return *shadow; |
| 60 } | 66 } |
| 61 | 67 |
| 62 } // namespace | 68 } // namespace |
| 63 | 69 |
| 64 namespace wm { | 70 Shadow::Shadow() {} |
| 65 | 71 |
| 66 Shadow::Shadow() : style_(STYLE_ACTIVE), interior_inset_(0) { | 72 Shadow::~Shadow() {} |
| 67 } | |
| 68 | |
| 69 Shadow::~Shadow() { | |
| 70 } | |
| 71 | 73 |
| 72 void Shadow::Init(Style style) { | 74 void Shadow::Init(Style style) { |
| 73 style_ = style; | 75 style_ = style; |
| 74 | 76 |
| 75 layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN)); | 77 layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN)); |
| 76 shadow_layer_.reset(new ui::Layer(ui::LAYER_NINE_PATCH)); | 78 shadow_layer_.reset(new ui::Layer(ui::LAYER_NINE_PATCH)); |
| 77 layer()->Add(shadow_layer_.get()); | 79 layer()->Add(shadow_layer_.get()); |
| 78 | 80 |
| 79 UpdateImagesForStyle(); | 81 UpdateImagesForStyle(); |
| 80 shadow_layer_->set_name("Shadow"); | 82 shadow_layer_->set_name("Shadow"); |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 142 // If we just finished going inactive, switch images. This doesn't cause | 144 // If we just finished going inactive, switch images. This doesn't cause |
| 143 // a visual pop because the inactive image opacity is so low. | 145 // a visual pop because the inactive image opacity is so low. |
| 144 if (style_ == STYLE_INACTIVE) { | 146 if (style_ == STYLE_INACTIVE) { |
| 145 UpdateImagesForStyle(); | 147 UpdateImagesForStyle(); |
| 146 // Opacity is baked into inactive image, so set fully opaque. | 148 // Opacity is baked into inactive image, so set fully opaque. |
| 147 shadow_layer_->SetOpacity(1.0f); | 149 shadow_layer_->SetOpacity(1.0f); |
| 148 } | 150 } |
| 149 } | 151 } |
| 150 | 152 |
| 151 void Shadow::UpdateImagesForStyle() { | 153 void Shadow::UpdateImagesForStyle() { |
| 152 ResourceBundle& res = ResourceBundle::GetSharedInstance(); | 154 const ShadowDetails& details = GetDetailsForElevation(ElevationForStyle()); |
| 153 gfx::Image image; | 155 shadow_layer_->UpdateNinePatchLayerImage(details.ninebox_image); |
| 154 switch (style_) { | 156 // The ninebox grid is defined in terms of the image size. The shadow blurs in |
| 155 case STYLE_ACTIVE: | 157 // both inward and outward directions from the edge of the contents, so the |
| 156 image = res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE); | 158 // aperture goes further inside the image than the shadow margins (which |
| 157 break; | 159 // represent exterior blur). |
| 158 case STYLE_INACTIVE: | 160 gfx::Rect aperture(details.ninebox_image.size()); |
| 159 image = res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE); | 161 gfx::Insets blur_region = gfx::ShadowValue::GetBlurRegion(details.values) + |
| 160 break; | 162 gfx::Insets(kRoundedCornerRadius); |
| 161 case STYLE_SMALL: | 163 aperture.Inset(blur_region); |
| 162 image = res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL); | 164 shadow_layer_->UpdateNinePatchLayerAperture(aperture); |
| 163 break; | |
| 164 default: | |
| 165 NOTREACHED() << "Unhandled style " << style_; | |
| 166 break; | |
| 167 } | |
| 168 | |
| 169 shadow_layer_->UpdateNinePatchLayerImage(image.AsImageSkia()); | |
| 170 image_size_ = image.Size(); | |
| 171 interior_inset_ = GetInteriorInsetForStyle(style_); | |
| 172 | |
| 173 // Image sizes may have changed. | |
| 174 UpdateLayerBounds(); | 165 UpdateLayerBounds(); |
| 175 } | 166 } |
| 176 | 167 |
| 177 void Shadow::UpdateLayerBounds() { | 168 void Shadow::UpdateLayerBounds() { |
| 178 // Update bounds based on content bounds and interior inset. | 169 const ShadowDetails& details = GetDetailsForElevation(ElevationForStyle()); |
| 170 // Shadow margins are negative, so this expands outwards from |
| 171 // |content_bounds_|. |
| 172 const gfx::Insets margins = gfx::ShadowValue::GetMargin(details.values); |
| 179 gfx::Rect layer_bounds = content_bounds_; | 173 gfx::Rect layer_bounds = content_bounds_; |
| 180 layer_bounds.Inset(-interior_inset_, -interior_inset_); | 174 layer_bounds.Inset(margins); |
| 181 layer()->SetBounds(layer_bounds); | 175 layer()->SetBounds(layer_bounds); |
| 182 shadow_layer_->SetBounds(gfx::Rect(layer_bounds.size())); | 176 const gfx::Rect shadow_layer_bounds(layer_bounds.size()); |
| 177 shadow_layer_->SetBounds(shadow_layer_bounds); |
| 183 | 178 |
| 184 // Update the shadow aperture and border for style. Note that border is in | 179 // Occlude the region inside the bounding box. Occlusion uses shadow layer |
| 185 // layer space and it cannot exceed the bounds of the layer. | 180 // space. See nine_patch_layer.h for more context on what's going on here. |
| 186 int aperture = GetShadowApertureForStyle(style_); | 181 gfx::Rect occlusion_bounds = shadow_layer_bounds; |
| 187 int aperture_x = std::min(aperture, layer_bounds.width() / 2); | 182 occlusion_bounds.Inset(-margins + gfx::Insets(kRoundedCornerRadius)); |
| 188 int aperture_y = std::min(aperture, layer_bounds.height() / 2); | 183 shadow_layer_->UpdateNinePatchOcclusion(occlusion_bounds); |
| 189 gfx::Rect aperture_rect(aperture_x, aperture_y, | |
| 190 image_size_.width() - aperture_x * 2, | |
| 191 image_size_.height() - aperture_y * 2); | |
| 192 | 184 |
| 193 shadow_layer_->UpdateNinePatchLayerAperture(aperture_rect); | 185 // The border is more or less the same inset as the aperture, but can be no |
| 186 // larger than the shadow layer. When the shadow layer is too small, shrink |
| 187 // the dimensions proportionally. |
| 188 gfx::Insets blur_region = gfx::ShadowValue::GetBlurRegion(details.values) + |
| 189 gfx::Insets(kRoundedCornerRadius); |
| 190 int border_w = std::min(blur_region.width(), shadow_layer_bounds.width()); |
| 191 int border_x = border_w * blur_region.left() / blur_region.width(); |
| 192 int border_h = std::min(blur_region.height(), shadow_layer_bounds.height()); |
| 193 int border_y = border_h * blur_region.top() / blur_region.height(); |
| 194 shadow_layer_->UpdateNinePatchLayerBorder( | 194 shadow_layer_->UpdateNinePatchLayerBorder( |
| 195 gfx::Rect(aperture_x, aperture_y, aperture_x * 2, aperture_y * 2)); | 195 gfx::Rect(border_x, border_y, border_w, border_h)); |
| 196 } |
| 196 | 197 |
| 197 // The content bounds in the shadow's layer space are offsetted by | 198 int Shadow::ElevationForStyle() { |
| 198 // |interior_inset_|. The occlusion area also has to be shrunk to allow | 199 switch (style_) { |
| 199 // rounded corners overdrawing on top of the window's content. | 200 case STYLE_ACTIVE: |
| 200 gfx::Rect content_bounds(interior_inset_ + kRoundedCornerRadius, | 201 return 24; |
| 201 interior_inset_ + kRoundedCornerRadius, | 202 case STYLE_INACTIVE: |
| 202 content_bounds_.width() - 2 * kRoundedCornerRadius, | 203 return 8; |
| 203 content_bounds_.height() - 2 * kRoundedCornerRadius); | 204 case STYLE_SMALL: |
| 204 shadow_layer_->UpdateNinePatchOcclusion(content_bounds); | 205 return 6; |
| 206 } |
| 207 NOTREACHED(); |
| 208 return 0; |
| 205 } | 209 } |
| 206 | 210 |
| 207 } // namespace wm | 211 } // namespace wm |
| OLD | NEW |