diff options
Diffstat (limited to 'ash')
56 files changed, 4634 insertions, 11 deletions
diff --git a/ash/app_list/app_list.cc b/ash/app_list/app_list.cc new file mode 100644 index 0000000..4c8de88 --- /dev/null +++ b/ash/app_list/app_list.cc @@ -0,0 +1,195 @@ +// 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/app_list/app_list.h" + +#include "ash/app_list/app_list_model.h" +#include "ash/app_list/app_list_view.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "ui/aura/event.h" +#include "ui/aura/window.h" +#include "ui/aura_shell/aura_shell_switches.h" +#include "ui/aura_shell/shell_delegate.h" +#include "ui/aura_shell/shell.h" +#include "ui/aura_shell/shell_window_ids.h" +#include "ui/gfx/screen.h" + +namespace aura_shell { +namespace internal { + +namespace { + +// Gets preferred bounds of app list window in show/hide state. +gfx::Rect GetPreferredBounds(bool show) { + // The y-axis offset used at the beginning of showing animation. + static const int kMoveUpAnimationOffset = 50; + + gfx::Point cursor = gfx::Screen::GetCursorScreenPoint(); + gfx::Rect work_area = gfx::Screen::GetMonitorWorkAreaNearestPoint(cursor); + gfx::Rect widget_bounds(work_area); + widget_bounds.Inset(100, 100); + if (!show) + widget_bounds.Offset(0, kMoveUpAnimationOffset); + + return widget_bounds; +} + +ui::Layer* GetLayer(views::Widget* widget) { + return widget->GetNativeView()->layer(); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// AppList, public: + +AppList::AppList() + : aura::EventFilter(NULL), + is_visible_(false), + widget_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(set_widget_factory_(this)) { +} + +AppList::~AppList() { + ResetWidget(); +} + +void AppList::SetVisible(bool visible) { + if (visible == is_visible_) + return; + + is_visible_ = visible; + + if (widget_) { + ScheduleAnimation(); + } else if (is_visible_ && !set_widget_factory_.HasWeakPtrs()) { + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAuraViewsAppList)) { + scoped_ptr<AppListModel> model(new AppListModel); + Shell::GetInstance()->delegate()->BuildAppListModel(model.get()); + + // AppListModel and AppListViewDelegate are owned by AppListView. They + // will be released with AppListView on close. + new AppListView( + model.release(), + Shell::GetInstance()->delegate()->CreateAppListViewDelegate(), + GetPreferredBounds(false), + base::Bind(&AppList::SetWidget, set_widget_factory_.GetWeakPtr())); + } else { + Shell::GetInstance()->delegate()->RequestAppListWidget( + GetPreferredBounds(false), + base::Bind(&AppList::SetWidget, set_widget_factory_.GetWeakPtr())); + } + } +} + +bool AppList::IsVisible() { + return widget_ && widget_->IsVisible(); +} + +//////////////////////////////////////////////////////////////////////////////// +// AppList, private: + +void AppList::SetWidget(views::Widget* widget) { + DCHECK(widget_ == NULL); + set_widget_factory_.InvalidateWeakPtrs(); + + if (is_visible_) { + widget_ = widget; + widget_->AddObserver(this); + GetLayer(widget_)->GetAnimator()->AddObserver(this); + Shell::GetInstance()->AddRootWindowEventFilter(this); + + widget_->SetBounds(GetPreferredBounds(false)); + widget_->SetOpacity(0); + ScheduleAnimation(); + + widget_->Show(); + } else { + widget->Close(); + } +} + +void AppList::ResetWidget() { + if (!widget_) + return; + + widget_->RemoveObserver(this); + GetLayer(widget_)->GetAnimator()->RemoveObserver(this); + Shell::GetInstance()->RemoveRootWindowEventFilter(this); + widget_ = NULL; +} + +void AppList::ScheduleAnimation() { + ui::Layer* layer = GetLayer(widget_); + ui::LayerAnimator::ScopedSettings app_list_animation(layer->GetAnimator()); + layer->SetBounds(GetPreferredBounds(is_visible_)); + layer->SetOpacity(is_visible_ ? 1.0 : 0.0); + + ui::Layer* default_container_layer = Shell::GetInstance()->GetContainer( + internal::kShellWindowId_DefaultContainer)->layer(); + ui::LayerAnimator::ScopedSettings default_container_animation( + default_container_layer->GetAnimator()); + default_container_layer->SetOpacity(is_visible_ ? 0.0 : 1.0); +} + +//////////////////////////////////////////////////////////////////////////////// +// AppList, aura::EventFilter implementation: + +bool AppList::PreHandleKeyEvent(aura::Window* target, + aura::KeyEvent* event) { + return false; +} + +bool AppList::PreHandleMouseEvent(aura::Window* target, + aura::MouseEvent* event) { + if (widget_ && is_visible_ && event->type() == ui::ET_MOUSE_PRESSED) { + aura::MouseEvent translated(*event, target, widget_->GetNativeView()); + if (!widget_->GetNativeView()->ContainsPoint(translated.location())) + SetVisible(false); + } + return false; +} + +ui::TouchStatus AppList::PreHandleTouchEvent(aura::Window* target, + aura::TouchEvent* event) { + return ui::TOUCH_STATUS_UNKNOWN; +} + +//////////////////////////////////////////////////////////////////////////////// +// AppList, ui::LayerAnimationObserver implementation: + +void AppList::OnLayerAnimationEnded( + const ui::LayerAnimationSequence* sequence) { + if (!is_visible_ ) + widget_->Close(); +} + +void AppList::OnLayerAnimationAborted( + const ui::LayerAnimationSequence* sequence) { +} + +void AppList::OnLayerAnimationScheduled( + const ui::LayerAnimationSequence* sequence) { +} + +//////////////////////////////////////////////////////////////////////////////// +// AppList, views::Widget::Observer implementation: + +void AppList::OnWidgetClosing(views::Widget* widget) { + DCHECK(widget_ == widget); + if (is_visible_) + SetVisible(false); + ResetWidget(); +} + +void AppList::OnWidgetActivationChanged(views::Widget* widget, bool active) { + DCHECK(widget_ == widget); + if (widget_ && is_visible_ && !active) + SetVisible(false); +} + +} // namespace internal +} // namespace aura_shell diff --git a/ash/app_list/app_list.h b/ash/app_list/app_list.h new file mode 100644 index 0000000..114a3fb --- /dev/null +++ b/ash/app_list/app_list.h @@ -0,0 +1,83 @@ +// 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_APP_LIST_APP_LIST_H_ +#define ASH_APP_LIST_APP_LIST_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "ui/aura/event_filter.h" +#include "ui/gfx/compositor/layer_animation_observer.h" +#include "ui/views/widget/widget.h" + +namespace aura_shell { +namespace internal { + +// AppList is a controller that manages app list UI for shell. To show the UI, +// it requests app list widget from ShellDelegate and shows it when ready. +// While the UI is visible, it monitors things such as app list widget's +// activation state and desktop mouse click to auto dismiss the UI. +class AppList : public aura::EventFilter, + public ui::LayerAnimationObserver, + public views::Widget::Observer { + public: + AppList(); + virtual ~AppList(); + + // Show/hide app list window. + void SetVisible(bool visible); + + // Whether app list window is visible (shown or being shown). + bool IsVisible(); + + private: + // Sets app list widget. If we are in visible mode, start showing animation. + // Otherwise, we just close the widget. + void SetWidget(views::Widget* widget); + + // Forgets the widget. + void ResetWidget(); + + // Starts show/hide animation. + void ScheduleAnimation(); + + // aura::EventFilter overrides: + virtual bool PreHandleKeyEvent(aura::Window* target, + aura::KeyEvent* event) OVERRIDE; + virtual bool PreHandleMouseEvent(aura::Window* target, + aura::MouseEvent* event) OVERRIDE; + virtual ui::TouchStatus PreHandleTouchEvent(aura::Window* target, + aura::TouchEvent* event) OVERRIDE; + + // ui::LayerAnimationObserver overrides: + virtual void OnLayerAnimationEnded( + const ui::LayerAnimationSequence* sequence) OVERRIDE; + virtual void OnLayerAnimationAborted( + const ui::LayerAnimationSequence* sequence) OVERRIDE; + virtual void OnLayerAnimationScheduled( + const ui::LayerAnimationSequence* sequence) OVERRIDE; + + // views::Widget::Observer overrides: + virtual void OnWidgetClosing(views::Widget* widget) OVERRIDE; + virtual void OnWidgetActivationChanged(views::Widget* widget, + bool active) OVERRIDE; + + // Whether we should show or hide app list widget. + bool is_visible_; + + // App list widget we get from ShellDelegate. + views::Widget* widget_; + + // A weak ptr factory for callbacks that ShellDelegate used to set widget. + base::WeakPtrFactory<AppList> set_widget_factory_; + + DISALLOW_COPY_AND_ASSIGN(AppList); +}; + +} // namespace internal +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_H_ diff --git a/ash/app_list/app_list_groups_view.cc b/ash/app_list/app_list_groups_view.cc new file mode 100644 index 0000000..bd56859 --- /dev/null +++ b/ash/app_list/app_list_groups_view.cc @@ -0,0 +1,243 @@ +// 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/app_list/app_list_groups_view.h" + +#include "ash/app_list/app_list_item_group_model.h" +#include "ash/app_list/app_list_item_group_view.h" +#include "ash/app_list/app_list_item_view.h" +#include "ash/app_list/app_list_model.h" +#include "base/utf_string_conversions.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/views/animation/bounds_animator.h" +#include "ui/views/controls/button/text_button.h" +#include "ui/views/layout/box_layout.h" + +namespace aura_shell { + +namespace { + +const SkColor kPageHeaderColor = SkColorSetARGB(0xFF, 0xB2, 0xB2, 0xB2); +const SkColor kSelectedPageHeaderColor = SK_ColorWHITE; + +// Creates page headers view that hosts page title buttons. +views::View* CreatePageHeader() { + views::View* header = new views::View; + header->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); + return header; +} + +// Creates page header button view that shows page title. +views::View* CreatePageHeaderButton(views::ButtonListener* listener, + const std::string& title ) { + views::TextButton* button = new views::TextButton(listener, + UTF8ToUTF16(title)); + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + button->SetFont(rb.GetFont(ResourceBundle::BaseFont).DeriveFont( + 2, gfx::Font::BOLD)); + button->SetEnabledColor(kPageHeaderColor); + return button; +} + +// Gets preferred bounds of buttons and page. +void GetPageAndHeaderBounds(views::View* parent, + views::View* buttons, + views::View* page, + gfx::Rect* buttons_bounds, + gfx::Rect* page_bounds) { + gfx::Rect content_bounds = parent->GetContentsBounds(); + + if (buttons) { + gfx::Size buttons_size = buttons->GetPreferredSize(); + if (buttons_bounds) { + buttons_bounds->SetRect( + (content_bounds.width() - buttons_size.width()) / 2, + content_bounds.bottom() - buttons_size.height(), + buttons_size.width(), buttons_size.height()); + } + + content_bounds.set_height( + std::max(0, content_bounds.height() - buttons_size.height())); + } + + if (page_bounds) { + gfx::Size page_size = page->GetPreferredSize(); + *page_bounds = content_bounds.Center(page_size); + } +} + +} // namespace + +AppListGroupsView::AppListGroupsView(AppListModel* model, + AppListItemViewListener* listener) + : model_(model), + listener_(listener), + page_buttons_(NULL), + current_page_(0) { + animator_.reset(new views::BoundsAnimator(this)); + model_->AddObserver(this); + Update(); +} + +AppListGroupsView::~AppListGroupsView() { + model_->RemoveObserver(this); +} + +views::View* AppListGroupsView::GetFocusedTile() const { + AppListItemGroupView* page = GetCurrentPageView(); + return page ? page->GetFocusedTile() : NULL; +} + +void AppListGroupsView::Update() { + current_page_ = 0; + page_buttons_ = NULL; + RemoveAllChildViews(true); + + int page_count = model_->group_count(); + if (!page_count) + return; + + if (page_count > 1) + AddChildView(page_buttons_ = CreatePageHeader()); + + for (int i = 0; i < page_count; ++i) { + AppListItemGroupModel* group = model_->GetGroup(i); + AddPage(group->title(), new AppListItemGroupView(group, listener_)); + } + + if (!size().IsEmpty()) + Layout(); + SetCurrentPage(0); +} + +void AppListGroupsView::AddPage(const std::string& title, + AppListItemGroupView* page) { + pages_.push_back(page); + AddChildView(page); + + if (page_buttons_) + page_buttons_->AddChildView(CreatePageHeaderButton(this, title)); +} + +int AppListGroupsView::GetPreferredTilesPerRow() const { + return std::max(1, width() / AppListItemView::kTileSize); +} + +AppListItemGroupView* AppListGroupsView::GetCurrentPageView() const { + return static_cast<size_t>(current_page_) < pages_.size() ? + pages_[current_page_] : NULL; +} + +void AppListGroupsView::SetCurrentPage(int page) { + int previous_page = current_page_; + current_page_ = page; + + // Updates page buttons. + if (page_buttons_) { + for (int i = 0; i < page_buttons_->child_count(); ++i) { + views::TextButton* button = static_cast<views::TextButton*>( + page_buttons_->child_at(i)); + + button->SetEnabledColor(i == current_page_ ? + kSelectedPageHeaderColor : kPageHeaderColor); + } + page_buttons_->SchedulePaint(); + } + + // Gets sliding animation direction. + int dir = previous_page < current_page_ ? -1 : + previous_page > current_page_ ? 1 : 0; + + // Skips animation if no sliding needed or no valid size. + if (dir == 0 || size().IsEmpty()) + return; + + animator_->Cancel(); + + // Makes sure new page has correct layout and focus to its focused tile. + AppListItemGroupView* current_view = GetCurrentPageView(); + current_view->SetTilesPerRow(GetPreferredTilesPerRow()); + views::View* tile = current_view->GetFocusedTile(); + if (tile) + tile->RequestFocus(); + + // Prepares current page before animation. + gfx::Rect current_page_bounds; + GetPageAndHeaderBounds(this, page_buttons_, current_view, + NULL, ¤t_page_bounds); + current_page_bounds.Offset(- dir * width(), 0); + current_view->SetBoundsRect(current_page_bounds); + + // Schedules animations to slide out previous page and slide in current page. + AppListItemGroupView* previous_view = pages_[previous_page]; + gfx::Rect previous_page_bounds = previous_view->bounds(); + previous_page_bounds.Offset(dir * width(), 0); + animator_->AnimateViewTo(previous_view, previous_page_bounds); + + current_page_bounds.Offset(dir * width(), 0); + animator_->AnimateViewTo(current_view, current_page_bounds); +} + +void AppListGroupsView::Layout() { + AppListItemGroupView* page = GetCurrentPageView(); + if (!page) + return; + + page->SetTilesPerRow(GetPreferredTilesPerRow()); + + gfx::Rect buttons_bounds; + gfx::Rect page_bounds; + GetPageAndHeaderBounds(this, page_buttons_, page, + &buttons_bounds, &page_bounds); + + if (page_buttons_) + page_buttons_->SetBoundsRect(buttons_bounds); + + page->SetBoundsRect(page_bounds); +} + +bool AppListGroupsView::OnKeyPressed(const views::KeyEvent& event) { + if (event.IsControlDown()) { + switch (event.key_code()) { + case ui::VKEY_LEFT: + if (current_page_ > 0) + SetCurrentPage(current_page_ - 1); + return true; + case ui::VKEY_RIGHT: + if (static_cast<size_t>(current_page_ + 1) < pages_.size()) + SetCurrentPage(current_page_ + 1); + return true; + default: + break; + } + } + + return false; +} + +void AppListGroupsView::ButtonPressed(views::Button* sender, + const views::Event& event) { + DCHECK(page_buttons_); + for (int i = 0; i < page_buttons_->child_count(); ++i) { + if (page_buttons_->child_at(i) == sender) + SetCurrentPage(i); + } +} + +void AppListGroupsView::ListItemsAdded(int start, int count) { + Update(); +} + +void AppListGroupsView::ListItemsRemoved(int start, int count) { + Update(); +} + +void AppListGroupsView::ListItemsChanged(int start, int count) { + NOTREACHED(); +} + +} // namespace aura_shell diff --git a/ash/app_list/app_list_groups_view.h b/ash/app_list/app_list_groups_view.h new file mode 100644 index 0000000..4cca797 --- /dev/null +++ b/ash/app_list/app_list_groups_view.h @@ -0,0 +1,84 @@ +// 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_APP_LIST_APP_LIST_GROUPS_VIEW_H_ +#define ASH_APP_LIST_APP_LIST_GROUPS_VIEW_H_ +#pragma once + +#include <string> +#include <vector> + +#include "ui/aura_shell/aura_shell_export.h" +#include "ui/base/models/list_model_observer.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/view.h" + +namespace views { +class BoundsAnimator; +} + +namespace aura_shell { + +class AppListItemGroupView; +class AppListItemViewListener; +class AppListModel; + +// AppListGroupsView displays the UI for an AppListModel. If there are more than +// one group in the model , a button strip is displayed to allow user to switch +// between pages. +class AURA_SHELL_EXPORT AppListGroupsView : public views::View, + public views::ButtonListener, + public ui::ListModelObserver { + public: + AppListGroupsView(AppListModel* model, + AppListItemViewListener* listener); + virtual ~AppListGroupsView(); + + // Gets current focused tile. + views::View* GetFocusedTile() const; + + private: + // Updates from model. + void Update(); + + // Adds a result group page. + void AddPage(const std::string& title, AppListItemGroupView* page); + + // Gets preferred number of tiles per row. + int GetPreferredTilesPerRow() const; + + // Gets current result page. + AppListItemGroupView* GetCurrentPageView() const; + + // Sets current result page. + void SetCurrentPage(int page); + + // Overridden from views::View: + virtual void Layout() OVERRIDE; + virtual bool OnKeyPressed(const views::KeyEvent& event) OVERRIDE; + + // Overridden from views::ButtonListener: + virtual void ButtonPressed(views::Button* sender, + const views::Event& event) OVERRIDE; + + // Overridden from ListModelObserver: + virtual void ListItemsAdded(int start, int count) OVERRIDE; + virtual void ListItemsRemoved(int start, int count) OVERRIDE; + virtual void ListItemsChanged(int start, int count) OVERRIDE; + + AppListModel* model_; // Owned by parent AppListView. + AppListItemViewListener* listener_; + + std::vector<AppListItemGroupView*> pages_; + views::View* page_buttons_; + int current_page_; + + scoped_ptr<views::BoundsAnimator> animator_; + + DISALLOW_COPY_AND_ASSIGN(AppListGroupsView); +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_GROUPS_VIEW_H_ diff --git a/ash/app_list/app_list_item_group_model.cc b/ash/app_list/app_list_item_group_model.cc new file mode 100644 index 0000000..0bf3974 --- /dev/null +++ b/ash/app_list/app_list_item_group_model.cc @@ -0,0 +1,25 @@ +// 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/app_list/app_list_item_group_model.h" + +namespace aura_shell { + +AppListItemGroupModel::AppListItemGroupModel(const std::string& title) + : title_(title) { +} + +AppListItemGroupModel::~AppListItemGroupModel() { +} + +void AppListItemGroupModel::AddItem(AppListItemModel* item) { + items_.Add(item); +} + +AppListItemModel* AppListItemGroupModel::GetItem(int index) { + DCHECK(index >= 0 && index < item_count()); + return items_.item_at(index); +} + +} // namespace aura_shell diff --git a/ash/app_list/app_list_item_group_model.h b/ash/app_list/app_list_item_group_model.h new file mode 100644 index 0000000..985016c --- /dev/null +++ b/ash/app_list/app_list_item_group_model.h @@ -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. + +#ifndef ASH_APP_LIST_APP_LIST_ITEM_GROUP_MODEL_H_ +#define ASH_APP_LIST_APP_LIST_ITEM_GROUP_MODEL_H_ +#pragma once + +#include <string> + +#include "ash/app_list/app_list_item_model.h" +#include "base/basictypes.h" +#include "ui/aura_shell/aura_shell_export.h" +#include "ui/base/models/list_model.h" + +namespace aura_shell { + +// AppListItemGroupModel holds a list of AppListItemModels. +class AURA_SHELL_EXPORT AppListItemGroupModel { + public: + typedef ui::ListModel<AppListItemModel> Items; + + explicit AppListItemGroupModel(const std::string& title); + virtual ~AppListItemGroupModel(); + + void AddItem(AppListItemModel* item); + AppListItemModel* GetItem(int index); + + const std::string& title() const { + return title_; + } + + int item_count() const { + return items_.item_count(); + } + + private: + const std::string title_; + Items items_; + + DISALLOW_COPY_AND_ASSIGN(AppListItemGroupModel); +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_ITEM_GROUP_MODEL_H_ diff --git a/ash/app_list/app_list_item_group_view.cc b/ash/app_list/app_list_item_group_view.cc new file mode 100644 index 0000000..a316c08 --- /dev/null +++ b/ash/app_list/app_list_item_group_view.cc @@ -0,0 +1,115 @@ +// 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/app_list/app_list_item_group_view.h" + +#include "ash/app_list/app_list_item_group_model.h" +#include "ash/app_list/app_list_item_view.h" +#include "ui/views/layout/grid_layout.h" + +namespace aura_shell { + +AppListItemGroupView::AppListItemGroupView(AppListItemGroupModel* model, + AppListItemViewListener* listener) + : model_(model), + listener_(listener), + tiles_per_row_(0), + focused_index_(0) { + Update(); +} + +AppListItemGroupView::~AppListItemGroupView() { +} + +void AppListItemGroupView::SetTilesPerRow(int tiles_per_row) { + if (tiles_per_row_ == tiles_per_row) + return; + + tiles_per_row_ = tiles_per_row; + Update(); +} + +void AppListItemGroupView::Update() { + RemoveAllChildViews(true); + if (model_->item_count() == 0 || tiles_per_row_ == 0) + return; + + views::GridLayout* layout = new views::GridLayout(this); + SetLayoutManager(layout); + + const int kTileColumnSetId = 0; + views::ColumnSet* column_set = layout->AddColumnSet(kTileColumnSetId); + for (int i = 0; i < tiles_per_row_; ++i) { + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, + views::GridLayout::USE_PREF, 0, 0); + } + + for (int i = 0; i < model_->item_count(); ++i) { + if (i % tiles_per_row_ == 0) + layout->StartRow(0, kTileColumnSetId); + + layout->AddView(new AppListItemView(model_->GetItem(i), listener_)); + } +} + +views::View* AppListItemGroupView::GetFocusedTile() { + return focused_index_ < child_count() ? child_at(focused_index_) : NULL; +} + +void AppListItemGroupView::UpdateFocusedTile(views::View* tile) { + for (int i = 0; i < child_count(); ++i) { + if (child_at(i) == tile) { + focused_index_ = i; + break; + } + } +} + +void AppListItemGroupView::SetFocusedTileByIndex(int index) { + index = std::max(0, std::min(child_count() - 1, index)); + if (index != focused_index_) + child_at(index)->RequestFocus(); +} + +bool AppListItemGroupView::OnKeyPressed(const views::KeyEvent& event) { + if (!event.IsControlDown() && !event.IsShiftDown() && !event.IsAltDown()) { + // Arrow keys navigates in tile grid. + switch (event.key_code()) { + case ui::VKEY_LEFT: + if (focused_index_ > 0) + SetFocusedTileByIndex(focused_index_ - 1); + return true; + case ui::VKEY_RIGHT: + if (focused_index_ + 1 < child_count()) + SetFocusedTileByIndex(focused_index_ + 1); + return true; + case ui::VKEY_UP: + if (focused_index_ - tiles_per_row_ >= 0) + SetFocusedTileByIndex(focused_index_ - tiles_per_row_); + return true; + case ui::VKEY_DOWN: + if (focused_index_ + tiles_per_row_ < child_count()) + SetFocusedTileByIndex(focused_index_ + tiles_per_row_); + return true; + default: + break; + } + } + + return false; +} + +void AppListItemGroupView::ListItemsAdded(int start, int count) { + Update(); +} + +void AppListItemGroupView::ListItemsRemoved(int start, int count) { + Update(); +} + +void AppListItemGroupView::ListItemsChanged(int start, int count) { + NOTREACHED(); +} + +} // namespace aura_shell diff --git a/ash/app_list/app_list_item_group_view.h b/ash/app_list/app_list_item_group_view.h new file mode 100644 index 0000000..f2b5d19 --- /dev/null +++ b/ash/app_list/app_list_item_group_view.h @@ -0,0 +1,65 @@ +// 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_APP_LIST_APP_LIST_ITEM_GROUP_VIEW_H_ +#define ASH_APP_LIST_APP_LIST_ITEM_GROUP_VIEW_H_ +#pragma once + +#include "ui/aura_shell/aura_shell_export.h" +#include "ui/base/models/list_model_observer.h" +#include "ui/views/view.h" + +namespace aura_shell { + +class AppListItemGroupModel; +class AppListItemViewListener; + +// AppListItemGroupView displays its children tiles in a grid. +class AURA_SHELL_EXPORT AppListItemGroupView + : public views::View, + public ui::ListModelObserver { + public: + AppListItemGroupView(AppListItemGroupModel* model, + AppListItemViewListener* listener); + virtual ~AppListItemGroupView(); + + // Sets tiles per row. + void SetTilesPerRow(int tiles_per_row); + + // Gets currently focused tile. + views::View* GetFocusedTile(); + + // Updates tiles page when a tile gets focus. + void UpdateFocusedTile(views::View* tile); + + private: + // Updates from model. + void Update(); + + // Sets focused tile by index. + void SetFocusedTileByIndex(int index); + + // Overridden from views::View: + virtual bool OnKeyPressed(const views::KeyEvent& event) OVERRIDE; + + // Overridden from ListModelObserver: + virtual void ListItemsAdded(int start, int count) OVERRIDE; + virtual void ListItemsRemoved(int start, int count) OVERRIDE; + virtual void ListItemsChanged(int start, int count) OVERRIDE; + + AppListItemGroupModel* model_; + AppListItemViewListener* listener_; + + // Tiles per row. + int tiles_per_row_; + + // Index of focused tile view. + int focused_index_; + + DISALLOW_COPY_AND_ASSIGN(AppListItemGroupView); +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_ITEM_GROUP_VIEW_H_ diff --git a/ash/app_list/app_list_item_model.cc b/ash/app_list/app_list_item_model.cc new file mode 100644 index 0000000..99708a7 --- /dev/null +++ b/ash/app_list/app_list_item_model.cc @@ -0,0 +1,37 @@ +// 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/app_list/app_list_item_model.h" + +#include "ash/app_list/app_list_item_model_observer.h" + +namespace aura_shell { + +AppListItemModel::AppListItemModel() { +} + +AppListItemModel::~AppListItemModel() { +} + +void AppListItemModel::SetIcon(const SkBitmap& icon) { + icon_ = icon; + FOR_EACH_OBSERVER(AppListItemModelObserver, observers_, + ItemIconChanged()); +} + +void AppListItemModel::SetTitle(const std::string& title) { + title_ = title; + FOR_EACH_OBSERVER(AppListItemModelObserver, observers_, + ItemTitleChanged()); +} + +void AppListItemModel::AddObserver(AppListItemModelObserver* observer) { + observers_.AddObserver(observer); +} + +void AppListItemModel::RemoveObserver(AppListItemModelObserver* observer) { + observers_.RemoveObserver(observer); +} + +} // namespace aura_shell diff --git a/ash/app_list/app_list_item_model.h b/ash/app_list/app_list_item_model.h new file mode 100644 index 0000000..60a835f --- /dev/null +++ b/ash/app_list/app_list_item_model.h @@ -0,0 +1,54 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_AURA_SHELL_APP_LIST_APP_LIST_ITEM_MODEL_H_ +#define UI_AURA_SHELL_APP_LIST_APP_LIST_ITEM_MODEL_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/aura_shell/aura_shell_export.h" + +namespace aura_shell { + +class AppListItemModelObserver; + +// AppListItemModel provides icon and title to be shown in a TileView and +// action to be executed when the TileView is activated (clicked or enter +// key it hit). +class AURA_SHELL_EXPORT AppListItemModel { + public: + AppListItemModel(); + virtual ~AppListItemModel(); + + // Changes icon and title for the model. + void SetIcon(const SkBitmap& icon); + void SetTitle(const std::string& title); + + void AddObserver(AppListItemModelObserver* observer); + void RemoveObserver(AppListItemModelObserver* observer); + + const SkBitmap& icon() const { + return icon_; + } + + const std::string& title() const { + return title_; + } + + private: + SkBitmap icon_; + std::string title_; + + ObserverList<AppListItemModelObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(AppListItemModel); +}; + +} // namespace aura_shell + +#endif // #define ASH_APP_LIST_APP_LIST_ITEM_MODEL_OBSERVER_H_ diff --git a/ash/app_list/app_list_item_model_observer.h b/ash/app_list/app_list_item_model_observer.h new file mode 100644 index 0000000..d44b02c8 --- /dev/null +++ b/ash/app_list/app_list_item_model_observer.h @@ -0,0 +1,27 @@ +// 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_APP_LIST_APP_LIST_ITEM_MODEL_OBSERVER_H_ +#define ASH_APP_LIST_APP_LIST_ITEM_MODEL_OBSERVER_H_ +#pragma once + +#include "ui/aura_shell/aura_shell_export.h" + +namespace aura_shell { + +class AURA_SHELL_EXPORT AppListItemModelObserver { + public: + // Invoked after app list item's icon is changed. + virtual void ItemIconChanged() = 0; + + // Invoked after app list item's title is changed. + virtual void ItemTitleChanged() = 0; + + protected: + virtual ~AppListItemModelObserver() {} +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_ITEM_MODEL_OBSERVER_H_ diff --git a/ash/app_list/app_list_item_view.cc b/ash/app_list/app_list_item_view.cc new file mode 100644 index 0000000..8d16f07 --- /dev/null +++ b/ash/app_list/app_list_item_view.cc @@ -0,0 +1,162 @@ +// 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/app_list/app_list_item_view.h" + +#include "ash/app_list/app_list_item_group_view.h" +#include "ash/app_list/app_list_item_model.h" +#include "ash/app_list/app_list_item_view_listener.h" +#include "ash/app_list/drop_shadow_label.h" +#include "base/utf_string_conversions.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/font.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/widget/widget.h" + +namespace aura_shell { + +namespace { + +const double kFocusedScale = 1.1; + +const SkColor kTitleColor = SK_ColorWHITE; + +gfx::Font GetTitleFont() { + static gfx::Font* font = NULL; + if (!font) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + font = new gfx::Font(rb.GetFont(ResourceBundle::BaseFont).DeriveFont( + 2, gfx::Font::BOLD)); + } + return *font; +} + +} // namespace + +AppListItemView::AppListItemView(AppListItemModel* model, + AppListItemViewListener* listener) + : model_(model), + listener_(listener), + icon_(new views::ImageView), + title_(new DropShadowLabel) { + set_focusable(true); + + title_->SetFont(GetTitleFont()); + title_->SetBackgroundColor(0); + title_->SetEnabledColor(kTitleColor); + + AddChildView(icon_); + AddChildView(title_); + + ItemIconChanged(); + ItemTitleChanged(); + model_->AddObserver(this); +} + +AppListItemView::~AppListItemView() { + model_->RemoveObserver(this); +} + +void AppListItemView::NotifyActivated(int event_flags) { + if (listener_) + listener_->AppListItemActivated(this, event_flags); +} + +void AppListItemView::ItemIconChanged() { + icon_->SetImage(model_->icon()); +} + +void AppListItemView::ItemTitleChanged() { + title_->SetText(UTF8ToUTF16(model_->title())); +} + +gfx::Size AppListItemView::GetPreferredSize() { + return gfx::Size(kTileSize, kTileSize); +} + +void AppListItemView::Layout() { + gfx::Rect rect(GetContentsBounds()); + gfx::Size title_size = title_->GetPreferredSize(); + + if (!HasFocus()) { + const int horiz_padding = (kTileSize - kIconSize) / 2; + const int vert_padding = (kTileSize - title_size.height() - kIconSize) / 2; + rect.Inset(horiz_padding, vert_padding); + } + + icon_->SetBounds(rect.x(), rect.y(), + rect.width(), rect.height() - title_size.height()); + + title_->SetBounds(rect.x(), rect.bottom() - title_size.height(), + rect.width(), title_size.height()); +} + +void AppListItemView::OnFocus() { + View::OnFocus(); + + gfx::Size icon_size = icon_->GetPreferredSize(); + icon_size.set_width(icon_size.width() * kFocusedScale); + icon_size.set_height(icon_size.height() * kFocusedScale); + + gfx::Size max_size = GetPreferredSize(); + if (icon_size.width() > max_size.width() || + icon_size.height() > max_size.height()) { + double aspect = + static_cast<double>(icon_size.width()) / icon_size.height(); + + if (aspect > 1) { + icon_size.set_width(max_size.width()); + icon_size.set_height(icon_size.width() / aspect); + } else { + icon_size.set_height(max_size.height()); + icon_size.set_width(icon_size.height() * aspect); + } + } + + icon_->SetImageSize(icon_size); + Layout(); + + AppListItemGroupView* group_view = + static_cast<AppListItemGroupView*>(parent()); + group_view->UpdateFocusedTile(this); +} + +void AppListItemView::OnBlur() { + icon_->ResetImageSize(); + Layout(); + SchedulePaint(); +} + +bool AppListItemView::OnKeyPressed(const views::KeyEvent& event) { + if (event.key_code() == ui::VKEY_RETURN) { + NotifyActivated(event.flags()); + return true; + } + + return false; +} + +bool AppListItemView::OnMousePressed(const views::MouseEvent& event) { + views::View* hit_view = GetEventHandlerForPoint(event.location()); + bool hit = hit_view != this; + if (hit) + RequestFocus(); + + return hit; +} + +void AppListItemView::OnMouseReleased(const views::MouseEvent& event) { + views::View* hit_view = GetEventHandlerForPoint(event.location()); + if (hit_view != this) + NotifyActivated(event.flags()); +} + +void AppListItemView::OnPaintFocusBorder(gfx::Canvas* canvas) { + // No focus border for AppListItemView. +} + +} // namespace aura_shell diff --git a/ash/app_list/app_list_item_view.h b/ash/app_list/app_list_item_view.h new file mode 100644 index 0000000..2a9ef68 --- /dev/null +++ b/ash/app_list/app_list_item_view.h @@ -0,0 +1,72 @@ +// 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_APP_LIST_APP_LIST_ITEM_VIEW_H_ +#define ASH_APP_LIST_APP_LIST_ITEM_VIEW_H_ +#pragma once + +#include "ash/app_list/app_list_item_model_observer.h" +#include "ui/aura_shell/aura_shell_export.h" +#include "ui/views/view.h" + +class SkBitmap; + +namespace views { +class ImageView; +class Label; +} + +namespace aura_shell { + +class AppListItemModel; +class AppListItemViewListener; + +class AURA_SHELL_EXPORT AppListItemView : public views::View, + public AppListItemModelObserver { + public: + AppListItemView(AppListItemModel* model, + AppListItemViewListener* listener); + virtual ~AppListItemView(); + + AppListItemModel* model() const { + return model_; + } + + // Tile size + static const int kTileSize = 180; + + // Preferred icon size. + static const int kIconSize = 128; + + protected: + // Notifies listener when activated. + void NotifyActivated(int event_flags); + + // AppListItemModelObserver overrides: + virtual void ItemIconChanged() OVERRIDE; + virtual void ItemTitleChanged() OVERRIDE; + + // views::View overrides: + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual void Layout() OVERRIDE; + virtual void OnFocus() OVERRIDE; + virtual void OnBlur() OVERRIDE; + virtual bool OnKeyPressed(const views::KeyEvent& event) OVERRIDE; + virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE; + virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE; + virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE; + + private: + AppListItemModel* model_; + AppListItemViewListener* listener_; + + views::ImageView* icon_; + views::Label* title_; + + DISALLOW_COPY_AND_ASSIGN(AppListItemView); +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_ITEM_VIEW_H_ diff --git a/ash/app_list/app_list_item_view_listener.h b/ash/app_list/app_list_item_view_listener.h new file mode 100644 index 0000000..06dfde0 --- /dev/null +++ b/ash/app_list/app_list_item_view_listener.h @@ -0,0 +1,27 @@ +// 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_APP_LIST_APP_LIST_ITEM_VIEW_LISTENER_H_ +#define ASH_APP_LIST_APP_LIST_ITEM_VIEW_LISTENER_H_ +#pragma once + +#include "ui/aura_shell/aura_shell_export.h" + +namespace aura_shell { + +class AppListItemView; + +class AURA_SHELL_EXPORT AppListItemViewListener { + public: + // Invoked when an AppListeItemModelView is activated by click or enter key. + virtual void AppListItemActivated(AppListItemView* sender, + int event_flags) = 0; + + protected: + virtual ~AppListItemViewListener() {} +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_ITEM_VIEW_LISTENER_H_ diff --git a/ash/app_list/app_list_model.cc b/ash/app_list/app_list_model.cc new file mode 100644 index 0000000..7b49e15 --- /dev/null +++ b/ash/app_list/app_list_model.cc @@ -0,0 +1,32 @@ +// 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/app_list/app_list_model.h" + +namespace aura_shell { + +AppListModel::AppListModel() { +} + +AppListModel::~AppListModel() { +} + +void AppListModel::AddGroup(AppListItemGroupModel* group) { + groups_.Add(group); +} + +AppListItemGroupModel* AppListModel::GetGroup(int index) { + DCHECK(index >= 0 && index < group_count()); + return groups_.item_at(index); +} + +void AppListModel::AddObserver(ui::ListModelObserver* observer) { + groups_.AddObserver(observer); +} + +void AppListModel::RemoveObserver(ui::ListModelObserver* observer) { + groups_.RemoveObserver(observer); +} + +} // namespace aura_shell diff --git a/ash/app_list/app_list_model.h b/ash/app_list/app_list_model.h new file mode 100644 index 0000000..dae1235 --- /dev/null +++ b/ash/app_list/app_list_model.h @@ -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. + +#ifndef ASH_APP_LIST_APP_LIST_MODEL_H_ +#define ASH_APP_LIST_APP_LIST_MODEL_H_ +#pragma once + +#include "ash/app_list/app_list_item_group_model.h" +#include "base/basictypes.h" +#include "ui/aura_shell/aura_shell_export.h" +#include "ui/base/models/list_model.h" + +namespace aura_shell { + +// Model for AppListView. It is consisted of a list of AppListItemGroupModels, +// which in turn owns a list of AppListItemModels. +class AURA_SHELL_EXPORT AppListModel { + public: + AppListModel(); + virtual ~AppListModel(); + + void AddGroup(AppListItemGroupModel* group); + AppListItemGroupModel* GetGroup(int index); + + void AddObserver(ui::ListModelObserver* observer); + void RemoveObserver(ui::ListModelObserver* observer); + + int group_count() const { + return groups_.item_count(); + } + + private: + ui::ListModel<AppListItemGroupModel> groups_; + + DISALLOW_COPY_AND_ASSIGN(AppListModel); +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_MODEL_H_ diff --git a/ash/app_list/app_list_view.cc b/ash/app_list/app_list_view.cc new file mode 100644 index 0000000..52c78bf --- /dev/null +++ b/ash/app_list/app_list_view.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/app_list/app_list_view.h" + +#include "ash/app_list/app_list_groups_view.h" +#include "ash/app_list/app_list_item_view.h" +#include "ash/app_list/app_list_model.h" +#include "ash/app_list/app_list_view_delegate.h" +#include "ui/aura_shell/shell.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/widget/widget.h" + +namespace aura_shell { + +AppListView::AppListView( + AppListModel* model, + AppListViewDelegate* delegate, + const gfx::Rect& bounds, + const aura_shell::ShellDelegate::SetWidgetCallback& callback) + : model_(model), + delegate_(delegate) { + Init(bounds, callback); +} + +AppListView::~AppListView() { +} + +void AppListView::Close() { + if (GetWidget()->IsVisible()) + Shell::GetInstance()->ToggleAppList(); +} + +void AppListView::Init(const gfx::Rect& bounds, + const ShellDelegate::SetWidgetCallback& callback) { + SetLayoutManager(new views::FillLayout); + AppListGroupsView* groups_view = new AppListGroupsView(model_.get(), this); + AddChildView(groups_view); + + views::Widget::InitParams widget_params( + views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); + widget_params.bounds = bounds; + widget_params.delegate = this; + widget_params.keep_on_top = true; + widget_params.transparent = true; + + views::Widget* widget = new views::Widget; + widget->Init(widget_params); + widget->SetContentsView(this); + + callback.Run(widget); + if (groups_view->GetFocusedTile()) + groups_view->GetFocusedTile()->RequestFocus(); +} + +bool AppListView::OnKeyPressed(const views::KeyEvent& event) { + if (event.key_code() == ui::VKEY_ESCAPE) { + Close(); + return true; + } + + return false; +} + +bool AppListView::OnMousePressed(const views::MouseEvent& event) { + // If mouse click reaches us, this means user clicks on blank area. So close. + Close(); + + return true; +} + +void AppListView::AppListItemActivated(AppListItemView* sender, + int event_flags) { + if (delegate_.get()) + delegate_->OnAppListItemActivated(sender->model(), event_flags); + Close(); +} + +} // namespace aura_shell diff --git a/ash/app_list/app_list_view.h b/ash/app_list/app_list_view.h new file mode 100644 index 0000000..1e39a24 --- /dev/null +++ b/ash/app_list/app_list_view.h @@ -0,0 +1,61 @@ +// 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_APP_LIST_APP_LIST_VIEW_H_ +#define ASH_APP_LIST_APP_LIST_VIEW_H_ +#pragma once + +#include "ash/app_list/app_list_item_view_listener.h" +#include "base/memory/scoped_ptr.h" +#include "ui/aura_shell/aura_shell_export.h" +#include "ui/aura_shell/shell_delegate.h" +#include "ui/views/widget/widget_delegate.h" + +namespace views { +class View; +} + +namespace aura_shell { + +class AppListModel; +class AppListViewDelegate; + +// AppListView is the top-level view and controller of app list UI. It creates +// and hosts a AppListModelView and passes AppListModel to it for display. +class AURA_SHELL_EXPORT AppListView : public views::WidgetDelegateView, + public AppListItemViewListener { + public: + // Takes ownership of |model| and |delegate|. + AppListView(AppListModel* model, + AppListViewDelegate* delegate, + const gfx::Rect& bounds, + const ShellDelegate::SetWidgetCallback& callback); + virtual ~AppListView(); + + // Closes app list. + void Close(); + + private: + // Initializes the window. + void Init(const gfx::Rect& bounds, + const ShellDelegate::SetWidgetCallback& callback); + + // Overridden from views::View: + virtual bool OnKeyPressed(const views::KeyEvent& event) OVERRIDE; + virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE; + + // Overridden from AppListItemModelViewListener: + virtual void AppListItemActivated(AppListItemView* sender, + int event_flags) OVERRIDE; + + scoped_ptr<AppListModel> model_; + + scoped_ptr<AppListViewDelegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(AppListView); +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_VIEW_H_ diff --git a/ash/app_list/app_list_view_delegate.h b/ash/app_list/app_list_view_delegate.h new file mode 100644 index 0000000..7e0937a --- /dev/null +++ b/ash/app_list/app_list_view_delegate.h @@ -0,0 +1,27 @@ +// 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_APP_LIST_APP_LIST_VIEW_DELEGATE_H_ +#define ASH_APP_LIST_APP_LIST_VIEW_DELEGATE_H_ +#pragma once + +#include "ui/aura_shell/aura_shell_export.h" + +namespace aura_shell { + +class AppListItemModel; + +class AURA_SHELL_EXPORT AppListViewDelegate { + public: + // AppListView owns the delegate. + virtual ~AppListViewDelegate() {} + + // Invoked an AppListeItemModelView is activated by click or enter key. + virtual void OnAppListItemActivated(AppListItemModel* item, + int event_flags) = 0; +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_APP_LIST_VIEW_DELEGATE_H_ diff --git a/ash/app_list/drop_shadow_label.cc b/ash/app_list/drop_shadow_label.cc new file mode 100644 index 0000000..f227b06 --- /dev/null +++ b/ash/app_list/drop_shadow_label.cc @@ -0,0 +1,118 @@ +// 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/app_list/drop_shadow_label.h" + +#include "base/utf_string_conversions.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "ui/gfx/canvas_skia.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/skbitmap_operations.h" + +using views::Label; + +namespace aura_shell { + +static const int kDefaultDropShadowSize = 2; + +DropShadowLabel::DropShadowLabel() : drop_shadow_size_(kDefaultDropShadowSize) { +} + +void DropShadowLabel::SetDropShadowSize(int drop_shadow_size) { + if (drop_shadow_size != drop_shadow_size_) { + drop_shadow_size_ = drop_shadow_size; + invalidate_text_size(); + SchedulePaint(); + } +} + +void DropShadowLabel::PaintText(gfx::Canvas* canvas, + const string16& text, + const gfx::Rect& text_bounds, + int flags) { + SkColor text_color = enabled() ? enabled_color() : disabled_color(); + if (drop_shadow_size_ > 0) { + // To properly render shadow with elliding fade effect, text and shadow + // is rendered to this canvas first with elliding disable so that underlying + // code would not mix shadow color into text area because of elliding fade. + // When that is done and if we need elliding fade, an alpha mask is applied + // when transfering contents on this canvas to target canvas. + gfx::Size canvas_size(text_bounds.width() + drop_shadow_size_, + text_bounds.height() + drop_shadow_size_); + gfx::CanvasSkia text_canvas(canvas_size, false); + + const double kShadowOpacity = 0.2; + const SkColor shadow_color = + SkColorSetA(SK_ColorBLACK, kShadowOpacity * SkColorGetA(text_color)); + gfx::Size text_size = GetTextSize(); + for (int i = 0; i < drop_shadow_size_; i++) { + text_canvas.DrawStringInt(text, font(), shadow_color, i, 0, + text_size.width(), text_size.height(), + flags | gfx::Canvas::NO_ELLIPSIS); + text_canvas.DrawStringInt(text, font(), shadow_color, i, i, + text_size.width(), text_size.height(), + flags | gfx::Canvas::NO_ELLIPSIS); + text_canvas.DrawStringInt(text, font(), shadow_color, 0, i, + text_size.width(), text_size.height(), + flags | gfx::Canvas::NO_ELLIPSIS); + } + text_canvas.DrawStringInt(text, font(), text_color, 0, 0, + text_size.width(), text_size.height(), + flags | gfx::Canvas::NO_ELLIPSIS); + + const SkBitmap& text_bitmap = const_cast<SkBitmap&>( + skia::GetTopDevice(*text_canvas.sk_canvas())->accessBitmap(false)); + + if (text_size.width() > text_bounds.width() && + !(flags & gfx::Canvas::NO_ELLIPSIS)) { + // Apply an gradient alpha mask for elliding fade effect. + const double kFadeWidthFactor = 1.5; + int fade_width = std::min(text_size.width() / 2, + static_cast<int>(text_size.height() * kFadeWidthFactor)); + + const SkColor kColors[] = { SK_ColorWHITE, 0 }; + const SkScalar kPoints[] = { SkIntToScalar(0), SkIntToScalar(1) }; + SkPoint p[2]; + p[0].set(SkIntToScalar(text_bounds.width() - fade_width), + SkIntToScalar(0)); + p[1].set(SkIntToScalar(text_bounds.width()), + SkIntToScalar(0)); + SkShader* s = SkGradientShader::CreateLinear( + p, kColors, kPoints, 2, SkShader::kClamp_TileMode, NULL); + + SkPaint paint; + paint.setShader(s)->unref(); + + gfx::CanvasSkia alpha_canvas(canvas_size, false); + alpha_canvas.DrawRect(gfx::Rect(canvas_size), paint); + + const SkBitmap& alpha_bitmap = const_cast<SkBitmap&>( + skia::GetTopDevice(*alpha_canvas.sk_canvas())->accessBitmap(false)); + SkBitmap blended = SkBitmapOperations::CreateMaskedBitmap(text_bitmap, + alpha_bitmap); + canvas->DrawBitmapInt(blended, text_bounds.x(), text_bounds.y()); + } else { + canvas->DrawBitmapInt(text_bitmap, text_bounds.x(), text_bounds.y()); + } + } else { + canvas->DrawStringInt(text, font(), text_color, text_bounds.x(), + text_bounds.y(), text_bounds.width(), text_bounds.height(), flags); + } + + if (HasFocus() || paint_as_focused()) { + gfx::Rect focus_bounds = text_bounds; + focus_bounds.Inset(-Label::kFocusBorderPadding, + -Label::kFocusBorderPadding); + canvas->DrawFocusRect(focus_bounds); + } +} + +gfx::Size DropShadowLabel::GetTextSize() const { + gfx::Size text_size = Label::GetTextSize(); + text_size.SetSize(text_size.width() + drop_shadow_size_, + text_size.height() + drop_shadow_size_); + return text_size; +} + +} // namespace aura_shell diff --git a/ash/app_list/drop_shadow_label.h b/ash/app_list/drop_shadow_label.h new file mode 100644 index 0000000..cfd5d55 --- /dev/null +++ b/ash/app_list/drop_shadow_label.h @@ -0,0 +1,54 @@ +// 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_APP_LIST_DROP_SHADOW_LABEL_H_ +#define ASH_APP_LIST_DROP_SHADOW_LABEL_H_ +#pragma once + +#include "ui/gfx/font.h" +#include "ui/views/controls/label.h" + +namespace aura_shell { + +///////////////////////////////////////////////////////////////////////////// +// +// DropShadowLabel class +// +// A drop shadow label is a view subclass that can display a string +// with a drop shadow. +// +///////////////////////////////////////////////////////////////////////////// +class DropShadowLabel : public views::Label { + public: + DropShadowLabel(); + + // Sets the size of the drop shadow drawn under the text. + // Defaults to two. Note that this is a really simplistic drop + // shadow -- it gets more expensive to draw the larger it gets, + // since it simply draws more copies of the string. For instance, + // for a value of two, the string is draw seven times. In general, + // it is drawn three extra times for each increment of |size|. + void SetDropShadowSize(int size); + + // Return the size of the drop shadow in pixels. + int drop_shadow_size() const { return drop_shadow_size_; } + + // Overridden to paint the text differently from the base class. + virtual void PaintText(gfx::Canvas* canvas, + const string16& text, + const gfx::Rect& text_bounds, + int flags) OVERRIDE; + + protected: + virtual gfx::Size GetTextSize() const OVERRIDE; + + private: + int drop_shadow_size_; + + DISALLOW_COPY_AND_ASSIGN(DropShadowLabel); +}; + +} // namespace aura_shell + +#endif // ASH_APP_LIST_DROP_SHADOW_LABEL_H_ 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 diff --git a/ash/shell/app_list.cc b/ash/shell/app_list.cc index b8022ab..5f260cd 100644 --- a/ash/shell/app_list.cc +++ b/ash/shell/app_list.cc @@ -2,15 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "ash/app_list/app_list_item_group_model.h" +#include "ash/app_list/app_list_item_model.h" +#include "ash/app_list/app_list_item_view.h" +#include "ash/app_list/app_list_model.h" +#include "ash/app_list/app_list_view_delegate.h" +#include "ash/app_list/app_list_view.h" #include "ash/shell/example_factory.h" #include "ash/shell/toplevel_window.h" #include "base/basictypes.h" -#include "ui/aura_shell/app_list/app_list_item_group_model.h" -#include "ui/aura_shell/app_list/app_list_item_model.h" -#include "ui/aura_shell/app_list/app_list_item_view.h" -#include "ui/aura_shell/app_list/app_list_model.h" -#include "ui/aura_shell/app_list/app_list_view_delegate.h" -#include "ui/aura_shell/app_list/app_list_view.h" #include "ui/views/examples/examples_window.h" namespace ash { diff --git a/ash/shell/shell_main.cc b/ash/shell/shell_main.cc index fd6e064..7142406 100644 --- a/ash/shell/shell_main.cc +++ b/ash/shell/shell_main.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "ash/launcher/launcher_types.h" #include "ash/shell/example_factory.h" #include "ash/shell/toplevel_window.h" #include "ash/wm/window_util.h" @@ -11,7 +12,6 @@ #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "ui/aura/root_window.h" -#include "ui/aura_shell/launcher/launcher_types.h" #include "ui/aura_shell/shell.h" #include "ui/aura_shell/shell_delegate.h" #include "ui/aura_shell/shell_factory.h" diff --git a/ash/wm/compact_layout_manager.cc b/ash/wm/compact_layout_manager.cc new file mode 100644 index 0000000..7bd1fb2 --- /dev/null +++ b/ash/wm/compact_layout_manager.cc @@ -0,0 +1,85 @@ +// 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/wm/compact_layout_manager.h" + +#include "ash/wm/window_util.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/window.h" +#include "ui/base/ui_base_types.h" +#include "ui/gfx/screen.h" +#include "ui/views/widget/widget.h" + +namespace aura_shell { +namespace internal { + +CompactLayoutManager::CompactLayoutManager(views::Widget* status_area_widget) + : status_area_widget_(status_area_widget) { +} + +CompactLayoutManager::~CompactLayoutManager() { + for (Windows::const_iterator i = windows_.begin(); i != windows_.end(); ++i) + (*i)->RemoveObserver(this); +} + +void CompactLayoutManager::OnWindowResized() { +} + +void CompactLayoutManager::OnWindowAddedToLayout(aura::Window* child) { + windows_.insert(child); + child->AddObserver(this); + if (child->GetProperty(aura::client::kShowStateKey)) { + UpdateBoundsFromShowState(child); + UpdateStatusAreaVisibility(); + } +} + +void CompactLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) { + windows_.erase(child); + child->RemoveObserver(this); +} + +void CompactLayoutManager::OnChildWindowVisibilityChanged( + aura::Window* child, + bool visibile) { + UpdateStatusAreaVisibility(); +} + +void CompactLayoutManager::SetChildBounds( + aura::Window* child, + const gfx::Rect& requested_bounds) { + gfx::Rect bounds = requested_bounds; + // Avoid a janky resize on startup by ensuring the initial bounds fill the + // screen. + if (IsWindowMaximized(child)) + bounds = gfx::Screen::GetPrimaryMonitorBounds(); + SetChildBoundsDirect(child, bounds); +} + +void CompactLayoutManager::OnWindowPropertyChanged(aura::Window* window, + const char* name, + void* old) { + if (name == aura::client::kShowStateKey) { + UpdateBoundsFromShowState(window); + UpdateStatusAreaVisibility(); + } +} + +////////////////////////////////////////////////////////////////////////////// +// CompactLayoutManager, private: + +void CompactLayoutManager::UpdateStatusAreaVisibility() { + if (!status_area_widget_) + return; + // Full screen windows should hide the status area widget. + bool fullscreen_window = HasFullscreenWindow(windows_); + bool widget_visible = status_area_widget_->IsVisible(); + if (fullscreen_window && widget_visible) + status_area_widget_->Hide(); + else if (!fullscreen_window && !widget_visible) + status_area_widget_->Show(); +} + +} // namespace internal +} // namespace aura_shell diff --git a/ash/wm/compact_layout_manager.h b/ash/wm/compact_layout_manager.h new file mode 100644 index 0000000..1e713e8 --- /dev/null +++ b/ash/wm/compact_layout_manager.h @@ -0,0 +1,66 @@ +// 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_WM_COMPACT_LAYOUT_MANAGER_H_ +#define ASH_WM_COMPACT_LAYOUT_MANAGER_H_ +#pragma once + +#include <set> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/aura/layout_manager.h" +#include "ui/aura/window_observer.h" +#include "ui/aura_shell/aura_shell_export.h" + +namespace views { +class Widget; +} + +namespace aura_shell { +namespace internal { + +// CompactLayoutManager is an alternate LayoutManager for the container that +// hosts what the shell considers to be top-level windows. It is used for low +// resolution screens and keeps the main browser window maximized. +// It listens for changes to kShowStateKey and resizes the window appropriately. +class AURA_SHELL_EXPORT CompactLayoutManager : public aura::LayoutManager, + public aura::WindowObserver { + public: + explicit CompactLayoutManager(views::Widget* status_area_widget); + virtual ~CompactLayoutManager(); + + // LayoutManager overrides: + virtual void OnWindowResized() OVERRIDE; + virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE; + virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE; + virtual void OnChildWindowVisibilityChanged(aura::Window* child, + bool visibile) OVERRIDE; + virtual void SetChildBounds(aura::Window* child, + const gfx::Rect& requested_bounds) OVERRIDE; + + // WindowObserver overrides: + virtual void OnWindowPropertyChanged(aura::Window* window, + const char* name, + void* old) OVERRIDE; + + private: + typedef std::set<aura::Window*> Windows; + + // Hides the status area when full screen windows cover it. + void UpdateStatusAreaVisibility(); + + // Set of windows we're listening to. + Windows windows_; + + // Weak pointer to status area with clock, network, battery, etc. icons. + views::Widget* status_area_widget_; + + DISALLOW_COPY_AND_ASSIGN(CompactLayoutManager); +}; + +} // namespace aura_shell +} // namespace internal + +#endif // ASH_WM_COMPACT_LAYOUT_MANAGER_H_ diff --git a/ash/wm/compact_status_area_layout_manager.cc b/ash/wm/compact_status_area_layout_manager.cc new file mode 100644 index 0000000..3bbaae3 --- /dev/null +++ b/ash/wm/compact_status_area_layout_manager.cc @@ -0,0 +1,69 @@ +// 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/wm/compact_status_area_layout_manager.h" + +#include "ui/gfx/rect.h" +#include "ui/gfx/screen.h" +#include "ui/views/widget/widget.h" + +namespace { +// Padding between the right edge of status area and right edge of screen. +const int kRightEdgePad = 3; +} // namespace + +namespace aura_shell { +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// CompactStatusAreaLayoutManager, public: + +CompactStatusAreaLayoutManager::CompactStatusAreaLayoutManager( + views::Widget* status_widget) + : status_widget_(status_widget) { +} + +CompactStatusAreaLayoutManager::~CompactStatusAreaLayoutManager() { +} + +//////////////////////////////////////////////////////////////////////////////// +// CompactStatusAreaLayoutManager, aura::LayoutManager implementation: + +void CompactStatusAreaLayoutManager::OnWindowResized() { + LayoutStatusArea(); +} + +void CompactStatusAreaLayoutManager::OnWindowAddedToLayout( + aura::Window* child) { + LayoutStatusArea(); +} + +void CompactStatusAreaLayoutManager::OnWillRemoveWindowFromLayout( + aura::Window* child) { +} + +void CompactStatusAreaLayoutManager::OnChildWindowVisibilityChanged( + aura::Window* child, bool visible) { +} + +void CompactStatusAreaLayoutManager::SetChildBounds( + aura::Window* child, const gfx::Rect& requested_bounds) { + SetChildBoundsDirect(child, requested_bounds); +} + +//////////////////////////////////////////////////////////////////////////////// +// CompactStatusAreaLayoutManager, private: + +void CompactStatusAreaLayoutManager::LayoutStatusArea() { + // Place the widget in the top-right corner of the screen. + gfx::Rect monitor_bounds = gfx::Screen::GetPrimaryMonitorBounds(); + gfx::Rect widget_bounds = status_widget_->GetRestoredBounds(); + widget_bounds.set_x( + monitor_bounds.width() - widget_bounds.width() - kRightEdgePad); + widget_bounds.set_y(0); + status_widget_->SetBounds(widget_bounds); +} + +} // internal +} // aura_shell diff --git a/ash/wm/compact_status_area_layout_manager.h b/ash/wm/compact_status_area_layout_manager.h new file mode 100644 index 0000000..336f02c --- /dev/null +++ b/ash/wm/compact_status_area_layout_manager.h @@ -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. + +#ifndef ASH_WM_COMPACT_STATUS_AREA_LAYOUT_MANAGER_H_ +#define ASH_WM_COMPACT_STATUS_AREA_LAYOUT_MANAGER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/aura/layout_manager.h" + +namespace views { +class Widget; +} + +namespace aura_shell { +namespace internal { + +// CompactStatusAreaLayoutManager places the status area in the top-right +// corner of the screen. +class CompactStatusAreaLayoutManager : public aura::LayoutManager { + public: + explicit CompactStatusAreaLayoutManager(views::Widget* status_widget); + virtual ~CompactStatusAreaLayoutManager(); + + // Overridden from aura::LayoutManager: + virtual void OnWindowResized() OVERRIDE; + virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE; + virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE; + virtual void OnChildWindowVisibilityChanged(aura::Window* child, + bool visible) OVERRIDE; + virtual void SetChildBounds(aura::Window* child, + const gfx::Rect& requested_bounds) OVERRIDE; + + private: + // Place the status area widget in the corner of the screen. + void LayoutStatusArea(); + + views::Widget* status_widget_; + + DISALLOW_COPY_AND_ASSIGN(CompactStatusAreaLayoutManager); +}; + +} // namespace internal +} // namespace aura_shell + +#endif // ASH_WM_COMPACT_STATUS_AREA_LAYOUT_MANAGER_H_ diff --git a/ash/wm/shelf_layout_manager.cc b/ash/wm/shelf_layout_manager.cc new file mode 100644 index 0000000..05661ec --- /dev/null +++ b/ash/wm/shelf_layout_manager.cc @@ -0,0 +1,159 @@ +// 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/wm/shelf_layout_manager.h" + +#include "ash/launcher/launcher.h" +#include "base/auto_reset.h" +#include "ui/aura/root_window.h" +#include "ui/aura/screen_aura.h" +#include "ui/aura_shell/shell.h" +#include "ui/gfx/compositor/layer.h" +#include "ui/gfx/compositor/layer_animator.h" +#include "ui/views/widget/widget.h" + +namespace aura_shell { +namespace internal { + +namespace { + +ui::Layer* GetLayer(views::Widget* widget) { + return widget->GetNativeView()->layer(); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// ShelfLayoutManager, public: + +ShelfLayoutManager::ShelfLayoutManager(views::Widget* launcher, + views::Widget* status) + : animating_(false), + in_layout_(false), + visible_(true), + max_height_(-1), + launcher_(launcher), + status_(status) { + gfx::Rect launcher_bounds = launcher->GetWindowScreenBounds(); + gfx::Rect status_bounds = status->GetWindowScreenBounds(); + max_height_ = std::max(launcher_bounds.height(), status_bounds.height()); + GetLayer(launcher)->GetAnimator()->AddObserver(this); +} + +ShelfLayoutManager::~ShelfLayoutManager() { + // Do not try to remove observer from layer as the Launcher is + // already deleted. +} + +void ShelfLayoutManager::LayoutShelf() { + AutoReset<bool> auto_reset_in_layout(&in_layout_, true); + StopAnimating(); + TargetBounds target_bounds; + float target_opacity = visible_ ? 1.0f : 0.0f; + CalculateTargetBounds(visible_, &target_bounds); + GetLayer(launcher_)->SetOpacity(target_opacity); + GetLayer(status_)->SetOpacity(target_opacity); + launcher_->SetBounds(target_bounds.launcher_bounds); + status_->SetBounds(target_bounds.status_bounds); + Shell::GetInstance()->launcher()->SetStatusWidth( + target_bounds.status_bounds.width()); + aura::RootWindow::GetInstance()->screen()->set_work_area_insets( + target_bounds.work_area_insets); +} + +void ShelfLayoutManager::SetVisible(bool visible) { + bool current_visibility = animating_ ? !visible_ : visible_; + if (visible == current_visibility) + return; // Nothing changed. + + StopAnimating(); + + TargetBounds target_bounds; + float target_opacity = visible ? 1.0f : 0.0f; + CalculateTargetBounds(visible, &target_bounds); + AnimateWidgetTo(launcher_, target_bounds.launcher_bounds, target_opacity); + AnimateWidgetTo(status_, target_bounds.status_bounds, target_opacity); + animating_ = true; + // |visible_| is updated once the animation completes. +} + +//////////////////////////////////////////////////////////////////////////////// +// ShelfLayoutManager, aura::LayoutManager implementation: + +void ShelfLayoutManager::OnWindowResized() { + LayoutShelf(); +} + +void ShelfLayoutManager::OnWindowAddedToLayout(aura::Window* child) { +} + +void ShelfLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) { +} + +void ShelfLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child, + bool visible) { +} + +void ShelfLayoutManager::SetChildBounds(aura::Window* child, + const gfx::Rect& requested_bounds) { + SetChildBoundsDirect(child, requested_bounds); + if (!in_layout_) + LayoutShelf(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ShelfLayoutManager, private: + +void ShelfLayoutManager::StopAnimating() { + if (animating_) { + animating_ = false; + visible_ = !visible_; + } + GetLayer(launcher_)->GetAnimator()->StopAnimating(); + GetLayer(status_)->GetAnimator()->StopAnimating(); +} + +void ShelfLayoutManager::CalculateTargetBounds(bool visible, + TargetBounds* target_bounds) { + const gfx::Rect& available_bounds(aura::RootWindow::GetInstance()->bounds()); + int y = available_bounds.bottom() - (visible ? max_height_ : 0); + gfx::Rect status_bounds(status_->GetWindowScreenBounds()); + target_bounds->status_bounds = gfx::Rect( + available_bounds.right() - status_bounds.width(), + y + (max_height_ - status_bounds.height()) / 2, + status_bounds.width(), status_bounds.height()); + gfx::Rect launcher_bounds(launcher_->GetWindowScreenBounds()); + target_bounds->launcher_bounds = gfx::Rect( + available_bounds.x(), y + (max_height_ - launcher_bounds.height()) / 2, + available_bounds.width(), + launcher_bounds.height()); + if (visible) + target_bounds->work_area_insets = gfx::Insets(0, 0, max_height_, 0); +} + +void ShelfLayoutManager::AnimateWidgetTo(views::Widget* widget, + const gfx::Rect& target_bounds, + float target_opacity) { + ui::Layer* layer = GetLayer(widget); + ui::LayerAnimator::ScopedSettings animation_setter(layer->GetAnimator()); + // Don't go through the widget, otherwise we end up back in SetChildBounds and + // cancel the animation/layout. + layer->SetBounds(target_bounds); + layer->SetOpacity(target_opacity); +} + +void ShelfLayoutManager::OnLayerAnimationEnded( + const ui::LayerAnimationSequence* sequence) { + if (!animating_) + return; + animating_ = false; + visible_ = !visible_; + TargetBounds target_bounds; + CalculateTargetBounds(visible_, &target_bounds); + aura::RootWindow::GetInstance()->screen()->set_work_area_insets( + target_bounds.work_area_insets); +} + +} // internal +} // aura_shell diff --git a/ash/wm/shelf_layout_manager.h b/ash/wm/shelf_layout_manager.h new file mode 100644 index 0000000..6beed27 --- /dev/null +++ b/ash/wm/shelf_layout_manager.h @@ -0,0 +1,111 @@ +// 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_WM_SHELF_LAYOUT_MANAGER_H_ +#define ASH_WM_SHELF_LAYOUT_MANAGER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/aura/layout_manager.h" +#include "ui/aura_shell/aura_shell_export.h" +#include "ui/gfx/compositor/layer_animation_observer.h" +#include "ui/gfx/insets.h" +#include "ui/gfx/rect.h" + +namespace views { +class Widget; +} + +namespace aura_shell { +namespace internal { + +// ShelfLayoutManager is the layout manager responsible for the launcher and +// status widgets. The launcher is given the total available width and told the +// width of the status area. This allows the launcher to draw the background and +// layout to the status area. +// To respond to bounds changes in the status area StatusAreaLayoutManager works +// closely with ShelfLayoutManager. +class AURA_SHELL_EXPORT ShelfLayoutManager : public aura::LayoutManager, + public ui::LayerAnimationObserver { + public: + ShelfLayoutManager(views::Widget* launcher, views::Widget* status); + virtual ~ShelfLayoutManager(); + + bool in_layout() const { return in_layout_; } + + // Stops any animations and sets the bounds of the launcher and status + // widgets. + void LayoutShelf(); + + // Sets the visibility of the shelf to |visible|. + void SetVisible(bool visible); + bool visible() const { return animating_ ? !visible_ : visible_; } + + views::Widget* launcher() { return launcher_; } + views::Widget* status() { return status_; } + + // See description above field. + int max_height() const { return max_height_; } + + // Overridden from aura::LayoutManager: + virtual void OnWindowResized() OVERRIDE; + virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE; + virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE; + virtual void OnChildWindowVisibilityChanged(aura::Window* child, + bool visible) OVERRIDE; + virtual void SetChildBounds(aura::Window* child, + const gfx::Rect& requested_bounds) OVERRIDE; + + private: + struct TargetBounds { + gfx::Rect launcher_bounds; + gfx::Rect status_bounds; + gfx::Insets work_area_insets; + }; + + // Stops any animations. + void StopAnimating(); + + // Calculates the target bounds assuming visibility of |visible|. + void CalculateTargetBounds(bool visible, + TargetBounds* target_bounds); + + // Animates |widget| to the specified bounds and opacity. + void AnimateWidgetTo(views::Widget* widget, + const gfx::Rect& target_bounds, + float target_opacity); + + // LayerAnimationObserver overrides: + virtual void OnLayerAnimationEnded( + const ui::LayerAnimationSequence* sequence) OVERRIDE; + virtual void OnLayerAnimationAborted( + const ui::LayerAnimationSequence* sequence) OVERRIDE {} + virtual void OnLayerAnimationScheduled( + const ui::LayerAnimationSequence* sequence) OVERRIDE {} + + // Are we animating? + bool animating_; + + // True when inside LayoutShelf method. Used to prevent calling LayoutShelf + // again from SetChildBounds(). + bool in_layout_; + + // Current visibility. When the visibility changes this field is reset once + // the animation completes. + bool visible_; + + // Max height needed. + int max_height_; + + views::Widget* launcher_; + views::Widget* status_; + + DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManager); +}; + +} // namespace internal +} // namespace aura_shell + +#endif // ASH_WM_SHELF_LAYOUT_MANAGER_H_ diff --git a/ash/wm/shelf_layout_manager_unittest.cc b/ash/wm/shelf_layout_manager_unittest.cc new file mode 100644 index 0000000..77974b1 --- /dev/null +++ b/ash/wm/shelf_layout_manager_unittest.cc @@ -0,0 +1,134 @@ +// 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/wm/shelf_layout_manager.h" + +#include "ash/launcher/launcher.h" +#include "ui/aura/root_window.h" +#include "ui/aura/screen_aura.h" +#include "ui/aura/window.h" +#include "ui/aura_shell/shell.h" +#include "ui/aura_shell/shell_window_ids.h" +#include "ui/aura_shell/test/aura_shell_test_base.h" +#include "ui/base/animation/animation_container_element.h" +#include "ui/gfx/compositor/layer_animator.h" +#include "ui/gfx/compositor/layer.h" +#include "ui/views/widget/widget.h" + +namespace aura_shell { +namespace internal { + +namespace { + +void StepWidgetLayerAnimatorToEnd(views::Widget* widget) { + ui::AnimationContainerElement* element = + static_cast<ui::AnimationContainerElement*>( + widget->GetNativeView()->layer()->GetAnimator()); + element->Step(base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1)); +} + +ShelfLayoutManager* GetShelfLayoutManager() { + aura::Window* window = aura_shell::Shell::GetInstance()->GetContainer( + aura_shell::internal::kShellWindowId_LauncherContainer); + return static_cast<ShelfLayoutManager*>(window->layout_manager()); +} + +} // namespace + +typedef aura_shell::test::AuraShellTestBase ShelfLayoutManagerTest; + +// Makes sure SetVisible updates work area and widget appropriately. +TEST_F(ShelfLayoutManagerTest, SetVisible) { + ShelfLayoutManager* shelf = GetShelfLayoutManager(); + // Force an initial layout. + shelf->LayoutShelf(); + ASSERT_TRUE(shelf->visible()); + + aura::ScreenAura* screen = aura::RootWindow::GetInstance()->screen(); + ASSERT_TRUE(screen); + // Bottom inset should be the max of widget heights. + EXPECT_EQ(shelf->max_height(), screen->work_area_insets().bottom()); + + // Hide the shelf. + shelf->SetVisible(false); + // Run the animation to completion. + StepWidgetLayerAnimatorToEnd(shelf->launcher()); + StepWidgetLayerAnimatorToEnd(shelf->status()); + EXPECT_FALSE(shelf->visible()); + EXPECT_EQ(0, screen->work_area_insets().bottom()); + + // Make sure the bounds of the two widgets changed. + EXPECT_GE(shelf->launcher()->GetNativeView()->bounds().y(), + gfx::Screen::GetPrimaryMonitorBounds().bottom()); + EXPECT_GE(shelf->status()->GetNativeView()->bounds().y(), + gfx::Screen::GetPrimaryMonitorBounds().bottom()); + + // And show it again. + shelf->SetVisible(true); + // Run the animation to completion. + StepWidgetLayerAnimatorToEnd(shelf->launcher()); + StepWidgetLayerAnimatorToEnd(shelf->status()); + EXPECT_TRUE(shelf->visible()); + EXPECT_EQ(shelf->max_height(), screen->work_area_insets().bottom()); + + // Make sure the bounds of the two widgets changed. + gfx::Rect launcher_bounds(shelf->launcher()->GetNativeView()->bounds()); + int bottom = gfx::Screen::GetPrimaryMonitorBounds().bottom() - + shelf->max_height(); + EXPECT_EQ(launcher_bounds.y(), + bottom + (shelf->max_height() - launcher_bounds.height()) / 2); + gfx::Rect status_bounds(shelf->status()->GetNativeView()->bounds()); + EXPECT_EQ(status_bounds.y(), + bottom + (shelf->max_height() - status_bounds.height()) / 2); +} + +// Makes sure LayoutShelf invoked while animating cleans things up. +TEST_F(ShelfLayoutManagerTest, LayoutShelfWhileAnimating) { + ShelfLayoutManager* shelf = GetShelfLayoutManager(); + // Force an initial layout. + shelf->LayoutShelf(); + ASSERT_TRUE(shelf->visible()); + + aura::ScreenAura* screen = aura::RootWindow::GetInstance()->screen(); + + // Hide the shelf. + shelf->SetVisible(false); + shelf->LayoutShelf(); + EXPECT_FALSE(shelf->visible()); + EXPECT_FALSE(shelf->visible()); + EXPECT_EQ(0, screen->work_area_insets().bottom()); + // Make sure the bounds of the two widgets changed. + EXPECT_GE(shelf->launcher()->GetNativeView()->bounds().y(), + gfx::Screen::GetPrimaryMonitorBounds().bottom()); + EXPECT_GE(shelf->status()->GetNativeView()->bounds().y(), + gfx::Screen::GetPrimaryMonitorBounds().bottom()); +} + +// Makes sure the launcher is initially sized correctly. +TEST_F(ShelfLayoutManagerTest, LauncherInitiallySized) { + Launcher* launcher = Shell::GetInstance()->launcher(); + ASSERT_TRUE(launcher); + ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManager(); + ASSERT_TRUE(shelf_layout_manager); + ASSERT_TRUE(shelf_layout_manager->status()); + int status_width = + shelf_layout_manager->status()->GetWindowScreenBounds().width(); + // Test only makes sense if the status is > 0, which is better be. + EXPECT_GT(status_width, 0); + EXPECT_EQ(status_width, launcher->GetStatusWidth()); +} + +// Makes sure the launcher is sized when the status area changes size. +TEST_F(ShelfLayoutManagerTest, LauncherUpdatedWhenStatusAreaChangesSize) { + Launcher* launcher = Shell::GetInstance()->launcher(); + ASSERT_TRUE(launcher); + ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManager(); + ASSERT_TRUE(shelf_layout_manager); + ASSERT_TRUE(shelf_layout_manager->status()); + shelf_layout_manager->status()->SetBounds(gfx::Rect(0, 0, 200, 200)); + EXPECT_EQ(200, launcher->GetStatusWidth()); +} + +} // namespace internal +} // namespace aura_shell diff --git a/ash/wm/status_area_layout_manager.cc b/ash/wm/status_area_layout_manager.cc new file mode 100644 index 0000000..e933f75 --- /dev/null +++ b/ash/wm/status_area_layout_manager.cc @@ -0,0 +1,62 @@ +// 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/wm/status_area_layout_manager.h" + +#include "ash/wm/shelf_layout_manager.h" +#include "base/auto_reset.h" + +namespace aura_shell { +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// StatusAreaLayoutManager, public: + +StatusAreaLayoutManager::StatusAreaLayoutManager(ShelfLayoutManager* shelf) + : in_layout_(false), + shelf_(shelf) { +} + +StatusAreaLayoutManager::~StatusAreaLayoutManager() { +} + +//////////////////////////////////////////////////////////////////////////////// +// StatusAreaLayoutManager, aura::LayoutManager implementation: + +void StatusAreaLayoutManager::OnWindowResized() { + LayoutStatusArea(); +} + +void StatusAreaLayoutManager::OnWindowAddedToLayout(aura::Window* child) { +} + +void StatusAreaLayoutManager::OnWillRemoveWindowFromLayout( + aura::Window* child) { +} + +void StatusAreaLayoutManager::OnChildWindowVisibilityChanged( + aura::Window* child, bool visible) { +} + +void StatusAreaLayoutManager::SetChildBounds( + aura::Window* child, const gfx::Rect& requested_bounds) { + SetChildBoundsDirect(child, requested_bounds); + if (!in_layout_) + LayoutStatusArea(); +} + +//////////////////////////////////////////////////////////////////////////////// +// StatusAreaLayoutManager, private: + +void StatusAreaLayoutManager::LayoutStatusArea() { + // Shelf layout manager may be already doing layout. + if (shelf_->in_layout()) + return; + + AutoReset<bool> auto_reset_in_layout(&in_layout_, true); + shelf_->LayoutShelf(); +} + +} // internal +} // aura_shell diff --git a/ash/wm/status_area_layout_manager.h b/ash/wm/status_area_layout_manager.h new file mode 100644 index 0000000..2cc3655 --- /dev/null +++ b/ash/wm/status_area_layout_manager.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_WM_STATUS_AREA_LAYOUT_MANAGER_H_ +#define ASH_WM_STATUS_AREA_LAYOUT_MANAGER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/aura/layout_manager.h" + +namespace aura_shell { +namespace internal { + +class ShelfLayoutManager; + +// StatusAreaLayoutManager is a layout manager responsible for the status area. +// In any case when status area needs relayout it redirects this call to +// ShelfLayoutManager. +class StatusAreaLayoutManager : public aura::LayoutManager { + public: + explicit StatusAreaLayoutManager(ShelfLayoutManager* shelf); + virtual ~StatusAreaLayoutManager(); + + // Overridden from aura::LayoutManager: + virtual void OnWindowResized() OVERRIDE; + virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE; + virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE; + virtual void OnChildWindowVisibilityChanged(aura::Window* child, + bool visible) OVERRIDE; + virtual void SetChildBounds(aura::Window* child, + const gfx::Rect& requested_bounds) OVERRIDE; + + private: + // Updates layout of the status area. Effectively calls ShelfLayoutManager + // to update layout of the shelf. + void LayoutStatusArea(); + + // True when inside LayoutStatusArea method. + // Used to prevent calling itself again from SetChildBounds(). + bool in_layout_; + + ShelfLayoutManager* shelf_; + + DISALLOW_COPY_AND_ASSIGN(StatusAreaLayoutManager); +}; + +} // namespace internal +} // namespace aura_shell + +#endif // ASH_WM_STATUS_AREA_LAYOUT_MANAGER_H_ diff --git a/ash/wm/toplevel_layout_manager.cc b/ash/wm/toplevel_layout_manager.cc index d56f30b..9f7601a 100644 --- a/ash/wm/toplevel_layout_manager.cc +++ b/ash/wm/toplevel_layout_manager.cc @@ -4,10 +4,10 @@ #include "ash/wm/toplevel_layout_manager.h" +#include "ash/wm/shelf_layout_manager.h" #include "ash/wm/window_util.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" -#include "ui/aura_shell/shelf_layout_manager.h" #include "ui/base/ui_base_types.h" #include "ui/gfx/screen.h" diff --git a/ash/wm/workspace_controller.cc b/ash/wm/workspace_controller.cc index dbc2ccf..c21383a 100644 --- a/ash/wm/workspace_controller.cc +++ b/ash/wm/workspace_controller.cc @@ -4,6 +4,8 @@ #include "ash/wm/workspace_controller.h" +#include "ash/launcher/launcher.h" +#include "ash/launcher/launcher_model.h" #include "ash/wm/default_container_layout_manager.h" #include "ash/wm/window_util.h" #include "ash/wm/workspace/workspace.h" @@ -12,8 +14,6 @@ #include "ui/aura/client/aura_constants.h" #include "ui/aura/root_window.h" #include "ui/aura/window.h" -#include "ui/aura_shell/launcher/launcher.h" -#include "ui/aura_shell/launcher/launcher_model.h" #include "ui/aura_shell/shell.h" namespace aura_shell { diff --git a/ash/wm/workspace_controller.h b/ash/wm/workspace_controller.h index 587c4f9..a244e3e 100644 --- a/ash/wm/workspace_controller.h +++ b/ash/wm/workspace_controller.h @@ -6,13 +6,13 @@ #define UI_AURA_SHELL_WORKSPACE_CONTROLLER_H_ #pragma once +#include "ash/launcher/launcher_model_observer.h" #include "ash/wm/workspace/workspace_observer.h" #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "ui/aura/root_window_observer.h" #include "ui/aura/window_observer.h" #include "ui/aura_shell/aura_shell_export.h" -#include "ui/aura_shell/launcher/launcher_model_observer.h" namespace aura { class Window; |