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