summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-20 15:34:18 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-20 15:34:18 +0000
commitf94b88f6871d30b7df821c1cab44da45b6c32e68 (patch)
tree368f8c243cea7063c274a768eeccceb4537f1767
parentb43c8b043e770f80abb558860c8fa508d45532d2 (diff)
downloadchromium_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.gyp10
-rw-r--r--ui/aura_shell/launcher/launcher_model_unittest.cc2
-rw-r--r--ui/aura_shell/launcher/launcher_view.cc165
-rw-r--r--ui/aura_shell/launcher/launcher_view.h31
-rwxr-xr-xui/aura_shell/launcher/view_model.cc52
-rwxr-xr-xui/aura_shell/launcher/view_model.h81
-rw-r--r--ui/aura_shell/launcher/view_model_unittest.cc26
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