summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorskuhne@chromium.org <skuhne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-29 04:06:26 +0000
committerskuhne@chromium.org <skuhne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-29 04:06:26 +0000
commitafcade6dd7ada5e53fa96e325268dc3986b29332 (patch)
treeba8ba4953aea35f06737bd76502b2da05e5202e3
parent4d577be73fc82b6c387dbf023be8a4e0f69a3a3f (diff)
downloadchromium_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.cc10
-rw-r--r--ash/drag_drop/drag_image_view.h10
-rw-r--r--ash/launcher/launcher_button.cc31
-rw-r--r--ash/launcher/launcher_button.h8
-rw-r--r--ash/launcher/launcher_view.cc133
-rw-r--r--ash/launcher/launcher_view.h12
-rw-r--r--chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc10
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.