From a54e65be68a438559f1c11c6ac1cbcc8999528cf Mon Sep 17 00:00:00 2001 From: "derat@chromium.org" Date: Mon, 21 Nov 2011 22:03:34 +0000 Subject: aura: Draw drop shadows under browsers and menus. This is largely a port of the existing X window manager's code for drawing shadows around windows. It adds an ImageGrid class for drawing a scaled 3x3 grid of gfx::Images and a Shadow class (managed by aura_shell::ShadowController) that uses ImageGrid to draw a shadow. Shadows can be disabled via --aura-no-shadows. BUG=101977 TEST=added unit tests; also manually checked that shadows get drawn Review URL: http://codereview.chromium.org/8555025 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@111009 0039d316-1c4b-4281-b951-d872f2087c98 --- ui/aura_shell/aura_shell.gyp | 8 ++ ui/aura_shell/desktop_layout_manager.cc | 2 +- ui/aura_shell/desktop_layout_manager.h | 2 +- ui/aura_shell/image_grid.cc | 182 +++++++++++++++++++++++++ ui/aura_shell/image_grid.h | 187 ++++++++++++++++++++++++++ ui/aura_shell/image_grid_unittest.cc | 201 ++++++++++++++++++++++++++++ ui/aura_shell/shadow.cc | 54 ++++++++ ui/aura_shell/shadow.h | 53 ++++++++ ui/aura_shell/shadow_controller.cc | 143 ++++++++++++++++++++ ui/aura_shell/shadow_controller.h | 96 +++++++++++++ ui/aura_shell/shadow_controller_unittest.cc | 90 +++++++++++++ ui/aura_shell/shell.cc | 6 +- ui/aura_shell/shell.h | 7 + ui/aura_shell/show_state_controller.h | 2 +- 14 files changed, 1028 insertions(+), 5 deletions(-) create mode 100644 ui/aura_shell/image_grid.cc create mode 100644 ui/aura_shell/image_grid.h create mode 100644 ui/aura_shell/image_grid_unittest.cc create mode 100644 ui/aura_shell/shadow.cc create mode 100644 ui/aura_shell/shadow.h create mode 100644 ui/aura_shell/shadow_controller.cc create mode 100644 ui/aura_shell/shadow_controller.h create mode 100644 ui/aura_shell/shadow_controller_unittest.cc (limited to 'ui/aura_shell') diff --git a/ui/aura_shell/aura_shell.gyp b/ui/aura_shell/aura_shell.gyp index 5639feb..345b999 100644 --- a/ui/aura_shell/aura_shell.gyp +++ b/ui/aura_shell/aura_shell.gyp @@ -49,6 +49,8 @@ 'drag_drop_controller.h', 'drag_image_view.cc', 'drag_image_view.h', + 'image_grid.cc', + 'image_grid.h', 'launcher/app_launcher_button.cc', 'launcher/app_launcher_button.h', 'launcher/launcher.cc', @@ -72,6 +74,10 @@ 'modality_event_filter_delegate.h', 'property_util.cc', 'property_util.h', + 'shadow.cc', + 'shadow.h', + 'shadow_controller.cc', + 'shadow_controller.h', 'shelf_layout_controller.cc', 'shelf_layout_controller.h', 'shell.cc', @@ -129,11 +135,13 @@ 'default_container_layout_manager_unittest.cc', 'desktop_event_filter_unittest.cc', 'drag_drop_controller_unittest.cc', + 'image_grid_unittest.cc', 'launcher/launcher_model_unittest.cc', 'launcher/view_model_unittest.cc', 'launcher/view_model_utils_unittest.cc', 'modal_container_layout_manager_unittest.cc', 'run_all_unittests.cc', + 'shadow_controller_unittest.cc', 'shell_unittest.cc', 'stacking_controller_unittest.cc', 'test_suite.cc', diff --git a/ui/aura_shell/desktop_layout_manager.cc b/ui/aura_shell/desktop_layout_manager.cc index 4160a43..90626ab 100644 --- a/ui/aura_shell/desktop_layout_manager.cc +++ b/ui/aura_shell/desktop_layout_manager.cc @@ -47,7 +47,7 @@ void DesktopLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) { } void DesktopLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child, - bool visibile) { + bool visible) { } void DesktopLayoutManager::SetChildBounds(aura::Window* child, diff --git a/ui/aura_shell/desktop_layout_manager.h b/ui/aura_shell/desktop_layout_manager.h index 96abeeb..7af3039 100644 --- a/ui/aura_shell/desktop_layout_manager.h +++ b/ui/aura_shell/desktop_layout_manager.h @@ -43,7 +43,7 @@ class DesktopLayoutManager : public aura::LayoutManager { virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE; virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE; virtual void OnChildWindowVisibilityChanged(aura::Window* child, - bool visibile) OVERRIDE; + bool visible) OVERRIDE; virtual void SetChildBounds(aura::Window* child, const gfx::Rect& requested_bounds) OVERRIDE; diff --git a/ui/aura_shell/image_grid.cc b/ui/aura_shell/image_grid.cc new file mode 100644 index 0000000..14d69ce --- /dev/null +++ b/ui/aura_shell/image_grid.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2011 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/aura_shell/image_grid.h" + +#include + +#include "ui/gfx/canvas.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/transform.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkXfermode.h" + +using std::max; + +namespace aura_shell { +namespace internal { + +gfx::Rect ImageGrid::TestAPI::GetTransformedLayerBounds( + const ui::Layer& layer) { + gfx::Rect bounds = layer.bounds(); + layer.transform().TransformRect(&bounds); + return bounds; +} + +ImageGrid::ImageGrid() + : top_image_height_(0), + bottom_image_height_(0), + left_image_width_(0), + right_image_width_(0), + top_row_height_(0), + bottom_row_height_(0), + left_column_width_(0), + right_column_width_(0) { +} + +ImageGrid::~ImageGrid() { +} + +void ImageGrid::Init(const gfx::Image* top_left_image, + const gfx::Image* top_image, + const gfx::Image* top_right_image, + const gfx::Image* left_image, + const gfx::Image* center_image, + const gfx::Image* right_image, + const gfx::Image* bottom_left_image, + const gfx::Image* bottom_image, + const gfx::Image* bottom_right_image) { + layer_.reset(new ui::Layer(ui::Layer::LAYER_HAS_NO_TEXTURE)); + + InitImage(top_left_image, &top_left_layer_, &top_left_painter_); + InitImage(top_image, &top_layer_, &top_painter_); + InitImage(top_right_image, &top_right_layer_, &top_right_painter_); + InitImage(left_image, &left_layer_, &left_painter_); + InitImage(center_image, ¢er_layer_, ¢er_painter_); + InitImage(right_image, &right_layer_, &right_painter_); + InitImage(bottom_left_image, &bottom_left_layer_, &bottom_left_painter_); + InitImage(bottom_image, &bottom_layer_, &bottom_painter_); + InitImage(bottom_right_image, &bottom_right_layer_, &bottom_right_painter_); + + top_image_height_ = GetImageSize(top_image).height(); + bottom_image_height_ = GetImageSize(bottom_image).height(); + left_image_width_ = GetImageSize(left_image).width(); + right_image_width_ = GetImageSize(right_image).width(); + + top_row_height_ = max(GetImageSize(top_left_image).height(), + max(GetImageSize(top_image).height(), + GetImageSize(top_right_image).height())); + bottom_row_height_ = max(GetImageSize(bottom_left_image).height(), + max(GetImageSize(bottom_image).height(), + GetImageSize(bottom_right_image).height())); + left_column_width_ = max(GetImageSize(top_left_image).width(), + max(GetImageSize(left_image).width(), + GetImageSize(bottom_left_image).width())); + right_column_width_ = max(GetImageSize(top_right_image).width(), + max(GetImageSize(right_image).width(), + GetImageSize(bottom_right_image).width())); +} + +void ImageGrid::SetSize(const gfx::Size& size) { + if (size_ == size) + return; + + size_ = size; + + gfx::Rect updated_bounds = layer_->bounds(); + updated_bounds.set_size(size); + layer_->SetBounds(updated_bounds); + + float center_width = size.width() - left_column_width_ - right_column_width_; + float center_height = size.height() - top_row_height_ - bottom_row_height_; + + if (top_layer_.get()) { + ui::Transform transform; + transform.SetScaleX(center_width / top_layer_->bounds().width()); + transform.ConcatTranslate(left_column_width_, 0); + top_layer_->SetTransform(transform); + } + if (bottom_layer_.get()) { + ui::Transform transform; + transform.SetScaleX(center_width / bottom_layer_->bounds().width()); + transform.ConcatTranslate( + left_column_width_, size.height() - bottom_layer_->bounds().height()); + bottom_layer_->SetTransform(transform); + } + if (left_layer_.get()) { + ui::Transform transform; + transform.SetScaleY(center_height / left_layer_->bounds().height()); + transform.ConcatTranslate(0, top_row_height_); + left_layer_->SetTransform(transform); + } + if (right_layer_.get()) { + ui::Transform transform; + transform.SetScaleY(center_height / right_layer_->bounds().height()); + transform.ConcatTranslate( + size.width() - right_layer_->bounds().width(), top_row_height_); + right_layer_->SetTransform(transform); + } + + if (top_left_layer_.get()) { + // No transformation needed; it should be at (0, 0) and unscaled. + } + if (top_right_layer_.get()) { + ui::Transform transform; + transform.SetTranslateX(size.width() - top_right_layer_->bounds().width()); + top_right_layer_->SetTransform(transform); + } + if (bottom_left_layer_.get()) { + ui::Transform transform; + transform.SetTranslateY( + size.height() - bottom_left_layer_->bounds().height()); + bottom_left_layer_->SetTransform(transform); + } + if (bottom_right_layer_.get()) { + ui::Transform transform; + transform.SetTranslate( + size.width() - bottom_right_layer_->bounds().width(), + size.height() - bottom_right_layer_->bounds().height()); + bottom_right_layer_->SetTransform(transform); + } + + if (center_layer_.get()) { + ui::Transform transform; + transform.SetScale(center_width / center_layer_->bounds().width(), + center_height / center_layer_->bounds().height()); + transform.ConcatTranslate(left_column_width_, top_row_height_); + center_layer_->SetTransform(transform); + } +} + +void ImageGrid::ImagePainter::OnPaintLayer(gfx::Canvas* canvas) { + canvas->DrawBitmapInt(*(image_->ToSkBitmap()), 0, 0); +} + +// static +gfx::Size ImageGrid::GetImageSize(const gfx::Image* image) { + return image ? + gfx::Size(image->ToSkBitmap()->width(), image->ToSkBitmap()->height()) : + gfx::Size(); +} + +void ImageGrid::InitImage(const gfx::Image* image, + scoped_ptr* layer_ptr, + scoped_ptr* painter_ptr) { + if (!image) + return; + + layer_ptr->reset(new ui::Layer(ui::Layer::LAYER_HAS_TEXTURE)); + + const gfx::Size size = GetImageSize(image); + layer_ptr->get()->SetBounds(gfx::Rect(0, 0, size.width(), size.height())); + + painter_ptr->reset(new ImagePainter(image)); + layer_ptr->get()->set_delegate(painter_ptr->get()); + layer_ptr->get()->SetFillsBoundsOpaquely(false); + layer_ptr->get()->SetVisible(true); + layer_->Add(layer_ptr->get()); +} + +} // namespace internal +} // namespace aura_shell diff --git a/ui/aura_shell/image_grid.h b/ui/aura_shell/image_grid.h new file mode 100644 index 0000000..e53d97d --- /dev/null +++ b/ui/aura_shell/image_grid.h @@ -0,0 +1,187 @@ +// Copyright (c) 2011 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. + +#ifndef UI_AURA_SHELL_IMAGE_GRID_H_ +#define UI_AURA_SHELL_IMAGE_GRID_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "ui/gfx/compositor/layer.h" +#include "ui/gfx/compositor/layer_delegate.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace gfx { +class Image; +} // namespace gfx + +namespace aura_shell { +namespace internal { + +// An ImageGrid is a 3x3 array of ui::Layers, each containing an image. +// +// As the grid is resized, its images fill the requested space: +// - corner images are not scaled +// - top and bottom images are scaled horizontally +// - left and right images are scaled vertically +// - the center image is scaled in both directions +// +// If one of the non-center images is smaller than the largest images in its +// row or column, it will be aligned with the outside of the grid. For +// example, given 4x4 top-left and top-right images and a 1x2 top images: +// +// +--------+---------------------+--------+ +// | | top | | +// | top- +---------------------+ top- + +// | left | | right | +// +----+---+ +---+----+ +// | | | | +// ... +// +// This may seem odd at first, but it lets ImageGrid be used to draw shadows +// with curved corners that extend inwards beyond a window's borders. In the +// below example, the top-left corner image is overlaid on top of the window's +// top-left corner: +// +// +---------+----------------------- +// | ..xxx|XXXXXXXXXXXXXXXXXX +// | .xXXXXX|XXXXXXXXXXXXXXXXXX_____ +// | .xXX | ^ window's top edge +// | .xXX | +// +---------+ +// | xXX| +// | xXX|< window's left edge +// | xXX| +// ... +// +class ImageGrid { + public: + // Helper class for use by tests. + class TestAPI { + public: + TestAPI(ImageGrid* grid) : grid_(grid) {} + ui::Layer* top_left_layer() const { return grid_->top_left_layer_.get(); } + ui::Layer* top_layer() const { return grid_->top_layer_.get(); } + ui::Layer* top_right_layer() const { return grid_->top_right_layer_.get(); } + ui::Layer* left_layer() const { return grid_->left_layer_.get(); } + ui::Layer* center_layer() const { return grid_->center_layer_.get(); } + ui::Layer* right_layer() const { return grid_->right_layer_.get(); } + ui::Layer* bottom_left_layer() const { + return grid_->bottom_left_layer_.get(); + } + ui::Layer* bottom_layer() const { return grid_->bottom_layer_.get(); } + ui::Layer* bottom_right_layer() const { + return grid_->bottom_right_layer_.get(); + } + + // Returns |layer|'s bounds after applying the layer's current transform. + gfx::Rect GetTransformedLayerBounds(const ui::Layer& layer); + + private: + ImageGrid* grid_; // not owned + + DISALLOW_COPY_AND_ASSIGN(TestAPI); + }; + + ImageGrid(); + ~ImageGrid(); + + ui::Layer* layer() { return layer_.get(); } + int top_image_height() const { return top_image_height_; } + int bottom_image_height() const { return bottom_image_height_; } + int left_image_width() const { return left_image_width_; } + int right_image_width() const { return right_image_width_; } + + // Initializes the grid to display the passed-in images (any of which can be + // NULL). Ownership of the images remains with the caller. + void Init(const gfx::Image* top_left_image, + const gfx::Image* top_image, + const gfx::Image* top_right_image, + const gfx::Image* left_image, + const gfx::Image* center_image, + const gfx::Image* right_image, + const gfx::Image* bottom_left_image, + const gfx::Image* bottom_image, + const gfx::Image* bottom_right_image); + + void SetSize(const gfx::Size& size); + + private: + // Delegate responsible for painting a specific image on a layer. + class ImagePainter : public ui::LayerDelegate { + public: + ImagePainter(const gfx::Image* image) : image_(image) {} + virtual ~ImagePainter() {} + + // ui::LayerDelegate implementation: + virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE; + + private: + const gfx::Image* image_; // not owned + + DISALLOW_COPY_AND_ASSIGN(ImagePainter); + }; + + // Returns the dimensions of |image| if non-NULL or gfx::Size(0, 0) otherwise. + static gfx::Size GetImageSize(const gfx::Image* image); + + // Initializes |layer_ptr| and |painter_ptr| to display |image|. + // Also adds the passed-in layer to |layer_|. + void InitImage(const gfx::Image* image, + scoped_ptr* layer_ptr, + scoped_ptr* painter_ptr); + + // Layer that contains all of the image layers. + scoped_ptr layer_; + + // The grid's dimensions. + gfx::Size size_; + + // Heights and widths of the images displayed by |top_layer_|, + // |bottom_layer_|, |left_layer_|, and |right_layer_|. + int top_image_height_; + int bottom_image_height_; + int left_image_width_; + int right_image_width_; + + // Heights of the tallest images in the top and bottom rows and the widest + // images in the left and right columns. + int top_row_height_; + int bottom_row_height_; + int left_column_width_; + int right_column_width_; + + // Layers used to display the various images. Children of |layer_|. + // Positions for which no images were supplied are NULL. + scoped_ptr top_left_layer_; + scoped_ptr top_layer_; + scoped_ptr top_right_layer_; + scoped_ptr left_layer_; + scoped_ptr center_layer_; + scoped_ptr right_layer_; + scoped_ptr bottom_left_layer_; + scoped_ptr bottom_layer_; + scoped_ptr bottom_right_layer_; + + // Delegates responsible for painting the above layers. + // Positions for which no images were supplied are NULL. + scoped_ptr top_left_painter_; + scoped_ptr top_painter_; + scoped_ptr top_right_painter_; + scoped_ptr left_painter_; + scoped_ptr center_painter_; + scoped_ptr right_painter_; + scoped_ptr bottom_left_painter_; + scoped_ptr bottom_painter_; + scoped_ptr bottom_right_painter_; + + DISALLOW_COPY_AND_ASSIGN(ImageGrid); +}; + +} // namespace internal +} // namespace aura_shell + +#endif // UI_AURA_SHELL_IMAGE_GRID_H_ diff --git a/ui/aura_shell/image_grid_unittest.cc b/ui/aura_shell/image_grid_unittest.cc new file mode 100644 index 0000000..e75bd403 --- /dev/null +++ b/ui/aura_shell/image_grid_unittest.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/aura_shell/image_grid.h" +#include "ui/aura_shell/test/aura_shell_test_base.h" +#include "ui/gfx/image/image.h" + +using aura_shell::internal::ImageGrid; + +namespace aura_shell { +namespace test { + +namespace { + +// Creates a gfx::Image with the requested dimensions. +gfx::Image* CreateImage(const gfx::Size& size) { + SkBitmap* bitmap = new SkBitmap(); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height()); + return new gfx::Image(bitmap); +} + +} // namespace + +typedef aura_shell::test::AuraShellTestBase ImageGridTest; + +// Test that an ImageGrid's layers are transformed correctly when SetSize() is +// called. +TEST_F(ImageGridTest, Basic) { + // Size of the images around the grid's border. + const int kBorder = 2; + + scoped_ptr image_1x1(CreateImage(gfx::Size(1, 1))); + scoped_ptr image_1xB(CreateImage(gfx::Size(1, kBorder))); + scoped_ptr image_Bx1(CreateImage(gfx::Size(kBorder, 1))); + scoped_ptr image_BxB(CreateImage(gfx::Size(kBorder, kBorder))); + + ImageGrid grid; + grid.Init(image_BxB.get(), image_1xB.get(), image_BxB.get(), + image_Bx1.get(), image_1x1.get(), image_Bx1.get(), + image_BxB.get(), image_1xB.get(), image_BxB.get()); + + ImageGrid::TestAPI test_api(&grid); + ASSERT_TRUE(test_api.top_left_layer() != NULL); + ASSERT_TRUE(test_api.top_layer() != NULL); + ASSERT_TRUE(test_api.top_right_layer() != NULL); + ASSERT_TRUE(test_api.left_layer() != NULL); + ASSERT_TRUE(test_api.center_layer() != NULL); + ASSERT_TRUE(test_api.right_layer() != NULL); + ASSERT_TRUE(test_api.bottom_left_layer() != NULL); + ASSERT_TRUE(test_api.bottom_layer() != NULL); + ASSERT_TRUE(test_api.bottom_right_layer() != NULL); + + const gfx::Size size(20, 30); + grid.SetSize(size); + + // The top-left layer should be flush with the top-left corner and unscaled. + EXPECT_EQ(gfx::Rect(0, 0, kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.top_left_layer()).ToString()); + + // The top layer should be flush with the top edge and stretched horizontally + // between the two top corners. + EXPECT_EQ(gfx::Rect( + kBorder, 0, size.width() - 2 * kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.top_layer()).ToString()); + + // The top-right layer should be flush with the top-right corner and unscaled. + EXPECT_EQ(gfx::Rect(size.width() - kBorder, 0, kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.top_right_layer()).ToString()); + + // The left layer should be flush with the left edge and stretched vertically + // between the two left corners. + EXPECT_EQ(gfx::Rect( + 0, kBorder, kBorder, size.height() - 2 * kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.left_layer()).ToString()); + + // The center layer should fill the space in the middle of the grid. + EXPECT_EQ(gfx::Rect( + kBorder, kBorder, size.width() - 2 * kBorder, + size.height() - 2 * kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.center_layer()).ToString()); + + // The right layer should be flush with the right edge and stretched + // vertically between the two right corners. + EXPECT_EQ(gfx::Rect( + size.width() - kBorder, kBorder, + kBorder, size.height() - 2 * kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.right_layer()).ToString()); + + // The bottom-left layer should be flush with the bottom-left corner and + // unscaled. + EXPECT_EQ(gfx::Rect(0, size.height() - kBorder, kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.bottom_left_layer()).ToString()); + + // The bottom layer should be flush with the bottom edge and stretched + // horizontally between the two bottom corners. + EXPECT_EQ(gfx::Rect( + kBorder, size.height() - kBorder, + size.width() - 2 * kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.bottom_layer()).ToString()); + + // The bottom-right layer should be flush with the bottom-right corner and + // unscaled. + EXPECT_EQ(gfx::Rect( + size.width() - kBorder, size.height() - kBorder, + kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.bottom_right_layer()).ToString()); +} + +// Check that we don't crash if only a single image is supplied. +TEST_F(ImageGridTest, SingleImage) { + const int kBorder = 1; + scoped_ptr image(CreateImage(gfx::Size(kBorder, kBorder))); + + ImageGrid grid; + grid.Init(NULL, image.get(), NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + + ImageGrid::TestAPI test_api(&grid); + EXPECT_TRUE(test_api.top_left_layer() == NULL); + ASSERT_TRUE(test_api.top_layer() != NULL); + EXPECT_TRUE(test_api.top_right_layer() == NULL); + EXPECT_TRUE(test_api.left_layer() == NULL); + EXPECT_TRUE(test_api.center_layer() == NULL); + EXPECT_TRUE(test_api.right_layer() == NULL); + EXPECT_TRUE(test_api.bottom_left_layer() == NULL); + EXPECT_TRUE(test_api.bottom_layer() == NULL); + EXPECT_TRUE(test_api.bottom_right_layer() == NULL); + + const gfx::Size kSize(10, 10); + grid.SetSize(kSize); + + // The top layer should be scaled horizontally across the entire width, but it + // shouldn't be scaled vertically. + EXPECT_EQ(gfx::Rect(0, 0, kSize.width(), kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.top_layer()).ToString()); +} + +// Test that side (top, left, right, bottom) layers that are narrower than their +// adjacent corner layers stay pinned to the outside edges instead of getting +// moved inwards or scaled. This exercises the scenario used for shadows. +TEST_F(ImageGridTest, SmallerSides) { + const int kCorner = 2; + const int kEdge = 1; + + scoped_ptr top_left_image( + CreateImage(gfx::Size(kCorner, kCorner))); + scoped_ptr top_image(CreateImage(gfx::Size(kEdge, kEdge))); + scoped_ptr top_right_image( + CreateImage(gfx::Size(kCorner, kCorner))); + scoped_ptr left_image(CreateImage(gfx::Size(kEdge, kEdge))); + scoped_ptr right_image(CreateImage(gfx::Size(kEdge, kEdge))); + + ImageGrid grid; + grid.Init(top_left_image.get(), top_image.get(), top_right_image.get(), + left_image.get(), NULL, right_image.get(), + NULL, NULL, NULL); + ImageGrid::TestAPI test_api(&grid); + + const gfx::Size kSize(20, 30); + grid.SetSize(kSize); + + // The top layer should be flush with the top edge and stretched horizontally + // between the two top corners. + EXPECT_EQ(gfx::Rect( + kCorner, 0, kSize.width() - 2 * kCorner, kEdge).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.top_layer()).ToString()); + + + // The left layer should be flush with the left edge and stretched vertically + // between the top left corner and the bottom. + EXPECT_EQ(gfx::Rect( + 0, kCorner, kEdge, kSize.height() - kCorner).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.left_layer()).ToString()); + + // The right layer should be flush with the right edge and stretched + // vertically between the top right corner and the bottom. + EXPECT_EQ(gfx::Rect( + kSize.width() - kEdge, kCorner, + kEdge, kSize.height() - kCorner).ToString(), + test_api.GetTransformedLayerBounds( + *test_api.right_layer()).ToString()); +} + +} // namespace test +} // namespace aura_shell diff --git a/ui/aura_shell/shadow.cc b/ui/aura_shell/shadow.cc new file mode 100644 index 0000000..2963dd5 --- /dev/null +++ b/ui/aura_shell/shadow.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2011 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/aura_shell/shadow.h" + +#include "grit/ui_resources.h" +#include "ui/aura_shell/image_grid.h" +#include "ui/base/resource/resource_bundle.h" + +namespace aura_shell { +namespace internal { + +Shadow::Shadow() { +} + +Shadow::~Shadow() { +} + +ui::Layer* Shadow::layer() const { return image_grid_->layer(); } + +void Shadow::Init() { + image_grid_.reset(new ImageGrid); + + ResourceBundle& res = ResourceBundle::GetSharedInstance(); + image_grid_->Init(&res.GetImageNamed(IDR_AURA_SHADOW_RECT_TOP_LEFT), + &res.GetImageNamed(IDR_AURA_SHADOW_RECT_TOP), + &res.GetImageNamed(IDR_AURA_SHADOW_RECT_TOP_RIGHT), + &res.GetImageNamed(IDR_AURA_SHADOW_RECT_LEFT), + NULL, + &res.GetImageNamed(IDR_AURA_SHADOW_RECT_RIGHT), + &res.GetImageNamed(IDR_AURA_SHADOW_RECT_BOTTOM_LEFT), + &res.GetImageNamed(IDR_AURA_SHADOW_RECT_BOTTOM), + &res.GetImageNamed(IDR_AURA_SHADOW_RECT_BOTTOM_RIGHT)); +} + +void Shadow::SetContentBounds(const gfx::Rect& content_bounds) { + content_bounds_ = content_bounds; + image_grid_->SetSize( + gfx::Size(content_bounds.width() + + image_grid_->left_image_width() + + image_grid_->right_image_width(), + content_bounds.height() + + image_grid_->top_image_height() + + image_grid_->bottom_image_height())); + image_grid_->layer()->SetBounds( + gfx::Rect(content_bounds.x() - image_grid_->left_image_width(), + content_bounds.y() - image_grid_->top_image_height(), + image_grid_->layer()->bounds().width(), + image_grid_->layer()->bounds().height())); +} + +} // namespace internal +} // namespace aura_shell diff --git a/ui/aura_shell/shadow.h b/ui/aura_shell/shadow.h new file mode 100644 index 0000000..0fea19a --- /dev/null +++ b/ui/aura_shell/shadow.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011 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. + +#ifndef UI_AURA_SHELL_SHADOW_H_ +#define UI_AURA_SHELL_SHADOW_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "ui/gfx/rect.h" + +namespace ui { +class Layer; +} // namespace ui + +namespace aura_shell { +namespace internal { + +class ImageGrid; + +// Simple class that draws a drop shadow around content at given bounds. +class Shadow { + public: + Shadow(); + ~Shadow(); + + // Returns |image_grid_|'s ui::Layer. This is exposed so it can be added to + // the same layer as the content and stacked below it. SetContentBounds() + // should be used to adjust the shadow's size and position (rather than + // applying transformations to this layer). + ui::Layer* layer() const; + + const gfx::Rect& content_bounds() const { return content_bounds_; } + + void Init(); + + // Moves and resizes |image_grid_| to frame |content_bounds|. + void SetContentBounds(const gfx::Rect& content_bounds); + + private: + scoped_ptr image_grid_; + + // Bounds of the content that the shadow encloses. + gfx::Rect content_bounds_; + + DISALLOW_COPY_AND_ASSIGN(Shadow); +}; + +} // namespace internal +} // namespace aura_shell + +#endif // UI_AURA_SHELL_SHADOW_H_ diff --git a/ui/aura_shell/shadow_controller.cc b/ui/aura_shell/shadow_controller.cc new file mode 100644 index 0000000..e2f2d80 --- /dev/null +++ b/ui/aura_shell/shadow_controller.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2011 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/aura_shell/shadow_controller.h" + +#include + +#include "base/logging.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/client/shadow_types.h" +#include "ui/aura/desktop.h" +#include "ui/aura/window.h" +#include "ui/aura_shell/shadow.h" + +using std::make_pair; + +namespace aura_shell { +namespace internal { + +ShadowController::ShadowController() { + aura::Desktop::GetInstance()->AddObserver(this); +} + +ShadowController::~ShadowController() { + for (WindowShadowMap::const_iterator it = window_shadows_.begin(); + it != window_shadows_.end(); ++it) { + it->first->RemoveObserver(this); + } + aura::Desktop::GetInstance()->RemoveObserver(this); +} + +void ShadowController::OnWindowInitialized(aura::Window* window) { + window->AddObserver(this); +} + +void ShadowController::OnWindowParentChanged(aura::Window* window, + aura::Window* parent) { + Shadow* shadow = GetShadowForWindow(window); + + if (parent) { + if (shadow) { + parent->layer()->Add(shadow->layer()); + StackShadowBelowWindow(shadow, window); + } else { + if (ShouldShowShadowForWindow(window)) + CreateShadowForWindow(window); + } + } else { + if (shadow && shadow->layer()->parent()) + shadow->layer()->parent()->Remove(shadow->layer()); + } +} + +void ShadowController::OnPropertyChanged(aura::Window* window, + const char* name, + void* old) { + if (name == aura::kShadowTypeKey) + HandlePossibleShadowVisibilityChange(window); +} + +void ShadowController::OnWindowVisibilityChanged(aura::Window* window, + bool visible) { + HandlePossibleShadowVisibilityChange(window); +} + +void ShadowController::OnWindowBoundsChanged(aura::Window* window, + const gfx::Rect& bounds) { + Shadow* shadow = GetShadowForWindow(window); + if (shadow) + shadow->SetContentBounds(bounds); +} + +void ShadowController::OnWindowStackingChanged(aura::Window* window) { + Shadow* shadow = GetShadowForWindow(window); + if (shadow) + StackShadowBelowWindow(shadow, window); +} + +void ShadowController::OnWindowDestroyed(aura::Window* window) { + window_shadows_.erase(window); +} + +bool ShadowController::ShouldShowShadowForWindow(aura::Window* window) const { + const aura::ShadowType type = static_cast( + window->GetIntProperty(aura::kShadowTypeKey)); + bool requested = false; + switch (type) { + case aura::SHADOW_TYPE_NONE: + break; + case aura::SHADOW_TYPE_RECTANGULAR: + requested = true; + break; + default: + NOTREACHED() << "Unknown shadow type " << type; + } + + return requested && window->layer()->visible(); +} + +Shadow* ShadowController::GetShadowForWindow(aura::Window* window) { + WindowShadowMap::const_iterator it = window_shadows_.find(window); + return it != window_shadows_.end() ? it->second.get() : NULL; +} + +void ShadowController::HandlePossibleShadowVisibilityChange( + aura::Window* window) { + const bool should_show = ShouldShowShadowForWindow(window); + Shadow* shadow = GetShadowForWindow(window); + if (shadow) + shadow->layer()->SetVisible(should_show); + else if (should_show && !shadow) + CreateShadowForWindow(window); +} + +void ShadowController::CreateShadowForWindow(aura::Window* window) { + linked_ptr shadow(new Shadow()); + window_shadows_.insert(make_pair(window, shadow)); + + shadow->Init(); + shadow->SetContentBounds(window->bounds()); + shadow->layer()->SetVisible(ShouldShowShadowForWindow(window)); + + if (window->parent()) { + window->parent()->layer()->Add(shadow->layer()); + StackShadowBelowWindow(shadow.get(), window); + } +} + +void ShadowController::StackShadowBelowWindow(Shadow* shadow, + aura::Window* window) { + ui::Layer* parent_layer = window->parent()->layer(); + DCHECK_EQ(shadow->layer()->parent(), parent_layer); + + // TODO(derat): Add a MoveBelow() method and use that instead (although we + // then run the risk of other layers getting stacked between a window and its + // shadow). + parent_layer->MoveAbove(shadow->layer(), window->layer()); + parent_layer->MoveAbove(window->layer(), shadow->layer()); +} + +} // namespace internal +} // namespace aura_shell diff --git a/ui/aura_shell/shadow_controller.h b/ui/aura_shell/shadow_controller.h new file mode 100644 index 0000000..19fbfad --- /dev/null +++ b/ui/aura_shell/shadow_controller.h @@ -0,0 +1,96 @@ +// Copyright (c) 2011 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. + +#ifndef UI_AURA_SHELL_SHADOW_CONTROLLER_H_ +#define UI_AURA_SHELL_SHADOW_CONTROLLER_H_ +#pragma once + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/linked_ptr.h" +#include "ui/aura/desktop_observer.h" +#include "ui/aura/window_observer.h" + +namespace aura { +class Window; +} +namespace gfx { +class Rect; +} + +namespace aura_shell { +namespace internal { + +class Shadow; + +// ShadowController observes changes to windows and creates and updates drop +// shadows as needed. +class ShadowController : public aura::DesktopObserver, + public aura::WindowObserver { +public: + class TestApi { + public: + explicit TestApi(ShadowController* controller) : controller_(controller) {} + ~TestApi() {} + + Shadow* GetShadowForWindow(aura::Window* window) { + return controller_->GetShadowForWindow(window); + } + + private: + ShadowController* controller_; // not owned + + DISALLOW_COPY_AND_ASSIGN(TestApi); + }; + + explicit ShadowController(); + virtual ~ShadowController(); + + // aura::DesktopObserver override: + virtual void OnWindowInitialized(aura::Window* window) OVERRIDE; + + // aura::WindowObserver overrides: + virtual void OnWindowParentChanged( + aura::Window* window, aura::Window* parent) OVERRIDE; + virtual void OnPropertyChanged( + aura::Window* window, const char* name, void* old) OVERRIDE; + virtual void OnWindowVisibilityChanged( + aura::Window* window, bool visible) OVERRIDE; + virtual void OnWindowBoundsChanged( + aura::Window* window, const gfx::Rect& bounds) OVERRIDE; + virtual void OnWindowStackingChanged(aura::Window* window) OVERRIDE; + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + + private: + typedef std::map > WindowShadowMap; + + // Checks if |window| is visible and contains a property requesting a shadow. + bool ShouldShowShadowForWindow(aura::Window* window) const; + + // Returns |window|'s shadow from |window_shadows_|, or NULL if no shadow + // exists. + Shadow* GetShadowForWindow(aura::Window* window); + + // Shows or hides |window|'s shadow as needed (creating the shadow if + // necessary). + void HandlePossibleShadowVisibilityChange(aura::Window* window); + + // Creates a new shadow for |window| and stores it in |window_shadows_|. The + // shadow's visibility, bounds, and stacking are initialized appropriately. + void CreateShadowForWindow(aura::Window* window); + + // Stacks |shadow|'s layer directly beneath |window|'s layer. + void StackShadowBelowWindow(Shadow* shadow, aura::Window* window); + + WindowShadowMap window_shadows_; + + DISALLOW_COPY_AND_ASSIGN(ShadowController); +}; + +} // namepsace aura_shell +} // namepsace internal + +#endif // UI_AURA_SHELL_SHADOW_CONTROLLER_H_ diff --git a/ui/aura_shell/shadow_controller_unittest.cc b/ui/aura_shell/shadow_controller_unittest.cc new file mode 100644 index 0000000..523543f --- /dev/null +++ b/ui/aura_shell/shadow_controller_unittest.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2011 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 "base/memory/scoped_ptr.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/client/shadow_types.h" +#include "ui/aura/desktop.h" +#include "ui/aura/window.h" +#include "ui/aura_shell/shadow.h" +#include "ui/aura_shell/shadow_controller.h" +#include "ui/aura_shell/shell.h" +#include "ui/aura_shell/test/aura_shell_test_base.h" + +namespace aura_shell { +namespace test { + +typedef aura_shell::test::AuraShellTestBase ShadowControllerTest; + +// Tests that various methods in Window update the Shadow object as expected. +TEST_F(ShadowControllerTest, Shadow) { + scoped_ptr window(new aura::Window(NULL)); + window->SetType(aura::WINDOW_TYPE_NORMAL); + window->SetIntProperty(aura::kShadowTypeKey, aura::SHADOW_TYPE_RECTANGULAR); + window->Init(ui::Layer::LAYER_HAS_TEXTURE); + window->SetParent(NULL); + + // We shouldn't create the shadow before the window is visible. + internal::ShadowController::TestApi api( + aura_shell::Shell::GetInstance()->shadow_controller()); + EXPECT_TRUE(api.GetShadowForWindow(window.get()) == NULL); + + // The shadow's visibility should be updated along with the window's. + window->Show(); + const internal::Shadow* shadow = api.GetShadowForWindow(window.get()); + ASSERT_TRUE(shadow != NULL); + EXPECT_TRUE(shadow->layer()->visible()); + window->Hide(); + EXPECT_FALSE(shadow->layer()->visible()); + + // If the shadow is disabled, it shouldn't be shown even when the window is. + window->SetIntProperty(aura::kShadowTypeKey, aura::SHADOW_TYPE_NONE); + window->Show(); + EXPECT_FALSE(shadow->layer()->visible()); + window->SetIntProperty(aura::kShadowTypeKey, aura::SHADOW_TYPE_RECTANGULAR); + EXPECT_TRUE(shadow->layer()->visible()); + + // The shadow's layer should have the same parent as the window's. + EXPECT_EQ(window->parent()->layer(), shadow->layer()->parent()); + + // TODO(derat): Test stacking (after adding additional methods to ui::Layer so + // that stacking order can be queried). + + // When we remove the window from the hierarchy, its shadow should be removed + // too. + window->parent()->RemoveChild(window.get()); + EXPECT_TRUE(shadow->layer()->parent() == NULL); + + aura::Window* window_ptr = window.get(); + window.reset(); + EXPECT_TRUE(api.GetShadowForWindow(window_ptr) == NULL); +} + +// Tests that the window's shadow's bounds are updated correctly. +TEST_F(ShadowControllerTest, ShadowBounds) { + scoped_ptr window(new aura::Window(NULL)); + window->SetType(aura::WINDOW_TYPE_NORMAL); + window->Init(ui::Layer::LAYER_HAS_TEXTURE); + window->SetParent(NULL); + window->Show(); + + const gfx::Rect kOldBounds(20, 30, 400, 300); + window->SetBounds(kOldBounds); + + // When the shadow is first created, it should use the window's bounds. + window->SetIntProperty(aura::kShadowTypeKey, aura::SHADOW_TYPE_RECTANGULAR); + internal::ShadowController::TestApi api( + aura_shell::Shell::GetInstance()->shadow_controller()); + const internal::Shadow* shadow = api.GetShadowForWindow(window.get()); + ASSERT_TRUE(shadow != NULL); + EXPECT_EQ(kOldBounds, shadow->content_bounds()); + + // When we change the window's bounds, the shadow's should be updated too. + gfx::Rect kNewBounds(50, 60, 500, 400); + window->SetBounds(kNewBounds); + EXPECT_EQ(kNewBounds, shadow->content_bounds()); +} + +} // namespace test +} // namespace aura_shell diff --git a/ui/aura_shell/shell.cc b/ui/aura_shell/shell.cc index b439de1..8a63f21 100644 --- a/ui/aura_shell/shell.cc +++ b/ui/aura_shell/shell.cc @@ -19,6 +19,7 @@ #include "ui/aura_shell/drag_drop_controller.h" #include "ui/aura_shell/launcher/launcher.h" #include "ui/aura_shell/modal_container_layout_manager.h" +#include "ui/aura_shell/shadow_controller.h" #include "ui/aura_shell/shelf_layout_controller.h" #include "ui/aura_shell/shell_delegate.h" #include "ui/aura_shell/shell_factory.h" @@ -172,9 +173,11 @@ void Shell::Init() { shelf_layout_controller_.reset(new internal::ShelfLayoutController( launcher_->widget(), status_widget)); - desktop_layout->set_shelf(shelf_layout_controller_.get()); + if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kAuraNoShadows)) + shadow_controller_.reset(new internal::ShadowController()); + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAuraWindows)) { EnableWorkspaceManager(); } else { @@ -187,7 +190,6 @@ void Shell::Init() { // Force a layout. desktop_layout->OnWindowResized(); - // Initialize drag drop controller. drag_drop_controller_.reset(new internal::DragDropController); aura::Desktop::GetInstance()->SetProperty(aura::kDesktopDragDropClientKey, static_cast(drag_drop_controller_.get())); diff --git a/ui/aura_shell/shell.h b/ui/aura_shell/shell.h index fd0d596..3f4e3ac 100644 --- a/ui/aura_shell/shell.h +++ b/ui/aura_shell/shell.h @@ -31,6 +31,7 @@ class ShellDelegate; namespace internal { class DragDropController; +class ShadowController; class ShelfLayoutController; class WorkspaceController; } @@ -64,6 +65,11 @@ class AURA_SHELL_EXPORT Shell { ShellDelegate* delegate() { return delegate_.get(); } Launcher* launcher() { return launcher_.get(); } + // Made available for tests. + internal::ShadowController* shadow_controller() { + return shadow_controller_.get(); + } + private: typedef std::pair WindowAndBoundsPair; @@ -88,6 +94,7 @@ class AURA_SHELL_EXPORT Shell { scoped_ptr drag_drop_controller_; scoped_ptr workspace_controller_; scoped_ptr shelf_layout_controller_; + scoped_ptr shadow_controller_; DISALLOW_COPY_AND_ASSIGN(Shell); }; diff --git a/ui/aura_shell/show_state_controller.h b/ui/aura_shell/show_state_controller.h index 45b2855..02087de 100644 --- a/ui/aura_shell/show_state_controller.h +++ b/ui/aura_shell/show_state_controller.h @@ -26,7 +26,7 @@ public: explicit ShowStateController(WorkspaceManager* layout_manager); virtual ~ShowStateController(); - // Invoked when window proparty has changed. + // aura::WindowObserver overrides: virtual void OnPropertyChanged(aura::Window* window, const char* name, void* old) OVERRIDE; -- cgit v1.1