blob: 8cf70412b416df0af0aad8ebfad0fa2e9e5e97d9 [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Joe Onoratoa5902522009-07-30 13:37:37 -070017package com.android.launcher2;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070019import com.android.launcher.R;
Winson Chungaafa03c2010-06-11 17:34:16 -070020
21import android.app.WallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080022import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040023import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070024import android.content.res.TypedArray;
Michael Jurkadee05892010-07-27 10:01:56 -070025import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070026import android.graphics.Canvas;
Michael Jurkaa63c4522010-08-19 13:52:27 -070027import android.graphics.Color;
Michael Jurkadee05892010-07-27 10:01:56 -070028import android.graphics.Matrix;
29import android.graphics.Paint;
30import android.graphics.PorterDuff;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080031import android.graphics.Rect;
32import android.graphics.RectF;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070033import android.graphics.drawable.Drawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080034import android.util.AttributeSet;
35import android.view.ContextMenu;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewDebug;
39import android.view.ViewGroup;
Michael Jurkadee05892010-07-27 10:01:56 -070040import android.view.View.OnTouchListener;
Winson Chungaafa03c2010-06-11 17:34:16 -070041import android.view.animation.Animation;
42import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070044import java.util.ArrayList;
45import java.util.Arrays;
Romain Guyedcce092010-03-04 13:03:17 -080046
The Android Open Source Project31dd5032009-03-03 19:32:27 -080047public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070048 static final String TAG = "CellLayout";
Michael Jurkadee05892010-07-27 10:01:56 -070049 // we make the dimmed bitmap smaller than the screen itself for memory + perf reasons
50 static final float DIMMED_BITMAP_SCALE = 0.25f;
Winson Chungaafa03c2010-06-11 17:34:16 -070051
The Android Open Source Project31dd5032009-03-03 19:32:27 -080052 private boolean mPortrait;
53
54 private int mCellWidth;
55 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070056
Winson Chungaafa03c2010-06-11 17:34:16 -070057 private int mLeftPadding;
58 private int mRightPadding;
59 private int mTopPadding;
60 private int mBottomPadding;
61
Adam Cohend22015c2010-07-26 22:02:18 -070062 private int mCountX;
63 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080064
65 private int mWidthGap;
66 private int mHeightGap;
67
68 private final Rect mRect = new Rect();
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -070069 private final RectF mRectF = new RectF();
The Android Open Source Project31dd5032009-03-03 19:32:27 -080070 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070071
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070072 // This is a temporary variable to prevent having to allocate a new object just to
73 // return an (x, y) value from helper functions. Do NOT use it to maintain other state.
74 private final int[] mTmpCellXY = new int[2];
75
The Android Open Source Project31dd5032009-03-03 19:32:27 -080076 boolean[][] mOccupied;
77
Michael Jurkadee05892010-07-27 10:01:56 -070078 private OnTouchListener mInterceptTouchListener;
79
80 // this is what the home screen fades to when it shrinks
81 // (ie in all apps and in home screen customize mode)
82 private Bitmap mDimmedBitmap;
83 private Canvas mDimmedBitmapCanvas;
84 private float mDimmedBitmapAlpha;
Michael Jurkaa63c4522010-08-19 13:52:27 -070085 private boolean mDimmedBitmapDirty = false;
Michael Jurkadee05892010-07-27 10:01:56 -070086 private final Paint mDimmedBitmapPaint = new Paint();
87 private final Rect mLayoutRect = new Rect();
88 private final Rect mDimmedBitmapRect = new Rect();
Michael Jurkaa63c4522010-08-19 13:52:27 -070089 private Drawable mDimmedBitmapBackground;
90 private Drawable mDimmedBitmapBackgroundHover;
91 // If we're actively dragging something over this screen and it's small,
92 // mHover is true
93 private boolean mHover = false;
Michael Jurkadee05892010-07-27 10:01:56 -070094
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070095 private final RectF mDragRect = new RectF();
96
97 // When dragging, used to indicate a vacant drop location
98 private Drawable mVacantDrawable;
99
100 // When dragging, used to indicate an occupied drop location
101 private Drawable mOccupiedDrawable;
102
103 // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate
104 private Drawable mDragRectDrawable;
105
106 // When a drag operation is in progress, holds the nearest cell to the touch point
107 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800108
109 private boolean mDirtyTag;
Mike Cleronf8bbd342009-10-23 16:15:16 -0700110 private boolean mLastDownOnOccupiedCell = false;
Winson Chungaafa03c2010-06-11 17:34:16 -0700111
112 private final WallpaperManager mWallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800113
114 public CellLayout(Context context) {
115 this(context, null);
116 }
117
118 public CellLayout(Context context, AttributeSet attrs) {
119 this(context, attrs, 0);
120 }
121
122 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
123 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700124
125 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
126 // the user where a dragged item will land when dropped.
127 setWillNotDraw(false);
128 mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green);
129 mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red);
130
Michael Jurkaa63c4522010-08-19 13:52:27 -0700131 if (LauncherApplication.isScreenXLarge()) {
132 mDimmedBitmapBackground = getResources().getDrawable(
133 R.drawable.mini_home_screen_bg);
Michael Jurka43477052010-09-09 12:09:25 -0700134 mDimmedBitmapBackground.setFilterBitmap(true);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700135
136 mDimmedBitmapBackgroundHover = getResources().getDrawable(
137 R.drawable.mini_home_screen_bg_hover);
Michael Jurka43477052010-09-09 12:09:25 -0700138 mDimmedBitmapBackgroundHover.setFilterBitmap(true);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700139 }
140
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800141 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
142
143 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
144 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700145
Adam Cohend22015c2010-07-26 22:02:18 -0700146 mLeftPadding =
147 a.getDimensionPixelSize(R.styleable.CellLayout_xAxisStartPadding, 10);
148 mRightPadding =
149 a.getDimensionPixelSize(R.styleable.CellLayout_xAxisEndPadding, 10);
150 mTopPadding =
151 a.getDimensionPixelSize(R.styleable.CellLayout_yAxisStartPadding, 10);
152 mBottomPadding =
153 a.getDimensionPixelSize(R.styleable.CellLayout_yAxisEndPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700154
Adam Cohend22015c2010-07-26 22:02:18 -0700155 mCountX = LauncherModel.getCellCountX();
156 mCountY = LauncherModel.getCellCountY();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800157
158 a.recycle();
159
160 setAlwaysDrawnWithCacheEnabled(false);
161
Romain Guy84f296c2009-11-04 15:00:44 -0800162 mWallpaperManager = WallpaperManager.getInstance(getContext());
Michael Jurkadee05892010-07-27 10:01:56 -0700163
164 mDimmedBitmapPaint.setFilterBitmap(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800165 }
166
Michael Jurkaa63c4522010-08-19 13:52:27 -0700167 public void setHover(boolean value) {
168 if (mHover != value) {
169 invalidate();
170 }
171 mHover = value;
172 }
173
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700174 @Override
Romain Guya6abce82009-11-10 02:54:41 -0800175 public void dispatchDraw(Canvas canvas) {
Michael Jurkaa63c4522010-08-19 13:52:27 -0700176 if (mDimmedBitmapAlpha > 0.0f) {
177 final Drawable bg = mHover ? mDimmedBitmapBackgroundHover : mDimmedBitmapBackground;
178 bg.setAlpha((int) (mDimmedBitmapAlpha * 255));
179 bg.draw(canvas);
180 }
Romain Guya6abce82009-11-10 02:54:41 -0800181 super.dispatchDraw(canvas);
182 }
183
184 @Override
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700185 protected void onDraw(Canvas canvas) {
186 if (!mDragRect.isEmpty()) {
187 mDragRectDrawable.setBounds(
188 (int)mDragRect.left,
189 (int)mDragRect.top,
190 (int)mDragRect.right,
191 (int)mDragRect.bottom);
192 mDragRectDrawable.draw(canvas);
193 }
Michael Jurkadee05892010-07-27 10:01:56 -0700194 if (mDimmedBitmap != null && mDimmedBitmapAlpha > 0.0f) {
195 if (mDimmedBitmapDirty) {
196 updateDimmedBitmap();
197 mDimmedBitmapDirty = false;
198 }
199 mDimmedBitmapPaint.setAlpha((int) (mDimmedBitmapAlpha * 255));
200
Michael Jurka43477052010-09-09 12:09:25 -0700201 //canvas.drawBitmap(mDimmedBitmap, mDimmedBitmapRect, mLayoutRect, mDimmedBitmapPaint);
Michael Jurkadee05892010-07-27 10:01:56 -0700202 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700203 }
204
205 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700206 public void cancelLongPress() {
207 super.cancelLongPress();
208
209 // Cancel long press for all children
210 final int count = getChildCount();
211 for (int i = 0; i < count; i++) {
212 final View child = getChildAt(i);
213 child.cancelLongPress();
214 }
215 }
216
Michael Jurkadee05892010-07-27 10:01:56 -0700217 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
218 mInterceptTouchListener = listener;
219 }
220
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800221 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700222 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800223 }
224
225 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700226 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800227 }
228
Winson Chungaafa03c2010-06-11 17:34:16 -0700229 // Takes canonical layout parameters
230 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) {
231 final LayoutParams lp = params;
232
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800233 // Generate an id for each view, this assumes we have at most 256x256 cells
234 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700235 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700236 // If the horizontal or vertical span is set to -1, it is taken to
237 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700238 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
239 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800240
Winson Chungaafa03c2010-06-11 17:34:16 -0700241 child.setId(childId);
242
Michael Jurkadee05892010-07-27 10:01:56 -0700243 // We might be in the middle or end of shrinking/fading to a dimmed view
244 // Make sure this view's alpha is set the same as all the rest of the views
Michael Jurka43477052010-09-09 12:09:25 -0700245 //child.setAlpha(1.0f - mDimmedBitmapAlpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700246
Winson Chungaafa03c2010-06-11 17:34:16 -0700247 addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700248
249 // next time we draw the dimmed bitmap we need to update it
250 mDimmedBitmapDirty = true;
Winson Chungaafa03c2010-06-11 17:34:16 -0700251 return true;
252 }
253 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800254 }
255
256 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700257 public void removeView(View view) {
258 super.removeView(view);
259 mDimmedBitmapDirty = true;
260 }
261
262 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800263 public void requestChildFocus(View child, View focused) {
264 super.requestChildFocus(child, focused);
265 if (child != null) {
266 Rect r = new Rect();
267 child.getDrawingRect(r);
268 requestRectangleOnScreen(r);
269 }
270 }
271
272 @Override
273 protected void onAttachedToWindow() {
274 super.onAttachedToWindow();
275 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
276 }
277
Michael Jurkaaf442092010-06-10 17:01:57 -0700278 public void setTagToCellInfoForPoint(int touchX, int touchY) {
279 final CellInfo cellInfo = mCellInfo;
280 final Rect frame = mRect;
281 final int x = touchX + mScrollX;
282 final int y = touchY + mScrollY;
283 final int count = getChildCount();
284
285 boolean found = false;
286 for (int i = count - 1; i >= 0; i--) {
287 final View child = getChildAt(i);
288
289 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
290 child.getHitRect(frame);
291 if (frame.contains(x, y)) {
292 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
293 cellInfo.cell = child;
294 cellInfo.cellX = lp.cellX;
295 cellInfo.cellY = lp.cellY;
296 cellInfo.spanX = lp.cellHSpan;
297 cellInfo.spanY = lp.cellVSpan;
Michael Jurkac28de512010-08-13 11:27:44 -0700298 cellInfo.intersectX = lp.cellX;
299 cellInfo.intersectY = lp.cellY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700300 cellInfo.valid = true;
301 found = true;
302 mDirtyTag = false;
303 break;
304 }
305 }
306 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700307
Michael Jurkaaf442092010-06-10 17:01:57 -0700308 mLastDownOnOccupiedCell = found;
309
310 if (!found) {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700311 final int cellXY[] = mTmpCellXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700312 pointToCellExact(x, y, cellXY);
313
314 final boolean portrait = mPortrait;
Adam Cohend22015c2010-07-26 22:02:18 -0700315 final int xCount = mCountX;
316 final int yCount = mCountY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700317
318 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700319 findOccupiedCells(xCount, yCount, occupied, null, true);
Michael Jurkaaf442092010-06-10 17:01:57 -0700320
321 cellInfo.cell = null;
322 cellInfo.cellX = cellXY[0];
323 cellInfo.cellY = cellXY[1];
324 cellInfo.spanX = 1;
325 cellInfo.spanY = 1;
Michael Jurkac28de512010-08-13 11:27:44 -0700326 cellInfo.intersectX = cellXY[0];
327 cellInfo.intersectY = cellXY[1];
Michael Jurkaaf442092010-06-10 17:01:57 -0700328 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
329 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
330
331 // Instead of finding the interesting vacant cells here, wait until a
332 // caller invokes getTag() to retrieve the result. Finding the vacant
333 // cells is a bit expensive and can generate many new objects, it's
334 // therefore better to defer it until we know we actually need it.
335
336 mDirtyTag = true;
337 }
338 setTag(cellInfo);
339 }
340
Winson Chungaafa03c2010-06-11 17:34:16 -0700341
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800342 @Override
343 public boolean onInterceptTouchEvent(MotionEvent ev) {
Michael Jurkadee05892010-07-27 10:01:56 -0700344 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
345 return true;
346 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800347 final int action = ev.getAction();
348 final CellInfo cellInfo = mCellInfo;
349
350 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700351 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800352 } else if (action == MotionEvent.ACTION_UP) {
353 cellInfo.cell = null;
354 cellInfo.cellX = -1;
355 cellInfo.cellY = -1;
356 cellInfo.spanX = 0;
357 cellInfo.spanY = 0;
358 cellInfo.valid = false;
359 mDirtyTag = false;
360 setTag(cellInfo);
361 }
362
363 return false;
364 }
365
366 @Override
367 public CellInfo getTag() {
368 final CellInfo info = (CellInfo) super.getTag();
369 if (mDirtyTag && info.valid) {
Adam Cohend22015c2010-07-26 22:02:18 -0700370 final int xCount = mCountX;
371 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800372
373 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700374 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800375
Michael Jurkac28de512010-08-13 11:27:44 -0700376 info.updateOccupiedCells(occupied, mCountX, mCountY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800377
378 mDirtyTag = false;
379 }
380 return info;
381 }
382
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700383 /**
384 * Check if the column 'x' is empty from rows 'top' to 'bottom', inclusive.
385 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800386 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
387 for (int y = top; y <= bottom; y++) {
388 if (occupied[x][y]) {
389 return false;
390 }
391 }
392 return true;
393 }
394
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700395 /**
396 * Check if the row 'y' is empty from columns 'left' to 'right', inclusive.
397 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800398 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
399 for (int x = left; x <= right; x++) {
400 if (occupied[x][y]) {
401 return false;
402 }
403 }
404 return true;
405 }
406
Michael Jurkac28de512010-08-13 11:27:44 -0700407 CellInfo updateOccupiedCells(boolean[] occupiedCells, View ignoreView) {
Adam Cohend22015c2010-07-26 22:02:18 -0700408 final int xCount = mCountX;
409 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800410
411 boolean[][] occupied = mOccupied;
412
413 if (occupiedCells != null) {
414 for (int y = 0; y < yCount; y++) {
415 for (int x = 0; x < xCount; x++) {
416 occupied[x][y] = occupiedCells[y * xCount + x];
417 }
418 }
419 } else {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700420 findOccupiedCells(xCount, yCount, occupied, ignoreView, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800421 }
422
423 CellInfo cellInfo = new CellInfo();
424
425 cellInfo.cellX = -1;
426 cellInfo.cellY = -1;
Michael Jurkac28de512010-08-13 11:27:44 -0700427 cellInfo.intersectX = -1;
428 cellInfo.intersectY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800429 cellInfo.spanY = 0;
430 cellInfo.spanX = 0;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800431 cellInfo.screen = mCellInfo.screen;
432
Michael Jurkac28de512010-08-13 11:27:44 -0700433 cellInfo.updateOccupiedCells(occupied, mCountX, mCountY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800434
Michael Jurkac28de512010-08-13 11:27:44 -0700435 cellInfo.valid = cellInfo.existsEmptyCell();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800436
437 // Assume the caller will perform their own cell searching, otherwise we
438 // risk causing an unnecessary rebuild after findCellForSpan()
Winson Chungaafa03c2010-06-11 17:34:16 -0700439
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800440 return cellInfo;
441 }
442
443 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700444 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800445 * @param x X coordinate of the point
446 * @param y Y coordinate of the point
447 * @param result Array of 2 ints to hold the x and y coordinate of the cell
448 */
449 void pointToCellExact(int x, int y, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700450 final int hStartPadding = getLeftPadding();
451 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800452
453 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
454 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
455
Adam Cohend22015c2010-07-26 22:02:18 -0700456 final int xAxis = mCountX;
457 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800458
459 if (result[0] < 0) result[0] = 0;
460 if (result[0] >= xAxis) result[0] = xAxis - 1;
461 if (result[1] < 0) result[1] = 0;
462 if (result[1] >= yAxis) result[1] = yAxis - 1;
463 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700464
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800465 /**
466 * Given a point, return the cell that most closely encloses that point
467 * @param x X coordinate of the point
468 * @param y Y coordinate of the point
469 * @param result Array of 2 ints to hold the x and y coordinate of the cell
470 */
471 void pointToCellRounded(int x, int y, int[] result) {
472 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
473 }
474
475 /**
476 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700477 *
478 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800479 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700480 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800481 * @param result Array of 2 ints to hold the x and y coordinate of the point
482 */
483 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700484 final int hStartPadding = getLeftPadding();
485 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800486
487 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
488 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
489 }
490
Romain Guy84f296c2009-11-04 15:00:44 -0800491 int getCellWidth() {
492 return mCellWidth;
493 }
494
495 int getCellHeight() {
496 return mCellHeight;
497 }
498
Romain Guy1a304a12009-11-10 00:02:32 -0800499 int getLeftPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700500 return mLeftPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800501 }
502
503 int getTopPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700504 return mTopPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800505 }
506
507 int getRightPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700508 return mRightPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800509 }
510
511 int getBottomPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700512 return mBottomPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800513 }
514
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800515 @Override
516 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
517 // TODO: currently ignoring padding
Winson Chungaafa03c2010-06-11 17:34:16 -0700518
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800519 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700520 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
521
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800522 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
523 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700524
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800525 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
526 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
527 }
528
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800529 final int cellWidth = mCellWidth;
530 final int cellHeight = mCellHeight;
531
Adam Cohend22015c2010-07-26 22:02:18 -0700532 if (mOccupied == null) {
533 mOccupied = new boolean[mCountX][mCountY];
Winson Chungaafa03c2010-06-11 17:34:16 -0700534 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800535
Adam Cohend22015c2010-07-26 22:02:18 -0700536 int numWidthGaps = mCountX - 1;
537 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800538
Adam Cohend22015c2010-07-26 22:02:18 -0700539 int vSpaceLeft = heightSpecSize - mTopPadding
540 - mBottomPadding - (cellHeight * mCountY);
541 mHeightGap = vSpaceLeft / numHeightGaps;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800542
Adam Cohend22015c2010-07-26 22:02:18 -0700543 int hSpaceLeft = widthSpecSize - mLeftPadding
544 - mRightPadding - (cellWidth * mCountX);
545 mWidthGap = hSpaceLeft / numWidthGaps;
Winson Chungaafa03c2010-06-11 17:34:16 -0700546
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800547 int count = getChildCount();
548
549 for (int i = 0; i < count; i++) {
550 View child = getChildAt(i);
551 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Winson Chungaafa03c2010-06-11 17:34:16 -0700552 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
553 mLeftPadding, mTopPadding);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800554
Winson Chungaafa03c2010-06-11 17:34:16 -0700555 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
556 MeasureSpec.EXACTLY);
557 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
558 MeasureSpec.EXACTLY);
559
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800560 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
561 }
562
563 setMeasuredDimension(widthSpecSize, heightSpecSize);
564 }
565
566 @Override
567 protected void onLayout(boolean changed, int l, int t, int r, int b) {
568 int count = getChildCount();
569
570 for (int i = 0; i < count; i++) {
571 View child = getChildAt(i);
572 if (child.getVisibility() != GONE) {
573
574 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
575
576 int childLeft = lp.x;
577 int childTop = lp.y;
578 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
Romain Guy84f296c2009-11-04 15:00:44 -0800579
580 if (lp.dropped) {
581 lp.dropped = false;
582
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700583 final int[] cellXY = mTmpCellXY;
Romain Guy06762ab2010-01-25 16:51:08 -0800584 getLocationOnScreen(cellXY);
Romain Guy84f296c2009-11-04 15:00:44 -0800585 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
Romain Guy06762ab2010-01-25 16:51:08 -0800586 cellXY[0] + childLeft + lp.width / 2,
587 cellXY[1] + childTop + lp.height / 2, 0, null);
Romain Guy84f296c2009-11-04 15:00:44 -0800588 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800589 }
590 }
591 }
592
593 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700594 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
595 super.onSizeChanged(w, h, oldw, oldh);
596 mLayoutRect.set(0, 0, w, h);
597 mDimmedBitmapRect.set(0, 0, (int) (DIMMED_BITMAP_SCALE * w), (int) (DIMMED_BITMAP_SCALE * h));
Michael Jurkaa63c4522010-08-19 13:52:27 -0700598 if (mDimmedBitmapBackground != null) {
599 mDimmedBitmapBackground.setBounds(mLayoutRect);
600 }
601 if (mDimmedBitmapBackgroundHover != null) {
602 mDimmedBitmapBackgroundHover.setBounds(mLayoutRect);
603 }
Michael Jurkadee05892010-07-27 10:01:56 -0700604 }
605
606 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800607 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
608 final int count = getChildCount();
609 for (int i = 0; i < count; i++) {
610 final View view = getChildAt(i);
611 view.setDrawingCacheEnabled(enabled);
612 // Update the drawing caches
Adam Powellfefa0ce2010-05-03 10:23:50 -0700613 view.buildDrawingCache(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800614 }
615 }
616
617 @Override
618 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
619 super.setChildrenDrawnWithCacheEnabled(enabled);
620 }
621
Michael Jurkadee05892010-07-27 10:01:56 -0700622 public float getDimmedBitmapAlpha() {
623 return mDimmedBitmapAlpha;
624 }
625
626 public void setDimmedBitmapAlpha(float alpha) {
627 // If we're dimming the screen after it was not dimmed, refresh
628 // to allow for updated widgets. We don't continually refresh it
629 // after this point, however, as an optimization
630 if (mDimmedBitmapAlpha == 0.0f && alpha > 0.0f) {
631 updateDimmedBitmap();
632 }
633 mDimmedBitmapAlpha = alpha;
Michael Jurka43477052010-09-09 12:09:25 -0700634 //setChildrenAlpha(1.0f - mDimmedBitmapAlpha);
Michael Jurka0142d492010-08-25 17:46:15 -0700635 invalidate();
Michael Jurkadee05892010-07-27 10:01:56 -0700636 }
637
638 private void setChildrenAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -0700639 final int childCount = getChildCount();
640 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -0700641 getChildAt(i).setAlpha(alpha);
642 }
643 }
644
645 public void updateDimmedBitmap() {
646 if (mDimmedBitmap == null) {
647 mDimmedBitmap = Bitmap.createBitmap((int) (getWidth() * DIMMED_BITMAP_SCALE),
648 (int) (getHeight() * DIMMED_BITMAP_SCALE), Bitmap.Config.ARGB_8888);
649 mDimmedBitmapCanvas = new Canvas(mDimmedBitmap);
650 mDimmedBitmapCanvas.scale(DIMMED_BITMAP_SCALE, DIMMED_BITMAP_SCALE);
651 }
652 // clear the canvas
653 mDimmedBitmapCanvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
654
655 // draw the screen into the bitmap
656 // just for drawing to the bitmap, make all the items on the screen opaque
Michael Jurka43477052010-09-09 12:09:25 -0700657 //setChildrenAlpha(1.0f);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700658 // call our superclass's dispatchdraw so we don't draw the background
659 super.dispatchDraw(mDimmedBitmapCanvas);
Michael Jurka43477052010-09-09 12:09:25 -0700660 //setChildrenAlpha(1.0f - mDimmedBitmapAlpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700661
Michael Jurkaa63c4522010-08-19 13:52:27 -0700662 // replace all colored areas with a dark (semi-transparent black)
663 mDimmedBitmapCanvas.drawColor(Color.argb(160, 0, 0, 0), PorterDuff.Mode.SRC_IN);
Michael Jurkadee05892010-07-27 10:01:56 -0700664 }
665
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700666 private boolean isVacant(int originX, int originY, int spanX, int spanY) {
667 for (int i = 0; i < spanY; i++) {
668 if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) {
669 return false;
670 }
671 }
672 return true;
673 }
674
Patrick Dubroy440c3602010-07-13 17:50:32 -0700675 public View getChildAt(int x, int y) {
676 final int count = getChildCount();
677 for (int i = 0; i < count; i++) {
678 View child = getChildAt(i);
679 LayoutParams lp = (LayoutParams) child.getLayoutParams();
680
681 if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
682 (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
683 return child;
684 }
685 }
686 return null;
687 }
688
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700689 /**
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700690 * Estimate the size that a child with the given dimensions will take in the layout.
691 */
692 void estimateChildSize(int minWidth, int minHeight, int[] result) {
693 // Assuming it's placed at 0, 0, find where the bottom right cell will land
694 rectToCell(minWidth, minHeight, result);
695
696 // Then figure out the rect it will occupy
697 cellToRect(0, 0, result[0], result[1], mRectF);
698 result[0] = (int)mRectF.width();
699 result[1] = (int)mRectF.height();
700 }
701
702 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700703 * Estimate where the top left cell of the dragged item will land if it is dropped.
704 *
705 * @param originX The X value of the top left corner of the item
706 * @param originY The Y value of the top left corner of the item
707 * @param spanX The number of horizontal cells that the item spans
708 * @param spanY The number of vertical cells that the item spans
709 * @param result The estimated drop cell X and Y.
710 */
711 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -0700712 final int countX = mCountX;
713 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700714
Michael Jurkaa63c4522010-08-19 13:52:27 -0700715 // pointToCellRounded takes the top left of a cell but will pad that with
716 // cellWidth/2 and cellHeight/2 when finding the matching cell
717 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700718
719 // If the item isn't fully on this screen, snap to the edges
720 int rightOverhang = result[0] + spanX - countX;
721 if (rightOverhang > 0) {
722 result[0] -= rightOverhang; // Snap to right
723 }
724 result[0] = Math.max(0, result[0]); // Snap to left
725 int bottomOverhang = result[1] + spanY - countY;
726 if (bottomOverhang > 0) {
727 result[1] -= bottomOverhang; // Snap to bottom
728 }
729 result[1] = Math.max(0, result[1]); // Snap to top
730 }
731
732 void visualizeDropLocation(View view, int originX, int originY, int spanX, int spanY) {
733 final int[] originCell = mDragCell;
734 final int[] cellXY = mTmpCellXY;
735 estimateDropCell(originX, originY, spanX, spanY, cellXY);
736
737 // Only recalculate the bounding rect when necessary
738 if (!Arrays.equals(cellXY, originCell)) {
739 originCell[0] = cellXY[0];
740 originCell[1] = cellXY[1];
741
742 // Find the top left corner of the rect the object will occupy
743 final int[] topLeft = mTmpCellXY;
744 cellToPoint(originCell[0], originCell[1], topLeft);
745 final int left = topLeft[0];
746 final int top = topLeft[1];
747
748 // Now find the bottom right
749 final int[] bottomRight = mTmpCellXY;
750 cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight);
751 bottomRight[0] += mCellWidth;
752 bottomRight[1] += mCellHeight;
753
Adam Cohend22015c2010-07-26 22:02:18 -0700754 final int countX = mCountX;
755 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700756 // TODO: It's not necessary to do this every time, but it's not especially expensive
757 findOccupiedCells(countX, countY, mOccupied, view, false);
758
759 boolean vacant = isVacant(originCell[0], originCell[1], spanX, spanY);
760 mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable;
761
762 // mDragRect will be rendered in onDraw()
763 mDragRect.set(left, top, bottomRight[0], bottomRight[1]);
764 invalidate();
765 }
766 }
767
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800768 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700769 * Find a vacant area that will fit the given bounds nearest the requested
770 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -0700771 *
Romain Guy51afc022009-05-04 18:03:43 -0700772 * @param pixelX The X location at which you want to search for a vacant area.
773 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -0700774 * @param spanX Horizontal span of the object.
775 * @param spanY Vertical span of the object.
776 * @param vacantCells Pre-computed set of vacant cells to search.
777 * @param recycle Previously returned value to possibly recycle.
778 * @return The X, Y cell of a vacant area that can contain this object,
779 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800780 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700781 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
782 CellInfo vacantCells, int[] recycle) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700783
Jeff Sharkey70864282009-04-07 21:08:40 -0700784 // Keep track of best-scoring drop area
785 final int[] bestXY = recycle != null ? recycle : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -0700786 double bestDistance = Double.MAX_VALUE;
Winson Chungaafa03c2010-06-11 17:34:16 -0700787
Michael Jurkac28de512010-08-13 11:27:44 -0700788 for (int x = 0; x < mCountX - (spanX - 1); x++) {
789 inner:
790 for (int y = 0; y < mCountY - (spanY - 1); y++) {
791 for (int i = 0; i < spanX; i++) {
792 for (int j = 0; j < spanY; j++) {
793 if (mOccupied[x + i][y + j]) {
794 // small optimization: we can skip to below the row we just found
795 // an occupied cell
796 y += j;
797 continue inner;
798 }
799 }
800 }
801 final int[] cellXY = mTmpCellXY;
802 cellToPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800803
Michael Jurkac28de512010-08-13 11:27:44 -0700804 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
805 + Math.pow(cellXY[1] - pixelY, 2));
806 if (distance <= bestDistance) {
807 bestDistance = distance;
808 bestXY[0] = x;
809 bestXY[1] = y;
810 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800811 }
812 }
813
Winson Chungaafa03c2010-06-11 17:34:16 -0700814 // Return null if no suitable location found
Jeff Sharkey70864282009-04-07 21:08:40 -0700815 if (bestDistance < Double.MAX_VALUE) {
816 return bestXY;
817 } else {
818 return null;
819 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800820 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700821
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800822 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700823 * Called when a drag and drop operation has finished (successfully or not).
824 */
825 void onDragComplete() {
826 // Invalidate the drag data
827 mDragCell[0] = -1;
828 mDragCell[1] = -1;
829
Michael Jurkaa63c4522010-08-19 13:52:27 -0700830 setHover(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700831 mDragRect.setEmpty();
832 invalidate();
833 }
834
835 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700836 * Mark a child as having been dropped.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800837 *
838 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800839 */
Winson Chungaafa03c2010-06-11 17:34:16 -0700840 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -0700841 if (child != null) {
842 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guyd94533d2009-08-17 10:01:15 -0700843 lp.isDragging = false;
Romain Guy84f296c2009-11-04 15:00:44 -0800844 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -0700845 mDragRect.setEmpty();
846 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -0700847 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700848 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800849 }
850
851 void onDropAborted(View child) {
852 if (child != null) {
853 ((LayoutParams) child.getLayoutParams()).isDragging = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800854 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700855 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800856 }
857
858 /**
859 * Start dragging the specified child
Winson Chungaafa03c2010-06-11 17:34:16 -0700860 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800861 * @param child The child that is being dragged
862 */
863 void onDragChild(View child) {
864 LayoutParams lp = (LayoutParams) child.getLayoutParams();
865 lp.isDragging = true;
866 mDragRect.setEmpty();
867 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700868
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800869 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800870 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -0700871 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800872 * @param cellX X coordinate of upper left corner expressed as a cell position
873 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -0700874 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800875 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700876 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800877 */
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700878 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800879 final int cellWidth = mCellWidth;
880 final int cellHeight = mCellHeight;
881 final int widthGap = mWidthGap;
882 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -0700883
884 final int hStartPadding = getLeftPadding();
885 final int vStartPadding = getTopPadding();
886
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800887 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
888 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
889
890 int x = hStartPadding + cellX * (cellWidth + widthGap);
891 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -0700892
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700893 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800894 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700895
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800896 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700897 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800898 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -0700899 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800900 * @param width Width in pixels
901 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700902 * @param result An array of length 2 in which to store the result (may be null).
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800903 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700904 public int[] rectToCell(int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800905 // Always assume we're working with the smallest span to make sure we
906 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -0400907 final Resources resources = getResources();
908 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
909 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800910 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -0400911
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800912 // Always round up to next largest cell
913 int spanX = (width + smallerSize) / smallerSize;
914 int spanY = (height + smallerSize) / smallerSize;
Joe Onorato79e56262009-09-21 15:23:04 -0400915
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700916 if (result == null) {
917 return new int[] { spanX, spanY };
918 }
919 result[0] = spanX;
920 result[1] = spanY;
921 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800922 }
923
924 /**
925 * Find the first vacant cell, if there is one.
926 *
927 * @param vacant Holds the x and y coordinate of the vacant cell
928 * @param spanX Horizontal cell span.
929 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -0700930 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800931 * @return True if a vacant cell was found
932 */
933 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
Adam Cohend22015c2010-07-26 22:02:18 -0700934 final int xCount = mCountX;
935 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800936 final boolean[][] occupied = mOccupied;
937
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700938 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800939
940 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
941 }
942
943 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
944 int xCount, int yCount, boolean[][] occupied) {
945
946 for (int x = 0; x < xCount; x++) {
947 for (int y = 0; y < yCount; y++) {
948 boolean available = !occupied[x][y];
949out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
950 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
951 available = available && !occupied[i][j];
952 if (!available) break out;
953 }
954 }
955
956 if (available) {
957 vacant[0] = x;
958 vacant[1] = y;
959 return true;
960 }
961 }
962 }
963
964 return false;
965 }
966
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700967 /**
968 * Update the array of occupied cells (mOccupied), and return a flattened copy of the array.
969 */
970 boolean[] getOccupiedCellsFlattened() {
Adam Cohend22015c2010-07-26 22:02:18 -0700971 final int xCount = mCountX;
972 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800973 final boolean[][] occupied = mOccupied;
974
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700975 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800976
977 final boolean[] flat = new boolean[xCount * yCount];
978 for (int y = 0; y < yCount; y++) {
979 for (int x = 0; x < xCount; x++) {
980 flat[y * xCount + x] = occupied[x][y];
981 }
982 }
983
984 return flat;
985 }
986
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700987 /**
988 * Update the array of occupied cells.
989 * @param ignoreView If non-null, the space occupied by this View is treated as vacant
990 * @param ignoreFolders If true, a cell occupied by a Folder is treated as vacant
991 */
992 private void findOccupiedCells(
993 int xCount, int yCount, boolean[][] occupied, View ignoreView, boolean ignoreFolders) {
994
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800995 for (int x = 0; x < xCount; x++) {
996 for (int y = 0; y < yCount; y++) {
997 occupied[x][y] = false;
998 }
999 }
1000
1001 int count = getChildCount();
1002 for (int i = 0; i < count; i++) {
1003 View child = getChildAt(i);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001004 if ((ignoreFolders && child instanceof Folder) || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001005 continue;
1006 }
1007 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1008
1009 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
1010 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
1011 occupied[x][y] = true;
1012 }
1013 }
1014 }
1015 }
1016
1017 @Override
1018 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1019 return new CellLayout.LayoutParams(getContext(), attrs);
1020 }
1021
1022 @Override
1023 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1024 return p instanceof CellLayout.LayoutParams;
1025 }
1026
1027 @Override
1028 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1029 return new CellLayout.LayoutParams(p);
1030 }
1031
Winson Chungaafa03c2010-06-11 17:34:16 -07001032 public static class CellLayoutAnimationController extends LayoutAnimationController {
1033 public CellLayoutAnimationController(Animation animation, float delay) {
1034 super(animation, delay);
1035 }
1036
1037 @Override
1038 protected long getDelayForView(View view) {
1039 return (int) (Math.random() * 150);
1040 }
1041 }
1042
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001043 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1044 /**
1045 * Horizontal location of the item in the grid.
1046 */
1047 @ViewDebug.ExportedProperty
1048 public int cellX;
1049
1050 /**
1051 * Vertical location of the item in the grid.
1052 */
1053 @ViewDebug.ExportedProperty
1054 public int cellY;
1055
1056 /**
1057 * Number of cells spanned horizontally by the item.
1058 */
1059 @ViewDebug.ExportedProperty
1060 public int cellHSpan;
1061
1062 /**
1063 * Number of cells spanned vertically by the item.
1064 */
1065 @ViewDebug.ExportedProperty
1066 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07001067
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001068 /**
1069 * Is this item currently being dragged
1070 */
1071 public boolean isDragging;
1072
1073 // X coordinate of the view in the layout.
1074 @ViewDebug.ExportedProperty
1075 int x;
1076 // Y coordinate of the view in the layout.
1077 @ViewDebug.ExportedProperty
1078 int y;
1079
Romain Guy84f296c2009-11-04 15:00:44 -08001080 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07001081
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001082 public LayoutParams(Context c, AttributeSet attrs) {
1083 super(c, attrs);
1084 cellHSpan = 1;
1085 cellVSpan = 1;
1086 }
1087
1088 public LayoutParams(ViewGroup.LayoutParams source) {
1089 super(source);
1090 cellHSpan = 1;
1091 cellVSpan = 1;
1092 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001093
1094 public LayoutParams(LayoutParams source) {
1095 super(source);
1096 this.cellX = source.cellX;
1097 this.cellY = source.cellY;
1098 this.cellHSpan = source.cellHSpan;
1099 this.cellVSpan = source.cellVSpan;
1100 }
1101
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001102 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08001103 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001104 this.cellX = cellX;
1105 this.cellY = cellY;
1106 this.cellHSpan = cellHSpan;
1107 this.cellVSpan = cellVSpan;
1108 }
1109
1110 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
1111 int hStartPadding, int vStartPadding) {
Winson Chungaafa03c2010-06-11 17:34:16 -07001112
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001113 final int myCellHSpan = cellHSpan;
1114 final int myCellVSpan = cellVSpan;
1115 final int myCellX = cellX;
1116 final int myCellY = cellY;
Winson Chungaafa03c2010-06-11 17:34:16 -07001117
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001118 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
1119 leftMargin - rightMargin;
1120 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
1121 topMargin - bottomMargin;
1122
1123 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
1124 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
1125 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001126
1127 public String toString() {
1128 return "(" + this.cellX + ", " + this.cellY + ")";
1129 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001130 }
1131
1132 static final class CellInfo implements ContextMenu.ContextMenuInfo {
Michael Jurkac28de512010-08-13 11:27:44 -07001133 private boolean[][] mOccupied;
1134 private int mCountX;
1135 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001136 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07001137 int cellX = -1;
1138 int cellY = -1;
Michael Jurkac28de512010-08-13 11:27:44 -07001139 // intersectX and intersectY constrain the results of findCellForSpan; any empty space
1140 // it results must include this point (unless intersectX and intersectY are -1)
Michael Jurkaa63c4522010-08-19 13:52:27 -07001141 int intersectX = -1;
1142 int intersectY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001143 int spanX;
1144 int spanY;
1145 int screen;
1146 boolean valid;
1147
Michael Jurkac28de512010-08-13 11:27:44 -07001148 void updateOccupiedCells(boolean[][] occupied, int xCount, int yCount) {
1149 mOccupied = occupied.clone();
1150 mCountX = xCount;
1151 mCountY = yCount;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001152 }
1153
Michael Jurkac28de512010-08-13 11:27:44 -07001154 void updateOccupiedCells(boolean[] occupied, int xCount, int yCount) {
1155 if (mOccupied == null || mCountX != xCount || mCountY != yCount) {
1156 mOccupied = new boolean[xCount][yCount];
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001157 }
Michael Jurkac28de512010-08-13 11:27:44 -07001158 mCountX = xCount;
1159 mCountY = yCount;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001160 for (int y = 0; y < yCount; y++) {
1161 for (int x = 0; x < xCount; x++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001162 mOccupied[x][y] = occupied[y * xCount + x];
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001163 }
1164 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001165 }
1166
Michael Jurkac28de512010-08-13 11:27:44 -07001167 boolean existsEmptyCell() {
1168 return findCellForSpan(null, 1, 1);
1169 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001170 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001171 * Finds the upper-left coordinate of the first rectangle in the grid that can
Michael Jurkac28de512010-08-13 11:27:44 -07001172 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
1173 * then this method will only return coordinates for rectangles that contain the cell
1174 * (intersectX, intersectY)
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001175 *
1176 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1177 * can be found.
1178 * @param spanX The horizontal span of the cell we want to find.
1179 * @param spanY The vertical span of the cell we want to find.
1180 *
1181 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1182 */
1183 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka0e260592010-06-30 17:07:39 -07001184 // return the span represented by the CellInfo only there is no view there
1185 // (this.cell == null) and there is enough space
Michael Jurkac28de512010-08-13 11:27:44 -07001186
Michael Jurka0e260592010-06-30 17:07:39 -07001187 if (this.cell == null && this.spanX >= spanX && this.spanY >= spanY) {
Michael Jurkac28de512010-08-13 11:27:44 -07001188 if (cellXY != null) {
1189 cellXY[0] = cellX;
1190 cellXY[1] = cellY;
1191 }
1192 return true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001193 }
1194
Michael Jurkac28de512010-08-13 11:27:44 -07001195 int startX = 0;
1196 if (intersectX >= 0) {
1197 startX = Math.max(startX, intersectX - (spanX - 1));
1198 }
1199 int endX = mCountX - (spanX - 1);
1200 if (intersectX >= 0) {
1201 endX = Math.min(endX, intersectX + (spanX - 1));
1202 }
1203 int startY = 0;
1204 if (intersectY >= 0) {
1205 startY = Math.max(startY, intersectY - (spanY - 1));
1206 }
1207 int endY = mCountY - (spanY - 1);
1208 if (intersectY >= 0) {
1209 endY = Math.min(endY, intersectY + (spanY - 1));
1210 }
1211
Michael Jurkaa63c4522010-08-19 13:52:27 -07001212 for (int x = startX; x < endX; x++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001213 inner:
1214 for (int y = startY; y < endY; y++) {
1215 for (int i = 0; i < spanX; i++) {
1216 for (int j = 0; j < spanY; j++) {
1217 if (mOccupied[x + i][y + j]) {
1218 // small optimization: we can skip to below the row we just found
1219 // an occupied cell
1220 y += j;
1221 continue inner;
1222 }
1223 }
1224 }
1225 if (cellXY != null) {
1226 cellXY[0] = x;
1227 cellXY[1] = y;
1228 }
1229 return true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001230 }
1231 }
Michael Jurkac28de512010-08-13 11:27:44 -07001232 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001233 }
1234
1235 @Override
1236 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001237 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
1238 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001239 }
1240 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001241
1242 public boolean lastDownOnOccupiedCell() {
1243 return mLastDownOnOccupiedCell;
1244 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001245}