Update Status Bar, Settings, Terminal alignment and codepage testing
Build and Test a3270 / Build Android APK (push) Successful in 1m58s
Build and Test a3270 / Build Android APK (push) Successful in 1m58s
This commit is contained in:
@@ -546,11 +546,34 @@ fun MainScreen(
|
||||
val hotspotsPfEnabled by viewModel.hotspotsPfEnabled.collectAsState()
|
||||
val hotspotsMenuChoicesEnabled by viewModel.hotspotsMenuChoicesEnabled.collectAsState()
|
||||
|
||||
val isNumericField = remember(screenBuffer, cursorAddr, screenVersion) {
|
||||
if (screenBuffer != null && screenBuffer?.isFormatted == true) {
|
||||
val fa = screenBuffer?.getFieldAttributeAt(cursorAddr) ?: 0.toByte()
|
||||
(fa.toInt() and haus.nightmare.lib3270j.protocol.DS3270Constants.FA_NUMERIC) != 0
|
||||
} else false
|
||||
val isDbcsSession = remember(screenBuffer, screenVersion) {
|
||||
screenBuffer?.translator?.isDBCS == true
|
||||
}
|
||||
|
||||
val fieldStatus = remember(screenBuffer, cursorAddr, screenVersion) {
|
||||
val buf = screenBuffer
|
||||
if (buf != null && buf.isFormatted) {
|
||||
val cell = buf.getCell(cursorAddr)
|
||||
val isDbcs = (cell != null && (cell.db == haus.nightmare.lib3270j.screen.ExtendedAttribute.DB_LEFT ||
|
||||
cell.db == haus.nightmare.lib3270j.screen.ExtendedAttribute.DB_RIGHT ||
|
||||
cell.cs == haus.nightmare.lib3270j.screen.ExtendedAttribute.CS_DBCS))
|
||||
if (isDbcs) {
|
||||
"DBCS"
|
||||
} else {
|
||||
val fa = buf.getFieldAttributeAt(cursorAddr)
|
||||
if ((fa.toInt() and haus.nightmare.lib3270j.protocol.DS3270Constants.FA_NUMERIC) != 0) {
|
||||
"NUM"
|
||||
} else {
|
||||
"ALPHA"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
"ALPHA"
|
||||
}
|
||||
}
|
||||
|
||||
val isNumericField = remember(fieldStatus) {
|
||||
fieldStatus == "NUM"
|
||||
}
|
||||
|
||||
var showConnectDialog by remember { mutableStateOf(false) }
|
||||
@@ -717,6 +740,7 @@ fun MainScreen(
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
TerminalInputView(ctx).apply {
|
||||
this.isDbcsSession = isDbcsSession
|
||||
onInputText = { text ->
|
||||
viewModel.typeString(text)
|
||||
onClearShift()
|
||||
@@ -738,6 +762,7 @@ fun MainScreen(
|
||||
}
|
||||
},
|
||||
update = { inputView ->
|
||||
inputView.isDbcsSession = isDbcsSession
|
||||
terminalInputViewRef = inputView
|
||||
},
|
||||
modifier = Modifier.size(1.dp)
|
||||
@@ -880,6 +905,7 @@ fun MainScreen(
|
||||
inhibitReason = viewModel.getClient()?.oia?.inputInhibited ?: 0,
|
||||
luName = viewModel.getClient()?.telnetFSM?.connectedLu ?: "",
|
||||
isNumericField = isNumericField,
|
||||
fieldStatus = fieldStatus,
|
||||
uiTheme = uiTheme,
|
||||
onToggleDocMode = { viewModel.toggleDocMode() },
|
||||
onToggleWordWrap = { viewModel.toggleWordWrap() },
|
||||
|
||||
@@ -176,6 +176,10 @@ object HostStorage {
|
||||
"1149" to "1149 - Iceland (Euro €)",
|
||||
"930" to "930 - Japanese Katakana Mixed DBCS",
|
||||
"939" to "939 - Japanese Latin Mixed DBCS",
|
||||
"290" to "290 - Japanese Katakana SBCS",
|
||||
"1027" to "1027 - Japanese Latin Extended SBCS",
|
||||
"1390" to "1390 - Japanese Katakana DBCS Euro",
|
||||
"1399" to "1399 - Japanese Latin DBCS Euro",
|
||||
"935" to "935 - Simplified Chinese Mixed DBCS",
|
||||
"937" to "937 - Traditional Chinese Mixed DBCS",
|
||||
"1388" to "1388 - Simplified Chinese Extended DBCS",
|
||||
|
||||
@@ -40,6 +40,7 @@ fun OiaStatusBar(
|
||||
inhibitReason: Int = 0,
|
||||
luName: String = "",
|
||||
isNumericField: Boolean = false,
|
||||
fieldStatus: String = "",
|
||||
uiTheme: String = "DARK",
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@@ -163,16 +164,22 @@ fun OiaStatusBar(
|
||||
}
|
||||
}
|
||||
|
||||
// Field Status: NUM vs ALPHA
|
||||
// Field Status: DBCS vs NUM vs ALPHA
|
||||
if (connectionState.isConnected() && rows > 0 && cols > 0) {
|
||||
val effectiveFieldStatus = if (fieldStatus.isNotBlank()) fieldStatus else (if (isNumericField) "NUM" else "ALPHA")
|
||||
val badgeColor = when (effectiveFieldStatus) {
|
||||
"DBCS" -> sysAvailClr
|
||||
"NUM" -> Color(0xFFFFD43B)
|
||||
else -> Color(0xFF74C0FC)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Surface(
|
||||
color = Color(0xFF339AF0).copy(alpha = 0.2f),
|
||||
color = badgeColor.copy(alpha = 0.2f),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (isNumericField) "NUM" else "ALPHA",
|
||||
color = if (isNumericField) Color(0xFFFFD43B) else Color(0xFF74C0FC),
|
||||
text = effectiveFieldStatus,
|
||||
color = badgeColor,
|
||||
fontSize = 9.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.core.content.FileProvider
|
||||
import haus.nightmare.lib3270j.graphics.GraphicsPlane
|
||||
import haus.nightmare.lib3270j.graphics.ProgramSymbolManager
|
||||
import haus.nightmare.lib3270j.protocol.DS3270Constants.*
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -296,6 +297,10 @@ private fun generatePlainText(screenBuffer: ScreenBuffer?, rows: Int, cols: Int,
|
||||
val faVal = cell.fa.toInt() and 0xFF
|
||||
currentHidden = (faVal and FA_INT_HIGH_SEL) == 0
|
||||
rowSb.append(' ')
|
||||
} else if (cell.db == ExtendedAttribute.DB_RIGHT) {
|
||||
if (c > 0) continue else rowSb.append(' ')
|
||||
} else if (cell.db == ExtendedAttribute.DB_SO || cell.db == ExtendedAttribute.DB_SI) {
|
||||
rowSb.append(' ')
|
||||
} else {
|
||||
if (currentHidden && maskHidden) {
|
||||
rowSb.append(' ')
|
||||
@@ -346,6 +351,10 @@ private fun generateHtml(screenBuffer: ScreenBuffer?, rows: Int, cols: Int, mask
|
||||
currentHidden = (faVal and FA_INT_HIGH_SEL) == 0
|
||||
currentFieldEa = cell
|
||||
sb.append(' ')
|
||||
} else if (cell.db == ExtendedAttribute.DB_RIGHT) {
|
||||
if (c > 0) continue else sb.append(' ')
|
||||
} else if (cell.db == ExtendedAttribute.DB_SO || cell.db == ExtendedAttribute.DB_SI) {
|
||||
sb.append(' ')
|
||||
} else {
|
||||
if (currentHidden && maskHidden) {
|
||||
sb.append(' ')
|
||||
|
||||
@@ -111,6 +111,10 @@ fun SettingsDialog(
|
||||
var hotspotsPfEnabled by remember { mutableStateOf(initialHotspotsPfEnabled) }
|
||||
var hotspotsMenuChoicesEnabled by remember { mutableStateOf(initialHotspotsMenuChoicesEnabled) }
|
||||
var startupBehavior by remember { mutableStateOf(initialStartupBehavior) }
|
||||
val savedHosts = remember { haus.nightmare.a3270.storage.HostStorage.getSavedHosts(context) }
|
||||
var selectedAutoHostId by remember {
|
||||
mutableStateOf(haus.nightmare.a3270.storage.HostStorage.getAutoConnectHost(context)?.id ?: savedHosts.firstOrNull()?.id ?: "")
|
||||
}
|
||||
var dynamicRowsStr by remember { mutableStateOf(initialDynamicRows.toString()) }
|
||||
var dynamicColsStr by remember { mutableStateOf(initialDynamicCols.toString()) }
|
||||
|
||||
@@ -488,6 +492,47 @@ fun SettingsDialog(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (startupBehavior.equals("AUTO_CONNECT", ignoreCase = true) && savedHosts.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text("Auto-Connect Target Profile", fontSize = 12.sp, color = Color.LightGray)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
var hostDropdownExpanded by remember { mutableStateOf(false) }
|
||||
val selectedHost = savedHosts.find { it.id == selectedAutoHostId } ?: savedHosts.first()
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
OutlinedButton(
|
||||
onClick = { hostDropdownExpanded = true },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "${selectedHost.name} (${selectedHost.host}:${selectedHost.port})",
|
||||
fontSize = 12.sp,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = hostDropdownExpanded,
|
||||
onDismissRequest = { hostDropdownExpanded = false }
|
||||
) {
|
||||
savedHosts.forEach { host ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
text = "${host.name} (${host.host}:${host.port})",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = if (host.id == selectedAutoHostId) FontWeight.Bold else FontWeight.Normal
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
selectedAutoHostId = host.id
|
||||
hostDropdownExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
@@ -914,6 +959,18 @@ fun SettingsDialog(
|
||||
numericFieldLock,
|
||||
autoSkipEnabled
|
||||
)
|
||||
|
||||
if (startupBehavior.equals("AUTO_CONNECT", ignoreCase = true)) {
|
||||
val targetHost = savedHosts.find { it.id == selectedAutoHostId }
|
||||
if (targetHost != null) {
|
||||
haus.nightmare.a3270.storage.HostStorage.saveHost(context, targetHost.copy(autoConnect = true))
|
||||
}
|
||||
} else {
|
||||
val autoHost = haus.nightmare.a3270.storage.HostStorage.getAutoConnectHost(context)
|
||||
if (autoHost != null) {
|
||||
haus.nightmare.a3270.storage.HostStorage.saveHost(context, autoHost.copy(autoConnect = false))
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF2B8A3E))
|
||||
) {
|
||||
|
||||
@@ -88,6 +88,14 @@ class TerminalInputView(context: Context) : View(context) {
|
||||
var onBackspace: () -> Unit = {}
|
||||
var onTab: () -> Unit = {}
|
||||
var onBackTab: () -> Unit = {}
|
||||
var isDbcsSession: Boolean = false
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
imm?.restartInput(this)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
isFocusable = true
|
||||
@@ -97,9 +105,13 @@ class TerminalInputView(context: Context) : View(context) {
|
||||
override fun onCheckIsTextEditor(): Boolean = true
|
||||
|
||||
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
|
||||
outAttrs.inputType = InputType.TYPE_CLASS_TEXT or
|
||||
outAttrs.inputType = if (isDbcsSession) {
|
||||
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
} else {
|
||||
InputType.TYPE_CLASS_TEXT or
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or
|
||||
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
}
|
||||
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN or
|
||||
EditorInfo.IME_FLAG_NO_EXTRACT_UI or
|
||||
EditorInfo.IME_ACTION_NONE
|
||||
|
||||
@@ -355,9 +355,9 @@ fun TerminalView(
|
||||
var csVal = 0
|
||||
var ecVal = 0
|
||||
|
||||
if (buf != null) {
|
||||
val ea = buf.getCell(addr)
|
||||
val ea = if (buf != null) buf.getCell(addr) else null
|
||||
|
||||
if (ea != null) {
|
||||
if (ea.isFieldAttribute) {
|
||||
currentFA = ea.fa
|
||||
currentFieldEa = ea
|
||||
@@ -411,7 +411,10 @@ fun TerminalView(
|
||||
bgColor = tmp
|
||||
}
|
||||
|
||||
if (bgColor != COLOR_BLACK && bgColor != Color.Transparent) {
|
||||
val tr = if (ea != null && ea.tr != 0.toByte()) ea.tr else (currentFieldEa?.tr ?: 0)
|
||||
val isOpaque = ((tr.toInt() and 0xFF) == (ExtendedAttribute.TR_OPAQUE.toInt() and 0xFF))
|
||||
|
||||
if (isOpaque || (bgColor != COLOR_BLACK && bgColor != Color.Transparent)) {
|
||||
drawRect(
|
||||
color = bgColor,
|
||||
topLeft = Offset(left, top),
|
||||
@@ -449,19 +452,22 @@ fun TerminalView(
|
||||
|
||||
// Cursor indicator (respects blinking toggle & timer, cursor style, insert mode)
|
||||
if (addr == activeCursorAddr && !isSelected && cursorVisible) {
|
||||
val isDbcsCur = ea != null && (ea.db == ExtendedAttribute.DB_LEFT || ea.db == ExtendedAttribute.DB_RIGHT)
|
||||
val curWidth = if (isDbcsCur) cellWidth * 2f else cellWidth
|
||||
val curLeft = if (isDbcsCur && ea?.db == ExtendedAttribute.DB_RIGHT && c > 0) left - cellWidth else left
|
||||
val isUnderlineCursor = cursorStyle.equals("UNDERLINE", ignoreCase = true) || isInsertMode
|
||||
if (isUnderlineCursor) {
|
||||
val ulH = maxOf(4f, cellHeight / 4f)
|
||||
drawRect(
|
||||
color = Color(255, 255, 255, 180),
|
||||
topLeft = Offset(left, top + cellHeight - ulH),
|
||||
size = Size(cellWidth, ulH)
|
||||
topLeft = Offset(curLeft, top + cellHeight - ulH),
|
||||
size = Size(curWidth, ulH)
|
||||
)
|
||||
} else {
|
||||
drawRect(
|
||||
color = Color(255, 255, 255, 180),
|
||||
topLeft = Offset(left, top),
|
||||
size = Size(cellWidth, cellHeight)
|
||||
topLeft = Offset(curLeft, top),
|
||||
size = Size(curWidth, cellHeight)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -487,12 +493,14 @@ fun TerminalView(
|
||||
}
|
||||
}
|
||||
|
||||
val suppressText = isBlink && !textBlinkVisible
|
||||
val isDbcsRight = ea?.db == ExtendedAttribute.DB_RIGHT
|
||||
val isDbcsSoSi = ea?.db == ExtendedAttribute.DB_SO || ea?.db == ExtendedAttribute.DB_SI
|
||||
val suppressText = (isBlink && !textBlinkVisible) || isDbcsRight || isDbcsSoSi
|
||||
if (!drawnAsPs && charVal != ' ' && !suppressText) {
|
||||
paint.color = fgColor.toArgb()
|
||||
paint.isFakeBoldText = isBold
|
||||
val fontMetrics = paint.fontMetrics
|
||||
val textX = left + cellWidth / 2f
|
||||
val textX = if (ea?.db == ExtendedAttribute.DB_LEFT) left + cellWidth else left + cellWidth / 2f
|
||||
val textY = top + (cellHeight - fontMetrics.bottom + fontMetrics.top) / 2f - fontMetrics.top
|
||||
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
@@ -840,8 +848,8 @@ fun calculateGridMetrics(
|
||||
|
||||
val gridW = cols * cellW
|
||||
val gridH = rows * cellH
|
||||
val offX = (viewWidth - gridW) / 2f
|
||||
val offY = (viewHeight - gridH) / 2f
|
||||
val offX = maxOf(0f, (viewWidth - gridW) / 2f)
|
||||
val offY = maxOf(0f, (viewHeight - gridH) / 2f)
|
||||
|
||||
return TerminalGridMetrics(cellW, cellH, gridW, gridH, offX, offY)
|
||||
}
|
||||
@@ -882,11 +890,24 @@ private fun copySelection(
|
||||
val addr = r * cols + c
|
||||
if (addr < (rows * cols)) {
|
||||
val cell = screenBuffer.getCell(addr)
|
||||
if (cell != null && !cell.isFieldAttribute && cell.ucs4 > ' ' && cell.ucs4.code != 0xFFFF) {
|
||||
if (cell != null && !cell.isFieldAttribute) {
|
||||
if (cell.db == ExtendedAttribute.DB_RIGHT) {
|
||||
val isSubsequent = if (blockSelectMode) c > minCol else (addr > (minRow * cols + minCol))
|
||||
if (isSubsequent) {
|
||||
continue // Character already added by DB_LEFT
|
||||
} else {
|
||||
line.append(' ')
|
||||
}
|
||||
} else if (cell.db == ExtendedAttribute.DB_SO || cell.db == ExtendedAttribute.DB_SI) {
|
||||
line.append(' ')
|
||||
} else if (cell.ucs4 > ' ' && cell.ucs4.code != 0xFFFF) {
|
||||
line.append(cell.ucs4)
|
||||
} else {
|
||||
line.append(' ')
|
||||
}
|
||||
} else {
|
||||
line.append(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append(line.toString().trimEnd()).append('\n')
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package haus.nightmare.a3270
|
||||
|
||||
import haus.nightmare.a3270.storage.HostStorage
|
||||
import haus.nightmare.lib3270j.TerminalModel
|
||||
import haus.nightmare.lib3270j.charset.CodePageRegistry
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import java.lang.reflect.Method
|
||||
|
||||
class DBCSAndParityCompatibilityTest {
|
||||
|
||||
@Test
|
||||
fun testNewJapaneseCodePagesAvailableAndResolvable() {
|
||||
val availableKeys = HostStorage.AVAILABLE_CODE_PAGES.map { it.first }
|
||||
assertTrue("290 should be available in HostStorage", availableKeys.contains("290"))
|
||||
assertTrue("1027 should be available in HostStorage", availableKeys.contains("1027"))
|
||||
assertTrue("1390 should be available in HostStorage", availableKeys.contains("1390"))
|
||||
assertTrue("1399 should be available in HostStorage", availableKeys.contains("1399"))
|
||||
|
||||
val cp290 = CodePageRegistry.getCodePage("290")
|
||||
assertNotNull("Cp290 must resolve", cp290)
|
||||
assertFalse("Cp290 is SBCS", cp290.isDBCS)
|
||||
|
||||
val cp1027 = CodePageRegistry.getCodePage("1027")
|
||||
assertNotNull("Cp1027 must resolve", cp1027)
|
||||
assertFalse("Cp1027 is SBCS", cp1027.isDBCS)
|
||||
|
||||
val cp1390 = CodePageRegistry.getCodePage("1390")
|
||||
assertNotNull("Cp1390 must resolve", cp1390)
|
||||
assertTrue("Cp1390 is DBCS", cp1390.isDBCS)
|
||||
|
||||
val cp1399 = CodePageRegistry.getCodePage("1399")
|
||||
assertNotNull("Cp1399 must resolve", cp1399)
|
||||
assertTrue("Cp1399 is DBCS", cp1399.isDBCS)
|
||||
|
||||
val trans1390 = EbcdicTranslator("1390")
|
||||
assertTrue("Translator for 1390 must report isDBCS", trans1390.isDBCS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScreenExporterDBCSNoDuplication() {
|
||||
val trans = EbcdicTranslator("930")
|
||||
val screen = ScreenBuffer(TerminalModel.IBM_3279_2, trans)
|
||||
screen.erase(false)
|
||||
|
||||
// Put a 2-cell DBCS character at cells 0 and 1: '東' (\u6771)
|
||||
val cell0 = screen.getCell(0)
|
||||
cell0.db = ExtendedAttribute.DB_LEFT
|
||||
cell0.ucs4 = '\u6771'
|
||||
|
||||
val cell1 = screen.getCell(1)
|
||||
cell1.db = ExtendedAttribute.DB_RIGHT
|
||||
cell1.ucs4 = '\u6771'
|
||||
|
||||
// Put SBCS 'X' at cell 2
|
||||
val cell2 = screen.getCell(2)
|
||||
cell2.db = ExtendedAttribute.DB_NONE
|
||||
cell2.ucs4 = 'X'
|
||||
|
||||
// Invoke private generatePlainText via reflection
|
||||
val exporterClass = Class.forName("haus.nightmare.a3270.ui.ScreenExporterDialogKt")
|
||||
val method: Method = exporterClass.getDeclaredMethod(
|
||||
"generatePlainText",
|
||||
ScreenBuffer::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
Boolean::class.javaPrimitiveType
|
||||
)
|
||||
method.isAccessible = true
|
||||
|
||||
val plainText = method.invoke(null, screen, 1, 80, true) as String
|
||||
assertTrue("Output should start with single DBCS char + X: expected '東X', got: '$plainText'", plainText.startsWith("東X"))
|
||||
assertFalse("DBCS char must not be doubled into '東東X'", plainText.startsWith("東東"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTerminalGridMetricsCenteringClampedAtZero() {
|
||||
val terminalViewClass = Class.forName("haus.nightmare.a3270.ui.TerminalViewKt")
|
||||
val method = terminalViewClass.getDeclaredMethod(
|
||||
"calculateGridMetrics",
|
||||
Float::class.javaPrimitiveType,
|
||||
Float::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
method.isAccessible = true
|
||||
|
||||
// View is constrained / non-negative offsets
|
||||
val constrainedMetrics = method.invoke(null, 400f, 300f, 80, 24)
|
||||
val offXField = constrainedMetrics.javaClass.getDeclaredField("offsetX")
|
||||
offXField.isAccessible = true
|
||||
val offX = offXField.getFloat(constrainedMetrics)
|
||||
|
||||
val offYField = constrainedMetrics.javaClass.getDeclaredField("offsetY")
|
||||
offYField.isAccessible = true
|
||||
val offY = offYField.getFloat(constrainedMetrics)
|
||||
|
||||
assertTrue("Constrained offX must be non-negative", offX >= 0f)
|
||||
assertTrue("Constrained offY must be non-negative", offY >= 0f)
|
||||
|
||||
// Wide view (pillarbox with positive offX)
|
||||
val wideMetrics = method.invoke(null, 3000f, 1000f, 80, 24)
|
||||
val wideOffX = offXField.getFloat(wideMetrics)
|
||||
val wideOffY = offYField.getFloat(wideMetrics)
|
||||
assertTrue("Wide view offX should be positive", wideOffX > 0f)
|
||||
assertEquals(0f, wideOffY, 0.001f)
|
||||
|
||||
// Tall view (letterbox with positive offY)
|
||||
val tallMetrics = method.invoke(null, 1000f, 3000f, 80, 24)
|
||||
val tallOffX = offXField.getFloat(tallMetrics)
|
||||
val tallOffY = offYField.getFloat(tallMetrics)
|
||||
assertEquals(0f, tallOffX, 0.001f)
|
||||
assertTrue("Tall view offY should be positive", tallOffY > 0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExtendedAttributeTransparencyConstants() {
|
||||
assertEquals(0x00.toByte(), ExtendedAttribute.TR_DEFAULT)
|
||||
assertEquals(0xF0.toByte(), ExtendedAttribute.TR_OR)
|
||||
assertEquals(0xFF.toByte(), ExtendedAttribute.TR_OPAQUE)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user