Add screen ratio fixing

This commit is contained in:
2026-08-21 17:59:41 -04:00
parent d3c149fe4a
commit 254656a329
@@ -142,11 +142,10 @@ fun TerminalView(
showContextMenu = false showContextMenu = false
selectionStart = null selectionStart = null
selectionEnd = null selectionEnd = null
val cellWidth = size.width / cols val metrics = calculateGridMetrics(size.width.toFloat(), size.height.toFloat(), cols, rows)
val cellHeight = size.height / rows if (metrics.cellWidth > 0 && metrics.cellHeight > 0) {
if (cellWidth > 0 && cellHeight > 0) { val col = ((startPos.x - metrics.offsetX) / metrics.cellWidth).toInt().coerceIn(0, cols - 1)
val col = (startPos.x / cellWidth).toInt().coerceIn(0, cols - 1) val row = ((startPos.y - metrics.offsetY) / metrics.cellHeight).toInt().coerceIn(0, rows - 1)
val row = (startPos.y / cellHeight).toInt().coerceIn(0, rows - 1)
var addr = row * cols + col var addr = row * cols + col
val buf = screenBuffer val buf = screenBuffer
@@ -167,17 +166,26 @@ fun TerminalView(
val activeCursorAddr = currentCursorAddr val activeCursorAddr = currentCursorAddr
if (version == -1L) return@Canvas if (version == -1L) return@Canvas
val width = size.width val metrics = calculateGridMetrics(size.width, size.height, cols, rows)
val height = size.height if (metrics.cellWidth <= 0f || metrics.cellHeight <= 0f) return@Canvas
val cellWidth = width / cols
val cellHeight = height / rows
if (cellWidth <= 0 || cellHeight <= 0) return@Canvas val cellWidth = metrics.cellWidth
val cellHeight = metrics.cellHeight
val offsetX = metrics.offsetX
val offsetY = metrics.offsetY
val paint = Paint().apply { val paint = Paint().apply {
isAntiAlias = true isAntiAlias = true
typeface = Typeface.MONOSPACE typeface = Typeface.MONOSPACE
textSize = cellHeight * 0.85f textAlign = Paint.Align.CENTER
}
// Calculate optimal font size ensuring characters fit cleanly in cell without crowding
val baseTextSize = cellHeight * 0.78f
paint.textSize = baseTextSize
val measuredW = paint.measureText("W")
if (measuredW > cellWidth * 0.85f && measuredW > 0f) {
paint.textSize = baseTextSize * (cellWidth * 0.85f / measuredW)
} }
val buf = screenBuffer val buf = screenBuffer
@@ -194,11 +202,11 @@ fun TerminalView(
val start = selectionStart val start = selectionStart
val end = selectionEnd val end = selectionEnd
if (start != null && end != null) { if (start != null && end != null && cellWidth > 0f && cellHeight > 0f) {
val startCol = (start.x / cellWidth).toInt().coerceIn(0, cols - 1) val startCol = ((start.x - offsetX) / cellWidth).toInt().coerceIn(0, cols - 1)
val startRow = (start.y / cellHeight).toInt().coerceIn(0, rows - 1) val startRow = ((start.y - offsetY) / cellHeight).toInt().coerceIn(0, rows - 1)
val endCol = (end.x / cellWidth).toInt().coerceIn(0, cols - 1) val endCol = ((end.x - offsetX) / cellWidth).toInt().coerceIn(0, cols - 1)
val endRow = (end.y / cellHeight).toInt().coerceIn(0, rows - 1) val endRow = ((end.y - offsetY) / cellHeight).toInt().coerceIn(0, rows - 1)
selMinRow = minOf(startRow, endRow) selMinRow = minOf(startRow, endRow)
selMaxRow = maxOf(startRow, endRow) selMaxRow = maxOf(startRow, endRow)
@@ -211,8 +219,8 @@ fun TerminalView(
val addr = r * cols + c val addr = r * cols + c
if (addr >= totalCells) break if (addr >= totalCells) break
val left = c * cellWidth val left = offsetX + c * cellWidth
val top = r * cellHeight val top = offsetY + r * cellHeight
var charVal = ' ' var charVal = ' '
var fgColor: Color var fgColor: Color
@@ -286,7 +294,7 @@ fun TerminalView(
) )
} }
// Highlight selected block range // Draw selection box highlight if selected
val isSelected = r in selMinRow..selMaxRow && c in selMinCol..selMaxCol val isSelected = r in selMinRow..selMaxRow && c in selMinCol..selMaxCol
if (isSelected) { if (isSelected) {
drawRect( drawRect(
@@ -314,7 +322,7 @@ fun TerminalView(
val symHeight = slot.height val symHeight = slot.height
val rgbArray = slot.getRgbPixels(fgColor.toArgb(), bgColor.toArgb()) val rgbArray = slot.getRgbPixels(fgColor.toArgb(), bgColor.toArgb())
if (rgbArray != null && symWidth > 0 && symHeight > 0) { if (rgbArray != null && symWidth > 0 && symHeight > 0) {
val bmp = android.graphics.Bitmap.createBitmap(rgbArray, symWidth, symHeight, android.graphics.Bitmap.Config.ARGB_8888) val bmp = android.graphics.Bitmap.createBitmap(rgbArray as IntArray, symWidth, symHeight, android.graphics.Bitmap.Config.ARGB_8888)
drawContext.canvas.nativeCanvas.drawBitmap( drawContext.canvas.nativeCanvas.drawBitmap(
bmp, bmp,
null, null,
@@ -330,11 +338,12 @@ fun TerminalView(
paint.color = fgColor.toArgb() paint.color = fgColor.toArgb()
paint.isFakeBoldText = isBold paint.isFakeBoldText = isBold
val fontMetrics = paint.fontMetrics val fontMetrics = paint.fontMetrics
val textY = top + (cellHeight - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top val textX = left + cellWidth / 2f
val textY = top + (cellHeight - fontMetrics.bottom + fontMetrics.top) / 2f - fontMetrics.top
drawContext.canvas.nativeCanvas.drawText( drawContext.canvas.nativeCanvas.drawText(
charVal.toString(), charVal.toString(),
left + (cellWidth / 4), textX,
textY, textY,
paint paint
) )
@@ -343,8 +352,8 @@ fun TerminalView(
if (isUnderline) { if (isUnderline) {
drawLine( drawLine(
color = fgColor, color = fgColor,
start = Offset(left, top + cellHeight - 2), start = Offset(left, top + cellHeight - 2f),
end = Offset(left + cellWidth, top + cellHeight - 2), end = Offset(left + cellWidth, top + cellHeight - 2f),
strokeWidth = 2f strokeWidth = 2f
) )
} }
@@ -353,8 +362,8 @@ fun TerminalView(
// Draw Vector Graphics Plane overlay if present // Draw Vector Graphics Plane overlay if present
if (graphicsPlane != null && graphicsPlane.hasContent()) { if (graphicsPlane != null && graphicsPlane.hasContent()) {
val gridW = (cols * cellWidth).toInt() val gridW = metrics.gridWidth.toInt()
val gridH = (rows * cellHeight).toInt() val gridH = metrics.gridHeight.toInt()
if (gridW > 0 && gridH > 0) { if (gridW > 0 && gridH > 0) {
graphicsPlane.resize(gridW, gridH) graphicsPlane.resize(gridW, gridH)
val rgb = graphicsPlane.rgbBuffer val rgb = graphicsPlane.rgbBuffer
@@ -363,7 +372,7 @@ fun TerminalView(
drawContext.canvas.nativeCanvas.drawBitmap( drawContext.canvas.nativeCanvas.drawBitmap(
bmp, bmp,
null, null,
android.graphics.RectF(0f, 0f, gridW.toFloat(), gridH.toFloat()), android.graphics.RectF(offsetX, offsetY, offsetX + gridW.toFloat(), offsetY + gridH.toFloat()),
null null
) )
} }
@@ -425,6 +434,57 @@ fun TerminalView(
} }
} }
data class TerminalGridMetrics(
val cellWidth: Float,
val cellHeight: Float,
val gridWidth: Float,
val gridHeight: Float,
val offsetX: Float,
val offsetY: Float
)
fun calculateGridMetrics(
viewWidth: Float,
viewHeight: Float,
cols: Int,
rows: Int
): TerminalGridMetrics {
if (viewWidth <= 0f || viewHeight <= 0f || cols <= 0 || rows <= 0) {
return TerminalGridMetrics(0f, 0f, 0f, 0f, 0f, 0f)
}
val rawCellW = viewWidth / cols
val rawCellH = viewHeight / rows
val currentRatio = rawCellW / rawCellH
// Target terminal cell aspect ratio is roughly 0.50 to 0.60 (width / height)
val targetMinRatio = 0.48f
val targetMaxRatio = 0.60f
val cellW: Float
val cellH: Float
if (currentRatio > targetMaxRatio) {
// Screen is wider than ideal aspect ratio -> clamp width, pillarbox (equal blank bars left/right)
cellH = rawCellH
cellW = rawCellH * targetMaxRatio
} else if (currentRatio < targetMinRatio) {
// Screen is taller than ideal aspect ratio -> clamp height, letterbox (equal blank bars top/bottom)
cellW = rawCellW
cellH = rawCellW / targetMinRatio
} else {
cellW = rawCellW
cellH = rawCellH
}
val gridW = cols * cellW
val gridH = rows * cellH
val offX = (viewWidth - gridW) / 2f
val offY = (viewHeight - gridH) / 2f
return TerminalGridMetrics(cellW, cellH, gridW, gridH, offX, offY)
}
private fun copySelection( private fun copySelection(
start: Offset?, start: Offset?,
end: Offset?, end: Offset?,
@@ -436,13 +496,15 @@ private fun copySelection(
) { ) {
if (start != null && end != null && screenBuffer != null && cols > 0 && rows > 0) { if (start != null && end != null && screenBuffer != null && cols > 0 && rows > 0) {
val displayMetrics = context.resources.displayMetrics val displayMetrics = context.resources.displayMetrics
val cellWidth = displayMetrics.widthPixels.toFloat() / cols val viewW = displayMetrics.widthPixels.toFloat()
val cellHeight = displayMetrics.heightPixels.toFloat() / rows val viewH = displayMetrics.heightPixels.toFloat()
val metrics = calculateGridMetrics(viewW, viewH, cols, rows)
if (metrics.cellWidth <= 0f || metrics.cellHeight <= 0f) return
val startCol = (start.x / cellWidth).toInt().coerceIn(0, cols - 1) val startCol = ((start.x - metrics.offsetX) / metrics.cellWidth).toInt().coerceIn(0, cols - 1)
val startRow = (start.y / cellHeight).toInt().coerceIn(0, rows - 1) val startRow = ((start.y - metrics.offsetY) / metrics.cellHeight).toInt().coerceIn(0, rows - 1)
val endCol = (end.x / cellWidth).toInt().coerceIn(0, cols - 1) val endCol = ((end.x - metrics.offsetX) / metrics.cellWidth).toInt().coerceIn(0, cols - 1)
val endRow = (end.y / cellHeight).toInt().coerceIn(0, rows - 1) val endRow = ((end.y - metrics.offsetY) / metrics.cellHeight).toInt().coerceIn(0, rows - 1)
val minRow = minOf(startRow, endRow) val minRow = minOf(startRow, endRow)
val maxRow = maxOf(startRow, endRow) val maxRow = maxOf(startRow, endRow)