From 16ae0289babd745cf8c54313b42795448c99ebb3 Mon Sep 17 00:00:00 2001 From: msw Date: Wed, 27 Jan 2016 13:07:14 -0800 Subject: [PATCH] Fork a subset of ash/shelf for use in mash. Fork Shelf View, Model, Button, TooltipManager, etc. These provide a baseline of ash/shelf-like functionality. (show open mash windows, support reordering, tooltips, etc.) Restructure some ownership patterns (eg. shelf owns model). Minimal code refactoring, removal and commenting. (attempt to minimize diff, keep/stub some features) TODO: Restore missing functionality/tests. BUG=557406 TEST=mojo:mash_shell's shelf starts looking more like ash. R=sky@chromium.org,bungeman@google.com Review URL: https://codereview.chromium.org/1585363002 Cr-Commit-Position: refs/heads/master@{#371866} --- mash/shelf/BUILD.gn | 17 + mash/shelf/DEPS | 3 + mash/shelf/shelf_application.cc | 6 +- mash/shelf/shelf_button.cc | 567 ++++++++ mash/shelf/shelf_button.h | 148 ++ mash/shelf/shelf_button_host.h | 92 ++ mash/shelf/shelf_constants.cc | 19 + mash/shelf/shelf_constants.h | 41 + mash/shelf/shelf_item_types.cc | 21 + mash/shelf/shelf_item_types.h | 92 ++ mash/shelf/shelf_model.cc | 229 ++++ mash/shelf/shelf_model.h | 127 ++ mash/shelf/shelf_model_observer.h | 42 + mash/shelf/shelf_tooltip_manager.cc | 361 +++++ mash/shelf/shelf_tooltip_manager.h | 107 ++ mash/shelf/shelf_types.h | 59 + mash/shelf/shelf_view.cc | 1941 ++++++++++++++++++++++++++- mash/shelf/shelf_view.h | 437 +++++- 18 files changed, 4229 insertions(+), 80 deletions(-) create mode 100644 mash/shelf/DEPS create mode 100644 mash/shelf/shelf_button.cc create mode 100644 mash/shelf/shelf_button.h create mode 100644 mash/shelf/shelf_button_host.h create mode 100644 mash/shelf/shelf_constants.cc create mode 100644 mash/shelf/shelf_constants.h create mode 100644 mash/shelf/shelf_item_types.cc create mode 100644 mash/shelf/shelf_item_types.h create mode 100644 mash/shelf/shelf_model.cc create mode 100644 mash/shelf/shelf_model.h create mode 100644 mash/shelf/shelf_model_observer.h create mode 100644 mash/shelf/shelf_tooltip_manager.cc create mode 100644 mash/shelf/shelf_tooltip_manager.h create mode 100644 mash/shelf/shelf_types.h diff --git a/mash/shelf/BUILD.gn b/mash/shelf/BUILD.gn index 8c316a7c6496e..284f77fd50e0f 100644 --- a/mash/shelf/BUILD.gn +++ b/mash/shelf/BUILD.gn @@ -6,11 +6,25 @@ import("//build/config/ui.gni") import("//mojo/public/mojo_application.gni") import("//mojo/public/tools/bindings/mojom.gni") +# TODO(msw): Port unit/app tests: model, view, tooltip_manager, etc. mojo_native_application("shelf") { sources = [ "main.cc", "shelf_application.cc", "shelf_application.h", + "shelf_button.cc", + "shelf_button.h", + "shelf_button_host.h", + "shelf_constants.cc", + "shelf_constants.h", + "shelf_item_types.cc", + "shelf_item_types.h", + "shelf_model.cc", + "shelf_model.h", + "shelf_model_observer.h", + "shelf_tooltip_manager.cc", + "shelf_tooltip_manager.h", + "shelf_types.h", "shelf_view.cc", "shelf_view.h", ] @@ -24,9 +38,12 @@ mojo_native_application("shelf") { "//mojo/services/tracing/public/cpp", "//mojo/shell/public/cpp", "//skia", + "//ui/accessibility", "//ui/aura", + "//ui/resources", "//ui/views", "//ui/views/mus:for_mojo_application", + "//ui/wm", ] resources = [ "$root_out_dir/views_mus_resources.pak" ] diff --git a/mash/shelf/DEPS b/mash/shelf/DEPS new file mode 100644 index 0000000000000..b39a3000f7a11 --- /dev/null +++ b/mash/shelf/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+skia", +] \ No newline at end of file diff --git a/mash/shelf/shelf_application.cc b/mash/shelf/shelf_application.cc index cb829fe362e7c..f3567a6b11d82 100644 --- a/mash/shelf/shelf_application.cc +++ b/mash/shelf/shelf_application.cc @@ -27,11 +27,10 @@ void ShelfApplication::Initialize(mojo::ApplicationImpl* app) { aura_init_.reset(new views::AuraInit(app, "views_mus_resources.pak")); views::WindowManagerConnection::Create(app); + // Construct the shelf using a container tagged for positioning by the WM. views::Widget* widget = new views::Widget; views::Widget::InitParams params( views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); - params.delegate = new ShelfView(app); - std::map> properties; properties[mash::wm::mojom::kWindowContainer_Property] = mojo::TypeConverter, int32_t>::Convert( @@ -41,6 +40,9 @@ void ShelfApplication::Initialize(mojo::ApplicationImpl* app) { params.native_widget = new views::NativeWidgetMus( widget, app->shell(), window, mus::mojom::SurfaceType::DEFAULT); widget->Init(params); + widget->SetContentsView(new ShelfView(app)); + // Call CenterWindow to mimic Widget::Init's placement with a widget delegate. + widget->CenterWindow(widget->GetContentsView()->GetPreferredSize()); widget->Show(); } diff --git a/mash/shelf/shelf_button.cc b/mash/shelf/shelf_button.cc new file mode 100644 index 0000000000000..a8d4e919ac940 --- /dev/null +++ b/mash/shelf/shelf_button.cc @@ -0,0 +1,567 @@ +// Copyright 2013 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 "mash/shelf/shelf_button.h" + +#include + +#include "base/time/time.h" +#include "mash/shelf/shelf_button_host.h" +#include "skia/ext/image_operations.h" +#include "ui/accessibility/ax_view_state.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/events/event_constants.h" +#include "ui/gfx/animation/animation_delegate.h" +#include "ui/gfx/animation/throb_animation.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/gfx/skbitmap_operations.h" +#include "ui/views/controls/image_view.h" + +namespace { + +// Size of the bar. This is along the opposite axis of the shelf. For example, +// if the shelf is aligned horizontally then this is the height of the bar. +const int kBarSize = 3; +const int kIconSize = 32; +const int kIconPad = 5; +const int kIconPadVertical = 6; +const int kAttentionThrobDurationMS = 800; +const int kMaxAnimationSeconds = 10; + +// Simple AnimationDelegate that owns a single ThrobAnimation instance to +// keep all Draw Attention animations in sync. +class ShelfButtonAnimation : public gfx::AnimationDelegate { + public: + class Observer { + public: + virtual void AnimationProgressed() = 0; + + protected: + virtual ~Observer() {} + }; + + static ShelfButtonAnimation* GetInstance() { + static ShelfButtonAnimation* s_instance = new ShelfButtonAnimation(); + return s_instance; + } + + void AddObserver(Observer* observer) { + observers_.AddObserver(observer); + } + + void RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); + if (!observers_.might_have_observers()) + animation_.Stop(); + } + + int GetAlpha() { + return GetThrobAnimation().CurrentValueBetween(0, 255); + } + + double GetAnimation() { + return GetThrobAnimation().GetCurrentValue(); + } + + private: + ShelfButtonAnimation() + : animation_(this) { + animation_.SetThrobDuration(kAttentionThrobDurationMS); + animation_.SetTweenType(gfx::Tween::SMOOTH_IN_OUT); + } + + ~ShelfButtonAnimation() override {} + + gfx::ThrobAnimation& GetThrobAnimation() { + if (!animation_.is_animating()) { + animation_.Reset(); + animation_.StartThrobbing(-1 /*throb indefinitely*/); + } + return animation_; + } + + // gfx::AnimationDelegate + void AnimationProgressed(const gfx::Animation* animation) override { + if (animation != &animation_) + return; + if (!animation_.is_animating()) + return; + FOR_EACH_OBSERVER(Observer, observers_, AnimationProgressed()); + } + + gfx::ThrobAnimation animation_; + base::ObserverList observers_; + + DISALLOW_COPY_AND_ASSIGN(ShelfButtonAnimation); +}; + +} // namespace + +namespace mash { +namespace shelf { + +//////////////////////////////////////////////////////////////////////////////// +// ShelfButton::BarView + +class ShelfButton::BarView : public views::ImageView, + public ShelfButtonAnimation::Observer { + public: + BarView(ShelfButton* host) + : host_(host), + show_attention_(false), + animation_end_time_(base::TimeTicks()), + animating_(false) { + // Make sure the events reach the parent view for handling. + set_interactive(false); + } + + ~BarView() override { + if (show_attention_) + ShelfButtonAnimation::GetInstance()->RemoveObserver(this); + } + + // views::View: + void OnPaint(gfx::Canvas* canvas) override { + if (show_attention_) { + int alpha = + animating_ ? ShelfButtonAnimation::GetInstance()->GetAlpha() : 255; + canvas->SaveLayerAlpha(alpha); + views::ImageView::OnPaint(canvas); + canvas->Restore(); + } else { + views::ImageView::OnPaint(canvas); + } + } + + // ShelfButtonAnimation::Observer + void AnimationProgressed() override { + UpdateBounds(); + SchedulePaint(); + } + + void SetBarBoundsRect(const gfx::Rect& bounds) { + base_bounds_ = bounds; + UpdateBounds(); + } + + void ShowAttention(bool show) { + if (show_attention_ != show) { + show_attention_ = show; + if (show_attention_) { + animating_ = true; + animation_end_time_ = base::TimeTicks::Now() + + base::TimeDelta::FromSeconds(kMaxAnimationSeconds); + ShelfButtonAnimation::GetInstance()->AddObserver(this); + } else { + animating_ = false; + ShelfButtonAnimation::GetInstance()->RemoveObserver(this); + } + } + UpdateBounds(); + } + + private: + void UpdateBounds() { + gfx::Rect bounds = base_bounds_; + if (show_attention_) { + // Scale from .35 to 1.0 of the total width (which is wider than the + // visible width of the image), so the animation "rests" briefly at full + // visible width. Cap bounds length at kIconSize to prevent visual + // flutter while centering bar within further expanding bounds. + double animation = animating_ ? + ShelfButtonAnimation::GetInstance()->GetAnimation() : 1.0; + double scale = .35 + .65 * animation; + if (host_->host()->GetAlignment() == SHELF_ALIGNMENT_BOTTOM) { + int width = base_bounds_.width() * scale; + bounds.set_width(std::min(width, kIconSize)); + int x_offset = (base_bounds_.width() - bounds.width()) / 2; + bounds.set_x(base_bounds_.x() + x_offset); + UpdateAnimating(bounds.width() == kIconSize); + } else { + int height = base_bounds_.height() * scale; + bounds.set_height(std::min(height, kIconSize)); + int y_offset = (base_bounds_.height() - bounds.height()) / 2; + bounds.set_y(base_bounds_.y() + y_offset); + UpdateAnimating(bounds.height() == kIconSize); + } + } + SetBoundsRect(bounds); + } + + void UpdateAnimating(bool max_length) { + if (!max_length) + return; + if (base::TimeTicks::Now() > animation_end_time_) { + animating_ = false; + ShelfButtonAnimation::GetInstance()->RemoveObserver(this); + } + } + + ShelfButton* host_; + bool show_attention_; + base::TimeTicks animation_end_time_; // For attention throbbing underline. + bool animating_; // Is time-limited attention animation running? + gfx::Rect base_bounds_; + + DISALLOW_COPY_AND_ASSIGN(BarView); +}; + +//////////////////////////////////////////////////////////////////////////////// +// ShelfButton::IconView + +ShelfButton::IconView::IconView() : icon_size_(kIconSize) { + // Do not make this interactive, so that events are sent to ShelfView for + // handling. + set_interactive(false); +} + +ShelfButton::IconView::~IconView() { +} + +//////////////////////////////////////////////////////////////////////////////// +// ShelfButton + +// static +const char ShelfButton::kViewClassName[] = "ash/ShelfButton"; + +ShelfButton* ShelfButton::Create(views::ButtonListener* listener, + ShelfButtonHost* host) { + ShelfButton* button = new ShelfButton(listener, host); + button->Init(); + return button; +} + +ShelfButton::ShelfButton(views::ButtonListener* listener, + ShelfButtonHost* host) + : CustomButton(listener), + host_(host), + icon_view_(NULL), + bar_(new BarView(this)), + state_(STATE_NORMAL), + destroyed_flag_(NULL) { + SetAccessibilityFocusable(true); + + const gfx::ShadowValue kShadows[] = { + gfx::ShadowValue(gfx::Vector2d(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)), + gfx::ShadowValue(gfx::Vector2d(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)), + gfx::ShadowValue(gfx::Vector2d(0, 0), 1, SkColorSetARGB(0x54, 0, 0, 0)), + }; + icon_shadows_.assign(kShadows, kShadows + arraysize(kShadows)); + + AddChildView(bar_); +} + +ShelfButton::~ShelfButton() { + if (destroyed_flag_) + *destroyed_flag_ = true; +} + +void ShelfButton::SetShadowedImage(const gfx::ImageSkia& image) { + icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow( + image, icon_shadows_)); +} + +void ShelfButton::SetImage(const gfx::ImageSkia& image) { + if (image.isNull()) { + // TODO: need an empty image. + icon_view_->SetImage(image); + return; + } + + if (icon_view_->icon_size() == 0) { + SetShadowedImage(image); + return; + } + + // Resize the image maintaining our aspect ratio. + int pref = icon_view_->icon_size(); + float aspect_ratio = + static_cast(image.width()) / static_cast(image.height()); + int height = pref; + int width = static_cast(aspect_ratio * height); + if (width > pref) { + width = pref; + height = static_cast(width / aspect_ratio); + } + + if (width == image.width() && height == image.height()) { + SetShadowedImage(image); + return; + } + + SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(image, + skia::ImageOperations::RESIZE_BEST, gfx::Size(width, height))); +} + +const gfx::ImageSkia& ShelfButton::GetImage() const { + return icon_view_->GetImage(); +} + +void ShelfButton::AddState(State state) { + if (!(state_ & state)) { + state_ |= state; + Layout(); + if (state & STATE_ATTENTION) + bar_->ShowAttention(true); + } +} + +void ShelfButton::ClearState(State state) { + if (state_ & state) { + state_ &= ~state; + Layout(); + if (state & STATE_ATTENTION) + bar_->ShowAttention(false); + } +} + +gfx::Rect ShelfButton::GetIconBounds() const { + return icon_view_->bounds(); +} + +void ShelfButton::ShowContextMenu(const gfx::Point& p, + ui::MenuSourceType source_type) { + if (!context_menu_controller()) + return; + + bool destroyed = false; + destroyed_flag_ = &destroyed; + + CustomButton::ShowContextMenu(p, source_type); + + if (!destroyed) { + destroyed_flag_ = NULL; + // The menu will not propagate mouse events while its shown. To address, + // the hover state gets cleared once the menu was shown (and this was not + // destroyed). + ClearState(STATE_HOVERED); + } +} + +const char* ShelfButton::GetClassName() const { + return kViewClassName; +} + +bool ShelfButton::OnMousePressed(const ui::MouseEvent& event) { + CustomButton::OnMousePressed(event); + host_->PointerPressedOnButton(this, ShelfButtonHost::MOUSE, event); + return true; +} + +void ShelfButton::OnMouseReleased(const ui::MouseEvent& event) { + CustomButton::OnMouseReleased(event); + host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, false); +} + +void ShelfButton::OnMouseCaptureLost() { + ClearState(STATE_HOVERED); + host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, true); + CustomButton::OnMouseCaptureLost(); +} + +bool ShelfButton::OnMouseDragged(const ui::MouseEvent& event) { + CustomButton::OnMouseDragged(event); + host_->PointerDraggedOnButton(this, ShelfButtonHost::MOUSE, event); + return true; +} + +void ShelfButton::OnMouseMoved(const ui::MouseEvent& event) { + CustomButton::OnMouseMoved(event); + host_->MouseMovedOverButton(this); +} + +void ShelfButton::OnMouseEntered(const ui::MouseEvent& event) { + AddState(STATE_HOVERED); + CustomButton::OnMouseEntered(event); + host_->MouseEnteredButton(this); +} + +void ShelfButton::OnMouseExited(const ui::MouseEvent& event) { + ClearState(STATE_HOVERED); + CustomButton::OnMouseExited(event); + host_->MouseExitedButton(this); +} + +void ShelfButton::GetAccessibleState(ui::AXViewState* state) { + state->role = ui::AX_ROLE_BUTTON; + state->name = host_->GetAccessibleName(this); +} + +void ShelfButton::Layout() { + const gfx::Rect button_bounds(GetContentsBounds()); + int icon_pad = host_->GetAlignment() != SHELF_ALIGNMENT_BOTTOM ? + kIconPadVertical : kIconPad; + int x_offset = host_->PrimaryAxisValue(0, icon_pad); + int y_offset = host_->PrimaryAxisValue(icon_pad, 0); + + int icon_width = std::min(kIconSize, + button_bounds.width() - x_offset); + int icon_height = std::min(kIconSize, + button_bounds.height() - y_offset); + + // If on the left or top 'invert' the inset so the constant gap is on + // the interior (towards the center of display) edge of the shelf. + if (SHELF_ALIGNMENT_LEFT == host_->GetAlignment()) + x_offset = button_bounds.width() - (kIconSize + icon_pad); + + if (SHELF_ALIGNMENT_TOP == host_->GetAlignment()) + y_offset = button_bounds.height() - (kIconSize + icon_pad); + + // Center icon with respect to the secondary axis, and ensure + // that the icon doesn't occlude the bar highlight. + if (host_->IsHorizontalAlignment()) { + x_offset = std::max(0, button_bounds.width() - icon_width) / 2; + if (y_offset + icon_height + kBarSize > button_bounds.height()) + icon_height = button_bounds.height() - (y_offset + kBarSize); + } else { + y_offset = std::max(0, button_bounds.height() - icon_height) / 2; + if (x_offset + icon_width + kBarSize > button_bounds.width()) + icon_width = button_bounds.width() - (x_offset + kBarSize); + } + + // Expand bounds to include shadows. + gfx::Insets insets_shadows = gfx::ShadowValue::GetMargin(icon_shadows_); + // Adjust offsets to center icon, not icon + shadow. + x_offset += (insets_shadows.left() - insets_shadows.right()) / 2; + y_offset += (insets_shadows.top() - insets_shadows.bottom()) / 2; + gfx::Rect icon_view_bounds = + gfx::Rect(button_bounds.x() + x_offset, button_bounds.y() + y_offset, + icon_width, icon_height); + icon_view_bounds.Inset(insets_shadows); + icon_view_->SetBoundsRect(icon_view_bounds); + + // Icon size has been incorrect when running + // PanelLayoutManagerTest.PanelAlignmentSecondDisplay on valgrind bot, see + // http://crbug.com/234854. + DCHECK_LE(icon_width, kIconSize); + DCHECK_LE(icon_height, kIconSize); + + bar_->SetBarBoundsRect(button_bounds); + + UpdateState(); +} + +void ShelfButton::ChildPreferredSizeChanged(views::View* child) { + Layout(); +} + +void ShelfButton::OnFocus() { + AddState(STATE_FOCUSED); + CustomButton::OnFocus(); +} + +void ShelfButton::OnBlur() { + ClearState(STATE_FOCUSED); + CustomButton::OnBlur(); +} + +void ShelfButton::OnPaint(gfx::Canvas* canvas) { + CustomButton::OnPaint(canvas); + if (HasFocus()) { + gfx::Rect paint_bounds(GetLocalBounds()); + paint_bounds.Inset(1, 1, 1, 1); + const SkColor kFocusBorderColor = SkColorSetRGB(64, 128, 250); + canvas->DrawSolidFocusRect(paint_bounds, kFocusBorderColor); + } +} + +void ShelfButton::OnGestureEvent(ui::GestureEvent* event) { + switch (event->type()) { + case ui::ET_GESTURE_TAP_DOWN: + AddState(STATE_HOVERED); + return CustomButton::OnGestureEvent(event); + case ui::ET_GESTURE_END: + ClearState(STATE_HOVERED); + return CustomButton::OnGestureEvent(event); + case ui::ET_GESTURE_SCROLL_BEGIN: + host_->PointerPressedOnButton(this, ShelfButtonHost::TOUCH, *event); + event->SetHandled(); + return; + case ui::ET_GESTURE_SCROLL_UPDATE: + host_->PointerDraggedOnButton(this, ShelfButtonHost::TOUCH, *event); + event->SetHandled(); + return; + case ui::ET_GESTURE_SCROLL_END: + case ui::ET_SCROLL_FLING_START: + host_->PointerReleasedOnButton(this, ShelfButtonHost::TOUCH, false); + event->SetHandled(); + return; + default: + return CustomButton::OnGestureEvent(event); + } +} + +void ShelfButton::Init() { + icon_view_ = CreateIconView(); + + // TODO: refactor the layers so each button doesn't require 2. + icon_view_->SetPaintToLayer(true); + icon_view_->SetFillsBoundsOpaquely(false); + icon_view_->SetHorizontalAlignment(views::ImageView::CENTER); + icon_view_->SetVerticalAlignment(views::ImageView::LEADING); + + AddChildView(icon_view_); +} + +ShelfButton::IconView* ShelfButton::CreateIconView() { + return new IconView; +} + +void ShelfButton::UpdateState() { + UpdateBar(); + + icon_view_->SetHorizontalAlignment(host_->PrimaryAxisValue( + views::ImageView::CENTER, views::ImageView::LEADING)); + icon_view_->SetVerticalAlignment(host_->PrimaryAxisValue( + views::ImageView::LEADING, views::ImageView::CENTER)); + SchedulePaint(); +} + +void ShelfButton::UpdateBar() { + if (state_ & STATE_HIDDEN) { + bar_->SetVisible(false); + return; + } + + int bar_id = 0; + /* TODO(msw): Restore functionality: + if (state_ & (STATE_ACTIVE)) + bar_id = IDR_ASH_SHELF_UNDERLINE_ACTIVE; + else if (state_ & STATE_ATTENTION) + bar_id = IDR_ASH_SHELF_UNDERLINE_ATTENTION; + else if (state_ & STATE_RUNNING) + bar_id = IDR_ASH_SHELF_UNDERLINE_RUNNING;*/ + + if (bar_id != 0) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + const gfx::ImageSkia* image = rb.GetImageNamed(bar_id).ToImageSkia(); + if (host_->GetAlignment() == SHELF_ALIGNMENT_BOTTOM) { + bar_->SetImage(*image); + } else { + bar_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(*image, + host_->SelectValueForShelfAlignment( + SkBitmapOperations::ROTATION_90_CW, + SkBitmapOperations::ROTATION_90_CW, + SkBitmapOperations::ROTATION_270_CW, + SkBitmapOperations::ROTATION_180_CW))); + } + bar_->SetHorizontalAlignment(host_->SelectValueForShelfAlignment( + views::ImageView::CENTER, views::ImageView::LEADING, + views::ImageView::TRAILING, views::ImageView::CENTER)); + bar_->SetVerticalAlignment(host_->SelectValueForShelfAlignment( + views::ImageView::TRAILING, views::ImageView::CENTER, + views::ImageView::CENTER, views::ImageView::LEADING)); + bar_->SchedulePaint(); + } + + bar_->SetVisible(bar_id != 0 && state_ != STATE_NORMAL); +} + +} // namespace shelf +} // namespace mash diff --git a/mash/shelf/shelf_button.h b/mash/shelf/shelf_button.h new file mode 100644 index 0000000000000..83b1bc38ad2e2 --- /dev/null +++ b/mash/shelf/shelf_button.h @@ -0,0 +1,148 @@ +// Copyright 2013 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 MASH_SHELF_SHELF_BUTTON_H_ +#define MASH_SHELF_SHELF_BUTTON_H_ + +#include "base/macros.h" +#include "mash/shelf/shelf_types.h" +#include "ui/gfx/shadow_value.h" +#include "ui/views/controls/button/custom_button.h" +#include "ui/views/controls/image_view.h" + +namespace mash { +namespace shelf { +class ShelfButtonHost; + +// Button used for items on the launcher, except for the AppList. +class ShelfButton : public views::CustomButton { + public: + static const char kViewClassName[]; + + // Used to indicate the current state of the button. + enum State { + // Nothing special. Usually represents an app shortcut item with no running + // instance. + STATE_NORMAL = 0, + // Button has mouse hovering on it. + STATE_HOVERED = 1 << 0, + // Underlying ShelfItem has a running instance. + STATE_RUNNING = 1 << 1, + // Underlying ShelfItem is active (i.e. has focus). + STATE_ACTIVE = 1 << 2, + // Underlying ShelfItem needs user's attention. + STATE_ATTENTION = 1 << 3, + STATE_FOCUSED = 1 << 4, + // Hide the status (temporarily for some animations). + STATE_HIDDEN = 1 << 5, + }; + + ~ShelfButton() override; + + // Called to create an instance of a ShelfButton. + static ShelfButton* Create(views::ButtonListener* listener, + ShelfButtonHost* host); + + // Sets the image to display for this entry. + void SetImage(const gfx::ImageSkia& image); + + // Retrieve the image to show proxy operations. + const gfx::ImageSkia& GetImage() const; + + // |state| is or'd into the current state. + void AddState(State state); + void ClearState(State state); + int state() const { return state_; } + + // Returns the bounds of the icon. + gfx::Rect GetIconBounds() const; + + // Overrides to views::CustomButton: + void ShowContextMenu(const gfx::Point& p, + ui::MenuSourceType source_type) override; + + // View override - needed by unit test. + void OnMouseCaptureLost() override; + + protected: + ShelfButton(views::ButtonListener* listener, + ShelfButtonHost* host); + + // Class that draws the icon part of a button, so it can be animated + // independently of the rest. This can be subclassed to provide a custom + // implementation, by overriding CreateIconView(). + class IconView : public views::ImageView { + public: + IconView(); + ~IconView() override; + + void set_icon_size(int icon_size) { icon_size_ = icon_size; } + int icon_size() const { return icon_size_; } + + private: + // Set to non-zero to force icons to be resized to fit within a square, + // while maintaining original proportions. + int icon_size_; + + DISALLOW_COPY_AND_ASSIGN(IconView); + }; + + // View overrides: + const char* GetClassName() const override; + bool OnMousePressed(const ui::MouseEvent& event) override; + void OnMouseReleased(const ui::MouseEvent& event) override; + bool OnMouseDragged(const ui::MouseEvent& event) override; + void OnMouseMoved(const ui::MouseEvent& event) override; + void OnMouseEntered(const ui::MouseEvent& event) override; + void OnMouseExited(const ui::MouseEvent& event) override; + void GetAccessibleState(ui::AXViewState* state) override; + void Layout() override; + void ChildPreferredSizeChanged(views::View* child) override; + void OnFocus() override; + void OnBlur() override; + void OnPaint(gfx::Canvas* canvas) override; + + // ui::EventHandler overrides: + void OnGestureEvent(ui::GestureEvent* event) override; + + // Sets the icon image with a shadow. + void SetShadowedImage(const gfx::ImageSkia& bitmap); + // Override for custom initialization. + virtual void Init(); + // Override to subclass IconView. + virtual IconView* CreateIconView(); + IconView* icon_view() const { return icon_view_; } + ShelfButtonHost* host() const { return host_; } + + private: + class BarView; + + // Updates the parts of the button to reflect the current |state_| and + // alignment. This may add or remove views, layout and paint. + void UpdateState(); + + // Updates the status bar (bitmap, orientation, visibility). + void UpdateBar(); + + ShelfButtonHost* host_; + IconView* icon_view_; + // Draws a bar underneath the image to represent the state of the application. + BarView* bar_; + // The current state of the application, multiple values of AppState are or'd + // together. + int state_; + + gfx::ShadowValues icon_shadows_; + + // If non-null the destuctor sets this to true. This is set while the menu is + // showing and used to detect if the menu was deleted while running. + bool* destroyed_flag_; + + DISALLOW_COPY_AND_ASSIGN(ShelfButton); +}; + +} // namespace shelf +} // namespace mash + +#endif // MASH_SHELF_SHELF_BUTTON_H_ diff --git a/mash/shelf/shelf_button_host.h b/mash/shelf/shelf_button_host.h new file mode 100644 index 0000000000000..4111fdd2c797e --- /dev/null +++ b/mash/shelf/shelf_button_host.h @@ -0,0 +1,92 @@ +// Copyright 2013 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 MASH_SHELF_SHELF_BUTTON_HOST_H_ +#define MASH_SHELF_SHELF_BUTTON_HOST_H_ + +#include "base/logging.h" +#include "base/strings/string16.h" +#include "mash/shelf/shelf_types.h" + +namespace ui { +class LocatedEvent; +} + +namespace views { +class View; +} + +namespace mash { +namespace shelf { + +// TODO(msw): Eliminate this abstraction and just pass pointers to ShelfView? +// The shelf buttons communicate back to the host by way of this interface. +// This interface is used to enable reordering the items on the shelf. +class ShelfButtonHost { + public: + enum Pointer { + NONE, + DRAG_AND_DROP, + MOUSE, + TOUCH, + }; + + virtual ShelfAlignment GetAlignment() const = 0; + + virtual bool IsHorizontalAlignment() const = 0; + + // Helper function for choosing values specific to the shelf alignment. + template + T PrimaryAxisValue(T horizontal, T vertical) const { + return IsHorizontalAlignment() ? horizontal : vertical; + } + + // Helper function for choosing values specific to the shelf alignment. + template + T SelectValueForShelfAlignment(T bottom, T left, T right, T top) const { + switch (GetAlignment()) { + case SHELF_ALIGNMENT_BOTTOM: return bottom; + case SHELF_ALIGNMENT_LEFT: return left; + case SHELF_ALIGNMENT_RIGHT: return right; + case SHELF_ALIGNMENT_TOP: return top; + } + NOTREACHED(); + return bottom; + } + + // Invoked when a pointer device is pressed on a view. + virtual void PointerPressedOnButton(views::View* view, + Pointer pointer, + const ui::LocatedEvent& event) = 0; + + // Invoked when a pointer device is dragged over a view. + virtual void PointerDraggedOnButton(views::View* view, + Pointer pointer, + const ui::LocatedEvent& event) = 0; + + // Invoked either if a pointer device is released or mouse capture canceled. + virtual void PointerReleasedOnButton(views::View* view, + Pointer pointer, + bool canceled) = 0; + + // Invoked when the mouse moves on the item. + virtual void MouseMovedOverButton(views::View* view) = 0; + + // Invoked when the mouse enters the item. + virtual void MouseEnteredButton(views::View* view) = 0; + + // Invoked when the mouse exits the item. + virtual void MouseExitedButton(views::View* view) = 0; + + // Invoked to get the accessible name of the item. + virtual base::string16 GetAccessibleName(const views::View* view) = 0; + + protected: + virtual ~ShelfButtonHost() {} +}; + +} // namespace shelf +} // namespace mash + +#endif // MASH_SHELF_SHELF_BUTTON_HOST_H_ diff --git a/mash/shelf/shelf_constants.cc b/mash/shelf/shelf_constants.cc new file mode 100644 index 0000000000000..8da7f789c36bc --- /dev/null +++ b/mash/shelf/shelf_constants.cc @@ -0,0 +1,19 @@ +// Copyright 2013 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 "mash/shelf/shelf_constants.h" + +namespace mash { +namespace shelf { + +const int kShelfBackgroundAlpha = 204; +const int kInvalidImageResourceID = -1; +const int kInvalidShelfID = 0; +const int kShelfSize = 47; +const int kShelfButtonSpacing = 10; +const int kShelfButtonSize = 44; +const int kTimeToSwitchBackgroundMs = 1000; + +} // namespace shelf +} // namespace mash diff --git a/mash/shelf/shelf_constants.h b/mash/shelf/shelf_constants.h new file mode 100644 index 0000000000000..19d926270ac7b --- /dev/null +++ b/mash/shelf/shelf_constants.h @@ -0,0 +1,41 @@ +// Copyright 2013 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 MASH_SHELF_SHELF_CONSTANTS_H_ +#define MASH_SHELF_SHELF_CONSTANTS_H_ + + +namespace mash { +namespace shelf { + +// Max alpha of the shelf background. +extern const int kShelfBackgroundAlpha; + +// Invalid image resource id used for ShelfItemDetails. +extern const int kInvalidImageResourceID; + +extern const int kInvalidShelfID; + +// Size of the shelf when visible (height when the shelf is horizontal). +extern const int kShelfSize; + +// Size of the space between buttons on the shelf. +extern const int kShelfButtonSpacing; + +// Size allocated for each button on the shelf. +extern const int kShelfButtonSize; + +// Animation duration for switching black shelf and dock background on and off. +extern const int kTimeToSwitchBackgroundMs; + +// The direction of the focus cycling. +enum CycleDirection { + CYCLE_FORWARD, + CYCLE_BACKWARD +}; + +} // namespace shelf +} // namespace mash + +#endif // MASH_SHELF_SHELF_CONSTANTS_H_ diff --git a/mash/shelf/shelf_item_types.cc b/mash/shelf/shelf_item_types.cc new file mode 100644 index 0000000000000..6d9a14ad00e1b --- /dev/null +++ b/mash/shelf/shelf_item_types.cc @@ -0,0 +1,21 @@ +// Copyright 2014 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 "mash/shelf/shelf_item_types.h" + +#include "mash/shelf/shelf_constants.h" + +namespace mash { +namespace shelf { + +ShelfItem::ShelfItem() + : type(TYPE_UNDEFINED), + id(kInvalidShelfID), + status(STATUS_CLOSED), + window_id(0) {} + +ShelfItem::~ShelfItem() {} + +} // namespace shelf +} // namespace mash diff --git a/mash/shelf/shelf_item_types.h b/mash/shelf/shelf_item_types.h new file mode 100644 index 0000000000000..de46bc994ffff --- /dev/null +++ b/mash/shelf/shelf_item_types.h @@ -0,0 +1,92 @@ +// Copyright 2014 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 MASH_SHELF_SHELF_ITEM_TYPES_H_ +#define MASH_SHELF_SHELF_ITEM_TYPES_H_ + +#include + +#include "base/strings/string16.h" +#include "ui/gfx/image/image_skia.h" + +namespace mash { +namespace shelf { + +typedef int ShelfID; + +// The type of a shelf item. +enum ShelfItemType { + // Represents a running app panel. + TYPE_APP_PANEL, + + // Represents a pinned shortcut to an app. + TYPE_APP_SHORTCUT, + + // Toggles visiblity of the app list. + TYPE_APP_LIST, + + // The browser shortcut button. + TYPE_BROWSER_SHORTCUT, + + // Represents a platform app. + TYPE_PLATFORM_APP, + + // Represents a windowed V1 browser app. + TYPE_WINDOWED_APP, + + // Represents a dialog. + TYPE_DIALOG, + + // TODO(msw): Integrate mojo apps with another enum type. + TYPE_MOJO_APP, + + // Default value. + TYPE_UNDEFINED, +}; + +// Represents the status of applications in the shelf. +enum ShelfItemStatus { + // A closed shelf item, i.e. has no live instance. + STATUS_CLOSED, + // A shelf item that has live instance. + STATUS_RUNNING, + // An active shelf item that has focus. + STATUS_ACTIVE, + // A shelf item that needs user's attention. + STATUS_ATTENTION, +}; + +struct ShelfItem { + ShelfItem(); + ~ShelfItem(); + + ShelfItemType type; + + // Image to display in the shelf. + gfx::ImageSkia image; + + // Assigned by the model when the item is added. + ShelfID id; + + // Running status. + ShelfItemStatus status; + + // The id of an associated open window. + // TODO(msw): Support multiple open windows per button. + uint32_t window_id; + + // Resource id of the image to display on the shelf. + // TODO(msw): Support window icons on the shelf. + //int image_resource_id; + + // Title of the item. + base::string16 title; +}; + +typedef std::vector ShelfItems; + +} // namespace shelf +} // namespace mash + +#endif // MASH_SHELF_SHELF_ITEM_TYPES_H_ diff --git a/mash/shelf/shelf_model.cc b/mash/shelf/shelf_model.cc new file mode 100644 index 0000000000000..e81c8a4d26dba --- /dev/null +++ b/mash/shelf/shelf_model.cc @@ -0,0 +1,229 @@ +// Copyright 2013 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 "mash/shelf/shelf_model.h" + +#include + +#include "mash/shelf/shelf_model_observer.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/shell/public/cpp/application_impl.h" + +namespace mash { +namespace shelf { + +namespace { + +int ShelfItemTypeToWeight(ShelfItemType type) { + switch (type) { + case TYPE_APP_LIST: + // TODO(skuhne): If the app list item becomes movable again, this need + // to be a fallthrough. + return 0; + case TYPE_BROWSER_SHORTCUT: + case TYPE_APP_SHORTCUT: + return 1; + case TYPE_WINDOWED_APP: + case TYPE_PLATFORM_APP: + case TYPE_MOJO_APP: + return 2; + case TYPE_DIALOG: + return 3; + case TYPE_APP_PANEL: + return 4; + case TYPE_UNDEFINED: + NOTREACHED() << "ShelfItemType must be set"; + return -1; + } + + NOTREACHED() << "Invalid type " << type; + return 1; +} + +bool CompareByWeight(const ShelfItem& a, const ShelfItem& b) { + return ShelfItemTypeToWeight(a.type) < ShelfItemTypeToWeight(b.type); +} + +} // namespace + +ShelfModel::ShelfModel(mojo::ApplicationImpl* app) + : next_id_(1), status_(STATUS_NORMAL), binding_(this) { + app->ConnectToService("mojo:desktop_wm", &user_window_controller_); + user_window_controller_->AddUserWindowObserver( + binding_.CreateInterfacePtrAndBind()); +} + +ShelfModel::~ShelfModel() { +} + +int ShelfModel::Add(const ShelfItem& item) { + return AddAt(static_cast(items_.size()), item); +} + +int ShelfModel::AddAt(int index, const ShelfItem& item) { + index = ValidateInsertionIndex(item.type, index); + items_.insert(items_.begin() + index, item); + items_[index].id = next_id_++; + FOR_EACH_OBSERVER(ShelfModelObserver, observers_, ShelfItemAdded(index)); + return index; +} + +void ShelfModel::RemoveItemAt(int index) { + DCHECK(index >= 0 && index < item_count()); + // The app list and browser shortcut can't be removed. + DCHECK(items_[index].type != TYPE_APP_LIST && + items_[index].type != TYPE_BROWSER_SHORTCUT); + ShelfID id = items_[index].id; + items_.erase(items_.begin() + index); + FOR_EACH_OBSERVER(ShelfModelObserver, observers_, + ShelfItemRemoved(index, id)); +} + +void ShelfModel::Move(int index, int target_index) { + if (index == target_index) + return; + // TODO: this needs to enforce valid ranges. + ShelfItem item(items_[index]); + items_.erase(items_.begin() + index); + items_.insert(items_.begin() + target_index, item); + FOR_EACH_OBSERVER(ShelfModelObserver, observers_, + ShelfItemMoved(index, target_index)); +} + +void ShelfModel::Set(int index, const ShelfItem& item) { + DCHECK(index >= 0 && index < item_count()); + int new_index = item.type == items_[index].type ? + index : ValidateInsertionIndex(item.type, index); + + ShelfItem old_item(items_[index]); + items_[index] = item; + items_[index].id = old_item.id; + FOR_EACH_OBSERVER(ShelfModelObserver, observers_, + ShelfItemChanged(index, old_item)); + + // If the type changes confirm that the item is still in the right order. + if (new_index != index) { + // The move function works by removing one item and then inserting it at the + // new location. However - by removing the item first the order will change + // so that our target index needs to be corrected. + // TODO(skuhne): Moving this into the Move function breaks lots of unit + // tests. So several functions were already using this incorrectly. + // That needs to be cleaned up. + if (index < new_index) + new_index--; + + Move(index, new_index); + } +} + +int ShelfModel::ItemIndexByID(ShelfID id) const { + ShelfItems::const_iterator i = ItemByID(id); + return i == items_.end() ? -1 : static_cast(i - items_.begin()); +} + +int ShelfModel::GetItemIndexForType(ShelfItemType type) { + for (size_t i = 0; i < items_.size(); ++i) { + if (items_[i].type == type) + return static_cast(i); + } + return -1; +} + +ShelfItems::const_iterator ShelfModel::ItemByID(int id) const { + for (ShelfItems::const_iterator i = items_.begin(); + i != items_.end(); ++i) { + if (i->id == id) + return i; + } + return items_.end(); +} + +int ShelfModel::FirstRunningAppIndex() const { + // Since lower_bound only checks weights against each other, we do not need + // to explicitly change different running application types. + DCHECK_EQ(ShelfItemTypeToWeight(TYPE_WINDOWED_APP), + ShelfItemTypeToWeight(TYPE_PLATFORM_APP)); + ShelfItem weight_dummy; + weight_dummy.type = TYPE_WINDOWED_APP; + return std::lower_bound(items_.begin(), items_.end(), weight_dummy, + CompareByWeight) - items_.begin(); +} + +int ShelfModel::FirstPanelIndex() const { + ShelfItem weight_dummy; + weight_dummy.type = TYPE_APP_PANEL; + return std::lower_bound(items_.begin(), items_.end(), weight_dummy, + CompareByWeight) - items_.begin(); +} + +void ShelfModel::SetStatus(Status status) { + if (status_ == status) + return; + + status_ = status; + FOR_EACH_OBSERVER(ShelfModelObserver, observers_, ShelfStatusChanged()); +} + +void ShelfModel::AddObserver(ShelfModelObserver* observer) { + observers_.AddObserver(observer); +} + +void ShelfModel::RemoveObserver(ShelfModelObserver* observer) { + observers_.RemoveObserver(observer); +} + +int ShelfModel::ValidateInsertionIndex(ShelfItemType type, int index) const { + DCHECK(index >= 0 && index <= item_count() + 1); + + // Clamp |index| to the allowed range for the type as determined by |weight|. + ShelfItem weight_dummy; + weight_dummy.type = type; + index = std::max(std::lower_bound(items_.begin(), items_.end(), weight_dummy, + CompareByWeight) - items_.begin(), + static_cast(index)); + index = std::min(std::upper_bound(items_.begin(), items_.end(), weight_dummy, + CompareByWeight) - items_.begin(), + static_cast(index)); + + return index; +} + +void ShelfModel::OnUserWindowObserverAdded( + mojo::Array user_windows) { + for (size_t i = 0; i < user_windows.size(); ++i) + OnUserWindowAdded(std::move(user_windows[i])); +} + +void ShelfModel::OnUserWindowAdded(mash::wm::mojom::UserWindowPtr user_window) { + ShelfItem item; + item.type = TYPE_MOJO_APP; + item.status = STATUS_RUNNING; + item.window_id = user_window->window_id; + item.title = user_window->window_title.To(); + Add(item); +} + +void ShelfModel::OnUserWindowRemoved(uint32_t window_id) { + RemoveItemAt(ItemIndexByWindowID(window_id)); +} + +void ShelfModel::OnUserWindowTitleChanged(uint32_t window_id, + const mojo::String& window_title) { + const int index = ItemIndexByWindowID(window_id); + ShelfItem old_item(items_[index]); + items_[index].title = window_title.To(); + FOR_EACH_OBSERVER(ShelfModelObserver, observers_, + ShelfItemChanged(index, old_item)); +} + +int ShelfModel::ItemIndexByWindowID(uint32_t window_id) const { + for (size_t i = 0; i < items_.size(); ++i) { + if (items_[i].window_id == window_id) + return static_cast(i); + } + return -1; +} + +} // namespace shelf +} // namespace mash diff --git a/mash/shelf/shelf_model.h b/mash/shelf/shelf_model.h new file mode 100644 index 0000000000000..99c5767699f8a --- /dev/null +++ b/mash/shelf/shelf_model.h @@ -0,0 +1,127 @@ +// Copyright 2013 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 MASH_SHELF_SHELF_MODEL_H_ +#define MASH_SHELF_SHELF_MODEL_H_ + +#include "base/macros.h" +#include "base/observer_list.h" +#include "mash/shelf/shelf_item_types.h" +#include "mash/wm/public/interfaces/user_window_controller.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mojo { +class ApplicationImpl; +} + +namespace mash { +namespace shelf { + +class ShelfModelObserver; + +// Model used by ShelfView. +class ShelfModel : public mash::wm::mojom::UserWindowObserver { + public: + enum Status { + STATUS_NORMAL, + // A status that indicates apps are syncing/loading. + STATUS_LOADING, + }; + + explicit ShelfModel(mojo::ApplicationImpl* app); + ~ShelfModel() override; + + // Adds a new item to the model. Returns the resulting index. + int Add(const ShelfItem& item); + + // Adds the item. |index| is the requested insertion index, which may be + // modified to meet type-based ordering. Returns the actual insertion index. + int AddAt(int index, const ShelfItem& 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); + + // Resets the item at the specified index. The item maintains its existing + // id and type. + void Set(int index, const ShelfItem& item); + + // Returns the index of the item by id. + int ItemIndexByID(ShelfID id) const; + + // Returns the |index| of the item matching |type| in |items_|. + // Returns -1 if the matching item is not found. + // Note: Requires a linear search. + int GetItemIndexForType(ShelfItemType type); + + // Returns the index of the first running application or the index where the + // first running application would go if there are no running (non pinned) + // applications yet. + int FirstRunningAppIndex() const; + + // Returns the index of the first panel or the index where the first panel + // would go if there are no panels. + int FirstPanelIndex() const; + + // Returns the id assigned to the next item added. + ShelfID next_id() const { return next_id_; } + + // Returns a reserved id which will not be used by the |ShelfModel|. + ShelfID reserve_external_id() { return next_id_++; } + + // Returns an iterator into items() for the item with the specified id, or + // items().end() if there is no item with the specified id. + ShelfItems::const_iterator ItemByID(ShelfID id) const; + + const ShelfItems& items() const { return items_; } + int item_count() const { return static_cast(items_.size()); } + + void SetStatus(Status status); + Status status() const { return status_; } + + void AddObserver(ShelfModelObserver* observer); + void RemoveObserver(ShelfModelObserver* observer); + + // TODO(msw): Do not expose this member, used by ShelfView to FocusUserWindow. + mash::wm::mojom::UserWindowController* user_window_controller() const { + return user_window_controller_.get(); + } + + private: + // Overridden from mash::wm::mojom::UserWindowObserver: + void OnUserWindowObserverAdded( + mojo::Array user_windows) override; + void OnUserWindowAdded(mash::wm::mojom::UserWindowPtr user_window) override; + void OnUserWindowRemoved(uint32_t window_id) override; + void OnUserWindowTitleChanged(uint32_t window_id, + const mojo::String& window_title) override; + + // Makes sure |index| is in line with the type-based order of items. If that + // is not the case, adjusts index by shifting it to the valid range and + // returns the new value. + int ValidateInsertionIndex(ShelfItemType type, int index) const; + + // Return the index of the item by window id. + int ItemIndexByWindowID(uint32_t window_id) const; + + // ID assigned to the next item. + ShelfID next_id_; + + ShelfItems items_; + Status status_; + base::ObserverList observers_; + + mash::wm::mojom::UserWindowControllerPtr user_window_controller_; + mojo::Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(ShelfModel); +}; + +} // namespace shelf +} // namespace mash + +#endif // MASH_SHELF_SHELF_MODEL_H_ diff --git a/mash/shelf/shelf_model_observer.h b/mash/shelf/shelf_model_observer.h new file mode 100644 index 0000000000000..cb3ebe3808a70 --- /dev/null +++ b/mash/shelf/shelf_model_observer.h @@ -0,0 +1,42 @@ +// Copyright 2013 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 MASH_SHELF_SHELF_MODEL_OBSERVER_H_ +#define MASH_SHELF_SHELF_MODEL_OBSERVER_H_ + +#include "mash/shelf/shelf_item_types.h" + +namespace mash { +namespace shelf { + +struct ShelfItem; + +class ShelfModelObserver { + public: + // Invoked after an item has been added to the model. + virtual void ShelfItemAdded(int index) = 0; + + // Invoked after an item has been removed. |index| is the index the item was + // at. + virtual void ShelfItemRemoved(int index, ShelfID id) = 0; + + // Invoked after an item has been moved. See ShelfModel::Move() for details + // of the arguments. + virtual void ShelfItemMoved(int start_index, int target_index) = 0; + + // Invoked when the state of an item changes. |old_item| is the item + // before the change. + virtual void ShelfItemChanged(int index, const ShelfItem& old_item) = 0; + + // Invoked when shelf status is changed. + virtual void ShelfStatusChanged() = 0; + + protected: + virtual ~ShelfModelObserver() {} +}; + +} // namespace shelf +} // namespace mash + +#endif // MASH_SHELF_SHELF_MODEL_OBSERVER_H_ diff --git a/mash/shelf/shelf_tooltip_manager.cc b/mash/shelf/shelf_tooltip_manager.cc new file mode 100644 index 0000000000000..635b2a58496fb --- /dev/null +++ b/mash/shelf/shelf_tooltip_manager.cc @@ -0,0 +1,361 @@ +// Copyright 2013 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 "mash/shelf/shelf_tooltip_manager.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "components/mus/public/cpp/property_type_converters.h" +#include "mash/shelf/shelf_view.h" +#include "mash/wm/public/interfaces/container.mojom.h" +#include "mojo/shell/public/cpp/application_impl.h" +#include "ui/aura/window.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/views/bubble/bubble_delegate.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/mus/native_widget_mus.h" +#include "ui/views/mus/window_manager_connection.h" +#include "ui/views/widget/widget.h" +#include "ui/wm/core/window_animations.h" + +namespace mash { +namespace shelf { +namespace { +const int kTooltipTopBottomMargin = 3; +const int kTooltipLeftRightMargin = 10; +const int kTooltipAppearanceDelay = 1000; // msec +const int kTooltipMinHeight = 29 - 2 * kTooltipTopBottomMargin; +const SkColor kTooltipTextColor = SkColorSetRGB(0x22, 0x22, 0x22); + +// The maximum width of the tooltip bubble. Borrowed the value from +// ash/tooltip/tooltip_controller.cc +const int kTooltipMaxWidth = 250; + +// The offset for the tooltip bubble - making sure that the bubble is flush +// with the shelf. The offset includes the arrow size in pixels as well as +// the activation bar and other spacing elements. +const int kArrowOffsetLeftRight = 11; +const int kArrowOffsetTopBottom = 7; + +} // namespace + +// The implementation of tooltip of the launcher. +class ShelfTooltipManager::ShelfTooltipBubble + : public views::BubbleDelegateView { + public: + ShelfTooltipBubble(views::View* anchor, + views::BubbleBorder::Arrow arrow, + const base::string16& text, + ShelfTooltipManager* host); + + void SetText(const base::string16& text); + void Close(); + + private: + // views::BubbleDelegateView overrides: + void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params, + views::Widget* widget) const override; + + // views::WidgetDelegate overrides: + bool ShouldShowWindowTitle() const override; + void WindowClosing() override; + + // views::View overrides: + gfx::Size GetPreferredSize() const override; + + ShelfTooltipManager* host_; + views::Label* label_; + + DISALLOW_COPY_AND_ASSIGN(ShelfTooltipBubble); +}; + +ShelfTooltipManager::ShelfTooltipBubble::ShelfTooltipBubble( + views::View* anchor, + views::BubbleBorder::Arrow arrow, + const base::string16& text, + ShelfTooltipManager* host) + : views::BubbleDelegateView(anchor, arrow), host_(host) { + gfx::Insets insets = gfx::Insets(kArrowOffsetTopBottom, + kArrowOffsetLeftRight, + kArrowOffsetTopBottom, + kArrowOffsetLeftRight); + // Shelf items can have an asymmetrical border for spacing reasons. + // Adjust anchor location for this. + if (anchor->border()) + insets += anchor->border()->GetInsets(); + + set_anchor_view_insets(insets); + set_close_on_esc(false); + set_close_on_deactivate(false); + set_can_activate(false); + set_accept_events(false); + set_margins(gfx::Insets(kTooltipTopBottomMargin, kTooltipLeftRightMargin, + kTooltipTopBottomMargin, kTooltipLeftRightMargin)); + set_shadow(views::BubbleBorder::SMALL_SHADOW); + SetLayoutManager(new views::FillLayout()); + label_ = new views::Label(text); + label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + label_->SetEnabledColor(kTooltipTextColor); + AddChildView(label_); + views::BubbleDelegateView::CreateBubble(this); +} + +void ShelfTooltipManager::ShelfTooltipBubble::Close() { + if (GetWidget()) { + host_ = nullptr; + GetWidget()->Close(); + } +} + +void ShelfTooltipManager::ShelfTooltipBubble::OnBeforeBubbleWidgetInit( + views::Widget::InitParams* params, + views::Widget* widget) const { + // Ensure the widget is treated as a tooltip by the window manager. + std::map> properties; + properties[mash::wm::mojom::kWindowContainer_Property] = + mojo::TypeConverter, int32_t>::Convert( + static_cast(mash::wm::mojom::Container::TOOLTIPS)); + mus::Window* window = + views::WindowManagerConnection::Get()->NewWindow(properties); + params->native_widget = new views::NativeWidgetMus( + widget, host_->shelf_view()->app()->shell(), window, + mus::mojom::SurfaceType::DEFAULT); +} + +void ShelfTooltipManager::ShelfTooltipBubble::WindowClosing() { + views::BubbleDelegateView::WindowClosing(); + if (host_) + host_->OnBubbleClosed(this); +} + +bool ShelfTooltipManager::ShelfTooltipBubble::ShouldShowWindowTitle() const { + return false; +} + +gfx::Size ShelfTooltipManager::ShelfTooltipBubble::GetPreferredSize() const { + gfx::Size pref_size = views::BubbleDelegateView::GetPreferredSize(); + if (pref_size.height() < kTooltipMinHeight) + pref_size.set_height(kTooltipMinHeight); + if (pref_size.width() > kTooltipMaxWidth) + pref_size.set_width(kTooltipMaxWidth); + return pref_size; +} + +ShelfTooltipManager::ShelfTooltipManager(ShelfView* shelf_view) + : view_(nullptr), + widget_(nullptr), + shelf_view_(shelf_view), + weak_factory_(this) { + // Ensure mouse movements between the shelf and its buttons isn't an exit. + shelf_view_->set_notify_enter_exit_on_child(true); + shelf_view_->AddPreTargetHandler(this); +} + +ShelfTooltipManager::~ShelfTooltipManager() { + CancelHidingAnimation(); + Close(); + shelf_view_->RemovePreTargetHandler(this); +} + +void ShelfTooltipManager::ShowDelayed(views::View* anchor, + const base::string16& text) { + if (view_) { + if (timer_.get() && timer_->IsRunning()) { + return; + } else { + CancelHidingAnimation(); + Close(); + } + } + + if (!shelf_view_->GetWidget() || !shelf_view_->GetWidget()->IsVisible()) + return; + + CreateBubble(anchor, text); + ResetTimer(); +} + +void ShelfTooltipManager::ShowImmediately(views::View* anchor, + const base::string16& text) { + if (view_) { + if (timer_.get() && timer_->IsRunning()) + StopTimer(); + CancelHidingAnimation(); + Close(); + } + + if (!shelf_view_->GetWidget() || !shelf_view_->GetWidget()->IsVisible()) + return; + + CreateBubble(anchor, text); + ShowInternal(); +} + +void ShelfTooltipManager::Close() { + StopTimer(); + if (view_) { + view_->Close(); + view_ = nullptr; + widget_ = nullptr; + } +} + +void ShelfTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) { + if (view == view_) { + view_ = nullptr; + widget_ = nullptr; + } +} + +void ShelfTooltipManager::ResetTimer() { + if (timer_.get() && timer_->IsRunning()) { + timer_->Reset(); + return; + } + + // We don't start the timer if the shelf isn't visible. + if (!shelf_view_->GetWidget() || !shelf_view_->GetWidget()->IsVisible()) + return; + + CreateTimer(kTooltipAppearanceDelay); +} + +void ShelfTooltipManager::StopTimer() { + timer_.reset(); +} + +bool ShelfTooltipManager::IsVisible() const { + if (timer_.get() && timer_->IsRunning()) + return false; + + return widget_ && widget_->IsVisible(); +} + +views::View* ShelfTooltipManager::GetCurrentAnchorView() const { + return view_ ? view_->GetAnchorView() : nullptr; +} + +void ShelfTooltipManager::CreateZeroDelayTimerForTest() { + CreateTimer(0); +} + +void ShelfTooltipManager::OnMouseEvent(ui::MouseEvent* event) { + DCHECK(event); + DCHECK(event->target()); + if (!widget_ || !widget_->IsVisible()) + return; + + DCHECK(view_); + DCHECK(shelf_view_); + + // Close the tooltip when the mouse is pressed or exits the shelf view area. + if (event->type() == ui::ET_MOUSE_PRESSED || + (event->type() == ui::ET_MOUSE_EXITED && + event->target() == shelf_view_)) { + CloseSoon(); + return; + } + + gfx::Point location = event->location(); + views::View* target = static_cast(event->target()); + views::View::ConvertPointToTarget(target, shelf_view_, &location); + if (shelf_view_->ShouldHideTooltip(location)) { + // Because this mouse event may arrive to |view_|, here we just schedule + // the closing event rather than directly calling Close(). + CloseSoon(); + } +} + +void ShelfTooltipManager::OnTouchEvent(ui::TouchEvent* event) { + // TODO(msw): Fix this; touch events outside the shelf are not captured. + views::View* view = static_cast(event->target()); + aura::Window* target = view->GetWidget()->GetNativeWindow(); + if (widget_ && widget_->IsVisible() && widget_->GetNativeWindow() != target) + Close(); +} + +void ShelfTooltipManager::OnGestureEvent(ui::GestureEvent* event) { + if (widget_ && widget_->IsVisible()) { + // Because this mouse event may arrive to |view_|, here we just schedule + // the closing event rather than directly calling Close(). + CloseSoon(); + } +} + +void ShelfTooltipManager::OnCancelMode(ui::CancelModeEvent* event) { + Close(); +} + +/* TODO(msw): Restore functionality: +void ShelfTooltipManager::WillChangeVisibilityState( + ShelfVisibilityState new_state) { + if (new_state == SHELF_HIDDEN) { + StopTimer(); + Close(); + } +} + +void ShelfTooltipManager::OnAutoHideStateChanged(ShelfAutoHideState new_state) { + if (new_state == SHELF_AUTO_HIDE_HIDDEN) { + StopTimer(); + // AutoHide state change happens during an event filter, so immediate close + // may cause a crash in the HandleMouseEvent() after the filter. So we just + // schedule the Close here. + CloseSoon(); + } +}*/ + +void ShelfTooltipManager::CancelHidingAnimation() { + if (!widget_ || !widget_->GetNativeView()) + return; + + ::wm::SetWindowVisibilityAnimationTransition(widget_->GetNativeView(), + ::wm::ANIMATE_NONE); +} + +void ShelfTooltipManager::CloseSoon() { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&ShelfTooltipManager::Close, weak_factory_.GetWeakPtr())); +} + +void ShelfTooltipManager::ShowInternal() { + if (view_) + view_->GetWidget()->Show(); + + timer_.reset(); +} + +void ShelfTooltipManager::CreateBubble(views::View* anchor, + const base::string16& text) { + DCHECK(!view_); + views::BubbleBorder::Arrow arrow = shelf_view_->SelectValueForShelfAlignment( + views::BubbleBorder::BOTTOM_CENTER, views::BubbleBorder::LEFT_CENTER, + views::BubbleBorder::RIGHT_CENTER, views::BubbleBorder::TOP_CENTER); + view_ = new ShelfTooltipBubble(anchor, arrow, text, this); + widget_ = view_->GetWidget(); + + gfx::NativeView native_view = widget_->GetNativeView(); + ::wm::SetWindowVisibilityAnimationType( + native_view, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL); + ::wm::SetWindowVisibilityAnimationTransition( + native_view, ::wm::ANIMATE_HIDE); +} + +void ShelfTooltipManager::CreateTimer(int delay_in_ms) { + base::OneShotTimer* new_timer = new base::OneShotTimer(); + new_timer->Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(delay_in_ms), + this, + &ShelfTooltipManager::ShowInternal); + timer_.reset(new_timer); +} + +} // namespace shelf +} // namespace mash diff --git a/mash/shelf/shelf_tooltip_manager.h b/mash/shelf/shelf_tooltip_manager.h new file mode 100644 index 0000000000000..48d598602d1f2 --- /dev/null +++ b/mash/shelf/shelf_tooltip_manager.h @@ -0,0 +1,107 @@ +// Copyright 2013 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 MASH_SHELF_SHELF_TOOLTIP_MANAGER_H_ +#define MASH_SHELF_SHELF_TOOLTIP_MANAGER_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "mash/shelf/shelf_types.h" +#include "ui/events/event_handler.h" + +namespace base { +class Timer; +} + +namespace views { +class BubbleDelegateView; +class View; +class Widget; +} + +namespace mash { +namespace shelf { + +class ShelfView; + +namespace test { +class ShelfTooltipManagerTest; +class ShelfViewTest; +} + +// ShelfTooltipManager manages the tooltip balloon poping up on shelf items. +class ShelfTooltipManager : public ui::EventHandler { + public: + explicit ShelfTooltipManager(ShelfView* shelf_view); + ~ShelfTooltipManager() override; + + // Called when the bubble is closed. + void OnBubbleClosed(views::BubbleDelegateView* view); + + // Shows the tooltip after a delay. It also has the appearing animation. + void ShowDelayed(views::View* anchor, const base::string16& text); + + // Shows the tooltip immediately. It omits the appearing animation. + void ShowImmediately(views::View* anchor, const base::string16& text); + + // Closes the tooltip. + void Close(); + + // Resets the timer for the delayed showing |view_|. If the timer isn't + // running, it starts a new timer. + void ResetTimer(); + + // Stops the timer for the delayed showing |view_|. + void StopTimer(); + + // Returns true if the tooltip is currently visible. + bool IsVisible() const; + + // Returns the view to which the tooltip bubble is anchored. May be NULL. + views::View* GetCurrentAnchorView() const; + + // Create an instant timer for test purposes. + void CreateZeroDelayTimerForTest(); + + ShelfView* shelf_view() const { return shelf_view_; } + +protected: + // ui::EventHandler overrides: + void OnMouseEvent(ui::MouseEvent* event) override; + void OnTouchEvent(ui::TouchEvent* event) override; + void OnGestureEvent(ui::GestureEvent* event) override; + void OnCancelMode(ui::CancelModeEvent* event) override; + + /* TODO(msw): Restore functionality: + // ShelfLayoutManagerObserver overrides: + void WillChangeVisibilityState(ShelfVisibilityState new_state) override; + void OnAutoHideStateChanged(ShelfAutoHideState new_state) override;*/ + + private: + class ShelfTooltipBubble; + friend class test::ShelfViewTest; + friend class test::ShelfTooltipManagerTest; + + void CancelHidingAnimation(); + void CloseSoon(); + void ShowInternal(); + void CreateBubble(views::View* anchor, const base::string16& text); + void CreateTimer(int delay_in_ms); + + ShelfTooltipBubble* view_; + views::Widget* widget_; + scoped_ptr timer_; + ShelfView* shelf_view_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ShelfTooltipManager); +}; + +} // namespace shelf +} // namespace mash + +#endif // MASH_SHELF_SHELF_TOOLTIP_MANAGER_H_ diff --git a/mash/shelf/shelf_types.h b/mash/shelf/shelf_types.h new file mode 100644 index 0000000000000..e2a7ee4a6faab --- /dev/null +++ b/mash/shelf/shelf_types.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 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 MASH_SHELF_SHELF_TYPES_H_ +#define MASH_SHELF_SHELF_TYPES_H_ + +namespace mash { +namespace shelf { + +enum ShelfAlignment { + SHELF_ALIGNMENT_BOTTOM, + SHELF_ALIGNMENT_LEFT, + SHELF_ALIGNMENT_RIGHT, + SHELF_ALIGNMENT_TOP, +}; + +enum ShelfAutoHideBehavior { + // Always auto-hide. + SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, + + // Never auto-hide. + SHELF_AUTO_HIDE_BEHAVIOR_NEVER, + + // Always hide. + SHELF_AUTO_HIDE_ALWAYS_HIDDEN, +}; + +enum ShelfVisibilityState { + // Always visible. + SHELF_VISIBLE, + + // A couple of pixels are reserved at the bottom for the shelf. + SHELF_AUTO_HIDE, + + // Nothing is shown. Used for fullscreen windows. + SHELF_HIDDEN, +}; + +enum ShelfAutoHideState { + SHELF_AUTO_HIDE_SHOWN, + SHELF_AUTO_HIDE_HIDDEN, +}; + +enum ShelfBackgroundType { + // The default transparent background. + SHELF_BACKGROUND_DEFAULT, + + // The background when a window is overlapping. + SHELF_BACKGROUND_OVERLAP, + + // The background when a window is maximized. + SHELF_BACKGROUND_MAXIMIZED, +}; + +} // namespace shelf +} // namespace mash + +#endif // MASH_SHELF_SHELF_TYPES_H_ diff --git a/mash/shelf/shelf_view.cc b/mash/shelf/shelf_view.cc index d25f48ccb77de..c22a0e39df787 100644 --- a/mash/shelf/shelf_view.cc +++ b/mash/shelf/shelf_view.cc @@ -4,92 +4,1927 @@ #include "mash/shelf/shelf_view.h" -#include "base/strings/stringprintf.h" -#include "base/strings/utf_string_conversions.h" -#include "mojo/common/common_type_converters.h" -#include "mojo/shell/public/cpp/application_impl.h" +#include + +#include "base/auto_reset.h" +#include "mash/shelf/shelf_button.h" +#include "mash/shelf/shelf_constants.h" +#include "ui/accessibility/ax_view_state.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/simple_menu_model.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_animator.h" +#include "ui/compositor/scoped_animation_duration_scale_mode.h" +#include "ui/events/event_utils.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/point.h" +#include "ui/resources/grit/ui_resources.h" +#include "ui/views/animation/bounds_animator.h" +#include "ui/views/background.h" +#include "ui/views/border.h" +#include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/label_button.h" -#include "ui/views/layout/box_layout.h" +#include "ui/views/controls/menu/menu_model_adapter.h" +#include "ui/views/controls/menu/menu_runner.h" +#include "ui/views/focus/focus_search.h" +#include "ui/views/view_model_utils.h" +#include "ui/views/widget/widget.h" +#include "ui/wm/core/coordinate_conversion.h" namespace mash { namespace shelf { -ShelfView::ShelfView(mojo::ApplicationImpl* app) : binding_(this) { - app->ConnectToService("mojo:desktop_wm", &user_window_controller_); +const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM = 0; +const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT = 1; +const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT = 2; +const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT = 3; + +// Default amount content is inset on the left edge. +const int kDefaultLeadingInset = 8; + +// Minimum distance before drag starts. +const int kMinimumDragDistance = 8; + +// The proportion of the shelf space reserved for non-panel icons. Panels +// may flow into this space but will be put into the overflow bubble if there +// is contention for the space. +const float kReservedNonPanelIconProportion = 0.67f; + +/* TODO(msw): Restore functionality: +// The distance of the cursor from the outer rim of the shelf before it +// separates. +const int kRipOffDistance = 48; + +// The rip off drag and drop proxy image should get scaled by this factor. +const float kDragAndDropProxyScale = 1.5f; + +// The opacity represents that this partially disappeared item will get removed. +const float kDraggedImageOpacity = 0.5f;*/ + +namespace { + +// A class to temporarily disable a given bounds animator. +class BoundsAnimatorDisabler { + public: + explicit BoundsAnimatorDisabler(views::BoundsAnimator* bounds_animator) + : old_duration_(bounds_animator->GetAnimationDuration()), + bounds_animator_(bounds_animator) { + bounds_animator_->SetAnimationDuration(1); + } + + ~BoundsAnimatorDisabler() { + bounds_animator_->SetAnimationDuration(old_duration_); + } + + private: + // The previous animation duration. + int old_duration_; + // The bounds animator which gets used. + views::BoundsAnimator* bounds_animator_; + + DISALLOW_COPY_AND_ASSIGN(BoundsAnimatorDisabler); +}; + +// Custom FocusSearch used to navigate the shelf in the order items are in +// the ViewModel. +class ShelfFocusSearch : public views::FocusSearch { + public: + explicit ShelfFocusSearch(views::ViewModel* view_model) + : FocusSearch(nullptr, true, true), + view_model_(view_model) {} + ~ShelfFocusSearch() override {} + + // views::FocusSearch overrides: + views::View* FindNextFocusableView( + views::View* starting_view, + bool reverse, + Direction direction, + bool check_starting_view, + views::FocusTraversable** focus_traversable, + views::View** focus_traversable_view) override { + int index = view_model_->GetIndexOfView(starting_view); + if (index == -1) + return view_model_->view_at(0); + + if (reverse) { + --index; + if (index < 0) + index = view_model_->view_size() - 1; + } else { + ++index; + if (index >= view_model_->view_size()) + index = 0; + } + return view_model_->view_at(index); + } + + private: + views::ViewModel* view_model_; + + DISALLOW_COPY_AND_ASSIGN(ShelfFocusSearch); +}; + +// AnimationDelegate used when inserting a new item. This steadily increases the +// opacity of the layer as the animation progress. +class FadeInAnimationDelegate : public gfx::AnimationDelegate { + public: + explicit FadeInAnimationDelegate(views::View* view) : view_(view) {} + ~FadeInAnimationDelegate() override {} + + // AnimationDelegate overrides: + void AnimationProgressed(const gfx::Animation* animation) override { + view_->layer()->SetOpacity(animation->GetCurrentValue()); + view_->layer()->ScheduleDraw(); + } + void AnimationEnded(const gfx::Animation* animation) override { + view_->layer()->SetOpacity(1.0f); + view_->layer()->ScheduleDraw(); + } + void AnimationCanceled(const gfx::Animation* animation) override { + view_->layer()->SetOpacity(1.0f); + view_->layer()->ScheduleDraw(); + } + + private: + views::View* view_; + + DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate); +}; + +void ReflectItemStatus(const ShelfItem& item, ShelfButton* button) { + switch (item.status) { + case STATUS_CLOSED: + button->ClearState(ShelfButton::STATE_ACTIVE); + button->ClearState(ShelfButton::STATE_RUNNING); + button->ClearState(ShelfButton::STATE_ATTENTION); + break; + case STATUS_RUNNING: + button->ClearState(ShelfButton::STATE_ACTIVE); + button->AddState(ShelfButton::STATE_RUNNING); + button->ClearState(ShelfButton::STATE_ATTENTION); + break; + case STATUS_ACTIVE: + button->AddState(ShelfButton::STATE_ACTIVE); + button->ClearState(ShelfButton::STATE_RUNNING); + button->ClearState(ShelfButton::STATE_ATTENTION); + break; + case STATUS_ATTENTION: + button->ClearState(ShelfButton::STATE_ACTIVE); + button->ClearState(ShelfButton::STATE_RUNNING); + button->AddState(ShelfButton::STATE_ATTENTION); + break; + } +} + +} // namespace + +// AnimationDelegate used when deleting an item. This steadily decreased the +// opacity of the layer as the animation progress. +class ShelfView::FadeOutAnimationDelegate : public gfx::AnimationDelegate { + public: + FadeOutAnimationDelegate(ShelfView* host, views::View* view) + : shelf_view_(host), + view_(view) {} + ~FadeOutAnimationDelegate() override {} + + // AnimationDelegate overrides: + void AnimationProgressed(const gfx::Animation* animation) override { + view_->layer()->SetOpacity(1 - animation->GetCurrentValue()); + view_->layer()->ScheduleDraw(); + } + void AnimationEnded(const gfx::Animation* animation) override { + shelf_view_->OnFadeOutAnimationEnded(); + } + void AnimationCanceled(const gfx::Animation* animation) override {} + + private: + ShelfView* shelf_view_; + scoped_ptr 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 ShelfView::StartFadeAnimationDelegate : public gfx::AnimationDelegate { + public: + StartFadeAnimationDelegate(ShelfView* host, views::View* view) + : shelf_view_(host), + view_(view) {} + ~StartFadeAnimationDelegate() override {} + + // AnimationDelegate overrides: + void AnimationEnded(const gfx::Animation* animation) override { + shelf_view_->FadeIn(view_); + } + void AnimationCanceled(const gfx::Animation* animation) override { + view_->layer()->SetOpacity(1.0f); + } + + private: + ShelfView* shelf_view_; + views::View* view_; + + DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate); +}; + +ShelfView::ShelfView(mojo::ApplicationImpl* app) + : app_(app), + model_(app), + alignment_(SHELF_ALIGNMENT_BOTTOM), + first_visible_index_(0), + last_visible_index_(-1), + /* TODO(msw): Restore functionality: + overflow_button_(nullptr), + owner_overflow_bubble_(nullptr),*/ + tooltip_(this), + drag_pointer_(NONE), + drag_view_(nullptr), + start_drag_index_(-1), + context_menu_id_(0), + leading_inset_(kDefaultLeadingInset), + cancelling_drag_model_changed_(false), + last_hidden_index_(0), + closing_event_time_(), + got_deleted_(nullptr), + drag_and_drop_item_pinned_(false), + drag_and_drop_shelf_id_(0), + drag_replaced_view_(nullptr), + dragged_off_shelf_(false), + snap_back_from_rip_off_view_(nullptr), + /* TODO(msw): Restore functionality: + item_manager_(Shell::GetInstance()->shelf_item_delegate_manager()),*/ + overflow_mode_(false), + main_shelf_(nullptr), + dragged_off_from_overflow_to_shelf_(false), + is_repost_event_(false), + last_pressed_index_(-1) { + bounds_animator_.reset(new views::BoundsAnimator(this)); + bounds_animator_->AddObserver(this); + set_context_menu_controller(this); + focus_search_.reset(new ShelfFocusSearch(&view_model_)); + + model_.AddObserver(this); + const ShelfItems& items(model_.items()); + for (ShelfItems::const_iterator i = items.begin(); i != items.end(); ++i) { + views::View* child = CreateViewForItem(*i); + view_model_.Add(child, static_cast(i - items.begin())); + AddChildView(child); + } + + /* TODO(msw): Restore functionality: + overflow_button_ = new OverflowButton(this); + overflow_button_->set_context_menu_controller(this); + ConfigureChildView(overflow_button_); + AddChildView(overflow_button_);*/ + + /* TODO(msw): Add a stub apps button? + ShelfItem app_list; + app_list.type = TYPE_APP_LIST; + app_list.title = base::ASCIIToUTF16("APPS"); + model()->Add(app_list);*/ + + // TODO(msw): Needed to paint children as layers??? + SetPaintToLayer(true); + set_background(views::Background::CreateSolidBackground(SK_ColorYELLOW)); + + // We'll layout when our bounds change. +} + +ShelfView::~ShelfView() { + bounds_animator_->RemoveObserver(this); + model_.RemoveObserver(this); + // If we are inside the MenuRunner, we need to know if we were getting + // deleted while it was running. + if (got_deleted_) + *got_deleted_ = true; +} + +void ShelfView::SetAlignment(ShelfAlignment alignment) { + if (alignment_ == alignment) + return; + + alignment_ = alignment; + /* TODO(msw): Restore functionality: + overflow_button_->OnShelfAlignmentChanged();*/ + LayoutToIdealBounds(); + for (int i = 0; i < view_model_.view_size(); ++i) { + if (i >= first_visible_index_ && i <= last_visible_index_) + view_model_.view_at(i)->Layout(); + } + tooltip_.Close(); + /* TODO(msw): Restore functionality: + if (overflow_bubble_) + overflow_bubble_->Hide();*/ +} + +void ShelfView::SchedulePaintForAllButtons() { + for (int i = 0; i < view_model_.view_size(); ++i) { + if (i >= first_visible_index_ && i <= last_visible_index_) + view_model_.view_at(i)->SchedulePaint(); + } + /* TODO(msw): Restore functionality: + if (overflow_button_ && overflow_button_->visible()) + overflow_button_->SchedulePaint();*/ +} + +gfx::Rect ShelfView::GetIdealBoundsOfItemIcon(ShelfID id) { + int index = model_.ItemIndexByID(id); + if (index == -1) + return gfx::Rect(); + // Map all items from overflow area to the overflow button. Note that the + // section between last_index_hidden_ and model_.FirstPanelIndex() is the + // list of invisible panel items. However, these items are currently nowhere + // represented and get dropped instead - see (crbug.com/378907). As such there + // is no way to address them or place them. We therefore move them over the + // overflow button. + if (index > last_visible_index_ && index < model_.FirstPanelIndex()) + index = last_visible_index_ + 1; + const gfx::Rect& ideal_bounds(view_model_.ideal_bounds(index)); + DCHECK_NE(TYPE_APP_LIST, model_.items()[index].type); + views::View* view = view_model_.view_at(index); + CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName()); + ShelfButton* button = static_cast(view); + gfx::Rect icon_bounds = button->GetIconBounds(); + return gfx::Rect(GetMirroredXWithWidthInView( + ideal_bounds.x() + icon_bounds.x(), icon_bounds.width()), + ideal_bounds.y() + icon_bounds.y(), + icon_bounds.width(), + icon_bounds.height()); +} + +void ShelfView::UpdatePanelIconPosition(ShelfID id, + const gfx::Point& midpoint) { + int current_index = model_.ItemIndexByID(id); + int first_panel_index = model_.FirstPanelIndex(); + if (current_index < first_panel_index) + return; + + gfx::Point midpoint_in_view(GetMirroredXInView(midpoint.x()), + midpoint.y()); + int target_index = current_index; + while (target_index > first_panel_index && + PrimaryAxisValue(view_model_.ideal_bounds(target_index).x(), + view_model_.ideal_bounds(target_index).y()) > + PrimaryAxisValue(midpoint_in_view.x(), + midpoint_in_view.y())) { + --target_index; + } + while (target_index < view_model_.view_size() - 1 && + PrimaryAxisValue(view_model_.ideal_bounds(target_index).right(), + view_model_.ideal_bounds(target_index).bottom()) < + PrimaryAxisValue(midpoint_in_view.x(), + midpoint_in_view.y())) { + ++target_index; + } + if (current_index != target_index) + model_.Move(current_index, target_index); +} + +bool ShelfView::IsShowingMenu() const { + return (launcher_menu_runner_.get() && + launcher_menu_runner_->IsRunning()); +} + +bool ShelfView::IsShowingOverflowBubble() const { + /* TODO(msw): Restore functionality: + return overflow_bubble_.get() && overflow_bubble_->IsShowing();*/ + return false; +} + +views::View* ShelfView::GetAppListButtonView() const { + for (int i = 0; i < model_.item_count(); ++i) { + if (model_.items()[i].type == TYPE_APP_LIST) + return view_model_.view_at(i); + } + + NOTREACHED() << "Applist button not found"; + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// ShelfView, FocusTraversable implementation: + +views::FocusSearch* ShelfView::GetFocusSearch() { + return focus_search_.get(); +} + +views::FocusTraversable* ShelfView::GetFocusTraversableParent() { + return parent()->GetFocusTraversable(); +} + +views::View* ShelfView::GetFocusTraversableParentView() { + return this; +} + +/* TODO(msw): Restore drag/drop functionality. +void ShelfView::CreateDragIconProxy( + const gfx::Point& location_in_screen_coordinates, + const gfx::ImageSkia& icon, + views::View* replaced_view, + const gfx::Vector2d& cursor_offset_from_center, + float scale_factor) { + drag_replaced_view_ = replaced_view; + drag_image_.reset(new ash::DragImageView( + drag_replaced_view_->GetWidget()->GetNativeWindow()->GetRootWindow(), + ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE)); + drag_image_->SetImage(icon); + gfx::Size size = drag_image_->GetPreferredSize(); + size.set_width(size.width() * scale_factor); + size.set_height(size.height() * scale_factor); + drag_image_offset_ = gfx::Vector2d(size.width() / 2, size.height() / 2) + + cursor_offset_from_center; + gfx::Rect drag_image_bounds( + location_in_screen_coordinates - drag_image_offset_, + size); + drag_image_->SetBoundsInScreen(drag_image_bounds); + drag_image_->SetWidgetVisible(true); +} + +void ShelfView::UpdateDragIconProxy( + const gfx::Point& location_in_screen_coordinates) { + // TODO(jennyz): Investigate why drag_image_ becomes NULL at this point per + // crbug.com/34722, while the app list item is still being dragged around. + if (drag_image_) { + drag_image_->SetScreenPosition( + location_in_screen_coordinates - drag_image_offset_); + } +} + +void ShelfView::DestroyDragIconProxy() { + drag_image_.reset(); + drag_image_offset_ = gfx::Vector2d(0, 0); +} + +bool ShelfView::StartDrag(const std::string& app_id, + const gfx::Point& location_in_screen_coordinates) { + // Bail if an operation is already going on - or the cursor is not inside. + // This could happen if mouse / touch operations overlap. + if (drag_and_drop_shelf_id_ || + !GetBoundsInScreen().Contains(location_in_screen_coordinates)) + return false; + + // If the AppsGridView (which was dispatching this event) was opened by our + // button, ShelfView dragging operations are locked and we have to unlock. + CancelDrag(-1); + drag_and_drop_item_pinned_ = false; + drag_and_drop_app_id_ = app_id; + drag_and_drop_shelf_id_ = + delegate_->GetShelfIDForAppID(drag_and_drop_app_id_); + // Check if the application is known and pinned - if not, we have to pin it so + // that we can re-arrange the shelf order accordingly. Note that items have + // to be pinned to give them the same (order) possibilities as a shortcut. + // When an item is dragged from overflow to shelf, IsShowingOverflowBubble() + // returns true. At this time, we don't need to pin the item. + if (!IsShowingOverflowBubble() && + (!drag_and_drop_shelf_id_ || + !delegate_->IsAppPinned(app_id))) { + delegate_->PinAppWithID(app_id); + drag_and_drop_shelf_id_ = + delegate_->GetShelfIDForAppID(drag_and_drop_app_id_); + if (!drag_and_drop_shelf_id_) + return false; + drag_and_drop_item_pinned_ = true; + } + views::View* drag_and_drop_view = view_model_->view_at( + model_.ItemIndexByID(drag_and_drop_shelf_id_)); + DCHECK(drag_and_drop_view); + + // Since there is already an icon presented by the caller, we hide this item + // for now. That has to be done by reducing the size since the visibility will + // change once a regrouping animation is performed. + pre_drag_and_drop_size_ = drag_and_drop_view->size(); + drag_and_drop_view->SetSize(gfx::Size()); + + // First we have to center the mouse cursor over the item. + gfx::Point pt = drag_and_drop_view->GetBoundsInScreen().CenterPoint(); + views::View::ConvertPointFromScreen(drag_and_drop_view, &pt); + gfx::Point point_in_root = location_in_screen_coordinates; + ::wm::ConvertPointFromScreen( + ash::wm::GetRootWindowAt(location_in_screen_coordinates), &point_in_root); + ui::MouseEvent event(ui::ET_MOUSE_PRESSED, pt, point_in_root, + ui::EventTimeForNow(), 0, 0); + PointerPressedOnButton(drag_and_drop_view, + ShelfButtonHost::DRAG_AND_DROP, + event); + + // Drag the item where it really belongs. + Drag(location_in_screen_coordinates); + return true; +} + +bool ShelfView::Drag(const gfx::Point& location_in_screen_coordinates) { + if (!drag_and_drop_shelf_id_ || + !GetBoundsInScreen().Contains(location_in_screen_coordinates)) + return false; + + gfx::Point pt = location_in_screen_coordinates; + views::View* drag_and_drop_view = view_model_->view_at( + model_.ItemIndexByID(drag_and_drop_shelf_id_)); + ConvertPointFromScreen(drag_and_drop_view, &pt); + gfx::Point point_in_root = location_in_screen_coordinates; + ::wm::ConvertPointFromScreen( + ash::wm::GetRootWindowAt(location_in_screen_coordinates), &point_in_root); + ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, point_in_root, + ui::EventTimeForNow(), 0, 0); + PointerDraggedOnButton(drag_and_drop_view, + ShelfButtonHost::DRAG_AND_DROP, + event); + return true; +} + +void ShelfView::EndDrag(bool cancel) { + if (!drag_and_drop_shelf_id_) + return; + + views::View* drag_and_drop_view = view_model_->view_at( + model_.ItemIndexByID(drag_and_drop_shelf_id_)); + PointerReleasedOnButton( + drag_and_drop_view, ShelfButtonHost::DRAG_AND_DROP, cancel); + + // Either destroy the temporarily created item - or - make the item visible. + if (drag_and_drop_item_pinned_ && cancel) { + delegate_->UnpinAppWithID(drag_and_drop_app_id_); + } else if (drag_and_drop_view) { + if (cancel) { + // When a hosted drag gets canceled, the item can remain in the same slot + // and it might have moved within the bounds. In that case the item need + // to animate back to its correct location. + AnimateToIdealBounds(); + } else { + drag_and_drop_view->SetSize(pre_drag_and_drop_size_); + } + } + + drag_and_drop_shelf_id_ = 0; +}*/ + +void ShelfView::LayoutToIdealBounds() { + if (bounds_animator_->IsAnimating()) { + AnimateToIdealBounds(); + return; + } + + CalculateIdealBounds(); + /* TODO(msw): Restore functionality: + gfx::Rect overflow_bounds = CalculateIdealBounds();*/ + views::ViewModelUtils::SetViewBoundsToIdealBounds(view_model_); + /* TODO(msw): Restore functionality: + overflow_button_->SetBoundsRect(overflow_bounds);*/ +} + +void ShelfView::UpdateAllButtonsVisibilityInOverflowMode() { + // The overflow button is not shown in overflow mode. + /* TODO(msw): Restore functionality: + overflow_button_->SetVisible(false);*/ + DCHECK_LT(last_visible_index_, view_model_.view_size()); + for (int i = 0; i < view_model_.view_size(); ++i) { + bool visible = i >= first_visible_index_ && + i <= last_visible_index_; + // To track the dragging of |drag_view_| continuously, its visibility + // should be always true regardless of its position. + if (dragged_off_from_overflow_to_shelf_ && + view_model_.view_at(i) == drag_view_) + view_model_.view_at(i)->SetVisible(true); + else + view_model_.view_at(i)->SetVisible(visible); + } +} + +gfx::Rect ShelfView::CalculateIdealBounds() { + int available_size = PrimaryAxisValue(width(), height()); + DCHECK_EQ(model_.item_count(), view_model_.view_size()); + if (!available_size || model_.items().empty()) + return gfx::Rect(); + + int x = 0; + int y = 0; + int button_size = kShelfButtonSize; + int button_spacing = kShelfButtonSpacing; + + int w = PrimaryAxisValue(button_size, width()); + int h = PrimaryAxisValue(height(), button_size); + for (int i = 0; i < view_model_.view_size(); ++i) { + if (i < first_visible_index_) { + view_model_.set_ideal_bounds(i, gfx::Rect(x, y, 0, 0)); + continue; + } + + view_model_.set_ideal_bounds(i, gfx::Rect(x, y, w, h)); + x = PrimaryAxisValue(x + w + button_spacing, x); + y = PrimaryAxisValue(y, y + h + button_spacing); + } + + if (is_overflow_mode()) { + UpdateAllButtonsVisibilityInOverflowMode(); + return gfx::Rect(); + } + + // Right aligned icons. + int end_position = available_size - button_spacing; + x = PrimaryAxisValue(end_position, 0); + y = PrimaryAxisValue(0, end_position); + int first_panel_index = model_.FirstPanelIndex(); + for (int i = view_model_.view_size() - 1; i >= first_panel_index; --i) { + x = PrimaryAxisValue(x - w - button_spacing, x); + y = PrimaryAxisValue(y, y - h - button_spacing); + view_model_.set_ideal_bounds(i, gfx::Rect(x, y, w, h)); + end_position = PrimaryAxisValue(x, y); + } + + int last_button_index = first_panel_index - 1; + if (last_button_index < 0) + return gfx::Rect(); + + // Icons on the left / top are guaranteed up to kLeftIconProportion of + // the available space. + int last_icon_position = PrimaryAxisValue( + view_model_.ideal_bounds(last_button_index).right(), + view_model_.ideal_bounds(last_button_index).bottom()) + button_size; + int reserved_icon_space = available_size * kReservedNonPanelIconProportion; + if (last_icon_position < reserved_icon_space) + end_position = last_icon_position; + else + end_position = std::max(end_position, reserved_icon_space); + + gfx::Rect overflow_bounds(PrimaryAxisValue(w, width()), + PrimaryAxisValue(height(), h)); + + last_visible_index_ = DetermineLastVisibleIndex(end_position - button_size); + last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1; + bool show_overflow = last_visible_index_ < last_button_index || + last_hidden_index_ >= first_panel_index; + + // Create Space for the overflow button + if (show_overflow) { + // The following code makes sure that platform apps icons (aligned to left / + // top) are favored over panel apps icons (aligned to right / bottom). + if (last_visible_index_ > 0 && last_visible_index_ < last_button_index) { + // This condition means that we will take one platform app and replace it + // with the overflow button and put the app in the overflow bubble. + // This happens when the space needed for platform apps exceeds the + // reserved area for non-panel icons, + // (i.e. |last_icon_position| > |reserved_icon_space|). + --last_visible_index_; + } else if (last_hidden_index_ >= first_panel_index && + last_hidden_index_ < view_model_.view_size() - 1) { + // This condition means that we will take a panel app icon and replace it + // with the overflow button. + // This happens when there is still room for platform apps in the reserved + // area for non-panel icons, + // (i.e. |last_icon_position| < |reserved_icon_space|). + ++last_hidden_index_; + } + } + + for (int i = 0; i < view_model_.view_size(); ++i) { + bool visible = i <= last_visible_index_ || i > last_hidden_index_; + // To receive drag event continuously from |drag_view_| during the dragging + // off from the shelf, don't make |drag_view_| invisible. It will be + // eventually invisible and removed from the |view_model_| by + // FinalizeRipOffDrag(). + if (dragged_off_shelf_ && view_model_.view_at(i) == drag_view_) + continue; + view_model_.view_at(i)->SetVisible(visible); + } + + /* TODO(msw): Restore functionality: + overflow_button_->SetVisible(show_overflow);*/ + if (show_overflow) { + DCHECK_NE(0, view_model_.view_size()); + if (last_visible_index_ == -1) { + x = 0; + y = 0; + } else { + x = PrimaryAxisValue( + view_model_.ideal_bounds(last_visible_index_).right(), + view_model_.ideal_bounds(last_visible_index_).x()); + y = PrimaryAxisValue( + view_model_.ideal_bounds(last_visible_index_).y(), + view_model_.ideal_bounds(last_visible_index_).bottom()); + } + // Set all hidden panel icon positions to be on the overflow button. + for (int i = first_panel_index; i <= last_hidden_index_; ++i) + view_model_.set_ideal_bounds(i, gfx::Rect(x, y, w, h)); + + // Add more space between last visible item and overflow button. + // Without this, two buttons look too close compared with other items. + x = PrimaryAxisValue(x + button_spacing, x); + y = PrimaryAxisValue(y, y + button_spacing); + + overflow_bounds.set_x(x); + overflow_bounds.set_y(y); + /* TODO(msw): Restore functionality: + if (overflow_bubble_.get() && overflow_bubble_->IsShowing()) + UpdateOverflowRange(overflow_bubble_->shelf_view());*/ + } else { + /* TODO(msw): Restore functionality: + if (overflow_bubble_) + overflow_bubble_->Hide();*/ + } + return overflow_bounds; +} + +int ShelfView::DetermineLastVisibleIndex(int max_value) const { + int index = model_.FirstPanelIndex() - 1; + while (index >= 0 && + PrimaryAxisValue( + view_model_.ideal_bounds(index).right(), + view_model_.ideal_bounds(index).bottom()) > max_value) { + index--; + } + return index; +} + +int ShelfView::DetermineFirstVisiblePanelIndex(int min_value) const { + int index = model_.FirstPanelIndex(); + while (index < view_model_.view_size() && + PrimaryAxisValue( + view_model_.ideal_bounds(index).right(), + view_model_.ideal_bounds(index).bottom()) < min_value) { + ++index; + } + return index; +} + +/* TODO(msw): Restore functionality: +void ShelfView::AddIconObserver(ShelfIconObserver* observer) { + observers_.AddObserver(observer); +} + +void ShelfView::RemoveIconObserver(ShelfIconObserver* observer) { + observers_.RemoveObserver(observer); +}*/ + +void ShelfView::AnimateToIdealBounds() { + /* TODO(msw): Restore functionality: + gfx::Rect overflow_bounds = CalculateIdealBounds();*/ + CalculateIdealBounds(); + for (int i = 0; i < view_model_.view_size(); ++i) { + views::View* view = view_model_.view_at(i); + bounds_animator_->AnimateViewTo(view, view_model_.ideal_bounds(i)); + // Now that the item animation starts, we have to make sure that the + // padding of the first gets properly transferred to the new first item. + if (i && view->border()) + view->SetBorder(views::Border::NullBorder()); + } + /* TODO(msw): Restore functionality: + overflow_button_->SetBoundsRect(overflow_bounds);*/ +} + +views::View* ShelfView::CreateViewForItem(const ShelfItem& item) { + views::View* view = nullptr; + switch (item.type) { + case TYPE_BROWSER_SHORTCUT: + case TYPE_APP_SHORTCUT: + case TYPE_WINDOWED_APP: + case TYPE_PLATFORM_APP: + case TYPE_DIALOG: + case TYPE_APP_PANEL: { + ShelfButton* button = ShelfButton::Create(this, this); + button->SetImage(item.image); + ReflectItemStatus(item, button); + view = button; + break; + } + + case TYPE_APP_LIST: { + /* TODO(msw): Restore functionality: + view = new AppListButton(this, this);*/ + view = new views::LabelButton(nullptr, item.title); + break; + } + + case TYPE_MOJO_APP: { + // TODO(msw): Support item images, etc. + ShelfButton* button = ShelfButton::Create(this, this); + int image_resource_id = IDR_DEFAULT_FAVICON; + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + button->SetImage(*rb.GetImageSkiaNamed(image_resource_id)); + ReflectItemStatus(item, button); + view = button; + break; + } + + default: + break; + } + view->set_context_menu_controller(this); + + DCHECK(view); + ConfigureChildView(view); + return view; +} + +void ShelfView::FadeIn(views::View* view) { + view->SetVisible(true); + view->layer()->SetOpacity(0); + AnimateToIdealBounds(); + bounds_animator_->SetAnimationDelegate( + view, + scoped_ptr(new FadeInAnimationDelegate(view))); +} + +void ShelfView::PrepareForDrag(Pointer pointer, const ui::LocatedEvent& event) { + DCHECK(!dragging()); + DCHECK(drag_view_); + drag_pointer_ = pointer; + start_drag_index_ = view_model_.GetIndexOfView(drag_view_); + + if (start_drag_index_== -1) { + CancelDrag(-1); + return; + } + + // If the item is no longer draggable, bail out. + /* TODO(msw): Restore functionality: + ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate( + model_.items()[start_drag_index_].id); + if (!item_delegate->IsDraggable()) { + CancelDrag(-1); + return; + }*/ + + // 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 ShelfView::ContinueDrag(const ui::LocatedEvent& event) { + // Due to a syncing operation the application might have been removed. + // Bail if it is gone. + int current_index = view_model_.GetIndexOfView(drag_view_); + DCHECK_NE(-1, current_index); + + /* TODO(msw): Restore functionality: + ShelfItemDelegate* item_delegate = + item_manager_->GetShelfItemDelegate(model_.items()[current_index].id); + if (!item_delegate->IsDraggable()) { + CancelDrag(-1); + return; + }*/ + + // If this is not a drag and drop host operation and not the app list item, + // check if the item got ripped off the shelf - if it did we are done. + if (!drag_and_drop_shelf_id_ && + RemovableByRipOff(current_index) != NOT_REMOVABLE) { + if (HandleRipOffDrag(event)) + return; + // The rip off handler could have changed the location of the item. + current_index = view_model_.GetIndexOfView(drag_view_); + } + + // TODO: I don't think this works correctly with RTL. + gfx::Point drag_point(event.location()); + ConvertPointToTarget(drag_view_, this, &drag_point); + + // Constrain the location to the range of valid indices for the type. + std::pair indices(GetDragRange(current_index)); + int first_drag_index = indices.first; + int last_drag_index = indices.second; + // If the last index isn't valid, we're overflowing. Constrain to the app list + // (which is the last visible item). + if (first_drag_index < model_.FirstPanelIndex() && + last_drag_index > last_visible_index_) + last_drag_index = last_visible_index_; + int x = 0, y = 0; + if (IsHorizontalAlignment()) { + x = std::max(view_model_.ideal_bounds(indices.first).x(), + drag_point.x() - drag_origin_.x()); + x = std::min(view_model_.ideal_bounds(last_drag_index).right() - + view_model_.ideal_bounds(current_index).width(), + x); + if (drag_view_->x() == x) + return; + drag_view_->SetX(x); + } else { + y = std::max(view_model_.ideal_bounds(indices.first).y(), + drag_point.y() - drag_origin_.y()); + y = std::min(view_model_.ideal_bounds(last_drag_index).bottom() - + view_model_.ideal_bounds(current_index).height(), + y); + if (drag_view_->y() == y) + return; + drag_view_->SetY(y); + } + + int target_index = + views::ViewModelUtils::DetermineMoveIndex( + view_model_, drag_view_, + IsHorizontalAlignment() ? views::ViewModelUtils::HORIZONTAL : + views::ViewModelUtils::VERTICAL, + x, y); + target_index = + std::min(indices.second, std::max(target_index, indices.first)); + + int first_draggable_item = 0; + /* TODO(msw): Restore functionality: + while (first_draggable_item < static_cast(model_.items().size()) && + !item_manager_->GetShelfItemDelegate( + model_.items()[first_draggable_item].id)->IsDraggable()) { + first_draggable_item++; + }*/ + + target_index = std::max(target_index, first_draggable_item); + + if (target_index == current_index) + return; + + // Change the model, the ShelfItemMoved() callback will handle the + // |view_model_| update. + model_.Move(current_index, target_index); + bounds_animator_->StopAnimatingView(drag_view_); +} + +bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) { + /* TODO(msw): Restore functionality: + int current_index = view_model_.GetIndexOfView(drag_view_); + DCHECK_NE(-1, current_index); + std::string dragged_app_id = + delegate_->GetAppIDForShelfID(model_.items()[current_index].id); + + gfx::Point screen_location = event.root_location(); + views::View::ConvertPointToScreen(this, &screen_location); + + // To avoid ugly forwards and backwards flipping we use different constants + // for ripping off / re-inserting the items. + if (dragged_off_shelf_) { + // If the shelf/overflow bubble bounds contains |screen_location| we insert + // the item back into the shelf. + if (GetBoundsForDragInsertInScreen().Contains(screen_location)) { + if (dragged_off_from_overflow_to_shelf_) { + // During the dragging an item from Shelf to Overflow, it can enter here + // directly because both are located very closly. + main_shelf_->EndDrag(true); + // Stops the animation of |drag_view_| and sets its bounds explicitly + // becase ContinueDrag() stops its animation. Without this, unexpected + // bounds will be set. + bounds_animator_->StopAnimatingView(drag_view_); + int drag_view_index = view_model_.GetIndexOfView(drag_view_); + drag_view_->SetBoundsRect(view_model_.ideal_bounds(drag_view_index)); + dragged_off_from_overflow_to_shelf_ = false; + } + // Destroy our proxy view item. + DestroyDragIconProxy(); + // Re-insert the item and return simply false since the caller will handle + // the move as in any normal case. + dragged_off_shelf_ = false; + drag_view_->layer()->SetOpacity(1.0f); + // The size of Overflow bubble should be updated immediately when an item + // is re-inserted. + if (is_overflow_mode()) + PreferredSizeChanged(); + return false; + } else if (is_overflow_mode() && + main_shelf_->GetBoundsForDragInsertInScreen().Contains( + screen_location)) { + if (!dragged_off_from_overflow_to_shelf_) { + dragged_off_from_overflow_to_shelf_ = true; + drag_image_->SetOpacity(1.0f); + main_shelf_->StartDrag(dragged_app_id, screen_location); + } else { + main_shelf_->Drag(screen_location); + } + } else if (dragged_off_from_overflow_to_shelf_) { + // Makes the |drag_image_| partially disappear again. + dragged_off_from_overflow_to_shelf_ = false; + drag_image_->SetOpacity(kDraggedImageOpacity); + main_shelf_->EndDrag(true); + bounds_animator_->StopAnimatingView(drag_view_); + int drag_view_index = view_model_.GetIndexOfView(drag_view_); + drag_view_->SetBoundsRect(view_model_.ideal_bounds(drag_view_index)); + } + // Move our proxy view item. + UpdateDragIconProxy(screen_location); + return true; + } + // Check if we are too far away from the shelf to enter the ripped off state. + // Determine the distance to the shelf. + int delta = CalculateShelfDistance(screen_location); + if (delta > kRipOffDistance) { + // Create a proxy view item which can be moved anywhere. + CreateDragIconProxy(event.root_location(), + drag_view_->GetImage(), + drag_view_, + gfx::Vector2d(0, 0), + kDragAndDropProxyScale); + drag_view_->layer()->SetOpacity(0.0f); + dragged_off_shelf_ = true; + if (RemovableByRipOff(current_index) == REMOVABLE) { + // Move the item to the front of the first panel item and hide it. + // ShelfItemMoved() callback will handle the |view_model_| update and + // call AnimateToIdealBounds(). + if (current_index != model_.FirstPanelIndex() - 1) { + model_.Move(current_index, model_.FirstPanelIndex() - 1); + StartFadeInLastVisibleItem(); + } else if (is_overflow_mode()) { + // Overflow bubble should be shrunk when an item is ripped off. + PreferredSizeChanged(); + } + // Make the item partially disappear to show that it will get removed if + // dropped. + drag_image_->SetOpacity(kDraggedImageOpacity); + } + return true; + }*/ + return false; +} + +void ShelfView::FinalizeRipOffDrag(bool cancel) { + /* TODO(msw): Restore functionality: + if (!dragged_off_shelf_) + return; + // Make sure we do not come in here again. + dragged_off_shelf_ = false; + + // Coming here we should always have a |drag_view_|. + DCHECK(drag_view_); + int current_index = view_model_.GetIndexOfView(drag_view_); + // If the view isn't part of the model anymore (|current_index| == -1), a sync + // operation must have removed it. In that case we shouldn't change the model + // and only delete the proxy image. + if (current_index == -1) { + DestroyDragIconProxy(); + return; + } + + // Set to true when the animation should snap back to where it was before. + bool snap_back = false; + // Items which cannot be dragged off will be handled as a cancel. + if (!cancel) { + if (dragged_off_from_overflow_to_shelf_) { + dragged_off_from_overflow_to_shelf_ = false; + main_shelf_->EndDrag(false); + drag_view_->layer()->SetOpacity(1.0f); + } else if (RemovableByRipOff(current_index) != REMOVABLE) { + // Make sure we do not try to remove un-removable items like items which + // were not pinned or have to be always there. + cancel = true; + snap_back = true; + } else { + // Make sure the item stays invisible upon removal. + drag_view_->SetVisible(false); + std::string app_id = 0; + std::string app_id = + delegate_->GetAppIDForShelfID(model_.items()[current_index].id); + delegate_->UnpinAppWithID(app_id); + } + } + if (cancel || snap_back) { + if (dragged_off_from_overflow_to_shelf_) { + dragged_off_from_overflow_to_shelf_ = false; + // Main shelf handles revert of dragged item. + main_shelf_->EndDrag(true); + drag_view_->layer()->SetOpacity(1.0f); + } else if (!cancelling_drag_model_changed_) { + // Only do something if the change did not come through a model change. + gfx::Rect drag_bounds; + gfx::Rect drag_bounds = drag_image_->GetBoundsInScreen(); + gfx::Point relative_to = GetBoundsInScreen().origin(); + gfx::Rect target( + gfx::PointAtOffsetFromOrigin(drag_bounds.origin()- relative_to), + drag_bounds.size()); + drag_view_->SetBoundsRect(target); + // Hide the status from the active item since we snap it back now. Upon + // animation end the flag gets cleared if |snap_back_from_rip_off_view_| + // is set. + snap_back_from_rip_off_view_ = drag_view_; + drag_view_->AddState(ShelfButton::STATE_HIDDEN); + // When a canceling drag model is happening, the view model is diverged + // from the menu model and movements / animations should not be done. + model_.Move(current_index, start_drag_index_); + AnimateToIdealBounds(); + } + drag_view_->layer()->SetOpacity(1.0f); + } + DestroyDragIconProxy();*/ +} + +ShelfView::RemovableState ShelfView::RemovableByRipOff(int index) const { + DCHECK(index >= 0 && index < model_.item_count()); + ShelfItemType type = model_.items()[index].type; + if (type == TYPE_APP_LIST || type == TYPE_DIALOG) + return NOT_REMOVABLE; + + /* TODO(msw): Restore functionality: + std::string app_id = + delegate_->GetAppIDForShelfID(model_.items()[index].id); + ShelfItemDelegate* item_delegate = + item_manager_->GetShelfItemDelegate(model_.items()[index].id); + if (!item_delegate->CanPin()) + return NOT_REMOVABLE; + Note: Only pinned app shortcuts can be removed! + return (type == TYPE_APP_SHORTCUT && delegate_->IsAppPinned(app_id)) ? + REMOVABLE : DRAGGABLE;*/ + return REMOVABLE; +} + +bool ShelfView::SameDragType(ShelfItemType typea, ShelfItemType typeb) const { + switch (typea) { + case TYPE_APP_SHORTCUT: + case TYPE_BROWSER_SHORTCUT: + return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT); + case TYPE_APP_LIST: + case TYPE_PLATFORM_APP: + case TYPE_WINDOWED_APP: + case TYPE_MOJO_APP: + case TYPE_APP_PANEL: + case TYPE_DIALOG: + return typeb == typea; + case TYPE_UNDEFINED: + NOTREACHED() << "ShelfItemType must be set."; + return false; + } + NOTREACHED(); + return false; +} + +std::pair ShelfView::GetDragRange(int index) { + int min_index = -1; + int max_index = -1; + ShelfItemType type = model_.items()[index].type; + for (int i = 0; i < model_.item_count(); ++i) { + if (SameDragType(model_.items()[i].type, type)) { + if (min_index == -1) + min_index = i; + max_index = i; + } + } + return std::pair(min_index, max_index); +} + +void ShelfView::ConfigureChildView(views::View* view) { + view->SetPaintToLayer(true); + view->layer()->SetFillsBoundsOpaquely(false); +} + +void ShelfView::ToggleOverflowBubble() { + /* TODO(msw): Restore functionality: + if (IsShowingOverflowBubble()) { + overflow_bubble_->Hide(); + return; + } + + if (!overflow_bubble_) + overflow_bubble_.reset(new OverflowBubble()); + + ShelfView* overflow_view = new ShelfView(app); + overflow_view->overflow_mode_ = true; + overflow_view->Init(); + overflow_view->set_owner_overflow_bubble(overflow_bubble_.get()); + overflow_view->OnShelfAlignmentChanged(); + overflow_view->main_shelf_ = this; + UpdateOverflowRange(overflow_view); + + overflow_bubble_->Show(overflow_button_, overflow_view); + + Shell::GetInstance()->UpdateShelfVisibility();*/ +} + +void ShelfView::OnFadeOutAnimationEnded() { + AnimateToIdealBounds(); + StartFadeInLastVisibleItem(); +} + +void ShelfView::StartFadeInLastVisibleItem() { + // If overflow button is visible and there is a valid new last item, fading + // the new last item in after sliding animation is finished. + /* TODO(msw): Restore functionality: + if (overflow_button_->visible() && last_visible_index_ >= 0) { + views::View* last_visible_view = + view_model_.view_at(last_visible_index_); + last_visible_view->layer()->SetOpacity(0); + bounds_animator_->SetAnimationDelegate( + last_visible_view, + scoped_ptr( + new StartFadeAnimationDelegate(this, last_visible_view))); + }*/ +} + +void ShelfView::UpdateOverflowRange(ShelfView* overflow_view) const { + const int first_overflow_index = last_visible_index_ + 1; + const int last_overflow_index = last_hidden_index_; + DCHECK_LE(first_overflow_index, last_overflow_index); + DCHECK_LT(last_overflow_index, view_model_.view_size()); + + /* TODO(msw): Restore functionality: + overflow_view->first_visible_index_ = first_overflow_index; + overflow_view->last_visible_index_ = last_overflow_index;*/ +} + +bool ShelfView::ShouldHideTooltip(const gfx::Point& cursor_location) { + gfx::Rect active_bounds; + + for (int i = 0; i < child_count(); ++i) { + views::View* child = child_at(i); + /* TODO(msw): Restore functionality: + if (child == overflow_button_) + continue;*/ + if (!ShouldShowTooltipForView(child)) + continue; - user_window_controller_->AddUserWindowObserver( - binding_.CreateInterfacePtrAndBind()); + gfx::Rect child_bounds = child->GetMirroredBounds(); + active_bounds.Union(child_bounds); + } - SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); + return !active_bounds.Contains(cursor_location); } -ShelfView::~ShelfView() {} +gfx::Rect ShelfView::GetVisibleItemsBoundsInScreen() { + gfx::Size preferred_size = GetPreferredSize(); + gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0); + ConvertPointToScreen(this, &origin); + return gfx::Rect(origin, preferred_size); +} + +gfx::Rect ShelfView::GetBoundsForDragInsertInScreen() { + gfx::Size preferred_size; + if (is_overflow_mode()) { + /* TODO(msw): Restore functionality: + DCHECK(owner_overflow_bubble_); + gfx::Rect bubble_bounds = + owner_overflow_bubble_->bubble_view()->GetBubbleBounds(); + preferred_size = bubble_bounds.size();*/ + } else { + const int last_button_index = view_model_.view_size() - 1; + gfx::Rect last_button_bounds = + view_model_.view_at(last_button_index)->bounds(); + /* TODO(msw): Restore functionality: + if (overflow_button_->visible() && + model_.GetItemIndexForType(TYPE_APP_PANEL) == -1) { + // When overflow button is visible and shelf has no panel items, + // last_button_bounds should be overflow button's bounds. + last_button_bounds = overflow_button_->bounds(); + }*/ + + if (IsHorizontalAlignment()) { + preferred_size = gfx::Size(last_button_bounds.right() + leading_inset_, + kShelfSize); + } else { + preferred_size = gfx::Size(kShelfSize, + last_button_bounds.bottom() + leading_inset_); + } + } + gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0); -size_t ShelfView::GetButtonIndexById(uint32_t window_id) const { - for (size_t i = 0; i < open_window_buttons_.size(); ++i) - if (static_cast(open_window_buttons_[i]->tag()) == window_id) - return i; - return open_window_buttons_.size(); + // In overflow mode, we should use OverflowBubbleView as a source for + // converting |origin| to screen coordinates. When a scroll operation is + // occurred in OverflowBubble, the bounds of ShelfView in OverflowBubble can + // be changed. + /* TODO(msw): Restore functionality: + if (is_overflow_mode()) + ConvertPointToScreen(owner_overflow_bubble_->bubble_view(), &origin); + else + ConvertPointToScreen(this, &origin);*/ + + return gfx::Rect(origin, preferred_size); } -void ShelfView::OnPaint(gfx::Canvas* canvas) { - canvas->FillRect(GetLocalBounds(), SK_ColorYELLOW); - views::View::OnPaint(canvas); +int ShelfView::CancelDrag(int modified_index) { + FinalizeRipOffDrag(true); + if (!drag_view_) + return modified_index; + bool was_dragging = dragging(); + int drag_view_index = view_model_.GetIndexOfView(drag_view_); + drag_pointer_ = NONE; + drag_view_ = nullptr; + if (drag_view_index == modified_index) { + // The view that was being dragged is being modified. Don't do anything. + return modified_index; + } + if (!was_dragging) + return modified_index; + + // Restore previous position, tracking the position of the modified view. + bool at_end = modified_index == view_model_.view_size(); + views::View* modified_view = + (modified_index >= 0 && !at_end) ? + view_model_.view_at(modified_index) : nullptr; + model_.Move(drag_view_index, start_drag_index_); + + // If the modified view will be at the end of the list, return the new end of + // the list. + if (at_end) + return view_model_.view_size(); + return modified_view ? view_model_.GetIndexOfView(modified_view) : -1; } gfx::Size ShelfView::GetPreferredSize() const { - return gfx::Size(1, 48); + const_cast(this)->CalculateIdealBounds(); + + int last_button_index = is_overflow_mode() ? + last_visible_index_ : view_model_.view_size() - 1; + + // When an item is dragged off from the overflow bubble, it is moved to last + // position and and changed to invisible. Overflow bubble size should be + // shrunk to fit only for visible items. + // If |dragged_off_from_overflow_to_shelf_| is set, there will be no invisible + // items in the shelf. + if (is_overflow_mode() && + dragged_off_shelf_ && + !dragged_off_from_overflow_to_shelf_ && + RemovableByRipOff(view_model_.GetIndexOfView(drag_view_)) == REMOVABLE) + last_button_index--; + + const gfx::Rect last_button_bounds = + last_button_index >= first_visible_index_ ? + view_model_.ideal_bounds(last_button_index) : + gfx::Rect(gfx::Size(kShelfSize, kShelfSize)); + + if (IsHorizontalAlignment()) + return gfx::Size(last_button_bounds.right() + leading_inset_, kShelfSize); + + return gfx::Size(kShelfSize, last_button_bounds.bottom() + leading_inset_); +} + +void ShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) { + // This bounds change is produced by the shelf movement and all content has + // to follow. Using an animation at that time would produce a time lag since + // the animation of the BoundsAnimator has itself a delay before it arrives + // at the required location. As such we tell the animator to go there + // immediately. + BoundsAnimatorDisabler disabler(bounds_animator_.get()); + LayoutToIdealBounds(); + /* TODO(msw): Restore functionality: + FOR_EACH_OBSERVER(ShelfIconObserver, observers_, + OnShelfIconPositionsChanged()); + + if (IsShowingOverflowBubble()) + overflow_bubble_->Hide();*/ } -views::View* ShelfView::GetContentsView() { +views::FocusTraversable* ShelfView::GetPaneFocusTraversable() { return this; } -void ShelfView::ButtonPressed(views::Button* sender, const ui::Event& event) { - user_window_controller_->FocusUserWindow(sender->tag()); +void ShelfView::GetAccessibleState(ui::AXViewState* state) { + state->role = ui::AX_ROLE_TOOLBAR; + /* TODO(msw): Restore functionality: + state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME);*/ +} + +/* TODO(msw): Restore functionality: +void ShelfView::OnGestureEvent(ui::GestureEvent* event) { + aura::Window* target_window = static_cast(event->target()) + ->GetWidget() + ->GetNativeWindow(); + if (gesture_handler_.ProcessGestureEvent(*event, target_window)) + event->StopPropagation(); +}*/ + +void ShelfView::ShelfItemAdded(int model_index) { + { + base::AutoReset cancelling_drag( + &cancelling_drag_model_changed_, true); + model_index = CancelDrag(model_index); + } + views::View* view = CreateViewForItem(model_.items()[model_index]); + AddChildView(view); + // Hide the view, it'll be made visible when the animation is done. Using + // opacity 0 here to avoid messing with CalculateIdealBounds which touches + // the view's visibility. + view->layer()->SetOpacity(0); + view_model_.Add(view, model_index); + + // Give the button its ideal bounds. That way if we end up animating the + // button before this animation completes it doesn't appear at some random + // spot (because it was in the middle of animating from 0,0 0x0 to its + // target). + CalculateIdealBounds(); + view->SetBoundsRect(view_model_.ideal_bounds(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 (model_index <= last_visible_index_ || + model_index >= model_.FirstPanelIndex()) { + bounds_animator_->SetAnimationDelegate( + view, + scoped_ptr( + new StartFadeAnimationDelegate(this, view))); + } else { + // Undo the hiding if animation does not run. + view->layer()->SetOpacity(1.0f); + } +} + +void ShelfView::ShelfItemRemoved(int model_index, ShelfID id) { + if (id == context_menu_id_) + launcher_menu_runner_.reset(); + { + base::AutoReset cancelling_drag( + &cancelling_drag_model_changed_, true); + model_index = CancelDrag(model_index); + } + views::View* view = view_model_.view_at(model_index); + view_model_.Remove(model_index); + + // When the overflow bubble is visible, the overflow range needs to be set + // before CalculateIdealBounds() gets called. Otherwise CalculateIdealBounds() + // could trigger a ShelfItemChanged() by hiding the overflow bubble and + // since the overflow bubble is not yet synced with the ShelfModel this + // could cause a crash. + /* TODO(msw): Restore functionality: + if (overflow_bubble_ && overflow_bubble_->IsShowing()) { + last_hidden_index_ = std::min(last_hidden_index_, + view_model_.view_size() - 1); + UpdateOverflowRange(overflow_bubble_->shelf_view()); + }*/ + + if (view->visible()) { + // 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, + scoped_ptr( + new FadeOutAnimationDelegate(this, view))); + } else { + // We don't need to show a fade out animation for invisible |view|. When an + // item is ripped out from the shelf, its |view| is already invisible. + AnimateToIdealBounds(); + } + + // Close the tooltip because it isn't needed any longer and its anchor view + // will be deleted soon. + if (tooltip_.GetCurrentAnchorView() == view) + tooltip_.Close(); +} + +void ShelfView::ShelfItemChanged(int model_index, const ShelfItem& old_item) { + const ShelfItem& item(model_.items()[model_index]); + if (old_item.type != item.type) { + // Type changed, swap the views. + model_index = CancelDrag(model_index); + scoped_ptr old_view(view_model_.view_at(model_index)); + bounds_animator_->StopAnimatingView(old_view.get()); + // Removing and re-inserting a view in our view model will strip the ideal + // bounds from the item. To avoid recalculation of everything the bounds + // get remembered and restored after the insertion to the previous value. + gfx::Rect old_ideal_bounds = view_model_.ideal_bounds(model_index); + view_model_.Remove(model_index); + views::View* new_view = CreateViewForItem(item); + AddChildView(new_view); + view_model_.Add(new_view, model_index); + view_model_.set_ideal_bounds(model_index, old_ideal_bounds); + new_view->SetBoundsRect(old_view->bounds()); + return; + } + + views::View* view = view_model_.view_at(model_index); + switch (item.type) { + case TYPE_BROWSER_SHORTCUT: + // Fallthrough for the new Shelf since it needs to show the activation + // change as well. + case TYPE_APP_SHORTCUT: + case TYPE_WINDOWED_APP: + case TYPE_PLATFORM_APP: + case TYPE_DIALOG: + case TYPE_APP_PANEL: { + CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName()); + ShelfButton* button = static_cast(view); + ReflectItemStatus(item, button); + // The browser shortcut is currently not a "real" item and as such the + // the image is bogous as well. We therefore keep the image as is for it. + if (item.type != TYPE_BROWSER_SHORTCUT) + button->SetImage(item.image); + button->SchedulePaint(); + break; + } + case TYPE_MOJO_APP: { + CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName()); + ShelfButton* button = static_cast(view); + button->SetTooltipText(item.title); + break; + } + + default: + break; + } +} + +void ShelfView::ShelfItemMoved(int start_index, int target_index) { + view_model_.Move(start_index, target_index); + // When cancelling a drag due to a shelf item being added, the currently + // dragged item is moved back to its initial position. AnimateToIdealBounds + // will be called again when the new item is added to the |view_model_| but + // at this time the |view_model_| is inconsistent with the |model_|. + if (!cancelling_drag_model_changed_) + AnimateToIdealBounds(); +} + +void ShelfView::ShelfStatusChanged() { + // Nothing to do here. +} + +ShelfAlignment ShelfView::GetAlignment() const { + return alignment_; } -void ShelfView::OnUserWindowObserverAdded( - mojo::Array user_windows) { - for (size_t i = 0; i < user_windows.size(); ++i) - OnUserWindowAdded(std::move(user_windows[i])); +bool ShelfView::IsHorizontalAlignment() const { + return alignment_ == SHELF_ALIGNMENT_BOTTOM || + alignment_ == SHELF_ALIGNMENT_TOP; } -void ShelfView::OnUserWindowAdded(mash::wm::mojom::UserWindowPtr user_window) { - views::LabelButton* open_window_button = new views::LabelButton( - this, user_window->window_title.To()); - open_window_button->set_tag(user_window->window_id); - open_window_buttons_.push_back(open_window_button); - AddChildView(open_window_button); - Layout(); - SchedulePaint(); +void ShelfView::PointerPressedOnButton(views::View* view, + Pointer pointer, + const ui::LocatedEvent& event) { + if (drag_view_) + return; + + int index = view_model_.GetIndexOfView(view); + if (index == -1) + return; + + /* TODO(msw): Restore functionality: + ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate( + model_.items()[index].id); + if (view_model_.view_size() <= 1 || !item_delegate->IsDraggable()) + return; // View is being deleted or not draggable, ignore request.*/ + + // Only when the repost event occurs on the same shelf item, we should ignore + // the call in ShelfView::ButtonPressed(...). + is_repost_event_ = IsRepostEvent(event) && (last_pressed_index_ == index); + + CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName()); + drag_view_ = static_cast(view); + drag_origin_ = gfx::Point(event.x(), event.y()); + /* TODO(msw): Restore functionality: + UMA_HISTOGRAM_ENUMERATION("Ash.ShelfAlignmentUsage", + layout_manager_->SelectValueForShelfAlignment( + SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM, + SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT, + SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT, + -1), + SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT);*/ +} + +void ShelfView::PointerDraggedOnButton(views::View* view, + Pointer pointer, + const ui::LocatedEvent& event) { + // To prepare all drag types (moving an item in the shelf and dragging off), + // we should check the x-axis and y-axis offset. + if (!dragging() && drag_view_ && + ((std::abs(event.x() - drag_origin_.x()) >= kMinimumDragDistance) || + (std::abs(event.y() - drag_origin_.y()) >= kMinimumDragDistance))) { + PrepareForDrag(pointer, event); + } + if (drag_pointer_ == pointer) + ContinueDrag(event); +} + +void ShelfView::PointerReleasedOnButton(views::View* view, + Pointer pointer, + bool canceled) { + // Reset |is_repost_event| to false. + is_repost_event_ = false; + + if (canceled) { + CancelDrag(-1); + } else if (drag_pointer_ == pointer) { + FinalizeRipOffDrag(false); + drag_pointer_ = NONE; + AnimateToIdealBounds(); + } + // If the drag pointer is NONE, no drag operation is going on and the + // drag_view can be released. + if (drag_pointer_ == NONE) + drag_view_ = nullptr; +} + +void ShelfView::MouseMovedOverButton(views::View* view) { + if (!ShouldShowTooltipForView(view)) + return; + + if (!tooltip_.IsVisible()) + tooltip_.ResetTimer(); +} + +void ShelfView::MouseEnteredButton(views::View* view) { + // TODO(msw): Fix the initial (0,0) mouse event (causes tooltip on startup). + if (!ShouldShowTooltipForView(view)) + return; + + if (tooltip_.IsVisible()) + tooltip_.ShowImmediately(view, GetAccessibleName(view)); + else + tooltip_.ShowDelayed(view, GetAccessibleName(view)); +} + +void ShelfView::MouseExitedButton(views::View* view) { + if (!tooltip_.IsVisible()) + tooltip_.StopTimer(); +} + +base::string16 ShelfView::GetAccessibleName(const views::View* view) { + int view_index = view_model_.GetIndexOfView(view); + // May be -1 while in the process of animating closed. + if (view_index == -1) + return base::string16(); + return model_.items()[view_index].title; +} + +void ShelfView::ButtonPressed(views::Button* sender, const ui::Event& event) { + // Do not handle mouse release during drag. + if (dragging()) + return; + + /* TODO(msw): Restore functionality: + if (sender == overflow_button_) { + ToggleOverflowBubble(); + shelf_button_pressed_metric_tracker_.ButtonPressed( + event, sender, ShelfItemDelegate::kNoAction); + return; + }*/ + + int view_index = view_model_.GetIndexOfView(sender); + // May be -1 while in the process of animating closed. + if (view_index == -1) + return; + + // If the menu was just closed by the same event as this one, we ignore + // the call and don't open the menu again. See crbug.com/343005 for more + // detail. + if (is_repost_event_) + return; + + // Record the index for the last pressed shelf item. + last_pressed_index_ = view_index; + + // Don't activate the item twice on double-click. Otherwise the window starts + // animating open due to the first click, then immediately minimizes due to + // the second click. The user most likely intended to open or minimize the + // item once, not do both. + if (event.flags() & ui::EF_IS_DOUBLE_CLICK) + return; + + { + /* TODO(msw): Restore functionality: + ScopedTargetRootWindow scoped_target( + sender->GetWidget()->GetNativeView()->GetRootWindow());*/ + // Slow down activation animations if shift key is pressed. + scoped_ptr slowing_animations; + if (event.IsShiftDown()) { + slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode( + ui::ScopedAnimationDurationScaleMode::SLOW_DURATION)); + } + + // Collect usage statistics before we decide what to do with the click. + const ShelfItem& item(model_.items()[view_index]); + switch (item.type) { + case TYPE_APP_SHORTCUT: + case TYPE_WINDOWED_APP: + case TYPE_PLATFORM_APP: + case TYPE_MOJO_APP: + case TYPE_BROWSER_SHORTCUT: + /* TODO(msw): Restore functionality: + Shell::GetInstance()->metrics()->RecordUserMetricsAction( + UMA_LAUNCHER_CLICK_ON_APP);*/ + break; + + case TYPE_APP_LIST: + /* TODO(msw): Restore functionality: + Shell::GetInstance()->metrics()->RecordUserMetricsAction( + UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);*/ + break; + + case TYPE_APP_PANEL: + case TYPE_DIALOG: + break; + + case TYPE_UNDEFINED: + NOTREACHED() << "ShelfItemType must be set."; + break; + } + + // TODO(msw): Support GetShelfItemDelegate[Manager], actions, etc. + if (item.type == TYPE_MOJO_APP) + model_.user_window_controller()->FocusUserWindow(item.window_id); + + /* TODO(msw): Restore functionality: + ShelfItemDelegate::PerformedAction performed_action = + item_manager_->GetShelfItemDelegate(model_.items()[view_index].id) + ->ItemSelected(event); + + shelf_button_pressed_metric_tracker_.ButtonPressed(event, sender, + performed_action); + + if (performed_action != ShelfItemDelegate::kNewWindowCreated) + ShowListMenuForView(model_.items()[view_index], sender, event);*/ + } +} + +bool ShelfView::ShowListMenuForView(const ShelfItem& item, + views::View* source, + const ui::Event& event) { + /* TODO(msw): Restore functionality: + ShelfItemDelegate* item_delegate = + item_manager_->GetShelfItemDelegate(item.id); + scoped_ptr list_menu_model( + item_delegate->CreateApplicationMenu(event.flags())); + + Make sure we have a menu and it has at least two items in addition to the + application title and the 3 spacing separators. + if (!list_menu_model.get() || list_menu_model->GetItemCount() <= 5) + return false; + + ShowMenu(list_menu_model.get(), + source, + gfx::Point(), + false, + ui::GetMenuSourceTypeForEvent(event));*/ + return true; } -void ShelfView::OnUserWindowRemoved(uint32_t window_id) { - const size_t index = GetButtonIndexById(window_id); - if (index >= open_window_buttons_.size()) +void ShelfView::ShowContextMenuForView(views::View* source, + const gfx::Point& point, + ui::MenuSourceType source_type) { + int view_index = view_model_.GetIndexOfView(source); + if (view_index == -1) { + /* TODO(msw): Restore functionality: + Shell::GetInstance()->ShowContextMenu(point, source_type);*/ + return; + } + + /* TODO(msw): Restore functionality: + ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate( + model_.items()[view_index].id); + context_menu_model_.reset(item_delegate->CreateContextMenu( + source->GetWidget()->GetNativeView()->GetRootWindow())); + if (!context_menu_model_) return; - views::LabelButton* button = open_window_buttons_[index]; - open_window_buttons_.erase(open_window_buttons_.begin() + index); - RemoveChildView(button); - delete button; - Layout(); - SchedulePaint(); + base::AutoReset reseter( + &context_menu_id_, + view_index == -1 ? 0 : model_.items()[view_index].id); + + ShowMenu(context_menu_model_.get(), + source, + point, + true, + source_type);*/ } -void ShelfView::OnUserWindowTitleChanged(uint32_t window_id, - const mojo::String& window_title) { - const size_t index = GetButtonIndexById(window_id); - if (index >= open_window_buttons_.size()) +void ShelfView::ShowMenu(ui::MenuModel* menu_model, + views::View* source, + const gfx::Point& click_point, + bool context_menu, + ui::MenuSourceType source_type) { + closing_event_time_ = base::TimeDelta(); + launcher_menu_runner_.reset(new views::MenuRunner( + menu_model, context_menu ? views::MenuRunner::CONTEXT_MENU : 0)); + + /* TODO(msw): Restore functionality: + ScopedTargetRootWindow scoped_target( + source->GetWidget()->GetNativeView()->GetRootWindow());*/ + + // Determine the menu alignment dependent on the shelf. + views::MenuAnchorPosition menu_alignment = views::MENU_ANCHOR_TOPLEFT; + gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size()); + + if (!context_menu) { + // Application lists use a bubble. + anchor_point = source->GetBoundsInScreen(); + + // It is possible to invoke the menu while it is sliding into view. To cover + // that case, the screen coordinates are offsetted by the animation delta. + gfx::Vector2d offset = + source->GetWidget()->GetNativeWindow()->bounds().origin() - + source->GetWidget()->GetNativeWindow()->GetTargetBounds().origin(); + anchor_point.set_x(anchor_point.x() - offset.x()); + anchor_point.set_y(anchor_point.y() - offset.y()); + + // Shelf items can have an asymmetrical border for spacing reasons. + // Adjust anchor location for this. + if (source->border()) + anchor_point.Inset(source->border()->GetInsets()); + + menu_alignment = SelectValueForShelfAlignment( + views::MENU_ANCHOR_BUBBLE_ABOVE, views::MENU_ANCHOR_BUBBLE_RIGHT, + views::MENU_ANCHOR_BUBBLE_LEFT, views::MENU_ANCHOR_BUBBLE_BELOW); + } + // If this gets deleted while we are in the menu, the shelf will be gone + // as well. + bool got_deleted = false; + got_deleted_ = &got_deleted; + + /* TODO(msw): Restore functionality: + shelf->ForceUndimming(true);*/ + // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building + // code. + if (launcher_menu_runner_->RunMenuAt(source->GetWidget(), + nullptr, + anchor_point, + menu_alignment, + source_type) == + views::MenuRunner::MENU_DELETED) { + if (!got_deleted) { + got_deleted_ = nullptr; + /* TODO(msw): Restore functionality: + shelf->ForceUndimming(false);*/ + } return; + } + got_deleted_ = nullptr; + /* TODO(msw): Restore functionality: + shelf->ForceUndimming(false);*/ + + // If it is a context menu and we are showing overflow bubble + // we want to hide overflow bubble. + /* TODO(msw): Restore functionality: + if (owner_overflow_bubble_) + owner_overflow_bubble_->HideBubbleAndRefreshButton();*/ + + // Unpinning an item will reset the |launcher_menu_runner_| before coming + // here. + if (launcher_menu_runner_) + closing_event_time_ = launcher_menu_runner_->closing_event_time(); + /* TODO(msw): Restore functionality: + Shell::GetInstance()->UpdateShelfVisibility();*/ +} + +void ShelfView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) { + /* TODO(msw): Restore functionality: + FOR_EACH_OBSERVER(ShelfIconObserver, observers_, + OnShelfIconPositionsChanged());*/ + PreferredSizeChanged(); +} + +void ShelfView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) { + if (snap_back_from_rip_off_view_ && animator == bounds_animator_.get()) { + if (!animator->IsAnimating(snap_back_from_rip_off_view_)) { + // Coming here the animation of the ShelfButton is finished and the + // previously hidden status can be shown again. Since the button itself + // might have gone away or changed locations we check that the button + // is still in the shelf and show its status again. + for (int index = 0; index < view_model_.view_size(); index++) { + views::View* view = view_model_.view_at(index); + if (view == snap_back_from_rip_off_view_) { + CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName()); + ShelfButton* button = static_cast(view); + button->ClearState(ShelfButton::STATE_HIDDEN); + break; + } + } + snap_back_from_rip_off_view_ = nullptr; + } + } +} + +bool ShelfView::IsRepostEvent(const ui::Event& event) { + if (closing_event_time_ == base::TimeDelta()) + return false; + + base::TimeDelta delta = + base::TimeDelta(event.time_stamp() - closing_event_time_); + closing_event_time_ = base::TimeDelta(); + // If the current (press down) event is a repost event, the time stamp of + // these two events should be the same. + return (delta.InMilliseconds() == 0); +} + +const ShelfItem* ShelfView::ShelfItemForView(const views::View* view) const { + int view_index = view_model_.GetIndexOfView(view); + if (view_index == -1) + return nullptr; + return &(model_.items()[view_index]); +} + +bool ShelfView::ShouldShowTooltipForView(const views::View* view) const { + /* TODO(msw): Restore functionality: + if (view == GetAppListButtonView() && + Shell::GetInstance()->GetAppListWindow()) + return false; + const ShelfItem* item = ShelfItemForView(view); + if (!item) + return true; + return item_manager_->GetShelfItemDelegate(item->id)->ShouldShowTooltip();*/ + return true; +} - open_window_buttons_[index]->SetText(window_title.To()); - open_window_buttons_[index]->SetMinSize(gfx::Size()); - Layout(); - SchedulePaint(); +int ShelfView::CalculateShelfDistance(const gfx::Point& coordinate) const { + const gfx::Rect bounds = GetBoundsInScreen(); + int distance = 0; + switch (GetAlignment()) { + case SHELF_ALIGNMENT_BOTTOM: + distance = bounds.y() - coordinate.y(); + break; + case SHELF_ALIGNMENT_LEFT: + distance = coordinate.x() - bounds.right(); + break; + case SHELF_ALIGNMENT_RIGHT: + distance = bounds.x() - coordinate.x(); + break; + case SHELF_ALIGNMENT_TOP: + distance = coordinate.y() - bounds.bottom(); + break; + } + return distance > 0 ? distance : 0; } } // namespace shelf diff --git a/mash/shelf/shelf_view.h b/mash/shelf/shelf_view.h index dbe7ac02054cf..bf3e81afe12bf 100644 --- a/mash/shelf/shelf_view.h +++ b/mash/shelf/shelf_view.h @@ -1,62 +1,449 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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 MASH_SHELF_SHELF_VIEW_H_ #define MASH_SHELF_SHELF_VIEW_H_ -#include +#include +#include +#include #include "base/macros.h" -#include "mash/wm/public/interfaces/user_window_controller.mojom.h" -#include "mojo/public/cpp/bindings/binding.h" +#include "base/memory/scoped_ptr.h" +#include "mash/shelf/shelf_button_host.h" +#include "mash/shelf/shelf_model.h" +#include "mash/shelf/shelf_model_observer.h" +#include "mash/shelf/shelf_tooltip_manager.h" +#include "mash/shelf/shelf_types.h" +#include "ui/views/animation/bounds_animator_observer.h" +#include "ui/views/context_menu_controller.h" #include "ui/views/controls/button/button.h" -#include "ui/views/widget/widget_delegate.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/view.h" +#include "ui/views/view_model.h" namespace mojo { class ApplicationImpl; } +namespace ui { +class MenuModel; +} + namespace views { -class LabelButton; +class BoundsAnimator; +class MenuRunner; } namespace mash { namespace shelf { -// A rudimentary mash shelf, used to build up the required wm interfaces. -class ShelfView : public views::WidgetDelegateView, +class ShelfButton; + +namespace test { +class ShelfViewTestAPI; +} + +extern const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM; +extern const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT; +extern const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT; +extern const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT; + +// TODO(msw): Restore missing features and testing from the ash shelf: +// Alignment, overflow, applist, drag, background, hiding, pinning, panels, etc. +class ShelfView : public views::View, + public ShelfModelObserver, public views::ButtonListener, - public mash::wm::mojom::UserWindowObserver { + public ShelfButtonHost, + public views::ContextMenuController, + public views::FocusTraversable, + public views::BoundsAnimatorObserver { public: explicit ShelfView(mojo::ApplicationImpl* app); ~ShelfView() override; + mojo::ApplicationImpl* app() const { return app_; } + + void SetAlignment(ShelfAlignment alignment); + + void SchedulePaintForAllButtons(); + + // Returns the ideal bounds of the specified item, or an empty rect if id + // isn't know. If the item is in an overflow shelf, the overflow icon location + // will be returned. + gfx::Rect GetIdealBoundsOfItemIcon(ShelfID id); + + // Repositions the icon for the specified item by the midpoint of the window. + void UpdatePanelIconPosition(ShelfID id, const gfx::Point& midpoint); + + /* TODO(msw): Restore functionality: + void AddIconObserver(ShelfIconObserver* observer); + void RemoveIconObserver(ShelfIconObserver* observer);*/ + + // Returns true if we're showing a menu. + bool IsShowingMenu() const; + + // Returns true if overflow bubble is shown. + bool IsShowingOverflowBubble() const; + + // Sets owner overflow bubble instance from which this shelf view pops + // out as overflow. + /* TODO(msw): Restore functionality: + void set_owner_overflow_bubble(OverflowBubble* owner) { + owner_overflow_bubble_ = owner; + }*/ + + views::View* GetAppListButtonView() const; + + // Returns true if the mouse cursor exits the area for launcher tooltip. + // There are thin gaps between launcher buttons but the tooltip shouldn't hide + // in the gaps, but the tooltip should hide if the mouse moved totally outside + // of the buttons area. + bool ShouldHideTooltip(const gfx::Point& cursor_location); + + // Returns rectangle bounding all visible launcher items. Used screen + // coordinate system. + gfx::Rect GetVisibleItemsBoundsInScreen(); + + // Overridden from FocusTraversable: + views::FocusSearch* GetFocusSearch() override; + FocusTraversable* GetFocusTraversableParent() override; + View* GetFocusTraversableParentView() override; + + /* TODO(msw): Restore drag/drop functionality. + // Overridden from app_list::ApplicationDragAndDropHost: + void CreateDragIconProxy(const gfx::Point& location_in_screen_coordinates, + const gfx::ImageSkia& icon, + views::View* replaced_view, + const gfx::Vector2d& cursor_offset_from_center, + float scale_factor) override; + void UpdateDragIconProxy( + const gfx::Point& location_in_screen_coordinates) override; + void DestroyDragIconProxy() override; + bool StartDrag(const std::string& app_id, + const gfx::Point& location_in_screen_coordinates) override; + bool Drag(const gfx::Point& location_in_screen_coordinates) override; + void EndDrag(bool cancel) override; + */ + + // Return the view model for test purposes. + const views::ViewModel* view_model_for_test() const { return &view_model_; } + private: - // Return the index in |open_window_buttons_| corresponding to |window_id|. - size_t GetButtonIndexById(uint32_t window_id) const; + friend class mash::shelf::test::ShelfViewTestAPI; + + class FadeOutAnimationDelegate; + class StartFadeAnimationDelegate; + + enum RemovableState { + REMOVABLE, // Item can be removed when dragged away. + DRAGGABLE, // Item can be dragged, but will snap always back to origin. + NOT_REMOVABLE, // Item is fixed and can never be removed. + }; + + // Returns true when this ShelfView is used for Overflow Bubble. + // In this mode, it does not show app list, panel and overflow button. + // Note: + // * When Shelf can contain only one item (overflow button) due to very + // small resolution screen, overflow bubble can show app list and panel + // button. + bool is_overflow_mode() const { return overflow_mode_; } + + bool dragging() const { return drag_pointer_ != NONE; } + + // Sets the bounds of each view to its ideal bounds. + void LayoutToIdealBounds(); + + // Update all button's visibility in overflow. + void UpdateAllButtonsVisibilityInOverflowMode(); + + // Calculates the ideal bounds. The bounds of each button corresponding to an + // item in the model is set in |view_model_|. Returns overflow button bounds. + gfx::Rect CalculateIdealBounds(); + + // Returns the index of the last view whose max primary axis coordinate is + // less than |max_value|. Returns -1 if nothing fits, or there are no views. + int DetermineLastVisibleIndex(int max_value) const; + + // Returns the index of the first panel whose min primary axis coordinate is + // at least |min_value|. Returns the index past the last panel if none fit. + int DetermineFirstVisiblePanelIndex(int min_value) const; + + // Animates the bounds of each view to its ideal bounds. + void AnimateToIdealBounds(); + + // Creates the view used to represent |item|. + views::View* CreateViewForItem(const ShelfItem& 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 pointer has moved enough to trigger a drag. Sets + // internal state in preparation for the drag. + void PrepareForDrag(Pointer pointer, const ui::LocatedEvent& event); + + // Invoked when the mouse is dragged. Updates the models as appropriate. + void ContinueDrag(const ui::LocatedEvent& event); + + // Handles ripping off an item from the shelf. Returns true when the item got + // removed. + bool HandleRipOffDrag(const ui::LocatedEvent& event); + + // Finalize the rip off dragging by either |cancel| the action or validating. + void FinalizeRipOffDrag(bool cancel); + + // Check if an item can be ripped off or not. + RemovableState RemovableByRipOff(int index) const; + + // Returns true if |typea| and |typeb| should be in the same drag range. + bool SameDragType(ShelfItemType typea, ShelfItemType typeb) const; + + // Returns the range (in the model) the item at the specified index can be + // dragged to. + std::pair GetDragRange(int index); + + // If there is a drag operation in progress it's canceled. If |modified_index| + // is valid, the new position of the corresponding item is returned. + int CancelDrag(int modified_index); + + // Returns rectangle bounds used for drag insertion. + // Note: + // * When overflow button is visible, returns bounds from first item + // to overflow button. + // * When overflow button is visible and one or more panel items exists, + // returns bounds from first item to last panel item. + // * In the overflow mode, returns only bubble's bounds. + gfx::Rect GetBoundsForDragInsertInScreen(); + + // Common setup done for all children. + void ConfigureChildView(views::View* view); + + // Toggles the overflow menu. + void ToggleOverflowBubble(); + + // Invoked after the fading out animation for item deletion is ended. + void OnFadeOutAnimationEnded(); + + // Fade in last visible item. + void StartFadeInLastVisibleItem(); + + // Updates the visible range of overflow items in |overflow_view|. + void UpdateOverflowRange(ShelfView* overflow_view) const; // Overridden from views::View: - void OnPaint(gfx::Canvas* canvas) override; gfx::Size GetPreferredSize() const override; + void OnBoundsChanged(const gfx::Rect& previous_bounds) override; + FocusTraversable* GetPaneFocusTraversable() override; + void GetAccessibleState(ui::AXViewState* state) override; + + /* TODO(msw): Restore functionality: + // Overridden from ui::EventHandler: + void OnGestureEvent(ui::GestureEvent* event) override;*/ + + // Overridden from ShelfModelObserver: + void ShelfItemAdded(int model_index) override; + void ShelfItemRemoved(int model_index, ShelfID id) override; + void ShelfItemChanged(int model_index, const ShelfItem& old_item) override; + void ShelfItemMoved(int start_index, int target_index) override; + void ShelfStatusChanged() override; - // Overridden from views::WidgetDelegate: - views::View* GetContentsView() override; + // Overridden from ShelfButtonHost: + ShelfAlignment GetAlignment() const override; + bool IsHorizontalAlignment() const override; + void PointerPressedOnButton(views::View* view, + Pointer pointer, + const ui::LocatedEvent& event) override; + void PointerDraggedOnButton(views::View* view, + Pointer pointer, + const ui::LocatedEvent& event) override; + void PointerReleasedOnButton(views::View* view, + Pointer pointer, + bool canceled) override; + void MouseMovedOverButton(views::View* view) override; + void MouseEnteredButton(views::View* view) override; + void MouseExitedButton(views::View* view) override; + base::string16 GetAccessibleName(const views::View* view) override; // Overridden from views::ButtonListener: void ButtonPressed(views::Button* sender, const ui::Event& event) override; - // Overridden from mash::wm::mojom::UserWindowObserver: - void OnUserWindowObserverAdded( - mojo::Array user_windows) override; - void OnUserWindowAdded(mash::wm::mojom::UserWindowPtr user_window) override; - void OnUserWindowRemoved(uint32_t window_id) override; - void OnUserWindowTitleChanged(uint32_t window_id, - const mojo::String& window_title) override; - - std::vector open_window_buttons_; - mash::wm::mojom::UserWindowControllerPtr user_window_controller_; - mojo::Binding binding_; + // Show the list of all running items for this |item|. It will return true + // when the menu was shown and false if there were no possible items to + // choose from. |source| specifies the view which is responsible for showing + // the menu, and the bubble will point towards it. + // The |event_flags| are the flags of the event which triggered this menu. + bool ShowListMenuForView(const ShelfItem& item, + views::View* source, + const ui::Event& event); + + // Overridden from views::ContextMenuController: + void ShowContextMenuForView(views::View* source, + const gfx::Point& point, + ui::MenuSourceType source_type) override; + + // Show either a context or normal click menu of given |menu_model|. + // If |context_menu| is set, the displayed menu is a context menu and not + // a menu listing one or more running applications. + // The |click_point| is only used for |context_menu|'s. + void ShowMenu(ui::MenuModel* menu_model, + views::View* source, + const gfx::Point& click_point, + bool context_menu, + ui::MenuSourceType source_type); + + // Overridden from views::BoundsAnimatorObserver: + void OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) override; + void OnBoundsAnimatorDone(views::BoundsAnimator* animator) override; + + // Returns true if the (press down) |event| is a repost event from an event + // which just closed the menu of a shelf item. If it occurs on the same shelf + // item, we should ignore the call. + bool IsRepostEvent(const ui::Event& event); + + // Convenience accessor to model_->items(). + const ShelfItem* ShelfItemForView(const views::View* view) const; + + // Returns true if a tooltip should be shown for |view|. + bool ShouldShowTooltipForView(const views::View* view) const; + + // Get the distance from the given |coordinate| to the closest point on this + // launcher/shelf. + int CalculateShelfDistance(const gfx::Point& coordinate) const; + + // The shelf application instance. + mojo::ApplicationImpl* app_; + + // The shelf model. + ShelfModel model_; + + ShelfAlignment alignment_; + + // Used to manage the set of active launcher buttons. There is a view per + // item in |model_|. + views::ViewModel view_model_; + + // Index of first visible launcher item. + int first_visible_index_; + + // Last index of a launcher button that is visible + // (does not go into overflow). + mutable int last_visible_index_; + + scoped_ptr bounds_animator_; + + /* TODO(msw): Restore the overflow button and bubble, etc. + OverflowButton* overflow_button_; + + scoped_ptr overflow_bubble_; + + OverflowBubble* owner_overflow_bubble_;*/ + + ShelfTooltipManager tooltip_; + + // Pointer device that initiated the current drag operation. If there is no + // current dragging operation, this is NONE. + Pointer drag_pointer_; + + // The view being dragged. This is set immediately when the mouse is pressed. + // |dragging_| is set only if the mouse is dragged far enough. + ShelfButton* drag_view_; + + // Position of the mouse down event in |drag_view_|'s coordinates. + gfx::Point drag_origin_; + + // Index |drag_view_| was initially at. + int start_drag_index_; + + // Used for the context menu of a particular item. + ShelfID context_menu_id_; + + scoped_ptr focus_search_; + + scoped_ptr context_menu_model_; + + scoped_ptr launcher_menu_runner_; + + /* TODO(msw): Restore functionality: + base::ObserverList observers_;*/ + + // Amount content is inset on the left edge (or top edge for vertical + // alignment). + int leading_inset_; + + /* TODO(msw): Restore functionality: + ShelfGestureHandler gesture_handler_;*/ + + // True when an item being inserted or removed in the model cancels a drag. + bool cancelling_drag_model_changed_; + + // Index of the last hidden launcher item. If there are no hidden items this + // will be equal to last_visible_index_ + 1. + mutable int last_hidden_index_; + + // The timestamp of the event which closed the last menu - or 0. + base::TimeDelta closing_event_time_; + + // When this object gets deleted while a menu is shown, this pointed + // element will be set to false. + bool* got_deleted_; + + // True if a drag and drop operation created/pinned the item in the launcher + // and it needs to be deleted/unpinned again if the operation gets cancelled. + bool drag_and_drop_item_pinned_; + + // The ShelfItem which is currently used for a drag and a drop operation + // or 0 otherwise. + ShelfID drag_and_drop_shelf_id_; + + // The application ID of the application which we drag and drop. + std::string drag_and_drop_app_id_; + + // The original launcher item's size before the dragging operation. + gfx::Size pre_drag_and_drop_size_; + + // The image proxy for drag operations when a drag and drop host exists and + // the item can be dragged outside the app grid. + /* TODO(msw): Restore functionality: + scoped_ptr drag_image_;*/ + + // The cursor offset to the middle of the dragged item. + gfx::Vector2d drag_image_offset_; + + // The view which gets replaced by our drag icon proxy. + views::View* drag_replaced_view_; + + // True when the icon was dragged off the shelf. + bool dragged_off_shelf_; + + // The rip off view when a snap back operation is underway. + views::View* snap_back_from_rip_off_view_; + + /* TODO(msw): Restore functionality: + // Holds ShelfItemDelegateManager. + ShelfItemDelegateManager* item_manager_;*/ + + // True when this ShelfView is used for Overflow Bubble. + bool overflow_mode_; + + // Holds a pointer to main ShelfView when a ShelfView is in overflow mode. + ShelfView* main_shelf_; + + // True when ripped item from overflow bubble is entered into Shelf. + bool dragged_off_from_overflow_to_shelf_; + + // True if the event is a repost event from a event which has just closed the + // menu of the same shelf item. + bool is_repost_event_; + + // Record the index for the last pressed shelf item. This variable is used to + // check if a repost event occurs on the same shelf item as previous one. If + // so, the repost event should be ignored. + int last_pressed_index_; + + /* TODO(msw): Restore functionality: + // Tracks UMA metrics based on shelf button press actions. + ShelfButtonPressedMetricTracker shelf_button_pressed_metric_tracker_;*/ DISALLOW_COPY_AND_ASSIGN(ShelfView); };