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