Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
242d7a7c42 |
+2
-2
@@ -6,7 +6,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "com.tpuahsiew.wirc"
|
group = "com.tpuahsiew.wirc"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
|
|
||||||
// No project-level repositories{} block here: settings.gradle.kts sets
|
// No project-level repositories{} block here: settings.gradle.kts sets
|
||||||
// dependencyResolutionManagement.repositoriesMode = FAIL_ON_PROJECT_REPOS and already
|
// dependencyResolutionManagement.repositoriesMode = FAIL_ON_PROJECT_REPOS and already
|
||||||
@@ -38,7 +38,7 @@ compose.desktop {
|
|||||||
appResourcesRootDir.set(project.file("installerResources"))
|
appResourcesRootDir.set(project.file("installerResources"))
|
||||||
targetFormats(TargetFormat.Msi, TargetFormat.Exe)
|
targetFormats(TargetFormat.Msi, TargetFormat.Exe)
|
||||||
packageName = "wIRC"
|
packageName = "wIRC"
|
||||||
packageVersion = "0.1.5"
|
packageVersion = "0.1.6"
|
||||||
windows {
|
windows {
|
||||||
menuGroup = "wIRC"
|
menuGroup = "wIRC"
|
||||||
upgradeUuid = "8f14e45f-ceea-4a5e-9c3b-5b1f0e8c2b7a"
|
upgradeUuid = "8f14e45f-ceea-4a5e-9c3b-5b1f0e8c2b7a"
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.hoverable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
||||||
import androidx.compose.foundation.window.WindowDraggableArea
|
import androidx.compose.foundation.window.WindowDraggableArea
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -27,6 +31,7 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -68,11 +73,12 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
// TEMP DEBUG (see task: auto-scroll from-scratch audit) - remove once confirmed fixed.
|
// Title-bar button fade (nicklist / settings / minimize / close). The buttons sit at
|
||||||
// label identifies which window's log line this is, so multi-window repros are legible.
|
// TITLE_BAR_BUTTON_REST_ALPHA until hovered, then animate to TITLE_BAR_BUTTON_HOVER_ALPHA
|
||||||
private fun ircUiDebug(label: String, msg: String) {
|
// over TITLE_BAR_BUTTON_FADE_MS. The channel name text is not affected.
|
||||||
println("[ui-debug][$label] $msg")
|
private const val TITLE_BAR_BUTTON_REST_ALPHA = 0f
|
||||||
}
|
private const val TITLE_BAR_BUTTON_HOVER_ALPHA = 1f
|
||||||
|
private const val TITLE_BAR_BUTTON_FADE_MS = 150
|
||||||
|
|
||||||
// Single-space separator between the padded nick column and the message body. Previously
|
// Single-space separator between the padded nick column and the message body. Previously
|
||||||
// a 2-space trailing gap on CHAT lines (on top of the padEnd() column padding itself),
|
// a 2-space trailing gap on CHAT lines (on top of the padEnd() column padding itself),
|
||||||
@@ -406,6 +412,8 @@ fun WindowScope.ChannelScreen(
|
|||||||
// because manually accumulating dragAmount into windowState.position fights
|
// because manually accumulating dragAmount into windowState.position fights
|
||||||
// with the OS's own window-move handling; WindowDraggableArea talks to that
|
// with the OS's own window-move handling; WindowDraggableArea talks to that
|
||||||
// directly instead of reimplementing it.
|
// directly instead of reimplementing it.
|
||||||
|
// The four buttons on the right fade in on hover (see FadingTitleBarButton); the
|
||||||
|
// channel name stays fully opaque.
|
||||||
WindowDraggableArea(modifier = Modifier.fillMaxWidth()) {
|
WindowDraggableArea(modifier = Modifier.fillMaxWidth()) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -416,20 +424,20 @@ fun WindowScope.ChannelScreen(
|
|||||||
) {
|
) {
|
||||||
Text(channelName, color = textColor, fontFamily = FontFamily.Monospace, style = MaterialTheme.typography.labelSmall)
|
Text(channelName, color = textColor, fontFamily = FontFamily.Monospace, style = MaterialTheme.typography.labelSmall)
|
||||||
Row {
|
Row {
|
||||||
IconButton(onClick = { onShowUserListChange(!displaySettings.showUserList) }, modifier = Modifier.size(28.dp)) {
|
FadingTitleBarButton(onClick = { onShowUserListChange(!displaySettings.showUserList) }) {
|
||||||
Icon(Icons.Default.Person, contentDescription = "Toggle user list", tint = textColor, modifier = Modifier.size(16.dp))
|
Icon(Icons.Default.Person, contentDescription = "Toggle user list", tint = textColor, modifier = Modifier.size(16.dp))
|
||||||
}
|
}
|
||||||
IconButton(onClick = { showSettings = !showSettings }, modifier = Modifier.size(28.dp)) {
|
FadingTitleBarButton(onClick = { showSettings = !showSettings }) {
|
||||||
Icon(Icons.Default.Settings, contentDescription = "Window settings", tint = textColor, modifier = Modifier.size(16.dp))
|
Icon(Icons.Default.Settings, contentDescription = "Window settings", tint = textColor, modifier = Modifier.size(16.dp))
|
||||||
}
|
}
|
||||||
IconButton(onClick = { windowState.isMinimized = true }, modifier = Modifier.size(28.dp)) {
|
FadingTitleBarButton(onClick = { windowState.isMinimized = true }) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 10.dp, height = 2.dp)
|
.size(width = 10.dp, height = 2.dp)
|
||||||
.background(textColor)
|
.background(textColor)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onCloseWindow, modifier = Modifier.size(28.dp)) {
|
FadingTitleBarButton(onClick = onCloseWindow) {
|
||||||
Icon(Icons.Default.Close, contentDescription = "Close window", tint = textColor, modifier = Modifier.size(16.dp))
|
Icon(Icons.Default.Close, contentDescription = "Close window", tint = textColor, modifier = Modifier.size(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -796,6 +804,31 @@ fun WindowScope.ChannelScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A 28dp title-bar IconButton that rests invisible and fades in while hovered. The fade
|
||||||
|
// is a graphicsLayer alpha only - the button is always laid out, drawn and hit-tested, so
|
||||||
|
// it still gets hover and clicks at alpha 0. The alpha is read inside the graphicsLayer
|
||||||
|
// lambda so the animation only re-draws the layer instead of recomposing every frame.
|
||||||
|
// IconButton's own hover/press state layer is drawn inside this layer, so it still shows
|
||||||
|
// once the button has faded in.
|
||||||
|
@Composable
|
||||||
|
private fun FadingTitleBarButton(onClick: () -> Unit, content: @Composable () -> Unit) {
|
||||||
|
val hoverSource = remember { MutableInteractionSource() }
|
||||||
|
val hovered by hoverSource.collectIsHoveredAsState()
|
||||||
|
val alpha by animateFloatAsState(
|
||||||
|
targetValue = if (hovered) TITLE_BAR_BUTTON_HOVER_ALPHA else TITLE_BAR_BUTTON_REST_ALPHA,
|
||||||
|
animationSpec = tween(durationMillis = TITLE_BAR_BUTTON_FADE_MS)
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(28.dp)
|
||||||
|
.hoverable(hoverSource)
|
||||||
|
.graphicsLayer { this.alpha = alpha }
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// In-memory LRU cache of decoded image bitmaps, keyed by URL, so scrolling a channel
|
// In-memory LRU cache of decoded image bitmaps, keyed by URL, so scrolling a channel
|
||||||
// with image previews on doesn't re-fetch/re-decode images that already loaded.
|
// with image previews on doesn't re-fetch/re-decode images that already loaded.
|
||||||
private object ImageCache {
|
private object ImageCache {
|
||||||
|
|||||||
Reference in New Issue
Block a user