blob: 3f60d504725db1d2cdd53314839f83668ab27e55 [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;
25import android.graphics.Canvas;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.graphics.Rect;
27import android.graphics.RectF;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070028import android.graphics.drawable.Drawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080029import android.util.AttributeSet;
30import android.view.ContextMenu;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewDebug;
34import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070035import android.view.animation.Animation;
36import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080037
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070038import java.util.ArrayList;
39import java.util.Arrays;
Romain Guyedcce092010-03-04 13:03:17 -080040
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070042 static final String TAG = "CellLayout";
43
The Android Open Source Project31dd5032009-03-03 19:32:27 -080044 private boolean mPortrait;
45
46 private int mCellWidth;
47 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070048
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049 private int mLongAxisStartPadding;
50 private int mLongAxisEndPadding;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080051 private int mShortAxisStartPadding;
52 private int mShortAxisEndPadding;
53
Winson Chungaafa03c2010-06-11 17:34:16 -070054 private int mLeftPadding;
55 private int mRightPadding;
56 private int mTopPadding;
57 private int mBottomPadding;
58
The Android Open Source Project31dd5032009-03-03 19:32:27 -080059 private int mShortAxisCells;
60 private int mLongAxisCells;
61
62 private int mWidthGap;
63 private int mHeightGap;
64
65 private final Rect mRect = new Rect();
66 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070067
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070068 // This is a temporary variable to prevent having to allocate a new object just to
69 // return an (x, y) value from helper functions. Do NOT use it to maintain other state.
70 private final int[] mTmpCellXY = new int[2];
71
The Android Open Source Project31dd5032009-03-03 19:32:27 -080072 boolean[][] mOccupied;
73
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070074 private final RectF mDragRect = new RectF();
75
76 // When dragging, used to indicate a vacant drop location
77 private Drawable mVacantDrawable;
78
79 // When dragging, used to indicate an occupied drop location
80 private Drawable mOccupiedDrawable;
81
82 // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate
83 private Drawable mDragRectDrawable;
84
85 // When a drag operation is in progress, holds the nearest cell to the touch point
86 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -080087
88 private boolean mDirtyTag;
Mike Cleronf8bbd342009-10-23 16:15:16 -070089 private boolean mLastDownOnOccupiedCell = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070090
91 private final WallpaperManager mWallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080092
93 public CellLayout(Context context) {
94 this(context, null);
95 }
96
97 public CellLayout(Context context, AttributeSet attrs) {
98 this(context, attrs, 0);
99 }
100
101 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
102 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700103
104 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
105 // the user where a dragged item will land when dropped.
106 setWillNotDraw(false);
107 mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green);
108 mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red);
109
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800110 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
111
112 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
113 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700114
115 mLongAxisStartPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800116 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700117 mLongAxisEndPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800118 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
119 mShortAxisStartPadding =
120 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700121 mShortAxisEndPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800122 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700123
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800124 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
125 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
126
127 a.recycle();
128
129 setAlwaysDrawnWithCacheEnabled(false);
130
Romain Guy84f296c2009-11-04 15:00:44 -0800131 mWallpaperManager = WallpaperManager.getInstance(getContext());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800132 }
133
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700134 @Override
Romain Guya6abce82009-11-10 02:54:41 -0800135 public void dispatchDraw(Canvas canvas) {
136 super.dispatchDraw(canvas);
137 }
138
139 @Override
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700140 protected void onDraw(Canvas canvas) {
141 if (!mDragRect.isEmpty()) {
142 mDragRectDrawable.setBounds(
143 (int)mDragRect.left,
144 (int)mDragRect.top,
145 (int)mDragRect.right,
146 (int)mDragRect.bottom);
147 mDragRectDrawable.draw(canvas);
148 }
149 }
150
151 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700152 public void cancelLongPress() {
153 super.cancelLongPress();
154
155 // Cancel long press for all children
156 final int count = getChildCount();
157 for (int i = 0; i < count; i++) {
158 final View child = getChildAt(i);
159 child.cancelLongPress();
160 }
161 }
162
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800163 int getCountX() {
164 return mPortrait ? mShortAxisCells : mLongAxisCells;
165 }
166
167 int getCountY() {
168 return mPortrait ? mLongAxisCells : mShortAxisCells;
169 }
170
Winson Chungaafa03c2010-06-11 17:34:16 -0700171 // Takes canonical layout parameters
172 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) {
173 final LayoutParams lp = params;
174
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800175 // Generate an id for each view, this assumes we have at most 256x256 cells
176 // per workspace screen
Winson Chungaafa03c2010-06-11 17:34:16 -0700177 if (lp.cellX >= 0 && lp.cellX <= getCountX() - 1 && lp.cellY >= 0 && lp.cellY <= getCountY() - 1) {
178 // If the horizontal or vertical span is set to -1, it is taken to
179 // mean that it spans the extent of the CellLayout
180 if (lp.cellHSpan < 0) lp.cellHSpan = getCountX();
181 if (lp.cellVSpan < 0) lp.cellVSpan = getCountY();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800182
Winson Chungaafa03c2010-06-11 17:34:16 -0700183 child.setId(childId);
184
185 addView(child, index, lp);
186 return true;
187 }
188 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800189 }
190
191 @Override
192 public void requestChildFocus(View child, View focused) {
193 super.requestChildFocus(child, focused);
194 if (child != null) {
195 Rect r = new Rect();
196 child.getDrawingRect(r);
197 requestRectangleOnScreen(r);
198 }
199 }
200
201 @Override
202 protected void onAttachedToWindow() {
203 super.onAttachedToWindow();
204 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
205 }
206
Michael Jurkaaf442092010-06-10 17:01:57 -0700207 public void setTagToCellInfoForPoint(int touchX, int touchY) {
208 final CellInfo cellInfo = mCellInfo;
209 final Rect frame = mRect;
210 final int x = touchX + mScrollX;
211 final int y = touchY + mScrollY;
212 final int count = getChildCount();
213
214 boolean found = false;
215 for (int i = count - 1; i >= 0; i--) {
216 final View child = getChildAt(i);
217
218 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
219 child.getHitRect(frame);
220 if (frame.contains(x, y)) {
221 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
222 cellInfo.cell = child;
223 cellInfo.cellX = lp.cellX;
224 cellInfo.cellY = lp.cellY;
225 cellInfo.spanX = lp.cellHSpan;
226 cellInfo.spanY = lp.cellVSpan;
227 cellInfo.valid = true;
228 found = true;
229 mDirtyTag = false;
230 break;
231 }
232 }
233 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700234
Michael Jurkaaf442092010-06-10 17:01:57 -0700235 mLastDownOnOccupiedCell = found;
236
237 if (!found) {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700238 final int cellXY[] = mTmpCellXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700239 pointToCellExact(x, y, cellXY);
240
241 final boolean portrait = mPortrait;
242 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
243 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
244
245 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700246 findOccupiedCells(xCount, yCount, occupied, null, true);
Michael Jurkaaf442092010-06-10 17:01:57 -0700247
248 cellInfo.cell = null;
249 cellInfo.cellX = cellXY[0];
250 cellInfo.cellY = cellXY[1];
251 cellInfo.spanX = 1;
252 cellInfo.spanY = 1;
253 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
254 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
255
256 // Instead of finding the interesting vacant cells here, wait until a
257 // caller invokes getTag() to retrieve the result. Finding the vacant
258 // cells is a bit expensive and can generate many new objects, it's
259 // therefore better to defer it until we know we actually need it.
260
261 mDirtyTag = true;
262 }
263 setTag(cellInfo);
264 }
265
Winson Chungaafa03c2010-06-11 17:34:16 -0700266
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800267 @Override
268 public boolean onInterceptTouchEvent(MotionEvent ev) {
269 final int action = ev.getAction();
270 final CellInfo cellInfo = mCellInfo;
271
272 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700273 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800274 } else if (action == MotionEvent.ACTION_UP) {
275 cellInfo.cell = null;
276 cellInfo.cellX = -1;
277 cellInfo.cellY = -1;
278 cellInfo.spanX = 0;
279 cellInfo.spanY = 0;
280 cellInfo.valid = false;
281 mDirtyTag = false;
282 setTag(cellInfo);
283 }
284
285 return false;
286 }
287
288 @Override
289 public CellInfo getTag() {
290 final CellInfo info = (CellInfo) super.getTag();
291 if (mDirtyTag && info.valid) {
292 final boolean portrait = mPortrait;
293 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
294 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
295
296 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700297 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800298
299 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
300
301 mDirtyTag = false;
302 }
303 return info;
304 }
305
Winson Chungaafa03c2010-06-11 17:34:16 -0700306 private static void findIntersectingVacantCells(CellInfo cellInfo, int x,
307 int y, int xCount, int yCount, boolean[][] occupied) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800308
309 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
310 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
311 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
312 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
313 cellInfo.clearVacantCells();
314
315 if (occupied[x][y]) {
316 return;
317 }
318
319 cellInfo.current.set(x, y, x, y);
320
321 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
322 }
323
324 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
325 CellInfo cellInfo) {
326
327 addVacantCell(current, cellInfo);
328
329 if (current.left > 0) {
330 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
331 current.left--;
332 findVacantCell(current, xCount, yCount, occupied, cellInfo);
333 current.left++;
334 }
335 }
336
337 if (current.right < xCount - 1) {
338 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
339 current.right++;
340 findVacantCell(current, xCount, yCount, occupied, cellInfo);
341 current.right--;
342 }
343 }
344
345 if (current.top > 0) {
346 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
347 current.top--;
348 findVacantCell(current, xCount, yCount, occupied, cellInfo);
349 current.top++;
350 }
351 }
352
353 if (current.bottom < yCount - 1) {
354 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
355 current.bottom++;
356 findVacantCell(current, xCount, yCount, occupied, cellInfo);
357 current.bottom--;
358 }
359 }
360 }
361
362 private static void addVacantCell(Rect current, CellInfo cellInfo) {
363 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
364 cell.cellX = current.left;
365 cell.cellY = current.top;
366 cell.spanX = current.right - current.left + 1;
367 cell.spanY = current.bottom - current.top + 1;
368 if (cell.spanX > cellInfo.maxVacantSpanX) {
369 cellInfo.maxVacantSpanX = cell.spanX;
370 cellInfo.maxVacantSpanXSpanY = cell.spanY;
371 }
372 if (cell.spanY > cellInfo.maxVacantSpanY) {
373 cellInfo.maxVacantSpanY = cell.spanY;
374 cellInfo.maxVacantSpanYSpanX = cell.spanX;
375 }
376 cellInfo.vacantCells.add(cell);
377 }
378
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700379 /**
380 * Check if the column 'x' is empty from rows 'top' to 'bottom', inclusive.
381 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800382 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
383 for (int y = top; y <= bottom; y++) {
384 if (occupied[x][y]) {
385 return false;
386 }
387 }
388 return true;
389 }
390
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700391 /**
392 * Check if the row 'y' is empty from columns 'left' to 'right', inclusive.
393 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800394 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
395 for (int x = left; x <= right; x++) {
396 if (occupied[x][y]) {
397 return false;
398 }
399 }
400 return true;
401 }
402
Jeff Sharkey70864282009-04-07 21:08:40 -0700403 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800404 final boolean portrait = mPortrait;
405 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
406 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
407
408 boolean[][] occupied = mOccupied;
409
410 if (occupiedCells != null) {
411 for (int y = 0; y < yCount; y++) {
412 for (int x = 0; x < xCount; x++) {
413 occupied[x][y] = occupiedCells[y * xCount + x];
414 }
415 }
416 } else {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700417 findOccupiedCells(xCount, yCount, occupied, ignoreView, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800418 }
419
420 CellInfo cellInfo = new CellInfo();
421
422 cellInfo.cellX = -1;
423 cellInfo.cellY = -1;
424 cellInfo.spanY = 0;
425 cellInfo.spanX = 0;
426 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
427 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
428 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
429 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
430 cellInfo.screen = mCellInfo.screen;
431
432 Rect current = cellInfo.current;
433
434 for (int x = 0; x < xCount; x++) {
435 for (int y = 0; y < yCount; y++) {
436 if (!occupied[x][y]) {
437 current.set(x, y, x, y);
438 findVacantCell(current, xCount, yCount, occupied, cellInfo);
439 occupied[x][y] = true;
440 }
441 }
442 }
443
444 cellInfo.valid = cellInfo.vacantCells.size() > 0;
445
446 // Assume the caller will perform their own cell searching, otherwise we
447 // risk causing an unnecessary rebuild after findCellForSpan()
Winson Chungaafa03c2010-06-11 17:34:16 -0700448
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800449 return cellInfo;
450 }
451
452 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700453 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800454 * @param x X coordinate of the point
455 * @param y Y coordinate of the point
456 * @param result Array of 2 ints to hold the x and y coordinate of the cell
457 */
458 void pointToCellExact(int x, int y, int[] result) {
459 final boolean portrait = mPortrait;
Winson Chungaafa03c2010-06-11 17:34:16 -0700460
461 final int hStartPadding = getLeftPadding();
462 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800463
464 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
465 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
466
467 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
468 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
469
470 if (result[0] < 0) result[0] = 0;
471 if (result[0] >= xAxis) result[0] = xAxis - 1;
472 if (result[1] < 0) result[1] = 0;
473 if (result[1] >= yAxis) result[1] = yAxis - 1;
474 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700475
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800476 /**
477 * Given a point, return the cell that most closely encloses that point
478 * @param x X coordinate of the point
479 * @param y Y coordinate of the point
480 * @param result Array of 2 ints to hold the x and y coordinate of the cell
481 */
482 void pointToCellRounded(int x, int y, int[] result) {
483 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
484 }
485
486 /**
487 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700488 *
489 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800490 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700491 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800492 * @param result Array of 2 ints to hold the x and y coordinate of the point
493 */
494 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700495 final int hStartPadding = getLeftPadding();
496 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800497
498 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
499 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
500 }
501
Romain Guy84f296c2009-11-04 15:00:44 -0800502 int getCellWidth() {
503 return mCellWidth;
504 }
505
506 int getCellHeight() {
507 return mCellHeight;
508 }
509
Romain Guy1a304a12009-11-10 00:02:32 -0800510 int getLeftPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700511 return mLeftPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800512 }
513
514 int getTopPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700515 return mTopPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800516 }
517
518 int getRightPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700519 return mRightPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800520 }
521
522 int getBottomPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700523 return mBottomPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800524 }
525
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800526 @Override
527 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
528 // TODO: currently ignoring padding
Winson Chungaafa03c2010-06-11 17:34:16 -0700529
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800530 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700531 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
532
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800533 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
534 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700535
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800536 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
537 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
538 }
539
540 final int shortAxisCells = mShortAxisCells;
541 final int longAxisCells = mLongAxisCells;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800542 final int cellWidth = mCellWidth;
543 final int cellHeight = mCellHeight;
544
Winson Chungaafa03c2010-06-11 17:34:16 -0700545 boolean portrait = heightSpecSize > widthSpecSize;
546 if (portrait != mPortrait || mOccupied == null) {
547 if (portrait) {
548 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
549 } else {
550 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
551 }
552 }
553 mPortrait = portrait;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800554
555 int numShortGaps = shortAxisCells - 1;
556 int numLongGaps = longAxisCells - 1;
557
558 if (mPortrait) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700559 int vSpaceLeft = heightSpecSize - mLongAxisStartPadding
560 - mLongAxisEndPadding - (cellHeight * longAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800561 mHeightGap = vSpaceLeft / numLongGaps;
562
Winson Chungaafa03c2010-06-11 17:34:16 -0700563 int hSpaceLeft = widthSpecSize - mShortAxisStartPadding
564 - mShortAxisEndPadding - (cellWidth * shortAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800565 if (numShortGaps > 0) {
566 mWidthGap = hSpaceLeft / numShortGaps;
567 } else {
568 mWidthGap = 0;
569 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700570
571 if (LauncherApplication.isInPlaceRotationEnabled()) {
572 mWidthGap = mHeightGap = Math.min(mHeightGap, mWidthGap);
573 mLeftPadding = mRightPadding = (widthSpecSize - cellWidth
574 * shortAxisCells - (shortAxisCells - 1) * mWidthGap) / 2;
575 mTopPadding = mBottomPadding = (heightSpecSize - cellHeight
576 * longAxisCells - (longAxisCells - 1) * mHeightGap) / 2;
577 } else {
578 mLeftPadding = mShortAxisStartPadding;
579 mRightPadding = mShortAxisEndPadding;
580 mTopPadding = mLongAxisStartPadding;
581 mBottomPadding = mLongAxisEndPadding;
582 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800583 } else {
Winson Chungaafa03c2010-06-11 17:34:16 -0700584 int hSpaceLeft = widthSpecSize - mLongAxisStartPadding
585 - mLongAxisEndPadding - (cellWidth * longAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800586 mWidthGap = hSpaceLeft / numLongGaps;
587
Winson Chungaafa03c2010-06-11 17:34:16 -0700588 int vSpaceLeft = heightSpecSize - mShortAxisStartPadding
589 - mShortAxisEndPadding - (cellHeight * shortAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800590 if (numShortGaps > 0) {
591 mHeightGap = vSpaceLeft / numShortGaps;
592 } else {
593 mHeightGap = 0;
594 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700595
596 if (LauncherApplication.isScreenXLarge()) {
597 mWidthGap = mHeightGap = Math.min(mHeightGap, mWidthGap);
598 mLeftPadding = mRightPadding = (widthSpecSize - cellWidth
599 * longAxisCells - (longAxisCells - 1) * mWidthGap) / 2 ;
600 mTopPadding = mBottomPadding = (heightSpecSize - cellHeight
601 * shortAxisCells - (shortAxisCells - 1) * mHeightGap) / 2;
602 } else {
603 mLeftPadding = mLongAxisStartPadding;
604 mRightPadding = mLongAxisEndPadding;
605 mTopPadding = mShortAxisStartPadding;
606 mBottomPadding = mShortAxisEndPadding;
607 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800608 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800609 int count = getChildCount();
610
611 for (int i = 0; i < count; i++) {
612 View child = getChildAt(i);
613 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Winson Chungaafa03c2010-06-11 17:34:16 -0700614 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
615 mLeftPadding, mTopPadding);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800616
Winson Chungaafa03c2010-06-11 17:34:16 -0700617 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
618 MeasureSpec.EXACTLY);
619 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
620 MeasureSpec.EXACTLY);
621
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800622 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
623 }
624
625 setMeasuredDimension(widthSpecSize, heightSpecSize);
626 }
627
628 @Override
629 protected void onLayout(boolean changed, int l, int t, int r, int b) {
630 int count = getChildCount();
631
632 for (int i = 0; i < count; i++) {
633 View child = getChildAt(i);
634 if (child.getVisibility() != GONE) {
635
636 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
637
638 int childLeft = lp.x;
639 int childTop = lp.y;
640 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
Romain Guy84f296c2009-11-04 15:00:44 -0800641
642 if (lp.dropped) {
643 lp.dropped = false;
644
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700645 final int[] cellXY = mTmpCellXY;
Romain Guy06762ab2010-01-25 16:51:08 -0800646 getLocationOnScreen(cellXY);
Romain Guy84f296c2009-11-04 15:00:44 -0800647 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
Romain Guy06762ab2010-01-25 16:51:08 -0800648 cellXY[0] + childLeft + lp.width / 2,
649 cellXY[1] + childTop + lp.height / 2, 0, null);
Romain Guy84f296c2009-11-04 15:00:44 -0800650 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800651 }
652 }
653 }
654
655 @Override
656 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
657 final int count = getChildCount();
658 for (int i = 0; i < count; i++) {
659 final View view = getChildAt(i);
660 view.setDrawingCacheEnabled(enabled);
661 // Update the drawing caches
Adam Powellfefa0ce2010-05-03 10:23:50 -0700662 view.buildDrawingCache(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800663 }
664 }
665
666 @Override
667 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
668 super.setChildrenDrawnWithCacheEnabled(enabled);
669 }
670
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700671 private boolean isVacant(int originX, int originY, int spanX, int spanY) {
672 for (int i = 0; i < spanY; i++) {
673 if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) {
674 return false;
675 }
676 }
677 return true;
678 }
679
Patrick Dubroy440c3602010-07-13 17:50:32 -0700680 public View getChildAt(int x, int y) {
681 final int count = getChildCount();
682 for (int i = 0; i < count; i++) {
683 View child = getChildAt(i);
684 LayoutParams lp = (LayoutParams) child.getLayoutParams();
685
686 if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
687 (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
688 return child;
689 }
690 }
691 return null;
692 }
693
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700694 /**
695 * Estimate where the top left cell of the dragged item will land if it is dropped.
696 *
697 * @param originX The X value of the top left corner of the item
698 * @param originY The Y value of the top left corner of the item
699 * @param spanX The number of horizontal cells that the item spans
700 * @param spanY The number of vertical cells that the item spans
701 * @param result The estimated drop cell X and Y.
702 */
703 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
704 final int countX = getCountX();
705 final int countY = getCountY();
706
Patrick Dubroy440c3602010-07-13 17:50:32 -0700707 pointToCellRounded(originX + (mCellWidth / 2), originY + (mCellHeight / 2), result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700708
709 // If the item isn't fully on this screen, snap to the edges
710 int rightOverhang = result[0] + spanX - countX;
711 if (rightOverhang > 0) {
712 result[0] -= rightOverhang; // Snap to right
713 }
714 result[0] = Math.max(0, result[0]); // Snap to left
715 int bottomOverhang = result[1] + spanY - countY;
716 if (bottomOverhang > 0) {
717 result[1] -= bottomOverhang; // Snap to bottom
718 }
719 result[1] = Math.max(0, result[1]); // Snap to top
720 }
721
722 void visualizeDropLocation(View view, int originX, int originY, int spanX, int spanY) {
723 final int[] originCell = mDragCell;
724 final int[] cellXY = mTmpCellXY;
725 estimateDropCell(originX, originY, spanX, spanY, cellXY);
726
727 // Only recalculate the bounding rect when necessary
728 if (!Arrays.equals(cellXY, originCell)) {
729 originCell[0] = cellXY[0];
730 originCell[1] = cellXY[1];
731
732 // Find the top left corner of the rect the object will occupy
733 final int[] topLeft = mTmpCellXY;
734 cellToPoint(originCell[0], originCell[1], topLeft);
735 final int left = topLeft[0];
736 final int top = topLeft[1];
737
738 // Now find the bottom right
739 final int[] bottomRight = mTmpCellXY;
740 cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight);
741 bottomRight[0] += mCellWidth;
742 bottomRight[1] += mCellHeight;
743
744 final int countX = mPortrait ? mShortAxisCells : mLongAxisCells;
745 final int countY = mPortrait ? mLongAxisCells : mShortAxisCells;
746 // TODO: It's not necessary to do this every time, but it's not especially expensive
747 findOccupiedCells(countX, countY, mOccupied, view, false);
748
749 boolean vacant = isVacant(originCell[0], originCell[1], spanX, spanY);
750 mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable;
751
752 // mDragRect will be rendered in onDraw()
753 mDragRect.set(left, top, bottomRight[0], bottomRight[1]);
754 invalidate();
755 }
756 }
757
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800758 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700759 * Find a vacant area that will fit the given bounds nearest the requested
760 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -0700761 *
Romain Guy51afc022009-05-04 18:03:43 -0700762 * @param pixelX The X location at which you want to search for a vacant area.
763 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -0700764 * @param spanX Horizontal span of the object.
765 * @param spanY Vertical span of the object.
766 * @param vacantCells Pre-computed set of vacant cells to search.
767 * @param recycle Previously returned value to possibly recycle.
768 * @return The X, Y cell of a vacant area that can contain this object,
769 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800770 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700771 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
772 CellInfo vacantCells, int[] recycle) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700773
Jeff Sharkey70864282009-04-07 21:08:40 -0700774 // Keep track of best-scoring drop area
775 final int[] bestXY = recycle != null ? recycle : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -0700776 double bestDistance = Double.MAX_VALUE;
Winson Chungaafa03c2010-06-11 17:34:16 -0700777
Jeff Sharkey70864282009-04-07 21:08:40 -0700778 // Bail early if vacant cells aren't valid
779 if (!vacantCells.valid) {
780 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800781 }
782
Jeff Sharkey70864282009-04-07 21:08:40 -0700783 // Look across all vacant cells for best fit
784 final int size = vacantCells.vacantCells.size();
785 for (int i = 0; i < size; i++) {
786 final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
Winson Chungaafa03c2010-06-11 17:34:16 -0700787
Jeff Sharkey70864282009-04-07 21:08:40 -0700788 // Reject if vacant cell isn't our exact size
789 if (cell.spanX != spanX || cell.spanY != spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800790 continue;
791 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700792
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700793 // Score is distance from requested pixel to the top left of each cell
794 final int[] cellXY = mTmpCellXY;
Jeff Sharkey70864282009-04-07 21:08:40 -0700795 cellToPoint(cell.cellX, cell.cellY, cellXY);
Winson Chungaafa03c2010-06-11 17:34:16 -0700796
797 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
798 + Math.pow(cellXY[1] - pixelY, 2));
Jeff Sharkey70864282009-04-07 21:08:40 -0700799 if (distance <= bestDistance) {
800 bestDistance = distance;
801 bestXY[0] = cell.cellX;
802 bestXY[1] = cell.cellY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800803 }
804 }
805
Winson Chungaafa03c2010-06-11 17:34:16 -0700806 // Return null if no suitable location found
Jeff Sharkey70864282009-04-07 21:08:40 -0700807 if (bestDistance < Double.MAX_VALUE) {
808 return bestXY;
809 } else {
810 return null;
811 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800812 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700813
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800814 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700815 * Called when a drag and drop operation has finished (successfully or not).
816 */
817 void onDragComplete() {
818 // Invalidate the drag data
819 mDragCell[0] = -1;
820 mDragCell[1] = -1;
821
822 mDragRect.setEmpty();
823 invalidate();
824 }
825
826 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700827 * Mark a child as having been dropped.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800828 *
829 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800830 */
Winson Chungaafa03c2010-06-11 17:34:16 -0700831 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -0700832 if (child != null) {
833 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guyd94533d2009-08-17 10:01:15 -0700834 lp.isDragging = false;
Romain Guy84f296c2009-11-04 15:00:44 -0800835 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -0700836 mDragRect.setEmpty();
837 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -0700838 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700839 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800840 }
841
842 void onDropAborted(View child) {
843 if (child != null) {
844 ((LayoutParams) child.getLayoutParams()).isDragging = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800845 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700846 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800847 }
848
849 /**
850 * Start dragging the specified child
Winson Chungaafa03c2010-06-11 17:34:16 -0700851 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800852 * @param child The child that is being dragged
853 */
854 void onDragChild(View child) {
855 LayoutParams lp = (LayoutParams) child.getLayoutParams();
856 lp.isDragging = true;
857 mDragRect.setEmpty();
858 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700859
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800860 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800861 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -0700862 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800863 * @param cellX X coordinate of upper left corner expressed as a cell position
864 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -0700865 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800866 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700867 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868 */
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700869 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
870 final boolean portrait = mPortrait;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800871 final int cellWidth = mCellWidth;
872 final int cellHeight = mCellHeight;
873 final int widthGap = mWidthGap;
874 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -0700875
876 final int hStartPadding = getLeftPadding();
877 final int vStartPadding = getTopPadding();
878
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800879 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
880 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
881
882 int x = hStartPadding + cellX * (cellWidth + widthGap);
883 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -0700884
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700885 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800886 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700887
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800888 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700889 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800890 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -0700891 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800892 * @param width Width in pixels
893 * @param height Height in pixels
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800894 */
895 public int[] rectToCell(int width, int height) {
896 // Always assume we're working with the smallest span to make sure we
897 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -0400898 final Resources resources = getResources();
899 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
900 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800901 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -0400902
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800903 // Always round up to next largest cell
904 int spanX = (width + smallerSize) / smallerSize;
905 int spanY = (height + smallerSize) / smallerSize;
Joe Onorato79e56262009-09-21 15:23:04 -0400906
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800907 return new int[] { spanX, spanY };
908 }
909
910 /**
911 * Find the first vacant cell, if there is one.
912 *
913 * @param vacant Holds the x and y coordinate of the vacant cell
914 * @param spanX Horizontal cell span.
915 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -0700916 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800917 * @return True if a vacant cell was found
918 */
919 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
920 final boolean portrait = mPortrait;
921 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
922 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
923 final boolean[][] occupied = mOccupied;
924
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700925 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800926
927 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
928 }
929
930 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
931 int xCount, int yCount, boolean[][] occupied) {
932
933 for (int x = 0; x < xCount; x++) {
934 for (int y = 0; y < yCount; y++) {
935 boolean available = !occupied[x][y];
936out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
937 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
938 available = available && !occupied[i][j];
939 if (!available) break out;
940 }
941 }
942
943 if (available) {
944 vacant[0] = x;
945 vacant[1] = y;
946 return true;
947 }
948 }
949 }
950
951 return false;
952 }
953
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700954 /**
955 * Update the array of occupied cells (mOccupied), and return a flattened copy of the array.
956 */
957 boolean[] getOccupiedCellsFlattened() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800958 final boolean portrait = mPortrait;
959 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
960 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
961 final boolean[][] occupied = mOccupied;
962
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700963 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800964
965 final boolean[] flat = new boolean[xCount * yCount];
966 for (int y = 0; y < yCount; y++) {
967 for (int x = 0; x < xCount; x++) {
968 flat[y * xCount + x] = occupied[x][y];
969 }
970 }
971
972 return flat;
973 }
974
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700975 /**
976 * Update the array of occupied cells.
977 * @param ignoreView If non-null, the space occupied by this View is treated as vacant
978 * @param ignoreFolders If true, a cell occupied by a Folder is treated as vacant
979 */
980 private void findOccupiedCells(
981 int xCount, int yCount, boolean[][] occupied, View ignoreView, boolean ignoreFolders) {
982
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800983 for (int x = 0; x < xCount; x++) {
984 for (int y = 0; y < yCount; y++) {
985 occupied[x][y] = false;
986 }
987 }
988
989 int count = getChildCount();
990 for (int i = 0; i < count; i++) {
991 View child = getChildAt(i);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700992 if ((ignoreFolders && child instanceof Folder) || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800993 continue;
994 }
995 LayoutParams lp = (LayoutParams) child.getLayoutParams();
996
997 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
998 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
999 occupied[x][y] = true;
1000 }
1001 }
1002 }
1003 }
1004
1005 @Override
1006 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1007 return new CellLayout.LayoutParams(getContext(), attrs);
1008 }
1009
1010 @Override
1011 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1012 return p instanceof CellLayout.LayoutParams;
1013 }
1014
1015 @Override
1016 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1017 return new CellLayout.LayoutParams(p);
1018 }
1019
Winson Chungaafa03c2010-06-11 17:34:16 -07001020 public static class CellLayoutAnimationController extends LayoutAnimationController {
1021 public CellLayoutAnimationController(Animation animation, float delay) {
1022 super(animation, delay);
1023 }
1024
1025 @Override
1026 protected long getDelayForView(View view) {
1027 return (int) (Math.random() * 150);
1028 }
1029 }
1030
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001031 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1032 /**
1033 * Horizontal location of the item in the grid.
1034 */
1035 @ViewDebug.ExportedProperty
1036 public int cellX;
1037
1038 /**
1039 * Vertical location of the item in the grid.
1040 */
1041 @ViewDebug.ExportedProperty
1042 public int cellY;
1043
1044 /**
1045 * Number of cells spanned horizontally by the item.
1046 */
1047 @ViewDebug.ExportedProperty
1048 public int cellHSpan;
1049
1050 /**
1051 * Number of cells spanned vertically by the item.
1052 */
1053 @ViewDebug.ExportedProperty
1054 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07001055
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001056 /**
1057 * Is this item currently being dragged
1058 */
1059 public boolean isDragging;
1060
1061 // X coordinate of the view in the layout.
1062 @ViewDebug.ExportedProperty
1063 int x;
1064 // Y coordinate of the view in the layout.
1065 @ViewDebug.ExportedProperty
1066 int y;
1067
Romain Guy84f296c2009-11-04 15:00:44 -08001068 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07001069
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001070 public LayoutParams(Context c, AttributeSet attrs) {
1071 super(c, attrs);
1072 cellHSpan = 1;
1073 cellVSpan = 1;
1074 }
1075
1076 public LayoutParams(ViewGroup.LayoutParams source) {
1077 super(source);
1078 cellHSpan = 1;
1079 cellVSpan = 1;
1080 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001081
1082 public LayoutParams(LayoutParams source) {
1083 super(source);
1084 this.cellX = source.cellX;
1085 this.cellY = source.cellY;
1086 this.cellHSpan = source.cellHSpan;
1087 this.cellVSpan = source.cellVSpan;
1088 }
1089
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001090 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08001091 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001092 this.cellX = cellX;
1093 this.cellY = cellY;
1094 this.cellHSpan = cellHSpan;
1095 this.cellVSpan = cellVSpan;
1096 }
1097
1098 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
1099 int hStartPadding, int vStartPadding) {
Winson Chungaafa03c2010-06-11 17:34:16 -07001100
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001101 final int myCellHSpan = cellHSpan;
1102 final int myCellVSpan = cellVSpan;
1103 final int myCellX = cellX;
1104 final int myCellY = cellY;
Winson Chungaafa03c2010-06-11 17:34:16 -07001105
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001106 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
1107 leftMargin - rightMargin;
1108 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
1109 topMargin - bottomMargin;
1110
1111 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
1112 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
1113 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001114
1115 public String toString() {
1116 return "(" + this.cellX + ", " + this.cellY + ")";
1117 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001118 }
1119
1120 static final class CellInfo implements ContextMenu.ContextMenuInfo {
1121 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07001122 * See View.AttachInfo.InvalidateInfo for futher explanations about the
1123 * recycling mechanism. In this case, we recycle the vacant cells
1124 * instances because up to several hundreds can be instanciated when the
1125 * user long presses an empty cell.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001126 */
1127 static final class VacantCell {
1128 int cellX;
1129 int cellY;
1130 int spanX;
1131 int spanY;
1132
1133 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
1134 // like a reasonable compromise given the size of a VacantCell and
1135 // the fact that the user is not likely to touch an empty 4x4 grid
Winson Chungaafa03c2010-06-11 17:34:16 -07001136 // very often
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001137 private static final int POOL_LIMIT = 100;
1138 private static final Object sLock = new Object();
1139
1140 private static int sAcquiredCount = 0;
1141 private static VacantCell sRoot;
1142
1143 private VacantCell next;
1144
1145 static VacantCell acquire() {
1146 synchronized (sLock) {
1147 if (sRoot == null) {
1148 return new VacantCell();
1149 }
1150
1151 VacantCell info = sRoot;
1152 sRoot = info.next;
1153 sAcquiredCount--;
1154
1155 return info;
1156 }
1157 }
1158
1159 void release() {
1160 synchronized (sLock) {
1161 if (sAcquiredCount < POOL_LIMIT) {
1162 sAcquiredCount++;
1163 next = sRoot;
1164 sRoot = this;
1165 }
1166 }
1167 }
1168
1169 @Override
1170 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001171 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX="
1172 + spanX + ", spanY=" + spanY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001173 }
1174 }
1175
1176 View cell;
1177 int cellX;
1178 int cellY;
1179 int spanX;
1180 int spanY;
1181 int screen;
1182 boolean valid;
1183
1184 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
1185 int maxVacantSpanX;
1186 int maxVacantSpanXSpanY;
1187 int maxVacantSpanY;
1188 int maxVacantSpanYSpanX;
1189 final Rect current = new Rect();
1190
Romain Guy4c58c482009-05-12 17:35:41 -07001191 void clearVacantCells() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001192 final ArrayList<VacantCell> list = vacantCells;
1193 final int count = list.size();
1194
Winson Chungaafa03c2010-06-11 17:34:16 -07001195 for (int i = 0; i < count; i++) {
1196 list.get(i).release();
1197 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001198
1199 list.clear();
1200 }
1201
1202 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
1203 if (cellX < 0 || cellY < 0) {
1204 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
1205 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
1206 clearVacantCells();
1207 return;
1208 }
1209
1210 final boolean[][] unflattened = new boolean[xCount][yCount];
1211 for (int y = 0; y < yCount; y++) {
1212 for (int x = 0; x < xCount; x++) {
1213 unflattened[x][y] = occupied[y * xCount + x];
1214 }
1215 }
1216 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
1217 }
1218
1219 /**
1220 * This method can be called only once! Calling #findVacantCellsFromOccupied will
1221 * restore the ability to call this method.
1222 *
1223 * Finds the upper-left coordinate of the first rectangle in the grid that can
1224 * hold a cell of the specified dimensions.
1225 *
1226 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1227 * can be found.
1228 * @param spanX The horizontal span of the cell we want to find.
1229 * @param spanY The vertical span of the cell we want to find.
1230 *
1231 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1232 */
1233 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Romain Guy4c58c482009-05-12 17:35:41 -07001234 return findCellForSpan(cellXY, spanX, spanY, true);
1235 }
1236
1237 boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001238 final ArrayList<VacantCell> list = vacantCells;
1239 final int count = list.size();
1240
1241 boolean found = false;
1242
Michael Jurka0e260592010-06-30 17:07:39 -07001243 // return the span represented by the CellInfo only there is no view there
1244 // (this.cell == null) and there is enough space
1245 if (this.cell == null && this.spanX >= spanX && this.spanY >= spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001246 cellXY[0] = cellX;
1247 cellXY[1] = cellY;
1248 found = true;
1249 }
1250
1251 // Look for an exact match first
1252 for (int i = 0; i < count; i++) {
1253 VacantCell cell = list.get(i);
1254 if (cell.spanX == spanX && cell.spanY == spanY) {
1255 cellXY[0] = cell.cellX;
1256 cellXY[1] = cell.cellY;
1257 found = true;
1258 break;
1259 }
1260 }
1261
1262 // Look for the first cell large enough
1263 for (int i = 0; i < count; i++) {
1264 VacantCell cell = list.get(i);
1265 if (cell.spanX >= spanX && cell.spanY >= spanY) {
1266 cellXY[0] = cell.cellX;
1267 cellXY[1] = cell.cellY;
1268 found = true;
1269 break;
1270 }
1271 }
1272
Winson Chungaafa03c2010-06-11 17:34:16 -07001273 if (clear) {
1274 clearVacantCells();
1275 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001276
1277 return found;
1278 }
1279
1280 @Override
1281 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001282 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
1283 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001284 }
1285 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001286
1287 public boolean lastDownOnOccupiedCell() {
1288 return mLastDownOnOccupiedCell;
1289 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001290}