From 51ec94a1257cd9dd39ddb18c05c1b95d63e01b73 Mon Sep 17 00:00:00 2001 From: Sarthak Agrawal Date: Mon, 5 Oct 2026 01:07:49 +0530 Subject: [PATCH] Show direct white labels on contrast-adjusted storage tiles --- Sources/StorageDaddy/DiskMapView.swift | 9 ++--- Sources/StorageDaddy/MapTileFill.swift | 35 +++++++++++++++++++ .../StorageDaddyTests/MapTileFillTests.swift | 28 +++++++++++++++ .../StorageMapReviewTests.swift | 5 +-- 4 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 Sources/StorageDaddy/MapTileFill.swift create mode 100644 Tests/StorageDaddyTests/MapTileFillTests.swift diff --git a/Sources/StorageDaddy/DiskMapView.swift b/Sources/StorageDaddy/DiskMapView.swift index 2253b51..91d4905 100644 --- a/Sources/StorageDaddy/DiskMapView.swift +++ b/Sources/StorageDaddy/DiskMapView.swift @@ -22,7 +22,7 @@ struct DiskMapView: View { let matches = m.matchesFilter(shape.node) let tint = isStaged ? Tints.coral : Tints.forKind(m.storageKind(shape.node)) let opacity = matches ? (isChild ? 0.65 : 0.9) : 0.18 - ctx.fill(shape.path, with: .color(tint.opacity(opacity))) + ctx.fill(shape.path, with: .color(MapTileFill.color(tint, intensity: opacity))) if isStaged { ctx.stroke(shape.path, with: .color(.white.opacity(0.85)), style: StrokeStyle(lineWidth: 2, dash: [5, 3])) } @@ -41,12 +41,10 @@ struct DiskMapView: View { ctx.stroke(shape.path, with: .color(selected ? Color.white : Color.black), lineWidth: 3) } if shape.labelRect.width > 55 && shape.labelRect.height > 28 { - // An opaque dark label backing keeps small text readable over - // semantic, nested, filtered and staged fills alike. + // Direct white labels sit on contrast-adjusted category fills. let ink = matches ? Color.white : Color(white: 0.78) if m.mode == .treemap, shape.labelRect.height > 65, shape.labelRect.width > 95 { let r = shape.labelRect.insetBy(dx: isChild ? 10 : 14, dy: isChild ? 9 : 12) - ctx.fill(Path(roundedRect: CGRect(x: r.minX - 4, y: r.minY - 3, width: r.width + 8, height: 52), cornerRadius: 5), with: .color(.black)) let name = shortenedTileName((isStaged ? "✓ Cleanup · " : "") + StorageLabels.name(shape.node), width: r.width) let size = m.mapLabel(m.mapWeight(shape.node)) let detail: String @@ -58,9 +56,8 @@ struct DiskMapView: View { detail = size } ctx.draw(Text(name).font(.system(size: isChild ? 14 : 17, weight: .semibold, design: .rounded)).foregroundColor(ink), in: CGRect(x: r.minX, y: r.minY, width: r.width, height: 24)) - ctx.draw(Text(detail).font(.system(size: isChild ? 12 : 14, weight: .medium)).foregroundColor(ink.opacity(0.85)), in: CGRect(x: r.minX, y: r.minY + 26, width: r.width, height: 20)) + ctx.draw(Text(detail).font(.system(size: isChild ? 12 : 14, weight: .medium)).foregroundColor(ink), in: CGRect(x: r.minX, y: r.minY + 26, width: r.width, height: 20)) } else { - ctx.fill(Path(roundedRect: shape.labelRect.insetBy(dx: 3, dy: 3), cornerRadius: 5), with: .color(.black)) let text = Text((isStaged ? "✓ " : "") + StorageLabels.name(shape.node)).font(.system(size: 12, weight: .semibold)).foregroundColor(ink) ctx.draw(text, in: shape.labelRect.insetBy(dx: 7, dy: 5)) } diff --git a/Sources/StorageDaddy/MapTileFill.swift b/Sources/StorageDaddy/MapTileFill.swift new file mode 100644 index 0000000..1e78f89 --- /dev/null +++ b/Sources/StorageDaddy/MapTileFill.swift @@ -0,0 +1,35 @@ +import AppKit +import SwiftUI + +/// Keep the semantic hue while providing contrast for small direct white labels. +enum MapTileFill { + static func color(_ tint: Color, intensity: Double) -> Color { + Color(nsColor: resolved(tint, intensity: intensity)) + } + + static func resolved(_ tint: Color, intensity: Double) -> NSColor { + let source = NSColor(tint).usingColorSpace(.sRGB) ?? .black + let depthScale = min(1, max(0, intensity / 0.9)) + let red = source.redComponent * 0.9 + let green = source.greenComponent * 0.9 + let blue = source.blueComponent * 0.9 + func luminance(_ scale: Double) -> Double { + func linear(_ channel: Double) -> Double { + channel <= 0.04045 ? channel / 12.92 : pow((channel + 0.055) / 1.055, 2.4) + } + return 0.2126 * linear(red * scale) + 0.7152 * linear(green * scale) + 0.0722 * linear(blue * scale) + } + var scale = 1.0 + if luminance(scale) > 0.14 { + var low = 0.0, high = 1.0 + for _ in 0..<24 { + let midpoint = (low + high) / 2 + if luminance(midpoint) <= 0.14 { low = midpoint } else { high = midpoint } + } + scale = low + } + // Opaque tiles prevent nested colors from accumulating over parent fills. + // Intensity still makes children darker and filtered tiles quieter. + return NSColor(srgbRed: red * scale * depthScale, green: green * scale * depthScale, blue: blue * scale * depthScale, alpha: 1) + } +} diff --git a/Tests/StorageDaddyTests/MapTileFillTests.swift b/Tests/StorageDaddyTests/MapTileFillTests.swift new file mode 100644 index 0000000..22d82d8 --- /dev/null +++ b/Tests/StorageDaddyTests/MapTileFillTests.swift @@ -0,0 +1,28 @@ +import AppKit +import Testing +import DiskCore +@testable import StorageDaddy + +@MainActor struct MapTileFillTests { + @Test func directLabelsHaveContrastAcrossEverySemanticAndReviewState() { + func luminance(_ color: NSColor) -> Double { + let c = color.usingColorSpace(.sRGB)! + func linear(_ value: Double) -> Double { + value <= 0.04045 ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4) + } + return 0.2126 * linear(c.redComponent) + 0.7152 * linear(c.greenComponent) + 0.0722 * linear(c.blueComponent) + } + for tint in StorageKind.allCases.map(Tints.forKind) + [Tints.coral] { + let parent = MapTileFill.resolved(tint, intensity: 0.9) + let child = MapTileFill.resolved(tint, intensity: 0.65) + let filtered = MapTileFill.resolved(tint, intensity: 0.18) + for fill in [parent, child] { + #expect(1.05 / (luminance(fill) + 0.05) >= 5.5) + #expect(fill.alphaComponent == 1) + } + #expect((luminance(NSColor(white: 0.78, alpha: 1)) + 0.05) / (luminance(filtered) + 0.05) >= 4.5) + #expect(luminance(child) <= luminance(parent)) + #expect(luminance(filtered) < luminance(child)) + } + } +} diff --git a/Tests/StorageDaddyTests/StorageMapReviewTests.swift b/Tests/StorageDaddyTests/StorageMapReviewTests.swift index 9fb0d97..a4ec172 100644 --- a/Tests/StorageDaddyTests/StorageMapReviewTests.swift +++ b/Tests/StorageDaddyTests/StorageMapReviewTests.swift @@ -160,8 +160,9 @@ struct StorageMapReviewTests { m.volumeFree = 45_000_000_000; m.volumeCapacity = 500_000_000_000 m.progress = "Synthetic scan · Nothing moved"; m.refreshFocus() let wasActive = NSApplication.shared.isActive - let directory = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() - .appendingPathComponent("artifacts/compact-map") + let directory = ProcessInfo.processInfo.environment["STORAGEDADDY_MAP_EVIDENCE_DIR"].map { URL(fileURLWithPath: $0) } + ?? URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() + .appendingPathComponent("artifacts/compact-map") try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) for (width, height) in [(880, 600), (1200, 850), (1440, 900)] { let states = ["bytes", "files", "filter", "staged", "inspector-hidden"]