diff options
Diffstat (limited to 'ash/launcher/launcher_view.cc')
-rw-r--r-- | ash/launcher/launcher_view.cc | 597 |
1 files changed, 597 insertions, 0 deletions
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 |