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