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 com.android.launcher3.model.WidgetItem; 19 20 import java.util.ArrayList; 21 import java.util.Comparator; 22 import java.util.List; 23 import java.util.stream.Collectors; 24 25 /** An utility class which groups {@link WidgetItem}s into a table. */ 26 public final class WidgetsTableUtils { 27 28 /** 29 * Groups widgets in the following order: 30 * 1. Widgets always go before shortcuts. 31 * 2. Widgets with smaller horizontal spans will be shown first. 32 * 3. If widgets have the same horizontal spans, then widgets with a smaller vertical spans will 33 * go first. 34 * 4. If both widgets have the same horizontal and vertical spans, they will use the same order 35 * from the given {@code widgetItems}. 36 */ 37 private static final Comparator<WidgetItem> WIDGET_SHORTCUT_COMPARATOR = (item, otherItem) -> { 38 if (item.widgetInfo != null && otherItem.widgetInfo == null) return -1; 39 40 if (item.widgetInfo == null && otherItem.widgetInfo != null) return 1; 41 if (item.spanX == otherItem.spanX) { 42 if (item.spanY == otherItem.spanY) return 0; 43 return item.spanY > otherItem.spanY ? 1 : -1; 44 } 45 return item.spanX > otherItem.spanX ? 1 : -1; 46 }; 47 48 /** 49 * Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI 50 * table. This takes liberty to rearrange widgets to make the table visually appealing. 51 */ groupWidgetItemsIntoTableWithReordering( List<WidgetItem> widgetItems, final int maxSpansPerRow)52 public static List<ArrayList<WidgetItem>> groupWidgetItemsIntoTableWithReordering( 53 List<WidgetItem> widgetItems, final int maxSpansPerRow) { 54 List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR) 55 .collect(Collectors.toList()); 56 return groupWidgetItemsIntoTableWithoutReordering(sortedWidgetItems, maxSpansPerRow); 57 } 58 59 /** 60 * Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while 61 * maintaining their order. 62 * 63 * <p>Grouping: 64 * 1. Widgets and shortcuts never group together in the same row. 65 * 2. The ordered widgets are grouped together in the same row until their total horizontal 66 * spans exceed the {@code maxSpansPerRow} - 1. 67 * 3. The order shortcuts are grouped together in the same row until their total horizontal 68 * spans exceed the {@code maxSpansPerRow} - 1. 69 * 4. If there is only one widget in a row, its width may exceed the {@code maxSpansPerRow}. 70 * 71 * <p>Let's say the {@code maxSpansPerRow} is set to 6. Widgets can be grouped in the same row 72 * if their total horizontal spans added don't exceed 5. 73 * Example 1: Row 1: 2x2, 2x3, 1x1. Total horizontal spans is 5. This is okay. 74 * Example 2: Row 1: 2x2, 4x3, 1x1. the total horizontal spans is 7. This is wrong. 4x3 and 1x1 75 * should be moved to a new row. 76 * Example 3: Row 1: 6x4. This is okay because this is the only item in the row. 77 */ groupWidgetItemsIntoTableWithoutReordering( List<WidgetItem> widgetItems, final int maxSpansPerRow)78 public static List<ArrayList<WidgetItem>> groupWidgetItemsIntoTableWithoutReordering( 79 List<WidgetItem> widgetItems, final int maxSpansPerRow) { 80 81 List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>(); 82 ArrayList<WidgetItem> widgetItemsAtRow = null; 83 for (WidgetItem widgetItem : widgetItems) { 84 if (widgetItemsAtRow == null) { 85 widgetItemsAtRow = new ArrayList<>(); 86 widgetItemsTable.add(widgetItemsAtRow); 87 } 88 int numOfWidgetItems = widgetItemsAtRow.size(); 89 int totalHorizontalSpan = widgetItemsAtRow.stream().map(item -> item.spanX) 90 .reduce(/* default= */ 0, Integer::sum); 91 int totalHorizontalSpanAfterAddingWidget = widgetItem.spanX + totalHorizontalSpan; 92 if (numOfWidgetItems == 0) { 93 widgetItemsAtRow.add(widgetItem); 94 } else if ( 95 // The max spans per row is reduced by 1 to ensure we don't pack too many 96 // 1xn widgets on the same row, which may reduce the space for rendering a 97 // widget's description. 98 totalHorizontalSpanAfterAddingWidget <= maxSpansPerRow - 1 99 && widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))) { 100 // Group items in the same row if 101 // 1. they are with the same type, i.e. a row can only have widgets or shortcuts but 102 // never a mix of both. 103 // 2. the total number of horizontal spans are smaller than or equal to 104 // MAX_SPAN_PER_ROW. If an item has a horizontal span > MAX_SPAN_PER_ROW, we just 105 // place it in its own row regardless of the horizontal span limit. 106 widgetItemsAtRow.add(widgetItem); 107 } else { 108 widgetItemsAtRow = new ArrayList<>(); 109 widgetItemsTable.add(widgetItemsAtRow); 110 widgetItemsAtRow.add(widgetItem); 111 } 112 } 113 return widgetItemsTable; 114 } 115 } 116