• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.launcher3.celllayout;
17 
18 import android.graphics.Point;
19 import android.graphics.Rect;
20 
21 import java.util.ArrayDeque;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Queue;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 
31 
32 public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
33 
intersects(Rect r1, Rect r2)34     private boolean intersects(Rect r1, Rect r2) {
35         // If one rectangle is on left side of other
36         if (r1.left > r2.right || r2.left > r1.right) {
37             return false;
38         }
39 
40         // If one rectangle is above other
41         if (r1.bottom > r2.top || r2.bottom > r1.top) {
42             return false;
43         }
44 
45         return true;
46     }
47 
overlapsWithIgnored(Set<Rect> ignoredRectangles, Rect rect)48     private boolean overlapsWithIgnored(Set<Rect> ignoredRectangles, Rect rect) {
49         for (Rect ignoredRect : ignoredRectangles) {
50             // Using the built in intersects doesn't work because it doesn't account for area 0
51             if (intersects(ignoredRect, rect)) {
52                 return true;
53             }
54         }
55         return false;
56     }
57 
58     @Override
compareTo(CellLayoutBoard cellLayoutBoard)59     public int compareTo(CellLayoutBoard cellLayoutBoard) {
60         // to be equal they need to have the same number of widgets and the same dimensions
61         // their order can be different
62         Set<Rect> widgetsSet = new HashSet<>();
63         Set<Rect> ignoredRectangles = new HashSet<>();
64         for (WidgetRect rect : mWidgetsRects) {
65             if (rect.shouldIgnore()) {
66                 ignoredRectangles.add(rect.mBounds);
67             } else {
68                 widgetsSet.add(rect.mBounds);
69             }
70         }
71         for (WidgetRect rect : cellLayoutBoard.mWidgetsRects) {
72             // ignore rectangles overlapping with the area marked by x
73             if (overlapsWithIgnored(ignoredRectangles, rect.mBounds)) {
74                 continue;
75             }
76             if (!widgetsSet.contains(rect.mBounds)) {
77                 return -1;
78             }
79             widgetsSet.remove(rect.mBounds);
80         }
81         if (!widgetsSet.isEmpty()) {
82             return 1;
83         }
84 
85         // to be equal they need to have the same number of icons their order can be different
86         Set<Point> iconsSet = new HashSet<>();
87         mIconPoints.forEach(icon -> iconsSet.add(icon.getCoord()));
88         for (IconPoint icon : cellLayoutBoard.mIconPoints) {
89             if (!iconsSet.contains(icon.getCoord())) {
90                 return -1;
91             }
92             iconsSet.remove(icon.getCoord());
93         }
94         if (!iconsSet.isEmpty()) {
95             return 1;
96         }
97         return 0;
98     }
99 
100     public static class CellType {
101         // The cells marked by this will be filled by 1x1 widgets and will be ignored when
102         // validating
103         public static final char IGNORE = 'x';
104         // The cells marked by this will be filled by app icons
105         public static final char ICON = 'i';
106         // Empty space
107         public static final char EMPTY = '-';
108         // Widget that will be saved as "main widget" for easier retrieval
109         public static final char MAIN_WIDGET = 'm';
110         // Everything else will be consider a widget
111     }
112 
113     public static class WidgetRect {
114         public char mType;
115         public Rect mBounds;
116 
WidgetRect(char type, Rect bounds)117         WidgetRect(char type, Rect bounds) {
118             this.mType = type;
119             this.mBounds = bounds;
120         }
121 
getSpanX()122         int getSpanX() {
123             return mBounds.right - mBounds.left + 1;
124         }
125 
getSpanY()126         int getSpanY() {
127             return mBounds.top - mBounds.bottom + 1;
128         }
129 
getCellX()130         int getCellX() {
131             return mBounds.left;
132         }
133 
getCellY()134         int getCellY() {
135             return mBounds.bottom;
136         }
137 
shouldIgnore()138         boolean shouldIgnore() {
139             return this.mType == CellType.IGNORE;
140         }
141 
142         @Override
toString()143         public String toString() {
144             return "WidgetRect type = " + mType + " bounds = " + mBounds.toString();
145         }
146     }
147 
148     public static class IconPoint {
149         public Point coord;
150         public char mType;
151 
IconPoint(Point coord, char type)152         public IconPoint(Point coord, char type) {
153             this.coord = coord;
154             mType = type;
155         }
156 
getType()157         public char getType() {
158             return mType;
159         }
160 
setType(char type)161         public void setType(char type) {
162             mType = type;
163         }
164 
getCoord()165         public Point getCoord() {
166             return coord;
167         }
168 
setCoord(Point coord)169         public void setCoord(Point coord) {
170             this.coord = coord;
171         }
172     }
173 
174     static final int INFINITE = 99999;
175 
176     char[][] mWidget = new char[30][30];
177 
178     List<WidgetRect> mWidgetsRects = new ArrayList<>();
179     Map<Character, WidgetRect> mWidgetsMap = new HashMap<>();
180 
181     List<IconPoint> mIconPoints = new ArrayList<>();
182     Map<Character, IconPoint> mIconsMap = new HashMap<>();
183 
184     WidgetRect mMain = null;
185 
CellLayoutBoard()186     CellLayoutBoard() {
187         for (int x = 0; x < mWidget.length; x++) {
188             for (int y = 0; y < mWidget[0].length; y++) {
189                 mWidget[x][y] = CellType.EMPTY;
190             }
191         }
192     }
193 
getWidgets()194     public List<WidgetRect> getWidgets() {
195         return mWidgetsRects;
196     }
197 
getIcons()198     public List<IconPoint> getIcons() {
199         return mIconPoints;
200     }
201 
getMain()202     public WidgetRect getMain() {
203         return mMain;
204     }
205 
getWidgetRect(char c)206     public WidgetRect getWidgetRect(char c) {
207         return mWidgetsMap.get(c);
208     }
209 
removeWidgetFromBoard(WidgetRect widget)210     private void removeWidgetFromBoard(WidgetRect widget) {
211         for (int xi = widget.mBounds.left; xi < widget.mBounds.right; xi++) {
212             for (int yi = widget.mBounds.top; yi < widget.mBounds.bottom; yi++) {
213                 mWidget[xi][yi] = '-';
214             }
215         }
216     }
217 
removeOverlappingItems(Rect rect)218     private void removeOverlappingItems(Rect rect) {
219         // Remove overlapping widgets and remove them from the board
220         mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
221             if (rect.intersect(widget.mBounds)) {
222                 removeWidgetFromBoard(widget);
223                 return false;
224             }
225             return true;
226         }).collect(Collectors.toList());
227         // Remove overlapping icons and remove them from the board
228         mIconPoints = mIconPoints.stream().filter(iconPoint -> {
229             int x = iconPoint.coord.x;
230             int y = iconPoint.coord.y;
231             if (rect.contains(x, y)) {
232                 mWidget[x][y] = '-';
233                 return false;
234             }
235             return true;
236         }).collect(Collectors.toList());
237     }
238 
removeOverlappingItems(Point p)239     private void removeOverlappingItems(Point p) {
240         // Remove overlapping widgets and remove them from the board
241         mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
242             if (widget.mBounds.contains(p.x, p.y)) {
243                 removeWidgetFromBoard(widget);
244                 return false;
245             }
246             return true;
247         }).collect(Collectors.toList());
248         // Remove overlapping icons and remove them from the board
249         mIconPoints = mIconPoints.stream().filter(iconPoint -> {
250             int x = iconPoint.coord.x;
251             int y = iconPoint.coord.y;
252             if (p.x == x && p.y == y) {
253                 mWidget[x][y] = '-';
254                 return false;
255             }
256             return true;
257         }).collect(Collectors.toList());
258     }
259 
addWidget(int x, int y, int spanX, int spanY, char type)260     public void addWidget(int x, int y, int spanX, int spanY, char type) {
261         Rect rect = new Rect(x, y + spanY - 1, x + spanX - 1, y);
262         removeOverlappingItems(rect);
263         WidgetRect widgetRect = new WidgetRect(type, rect);
264         mWidgetsRects.add(widgetRect);
265         for (int xi = rect.left; xi < rect.right + 1; xi++) {
266             for (int yi = rect.bottom; yi < rect.top + 1; yi++) {
267                 mWidget[xi][yi] = type;
268             }
269         }
270     }
271 
addIcon(int x, int y)272     public void addIcon(int x, int y) {
273         removeOverlappingItems(new Point(x, y));
274         mWidget[x][y] = 'i';
275     }
276 
getWidgetRect(int x, int y, Set<Point> used, char[][] board)277     public static WidgetRect getWidgetRect(int x, int y, Set<Point> used, char[][] board) {
278         char type = board[x][y];
279         Queue<Point> search = new ArrayDeque<Point>();
280         Point current = new Point(x, y);
281         search.add(current);
282         used.add(current);
283         List<Point> neighbors = new ArrayList<>(List.of(
284                 new Point(-1, 0),
285                 new Point(0, -1),
286                 new Point(1, 0),
287                 new Point(0, 1))
288         );
289         Rect widgetRect = new Rect(INFINITE, -INFINITE, -INFINITE, INFINITE);
290         while (!search.isEmpty()) {
291             current = search.poll();
292             widgetRect.top = Math.max(widgetRect.top, current.y);
293             widgetRect.right = Math.max(widgetRect.right, current.x);
294             widgetRect.bottom = Math.min(widgetRect.bottom, current.y);
295             widgetRect.left = Math.min(widgetRect.left, current.x);
296             for (Point p : neighbors) {
297                 Point next = new Point(current.x + p.x, current.y + p.y);
298                 if (next.x < 0 || next.x >= board.length) continue;
299                 if (next.y < 0 || next.y >= board[0].length) continue;
300                 if (board[next.x][next.y] == type && !used.contains(next)) {
301                     used.add(next);
302                     search.add(next);
303                 }
304             }
305         }
306         return new WidgetRect(type, widgetRect);
307     }
308 
isWidget(char type)309     public static boolean isWidget(char type) {
310         return type != CellType.ICON && type != CellType.EMPTY;
311     }
312 
isIcon(char type)313     public static boolean isIcon(char type) {
314         return type == CellType.ICON;
315     }
316 
getRects(char[][] board)317     private static List<WidgetRect> getRects(char[][] board) {
318         Set<Point> used = new HashSet<>();
319         List<WidgetRect> widgetsRects = new ArrayList<>();
320         for (int x = 0; x < board.length; x++) {
321             for (int y = 0; y < board[0].length; y++) {
322                 if (!used.contains(new Point(x, y)) && isWidget(board[x][y])) {
323                     widgetsRects.add(getWidgetRect(x, y, used, board));
324                 }
325             }
326         }
327         return widgetsRects;
328     }
329 
getIconPoints(char[][] board)330     private static List<IconPoint> getIconPoints(char[][] board) {
331         List<IconPoint> iconPoints = new ArrayList<>();
332         for (int x = 0; x < board.length; x++) {
333             for (int y = 0; y < board[0].length; y++) {
334                 if (isIcon(board[x][y])) {
335                     iconPoints.add(new IconPoint(new Point(x, y), board[x][y]));
336                 }
337             }
338         }
339         return iconPoints;
340     }
341 
getMainFromList(List<CellLayoutBoard> boards)342     public static WidgetRect getMainFromList(List<CellLayoutBoard> boards) {
343         for (CellLayoutBoard board : boards) {
344             WidgetRect main = board.getMain();
345             if (main != null) {
346                 return main;
347             }
348         }
349         return null;
350     }
351 
boardFromString(String boardStr)352     public static CellLayoutBoard boardFromString(String boardStr) {
353         String[] lines = boardStr.split("\n");
354         CellLayoutBoard board = new CellLayoutBoard();
355 
356         for (int y = 0; y < lines.length; y++) {
357             String line = lines[y];
358             for (int x = 0; x < line.length(); x++) {
359                 char c = line.charAt(x);
360                 if (c != CellType.EMPTY) {
361                     board.mWidget[x][y] = line.charAt(x);
362                 }
363             }
364         }
365         board.mWidgetsRects = getRects(board.mWidget);
366         board.mWidgetsRects.forEach(widgetRect -> {
367             if (widgetRect.mType == CellType.MAIN_WIDGET) {
368                 board.mMain = widgetRect;
369             }
370             board.mWidgetsMap.put(widgetRect.mType, widgetRect);
371         });
372         board.mIconPoints = getIconPoints(board.mWidget);
373         return board;
374     }
375 
toString(int maxX, int maxY)376     public String toString(int maxX, int maxY) {
377         StringBuilder s = new StringBuilder();
378         maxX = Math.min(maxX, mWidget.length);
379         maxY = Math.min(maxY, mWidget[0].length);
380         for (int y = 0; y < maxY; y++) {
381             for (int x = 0; x < maxX; x++) {
382                 s.append(mWidget[x][y]);
383             }
384             s.append('\n');
385         }
386         return s.toString();
387     }
388 
boardListFromString(String boardsStr)389     public static List<CellLayoutBoard> boardListFromString(String boardsStr) {
390         String[] lines = boardsStr.split("\n");
391         ArrayList<String> individualBoards = new ArrayList<>();
392         ArrayList<CellLayoutBoard> boards = new ArrayList<>();
393         for (String line : lines) {
394             String[] boardSegment = line.split("\\|");
395             for (int i = 0; i < boardSegment.length; i++) {
396                 if (i >= individualBoards.size()) {
397                     individualBoards.add(boardSegment[i]);
398                 } else {
399                     individualBoards.set(i, individualBoards.get(i) + "\n" + boardSegment[i]);
400                 }
401             }
402         }
403         for (String board : individualBoards) {
404             boards.add(CellLayoutBoard.boardFromString(board));
405         }
406         return boards;
407     }
408 }
409