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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
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.Key
|
||||||
import androidx.compose.ui.input.key.KeyShortcut
|
import androidx.compose.ui.input.key.KeyShortcut
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -252,10 +254,24 @@ fun main() {
|
|||||||
// Callback set by App() for single pane navigation from MenuBar
|
// Callback set by App() for single pane navigation from MenuBar
|
||||||
var navigateToScreen by remember { mutableStateOf<((DeckColumnType) -> Unit)?>(null) }
|
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(
|
Window(
|
||||||
onCloseRequest = ::exitApplication,
|
onCloseRequest = ::exitApplication,
|
||||||
state = windowState,
|
state = windowState,
|
||||||
title = "Amethyst",
|
title = "Amethyst",
|
||||||
|
icon = appIcon,
|
||||||
) {
|
) {
|
||||||
// macOS: transparent + full-window-content title bar so the deck/sidebar
|
// macOS: transparent + full-window-content title bar so the deck/sidebar
|
||||||
// shows through, with traffic lights still drawn on top. No-op elsewhere.
|
// shows through, with traffic lights still drawn on top. No-op elsewhere.
|
||||||
|
|||||||
+45
-12
@@ -77,26 +77,59 @@ object PlatformColorScheme {
|
|||||||
outlineVariant = Color(0xFF3A3A3A),
|
outlineVariant = Color(0xFF3A3A3A),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun macOSLight(accent: Color) =
|
private fun macOSLight(accent: Color): ColorScheme {
|
||||||
lightColorScheme(
|
// Apple's accent palette includes Yellow (#F8BA00) and Graphite (#8C8C8C)
|
||||||
primary = accent,
|
// which have terrible contrast on any light surface. Darken high-luminance
|
||||||
onPrimary = onAccent(accent),
|
// accents so the primary color stays readable without disabling the user's
|
||||||
secondary = accent,
|
// preference — the hue is preserved, just pulled toward a usable lightness.
|
||||||
tertiary = accent,
|
val readable = darkenForLight(accent)
|
||||||
background = Color(0xFFECECEC),
|
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),
|
onBackground = Color(0xFF1A1A1A),
|
||||||
surface = Color(0xFFFFFFFF),
|
surface = Color(0xFFFFFFFF),
|
||||||
onSurface = Color(0xFF1A1A1A),
|
onSurface = Color(0xFF1A1A1A),
|
||||||
surfaceVariant = Color(0xFFF2F2F2),
|
surfaceVariant = Color(0xFFECECEC),
|
||||||
onSurfaceVariant = Color(0xFF555555),
|
onSurfaceVariant = Color(0xFF555555),
|
||||||
surfaceContainer = Color(0xFFF6F6F6),
|
surfaceContainer = Color(0xFFECECEC),
|
||||||
surfaceContainerHigh = Color(0xFFEDEDED),
|
surfaceContainerHigh = Color(0xFFE5E5E5),
|
||||||
surfaceContainerHighest = Color(0xFFE5E5E5),
|
surfaceContainerHighest = Color(0xFFDDDDDD),
|
||||||
surfaceContainerLow = Color(0xFFFAFAFA),
|
surfaceContainerLow = Color(0xFFF0F0F0),
|
||||||
surfaceContainerLowest = Color(0xFFFFFFFF),
|
surfaceContainerLowest = Color(0xFFFFFFFF),
|
||||||
outline = Color(0xFFB0B0B0),
|
outline = Color(0xFFB0B0B0),
|
||||||
outlineVariant = Color(0xFFD8D8D8),
|
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) ────────────────────────────────────────────────────
|
// ── 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