martmists
04/21/2025, 10:58 PMmartmists
04/21/2025, 11:01 PMmartmists
04/21/2025, 11:03 PMTextField(
value!!,
{},
readOnly = true, // not the case for all TextFields!
singleLine = false,
modifier = Modifier.fillMaxSize().padding(5.dp),
textStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace),
)
ephemient
04/22/2025, 12:13 AMSelectionContainer { LazyColumn { items { Text() } } }
instead of a single TextField
romainguy
04/22/2025, 12:27 AM\xXX
)romainguy
04/22/2025, 12:28 AMText()
will be much better for this purposemartmists
04/22/2025, 1:02 AMMonospacing comes from the font, the problem you are mentioning is an alignement issue. You basically need to snap the width of your text component to a multiple of 4 charactersNo, look at the bottom right, the 0 is not aligned under the x but half a character off to the side.
martmists
04/22/2025, 1:02 AMmartmists
04/22/2025, 1:06 AMephemient
04/22/2025, 1:11 AMmartmists
04/22/2025, 1:12 AMromainguy
04/22/2025, 1:13 AMmartmists
04/22/2025, 1:14 AMLazyColumn { item { Text(textVariableHere) } }
ephemient
04/22/2025, 1:14 AMephemient
04/22/2025, 1:15 AMromainguy
04/22/2025, 1:22 AMtextVariable
though? How you read the file and split it?romainguy
04/22/2025, 1:22 AMmartmists
04/22/2025, 1:23 AMtextVariable = fileByteArray.map { if (it >= 0x20) it.toInt().toChar() else "\\x${it.toHexString(HexFormat.UpperCase)}" }.joinToString("")
romainguy
04/22/2025, 1:24 AMromainguy
04/22/2025, 1:25 AMmartmists
04/22/2025, 1:25 AMromainguy
04/22/2025, 1:26 AMText()
item per row you want to displayromainguy
04/22/2025, 1:26 AMephemient
04/22/2025, 1:26 AMromainguy
04/22/2025, 1:27 AMText()
item display a sub-range for your original byte arrayromainguy
04/22/2025, 1:28 AMromainguy
04/22/2025, 1:28 AMStringBuilder(sizeOfRange * 4)
(sizeOfRange
is a number of bytes to display)romainguy
04/22/2025, 1:28 AMmartmists
04/22/2025, 1:44 AMromainguy
04/22/2025, 1:45 AMromainguy
04/22/2025, 1:45 AMmartmists
04/22/2025, 1:45 AMromainguy
04/22/2025, 1:46 AMephemient
04/22/2025, 1:46 AMmartmists
04/22/2025, 1:46 AMmartmists
04/22/2025, 2:11 AMephemient
04/22/2025, 2:16 AMmartmists
04/22/2025, 4:09 PM// Assumes mono text style
@Composable
fun LargeTextField(
text: String,
onChange: (String) -> Unit,
beforeLoadingComplete: @Composable () -> Unit,
readonly: Boolean = false,
style: TextStyle = LocalTextStyle.current,
modifier: Modifier = Modifier,
isError: Boolean = false,
supportingText: (@Composable () -> Unit)? = null
) {
val measurer = rememberTextMeasurer()
val scope = rememberCoroutineScope()
val scroll = rememberScrollState()
val state = rememberLazyListState()
var displayText by remember(text) { mutableStateOf(text) }
var displayLines by remember { mutableStateOf(emptyArray<String>()) } // FIXME: This needs to be reset when the text parameter changes, but not when it changes from onChange() changing the text
val textLength by derivedStateOf { displayText.length }
val mod = if (readonly) {
modifier.padding(end = 5.dp).border(1.dp, Color.Gray, RoundedCornerShape(4.dp)).padding(16.dp)
} else modifier.padding(end = 5.dp)
Box {
BoxWithConstraints(mod, contentAlignment = Alignment.TopStart) {
LaunchedEffect(textLength, constraints) {
scope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
val s = text.substring(0 until 10_000.coerceAtMost(text.length)).replace("\n", "")
val res = measurer.measure(s, style, constraints = constraints)
var width = (0 until res.lineCount).maxOf { res.getLineEnd(it, true) - res.getLineStart(it) }
if (!readonly) width -= 4
width = width.coerceAtLeast(1)
var lines = text.length / width
if (text.length % width != 0) {
lines++
}
displayLines = Array(lines) {
val start = it * width
val end = ((it + 1) * width).coerceAtMost(text.length)
text.substring(start, end)
}
}
}
if (displayLines.isEmpty()) {
beforeLoadingComplete()
} else if (readonly) {
SelectionContainer {
LazyColumn(modifier = Modifier.fillMaxSize(), state = state, horizontalAlignment = Alignment.Start, verticalArrangement = <http://Arrangement.Top|Arrangement.Top>) {
items(displayLines) {
Text(it, style = style)
}
}
}
} else {
// FIXME: Implement this more efficiently
// I considered using displayLines.joinToString('\n') so the text measuring inside OutlinedTextField would be faster,
// but then turning it back into displayText is something I don't know how to do.
// Especially since the text itself might contain newlines.
OutlinedTextField(
displayText,
{
displayText = it
onChange(it)
},
textStyle = style,
isError = isError,
supportingText = supportingText,
modifier = Modifier.verticalScroll(scroll)
)
}
}
if (displayLines.isNotEmpty()) {
val adapter = if (readonly) {
rememberScrollbarAdapter(state)
} else {
rememberScrollbarAdapter(scroll)
}
VerticalScrollbar(adapter, modifier = Modifier.align(Alignment.CenterEnd).padding(vertical = 2.dp).fillMaxHeight(), style = LocalScrollbarStyle.current.copy(unhoverColor = MaterialTheme.colorScheme.primaryContainer, hoverColor = MaterialTheme.colorScheme.primary))
}
}
}
martmists
04/22/2025, 4:10 PM