blob: 1e95333ff2220edcee89d3453de7742486e56123 [file] [log] [blame]
Daniel Sandler325dc232013-06-05 22:57:57 -04001package com.android.launcher3;
Adam Cohend4844c32011-02-18 19:25:06 -08002
3import android.animation.AnimatorSet;
4import android.animation.ObjectAnimator;
5import android.animation.PropertyValuesHolder;
6import android.animation.ValueAnimator;
7import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohen0cf2a7c2011-11-08 15:07:01 -08008import android.appwidget.AppWidgetHostView;
Adam Cohend4844c32011-02-18 19:25:06 -08009import android.appwidget.AppWidgetProviderInfo;
10import android.content.Context;
Sunny Goyal756cd262015-08-20 12:33:21 -070011import android.graphics.Point;
Adam Cohen0cf2a7c2011-11-08 15:07:01 -080012import android.graphics.Rect;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070013import android.util.AttributeSet;
Tony Wickham71255bb2016-02-10 16:18:15 -080014import android.view.KeyEvent;
Sunny Goyal6ad72f02016-09-23 11:01:10 -070015import android.view.MotionEvent;
Tony Wickham71255bb2016-02-10 16:18:15 -080016import android.view.View;
Sunny Goyal37920962017-09-28 13:43:24 -070017import android.view.ViewGroup;
Adam Cohend4844c32011-02-18 19:25:06 -080018
Sunny Goyale78e3d72015-09-24 11:23:31 -070019import com.android.launcher3.accessibility.DragViewStateAnnouncer;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070020import com.android.launcher3.dragndrop.DragLayer;
Tony Wickham71255bb2016-02-10 16:18:15 -080021import com.android.launcher3.util.FocusLogic;
Sunny Goyale78e3d72015-09-24 11:23:31 -070022
Sunny Goyal37920962017-09-28 13:43:24 -070023public class AppWidgetResizeFrame extends AbstractFloatingView implements View.OnKeyListener {
Sunny Goyalba776d52015-05-18 20:52:57 -070024 private static final int SNAP_DURATION = 150;
25 private static final float DIMMED_HANDLE_ALPHA = 0f;
26 private static final float RESIZE_THRESHOLD = 0.66f;
27
Sunny Goyal756cd262015-08-20 12:33:21 -070028 private static final Rect sTmpRect = new Rect();
29
30 // Represents the cell size on the grid in the two orientations.
31 private static Point[] sCellSize;
Sunny Goyalba776d52015-05-18 20:52:57 -070032
Sunny Goyal08ca40f2016-09-13 21:07:31 -070033 private static final int HANDLE_COUNT = 4;
34 private static final int INDEX_LEFT = 0;
35 private static final int INDEX_TOP = 1;
36 private static final int INDEX_RIGHT = 2;
37 private static final int INDEX_BOTTOM = 3;
38
Sunny Goyalba776d52015-05-18 20:52:57 -070039 private final Launcher mLauncher;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070040 private final DragViewStateAnnouncer mStateAnnouncer;
Sunny Goyalba776d52015-05-18 20:52:57 -070041
Sunny Goyal08ca40f2016-09-13 21:07:31 -070042 private final View[] mDragHandles = new View[HANDLE_COUNT];
Sunny Goyalba776d52015-05-18 20:52:57 -070043
Sunny Goyal08ca40f2016-09-13 21:07:31 -070044 private LauncherAppWidgetHostView mWidgetView;
45 private CellLayout mCellLayout;
46 private DragLayer mDragLayer;
47
48 private Rect mWidgetPadding;
Sunny Goyalba776d52015-05-18 20:52:57 -070049
50 private final int mBackgroundPadding;
51 private final int mTouchTargetWidth;
52
53 private final int[] mDirectionVector = new int[2];
54 private final int[] mLastDirectionVector = new int[2];
Adam Cohend4844c32011-02-18 19:25:06 -080055
Sunny Goyal08ca40f2016-09-13 21:07:31 -070056 private final IntRange mTempRange1 = new IntRange();
57 private final IntRange mTempRange2 = new IntRange();
58
59 private final IntRange mDeltaXRange = new IntRange();
60 private final IntRange mBaselineX = new IntRange();
61
62 private final IntRange mDeltaYRange = new IntRange();
63 private final IntRange mBaselineY = new IntRange();
Sunny Goyale78e3d72015-09-24 11:23:31 -070064
Adam Cohend4844c32011-02-18 19:25:06 -080065 private boolean mLeftBorderActive;
66 private boolean mRightBorderActive;
67 private boolean mTopBorderActive;
68 private boolean mBottomBorderActive;
69
Adam Cohend4844c32011-02-18 19:25:06 -080070 private int mResizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080071
Adam Cohend4844c32011-02-18 19:25:06 -080072 private int mRunningHInc;
73 private int mRunningVInc;
74 private int mMinHSpan;
75 private int mMinVSpan;
76 private int mDeltaX;
77 private int mDeltaY;
Adam Cohenbebf0422012-04-11 18:06:28 -070078 private int mDeltaXAddOn;
79 private int mDeltaYAddOn;
Adam Cohen1b607ed2011-03-03 17:26:50 -080080
Adam Cohen4459d6b2012-07-13 15:59:15 -070081 private int mTopTouchRegionAdjustment = 0;
82 private int mBottomTouchRegionAdjustment = 0;
83
Sunny Goyal6ad72f02016-09-23 11:01:10 -070084 private int mXDown, mYDown;
85
Sunny Goyal08ca40f2016-09-13 21:07:31 -070086 public AppWidgetResizeFrame(Context context) {
87 this(context, null);
88 }
Adam Cohend4844c32011-02-18 19:25:06 -080089
Sunny Goyal08ca40f2016-09-13 21:07:31 -070090 public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
91 this(context, attrs, 0);
92 }
93
94 public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
95 super(context, attrs, defStyleAttr);
96
97 mLauncher = Launcher.getLauncher(context);
98 mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
99
100 mBackgroundPadding = getResources()
101 .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
102 mTouchTargetWidth = 2 * mBackgroundPadding;
103 }
104
105 @Override
106 protected void onFinishInflate() {
107 super.onFinishInflate();
108
Sunny Goyal37920962017-09-28 13:43:24 -0700109 ViewGroup content = (ViewGroup) getChildAt(0);
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700110 for (int i = 0; i < HANDLE_COUNT; i ++) {
Sunny Goyal37920962017-09-28 13:43:24 -0700111 mDragHandles[i] = content.getChildAt(i);
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700112 }
113 }
114
Sunny Goyal37920962017-09-28 13:43:24 -0700115 public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
116 Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
117 AbstractFloatingView.closeAllOpenViews(launcher);
118
119 DragLayer dl = launcher.getDragLayer();
120 AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
121 .inflate(R.layout.app_widget_resize_frame, dl, false);
122 frame.setupForWidget(widget, cellLayout, dl);
123 ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
124
125 dl.addView(frame);
126 frame.mIsOpen = true;
127 frame.snapToWidget(false);
128 }
129
130 private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700131 DragLayer dragLayer) {
Adam Cohend4844c32011-02-18 19:25:06 -0800132 mCellLayout = cellLayout;
133 mWidgetView = widgetView;
Adam Cohen59400422014-03-05 18:07:04 -0800134 LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
135 widgetView.getAppWidgetInfo();
136 mResizeMode = info.resizeMode;
Adam Cohen67882692011-03-11 15:29:03 -0800137 mDragLayer = dragLayer;
Adam Cohen3cba7222011-03-02 19:03:11 -0800138
Sunny Goyal233ee962015-08-03 13:05:01 -0700139 mMinHSpan = info.minSpanX;
140 mMinVSpan = info.minSpanY;
Adam Cohend4844c32011-02-18 19:25:06 -0800141
Sunny Goyal952e63d2017-08-16 04:59:08 -0700142 mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
143 widgetView.getAppWidgetInfo().provider, null);
Adam Cohen59400422014-03-05 18:07:04 -0800144
Adam Cohend4844c32011-02-18 19:25:06 -0800145 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700146 mDragHandles[INDEX_TOP].setVisibility(GONE);
147 mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
Adam Cohend4844c32011-02-18 19:25:06 -0800148 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700149 mDragHandles[INDEX_LEFT].setVisibility(GONE);
150 mDragHandles[INDEX_RIGHT].setVisibility(GONE);
Adam Cohen3cba7222011-03-02 19:03:11 -0800151 }
152
Adam Cohenbebf0422012-04-11 18:06:28 -0700153 // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
154 // cells (same if not resized, or different) will be marked as occupied when the resize
155 // frame is dismissed.
156 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
Tony Wickham71255bb2016-02-10 16:18:15 -0800157
158 setOnKeyListener(this);
Adam Cohend4844c32011-02-18 19:25:06 -0800159 }
160
161 public boolean beginResizeIfPointInRegion(int x, int y) {
162 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
163 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
Adam Cohen4459d6b2012-07-13 15:59:15 -0700164
Adam Cohen3cba7222011-03-02 19:03:11 -0800165 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
166 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
Adam Cohen4459d6b2012-07-13 15:59:15 -0700167 mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
168 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
169 && verticalActive;
Adam Cohend4844c32011-02-18 19:25:06 -0800170
171 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
172 || mTopBorderActive || mBottomBorderActive;
173
Adam Cohend4844c32011-02-18 19:25:06 -0800174 if (anyBordersActive) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700175 mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
176 mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
177 mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
178 mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
Adam Cohend4844c32011-02-18 19:25:06 -0800179 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700180
181 if (mLeftBorderActive) {
182 mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
183 } else if (mRightBorderActive) {
184 mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
185 } else {
186 mDeltaXRange.set(0, 0);
187 }
188 mBaselineX.set(getLeft(), getRight());
189
190 if (mTopBorderActive) {
191 mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
192 } else if (mBottomBorderActive) {
193 mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
194 } else {
195 mDeltaYRange.set(0, 0);
196 }
197 mBaselineY.set(getTop(), getBottom());
198
Adam Cohend4844c32011-02-18 19:25:06 -0800199 return anyBordersActive;
200 }
201
Adam Cohen1b607ed2011-03-03 17:26:50 -0800202 /**
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700203 * Based on the deltas, we resize the frame.
Adam Cohen1b607ed2011-03-03 17:26:50 -0800204 */
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700205 public void visualizeResizeForDelta(int deltaX, int deltaY) {
206 mDeltaX = mDeltaXRange.clamp(deltaX);
207 mDeltaY = mDeltaYRange.clamp(deltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800208
Adam Cohen67882692011-03-11 15:29:03 -0800209 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700210 mDeltaX = mDeltaXRange.clamp(deltaX);
211 mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
212 lp.x = mTempRange1.start;
213 lp.width = mTempRange1.size();
Adam Cohen67882692011-03-11 15:29:03 -0800214
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700215 mDeltaY = mDeltaYRange.clamp(deltaY);
216 mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
217 lp.y = mTempRange1.start;
218 lp.height = mTempRange1.size();
Adam Cohend4844c32011-02-18 19:25:06 -0800219
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700220 resizeWidgetIfNeeded(false);
Jon Miranda097a7252016-12-28 10:49:55 -0800221
222 // When the widget resizes in multi-window mode, the translation value changes to maintain
223 // a center fit. These overrides ensure the resize frame always aligns with the widget view.
224 getSnappedRectRelativeToDragLayer(sTmpRect);
225 if (mLeftBorderActive) {
226 lp.width = sTmpRect.width() + sTmpRect.left - lp.x;
227 }
228 if (mTopBorderActive) {
229 lp.height = sTmpRect.height() + sTmpRect.top - lp.y;
230 }
231 if (mRightBorderActive) {
232 lp.x = sTmpRect.left;
233 }
234 if (mBottomBorderActive) {
235 lp.y = sTmpRect.top;
236 }
237
Adam Cohend4844c32011-02-18 19:25:06 -0800238 requestLayout();
239 }
240
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700241 private static int getSpanIncrement(float deltaFrac) {
242 return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
243 }
244
Adam Cohen1b607ed2011-03-03 17:26:50 -0800245 /**
246 * Based on the current deltas, we determine if and how to resize the widget.
247 */
Adam Cohenbebf0422012-04-11 18:06:28 -0700248 private void resizeWidgetIfNeeded(boolean onDismiss) {
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530249 float xThreshold = mCellLayout.getCellWidth();
250 float yThreshold = mCellLayout.getCellHeight();
Adam Cohend4844c32011-02-18 19:25:06 -0800251
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700252 int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
253 int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
Adam Cohene4b77292011-03-08 18:35:52 -0800254
Adam Cohenbebf0422012-04-11 18:06:28 -0700255 if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
Adam Cohend4844c32011-02-18 19:25:06 -0800256
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700257 mDirectionVector[0] = 0;
258 mDirectionVector[1] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -0800259
260 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
Adam Cohen1b607ed2011-03-03 17:26:50 -0800261
Adam Cohenbebf0422012-04-11 18:06:28 -0700262 int spanX = lp.cellHSpan;
263 int spanY = lp.cellVSpan;
264 int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
265 int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
266
Adam Cohen1b607ed2011-03-03 17:26:50 -0800267 // For each border, we bound the resizing based on the minimum width, and the maximum
268 // expandability.
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700269 mTempRange1.set(cellX, spanX + cellX);
270 int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
271 hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
272 cellX = mTempRange2.start;
273 spanX = mTempRange2.size();
274 if (hSpanDelta != 0) {
275 mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
Adam Cohend4844c32011-02-18 19:25:06 -0800276 }
277
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700278 mTempRange1.set(cellY, spanY + cellY);
279 int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
280 vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
281 cellY = mTempRange2.start;
282 spanY = mTempRange2.size();
283 if (vSpanDelta != 0) {
284 mDirectionVector[1] = mTopBorderActive ? -1 : 1;
Adam Cohend4844c32011-02-18 19:25:06 -0800285 }
286
Adam Cohenbebf0422012-04-11 18:06:28 -0700287 if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
Adam Cohend4844c32011-02-18 19:25:06 -0800288
Adam Cohene0489502012-08-27 15:18:53 -0700289 // We always want the final commit to match the feedback, so we make sure to use the
290 // last used direction vector when committing the resize / reorder.
291 if (onDismiss) {
292 mDirectionVector[0] = mLastDirectionVector[0];
293 mDirectionVector[1] = mLastDirectionVector[1];
294 } else {
295 mLastDirectionVector[0] = mDirectionVector[0];
296 mLastDirectionVector[1] = mDirectionVector[1];
297 }
298
Adam Cohenbebf0422012-04-11 18:06:28 -0700299 if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
300 mDirectionVector, onDismiss)) {
Sunny Goyale78e3d72015-09-24 11:23:31 -0700301 if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) {
302 mStateAnnouncer.announce(
303 mLauncher.getString(R.string.widget_resized, spanX, spanY));
304 }
305
Adam Cohenbebf0422012-04-11 18:06:28 -0700306 lp.tmpCellX = cellX;
307 lp.tmpCellY = cellY;
308 lp.cellHSpan = spanX;
309 lp.cellVSpan = spanY;
310 mRunningVInc += vSpanDelta;
311 mRunningHInc += hSpanDelta;
Sunny Goyale78e3d72015-09-24 11:23:31 -0700312
Adam Cohena897f392012-04-27 18:12:05 -0700313 if (!onDismiss) {
314 updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
315 }
Adam Cohenbebf0422012-04-11 18:06:28 -0700316 }
Adam Cohen67882692011-03-11 15:29:03 -0800317 mWidgetView.requestLayout();
Adam Cohend4844c32011-02-18 19:25:06 -0800318 }
319
Adam Cohena897f392012-04-27 18:12:05 -0700320 static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
321 int spanX, int spanY) {
Sunny Goyalba776d52015-05-18 20:52:57 -0700322 getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);
323 widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,
324 sTmpRect.right, sTmpRect.bottom);
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700325 }
326
Sunny Goyal16466f12016-03-10 05:34:30 -0800327 public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
Sunny Goyal756cd262015-08-20 12:33:21 -0700328 if (sCellSize == null) {
Sunny Goyal87f784c2017-01-11 10:48:34 -0800329 InvariantDeviceProfile inv = LauncherAppState.getIDP(context);
Sunny Goyal756cd262015-08-20 12:33:21 -0700330
331 // Initiate cell sizes.
332 sCellSize = new Point[2];
333 sCellSize[0] = inv.landscapeProfile.getCellSize();
334 sCellSize[1] = inv.portraitProfile.getCellSize();
335 }
336
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700337 if (rect == null) {
338 rect = new Rect();
339 }
Sunny Goyal16466f12016-03-10 05:34:30 -0800340 final float density = context.getResources().getDisplayMetrics().density;
Adam Cohena897f392012-04-27 18:12:05 -0700341
342 // Compute landscape size
Sunny Goyal756cd262015-08-20 12:33:21 -0700343 int landWidth = (int) ((spanX * sCellSize[0].x) / density);
344 int landHeight = (int) ((spanY * sCellSize[0].y) / density);
Adam Cohena897f392012-04-27 18:12:05 -0700345
346 // Compute portrait size
Sunny Goyal756cd262015-08-20 12:33:21 -0700347 int portWidth = (int) ((spanX * sCellSize[1].x) / density);
348 int portHeight = (int) ((spanY * sCellSize[1].y) / density);
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700349 rect.set(portWidth, landHeight, landWidth, portHeight);
350 return rect;
Adam Cohena897f392012-04-27 18:12:05 -0700351 }
352
Sunny Goyal988ad272017-08-18 02:05:01 -0700353 @Override
354 protected void onDetachedFromWindow() {
355 super.onDetachedFromWindow();
356
357 // We are done with resizing the widget. Save the widget size & position to LauncherModel
Adam Cohenbebf0422012-04-11 18:06:28 -0700358 resizeWidgetIfNeeded(true);
Adam Cohenbebf0422012-04-11 18:06:28 -0700359 }
Adam Cohend4844c32011-02-18 19:25:06 -0800360
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700361 private void onTouchUp() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530362 int xThreshold = mCellLayout.getCellWidth();
363 int yThreshold = mCellLayout.getCellHeight();
Adam Cohend4844c32011-02-18 19:25:06 -0800364
Jon Miranda097a7252016-12-28 10:49:55 -0800365 mDeltaXAddOn = mRunningHInc * xThreshold;
366 mDeltaYAddOn = mRunningVInc * yThreshold;
Adam Cohenbebf0422012-04-11 18:06:28 -0700367 mDeltaX = 0;
368 mDeltaY = 0;
369
Adam Cohend4844c32011-02-18 19:25:06 -0800370 post(new Runnable() {
Adam Cohenbebf0422012-04-11 18:06:28 -0700371 @Override
Adam Cohend4844c32011-02-18 19:25:06 -0800372 public void run() {
373 snapToWidget(true);
374 }
375 });
376 }
377
Jon Miranda097a7252016-12-28 10:49:55 -0800378 /**
379 * Returns the rect of this view when the frame is snapped around the widget, with the bounds
380 * relative to the {@link DragLayer}.
381 */
382 private void getSnappedRectRelativeToDragLayer(Rect out) {
Jon Miranda21266912016-12-19 14:12:05 -0800383 float scale = mWidgetView.getScaleToFit();
Adam Cohen37b59ff2011-06-13 17:13:42 -0700384
Jon Miranda097a7252016-12-28 10:49:55 -0800385 mDragLayer.getViewRectRelativeToSelf(mWidgetView, out);
Adam Cohend6e7aa32013-07-09 15:32:37 -0700386
Jon Miranda097a7252016-12-28 10:49:55 -0800387 int width = 2 * mBackgroundPadding
388 + (int) (scale * (out.width() - mWidgetPadding.left - mWidgetPadding.right));
389 int height = 2 * mBackgroundPadding
390 + (int) (scale * (out.height() - mWidgetPadding.top - mWidgetPadding.bottom));
Jon Miranda26e4d172016-12-12 15:43:18 -0800391
Jon Miranda097a7252016-12-28 10:49:55 -0800392 int x = (int) (out.left - mBackgroundPadding + scale * mWidgetPadding.left);
393 int y = (int) (out.top - mBackgroundPadding + scale * mWidgetPadding.top);
394
395 out.left = x;
396 out.top = y;
397 out.right = out.left + width;
398 out.bottom = out.top + height;
399 }
400
Sunny Goyal37920962017-09-28 13:43:24 -0700401 private void snapToWidget(boolean animate) {
Jon Miranda097a7252016-12-28 10:49:55 -0800402 getSnappedRectRelativeToDragLayer(sTmpRect);
403 int newWidth = sTmpRect.width();
404 int newHeight = sTmpRect.height();
405 int newX = sTmpRect.left;
406 int newY = sTmpRect.top;
Adam Cohen3cba7222011-03-02 19:03:11 -0800407
Sunny Goyalba776d52015-05-18 20:52:57 -0700408 // We need to make sure the frame's touchable regions lie fully within the bounds of the
Adam Cohen4459d6b2012-07-13 15:59:15 -0700409 // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
410 // down accordingly to provide a proper touch target.
Adam Cohen3cba7222011-03-02 19:03:11 -0800411 if (newY < 0) {
Adam Cohen4459d6b2012-07-13 15:59:15 -0700412 // In this case we shift the touch region down to start at the top of the DragLayer
413 mTopTouchRegionAdjustment = -newY;
414 } else {
415 mTopTouchRegionAdjustment = 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800416 }
Adam Cohen67882692011-03-11 15:29:03 -0800417 if (newY + newHeight > mDragLayer.getHeight()) {
Adam Cohen4459d6b2012-07-13 15:59:15 -0700418 // In this case we shift the touch region up to end at the bottom of the DragLayer
419 mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
420 } else {
421 mBottomTouchRegionAdjustment = 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800422 }
423
Jon Miranda26e4d172016-12-12 15:43:18 -0800424 final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Adam Cohend4844c32011-02-18 19:25:06 -0800425 if (!animate) {
426 lp.width = newWidth;
427 lp.height = newHeight;
428 lp.x = newX;
429 lp.y = newY;
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700430 for (int i = 0; i < HANDLE_COUNT; i++) {
431 mDragHandles[i].setAlpha(1.0f);
432 }
Adam Cohend4844c32011-02-18 19:25:06 -0800433 requestLayout();
434 } else {
435 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
Adam Cohen1b607ed2011-03-03 17:26:50 -0800436 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
437 newHeight);
Adam Cohend4844c32011-02-18 19:25:06 -0800438 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
439 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100440 ObjectAnimator oa =
441 LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
Adam Cohend4844c32011-02-18 19:25:06 -0800442 oa.addUpdateListener(new AnimatorUpdateListener() {
443 public void onAnimationUpdate(ValueAnimator animation) {
444 requestLayout();
445 }
446 });
Michael Jurka2ecf9952012-06-18 12:52:28 -0700447 AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700448 set.play(oa);
449 for (int i = 0; i < HANDLE_COUNT; i++) {
450 set.play(LauncherAnimUtils.ofFloat(mDragHandles[i], ALPHA, 1.0f));
Adam Cohend4844c32011-02-18 19:25:06 -0800451 }
452
453 set.setDuration(SNAP_DURATION);
454 set.start();
455 }
Tony Wickham71255bb2016-02-10 16:18:15 -0800456
457 setFocusableInTouchMode(true);
458 requestFocus();
459 }
460
461 @Override
462 public boolean onKey(View v, int keyCode, KeyEvent event) {
463 // Clear the frame and give focus to the widget host view when a directional key is pressed.
464 if (FocusLogic.shouldConsume(keyCode)) {
Sunny Goyal37920962017-09-28 13:43:24 -0700465 close(false);
Tony Wickham71255bb2016-02-10 16:18:15 -0800466 mWidgetView.requestFocus();
467 return true;
468 }
469 return false;
Adam Cohend4844c32011-02-18 19:25:06 -0800470 }
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700471
472 private boolean handleTouchDown(MotionEvent ev) {
473 Rect hitRect = new Rect();
474 int x = (int) ev.getX();
475 int y = (int) ev.getY();
476
477 getHitRect(hitRect);
478 if (hitRect.contains(x, y)) {
479 if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
480 mXDown = x;
481 mYDown = y;
482 return true;
483 }
484 }
485 return false;
486 }
487
488 @Override
489 public boolean onControllerTouchEvent(MotionEvent ev) {
490 int action = ev.getAction();
491 int x = (int) ev.getX();
492 int y = (int) ev.getY();
493
494 switch (action) {
495 case MotionEvent.ACTION_DOWN:
496 return handleTouchDown(ev);
497 case MotionEvent.ACTION_MOVE:
498 visualizeResizeForDelta(x - mXDown, y - mYDown);
499 break;
500 case MotionEvent.ACTION_CANCEL:
501 case MotionEvent.ACTION_UP:
502 visualizeResizeForDelta(x - mXDown, y - mYDown);
503 onTouchUp();
504 mXDown = mYDown = 0;
505 break;
506 }
507 return true;
508 }
509
510 @Override
511 public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
512 if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
513 return true;
514 }
Sunny Goyal37920962017-09-28 13:43:24 -0700515 close(false);
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700516 return false;
517 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700518
Sunny Goyal37920962017-09-28 13:43:24 -0700519 @Override
520 protected void handleClose(boolean animate) {
521 mDragLayer.removeView(this);
522 }
523
524 @Override
525 public void logActionCommand(int command) {
526 // TODO: Log this case.
527 }
528
529 @Override
530 protected boolean isOfType(int type) {
531 return (type & TYPE_WIDGET_RESIZE_FRAME) != 0;
532 }
533
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700534 /**
535 * A mutable class for describing the range of two int values.
536 */
537 private static class IntRange {
538
539 public int start, end;
540
541 public int clamp(int value) {
542 return Utilities.boundToRange(value, start, end);
543 }
544
545 public void set(int s, int e) {
546 start = s;
547 end = e;
548 }
549
550 public int size() {
551 return end - start;
552 }
553
554 /**
555 * Moves either the start or end edge (but never both) by {@param delta} and sets the
556 * result in {@param out}
557 */
558 public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
559 out.start = moveStart ? start + delta : start;
560 out.end = moveEnd ? end + delta : end;
561 }
562
563 /**
564 * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
565 * with extra conditions.
566 * @param minSize minimum size after with the moving edge should not be shifted any further.
567 * For eg, if delta = -3 when moving the endEdge brings the size to less than
568 * minSize, only delta = -2 will applied
569 * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
570 * @return the amount of increase when endEdge was moves and the amount of decrease when
571 * the start edge was moved.
572 */
573 public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
574 int minSize, int maxEnd, IntRange out) {
575 applyDelta(moveStart, moveEnd, delta, out);
Jon Miranda9f33cb22017-01-03 09:30:00 -0800576 if (out.start < 0) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700577 out.start = 0;
578 }
Jon Miranda9f33cb22017-01-03 09:30:00 -0800579 if (out.end > maxEnd) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700580 out.end = maxEnd;
581 }
582 if (out.size() < minSize) {
583 if (moveStart) {
584 out.start = out.end - minSize;
585 } else if (moveEnd) {
586 out.end = out.start + minSize;
587 }
588 }
589 return moveEnd ? out.size() - size() : size() - out.size();
590 }
591 }
Adam Cohend4844c32011-02-18 19:25:06 -0800592}