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.board;
17 
18 import android.graphics.Point;
19 import android.graphics.Rect;
20 
21 import androidx.annotation.NonNull;
22 
23 import java.util.ArrayDeque;
24 import java.util.ArrayList;
25 import java.util.Comparator;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Queue;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33 
34 
35 public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
36 
37     public static final Comparator<CellLayoutBoard> COMPARATOR = new IdenticalBoardComparator();
38 
39     @Override
compareTo(@onNull CellLayoutBoard cellLayoutBoard)40     public int compareTo(@NonNull CellLayoutBoard cellLayoutBoard) {
41         return COMPARATOR.compare(this, cellLayoutBoard);
42     }
43 
44     private HashSet<Character> mUsedWidgetTypes = new HashSet<>();
45 
46     static final int INFINITE = 99999;
47 
48     char[][] mWidget = new char[30][30];
49 
50     List<WidgetRect> mWidgetsRects = new ArrayList<>();
51     Map<Character, WidgetRect> mWidgetsMap = new HashMap<>();
52 
53     List<IconPoint> mIconPoints = new ArrayList<>();
54     List<FolderPoint> mFolderPoints = new ArrayList<>();
55 
56     WidgetRect mMain = null;
57 
58     int mWidth, mHeight;
59 
CellLayoutBoard()60     public CellLayoutBoard() {
61         for (int x = 0; x < mWidget.length; x++) {
62             for (int y = 0; y < mWidget[0].length; y++) {
63                 mWidget[x][y] = CellType.EMPTY;
64             }
65         }
66     }
67 
CellLayoutBoard(int width, int height)68     public CellLayoutBoard(int width, int height) {
69         mWidget = new char[width][height];
70         this.mWidth = width;
71         this.mHeight = height;
72         for (int x = 0; x < mWidget.length; x++) {
73             for (int y = 0; y < mWidget[0].length; y++) {
74                 mWidget[x][y] = CellType.EMPTY;
75             }
76         }
77     }
78 
pointInsideRect(int x, int y, WidgetRect rect)79     public boolean pointInsideRect(int x, int y, WidgetRect rect) {
80         Boolean isXInRect = x >= rect.getCellX() && x < rect.getCellX() + rect.getSpanX();
81         Boolean isYInRect = y >= rect.getCellY() && y < rect.getCellY() + rect.getSpanY();
82         return isXInRect && isYInRect;
83     }
84 
85     public WidgetRect getWidgetAt(Point p) {
86         return getWidgetAt(p.x, p.y);
87     }
88 
89     public WidgetRect getWidgetOfType(char type) {
90         return mWidgetsRects.stream()
91                 .filter(widgetRect -> widgetRect.mType == type).findFirst().orElse(null);
92     }
93 
getWidgetAt(int x, int y)94     public WidgetRect getWidgetAt(int x, int y) {
95         return mWidgetsRects.stream()
96                 .filter(widgetRect -> pointInsideRect(x, y, widgetRect)).findFirst().orElse(null);
97     }
98 
getWidgets()99     public List<WidgetRect> getWidgets() {
100         return mWidgetsRects;
101     }
102 
getIcons()103     public List<IconPoint> getIcons() {
104         return mIconPoints;
105     }
106 
getFolders()107     public List<FolderPoint> getFolders() {
108         return mFolderPoints;
109     }
110 
getMain()111     public WidgetRect getMain() {
112         return mMain;
113     }
114 
getWidgetRect(char c)115     public WidgetRect getWidgetRect(char c) {
116         return mWidgetsMap.get(c);
117     }
118 
removeWidgetFromBoard(WidgetRect widget)119     private void removeWidgetFromBoard(WidgetRect widget) {
120         for (int xi = widget.mBounds.left; xi <= widget.mBounds.right; xi++) {
121             for (int yi = widget.mBounds.bottom; yi <= widget.mBounds.top; yi++) {
122                 mWidget[xi][yi] = '-';
123             }
124         }
125     }
126 
removeOverlappingItems(Rect rect)127     private void removeOverlappingItems(Rect rect) {
128         // Remove overlapping widgets and remove them from the board
129         mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
130             if (rect.intersect(widget.mBounds)) {
131                 removeWidgetFromBoard(widget);
132                 return false;
133             }
134             return true;
135         }).collect(Collectors.toList());
136         // Remove overlapping icons and remove them from the board
137         mIconPoints = mIconPoints.stream().filter(iconPoint -> {
138             int x = iconPoint.coord.x;
139             int y = iconPoint.coord.y;
140             if (rect.contains(x, y)) {
141                 mWidget[x][y] = '-';
142                 return false;
143             }
144             return true;
145         }).collect(Collectors.toList());
146 
147         // Remove overlapping folders and remove them from the board
148         mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
149             int x = folderPoint.coord.x;
150             int y = folderPoint.coord.y;
151             if (rect.contains(x, y)) {
152                 mWidget[x][y] = '-';
153                 return false;
154             }
155             return true;
156         }).collect(Collectors.toList());
157     }
158 
removeOverlappingItems(Point p)159     private void removeOverlappingItems(Point p) {
160         // Remove overlapping widgets and remove them from the board
161         mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
162             if (IdenticalBoardComparator.Companion.touchesPoint(widget.mBounds, p)) {
163                 removeWidgetFromBoard(widget);
164                 return false;
165             }
166             return true;
167         }).collect(Collectors.toList());
168         // Remove overlapping icons and remove them from the board
169         mIconPoints = mIconPoints.stream().filter(iconPoint -> {
170             int x = iconPoint.coord.x;
171             int y = iconPoint.coord.y;
172             if (p.x == x && p.y == y) {
173                 mWidget[x][y] = '-';
174                 return false;
175             }
176             return true;
177         }).collect(Collectors.toList());
178 
179         // Remove overlapping folders and remove them from the board
180         mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
181             int x = folderPoint.coord.x;
182             int y = folderPoint.coord.y;
183             if (p.x == x && p.y == y) {
184                 mWidget[x][y] = '-';
185                 return false;
186             }
187             return true;
188         }).collect(Collectors.toList());
189     }
190 
getNextWidgetType()191     private char getNextWidgetType() {
192         for (char type = 'a'; type < 'z'; type++) {
193             if (type == CellType.ICON) continue;
194             if (type == CellType.IGNORE) continue;
195             if (mUsedWidgetTypes.contains(type)) continue;
196             mUsedWidgetTypes.add(type);
197             return type;
198         }
199         return 'z';
200     }
201 
202     /**
203      * Check if the given area is empty.
204      */
isEmpty(int x, int y, int spanX, int spanY)205     public boolean isEmpty(int x, int y, int spanX, int spanY) {
206         for (int xi = x; xi < x + spanX; xi++) {
207             for (int yi = y; yi < y + spanY; yi++) {
208                 if (mWidget[xi][yi] == CellType.IGNORE) continue;
209                 if (mWidget[xi][yi] != CellType.EMPTY) return false;
210             }
211         }
212         return true;
213     }
214 
addWidget(int x, int y, int spanX, int spanY, char type)215     public void addWidget(int x, int y, int spanX, int spanY, char type) {
216         Rect rect = new Rect(x, y + spanY - 1, x + spanX - 1, y);
217         removeOverlappingItems(rect);
218         WidgetRect widgetRect = new WidgetRect(type, rect);
219         mWidgetsRects.add(widgetRect);
220         for (int xi = rect.left; xi < rect.right + 1; xi++) {
221             for (int yi = rect.bottom; yi < rect.top + 1; yi++) {
222                 mWidget[xi][yi] = type;
223             }
224         }
225     }
226 
removeItem(char type)227     public void removeItem(char type) {
228         mWidgetsRects.stream()
229                 .filter(widgetRect -> widgetRect.mType == type)
230                 .forEach(widgetRect -> removeOverlappingItems(
231                         new Point(widgetRect.getCellX(), widgetRect.getCellY())));
232     }
233 
removeItem(Point p)234     public void removeItem(Point p) {
235         removeOverlappingItems(p);
236     }
237 
addWidget(int x, int y, int spanX, int spanY)238     public void addWidget(int x, int y, int spanX, int spanY) {
239         addWidget(x, y, spanX, spanY, getNextWidgetType());
240     }
241 
addIcon(int x, int y)242     public void addIcon(int x, int y) {
243         Point iconCoord = new Point(x, y);
244         removeOverlappingItems(iconCoord);
245         mIconPoints.add(new IconPoint(iconCoord, CellType.ICON));
246         mWidget[x][y] = 'i';
247     }
248 
getWidgetRect(int x, int y, Set<Point> used, char[][] board)249     public static WidgetRect getWidgetRect(int x, int y, Set<Point> used, char[][] board) {
250         char type = board[x][y];
251         Queue<Point> search = new ArrayDeque<Point>();
252         Point current = new Point(x, y);
253         search.add(current);
254         used.add(current);
255         List<Point> neighbors = new ArrayList<>(List.of(
256                 new Point(-1, 0),
257                 new Point(0, -1),
258                 new Point(1, 0),
259                 new Point(0, 1))
260         );
261         Rect widgetRect = new Rect(INFINITE, -INFINITE, -INFINITE, INFINITE);
262         while (!search.isEmpty()) {
263             current = search.poll();
264             widgetRect.top = Math.max(widgetRect.top, current.y);
265             widgetRect.right = Math.max(widgetRect.right, current.x);
266             widgetRect.bottom = Math.min(widgetRect.bottom, current.y);
267             widgetRect.left = Math.min(widgetRect.left, current.x);
268             for (Point p : neighbors) {
269                 Point next = new Point(current.x + p.x, current.y + p.y);
270                 if (next.x < 0 || next.x >= board.length) continue;
271                 if (next.y < 0 || next.y >= board[0].length) continue;
272                 if (board[next.x][next.y] == type && !used.contains(next)) {
273                     used.add(next);
274                     search.add(next);
275                 }
276             }
277         }
278         return new WidgetRect(type, widgetRect);
279     }
280 
isFolder(char type)281     public static boolean isFolder(char type) {
282         return type >= 'A' && type <= 'Z';
283     }
284 
isWidget(char type)285     public static boolean isWidget(char type) {
286         return type != CellType.ICON && type != CellType.EMPTY && (type >= 'a' && type <= 'z');
287     }
288 
isIcon(char type)289     public static boolean isIcon(char type) {
290         return type == CellType.ICON;
291     }
292 
getRects(char[][] board)293     private static List<WidgetRect> getRects(char[][] board) {
294         Set<Point> used = new HashSet<>();
295         List<WidgetRect> widgetsRects = new ArrayList<>();
296         for (int x = 0; x < board.length; x++) {
297             for (int y = 0; y < board[0].length; y++) {
298                 if (!used.contains(new Point(x, y)) && isWidget(board[x][y])) {
299                     widgetsRects.add(getWidgetRect(x, y, used, board));
300                 }
301             }
302         }
303         return widgetsRects;
304     }
305 
getIconPoints(char[][] board)306     private static List<IconPoint> getIconPoints(char[][] board) {
307         List<IconPoint> iconPoints = new ArrayList<>();
308         for (int x = 0; x < board.length; x++) {
309             for (int y = 0; y < board[0].length; y++) {
310                 if (isIcon(board[x][y])) {
311                     iconPoints.add(new IconPoint(new Point(x, y), board[x][y]));
312                 }
313             }
314         }
315         return iconPoints;
316     }
317 
getFolderPoints(char[][] board)318     private static List<FolderPoint> getFolderPoints(char[][] board) {
319         List<FolderPoint> folderPoints = new ArrayList<>();
320         for (int x = 0; x < board.length; x++) {
321             for (int y = 0; y < board[0].length; y++) {
322                 if (isFolder(board[x][y])) {
323                     folderPoints.add(new FolderPoint(new Point(x, y), board[x][y]));
324                 }
325             }
326         }
327         return folderPoints;
328     }
329 
getMainFromList(List<CellLayoutBoard> boards)330     public static WidgetRect getMainFromList(List<CellLayoutBoard> boards) {
331         for (CellLayoutBoard board : boards) {
332             WidgetRect main = board.getMain();
333             if (main != null) {
334                 return main;
335             }
336         }
337         return null;
338     }
339 
getWidgetIn(List<CellLayoutBoard> boards, int x, int y)340     public static WidgetRect getWidgetIn(List<CellLayoutBoard> boards, int x, int y) {
341         for (CellLayoutBoard board : boards) {
342             WidgetRect main = board.getWidgetAt(x, y);
343             if (main != null) {
344                 return main;
345             }
346             x -= board.mWidth;
347         }
348         return null;
349     }
350 
boardFromString(String boardStr)351     public static CellLayoutBoard boardFromString(String boardStr) {
352         String[] lines = boardStr.split("\n");
353         CellLayoutBoard board = new CellLayoutBoard();
354 
355         for (int y = 0; y < lines.length; y++) {
356             String line = lines[y];
357             for (int x = 0; x < line.length(); x++) {
358                 char c = line.charAt(x);
359                 if (c != CellType.EMPTY) {
360                     board.mWidget[x][y] = line.charAt(x);
361                 }
362             }
363         }
364         board.mHeight = lines.length;
365         board.mWidth = lines[0].length();
366         board.mWidgetsRects = getRects(board.mWidget);
367         board.mWidgetsRects.forEach(widgetRect -> {
368             if (widgetRect.mType == CellType.MAIN_WIDGET) {
369                 board.mMain = widgetRect;
370             }
371             board.mWidgetsMap.put(widgetRect.mType, widgetRect);
372         });
373         board.mIconPoints = getIconPoints(board.mWidget);
374         board.mFolderPoints = getFolderPoints(board.mWidget);
375         return board;
376     }
377 
toString(int maxX, int maxY)378     public String toString(int maxX, int maxY) {
379         StringBuilder s = new StringBuilder();
380         s.append("board: ");
381         s.append(maxX);
382         s.append("x");
383         s.append(maxY);
384         s.append("\n");
385         maxX = Math.min(maxX, mWidget.length);
386         maxY = Math.min(maxY, mWidget[0].length);
387         for (int y = 0; y < maxY; y++) {
388             for (int x = 0; x < maxX; x++) {
389                 s.append(mWidget[x][y]);
390             }
391             s.append('\n');
392         }
393         return s.toString();
394     }
395 
396     @Override
toString()397     public String toString() {
398         return toString(mWidth, mHeight);
399     }
400 
boardListFromString(String boardsStr)401     public static List<CellLayoutBoard> boardListFromString(String boardsStr) {
402         String[] lines = boardsStr.split("\n");
403         ArrayList<String> individualBoards = new ArrayList<>();
404         ArrayList<CellLayoutBoard> boards = new ArrayList<>();
405         for (String line : lines) {
406             String[] boardSegment = line.split("\\|");
407             for (int i = 0; i < boardSegment.length; i++) {
408                 if (i >= individualBoards.size()) {
409                     individualBoards.add(boardSegment[i]);
410                 } else {
411                     individualBoards.set(i, individualBoards.get(i) + "\n" + boardSegment[i]);
412                 }
413             }
414         }
415         for (String board : individualBoards) {
416             boards.add(CellLayoutBoard.boardFromString(board));
417         }
418         return boards;
419     }
420 
getWidth()421     public int getWidth() {
422         return mWidth;
423     }
424 
getHeight()425     public int getHeight() {
426         return mHeight;
427     }
428 }
429