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