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