diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-20 15:34:18 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-20 15:34:18 +0000 |
commit | f94b88f6871d30b7df821c1cab44da45b6c32e68 (patch) | |
tree | 368f8c243cea7063c274a768eeccceb4537f1767 | |
parent | b43c8b043e770f80abb558860c8fa508d45532d2 (diff) | |
download | chromium_src-f94b88f6871d30b7df821c1cab44da45b6c32e68.zip chromium_src-f94b88f6871d30b7df821c1cab44da45b6c32e68.tar.gz chromium_src-f94b88f6871d30b7df821c1cab44da45b6c32e68.tar.bz2 |
Adds simple animation to the launcher when items are added/removed.
BUG=98345
TEST=none
R=ben@chromium.org
Review URL: http://codereview.chromium.org/8355026
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@106500 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ui/aura_shell/aura_shell.gyp | 10 | ||||
-rw-r--r-- | ui/aura_shell/launcher/launcher_model_unittest.cc | 2 | ||||
-rw-r--r-- | ui/aura_shell/launcher/launcher_view.cc | 165 | ||||
-rw-r--r-- | ui/aura_shell/launcher/launcher_view.h | 31 | ||||
-rwxr-xr-x | ui/aura_shell/launcher/view_model.cc | 52 | ||||
-rwxr-xr-x | ui/aura_shell/launcher/view_model.h | 81 | ||||
-rw-r--r-- | ui/aura_shell/launcher/view_model_unittest.cc | 26 |
7 files changed, 315 insertions, 52 deletions
diff --git a/ui/aura_shell/aura_shell.gyp b/ui/aura_shell/aura_shell.gyp index 338992f..637534b 100644 --- a/ui/aura_shell/aura_shell.gyp +++ b/ui/aura_shell/aura_shell.gyp @@ -47,6 +47,8 @@ 'launcher/launcher_view.h', 'launcher/tabbed_launcher_button.cc', 'launcher/tabbed_launcher_button.h', + 'launcher/view_model.cc', + 'launcher/view_model.h', 'shell.cc', 'shell.h', 'shell_delegate.h', @@ -79,6 +81,7 @@ ], 'sources': [ 'launcher/launcher_model_unittest.cc', + 'launcher/view_model_unittest.cc', 'run_all_unittests.cc', 'test_suite.cc', 'test_suite.h', @@ -87,6 +90,13 @@ '<(SHARED_INTERMEDIATE_DIR)/ui/ui_resources/ui_resources.rc', '<(SHARED_INTERMEDIATE_DIR)/ui/ui_resources_standard/ui_resources_standard.rc', ], + 'conditions': [ + ['OS!="mac"', { + 'dependencies': [ + '../../chrome/chrome.gyp:packed_resources', + ], + }], + ], }, { 'target_name': 'aura_shell_exe', diff --git a/ui/aura_shell/launcher/launcher_model_unittest.cc b/ui/aura_shell/launcher/launcher_model_unittest.cc index 0c10e13..7e44356 100644 --- a/ui/aura_shell/launcher/launcher_model_unittest.cc +++ b/ui/aura_shell/launcher/launcher_model_unittest.cc @@ -52,7 +52,7 @@ class TestLauncherModelObserver : public LauncherModelObserver { } // namespace -TEST(TestLauncher, BasicAssertions) { +TEST(LauncherModel, BasicAssertions) { TestLauncherModelObserver observer; LauncherModel model; // Add an item. diff --git a/ui/aura_shell/launcher/launcher_view.cc b/ui/aura_shell/launcher/launcher_view.cc index 7380c34..3bbccc6 100644 --- a/ui/aura_shell/launcher/launcher_view.cc +++ b/ui/aura_shell/launcher/launcher_view.cc @@ -8,10 +8,12 @@ #include "grit/ui_resources.h" #include "ui/aura_shell/launcher/launcher_model.h" #include "ui/aura_shell/launcher/tabbed_launcher_button.h" +#include "ui/aura_shell/launcher/view_model.h" #include "ui/aura_shell/shell.h" #include "ui/aura_shell/shell_delegate.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image.h" +#include "views/animation/bounds_animator.h" #include "views/controls/button/image_button.h" #include "views/widget/widget.h" @@ -30,11 +32,32 @@ static const int kLeadingInset = 8; // added/removed. static const int kPreferredHeight = 48; +namespace { + +// AnimationDelegate that deletes a view when done. This is used when a launcher +// item is removed, which triggers a remove animation. When the animation is +// done we delete the view. +class DeleteViewAnimationDelegate : + public views::BoundsAnimator::OwnedAnimationDelegate { + public: + DeleteViewAnimationDelegate(views::View* view) : view_(view) {} + virtual ~DeleteViewAnimationDelegate() {} + + private: + scoped_ptr<views::View> view_; + + DISALLOW_COPY_AND_ASSIGN(DeleteViewAnimationDelegate); +}; + +} // namespace + LauncherView::LauncherView(LauncherModel* model) : model_(model), + view_model_(new ViewModel), new_browser_button_(NULL), show_apps_button_(NULL) { DCHECK(model_); + bounds_animator_.reset(new views::BoundsAnimator(this)); } LauncherView::~LauncherView() { @@ -51,14 +74,65 @@ void LauncherView::Init() { AddChildView(new_browser_button_); const LauncherItems& items(model_->items()); - for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) - AddChildView(CreateViewForItem(*i)); + for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) { + views::View* child = CreateViewForItem(*i); + view_model_->Add(child, static_cast<int>(i - items.begin())); + AddChildView(child); + } show_apps_button_ = new views::ImageButton(this); show_apps_button_->SetImage( views::CustomButton::BS_NORMAL, rb.GetImageNamed(IDR_AURA_LAUNCHER_ICON_APPLIST).ToSkBitmap()); AddChildView(show_apps_button_); + + LayoutToIdealBounds(); +} + +void LauncherView::LayoutToIdealBounds() { + IdealBounds ideal_bounds; + CalculateIdealBounds(&ideal_bounds); + new_browser_button_->SetBoundsRect(ideal_bounds.new_browser_bounds); + show_apps_button_->SetBoundsRect(ideal_bounds.show_apps_bounds); + view_model_->SetViewBoundsToIdealBounds(); +} + +void LauncherView::CalculateIdealBounds(IdealBounds* bounds) { + // new_browser_button first. + int x = kLeadingInset; + gfx::Size pref = new_browser_button_->GetPreferredSize(); + bounds->new_browser_bounds = gfx::Rect( + x, (kPreferredHeight - pref.height()) / 2, pref.width(), pref.height()); + x += bounds->new_browser_bounds.width() + kHorizontalPadding; + + // Then launcher buttons. + for (int i = 0; i < view_model_->view_size(); ++i) { + pref = view_model_->view_at(i)->GetPreferredSize(); + view_model_->set_ideal_bounds(i, gfx::Rect( + x, (kPreferredHeight - pref.height()) / 2, pref.width(), + pref.height())); + x += pref.width() + kHorizontalPadding; + } + + // And the show_apps_button. + pref = show_apps_button_->GetPreferredSize(); + // TODO(sky): -8 is a hack, remove when we get better images. + bounds->show_apps_bounds = gfx::Rect( + x - 8, (kPreferredHeight - pref.width()) / 2, pref.width(), + pref.height()); +} + +void LauncherView::AnimateToIdealBounds() { + IdealBounds ideal_bounds; + CalculateIdealBounds(&ideal_bounds); + bounds_animator_->AnimateViewTo(new_browser_button_, + ideal_bounds.new_browser_bounds); + for (int i = 0; i < view_model_->view_size(); ++i) { + bounds_animator_->AnimateViewTo(view_model_->view_at(i), + view_model_->ideal_bounds(i)); + } + bounds_animator_->AnimateViewTo(show_apps_button_, + ideal_bounds.show_apps_bounds); } views::View* LauncherView::CreateViewForItem(const LauncherItem& item) { @@ -79,69 +153,66 @@ views::View* LauncherView::CreateViewForItem(const LauncherItem& item) { } void LauncherView::Resize() { + // TODO: we may want to force the width to a specific size. int y = GetWidget()->GetClientAreaScreenBounds().y(); gfx::Size pref(GetPreferredSize()); GetWidget()->SetBounds(gfx::Rect(0, y, pref.width(), pref.height())); Layout(); } -void LauncherView::Layout() { - int x = kLeadingInset; - for (int i = 0; i < child_count(); ++i) { - View* child = child_at(i); - if (child->IsVisible()) { - gfx::Size pref_size = child->GetPreferredSize(); - int y = (height() - pref_size.height()) / 2; - child->SetBounds(x, y, pref_size.width(), pref_size.height()); - x += child->width() + kHorizontalPadding; - } - } - // TODO: remove this when we get a better image. - show_apps_button_->SetX(show_apps_button_->x() - 8); -} - gfx::Size LauncherView::GetPreferredSize() { - int x = kLeadingInset; - for (int i = 0; i < child_count(); ++i) { - View* child = child_at(i); - if (child->IsVisible()) { - gfx::Size pref_size = child->GetPreferredSize(); - x += pref_size.width() + kHorizontalPadding; - } - } - // TODO: remove this when we get a better image. - x -= 10; - return gfx::Size(x, kPreferredHeight); + IdealBounds ideal_bounds; + CalculateIdealBounds(&ideal_bounds); + return gfx::Size(ideal_bounds.show_apps_bounds.right() + kLeadingInset, + kPreferredHeight); } -void LauncherView::LauncherItemAdded(int index) { - // TODO: to support animations is going to require coordinate conversions. - AddChildViewAt(CreateViewForItem(model_->items()[index]), index + 1); +void LauncherView::LauncherItemAdded(int model_index) { + views::View* view = CreateViewForItem(model_->items()[model_index]); + AddChildView(view); + view_model_->Add(view, model_index); + + // Update the bounds and reset the bounds of the newly created view to 0 width + // so that it appears to animate open. + IdealBounds ideal_bounds; + CalculateIdealBounds(&ideal_bounds); + gfx::Rect bounds = view_model_->ideal_bounds(model_index); + bounds.set_width(0); + view->SetBoundsRect(bounds); + + // Resize and animate all the views. Resize(); + AnimateToIdealBounds(); } -void LauncherView::LauncherItemRemoved(int index) { - // TODO: to support animations is going to require coordinate conversions. - RemoveChildView(child_at(index + 1)); +void LauncherView::LauncherItemRemoved(int model_index) { + views::View* view = view_model_->view_at(model_index); + view_model_->Remove(model_index); Resize(); + AnimateToIdealBounds(); + gfx::Rect target_bounds = view->bounds(); + target_bounds.set_width(0); + bounds_animator_->AnimateViewTo(view, target_bounds); + bounds_animator_->SetAnimationDelegate( + view, new DeleteViewAnimationDelegate(view), true); } -void LauncherView::LauncherItemImagesChanged(int index) { - // TODO: implement better coordinate conversion. - const LauncherItem& item(model_->items()[index]); +void LauncherView::LauncherItemImagesChanged(int model_index) { + const LauncherItem& item(model_->items()[model_index]); + views::View* view = view_model_->view_at(model_index); if (item.type == TYPE_TABBED) { - TabbedLauncherButton* button = - static_cast<TabbedLauncherButton*>(child_at(index + 1)); + TabbedLauncherButton* button = static_cast<TabbedLauncherButton*>(view); gfx::Size pref = button->GetPreferredSize(); button->SetImages(item.tab_images); - if (pref != button->GetPreferredSize()) + if (pref != button->GetPreferredSize()) { Resize(); - else + AnimateToIdealBounds(); + } else { button->SchedulePaint(); + } } else { DCHECK_EQ(TYPE_APP, item.type); - views::ImageButton* button = - static_cast<views::ImageButton*>(child_at(index + 1)); + views::ImageButton* button = static_cast<views::ImageButton*>(view); button->SetImage(views::CustomButton::BS_NORMAL, &item.app_image); button->SchedulePaint(); } @@ -157,10 +228,10 @@ void LauncherView::ButtonPressed(views::Button* sender, } else if (sender == show_apps_button_) { delegate->ShowApps(); } else { - int index = GetIndexOf(sender); - DCHECK_NE(-1, index); - // TODO: animations will require coordinate transforms. - delegate->LauncherItemClicked(model_->items()[index - 1]); + int view_index = view_model_->GetIndexOfView(sender); + // May be -1 while in the process of animating closed. + if (view_index != -1) + delegate->LauncherItemClicked(model_->items()[view_index]); } } diff --git a/ui/aura_shell/launcher/launcher_view.h b/ui/aura_shell/launcher/launcher_view.h index 65b2696..1c25e7f 100644 --- a/ui/aura_shell/launcher/launcher_view.h +++ b/ui/aura_shell/launcher/launcher_view.h @@ -11,6 +11,7 @@ #include "views/widget/widget_delegate.h" namespace views { +class BoundsAnimator; class ImageButton; } @@ -18,6 +19,7 @@ namespace aura_shell { struct LauncherItem; class LauncherModel; +class ViewModel; namespace internal { @@ -31,6 +33,22 @@ class LauncherView : public views::WidgetDelegateView, void Init(); private: + struct IdealBounds { + gfx::Rect new_browser_bounds; + gfx::Rect show_apps_bounds; + }; + + // Sets the bounds of each view to its ideal bounds. + void LayoutToIdealBounds(); + + // Calculates the ideal bounds. The bounds of each button corresponding to an + // item in the model is set in |view_model_|, the bounds of the + // |new_browser_button_| and |show_apps_button_| is set in |bounds|. + void CalculateIdealBounds(IdealBounds* bounds); + + // Animates the bounds of each view to its ideal bounds. + void AnimateToIdealBounds(); + // Creates the view used to represent |item|. views::View* CreateViewForItem(const LauncherItem& item); @@ -38,13 +56,12 @@ class LauncherView : public views::WidgetDelegateView, void Resize(); // Overridden from views::View: - virtual void Layout() OVERRIDE; virtual gfx::Size GetPreferredSize() OVERRIDE; // Overridden from LauncherModelObserver: - virtual void LauncherItemAdded(int index) OVERRIDE; - virtual void LauncherItemRemoved(int index) OVERRIDE; - virtual void LauncherItemImagesChanged(int index) OVERRIDE; + virtual void LauncherItemAdded(int model_index) OVERRIDE; + virtual void LauncherItemRemoved(int model_index) OVERRIDE; + virtual void LauncherItemImagesChanged(int model_index) OVERRIDE; // Overriden from views::ButtonListener: virtual void ButtonPressed(views::Button* sender, @@ -53,6 +70,12 @@ class LauncherView : public views::WidgetDelegateView, // The model; owned by Launcher. LauncherModel* model_; + // Used to manage the set of active launcher buttons. There is a view per + // item in |model_|. + scoped_ptr<ViewModel> view_model_; + + scoped_ptr<views::BoundsAnimator> bounds_animator_; + views::ImageButton* new_browser_button_; views::ImageButton* show_apps_button_; diff --git a/ui/aura_shell/launcher/view_model.cc b/ui/aura_shell/launcher/view_model.cc new file mode 100755 index 0000000..d54cbf3 --- /dev/null +++ b/ui/aura_shell/launcher/view_model.cc @@ -0,0 +1,52 @@ +// 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/launcher/view_model.h" + +#include "base/logging.h" +#include "views/view.h" + +namespace aura_shell { + +ViewModel::ViewModel() { +} + +ViewModel::~ViewModel() { + // view are owned by their parent, no need to delete them. +} + +void ViewModel::Add(views::View* view, int index) { + Entry entry; + entry.view = view; + entries_.insert(entries_.begin() + index, entry); +} + +void ViewModel::Remove(int index) { + if (index == -1) + return; + + entries_.erase(entries_.begin() + index); +} + +void ViewModel::Clear() { + Entries entries; + entries.swap(entries_); + for (size_t i = 0; i < entries.size(); ++i) + delete entries[i].view; +} + +void ViewModel::SetViewBoundsToIdealBounds() { + for (size_t i = 0; i < entries_.size(); ++i) + entries_[i].view->SetBoundsRect(entries_[i].ideal_bounds); +} + +int ViewModel::GetIndexOfView(views::View* view) { + for (size_t i = 0; i < entries_.size(); ++i) { + if (entries_[i].view == view) + return static_cast<int>(i); + } + return -1; +} + +} // namespace aura_shell diff --git a/ui/aura_shell/launcher/view_model.h b/ui/aura_shell/launcher/view_model.h new file mode 100755 index 0000000..64e46c03 --- /dev/null +++ b/ui/aura_shell/launcher/view_model.h @@ -0,0 +1,81 @@ +// 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_LAUNCHER_VIEW_MODEL_H_ +#define UI_AURA_SHELL_LAUNCHER_VIEW_MODEL_H_ +#pragma once + +#include <vector> + +#include "base/basictypes.h" +#include "ui/aura_shell/aura_shell_export.h" +#include "ui/gfx/rect.h" + +namespace views { +class View; +} + +namespace aura_shell { + +// ViewModel is used to track an 'interesting' set of a views. Often times +// during animations views are removed after a delay, which makes for tricky +// coordinate conversion as you have to account for the possibility of the +// indices from the model not lining up with those you expect. This class lets +// you define the 'interesting' views and operate on those views. +class AURA_SHELL_EXPORT ViewModel { + public: + ViewModel(); + ~ViewModel(); + + // Adds |view| to this model. This does not add |view| to a view hierarchy, + // only to this model. + void Add(views::View* view, int index); + + // Removes the view at the specified index. This does not actually remove the + // view from the view hierarchy. + void Remove(int index); + + // Returns the number of views. + int view_size() const { return static_cast<int>(entries_.size()); } + + // Removes and deletes all the views. + void Clear(); + + // Returns the view at the specified index. + views::View* view_at(int index) { + return entries_[index].view; + } + + void set_ideal_bounds(int index, const gfx::Rect& bounds) { + entries_[index].ideal_bounds = bounds; + } + + const gfx::Rect& ideal_bounds(int index) const { + return entries_[index].ideal_bounds; + } + + // Sets the bounds of each view to its ideal bounds. + void SetViewBoundsToIdealBounds(); + + // Returns the index of the specified view, or -1 if the view isn't in the + // model. + int GetIndexOfView(views::View* view); + + private: + struct Entry { + Entry() : view(NULL) {} + + views::View* view; + gfx::Rect ideal_bounds; + }; + typedef std::vector<Entry> Entries; + + Entries entries_; + + DISALLOW_COPY_AND_ASSIGN(ViewModel); +}; + +} // namespace aura_shell + +#endif // UI_AURA_SHELL_LAUNCHER_VIEW_MODEL_H_ diff --git a/ui/aura_shell/launcher/view_model_unittest.cc b/ui/aura_shell/launcher/view_model_unittest.cc new file mode 100644 index 0000000..2836079 --- /dev/null +++ b/ui/aura_shell/launcher/view_model_unittest.cc @@ -0,0 +1,26 @@ +// 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/launcher/view_model.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "views/view.h" + +namespace aura_shell { + +TEST(ViewModel, BasicAssertions) { + views::View v1; + ViewModel model; + model.Add(&v1, 0); + EXPECT_EQ(1, model.view_size()); + EXPECT_EQ(&v1, model.view_at(0)); + gfx::Rect v1_bounds(1, 2, 3, 4); + model.set_ideal_bounds(0, v1_bounds); + EXPECT_EQ(v1_bounds, model.ideal_bounds(0)); + EXPECT_EQ(0, model.GetIndexOfView(&v1)); + model.SetViewBoundsToIdealBounds(); + EXPECT_EQ(v1_bounds, v1.bounds()); +} + +} // namespace aura_shell |