summaryrefslogtreecommitdiffstats
path: root/ash/launcher
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-23 21:35:52 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-23 21:35:52 +0000
commit7634f96b932b670de6e34552a9e0923147da3486 (patch)
tree2d755027fd1a3038335e3a3c38680274b448cdbe /ash/launcher
parent7edb817aa768be2278f9b5bc34fda9d95cc4bdde (diff)
downloadchromium_src-7634f96b932b670de6e34552a9e0923147da3486.zip
chromium_src-7634f96b932b670de6e34552a9e0923147da3486.tar.gz
chromium_src-7634f96b932b670de6e34552a9e0923147da3486.tar.bz2
Move more stuff down into ash.
http://crbug.com/108457 TEST=none TBR=sky Review URL: http://codereview.chromium.org/9030007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@115739 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash/launcher')
-rw-r--r--ash/launcher/app_launcher_button.cc80
-rw-r--r--ash/launcher/app_launcher_button.h42
-rw-r--r--ash/launcher/launcher.cc183
-rw-r--r--ash/launcher/launcher.h74
-rw-r--r--ash/launcher/launcher_button_host.h40
-rw-r--r--ash/launcher/launcher_model.cc87
-rw-r--r--ash/launcher/launcher_model.h67
-rw-r--r--ash/launcher/launcher_model_observer.h39
-rw-r--r--ash/launcher/launcher_model_unittest.cc122
-rw-r--r--ash/launcher/launcher_types.h67
-rw-r--r--ash/launcher/launcher_unittest.cc31
-rw-r--r--ash/launcher/launcher_view.cc597
-rw-r--r--ash/launcher/launcher_view.h150
-rw-r--r--ash/launcher/tabbed_launcher_button.cc196
-rw-r--r--ash/launcher/tabbed_launcher_button.h106
-rw-r--r--ash/launcher/view_model.cc55
-rw-r--r--ash/launcher/view_model.h82
-rw-r--r--ash/launcher/view_model_unittest.cc41
-rw-r--r--ash/launcher/view_model_utils.cc48
-rw-r--r--ash/launcher/view_model_utils.h36
-rw-r--r--ash/launcher/view_model_utils_unittest.cc46
21 files changed, 2189 insertions, 0 deletions
diff --git a/ash/launcher/app_launcher_button.cc b/ash/launcher/app_launcher_button.cc
new file mode 100644
index 0000000..9c9bd23
--- /dev/null
+++ b/ash/launcher/app_launcher_button.cc
@@ -0,0 +1,80 @@
+// 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 "ash/launcher/app_launcher_button.h"
+
+#include <algorithm>
+
+#include "ash/launcher/launcher_button_host.h"
+#include "ui/gfx/canvas_skia.h"
+
+namespace aura_shell {
+
+namespace internal {
+
+const int kImageSize = 32;
+
+AppLauncherButton::AppLauncherButton(views::ButtonListener* listener,
+ LauncherButtonHost* host)
+ : views::ImageButton(listener),
+ host_(host) {
+ SetPreferredSize(gfx::Size(kImageSize, kImageSize));
+ SetImageAlignment(views::ImageButton::ALIGN_CENTER,
+ views::ImageButton::ALIGN_MIDDLE);
+}
+
+AppLauncherButton::~AppLauncherButton() {
+}
+
+void AppLauncherButton::SetAppImage(const SkBitmap& image) {
+ if (image.empty()) {
+ // TODO: need an empty image.
+ SetImage(BS_NORMAL, &image);
+ return;
+ }
+ // Resize the image maintaining our aspect ratio.
+ int pref = kImageSize;
+ float aspect_ratio =
+ static_cast<float>(image.width()) / static_cast<float>(image.height());
+ int height = pref;
+ int width = static_cast<int>(aspect_ratio * height);
+ if (width > pref) {
+ width = pref;
+ height = static_cast<int>(width / aspect_ratio);
+ }
+ if (width == image.width() && height == image.height()) {
+ SetImage(BS_NORMAL, &image);
+ return;
+ }
+ gfx::CanvasSkia canvas(gfx::Size(width, height), false);
+ canvas.DrawBitmapInt(image, 0, 0, image.width(), image.height(),
+ 0, 0, width, height, false);
+ SkBitmap resized_image(canvas.ExtractBitmap());
+ SetImage(BS_NORMAL, &resized_image);
+}
+
+bool AppLauncherButton::OnMousePressed(const views::MouseEvent& event) {
+ ImageButton::OnMousePressed(event);
+ host_->MousePressedOnButton(this, event);
+ return true;
+}
+
+void AppLauncherButton::OnMouseReleased(const views::MouseEvent& event) {
+ host_->MouseReleasedOnButton(this, false);
+ ImageButton::OnMouseReleased(event);
+}
+
+void AppLauncherButton::OnMouseCaptureLost() {
+ host_->MouseReleasedOnButton(this, true);
+ ImageButton::OnMouseCaptureLost();
+}
+
+bool AppLauncherButton::OnMouseDragged(const views::MouseEvent& event) {
+ ImageButton::OnMouseDragged(event);
+ host_->MouseDraggedOnButton(this, event);
+ return true;
+}
+
+} // namespace internal
+} // namespace aura_shell
diff --git a/ash/launcher/app_launcher_button.h b/ash/launcher/app_launcher_button.h
new file mode 100644
index 0000000..5ee9b4c
--- /dev/null
+++ b/ash/launcher/app_launcher_button.h
@@ -0,0 +1,42 @@
+// 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 ASH_LAUNCHER_APP_LAUNCHER_BUTTON_H_
+#define ASH_LAUNCHER_APP_LAUNCHER_BUTTON_H_
+#pragma once
+
+#include "ui/views/controls/button/image_button.h"
+
+namespace aura_shell {
+namespace internal {
+
+class LauncherButtonHost;
+
+// Button used for items on the launcher corresponding to an app window.
+class AppLauncherButton : public views::ImageButton {
+ public:
+ AppLauncherButton(views::ButtonListener* listener,
+ LauncherButtonHost* host);
+ virtual ~AppLauncherButton();
+
+ // Sets the image to display for this entry.
+ void SetAppImage(const SkBitmap& image);
+
+ protected:
+ // View overrides:
+ virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual bool OnMouseDragged(const views::MouseEvent& event) OVERRIDE;
+
+ private:
+ LauncherButtonHost* host_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppLauncherButton);
+};
+
+} // namespace internal
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_APP_LAUNCHER_BUTTON_H_
diff --git a/ash/launcher/launcher.cc b/ash/launcher/launcher.cc
new file mode 100644
index 0000000..e275552
--- /dev/null
+++ b/ash/launcher/launcher.cc
@@ -0,0 +1,183 @@
+// 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 "ash/launcher/launcher.h"
+
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_view.h"
+#include "grit/ui_resources.h"
+#include "ui/aura/window.h"
+#include "ui/aura_shell/shell.h"
+#include "ui/aura_shell/shell_delegate.h"
+#include "ui/aura_shell/shell_window_ids.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/compositor/layer.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/painter.h"
+#include "ui/views/widget/widget.h"
+
+namespace aura_shell {
+
+namespace {
+
+// Used to draw the background of the shelf.
+class ShelfPainter : public views::Painter {
+ public:
+ ShelfPainter() {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ image_ = *rb.GetImageNamed(IDR_AURA_LAUNCHER_BACKGROUND).ToSkBitmap();
+ }
+
+ virtual void Paint(int w, int h, gfx::Canvas* canvas) OVERRIDE {
+ canvas->TileImageInt(image_, 0, 0, w, h);
+ }
+
+ private:
+ SkBitmap image_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfPainter);
+};
+
+} // namespace
+
+// The contents view of the Widget. This view contains LauncherView and
+// sizes it to the width of the widget minus the size of the status area.
+class Launcher::DelegateView : public views::WidgetDelegateView {
+ public:
+ explicit DelegateView();
+ virtual ~DelegateView();
+
+ void SetStatusWidth(int width);
+ int status_width() const { return status_width_; }
+
+ // views::View overrides
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+
+ private:
+ int status_width_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegateView);
+};
+
+Launcher::DelegateView::DelegateView()
+ : status_width_(0) {
+ set_background(
+ views::Background::CreateBackgroundPainter(true, new ShelfPainter()));
+}
+
+Launcher::DelegateView::~DelegateView() {
+}
+
+void Launcher::DelegateView::SetStatusWidth(int width) {
+ if (status_width_ == width)
+ return;
+
+ status_width_ = width;
+ Layout();
+}
+
+gfx::Size Launcher::DelegateView::GetPreferredSize() {
+ return child_count() > 0 ? child_at(0)->GetPreferredSize() : gfx::Size();
+}
+
+void Launcher::DelegateView::Layout() {
+ if (child_count() == 0)
+ return;
+ child_at(0)->SetBounds(0, 0, std::max(0, width() - status_width_), height());
+}
+
+Launcher::Launcher(aura::Window* window_container)
+ : widget_(NULL),
+ window_container_(window_container),
+ delegate_view_(NULL) {
+ window_container->AddObserver(this);
+
+ model_.reset(new LauncherModel);
+
+ widget_ = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
+ params.create_texture_for_layer = true;
+ params.transparent = true;
+ params.parent = Shell::GetInstance()->GetContainer(
+ aura_shell::internal::kShellWindowId_LauncherContainer);
+ internal::LauncherView* launcher_view =
+ new internal::LauncherView(model_.get());
+ launcher_view->Init();
+ delegate_view_ = new DelegateView;
+ delegate_view_->AddChildView(launcher_view);
+ params.delegate = delegate_view_;
+ widget_->Init(params);
+ gfx::Size pref = static_cast<views::View*>(launcher_view)->GetPreferredSize();
+ widget_->SetBounds(gfx::Rect(0, 0, pref.width(), pref.height()));
+ widget_->SetContentsView(delegate_view_);
+ widget_->Show();
+ widget_->GetNativeView()->SetName("LauncherView");
+}
+
+Launcher::~Launcher() {
+ widget_->CloseNow();
+ window_container_->RemoveObserver(this);
+ for (WindowMap::iterator i = known_windows_.begin();
+ i != known_windows_.end(); ++i) {
+ i->first->RemoveObserver(this);
+ }
+}
+
+void Launcher::SetStatusWidth(int width) {
+ delegate_view_->SetStatusWidth(width);
+}
+
+int Launcher::GetStatusWidth() {
+ return delegate_view_->status_width();
+}
+
+void Launcher::MaybeAdd(aura::Window* window) {
+ if (known_windows_[window] == true)
+ return; // We already tried to add this window.
+
+ known_windows_[window] = true;
+ ShellDelegate* delegate = Shell::GetInstance()->delegate();
+ if (!delegate)
+ return;
+ LauncherItem item;
+ item.window = window;
+ if (!delegate->ConfigureLauncherItem(&item))
+ return; // The delegate doesn't want to show this item in the launcher.
+ model_->Add(model_->items().size(), item);
+}
+
+void Launcher::OnWindowAdded(aura::Window* new_window) {
+ if (new_window->parent() != window_container_)
+ return;
+
+ DCHECK(known_windows_.find(new_window) == known_windows_.end());
+ known_windows_[new_window] = false;
+ new_window->AddObserver(this);
+ // Windows are created initially invisible. Wait until the window is made
+ // visible before asking, as othewise the delegate likely doesn't know about
+ // window (it's still creating it).
+ if (new_window->IsVisible())
+ MaybeAdd(new_window);
+}
+
+void Launcher::OnWillRemoveWindow(aura::Window* window) {
+ if (window->parent() != window_container_)
+ return;
+
+ window->RemoveObserver(this);
+ known_windows_.erase(window);
+ LauncherItems::const_iterator i = model_->ItemByWindow(window);
+ if (i != model_->items().end())
+ model_->RemoveItemAt(i - model_->items().begin());
+}
+
+void Launcher::OnWindowVisibilityChanged(aura::Window* window,
+ bool visibile) {
+ if (visibile && !known_windows_[window])
+ MaybeAdd(window);
+}
+
+} // namespace aura_shell
diff --git a/ash/launcher/launcher.h b/ash/launcher/launcher.h
new file mode 100644
index 0000000..e2f223c
--- /dev/null
+++ b/ash/launcher/launcher.h
@@ -0,0 +1,74 @@
+// 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 ASH_LAUNCHER_LAUNCHER_H_
+#define ASH_LAUNCHER_LAUNCHER_H_
+#pragma once
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/window_observer.h"
+#include "ui/aura_shell/aura_shell_export.h"
+
+namespace aura {
+class Window;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace aura_shell {
+
+class LauncherModel;
+
+class AURA_SHELL_EXPORT Launcher : public aura::WindowObserver {
+ public:
+ explicit Launcher(aura::Window* window_container);
+ ~Launcher();
+
+ // Sets the width of the status area.
+ void SetStatusWidth(int width);
+ int GetStatusWidth();
+
+ LauncherModel* model() { return model_.get(); }
+ views::Widget* widget() { return widget_; }
+
+ private:
+ class DelegateView;
+
+ typedef std::map<aura::Window*, bool> WindowMap;
+
+ // If necessary asks the delegate if an entry should be created in the
+ // launcher for |window|. This only asks the delegate once for a window.
+ void MaybeAdd(aura::Window* window);
+
+ // WindowObserver overrides:
+ virtual void OnWindowAdded(aura::Window* new_window) OVERRIDE;
+ virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE;
+ virtual void OnWindowVisibilityChanged(aura::Window* window,
+ bool visibile) OVERRIDE;
+
+ scoped_ptr<LauncherModel> model_;
+
+ // Widget hosting the view.
+ views::Widget* widget_;
+
+ aura::Window* window_container_;
+
+ // The set of windows we know about. The boolean indicates whether we've asked
+ // the delegate if the window should added to the launcher.
+ WindowMap known_windows_;
+
+ // Contents view of the widget. Houses the LauncherView.
+ DelegateView* delegate_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(Launcher);
+};
+
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_LAUNCHER_H_
diff --git a/ash/launcher/launcher_button_host.h b/ash/launcher/launcher_button_host.h
new file mode 100644
index 0000000..32a5241
--- /dev/null
+++ b/ash/launcher/launcher_button_host.h
@@ -0,0 +1,40 @@
+// 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 ASH_LAUNCHER_LAUNCHER_BUTTON_HOST_H_
+#define ASH_LAUNCHER_LAUNCHER_BUTTON_HOST_H_
+#pragma once
+
+namespace views {
+class MouseEvent;
+class View;
+}
+
+namespace aura_shell {
+namespace internal {
+
+// The launcher buttons communicate back to the host by way of this interface.
+// This interface is used to enable reordering the items on the launcher.
+class LauncherButtonHost {
+ public:
+ // Invoked when the mose is pressed on a view.
+ virtual void MousePressedOnButton(views::View* view,
+ const views::MouseEvent& event) = 0;
+
+ // Invoked when the mouse is dragged over a view.
+ virtual void MouseDraggedOnButton(views::View* view,
+ const views::MouseEvent& event) = 0;
+
+ // Invoked either if the mouse is released or mouse capture canceled.
+ virtual void MouseReleasedOnButton(views::View* view,
+ bool canceled) = 0;
+
+ protected:
+ virtual ~LauncherButtonHost() {}
+};
+
+} // namespace internal
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_LAUNCHER_BUTTON_HOST_H_
diff --git a/ash/launcher/launcher_model.cc b/ash/launcher/launcher_model.cc
new file mode 100644
index 0000000..ec19026
--- /dev/null
+++ b/ash/launcher/launcher_model.cc
@@ -0,0 +1,87 @@
+// 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 "ash/launcher/launcher_model.h"
+
+#include "ui/aura/window.h"
+#include "ash/launcher/launcher_model_observer.h"
+
+namespace aura_shell {
+
+LauncherModel::LauncherModel() {
+}
+
+LauncherModel::~LauncherModel() {
+}
+
+void LauncherModel::Add(int index, const LauncherItem& item) {
+ DCHECK(index >= 0 && index <= item_count());
+ items_.insert(items_.begin() + index, item);
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemAdded(index));
+}
+
+void LauncherModel::RemoveItemAt(int index) {
+ DCHECK(index >= 0 && index < item_count());
+ items_.erase(items_.begin() + index);
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemRemoved(index));
+}
+
+void LauncherModel::Move(int index, int target_index) {
+ if (index == target_index)
+ return;
+ LauncherItem item(items_[index]);
+ items_.erase(items_.begin() + index);
+ items_.insert(items_.begin() + target_index, item);
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemMoved(index, target_index));
+}
+
+void LauncherModel::SetTabbedImages(int index,
+ const LauncherTabbedImages& images) {
+ DCHECK(index >= 0 && index < item_count());
+ DCHECK_EQ(TYPE_TABBED, items_[index].type);
+ items_[index].tab_images = images;
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemImagesChanged(index));
+}
+
+void LauncherModel::SetAppImage(int index, const SkBitmap& image) {
+ DCHECK(index >= 0 && index < item_count());
+ DCHECK_EQ(TYPE_APP, items_[index].type);
+ items_[index].app_image = image;
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemImagesChanged(index));
+}
+
+void LauncherModel::SetPendingUpdate(int index) {
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemImagesWillChange(index));
+}
+
+int LauncherModel::ItemIndexByWindow(aura::Window* window) {
+ LauncherItems::const_iterator i = ItemByWindow(window);
+ return i == items_.end() ? -1 : static_cast<int>((i - items_.begin()));
+}
+
+LauncherItems::const_iterator LauncherModel::ItemByWindow(
+ aura::Window* window) const {
+ for (LauncherItems::const_iterator i = items_.begin();
+ i != items_.end(); ++i) {
+ if (i->window == window)
+ return i;
+ }
+ return items_.end();
+}
+
+void LauncherModel::AddObserver(LauncherModelObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void LauncherModel::RemoveObserver(LauncherModelObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace aura_shell
diff --git a/ash/launcher/launcher_model.h b/ash/launcher/launcher_model.h
new file mode 100644
index 0000000..a28cd31
--- /dev/null
+++ b/ash/launcher/launcher_model.h
@@ -0,0 +1,67 @@
+// 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 ASH_LAUNCHER_LAUNCHER_MODEL_H_
+#define ASH_LAUNCHER_LAUNCHER_MODEL_H_
+#pragma once
+
+#include <vector>
+
+#include "ash/launcher/launcher_types.h"
+#include "base/observer_list.h"
+#include "ui/aura_shell/aura_shell_export.h"
+
+namespace aura {
+class Window;
+}
+
+namespace aura_shell {
+
+class LauncherModelObserver;
+
+// Model used by LauncherView.
+class AURA_SHELL_EXPORT LauncherModel {
+ public:
+ LauncherModel();
+ ~LauncherModel();
+
+ // Adds a new item to the model.
+ void Add(int index, const LauncherItem& item);
+
+ // Removes the item at |index|.
+ void RemoveItemAt(int index);
+
+ // Moves the item at |index| to |target_index|. |target_index| is in terms
+ // of the model *after* the item at |index| is removed.
+ void Move(int index, int target_index);
+
+ // Changes the images of the specified item.
+ void SetTabbedImages(int index, const LauncherTabbedImages& images);
+ void SetAppImage(int index, const SkBitmap& image);
+
+ // Sends LauncherItemImagesWillChange() to the observers. Used when the images
+ // are going to change for an item, but not for a while.
+ void SetPendingUpdate(int index);
+
+ // Returns the index of the item with the specified window.
+ int ItemIndexByWindow(aura::Window* window);
+
+ LauncherItems::const_iterator ItemByWindow(aura::Window* window) const;
+
+ const LauncherItems& items() const { return items_; }
+ int item_count() const { return static_cast<int>(items_.size()); }
+
+ void AddObserver(LauncherModelObserver* observer);
+ void RemoveObserver(LauncherModelObserver* observer);
+
+ private:
+ LauncherItems items_;
+ ObserverList<LauncherModelObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherModel);
+};
+
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_LAUNCHER_MODEL_H_
diff --git a/ash/launcher/launcher_model_observer.h b/ash/launcher/launcher_model_observer.h
new file mode 100644
index 0000000..4f9b7ee
--- /dev/null
+++ b/ash/launcher/launcher_model_observer.h
@@ -0,0 +1,39 @@
+// 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 ASH_LAUNCHER_LAUNCHER_MODEL_OBSERVER_H_
+#define ASH_LAUNCHER_LAUNCHER_MODEL_OBSERVER_H_
+#pragma once
+
+#include "ui/aura_shell/aura_shell_export.h"
+
+namespace aura_shell {
+
+class AURA_SHELL_EXPORT LauncherModelObserver {
+ public:
+ // Invoked after an item has been added to the model.
+ virtual void LauncherItemAdded(int index) = 0;
+
+ // Invoked after an item has been removed. |index| is the index the item was
+ // at.
+ virtual void LauncherItemRemoved(int index) = 0;
+
+ // Invoked after an item has been moved. See LauncherModel::Move() for details
+ // of the arguments.
+ virtual void LauncherItemMoved(int start_index, int target_index) = 0;
+
+ // Invoked when the images of an item change.
+ virtual void LauncherItemImagesChanged(int index) = 0;
+
+ // Signals that LauncherItemImagesChanged() is going to be sent in the
+ // near future.
+ virtual void LauncherItemImagesWillChange(int index) = 0;
+
+ protected:
+ virtual ~LauncherModelObserver() {}
+};
+
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_LAUNCHER_MODEL_OBSERVER_H_
diff --git a/ash/launcher/launcher_model_unittest.cc b/ash/launcher/launcher_model_unittest.cc
new file mode 100644
index 0000000..3c50b1e
--- /dev/null
+++ b/ash/launcher/launcher_model_unittest.cc
@@ -0,0 +1,122 @@
+// 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 "ash/launcher/launcher_model.h"
+
+#include "ash/launcher/launcher_model_observer.h"
+#include "base/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace aura_shell {
+
+namespace {
+
+// LauncherModelObserver implementation that tracks what message are invoked.
+class TestLauncherModelObserver : public LauncherModelObserver {
+ public:
+ TestLauncherModelObserver()
+ : added_count_(0),
+ removed_count_(0),
+ images_changed_count_(0),
+ moved_count_(0) {
+ }
+
+ // Returns a string description of the changes that have occurred since this
+ // was last invoked. Resets state to initial state.
+ std::string StateStringAndClear() {
+ std::string result;
+ AddToResult("added=%d", added_count_, &result);
+ AddToResult("removed=%d", removed_count_, &result);
+ AddToResult("images_changed=%d", images_changed_count_, &result);
+ AddToResult("moved=%d", moved_count_, &result);
+ added_count_ = removed_count_ = images_changed_count_ = moved_count_ = 0;
+ return result;
+ }
+
+ // LauncherModelObserver overrides:
+ virtual void LauncherItemAdded(int index) OVERRIDE {
+ added_count_++;
+ }
+ virtual void LauncherItemRemoved(int index) OVERRIDE {
+ removed_count_++;
+ }
+ virtual void LauncherItemImagesChanged(int index) OVERRIDE {
+ images_changed_count_++;
+ }
+ virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE {
+ moved_count_++;
+ }
+ virtual void LauncherItemImagesWillChange(int index) OVERRIDE {
+ }
+
+ private:
+ void AddToResult(const std::string& format, int count, std::string* result) {
+ if (!count)
+ return;
+ if (!result->empty())
+ *result += " ";
+ *result += base::StringPrintf(format.c_str(), count);
+ }
+
+ int added_count_;
+ int removed_count_;
+ int images_changed_count_;
+ int moved_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLauncherModelObserver);
+};
+
+} // namespace
+
+TEST(LauncherModel, BasicAssertions) {
+ TestLauncherModelObserver observer;
+ LauncherModel model;
+ // Add an item.
+ model.AddObserver(&observer);
+ LauncherItem item;
+ model.Add(0, item);
+ EXPECT_EQ(1, model.item_count());
+ EXPECT_EQ("added=1",
+ observer.StateStringAndClear());
+
+ // Change a tabbed image.
+ model.SetTabbedImages(0, LauncherTabbedImages());
+ EXPECT_EQ("images_changed=1",
+ observer.StateStringAndClear());
+
+ // Remove the item.
+ model.RemoveItemAt(0);
+ EXPECT_EQ(0, model.item_count());
+ EXPECT_EQ("removed=1", observer.StateStringAndClear());
+
+ // Add an app item.
+ item.type = TYPE_APP;
+ item.user_data = reinterpret_cast<void*>(1);
+ model.Add(0, item);
+ observer.StateStringAndClear();
+
+ // Change an app image.
+ model.SetAppImage(0, SkBitmap());
+ EXPECT_EQ("images_changed=1", observer.StateStringAndClear());
+
+ // Add another item.
+ item.type = TYPE_APP;
+ item.user_data = reinterpret_cast<void*>(2);
+ model.Add(1, item);
+ observer.StateStringAndClear();
+
+ // Move the second item to be first.
+ model.Move(1, 0);
+ EXPECT_EQ("moved=1", observer.StateStringAndClear());
+ EXPECT_EQ(reinterpret_cast<void*>(2), model.items()[0].user_data);
+ EXPECT_EQ(reinterpret_cast<void*>(1), model.items()[1].user_data);
+
+ // Move the first item to the second item.
+ model.Move(0, 1);
+ EXPECT_EQ("moved=1", observer.StateStringAndClear());
+ EXPECT_EQ(reinterpret_cast<void*>(1), model.items()[0].user_data);
+ EXPECT_EQ(reinterpret_cast<void*>(2), model.items()[1].user_data);
+}
+
+} // namespace aura_shell
diff --git a/ash/launcher/launcher_types.h b/ash/launcher/launcher_types.h
new file mode 100644
index 0000000..35150b2
--- /dev/null
+++ b/ash/launcher/launcher_types.h
@@ -0,0 +1,67 @@
+// 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 ASH_LAUNCHER_LAUNCHER_TYPES_H_
+#define ASH_LAUNCHER_LAUNCHER_TYPES_H_
+#pragma once
+
+#include <vector>
+
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/aura_shell/aura_shell_export.h"
+
+namespace aura {
+class Window;
+}
+
+namespace aura_shell {
+
+// Type the LauncherItem represents.
+enum AURA_SHELL_EXPORT LauncherItemType {
+ TYPE_TABBED,
+ TYPE_APP
+};
+
+// Represents an image in a launcher item of type TYPE_APP.
+struct AURA_SHELL_EXPORT LauncherTabbedImage {
+ LauncherTabbedImage() : user_data(NULL) {}
+ LauncherTabbedImage(const SkBitmap& image, void* user_data)
+ : image(image),
+ user_data(user_data) {
+ }
+
+ // The image to show.
+ SkBitmap image;
+
+ // Used to identify the image.
+ void* user_data;
+};
+
+typedef std::vector<LauncherTabbedImage> LauncherTabbedImages;
+
+struct AURA_SHELL_EXPORT LauncherItem {
+ LauncherItem() : type(TYPE_TABBED), window(NULL), user_data(NULL) {}
+ LauncherItem(LauncherItemType type,
+ aura::Window* window,
+ void* user_data)
+ : type(type),
+ window(window),
+ user_data(user_data) {}
+
+ LauncherItemType type;
+ aura::Window* window;
+ void* user_data;
+
+ // Image to display in the launcher if the item is of type TYPE_APP.
+ SkBitmap app_image;
+
+ // Image to display in the launcher if the item is of type TYPE_TABBED.
+ LauncherTabbedImages tab_images;
+};
+
+typedef std::vector<LauncherItem> LauncherItems;
+
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_LAUNCHER_TYPES_H_
diff --git a/ash/launcher/launcher_unittest.cc b/ash/launcher/launcher_unittest.cc
new file mode 100644
index 0000000..de65658
--- /dev/null
+++ b/ash/launcher/launcher_unittest.cc
@@ -0,0 +1,31 @@
+// 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 "ash/launcher/launcher.h"
+
+#include "ui/aura_shell/shell.h"
+#include "ui/aura_shell/test/aura_shell_test_base.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+typedef aura_shell::test::AuraShellTestBase LauncherTest;
+
+namespace aura_shell {
+
+// Makes sure invoking SetStatusWidth on the launcher changes the size of the
+// LauncherView.
+TEST_F(LauncherTest, SetStatusWidth) {
+ Launcher* launcher = Shell::GetInstance()->launcher();
+ ASSERT_TRUE(launcher);
+ views::View* launcher_view = launcher->widget()->GetContentsView();
+ ASSERT_EQ(1, launcher_view->child_count());
+ launcher_view = launcher_view->child_at(0);
+
+ int total_width = launcher->widget()->GetWindowScreenBounds().width();
+ ASSERT_GT(total_width, 0);
+ launcher->SetStatusWidth(total_width / 2);
+ EXPECT_EQ(total_width - total_width / 2, launcher_view->width());
+}
+
+} // namespace aura_shell
diff --git a/ash/launcher/launcher_view.cc b/ash/launcher/launcher_view.cc
new file mode 100644
index 0000000..01b69e2
--- /dev/null
+++ b/ash/launcher/launcher_view.cc
@@ -0,0 +1,597 @@
+// 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 "ash/launcher/launcher_view.h"
+
+#include "ash/launcher/app_launcher_button.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/tabbed_launcher_button.h"
+#include "ash/launcher/view_model.h"
+#include "ash/launcher/view_model_utils.h"
+#include "base/utf_string_conversions.h"
+#include "grit/ui_resources.h"
+#include "ui/aura/window.h"
+#include "ui/aura_shell/shell.h"
+#include "ui/aura_shell/shell_delegate.h"
+#include "ui/base/animation/animation.h"
+#include "ui/base/animation/throb_animation.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/compositor/layer.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/animation/bounds_animator.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/menu/menu_model_adapter.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/widget/widget.h"
+
+using ui::Animation;
+using views::View;
+
+namespace aura_shell {
+namespace internal {
+
+// Padding between each view.
+static const int kHorizontalPadding = 12;
+
+// Amount content is inset on the left edge.
+static const int kLeadingInset = 8;
+
+// Height of the LauncherView. Hard coded to avoid resizing as items are
+// added/removed.
+static const int kPreferredHeight = 48;
+
+// Minimum distance before drag starts.
+static const int kMinimumDragDistance = 8;
+
+// Opacity for the |new_browser_button_| and |show_apps_buttons_| when the mouse
+// isn't over it.
+static const float kDimmedButtonOpacity = .8f;
+
+namespace {
+
+// ui::SimpleMenuModel::Delegate implementation that remembers the id of the
+// menu that was activated.
+class MenuDelegateImpl : public ui::SimpleMenuModel::Delegate {
+ public:
+ MenuDelegateImpl() : activated_command_id_(-1) {}
+
+ int activated_command_id() const { return activated_command_id_; }
+
+ // ui::SimpleMenuModel::Delegate overrides:
+ virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
+ return false;
+ }
+ virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
+ return true;
+ }
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) OVERRIDE {
+ return false;
+ }
+ virtual void ExecuteCommand(int command_id) OVERRIDE {
+ activated_command_id_ = command_id;
+ }
+
+ private:
+ // ID of the command passed to ExecuteCommand.
+ int activated_command_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuDelegateImpl);
+};
+
+// 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:
+ explicit DeleteViewAnimationDelegate(views::View* view) : view_(view) {}
+ virtual ~DeleteViewAnimationDelegate() {}
+
+ private:
+ scoped_ptr<views::View> view_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteViewAnimationDelegate);
+};
+
+// AnimationDelegate used when inserting a new item. This steadily increases the
+// opacity of the layer as the animation progress.
+class FadeInAnimationDelegate :
+ public views::BoundsAnimator::OwnedAnimationDelegate {
+ public:
+ explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
+ virtual ~FadeInAnimationDelegate() {}
+
+ // AnimationDelegate overrides:
+ virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(animation->GetCurrentValue());
+ view_->layer()->ScheduleDraw();
+ }
+ virtual void AnimationEnded(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(1.0f);
+ view_->layer()->ScheduleDraw();
+ }
+ virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(1.0f);
+ view_->layer()->ScheduleDraw();
+ }
+
+ private:
+ views::View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
+};
+
+} // namespace
+
+// AnimationDelegate used when inserting a new item. This steadily decreased the
+// opacity of the layer as the animation progress.
+class LauncherView::FadeOutAnimationDelegate :
+ public views::BoundsAnimator::OwnedAnimationDelegate {
+ public:
+ FadeOutAnimationDelegate(LauncherView* host, views::View* view)
+ : launcher_view_(host),
+ view_(view) {}
+ virtual ~FadeOutAnimationDelegate() {}
+
+ // AnimationDelegate overrides:
+ virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
+ view_->layer()->ScheduleDraw();
+ }
+ virtual void AnimationEnded(const Animation* animation) OVERRIDE {
+ launcher_view_->AnimateToIdealBounds();
+ }
+ virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
+ }
+
+ private:
+ LauncherView* launcher_view_;
+ scoped_ptr<views::View> view_;
+
+ DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
+};
+
+// AnimationDelegate used to trigger fading an element in. When an item is
+// inserted this delegate is attached to the animation that expands the size of
+// the item. When done it kicks off another animation to fade the item in.
+class LauncherView::StartFadeAnimationDelegate :
+ public views::BoundsAnimator::OwnedAnimationDelegate {
+ public:
+ StartFadeAnimationDelegate(LauncherView* host,
+ views::View* view)
+ : launcher_view_(host),
+ view_(view) {}
+ virtual ~StartFadeAnimationDelegate() {}
+
+ // AnimationDelegate overrides:
+ virtual void AnimationEnded(const Animation* animation) OVERRIDE {
+ view_->SetVisible(true);
+ launcher_view_->FadeIn(view_);
+ }
+ virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
+ view_->SetVisible(true);
+ }
+
+ private:
+ LauncherView* launcher_view_;
+ views::View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
+};
+
+LauncherView::LauncherView(LauncherModel* model)
+ : model_(model),
+ view_model_(new ViewModel),
+ new_browser_button_(NULL),
+ show_apps_button_(NULL),
+ overflow_button_(NULL),
+ dragging_(NULL),
+ drag_view_(NULL),
+ drag_offset_(0),
+ start_drag_index_(-1) {
+ DCHECK(model_);
+ bounds_animator_.reset(new views::BoundsAnimator(this));
+}
+
+LauncherView::~LauncherView() {
+ model_->RemoveObserver(this);
+}
+
+void LauncherView::Init() {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ model_->AddObserver(this);
+
+ show_apps_button_ = new views::ImageButton(this);
+ show_apps_button_->SetImage(
+ views::CustomButton::BS_NORMAL,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_ICON_APPLIST).ToSkBitmap());
+ show_apps_button_->SetImage(
+ views::CustomButton::BS_HOT,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_ICON_APPLIST_HOT).ToSkBitmap());
+ show_apps_button_->SetImage(
+ views::CustomButton::BS_PUSHED,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_ICON_APPLIST_PUSHED).ToSkBitmap());
+ ConfigureChildView(show_apps_button_);
+ AddChildView(show_apps_button_);
+
+ const LauncherItems& items(model_->items());
+ for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) {
+ views::View* child = CreateViewForItem(*i);
+ child->SetPaintToLayer(true);
+ view_model_->Add(child, static_cast<int>(i - items.begin()));
+ AddChildView(child);
+ }
+
+ new_browser_button_ = new views::ImageButton(this);
+ new_browser_button_->SetImage(
+ views::CustomButton::BS_NORMAL,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_NEW_BROWSER).ToSkBitmap());
+ new_browser_button_->SetImage(
+ views::CustomButton::BS_HOT,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_NEW_BROWSER_HOT).ToSkBitmap());
+ new_browser_button_->SetImage(
+ views::CustomButton::BS_PUSHED,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_NEW_BROWSER_PUSHED).ToSkBitmap());
+ ConfigureChildView(new_browser_button_);
+ AddChildView(new_browser_button_);
+
+ overflow_button_ = new views::ImageButton(this);
+ overflow_button_->SetImage(
+ views::CustomButton::BS_NORMAL,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_OVERFLOW).ToSkBitmap());
+ overflow_button_->SetImage(
+ views::CustomButton::BS_HOT,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_OVERFLOW_HOT).ToSkBitmap());
+ overflow_button_->SetImage(
+ views::CustomButton::BS_PUSHED,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_OVERFLOW_PUSHED).ToSkBitmap());
+ ConfigureChildView(overflow_button_);
+ AddChildView(overflow_button_);
+
+ // We'll layout when our bounds change.
+}
+
+void LauncherView::LayoutToIdealBounds() {
+ IdealBounds ideal_bounds;
+ CalculateIdealBounds(&ideal_bounds);
+ show_apps_button_->SetBoundsRect(ideal_bounds.show_apps_bounds);
+ new_browser_button_->SetBoundsRect(ideal_bounds.new_browser_bounds);
+ ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
+}
+
+void LauncherView::CalculateIdealBounds(IdealBounds* bounds) {
+ int available_width = width();
+ if (!available_width)
+ return;
+
+ // show_apps_button_ first.
+ int x = kLeadingInset;
+ gfx::Size pref = show_apps_button_->GetPreferredSize();
+ bounds->show_apps_bounds = gfx::Rect(
+ x, (kPreferredHeight - pref.height()) / 2, pref.width(), pref.height());
+ x += bounds->show_apps_bounds.width() + kHorizontalPadding;
+ // TODO: remove when we get better images.
+ x -= 6;
+
+ // 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;
+ }
+
+ // new_browser_button_ and overflow button.
+ bounds->new_browser_bounds.set_size(new_browser_button_->GetPreferredSize());
+ bounds->overflow_bounds.set_size(overflow_button_->GetPreferredSize());
+ int last_visible_index = DetermineLastVisibleIndex(
+ available_width - kLeadingInset - bounds->new_browser_bounds.width() -
+ kHorizontalPadding - bounds->overflow_bounds.width() -
+ kHorizontalPadding);
+ bool show_overflow = (last_visible_index + 1 != view_model_->view_size());
+ if (overflow_button_->visible() != show_overflow) {
+ // Only change visibility of the views if the visibility of the overflow
+ // button changes. Otherwise we'll effect the insertion animation, which
+ // changes the visibility.
+ for (int i = 0; i <= last_visible_index; ++i)
+ view_model_->view_at(i)->SetVisible(true);
+ for (int i = last_visible_index + 1; i < view_model_->view_size(); ++i)
+ view_model_->view_at(i)->SetVisible(false);
+ }
+
+ overflow_button_->SetVisible(show_overflow);
+ if (show_overflow) {
+ DCHECK_NE(0, view_model_->view_size());
+ x = view_model_->ideal_bounds(last_visible_index).right() +
+ kHorizontalPadding;
+ bounds->overflow_bounds.set_x(x);
+ bounds->overflow_bounds.set_y(
+ (kPreferredHeight - bounds->overflow_bounds.height()) / 2);
+ x = bounds->overflow_bounds.right() + kHorizontalPadding;
+ }
+ bounds->new_browser_bounds.set_x(x);
+ bounds->new_browser_bounds.set_y(
+ (kPreferredHeight - bounds->new_browser_bounds.height()) / 2);
+}
+
+int LauncherView::DetermineLastVisibleIndex(int max_x) {
+ int index = view_model_->view_size() - 1;
+ while (index >= 0 && view_model_->ideal_bounds(index).right() > max_x)
+ index--;
+ return index;
+}
+
+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);
+ overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
+}
+
+views::View* LauncherView::CreateViewForItem(const LauncherItem& item) {
+ views::View* view = NULL;
+ if (item.type == TYPE_TABBED) {
+ TabbedLauncherButton* button = new TabbedLauncherButton(this, this);
+ button->SetImages(item.tab_images);
+ view = button;
+ } else {
+ DCHECK_EQ(TYPE_APP, item.type);
+ AppLauncherButton* button = new AppLauncherButton(this, this);
+ button->SetAppImage(item.app_image);
+ view = button;
+ }
+ ConfigureChildView(view);
+ return view;
+}
+
+void LauncherView::FadeIn(views::View* view) {
+ view->SetVisible(true);
+ view->layer()->SetOpacity(0);
+ AnimateToIdealBounds();
+ bounds_animator_->SetAnimationDelegate(
+ view, new FadeInAnimationDelegate(view), true);
+}
+
+void LauncherView::PrepareForDrag(const views::MouseEvent& event) {
+ DCHECK(drag_view_);
+ dragging_ = true;
+ start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
+ // Move the view to the front so that it appears on top of other views.
+ ReorderChildView(drag_view_, -1);
+ bounds_animator_->StopAnimatingView(drag_view_);
+}
+
+void LauncherView::ContinueDrag(const views::MouseEvent& event) {
+ // TODO: I don't think this works correctly with RTL.
+ gfx::Point drag_point(event.x(), 0);
+ views::View::ConvertPointToView(drag_view_, this, &drag_point);
+ int current_index = view_model_->GetIndexOfView(drag_view_);
+ DCHECK_NE(-1, current_index);
+
+ // Constrain the x location so that it doesn't overlap the two buttons.
+ int x = std::max(view_model_->ideal_bounds(0).x(),
+ drag_point.x() - drag_offset_);
+ x = std::min(view_model_->ideal_bounds(view_model_->view_size() - 1).right() -
+ view_model_->ideal_bounds(current_index).width(),
+ x);
+ if (drag_view_->x() == x)
+ return;
+
+ drag_view_->SetX(x);
+ int target_index =
+ ViewModelUtils::DetermineMoveIndex(*view_model_, drag_view_, x);
+ if (target_index == current_index)
+ return;
+
+ // Remove the observer while we mutate the model so that we don't attempt to
+ // cancel the drag.
+ model_->RemoveObserver(this);
+ model_->Move(current_index, target_index);
+ model_->AddObserver(this);
+ view_model_->Move(current_index, target_index);
+ AnimateToIdealBounds();
+ bounds_animator_->StopAnimatingView(drag_view_);
+}
+
+void LauncherView::ConfigureChildView(views::View* view) {
+ view->SetPaintToLayer(true);
+ view->layer()->SetFillsBoundsOpaquely(false);
+}
+
+void LauncherView::GetOverflowWindows(std::vector<aura::Window*>* names) {
+ int index = 0;
+ while (index < view_model_->view_size() &&
+ view_model_->view_at(index)->visible()) {
+ index++;
+ }
+ while (index < view_model_->view_size()) {
+ names->push_back(model_->items()[index].window);
+ index++;
+ }
+}
+
+void LauncherView::ShowOverflowMenu() {
+ std::vector<aura::Window*> windows;
+ GetOverflowWindows(&windows);
+ if (windows.empty())
+ return;
+
+ MenuDelegateImpl menu_delegate;
+ ui::SimpleMenuModel menu_model(&menu_delegate);
+ for (size_t i = 0; i < windows.size(); ++i)
+ menu_model.AddItem(static_cast<int>(i), windows[i]->title());
+ views::MenuModelAdapter menu_adapter(&menu_model);
+ overflow_menu_runner_.reset(new views::MenuRunner(menu_adapter.CreateMenu()));
+ gfx::Rect bounds(overflow_button_->size());
+ gfx::Point origin;
+ ConvertPointToScreen(overflow_button_, &origin);
+ if (overflow_menu_runner_->RunMenuAt(GetWidget(), NULL,
+ gfx::Rect(origin, size()), views::MenuItemView::TOPLEFT, 0) ==
+ views::MenuRunner::MENU_DELETED ||
+ menu_delegate.activated_command_id() == -1)
+ return;
+
+ aura::Window* activated_window =
+ windows[menu_delegate.activated_command_id()];
+ LauncherItems::const_iterator window_iter =
+ model_->ItemByWindow(activated_window);
+ if (window_iter == model_->items().end())
+ return; // Window was deleted while menu was up.
+ ShellDelegate* delegate = Shell::GetInstance()->delegate();
+ if (!delegate)
+ return;
+ delegate->LauncherItemClicked(*window_iter);
+}
+
+void LauncherView::CancelDrag(views::View* deleted_view) {
+ if (!drag_view_)
+ return;
+ bool was_dragging = dragging_;
+ views::View* drag_view = drag_view_;
+ dragging_ = false;
+ drag_view_ = NULL;
+ if (drag_view == deleted_view) {
+ // The view that was being dragged is being deleted. Don't do anything.
+ return;
+ }
+ if (!was_dragging)
+ return;
+
+ view_model_->Move(view_model_->GetIndexOfView(drag_view), start_drag_index_);
+ AnimateToIdealBounds();
+}
+
+gfx::Size LauncherView::GetPreferredSize() {
+ IdealBounds ideal_bounds;
+ CalculateIdealBounds(&ideal_bounds);
+ return gfx::Size(ideal_bounds.new_browser_bounds.right() + kLeadingInset,
+ kPreferredHeight);
+}
+
+void LauncherView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+ LayoutToIdealBounds();
+}
+
+void LauncherView::LauncherItemAdded(int model_index) {
+ CancelDrag(NULL);
+
+ views::View* view = CreateViewForItem(model_->items()[model_index]);
+ AddChildView(view);
+ // Hide the view, it'll be made visible when the animation is done.
+ view->SetVisible(false);
+ view_model_->Add(view, model_index);
+
+ // The first animation moves all the views to their target position. |view| is
+ // hidden, so it visually appears as though we are providing space for
+ // it. When done we'll fade the view in.
+ AnimateToIdealBounds();
+ if (!overflow_button_->visible()) {
+ bounds_animator_->SetAnimationDelegate(
+ view, new StartFadeAnimationDelegate(this, view), true);
+ }
+}
+
+void LauncherView::LauncherItemRemoved(int model_index) {
+ views::View* view = view_model_->view_at(model_index);
+ CancelDrag(view);
+ view_model_->Remove(model_index);
+ // The first animation fades out the view. When done we'll animate the rest of
+ // the views to their target location.
+ bounds_animator_->AnimateViewTo(view, view->bounds());
+ bounds_animator_->SetAnimationDelegate(
+ view, new FadeOutAnimationDelegate(this, view), true);
+}
+
+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*>(view);
+ gfx::Size pref = button->GetPreferredSize();
+ button->SetImages(item.tab_images);
+ if (pref != button->GetPreferredSize())
+ AnimateToIdealBounds();
+ else
+ button->SchedulePaint();
+ } else {
+ DCHECK_EQ(TYPE_APP, item.type);
+ AppLauncherButton* button = static_cast<AppLauncherButton*>(view);
+ button->SetAppImage(item.app_image);
+ button->SchedulePaint();
+ }
+}
+
+void LauncherView::LauncherItemMoved(int start_index, int target_index) {
+ view_model_->Move(start_index, target_index);
+ AnimateToIdealBounds();
+}
+
+void LauncherView::LauncherItemImagesWillChange(int index) {
+ const LauncherItem& item(model_->items()[index]);
+ views::View* view = view_model_->view_at(index);
+ if (item.type == TYPE_TABBED)
+ static_cast<TabbedLauncherButton*>(view)->PrepareForImageChange();
+}
+
+void LauncherView::MousePressedOnButton(views::View* view,
+ const views::MouseEvent& event) {
+ if (view_model_->GetIndexOfView(view) == -1 || view_model_->view_size() <= 1)
+ return; // View is being deleted, ignore request.
+
+ drag_view_ = view;
+ drag_offset_ = event.x();
+}
+
+void LauncherView::MouseDraggedOnButton(views::View* view,
+ const views::MouseEvent& event) {
+ if (!dragging_ && drag_view_ &&
+ abs(event.x() - drag_offset_) >= kMinimumDragDistance)
+ PrepareForDrag(event);
+ if (dragging_)
+ ContinueDrag(event);
+}
+
+void LauncherView::MouseReleasedOnButton(views::View* view,
+ bool canceled) {
+ if (canceled) {
+ CancelDrag(NULL);
+ } else {
+ dragging_ = false;
+ drag_view_ = NULL;
+ AnimateToIdealBounds();
+ }
+}
+
+void LauncherView::ButtonPressed(views::Button* sender,
+ const views::Event& event) {
+ ShellDelegate* delegate = Shell::GetInstance()->delegate();
+ if (!delegate)
+ return;
+ if (sender == new_browser_button_) {
+ delegate->CreateNewWindow();
+ } else if (sender == show_apps_button_) {
+ Shell::GetInstance()->ToggleAppList();
+ } else if (sender == overflow_button_) {
+ ShowOverflowMenu();
+ } else {
+ 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]);
+ }
+}
+
+} // namespace internal
+} // namespace aura_shell
diff --git a/ash/launcher/launcher_view.h b/ash/launcher/launcher_view.h
new file mode 100644
index 0000000..27617a0
--- /dev/null
+++ b/ash/launcher/launcher_view.h
@@ -0,0 +1,150 @@
+// 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 ASH_LAUNCHER_LAUNCHER_VIEW_H_
+#define ASH_LAUNCHER_LAUNCHER_VIEW_H_
+#pragma once
+
+#include <vector>
+
+#include "ash/launcher/launcher_button_host.h"
+#include "ash/launcher/launcher_model_observer.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace views {
+class BoundsAnimator;
+class ImageButton;
+class MenuRunner;
+}
+
+namespace aura_shell {
+
+struct LauncherItem;
+class LauncherModel;
+class ViewModel;
+
+namespace internal {
+
+class LauncherView : public views::WidgetDelegateView,
+ public LauncherModelObserver,
+ public views::ButtonListener,
+ public LauncherButtonHost {
+ public:
+ explicit LauncherView(LauncherModel* model);
+ virtual ~LauncherView();
+
+ void Init();
+
+ private:
+ class FadeOutAnimationDelegate;
+ class StartFadeAnimationDelegate;
+
+ struct IdealBounds {
+ gfx::Rect new_browser_bounds;
+ gfx::Rect show_apps_bounds;
+ gfx::Rect overflow_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);
+
+ // Returns the index of the last view whose max x-coordinate is less than
+ // |max_x|. Returns -1 if nothing fits, or there are no views.
+ int DetermineLastVisibleIndex(int max_x);
+
+ // 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);
+
+ // Fades |view| from an opacity of 0 to 1. This is when adding a new item.
+ void FadeIn(views::View* view);
+
+ // Invoked when the mouse has moved enough to trigger a drag. Sets internal
+ // state in preparation for the drag.
+ void PrepareForDrag(const views::MouseEvent& event);
+
+ // Invoked when the mouse is dragged. Updates the models as appropriate.
+ void ContinueDrag(const views::MouseEvent& event);
+
+ // If there is a drag operation in progress it's canceled.
+ void CancelDrag(views::View* deleted_view);
+
+ // Common setup done for all children.
+ void ConfigureChildView(views::View* view);
+
+ // Returns the windows whose icon is not show because it doesn't fit.
+ void GetOverflowWindows(std::vector<aura::Window*>* names);
+
+ // Shows the overflow menu.
+ void ShowOverflowMenu();
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
+
+ // Overridden from LauncherModelObserver:
+ virtual void LauncherItemAdded(int model_index) OVERRIDE;
+ virtual void LauncherItemRemoved(int model_index) OVERRIDE;
+ virtual void LauncherItemImagesChanged(int model_index) OVERRIDE;
+ virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE;
+ virtual void LauncherItemImagesWillChange(int index) OVERRIDE;
+
+ // Overridden from LauncherButtonHost:
+ virtual void MousePressedOnButton(views::View* view,
+ const views::MouseEvent& event) OVERRIDE;
+ virtual void MouseDraggedOnButton(views::View* view,
+ const views::MouseEvent& event) OVERRIDE;
+ virtual void MouseReleasedOnButton(views::View* view,
+ bool canceled) OVERRIDE;
+
+ // Overriden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const views::Event& event) OVERRIDE;
+
+ // 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_;
+
+ views::ImageButton* overflow_button_;
+
+ // Are we dragging? This is only set if the mouse is dragged far enough to
+ // trigger a drag.
+ bool dragging_;
+
+ // The view being dragged. This is set immediately when the mouse is pressed.
+ // |dragging_| is set only if the mouse is dragged far enough.
+ views::View* drag_view_;
+
+ // X coordinate of the mouse down event in |drag_view_|s coordinates.
+ int drag_offset_;
+
+ // Index |drag_view_| was initially at.
+ int start_drag_index_;
+
+ scoped_ptr<views::MenuRunner> overflow_menu_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherView);
+};
+
+} // namespace internal
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_LAUNCHER_VIEW_H_
diff --git a/ash/launcher/tabbed_launcher_button.cc b/ash/launcher/tabbed_launcher_button.cc
new file mode 100644
index 0000000..a07b596
--- /dev/null
+++ b/ash/launcher/tabbed_launcher_button.cc
@@ -0,0 +1,196 @@
+// 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 "ash/launcher/tabbed_launcher_button.h"
+
+#include <algorithm>
+
+#include "ash/launcher/launcher_button_host.h"
+#include "grit/ui_resources.h"
+#include "ui/base/animation/multi_animation.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/insets.h"
+
+namespace aura_shell {
+namespace internal {
+
+// The images drawn inside the background tab are drawn at this offset from
+// the edge.
+const int kBgImageContentInset = 12;
+
+// Padding between each of the images.
+const int kImagePadding = 8;
+
+// Insets used in painting the background if it's rendered bigger than the size
+// of the background image. See ImagePainter::CreateImagePainter for how these
+// are interpreted.
+const int kBgTopInset = 12;
+const int kBgLeftInset = 30;
+const int kBgBottomInset = 12;
+const int kBgRightInset = 8;
+
+TabbedLauncherButton::AnimationDelegateImpl::AnimationDelegateImpl(
+ TabbedLauncherButton* host)
+ : host_(host) {
+}
+
+TabbedLauncherButton::AnimationDelegateImpl::~AnimationDelegateImpl() {
+}
+
+void TabbedLauncherButton::AnimationDelegateImpl::AnimationEnded(
+ const ui::Animation* animation) {
+ AnimationProgressed(animation);
+ // Hide the image when the animation is done. We'll show it again the next
+ // time SetImages is invoked.
+ host_->show_image_ = false;
+}
+
+void TabbedLauncherButton::AnimationDelegateImpl::AnimationProgressed(
+ const ui::Animation* animation) {
+ if (host_->animation_->current_part_index() == 1)
+ host_->SchedulePaint();
+}
+
+// static
+TabbedLauncherButton::ImageSet* TabbedLauncherButton::bg_image_1_ = NULL;
+TabbedLauncherButton::ImageSet* TabbedLauncherButton::bg_image_2_ = NULL;
+TabbedLauncherButton::ImageSet* TabbedLauncherButton::bg_image_3_ = NULL;
+
+TabbedLauncherButton::TabbedLauncherButton(views::ButtonListener* listener,
+ LauncherButtonHost* host)
+ : views::ImageButton(listener),
+ host_(host),
+ ALLOW_THIS_IN_INITIALIZER_LIST(animation_delegate_(this)),
+ show_image_(true),
+ ALLOW_THIS_IN_INITIALIZER_LIST(hover_controller_(this)) {
+ if (!bg_image_1_) {
+ bg_image_1_ = CreateImageSet(IDR_AURA_LAUNCHER_TABBED_BROWSER_1,
+ IDR_AURA_LAUNCHER_TABBED_BROWSER_1_PUSHED,
+ IDR_AURA_LAUNCHER_TABBED_BROWSER_1_HOT);
+ bg_image_2_ = CreateImageSet(IDR_AURA_LAUNCHER_TABBED_BROWSER_2,
+ IDR_AURA_LAUNCHER_TABBED_BROWSER_2_PUSHED,
+ IDR_AURA_LAUNCHER_TABBED_BROWSER_2_HOT);
+ bg_image_3_ = CreateImageSet(IDR_AURA_LAUNCHER_TABBED_BROWSER_3,
+ IDR_AURA_LAUNCHER_TABBED_BROWSER_3_PUSHED,
+ IDR_AURA_LAUNCHER_TABBED_BROWSER_3_HOT);
+ }
+ SetImageAlignment(views::ImageButton::ALIGN_CENTER,
+ views::ImageButton::ALIGN_MIDDLE);
+}
+
+TabbedLauncherButton::~TabbedLauncherButton() {
+}
+
+void TabbedLauncherButton::PrepareForImageChange() {
+ if (!show_image_ || (animation_.get() && animation_->is_animating()))
+ return;
+
+ // Pause for 500ms, then ease out for 200ms.
+ ui::MultiAnimation::Parts animation_parts;
+ animation_parts.push_back(ui::MultiAnimation::Part(500, ui::Tween::ZERO));
+ animation_parts.push_back(ui::MultiAnimation::Part(200, ui::Tween::EASE_OUT));
+ animation_.reset(new ui::MultiAnimation(animation_parts));
+ animation_->set_continuous(false);
+ animation_->set_delegate(&animation_delegate_);
+ animation_->Start();
+}
+
+void TabbedLauncherButton::SetImages(const LauncherTabbedImages& images) {
+ animation_.reset();
+ show_image_ = true;
+ images_ = images;
+ ImageSet* set;
+ if (images_.size() <= 1)
+ set = bg_image_1_;
+ else if (images_.size() == 2)
+ set = bg_image_2_;
+ else
+ set = bg_image_3_;
+ SetImage(BS_NORMAL, set->normal_image);
+ SetImage(BS_HOT, set->hot_image);
+ SetImage(BS_PUSHED, set->pushed_image);
+ SchedulePaint();
+}
+
+void TabbedLauncherButton::OnPaint(gfx::Canvas* canvas) {
+ ImageButton::OnPaint(canvas);
+
+ hover_controller_.Draw(canvas, *bg_image_1_->normal_image);
+
+ if (images_.empty() || images_[0].image.empty() || !show_image_)
+ return;
+
+ bool save_layer = (animation_.get() && animation_->is_animating() &&
+ animation_->current_part_index() == 1);
+ if (save_layer)
+ canvas->SaveLayerAlpha(animation_->CurrentValueBetween(255, 0));
+
+ // Only show the first icon.
+ // TODO(sky): if we settle on just 1 icon, then we should simplify surrounding
+ // code (don't use a vector of images).
+ int x = (width() - images_[0].image.width()) / 2;
+ int y = (height() - images_[0].image.height()) / 2 + 1;
+ canvas->DrawBitmapInt(images_[0].image, x, y);
+
+ if (save_layer)
+ canvas->Restore();
+}
+
+bool TabbedLauncherButton::OnMousePressed(const views::MouseEvent& event) {
+ ImageButton::OnMousePressed(event);
+ host_->MousePressedOnButton(this, event);
+ hover_controller_.HideImmediately();
+ return true;
+}
+
+void TabbedLauncherButton::OnMouseReleased(const views::MouseEvent& event) {
+ host_->MouseReleasedOnButton(this, false);
+ ImageButton::OnMouseReleased(event);
+ hover_controller_.Show();
+}
+
+void TabbedLauncherButton::OnMouseCaptureLost() {
+ host_->MouseReleasedOnButton(this, true);
+ ImageButton::OnMouseCaptureLost();
+ hover_controller_.Hide();
+}
+
+bool TabbedLauncherButton::OnMouseDragged(const views::MouseEvent& event) {
+ ImageButton::OnMouseDragged(event);
+ host_->MouseDraggedOnButton(this, event);
+ return true;
+}
+
+void TabbedLauncherButton::OnMouseEntered(const views::MouseEvent& event) {
+ ImageButton::OnMouseEntered(event);
+ hover_controller_.Show();
+}
+
+void TabbedLauncherButton::OnMouseMoved(const views::MouseEvent& event) {
+ ImageButton::OnMouseMoved(event);
+ hover_controller_.SetLocation(event.location());
+}
+
+void TabbedLauncherButton::OnMouseExited(const views::MouseEvent& event) {
+ ImageButton::OnMouseExited(event);
+ hover_controller_.Hide();
+}
+
+// static
+TabbedLauncherButton::ImageSet* TabbedLauncherButton::CreateImageSet(
+ int normal_id,
+ int pushed_id,
+ int hot_id) {
+ ImageSet* set = new ImageSet;
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ set->normal_image = new SkBitmap(*rb.GetImageNamed(normal_id).ToSkBitmap());
+ set->pushed_image = new SkBitmap(*rb.GetImageNamed(pushed_id).ToSkBitmap());
+ set->hot_image = new SkBitmap(*rb.GetImageNamed(hot_id).ToSkBitmap());
+ return set;
+}
+
+} // namespace internal
+} // namespace aura_shell
diff --git a/ash/launcher/tabbed_launcher_button.h b/ash/launcher/tabbed_launcher_button.h
new file mode 100644
index 0000000..5e9dacd
--- /dev/null
+++ b/ash/launcher/tabbed_launcher_button.h
@@ -0,0 +1,106 @@
+// 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 ASH_LAUNCHER_TABBED_LAUNCHER_BUTTON_H_
+#define ASH_LAUNCHER_TABBED_LAUNCHER_BUTTON_H_
+#pragma once
+
+#include "ash/launcher/launcher_types.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/glow_hover_controller.h"
+
+namespace ui {
+class MultiAnimation;
+}
+
+namespace aura_shell {
+namespace internal {
+
+class LauncherButtonHost;
+
+// Button used for items on the launcher corresponding to tabbed windows.
+class TabbedLauncherButton : public views::ImageButton {
+ public:
+ TabbedLauncherButton(views::ButtonListener* listener,
+ LauncherButtonHost* host);
+ virtual ~TabbedLauncherButton();
+
+ // Notification that the images are about to change. Kicks off an animation.
+ void PrepareForImageChange();
+
+ // Sets the images to display for this entry.
+ void SetImages(const LauncherTabbedImages& images);
+
+ protected:
+ // View overrides:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual bool OnMouseDragged(const views::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseEntered(const views::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseMoved(const views::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const views::MouseEvent& event) OVERRIDE;
+
+ private:
+ // Used as the delegate for |animation_|. TabbedLauncherButton doesn't
+ // directly implement AnimationDelegate as one of it's superclasses already
+ // does.
+ class AnimationDelegateImpl : public ui::AnimationDelegate {
+ public:
+ explicit AnimationDelegateImpl(TabbedLauncherButton* host);
+ virtual ~AnimationDelegateImpl();
+
+ // ui::AnimationDelegateImpl overrides:
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ private:
+ TabbedLauncherButton* host_;
+
+ DISALLOW_COPY_AND_ASSIGN(AnimationDelegateImpl);
+ };
+
+ struct ImageSet {
+ SkBitmap* normal_image;
+ SkBitmap* pushed_image;
+ SkBitmap* hot_image;
+ };
+
+ // Creates an ImageSet using the specified image ids. Caller owns the returned
+ // value.
+ static ImageSet* CreateImageSet(int normal_id, int pushed_id, int hot_id);
+
+ LauncherTabbedImages images_;
+
+ LauncherButtonHost* host_;
+
+ // Delegate of |animation_|.
+ AnimationDelegateImpl animation_delegate_;
+
+ // Used to animate image.
+ scoped_ptr<ui::MultiAnimation> animation_;
+
+ // Should |images_| be shown? This is set to false soon after
+ // PrepareForImageChange() is invoked without a following call to SetImages().
+ bool show_image_;
+
+ // Background images. Which one is chosen depends upon how many images are
+ // provided.
+ static ImageSet* bg_image_1_;
+ static ImageSet* bg_image_2_;
+ static ImageSet* bg_image_3_;
+
+ views::GlowHoverController hover_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabbedLauncherButton);
+};
+
+} // namespace internal
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_TABBED_LAUNCHER_BUTTON_H_
diff --git a/ash/launcher/view_model.cc b/ash/launcher/view_model.cc
new file mode 100644
index 0000000..668f16f
--- /dev/null
+++ b/ash/launcher/view_model.cc
@@ -0,0 +1,55 @@
+// 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 "ash/launcher/view_model.h"
+
+#include "base/logging.h"
+#include "ui/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::Move(int index, int target_index) {
+ if (index == target_index)
+ return;
+ Entry entry(entries_[index]);
+ entries_.erase(entries_.begin() + index);
+ entries_.insert(entries_.begin() + target_index, entry);
+}
+
+void ViewModel::Clear() {
+ Entries entries;
+ entries.swap(entries_);
+ for (size_t i = 0; i < entries.size(); ++i)
+ delete entries[i].view;
+}
+
+int ViewModel::GetIndexOfView(views::View* view) const {
+ 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/ash/launcher/view_model.h b/ash/launcher/view_model.h
new file mode 100644
index 0000000..0e81c99
--- /dev/null
+++ b/ash/launcher/view_model.h
@@ -0,0 +1,82 @@
+// 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 ASH_LAUNCHER_VIEW_MODEL_H_
+#define ASH_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);
+
+ // Moves the view at |index| to |target_index|. |target_index| is in terms
+ // of the model *after* the view at |index| is removed.
+ void Move(int index, int target_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) const {
+ 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;
+ }
+
+ // Returns the index of the specified view, or -1 if the view isn't in the
+ // model.
+ int GetIndexOfView(views::View* view) const;
+
+ 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 // ASH_LAUNCHER_VIEW_MODEL_H_
diff --git a/ash/launcher/view_model_unittest.cc b/ash/launcher/view_model_unittest.cc
new file mode 100644
index 0000000..17ebd23
--- /dev/null
+++ b/ash/launcher/view_model_unittest.cc
@@ -0,0 +1,41 @@
+// 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 "ash/launcher/view_model.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/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));
+}
+
+TEST(ViewModel, Move) {
+ views::View v1, v2, v3;
+ ViewModel model;
+ model.Add(&v1, 0);
+ model.Add(&v2, 1);
+ model.Add(&v3, 2);
+ model.Move(0, 2);
+ EXPECT_EQ(&v1, model.view_at(2));
+ EXPECT_EQ(&v2, model.view_at(0));
+ EXPECT_EQ(&v3, model.view_at(1));
+
+ model.Move(2, 0);
+ EXPECT_EQ(&v1, model.view_at(0));
+ EXPECT_EQ(&v2, model.view_at(1));
+ EXPECT_EQ(&v3, model.view_at(2));
+}
+
+} // namespace aura_shell
diff --git a/ash/launcher/view_model_utils.cc b/ash/launcher/view_model_utils.cc
new file mode 100644
index 0000000..e17370e
--- /dev/null
+++ b/ash/launcher/view_model_utils.cc
@@ -0,0 +1,48 @@
+// 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 "ash/launcher/view_model_utils.h"
+
+#include <algorithm>
+
+#include "ash/launcher/view_model.h"
+#include "ui/views/view.h"
+
+namespace aura_shell {
+
+// static
+void ViewModelUtils::SetViewBoundsToIdealBounds(const ViewModel& model) {
+ for (int i = 0; i < model.view_size(); ++i)
+ model.view_at(i)->SetBoundsRect(model.ideal_bounds(i));
+}
+
+// static
+int ViewModelUtils::DetermineMoveIndex(const ViewModel& model,
+ views::View* view,
+ int x) {
+ int current_index = model.GetIndexOfView(view);
+ DCHECK_NE(-1, current_index);
+ for (int i = 0; i < current_index; ++i) {
+ int mid_x = model.ideal_bounds(i).x() + model.ideal_bounds(i).width() / 2;
+ if (x < mid_x)
+ return i;
+ }
+
+ if (current_index + 1 == model.view_size())
+ return current_index;
+
+ // For indices after the current index ignore the bounds of the view being
+ // dragged. This keeps the view from bouncing around as moved.
+ int delta = model.ideal_bounds(current_index + 1).x() -
+ model.ideal_bounds(current_index).x();
+ for (int i = current_index + 1; i < model.view_size(); ++i) {
+ const gfx::Rect& bounds(model.ideal_bounds(i));
+ int mid_x = bounds.x() + bounds.width() / 2 - delta;
+ if (x < mid_x)
+ return i - 1;
+ }
+ return model.view_size() - 1;
+}
+
+} // namespace aura_shell
diff --git a/ash/launcher/view_model_utils.h b/ash/launcher/view_model_utils.h
new file mode 100644
index 0000000..d6d9a9b
--- /dev/null
+++ b/ash/launcher/view_model_utils.h
@@ -0,0 +1,36 @@
+// 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 ASH_LAUNCHER_VIEW_MODEL_UTILS_H_
+#define ASH_LAUNCHER_VIEW_MODEL_UTILS_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "ui/aura_shell/aura_shell_export.h"
+
+namespace views {
+class View;
+}
+
+namespace aura_shell {
+
+class ViewModel;
+
+class AURA_SHELL_EXPORT ViewModelUtils {
+ public:
+ // Sets the bounds of each view to its ideal bounds.
+ static void SetViewBoundsToIdealBounds(const ViewModel& model);
+
+ // Returns the index to move |view| to based on a x-coordinate of |x|.
+ static int DetermineMoveIndex(const ViewModel& model,
+ views::View* view,
+ int x);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ViewModelUtils);
+};
+
+} // namespace aura_shell
+
+#endif // ASH_LAUNCHER_VIEW_MODEL_UTILS_H_
diff --git a/ash/launcher/view_model_utils_unittest.cc b/ash/launcher/view_model_utils_unittest.cc
new file mode 100644
index 0000000..a1486d8
--- /dev/null
+++ b/ash/launcher/view_model_utils_unittest.cc
@@ -0,0 +1,46 @@
+// 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 "ash/launcher/view_model_utils.h"
+
+#include "ash/launcher/view_model.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/view.h"
+
+namespace aura_shell {
+
+// Makes sure SetViewBoundsToIdealBounds updates the view appropriately.
+TEST(ViewModelUtils, SetViewBoundsToIdealBounds) {
+ views::View v1;
+ ViewModel model;
+ model.Add(&v1, 0);
+ gfx::Rect v1_bounds(1, 2, 3, 4);
+ model.set_ideal_bounds(0, v1_bounds);
+ ViewModelUtils::SetViewBoundsToIdealBounds(model);
+ EXPECT_EQ(v1_bounds, v1.bounds());
+}
+
+// Assertions for DetermineMoveIndex.
+TEST(ViewModelUtils, DetermineMoveIndex) {
+ views::View v1, v2, v3;
+ ViewModel model;
+ model.Add(&v1, 0);
+ model.Add(&v2, 1);
+ model.Add(&v3, 2);
+ model.set_ideal_bounds(0, gfx::Rect(0, 0, 10, 10));
+ model.set_ideal_bounds(1, gfx::Rect(10, 0, 1000, 10));
+ model.set_ideal_bounds(2, gfx::Rect(1010, 0, 2, 10));
+
+ EXPECT_EQ(0, ViewModelUtils::DetermineMoveIndex(model, &v1, -10));
+ EXPECT_EQ(0, ViewModelUtils::DetermineMoveIndex(model, &v1, 4));
+ EXPECT_EQ(1, ViewModelUtils::DetermineMoveIndex(model, &v1, 506));
+ EXPECT_EQ(2, ViewModelUtils::DetermineMoveIndex(model, &v1, 1010));
+ EXPECT_EQ(2, ViewModelUtils::DetermineMoveIndex(model, &v1, 2000));
+
+ EXPECT_EQ(0, ViewModelUtils::DetermineMoveIndex(model, &v2, -10));
+ EXPECT_EQ(0, ViewModelUtils::DetermineMoveIndex(model, &v2, 4));
+ EXPECT_EQ(2, ViewModelUtils::DetermineMoveIndex(model, &v2, 12));
+}
+
+} // namespace aura_shell