diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-23 21:35:52 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-23 21:35:52 +0000 |
commit | 7634f96b932b670de6e34552a9e0923147da3486 (patch) | |
tree | 2d755027fd1a3038335e3a3c38680274b448cdbe /ash/launcher | |
parent | 7edb817aa768be2278f9b5bc34fda9d95cc4bdde (diff) | |
download | chromium_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.cc | 80 | ||||
-rw-r--r-- | ash/launcher/app_launcher_button.h | 42 | ||||
-rw-r--r-- | ash/launcher/launcher.cc | 183 | ||||
-rw-r--r-- | ash/launcher/launcher.h | 74 | ||||
-rw-r--r-- | ash/launcher/launcher_button_host.h | 40 | ||||
-rw-r--r-- | ash/launcher/launcher_model.cc | 87 | ||||
-rw-r--r-- | ash/launcher/launcher_model.h | 67 | ||||
-rw-r--r-- | ash/launcher/launcher_model_observer.h | 39 | ||||
-rw-r--r-- | ash/launcher/launcher_model_unittest.cc | 122 | ||||
-rw-r--r-- | ash/launcher/launcher_types.h | 67 | ||||
-rw-r--r-- | ash/launcher/launcher_unittest.cc | 31 | ||||
-rw-r--r-- | ash/launcher/launcher_view.cc | 597 | ||||
-rw-r--r-- | ash/launcher/launcher_view.h | 150 | ||||
-rw-r--r-- | ash/launcher/tabbed_launcher_button.cc | 196 | ||||
-rw-r--r-- | ash/launcher/tabbed_launcher_button.h | 106 | ||||
-rw-r--r-- | ash/launcher/view_model.cc | 55 | ||||
-rw-r--r-- | ash/launcher/view_model.h | 82 | ||||
-rw-r--r-- | ash/launcher/view_model_unittest.cc | 41 | ||||
-rw-r--r-- | ash/launcher/view_model_utils.cc | 48 | ||||
-rw-r--r-- | ash/launcher/view_model_utils.h | 36 | ||||
-rw-r--r-- | ash/launcher/view_model_utils_unittest.cc | 46 |
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 |