• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.widget.util;
17 
18 import android.content.Context;
19 import android.util.Size;
20 
21 import androidx.annotation.Px;
22 
23 import com.android.launcher3.DeviceProfile;
24 import com.android.launcher3.model.WidgetItem;
25 import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
26 
27 import java.util.ArrayList;
28 import java.util.Comparator;
29 import java.util.List;
30 import java.util.stream.Collectors;
31 
32 /** An utility class which groups {@link WidgetItem}s into a table. */
33 public final class WidgetsTableUtils {
34     private static final int MAX_ITEMS_IN_ROW = 3;
35 
36     /**
37      * Groups widgets in the following order:
38      * 1. Widgets always go before shortcuts.
39      * 2. Widgets with smaller vertical spans will be shown first.
40      * 3. If widgets have the same vertical spans, then widgets with a smaller horizontal spans will
41      *    go first.
42      * 4. If both widgets have the same horizontal and vertical spans, they will use the same order
43      *    from the given {@code widgetItems}.
44      */
45     private static final Comparator<WidgetItem> WIDGET_SHORTCUT_COMPARATOR = (item, otherItem) -> {
46         if (item.widgetInfo != null && otherItem.widgetInfo == null) return -1;
47 
48         if (item.widgetInfo == null && otherItem.widgetInfo != null) return 1;
49         if (item.spanY == otherItem.spanY) {
50             if (item.spanX == otherItem.spanX) return 0;
51             return item.spanX > otherItem.spanX ? 1 : -1;
52         }
53         return item.spanY > otherItem.spanY ? 1 : -1;
54     };
55 
56     /**
57      * Comparator that enables displaying rows in increasing order of their size (totalW * H);
58      * except for shortcuts which always show at the bottom.
59      */
60     public static final Comparator<ArrayList<WidgetItem>> WIDGETS_TABLE_ROW_SIZE_COMPARATOR =
61             Comparator.comparingInt(row -> {
62                 if (row.stream().anyMatch(WidgetItem::isShortcut)) {
63                     return Integer.MAX_VALUE;
64                 } else {
65                     int rowWidth = row.stream().mapToInt(w -> w.spanX).sum();
66                     int rowHeight = row.get(0).spanY;
67                     return (rowWidth * rowHeight);
68                 }
69             });
70 
71     /**
72      * Comparator that enables displaying rows with more number of items at the top, and then
73      * rest of widgets shown in increasing order of their size (totalW * H).
74      */
75     public static final Comparator<ArrayList<WidgetItem>> WIDGETS_TABLE_ROW_COUNT_COMPARATOR =
76             Comparator.comparingInt(row -> {
77                 if (row.size() > 1) {
78                     return -row.size();
79                 } else {
80                     int rowWidth = row.stream().mapToInt(w -> w.spanX).sum();
81                     int rowHeight = row.get(0).spanY;
82                     return (rowWidth * rowHeight);
83                 }
84             });
85 
86     /**
87      * Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI
88      * table. This takes liberty to rearrange widgets to make the table visually appealing.
89      */
groupWidgetItemsUsingRowPxWithReordering( List<WidgetItem> widgetItems, Context context, final DeviceProfile dp, final @Px int rowPx, final @Px int cellPadding)90     public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithReordering(
91             List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
92             final @Px int rowPx, final @Px int cellPadding) {
93         List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR)
94                 .collect(Collectors.toList());
95         List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
96                 sortedWidgetItems, context, dp, rowPx,
97                 cellPadding);
98         return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).collect(Collectors.toList());
99     }
100 
101     /**
102      * Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while
103      * maintaining their order. This function is a variant of
104      * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget container's
105      * pixels for calculation.
106      *
107      * <p>Grouping:
108      * 1. Widgets and shortcuts never group together in the same row.
109      * 2. Widgets are grouped together only if they have same preview container size.
110      * 3. Widgets are grouped together in the same row until the total of individual container sizes
111      *    exceed the total allowed pixels for the row.
112      * 3. The ordered shortcuts are grouped together in the same row until their individual
113      *    occupying pixels exceed the total allowed pixels for the cell.
114      * 4. If there is only one widget in a row, its width may exceed the {@code rowPx}.
115      *
116      * <p>See WidgetTableUtilsTest
117      */
groupWidgetItemsUsingRowPxWithoutReordering( List<WidgetItem> widgetItems, Context context, final DeviceProfile dp, final @Px int rowPx, final @Px int cellPadding)118     public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithoutReordering(
119             List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
120             final @Px int rowPx, final @Px int cellPadding) {
121         List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>();
122         ArrayList<WidgetItem> widgetItemsAtRow = null;
123         // A row displays only items of same container size.
124         WidgetPreviewContainerSize containerSizeForRow = null;
125         @Px int currentRowWidth = 0;
126 
127         for (WidgetItem widgetItem : widgetItems) {
128             if (widgetItemsAtRow == null) {
129                 widgetItemsAtRow = new ArrayList<>();
130                 widgetItemsTable.add(widgetItemsAtRow);
131             }
132             int numOfWidgetItems = widgetItemsAtRow.size();
133 
134             WidgetPreviewContainerSize containerSize =
135                     WidgetPreviewContainerSize.Companion.forItem(widgetItem, dp);
136             Size containerSizePx = WidgetSizes.getWidgetSizePx(dp, containerSize.spanX,
137                     containerSize.spanY);
138             @Px int containerWidth = containerSizePx.getWidth() + (2 * cellPadding);
139 
140             if (numOfWidgetItems == 0) {
141                 widgetItemsAtRow.add(widgetItem);
142                 containerSizeForRow = containerSize;
143                 currentRowWidth = containerWidth;
144             } else if (widgetItemsAtRow.size() < MAX_ITEMS_IN_ROW
145                     && (currentRowWidth + containerWidth) <= rowPx
146                     && widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))
147                     && containerSize.equals(containerSizeForRow)) {
148                 // Group items in the same row if
149                 // 1. they are with the same type, i.e. a row can only have widgets or shortcuts but
150                 //    never a mix of both.
151                 // 2. Each widget in the given row has same preview container size.
152                 widgetItemsAtRow.add(widgetItem);
153                 currentRowWidth += containerWidth;
154             } else {
155                 widgetItemsAtRow = new ArrayList<>();
156                 widgetItemsTable.add(widgetItemsAtRow);
157                 widgetItemsAtRow.add(widgetItem);
158                 containerSizeForRow = containerSize;
159                 currentRowWidth = containerWidth;
160             }
161         }
162         return widgetItemsTable;
163     }
164 }
165