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