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 * Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI 73 * table. This takes liberty to rearrange widgets to make the table visually appealing. 74 */ groupWidgetItemsUsingRowPxWithReordering( List<WidgetItem> widgetItems, Context context, final DeviceProfile dp, final @Px int rowPx, final @Px int cellPadding)75 public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithReordering( 76 List<WidgetItem> widgetItems, Context context, final DeviceProfile dp, 77 final @Px int rowPx, final @Px int cellPadding) { 78 List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR) 79 .collect(Collectors.toList()); 80 List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering( 81 sortedWidgetItems, context, dp, rowPx, 82 cellPadding); 83 return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList(); 84 } 85 86 /** 87 * Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while 88 * maintaining their order. This function is a variant of 89 * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget container's 90 * pixels for calculation. 91 * 92 * <p>Grouping: 93 * 1. Widgets and shortcuts never group together in the same row. 94 * 2. Widgets are grouped together only if they have same preview container size. 95 * 3. Widgets are grouped together in the same row until the total of individual container sizes 96 * exceed the total allowed pixels for the row. 97 * 3. The ordered shortcuts are grouped together in the same row until their individual 98 * occupying pixels exceed the total allowed pixels for the cell. 99 * 4. If there is only one widget in a row, its width may exceed the {@code rowPx}. 100 * 101 * <p>See WidgetTableUtilsTest 102 */ groupWidgetItemsUsingRowPxWithoutReordering( List<WidgetItem> widgetItems, Context context, final DeviceProfile dp, final @Px int rowPx, final @Px int cellPadding)103 public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithoutReordering( 104 List<WidgetItem> widgetItems, Context context, final DeviceProfile dp, 105 final @Px int rowPx, final @Px int cellPadding) { 106 List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>(); 107 ArrayList<WidgetItem> widgetItemsAtRow = null; 108 // A row displays only items of same container size. 109 WidgetPreviewContainerSize containerSizeForRow = null; 110 @Px int currentRowWidth = 0; 111 112 for (WidgetItem widgetItem : widgetItems) { 113 if (widgetItemsAtRow == null) { 114 widgetItemsAtRow = new ArrayList<>(); 115 widgetItemsTable.add(widgetItemsAtRow); 116 } 117 int numOfWidgetItems = widgetItemsAtRow.size(); 118 119 WidgetPreviewContainerSize containerSize = 120 WidgetPreviewContainerSize.Companion.forItem(widgetItem, dp); 121 Size containerSizePx = WidgetSizes.getWidgetSizePx(dp, containerSize.spanX, 122 containerSize.spanY); 123 @Px int containerWidth = containerSizePx.getWidth() + (2 * cellPadding); 124 125 if (numOfWidgetItems == 0) { 126 widgetItemsAtRow.add(widgetItem); 127 containerSizeForRow = containerSize; 128 currentRowWidth = containerWidth; 129 } else if (widgetItemsAtRow.size() < MAX_ITEMS_IN_ROW 130 && (currentRowWidth + containerWidth) <= rowPx 131 && widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1)) 132 && containerSize.equals(containerSizeForRow)) { 133 // Group items in the same row if 134 // 1. they are with the same type, i.e. a row can only have widgets or shortcuts but 135 // never a mix of both. 136 // 2. Each widget in the given row has same preview container size. 137 widgetItemsAtRow.add(widgetItem); 138 currentRowWidth += containerWidth; 139 } else { 140 widgetItemsAtRow = new ArrayList<>(); 141 widgetItemsTable.add(widgetItemsAtRow); 142 widgetItemsAtRow.add(widgetItem); 143 containerSizeForRow = containerSize; 144 currentRowWidth = containerWidth; 145 } 146 } 147 return widgetItemsTable; 148 } 149 } 150