Keep image previews and wrapped messages on whole lines so the top line isn't cut off, bump to 0.1.13
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5bcd1a7bcd
commit
4ac7b75962
+2
-2
@@ -6,7 +6,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "com.tpuahsiew.wirc"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
|
||||
// No project-level repositories{} block here: settings.gradle.kts sets
|
||||
// dependencyResolutionManagement.repositoriesMode = FAIL_ON_PROJECT_REPOS and already
|
||||
@@ -38,7 +38,7 @@ compose.desktop {
|
||||
appResourcesRootDir.set(project.file("installerResources"))
|
||||
targetFormats(TargetFormat.Msi, TargetFormat.Exe)
|
||||
packageName = "wIRC"
|
||||
packageVersion = "0.1.12"
|
||||
packageVersion = "0.1.13"
|
||||
windows {
|
||||
menuGroup = "wIRC"
|
||||
upgradeUuid = "8f14e45f-ceea-4a5e-9c3b-5b1f0e8c2b7a"
|
||||
|
||||
@@ -61,6 +61,7 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.LayoutCoordinates
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
@@ -1059,7 +1060,10 @@ fun MessageScrollback(
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
// Every row is kept a whole number of lines tall (nearest whole line, to absorb
|
||||
// the pixel or two a wrapped message can be off by), so the line snapping above
|
||||
// stays lined up all the way to the top of the window instead of drifting.
|
||||
Column(modifier = Modifier.fillMaxWidth().snapHeightToLines(lineHeightPx, roundUp = false)) {
|
||||
// Timestamp + nick are one text and the body another, both BasicText with
|
||||
// the same explicit style - BasicText never merges the ambient LocalTextStyle,
|
||||
// so neither picks up MaterialTheme's bodyLarge 24sp lineHeight (which once
|
||||
@@ -1085,14 +1089,19 @@ fun MessageScrollback(
|
||||
}
|
||||
if (displaySettings.showImagePreviews) {
|
||||
extractImageUrls(msg.rawText).forEach { url ->
|
||||
UrlImagePreview(
|
||||
url = url,
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp, top = 2.dp, bottom = 4.dp)
|
||||
.heightIn(max = 200.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
onClick = { openUrlInBrowser(url) }
|
||||
)
|
||||
// A thumbnail can be any height, so its slot is rounded UP to whole lines
|
||||
// (never cropping the picture); otherwise every line above it in the
|
||||
// window would sit a fraction of a line off and show cut in half at the top.
|
||||
Box(modifier = Modifier.snapHeightToLines(lineHeightPx, roundUp = true)) {
|
||||
UrlImagePreview(
|
||||
url = url,
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp, top = 2.dp, bottom = 4.dp)
|
||||
.heightIn(max = 200.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
onClick = { openUrlInBrowser(url) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1106,6 +1115,20 @@ fun MessageScrollback(
|
||||
}
|
||||
}
|
||||
|
||||
// Reports this element's height as a whole number of text lines ([linePx] each): rounded up
|
||||
// for image slots (the picture is never cut), to the nearest line for text rows (a wrapped
|
||||
// message can measure a pixel or two off an exact multiple). Content is drawn from the top.
|
||||
private fun Modifier.snapHeightToLines(linePx: Int, roundUp: Boolean): Modifier = layout { measurable, constraints ->
|
||||
val placeable = measurable.measure(constraints.copy(minHeight = 0))
|
||||
val h = placeable.height
|
||||
val snapped = when {
|
||||
linePx <= 0 || h == 0 -> h
|
||||
roundUp -> ((h + linePx - 1) / linePx) * linePx
|
||||
else -> (((h + linePx / 2) / linePx).coerceAtLeast(1)) * linePx
|
||||
}.coerceIn(constraints.minHeight, constraints.maxHeight.coerceAtLeast(constraints.minHeight))
|
||||
layout(placeable.width, snapped) { placeable.place(0, 0) }
|
||||
}
|
||||
|
||||
// Which of a row's two texts a TextPos is in: the timestamp/nick prefix, or the body.
|
||||
private const val PART_PREFIX = 0
|
||||
private const val PART_BODY = 1
|
||||
|
||||
Reference in New Issue
Block a user