diff options
author | skuhne@chromium.org <skuhne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-29 04:06:26 +0000 |
---|---|---|
committer | skuhne@chromium.org <skuhne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-29 04:06:26 +0000 |
commit | afcade6dd7ada5e53fa96e325268dc3986b29332 (patch) | |
tree | ba8ba4953aea35f06737bd76502b2da05e5202e3 | |
parent | 4d577be73fc82b6c387dbf023be8a4e0f69a3a3f (diff) | |
download | chromium_src-afcade6dd7ada5e53fa96e325268dc3986b29332.zip chromium_src-afcade6dd7ada5e53fa96e325268dc3986b29332.tar.gz chromium_src-afcade6dd7ada5e53fa96e325268dc3986b29332.tar.bz2 |
Adding animation anf f/x logic for dragging items from shelf to the desktop to unpin them
Still not entirely done since I am waiting for the artwork from our designers. However the various functions and animations are now functional.
BUG=249081
TEST=browser test & manual testing
Review URL: https://chromiumcodereview.appspot.com/23655003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@220185 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ash/drag_drop/drag_image_view.cc | 10 | ||||
-rw-r--r-- | ash/drag_drop/drag_image_view.h | 10 | ||||
-rw-r--r-- | ash/launcher/launcher_button.cc | 31 | ||||
-rw-r--r-- | ash/launcher/launcher_button.h | 8 | ||||
-rw-r--r-- | ash/launcher/launcher_view.cc | 133 | ||||
-rw-r--r-- | ash/launcher/launcher_view.h | 12 | ||||
-rw-r--r-- | chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc | 10 |
7 files changed, 164 insertions, 50 deletions
diff --git a/ash/drag_drop/drag_image_view.cc b/ash/drag_drop/drag_image_view.cc index 4dd729b..5d0f2be 100644 --- a/ash/drag_drop/drag_image_view.cc +++ b/ash/drag_drop/drag_image_view.cc @@ -58,6 +58,10 @@ void DragImageView::SetScreenPosition(const gfx::Point& position) { widget_->SetBounds(gfx::Rect(position, widget_size_)); } +gfx::Rect DragImageView::GetBoundsInScreen() const { + return widget_->GetWindowBoundsInScreen(); +} + void DragImageView::SetWidgetVisible(bool visible) { if (visible != widget_->IsVisible()) { if (visible) @@ -67,6 +71,12 @@ void DragImageView::SetWidgetVisible(bool visible) { } } +void DragImageView::SetOpacity(float visibility) { + DCHECK_GE(visibility, 0.0f); + DCHECK_LE(visibility, 1.0f); + widget_->SetOpacity(static_cast<int>(0xff * visibility)); +} + void DragImageView::OnPaint(gfx::Canvas* canvas) { if (GetImage().isNull()) return; diff --git a/ash/drag_drop/drag_image_view.h b/ash/drag_drop/drag_image_view.h index c2791c4..5480d00 100644 --- a/ash/drag_drop/drag_image_view.h +++ b/ash/drag_drop/drag_image_view.h @@ -14,6 +14,10 @@ class Widget; namespace ash { namespace internal { +// This class allows to show a (native) view always on top of everything. It +// does this by creating a widget and setting the content as the given view. The +// caller can then use this object to freely move / drag it around on the +// desktop in screen coordinates. class DragImageView : public views::ImageView { public: explicit DragImageView(gfx::NativeView context); @@ -28,9 +32,15 @@ class DragImageView : public views::ImageView { // Sets the position of the native widget. void SetScreenPosition(const gfx::Point& position); + // Gets the image position in screen coordinates. + gfx::Rect GetBoundsInScreen() const; + // Sets the visibility of the native widget. void SetWidgetVisible(bool visible); + // Sets the |opacity| of the image view between 0.0 and 1.0. + void SetOpacity(float opacity); + private: // Overridden from views::ImageView. virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; diff --git a/ash/launcher/launcher_button.cc b/ash/launcher/launcher_button.cc index 7529f6f..2ce96eb 100644 --- a/ash/launcher/launcher_button.cc +++ b/ash/launcher/launcher_button.cc @@ -293,6 +293,10 @@ void LauncherButton::SetImage(const gfx::ImageSkia& image) { skia::ImageOperations::RESIZE_BEST, gfx::Size(width, height))); } +const gfx::ImageSkia& LauncherButton::GetImage() const { + return icon_view_->GetImage(); +} + void LauncherButton::AddState(State state) { if (!(state_ & state)) { if (!ash::switches::UseAlternateShelfLayout() && @@ -511,8 +515,23 @@ bool LauncherButton::IsShelfHorizontal() const { } void LauncherButton::UpdateState() { - // Even if not shown, the activation state image has an influence on the - // layout. To avoid any odd movement we assign a bitmap here. + UpdateBar(); + + icon_view_->SetHorizontalAlignment( + shelf_layout_manager_->PrimaryAxisValue(views::ImageView::CENTER, + views::ImageView::LEADING)); + icon_view_->SetVerticalAlignment( + shelf_layout_manager_->PrimaryAxisValue(views::ImageView::LEADING, + views::ImageView::CENTER)); + SchedulePaint(); +} + +void LauncherButton::UpdateBar() { + if (state_ & STATE_HIDDEN) { + bar_->SetVisible(false); + return; + } + int bar_id = 0; if (ash::switches::UseAlternateShelfLayout()) { if (state_ & STATE_ACTIVE) @@ -557,14 +576,6 @@ void LauncherButton::UpdateState() { } bar_->SetVisible(bar_id != 0 && state_ != STATE_NORMAL); - - icon_view_->SetHorizontalAlignment( - shelf_layout_manager_->PrimaryAxisValue(views::ImageView::CENTER, - views::ImageView::LEADING)); - icon_view_->SetVerticalAlignment( - shelf_layout_manager_->PrimaryAxisValue(views::ImageView::LEADING, - views::ImageView::CENTER)); - SchedulePaint(); } } // namespace internal diff --git a/ash/launcher/launcher_button.h b/ash/launcher/launcher_button.h index 273640d..dec0edc 100644 --- a/ash/launcher/launcher_button.h +++ b/ash/launcher/launcher_button.h @@ -33,6 +33,8 @@ class ASH_EXPORT LauncherButton : public views::CustomButton { // Underlying LauncherItem needs user's attention. STATE_ATTENTION = 1 << 3, STATE_FOCUSED = 1 << 4, + // Hide the status (temporarily for some animations). + STATE_HIDDEN = 1 << 5, }; virtual ~LauncherButton(); @@ -45,6 +47,9 @@ class ASH_EXPORT LauncherButton : public views::CustomButton { // 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); @@ -126,6 +131,9 @@ class ASH_EXPORT LauncherButton : public views::CustomButton { // alignment. This may add or remove views, layout and paint. void UpdateState(); + // Updates the status bar (bitmap, orientation, visibility). + void UpdateBar(); + LauncherButtonHost* host_; IconView* icon_view_; // Draws a bar underneath the image to represent the state of the application. diff --git a/ash/launcher/launcher_view.cc b/ash/launcher/launcher_view.cc index 9114032..c208721 100644 --- a/ash/launcher/launcher_view.cc +++ b/ash/launcher/launcher_view.cc @@ -105,6 +105,9 @@ const int kMaximumAppMenuItemLength = 350; const int kRipOffDistance = 48; const int kReinsertDistance = 32; +// The rip off drag and drop proxy image should get scaled by this factor. +const float kDragAndDropProxyScale = 1.5f; + namespace { // The MenuModelAdapter gets slightly changed to adapt the menu appearance to @@ -410,6 +413,7 @@ LauncherView::LauncherView(LauncherModel* model, drag_and_drop_item_pinned_(false), drag_and_drop_launcher_id_(0), dragged_off_shelf_(false), + snap_back_from_rip_off_view_(NULL), item_manager_(Shell::GetInstance()->launcher_item_delegate_manager()) { DCHECK(model_); bounds_animator_.reset(new views::BoundsAnimator(this)); @@ -999,9 +1003,10 @@ void LauncherView::ContinueDrag(const ui::LocatedEvent& event) { return; } - // If this is not a drag and drop host operation, check if the item got - // ripped off the shelf - if it did we are done. - if (!drag_and_drop_launcher_id_ && ash::switches::UseDragOffShelf()) { + // 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_launcher_id_ && ash::switches::UseDragOffShelf() && + RemovableByRipOff(current_index) != NOT_REMOVABLE) { if (HandleRipOffDrag(event)) return; // The rip off handler could have changed the location of the item. @@ -1074,7 +1079,7 @@ bool LauncherView::HandleRipOffDrag(const ui::LocatedEvent& event) { // rip off distance to avoid flickering. if (delta < kReinsertDistance) { // Destroy our proxy view item. - // TODO(skuhne): Do it! + 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; @@ -1082,17 +1087,27 @@ bool LauncherView::HandleRipOffDrag(const ui::LocatedEvent& event) { return false; } // Move our proxy view item. - // TODO(skuhne): Do it! + UpdateDragIconProxy(event.root_location()); return true; } // Check if we are too far away from the shelf to enter the ripped off state. if (delta > kRipOffDistance) { // Create a proxy view item which can be moved anywhere. - // TODO(skuhne): Do it! - // Move the item to the end of the launcher and hide it. + LauncherButton* button = static_cast<LauncherButton*>(drag_view_); + CreateDragIconProxy(event.root_location(), + button->GetImage(), + drag_view_, + gfx::Vector2d(0, 0), + kDragAndDropProxyScale); drag_view_->layer()->SetOpacity(0.0f); - model_->Move(current_index, model_->item_count() - 1); - AnimateToIdealBounds(); + if (RemovableByRipOff(current_index) == REMOVABLE) { + // Move the item to the end of the launcher and hide it. + model_->Move(current_index, model_->item_count() - 1); + AnimateToIdealBounds(); + // Make the item partially disappear to show that it will get removed if + // dropped. + drag_image_->SetOpacity(0.5f); + } dragged_off_shelf_ = true; return true; } @@ -1108,42 +1123,65 @@ void LauncherView::FinalizeRipOffDrag(bool cancel) { // 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, a sync operation must have - // removed it. In that case we shouldn't change the model and only delete the - // proxy image. + // 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) { - // TODO(skuhne): Destroy the proxy immediately. - } else { - // Items which cannot be dragged off will be handled as a cancel. - if (!cancel) { - // Make sure we do not try to remove un-removable items like items which - // were not pinned or have to be always there. - LauncherItemType type = model_->items()[current_index].type; + 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) { + // Make sure we do not try to remove un-removable items like items which + // were not pinned or have to be always there. + if (RemovableByRipOff(current_index) != REMOVABLE) { + cancel = true; + snap_back = true; + } else { + // Make sure the item stays invisible upon removal. + drag_view_->SetVisible(false); std::string app_id = delegate_->GetAppIDForLauncherID(model_->items()[current_index].id); - if (type == TYPE_APP_LIST || - type == TYPE_BROWSER_SHORTCUT || - !delegate_->IsAppPinned(app_id)) { - cancel = true; - } else { - // Make sure the item stays invisible upon removal. - drag_view_->SetVisible(false); - delegate_->UnpinAppWithID(app_id); - } + delegate_->UnpinAppWithID(app_id); } - if (cancel) { - // TODO(skuhne): This is not correct since it shows the animation from - // the outer rim towards the old location instead of the animation from - // the proxy towards the original location. - if (!cancelling_drag_model_changed_) { - // When a cancelling 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); + } + if (cancel || snap_back) { + if (!cancelling_drag_model_changed_) { + // Only do something if the change did not come through a model change. + 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_; + LauncherButton* button = static_cast<LauncherButton*>(drag_view_); + button->AddState(LauncherButton::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(); +} + +LauncherView::RemovableState LauncherView::RemovableByRipOff(int index) { + LauncherItemType type = model_->items()[index].type; + if (type == TYPE_APP_LIST) + return NOT_REMOVABLE; + std::string app_id = + delegate_->GetAppIDForLauncherID(model_->items()[index].id); + // Note: Only pinned app shortcuts can be removed! + return (type == TYPE_APP_SHORTCUT && delegate_->IsAppPinned(app_id)) ? + REMOVABLE : DRAGGABLE; } bool LauncherView::SameDragType(LauncherItemType typea, @@ -1778,6 +1816,23 @@ void LauncherView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) { } void LauncherView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) { + if (snap_back_from_rip_off_view_ && animator == bounds_animator_) { + if (!animator->IsAnimating(snap_back_from_rip_off_view_)) { + // Coming here the animation of the LauncherButton 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_) { + LauncherButton* button = static_cast<LauncherButton*>(view); + button->ClearState(LauncherButton::STATE_HIDDEN); + break; + } + } + snap_back_from_rip_off_view_ = NULL; + } + } } bool LauncherView::IsUsableEvent(const ui::Event& event) { diff --git a/ash/launcher/launcher_view.h b/ash/launcher/launcher_view.h index b1e4f75..dcaf265 100644 --- a/ash/launcher/launcher_view.h +++ b/ash/launcher/launcher_view.h @@ -133,6 +133,12 @@ class ASH_EXPORT LauncherView : public views::View, gfx::Rect overflow_bounds; }; + enum RemovableState { + REMOVABLE, // Item can be removed when dragged away. + DRAGGABLE, // Item can be removed, but will snap always back to origin. + NOT_REMOVABLE, // Item is fixed and can never be removed. + }; + bool is_overflow_mode() const { return first_visible_index_ > 0; } @@ -179,6 +185,9 @@ class ASH_EXPORT LauncherView : public views::View, // 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); + // Returns true if |typea| and |typeb| should be in the same drag range. bool SameDragType(LauncherItemType typea, LauncherItemType typeb) const; @@ -387,6 +396,9 @@ class ASH_EXPORT LauncherView : public views::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_; + // Holds LauncherItemDelegateManager. LauncherItemDelegateManager* item_manager_; diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc index dfc5b5e..54df0b1 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc @@ -284,15 +284,19 @@ class LauncherAppBrowserTest : public ExtensionBrowserTest { base::MessageLoop::current()->RunUntilIdle(); generator->MoveMouseTo(rip_off_point.x(), rip_off_point.y()); base::MessageLoop::current()->RunUntilIdle(); + test->RunMessageLoopUntilAnimationsDone(); if (command == RIP_OFF_ITEM_AND_RETURN) { generator->MoveMouseTo(start_point.x(), start_point.y()); base::MessageLoop::current()->RunUntilIdle(); + test->RunMessageLoopUntilAnimationsDone(); } else if (command == RIP_OFF_ITEM_AND_CANCEL) { // This triggers an internal cancel. Using VKEY_ESCAPE was too unreliable. button->OnMouseCaptureLost(); } - if (command != RIP_OFF_ITEM_AND_DONT_RELEASE_MOUSE) + if (command != RIP_OFF_ITEM_AND_DONT_RELEASE_MOUSE) { generator->ReleaseLeftButton(); + test->RunMessageLoopUntilAnimationsDone(); + } } ash::Launcher* launcher_; @@ -1607,6 +1611,10 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, DragOffShelf) { EXPECT_EQ(3, model_->item_count()); EXPECT_EQ(browser_index, GetIndexOfLauncherItemType(ash::TYPE_BROWSER_SHORTCUT)); + // Make sure that the hide state has been unset after the snap back animation + // finished. + ash::internal::LauncherButton* button = test.GetButton(browser_index); + EXPECT_FALSE(button->state() & ash::internal::LauncherButton::STATE_HIDDEN); // Test #2: Ripping out the application and canceling the operation should // not change anything. |