Simplify pane sizing and scaling algorithm
Simplify the scaling parameters and algorithm and reduce vertical padding. Originally a maxBlockRatio was specified at .12, which would have caused a block length to typically be 2560 * 0.12 = 307. However, this fails to account for smaller displays and high-DPI displays which have smaller dip dimensions. So instead choose a maximum edge length of 256dp, which I have found to give good results on a laptop device and a high DPI tablet, and which does not assume typical sizing for configured displays, and still accomplishes the purpose of limiting scrolling and dragging size. Using the aspect ratio of the display bounds in setting the height frequently caused a very large amount of padding, and limiting based on the max block height only reduced it slightly. Now we set the vertical height based on the minEdgeLength. This length is considered the smallest size that can be comfortably interacted with, and we use it to give ballpark ideal padding size. Note that the unit test cases have increased pane height but in practice (real-world pane width and constraints) this change seems to decrease the pane height in general. Flag: com.android.settings.flags.display_topology_pane_in_display_list Bug: b/352650922 Test: atest DisplayTopologyPreferenceTest.kt Test: atest TopologyScaleTest.kt Test: compare appearance on mid-dpi and high-dpi screens with a single 1080p external display, or with two external displays of smallish logical size Change-Id: Id189892c88a1e833c1f54b0e5447a15f92e3310f
This commit is contained in:
@@ -67,10 +67,10 @@ fun Float.atLeast(n: Number): Float = max(this, n.toFloat())
|
||||
* pane coordinates is necessary when rendering the original topology. Conversion in the other
|
||||
* direction, to display coordinates, is necessary for resolve a drag position to display space.
|
||||
*
|
||||
* The topology pane coordinates are integral and represent the relative position from the upper-
|
||||
* left corner of the pane. It uses a scale optimized for showing all displays with minimal or no
|
||||
* scrolling. The display coordinates are floating point and the origin can be in any position. In
|
||||
* practice the origin will be the upper-left coordinate of the primary display.
|
||||
* The topology pane coordinates are physical pixels and represent the relative position from the
|
||||
* upper-left corner of the pane. It uses a scale optimized for showing all displays with minimal
|
||||
* or no scrolling. The display coordinates are floating point and the origin can be in any
|
||||
* position. In 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
|
||||
@@ -79,14 +79,13 @@ fun Float.atLeast(n: Number): Float = max(this, n.toFloat())
|
||||
* @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.
|
||||
* @param maxBlockRatio the highest allowed ratio of block size to display size. For instance, a
|
||||
* value of 0.05 means the block will at most be 1/20 the size of the display
|
||||
* it represents. This limit may be breached to account for minEdgeLength,
|
||||
* which is considered an a11y requirement.
|
||||
* @param maxEdgeLength the longest width or height permitted of a display block. This will limit
|
||||
* the amount of dragging and scrolling the user will need to do to set the
|
||||
* arrangement.
|
||||
* @param displaysPos the absolute topology coordinates for each display in the topology.
|
||||
*/
|
||||
class TopologyScale(
|
||||
paneWidth: Int, minPaneHeight: Float, minEdgeLength: Float, maxBlockRatio: Float,
|
||||
paneWidth: Int, minPaneHeight: Float, minEdgeLength: Float, maxEdgeLength: Float,
|
||||
displaysPos: Collection<RectF>) {
|
||||
/** Scale of block sizes to real-world display sizes. Should be less than 1. */
|
||||
val blockRatio: Float
|
||||
@@ -104,40 +103,33 @@ class TopologyScale(
|
||||
val displayBounds = RectF(
|
||||
Float.MAX_VALUE, Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE)
|
||||
var smallestDisplayDim = Float.MAX_VALUE
|
||||
var biggestDisplayHeight = Float.MIN_VALUE
|
||||
var biggestDisplayDim = Float.MIN_VALUE
|
||||
|
||||
// displayBounds is the smallest rect encompassing all displays, in display space.
|
||||
// smallestDisplayDim is the size of the smallest display edge, in display space.
|
||||
for (pos in displaysPos) {
|
||||
displayBounds.union(pos)
|
||||
smallestDisplayDim = minOf(smallestDisplayDim, pos.height(), pos.width())
|
||||
biggestDisplayHeight = max(biggestDisplayHeight, pos.height())
|
||||
biggestDisplayDim = maxOf(biggestDisplayDim, pos.height(), pos.width())
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.
|
||||
// Initialize blockRatio such that there is 20% padding on left and right sides of the
|
||||
// display bounds.
|
||||
blockRatio = (paneWidth * 0.6 / displayBounds.width()).toFloat()
|
||||
// If the `ratio` is set too high because one of the displays will have an edge
|
||||
// greater than maxEdgeLength(px) long, decrease it such that the largest edge is
|
||||
// that long.
|
||||
.atMost(maxEdgeLength / biggestDisplayDim)
|
||||
// Also do the opposite of the above, this latter step taking precedence for a11y
|
||||
// requirements.
|
||||
.atLeast(minEdgeLength / smallestDisplayDim)
|
||||
|
||||
// Essentially, we just set the pane height based on the pre-determined pane width and the
|
||||
// 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
|
||||
paneHeight = minPaneHeight
|
||||
// A tall pane is likely to result in more scrolling. 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)
|
||||
// 1.5x of the minEdgeLength on each side. This keeps a comfortable amount of
|
||||
// padding without it resulting in too much deadspace.
|
||||
.atLeast(blockRatio * displayBounds.height() + minEdgeLength * 3f)
|
||||
|
||||
// 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.
|
||||
@@ -414,7 +406,7 @@ class DisplayTopologyPreference(context : Context)
|
||||
val scaling = TopologyScale(
|
||||
mPaneContent.width, minPaneHeight = mTopologyInfo?.scaling?.paneHeight ?: 0f,
|
||||
minEdgeLength = DisplayTopology.dpToPx(60f, dpi),
|
||||
maxBlockRatio = DisplayTopology.dpToPx(0.12f, dpi),
|
||||
maxEdgeLength = DisplayTopology.dpToPx(256f, dpi),
|
||||
newBounds.map { it.second }.toList())
|
||||
mPaneHolder.layoutParams.let {
|
||||
val newHeight = scaling.paneHeight.toInt()
|
||||
|
Reference in New Issue
Block a user