fix(desktop): macOS light-mode primary contrast + transparent window icon
Two separate readability fixes:
1. macOS light-mode primary was poor contrast on the gray background.
- Background was #ECECEC (Apple's title-bar chrome gray), now #F5F5F7
(Apple's secondarySystemBackgroundColor — used for main content).
- Surface container tones shifted accordingly so the sidebar
(surfaceContainer) is still visibly grayer than the content.
- Accents with high luminance (Apple Yellow, Graphite) now darken to
a readable shade in light mode via a luminance clamp that preserves
hue — saturated blue is untouched, but yellow pulls toward amber
so the primary color stays visible on near-white surfaces.
2. Window icon was the generic Java cup during `./gradlew :desktopApp:run`
because the Window composable had no `icon` param. The 100x100 icon
in resources also looked blocky in the dock. Now:
- icon.png replaced with the 512x512 transparent logo from
fastlane/metadata/android/en-US/images/icon.png.
- Window(icon = ...) loads it via Skia (non-deprecated path), shown
in the macOS dock / Windows taskbar / GNOME & KDE task switchers.
https://claude.ai/code/session_01NufduPfZvYQVYwLkbCjCUo
This commit is contained in:
@@ -61,6 +61,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyShortcut
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -252,10 +254,24 @@ fun main() {
|
||||
// Callback set by App() for single pane navigation from MenuBar
|
||||
var navigateToScreen by remember { mutableStateOf<((DeckColumnType) -> Unit)?>(null) }
|
||||
|
||||
// Transparent 512x512 PNG shown in the macOS dock / Windows taskbar / GNOME
|
||||
// & KDE task switchers while running via gradle (the packaged app uses the
|
||||
// icon.icns / icon.ico configured in nativeDistributions).
|
||||
val appIcon =
|
||||
remember {
|
||||
val bytes = Unit::class.java.getResourceAsStream("/icon.png")!!.readBytes()
|
||||
val bitmap =
|
||||
org.jetbrains.skia.Image
|
||||
.makeFromEncoded(bytes)
|
||||
.toComposeImageBitmap()
|
||||
BitmapPainter(bitmap)
|
||||
}
|
||||
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
state = windowState,
|
||||
title = "Amethyst",
|
||||
icon = appIcon,
|
||||
) {
|
||||
// macOS: transparent + full-window-content title bar so the deck/sidebar
|
||||
// shows through, with traffic lights still drawn on top. No-op elsewhere.
|
||||
|
||||
+45
-12
@@ -77,26 +77,59 @@ object PlatformColorScheme {
|
||||
outlineVariant = Color(0xFF3A3A3A),
|
||||
)
|
||||
|
||||
private fun macOSLight(accent: Color) =
|
||||
lightColorScheme(
|
||||
primary = accent,
|
||||
onPrimary = onAccent(accent),
|
||||
secondary = accent,
|
||||
tertiary = accent,
|
||||
background = Color(0xFFECECEC),
|
||||
private fun macOSLight(accent: Color): ColorScheme {
|
||||
// Apple's accent palette includes Yellow (#F8BA00) and Graphite (#8C8C8C)
|
||||
// which have terrible contrast on any light surface. Darken high-luminance
|
||||
// accents so the primary color stays readable without disabling the user's
|
||||
// preference — the hue is preserved, just pulled toward a usable lightness.
|
||||
val readable = darkenForLight(accent)
|
||||
return lightColorScheme(
|
||||
primary = readable,
|
||||
onPrimary = onAccent(readable),
|
||||
secondary = readable,
|
||||
tertiary = readable,
|
||||
// Apple's light-mode main content background is near-white (#F5F5F7 /
|
||||
// Apple's secondarySystemBackgroundColor); the #ECECEC gray is only
|
||||
// used behind the title bar chrome — which we map to surfaceContainer.
|
||||
background = Color(0xFFF5F5F7),
|
||||
onBackground = Color(0xFF1A1A1A),
|
||||
surface = Color(0xFFFFFFFF),
|
||||
onSurface = Color(0xFF1A1A1A),
|
||||
surfaceVariant = Color(0xFFF2F2F2),
|
||||
surfaceVariant = Color(0xFFECECEC),
|
||||
onSurfaceVariant = Color(0xFF555555),
|
||||
surfaceContainer = Color(0xFFF6F6F6),
|
||||
surfaceContainerHigh = Color(0xFFEDEDED),
|
||||
surfaceContainerHighest = Color(0xFFE5E5E5),
|
||||
surfaceContainerLow = Color(0xFFFAFAFA),
|
||||
surfaceContainer = Color(0xFFECECEC),
|
||||
surfaceContainerHigh = Color(0xFFE5E5E5),
|
||||
surfaceContainerHighest = Color(0xFFDDDDDD),
|
||||
surfaceContainerLow = Color(0xFFF0F0F0),
|
||||
surfaceContainerLowest = Color(0xFFFFFFFF),
|
||||
outline = Color(0xFFB0B0B0),
|
||||
outlineVariant = Color(0xFFD8D8D8),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales an accent color toward black until its relative luminance is at
|
||||
* most [maxLuminance]. Preserves hue (all RGB channels scale by the same
|
||||
* factor). Only applied on light surfaces — on dark surfaces the raw accent
|
||||
* already has good contrast.
|
||||
*
|
||||
* 0.38 was picked empirically: it keeps a saturated blue visible without
|
||||
* darkening it, while pulling yellow/graphite down to a readable amber/gray.
|
||||
*/
|
||||
private fun darkenForLight(
|
||||
c: Color,
|
||||
maxLuminance: Float = 0.38f,
|
||||
): Color {
|
||||
val lum = 0.2126f * c.red + 0.7152f * c.green + 0.0722f * c.blue
|
||||
if (lum <= maxLuminance) return c
|
||||
val scale = maxLuminance / lum
|
||||
return Color(
|
||||
red = (c.red * scale).coerceIn(0f, 1f),
|
||||
green = (c.green * scale).coerceIn(0f, 1f),
|
||||
blue = (c.blue * scale).coerceIn(0f, 1f),
|
||||
alpha = c.alpha,
|
||||
)
|
||||
}
|
||||
|
||||
// ── GNOME (libadwaita) ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 16 KiB |
Reference in New Issue
Block a user