Files
Lawnchair/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
T
Sebastian Franco e7c3d3d4ce Parsing test boards from text files to be able to add more and bigger tests
It also adds an assets folder inside of Launcher3 tests.

It also fixes a bug in fullReorder.

Test: atest ReorderWidgets
Flag: none
Bug: 229292911
Change-Id: I7dadd42a3057d353326691163bb7db71a2e9dcc4
2023-04-13 17:14:51 +00:00

414 lines
13 KiB
Java

/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.celllayout;
import android.graphics.Point;
import android.graphics.Rect;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;
public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
private boolean intersects(Rect r1, Rect r2) {
// If one rectangle is on left side of other
if (r1.left > r2.right || r2.left > r1.right) {
return false;
}
// If one rectangle is above other
if (r1.bottom > r2.top || r2.bottom > r1.top) {
return false;
}
return true;
}
private boolean overlapsWithIgnored(Set<Rect> ignoredRectangles, Rect rect) {
for (Rect ignoredRect : ignoredRectangles) {
// Using the built in intersects doesn't work because it doesn't account for area 0
if (intersects(ignoredRect, rect)) {
return true;
}
}
return false;
}
@Override
public int compareTo(CellLayoutBoard cellLayoutBoard) {
// to be equal they need to have the same number of widgets and the same dimensions
// their order can be different
Set<Rect> widgetsSet = new HashSet<>();
Set<Rect> ignoredRectangles = new HashSet<>();
for (WidgetRect rect : mWidgetsRects) {
if (rect.shouldIgnore()) {
ignoredRectangles.add(rect.mBounds);
} else {
widgetsSet.add(rect.mBounds);
}
}
for (WidgetRect rect : cellLayoutBoard.mWidgetsRects) {
// ignore rectangles overlapping with the area marked by x
if (overlapsWithIgnored(ignoredRectangles, rect.mBounds)) {
continue;
}
if (!widgetsSet.contains(rect.mBounds)) {
return -1;
}
widgetsSet.remove(rect.mBounds);
}
if (!widgetsSet.isEmpty()) {
return 1;
}
// to be equal they need to have the same number of icons their order can be different
Set<Point> iconsSet = new HashSet<>();
mIconPoints.forEach(icon -> iconsSet.add(icon.getCoord()));
for (IconPoint icon : cellLayoutBoard.mIconPoints) {
if (!iconsSet.contains(icon.getCoord())) {
return -1;
}
iconsSet.remove(icon.getCoord());
}
if (!iconsSet.isEmpty()) {
return 1;
}
return 0;
}
public static class CellType {
// The cells marked by this will be filled by 1x1 widgets and will be ignored when
// validating
public static final char IGNORE = 'x';
// The cells marked by this will be filled by app icons
public static final char ICON = 'i';
// Empty space
public static final char EMPTY = '-';
// Widget that will be saved as "main widget" for easier retrieval
public static final char MAIN_WIDGET = 'm';
// Everything else will be consider a widget
}
public static class WidgetRect {
public char mType;
public Rect mBounds;
WidgetRect(char type, Rect bounds) {
this.mType = type;
this.mBounds = bounds;
}
int getSpanX() {
return mBounds.right - mBounds.left + 1;
}
int getSpanY() {
return mBounds.top - mBounds.bottom + 1;
}
int getCellX() {
return mBounds.left;
}
int getCellY() {
return mBounds.bottom;
}
boolean shouldIgnore() {
return this.mType == CellType.IGNORE;
}
@Override
public String toString() {
return "WidgetRect type = " + mType + " bounds = " + mBounds.toString();
}
}
public static class IconPoint {
public Point coord;
public char mType;
public IconPoint(Point coord, char type) {
this.coord = coord;
mType = type;
}
public char getType() {
return mType;
}
public void setType(char type) {
mType = type;
}
public Point getCoord() {
return coord;
}
public void setCoord(Point coord) {
this.coord = coord;
}
}
static final int INFINITE = 99999;
char[][] mWidget = new char[30][30];
List<WidgetRect> mWidgetsRects = new ArrayList<>();
Map<Character, WidgetRect> mWidgetsMap = new HashMap<>();
List<IconPoint> mIconPoints = new ArrayList<>();
WidgetRect mMain = null;
CellLayoutBoard() {
for (int x = 0; x < mWidget.length; x++) {
for (int y = 0; y < mWidget[0].length; y++) {
mWidget[x][y] = CellType.EMPTY;
}
}
}
public List<WidgetRect> getWidgets() {
return mWidgetsRects;
}
public List<IconPoint> getIcons() {
return mIconPoints;
}
public WidgetRect getMain() {
return mMain;
}
public WidgetRect getWidgetRect(char c) {
return mWidgetsMap.get(c);
}
private void removeWidgetFromBoard(WidgetRect widget) {
for (int xi = widget.mBounds.left; xi < widget.mBounds.right; xi++) {
for (int yi = widget.mBounds.top; yi < widget.mBounds.bottom; yi++) {
mWidget[xi][yi] = '-';
}
}
}
private void removeOverlappingItems(Rect rect) {
// Remove overlapping widgets and remove them from the board
mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
if (rect.intersect(widget.mBounds)) {
removeWidgetFromBoard(widget);
return false;
}
return true;
}).collect(Collectors.toList());
// Remove overlapping icons and remove them from the board
mIconPoints = mIconPoints.stream().filter(iconPoint -> {
int x = iconPoint.coord.x;
int y = iconPoint.coord.y;
if (rect.contains(x, y)) {
mWidget[x][y] = '-';
return false;
}
return true;
}).collect(Collectors.toList());
}
private void removeOverlappingItems(Point p) {
// Remove overlapping widgets and remove them from the board
mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
if (widget.mBounds.contains(p.x, p.y)) {
removeWidgetFromBoard(widget);
return false;
}
return true;
}).collect(Collectors.toList());
// Remove overlapping icons and remove them from the board
mIconPoints = mIconPoints.stream().filter(iconPoint -> {
int x = iconPoint.coord.x;
int y = iconPoint.coord.y;
if (p.x == x && p.y == y) {
mWidget[x][y] = '-';
return false;
}
return true;
}).collect(Collectors.toList());
}
public void addWidget(int x, int y, int spanX, int spanY, char type) {
Rect rect = new Rect(x, y + spanY - 1, x + spanX - 1, y);
removeOverlappingItems(rect);
WidgetRect widgetRect = new WidgetRect(type, rect);
mWidgetsRects.add(widgetRect);
for (int xi = rect.left; xi < rect.right + 1; xi++) {
for (int yi = rect.bottom; yi < rect.top + 1; yi++) {
mWidget[xi][yi] = type;
}
}
}
public void addIcon(int x, int y) {
Point iconCoord = new Point(x, y);
removeOverlappingItems(iconCoord);
mIconPoints.add(new IconPoint(iconCoord, CellType.ICON));
mWidget[x][y] = 'i';
}
public static WidgetRect getWidgetRect(int x, int y, Set<Point> used, char[][] board) {
char type = board[x][y];
Queue<Point> search = new ArrayDeque<Point>();
Point current = new Point(x, y);
search.add(current);
used.add(current);
List<Point> neighbors = new ArrayList<>(List.of(
new Point(-1, 0),
new Point(0, -1),
new Point(1, 0),
new Point(0, 1))
);
Rect widgetRect = new Rect(INFINITE, -INFINITE, -INFINITE, INFINITE);
while (!search.isEmpty()) {
current = search.poll();
widgetRect.top = Math.max(widgetRect.top, current.y);
widgetRect.right = Math.max(widgetRect.right, current.x);
widgetRect.bottom = Math.min(widgetRect.bottom, current.y);
widgetRect.left = Math.min(widgetRect.left, current.x);
for (Point p : neighbors) {
Point next = new Point(current.x + p.x, current.y + p.y);
if (next.x < 0 || next.x >= board.length) continue;
if (next.y < 0 || next.y >= board[0].length) continue;
if (board[next.x][next.y] == type && !used.contains(next)) {
used.add(next);
search.add(next);
}
}
}
return new WidgetRect(type, widgetRect);
}
public static boolean isFolder(char type) {
return type >= 'A' && type <= 'Z';
}
public static boolean isWidget(char type) {
return type != CellType.ICON && type != CellType.EMPTY && (type >= 'a' && type <= 'z');
}
public static boolean isIcon(char type) {
return type == CellType.ICON;
}
private static List<WidgetRect> getRects(char[][] board) {
Set<Point> used = new HashSet<>();
List<WidgetRect> widgetsRects = new ArrayList<>();
for (int x = 0; x < board.length; x++) {
for (int y = 0; y < board[0].length; y++) {
if (!used.contains(new Point(x, y)) && isWidget(board[x][y])) {
widgetsRects.add(getWidgetRect(x, y, used, board));
}
}
}
return widgetsRects;
}
private static List<IconPoint> getIconPoints(char[][] board) {
List<IconPoint> iconPoints = new ArrayList<>();
for (int x = 0; x < board.length; x++) {
for (int y = 0; y < board[0].length; y++) {
if (isIcon(board[x][y])) {
iconPoints.add(new IconPoint(new Point(x, y), board[x][y]));
}
}
}
return iconPoints;
}
public static WidgetRect getMainFromList(List<CellLayoutBoard> boards) {
for (CellLayoutBoard board : boards) {
WidgetRect main = board.getMain();
if (main != null) {
return main;
}
}
return null;
}
public static CellLayoutBoard boardFromString(String boardStr) {
String[] lines = boardStr.split("\n");
CellLayoutBoard board = new CellLayoutBoard();
for (int y = 0; y < lines.length; y++) {
String line = lines[y];
for (int x = 0; x < line.length(); x++) {
char c = line.charAt(x);
if (c != CellType.EMPTY) {
board.mWidget[x][y] = line.charAt(x);
}
}
}
board.mWidgetsRects = getRects(board.mWidget);
board.mWidgetsRects.forEach(widgetRect -> {
if (widgetRect.mType == CellType.MAIN_WIDGET) {
board.mMain = widgetRect;
}
board.mWidgetsMap.put(widgetRect.mType, widgetRect);
});
board.mIconPoints = getIconPoints(board.mWidget);
return board;
}
public String toString(int maxX, int maxY) {
StringBuilder s = new StringBuilder();
maxX = Math.min(maxX, mWidget.length);
maxY = Math.min(maxY, mWidget[0].length);
for (int y = 0; y < maxY; y++) {
for (int x = 0; x < maxX; x++) {
s.append(mWidget[x][y]);
}
s.append('\n');
}
return s.toString();
}
public static List<CellLayoutBoard> boardListFromString(String boardsStr) {
String[] lines = boardsStr.split("\n");
ArrayList<String> individualBoards = new ArrayList<>();
ArrayList<CellLayoutBoard> boards = new ArrayList<>();
for (String line : lines) {
String[] boardSegment = line.split("\\|");
for (int i = 0; i < boardSegment.length; i++) {
if (i >= individualBoards.size()) {
individualBoards.add(boardSegment[i]);
} else {
individualBoards.set(i, individualBoards.get(i) + "\n" + boardSegment[i]);
}
}
}
for (String board : individualBoards) {
boards.add(CellLayoutBoard.boardFromString(board));
}
return boards;
}
}