blob: a0a674dcd8ace99578e84d844957464bcf1ad6b0 [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 "ash/wm/resize_shadow.h"
#include <memory>
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "ui/aura/window.h"
#include "ui/base/hit_test.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/canvas_image_source.h"
namespace {
// The width of the resize shadow that appears on the hovered edge of the
// window.
constexpr int kVisualThickness = 8;
// The corner radius of the resize shadow, which not coincidentally matches
// the corner radius of the actual window.
static constexpr int kCornerRadiusOfResizeShadow = 2;
static constexpr int kCornerRadiusOfWindow = 2;
// This class simply draws a roundrect. The layout and tiling is handled by
// ResizeShadow and NinePatchLayer.
class ResizeShadowImageSource : public gfx::CanvasImageSource {
public:
ResizeShadowImageSource()
: gfx::CanvasImageSource(gfx::Size(kImageSide, kImageSide),
false /* is opaque */) {}
~ResizeShadowImageSource() override = default;
// gfx::CanvasImageSource:
void Draw(gfx::Canvas* canvas) override {
cc::PaintFlags paint;
paint.setAntiAlias(true);
paint.setColor(SK_ColorBLACK);
canvas->DrawRoundRect(gfx::RectF(gfx::SizeF(size())),
kCornerRadiusOfResizeShadow, paint);
}
private:
// The image has to have enough space to depict the visual thickness (left and
// right) plus an inset for extending beneath the window's rounded corner plus
// one pixel for the center of the nine patch.
static constexpr int kImageSide =
2 * (kVisualThickness + kCornerRadiusOfWindow) + 1;
DISALLOW_COPY_AND_ASSIGN(ResizeShadowImageSource);
};
base::LazyInstance<std::unique_ptr<gfx::ImageSkia>>::Leaky g_shadow_image =
LAZY_INSTANCE_INITIALIZER;
} // namespace
namespace ash {
ResizeShadow::ResizeShadow(aura::Window* window)
: window_(window), last_hit_test_(HTNOWHERE) {
window_->AddObserver(this);
// Use a NinePatchLayer to tile the shadow image (which is simply a
// roundrect).
layer_.reset(new ui::Layer(ui::LAYER_NINE_PATCH));
layer_->set_name("WindowResizeShadow");
layer_->SetFillsBoundsOpaquely(false);
layer_->SetOpacity(0.f);
layer_->SetVisible(false);
if (!g_shadow_image.Get()) {
auto* source = new ResizeShadowImageSource();
g_shadow_image.Get() = std::make_unique<gfx::ImageSkia>(
base::WrapUnique(source), source->size());
}
layer_->UpdateNinePatchLayerImage(*g_shadow_image.Get());
gfx::Rect aperture(g_shadow_image.Get()->size());
constexpr gfx::Insets kApertureInsets(kVisualThickness +
kCornerRadiusOfWindow);
aperture.Inset(kApertureInsets);
layer_->UpdateNinePatchLayerAperture(aperture);
layer_->UpdateNinePatchLayerBorder(
gfx::Rect(kApertureInsets.left(), kApertureInsets.top(),
kApertureInsets.width(), kApertureInsets.height()));
ReparentLayer();
}
ResizeShadow::~ResizeShadow() {
window_->RemoveObserver(this);
}
void ResizeShadow::OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) {
UpdateBoundsAndVisibility();
}
void ResizeShadow::OnWindowHierarchyChanged(
const aura::WindowObserver::HierarchyChangeParams& params) {
ReparentLayer();
}
void ResizeShadow::OnWindowStackingChanged(aura::Window* window) {
ReparentLayer();
}
void ResizeShadow::ShowForHitTest(int hit) {
// Don't start animations unless something changed.
if (hit == last_hit_test_)
return;
last_hit_test_ = hit;
UpdateBoundsAndVisibility();
}
void ResizeShadow::Hide() {
ShowForHitTest(HTNOWHERE);
}
void ResizeShadow::ReparentLayer() {
DCHECK(window_->layer()->parent());
if (layer_->parent() != window_->layer()->parent())
window_->layer()->parent()->Add(layer_.get());
layer_->parent()->StackBelow(layer_.get(), window_->layer());
}
void ResizeShadow::UpdateBoundsAndVisibility() {
// The shadow layer is positioned such that one or two edges will stick out
// from underneath |window_|. Thus |window_| occludes the rest of the
// roundrect.
const int hit = last_hit_test_;
bool show_top = hit == HTTOPLEFT || hit == HTTOP || hit == HTTOPRIGHT;
bool show_left = hit == HTTOPLEFT || hit == HTLEFT || hit == HTBOTTOMLEFT;
bool show_bottom =
hit == HTBOTTOMLEFT || hit == HTBOTTOM || hit == HTBOTTOMRIGHT;
bool show_right = hit == HTTOPRIGHT || hit == HTRIGHT || hit == HTBOTTOMRIGHT;
const int outset = -kVisualThickness;
gfx::Insets outsets(show_top ? outset : 0, show_left ? outset : 0,
show_bottom ? outset : 0, show_right ? outset : 0);
bool visible = !outsets.IsEmpty();
if (!visible && !layer_->GetTargetVisibility())
return;
if (visible) {
gfx::Rect bounds = window_->bounds();
bounds.Inset(outsets);
layer_->SetBounds(bounds);
}
// The resize shadow snaps in but fades out.
ui::ScopedLayerAnimationSettings settings(layer_->GetAnimator());
if (!visible) {
constexpr int kShadowFadeOutDurationMs = 100;
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kShadowFadeOutDurationMs));
}
constexpr float kShadowTargetOpacity = 0.5f;
layer_->SetOpacity(visible ? kShadowTargetOpacity : 0.f);
layer_->SetVisible(visible);
}
} // namespace ash