blob: 05fc20c443d7eb90be8f71b99f0e2c4eff1dc2c2 [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
19import android.content.Context;
20import android.content.res.TypedArray;
Joe Onorato79e56262009-09-21 15:23:04 -040021import android.content.res.Resources;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080022import android.graphics.Rect;
23import android.graphics.RectF;
24import android.util.AttributeSet;
25import android.view.ContextMenu;
26import android.view.MotionEvent;
27import android.view.View;
28import android.view.ViewDebug;
29import android.view.ViewGroup;
Romain Guy84f296c2009-11-04 15:00:44 -080030import android.app.WallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080031
32import java.util.ArrayList;
33
34public class CellLayout extends ViewGroup {
35 private boolean mPortrait;
36
37 private int mCellWidth;
38 private int mCellHeight;
39
40 private int mLongAxisStartPadding;
41 private int mLongAxisEndPadding;
42
43 private int mShortAxisStartPadding;
44 private int mShortAxisEndPadding;
45
46 private int mShortAxisCells;
47 private int mLongAxisCells;
48
49 private int mWidthGap;
50 private int mHeightGap;
51
52 private final Rect mRect = new Rect();
53 private final CellInfo mCellInfo = new CellInfo();
54
55 int[] mCellXY = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -080056 boolean[][] mOccupied;
57
58 private RectF mDragRect = new RectF();
59
60 private boolean mDirtyTag;
Mike Cleronf8bbd342009-10-23 16:15:16 -070061 private boolean mLastDownOnOccupiedCell = false;
Romain Guy84f296c2009-11-04 15:00:44 -080062
63 private final WallpaperManager mWallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080064
65 public CellLayout(Context context) {
66 this(context, null);
67 }
68
69 public CellLayout(Context context, AttributeSet attrs) {
70 this(context, attrs, 0);
71 }
72
73 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
74 super(context, attrs, defStyle);
75 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
76
77 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
78 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
79
80 mLongAxisStartPadding =
81 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
82 mLongAxisEndPadding =
83 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
84 mShortAxisStartPadding =
85 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
86 mShortAxisEndPadding =
87 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
88
89 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
90 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
91
92 a.recycle();
93
94 setAlwaysDrawnWithCacheEnabled(false);
95
96 if (mOccupied == null) {
97 if (mPortrait) {
98 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
99 } else {
100 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
101 }
102 }
Romain Guy84f296c2009-11-04 15:00:44 -0800103
104 mWallpaperManager = WallpaperManager.getInstance(getContext());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800105 }
106
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700107 @Override
108 public void cancelLongPress() {
109 super.cancelLongPress();
110
111 // Cancel long press for all children
112 final int count = getChildCount();
113 for (int i = 0; i < count; i++) {
114 final View child = getChildAt(i);
115 child.cancelLongPress();
116 }
117 }
118
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800119 int getCountX() {
120 return mPortrait ? mShortAxisCells : mLongAxisCells;
121 }
122
123 int getCountY() {
124 return mPortrait ? mLongAxisCells : mShortAxisCells;
125 }
126
127 @Override
128 public void addView(View child, int index, ViewGroup.LayoutParams params) {
129 // Generate an id for each view, this assumes we have at most 256x256 cells
130 // per workspace screen
131 final LayoutParams cellParams = (LayoutParams) params;
Romain Guyfcb9e712009-10-02 16:06:52 -0700132 cellParams.regenerateId = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800133
134 super.addView(child, index, params);
135 }
136
137 @Override
138 public void requestChildFocus(View child, View focused) {
139 super.requestChildFocus(child, focused);
140 if (child != null) {
141 Rect r = new Rect();
142 child.getDrawingRect(r);
143 requestRectangleOnScreen(r);
144 }
145 }
146
147 @Override
148 protected void onAttachedToWindow() {
149 super.onAttachedToWindow();
150 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
151 }
152
153 @Override
154 public boolean onInterceptTouchEvent(MotionEvent ev) {
155 final int action = ev.getAction();
156 final CellInfo cellInfo = mCellInfo;
157
158 if (action == MotionEvent.ACTION_DOWN) {
159 final Rect frame = mRect;
160 final int x = (int) ev.getX() + mScrollX;
161 final int y = (int) ev.getY() + mScrollY;
162 final int count = getChildCount();
163
164 boolean found = false;
165 for (int i = count - 1; i >= 0; i--) {
166 final View child = getChildAt(i);
167
168 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
169 child.getHitRect(frame);
170 if (frame.contains(x, y)) {
171 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
172 cellInfo.cell = child;
173 cellInfo.cellX = lp.cellX;
174 cellInfo.cellY = lp.cellY;
175 cellInfo.spanX = lp.cellHSpan;
176 cellInfo.spanY = lp.cellVSpan;
177 cellInfo.valid = true;
178 found = true;
179 mDirtyTag = false;
180 break;
181 }
182 }
183 }
Mike Cleronf8bbd342009-10-23 16:15:16 -0700184
185 mLastDownOnOccupiedCell = found;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800186
187 if (!found) {
188 int cellXY[] = mCellXY;
189 pointToCellExact(x, y, cellXY);
190
191 final boolean portrait = mPortrait;
192 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
193 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
194
195 final boolean[][] occupied = mOccupied;
Jeff Sharkey70864282009-04-07 21:08:40 -0700196 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800197
198 cellInfo.cell = null;
199 cellInfo.cellX = cellXY[0];
200 cellInfo.cellY = cellXY[1];
201 cellInfo.spanX = 1;
202 cellInfo.spanY = 1;
203 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
204 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
205
206 // Instead of finding the interesting vacant cells here, wait until a
207 // caller invokes getTag() to retrieve the result. Finding the vacant
208 // cells is a bit expensive and can generate many new objects, it's
209 // therefore better to defer it until we know we actually need it.
210
211 mDirtyTag = true;
212 }
213 setTag(cellInfo);
214 } else if (action == MotionEvent.ACTION_UP) {
215 cellInfo.cell = null;
216 cellInfo.cellX = -1;
217 cellInfo.cellY = -1;
218 cellInfo.spanX = 0;
219 cellInfo.spanY = 0;
220 cellInfo.valid = false;
221 mDirtyTag = false;
222 setTag(cellInfo);
223 }
224
225 return false;
226 }
227
228 @Override
229 public CellInfo getTag() {
230 final CellInfo info = (CellInfo) super.getTag();
231 if (mDirtyTag && info.valid) {
232 final boolean portrait = mPortrait;
233 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
234 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
235
236 final boolean[][] occupied = mOccupied;
Jeff Sharkey70864282009-04-07 21:08:40 -0700237 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800238
239 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
240
241 mDirtyTag = false;
242 }
243 return info;
244 }
245
246 private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
247 int xCount, int yCount, boolean[][] occupied) {
248
249 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
250 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
251 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
252 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
253 cellInfo.clearVacantCells();
254
255 if (occupied[x][y]) {
256 return;
257 }
258
259 cellInfo.current.set(x, y, x, y);
260
261 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
262 }
263
264 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
265 CellInfo cellInfo) {
266
267 addVacantCell(current, cellInfo);
268
269 if (current.left > 0) {
270 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
271 current.left--;
272 findVacantCell(current, xCount, yCount, occupied, cellInfo);
273 current.left++;
274 }
275 }
276
277 if (current.right < xCount - 1) {
278 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
279 current.right++;
280 findVacantCell(current, xCount, yCount, occupied, cellInfo);
281 current.right--;
282 }
283 }
284
285 if (current.top > 0) {
286 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
287 current.top--;
288 findVacantCell(current, xCount, yCount, occupied, cellInfo);
289 current.top++;
290 }
291 }
292
293 if (current.bottom < yCount - 1) {
294 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
295 current.bottom++;
296 findVacantCell(current, xCount, yCount, occupied, cellInfo);
297 current.bottom--;
298 }
299 }
300 }
301
302 private static void addVacantCell(Rect current, CellInfo cellInfo) {
303 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
304 cell.cellX = current.left;
305 cell.cellY = current.top;
306 cell.spanX = current.right - current.left + 1;
307 cell.spanY = current.bottom - current.top + 1;
308 if (cell.spanX > cellInfo.maxVacantSpanX) {
309 cellInfo.maxVacantSpanX = cell.spanX;
310 cellInfo.maxVacantSpanXSpanY = cell.spanY;
311 }
312 if (cell.spanY > cellInfo.maxVacantSpanY) {
313 cellInfo.maxVacantSpanY = cell.spanY;
314 cellInfo.maxVacantSpanYSpanX = cell.spanX;
315 }
316 cellInfo.vacantCells.add(cell);
317 }
318
319 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
320 for (int y = top; y <= bottom; y++) {
321 if (occupied[x][y]) {
322 return false;
323 }
324 }
325 return true;
326 }
327
328 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
329 for (int x = left; x <= right; x++) {
330 if (occupied[x][y]) {
331 return false;
332 }
333 }
334 return true;
335 }
336
Jeff Sharkey70864282009-04-07 21:08:40 -0700337 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800338 final boolean portrait = mPortrait;
339 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
340 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
341
342 boolean[][] occupied = mOccupied;
343
344 if (occupiedCells != null) {
345 for (int y = 0; y < yCount; y++) {
346 for (int x = 0; x < xCount; x++) {
347 occupied[x][y] = occupiedCells[y * xCount + x];
348 }
349 }
350 } else {
Jeff Sharkey70864282009-04-07 21:08:40 -0700351 findOccupiedCells(xCount, yCount, occupied, ignoreView);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800352 }
353
354 CellInfo cellInfo = new CellInfo();
355
356 cellInfo.cellX = -1;
357 cellInfo.cellY = -1;
358 cellInfo.spanY = 0;
359 cellInfo.spanX = 0;
360 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
361 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
362 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
363 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
364 cellInfo.screen = mCellInfo.screen;
365
366 Rect current = cellInfo.current;
367
368 for (int x = 0; x < xCount; x++) {
369 for (int y = 0; y < yCount; y++) {
370 if (!occupied[x][y]) {
371 current.set(x, y, x, y);
372 findVacantCell(current, xCount, yCount, occupied, cellInfo);
373 occupied[x][y] = true;
374 }
375 }
376 }
377
378 cellInfo.valid = cellInfo.vacantCells.size() > 0;
379
380 // Assume the caller will perform their own cell searching, otherwise we
381 // risk causing an unnecessary rebuild after findCellForSpan()
382
383 return cellInfo;
384 }
385
386 /**
387 * Given a point, return the cell that strictly encloses that point
388 * @param x X coordinate of the point
389 * @param y Y coordinate of the point
390 * @param result Array of 2 ints to hold the x and y coordinate of the cell
391 */
392 void pointToCellExact(int x, int y, int[] result) {
393 final boolean portrait = mPortrait;
394
395 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
396 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
397
398 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
399 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
400
401 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
402 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
403
404 if (result[0] < 0) result[0] = 0;
405 if (result[0] >= xAxis) result[0] = xAxis - 1;
406 if (result[1] < 0) result[1] = 0;
407 if (result[1] >= yAxis) result[1] = yAxis - 1;
408 }
409
410 /**
411 * Given a point, return the cell that most closely encloses that point
412 * @param x X coordinate of the point
413 * @param y Y coordinate of the point
414 * @param result Array of 2 ints to hold the x and y coordinate of the cell
415 */
416 void pointToCellRounded(int x, int y, int[] result) {
417 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
418 }
419
420 /**
421 * Given a cell coordinate, return the point that represents the upper left corner of that cell
422 *
423 * @param cellX X coordinate of the cell
424 * @param cellY Y coordinate of the cell
425 *
426 * @param result Array of 2 ints to hold the x and y coordinate of the point
427 */
428 void cellToPoint(int cellX, int cellY, int[] result) {
429 final boolean portrait = mPortrait;
430
431 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
432 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
433
434
435 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
436 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
437 }
438
Romain Guy84f296c2009-11-04 15:00:44 -0800439 int getCellWidth() {
440 return mCellWidth;
441 }
442
443 int getCellHeight() {
444 return mCellHeight;
445 }
446
Romain Guy1a304a12009-11-10 00:02:32 -0800447 int getLeftPadding() {
448 return mPortrait ? mShortAxisStartPadding : mLongAxisStartPadding;
449 }
450
451 int getTopPadding() {
452 return mPortrait ? mLongAxisStartPadding : mShortAxisStartPadding;
453 }
454
455 int getRightPadding() {
456 return mPortrait ? mShortAxisEndPadding : mLongAxisEndPadding;
457 }
458
459 int getBottomPadding() {
460 return mPortrait ? mLongAxisEndPadding : mShortAxisEndPadding;
461 }
462
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800463 @Override
464 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
465 // TODO: currently ignoring padding
466
467 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
468 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
469
470 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
471 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
472
473 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
474 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
475 }
476
477 final int shortAxisCells = mShortAxisCells;
478 final int longAxisCells = mLongAxisCells;
479 final int longAxisStartPadding = mLongAxisStartPadding;
480 final int longAxisEndPadding = mLongAxisEndPadding;
481 final int shortAxisStartPadding = mShortAxisStartPadding;
482 final int shortAxisEndPadding = mShortAxisEndPadding;
483 final int cellWidth = mCellWidth;
484 final int cellHeight = mCellHeight;
485
486 mPortrait = heightSpecSize > widthSpecSize;
487
488 int numShortGaps = shortAxisCells - 1;
489 int numLongGaps = longAxisCells - 1;
490
491 if (mPortrait) {
492 int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
493 - (cellHeight * longAxisCells);
494 mHeightGap = vSpaceLeft / numLongGaps;
495
496 int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
497 - (cellWidth * shortAxisCells);
498 if (numShortGaps > 0) {
499 mWidthGap = hSpaceLeft / numShortGaps;
500 } else {
501 mWidthGap = 0;
502 }
503 } else {
504 int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
505 - (cellWidth * longAxisCells);
506 mWidthGap = hSpaceLeft / numLongGaps;
507
508 int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
509 - (cellHeight * shortAxisCells);
510 if (numShortGaps > 0) {
511 mHeightGap = vSpaceLeft / numShortGaps;
512 } else {
513 mHeightGap = 0;
514 }
515 }
516
517 int count = getChildCount();
518
519 for (int i = 0; i < count; i++) {
520 View child = getChildAt(i);
521 LayoutParams lp = (LayoutParams) child.getLayoutParams();
522
523 if (mPortrait) {
524 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
525 longAxisStartPadding);
526 } else {
527 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
528 shortAxisStartPadding);
529 }
Romain Guyfcb9e712009-10-02 16:06:52 -0700530
531 if (lp.regenerateId) {
532 child.setId(((getId() & 0xFF) << 16) | (lp.cellX & 0xFF) << 8 | (lp.cellY & 0xFF));
533 lp.regenerateId = false;
534 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800535
536 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
537 int childheightMeasureSpec =
538 MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
539 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
540 }
541
542 setMeasuredDimension(widthSpecSize, heightSpecSize);
543 }
544
545 @Override
546 protected void onLayout(boolean changed, int l, int t, int r, int b) {
547 int count = getChildCount();
548
549 for (int i = 0; i < count; i++) {
550 View child = getChildAt(i);
551 if (child.getVisibility() != GONE) {
552
553 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
554
555 int childLeft = lp.x;
556 int childTop = lp.y;
557 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
Romain Guy84f296c2009-11-04 15:00:44 -0800558
559 if (lp.dropped) {
560 lp.dropped = false;
561
562 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
563 childLeft + lp.width / 2, childTop + lp.height / 2, 0, null);
564 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800565 }
566 }
567 }
568
569 @Override
570 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
571 final int count = getChildCount();
572 for (int i = 0; i < count; i++) {
573 final View view = getChildAt(i);
574 view.setDrawingCacheEnabled(enabled);
575 // Update the drawing caches
Romain Guy806b0f32009-06-26 16:57:39 -0700576 view.buildDrawingCache(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800577 }
578 }
579
580 @Override
581 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
582 super.setChildrenDrawnWithCacheEnabled(enabled);
583 }
584
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800585 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700586 * Find a vacant area that will fit the given bounds nearest the requested
587 * cell location. Uses Euclidean distance to score multiple vacant areas.
588 *
Romain Guy51afc022009-05-04 18:03:43 -0700589 * @param pixelX The X location at which you want to search for a vacant area.
590 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -0700591 * @param spanX Horizontal span of the object.
592 * @param spanY Vertical span of the object.
593 * @param vacantCells Pre-computed set of vacant cells to search.
594 * @param recycle Previously returned value to possibly recycle.
595 * @return The X, Y cell of a vacant area that can contain this object,
596 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800597 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700598 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
599 CellInfo vacantCells, int[] recycle) {
600
601 // Keep track of best-scoring drop area
602 final int[] bestXY = recycle != null ? recycle : new int[2];
603 final int[] cellXY = mCellXY;
604 double bestDistance = Double.MAX_VALUE;
605
606 // Bail early if vacant cells aren't valid
607 if (!vacantCells.valid) {
608 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800609 }
610
Jeff Sharkey70864282009-04-07 21:08:40 -0700611 // Look across all vacant cells for best fit
612 final int size = vacantCells.vacantCells.size();
613 for (int i = 0; i < size; i++) {
614 final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
615
616 // Reject if vacant cell isn't our exact size
617 if (cell.spanX != spanX || cell.spanY != spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800618 continue;
619 }
Jeff Sharkey70864282009-04-07 21:08:40 -0700620
621 // Score is center distance from requested pixel
622 cellToPoint(cell.cellX, cell.cellY, cellXY);
623
624 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
625 Math.pow(cellXY[1] - pixelY, 2));
626 if (distance <= bestDistance) {
627 bestDistance = distance;
628 bestXY[0] = cell.cellX;
629 bestXY[1] = cell.cellY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800630 }
631 }
632
Jeff Sharkey70864282009-04-07 21:08:40 -0700633 // Return null if no suitable location found
634 if (bestDistance < Double.MAX_VALUE) {
635 return bestXY;
636 } else {
637 return null;
638 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800639 }
Jeff Sharkey70864282009-04-07 21:08:40 -0700640
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800641 /**
642 * Drop a child at the specified position
643 *
644 * @param child The child that is being dropped
Jeff Sharkey70864282009-04-07 21:08:40 -0700645 * @param targetXY Destination area to move to
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800646 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700647 void onDropChild(View child, int[] targetXY) {
Romain Guyd94533d2009-08-17 10:01:15 -0700648 if (child != null) {
649 LayoutParams lp = (LayoutParams) child.getLayoutParams();
650 lp.cellX = targetXY[0];
651 lp.cellY = targetXY[1];
652 lp.isDragging = false;
Romain Guy84f296c2009-11-04 15:00:44 -0800653 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -0700654 mDragRect.setEmpty();
655 child.requestLayout();
656 invalidate();
657 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800658 }
659
660 void onDropAborted(View child) {
661 if (child != null) {
662 ((LayoutParams) child.getLayoutParams()).isDragging = false;
663 invalidate();
664 }
665 mDragRect.setEmpty();
666 }
667
668 /**
669 * Start dragging the specified child
670 *
671 * @param child The child that is being dragged
672 */
673 void onDragChild(View child) {
674 LayoutParams lp = (LayoutParams) child.getLayoutParams();
675 lp.isDragging = true;
676 mDragRect.setEmpty();
677 }
678
679 /**
680 * Drag a child over the specified position
681 *
682 * @param child The child that is being dropped
683 * @param cellX The child's new x cell location
684 * @param cellY The child's new y cell location
685 */
686 void onDragOverChild(View child, int cellX, int cellY) {
687 int[] cellXY = mCellXY;
688 pointToCellRounded(cellX, cellY, cellXY);
689 LayoutParams lp = (LayoutParams) child.getLayoutParams();
690 cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
691 invalidate();
692 }
693
694 /**
695 * Computes a bounding rectangle for a range of cells
696 *
697 * @param cellX X coordinate of upper left corner expressed as a cell position
698 * @param cellY Y coordinate of upper left corner expressed as a cell position
699 * @param cellHSpan Width in cells
700 * @param cellVSpan Height in cells
701 * @param dragRect Rectnagle into which to put the results
702 */
703 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
704 final boolean portrait = mPortrait;
705 final int cellWidth = mCellWidth;
706 final int cellHeight = mCellHeight;
707 final int widthGap = mWidthGap;
708 final int heightGap = mHeightGap;
709
710 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
711 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
712
713 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
714 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
715
716 int x = hStartPadding + cellX * (cellWidth + widthGap);
717 int y = vStartPadding + cellY * (cellHeight + heightGap);
718
719 dragRect.set(x, y, x + width, y + height);
720 }
721
722 /**
723 * Computes the required horizontal and vertical cell spans to always
724 * fit the given rectangle.
725 *
726 * @param width Width in pixels
727 * @param height Height in pixels
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800728 */
729 public int[] rectToCell(int width, int height) {
730 // Always assume we're working with the smallest span to make sure we
731 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -0400732 final Resources resources = getResources();
733 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
734 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -0400736
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800737 // Always round up to next largest cell
738 int spanX = (width + smallerSize) / smallerSize;
739 int spanY = (height + smallerSize) / smallerSize;
Joe Onorato79e56262009-09-21 15:23:04 -0400740
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800741 return new int[] { spanX, spanY };
742 }
743
744 /**
745 * Find the first vacant cell, if there is one.
746 *
747 * @param vacant Holds the x and y coordinate of the vacant cell
748 * @param spanX Horizontal cell span.
749 * @param spanY Vertical cell span.
750 *
751 * @return True if a vacant cell was found
752 */
753 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
754 final boolean portrait = mPortrait;
755 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
756 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
757 final boolean[][] occupied = mOccupied;
758
Jeff Sharkey70864282009-04-07 21:08:40 -0700759 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800760
761 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
762 }
763
764 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
765 int xCount, int yCount, boolean[][] occupied) {
766
767 for (int x = 0; x < xCount; x++) {
768 for (int y = 0; y < yCount; y++) {
769 boolean available = !occupied[x][y];
770out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
771 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
772 available = available && !occupied[i][j];
773 if (!available) break out;
774 }
775 }
776
777 if (available) {
778 vacant[0] = x;
779 vacant[1] = y;
780 return true;
781 }
782 }
783 }
784
785 return false;
786 }
787
788 boolean[] getOccupiedCells() {
789 final boolean portrait = mPortrait;
790 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
791 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
792 final boolean[][] occupied = mOccupied;
793
Jeff Sharkey70864282009-04-07 21:08:40 -0700794 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800795
796 final boolean[] flat = new boolean[xCount * yCount];
797 for (int y = 0; y < yCount; y++) {
798 for (int x = 0; x < xCount; x++) {
799 flat[y * xCount + x] = occupied[x][y];
800 }
801 }
802
803 return flat;
804 }
805
Jeff Sharkey70864282009-04-07 21:08:40 -0700806 private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800807 for (int x = 0; x < xCount; x++) {
808 for (int y = 0; y < yCount; y++) {
809 occupied[x][y] = false;
810 }
811 }
812
813 int count = getChildCount();
814 for (int i = 0; i < count; i++) {
815 View child = getChildAt(i);
Jeff Sharkey70864282009-04-07 21:08:40 -0700816 if (child instanceof Folder || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800817 continue;
818 }
819 LayoutParams lp = (LayoutParams) child.getLayoutParams();
820
821 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
822 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
823 occupied[x][y] = true;
824 }
825 }
826 }
827 }
828
829 @Override
830 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
831 return new CellLayout.LayoutParams(getContext(), attrs);
832 }
833
834 @Override
835 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
836 return p instanceof CellLayout.LayoutParams;
837 }
838
839 @Override
840 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
841 return new CellLayout.LayoutParams(p);
842 }
843
844 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
845 /**
846 * Horizontal location of the item in the grid.
847 */
848 @ViewDebug.ExportedProperty
849 public int cellX;
850
851 /**
852 * Vertical location of the item in the grid.
853 */
854 @ViewDebug.ExportedProperty
855 public int cellY;
856
857 /**
858 * Number of cells spanned horizontally by the item.
859 */
860 @ViewDebug.ExportedProperty
861 public int cellHSpan;
862
863 /**
864 * Number of cells spanned vertically by the item.
865 */
866 @ViewDebug.ExportedProperty
867 public int cellVSpan;
868
869 /**
870 * Is this item currently being dragged
871 */
872 public boolean isDragging;
873
874 // X coordinate of the view in the layout.
875 @ViewDebug.ExportedProperty
876 int x;
877 // Y coordinate of the view in the layout.
878 @ViewDebug.ExportedProperty
879 int y;
880
Romain Guyfcb9e712009-10-02 16:06:52 -0700881 boolean regenerateId;
Romain Guy84f296c2009-11-04 15:00:44 -0800882
883 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -0700884
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800885 public LayoutParams(Context c, AttributeSet attrs) {
886 super(c, attrs);
887 cellHSpan = 1;
888 cellVSpan = 1;
889 }
890
891 public LayoutParams(ViewGroup.LayoutParams source) {
892 super(source);
893 cellHSpan = 1;
894 cellVSpan = 1;
895 }
896
897 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
898 super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
899 this.cellX = cellX;
900 this.cellY = cellY;
901 this.cellHSpan = cellHSpan;
902 this.cellVSpan = cellVSpan;
903 }
904
905 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
906 int hStartPadding, int vStartPadding) {
907
908 final int myCellHSpan = cellHSpan;
909 final int myCellVSpan = cellVSpan;
910 final int myCellX = cellX;
911 final int myCellY = cellY;
912
913 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
914 leftMargin - rightMargin;
915 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
916 topMargin - bottomMargin;
917
918 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
919 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
920 }
921 }
922
923 static final class CellInfo implements ContextMenu.ContextMenuInfo {
924 /**
925 * See View.AttachInfo.InvalidateInfo for futher explanations about
926 * the recycling mechanism. In this case, we recycle the vacant cells
927 * instances because up to several hundreds can be instanciated when
928 * the user long presses an empty cell.
929 */
930 static final class VacantCell {
931 int cellX;
932 int cellY;
933 int spanX;
934 int spanY;
935
936 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
937 // like a reasonable compromise given the size of a VacantCell and
938 // the fact that the user is not likely to touch an empty 4x4 grid
939 // very often
940 private static final int POOL_LIMIT = 100;
941 private static final Object sLock = new Object();
942
943 private static int sAcquiredCount = 0;
944 private static VacantCell sRoot;
945
946 private VacantCell next;
947
948 static VacantCell acquire() {
949 synchronized (sLock) {
950 if (sRoot == null) {
951 return new VacantCell();
952 }
953
954 VacantCell info = sRoot;
955 sRoot = info.next;
956 sAcquiredCount--;
957
958 return info;
959 }
960 }
961
962 void release() {
963 synchronized (sLock) {
964 if (sAcquiredCount < POOL_LIMIT) {
965 sAcquiredCount++;
966 next = sRoot;
967 sRoot = this;
968 }
969 }
970 }
971
972 @Override
973 public String toString() {
974 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
975 ", spanY=" + spanY + "]";
976 }
977 }
978
979 View cell;
980 int cellX;
981 int cellY;
982 int spanX;
983 int spanY;
984 int screen;
985 boolean valid;
986
987 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
988 int maxVacantSpanX;
989 int maxVacantSpanXSpanY;
990 int maxVacantSpanY;
991 int maxVacantSpanYSpanX;
992 final Rect current = new Rect();
993
Romain Guy4c58c482009-05-12 17:35:41 -0700994 void clearVacantCells() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800995 final ArrayList<VacantCell> list = vacantCells;
996 final int count = list.size();
997
998 for (int i = 0; i < count; i++) list.get(i).release();
999
1000 list.clear();
1001 }
1002
1003 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
1004 if (cellX < 0 || cellY < 0) {
1005 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
1006 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
1007 clearVacantCells();
1008 return;
1009 }
1010
1011 final boolean[][] unflattened = new boolean[xCount][yCount];
1012 for (int y = 0; y < yCount; y++) {
1013 for (int x = 0; x < xCount; x++) {
1014 unflattened[x][y] = occupied[y * xCount + x];
1015 }
1016 }
1017 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
1018 }
1019
1020 /**
1021 * This method can be called only once! Calling #findVacantCellsFromOccupied will
1022 * restore the ability to call this method.
1023 *
1024 * Finds the upper-left coordinate of the first rectangle in the grid that can
1025 * hold a cell of the specified dimensions.
1026 *
1027 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1028 * can be found.
1029 * @param spanX The horizontal span of the cell we want to find.
1030 * @param spanY The vertical span of the cell we want to find.
1031 *
1032 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1033 */
1034 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Romain Guy4c58c482009-05-12 17:35:41 -07001035 return findCellForSpan(cellXY, spanX, spanY, true);
1036 }
1037
1038 boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001039 final ArrayList<VacantCell> list = vacantCells;
1040 final int count = list.size();
1041
1042 boolean found = false;
1043
1044 if (this.spanX >= spanX && this.spanY >= spanY) {
1045 cellXY[0] = cellX;
1046 cellXY[1] = cellY;
1047 found = true;
1048 }
1049
1050 // Look for an exact match first
1051 for (int i = 0; i < count; i++) {
1052 VacantCell cell = list.get(i);
1053 if (cell.spanX == spanX && cell.spanY == spanY) {
1054 cellXY[0] = cell.cellX;
1055 cellXY[1] = cell.cellY;
1056 found = true;
1057 break;
1058 }
1059 }
1060
1061 // Look for the first cell large enough
1062 for (int i = 0; i < count; i++) {
1063 VacantCell cell = list.get(i);
1064 if (cell.spanX >= spanX && cell.spanY >= spanY) {
1065 cellXY[0] = cell.cellX;
1066 cellXY[1] = cell.cellY;
1067 found = true;
1068 break;
1069 }
1070 }
1071
Romain Guy4c58c482009-05-12 17:35:41 -07001072 if (clear) clearVacantCells();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001073
1074 return found;
1075 }
1076
1077 @Override
1078 public String toString() {
1079 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1080 ", y=" + cellY + "]";
1081 }
1082 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001083
1084 public boolean lastDownOnOccupiedCell() {
1085 return mLastDownOnOccupiedCell;
1086 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001087}
1088
1089