diff --git a/res/values/config.xml b/res/values/config.xml index 27211fd0f0..8f9731c534 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -217,4 +217,28 @@ true + + + 52dp + 55dp + 57dp + 59dp + 61dp + 63dp + 66dp + 72dp + 79dp + + + + @dimen/iconSize48dp + @dimen/iconSize50dp + @dimen/iconSize52dp + @dimen/iconSize54dp + @dimen/iconSize56dp + @dimen/iconSize58dp + @dimen/iconSize60dp + @dimen/iconSize66dp + @dimen/iconSize72dp + diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 0498032108..a16627154b 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -58,6 +58,7 @@ import com.android.launcher3.responsive.CalculatedAllAppsSpec; import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; +import com.android.launcher3.util.IconSizeSteps; import com.android.launcher3.util.ResourceHelper; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.workspace.CalculatedWorkspaceSpec; @@ -83,6 +84,7 @@ public class DeviceProfile { public final InvariantDeviceProfile inv; private final Info mInfo; private final DisplayMetrics mMetrics; + private final IconSizeSteps mIconSizeSteps; // Device properties public final boolean isTablet; @@ -330,6 +332,8 @@ public class DeviceProfile { final Resources res = context.getResources(); mMetrics = res.getDisplayMetrics(); + mIconSizeSteps = mIsResponsiveGrid ? new IconSizeSteps(res) : null; + // Determine sizes. widthPx = windowBounds.bounds.width(); heightPx = windowBounds.bounds.height(); @@ -535,12 +539,16 @@ public class DeviceProfile { // for the available height to be correct if (mIsResponsiveGrid) { mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId)); + int availableResponsiveWidth = + availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0); + // don't use availableHeightPx because it subtracts bottom padding, + // but the workspace go behind it + int availableResponsiveHeight = + heightPx - mInsets.top - (isVerticalBarLayout() ? 0 : hotseatBarSizePx); mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns, - availableWidthPx); + availableResponsiveWidth); mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows, - // don't use availableHeightPx because it subtracts bottom padding, - // but the hotseat go behind it - heightPx - mInsets.top - hotseatBarSizePx); + availableResponsiveHeight); mAllAppsSpecs = new AllAppsSpecs(new ResourceHelper(context, inv.allAppsSpecsId)); mAllAppsResponsiveWidthSpec = mAllAppsSpecs.getCalculatedWidthSpec(inv.numColumns, @@ -926,9 +934,32 @@ public class DeviceProfile { cellWidthPx = mResponsiveWidthSpec.getCellSizePx(); cellHeightPx = mResponsiveHeightSpec.getCellSizePx(); - cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; - // TODO(b/283929701): decrease icon size if content doesn't fit on cell + if (cellWidthPx < iconSizePx) { + // get a smaller icon size + iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx); + // calculate new cellContentHeight + cellContentHeight = iconSizePx + cellTextAndPaddingHeight; + } + + while (iconSizePx > mIconSizeSteps.minimumIconSize() + && cellContentHeight > cellHeightPx) { + int extraHeightRequired = cellContentHeight - cellHeightPx; + int newPadding = iconDrawablePaddingPx - extraHeightRequired; + if (newPadding >= 0) { + // Responsive uses the padding without scaling + iconDrawablePaddingPx = iconDrawablePaddingOriginalPx = newPadding; + cellTextAndPaddingHeight = + iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); + } else { + // get a smaller icon size + iconSizePx = mIconSizeSteps.getNextLowerIconSize(iconSizePx); + } + // calculate new cellContentHeight + cellContentHeight = iconSizePx + cellTextAndPaddingHeight; + } + + cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; } else if (mIsScalableGrid) { cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale); cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale); diff --git a/src/com/android/launcher3/util/IconSizeSteps.kt b/src/com/android/launcher3/util/IconSizeSteps.kt new file mode 100644 index 0000000000..2a5afe0ce2 --- /dev/null +++ b/src/com/android/launcher3/util/IconSizeSteps.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util + +import android.content.res.Resources +import androidx.core.content.res.getDimensionOrThrow +import androidx.core.content.res.use +import com.android.launcher3.R +import kotlin.math.max + +class IconSizeSteps(res: Resources) { + private val steps: List + + init { + steps = + res.obtainTypedArray(R.array.icon_size_steps).use { + (0 until it.length()).map { step -> it.getDimensionOrThrow(step).toInt() }.sorted() + } + } + + fun minimumIconSize(): Int = steps[0] + + fun getNextLowerIconSize(iconSizePx: Int): Int { + return steps[max(0, getIndexForIconSize(iconSizePx) - 1)] + } + + fun getIconSmallerThan(cellWidth: Int): Int { + return steps.lastOrNull { it <= cellWidth } ?: steps[0] + } + + private fun getIndexForIconSize(iconSizePx: Int): Int { + return max(0, steps.indexOfFirst { iconSizePx <= it }) + } +} diff --git a/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt b/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt new file mode 100644 index 0000000000..d9e3377df9 --- /dev/null +++ b/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util + +import android.content.Context +import android.content.res.Configuration +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class IconSizeStepsTest { + private var context: Context? = null + private val runningContext: Context = ApplicationProvider.getApplicationContext() + private lateinit var iconSizeSteps: IconSizeSteps + + @Before + fun setup() { + // 160dp makes 1px = 1dp + val config = + Configuration(runningContext.resources.configuration).apply { this.densityDpi = 160 } + context = runningContext.createConfigurationContext(config) + iconSizeSteps = IconSizeSteps(context!!.resources) + } + + @Test + fun minimumIconSize() { + assertThat(iconSizeSteps.minimumIconSize()).isEqualTo(52) + } + + @Test + fun getNextLowerIconSize() { + assertThat(iconSizeSteps.getNextLowerIconSize(66)).isEqualTo(63) + + // Assert that never goes below minimum + assertThat(iconSizeSteps.getNextLowerIconSize(52)).isEqualTo(52) + assertThat(iconSizeSteps.getNextLowerIconSize(30)).isEqualTo(52) + } + + @Test + fun getIconSmallerThan() { + assertThat(iconSizeSteps.getIconSmallerThan(60)).isEqualTo(59) + + // Assert that never goes below minimum + assertThat(iconSizeSteps.getIconSmallerThan(52)).isEqualTo(52) + assertThat(iconSizeSteps.getIconSmallerThan(30)).isEqualTo(52) + } +}