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