• 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 
20 import androidx.annotation.Px;
21 
22 import com.android.launcher3.DeviceProfile;
23 import com.android.launcher3.model.WidgetItem;
24 
25 import java.util.ArrayList;
26 import java.util.Comparator;
27 import java.util.List;
28 import java.util.stream.Collectors;
29 
30 /** An utility class which groups {@link WidgetItem}s into a table. */
31 public final class WidgetsTableUtils {
32 
33     /**
34      * Groups widgets in the following order:
35      * 1. Widgets always go before shortcuts.
36      * 2. Widgets with smaller horizontal spans will be shown first.
37      * 3. If widgets have the same horizontal spans, then widgets with a smaller vertical spans will
38      *    go first.
39      * 4. If both widgets have the same horizontal and vertical spans, they will use the same order
40      *    from the given {@code widgetItems}.
41      */
42     private static final Comparator<WidgetItem> WIDGET_SHORTCUT_COMPARATOR = (item, otherItem) -> {
43         if (item.widgetInfo != null && otherItem.widgetInfo == null) return -1;
44 
45         if (item.widgetInfo == null && otherItem.widgetInfo != null) return 1;
46         if (item.spanX == otherItem.spanX) {
47             if (item.spanY == otherItem.spanY) return 0;
48             return item.spanY > otherItem.spanY ? 1 : -1;
49         }
50         return item.spanX > otherItem.spanX ? 1 : -1;
51     };
52 
53     /**
54      * Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI
55      * table. This takes liberty to rearrange widgets to make the table visually appealing.
56      */
groupWidgetItemsUsingRowPxWithReordering( List<WidgetItem> widgetItems, Context context, final DeviceProfile dp, final @Px int rowPx, final @Px int cellPadding)57     public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithReordering(
58             List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
59             final @Px int rowPx, final @Px int cellPadding) {
60         List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR)
61                 .collect(Collectors.toList());
62         return groupWidgetItemsUsingRowPxWithoutReordering(sortedWidgetItems, context, dp, rowPx,
63                 cellPadding);
64     }
65 
66     /**
67      * Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while
68      * maintaining their order. This function is a variant of
69      * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget pixels for
70      * calculation.
71      *
72      * <p>Grouping:
73      * 1. Widgets and shortcuts never group together in the same row.
74      * 2. The ordered widgets are grouped together in the same row until their individual occupying
75      *    pixels exceed the total allowed pixels for the cell.
76      * 3. The ordered shortcuts are grouped together in the same row until their individual
77      *    occupying pixels exceed the total allowed pixels for the cell.
78      * 4. If there is only one widget in a row, its width may exceed the {@code rowPx}.
79      *
80      * <p>Let's say the {@code rowPx} is set to 600 and we have 5 widgets. Widgets can be grouped
81      * in the same row if each of their individual occupying pixels does not exceed
82      * {@code rowPx} / 5 - 2 * {@code cellPadding}.
83      * Example 1: Row 1: 200x200, 200x300, 100x100. Average horizontal pixels is 200 and no widgets
84      * exceed that width. This is okay.
85      * Example 2: Row 1: 200x200, 400x300, 100x100. Average horizontal pixels is 200 and one widget
86      * exceed that width. This is not allowed.
87      * Example 3: Row 1: 700x400. This is okay because this is the only item in the row.
88      */
groupWidgetItemsUsingRowPxWithoutReordering( List<WidgetItem> widgetItems, Context context, final DeviceProfile dp, final @Px int rowPx, final @Px int cellPadding)89     public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithoutReordering(
90             List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
91             final @Px int rowPx, final @Px int cellPadding) {
92 
93         List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>();
94         ArrayList<WidgetItem> widgetItemsAtRow = null;
95         for (WidgetItem widgetItem : widgetItems) {
96             if (widgetItemsAtRow == null) {
97                 widgetItemsAtRow = new ArrayList<>();
98                 widgetItemsTable.add(widgetItemsAtRow);
99             }
100             int numOfWidgetItems = widgetItemsAtRow.size();
101             @Px int individualSpan = (rowPx / (numOfWidgetItems + 1)) - (2 * cellPadding);
102             if (numOfWidgetItems == 0) {
103                 widgetItemsAtRow.add(widgetItem);
104             } else if (
105                     // Since the size of the widget cell is determined by dividing the maximum span
106                     // pixels evenly, making sure that each widget would have enough span pixels to
107                     // show their contents.
108                     widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))
109                     && widgetItemsAtRow.stream().allMatch(
110                             item -> WidgetSizes.getWidgetItemSizePx(context, dp, item)
111                                     .getWidth() <= individualSpan)
112                     && WidgetSizes.getWidgetItemSizePx(context, dp, widgetItem)
113                             .getWidth() <= individualSpan) {
114                 // Group items in the same row if
115                 // 1. they are with the same type, i.e. a row can only have widgets or shortcuts but
116                 //    never a mix of both.
117                 // 2. Each widget will have horizontal cell span pixels that is at least as large as
118                 //    it is required to fit in the horizontal content, unless the widget horizontal
119                 //    span pixels is larger than the maximum allowed.
120                 //    If an item has horizontal span pixels larger than the maximum allowed pixels
121                 //    per row, we just place it in its own row regardless of the horizontal span
122                 //    limit.
123                 widgetItemsAtRow.add(widgetItem);
124             } else {
125                 widgetItemsAtRow = new ArrayList<>();
126                 widgetItemsTable.add(widgetItemsAtRow);
127                 widgetItemsAtRow.add(widgetItem);
128             }
129         }
130         return widgetItemsTable;
131     }
132 }
133