Loading src/com/android/settings/connecteddevice/display/DisplayTopology.kt +31 −21 Original line number Diff line number Diff line Loading @@ -51,6 +51,10 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.min // These extension methods make calls to min and max chainable. fun Float.atMost(n: Number): Float = min(this, n.toFloat()) fun Float.atLeast(n: Number): Float = max(this, n.toFloat()) /** * Contains the parameters needed for transforming global display coordinates to and from topology * pane coordinates. This is necessary for implementing an interactive display topology pane. The Loading @@ -64,6 +68,9 @@ import kotlin.math.min * practice the origin will be the upper-left coordinate of the primary display. * * @param paneWidth width of the pane in view coordinates * @param minPaneHeight smallest allowed height of the pane in view coordinates. This will not * affect the block ratio, but only the final height of the pane and the * position of the display bounds' center. * @param minEdgeLength the smallest length permitted of a display block. This should be set based * on accessibility requirements, but also accounting for padding that appears * around each button. Loading @@ -74,7 +81,7 @@ import kotlin.math.min * @param displaysPos the absolute topology coordinates for each display in the topology. */ class TopologyScale( paneWidth : Int, minEdgeLength : Int, maxBlockRatio : Float, paneWidth: Int, minPaneHeight: Float, minEdgeLength: Int, maxBlockRatio: Float, displaysPos: Collection<RectF>) { /** Scale of block sizes to real-world display sizes. Should be less than 1. */ val blockRatio: Float Loading Loading @@ -102,28 +109,30 @@ class TopologyScale( biggestDisplayHeight = max(biggestDisplayHeight, pos.height()) } // Set height according to the width and the aspect ratio of the display bounds limitted by // Set height according to the width and the aspect ratio of the display bounds limited by // maxBlockRatio. It prevents blocks from being too large, which would make dragging and // dropping awkward. val rawBlockRatio = min(maxBlockRatio, paneWidth.toFloat() * 0.6f / displayBounds.width()) // If the `ratio` is set too low because one of the displays will have an edge less than // minEdgeLength(dp) long, increase it such that the smallest edge is that long. blockRatio = max(minEdgeLength.toFloat() / smallestDisplayDim, rawBlockRatio).toFloat() blockRatio = maxBlockRatio .atMost(paneWidth * 0.6 / displayBounds.width()) // If the `ratio` is set too low because one of the displays will have an edge less // than minEdgeLength(dp) long, increase it such that the smallest edge is that // long. .atLeast(minEdgeLength.toFloat() / smallestDisplayDim) // Essentially, we just set the pane height based on the pre-determined pane width and the // aspect ratio of the display bounds. But we may need to increase it slightly to achieve // 20% padding above and below the display bounds - this is where the 0.6 comes from. val rawPaneHeight = max( paneWidth.toDouble() / displayBounds.width() * displayBounds.height(), displayBounds.height() * blockRatio / 0.6).toFloat() // It is easy for the aspect ratio to result in an excessively tall pane, since the width is // pre-determined and may be considerably wider than necessary. So we prevent the height // from growing too large here, by limiting vertical padding to the size of the tallest // display. This improves results for very tall display bounds. paneHeight = min( rawPaneHeight, blockRatio * (displayBounds.height() + biggestDisplayHeight * 2f)) // aspect ratio of the display bounds. paneHeight = (paneWidth.toFloat() / displayBounds.width() * displayBounds.height()) // We may need to increase it slightly to achieve 20% padding above and below the // display bounds - this is where the 0.6 comes from. .atLeast(displayBounds.height() * blockRatio / 0.6) // It is easy for the aspect ratio to result in an excessively tall pane, since the // width is pre-determined and may be considerably wider than necessary. So we // prevent the height from growing too large here, by limiting vertical padding to // the size of the tallest display. This improves results for very tall display // bounds. .atMost(blockRatio * (displayBounds.height() + biggestDisplayHeight * 2f)) .atLeast(minPaneHeight) // Set originPaneXY (the location of 0,0 in display space in the pane's coordinate system) // such that the display bounds rect is centered in the pane. Loading Loading @@ -365,7 +374,8 @@ class DisplayTopologyPreference(context : Context) } val scaling = TopologyScale( mPaneContent.width, minEdgeLength = 60, maxBlockRatio = 0.12f, mPaneContent.width, minPaneHeight = mTopologyInfo?.scaling?.paneHeight ?: 0f, minEdgeLength = 60, maxBlockRatio = 0.12f, newBounds.map { it.second }.toList()) mPaneHolder.layoutParams.let { val newHeight = scaling.paneHeight.toInt() Loading tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt +17 −6 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ class TopologyScaleTest { @Test fun oneDisplay4to3Aspect() { val scale = TopologyScale( /* paneWidth= */ 640, /* paneWidth= */ 640, minPaneHeight = 0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 640f, 480f))) Loading @@ -47,12 +47,23 @@ class TopologyScaleTest { assertPointF(352f, 96f, 0.001f, scale.displayToPaneCoor(640f, 480f)) assertPointF(320f, 72f, 0.001f, scale.displayToPaneCoor(320f, 240f)) assertPointF(640f, 480f, 0.001f, scale.paneToDisplayCoor(352f, 96f)) // Same as original scale but made taller with minPaneHeight. // The paneHeight and origin coordinates are changed but the block ratio is the same. val taller = TopologyScale( /* paneWidth= */ 640, minPaneHeight = 155.0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 640f, 480f))) assertEquals( "{TopologyScale blockRatio=0.100000 originPaneXY=288.0,53.5 paneHeight=155.0}", "" + taller) } @Test fun twoUnalignedDisplays() { val scale = TopologyScale( /* paneWidth= */ 300, /* paneWidth= */ 300, minPaneHeight = 0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 1920f, 1200f), RectF(1920f, -300f, 3840f, 900f))) Loading @@ -68,7 +79,7 @@ class TopologyScaleTest { @Test fun twoDisplaysBlockRatioBumpedForGarSizeMinimumHorizontal() { val scale = TopologyScale( /* paneWidth= */ 192, /* paneWidth= */ 192, minPaneHeight = 0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 240f, 320f), RectF(-240f, -320f, 0f, 0f))) Loading @@ -86,7 +97,7 @@ class TopologyScaleTest { @Test fun paneVerticalPaddingLimitedByTallestDisplay() { val scale = TopologyScale( /* paneWidth= */ 300, /* paneWidth= */ 300, minPaneHeight = 0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf( RectF(0f, 0f, 640f, 480f), Loading @@ -106,7 +117,7 @@ class TopologyScaleTest { @Test fun limitedByCustomMaxBlockRatio() { val scale = TopologyScale( /* paneWidth= */ 300, /* paneWidth= */ 300, minPaneHeight = 0f, minEdgeLength = 24, maxBlockRatio = 0.12f, listOf( RectF(0f, 0f, 640f, 480f), Loading @@ -123,7 +134,7 @@ class TopologyScaleTest { fun largeCustomMinEdgeLength() { // minBlockEdgeLength/minDisplayEdgeLength = 80/480 = 1/6, so the block ratio will be 1/6 val scale = TopologyScale( /* paneWidth= */ 300, /* paneWidth= */ 300, minPaneHeight = 0f, minEdgeLength = 80, maxBlockRatio = 0.05f, listOf( RectF(0f, 0f, 640f, 480f), Loading Loading
src/com/android/settings/connecteddevice/display/DisplayTopology.kt +31 −21 Original line number Diff line number Diff line Loading @@ -51,6 +51,10 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.min // These extension methods make calls to min and max chainable. fun Float.atMost(n: Number): Float = min(this, n.toFloat()) fun Float.atLeast(n: Number): Float = max(this, n.toFloat()) /** * Contains the parameters needed for transforming global display coordinates to and from topology * pane coordinates. This is necessary for implementing an interactive display topology pane. The Loading @@ -64,6 +68,9 @@ import kotlin.math.min * practice the origin will be the upper-left coordinate of the primary display. * * @param paneWidth width of the pane in view coordinates * @param minPaneHeight smallest allowed height of the pane in view coordinates. This will not * affect the block ratio, but only the final height of the pane and the * position of the display bounds' center. * @param minEdgeLength the smallest length permitted of a display block. This should be set based * on accessibility requirements, but also accounting for padding that appears * around each button. Loading @@ -74,7 +81,7 @@ import kotlin.math.min * @param displaysPos the absolute topology coordinates for each display in the topology. */ class TopologyScale( paneWidth : Int, minEdgeLength : Int, maxBlockRatio : Float, paneWidth: Int, minPaneHeight: Float, minEdgeLength: Int, maxBlockRatio: Float, displaysPos: Collection<RectF>) { /** Scale of block sizes to real-world display sizes. Should be less than 1. */ val blockRatio: Float Loading Loading @@ -102,28 +109,30 @@ class TopologyScale( biggestDisplayHeight = max(biggestDisplayHeight, pos.height()) } // Set height according to the width and the aspect ratio of the display bounds limitted by // Set height according to the width and the aspect ratio of the display bounds limited by // maxBlockRatio. It prevents blocks from being too large, which would make dragging and // dropping awkward. val rawBlockRatio = min(maxBlockRatio, paneWidth.toFloat() * 0.6f / displayBounds.width()) // If the `ratio` is set too low because one of the displays will have an edge less than // minEdgeLength(dp) long, increase it such that the smallest edge is that long. blockRatio = max(minEdgeLength.toFloat() / smallestDisplayDim, rawBlockRatio).toFloat() blockRatio = maxBlockRatio .atMost(paneWidth * 0.6 / displayBounds.width()) // If the `ratio` is set too low because one of the displays will have an edge less // than minEdgeLength(dp) long, increase it such that the smallest edge is that // long. .atLeast(minEdgeLength.toFloat() / smallestDisplayDim) // Essentially, we just set the pane height based on the pre-determined pane width and the // aspect ratio of the display bounds. But we may need to increase it slightly to achieve // 20% padding above and below the display bounds - this is where the 0.6 comes from. val rawPaneHeight = max( paneWidth.toDouble() / displayBounds.width() * displayBounds.height(), displayBounds.height() * blockRatio / 0.6).toFloat() // It is easy for the aspect ratio to result in an excessively tall pane, since the width is // pre-determined and may be considerably wider than necessary. So we prevent the height // from growing too large here, by limiting vertical padding to the size of the tallest // display. This improves results for very tall display bounds. paneHeight = min( rawPaneHeight, blockRatio * (displayBounds.height() + biggestDisplayHeight * 2f)) // aspect ratio of the display bounds. paneHeight = (paneWidth.toFloat() / displayBounds.width() * displayBounds.height()) // We may need to increase it slightly to achieve 20% padding above and below the // display bounds - this is where the 0.6 comes from. .atLeast(displayBounds.height() * blockRatio / 0.6) // It is easy for the aspect ratio to result in an excessively tall pane, since the // width is pre-determined and may be considerably wider than necessary. So we // prevent the height from growing too large here, by limiting vertical padding to // the size of the tallest display. This improves results for very tall display // bounds. .atMost(blockRatio * (displayBounds.height() + biggestDisplayHeight * 2f)) .atLeast(minPaneHeight) // Set originPaneXY (the location of 0,0 in display space in the pane's coordinate system) // such that the display bounds rect is centered in the pane. Loading Loading @@ -365,7 +374,8 @@ class DisplayTopologyPreference(context : Context) } val scaling = TopologyScale( mPaneContent.width, minEdgeLength = 60, maxBlockRatio = 0.12f, mPaneContent.width, minPaneHeight = mTopologyInfo?.scaling?.paneHeight ?: 0f, minEdgeLength = 60, maxBlockRatio = 0.12f, newBounds.map { it.second }.toList()) mPaneHolder.layoutParams.let { val newHeight = scaling.paneHeight.toInt() Loading
tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt +17 −6 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ class TopologyScaleTest { @Test fun oneDisplay4to3Aspect() { val scale = TopologyScale( /* paneWidth= */ 640, /* paneWidth= */ 640, minPaneHeight = 0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 640f, 480f))) Loading @@ -47,12 +47,23 @@ class TopologyScaleTest { assertPointF(352f, 96f, 0.001f, scale.displayToPaneCoor(640f, 480f)) assertPointF(320f, 72f, 0.001f, scale.displayToPaneCoor(320f, 240f)) assertPointF(640f, 480f, 0.001f, scale.paneToDisplayCoor(352f, 96f)) // Same as original scale but made taller with minPaneHeight. // The paneHeight and origin coordinates are changed but the block ratio is the same. val taller = TopologyScale( /* paneWidth= */ 640, minPaneHeight = 155.0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 640f, 480f))) assertEquals( "{TopologyScale blockRatio=0.100000 originPaneXY=288.0,53.5 paneHeight=155.0}", "" + taller) } @Test fun twoUnalignedDisplays() { val scale = TopologyScale( /* paneWidth= */ 300, /* paneWidth= */ 300, minPaneHeight = 0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 1920f, 1200f), RectF(1920f, -300f, 3840f, 900f))) Loading @@ -68,7 +79,7 @@ class TopologyScaleTest { @Test fun twoDisplaysBlockRatioBumpedForGarSizeMinimumHorizontal() { val scale = TopologyScale( /* paneWidth= */ 192, /* paneWidth= */ 192, minPaneHeight = 0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf(RectF(0f, 0f, 240f, 320f), RectF(-240f, -320f, 0f, 0f))) Loading @@ -86,7 +97,7 @@ class TopologyScaleTest { @Test fun paneVerticalPaddingLimitedByTallestDisplay() { val scale = TopologyScale( /* paneWidth= */ 300, /* paneWidth= */ 300, minPaneHeight = 0f, minEdgeLength = 48, maxBlockRatio = 0.05f, listOf( RectF(0f, 0f, 640f, 480f), Loading @@ -106,7 +117,7 @@ class TopologyScaleTest { @Test fun limitedByCustomMaxBlockRatio() { val scale = TopologyScale( /* paneWidth= */ 300, /* paneWidth= */ 300, minPaneHeight = 0f, minEdgeLength = 24, maxBlockRatio = 0.12f, listOf( RectF(0f, 0f, 640f, 480f), Loading @@ -123,7 +134,7 @@ class TopologyScaleTest { fun largeCustomMinEdgeLength() { // minBlockEdgeLength/minDisplayEdgeLength = 80/480 = 1/6, so the block ratio will be 1/6 val scale = TopologyScale( /* paneWidth= */ 300, /* paneWidth= */ 300, minPaneHeight = 0f, minEdgeLength = 80, maxBlockRatio = 0.05f, listOf( RectF(0f, 0f, 640f, 480f), Loading