From 897a33b0239c48b705fe1ac9ce3082bffdb76916 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Tue, 25 Aug 2026 23:26:46 +0200 Subject: [PATCH 01/31] feat(keyboard): add M0116 keycap style --- .../KeyboardVisualizerGroupView.swift | 10 + .../Keyboard/KeyboardVisualizerTheme.swift | 7 +- .../Keyboard/KeycapAppearance.swift | 69 +++++++ .../Visualizers/Keyboard/KeycapStyle.swift | 2 + .../Keyboard/Renderers/KeycapContext.swift | 7 + .../Renderers/KeycapLegendRenderer.swift | 2 +- .../Renderers/KeycapRendererFactory.swift | 2 + .../KeycapStyle+SizeNormalization.swift | 3 + .../Renderers/M0116/M0116KeycapMetrics.swift | 77 ++++++++ .../Renderers/M0116/M0116KeycapRenderer.swift | 185 ++++++++++++++++++ .../Renderers/M0116/M0116Palette.swift | 41 ++++ .../Resources/de.lproj/Localizable.strings | 1 + .../Resources/en.lproj/Localizable.strings | 1 + .../Resources/es.lproj/Localizable.strings | 1 + .../Resources/fr.lproj/Localizable.strings | 1 + .../Resources/ja.lproj/Localizable.strings | 1 + .../Resources/nl.lproj/Localizable.strings | 1 + .../Resources/pl.lproj/Localizable.strings | 1 + .../Resources/pt-BR.lproj/Localizable.strings | 1 + .../Resources/uk.lproj/Localizable.strings | 1 + .../zh-Hans.lproj/Localizable.strings | 1 + ...oardVisualizerGroupViewSnapshotTests.swift | 74 ++++++- .../KeyboardVisualizerSettingsTests.swift | 16 ++ ...KKeycaps.m0116-command-shift-k-keycaps.png | Bin 0 -> 59655 bytes ...6StyleEscapeKeycap.m0116-escape-keycap.png | Bin 0 -> 26894 bytes ...stRendersM0116StyleKeycap.m0116-keycap.png | Bin 0 -> 22360 bytes 26 files changed, 498 insertions(+), 7 deletions(-) create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116Palette.swift create mode 100644 Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleCommandShiftKKeycaps.m0116-command-shift-k-keycaps.png create mode 100644 Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleEscapeKeycap.m0116-escape-keycap.png create mode 100644 Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleKeycap.m0116-keycap.png diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerGroupView.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerGroupView.swift index 1670afdc..08f4d183 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerGroupView.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerGroupView.swift @@ -142,6 +142,8 @@ private extension KeyboardVisualizerGroupView { fallbackHeight = MinimalKeycapMetrics.height case .retro: fallbackHeight = RetroKeycapMetrics.height + case .m0116: + fallbackHeight = M0116KeycapMetrics.height case .apple, .pbt: fallbackHeight = AppleKeycapMetrics.height } @@ -165,6 +167,8 @@ private extension KeyboardVisualizerGroupView { return MinimalKeycapMetrics.groupPadding case .retro: return RetroKeycapMetrics.groupPadding + case .m0116: + return M0116KeycapMetrics.groupPadding case .apple: return AppleKeycapMetrics.groupPadding case .pbt: @@ -178,6 +182,8 @@ private extension KeyboardVisualizerGroupView { return MinimalKeycapMetrics.itemSpacing case .retro: return RetroKeycapMetrics.itemSpacing + case .m0116: + return M0116KeycapMetrics.itemSpacing case .apple, .pbt: return AppleKeycapMetrics.itemSpacing } @@ -189,6 +195,8 @@ private extension KeyboardVisualizerGroupView { return MinimalKeycapMetrics.repeatBadgeInset case .retro: return RetroKeycapMetrics.repeatBadgeInset + case .m0116: + return M0116KeycapMetrics.repeatBadgeInset case .apple: return AppleKeycapMetrics.repeatBadgeInset case .pbt: @@ -208,6 +216,8 @@ private extension KeyboardVisualizerGroupView { radius = insetBounds.height / 2 case .retro: radius = 24 + case .m0116: + radius = 12 case .apple, .pbt: radius = 18 } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerTheme.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerTheme.swift index 313ad0aa..bd890618 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerTheme.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerTheme.swift @@ -47,7 +47,7 @@ extension KeyboardVisualizerTheme { // MARK: - Tokens extension KeyboardVisualizerTheme { - private static func badgeTokens(swatchColor: NSColor, textColor: NSColor) -> (fill: NSColor, stroke: NSColor, highlight: NSColor, text: NSColor) { + static func badgeTokens(swatchColor: NSColor, textColor: NSColor) -> (fill: NSColor, stroke: NSColor, highlight: NSColor, text: NSColor) { let baseFill = swatchColor.relativeLuminance > 0.6 ? swatchColor.darkened(by: 0.12) : swatchColor.lightened(by: 0.08) @@ -375,6 +375,11 @@ extension KeyboardVisualizerTheme { return KeycapAppearance.minimal(KeycapAppearance.Minimal(tokens: tokens)) case .retro: return KeycapAppearance.retro(KeycapAppearance.Retro(tokens: tokens)) + case .m0116: + // This style ships the keyboard's own colors; themes are not wired into it yet. + return KeycapAppearance.m0116( + KeycapAppearance.M0116(tokens: .m0116Hardware(legendColorOverride: legendColorOverride)) + ) } } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapAppearance.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapAppearance.swift index a1e1c5e2..935d7926 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapAppearance.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapAppearance.swift @@ -37,6 +37,7 @@ enum KeycapAppearance { case pbt(PBT) case minimal(Minimal) case retro(Retro) + case m0116(M0116) static let dotColor = NSColor(calibratedRed: 0.0, green: 1.0, blue: 0.1, alpha: 1) static let inactiveDotColor = NSColor(calibratedWhite: 0.42, alpha: 0.7) @@ -47,6 +48,7 @@ enum KeycapAppearance { case .pbt(let appearance): appearance.textColor case .minimal(let appearance): appearance.textColor case .retro(let appearance): appearance.textColor + case .m0116(let appearance): appearance.textColor } } @@ -56,6 +58,7 @@ enum KeycapAppearance { case .pbt(let appearance): appearance.groupBackgroundColor case .minimal(let appearance): appearance.groupBackgroundColor case .retro(let appearance): appearance.groupBackgroundColor + case .m0116(let appearance): appearance.groupBackgroundColor } } @@ -65,6 +68,7 @@ enum KeycapAppearance { case .pbt(let appearance): appearance.groupStrokeColor case .minimal(let appearance): appearance.groupStrokeColor case .retro(let appearance): appearance.groupStrokeColor + case .m0116(let appearance): appearance.groupStrokeColor } } @@ -74,6 +78,7 @@ enum KeycapAppearance { case .pbt(let appearance): appearance.badgeFillColor case .minimal(let appearance): appearance.badgeFillColor case .retro(let appearance): appearance.badgeFillColor + case .m0116(let appearance): appearance.badgeFillColor } } @@ -83,6 +88,7 @@ enum KeycapAppearance { case .pbt(let appearance): appearance.badgeStrokeColor case .minimal(let appearance): appearance.badgeStrokeColor case .retro(let appearance): appearance.badgeStrokeColor + case .m0116(let appearance): appearance.badgeStrokeColor } } @@ -92,6 +98,7 @@ enum KeycapAppearance { case .pbt(let appearance): appearance.badgeHighlightColor case .minimal(let appearance): appearance.badgeHighlightColor case .retro(let appearance): appearance.badgeHighlightColor + case .m0116(let appearance): appearance.badgeHighlightColor } } @@ -101,6 +108,7 @@ enum KeycapAppearance { case .pbt(let appearance): appearance.badgeTextColor case .minimal(let appearance): appearance.badgeTextColor case .retro(let appearance): appearance.badgeTextColor + case .m0116(let appearance): appearance.badgeTextColor } } @@ -123,6 +131,11 @@ enum KeycapAppearance { guard case .retro(let appearance) = self else { return nil } return appearance } + + var m0116: M0116? { + guard case .m0116(let appearance) = self else { return nil } + return appearance + } } // MARK: - Appearances helpers @@ -298,4 +311,60 @@ extension KeycapAppearance { self.glossColor = NSColor(white: 1, alpha: 0.08) } } + + /// Sculpted beige-era keycap: a lit body, an inset top face, and a front skirt, + /// all sitting in a near-black well like the plate of an Apple Standard Keyboard. + struct M0116 { + let textColor: NSColor + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + let badgeFillColor: NSColor + let badgeStrokeColor: NSColor + let badgeHighlightColor: NSColor + let badgeTextColor: NSColor + let wellColor: NSColor + let bodyGradient: NSGradient? + let skirtGradient: NSGradient? + let faceGradient: NSGradient? + let creaseColor: NSColor + let bodyStrokeColor: NSColor + + init(tokens: KeycapThemeTokens) { + let bodyEdge = tokens.surfaceShadowColor.darkened(by: 0.28) + let bodyHighlight = tokens.surfaceHighlightColor.blended( + withFraction: 0.35, + of: tokens.surfaceBaseColor + ) ?? tokens.surfaceHighlightColor + + self.textColor = tokens.textColor + self.groupBackgroundColor = tokens.groupBackgroundColor + self.groupStrokeColor = tokens.groupStrokeColor + self.badgeFillColor = tokens.badgeFillColor + self.badgeStrokeColor = tokens.badgeStrokeColor + self.badgeHighlightColor = tokens.badgeHighlightColor + self.badgeTextColor = tokens.badgeTextColor + self.wellColor = tokens.recessColor.darkened(by: 0.45) + // Lit from the left: bright edge, base across the middle, shaded right edge. + self.bodyGradient = NSGradient(colorsAndLocations: + (tokens.surfaceHighlightColor, 0.0), + (bodyHighlight, 0.14), + (tokens.surfaceBaseColor, 0.55), + (tokens.surfaceShadowColor, 0.92), + (bodyEdge, 1.0) + ) + // The front skirt catches more light at its lower edge than the face above it. + self.skirtGradient = NSGradient(colorsAndLocations: + (bodyEdge, 0.0), + (tokens.surfaceBaseColor, 0.45), + (tokens.surfaceHighlightColor, 1.0) + ) + self.faceGradient = NSGradient(colorsAndLocations: + (tokens.surfaceHighlightColor, 0.0), + (tokens.surfaceBaseColor, 0.58), + (tokens.surfaceShadowColor, 1.0) + ) + self.creaseColor = tokens.surfaceBorderColor.withAlphaComponent(0.42) + self.bodyStrokeColor = tokens.surfaceBorderColor + } + } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index f69eefac..85323c68 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -11,6 +11,7 @@ enum KeycapStyle: Int, CaseIterable { case pbt = 1 case minimal = 2 case retro = 3 + case m0116 = 4 static let `default`: KeycapStyle = .apple @@ -20,6 +21,7 @@ enum KeycapStyle: Int, CaseIterable { case .pbt: L10n.KeyboardVisualizer.Style.pbt case .minimal: L10n.KeyboardVisualizer.Style.minimal case .retro: L10n.KeyboardVisualizer.Style.retro + case .m0116: L10n.KeyboardVisualizer.Style.m0116 } } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapContext.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapContext.swift index 0816cb92..2fe09dce 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapContext.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapContext.swift @@ -45,4 +45,11 @@ extension KeycapContext { } return self.settings.theme.appearance(for: .retro).retro! } + + var m0116Appearance: KeycapAppearance.M0116 { + if let appearance = self.appearance.m0116 { + return appearance + } + return self.settings.theme.appearance(for: .m0116).m0116! + } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapLegendRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapLegendRenderer.swift index d19a2d23..500d82c0 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapLegendRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapLegendRenderer.swift @@ -170,7 +170,7 @@ struct KeycapLegendRenderer { } } - private func drawKeycapDot(in rect: NSRect, active: Bool) { + func drawKeycapDot(in rect: NSRect, active: Bool) { let dotSize: CGFloat = 8 let dotRect = NSRect( x: rect.minX + CommonKeycapMetrics.horizontalPadding, diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapRendererFactory.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapRendererFactory.swift index b4f41fd6..37b5ac76 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapRendererFactory.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapRendererFactory.swift @@ -19,6 +19,8 @@ enum KeycapRendererFactory { return MinimalKeycapRenderer() case .retro: return RetroKeycapRenderer() + case .m0116: + return M0116KeycapRenderer() } } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapStyle+SizeNormalization.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapStyle+SizeNormalization.swift index d342d53e..c21cbfaa 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapStyle+SizeNormalization.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapStyle+SizeNormalization.swift @@ -25,6 +25,9 @@ extension KeycapStyle { case .retro: return RetroKeycapMetrics.height + RetroKeycapMetrics.groupPadding.top + RetroKeycapMetrics.groupPadding.bottom + case .m0116: + return M0116KeycapMetrics.height + + M0116KeycapMetrics.groupPadding.top + M0116KeycapMetrics.groupPadding.bottom } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift new file mode 100644 index 00000000..d7c8bbd1 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift @@ -0,0 +1,77 @@ +// +// M0116KeycapMetrics.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +/// Proportions traced from an Apple Standard Keyboard (M0116): square-ish caps on an +/// 82pt pitch, a face inset from the sculpted sides, and a tall lit skirt at the front. +enum M0116KeycapMetrics { + static let height: CGFloat = 88 + static let minWidth: CGFloat = 80 + static let itemSpacing: CGFloat = 6 + static let groupPadding = NSEdgeInsets(top: 11, left: 11, bottom: 11, right: 11) + static let repeatBadgeInset = CGPoint(x: 6, y: 6) + + static let bodyCornerRadius: CGFloat = 9 + static let faceCornerRadius: CGFloat = 7 + static let faceSideInset: CGFloat = 5 + static let faceTopInset: CGFloat = 4 + static let faceBottomInset: CGFloat = 15 + static let horizontalPadding: CGFloat = 11 + static let legendBaselineInset: CGFloat = 6 + static let pressedTravel: CGFloat = 2 + + /// The near-black well each cap sits in. Half the item spacing on each side, so + /// neighbouring wells meet and read as one continuous plate. + static let wellSideInset: CGFloat = 3 + static let wellBottomInset: CGFloat = 3.5 + static let wellTopInset: CGFloat = 1.5 + + /// The original caps were printed in Univers 57 Condensed Oblique. Univers is a + /// licensed Linotype family that macOS does not ship, so it is used when the user + /// happens to have it installed and otherwise stood in for by condensing a Helvetica + /// oblique — the closest neo-grotesque available on every Mac. + struct LegendFace { + let name: String + /// Horizontal scale applied to reach Univers 57's condensed width. Faces that are + /// already condensed use 1. + let condensation: CGFloat + } + + static let legendFaces: [LegendFace] = [ + LegendFace(name: "Univers-CondensedOblique", condensation: 1), + LegendFace(name: "UniversLTStd-CnObl", condensation: 1), + LegendFace(name: "HelveticaNeue-Italic", condensation: 0.82), + LegendFace(name: "Helvetica-Oblique", condensation: 0.82), + LegendFace(name: "HelveticaNeue-LightItalic", condensation: 0.82), + LegendFace(name: "Helvetica-LightOblique", condensation: 0.82), + ] + + static func legendFont(ofSize size: CGFloat) -> NSFont { + for face in self.legendFaces { + guard NSFont(name: face.name, size: size) != nil else { continue } + if face.condensation == 1 { + return NSFont(name: face.name, size: size) ?? NSFont.systemFont(ofSize: size) + } + let matrix = AffineTransform(scaleByX: size * face.condensation, byY: size) + let descriptor = NSFontDescriptor(fontAttributes: [.name: face.name, .matrix: matrix]) + if let font = NSFont(descriptor: descriptor, size: 0) { + return font + } + } + + let descriptor = NSFont.systemFont(ofSize: size) + .fontDescriptor + .withSymbolicTraits([.italic, .condensed]) + return NSFont(descriptor: descriptor, size: size) ?? NSFont.systemFont(ofSize: size) + } + + static var charFont: NSFont { self.legendFont(ofSize: 24) } + static var labelFont: NSFont { self.legendFont(ofSize: 19) } + static var symbolFont: NSFont { self.legendFont(ofSize: 23) } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift new file mode 100644 index 00000000..45a7b789 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift @@ -0,0 +1,185 @@ +// +// M0116KeycapRenderer.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +/// Draws a sculpted Apple Standard Keyboard (M0116) keycap: a near-black well, a body lit +/// from the left, an inset top face, and the lit front skirt below it. Text legends sit on a +/// shared baseline in the lower-left corner, the way the originals were printed. +struct M0116KeycapRenderer: KeycapRendering { + private let legendRenderer = KeycapLegendRenderer() + + func size(for context: KeycapContext) -> CGSize { + CGSize( + width: self.m0116KeycapWidth(for: context.item), + height: M0116KeycapMetrics.height + ) + } + + func draw(context: KeycapContext, in rect: NSRect) { + let item = context.item + let appearance = context.m0116Appearance + let press = item.isPressed ? -M0116KeycapMetrics.pressedTravel : 0 + let bodyRect = rect.offsetBy(dx: 0, dy: press) + + self.drawWell(around: rect, appearance: appearance) + + let bodyPath = NSBezierPath( + roundedRect: bodyRect, + xRadius: M0116KeycapMetrics.bodyCornerRadius, + yRadius: M0116KeycapMetrics.bodyCornerRadius + ) + let faceRect = self.faceRect(in: bodyRect) + let facePath = NSBezierPath( + roundedRect: faceRect, + xRadius: M0116KeycapMetrics.faceCornerRadius, + yRadius: M0116KeycapMetrics.faceCornerRadius + ) + + NSGraphicsContext.saveGraphicsState() + bodyPath.addClip() + appearance.bodyGradient?.draw( + from: NSPoint(x: bodyRect.minX, y: bodyRect.midY), + to: NSPoint(x: bodyRect.maxX, y: bodyRect.midY), + options: [] + ) + appearance.skirtGradient?.draw( + in: NSRect( + x: bodyRect.minX, + y: bodyRect.minY, + width: bodyRect.width, + height: M0116KeycapMetrics.faceBottomInset + ), + angle: 90 + ) + NSGraphicsContext.restoreGraphicsState() + + NSGraphicsContext.saveGraphicsState() + facePath.addClip() + appearance.faceGradient?.draw(in: facePath, angle: 90) + NSGraphicsContext.restoreGraphicsState() + + appearance.creaseColor.setStroke() + facePath.lineWidth = StrokeWidth.standard + facePath.stroke() + + appearance.bodyStrokeColor.setStroke() + bodyPath.lineWidth = StrokeWidth.standard + bodyPath.stroke() + + self.drawLegend(item: item, in: faceRect, textColor: appearance.textColor) + } +} + +// MARK: - Geometry +private extension M0116KeycapRenderer { + /// The shared width helper measures legends in the app-wide label font. This style sets + /// them in a larger italic face, so the text is re-measured here to keep it from clipping. + func m0116KeycapWidth(for item: KeycapItem) -> CGFloat { + let sideInsets = 2 * (M0116KeycapMetrics.horizontalPadding + M0116KeycapMetrics.faceSideInset) + var legendWidth: CGFloat = 0 + if let label = item.label { + legendWidth = label.size(withAttributes: [.font: M0116KeycapMetrics.labelFont]).width + } else if item.image == nil, item.sfSymbolName == nil { + legendWidth = item.symbol.size(withAttributes: [.font: M0116KeycapMetrics.charFont]).width + } + if item.label != nil, !item.symbol.isEmpty { + legendWidth = max( + legendWidth, + item.symbol.size(withAttributes: [.font: M0116KeycapMetrics.symbolFont]).width + ) + } + + return max( + M0116KeycapMetrics.minWidth, + keycapWidth(for: item), + (legendWidth + sideInsets).rounded(.up) + ) + } + + func faceRect(in bodyRect: NSRect) -> NSRect { + NSRect( + x: bodyRect.minX + M0116KeycapMetrics.faceSideInset, + y: bodyRect.minY + M0116KeycapMetrics.faceBottomInset, + width: bodyRect.width - 2 * M0116KeycapMetrics.faceSideInset, + height: bodyRect.height - M0116KeycapMetrics.faceTopInset - M0116KeycapMetrics.faceBottomInset + ) + } + + /// The recess the cap sits in. It reaches half the item spacing to each side so + /// adjacent wells meet, leaving one uninterrupted dark plate between the caps. + func drawWell(around rect: NSRect, appearance: KeycapAppearance.M0116) { + let wellRect = NSRect( + x: rect.minX - M0116KeycapMetrics.wellSideInset, + y: rect.minY - M0116KeycapMetrics.wellBottomInset, + width: rect.width + 2 * M0116KeycapMetrics.wellSideInset, + height: rect.height + M0116KeycapMetrics.wellBottomInset + M0116KeycapMetrics.wellTopInset + ) + appearance.wellColor.setFill() + NSBezierPath( + roundedRect: wellRect, + xRadius: M0116KeycapMetrics.bodyCornerRadius + 2, + yRadius: M0116KeycapMetrics.bodyCornerRadius + 2 + ).fill() + } +} + +// MARK: - Legends +private extension M0116KeycapRenderer { + func drawLegend(item: KeycapItem, in faceRect: NSRect, textColor: NSColor) { + guard self.rendersOwnLegend(for: item) else { + self.legendRenderer.draw(item: item, in: faceRect, textColor: textColor) + return + } + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .left + let x = faceRect.minX + M0116KeycapMetrics.horizontalPadding + let baseline = faceRect.minY + M0116KeycapMetrics.legendBaselineInset + + let baseText = item.label ?? item.symbol + let baseFont = item.label == nil ? M0116KeycapMetrics.charFont : M0116KeycapMetrics.labelFont + let baseAttributes: [NSAttributedString.Key: Any] = [ + .font: baseFont, + .foregroundColor: textColor, + .paragraphStyle: paragraphStyle, + ] + baseText.draw(at: NSPoint(x: x, y: baseline), withAttributes: baseAttributes) + + // Keys carrying both a symbol and a label stack the symbol above the baseline, + // mirroring the shifted legends printed on the original caps. + if item.label != nil, !item.symbol.isEmpty { + let symbolAttributes: [NSAttributedString.Key: Any] = [ + .font: M0116KeycapMetrics.symbolFont, + .foregroundColor: textColor, + .paragraphStyle: paragraphStyle, + ] + let baseHeight = baseText.size(withAttributes: baseAttributes).height + item.symbol.draw( + at: NSPoint(x: x, y: baseline + baseHeight - 2), + withAttributes: symbolAttributes + ) + } + + if item.state.showsDot { + self.legendRenderer.drawKeycapDot(in: faceRect, active: item.state.isDotActive) + } + } + + /// Image and SF Symbol legends keep the shared centered treatment; only text legends + /// take the M0116's lower-left baseline. + func rendersOwnLegend(for item: KeycapItem) -> Bool { + guard item.image == nil, !item.rendersSymbolWithLabel, !item.rendersCenteredLabel else { + return false + } + if item.label != nil { + return true + } + return item.sfSymbolName == nil && !item.symbol.isEmpty + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116Palette.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116Palette.swift new file mode 100644 index 00000000..8842ac5d --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116Palette.swift @@ -0,0 +1,41 @@ +// +// M0116Palette.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +extension KeycapThemeTokens { + /// The Apple Standard Keyboard's own colors, sampled from the hardware: cool off-white + /// caps, a warm olive case, and the near-black plate showing through between the keys. + /// + /// The M0116 style paints from this palette rather than the selected theme, so it keeps + /// the original color scheme whichever theme is active. + static func m0116Hardware(legendColorOverride: NSColor? = nil) -> KeycapThemeTokens { + let textColor = legendColorOverride + ?? NSColor(calibratedRed: 0.290, green: 0.298, blue: 0.337, alpha: 1) + let swatchColor = NSColor(calibratedRed: 0.898, green: 0.902, blue: 0.910, alpha: 1) + let badge = KeyboardVisualizerTheme.badgeTokens(swatchColor: swatchColor, textColor: textColor) + + return KeycapThemeTokens( + swatchColor: swatchColor, + textColor: textColor, + groupBackgroundColor: NSColor(calibratedRed: 0.827, green: 0.831, blue: 0.804, alpha: 0.94), + groupStrokeColor: NSColor(calibratedRed: 0.596, green: 0.600, blue: 0.565, alpha: 0.70), + badgeFillColor: badge.fill, + badgeStrokeColor: badge.stroke, + badgeHighlightColor: badge.highlight, + badgeTextColor: badge.text, + surfaceHighlightColor: NSColor(calibratedRed: 0.937, green: 0.941, blue: 0.949, alpha: 1), + surfaceBaseColor: NSColor(calibratedRed: 0.898, green: 0.902, blue: 0.910, alpha: 1), + surfaceShadowColor: NSColor(calibratedRed: 0.804, green: 0.808, blue: 0.820, alpha: 1), + surfaceBorderColor: NSColor(calibratedRed: 0.404, green: 0.404, blue: 0.400, alpha: 1), + recessColor: NSColor(calibratedRed: 0.267, green: 0.271, blue: 0.267, alpha: 1), + undersideEdgeColor: NSColor(calibratedRed: 0.180, green: 0.184, blue: 0.180, alpha: 1), + undersideCenterColor: NSColor(calibratedRed: 0.404, green: 0.408, blue: 0.400, alpha: 1) + ) + } +} diff --git a/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings index 2e0cd57c..d93b91f2 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimal"; "keyboard_visualizer.style.retro" = "Retro"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "Anzeigedauer"; "keyboard_visualizer.linger_time_subtitle" = "Dauer, für die Tastenkappen vor dem Ausblenden vollständig sichtbar bleiben."; "keyboard_visualizer.linger_time.short" = "Kurz"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings index 628fc3a9..6afdd05d 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimal"; "keyboard_visualizer.style.retro" = "Retro"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "Linger Time"; "keyboard_visualizer.linger_time_subtitle" = "How long keycaps remain fully visible before fading."; "keyboard_visualizer.linger_time.short" = "Short"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings index 86a40724..01c2ad95 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimalista"; "keyboard_visualizer.style.retro" = "Retro"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "Tiempo en pantalla"; "keyboard_visualizer.linger_time_subtitle" = "Tiempo que las teclas permanecen totalmente visibles antes de desvanecerse."; "keyboard_visualizer.linger_time.short" = "Corto"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings index 0cadea16..0121678b 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimal"; "keyboard_visualizer.style.retro" = "Rétro"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "Durée d’affichage"; "keyboard_visualizer.linger_time_subtitle" = "Durée pendant laquelle les touches restent entièrement visibles avant de s’estomper."; "keyboard_visualizer.linger_time.short" = "Courte"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings index f1b25790..1ba187c0 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "ミニマル"; "keyboard_visualizer.style.retro" = "レトロ"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "表示時間"; "keyboard_visualizer.linger_time_subtitle" = "フェードアウトが始まるまでキーを完全に表示する時間です。"; "keyboard_visualizer.linger_time.short" = "短い"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings index 3f5f48b3..6d604d68 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimaal"; "keyboard_visualizer.style.retro" = "Retro"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "Blijftijd"; "keyboard_visualizer.linger_time_subtitle" = "Hoe lang toetsen volledig zichtbaar blijven voordat ze vervagen."; "keyboard_visualizer.linger_time.short" = "Kort"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings index 48340f24..9aa75160 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimalny"; "keyboard_visualizer.style.retro" = "Retro"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "Czas pozostawania"; "keyboard_visualizer.linger_time_subtitle" = "Jak długo klawisze pozostają w pełni widoczne przed zanikiem."; "keyboard_visualizer.linger_time.short" = "Krótko"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings index 5e77ddc5..38f0a861 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimalista"; "keyboard_visualizer.style.retro" = "Retrô"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "Tempo de Permanência"; "keyboard_visualizer.linger_time_subtitle" = "Por quanto tempo as teclas permanecem totalmente visíveis antes de desaparecer."; "keyboard_visualizer.linger_time.short" = "Curto"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings index 76afc31f..2dd8578a 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Мінімалізм"; "keyboard_visualizer.style.retro" = "Ретро"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "Час відображення"; "keyboard_visualizer.linger_time_subtitle" = "Як довго клавіші залишаються повністю видимими перед зникненням."; "keyboard_visualizer.linger_time.short" = "Короткий"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings index 0ea8e9b0..8e136a22 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings @@ -119,6 +119,7 @@ "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "极简"; "keyboard_visualizer.style.retro" = "复古"; +"keyboard_visualizer.style.m0116" = "M0116"; "keyboard_visualizer.linger_time_label" = "显示时间"; "keyboard_visualizer.linger_time_subtitle" = "按键在淡出前保持完全可见的时间。"; "keyboard_visualizer.linger_time.short" = "短"; diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift index aa3cd938..9a4c71e1 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift @@ -34,6 +34,52 @@ final class KeyboardVisualizerGroupViewSnapshotTests: XCTestCase { ) } + func testRendersM0116StyleKeycap() { + self.assertGroupSnapshot( + style: .m0116, + theme: .black, + items: self.keycapItems( + keyCode: KeyboardKeyCode.a.rawValue, + legend: EventLegend(text: "A"), + style: .m0116, + theme: .black + ), + named: "m0116-keycap" + ) + } + + func testRendersM0116StyleEscapeKeycap() { + self.assertGroupSnapshot( + style: .m0116, + theme: .black, + items: self.keycapItems( + keyCode: KeyboardKeyCode.escape.rawValue, + legend: EventLegend(text: KeyboardGlyphCatalog.symbol(for: .escape), label: KeyboardSpecialKey.escape.label), + style: .m0116, + theme: .black + ), + named: "m0116-escape-keycap" + ) + } + + func testRendersM0116StyleCommandShiftKKeycaps() { + let settings = self.settings(style: .m0116, theme: .black) + let items = KeycapItemFactory.modifierItems( + currentFlags: [], + releasedFlags: Self.commandShiftFlags, + palette: settings.palette + ) + + KeycapItemFactory.keycapItems( + keyCode: KeyboardKeyCode.k.rawValue, + legend: EventLegend(text: "K"), + modifierFlags: [], + isPressed: false, + palette: settings.palette + ) + + self.assertGroupSnapshot(style: .m0116, theme: .black, items: items, named: "m0116-command-shift-k-keycaps") + } + func testRendersAppleBlackEscapeKeycap() { self.assertAppleBlackSnapshot( items: self.keycapItems( @@ -53,14 +99,16 @@ private extension KeyboardVisualizerGroupViewSnapshotTests { func keycapItems( keyCode: UInt16, legend: EventLegend, - modifierFlags: NSEvent.ModifierFlags = [] + modifierFlags: NSEvent.ModifierFlags = [], + style: KeycapStyle = .apple, + theme: KeyboardVisualizerTheme = .black ) -> [KeycapItem] { KeycapItemFactory.keycapItems( keyCode: keyCode, legend: legend, modifierFlags: modifierFlags, isPressed: false, - palette: self.appleBlackSettings.palette + palette: self.settings(style: style, theme: theme).palette ) } @@ -87,7 +135,19 @@ private extension KeyboardVisualizerGroupViewSnapshotTests { testName: String = #function, line: UInt = #line ) { - let settings = self.appleBlackSettings + self.assertGroupSnapshot(style: .apple, theme: .black, items: items, named: name, file: file, testName: testName, line: line) + } + + func assertGroupSnapshot( + style: KeycapStyle, + theme: KeyboardVisualizerTheme, + items: [KeycapItem], + named name: String, + file: StaticString = #filePath, + testName: String = #function, + line: UInt = #line + ) { + let settings = self.settings(style: style, theme: theme) let view = KeyboardVisualizerGroupView(items: items, settings: settings) view.frame = NSRect(origin: .zero, size: view.preferredSize) @@ -102,9 +162,13 @@ private extension KeyboardVisualizerGroupViewSnapshotTests { } var appleBlackSettings: KeyboardVisualizerSettings { + self.settings(style: .apple, theme: .black) + } + + func settings(style: KeycapStyle, theme: KeyboardVisualizerTheme) -> KeyboardVisualizerSettings { let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) - settings.style = .apple - settings.theme = .black + settings.style = style + settings.theme = theme settings.scale = 1.0 return settings } diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift index 857df03c..8e797168 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift @@ -333,4 +333,20 @@ final class KeyboardVisualizerSettingsTests: XCTestCase { XCTAssertEqual(self.settings.themeTokens.surfaceBaseColor, KeyboardVisualizerTheme.black.tokens.surfaceBaseColor) XCTAssertEqual(self.settings.themeTokens.recessColor, KeyboardVisualizerTheme.black.tokens.recessColor) } + + + func testM0116StyleKeepsItsOwnPaletteAcrossThemes() { + self.settings.style = .m0116 + + self.settings.theme = .black + let onDark = self.settings.appearance.m0116 + + self.settings.theme = .white + let onLight = self.settings.appearance.m0116 + + XCTAssertNotNil(onDark) + XCTAssertEqual(onDark?.textColor, onLight?.textColor) + XCTAssertEqual(onDark?.wellColor, onLight?.wellColor) + XCTAssertEqual(onDark?.textColor, KeycapThemeTokens.m0116Hardware().textColor) + } } diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleCommandShiftKKeycaps.m0116-command-shift-k-keycaps.png b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleCommandShiftKKeycaps.m0116-command-shift-k-keycaps.png new file mode 100644 index 0000000000000000000000000000000000000000..f32f0ee0e0d5f810b602649507ad4231629ab2af GIT binary patch literal 59655 zcmagGWmH^ElrD@DAXp$cG#2E=gEj6J971qy+}#^@cWX32NP@e&ySux)Lj#w0?#wrH z=g)W6s#A4(?b@|#*Ood@oz>w=3R38(M5r(@Fz7PU;wmsOaB}bMkq=1kcYj&v*Y^t6 zNkvK&rfiJl@cqHsL_@|@ULNM3_x1-E1Xv;%#D7WNiy$oV|JRm;rGtV0kKf^7U_vcm z5dM=!;l2L%ihD2r_W8dxTrS*yQNYRN!v9wrX5?SefxO%M_X@>cTGI&z2Jg$i1y)9d z`T_<<7)C~1L8Q>Dk;86H3qzEYT9tE-nIiJuHK`3fOHz z7Ireh_HH_oPywyMlm9(*O??sM%-03c-tugQ1vUc9AVDoPE~2 zT|f6Z_d1s}HdQlqDQqg{(^KnfJ#TGp_oAYtqN0-eOHrrjbETUI;bzh15dU0yzy)W6 zu=Jq@+nj1baswFCad2?pU*G<6>J>^F5dWxT-;K`ANJP5ob?6VTI!;XZXN)7gXz{pb za$jQ!o>dnmlp>WtAK!uz2(tF+h3wpJ+*0w^{fBkq1gIDv)c~w zwFf2VJ`PPLH7Re+TcMiEg#-u$N{x+G%;XXjtm{)qNQsGxk&B6qW+5UaCMEulfd4|avCUG5`WZufno>9(u8PAETHa=DNV^}6D? zK$$*=j-5y8*>~~?IUY>p)idk0Jp8;24|j>A!7J6Nvp&4J!cpJ{vazuVuvMB{_AqnS zmj3s2{#DI|vv`L>%s+hGgp2iV>jz`HeEqJcD?Cka9j`?gWWbAowbti0P|+7(PTr;+ zc02EFV$9LrZ2vJ!+lEc>cztz!4kRB-QSZ<9;Vw54zQ|(M(n>p__zqA1 zPyKylfb+UPBVDaK{d0M`Tt~_ZMSi|NZyppZ^LneDe}$?e%`VQPB!Zz_t_Krq`e|td zzSX*FO&dWtiW+sEw;AxRTO6FfyHeWz_Z%b*-rB(Vu|ls8J!4tRZs&{5`_0IhBv1(7 zdCR@;LXj!*b1i5N=a*ai^C~T)HrE}17N5oE_R3^Ne~Maz4J4wh zc$c=4Db_Z7p%S!zk3q6<|0h1awf(J+`*o_$#~N}Obl7yf20j`Xc<|M#G9DaO;CJXH zJf6#Gvh((CX$^1A8K~p|>Rp!TuinhzuX7fTn zX1CUBAf09{Qyfd9^efdtv77cLD73Q2bLDo5DNTrfNv-~;IpY~4%$4cZ`_BGVW58q4 z^xU19Ia_~)?h6TRJ5e<&Y$2WZ%Hp*X0lF8H3-g2ooJ1s>IGg^b;x#6)HJpaDymY4h zr+;CPLMxfH$-EabJ=9r3dCOQgCt)ABNNg|q1Y}S9?OR3XW9t9I6aUlB(Rkp1{hGc< zf1XpAb$zl0{GOw^S1w)|JRjNdXfKq8W{VY{P77sITTg1H6#+H?Vyyh>t^a5Km3Xkv zOih-93B9V#uBSRS;}%-ZlM>^PGx%*6!7=?bv+pvXtR*H!vh+KpT(Ay-nCMD{{wIfb zw>Cw{f=M<@5jS(@{y{FA<3+UkbF?ZODSx1q!s@ zhi0_x;m|xsH{frDI$ti^%m1uf|6e9JM`fUh3^dpG*5>Bqy#4vB{8hvMKY|>eTa0d- zxzXgeX17BNuP8sl?>4JVF2=`++EO{*j>iKrS$tlC($yC3%XU=u4cHp|Jes9CwT_mz zN3*KMKe9O9!@+^Q1R2*`wt$xsjvl6>;N#ALKm6%)YBz=@gMk7z0f6}WNu$wmw??4Y z)N-+^&?$jEM-Ca2tmetxawnWNn*eNxoAdAiOz(i^dM7`Z!(-Buj_f}gduo-HH@M2v|a)L+8@XvTKKYP)*j_ zNzG^_X4}0Fhj9Qe2mQ3CL4WS(;0cTram(M0US%;os})cE&dVivaixw6PAt45Pb~B= z`1wxF>wc@YeyP#PEHoQO0cd!XT<+cbn)C1nupY{we7#y{v&?mshxu~4gWvwZ5_xyJ zsy^(BTXI;A$08|kaqFDfGFl&7+7e6%+bob@RsLh2NCcc~YSe8P(Y6shE@VH-Db#wO zq^gj9zm8wK{`6Py`f!}-sb}5S@hZGO=fEAukk;;jr~$x2LyP~&Mzu7*g~kr&`d4Z5 zAIiIN_sxjr7Y*A`_V=*D=X`7B>$sk*-g?u)dKca7c1q&4;s0EJy4HFfyLR&{=NUw} z!FP0A?t2bk)c4sMx8r|4k!i-|EH|@Vf0{c5`M&rcr>wZGztrMM%MYIe0T>HV=- z$h|n-#RaG6g>b@FKAXa%{Z|UGsv_|Eu>D!#Vpf6IqIc$4&-b=Xy=6=4Zd}UnY_-1r zsi*(V@CwKG4Kwjl++sxVRhX$n*Xx_h)gWy>G7OV2TC6*Q{c+EBtWdn=&g4;t|J4`E z;bn`NzT=wXiZV5wz(lApY>t4#Q7Q*cn_^`K13-0Ntzmenv*o^t)P177mXz?XzO$Xs$ED2nA zwQJnJCg8JUe{=o5btBlRIVoLal(mbb74{i?w;U&?8Vc2oY>oU~XS1$n<8c#@ij2t7 z0%<%hJ6Dh8zY4prU952E?p%K=^=)6i`a#J#%iXfS<|XxZ(f)Q>^$ND)+59`DNzC<~;YX&i9Zib5>uh}&O0u_Wxj``cKsl8%W=&|wj5S({DV0cEpGh^} zF@y1K99Sl1H<|q(bQ$I+_?nSy_kl8Hk)BWPN61uc0W<)wNq@$MiD&B|xH6xyFGg_MrY??i$a|EBt z_p@cZi>D;2?XSe`P5V6K9(S3TI<7)0w_`#+^D1760-H#M=rNyxrW*_XeHlE}S~hl! z3LR{qN0ix~ph|KC`mL+p=Yt>~CI_3!jwSQ3__^`L((wihq>o)ZiMWM2UHn8Abi?-N zG=rH529>Ekd#)mF7FG60`Q|k9(9^o{z7uO6BqEL?5Y}S7O_jSO;mgE>V?(-W^$4?c z?f6=Ukx+}-_}P#TLA~cmC6g^Ym+P?TZR-1F$qlig0+fdtFKo1O1&>fHijj+|G+ zfUesrPbGNyPEpV6blFF%YD3>dJ?+(YcW73~^NweKY#4pLf&Lux?U^s-bpzRLzq#gu zZ5a_?N2yf5eGKOn*DKZjurPJee@jQvU0mTM|)@$G(}{toReXO@I-{X=I!S8TR+Y~BnO+M+rG zjr*ZjUi^QZZFbyui7n zFK!%UyRs2`6D&R=?Xe3;mj@@$*1tqz?;^=)rQUPw718_lZ}Mm}l&l7)(CcLs(Q>bO zy|wzTudA#>;q63wt>vDx++)_~+p%x6#q_U9p@%>rt*3!KsVlWFEe;J2TanNR2I?$_ zz4Y1Y9I%DU-Qxa3zr$jCjimj;YGtWoTy`LY{R|S21fq<^$=cyj9Q@L6O?6iSu&yQ0 zW|?UGPWy{-NmR#*))W8pO_iOtdIXsh_Ko>g`LP@FS4<5wUHlc3A`)($_@78q+>gz> z4dyVMip0mfyRwzV3&%^c_+(Fm&+zx_+VA<^Z4qTU(H3uQ!smH;{g3YH3M;0aUy$pc}dbAUt#b zudJ@8E$&|&UC($s8z$$x`do_z~;N_sxYXA|k_6@ex^GXWf zb(5P>x5a&H)b;Q!3P-)uVt-&Sx{GDXf9qAa`Kt09xGpS z9)*-n%6;43aSH)5_Dh%U;HSO4L@ese1LCo2s;-jZihmOU_<0rG`aSQDz zrBo9Onc$IKg7loWE@$QpCZ-3)x|1s6BnZEqctQ=ZUy^8iA8HB*>Gx`cy(vWiyq8__W&Esi zkNurM2O>h!%C40ob#VtA$}(WyqLdp|{R%WX9F3)ABX`y6yOUxS6?C7)+hOZHl?i1N zK%#q+e|RH=#$x+i6_u5hHEexrA4-y0;ra%P;|4ZT9Q)!RXi5B}?)X!DjV8ILZ~ON& zsO4Y)DroGKfne&Kfj^c-`B*v2FZFY5Am>6{@pfW%=X?x4brMO7F)^Q=t z3ZnGeZzp@Bu~DyU=dYz*_2&Q?LR7ez~L9?k0{7y|C9&(YepS?+_fdfqVlsKCrRr~WaAXAS%7*OyR1 zXk{$e8q;`}T7aZ;qgRU9_eRRM#d$cT|7!4486(BzrNfGBdCT9YKl6Gv=f58>IWXa7 zj>19MMiscfab-TzBsrg0`U8WR@L*UnN3S~K9RA8Uox};D^u;k0G4Io+W8(jlMGrAn zEZ_k7MJ{Ivg@5hHVtTr1@5H=gWZ2?5C|H<~q75RK{>G1*+`PqJ70L0FinyO7RJV`1 zjijBL`zXL?pp$`60^1f}iU18mibSxu{MHF$F~>NpZ=av0jPbBic^=U3N5yO&0->_a z$;av-q%2@~8l=kFfcKfxBrc9*@rY?{CM+KldeeNh)l#t++u(aWP)u1OhNqwXj_z|_ z*4+1c#j>vJ)gq{nG{%4Z@fmL=d{%qys_sa0+%x66$2$RC12auw`2fL9v}d({r+8aZ zHjF>e!o5)p*BEFV9#X|q=uSNto_Z#c9Y%~Tddjqvp8qD8w*$$m>2lZ%;(R@7 zn!wX~LDa5VklwTXYL*FCz+xefmqCq&+CZPTAVE4P4RO=&unyV~$9 z0h4?<2cNn;Tz~3BiIZ*ETBt~UCg>FYB>9V)zsw^E(Fh`oM@QR%n#n5*WsB*%JhTjM z-EKeeNPXt>@z#U|F!kSUVuFpYj`f?_))TPzgSG{e-%Q`}pH|sNU=RaJs*YngfL-t9 z2vF{`Bc*3cYXK}s3)RR!$+tE`AjJQsV5oQr18+VsPT)~9g8x88HdT;44etgf|H|eO zy%O?Us1LZsOmUq1gEtQJagwX2L6ZOR8~jRk)7y^BUSiHqoR}EB0>@Vl)WRv0rT~0x z0|BCjKg&B`NCj;juyb1|5tCNt()h5KrHv9f zTlvbwzLFyfWvkJ`|2j)fi!|suP8SJDe1yNR&VgTK2^lr#mKm=RpQ%q`v`{`^g<#AN zP`ZFX=3poZJX}aNUfPFpe!FzO8U~Eyez`r_u>nBC3y9mj9UaOl4wPFGxPR#rM2jeL zY|LB#a`NB>#^E_vnrIO_YvSodqeQgLDb@)WmD4{YB4$>l8^@h|?VP2vO}Rrv(C1wc z4k>5n5YeJn0w}=!DCcc#+USb>%V*m@dS2Lp;nTcI9b>AVDfzYxfNoZ;zm>N)S1-fQ zUM%>zK{xqI%`t~`-i*3=_A|HjgMF?czId33LE(RrTk1HTjm{q&>`>Nk zhd&R%=@|#`7PH`}B%5c6x(9Il9m9NSHD_Z!QlT;lVv}rN(5Rm+((&0#6_csD67im) zC*8nT5HLTy6&63GPqE- z(uyy9{#ugTB)OId`Q!oy>LG-*lLj=KWF!&!=R3;5e~>>AMubxw~IU?|nG|MNNE<-|--{h5nI(6wVfdYtzSdh~^*5$PFD>T|HtNL`$!}6*2aC3hG`BxLjqf>vQxL24t5AlHV zWJHx5ORHA@zP32Tj*P4$jK(LdQ3$6D{#GDr#!geP?5-XhKUq7+lslKuAm43nw)9EK zsL%i5Tj3@D=8IN&j4-fu0cUS%7 zz>k|iQgr^t-{{&F_qbW$yT-fGT);vqZni4;Q1)$00(I&6+}Ggac!bAnv2V#X%{=kz zUU%Z1d<=Aqkh?<9aj@5iwc*L7y!^zc^MvKQD(z_=6Hqvp%zT=;`H|$d0@NR4`PTL_ z+bMRqilaMHWsu5^sy3b{J&8?55zZFy+c3Jc(=SaFyPsbRUpJG^b0U>_^CyK@#^}@3 z?-^}w1z7dsKe6A0u@1!7SM@$wp03iXs(>(&SnCs@2Etk04S{}sQ|Z=9D7k~zIx%~yeM^@l8%(PyCT)U?A%(YUT^I}~- z`VLe}xb_|y9XT;sz{AVcA7y+whre6-{W7b%T=!I8bYacuy~S1vZZldC4Lvc{`QFA}<#pJ%D-?YQ#j zHd}h&$`tsU>p5EN+coc}0208WjB~8r1~TUfKrgyy6BYS4^pkZMo*zr?4R79lQ9(KUDje77pB|9XDrsgM?ebb@Nya3 zs`kacjNNt;wT>ZpE{Q4A6!+kY$v^FR8y%x$K;^sWFdJPbTr*W&znGQ_z@V4+1H486 zNuCh`=O2WE1EAf*ZZu~+Rg5o-2v=sMt4^WB=sDh34(*zJEs{>RI)k`G&PE|{rQqR*eUOx7T0Y|UQ@hv+fFZ!CL}ms5^m8q27;jE7*9yyJ z!Hn(vRLFEktjC726#d3BOZeml#R%i*3;c`eGzqy@VjGX*-?e2L-^40;sFN! z-;V9zYxMim&9$ak*Ff&Ii!PHkMm!lTt3q>4qk28-1^c@8Kfv>r>%S1&CVIs+*W-E` z+xC|jw&yx+LGw+YB+Ml?DjdQ9Glo(fj|BZcO-FwLS0JRlyy=wT44VX4ozGE<+u6yM z)4xKCi%yRB8nz;cr;1LN!3|aXhIR>`_Wh38RDF_aC4fk&u?Mmte3BG~XpKV_AMrh1 zr@nr_^RY~kMC%=YC}fS8mabqK=ZL|ae*0-PG-&{(m4&j?{Yi(C?@B{Z+RX-C$3~unO<^wx>@mi*7K~|SADaGANcxdk(`9!9$<+B2 z5fX{6tj8y!CE~=JoJaJl1K!n2{*?yo)W!_&!^MIw8=z{58Am!{?dG*p+%?~+r*7Ap ze#&sTq^nQsJKdJP3n+cvCOu&GGGZ&&I{R}X^4C?=H(+05P(F#=%^YJ|%ShlI6_(>O z^!&-YpIKe}j$I5ylH{$|+4LXu3%!O9T9tX^`$ocn=D`-`;xhA2{H($DXsf!}6S$>ZY^$lcX5WX-b|ImsZ!c+x<3dF?_o> z3B3LJw$k*G-Zj~>O2Cv=hpZ`B2>|U4CT(k9wzd1@riDQn`(r6z{af{{6x;(_O#Z+J z?*i*~6{GR-=IJuWY<2LdmHs&6-9hA~w(aBIgUMZ@y2k}8mM&ddby$17)V!3bw-p>F zQ8!-*8cPi^>}Kv4UOaU9bBu+}!KpyZ75zm!1U0^s;}zM{T*nvz2+DeoMoZdwkpnYM zuIOiH!fIRoF3H^xf*^_x*hFQ*rMPjF2nE)wZ`tOsr`7_xLrBgmCwPn^aeCF{h)8kn z{uL}S5?YqWW@vnq#&$O%t{);4lzh-6^#*a6HYSgS+N}M&Lb$B?n|Wv3G%E)bgq6}T z1#WWN*>4vzWbrO5HD6uc=+ z|DtbHbBmM7^N?I?)soZkis;w&pk)j&c>F}<_q3vXiY2yi=yF)HC{icpvi_a2sFn{- z3B?q65U%ZSi{2U4=?&NP2vpvRiM>z|dXb~;hD0o!HSVuno_p^%KO!6^zFquTVuSBT z)fx2+(WS3g{~*gtuC1aHg>I0Axmca|PD{q8>DjPkqaN)5|0G##<(-HjRNAX!ehs3S zMJ}5Fy;%Zs7G<93>n^5e@tVV~2KGj4y+JCuV@e)8B2%9ycV8c1`PZ4bhU=GRUxD0o zJ{q)gUZ<85r1ieduF0r_v95z%PCe+8ylvW_{G z{3TY94jw$kp?x=H1biGf3*NgTCQy)j7_P)M3peQ6y@Zw!S{9A@TpWIY8=o`$b)lDHcwEFXXueYW1qQeO_pMaSVloIIcDV zF?SPbM~btVn@>DANq^&T1jz!=Oj`G|n@{VaX-~Hg;q?IS&hnStnkzy^H#iU8y+s11 zJ(&@WTu!=luUcrOib1)Zi1;m)y%5!v1iw%etCyD*irwI{A;=F%PMLyc^O(fuexcFx zJHE&lQ#3Fx#aDv!Yq`y1xdD8aHk7ls!H9jj;*=AgP=`y2qi?2=?5p<}q}$Xha?Hq} zgiAaO|4BHjY>b+qe9aF|en>_aaQ*mwvlUNw#1$!WWi)fNk zHr+|qbvbH6xY?*T=mAe~Pwt1|N2FJntJWm9NgdM_CcX|l#6J7JpGHJ{rhV$SdSGf) zNvcQ&luEASFE^9xSab<^j?8}b+K%QYUzurW3d-U^eamzjlUGj_70_`1o#@cjPkF!c z0?!A#yc&=Z1N1p-A|0Q#?BzdUda9`Bk|O* z?D&+bvGPc{`48Uuh_ToYw(6}}nv)B~nw)F@5>*AwM01q3%V@hNVyoG38jxBc%y0uD>muFYKnS=vOTUsfvLzkjV-XTd3UtQ-ZcKwQ@?CIb3J*41JmoDe& zQ5PzEP4x2+#oA|1`*kFKF{AC=4Hh8^!}zVNo9??YvefUVe+FNf(1EcuA4t3dIs}AK zzG|QnVK^Z6>l7rTJIHZhX9N}GM2aP{A}v+~0m=1Ay6dz9tvL>{&H^oTK=D7WOW&fz z%WosC$Q*k4_T+llV#nJqu@TPDmaN*fgJ`{}^lgGa`ecYF({z386N@Uo8UFZsNObQOOK zi@Gad&(U1Parf>PQET3+FKPbVxDTS^JaL`s6f%`CJCUFT zSF5N(aAofm9PQ)+U@Ow#`F@$D-jKo(RF;c2nA=}};!Kt>__4C382kxuAE^`bIOy>8 z`K+9jB!zjGJ9sX;D#j<>iHxHO{aiu1Qar5;fiMu`5Vlmss7P1<69~Yl`>YE-( zWX;FQ#qCSQ3Hu@$EFfAi>sHz^4NHv6cBpRLv|T>`QACxA?>kijpr5kzA=d5fGWkeV zQDC(#ll5>N>#DZ0_IA%B7h}$VS|Gl8NXelO^Jq!R0(5C=H}Z!@p%P*yu&AF;N|W)rJ-GZ3ugxEN z;s=H($n!+JH1^MO$)+;7@cUTJAX__)3*5(UDw7<+{}r5D>ZfP!*BXoj#m_nAkH!|&3fgPK-DPPYFZVm5R3NQ&`ExK42`Wh9 z+{DCXEiTnDKACeiLGfrGdSsTlJbAsJDz_3gW@yxfq|CHSz;P+~_`jIVI zFF-kEhug3Q*8Ny$E2_`ki4?=`h4%FRqxvGBuJ^U+{@5vx;w3XT*A7eee$X6L8Uu89 z+8FZ_mo)oL+`+qk*pn{(`8QZ0Hul%4RK;{BjK|aqqHY?4yz7|0oPnk`4xaBaOD+m; zwo&b7gJsqM%3okw=DpAa`u2AU~f`R7~$&D`#TuYId}XSy)iK!AGWim9IR-9{`&XyOCtVgk5Fr;^C`j)5NeQM}nTrk{UX zEc{&R8&d-HrchD=^mMTzvyrtv`o$nJBWf7rICwrqSI+rjN-NO!7WZUc-M8@KrWQ7TxsAze=OmtE%wUglu_QQuoOpwv%77XIh8TjHeNiauP$q)Z} zp?OCR;z@kGl-nUDKk9fU=fsDoW&;caLY=C@dE^qfK9CRJM470Wk#w z(Y{BI2)Sqb=oH`YrHToIH|4I+g5z*}_kn=5=KBi<)!5=<9j_9cWXpv_dQC8IC4v@G zXO|l5XnZ#BM)l2Sjh%0pFSaj2ymP+k0`MPYv)!&j_7lO>EN{b8+!d={t{_7%4*Znk zP85pqowLQ(VZrR|E34OVFP_EHlR*?A@>T?& z(NMz{f_Wv_`xukH7Hk4;?S5K*lq@%;)y8twuo+WNGo42*vhcp^YRFYJnqxP=nVE6zd^4!=Ft5g0FHI8yCmzfY{X8I_59G7$|r$ z0krNE`Bb5u82vNUh6rAu%_O)NP8>F`62!x8*5eo2ch> zuzufsU=x$|`r|+!Bt^ZNfOAZCreuM*->#kzDhmbdXQ<*NfChE9@mmF8QPiaVv%_^W zQLk!?=nrh#>!IT=Z+@{maX84QGUrWSD9>`dNV_3-qzb-3S~+RU9ll0aReM|oUC>PAUA&jG}oxtlDC!Lv>R~!8K5oFn zc?GdJWNA{8yx2BVw-MK9PPtp#>9!NAW1;TghVkjAZDhVlT&)eB^C|ZekWU5PF|5{t zkTE8L_*!(d=3Efe-0G*7^K!<MR*6* z^Kk55%;X5#sj6SI*<0F^8LW;;u0LDhK2smLp}ob40|&4QTNHeD12*ELP)Y(@hpg&< zJYu?)eD+-!2mN8ti!Gn7=gi|&>#3QGBDE3${ZxbYOtVl6&Bt9GA0Hl@;RLWxK`eNc zI@@$GLz-tlO_e+NmkofTC-kKyezMV!tLq?GoX0PkO=NrWFM}k93MozjSWXp}7 zsg?8`U4hFKS*#Q)&a`uw+qCr?ri>vRkR!?~i591GYi|W%A8^Yrsm}ad&OR*3ECa-x z#iX-(xKZ*ax@v2wXu@oGPxcLsqpAi?1L|C#9yF%vFHgW9AEJH)Z8&8@uWD~|U`dQ$ zWC;jmOQ@>U2#+swuKNls{>c3t{zY`GIZRBRhQ%nGl}b{rVD};8<9MKoIHHZhMaSA4 zLZWG0hCu*ZZ(RGD(b=Nkd}tBlcIIy+1T0}h^h*&8(+fwFU_rK>8)P-ysX1g&a-h+r zs+_fH-U(pjJJ&Ci>oG}{`CL})F(HyE6dW9gl_>X>mP!2v38u8q#f4}^o(85aRH8D~ zjgs<%|EGUGB!oqN5g8JFt>|)A8sV##q~f$!`mI{Y+xhrJR_%#vl2@B*k-vdY4$1v{;%pqO1@D#?2@kJG zXeMwo&~vbw!H{$KA;gs6>SNO+Fhu`$skSq z)s6@c+%KR|)*qH2c)w`>c-Ex#_F171g-!m_vuR#GB2V4p7bk<{7$SS}vD$%~|gb0th0TF-I=NDV1AslA9W)hmcq`5U&n#R@4xe^1aoau^(rHl$` zZMmCKjXpwn`V7IvuubJA#g3_eq%6Qo87$wXmmbJT`6L@Ys=^5XjeEM0ZoavV=?f4(%yTN!c+Mici| zE*w&QjYjKo`;30!4~cU8U`vpUPO71kn0LG@pBNi7ihiWxq(6%EIRE5pJik6qQHpu> z@p#W_pB`=*BR`lQ{^)iCM8p2U)+vDRxsE0cS87?1;%xY;Z9#7IK}QzhYm|EPw^`{@ zV9WO9xWt{afcFb@;TQ(ES{=OEOBo_vWiF*5Jf9Q-e#;_1w;#6RiGKt_7 zuMN6TuTk&^1C{7`g(!x^Ka^m6SI{HK`ZdBC`J=yfamUJx^5Cuuc~CK&*%zYJR`*){ zHGAy#K25BYrR^%!l}?-A-_Z<9{3_vr$m&JBWEAC$5jV7sCW%kcSDsv+@7|+!hl!MG z#%Hb!x`Pp%ZrPFtjPHLRi%pSjtQj!8FxoKcPSmido`4$}(PGQgQKw90O zgH_hT{wcbvhrNp^GJum~Y7vC1-}aqj#&8z7ie{FJdvkAjrR|P>k!s_Ffa==c({WYp zHmRPhl4Q73p7orYdDd2N&p5jwRmbKFv}sJXNL)2VKQJxyzeFO3)Lr2Ew^F$}N5Nu5 z&6saXPVDthW&lP|N}lEZD9u$&a63u#OQj||qI-{FFX-So_G&TVgNv?7w+VJCah3hPpExQ(UeQ)R zHW>q33HNx7$s^_+m=o+pui#S!x;WQwWQ-6kyf)8s?Op&&PtWkP72FDq{tH&55a3f_ zZPEcvPP%FXxbG7TPZ$3FQlik?8S!_Sg4PPyj_*6@^F%3bS+N=`}dC9ZTVU6Jt0kQuU3Y_AeadBP+T&vyHy{77%d`Lle zZ@GH%+S4R;JWYn*5@T#Oi>EX7qcb0nexR?Hy`>MU#!-^mZ!NLjJllwxI>DIpCj*+GAmI>=*IB7=Z6mZ? z?@o*t1AyQ7z%R1Kje*K`sYYhcyftdn?`=&=i!c#Lv(N##;(f@fWxW@}{M=}wKOOdL zdj1=q|^^V$c-_rgebY5VYM*KrP&Asb#LA)#X)I4dnDU4V1hDs#M(*F-n*lQ*+ z2HjZp5d}(@{ba-m9bb0?2KKn?ZAB3%xZGPG2QTS65QzsY~xui(f#J*c(j=*=NR><_B%kN(G zoc-{8f>sP6S^OK>!CYFt&b5{+1<#a@04CVo9P*yc4|5IlmWAN)L+GMEO>fi%3GdQGh5JR$(J$|Rl6 z0i4T$*vo~jO#4o0WSut4QIR6H$vJDD3@EpDx&^(|s6l;B%&aTrRiPb$BXfUW`k&!j zBM^%w#e%$4`9t6jrvSke{z&&E{e1blKDLWqy!{|FN!|^ux08F(q3~bqe*oam%nQam zzo7Nd8Q!jO9r^*?S<3zhXdn&bxem6kMZtnB91PbJo1+BV{w&fj7`Wi2Dc0 z58I9Hx*}e%BWyhIpo}sJ*AM&rn|0v(8sGl}IP*V|T#D@8$O6DI?kukdPCZ_en7m~F zKJe(df73YL34~ANE>~xHUE5|J+5Mv2_7J7a>}D405aw>T7&zJk6!&JOe^Kq{4|8Wh z`TyhD?$`tG`MBNk7vh6BdvHJ>OBfe9Sn!8}yN`N{(;;*+-q#OX=wm#4)juKcLiXbR zE5?&ZfBrZ&;JZTm%{%+?9+qbD#Ap8Lxr>%=3SGm1tUXcBOyN7&{wPQ0gRui8im=Ww zDnB|(#3|bNFO;9kMjZZ_P_21yIKAV&8xpDwsK0nFxlhw=+N3Gx3R1rWL0fw zN?XMkpyY(!N|oqFvG^Ij>f@8lBON{4VVQb-M8bjN=@0+_BM9xL{^eiK^A0c900vRE z2X|UMO2ety;Q!Ju4nTLEd)T{$rT5taGa>}TR25lcVC{~#7fLWoO7Zmfv3Zq60mp(T zT<^PTAu&;2tIY!>#moa-lG9<4EXMf7?ci(H&uBaD6;mQoe4m7CVz%<=Yi(*kT(66J zg{r7edH6Q!3s2+x9vO2vR(>mnufJKr{EHJVS*D~xEh?DV^~{)$!y)`pjSH&A5FAA- zg^?fK)uSNN?WQ}ib*ED{2ZZ^Y#)uOpGsYDC-iWhI+x#u0X^7*rVhz7-*B2jSBne|n5rg{6O z(fjKGy1n|8iA?t1ts7s#^vL8>=U4q=*7@Eg%@892VWMXvQH70N&rdTt=tCh0$+7{t z(XMFt)?@R=Ziitk-E}nHd5)j8H%Kwjay48~%=CySfsoR|h1k8@qG+JpVh+ZL{ggd) zv~`mj-eY*hWszBK@*=MO)?aBE?RbKHssG`%bGi$Z8(}zR?ww{Yeb3rS?1aQ{B(dA! z_Rk|@Tyket=;ngQ4(W$Il}HfwxtE^fN728>*m9oK_yt{AWBV>jPG(d5{gzc7Xs~k# zq?o)qb+?G}{(&8e$RlH`?g^1%c#5gdG=G_Y z(*C4TmgK(UDa{k~*vPeme+2uLBBU19gYHuxK~3tudBDpqgOj@J`=^Kdk3quK?YU?T z&b0jTli5q&+JGpAxzZld%i&7#5fv=N$!M#shAmrGuB=?pj1!G)sQO8d;5GqX1rj z&#d&T@Ju8PNIMO2BZjv*ep{Ibu2^e%5057;v{N6PPbQX%O7{*IuQl$UU2wkcN+*j6( zxH8tx?+jGhu+;pSmU!v0sg?t6mj+WZEN}=C97Mz)hUnA^*iz$C#hE*$i2Ow@5Ho%% zI|o$BzedQ~b3H?#5;&EM50D$umpNZ%gV)$ zwheTt_&Pz{=nu7b{m@rf&G>77Y=b`P7HZj#<1Jfa{D$9X@y^Hv`;D)t4nbm=)E>CV z=vaPvQpZ{q0_J=nU(~S>u+NR9sV1|-<_Vj0*AQKF_RZV(s>G{%FA1$9pf0E28)|hd zM(lGeieZGTyufepXzRY$?2QpX5bX{hWfUPzs{Mndi7Ly?b>@+J~jX1TP)&1 zrt5*v@ae_lW*}O@qCF;q)IZ4vvilER#Cf~yduN6#bZr4jB=%a^&GmBA#Qk360*f~V z^Zi(FnD&Yl7k&Jd_f;&Qs4gmR8jvcbqDv&P$CrQupWx6~F(W6te{JYrkOHp6WIr6S zOw$U^AP01>And0w_J(oomv3&z%C`a$3NEtmsvwef?{lAu28aBGEvxVA{i85KuJ%y* zv2srPg69L(|%hy>!hax=KL&a$B}`CRwV zl+0L6iSP^CLKuh+i2x;xa8ams&Es>kAGp7rw%g{8^M_^Njbs{j4KjKbhOgD+I~$R6 zWKkfv^=7kE5k{%mkfY9QhNVKzmutG+|L7*2W=42fi2bqPZ{JPk7BFJ)4S_)@}``VxOU8|W;j7!_cT%WS^FLAOBE_2d}PXpv;E?h;)Bz1Hu#>yVwXaZbWLOW0msjTLa<~xt|6H;tO_D z`^ZZ*$-AeSZX?OQQq)z#=t`P^ikHG^3_)-_AObR}dz2qES)^&P}O_ZT}Cz7(17TBk^2#kqy>VhG;r(onE&d z$|YmnQNZI&hc2h3PAyLkT7%(gZabEfhn0$Bh4nXWMt)W?-JSpA8})op$MA$pQoaQa z+ygHJ{(P;kG?!+z$exDoT2ph39;?9IPktEq+ef#4F=oop{Z*4XLjBh3AGq)eIk7E& zz3{DlslfcFly}KF0Kc4_`uH~zQm>Zq%I|vIMVPdD z1?#tw4H45xm|IW#C&j^`&2ir+#vb@S+}~n4HwUB=0=uHT1t0niRJQiCd8}y42EQKv5 zJ6JhWYr9KBor00+Wsd8!gC{ptI{*#{zkBz77{JU3<*@;dmX(@aexS7uqu9oqIzA9? zE*&n@>2i5J87&k~#I_cPQf#QbpG$%#YrY!_IlyI=*3IPedE-!kNcNhlz5cwFtU+wJ z$aWapEAb}&cysKk(!YGssU`I$<4b>&WK8wC6Ffk(8uk~q4;Q=(7UhgVLU8uO&iNIY z`i$CsR%E~3<@^;Q-M4JX*p{?+zS7b1GG78wbbiFVsG{(EL+(j$w;xDb_^SUruu%Tz z>w00D!wyu>Un%&W=39Qzap`Zd_CUVCV5v|&$zy7CZ&dRZZ*jdNeF&nf?y>8Wn}_w^ zydkFPKOFEK%W%(OO@wSMwIltiT*}CPF(E4Q=(rmtH?sGCK<_fB67;e*vAJc#MY74~ zef8wGy6(1V)&mfv*t;GXtE#MrRDhu^D;jL} z&@-spmT<$(vwR7|GEBWo!F{J9P>75s%hEXfWR|HD4)a~2(0R+um&ZRb3-aEL`7>7F zL5u*Z!+$_|_E~zotloG@=K5i3%l~ye1cM_NYF%J^ukPzD+GIMVhpw(o;rUPBb<@%I z{-LF-O{*z_UN@~buDGjS_oiTKE08%lFP1>=IBscC%=dIk>R04ZKDLARm|$I-U~VNy z-;1|Ny5347f$f{dDZj}yhLqe3&f);v&V&cw;>p16mvjC&|7#kL+Dy{SbjkI; zXOrz|VHo@%5re)NP-COU4^J2*hcF`#E`OPOSp4P81-O2ONeDO3HQRlE$lkf&qc@@~&VU1l+_Eq@JRbr=BaPWQC>)tuw)cVp<+y-$V*JgQy*8O#T&F zeZo@6XbGOd=?qJVw*BLF9~Xt0gnXOwB6frhG2RYPbStVV7d@4sJA^fA7(dkk238Aq z1i>e3y?{_LIf}0Z+KgKU%>c?DF4k-tiHgZ*dzPx@yte`Y;@Or-2N$rl>wc(kXB3JM{P(%I4^I)!@-U zAsdy}iI3CnR^&uuY(u0HA6$+8QLZ78>^MTXBY$#T+u%V&=BRMkQt(+5$0JLBDE78) zt@B~C_XY&E{032jyguVaaSuQ3bnf-3x4;VS21n;o38v)kG9!{6$#fzo5ZuhsynRSF zP=6OBJQ9v!+f?b^9Y$+|eZH(upoAUa-Ye(`1de3p?Rlw!T%9h!ALbm2Y6P8D zL!|DH3bb*geIHos444!91Y7{Q_$<%qdke$ue=$cP8`q&7wW8=Q`Q!j_M+=2xdnP6Z z(Qc2V(N(mxgkg!t6_RN7;u3r@5hfMA9saRDB4E-4Dt3%GxjEF;s?u-aj6Gj%s(2}x z@|}69#c2M}UbJuso+A93saE!%lKgp>kmjPr^Mftq!Bsk+NJON6EuEY50r_8sMOqqQ z1J!bJMYuU>7ZE5c5FwLp;qWIVU<((+jHN_TsWU)VJ^|D3CWck>PdVl2BZb|pYN2s% zC{LppFjBzUo7Lt09Drl3g+1I>N%BxPUOMZPu(E1we^_1m zVYyU&ef^&o$E7#q{-N5V<^DHjv7FKDzJOfOqMXlRz&WSlx19vZW=cwBa~~q`SBx7` zB|-7hwqKZxf}HP?N8YsjLT6@8tqNmQ_Xz&d6nLp#)&9bnLeBtWLmZaxePND1%slp| zRV^KAR*NgrvpuiT`qe%!=)fQEFE#JKoVVep#vEHVJVFmS??C*m_s#vjqeFyhJ=c~! zTW>wjr#)8p4l>&{jbkSJw!j4adXz1kLK9|T?{MSzEgc4VjE6*E`jP|JYmjaqWboC{#4c>j{SQp7v_ib zvNCwI0IzK#oHS%=62(;hka1@;xJ`FfIjBd&*jFqE$GjC9yAgaiK@N`JkA_~QtL^s; zaq8pAO!uBF1Wa0c@1x?0i#^R+?M~s3_M3=;CcMfW0nyF^I%+Vp_JY?jccHLEE0D)zA-&~s2HqG-*~5Rv?oO?H)#WL zPtM{^MYrH>3U2F?)Hz81HUjD%L|a@@J`4htSLnZzEJZ$-WUrdo(p=S%Il>xsoM#u2 z^SOMil8}_FSOU*mqQ&q`-0Y8+tM{Dj3x=W#rYG9o!8fK`dPL$lc z|2f;*XD~CG`_{EPHZ7{-&TgHpIOJXg_AD7<&$y#C)qP66N5&Dr&fxkEzx3uc@E^F6 zYSW?MV$Asxvi21w=fg&Yxgp*^x*(carF^>?jzb{WHK!cP<`q;A&4OreI>hRgQ1j?g0_A=IuV)yK z=lu6pDSdwu!or_NFwyB({q$+o<0IdqT6u)qGE*8qY2UEIYE9d74h+JL=`o$y@g?YO zC&}w#Uvel4C>Pj`T{Q;#mq`QLQzPHSzI_P0|6pE5LAli4=(iIApSANl(95cy2yt6c zu#E$i_q38d5ilPXyYSN;J#G(b-$JW3Y3rR}%gpL_Gg?s&^#Z$itADIyS5&((m=CK@ZvUwPxI(tUCcN;f6B2HWcVT%>4)U6mj7)G>hY~imf`) z{`K31qViTZ&gN$<+$G*AH+8)>xAQP09NWQ)&Ii2Fj00ix+LXZ_et8`G_Go2$EJ`12 zl>*AO6RldPj-$9~;zMwPBa3+y$#Z|;Le<%lc}pXfZuRggr41?kCAm{{e&58jDLHEO zQ`F54NJ%G=aVrN=sF%7*$qdpuM_A8Q6@L1Q4LO6J+1bM z_Wdfw`yIpWqRmtt_>2oo4()#XQ=_imah-fd;5JB~8g41&;5VM_!f?0Z3O~&Fxd)y~ zq8^4?PnAuGehiAUV%MTqRT5r<&owo`9Zh}aWO(zn%yl)_;M|=!g#b8N{RAg*d~ZE@_~DT5Rh5o!MPVZw>mAqe92wPrzqEYU3ypxM^u`a8 zik-A$_79uT7?guQjmvw_M@_0b6xBQA)I;=UM@Z)_<|bE%6T74;@I^60MC_yZtl&%s+NEfW94HO zkn6}W%%u-(IB0ynn%Ho!|9le7;8VSLetgFo1(#Gzplx{B`3CF;p25b)TRo*_B+SZA`n1+BVAUxCN zKnvxY-jeFyv8>e~*5?0?MQRl#DBnt}m5NY%%y?WotNzYfln}uSK-_Qr_f=l%76jCEmaK@wt=jhSu*dl0u$=}3Y?XU#x7~BPnL{?G|nu-_%=U^HZQXOOvXfEz|`?ToY>2C z8bY|$+I)^0)ilk9nc3)mDyn(e{SLiIE0goPjp!j(FyNV6%+0BiW$+qv7}bdGqs-0E zH%|FuPTBQ~GWOsoq;h7$3*3|BFYn~B_J+~li(dDb1tgbp>@*rolL3+hEux-8u6x{7 z_K49WMKDHFz*KO#5+hW~Cz3n^rLhUP(|GE%JB`1l?B)DEA4eSZ!^?NcxxG5^83GxYjp}Fi+<| z2yV3-Ya1TX-qbdX5fiOeDp#QBi^B~MYp8qBGhpFo2Nl6)t>=^{P~-ii1}ws1&J|IS z3;#Rgtw{X_<2rQ({QLmqOO7k{o_H>XxMSZ280_OYRNdx#J;D#UhhzR^a&P=6AmR9}Uy+?V+rh@Kv}Ez=4^k{rTsRDeDzo-q6w-Yf zGY!>Gm7-Ur$vm*#{rXoL@!}zFFdZB#NxQR#({)`_UN3K#QG)CtyIqSViIx}CqV4@+ zfpLS`bIC$6+Jt4QworC3B~l&M1Rbv1uh?t^=%ukFjYkuik3b zBz#LV*pkQt){nM(c9aI%YK`qPkNO`s-=t&%S}8+2u>{cW6EpX`AVPI_WG0JW9>zYR zsAAgrSbW?x@tmm8pXsS`%ze9FdNTvNm{*QMF}-rzMWrf?#^QiBf`&7=vsbT%|Fqt_oVY9Lq4qeCU9aCatjl%&^88`mdBsSh==id9b z^$X{t1Gh0~$3e*c1AW6Vm{k#FuN4Xr`-0rE{`~J8usuq6d|kkS z_Sd#!c|Bh@6F2Br(+wUdK`M+N1h(LliCp>^>zcZCO-zYD(#H~O8L@~0J{z!f@ldhJ)^Wfr1PS4YCQqnT0rb+E}W*Xh+ zDj;iOM=g9i{v#%^kzu@#?>_5TTjEJ-SIu+hdgy@?NUK*@^mGP_o4=0;saIRc0vrE` z`Mcn|+9wi+Av62~XDG^NY7gj!hkCQrQd`52Lxwu#yZ=Fektan!q5A+VgtX^RH6M6i znutBzKIrzTdVO_!Z}4}DKnd?=wn;3D@RkgNvz)sA>kHF@%PX7i_vy;!9OML#DBtyN zstAPj8n@>k*?QCu8ZmPU+UnN-?2>^~a(6R`repVQR}TH&&FEf{&RF&@8qJ`3$e5aV zL2`lI6;7Bi8>&M!_Ffc&=s$adLGB$ABkehICw~N;pg7^qX-7RlzhrL8`!En9<+ysz zZ8qvCXGu$RZ2WS|^mK(dKuUFKlNAwx7C!0AS_IHp9#jsw8~=qBa9VfgXPtYSbW)6i5lI9(t zc*n!=Mx(ln4Xl<52`e54C;(hAG%C#bwkIr2?j{qS@)cw(tO(;E6zpfuM|pfWt}@uR zZYz>p@PGF##fR`jjzzWj`X)J&;6!E_Y(F<$xf~4E|CW8Df$T(1DbhtPU()#iq54R{ z!qh2D{wo7PL*8+l6CWtBWh0oBIf9KEBldp7J^-JD+o;)H>;{nJzI(la(HE&nkB>0c|M)|k3o zuL6Kl46?1M?7IXx^S#Kor1G6DITm6f&{W0S<>On^yVqE@(~Unll1ZU(y&1qHNn_A- zZ46<|D7J)U@D!{{;#7t{hUu|lg`8$5RFy&by^vP^^pqOf$_W13ujDc|$N{g2Xu*i2 zh`RXEsKgAD=IifHRQ60xavKFP)O4O7kLjmV|CE@KM8??CaLX{TZgig(Mw=C~ z@u-Wqb)(ZfCG0zU*xc(vKTi6$XQG?wuz2Nj8H4FRpFkT2hgo+@x)*lq%LHyZj4(#G z#O$7ULz?6B%r$O5$Si!=;$2P$ida0HPD$EZN>w<;w`lc(i;7c!&JoR4uqQ@uUE|YV z4%cz6u;0{7V|!K_NN9V5#TpijaSljrTQ2bq+&on0f8m$%XuhZ-Ck_SKtg@x!+ATZ& z`=y+G{&WZU10X%6I+y0>4pYveo1KcvAd9VgU@qqvsTk+~c0Y5$J||P+%@e-i7q=l(!TSeRv+B{+)~A)AtW3kZR@ZzlXsb&VO29m;=1#x%=k zns_o&Fv=AlFJC4%`>Go-Nd_lFNST2pvWkmgvV%W(!mHq6)g&9jf2dfy9A_qP&L{@V z`#7hQjwj4F)PaX)s+y>?LM7Fo??lALFnh`tzT2Pivd(eKu)209-T5vwitUt5SzDH) ziT(v4`Qfjk!pMf1tSa!HToIh|@?p4$Eee9N;#Yf{n&&*Udmoo8DIA^tB|UBtA0KW<(eY_Q;ec0uP1n@4^N#`Vi7r&Z>^X$Z8aGJFKwO zfDKNUDVoPLzeQ8fp}@sag#wQ3KZeO~@&eLu%X~+PGOMr=^*#3(vlPw6ceb~p^c_ej zt8pjPfO|P{mEfoWFf9}OZND|qqzM$dyQ2dTRaO+vw^6>>OOBB%d808`Feh~;z#dh> zWZZ=@_7s&2F&9 zLm96uakl${RzU~#rMOgc^@boW6wAjhCwAs}wtHOlg*Na3IxhxXijt?u1}G_(%R_7z zyKWW1|2hf^j26mDsw*Zv4|t$nEZZ6R`MfR;{Q|r((Ay(0%OUvHuDZ!#-jf!Fn`b(C zw7?_qb59n`w*;NDrR4|9>kt8hpp?e-=38XDB98{DCuJBCwdvwKcm_X78;u zK*BE1U;OWoK`rqe5ilr&O7duYr%+g4BDwm`oh>num-q`G&Sdr#SXs%4Ho zWV>N4Qy^mng1!7(f-gycN)MC`A)wO#5(thB`!oE5@g2;D)bI~35ZRw>O9vHH8XNd8 z@2{f*8NXX@`?e^c#-{x_qc-1kE>N!n`)sYHryb%SdNbekxURn)T9)Jc>{!7CzO#HM zRs`uO(71YE*G4safPTo2_`*?Ap#15{thrRjnGzCu7LQs!!!A(yK=wI5joqR2oF<%89CgU6 z^dCxz;K&#UES&EnLNH1{`6@UT#r*4S#8V|^SIl=QiY({RzibVC2APeb}Ol9qog z@n{*!AV_vP8@$G;% z_rJM)Fb2@-Z#6%lC@4Tn>BI zFU4CPqgB}qTS&I^%$MXXf%a&uR(TF?yYdD{n_oEg)m)k!feZ>FF^UhqN5#@h>!CFh zR^`BJ<$C{y>-$U=X)s4N`l=sgfp)9YisW586-|R2AkR%na{k$^#XUN{yn8_>sf4Y- zYxO zULD+I*!XDU`RfVd@rYR7<@wnD<_q3YFSp!qa?VCnG)=7WGn+18%SCogJ>Vet>}f|T zt|3%AwOZJbGB}WBOTrq-|Lmdm5<7smt5$&sHI-?o10hOq z-tZi9?k@6dMooxJ#+LL!@R&363)~y+ZYI&>8U|oQJ?v=7q%S^8dci4(wfy=OSAvIh z;FhPMw2e)}bR2XmH|Lb=*Hw!`gKkAs2#@F`oU7|1ndvgkXEm3D&WC`6bBDv|!1_Tm z>xT6s?*s}x%hO5Ce-#+{zTuj_qfxY)M}9Q+A-@;AHMpUA7wSO-tqPoT`1TFqocyq% z(Wn0#-GwhdV(>hZ}56ZZQDf9MprWHbaKo4cm-lhy$Egp;m-*7dT^7KNh)O< zPC2FGhtez4OE|WUTf%>O(`F2^F*puG^~mO`As z^~ZWCxX?piHlgmx3!odc?hBZ4Ayr@xm^92l^Vi_>cn@_pC--8iE!YN_VATA1+mR%u zn;1ZRUR1Fvr5<+nargx_DP&k}c2gi;>QxWfHB^=>h#ktrVxLH=+v{2GrYiY)Hxjy& z-YEQM*H5Stxt?CH_xTz?qFZxHon4tbR{H@eT&eE)6=1-j7{MRdr=mZIx8x(NbNcDa zc><}3nSS+;%WsOS-o@fWJJ|Y2dIB+JM;gKAD+Eg3h5M2l=KA7*KZMavX`qWM!30^p z&-&bs6`ICcO7lc^3tG6VXvu4OI>vA#nB0OP${}c2(e)bqf^owPfz~dk}VZv-mNZ@ zGqJ?3X(~YbaYnRWXM$_xv+c~i@8I8Es~8WeRo0J)tcM3MhhxvYskt_7*h{oKvtKEM zTG&R@80hV&TUM)D*0)O+uLM26_r{R(d0SaoRcbX^6pi9L|H$g}aBjw>m%|jiQ(_9W zX*lF9tKu2DOQnjdqH*@DV(}C{HTq>Cbc_(yIx_gxT?t<2i!frRy5pLa3Ro2JO7F#_ zfUstdd>lO2f}>j%oDxk?p}u8Gs(4z(O<+$&EFUuLS(RdCaa#a+7u88$xgl@Vg57GE zF`~HBS{f*ijYGNC5wXPZ3|WCnT7m-V$%*_gJMq*g?Cr2*{4ubCy0MVIj09|9V~h|c zeB@4q?ThGxk`03c5v(mN#?DeFv7YX}gOL0aleJF*XpTYO?S$x?q78sxzhP&;QN1|Y zBZoO6v#SWB-s2WZfy@bCl{5;={35bv+9SNi(E?YpC(IOY;kX{|nb?^dKmT7C(Ko%N zO^hP7wTApv95+I3ltfGV4m%n(nHoJd);}Ld6@8l94>w-D#dF+tMo-#LfeD8Sb-3Br z_IhqV73Aci)GZbCvdqCRfxg<%K^6-y)c7_pV36;dPIe$g>NzJdTud}2)?Z`^sHw+X zAXS%1LF_lbIUC_>eI3b|<<7FOR_CtppWvn60fqCzgVK@>>vRQ4n*lf4)5lgSSZuwS zxkqr~^hmtheqLohFvoe6RUm@oTUs(K`Ns9PDHv*VZ_DA{w>A#Do78wY6Ev@RP6oP& z$lRSzNE<|nVeFiDu}^QH&}Kl-Gn*Vlpe>G$VKm8_z*_s3`PG6t>ymRmc2DN2KJBUpp-C{U6AsA7AK-wl1yEkCqIfX=aFEajZJ!!YjE z&`98&t$6#zcGlaF$aWaP-U!f4jopA5U2`^@Wi;nIrx!W z;epm6|5R^>2;~b2=-}Gbv$;liliy(--bi7-LHRGr>t5qkf>78w`$QMAssEEct1*AI z+dH(!P%1zAf;JnqmgA&nY#4aA9m}^;5NlKBbJ^2bz+iFa`>AS6l|?XVuKGgYrJ+lNhy+Gw|)aT@C#K)Ov+Tey58x9oRp zmwxyG?ulyp2c@`9OL)j8Tgxw>enxYk%5^;WzzAbY)blU*n^DLiVL&GAMCR37t>J!P zo~R`hN2ZBw-MuSR-|=KK%f=Ys!-tKS#ihXaBZTr*27iTdhqwRr#7-9^hKYBy`K-Z; zixa94MH`q0vJP2)=r5#1=0PB^yz_@rG);P_zKzBTN_HN09v62S$3{B%_wwFnv?*Zb z{9{4mF2bA+Hd^u}$l*gPu@rZ6gGWw#sJ|EDJ%T6a={(ZdZO3sqpCwA3NWu6hGzv58 zm_L5l4L$FNTS zj^MKX88K}%LK^Dia_p*NZ-?FI6y41ybdpHtbb)f9bQ5;mYy=Hd(1jqbji5EuQ@FNi z>lvN^KF{=Ps>A^<#&J;iE5aG20|i5=i2$a|Rp;%H8y*U`b|f|ef_VfWw~LOR9&x*R z=ERR-WHjXCEis~kFE3@e_>87N8BwADk+ZYNYW}%|EP!UrNN91H)oJ^q7L_ACi(&k zWM}Q`n;_Y_UrD{;;IL%5@9Q|Ee6@9(v2LEyhh!hU1`_`L0n+eDXW)sq)MaW34`&7= zjYQ@Dp^k-f7=d)xZF3i&MLLs|&xL?~lIBtGQ1F(Yy>0w2f1>e)qv^NsT{$uq`^Ajq z(R-4$>zF>qVJj1SB&r58SSG1o*ivSbmzgiFKU{k@Z)7&V#vQ$Mwujr_# zD3krLI+ciw10o2hk2IK2?G&{&R>nvQzjU;7ujHKGb-_aGQ}O3kks>PP1L@H%Aw%cMlS^*gNjG@OB*1V;5x=EnuGLDs2Z$*L!-}RkP`$lON_$=>0kX1gD z<_E0!Lv0#Hjh>#VOi#4n;W;dV0a}aJ_!NAflb& zbW%pIPN-++!$Do8J9e%=_N|I{)dd0`vGpl!2ft)kqtCCN7-~ZX$%~)}wq};=g{BH< zD{l{1`Tyaj=Ue0-*l{Aa#`Xk_P651ypxzOz= z#iKJ_1lk6e>+Pq4^Q35O2= zP3d>9f`&{cbORVcGtuEQ-6x1bO+hU+vWG=pf8AgBE8+dLD87=Pg~83@_wN0*rrYCuz3+x zbcx0ghDLjFIpX6FX!L0C_Qp4Gu!>Kqy*t(Rz26jwdsx--Qz~)Y8LdZp8-v+Epf>Y8 zk`U#NeAyGyd*gfD#*|nhA@>MZgEmg`^e=H|Vp)-!?f7P{8hryOTz3INIlcHyrQUJ= z+^jwkPq}^7 z*`4ssiIWp|9@MFChP=2?!~;R|)L@dLr1jsZ8eP~4wj%e3>TgMM*XHfi3tw!tk+e<> zlotyDzuEH4muRcVm7EbPqc@9L^n@_Odt$2?Dxzd<`}80tuWlWH+=r6U&RxxnwgF9v zb!_PT73By_L`njB_f-Jz)h%)o=o`m#0A(t6NeFxmQdiR1YtULg0Cx}TfKWUKnPpVc zOL|xBCO5>l3BCs*ME zS4^RFsD*gR$>V8&F<^t_w=MgZ3UH-nLj|s)PO??j6x$a>oOuEv%;d{!E#NrQPC0#; z^`~s44-pTKK8Eh0Vh|~pI7|yKy7kn~P}-6=?sVkD7K-$lxb*k2nF3{=MT~DTT5$=Y z3ueK$(>B;RAV@gHsJKkF=@7`WNe!4$kZ_P>Z{m=WK&jt8~@5_~?*l-ey!Hplr6uQZGS`1QCqS<|>SpY-N{@f^J>D3f&t=;}XLAzC z=?eIq=?hI8sd5gAn@oPKBu|m%4?AEK!&YH)tiG+@hcbnVemb8)5LkxxI&+NpnNTFL z$R~nEIlh}$iT|n!q>~RY+quZ0Q{#1a%8)qUvCD;dn@>Cv_g2QdD|QU!(nPM3x^ii8 zvp~AWE~KpX_$X|zi_8`VoeUFUQt6V<0w0M4MGsAOwi`$i2WV4j^~||IwnZP z2V0b6_XrJzy?t` z^6teb%G(N3YgJ#8DE-jb3cpHwMtZy*V*#26)0s@8eo-c=mwbH`^e68pFs!h$UBNK} zK0^L`wy#@J?m0CV_s3HWX*xnaE!lw!2{XZ6<+0V4aa2?tUilSPl#lR++AHYgegb(; z#gm4x5*q;ZvVQWK0;$bA3V^@0hSEFB4ptPNka-4bRc7hSgR}ibblT}% zLhztwiTqY<3ok~lfX%3AgQ`cOmz4TY&M+j001A+gLhrZuEXrkZfIg&jVy5suNPFOD zoP~XO9wgxy%D_=+#aLIQml{#<&RWO_m%k%~S;kKc8V_IEpO-EbQ`Nz02%!q>Fp7z^ zUG}L_3Box~iki$+%)AQk>>UWtG9}1OW>B^W(1ZLt(4a$0C#|kh6F z&e}~-#%=mir@LbXk0gb1iEP$FwYrY|kNf)_z6i?Ow%pD7(OEWBf_djxFx!uJYIlj& za1QB0)mhN|>JfFsf0uwH9$LVzFkOEjYk0P^yNLKHa()frGU|SYz>vL5D+;XFk)m$ zzg!YF6ZC!Fs#ozutG+O1YIzD3J~33#h!Z)@5lFR;=A9EJm`yJhdXm~}>eTNmi)n}H zf@}8ot1LaK;yZKn)ZcI!*0QUhVjr(&hV;A6z9w7-l+h#?|_Py!{YdgE%6 z-IYJ-7;uT1a~9C)_aaGuzVr$_alD^8rn*}AEciLjc4anl^}OY1^+jxzjYk6s88!jK zray5bp#Ot}*^;`C=9lB>a^y1(kN3p9zx6hev;8X}7N)t?Xx_b~QQ2P=h}b_)=L-A_ zJvuuVifQ=?vrkN{Qo*3089HkcHoHMz3!8U|`^rwY)l+Z0%pOG|$dg+rlunt>OhJiB zKCJ13Ju)-Jmrzuo^f_QF<=P}CzmYh1)twB)g>5Mioel~-QM75RdU=I_r_eu1lG%&h zYd4!5Kkde_Z`glY(hykyXU0#o=^y?z8ECIsz*^@CktxQAvFlfRAyU^FHPY5}xhG3IqAUtb0%8ed8UasRFPhcZW4H_6t`(E}N4>QvY&zN~kfMP#!j{Y`- z%ttCaw$0~AzI_SsT3Q7w5pyH;Tc5!?b=T*M^r#u>Eu5=Tg$1h-Z{0S=P_FhP;*@2= zu{ZRH{kNZBEB0@_=J<28+HqPGe4!*8T_HHmZMT8~ZT?z~^SfWj4-x%<7pXGYJ;g3x zrwdkc03hB)*g_jR@@c)P{pC#m{qfh^;cH<-#pct!esywo?I0sXGhv~R@1zu`@<6)4 z@5Ip-(}xB#+|Ez?n!`S?*{Vq$<`{+Y%uxJ z1Fis+{;brvxG50zo$Tc|44oy|8)8}Ioiw$-pjYs$n1Ij2bAxx%zar7I$gIrHqUT8U zyfDL{?i>jEh!nkTfv(G@K7^u-HGqVnG7}wV_c;d%7=^mKMcmlHR^T&@?6DQxcd6s- z>fGX;lw=1T^Y#O7K>*qC6pa{c) zGONuehWun#)ImUf**bCZ9$o+MLF(*s7@eGj7kXntS|WOu@cuSYQh7~N7VS!a-mY*A zjn|EUUdpcd9(-)Gqw~p9y)wNscq}ZbEfwqv3Wlnc^IO=+{i8rc~fz1-^2CO zgDdmnU-l{fE{~Z=JtaOtFWc5@dLKS6i=pfAI}-_<7WO^|m)yk{&G~y-HN2Yf==j6n z&Vi##D;3=naX*akT&`^JmMrJOC9l_bOqkFVcf@^+XzG{z+z}ytd zE(wp5cy~Q+vi(>HYIre&Mg}ML$iGjb%~)O%*1tZ%`!>ikapNQ+jbfVTa3kIKK)}xT z+;{c5EY*_&hm5w>-o&`_@9pme69S<+m!yK}^pZusjo!p(qwp2Sf3DfR#(mCy>b`0g zSNc#9fz4BIrU>8^xtSyXc&-OMUJYbatPklHh@-|A)7}S_i=SBYkyRa(93NKM zbYqo<6^TDHXP{sxRqvje#NFVVn$4IylLUxLD;(es=2{3XyVG;g^*U^CBGZ#y)=Ro? z`Cs0lw-nOH{C+P20a8x1$whtk;>nwpu5u`6kp6`y^s0le(ob^0&tLVx8;_j@9@j%G zR?}e-1#^HGYfsDq-+9c_)9PPnbjymfOD8e$*ekrv`_~V*e91E(Rad|p7>$)AOU8@R z2<~AOQaAD>?NpQrqn^6E@p8D>IIP%Cg$LVyt8n&m6iw6#{3TvHl=Hsc_ASPv3A<+X zA$+5u@b;~B!(u0#t3dVgR|N2uo5;ibRp`+t`^l@|J_Ql9Xhf@d7C6LO^N|d*DA!4i zrB&Z!H_{GdyN%{^>>S4P<+{0B{!OAzNQ%!Sef|kyK+@y8sI?SzF!?%NcDbVpG8jLa zXNIT1<~3bm`jIC3B5y>NM-^G(yJ5cp8*+HHnM^B%%5ajXFav|Gd;@RAB&t@p&=N7a zA_hrFnwbc;Y6TJH7Ggmc{^rNk#$zga`@d|c<zv{{Bo7%9I^xBjk@(HbZ3(-hXkU&T4(Gn?`fQ4)dpN0BV(#r9t+ zfF-MUO;7bZU7^i3!8X==pBFWbOB7qq-W8SCU+)!$@|v05Ct|`k`e!9pbY|zD=dtp> zS*5;7gzZZFg3;@IfOSppw<+N6W(ul;z1QYcPeuFI^P#2)TE<)Z&E0!JW?aglT+3&K zxm5oj+PI(3&hCF-lG4RIl#SnkWWuQ`Y@Sexk8Y}8n&Pn4PMthSh zKdxl5Xp%M6Zpq|w9)=D*XjM6dDE@yhfNrMKKr{y|t+&&Vg(Db4+p}^g$5r(#I(%TH z^k+4H@T#+Z&;H^XwKd4K>$a-9yKGWw>?Tu(>H&t0O6#qtg%WKH`OgWejBVfQv&DEL z+J!XV2V^Gj?lAzrR25|L&fX=q#wYGyQhM7mNCb`Fnm@@gmPIjGs;54XE(#+5NC!Z6 zcNC~EvKVwIAO1A{&bZ&WD%OmZsg*&bcxa0lzPRIS_Bz;C0txxmj|Y{Ha5>}-ni1_c^3ps-BRpJLQr84lW;vG$VSEjD0B1La`i4~gs_PVuzBICZbVX0 zq9D*w!WAzN!RwSCJkNz^U6($RBps+BlbaaA)dM}r@M%bT3p-8x3{$A*AGCxwTwCuu z)%4v0$h|7~gTSAtKi9!w57RR?^)iJd$nP|r>5tdOtu&ba0M$RI-4QsU%&U@r z6O&Z>Efj&xqc+WgoMvL3A%BWcs1r4nw{rgZghskL2dh zpJ#W<*{ymC+uu}^|DrOKJ)pa_Z=FurlLzHF!{|66pfiNH{b1sZ3P?2N)jnys%Cq8T z9i=l|zb;>>lDlxx!s;(R`Pu6J`yV7(2euEm_~HxebR7||n!lAa;t3NmJy_%!^pCIy zf=D3Q){UNImkmSWhCV5)1_w<4nqw_G9)~|B$&>5_-V{eiw5>oigezjo$ajEoO@bDv z$PZk>GCtXI;R%*B#4R8t^hCkvm6ozRO;$iTVd+vp3eg}TqvRBHN`@y$G{s$6;?t_l zTej8<26_E-mVLdtO^-EBT2#2iHa`n8;v)z>#esu{!HGG&M9PVkSGr@{7~P~- z87EKqf|R#^Dhqs$Ua#m2o@eQ@PXz72j<{E}!~XXP`2;LOUie6E#ev*;V25etfuneo zck7FeU=yDdG*la%J#Ps8p%8Knk|*eM^dNuohEH5Q58c2hX_RqESmxz;)CDK!?byLM z@|d%YwK%yf>^6L#aAIL0kBS0`@ZZ2lgH}om3%mu)9{3h?sh>E>eq;DYsedy*mi@3v z{CMl_;muoWVOh-mm9KogT7A#Gb)F)-c0E~LfBj9CG`enl(Fel?PN>i(h!17N$H13< zK#v#D>!|Kn^oA`L9l@r2q>vTpann_>*83UJ-v#qS>4Rj{X_qVGO?7NUcbJk2K8sGi zMV7Ts%mvjHz_PKUUd{{u(axYw!mc>*DZlctjFN(v+hNb{J=Nv*ou8Y3a+^(rHdMPU z&35&to^o>ahS$HU;yXd%0o1TyM9SN;OYIKRR-5G9c=IjQs#UAK^URtxvpUWeXusig zuddEI>-4DS$B>^}x0nBbk1$TFPCxxrf5!c#FMr){?QY$+t;XNBZF}{(&wtU=FM08c zs@w0l(-(wqw}DP!t9{q#bD#f`C(k?YEPE5>bcDupOlsPN9es zRf|weSBbm=sZm+j!O`!?(81Cmfd58&wV8=tvwjGsJk^ahcX=k!{`;rD$>pjT^nb}2 z@_Ug~845y715dUwCy1~x#NfqG`ioncXBd{0A5cs2iG1R*C!VOTz4ph|k8RL@_~9+p z9~Jui4wA-~PUB;c-v($o3u8ij$UC z=bn3Zb;(OEiZy+v|AP;%ufAzG_t=Sx1-Agy^VPjP2}SLM6W$EHmWS2Jcz zuUL2Z>R0_tb^iJ1Ml(Rrg7i10)pgh3=x@_*wDpcG2uA1m^X7SfzvQJCS4SUpl+|eJ zfB6rt@<%(2Fq01XV%P{Y zxuakjn{I9gH+q`fdY&vh^)}gR6ZJp+vrqcupp^U}d!?G+t+(D$z3~mNvEQo)`UGEO z{~xVyXK^QE_3AbLyXmG|st?$kLo003r9L5_;6h@fotPHd>)Pkp#E3pr`ux52xSHSX zcdV*@=eK^%cT;B1oAOofEnBu#S6q2@b(ih5{JS6hpKP+0U;AEb6FBvklTKPzede#O zs(xY@OA7SeYkzdT_x%t5`Uk4hR-RgY>QjGR-En6=G49@7xeTBB%->c&|8tkw#D1NRDJZLAF&DD+|oOL$#)aZrpx z#ewu;=m#X-?!)T;z@UeJMqS6}V4=*5oMDboPSPR^Rg+wzLLR@V95Ws)Uepnv5^_;{ zwN1ViXy;Y7U*DN$+Qd7=O;*u338vz%g{46Z$-UGJ9^*iix9tsnd5*)bSYGy3tnDAP zBSCE``P%fNUQkRmoO;Mfj${pj!}v$g8#c_ayJI_fZ*M{;gqS0N3aQp1|c z+rSP$kE?f}ERGWOfc!uabacxRq*U~Pd-m+EKJ(eX?@X4UsKDU)w}1D!YWwz`LD%Xr zc(WG=fz8N1{%4z?ClB#|21{y$zBPF#ZrQSh;1t+|ll32e>=T{I zQYkls4Ss!}t=m8*x06BQ-}?6Doyk&JmpD8=`KiANe|G&KQrX4FZo5E;zQvti7SfL8 z^W07P-sM+TpZxS+HzrFZFYfGIb=9>YpA{IF`eFyX?|tve#$<^+1$6wbZRq&dfAbHG z$&$(izwpI>u{#YhR-yl%Jx^DE`PskgOqPT%xYh1B{p08C?s1|2t|y+X-?F0Z|McU{ z3o+Fd{F=3a6F{-#Beca0H{4YH-GFULR1e6cRU1~4Ub-|M?R!5kdI9%$On@?{Df(*o zGdVN}fBPQPFG9{ti5SK(2#{R_T@JV+kBbr5CA#G}x*r)Azz^8|Bnl-8Z+Y|Ut6#9m zGkYzS;d|e|BK*(jl(ZyZnODYD9%i!2sK$>V=!uZF#!vDl@dMfOf&h9$U|;k{zNz(( zrhTVef45H@)T*S=;Xs7Z++hk0tPP_<(S;}qY8F^Gt`&M&Qdn`Ihq`hyNFXHIHE(93n;A2LjxO?KJFsQ>Sue8L|e!3mz+**x#Ob1LozJ@CMTw)fYU?au5r*X?Ux z|CWF1L3ZNq&{w|tjatZ)?D5;1?L@+xL_Es+f!+1F(Kciy=+5Y?UimY%pYf=g#~cJb znx=2O{AVty&Oh($>i!4TRZLKxu*Zc2;shR}amVX<=bvMDuP>HaZFRWu`tJ9xsIL3* zjb4N-o40JS$Cbx=`pPS>sn@h%zn8u2CDlvq@!|Xh^Q$ek{^jeoyT9&e-E+@ccOThr z{lCGnhDfZ}b=gvo~ zYp=WBCW1e3S(&&JsC(Sv#qLj@(LTT;1<9J&B_9l51jRiUe@$_nsNhle2aCJ3B9dqi z12VnnBQfSUcdX%ZO?Cg!lZCdNZ+QKytJl8vmEM-GeC4H$2{w=HHdwo|)|61%hqTL> zAZbhza+AvO&@bf$@MCBkbjj$_o4WC#VB3uDqkjyRH|t!NC!2_A!7l7+1=xYm)<1GB zKMmAwFGG@Fs(ci1L=z^|4|U4^6GjQO$S)m9&Qs=v!s!Gfar(^Pm$*1&bCPYm=!Nc2an$t)JRmU!AjF5BDE-2Iv} zXO0i1&~1ify>avpe9;yG>M1W_sHn+cV0EDiy7O9M?DhgvJTpnLvaW7XAO1XRVtQ)p z1Ejwz7QFCaB;{}aLtFtyyJ7{S9c+TS6;Q@+Ds6I&txns98HKcB~X_`=@m3t#$b zJ?T8|xJA{!`<)Nh>&1Bd|Bko4$tHb=RZMmj7=yTj9RWLFtFT9oCkLcNMNy%7Y*%mL zdjD|poX9iHCXtC!)Qh?;@^wXIT;CqHIrf?YGQ$ZKBxGu4~l^g4@$JltVo zX+*g`j60b;y7hp-peDf+P;wITwg_ZWiF^=`v2$wmcLpwLp+K!n=nu{?{xLHBML3D2 zaamHvj3YD})p0bB?Bs-@fUF^JSA-tov>_ELS^e=T4gECLL2s)*t1a^>Z1SuCTkRRxR zscLNp@H-V;*QBiU7EJU=LM!O)d;5p1xy?C6&mtnObj|vkAZB0xGl8t8|AD)I zA={Ueit7rinz|VwnaRthz$QFOV(pOvSItb8N;?r>ZWj|vm)ceu7C7niHV}cu;oQL> z@QV7c{p$ODa@4frwCZQy^xAsj!5g96QHr)~J}$$?T!<`NX1tt8D7<{x3C=@@fAhZg z#De9*C*pf(fpMf=RM1BJc)Yi{c+2U0yL0y5_t<2~{$Rmx;jwH)dxXeW+*xC?#9dzL z!UfJ5XRNGoSj>&Q*y5fwYdxRMWPir)E~$>_vtayKyBJakUy4+DLiVxc!;CFNkG% z`cB*8OpbNe5)8u2Z8H4Ux4g+>VKd3`ikDyNhB2!g~_2gnZ;!GxaV1=~Wq z0)l#;L619s6N!{oG(MN@)o85f)pg%RChLfU8q zbcn&rELR5fbOn<|@dScfI6;8@Kvivw5+|H6RLr`agWcI#XqXZHE`w;j5ReK=nEic9UvmRqffzTXDGA@}+lZ=!#s4W7a&uW<8Mu0!Gn z?d>21KPe%IyvPgKAw1uj%2EENT(oXEHq>KFOMlv& zjS>~|?*Dsj!h5B%eF?#__7wv8j>y21yGbXVxV(OJ#Kl?NzO}3&lkx?z<>bc%^bR_; zU_?xquu3C$xI}7G$iu;s{tF^xrXQSK1~^&(Q58tq&y$o{KwkA_PbZA_-B0d%vZ=6w zvmjygFS74~e)X&WTCZ*8$-xy@UgeK;GdH-xFC+-)Idz^RbPg?vNfHDR#B~h%UN8mv z$GHMWyABvUwYj@;se)Q1L7j{p zdQ%!QCl}$R#w!fv4rEwo+7+U1OLMpB_y6#Z>~-f?*j2-&zD@zI+}>|HUpZl6ZGs0G zKM9T++OLC8r_7wmfJ?63p|TT=0-rnaki4zDd*yeSD3mIJ*jR%R^$lSk^mlUYgF0GE zpQ1MyF9b@?=J+I2x^6fL9Xi+eu8i`mUeddU$wnC;9Q)Nli3Inf{cDnCWLW*FD^W}r zI-z$&@+>ZCOBR!k7k18O6|<6xxTq}IcSxVO{q|Kg(Tgv-(DwgI-F<*f-0#(P)@HoK zz?*FZ-W0ix=5AaT?K%KBC2h>1KQvFd(r+`PNU-Z30j+#C#HJxM0?cr$K*VIfwyU=S*(tX zuj-@;3GU;VQBG!|=976M1MEZJx%AQ(SKqMpY6@3eaZUAF^J(8#&qWM>cH4=DBWrw2 z<+VPNzXcDrF|*+xrf?qIlBuRp|70o%=Q#l^i-N>;6ktRXrZ5m4OSVg6WAbr1j$o-8 zip#*{D=yq-d6_!p6Xx4`znILFx(3tI|5u*6!mt~hEIqBU<}mj^ z`d;okEP@6Xs^VhN#-#1kQ%}yi7CJv_TO{c5j6PT!l&Wg?(|f9WY~lIMww8%KsJIy8 z<&i<*1YesK+#h@FQGdh@U@|?kWn4tCyy$5j+h8i?z z1%62(%7tC}4p=dECx)s9Bv>WZn||xt-?z6W_jncy_OH9{Mt`}$X$E;O3tL6B>u_!P zgeNEoMR0%doAjC~dDH&;#{oNMBo~mk;pYR#YMFBi;fN+oNEnkPoTa`sX^Ns4f$^4E zp%hiV7Z!)W30$(f_|4|$t+TmDAMJ0h=ycb620x~uGv(-0-r;Ir84jyI@?-OW#OMh5YBWl7=Ll^B z2YCpC^beXUorr!(qa{z#=1$G@mmiRBhXbj9v*kmshCE63M&6_o1Nwv2jqVWkL|55D za1i;)+JrZ_2;j3=SgkS{pU|T`>aVc1drjM@{egtN_O@7_oE>`%>v{J2BR(&-!wT

EBhxq?GMtwd~~Q2Cpz=y-=F>2 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
WeWJTufuns=I=GbCha=Xwg~VXl zkxZ8_wzGf@0Y+=>GII#oSCvE(CK~uA9B%1MJI|VUmpmScnjd7Npn!#jY)(i#0b`;F z>%zB_{WL&=g_t2O_liaRYGUo#PU)D^-kdj!X@A3cv7hnELP~N<^%0uI3$

^r5lA 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 DcO2Ha literal 0 HcmV?d00001 diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleKeycap.m0116-keycap.png b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleKeycap.m0116-keycap.png new file mode 100644 index 0000000000000000000000000000000000000000..dffaef86535ae0f3c30511c31417066bf289f4d3 GIT binary patch literal 22360 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 Date: Wed, 26 Aug 2026 00:33:29 +0200 Subject: [PATCH 02/31] refactor(keyboard): restrict theme options by keycap style --- .../Keyboard/KeyboardSettingsPane.swift | 4 +- .../KeyboardSettingsPaneViewModel.swift | 79 ++++++++++++++++-- .../Keyboard/KeyboardVisualizerSettings.swift | 57 +++++++++++-- .../Keyboard/KeyboardVisualizerTheme.swift | 14 +++- .../Visualizers/Keyboard/KeycapStyle.swift | 21 ++++- .../Renderers/M0116/M0116Palette.swift | 41 --------- .../Settings/ColorPickerOrderTests.swift | 7 ++ ...oardVisualizerGroupViewSnapshotTests.swift | 12 +-- .../KeyboardVisualizerSettingsTests.swift | 37 ++++++-- ...KKeycaps.m0116-command-shift-k-keycaps.png | Bin 59655 -> 47944 bytes ...6StyleEscapeKeycap.m0116-escape-keycap.png | Bin 26894 -> 20423 bytes ...stRendersM0116StyleKeycap.m0116-keycap.png | Bin 22360 -> 15452 bytes 12 files changed, 195 insertions(+), 77 deletions(-) delete mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116Palette.swift diff --git a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift index 584a996f..bd955c39 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift @@ -262,7 +262,7 @@ struct KeyboardSettingsPane: View { subtitle: L10n.KeyboardVisualizer.themeSubtitle ) { Picker("", selection: self.$model.themeSelection) { - ForEach(Array(KeyboardVisualizerTheme.pickerSections.enumerated()), id: \.offset) { index, section in + ForEach(Array(self.model.allowedThemeSections.enumerated()), id: \.offset) { index, section in if index > 0 { Divider() } @@ -341,7 +341,7 @@ struct KeyboardSettingsPane: View { ) -> some View { SettingsControlRow(title: title, subtitle: subtitle) { Picker("", selection: selection) { - ForEach(Array(KeyboardVisualizerTheme.pickerSections.enumerated()), id: \.offset) { index, section in + ForEach(Array(self.model.allowedThemeSections.enumerated()), id: \.offset) { index, section in if index > 0 { Divider() } diff --git a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift index f4a2681d..828dac09 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift @@ -46,7 +46,15 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { } @Published var theme: KeyboardVisualizerTheme { - didSet { self.settings.theme = self.theme } + didSet { + guard self.theme != oldValue else { return } + let sanitized = self.style.sanitize(theme: self.theme) + guard sanitized == self.theme else { + self.theme = sanitized + return + } + self.settings.theme = sanitized + } } @Published var legendColorMode: KeyboardLegendColorMode { @@ -77,27 +85,71 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { } @Published var modifierTheme: KeyboardVisualizerTheme { - didSet { self.settings.modifierTheme = self.modifierTheme } + didSet { + guard self.modifierTheme != oldValue else { return } + let sanitized = self.style.sanitize(theme: self.modifierTheme) + guard sanitized == self.modifierTheme else { + self.modifierTheme = sanitized + return + } + self.settings.modifierTheme = sanitized + } } @Published var specialTheme: KeyboardVisualizerTheme { - didSet { self.settings.specialTheme = self.specialTheme } + didSet { + guard self.specialTheme != oldValue else { return } + let sanitized = self.style.sanitize(theme: self.specialTheme) + guard sanitized == self.specialTheme else { + self.specialTheme = sanitized + return + } + self.settings.specialTheme = sanitized + } } @Published var mediaTheme: KeyboardVisualizerTheme { - didSet { self.settings.mediaTheme = self.mediaTheme } + didSet { + guard self.mediaTheme != oldValue else { return } + let sanitized = self.style.sanitize(theme: self.mediaTheme) + guard sanitized == self.mediaTheme else { + self.mediaTheme = sanitized + return + } + self.settings.mediaTheme = sanitized + } } @Published var mouseTheme: KeyboardVisualizerTheme { - didSet { self.settings.mouseTheme = self.mouseTheme } + didSet { + guard self.mouseTheme != oldValue else { return } + let sanitized = self.style.sanitize(theme: self.mouseTheme) + guard sanitized == self.mouseTheme else { + self.mouseTheme = sanitized + return + } + self.settings.mouseTheme = sanitized + } } @Published var groupBackgroundTheme: KeyboardVisualizerTheme { - didSet { self.settings.groupBackgroundTheme = self.groupBackgroundTheme } + didSet { + guard self.groupBackgroundTheme != oldValue else { return } + let sanitized = self.style.sanitize(theme: self.groupBackgroundTheme) + guard sanitized == self.groupBackgroundTheme else { + self.groupBackgroundTheme = sanitized + return + } + self.settings.groupBackgroundTheme = sanitized + } } @Published var style: KeycapStyle { - didSet { self.settings.style = self.style } + didSet { + guard self.style != oldValue else { return } + self.settings.style = self.style + self.syncThemeStateFromSettings() + } } @Published var scale: Double { @@ -175,6 +227,10 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { ].joined(separator: "-") } + var allowedThemeSections: [[KeyboardVisualizerTheme]] { + KeyboardVisualizerTheme.pickerSections(for: self.style.allowedThemes) + } + var previewRenderSettings: KeyboardVisualizerSettings { let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) settings.registerDefaults() @@ -246,6 +302,15 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { self.customLegendColor = color self.legendColorSelectionOverride = Self.customLegendColorSelectionID } + + private func syncThemeStateFromSettings() { + self.theme = self.settings.theme + self.modifierTheme = self.settings.modifierTheme + self.specialTheme = self.settings.specialTheme + self.mediaTheme = self.settings.mediaTheme + self.mouseTheme = self.settings.mouseTheme + self.groupBackgroundTheme = self.settings.groupBackgroundTheme + } } extension KeyboardSettingsPaneViewModel { diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift index 3b9944cd..d5231fc6 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift @@ -184,7 +184,12 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS /// Base theme. Applies to regular keys and — when `usesCustomThemePalette` is off — every key. @Stored(.enum(KeyboardVisualizerSettingsKeys.theme, default: .black)) - var theme: KeyboardVisualizerTheme + private var storedTheme: KeyboardVisualizerTheme + + var theme: KeyboardVisualizerTheme { + get { self.style.sanitize(theme: self.storedTheme) } + set { self.storedTheme = self.style.sanitize(theme: newValue) } + } /// Whether keycap legend drawing should use the theme-default color or a custom override. @Stored(.enum(KeyboardVisualizerSettingsKeys.legendColorMode, default: .automatic)) @@ -199,19 +204,44 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS var usesCustomThemePalette: Bool @Stored(.enum(KeyboardVisualizerSettingsKeys.modifierTheme, default: .black)) - var modifierTheme: KeyboardVisualizerTheme + private var storedModifierTheme: KeyboardVisualizerTheme + + var modifierTheme: KeyboardVisualizerTheme { + get { self.style.sanitize(theme: self.storedModifierTheme) } + set { self.storedModifierTheme = self.style.sanitize(theme: newValue) } + } @Stored(.enum(KeyboardVisualizerSettingsKeys.specialTheme, default: .black)) - var specialTheme: KeyboardVisualizerTheme + private var storedSpecialTheme: KeyboardVisualizerTheme + + var specialTheme: KeyboardVisualizerTheme { + get { self.style.sanitize(theme: self.storedSpecialTheme) } + set { self.storedSpecialTheme = self.style.sanitize(theme: newValue) } + } @Stored(.enum(KeyboardVisualizerSettingsKeys.mediaTheme, default: .black)) - var mediaTheme: KeyboardVisualizerTheme + private var storedMediaTheme: KeyboardVisualizerTheme + + var mediaTheme: KeyboardVisualizerTheme { + get { self.style.sanitize(theme: self.storedMediaTheme) } + set { self.storedMediaTheme = self.style.sanitize(theme: newValue) } + } @Stored(.enum(KeyboardVisualizerSettingsKeys.mouseTheme, default: .black)) - var mouseTheme: KeyboardVisualizerTheme + private var storedMouseTheme: KeyboardVisualizerTheme + + var mouseTheme: KeyboardVisualizerTheme { + get { self.style.sanitize(theme: self.storedMouseTheme) } + set { self.storedMouseTheme = self.style.sanitize(theme: newValue) } + } @Stored(.enum(KeyboardVisualizerSettingsKeys.groupBackgroundTheme, default: .black)) - var groupBackgroundTheme: KeyboardVisualizerTheme + private var storedGroupBackgroundTheme: KeyboardVisualizerTheme + + var groupBackgroundTheme: KeyboardVisualizerTheme { + get { self.style.sanitize(theme: self.storedGroupBackgroundTheme) } + set { self.storedGroupBackgroundTheme = self.style.sanitize(theme: newValue) } + } @Stored(.enum(KeyboardVisualizerSettingsKeys.anchor, default: .default)) var anchor: KeyboardVisualizerAnchor { @@ -309,7 +339,11 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS /// Visual style of the rendered keycaps. @Stored(.enum(KeyboardVisualizerSettingsKeys.style, default: .default)) - var style: KeycapStyle + var style: KeycapStyle { + didSet { + self.sanitizeThemesForStyle() + } + } /// Whether only keystrokes pressed with modifiers should be rendered. @Stored(.bool(KeyboardVisualizerSettingsKeys.onlyShowModifiedKeystrokes, default: false)) @@ -378,4 +412,13 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS } } + private func sanitizeThemesForStyle() { + self.storedTheme = self.style.sanitize(theme: self.storedTheme) + self.storedModifierTheme = self.style.sanitize(theme: self.storedModifierTheme) + self.storedSpecialTheme = self.style.sanitize(theme: self.storedSpecialTheme) + self.storedMediaTheme = self.style.sanitize(theme: self.storedMediaTheme) + self.storedMouseTheme = self.style.sanitize(theme: self.storedMouseTheme) + self.storedGroupBackgroundTheme = self.style.sanitize(theme: self.storedGroupBackgroundTheme) + } + } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerTheme.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerTheme.swift index bd890618..72905cf4 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerTheme.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerTheme.swift @@ -27,6 +27,15 @@ enum KeyboardVisualizerTheme: Int, CaseIterable { // MARK: - Titles extension KeyboardVisualizerTheme { + /// Filters the standard picker grouping down to the themes allowed by the current style. + static func pickerSections(for allowedThemes: [KeyboardVisualizerTheme]) -> [[KeyboardVisualizerTheme]] { + let allowed = Set(allowedThemes) + return Self.pickerSections.compactMap { section in + let filteredSection = section.filter { allowed.contains($0) } + return filteredSection.isEmpty ? nil : filteredSection + } + } + var title: String { switch self { case .black: L10n.KeyboardVisualizer.Theme.dark @@ -376,10 +385,7 @@ extension KeyboardVisualizerTheme { case .retro: return KeycapAppearance.retro(KeycapAppearance.Retro(tokens: tokens)) case .m0116: - // This style ships the keyboard's own colors; themes are not wired into it yet. - return KeycapAppearance.m0116( - KeycapAppearance.M0116(tokens: .m0116Hardware(legendColorOverride: legendColorOverride)) - ) + return KeycapAppearance.m0116(KeycapAppearance.M0116(tokens: tokens)) } } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 85323c68..6756d1e1 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -5,7 +5,6 @@ // SPDX-FileCopyrightText: 2026 Serhii Bykov // SPDX-License-Identifier: BSD-3-Clause // - enum KeycapStyle: Int, CaseIterable { case apple = 0 case pbt = 1 @@ -24,4 +23,24 @@ enum KeycapStyle: Int, CaseIterable { case .m0116: L10n.KeyboardVisualizer.Style.m0116 } } + + var allowedThemes: [KeyboardVisualizerTheme] { + switch self { + case .m0116: + return [.white] + case .apple, .pbt, .minimal, .retro: + return KeyboardVisualizerTheme.allCases + } + } + + func allows(theme: KeyboardVisualizerTheme) -> Bool { + self.allowedThemes.contains(theme) + } + + func sanitize(theme: KeyboardVisualizerTheme) -> KeyboardVisualizerTheme { + guard self.allows(theme: theme) else { + return self.allowedThemes.first ?? .black + } + return theme + } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116Palette.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116Palette.swift deleted file mode 100644 index 8842ac5d..00000000 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116Palette.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// M0116Palette.swift -// Keyty -// -// SPDX-FileCopyrightText: 2026 Serhii Bykov -// SPDX-License-Identifier: BSD-3-Clause -// - -import AppKit - -extension KeycapThemeTokens { - /// The Apple Standard Keyboard's own colors, sampled from the hardware: cool off-white - /// caps, a warm olive case, and the near-black plate showing through between the keys. - /// - /// The M0116 style paints from this palette rather than the selected theme, so it keeps - /// the original color scheme whichever theme is active. - static func m0116Hardware(legendColorOverride: NSColor? = nil) -> KeycapThemeTokens { - let textColor = legendColorOverride - ?? NSColor(calibratedRed: 0.290, green: 0.298, blue: 0.337, alpha: 1) - let swatchColor = NSColor(calibratedRed: 0.898, green: 0.902, blue: 0.910, alpha: 1) - let badge = KeyboardVisualizerTheme.badgeTokens(swatchColor: swatchColor, textColor: textColor) - - return KeycapThemeTokens( - swatchColor: swatchColor, - textColor: textColor, - groupBackgroundColor: NSColor(calibratedRed: 0.827, green: 0.831, blue: 0.804, alpha: 0.94), - groupStrokeColor: NSColor(calibratedRed: 0.596, green: 0.600, blue: 0.565, alpha: 0.70), - badgeFillColor: badge.fill, - badgeStrokeColor: badge.stroke, - badgeHighlightColor: badge.highlight, - badgeTextColor: badge.text, - surfaceHighlightColor: NSColor(calibratedRed: 0.937, green: 0.941, blue: 0.949, alpha: 1), - surfaceBaseColor: NSColor(calibratedRed: 0.898, green: 0.902, blue: 0.910, alpha: 1), - surfaceShadowColor: NSColor(calibratedRed: 0.804, green: 0.808, blue: 0.820, alpha: 1), - surfaceBorderColor: NSColor(calibratedRed: 0.404, green: 0.404, blue: 0.400, alpha: 1), - recessColor: NSColor(calibratedRed: 0.267, green: 0.271, blue: 0.267, alpha: 1), - undersideEdgeColor: NSColor(calibratedRed: 0.180, green: 0.184, blue: 0.180, alpha: 1), - undersideCenterColor: NSColor(calibratedRed: 0.404, green: 0.408, blue: 0.400, alpha: 1) - ) - } -} diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Settings/ColorPickerOrderTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Settings/ColorPickerOrderTests.swift index 978dc986..34bb8449 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Settings/ColorPickerOrderTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Settings/ColorPickerOrderTests.swift @@ -19,6 +19,13 @@ final class ColorPickerOrderTests: XCTestCase { ) } + func testM0116ThemePickerOnlyShowsWhite() { + XCTAssertEqual( + KeyboardVisualizerTheme.pickerSections(for: KeycapStyle.m0116.allowedThemes), + [[.white]] + ) + } + func testKeyboardLegendColorPickerOrdersColorsBySpectrum() { XCTAssertEqual( Array(KeyboardSettingsPaneViewModel.ColorPreset.presets.suffix(Self.colorOrder.count)).map(\.color.hexString), diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift index 9a4c71e1..68b2e4ad 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift @@ -37,12 +37,12 @@ final class KeyboardVisualizerGroupViewSnapshotTests: XCTestCase { func testRendersM0116StyleKeycap() { self.assertGroupSnapshot( style: .m0116, - theme: .black, + theme: .white, items: self.keycapItems( keyCode: KeyboardKeyCode.a.rawValue, legend: EventLegend(text: "A"), style: .m0116, - theme: .black + theme: .white ), named: "m0116-keycap" ) @@ -51,19 +51,19 @@ final class KeyboardVisualizerGroupViewSnapshotTests: XCTestCase { func testRendersM0116StyleEscapeKeycap() { self.assertGroupSnapshot( style: .m0116, - theme: .black, + theme: .white, items: self.keycapItems( keyCode: KeyboardKeyCode.escape.rawValue, legend: EventLegend(text: KeyboardGlyphCatalog.symbol(for: .escape), label: KeyboardSpecialKey.escape.label), style: .m0116, - theme: .black + theme: .white ), named: "m0116-escape-keycap" ) } func testRendersM0116StyleCommandShiftKKeycaps() { - let settings = self.settings(style: .m0116, theme: .black) + let settings = self.settings(style: .m0116, theme: .white) let items = KeycapItemFactory.modifierItems( currentFlags: [], releasedFlags: Self.commandShiftFlags, @@ -77,7 +77,7 @@ final class KeyboardVisualizerGroupViewSnapshotTests: XCTestCase { palette: settings.palette ) - self.assertGroupSnapshot(style: .m0116, theme: .black, items: items, named: "m0116-command-shift-k-keycaps") + self.assertGroupSnapshot(style: .m0116, theme: .white, items: items, named: "m0116-command-shift-k-keycaps") } func testRendersAppleBlackEscapeKeycap() { diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift index 8e797168..71252804 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift @@ -335,18 +335,37 @@ final class KeyboardVisualizerSettingsTests: XCTestCase { } - func testM0116StyleKeepsItsOwnPaletteAcrossThemes() { + func testM0116StyleClampsThemesToWhite() { + self.settings.theme = .black + self.settings.modifierTheme = .purple + self.settings.specialTheme = .rose + self.settings.mediaTheme = .blue + self.settings.mouseTheme = .green + self.settings.groupBackgroundTheme = .orange self.settings.style = .m0116 - self.settings.theme = .black - let onDark = self.settings.appearance.m0116 + XCTAssertEqual(self.settings.theme, .white) + XCTAssertEqual(self.settings.modifierTheme, .white) + XCTAssertEqual(self.settings.specialTheme, .white) + XCTAssertEqual(self.settings.mediaTheme, .white) + XCTAssertEqual(self.settings.mouseTheme, .white) + XCTAssertEqual(self.settings.groupBackgroundTheme, .white) + XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.theme), KeyboardVisualizerTheme.white.rawValue) + XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.modifierTheme), KeyboardVisualizerTheme.white.rawValue) + XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.specialTheme), KeyboardVisualizerTheme.white.rawValue) + XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.mediaTheme), KeyboardVisualizerTheme.white.rawValue) + XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.mouseTheme), KeyboardVisualizerTheme.white.rawValue) + XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.groupBackgroundTheme), KeyboardVisualizerTheme.white.rawValue) + XCTAssertEqual(self.settings.appearance.m0116?.textColor, KeyboardVisualizerTheme.white.tokens.textColor) + } + + func testM0116StyleRejectsNonWhiteThemeAssignments() { + self.settings.style = .m0116 - self.settings.theme = .white - let onLight = self.settings.appearance.m0116 + self.settings.theme = .black + self.settings.modifierTheme = .purple - XCTAssertNotNil(onDark) - XCTAssertEqual(onDark?.textColor, onLight?.textColor) - XCTAssertEqual(onDark?.wellColor, onLight?.wellColor) - XCTAssertEqual(onDark?.textColor, KeycapThemeTokens.m0116Hardware().textColor) + XCTAssertEqual(self.settings.theme, .white) + XCTAssertEqual(self.settings.modifierTheme, .white) } } diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleCommandShiftKKeycaps.m0116-command-shift-k-keycaps.png b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleCommandShiftKKeycaps.m0116-command-shift-k-keycaps.png index f32f0ee0e0d5f810b602649507ad4231629ab2af..6a3a0760eac9a215eb46aed5d9176a500784cb42 100644 GIT binary patch literal 47944 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 z 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 Kl>BA={{I7Ws%)nK literal 59655 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

EBhxq?GMtwd~~Q2Cpz=y-=F>2 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
WeWJTufuns=I=GbCha=Xwg~VXl zkxZ8_wzGf@0Y+=>GII#oSCvE(CK~uA9B%1MJI|VUmpmScnjd7Npn!#jY)(i#0b`;F z>%zB_{WL&=g_t2O_liaRYGUo#PU)D^-kdj!X@A3cv7hnELP~N<^%0uI3$

^r5lA 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 DcO2Ha diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleKeycap.m0116-keycap.png b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleKeycap.m0116-keycap.png index dffaef86535ae0f3c30511c31417066bf289f4d3..eea2639a1b5658a1e6f863d71dcc7db80f59892e 100644 GIT binary patch literal 15452 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 VekWt8M;*KO^-b=U+ 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 Date: Wed, 26 Aug 2026 00:39:32 +0200 Subject: [PATCH 03/31] docs(keyboard): clarify allowed theme picker sections --- .../Settings/Keyboard/KeyboardSettingsPaneViewModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift index 828dac09..1630dcf0 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift @@ -227,6 +227,7 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { ].joined(separator: "-") } + /// Theme picker sections filtered to the themes allowed by the selected keycap style. var allowedThemeSections: [[KeyboardVisualizerTheme]] { KeyboardVisualizerTheme.pickerSections(for: self.style.allowedThemes) } From 689b4aab04c55e502b6d47dc9a0893bede559631 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 00:46:41 +0200 Subject: [PATCH 04/31] refactor(keyboard): move keycap style helpers into extensions --- .../Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 6756d1e1..0f23f30c 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -13,7 +13,10 @@ enum KeycapStyle: Int, CaseIterable { case m0116 = 4 static let `default`: KeycapStyle = .apple +} +// MARK: - Titles +extension KeycapStyle { var title: String { switch self { case .apple: L10n.KeyboardVisualizer.Style.apple @@ -23,7 +26,10 @@ enum KeycapStyle: Int, CaseIterable { case .m0116: L10n.KeyboardVisualizer.Style.m0116 } } +} +// MARK: - Theme Policy +extension KeycapStyle { var allowedThemes: [KeyboardVisualizerTheme] { switch self { case .m0116: From 7154cfda17a0b24308bc6f78730546f83959a9cd Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 00:51:42 +0200 Subject: [PATCH 05/31] refactor(keyboard): split keycap appearance into style files --- .../Appearance/KeycapAppearance+Apple.swift | 46 +++ .../Appearance/KeycapAppearance+M0116.swift | 67 ++++ .../Appearance/KeycapAppearance+Minimal.swift | 31 ++ .../Appearance/KeycapAppearance+PBT.swift | 54 +++ .../Appearance/KeycapAppearance+Retro.swift | 85 ++++ .../Appearance/KeycapAppearance.swift | 146 +++++++ .../Keyboard/KeycapAppearance.swift | 370 ------------------ 7 files changed, 429 insertions(+), 370 deletions(-) create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift delete mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapAppearance.swift diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift new file mode 100644 index 00000000..f84c95b3 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift @@ -0,0 +1,46 @@ +// +// KeycapAppearance+Apple.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +extension KeycapAppearance { + struct Apple { + let textColor: NSColor + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + let badgeFillColor: NSColor + let badgeStrokeColor: NSColor + let badgeHighlightColor: NSColor + let badgeTextColor: NSColor + let strokeColor: NSColor + let undersideGradient: NSGradient? + let mainGradient: NSGradient? + + init(tokens: KeycapThemeTokens) { + self.textColor = tokens.textColor + self.groupBackgroundColor = tokens.groupBackgroundColor + self.groupStrokeColor = tokens.groupStrokeColor + self.badgeFillColor = tokens.badgeFillColor + self.badgeStrokeColor = tokens.badgeStrokeColor + self.badgeHighlightColor = tokens.badgeHighlightColor + self.badgeTextColor = tokens.badgeTextColor + self.strokeColor = tokens.surfaceBorderColor + let undersideEdgeColor = tokens.undersideEdgeColor ?? tokens.recessColor + let undersideCenterColor = tokens.undersideCenterColor ?? tokens.surfaceBaseColor + self.undersideGradient = NSGradient(colorsAndLocations: + (undersideEdgeColor, 0.0), + (undersideCenterColor, 0.5), + (undersideEdgeColor, 1.0) + ) + self.mainGradient = NSGradient(colorsAndLocations: + (tokens.surfaceBaseColor, 0.0), + (tokens.surfaceShadowColor, 1.0) + ) + } + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift new file mode 100644 index 00000000..ab9014dd --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift @@ -0,0 +1,67 @@ +// +// KeycapAppearance+M0116.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +extension KeycapAppearance { + /// Sculpted beige-era keycap: a lit body, an inset top face, and a front skirt, + /// all sitting in a near-black well like the plate of an Apple Standard Keyboard. + struct M0116 { + let textColor: NSColor + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + let badgeFillColor: NSColor + let badgeStrokeColor: NSColor + let badgeHighlightColor: NSColor + let badgeTextColor: NSColor + let wellColor: NSColor + let bodyGradient: NSGradient? + let skirtGradient: NSGradient? + let faceGradient: NSGradient? + let creaseColor: NSColor + let bodyStrokeColor: NSColor + + init(tokens: KeycapThemeTokens) { + let bodyEdge = tokens.surfaceShadowColor.darkened(by: 0.28) + let bodyHighlight = tokens.surfaceHighlightColor.blended( + withFraction: 0.35, + of: tokens.surfaceBaseColor + ) ?? tokens.surfaceHighlightColor + + self.textColor = tokens.textColor + self.groupBackgroundColor = tokens.groupBackgroundColor + self.groupStrokeColor = tokens.groupStrokeColor + self.badgeFillColor = tokens.badgeFillColor + self.badgeStrokeColor = tokens.badgeStrokeColor + self.badgeHighlightColor = tokens.badgeHighlightColor + self.badgeTextColor = tokens.badgeTextColor + self.wellColor = tokens.recessColor.darkened(by: 0.45) + // Lit from the left: bright edge, base across the middle, shaded right edge. + self.bodyGradient = NSGradient(colorsAndLocations: + (tokens.surfaceHighlightColor, 0.0), + (bodyHighlight, 0.14), + (tokens.surfaceBaseColor, 0.55), + (tokens.surfaceShadowColor, 0.92), + (bodyEdge, 1.0) + ) + // The front skirt catches more light at its lower edge than the face above it. + self.skirtGradient = NSGradient(colorsAndLocations: + (bodyEdge, 0.0), + (tokens.surfaceBaseColor, 0.45), + (tokens.surfaceHighlightColor, 1.0) + ) + self.faceGradient = NSGradient(colorsAndLocations: + (tokens.surfaceHighlightColor, 0.0), + (tokens.surfaceBaseColor, 0.58), + (tokens.surfaceShadowColor, 1.0) + ) + self.creaseColor = tokens.surfaceBorderColor.withAlphaComponent(0.42) + self.bodyStrokeColor = tokens.surfaceBorderColor + } + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift new file mode 100644 index 00000000..d4516822 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift @@ -0,0 +1,31 @@ +// +// KeycapAppearance+Minimal.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +extension KeycapAppearance { + struct Minimal { + let textColor: NSColor + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + let badgeFillColor: NSColor + let badgeStrokeColor: NSColor + let badgeHighlightColor: NSColor + let badgeTextColor: NSColor + + init(tokens: KeycapThemeTokens) { + self.textColor = tokens.textColor + self.groupBackgroundColor = tokens.groupBackgroundColor + self.groupStrokeColor = tokens.groupStrokeColor + self.badgeFillColor = tokens.badgeFillColor + self.badgeStrokeColor = tokens.badgeStrokeColor + self.badgeHighlightColor = tokens.badgeHighlightColor + self.badgeTextColor = tokens.badgeTextColor + } + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift new file mode 100644 index 00000000..86ead691 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift @@ -0,0 +1,54 @@ +// +// KeycapAppearance+PBT.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +extension KeycapAppearance { + struct PBT { + let textColor: NSColor + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + let badgeFillColor: NSColor + let badgeStrokeColor: NSColor + let badgeHighlightColor: NSColor + let badgeTextColor: NSColor + let bodyGradient: NSGradient? + let bodyStrokeColor: NSColor + let underDishColor: NSColor + let dishGradient: NSGradient? + + init(tokens: KeycapThemeTokens) { + let bodyTop = tokens.surfaceBaseColor.lightened(by: 0.08) + let bodyMid = tokens.surfaceShadowColor.lightened(by: 0.03) + let bodyBottom = tokens.surfaceShadowColor.darkened(by: 0.08) + let dishTop = tokens.surfaceBaseColor.lightened(by: 0.05) + let dishMid = tokens.surfaceBaseColor.lightened(by: 0.06) + let dishBottom = tokens.surfaceShadowColor.lightened(by: 0.05) + + self.textColor = tokens.textColor + self.groupBackgroundColor = tokens.groupBackgroundColor + self.groupStrokeColor = tokens.groupStrokeColor + self.badgeFillColor = tokens.badgeFillColor + self.badgeStrokeColor = tokens.badgeStrokeColor + self.badgeHighlightColor = tokens.badgeHighlightColor + self.badgeTextColor = tokens.badgeTextColor + self.bodyGradient = NSGradient(colorsAndLocations: + (bodyTop, 0.0), + (bodyMid, 0.48), + (bodyBottom, 1.0) + ) + self.bodyStrokeColor = tokens.surfaceBorderColor + self.underDishColor = tokens.surfaceBaseColor.lightened(by: 0.12) + self.dishGradient = NSGradient(colorsAndLocations: + (dishTop, 0.0), + (dishMid, 0.32), + (dishBottom, 1.0) + ) + } + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift new file mode 100644 index 00000000..d8306ec6 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift @@ -0,0 +1,85 @@ +// +// KeycapAppearance+Retro.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +extension KeycapAppearance { + struct Retro { + let textColor: NSColor + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + let badgeFillColor: NSColor + let badgeStrokeColor: NSColor + let badgeHighlightColor: NSColor + let badgeTextColor: NSColor + let lipGradient: NSGradient? + let bodyShadowColor: NSColor + let bodyGradient: NSGradient? + let bodyStrokeColor: NSColor + let faceShadowGradient: NSGradient? + let darkFaceGradient: NSGradient? + let darkFaceStrokeColor: NSColor + let lightFaceGradient: NSGradient? + let lightFaceStrokeColor: NSColor + let glossColor: NSColor + + init(tokens: KeycapThemeTokens) { + let bodyEdge = tokens.surfaceShadowColor.darkened(by: 0.08) + let bodyCenter = tokens.surfaceBaseColor.darkened(by: 0.10) + let darkFaceTop = tokens.surfaceBaseColor.lightened(by: 0.03) + let darkFaceBottom = tokens.surfaceBaseColor.darkened(by: 0.01) + let lightFaceTop = tokens.surfaceHighlightColor.lightened(by: 0.08) + let lightFaceBottom = tokens.surfaceBaseColor + let groupBackgroundColor = tokens.recessColor.darkened(by: 0.40) + let groupStrokeColor = tokens.surfaceBaseColor.withAlphaComponent(0.70) + let lipEdge = tokens.recessColor.darkened(by: 0.34).withAlphaComponent(0.96) + let lipCenter = tokens.recessColor.darkened(by: 0.12).withAlphaComponent(0.86) + let bodyStrokeColor = tokens.surfaceBorderColor.withAlphaComponent(0.20) + let darkFaceStrokeColor = tokens.surfaceBorderColor.withAlphaComponent(0.20) + let faceShadowEdge = tokens.recessColor.darkened(by: 0.34).withAlphaComponent(0.46) + let faceShadowCenter = tokens.recessColor.darkened(by: 0.12).withAlphaComponent(0.26) + + self.textColor = tokens.textColor + self.groupBackgroundColor = groupBackgroundColor + self.groupStrokeColor = groupStrokeColor + self.badgeFillColor = tokens.badgeFillColor + self.badgeStrokeColor = tokens.badgeStrokeColor + self.badgeHighlightColor = tokens.badgeHighlightColor + self.badgeTextColor = tokens.badgeTextColor + self.lipGradient = NSGradient(colorsAndLocations: + (lipEdge, 0.0), + (lipCenter, 0.5), + (lipEdge, 1.0) + ) + self.bodyShadowColor = NSColor.black.withAlphaComponent(0.6) + self.bodyGradient = NSGradient(colorsAndLocations: + (bodyEdge, 0.0), + (bodyCenter, 0.32), + (bodyCenter, 0.68), + (bodyEdge, 1.0) + ) + self.bodyStrokeColor = bodyStrokeColor + self.faceShadowGradient = NSGradient(colorsAndLocations: + (faceShadowEdge, 0.0), + (faceShadowCenter, 0.5), + (faceShadowEdge, 1.0) + ) + self.darkFaceGradient = NSGradient(colorsAndLocations: + (darkFaceTop, 0.0), + (darkFaceBottom, 1.0) + ) + self.darkFaceStrokeColor = darkFaceStrokeColor + self.lightFaceGradient = NSGradient(colorsAndLocations: + (lightFaceTop, 0.0), + (lightFaceBottom, 1.0) + ) + self.lightFaceStrokeColor = tokens.surfaceBorderColor.withAlphaComponent(0.55) + self.glossColor = NSColor(white: 1, alpha: 0.08) + } + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift new file mode 100644 index 00000000..20d387e6 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift @@ -0,0 +1,146 @@ +// +// KeycapAppearance.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +struct KeycapThemeTokens { + let swatchColor: NSColor + let textColor: NSColor + + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + + let badgeFillColor: NSColor + let badgeStrokeColor: NSColor + let badgeHighlightColor: NSColor + let badgeTextColor: NSColor + + let surfaceHighlightColor: NSColor + let surfaceBaseColor: NSColor + let surfaceShadowColor: NSColor + let surfaceBorderColor: NSColor + + let recessColor: NSColor + /// Optional Apple-style underside edge color override. Falls back to `recessColor`. + let undersideEdgeColor: NSColor? + /// Optional Apple-style underside center color override. Falls back to `surfaceBaseColor`. + let undersideCenterColor: NSColor? +} + +enum KeycapAppearance { + case apple(Apple) + case pbt(PBT) + case minimal(Minimal) + case retro(Retro) + case m0116(M0116) + + static let dotColor = NSColor(calibratedRed: 0.0, green: 1.0, blue: 0.1, alpha: 1) + static let inactiveDotColor = NSColor(calibratedWhite: 0.42, alpha: 0.7) +} + +// MARK: - Shared Accessors +extension KeycapAppearance { + var textColor: NSColor { + switch self { + case .apple(let appearance): appearance.textColor + case .pbt(let appearance): appearance.textColor + case .minimal(let appearance): appearance.textColor + case .retro(let appearance): appearance.textColor + case .m0116(let appearance): appearance.textColor + } + } + + var groupBackgroundColor: NSColor { + switch self { + case .apple(let appearance): appearance.groupBackgroundColor + case .pbt(let appearance): appearance.groupBackgroundColor + case .minimal(let appearance): appearance.groupBackgroundColor + case .retro(let appearance): appearance.groupBackgroundColor + case .m0116(let appearance): appearance.groupBackgroundColor + } + } + + var groupStrokeColor: NSColor { + switch self { + case .apple(let appearance): appearance.groupStrokeColor + case .pbt(let appearance): appearance.groupStrokeColor + case .minimal(let appearance): appearance.groupStrokeColor + case .retro(let appearance): appearance.groupStrokeColor + case .m0116(let appearance): appearance.groupStrokeColor + } + } + + var badgeFillColor: NSColor { + switch self { + case .apple(let appearance): appearance.badgeFillColor + case .pbt(let appearance): appearance.badgeFillColor + case .minimal(let appearance): appearance.badgeFillColor + case .retro(let appearance): appearance.badgeFillColor + case .m0116(let appearance): appearance.badgeFillColor + } + } + + var badgeStrokeColor: NSColor { + switch self { + case .apple(let appearance): appearance.badgeStrokeColor + case .pbt(let appearance): appearance.badgeStrokeColor + case .minimal(let appearance): appearance.badgeStrokeColor + case .retro(let appearance): appearance.badgeStrokeColor + case .m0116(let appearance): appearance.badgeStrokeColor + } + } + + var badgeHighlightColor: NSColor { + switch self { + case .apple(let appearance): appearance.badgeHighlightColor + case .pbt(let appearance): appearance.badgeHighlightColor + case .minimal(let appearance): appearance.badgeHighlightColor + case .retro(let appearance): appearance.badgeHighlightColor + case .m0116(let appearance): appearance.badgeHighlightColor + } + } + + var badgeTextColor: NSColor { + switch self { + case .apple(let appearance): appearance.badgeTextColor + case .pbt(let appearance): appearance.badgeTextColor + case .minimal(let appearance): appearance.badgeTextColor + case .retro(let appearance): appearance.badgeTextColor + case .m0116(let appearance): appearance.badgeTextColor + } + } + +} + +// MARK: - Case Accessors +extension KeycapAppearance { + var apple: Apple? { + guard case .apple(let appearance) = self else { return nil } + return appearance + } + + var pbt: PBT? { + guard case .pbt(let appearance) = self else { return nil } + return appearance + } + + var minimal: Minimal? { + guard case .minimal(let appearance) = self else { return nil } + return appearance + } + + var retro: Retro? { + guard case .retro(let appearance) = self else { return nil } + return appearance + } + + var m0116: M0116? { + guard case .m0116(let appearance) = self else { return nil } + return appearance + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapAppearance.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapAppearance.swift deleted file mode 100644 index 935d7926..00000000 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapAppearance.swift +++ /dev/null @@ -1,370 +0,0 @@ -// -// KeycapAppearance.swift -// Keyty -// -// SPDX-FileCopyrightText: 2026 Serhii Bykov -// SPDX-License-Identifier: BSD-3-Clause -// - -import AppKit - -struct KeycapThemeTokens { - let swatchColor: NSColor - let textColor: NSColor - - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor - - let surfaceHighlightColor: NSColor - let surfaceBaseColor: NSColor - let surfaceShadowColor: NSColor - let surfaceBorderColor: NSColor - - let recessColor: NSColor - /// Optional Apple-style underside edge color override. Falls back to `recessColor`. - let undersideEdgeColor: NSColor? - /// Optional Apple-style underside center color override. Falls back to `surfaceBaseColor`. - let undersideCenterColor: NSColor? -} - -enum KeycapAppearance { - case apple(Apple) - case pbt(PBT) - case minimal(Minimal) - case retro(Retro) - case m0116(M0116) - - static let dotColor = NSColor(calibratedRed: 0.0, green: 1.0, blue: 0.1, alpha: 1) - static let inactiveDotColor = NSColor(calibratedWhite: 0.42, alpha: 0.7) - - var textColor: NSColor { - switch self { - case .apple(let appearance): appearance.textColor - case .pbt(let appearance): appearance.textColor - case .minimal(let appearance): appearance.textColor - case .retro(let appearance): appearance.textColor - case .m0116(let appearance): appearance.textColor - } - } - - var groupBackgroundColor: NSColor { - switch self { - case .apple(let appearance): appearance.groupBackgroundColor - case .pbt(let appearance): appearance.groupBackgroundColor - case .minimal(let appearance): appearance.groupBackgroundColor - case .retro(let appearance): appearance.groupBackgroundColor - case .m0116(let appearance): appearance.groupBackgroundColor - } - } - - var groupStrokeColor: NSColor { - switch self { - case .apple(let appearance): appearance.groupStrokeColor - case .pbt(let appearance): appearance.groupStrokeColor - case .minimal(let appearance): appearance.groupStrokeColor - case .retro(let appearance): appearance.groupStrokeColor - case .m0116(let appearance): appearance.groupStrokeColor - } - } - - var badgeFillColor: NSColor { - switch self { - case .apple(let appearance): appearance.badgeFillColor - case .pbt(let appearance): appearance.badgeFillColor - case .minimal(let appearance): appearance.badgeFillColor - case .retro(let appearance): appearance.badgeFillColor - case .m0116(let appearance): appearance.badgeFillColor - } - } - - var badgeStrokeColor: NSColor { - switch self { - case .apple(let appearance): appearance.badgeStrokeColor - case .pbt(let appearance): appearance.badgeStrokeColor - case .minimal(let appearance): appearance.badgeStrokeColor - case .retro(let appearance): appearance.badgeStrokeColor - case .m0116(let appearance): appearance.badgeStrokeColor - } - } - - var badgeHighlightColor: NSColor { - switch self { - case .apple(let appearance): appearance.badgeHighlightColor - case .pbt(let appearance): appearance.badgeHighlightColor - case .minimal(let appearance): appearance.badgeHighlightColor - case .retro(let appearance): appearance.badgeHighlightColor - case .m0116(let appearance): appearance.badgeHighlightColor - } - } - - var badgeTextColor: NSColor { - switch self { - case .apple(let appearance): appearance.badgeTextColor - case .pbt(let appearance): appearance.badgeTextColor - case .minimal(let appearance): appearance.badgeTextColor - case .retro(let appearance): appearance.badgeTextColor - case .m0116(let appearance): appearance.badgeTextColor - } - } - - var apple: Apple? { - guard case .apple(let appearance) = self else { return nil } - return appearance - } - - var pbt: PBT? { - guard case .pbt(let appearance) = self else { return nil } - return appearance - } - - var minimal: Minimal? { - guard case .minimal(let appearance) = self else { return nil } - return appearance - } - - var retro: Retro? { - guard case .retro(let appearance) = self else { return nil } - return appearance - } - - var m0116: M0116? { - guard case .m0116(let appearance) = self else { return nil } - return appearance - } -} - -// MARK: - Appearances helpers -extension KeycapAppearance { - struct Apple { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor - let strokeColor: NSColor - let undersideGradient: NSGradient? - let mainGradient: NSGradient? - - init(tokens: KeycapThemeTokens) { - self.textColor = tokens.textColor - self.groupBackgroundColor = tokens.groupBackgroundColor - self.groupStrokeColor = tokens.groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor - self.strokeColor = tokens.surfaceBorderColor - let undersideEdgeColor = tokens.undersideEdgeColor ?? tokens.recessColor - let undersideCenterColor = tokens.undersideCenterColor ?? tokens.surfaceBaseColor - self.undersideGradient = NSGradient(colorsAndLocations: - (undersideEdgeColor, 0.0), - (undersideCenterColor, 0.5), - (undersideEdgeColor, 1.0) - ) - self.mainGradient = NSGradient(colorsAndLocations: - (tokens.surfaceBaseColor, 0.0), - (tokens.surfaceShadowColor, 1.0) - ) - } - } - - struct PBT { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor - let bodyGradient: NSGradient? - let bodyStrokeColor: NSColor - let underDishColor: NSColor - let dishGradient: NSGradient? - - init(tokens: KeycapThemeTokens) { - let bodyTop = tokens.surfaceBaseColor.lightened(by: 0.08) - let bodyMid = tokens.surfaceShadowColor.lightened(by: 0.03) - let bodyBottom = tokens.surfaceShadowColor.darkened(by: 0.08) - let dishTop = tokens.surfaceBaseColor.lightened(by: 0.05) - let dishMid = tokens.surfaceBaseColor.lightened(by: 0.06) - let dishBottom = tokens.surfaceShadowColor.lightened(by: 0.05) - - self.textColor = tokens.textColor - self.groupBackgroundColor = tokens.groupBackgroundColor - self.groupStrokeColor = tokens.groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor - self.bodyGradient = NSGradient(colorsAndLocations: - (bodyTop, 0.0), - (bodyMid, 0.48), - (bodyBottom, 1.0) - ) - self.bodyStrokeColor = tokens.surfaceBorderColor - self.underDishColor = tokens.surfaceBaseColor.lightened(by: 0.12) - self.dishGradient = NSGradient(colorsAndLocations: - (dishTop, 0.0), - (dishMid, 0.32), - (dishBottom, 1.0) - ) - } - } - - struct Minimal { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor - - init(tokens: KeycapThemeTokens) { - self.textColor = tokens.textColor - self.groupBackgroundColor = tokens.groupBackgroundColor - self.groupStrokeColor = tokens.groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor - } - } - - struct Retro { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor - let lipGradient: NSGradient? - let bodyShadowColor: NSColor - let bodyGradient: NSGradient? - let bodyStrokeColor: NSColor - let faceShadowGradient: NSGradient? - let darkFaceGradient: NSGradient? - let darkFaceStrokeColor: NSColor - let lightFaceGradient: NSGradient? - let lightFaceStrokeColor: NSColor - let glossColor: NSColor - - init(tokens: KeycapThemeTokens) { - let bodyEdge = tokens.surfaceShadowColor.darkened(by: 0.08) - let bodyCenter = tokens.surfaceBaseColor.darkened(by: 0.10) - let darkFaceTop = tokens.surfaceBaseColor.lightened(by: 0.03) - let darkFaceBottom = tokens.surfaceBaseColor.darkened(by: 0.01) - let lightFaceTop = tokens.surfaceHighlightColor.lightened(by: 0.08) - let lightFaceBottom = tokens.surfaceBaseColor - let groupBackgroundColor = tokens.recessColor.darkened(by: 0.40) - let groupStrokeColor = tokens.surfaceBaseColor.withAlphaComponent(0.70) - let lipEdge = tokens.recessColor.darkened(by: 0.34).withAlphaComponent(0.96) - let lipCenter = tokens.recessColor.darkened(by: 0.12).withAlphaComponent(0.86) - let bodyStrokeColor = tokens.surfaceBorderColor.withAlphaComponent(0.20) - let darkFaceStrokeColor = tokens.surfaceBorderColor.withAlphaComponent(0.20) - let faceShadowEdge = tokens.recessColor.darkened(by: 0.34).withAlphaComponent(0.46) - let faceShadowCenter = tokens.recessColor.darkened(by: 0.12).withAlphaComponent(0.26) - - self.textColor = tokens.textColor - self.groupBackgroundColor = groupBackgroundColor - self.groupStrokeColor = groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor - self.lipGradient = NSGradient(colorsAndLocations: - (lipEdge, 0.0), - (lipCenter, 0.5), - (lipEdge, 1.0) - ) - self.bodyShadowColor = NSColor.black.withAlphaComponent(0.6) - self.bodyGradient = NSGradient(colorsAndLocations: - (bodyEdge, 0.0), - (bodyCenter, 0.32), - (bodyCenter, 0.68), - (bodyEdge, 1.0) - ) - self.bodyStrokeColor = bodyStrokeColor - self.faceShadowGradient = NSGradient(colorsAndLocations: - (faceShadowEdge, 0.0), - (faceShadowCenter, 0.5), - (faceShadowEdge, 1.0) - ) - self.darkFaceGradient = NSGradient(colorsAndLocations: - (darkFaceTop, 0.0), - (darkFaceBottom, 1.0) - ) - self.darkFaceStrokeColor = darkFaceStrokeColor - self.lightFaceGradient = NSGradient(colorsAndLocations: - (lightFaceTop, 0.0), - (lightFaceBottom, 1.0) - ) - self.lightFaceStrokeColor = tokens.surfaceBorderColor.withAlphaComponent(0.55) - self.glossColor = NSColor(white: 1, alpha: 0.08) - } - } - - /// Sculpted beige-era keycap: a lit body, an inset top face, and a front skirt, - /// all sitting in a near-black well like the plate of an Apple Standard Keyboard. - struct M0116 { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor - let wellColor: NSColor - let bodyGradient: NSGradient? - let skirtGradient: NSGradient? - let faceGradient: NSGradient? - let creaseColor: NSColor - let bodyStrokeColor: NSColor - - init(tokens: KeycapThemeTokens) { - let bodyEdge = tokens.surfaceShadowColor.darkened(by: 0.28) - let bodyHighlight = tokens.surfaceHighlightColor.blended( - withFraction: 0.35, - of: tokens.surfaceBaseColor - ) ?? tokens.surfaceHighlightColor - - self.textColor = tokens.textColor - self.groupBackgroundColor = tokens.groupBackgroundColor - self.groupStrokeColor = tokens.groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor - self.wellColor = tokens.recessColor.darkened(by: 0.45) - // Lit from the left: bright edge, base across the middle, shaded right edge. - self.bodyGradient = NSGradient(colorsAndLocations: - (tokens.surfaceHighlightColor, 0.0), - (bodyHighlight, 0.14), - (tokens.surfaceBaseColor, 0.55), - (tokens.surfaceShadowColor, 0.92), - (bodyEdge, 1.0) - ) - // The front skirt catches more light at its lower edge than the face above it. - self.skirtGradient = NSGradient(colorsAndLocations: - (bodyEdge, 0.0), - (tokens.surfaceBaseColor, 0.45), - (tokens.surfaceHighlightColor, 1.0) - ) - self.faceGradient = NSGradient(colorsAndLocations: - (tokens.surfaceHighlightColor, 0.0), - (tokens.surfaceBaseColor, 0.58), - (tokens.surfaceShadowColor, 1.0) - ) - self.creaseColor = tokens.surfaceBorderColor.withAlphaComponent(0.42) - self.bodyStrokeColor = tokens.surfaceBorderColor - } - } -} From f061df27af6e9b7834fdb9a7f9597a5e92412542 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 01:34:48 +0200 Subject: [PATCH 06/31] refactor(keyboard): extract shared keycap appearance tokens --- .../Appearance/KeycapAppearance+Apple.swift | 16 +-- .../Appearance/KeycapAppearance+M0116.swift | 16 +-- .../Appearance/KeycapAppearance+Minimal.swift | 16 +-- .../Appearance/KeycapAppearance+PBT.swift | 16 +-- .../Appearance/KeycapAppearance+Retro.swift | 24 ++-- .../Appearance/KeycapAppearance.swift | 112 ++++++++++++------ .../Renderers/Apple/AppleKeycapRenderer.swift | 2 +- .../Renderers/M0116/M0116KeycapRenderer.swift | 2 +- .../Minimal/MinimalKeycapRenderer.swift | 8 +- .../Renderers/PBT/PBTKeycapRenderer.swift | 2 +- .../Renderers/Retro/RetroKeycapRenderer.swift | 2 +- .../KeyboardVisualizerSettingsTests.swift | 2 +- 12 files changed, 104 insertions(+), 114 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift index f84c95b3..1726a8e9 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift @@ -10,25 +10,13 @@ import AppKit extension KeycapAppearance { struct Apple { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor + let shared: Shared let strokeColor: NSColor let undersideGradient: NSGradient? let mainGradient: NSGradient? init(tokens: KeycapThemeTokens) { - self.textColor = tokens.textColor - self.groupBackgroundColor = tokens.groupBackgroundColor - self.groupStrokeColor = tokens.groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor + self.shared = Shared(tokens: tokens) self.strokeColor = tokens.surfaceBorderColor let undersideEdgeColor = tokens.undersideEdgeColor ?? tokens.recessColor let undersideCenterColor = tokens.undersideCenterColor ?? tokens.surfaceBaseColor diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift index ab9014dd..62c6ef33 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift @@ -12,13 +12,7 @@ extension KeycapAppearance { /// Sculpted beige-era keycap: a lit body, an inset top face, and a front skirt, /// all sitting in a near-black well like the plate of an Apple Standard Keyboard. struct M0116 { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor + let shared: Shared let wellColor: NSColor let bodyGradient: NSGradient? let skirtGradient: NSGradient? @@ -33,13 +27,7 @@ extension KeycapAppearance { of: tokens.surfaceBaseColor ) ?? tokens.surfaceHighlightColor - self.textColor = tokens.textColor - self.groupBackgroundColor = tokens.groupBackgroundColor - self.groupStrokeColor = tokens.groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor + self.shared = Shared(tokens: tokens) self.wellColor = tokens.recessColor.darkened(by: 0.45) // Lit from the left: bright edge, base across the middle, shaded right edge. self.bodyGradient = NSGradient(colorsAndLocations: diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift index d4516822..58b063b9 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift @@ -10,22 +10,10 @@ import AppKit extension KeycapAppearance { struct Minimal { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor + let shared: Shared init(tokens: KeycapThemeTokens) { - self.textColor = tokens.textColor - self.groupBackgroundColor = tokens.groupBackgroundColor - self.groupStrokeColor = tokens.groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor + self.shared = Shared(tokens: tokens) } } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift index 86ead691..7ec52f3a 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift @@ -10,13 +10,7 @@ import AppKit extension KeycapAppearance { struct PBT { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor + let shared: Shared let bodyGradient: NSGradient? let bodyStrokeColor: NSColor let underDishColor: NSColor @@ -30,13 +24,7 @@ extension KeycapAppearance { let dishMid = tokens.surfaceBaseColor.lightened(by: 0.06) let dishBottom = tokens.surfaceShadowColor.lightened(by: 0.05) - self.textColor = tokens.textColor - self.groupBackgroundColor = tokens.groupBackgroundColor - self.groupStrokeColor = tokens.groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor + self.shared = Shared(tokens: tokens) self.bodyGradient = NSGradient(colorsAndLocations: (bodyTop, 0.0), (bodyMid, 0.48), diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift index d8306ec6..c5ceaf08 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift @@ -10,13 +10,7 @@ import AppKit extension KeycapAppearance { struct Retro { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor + let shared: Shared let lipGradient: NSGradient? let bodyShadowColor: NSColor let bodyGradient: NSGradient? @@ -44,13 +38,15 @@ extension KeycapAppearance { let faceShadowEdge = tokens.recessColor.darkened(by: 0.34).withAlphaComponent(0.46) let faceShadowCenter = tokens.recessColor.darkened(by: 0.12).withAlphaComponent(0.26) - self.textColor = tokens.textColor - self.groupBackgroundColor = groupBackgroundColor - self.groupStrokeColor = groupStrokeColor - self.badgeFillColor = tokens.badgeFillColor - self.badgeStrokeColor = tokens.badgeStrokeColor - self.badgeHighlightColor = tokens.badgeHighlightColor - self.badgeTextColor = tokens.badgeTextColor + self.shared = Shared( + textColor: tokens.textColor, + groupBackgroundColor: groupBackgroundColor, + groupStrokeColor: groupStrokeColor, + badgeFillColor: tokens.badgeFillColor, + badgeStrokeColor: tokens.badgeStrokeColor, + badgeHighlightColor: tokens.badgeHighlightColor, + badgeTextColor: tokens.badgeTextColor + ) self.lipGradient = NSGradient(colorsAndLocations: (lipEdge, 0.0), (lipCenter, 0.5), diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift index 20d387e6..dc7c0511 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift @@ -43,75 +43,117 @@ enum KeycapAppearance { static let inactiveDotColor = NSColor(calibratedWhite: 0.42, alpha: 0.7) } +extension KeycapAppearance { + struct Shared { + let textColor: NSColor + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + let badgeFillColor: NSColor + let badgeStrokeColor: NSColor + let badgeHighlightColor: NSColor + let badgeTextColor: NSColor + + init( + textColor: NSColor, + groupBackgroundColor: NSColor, + groupStrokeColor: NSColor, + badgeFillColor: NSColor, + badgeStrokeColor: NSColor, + badgeHighlightColor: NSColor, + badgeTextColor: NSColor + ) { + self.textColor = textColor + self.groupBackgroundColor = groupBackgroundColor + self.groupStrokeColor = groupStrokeColor + self.badgeFillColor = badgeFillColor + self.badgeStrokeColor = badgeStrokeColor + self.badgeHighlightColor = badgeHighlightColor + self.badgeTextColor = badgeTextColor + } + + init(tokens: KeycapThemeTokens) { + self.init( + textColor: tokens.textColor, + groupBackgroundColor: tokens.groupBackgroundColor, + groupStrokeColor: tokens.groupStrokeColor, + badgeFillColor: tokens.badgeFillColor, + badgeStrokeColor: tokens.badgeStrokeColor, + badgeHighlightColor: tokens.badgeHighlightColor, + badgeTextColor: tokens.badgeTextColor + ) + } + } +} + // MARK: - Shared Accessors extension KeycapAppearance { var textColor: NSColor { switch self { - case .apple(let appearance): appearance.textColor - case .pbt(let appearance): appearance.textColor - case .minimal(let appearance): appearance.textColor - case .retro(let appearance): appearance.textColor - case .m0116(let appearance): appearance.textColor + case .apple(let appearance): appearance.shared.textColor + case .pbt(let appearance): appearance.shared.textColor + case .minimal(let appearance): appearance.shared.textColor + case .retro(let appearance): appearance.shared.textColor + case .m0116(let appearance): appearance.shared.textColor } } var groupBackgroundColor: NSColor { switch self { - case .apple(let appearance): appearance.groupBackgroundColor - case .pbt(let appearance): appearance.groupBackgroundColor - case .minimal(let appearance): appearance.groupBackgroundColor - case .retro(let appearance): appearance.groupBackgroundColor - case .m0116(let appearance): appearance.groupBackgroundColor + case .apple(let appearance): appearance.shared.groupBackgroundColor + case .pbt(let appearance): appearance.shared.groupBackgroundColor + case .minimal(let appearance): appearance.shared.groupBackgroundColor + case .retro(let appearance): appearance.shared.groupBackgroundColor + case .m0116(let appearance): appearance.shared.groupBackgroundColor } } var groupStrokeColor: NSColor { switch self { - case .apple(let appearance): appearance.groupStrokeColor - case .pbt(let appearance): appearance.groupStrokeColor - case .minimal(let appearance): appearance.groupStrokeColor - case .retro(let appearance): appearance.groupStrokeColor - case .m0116(let appearance): appearance.groupStrokeColor + case .apple(let appearance): appearance.shared.groupStrokeColor + case .pbt(let appearance): appearance.shared.groupStrokeColor + case .minimal(let appearance): appearance.shared.groupStrokeColor + case .retro(let appearance): appearance.shared.groupStrokeColor + case .m0116(let appearance): appearance.shared.groupStrokeColor } } var badgeFillColor: NSColor { switch self { - case .apple(let appearance): appearance.badgeFillColor - case .pbt(let appearance): appearance.badgeFillColor - case .minimal(let appearance): appearance.badgeFillColor - case .retro(let appearance): appearance.badgeFillColor - case .m0116(let appearance): appearance.badgeFillColor + case .apple(let appearance): appearance.shared.badgeFillColor + case .pbt(let appearance): appearance.shared.badgeFillColor + case .minimal(let appearance): appearance.shared.badgeFillColor + case .retro(let appearance): appearance.shared.badgeFillColor + case .m0116(let appearance): appearance.shared.badgeFillColor } } var badgeStrokeColor: NSColor { switch self { - case .apple(let appearance): appearance.badgeStrokeColor - case .pbt(let appearance): appearance.badgeStrokeColor - case .minimal(let appearance): appearance.badgeStrokeColor - case .retro(let appearance): appearance.badgeStrokeColor - case .m0116(let appearance): appearance.badgeStrokeColor + case .apple(let appearance): appearance.shared.badgeStrokeColor + case .pbt(let appearance): appearance.shared.badgeStrokeColor + case .minimal(let appearance): appearance.shared.badgeStrokeColor + case .retro(let appearance): appearance.shared.badgeStrokeColor + case .m0116(let appearance): appearance.shared.badgeStrokeColor } } var badgeHighlightColor: NSColor { switch self { - case .apple(let appearance): appearance.badgeHighlightColor - case .pbt(let appearance): appearance.badgeHighlightColor - case .minimal(let appearance): appearance.badgeHighlightColor - case .retro(let appearance): appearance.badgeHighlightColor - case .m0116(let appearance): appearance.badgeHighlightColor + case .apple(let appearance): appearance.shared.badgeHighlightColor + case .pbt(let appearance): appearance.shared.badgeHighlightColor + case .minimal(let appearance): appearance.shared.badgeHighlightColor + case .retro(let appearance): appearance.shared.badgeHighlightColor + case .m0116(let appearance): appearance.shared.badgeHighlightColor } } var badgeTextColor: NSColor { switch self { - case .apple(let appearance): appearance.badgeTextColor - case .pbt(let appearance): appearance.badgeTextColor - case .minimal(let appearance): appearance.badgeTextColor - case .retro(let appearance): appearance.badgeTextColor - case .m0116(let appearance): appearance.badgeTextColor + case .apple(let appearance): appearance.shared.badgeTextColor + case .pbt(let appearance): appearance.shared.badgeTextColor + case .minimal(let appearance): appearance.shared.badgeTextColor + case .retro(let appearance): appearance.shared.badgeTextColor + case .m0116(let appearance): appearance.shared.badgeTextColor } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapRenderer.swift index 265e48e8..d1f1c46d 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapRenderer.swift @@ -47,7 +47,7 @@ struct AppleKeycapRenderer: KeycapRendering { self.legendRenderer.draw( item: item, in: keycapRect, - textColor: appearance.textColor, + textColor: appearance.shared.textColor, centeredLabelWidth: self.centeredLabelWidth(for: item) ) } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift index 45a7b789..2eb4a0ec 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift @@ -72,7 +72,7 @@ struct M0116KeycapRenderer: KeycapRendering { bodyPath.lineWidth = StrokeWidth.standard bodyPath.stroke() - self.drawLegend(item: item, in: faceRect, textColor: appearance.textColor) + self.drawLegend(item: item, in: faceRect, textColor: appearance.shared.textColor) } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapRenderer.swift index 296f80d0..8e6f4566 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapRenderer.swift @@ -28,7 +28,7 @@ struct MinimalKeycapRenderer: KeycapRendering { if let image = item.image { let size = scaledImageSize(for: image) - KeycapImageRenderer.draw(image: image, badgeText: item.imageBadgeText, color: appearance.textColor, in: NSRect( + KeycapImageRenderer.draw(image: image, badgeText: item.imageBadgeText, color: appearance.shared.textColor, in: NSRect( x: rect.midX - size.width / 2, y: rect.midY - size.height / 2, width: size.width, @@ -37,7 +37,7 @@ struct MinimalKeycapRenderer: KeycapRendering { } else if let sfSymbolName = item.sfSymbolName, let image = SymbolImage.image( named: sfSymbolName, - color: appearance.textColor, + color: appearance.shared.textColor, pointSize: MinimalKeycapMetrics.symbolFont.pointSize ) { let size = image.size @@ -50,7 +50,7 @@ struct MinimalKeycapRenderer: KeycapRendering { } else if !item.symbol.isEmpty { let attributes: [NSAttributedString.Key: Any] = [ .font: MinimalKeycapMetrics.symbolFont, - .foregroundColor: appearance.textColor, + .foregroundColor: appearance.shared.textColor, ] let size = item.symbol.size(withAttributes: attributes) item.symbol.draw( @@ -65,7 +65,7 @@ struct MinimalKeycapRenderer: KeycapRendering { } else if let label = item.label { let attributes: [NSAttributedString.Key: Any] = [ .font: MinimalKeycapMetrics.labelFont, - .foregroundColor: appearance.textColor, + .foregroundColor: appearance.shared.textColor, ] let size = label.size(withAttributes: attributes) label.draw( diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapRenderer.swift index c88ddb4d..99b1fe62 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapRenderer.swift @@ -81,6 +81,6 @@ struct PBTKeycapRenderer: KeycapRendering { dishPath.fill() } - legendRenderer.draw(item: item, in: dishRect, textColor: appearance.textColor) + legendRenderer.draw(item: item, in: dishRect, textColor: appearance.shared.textColor) } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapRenderer.swift index 72ba8886..5001d1e4 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapRenderer.swift @@ -91,7 +91,7 @@ struct RetroKeycapRenderer: KeycapRendering { facePath.lineWidth = 1.25 facePath.stroke() - legendRenderer.draw(item: item, in: faceRect, textColor: appearance.textColor) + legendRenderer.draw(item: item, in: faceRect, textColor: appearance.shared.textColor) } private func retroKeycapWidth(for item: KeycapItem) -> CGFloat { diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift index 71252804..f41439b1 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift @@ -356,7 +356,7 @@ final class KeyboardVisualizerSettingsTests: XCTestCase { XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.mediaTheme), KeyboardVisualizerTheme.white.rawValue) XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.mouseTheme), KeyboardVisualizerTheme.white.rawValue) XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.groupBackgroundTheme), KeyboardVisualizerTheme.white.rawValue) - XCTAssertEqual(self.settings.appearance.m0116?.textColor, KeyboardVisualizerTheme.white.tokens.textColor) + XCTAssertEqual(self.settings.appearance.m0116?.shared.textColor, KeyboardVisualizerTheme.white.tokens.textColor) } func testM0116StyleRejectsNonWhiteThemeAssignments() { From b74b860d31644083c0690c682bec758806ebbc0e Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:02:02 +0200 Subject: [PATCH 07/31] refactor(keyboard): streamline shared keycap appearance access --- .../Appearance/KeycapAppearance+Apple.swift | 2 +- .../Appearance/KeycapAppearance+M0116.swift | 2 +- .../Appearance/KeycapAppearance+Minimal.swift | 2 +- .../Appearance/KeycapAppearance+PBT.swift | 2 +- .../Appearance/KeycapAppearance+Retro.swift | 2 +- .../Appearance/KeycapAppearance.swift | 78 +++---------------- .../KeyboardVisualizerGroupView.swift | 12 +-- .../KeyboardVisualizerSettingsTests.swift | 2 +- 8 files changed, 22 insertions(+), 80 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift index 1726a8e9..3677fdbc 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Apple.swift @@ -9,7 +9,7 @@ import AppKit extension KeycapAppearance { - struct Apple { + struct Apple: KeycapAppearanceProviding { let shared: Shared let strokeColor: NSColor let undersideGradient: NSGradient? diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift index 62c6ef33..2147de19 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift @@ -11,7 +11,7 @@ import AppKit extension KeycapAppearance { /// Sculpted beige-era keycap: a lit body, an inset top face, and a front skirt, /// all sitting in a near-black well like the plate of an Apple Standard Keyboard. - struct M0116 { + struct M0116: KeycapAppearanceProviding { let shared: Shared let wellColor: NSColor let bodyGradient: NSGradient? diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift index 58b063b9..470c94a9 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Minimal.swift @@ -9,7 +9,7 @@ import AppKit extension KeycapAppearance { - struct Minimal { + struct Minimal: KeycapAppearanceProviding { let shared: Shared init(tokens: KeycapThemeTokens) { diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift index 7ec52f3a..1e5f9c98 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+PBT.swift @@ -9,7 +9,7 @@ import AppKit extension KeycapAppearance { - struct PBT { + struct PBT: KeycapAppearanceProviding { let shared: Shared let bodyGradient: NSGradient? let bodyStrokeColor: NSColor diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift index c5ceaf08..32067b77 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift @@ -9,7 +9,7 @@ import AppKit extension KeycapAppearance { - struct Retro { + struct Retro: KeycapAppearanceProviding { let shared: Shared let lipGradient: NSGradient? let bodyShadowColor: NSColor diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift index dc7c0511..d8945832 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift @@ -8,6 +8,10 @@ import AppKit +protocol KeycapAppearanceProviding { + var shared: KeycapAppearance.Shared { get } +} + struct KeycapThemeTokens { let swatchColor: NSColor let textColor: NSColor @@ -85,78 +89,16 @@ extension KeycapAppearance { } } -// MARK: - Shared Accessors extension KeycapAppearance { - var textColor: NSColor { - switch self { - case .apple(let appearance): appearance.shared.textColor - case .pbt(let appearance): appearance.shared.textColor - case .minimal(let appearance): appearance.shared.textColor - case .retro(let appearance): appearance.shared.textColor - case .m0116(let appearance): appearance.shared.textColor - } - } - - var groupBackgroundColor: NSColor { - switch self { - case .apple(let appearance): appearance.shared.groupBackgroundColor - case .pbt(let appearance): appearance.shared.groupBackgroundColor - case .minimal(let appearance): appearance.shared.groupBackgroundColor - case .retro(let appearance): appearance.shared.groupBackgroundColor - case .m0116(let appearance): appearance.shared.groupBackgroundColor - } - } - - var groupStrokeColor: NSColor { - switch self { - case .apple(let appearance): appearance.shared.groupStrokeColor - case .pbt(let appearance): appearance.shared.groupStrokeColor - case .minimal(let appearance): appearance.shared.groupStrokeColor - case .retro(let appearance): appearance.shared.groupStrokeColor - case .m0116(let appearance): appearance.shared.groupStrokeColor - } - } - - var badgeFillColor: NSColor { + var shared: Shared { switch self { - case .apple(let appearance): appearance.shared.badgeFillColor - case .pbt(let appearance): appearance.shared.badgeFillColor - case .minimal(let appearance): appearance.shared.badgeFillColor - case .retro(let appearance): appearance.shared.badgeFillColor - case .m0116(let appearance): appearance.shared.badgeFillColor + case .apple(let appearance): appearance.shared + case .pbt(let appearance): appearance.shared + case .minimal(let appearance): appearance.shared + case .retro(let appearance): appearance.shared + case .m0116(let appearance): appearance.shared } } - - var badgeStrokeColor: NSColor { - switch self { - case .apple(let appearance): appearance.shared.badgeStrokeColor - case .pbt(let appearance): appearance.shared.badgeStrokeColor - case .minimal(let appearance): appearance.shared.badgeStrokeColor - case .retro(let appearance): appearance.shared.badgeStrokeColor - case .m0116(let appearance): appearance.shared.badgeStrokeColor - } - } - - var badgeHighlightColor: NSColor { - switch self { - case .apple(let appearance): appearance.shared.badgeHighlightColor - case .pbt(let appearance): appearance.shared.badgeHighlightColor - case .minimal(let appearance): appearance.shared.badgeHighlightColor - case .retro(let appearance): appearance.shared.badgeHighlightColor - case .m0116(let appearance): appearance.shared.badgeHighlightColor - } - } - - var badgeTextColor: NSColor { - switch self { - case .apple(let appearance): appearance.shared.badgeTextColor - case .pbt(let appearance): appearance.shared.badgeTextColor - case .minimal(let appearance): appearance.shared.badgeTextColor - case .retro(let appearance): appearance.shared.badgeTextColor - case .m0116(let appearance): appearance.shared.badgeTextColor - } - } - } // MARK: - Case Accessors diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerGroupView.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerGroupView.swift index 08f4d183..976a4d37 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerGroupView.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerGroupView.swift @@ -223,9 +223,9 @@ private extension KeyboardVisualizerGroupView { } let path = NSBezierPath(roundedRect: insetBounds, xRadius: radius, yRadius: radius) let appearance = self.settings.groupAppearance - appearance.groupBackgroundColor.setFill() + appearance.shared.groupBackgroundColor.setFill() path.fill() - appearance.groupStrokeColor.setStroke() + appearance.shared.groupStrokeColor.setStroke() path.lineWidth = StrokeWidth.standard path.stroke() } @@ -238,7 +238,7 @@ private extension KeyboardVisualizerGroupView { let appearance = self.settings.groupAppearance let attributes: [NSAttributedString.Key: Any] = [ .font: font, - .foregroundColor: appearance.badgeTextColor, + .foregroundColor: appearance.shared.badgeTextColor, .paragraphStyle: paragraphStyle, ] let labelSize = badgeText.size(withAttributes: attributes) @@ -257,9 +257,9 @@ private extension KeyboardVisualizerGroupView { xRadius: badgeHeight / 2, yRadius: badgeHeight / 2 ) - appearance.badgeFillColor.setFill() + appearance.shared.badgeFillColor.setFill() badgePath.fill() - appearance.badgeStrokeColor.setStroke() + appearance.shared.badgeStrokeColor.setStroke() badgePath.lineWidth = StrokeWidth.standard badgePath.stroke() @@ -269,7 +269,7 @@ private extension KeyboardVisualizerGroupView { xRadius: highlightRect.height / 2, yRadius: highlightRect.height / 2 ) - appearance.badgeHighlightColor.setStroke() + appearance.shared.badgeHighlightColor.setStroke() highlightPath.lineWidth = 1 highlightPath.stroke() diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift index f41439b1..24be5be2 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift @@ -321,7 +321,7 @@ final class KeyboardVisualizerSettingsTests: XCTestCase { self.settings.style = .pbt let pbtAppearance = self.settings.appearance - XCTAssertEqual(appleAppearance.textColor, pbtAppearance.textColor) + XCTAssertEqual(appleAppearance.shared.textColor, pbtAppearance.shared.textColor) XCTAssertTrue(appleAppearance.apple != nil) XCTAssertTrue(pbtAppearance.pbt != nil) XCTAssertEqual(pbtAppearance.pbt?.bodyStrokeColor, KeyboardVisualizerTheme.citrus.appearance(for: .pbt).pbt?.bodyStrokeColor) From 05b8a568aae3ca3d3f3f80725af38f3affb2c067 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:30:29 +0200 Subject: [PATCH 08/31] refactor(keyboard): add style-specific keycap presentation policy --- .../KeycapItemFactory+Keyboard.swift | 9 +++++-- .../Visualizers/Keyboard/KeycapStyle.swift | 25 ++++++++++++++++++ .../Keyboard/Models/KeycapPresentation.swift | 23 ++++++++++++++++ .../Keycaps/KeycapItemFactoryTests.swift | 19 +++++++++++++ ...6StyleEscapeKeycap.m0116-escape-keycap.png | Bin 20423 -> 19034 bytes 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapPresentation.swift diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift index e74c748c..87392a10 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift @@ -38,11 +38,16 @@ extension KeycapItemFactory { guard !legend.text.isEmpty || legend.kind != .text else { return result } let identity = KeycapIdentity.keyCode(keyCode) + let presentation = palette.style.presentation( + for: keyCode, + legend: Self.keycapLegend(for: keyCode, legend: legend), + layoutHints: Self.layoutHints(forKeyCode: keyCode) + ) result.append(KeycapItem( identity: identity, - legend: Self.keycapLegend(for: keyCode, legend: legend), + legend: presentation.legend, state: KeycapState(isPressed: isPressed), - layoutHints: Self.layoutHints(forKeyCode: keyCode), + layoutHints: presentation.layoutHints, appearance: palette.appearance(for: identity) )) return result diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 0f23f30c..3d9dcd08 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -50,3 +50,28 @@ extension KeycapStyle { return theme } } + +// MARK: - Presentation Policy +extension KeycapStyle { + func presentation( + for keyCode: UInt16, + legend: KeycapLegend, + layoutHints: KeycapLayoutHints + ) -> KeycapPresentation { + guard let specialKey = KeyboardSpecialKeyResolver.specialKey(for: keyCode) else { + return KeycapPresentation(legend: legend, layoutHints: layoutHints) + } + + switch (self, specialKey) { + case (.m0116, .escape): + return KeycapPresentation( + legend: KeycapLegend( + label: KeyboardSpecialKey.escape.label + ), + layoutHints: layoutHints + ) + case (.apple, _), (.pbt, _), (.minimal, _), (.retro, _), (.m0116, _): + return KeycapPresentation(legend: legend, layoutHints: layoutHints) + } + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapPresentation.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapPresentation.swift new file mode 100644 index 00000000..8c37cd75 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapPresentation.swift @@ -0,0 +1,23 @@ +// +// KeycapPresentation.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import Foundation + +/// Style-resolved content and layout for a rendered keycap. +struct KeycapPresentation { + let legend: KeycapLegend + let layoutHints: KeycapLayoutHints + + init( + legend: KeycapLegend, + layoutHints: KeycapLayoutHints = KeycapLayoutHints() + ) { + self.legend = legend + self.layoutHints = layoutHints + } +} diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index f5e47f47..8d3d56d0 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -171,6 +171,25 @@ final class KeycapItemFactoryTests: XCTestCase { } } + func testM0116EscapeOmitsSymbolButKeepsLayoutHints() { + let items = KeycapItemFactory.keycapItems( + keyCode: KeyboardKeyCode.escape.rawValue, + legend: EventLegend( + text: KeyboardSpecialKey.escape.displayText, + label: KeyboardSpecialKey.escape.label + ), + modifierFlags: [], + isPressed: true, + palette: Self.makePalette(style: .m0116, theme: .white) + ) + + XCTAssertEqual(items.map(\.identity), [.keyCode(KeyboardKeyCode.escape.rawValue)]) + XCTAssertEqual(items.first?.symbol, "") + XCTAssertEqual(items.first?.label, "esc") + XCTAssertEqual(items.first?.rendersCenteredLabel, false) + XCTAssertEqual(items.first?.layoutHints.alignment, .left) + } + func testAppleRendererKeepsPageNavigationKeysAtStandardWidth() throws { let palette = Self.makePalette() let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleEscapeKeycap.m0116-escape-keycap.png b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/__Snapshots__/KeyboardVisualizerGroupViewSnapshotTests/testRendersM0116StyleEscapeKeycap.m0116-escape-keycap.png index f61f1da8f7f6a2b7286a111b3396b612e8978d29..8acef23c6a51ed90eae48c5620590a765feda61e 100644 GIT binary patch delta 18501 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 D;({5T From f8df758cc4bc74aed955fb8fad38a9b7f3f77969 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:40:49 +0200 Subject: [PATCH 09/31] feat(keyboard): add dashed arrow legends for m0116 style --- .../Visualizers/Keyboard/KeycapStyle.swift | 8 +++++ .../Keyty/Services/Display/UnicodeToken.swift | 5 +++ .../Keycaps/KeycapItemFactoryTests.swift | 33 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 3d9dcd08..ea90d18f 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -70,6 +70,14 @@ extension KeycapStyle { ), layoutHints: layoutHints ) + case (.m0116, .leftArrow): + return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.leftwardsDashedArrow.string), layoutHints: layoutHints) + case (.m0116, .rightArrow): + return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.rightwardsDashedArrow.string), layoutHints: layoutHints) + case (.m0116, .upArrow): + return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.upwardsDashedArrow.string), layoutHints: layoutHints) + case (.m0116, .downArrow): + return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.downwardsDashedArrow.string), layoutHints: layoutHints) case (.apple, _), (.pbt, _), (.minimal, _), (.retro, _), (.m0116, _): return KeycapPresentation(legend: legend, layoutHints: layoutHints) } diff --git a/Apps/Keyty/Sources/Keyty/Services/Display/UnicodeToken.swift b/Apps/Keyty/Sources/Keyty/Services/Display/UnicodeToken.swift index 7af0d495..53aeb456 100644 --- a/Apps/Keyty/Sources/Keyty/Services/Display/UnicodeToken.swift +++ b/Apps/Keyty/Sources/Keyty/Services/Display/UnicodeToken.swift @@ -36,6 +36,11 @@ enum UnicodeToken { static let rightArrow: Unicode.Scalar = "\u{25B6}" static let upArrow: Unicode.Scalar = "\u{25B2}" static let downArrow: Unicode.Scalar = "\u{25BC}" + + static let leftwardsDashedArrow: Unicode.Scalar = "\u{21E0}" + static let upwardsDashedArrow: Unicode.Scalar = "\u{21E1}" + static let rightwardsDashedArrow: Unicode.Scalar = "\u{21E2}" + static let downwardsDashedArrow: Unicode.Scalar = "\u{21E3}" static let leftwardsArrow: Unicode.Scalar = "\u{2190}" static let rightwardsArrow: Unicode.Scalar = "\u{2192}" diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 8d3d56d0..948ce200 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -190,6 +190,39 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first?.layoutHints.alignment, .left) } + func testM0116ArrowsUseDashedArrowGlyphsOnlyForThatStyle() { + let m0116Palette = Self.makePalette(style: .m0116, theme: .white) + let applePalette = Self.makePalette(style: .apple, theme: .black) + let expectedM0116: [(KeyboardKeyCode, String, String)] = [ + (.leftArrow, UnicodeToken.leftwardsDashedArrow.string, UnicodeToken.leftArrow.string), + (.rightArrow, UnicodeToken.rightwardsDashedArrow.string, UnicodeToken.rightArrow.string), + (.upArrow, UnicodeToken.upwardsDashedArrow.string, UnicodeToken.upArrow.string), + (.downArrow, UnicodeToken.downwardsDashedArrow.string, UnicodeToken.downArrow.string), + ] + + for (keyCode, m0116Symbol, defaultSymbol) in expectedM0116 { + let legend = EventLegend(text: KeyboardSpecialKeyResolver.specialKey(for: keyCode.rawValue)?.displayText ?? "") + + let m0116Items = KeycapItemFactory.keycapItems( + keyCode: keyCode.rawValue, + legend: legend, + modifierFlags: [], + isPressed: true, + palette: m0116Palette + ) + let appleItems = KeycapItemFactory.keycapItems( + keyCode: keyCode.rawValue, + legend: legend, + modifierFlags: [], + isPressed: true, + palette: applePalette + ) + + XCTAssertEqual(m0116Items.first?.symbol, m0116Symbol) + XCTAssertEqual(appleItems.first?.symbol, defaultSymbol) + } + } + func testAppleRendererKeepsPageNavigationKeysAtStandardWidth() throws { let palette = Self.makePalette() let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) From 7b02c201e4cf2afd458f58ac84d4b4c1e1561af3 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:44:30 +0200 Subject: [PATCH 10/31] feat(keyboard): hide space legend for m0116 style --- .../Visualizers/Keyboard/KeycapStyle.swift | 5 +++++ .../Keycaps/KeycapItemFactoryTests.swift | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index ea90d18f..63a9570d 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -70,6 +70,11 @@ extension KeycapStyle { ), layoutHints: layoutHints ) + case (.m0116, .space): + return KeycapPresentation( + legend: KeycapLegend(), + layoutHints: layoutHints + ) case (.m0116, .leftArrow): return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.leftwardsDashedArrow.string), layoutHints: layoutHints) case (.m0116, .rightArrow): diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 948ce200..199bf58b 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -223,6 +223,22 @@ final class KeycapItemFactoryTests: XCTestCase { } } + func testM0116SpaceOmitsLegendButKeepsWideKeycap() { + let items = KeycapItemFactory.keycapItems( + keyCode: KeyboardKeyCode.space.rawValue, + legend: EventLegend(text: KeyboardSpecialKey.space.displayText), + modifierFlags: [], + isPressed: true, + palette: Self.makePalette(style: .m0116, theme: .white) + ) + + XCTAssertEqual(items.map(\.identity), [.keyCode(KeyboardKeyCode.space.rawValue)]) + XCTAssertEqual(items.first?.symbol, "") + XCTAssertEqual(items.first?.label, nil) + XCTAssertEqual(items.first?.rendersCenteredLabel, false) + XCTAssertEqual(items.first?.layoutHints.fixedWidth, 256) + } + func testAppleRendererKeepsPageNavigationKeysAtStandardWidth() throws { let palette = Self.makePalette() let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) From bbda51fdc751f8c9cc630ffe61fc068235d146f5 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:46:12 +0200 Subject: [PATCH 11/31] feat(keyboard): render text-only return key for m0116 --- .../Visualizers/Keyboard/KeycapStyle.swift | 7 +++++++ .../Keycaps/KeycapItemFactoryTests.swift | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 63a9570d..80a4ee4c 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -70,6 +70,13 @@ extension KeycapStyle { ), layoutHints: layoutHints ) + case (.m0116, .returnKey): + return KeycapPresentation( + legend: KeycapLegend( + label: KeyboardSpecialKey.returnKey.label + ), + layoutHints: layoutHints + ) case (.m0116, .space): return KeycapPresentation( legend: KeycapLegend(), diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 199bf58b..db5f80e8 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -239,6 +239,26 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first?.layoutHints.fixedWidth, 256) } + func testM0116ReturnOmitsSymbolButKeepsReturnKeyLayout() { + let items = KeycapItemFactory.keycapItems( + keyCode: KeyboardKeyCode.returnKey.rawValue, + legend: EventLegend( + text: KeyboardSpecialKey.returnKey.displayText, + label: KeyboardSpecialKey.returnKey.label + ), + modifierFlags: [], + isPressed: true, + palette: Self.makePalette(style: .m0116, theme: .white) + ) + + XCTAssertEqual(items.map(\.identity), [.keyCode(KeyboardKeyCode.returnKey.rawValue)]) + XCTAssertEqual(items.first?.symbol, "") + XCTAssertEqual(items.first?.label, "return") + XCTAssertEqual(items.first?.rendersCenteredLabel, false) + XCTAssertNil(items.first?.layoutHints.fixedWidth) + XCTAssertEqual(items.first.map(keycapWidth(for:)), 128) + } + func testAppleRendererKeepsPageNavigationKeysAtStandardWidth() throws { let palette = Self.makePalette() let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) From 6f3725bbd8467afeec45ad1389873d6ffea910ae Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:47:16 +0200 Subject: [PATCH 12/31] feat(keyboard): render text-only tab key for m0116 --- .../Visualizers/Keyboard/KeycapStyle.swift | 7 +++++++ .../Keycaps/KeycapItemFactoryTests.swift | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 80a4ee4c..45408d87 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -77,6 +77,13 @@ extension KeycapStyle { ), layoutHints: layoutHints ) + case (.m0116, .tab): + return KeycapPresentation( + legend: KeycapLegend( + label: KeyboardSpecialKey.tab.label + ), + layoutHints: layoutHints + ) case (.m0116, .space): return KeycapPresentation( legend: KeycapLegend(), diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index db5f80e8..a4be68b9 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -259,6 +259,26 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first.map(keycapWidth(for:)), 128) } + func testM0116TabOmitsSymbolButKeepsTabLayout() { + let items = KeycapItemFactory.keycapItems( + keyCode: KeyboardKeyCode.tab.rawValue, + legend: EventLegend( + text: KeyboardSpecialKey.tab.displayText, + label: KeyboardSpecialKey.tab.label + ), + modifierFlags: [], + isPressed: true, + palette: Self.makePalette(style: .m0116, theme: .white) + ) + + XCTAssertEqual(items.map(\.identity), [.keyCode(KeyboardKeyCode.tab.rawValue)]) + XCTAssertEqual(items.first?.symbol, "") + XCTAssertEqual(items.first?.label, "tab") + XCTAssertEqual(items.first?.rendersCenteredLabel, false) + XCTAssertEqual(items.first?.layoutHints.alignment, .left) + XCTAssertEqual(items.first.map(keycapWidth(for:)), 112) + } + func testAppleRendererKeepsPageNavigationKeysAtStandardWidth() throws { let palette = Self.makePalette() let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) From ac9e6824e1296f2923ab00ad899fa786ba72e0c4 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:49:36 +0200 Subject: [PATCH 13/31] feat(keyboard): render m0116 shift without modifier glyph --- .../KeycapItemFactory+Modifiers.swift | 2 +- .../Visualizers/Keyboard/KeycapStyle.swift | 9 +++++++++ .../Keycaps/KeycapItemFactoryTests.swift | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift index 6314bf01..3483c9fa 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift @@ -59,7 +59,7 @@ extension KeycapItemFactory { let identity = KeycapIdentity.modifier(modifierKey) return KeycapItem( identity: identity, - legend: KeycapLegend(symbol: modifierKey.kind.glyph, label: modifierKey.kind.label), + legend: palette.style.modifierLegend(for: modifierKey), state: KeycapState(isPressed: isPressed), layoutHints: KeycapLayoutHints(alignment: modifierKey.legendAlignment), appearance: palette.appearance(for: identity) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 45408d87..a93d2d59 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -53,6 +53,15 @@ extension KeycapStyle { // MARK: - Presentation Policy extension KeycapStyle { + func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { + switch (self, modifierKey.kind) { + case (.m0116, .shift): + return KeycapLegend(label: modifierKey.kind.label) + case (.apple, _), (.pbt, _), (.minimal, _), (.retro, _), (.m0116, _): + return KeycapLegend(symbol: modifierKey.kind.glyph, label: modifierKey.kind.label) + } + } + func presentation( for keyCode: UInt16, legend: KeycapLegend, diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index a4be68b9..ad7480b9 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -279,6 +279,26 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first.map(keycapWidth(for:)), 112) } + func testM0116ShiftOmitsGlyphButOtherStylesKeepIt() { + let m0116Items = KeycapItemFactory.modifierItems( + currentFlags: NSEvent.ModifierFlags.shift.addingRawMasks(UInt(NX_DEVICELSHIFTKEYMASK)), + releasedFlags: [], + palette: Self.makePalette(style: .m0116, theme: .white) + ) + let appleItems = KeycapItemFactory.modifierItems( + currentFlags: NSEvent.ModifierFlags.shift.addingRawMasks(UInt(NX_DEVICELSHIFTKEYMASK)), + releasedFlags: [], + palette: Self.makePalette(style: .apple, theme: .black) + ) + + XCTAssertEqual(m0116Items.map(\.identity), [.modifier(.leftShift)]) + XCTAssertEqual(m0116Items.first?.symbol, "") + XCTAssertEqual(m0116Items.first?.label, "shift") + XCTAssertEqual(m0116Items.first?.layoutHints.alignment, .right) + XCTAssertEqual(appleItems.first?.symbol, UnicodeToken.shift.string) + XCTAssertEqual(appleItems.first?.label, "shift") + } + func testAppleRendererKeepsPageNavigationKeysAtStandardWidth() throws { let palette = Self.makePalette() let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) From 7a22727b95b73544e6b95bea899e9033517693a6 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:54:20 +0200 Subject: [PATCH 14/31] feat(keyboard): hide control glyphs for m0116 modifiers --- .../Visualizers/Keyboard/KeycapStyle.swift | 2 +- .../Keycaps/KeycapItemFactoryTests.swift | 49 ++++++++++++------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index a93d2d59..c10dc5aa 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -55,7 +55,7 @@ extension KeycapStyle { extension KeycapStyle { func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { switch (self, modifierKey.kind) { - case (.m0116, .shift): + case (.m0116, .shift), (.m0116, .control): return KeycapLegend(label: modifierKey.kind.label) case (.apple, _), (.pbt, _), (.minimal, _), (.retro, _), (.m0116, _): return KeycapLegend(symbol: modifierKey.kind.glyph, label: modifierKey.kind.label) diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index ad7480b9..c836f8fe 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -279,24 +279,39 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first.map(keycapWidth(for:)), 112) } - func testM0116ShiftOmitsGlyphButOtherStylesKeepIt() { - let m0116Items = KeycapItemFactory.modifierItems( - currentFlags: NSEvent.ModifierFlags.shift.addingRawMasks(UInt(NX_DEVICELSHIFTKEYMASK)), - releasedFlags: [], - palette: Self.makePalette(style: .m0116, theme: .white) - ) - let appleItems = KeycapItemFactory.modifierItems( - currentFlags: NSEvent.ModifierFlags.shift.addingRawMasks(UInt(NX_DEVICELSHIFTKEYMASK)), - releasedFlags: [], - palette: Self.makePalette(style: .apple, theme: .black) - ) + func testM0116ShiftAndControlOmitGlyphButOtherStylesKeepIt() { + let cases: [(NSEvent.ModifierFlags, UInt, KeycapIdentity, String, String)] = [ + (.shift.addingRawMasks(UInt(NX_DEVICELSHIFTKEYMASK)), UInt(NX_DEVICELSHIFTKEYMASK), .modifier(.leftShift), "shift", UnicodeToken.shift.string), + (.control.addingRawMasks(UInt(NX_DEVICELCTLKEYMASK)), UInt(NX_DEVICELCTLKEYMASK), .modifier(.leftControl), "control", UnicodeToken.control.string), + ] - XCTAssertEqual(m0116Items.map(\.identity), [.modifier(.leftShift)]) - XCTAssertEqual(m0116Items.first?.symbol, "") - XCTAssertEqual(m0116Items.first?.label, "shift") - XCTAssertEqual(m0116Items.first?.layoutHints.alignment, .right) - XCTAssertEqual(appleItems.first?.symbol, UnicodeToken.shift.string) - XCTAssertEqual(appleItems.first?.label, "shift") + for (flags, _, expectedIdentity, expectedLabel, defaultSymbol) in cases { + let m0116Items = KeycapItemFactory.modifierItems( + currentFlags: flags, + releasedFlags: [], + palette: Self.makePalette(style: .m0116, theme: .white) + ) + let appleItems = KeycapItemFactory.modifierItems( + currentFlags: flags, + releasedFlags: [], + palette: Self.makePalette(style: .apple, theme: .black) + ) + + XCTAssertEqual(m0116Items.map(\.identity), [expectedIdentity]) + XCTAssertEqual(m0116Items.first?.symbol, "") + XCTAssertEqual(m0116Items.first?.label, expectedLabel) + XCTAssertEqual(appleItems.first?.symbol, defaultSymbol) + XCTAssertEqual(appleItems.first?.label, expectedLabel) + } + + XCTAssertEqual( + KeycapItemFactory.modifierItems( + currentFlags: .shift.addingRawMasks(UInt(NX_DEVICELSHIFTKEYMASK)), + releasedFlags: [], + palette: Self.makePalette(style: .m0116, theme: .white) + ).first?.layoutHints.alignment, + .right + ) } func testAppleRendererKeepsPageNavigationKeysAtStandardWidth() throws { From 4c2aecceafa90102390acc6bb814f3094107e880 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:56:07 +0200 Subject: [PATCH 15/31] feat(keyboard): remove m0116 option and delete glyphs --- .../Visualizers/Keyboard/KeycapStyle.swift | 9 +++++++- .../Keycaps/KeycapItemFactoryTests.swift | 22 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index c10dc5aa..9def89c3 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -55,7 +55,7 @@ extension KeycapStyle { extension KeycapStyle { func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { switch (self, modifierKey.kind) { - case (.m0116, .shift), (.m0116, .control): + case (.m0116, .shift), (.m0116, .control), (.m0116, .option): return KeycapLegend(label: modifierKey.kind.label) case (.apple, _), (.pbt, _), (.minimal, _), (.retro, _), (.m0116, _): return KeycapLegend(symbol: modifierKey.kind.glyph, label: modifierKey.kind.label) @@ -93,6 +93,13 @@ extension KeycapStyle { ), layoutHints: layoutHints ) + case (.m0116, .delete): + return KeycapPresentation( + legend: KeycapLegend( + label: KeyboardSpecialKey.delete.label + ), + layoutHints: layoutHints + ) case (.m0116, .space): return KeycapPresentation( legend: KeycapLegend(), diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index c836f8fe..29e70789 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -259,6 +259,25 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first.map(keycapWidth(for:)), 128) } + func testM0116DeleteOmitsSymbolButKeepsDeleteKeyLayout() { + let items = KeycapItemFactory.keycapItems( + keyCode: KeyboardKeyCode.delete.rawValue, + legend: EventLegend( + text: KeyboardSpecialKey.delete.displayText, + label: KeyboardSpecialKey.delete.label + ), + modifierFlags: [], + isPressed: true, + palette: Self.makePalette(style: .m0116, theme: .white) + ) + + XCTAssertEqual(items.map(\.identity), [.keyCode(KeyboardKeyCode.delete.rawValue)]) + XCTAssertEqual(items.first?.symbol, "") + XCTAssertEqual(items.first?.label, "delete") + XCTAssertEqual(items.first?.rendersCenteredLabel, false) + XCTAssertEqual(items.first.map(keycapWidth(for:)), 112) + } + func testM0116TabOmitsSymbolButKeepsTabLayout() { let items = KeycapItemFactory.keycapItems( keyCode: KeyboardKeyCode.tab.rawValue, @@ -279,10 +298,11 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first.map(keycapWidth(for:)), 112) } - func testM0116ShiftAndControlOmitGlyphButOtherStylesKeepIt() { + func testM0116ShiftControlAndOptionOmitGlyphButOtherStylesKeepIt() { let cases: [(NSEvent.ModifierFlags, UInt, KeycapIdentity, String, String)] = [ (.shift.addingRawMasks(UInt(NX_DEVICELSHIFTKEYMASK)), UInt(NX_DEVICELSHIFTKEYMASK), .modifier(.leftShift), "shift", UnicodeToken.shift.string), (.control.addingRawMasks(UInt(NX_DEVICELCTLKEYMASK)), UInt(NX_DEVICELCTLKEYMASK), .modifier(.leftControl), "control", UnicodeToken.control.string), + (.option.addingRawMasks(UInt(NX_DEVICELALTKEYMASK)), UInt(NX_DEVICELALTKEYMASK), .modifier(.leftOption), "option", UnicodeToken.option.string), ] for (flags, _, expectedIdentity, expectedLabel, defaultSymbol) in cases { From 1d7deabc03316af29444e6b6d1052cc23f341620 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 02:58:00 +0200 Subject: [PATCH 16/31] feat(keyboard): remove m0116 command glyph --- .../Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift | 2 +- .../Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 9def89c3..fd809e15 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -55,7 +55,7 @@ extension KeycapStyle { extension KeycapStyle { func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { switch (self, modifierKey.kind) { - case (.m0116, .shift), (.m0116, .control), (.m0116, .option): + case (.m0116, .shift), (.m0116, .control), (.m0116, .option), (.m0116, .command): return KeycapLegend(label: modifierKey.kind.label) case (.apple, _), (.pbt, _), (.minimal, _), (.retro, _), (.m0116, _): return KeycapLegend(symbol: modifierKey.kind.glyph, label: modifierKey.kind.label) diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 29e70789..fa2b1c42 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -298,11 +298,12 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first.map(keycapWidth(for:)), 112) } - func testM0116ShiftControlAndOptionOmitGlyphButOtherStylesKeepIt() { + func testM0116ModifiersOmitGlyphsButOtherStylesKeepThem() { let cases: [(NSEvent.ModifierFlags, UInt, KeycapIdentity, String, String)] = [ (.shift.addingRawMasks(UInt(NX_DEVICELSHIFTKEYMASK)), UInt(NX_DEVICELSHIFTKEYMASK), .modifier(.leftShift), "shift", UnicodeToken.shift.string), (.control.addingRawMasks(UInt(NX_DEVICELCTLKEYMASK)), UInt(NX_DEVICELCTLKEYMASK), .modifier(.leftControl), "control", UnicodeToken.control.string), (.option.addingRawMasks(UInt(NX_DEVICELALTKEYMASK)), UInt(NX_DEVICELALTKEYMASK), .modifier(.leftOption), "option", UnicodeToken.option.string), + (.command.addingRawMasks(UInt(NX_DEVICELCMDKEYMASK)), UInt(NX_DEVICELCMDKEYMASK), .modifier(.leftCommand), "command", UnicodeToken.command.string), ] for (flags, _, expectedIdentity, expectedLabel, defaultSymbol) in cases { From b9c256ce838cdd8eefd2ee75b4192c27e6040a65 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 03:00:33 +0200 Subject: [PATCH 17/31] feat(keyboard): render m0116 fn key as label-only --- .../KeycapItemFactory+Modifiers.swift | 2 +- .../Visualizers/Keyboard/KeycapStyle.swift | 9 ++++++++ .../Keycaps/KeycapItemFactoryTests.swift | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift index 3483c9fa..f832ce14 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift @@ -84,7 +84,7 @@ extension KeycapItemFactory { let identity = KeycapIdentity.keyCode(KeyboardKeyCode.function.rawValue) return KeycapItem( identity: identity, - legend: .function, + legend: palette.style.functionLegend, state: KeycapState(isPressed: isPressed), appearance: palette.appearance(for: identity) ) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index fd809e15..8a09da93 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -53,6 +53,15 @@ extension KeycapStyle { // MARK: - Presentation Policy extension KeycapStyle { + var functionLegend: KeycapLegend { + switch self { + case .m0116: + return KeycapLegend(label: KeyboardSpecialKey.function.label) + case .apple, .pbt, .minimal, .retro: + return .function + } + } + func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { switch (self, modifierKey.kind) { case (.m0116, .shift), (.m0116, .control), (.m0116, .option), (.m0116, .command): diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index fa2b1c42..8e25141f 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -35,6 +35,27 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first?.isPressed, true) } + func testM0116FunctionKeyMatchesModifierStyle() { + let m0116Items = KeycapItemFactory.modifierItems( + currentFlags: [.function], + releasedFlags: [], + palette: Self.makePalette(style: .m0116, theme: .white) + ) + let appleItems = KeycapItemFactory.modifierItems( + currentFlags: [.function], + releasedFlags: [], + palette: Self.makePalette(style: .apple, theme: .black) + ) + + XCTAssertEqual(m0116Items.count, 1) + XCTAssertEqual(m0116Items.first?.identity, .keyCode(KeyboardKeyCode.function.rawValue)) + XCTAssertEqual(m0116Items.first?.symbol, "") + XCTAssertEqual(m0116Items.first?.label, "fn") + XCTAssertNil(m0116Items.first?.sfSymbolName) + XCTAssertEqual(appleItems.first?.sfSymbolName, "globe") + XCTAssertEqual(appleItems.first?.label, "fn") + } + func testModifierItemsUseLocationSpecificModifierKeys() { let items = KeycapItemFactory.modifierItems( currentFlags: NSEvent.ModifierFlags.command.addingRawMasks( From 1278901c1215e1d2e8b0c1050e94f1012d1f598d Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 13:36:43 +0200 Subject: [PATCH 18/31] docs(keyboard): document keycap presentation policy contract --- .../KeycapItemFactory+Keyboard.swift | 2 +- .../KeycapItemFactory+Modifiers.swift | 4 +- .../Visualizers/Keyboard/KeycapStyle.swift | 71 +----------------- .../DefaultKeycapPresentationPolicy.swift | 25 +++++++ .../KeycapPresentationPolicy.swift | 23 ++++++ .../M0116KeycapPresentationPolicy.swift | 75 +++++++++++++++++++ 6 files changed, 129 insertions(+), 71 deletions(-) create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/DefaultKeycapPresentationPolicy.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/KeycapPresentationPolicy.swift create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift index 87392a10..4c16caa6 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift @@ -38,7 +38,7 @@ extension KeycapItemFactory { guard !legend.text.isEmpty || legend.kind != .text else { return result } let identity = KeycapIdentity.keyCode(keyCode) - let presentation = palette.style.presentation( + let presentation = palette.style.presentationPolicy.presentation( for: keyCode, legend: Self.keycapLegend(for: keyCode, legend: legend), layoutHints: Self.layoutHints(forKeyCode: keyCode) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift index f832ce14..ffdbcb57 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Modifiers.swift @@ -59,7 +59,7 @@ extension KeycapItemFactory { let identity = KeycapIdentity.modifier(modifierKey) return KeycapItem( identity: identity, - legend: palette.style.modifierLegend(for: modifierKey), + legend: palette.style.presentationPolicy.modifierLegend(for: modifierKey), state: KeycapState(isPressed: isPressed), layoutHints: KeycapLayoutHints(alignment: modifierKey.legendAlignment), appearance: palette.appearance(for: identity) @@ -84,7 +84,7 @@ extension KeycapItemFactory { let identity = KeycapIdentity.keyCode(KeyboardKeyCode.function.rawValue) return KeycapItem( identity: identity, - legend: palette.style.functionLegend, + legend: palette.style.presentationPolicy.functionLegend, state: KeycapState(isPressed: isPressed), appearance: palette.appearance(for: identity) ) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift index 8a09da93..1c944502 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapStyle.swift @@ -53,77 +53,12 @@ extension KeycapStyle { // MARK: - Presentation Policy extension KeycapStyle { - var functionLegend: KeycapLegend { + var presentationPolicy: any KeycapPresentationPolicy { switch self { case .m0116: - return KeycapLegend(label: KeyboardSpecialKey.function.label) + return M0116KeycapPresentationPolicy() case .apple, .pbt, .minimal, .retro: - return .function - } - } - - func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { - switch (self, modifierKey.kind) { - case (.m0116, .shift), (.m0116, .control), (.m0116, .option), (.m0116, .command): - return KeycapLegend(label: modifierKey.kind.label) - case (.apple, _), (.pbt, _), (.minimal, _), (.retro, _), (.m0116, _): - return KeycapLegend(symbol: modifierKey.kind.glyph, label: modifierKey.kind.label) - } - } - - func presentation( - for keyCode: UInt16, - legend: KeycapLegend, - layoutHints: KeycapLayoutHints - ) -> KeycapPresentation { - guard let specialKey = KeyboardSpecialKeyResolver.specialKey(for: keyCode) else { - return KeycapPresentation(legend: legend, layoutHints: layoutHints) - } - - switch (self, specialKey) { - case (.m0116, .escape): - return KeycapPresentation( - legend: KeycapLegend( - label: KeyboardSpecialKey.escape.label - ), - layoutHints: layoutHints - ) - case (.m0116, .returnKey): - return KeycapPresentation( - legend: KeycapLegend( - label: KeyboardSpecialKey.returnKey.label - ), - layoutHints: layoutHints - ) - case (.m0116, .tab): - return KeycapPresentation( - legend: KeycapLegend( - label: KeyboardSpecialKey.tab.label - ), - layoutHints: layoutHints - ) - case (.m0116, .delete): - return KeycapPresentation( - legend: KeycapLegend( - label: KeyboardSpecialKey.delete.label - ), - layoutHints: layoutHints - ) - case (.m0116, .space): - return KeycapPresentation( - legend: KeycapLegend(), - layoutHints: layoutHints - ) - case (.m0116, .leftArrow): - return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.leftwardsDashedArrow.string), layoutHints: layoutHints) - case (.m0116, .rightArrow): - return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.rightwardsDashedArrow.string), layoutHints: layoutHints) - case (.m0116, .upArrow): - return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.upwardsDashedArrow.string), layoutHints: layoutHints) - case (.m0116, .downArrow): - return KeycapPresentation(legend: KeycapLegend(symbol: UnicodeToken.downwardsDashedArrow.string), layoutHints: layoutHints) - case (.apple, _), (.pbt, _), (.minimal, _), (.retro, _), (.m0116, _): - return KeycapPresentation(legend: legend, layoutHints: layoutHints) + return DefaultKeycapPresentationPolicy() } } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/DefaultKeycapPresentationPolicy.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/DefaultKeycapPresentationPolicy.swift new file mode 100644 index 00000000..f8fdba95 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/DefaultKeycapPresentationPolicy.swift @@ -0,0 +1,25 @@ +// +// DefaultKeycapPresentationPolicy.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import Foundation + +struct DefaultKeycapPresentationPolicy: KeycapPresentationPolicy { + var functionLegend: KeycapLegend { .function } + + func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { + KeycapLegend(symbol: modifierKey.kind.glyph, label: modifierKey.kind.label) + } + + func presentation( + for keyCode: UInt16, + legend: KeycapLegend, + layoutHints: KeycapLayoutHints + ) -> KeycapPresentation { + KeycapPresentation(legend: legend, layoutHints: layoutHints) + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/KeycapPresentationPolicy.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/KeycapPresentationPolicy.swift new file mode 100644 index 00000000..0f4f5781 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/KeycapPresentationPolicy.swift @@ -0,0 +1,23 @@ +// +// KeycapPresentationPolicy.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import Foundation + +protocol KeycapPresentationPolicy { + /// Returns the legend used for the function key in this style. + var functionLegend: KeycapLegend { get } + + /// Returns the legend used for a modifier key in this style. + func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend + /// Returns the style-adjusted keycap presentation for a keyboard key. + func presentation( + for keyCode: UInt16, + legend: KeycapLegend, + layoutHints: KeycapLayoutHints + ) -> KeycapPresentation +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift new file mode 100644 index 00000000..c235cea1 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift @@ -0,0 +1,75 @@ +// +// M0116KeycapPresentationPolicy.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import Foundation + +struct M0116KeycapPresentationPolicy: KeycapPresentationPolicy { + var functionLegend: KeycapLegend { + KeycapLegend(label: KeyboardSpecialKey.function.label) + } + + func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { + KeycapLegend(label: modifierKey.kind.label) + } + + func presentation( + for keyCode: UInt16, + legend: KeycapLegend, + layoutHints: KeycapLayoutHints + ) -> KeycapPresentation { + guard let specialKey = KeyboardSpecialKeyResolver.specialKey(for: keyCode) else { + return KeycapPresentation(legend: legend, layoutHints: layoutHints) + } + + switch specialKey { + case .escape: + return self.textOnlyPresentation(for: .escape, layoutHints: layoutHints) + case .tab: + return self.textOnlyPresentation(for: .tab, layoutHints: layoutHints) + case .delete: + return self.textOnlyPresentation(for: .delete, layoutHints: layoutHints) + case .returnKey: + return self.textOnlyPresentation(for: .returnKey, layoutHints: layoutHints) + case .space: + return KeycapPresentation(legend: KeycapLegend(), layoutHints: layoutHints) + case .leftArrow: + return KeycapPresentation( + legend: KeycapLegend(symbol: UnicodeToken.leftwardsDashedArrow.string), + layoutHints: layoutHints + ) + case .rightArrow: + return KeycapPresentation( + legend: KeycapLegend(symbol: UnicodeToken.rightwardsDashedArrow.string), + layoutHints: layoutHints + ) + case .upArrow: + return KeycapPresentation( + legend: KeycapLegend(symbol: UnicodeToken.upwardsDashedArrow.string), + layoutHints: layoutHints + ) + case .downArrow: + return KeycapPresentation( + legend: KeycapLegend(symbol: UnicodeToken.downwardsDashedArrow.string), + layoutHints: layoutHints + ) + case .forwardDelete, .keypadEnter, .help, .insert, .keypadClear, .home, .end, .pageUp, .pageDown, + .function, .functionRow, .capsLock, .eisu, .kana, .system: + return KeycapPresentation(legend: legend, layoutHints: layoutHints) + } + } + + private func textOnlyPresentation( + for specialKey: KeyboardSpecialKey, + layoutHints: KeycapLayoutHints + ) -> KeycapPresentation { + KeycapPresentation( + legend: KeycapLegend(label: specialKey.label), + layoutHints: layoutHints + ) + } +} From 777330129049dec323d8615688cf2aa62329b385 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 13:39:28 +0200 Subject: [PATCH 19/31] feat(keyboard): hide caps lock indicator dot for m0116 --- .../Features/Visualizers/Keyboard/KeyboardVisualizer.swift | 3 ++- .../PresentationPolicy/DefaultKeycapPresentationPolicy.swift | 1 + .../PresentationPolicy/KeycapPresentationPolicy.swift | 2 ++ .../PresentationPolicy/M0116KeycapPresentationPolicy.swift | 1 + .../Keyboard/Keycaps/KeycapItemFactoryTests.swift | 5 +++++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift index 14b1b2fd..09a6030f 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift @@ -193,11 +193,12 @@ private extension KeyboardVisualizer { let capsNow = modifierFlags.contains(.capsLock) let capsWas = self.lastModifierFlags.contains(.capsLock) if self.visualizerSettings.showSpecialKeys, capsNow != capsWas { + let showsCapsLockDot = self.visualizerSettings.palette.style.presentationPolicy.showsCapsLockDot let group = self.eventCoordinator.handleStandalone( items: [KeycapItem( identity: .keyCode(KeyboardKeyCode.capsLock.rawValue), legend: .capsLock, - state: KeycapState(isPressed: false, showsDot: true, isDotActive: capsNow), + state: KeycapState(isPressed: false, showsDot: showsCapsLockDot, isDotActive: capsNow), layoutHints: KeycapLayoutHints(alignment: .left), appearance: self.visualizerSettings.palette.appearance(for: .keyCode(KeyboardKeyCode.capsLock.rawValue)) )], diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/DefaultKeycapPresentationPolicy.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/DefaultKeycapPresentationPolicy.swift index f8fdba95..62bbf2ee 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/DefaultKeycapPresentationPolicy.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/DefaultKeycapPresentationPolicy.swift @@ -9,6 +9,7 @@ import Foundation struct DefaultKeycapPresentationPolicy: KeycapPresentationPolicy { + var showsCapsLockDot: Bool { true } var functionLegend: KeycapLegend { .function } func modifierLegend(for modifierKey: KeyboardModifierKey) -> KeycapLegend { diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/KeycapPresentationPolicy.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/KeycapPresentationPolicy.swift index 0f4f5781..6fb130b0 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/KeycapPresentationPolicy.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/KeycapPresentationPolicy.swift @@ -9,6 +9,8 @@ import Foundation protocol KeycapPresentationPolicy { + /// Indicates whether caps lock should render its indicator dot in this style. + var showsCapsLockDot: Bool { get } /// Returns the legend used for the function key in this style. var functionLegend: KeycapLegend { get } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift index c235cea1..f3e43150 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift @@ -9,6 +9,7 @@ import Foundation struct M0116KeycapPresentationPolicy: KeycapPresentationPolicy { + var showsCapsLockDot: Bool { false } var functionLegend: KeycapLegend { KeycapLegend(label: KeyboardSpecialKey.function.label) } diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 8e25141f..71dab648 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -56,6 +56,11 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(appleItems.first?.label, "fn") } + func testM0116PresentationPolicyHidesCapsLockDot() { + XCTAssertFalse(Self.makePalette(style: .m0116, theme: .white).style.presentationPolicy.showsCapsLockDot) + XCTAssertTrue(Self.makePalette(style: .apple, theme: .black).style.presentationPolicy.showsCapsLockDot) + } + func testModifierItemsUseLocationSpecificModifierKeys() { let items = KeycapItemFactory.modifierItems( currentFlags: NSEvent.ModifierFlags.command.addingRawMasks( From 057bb92f37441855b5374a30b79fffec9954c7d9 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 14:15:22 +0200 Subject: [PATCH 20/31] feat(keyboard): darken m0116 legend color --- .../Appearance/KeycapAppearance+M0116.swift | 11 ++++- .../Renderers/M0116/M0116KeycapMetrics.swift | 1 + .../Renderers/M0116/M0116KeycapRenderer.swift | 48 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift index 2147de19..0b181117 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift @@ -26,8 +26,17 @@ extension KeycapAppearance { withFraction: 0.35, of: tokens.surfaceBaseColor ) ?? tokens.surfaceHighlightColor + let legendTextColor = tokens.textColor.darkened(by: 0.18) - self.shared = Shared(tokens: tokens) + self.shared = Shared( + textColor: legendTextColor, + groupBackgroundColor: tokens.groupBackgroundColor, + groupStrokeColor: tokens.groupStrokeColor, + badgeFillColor: tokens.badgeFillColor, + badgeStrokeColor: tokens.badgeStrokeColor, + badgeHighlightColor: tokens.badgeHighlightColor, + badgeTextColor: tokens.badgeTextColor + ) self.wellColor = tokens.recessColor.darkened(by: 0.45) // Lit from the left: bright edge, base across the middle, shaded right edge. self.bodyGradient = NSGradient(colorsAndLocations: diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift index d7c8bbd1..bfde538f 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift @@ -23,6 +23,7 @@ enum M0116KeycapMetrics { static let faceTopInset: CGFloat = 4 static let faceBottomInset: CGFloat = 15 static let horizontalPadding: CGFloat = 11 + static let capsLockLabelWidth: CGFloat = 52 static let legendBaselineInset: CGFloat = 6 static let pressedTravel: CGFloat = 2 diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift index 2eb4a0ec..715b1e3c 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift @@ -137,6 +137,14 @@ private extension M0116KeycapRenderer { return } + if self.rendersWrappedCapsLockLabel(for: item), let label = item.label { + self.drawWrappedCapsLockLabel(label, in: faceRect, textColor: textColor) + if item.state.showsDot { + self.legendRenderer.drawKeycapDot(in: faceRect, active: item.state.isDotActive) + } + return + } + let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .left let x = faceRect.minX + M0116KeycapMetrics.horizontalPadding @@ -171,6 +179,40 @@ private extension M0116KeycapRenderer { } } + func drawWrappedCapsLockLabel(_ label: String, in faceRect: NSRect, textColor: NSColor) { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .left + let attributes: [NSAttributedString.Key: Any] = [ + .font: M0116KeycapMetrics.labelFont, + .foregroundColor: textColor, + .paragraphStyle: paragraphStyle, + ] + let width = min( + M0116KeycapMetrics.capsLockLabelWidth, + faceRect.width - 2 * M0116KeycapMetrics.horizontalPadding + ) + let bounds = NSRect( + x: faceRect.minX + M0116KeycapMetrics.horizontalPadding, + y: faceRect.minY + M0116KeycapMetrics.legendBaselineInset, + width: width, + height: faceRect.height - M0116KeycapMetrics.legendBaselineInset + ) + let size = label.boundingRect( + with: bounds.size, + options: [.usesLineFragmentOrigin, .usesFontLeading], + attributes: attributes + ).integral.size + label.draw( + in: NSRect( + x: bounds.minX, + y: bounds.minY, + width: bounds.width, + height: size.height + ), + withAttributes: attributes + ) + } + /// Image and SF Symbol legends keep the shared centered treatment; only text legends /// take the M0116's lower-left baseline. func rendersOwnLegend(for item: KeycapItem) -> Bool { @@ -182,4 +224,10 @@ private extension M0116KeycapRenderer { } return item.sfSymbolName == nil && !item.symbol.isEmpty } + + func rendersWrappedCapsLockLabel(for item: KeycapItem) -> Bool { + item.identity == .keyCode(KeyboardKeyCode.capsLock.rawValue) + && item.label != nil + && item.symbol.isEmpty + } } From a36728babd05494bbb575bc04453a9f8c77f7b6b Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 14:26:05 +0200 Subject: [PATCH 21/31] feat(keyboard): refine m0116 keycap presentation --- .../M0116KeycapPresentationPolicy.swift | 7 +++++- .../Keycaps/KeycapItemFactoryTests.swift | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift index f3e43150..f7b5abb0 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift @@ -36,6 +36,11 @@ struct M0116KeycapPresentationPolicy: KeycapPresentationPolicy { return self.textOnlyPresentation(for: .delete, layoutHints: layoutHints) case .returnKey: return self.textOnlyPresentation(for: .returnKey, layoutHints: layoutHints) + case .home, .end, .pageUp, .pageDown: + return KeycapPresentation( + legend: KeycapLegend(label: legend.label), + layoutHints: layoutHints + ) case .space: return KeycapPresentation(legend: KeycapLegend(), layoutHints: layoutHints) case .leftArrow: @@ -58,7 +63,7 @@ struct M0116KeycapPresentationPolicy: KeycapPresentationPolicy { legend: KeycapLegend(symbol: UnicodeToken.downwardsDashedArrow.string), layoutHints: layoutHints ) - case .forwardDelete, .keypadEnter, .help, .insert, .keypadClear, .home, .end, .pageUp, .pageDown, + case .forwardDelete, .keypadEnter, .help, .insert, .keypadClear, .function, .functionRow, .capsLock, .eisu, .kana, .system: return KeycapPresentation(legend: legend, layoutHints: layoutHints) } diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 71dab648..2627e42d 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -304,6 +304,30 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first.map(keycapWidth(for:)), 112) } + func testM0116NavigationKeysUseRegularLabelRendering() { + let expected: [(KeyboardKeyCode, String)] = [ + (.home, "home"), + (.end, "end"), + (.pageUp, "page up"), + (.pageDown, "page down"), + ] + + for (keyCode, label) in expected { + let items = KeycapItemFactory.keycapItems( + keyCode: keyCode.rawValue, + legend: EventLegend(text: KeyboardSpecialKeyResolver.specialKey(for: keyCode.rawValue)?.displayText ?? ""), + modifierFlags: [], + isPressed: true, + palette: Self.makePalette(style: .m0116, theme: .white) + ) + + XCTAssertEqual(items.map(\.identity), [.keyCode(keyCode.rawValue)]) + XCTAssertEqual(items.first?.symbol, "") + XCTAssertEqual(items.first?.label, label) + XCTAssertEqual(items.first?.rendersCenteredLabel, false) + } + } + func testM0116TabOmitsSymbolButKeepsTabLayout() { let items = KeycapItemFactory.keycapItems( keyCode: KeyboardKeyCode.tab.rawValue, From fafd9598858d1ce65281b5a5dd31feabdcf5145e Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 14:30:26 +0200 Subject: [PATCH 22/31] feat(keyboard): wrap multiline m0116 key legends --- .../Keyboard/Models/KeycapItem.swift | 2 ++ .../Keyboard/Models/KeycapLegend.swift | 7 ++++-- .../M0116KeycapPresentationPolicy.swift | 7 +++++- .../Renderers/M0116/M0116KeycapMetrics.swift | 1 + .../Renderers/M0116/M0116KeycapRenderer.swift | 21 +++++++++++------- .../Keycaps/KeycapItemFactoryTests.swift | 22 +++++++++++++++++++ 6 files changed, 49 insertions(+), 11 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapItem.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapItem.swift index f3e2f06c..4dda31d7 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapItem.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapItem.swift @@ -42,6 +42,8 @@ struct KeycapItem { var label: String? { self.legend.label } var rendersSymbolWithLabel: Bool { self.legend.rendersSymbolWithLabel } var rendersCenteredLabel: Bool { self.legend.rendersCenteredLabel } + /// Indicates whether the label should be drawn in a constrained width so it can wrap across lines. + var wrapsLabel: Bool { self.legend.wrapsLabel } var isPressed: Bool { self.state.isPressed } var fixedWidth: CGFloat? { self.layoutHints.fixedWidth } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapLegend.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapLegend.swift index 31aad36a..9ca9bba1 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapLegend.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Models/KeycapLegend.swift @@ -19,6 +19,7 @@ struct KeycapLegend { let label: String? let rendersSymbolWithLabel: Bool let rendersCenteredLabel: Bool + let wrapsLabel: Bool init( symbol: String = "", @@ -27,7 +28,8 @@ struct KeycapLegend { sfSymbolName: String? = nil, label: String? = nil, rendersSymbolWithLabel: Bool = false, - rendersCenteredLabel: Bool = false + rendersCenteredLabel: Bool = false, + wrapsLabel: Bool = false ) { self.symbol = symbol self.image = image @@ -36,6 +38,7 @@ struct KeycapLegend { self.label = label self.rendersSymbolWithLabel = rendersSymbolWithLabel self.rendersCenteredLabel = rendersCenteredLabel + self.wrapsLabel = wrapsLabel } } @@ -81,5 +84,5 @@ extension KeycapLegend { static let end = KeycapLegend(label: "end", rendersCenteredLabel: true) static let pageUp = KeycapLegend(label: "page up", rendersCenteredLabel: true) static let pageDown = KeycapLegend(label: "page down", rendersCenteredLabel: true) - static let capsLock = KeycapLegend(label: KeyboardSpecialKey.capsLock.label) + static let capsLock = KeycapLegend(label: KeyboardSpecialKey.capsLock.label, wrapsLabel: true) } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift index f7b5abb0..8ba8318e 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/PresentationPolicy/M0116KeycapPresentationPolicy.swift @@ -36,11 +36,16 @@ struct M0116KeycapPresentationPolicy: KeycapPresentationPolicy { return self.textOnlyPresentation(for: .delete, layoutHints: layoutHints) case .returnKey: return self.textOnlyPresentation(for: .returnKey, layoutHints: layoutHints) - case .home, .end, .pageUp, .pageDown: + case .home, .end: return KeycapPresentation( legend: KeycapLegend(label: legend.label), layoutHints: layoutHints ) + case .pageUp, .pageDown: + return KeycapPresentation( + legend: KeycapLegend(label: legend.label, wrapsLabel: true), + layoutHints: layoutHints + ) case .space: return KeycapPresentation(legend: KeycapLegend(), layoutHints: layoutHints) case .leftArrow: diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift index bfde538f..9340495e 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift @@ -24,6 +24,7 @@ enum M0116KeycapMetrics { static let faceBottomInset: CGFloat = 15 static let horizontalPadding: CGFloat = 11 static let capsLockLabelWidth: CGFloat = 52 + static let pageNavigationLabelWidth: CGFloat = 48 static let legendBaselineInset: CGFloat = 6 static let pressedTravel: CGFloat = 2 diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift index 715b1e3c..efdaf7ab 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift @@ -137,8 +137,8 @@ private extension M0116KeycapRenderer { return } - if self.rendersWrappedCapsLockLabel(for: item), let label = item.label { - self.drawWrappedCapsLockLabel(label, in: faceRect, textColor: textColor) + if item.wrapsLabel, let label = item.label { + self.drawWrappedLabel(label, for: item, in: faceRect, textColor: textColor) if item.state.showsDot { self.legendRenderer.drawKeycapDot(in: faceRect, active: item.state.isDotActive) } @@ -179,7 +179,7 @@ private extension M0116KeycapRenderer { } } - func drawWrappedCapsLockLabel(_ label: String, in faceRect: NSRect, textColor: NSColor) { + func drawWrappedLabel(_ label: String, for item: KeycapItem, in faceRect: NSRect, textColor: NSColor) { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .left let attributes: [NSAttributedString.Key: Any] = [ @@ -188,7 +188,7 @@ private extension M0116KeycapRenderer { .paragraphStyle: paragraphStyle, ] let width = min( - M0116KeycapMetrics.capsLockLabelWidth, + self.wrappedLabelWidth(for: item, faceRect: faceRect), faceRect.width - 2 * M0116KeycapMetrics.horizontalPadding ) let bounds = NSRect( @@ -225,9 +225,14 @@ private extension M0116KeycapRenderer { return item.sfSymbolName == nil && !item.symbol.isEmpty } - func rendersWrappedCapsLockLabel(for item: KeycapItem) -> Bool { - item.identity == .keyCode(KeyboardKeyCode.capsLock.rawValue) - && item.label != nil - && item.symbol.isEmpty + func wrappedLabelWidth(for item: KeycapItem, faceRect: NSRect) -> CGFloat { + switch item.identity { + case .keyCode(KeyboardKeyCode.capsLock.rawValue): + return M0116KeycapMetrics.capsLockLabelWidth + case .keyCode(KeyboardKeyCode.pageUp.rawValue), .keyCode(KeyboardKeyCode.pageDown.rawValue): + return M0116KeycapMetrics.pageNavigationLabelWidth + default: + return faceRect.width - 2 * M0116KeycapMetrics.horizontalPadding + } } } diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 2627e42d..eee455ae 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -308,6 +308,27 @@ final class KeycapItemFactoryTests: XCTestCase { let expected: [(KeyboardKeyCode, String)] = [ (.home, "home"), (.end, "end"), + ] + + for (keyCode, label) in expected { + let items = KeycapItemFactory.keycapItems( + keyCode: keyCode.rawValue, + legend: EventLegend(text: KeyboardSpecialKeyResolver.specialKey(for: keyCode.rawValue)?.displayText ?? ""), + modifierFlags: [], + isPressed: true, + palette: Self.makePalette(style: .m0116, theme: .white) + ) + + XCTAssertEqual(items.map(\.identity), [.keyCode(keyCode.rawValue)]) + XCTAssertEqual(items.first?.symbol, "") + XCTAssertEqual(items.first?.label, label) + XCTAssertEqual(items.first?.rendersCenteredLabel, false) + XCTAssertEqual(items.first?.wrapsLabel, false) + } + } + + func testM0116PageNavigationKeysWrapLabels() { + let expected: [(KeyboardKeyCode, String)] = [ (.pageUp, "page up"), (.pageDown, "page down"), ] @@ -325,6 +346,7 @@ final class KeycapItemFactoryTests: XCTestCase { XCTAssertEqual(items.first?.symbol, "") XCTAssertEqual(items.first?.label, label) XCTAssertEqual(items.first?.rendersCenteredLabel, false) + XCTAssertEqual(items.first?.wrapsLabel, true) } } From 507b2aba73e34aed6534009e64c92faffbfbcb30 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Wed, 26 Aug 2026 14:58:51 +0200 Subject: [PATCH 23/31] feat(keyboard): keep m0116 navigation keys at regular width --- .../Renderers/M0116/M0116KeycapRenderer.swift | 16 ++++++++++++++++ .../Keycaps/KeycapItemFactoryTests.swift | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift index efdaf7ab..a9dbb557 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift @@ -81,6 +81,10 @@ private extension M0116KeycapRenderer { /// The shared width helper measures legends in the app-wide label font. This style sets /// them in a larger italic face, so the text is re-measured here to keep it from clipping. func m0116KeycapWidth(for item: KeycapItem) -> CGFloat { + if self.usesRegularNavigationWidth(item) { + return M0116KeycapMetrics.minWidth + } + let sideInsets = 2 * (M0116KeycapMetrics.horizontalPadding + M0116KeycapMetrics.faceSideInset) var legendWidth: CGFloat = 0 if let label = item.label { @@ -235,4 +239,16 @@ private extension M0116KeycapRenderer { return faceRect.width - 2 * M0116KeycapMetrics.horizontalPadding } } + + func usesRegularNavigationWidth(_ item: KeycapItem) -> Bool { + switch item.identity { + case .keyCode(KeyboardKeyCode.home.rawValue), + .keyCode(KeyboardKeyCode.end.rawValue), + .keyCode(KeyboardKeyCode.pageUp.rawValue), + .keyCode(KeyboardKeyCode.pageDown.rawValue): + return true + default: + return false + } + } } diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index eee455ae..6f738eee 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -426,6 +426,25 @@ final class KeycapItemFactoryTests: XCTestCase { } } + func testM0116RendererKeepsNavigationKeysAtRegularWidth() throws { + let palette = Self.makePalette(style: .m0116, theme: .white) + let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) + let renderer = M0116KeycapRenderer() + + for keyCode in [KeyboardKeyCode.home, .end, .pageUp, .pageDown] { + let items = KeycapItemFactory.keycapItems( + keyCode: keyCode.rawValue, + legend: EventLegend(text: KeyboardSpecialKeyResolver.specialKey(for: keyCode.rawValue)?.displayText ?? ""), + modifierFlags: [], + isPressed: true, + palette: palette + ) + + let context = KeycapContext(item: try XCTUnwrap(items.first), settings: settings) + XCTAssertEqual(renderer.size(for: context).width, M0116KeycapMetrics.minWidth) + } + } + func testMouseItemUsesPressedStateFromMouseEventType() { let palette = Self.makePalette() From 26b917151ee6d025b631eefdacac696cb1dd4c67 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 27 Aug 2026 02:10:54 +0200 Subject: [PATCH 24/31] feat(keyboard): restore sculpted m0116 keycap geometry --- .../Renderers/M0116/M0116KeycapMetrics.swift | 2 + .../Renderers/M0116/M0116KeycapRenderer.swift | 164 ++++++++++++++++-- 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift index 9340495e..aabbf50c 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapMetrics.swift @@ -19,9 +19,11 @@ enum M0116KeycapMetrics { static let bodyCornerRadius: CGFloat = 9 static let faceCornerRadius: CGFloat = 7 + static let frontLipCornerRadius: CGFloat = 9 static let faceSideInset: CGFloat = 5 static let faceTopInset: CGFloat = 4 static let faceBottomInset: CGFloat = 15 + static let frontLipOverlap: CGFloat = 4 static let horizontalPadding: CGFloat = 11 static let capsLockLabelWidth: CGFloat = 52 static let pageNavigationLabelWidth: CGFloat = 48 diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift index a9dbb557..a506c39b 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift @@ -35,11 +35,8 @@ struct M0116KeycapRenderer: KeycapRendering { yRadius: M0116KeycapMetrics.bodyCornerRadius ) let faceRect = self.faceRect(in: bodyRect) - let facePath = NSBezierPath( - roundedRect: faceRect, - xRadius: M0116KeycapMetrics.faceCornerRadius, - yRadius: M0116KeycapMetrics.faceCornerRadius - ) + let frontLipPath = self.frontLipPath(in: bodyRect, faceRect: faceRect) + let facePath = self.facePath(in: faceRect) NSGraphicsContext.saveGraphicsState() bodyPath.addClip() @@ -48,15 +45,11 @@ struct M0116KeycapRenderer: KeycapRendering { to: NSPoint(x: bodyRect.maxX, y: bodyRect.midY), options: [] ) - appearance.skirtGradient?.draw( - in: NSRect( - x: bodyRect.minX, - y: bodyRect.minY, - width: bodyRect.width, - height: M0116KeycapMetrics.faceBottomInset - ), - angle: 90 - ) + NSGraphicsContext.restoreGraphicsState() + + NSGraphicsContext.saveGraphicsState() + bodyPath.addClip() + appearance.skirtGradient?.draw(in: frontLipPath, angle: 90) NSGraphicsContext.restoreGraphicsState() NSGraphicsContext.saveGraphicsState() @@ -67,6 +60,7 @@ struct M0116KeycapRenderer: KeycapRendering { appearance.creaseColor.setStroke() facePath.lineWidth = StrokeWidth.standard facePath.stroke() + self.drawSideSeams(in: bodyRect, faceRect: faceRect, color: appearance.creaseColor) appearance.bodyStrokeColor.setStroke() bodyPath.lineWidth = StrokeWidth.standard @@ -115,6 +109,133 @@ private extension M0116KeycapRenderer { ) } + func facePath(in faceRect: NSRect) -> NSBezierPath { + let radius = M0116KeycapMetrics.faceCornerRadius + let path = NSBezierPath() + let lowerCornerInset = radius * 0.95 + let lowerVerticalInset = radius * 0.28 + + path.move(to: NSPoint(x: faceRect.minX + radius, y: faceRect.maxY)) + path.line(to: NSPoint(x: faceRect.maxX - radius, y: faceRect.maxY)) + path.curve( + to: NSPoint(x: faceRect.maxX, y: faceRect.maxY - radius), + controlPoint1: NSPoint(x: faceRect.maxX - radius * 0.45, y: faceRect.maxY), + controlPoint2: NSPoint(x: faceRect.maxX, y: faceRect.maxY - radius * 0.45) + ) + path.line(to: NSPoint(x: faceRect.maxX, y: faceRect.minY + lowerVerticalInset)) + path.curve( + to: NSPoint(x: faceRect.maxX - lowerCornerInset, y: faceRect.minY), + controlPoint1: NSPoint(x: faceRect.maxX, y: faceRect.minY + lowerVerticalInset * 0.35), + controlPoint2: NSPoint(x: faceRect.maxX - lowerCornerInset * 0.15, y: faceRect.minY) + ) + path.line(to: NSPoint(x: faceRect.minX + lowerCornerInset, y: faceRect.minY)) + path.curve( + to: NSPoint(x: faceRect.minX, y: faceRect.minY + lowerVerticalInset), + controlPoint1: NSPoint(x: faceRect.minX + lowerCornerInset * 0.15, y: faceRect.minY), + controlPoint2: NSPoint(x: faceRect.minX, y: faceRect.minY + lowerVerticalInset * 0.35) + ) + path.line(to: NSPoint(x: faceRect.minX, y: faceRect.maxY - radius)) + path.curve( + to: NSPoint(x: faceRect.minX + radius, y: faceRect.maxY), + controlPoint1: NSPoint(x: faceRect.minX, y: faceRect.maxY - radius * 0.45), + controlPoint2: NSPoint(x: faceRect.minX + radius * 0.45, y: faceRect.maxY) + ) + path.close() + return path + } + + func frontLipPath(in bodyRect: NSRect, faceRect: NSRect) -> NSBezierPath { + let path = NSBezierPath() + let leftSeam = self.sideSeamCurve( + side: .left, + in: bodyRect, + faceRect: faceRect + ) + let rightSeam = self.sideSeamCurve( + side: .right, + in: bodyRect, + faceRect: faceRect + ) + let topY = faceRect.minY + M0116KeycapMetrics.frontLipOverlap + let leftTopX = faceRect.minX + (M0116KeycapMetrics.faceCornerRadius * 0.45) + let rightTopX = faceRect.maxX - (M0116KeycapMetrics.faceCornerRadius * 0.45) + + path.move(to: NSPoint(x: bodyRect.minX + M0116KeycapMetrics.bodyCornerRadius, y: bodyRect.minY)) + path.line(to: NSPoint(x: bodyRect.maxX - M0116KeycapMetrics.bodyCornerRadius, y: bodyRect.minY)) + path.curve( + to: NSPoint(x: rightSeam.end.x, y: rightSeam.end.y), + controlPoint1: NSPoint(x: bodyRect.maxX, y: bodyRect.minY), + controlPoint2: rightSeam.controlPoint2 + ) + path.line(to: NSPoint(x: rightTopX, y: topY)) + path.line(to: NSPoint(x: leftTopX, y: topY)) + path.line(to: NSPoint(x: leftSeam.end.x, y: leftSeam.end.y)) + path.curve( + to: NSPoint(x: bodyRect.minX + M0116KeycapMetrics.bodyCornerRadius, y: bodyRect.minY), + controlPoint1: leftSeam.controlPoint2, + controlPoint2: NSPoint(x: bodyRect.minX, y: bodyRect.minY) + ) + path.close() + return path + } + + /// The M0116's side walls should be structurally readable on both sides. These seams + /// trace the transition from the inset face down into the front skirt so the lighting + /// can stay directional without making one side look accidentally sharper. + func drawSideSeams(in bodyRect: NSRect, faceRect: NSRect, color: NSColor) { + color.setStroke() + + for side in [KeycapSide.left, .right] { + let seamPath = self.sideSeamPath(side: side, in: bodyRect, faceRect: faceRect) + seamPath.lineWidth = StrokeWidth.standard + seamPath.stroke() + } + } + + func sideSeamPath(side: KeycapSide, in bodyRect: NSRect, faceRect: NSRect) -> NSBezierPath { + let path = NSBezierPath() + let curve = self.sideSeamCurve(side: side, in: bodyRect, faceRect: faceRect) + + path.move(to: curve.start) + path.line(to: curve.curveStart) + path.curve( + to: curve.end, + controlPoint1: curve.controlPoint1, + controlPoint2: curve.controlPoint2 + ) + return path + } + + func sideSeamCurve(side: KeycapSide, in bodyRect: NSRect, faceRect: NSRect) -> SideSeamCurve { + let isLeft = side == .left + let faceX = isLeft ? faceRect.minX : faceRect.maxX + let bodyX = isLeft ? bodyRect.minX : bodyRect.maxX + let start = NSPoint( + x: faceX, + y: faceRect.maxY - (M0116KeycapMetrics.faceCornerRadius * 0.75) + ) + let curveStart = NSPoint( + x: faceX, + y: faceRect.minY + (M0116KeycapMetrics.faceCornerRadius * 0.5) + ) + let end = NSPoint( + x: bodyX, + y: bodyRect.minY + M0116KeycapMetrics.bodyCornerRadius + ) + let controlPoint1 = NSPoint(x: faceX, y: faceRect.minY - 1) + let controlPoint2 = NSPoint( + x: faceX + ((bodyX - faceX) * 0.65), + y: bodyRect.minY + 2 + ) + return SideSeamCurve( + start: start, + curveStart: curveStart, + end: end, + controlPoint1: controlPoint1, + controlPoint2: controlPoint2 + ) + } + /// The recess the cap sits in. It reaches half the item spacing to each side so /// adjacent wells meet, leaving one uninterrupted dark plate between the caps. func drawWell(around rect: NSRect, appearance: KeycapAppearance.M0116) { @@ -252,3 +373,18 @@ private extension M0116KeycapRenderer { } } } + +private extension M0116KeycapRenderer { + enum KeycapSide { + case left + case right + } + + struct SideSeamCurve { + let start: NSPoint + let curveStart: NSPoint + let end: NSPoint + let controlPoint1: NSPoint + let controlPoint2: NSPoint + } +} From b9e0d6c1d3ceea6f303556acefae6bafc9c68724 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 27 Aug 2026 20:24:08 +0200 Subject: [PATCH 25/31] feat(keyboard): clarify Apple visualizer style names --- .../Sources/Keyty/Resources/de.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/en.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/es.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/fr.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/ja.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/nl.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/pl.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/uk.lproj/Localizable.strings | 4 ++-- .../Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings index d93b91f2..380f42d5 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "Gesamtskalierung der dargestellten Tastenkappen."; "keyboard_visualizer.style_label" = "Stil"; "keyboard_visualizer.style_subtitle" = "Allgemeine Form und Oberflächenbehandlung der Tastenkappen."; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimal"; "keyboard_visualizer.style.retro" = "Retro"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "Anzeigedauer"; "keyboard_visualizer.linger_time_subtitle" = "Dauer, für die Tastenkappen vor dem Ausblenden vollständig sichtbar bleiben."; "keyboard_visualizer.linger_time.short" = "Kurz"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings index 6afdd05d..103e1664 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "Overall scale of the rendered keycaps."; "keyboard_visualizer.style_label" = "Style"; "keyboard_visualizer.style_subtitle" = "Overall keycap construction and surface treatment."; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimal"; "keyboard_visualizer.style.retro" = "Retro"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "Linger Time"; "keyboard_visualizer.linger_time_subtitle" = "How long keycaps remain fully visible before fading."; "keyboard_visualizer.linger_time.short" = "Short"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings index 01c2ad95..7867d9c1 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "Escala general de las teclas representadas."; "keyboard_visualizer.style_label" = "Estilo"; "keyboard_visualizer.style_subtitle" = "Diseño general y acabado de la superficie de las teclas."; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimalista"; "keyboard_visualizer.style.retro" = "Retro"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "Tiempo en pantalla"; "keyboard_visualizer.linger_time_subtitle" = "Tiempo que las teclas permanecen totalmente visibles antes de desvanecerse."; "keyboard_visualizer.linger_time.short" = "Corto"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings index 0121678b..6477c408 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "Échelle générale des touches affichées."; "keyboard_visualizer.style_label" = "Style"; "keyboard_visualizer.style_subtitle" = "Forme générale et traitement de surface des touches."; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimal"; "keyboard_visualizer.style.retro" = "Rétro"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "Durée d’affichage"; "keyboard_visualizer.linger_time_subtitle" = "Durée pendant laquelle les touches restent entièrement visibles avant de s’estomper."; "keyboard_visualizer.linger_time.short" = "Courte"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings index 1ba187c0..d6295db2 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "表示するキー全体の大きさです。"; "keyboard_visualizer.style_label" = "スタイル"; "keyboard_visualizer.style_subtitle" = "キーの全体的な形状と表面の仕上げです。"; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "ミニマル"; "keyboard_visualizer.style.retro" = "レトロ"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "表示時間"; "keyboard_visualizer.linger_time_subtitle" = "フェードアウトが始まるまでキーを完全に表示する時間です。"; "keyboard_visualizer.linger_time.short" = "短い"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings index 6d604d68..b9e96c8a 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "Algemene schaal van de weergegeven toetsen."; "keyboard_visualizer.style_label" = "Stijl"; "keyboard_visualizer.style_subtitle" = "Algemene toetsconstructie en oppervlaktebehandeling."; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimaal"; "keyboard_visualizer.style.retro" = "Retro"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "Blijftijd"; "keyboard_visualizer.linger_time_subtitle" = "Hoe lang toetsen volledig zichtbaar blijven voordat ze vervagen."; "keyboard_visualizer.linger_time.short" = "Kort"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings index 9aa75160..a65d6419 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "Ogólna skala renderowanych klawiszy."; "keyboard_visualizer.style_label" = "Styl"; "keyboard_visualizer.style_subtitle" = "Ogólna konstrukcja i wykończenie powierzchni klawiszy."; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimalny"; "keyboard_visualizer.style.retro" = "Retro"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "Czas pozostawania"; "keyboard_visualizer.linger_time_subtitle" = "Jak długo klawisze pozostają w pełni widoczne przed zanikiem."; "keyboard_visualizer.linger_time.short" = "Krótko"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings index 38f0a861..fc36fafb 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/pt-BR.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "Escala geral das teclas renderizadas."; "keyboard_visualizer.style_label" = "Estilo"; "keyboard_visualizer.style_subtitle" = "Construção geral das teclas e tratamento da superfície."; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Minimalista"; "keyboard_visualizer.style.retro" = "Retrô"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "Tempo de Permanência"; "keyboard_visualizer.linger_time_subtitle" = "Por quanto tempo as teclas permanecem totalmente visíveis antes de desaparecer."; "keyboard_visualizer.linger_time.short" = "Curto"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings index 2dd8578a..3016607d 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "Загальний масштаб відображуваних клавіш."; "keyboard_visualizer.style_label" = "Стиль"; "keyboard_visualizer.style_subtitle" = "Загальний вигляд і оздоблення клавіш."; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "Мінімалізм"; "keyboard_visualizer.style.retro" = "Ретро"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "Час відображення"; "keyboard_visualizer.linger_time_subtitle" = "Як довго клавіші залишаються повністю видимими перед зникненням."; "keyboard_visualizer.linger_time.short" = "Короткий"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings index 8e136a22..6057dd67 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings @@ -115,11 +115,11 @@ "keyboard_visualizer.size_subtitle" = "所渲染按键的整体缩放比例。"; "keyboard_visualizer.style_label" = "样式"; "keyboard_visualizer.style_subtitle" = "按键的整体结构和表面效果。"; -"keyboard_visualizer.style.apple" = "Apple"; +"keyboard_visualizer.style.apple" = "Apple Magic Keyboard"; "keyboard_visualizer.style.pbt" = "PBT"; "keyboard_visualizer.style.minimal" = "极简"; "keyboard_visualizer.style.retro" = "复古"; -"keyboard_visualizer.style.m0116" = "M0116"; +"keyboard_visualizer.style.m0116" = "Apple Standard Keyboard"; "keyboard_visualizer.linger_time_label" = "显示时间"; "keyboard_visualizer.linger_time_subtitle" = "按键在淡出前保持完全可见的时间。"; "keyboard_visualizer.linger_time.short" = "短"; From d795579b7d4e26ca858966ad441f5a2a7f577901 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 27 Aug 2026 20:28:37 +0200 Subject: [PATCH 26/31] fix(settings): widen keyboard style picker --- .../Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift | 2 +- Apps/Keyty/Sources/Keyty/Presentation/Tokens/Size.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift index bd955c39..5167e3dd 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift @@ -381,7 +381,7 @@ struct KeyboardSettingsPane: View { } .labelsHidden() .accessibilityLabel(L10n.KeyboardVisualizer.styleLabel) - .frame(width: Size.Control.settingsPickerWidth, alignment: .trailing) + .frame(width: Size.Control.keyboardStylePickerWidth, alignment: .trailing) } } diff --git a/Apps/Keyty/Sources/Keyty/Presentation/Tokens/Size.swift b/Apps/Keyty/Sources/Keyty/Presentation/Tokens/Size.swift index a608c783..9c913c58 100644 --- a/Apps/Keyty/Sources/Keyty/Presentation/Tokens/Size.swift +++ b/Apps/Keyty/Sources/Keyty/Presentation/Tokens/Size.swift @@ -56,6 +56,7 @@ enum Size { static let colorWell = CGSize(width: Spacing.grid(11), height: Spacing.grid(6)) static let numberFieldWidth = Spacing.grid(11) static let settingsPickerWidth = Spacing.grid(45) + static let keyboardStylePickerWidth = Spacing.grid(56) static let themePickerWidth = settingsPickerWidth } } From fa77df5cce13dd3f1cb63857cdf37495a98fd386 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 27 Aug 2026 20:43:00 +0200 Subject: [PATCH 27/31] refactor(keyboard): split keycap appearance theme tokens into separate file --- .../Appearance/KeycapAppearance.swift | 65 +++++++------------ .../Appearance/KeycapThemeTokens.swift | 34 ++++++++++ 2 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapThemeTokens.swift diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift index d8945832..7e34fb7f 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance.swift @@ -8,34 +8,12 @@ import AppKit +/// Exposes the shared visual attributes every keycap appearance variant provides. protocol KeycapAppearanceProviding { var shared: KeycapAppearance.Shared { get } } -struct KeycapThemeTokens { - let swatchColor: NSColor - let textColor: NSColor - - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor - - let badgeFillColor: NSColor - let badgeStrokeColor: NSColor - let badgeHighlightColor: NSColor - let badgeTextColor: NSColor - - let surfaceHighlightColor: NSColor - let surfaceBaseColor: NSColor - let surfaceShadowColor: NSColor - let surfaceBorderColor: NSColor - - let recessColor: NSColor - /// Optional Apple-style underside edge color override. Falls back to `recessColor`. - let undersideEdgeColor: NSColor? - /// Optional Apple-style underside center color override. Falls back to `surfaceBaseColor`. - let undersideCenterColor: NSColor? -} - +/// Enumerates the concrete keycap appearance variants the keyboard visualizer can render. enum KeycapAppearance { case apple(Apple) case pbt(PBT) @@ -47,48 +25,51 @@ enum KeycapAppearance { static let inactiveDotColor = NSColor(calibratedWhite: 0.42, alpha: 0.7) } +// MARK: - Shared Appearance extension KeycapAppearance { + /// Holds the color values reused across all keycap appearance variants. struct Shared { - let textColor: NSColor - let groupBackgroundColor: NSColor - let groupStrokeColor: NSColor let badgeFillColor: NSColor - let badgeStrokeColor: NSColor let badgeHighlightColor: NSColor + let badgeStrokeColor: NSColor let badgeTextColor: NSColor + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + let textColor: NSColor init( - textColor: NSColor, - groupBackgroundColor: NSColor, - groupStrokeColor: NSColor, badgeFillColor: NSColor, - badgeStrokeColor: NSColor, badgeHighlightColor: NSColor, - badgeTextColor: NSColor + badgeStrokeColor: NSColor, + badgeTextColor: NSColor, + groupBackgroundColor: NSColor, + groupStrokeColor: NSColor, + textColor: NSColor ) { - self.textColor = textColor - self.groupBackgroundColor = groupBackgroundColor - self.groupStrokeColor = groupStrokeColor self.badgeFillColor = badgeFillColor - self.badgeStrokeColor = badgeStrokeColor self.badgeHighlightColor = badgeHighlightColor + self.badgeStrokeColor = badgeStrokeColor self.badgeTextColor = badgeTextColor + self.groupBackgroundColor = groupBackgroundColor + self.groupStrokeColor = groupStrokeColor + self.textColor = textColor } init(tokens: KeycapThemeTokens) { self.init( - textColor: tokens.textColor, - groupBackgroundColor: tokens.groupBackgroundColor, - groupStrokeColor: tokens.groupStrokeColor, badgeFillColor: tokens.badgeFillColor, - badgeStrokeColor: tokens.badgeStrokeColor, badgeHighlightColor: tokens.badgeHighlightColor, - badgeTextColor: tokens.badgeTextColor + badgeStrokeColor: tokens.badgeStrokeColor, + badgeTextColor: tokens.badgeTextColor, + groupBackgroundColor: tokens.groupBackgroundColor, + groupStrokeColor: tokens.groupStrokeColor, + textColor: tokens.textColor ) } } } +// MARK: - Shared Accessors extension KeycapAppearance { var shared: Shared { switch self { diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapThemeTokens.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapThemeTokens.swift new file mode 100644 index 00000000..6c756737 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapThemeTokens.swift @@ -0,0 +1,34 @@ +// +// KeycapThemeTokens.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +/// Carries the base colors used to construct a concrete keycap appearance. +struct KeycapThemeTokens { + let groupBackgroundColor: NSColor + let groupStrokeColor: NSColor + + let badgeFillColor: NSColor + let badgeHighlightColor: NSColor + let badgeStrokeColor: NSColor + let badgeTextColor: NSColor + + let surfaceBaseColor: NSColor + let surfaceBorderColor: NSColor + let surfaceHighlightColor: NSColor + let surfaceShadowColor: NSColor + + let recessColor: NSColor + /// Optional Apple-style underside center color override. Falls back to `surfaceBaseColor`. + let undersideCenterColor: NSColor? + /// Optional Apple-style underside edge color override. Falls back to `recessColor`. + let undersideEdgeColor: NSColor? + + let swatchColor: NSColor + let textColor: NSColor +} From f4460a33dbe2a4ed7a84e65233341c3d65b8e49f Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 27 Aug 2026 22:10:31 +0200 Subject: [PATCH 28/31] refactor(keyboard): align keycap shared appearance initializer ordering --- .../Keyboard/Appearance/KeycapAppearance+M0116.swift | 10 +++++----- .../Keyboard/Appearance/KeycapAppearance+Retro.swift | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift index 0b181117..4f67c154 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+M0116.swift @@ -29,13 +29,13 @@ extension KeycapAppearance { let legendTextColor = tokens.textColor.darkened(by: 0.18) self.shared = Shared( - textColor: legendTextColor, - groupBackgroundColor: tokens.groupBackgroundColor, - groupStrokeColor: tokens.groupStrokeColor, badgeFillColor: tokens.badgeFillColor, - badgeStrokeColor: tokens.badgeStrokeColor, badgeHighlightColor: tokens.badgeHighlightColor, - badgeTextColor: tokens.badgeTextColor + badgeStrokeColor: tokens.badgeStrokeColor, + badgeTextColor: tokens.badgeTextColor, + groupBackgroundColor: tokens.groupBackgroundColor, + groupStrokeColor: tokens.groupStrokeColor, + textColor: legendTextColor ) self.wellColor = tokens.recessColor.darkened(by: 0.45) // Lit from the left: bright edge, base across the middle, shaded right edge. diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift index 32067b77..d86942c9 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapAppearance+Retro.swift @@ -39,13 +39,13 @@ extension KeycapAppearance { let faceShadowCenter = tokens.recessColor.darkened(by: 0.12).withAlphaComponent(0.26) self.shared = Shared( - textColor: tokens.textColor, - groupBackgroundColor: groupBackgroundColor, - groupStrokeColor: groupStrokeColor, badgeFillColor: tokens.badgeFillColor, - badgeStrokeColor: tokens.badgeStrokeColor, badgeHighlightColor: tokens.badgeHighlightColor, - badgeTextColor: tokens.badgeTextColor + badgeStrokeColor: tokens.badgeStrokeColor, + badgeTextColor: tokens.badgeTextColor, + groupBackgroundColor: groupBackgroundColor, + groupStrokeColor: groupStrokeColor, + textColor: tokens.textColor ) self.lipGradient = NSGradient(colorsAndLocations: (lipEdge, 0.0), From ddb411d5f45253b71e6e081c2ced989862f04875 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 27 Aug 2026 22:14:15 +0200 Subject: [PATCH 29/31] refactor(keyboard): add explicit keycap theme tokens initializer --- .../Appearance/KeycapThemeTokens.swift | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapThemeTokens.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapThemeTokens.swift index 6c756737..8fb1ef00 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapThemeTokens.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Appearance/KeycapThemeTokens.swift @@ -31,4 +31,38 @@ struct KeycapThemeTokens { let swatchColor: NSColor let textColor: NSColor + + init( + swatchColor: NSColor, + textColor: NSColor, + groupBackgroundColor: NSColor, + groupStrokeColor: NSColor, + badgeFillColor: NSColor, + badgeStrokeColor: NSColor, + badgeHighlightColor: NSColor, + badgeTextColor: NSColor, + surfaceHighlightColor: NSColor, + surfaceBaseColor: NSColor, + surfaceShadowColor: NSColor, + surfaceBorderColor: NSColor, + recessColor: NSColor, + undersideEdgeColor: NSColor?, + undersideCenterColor: NSColor? + ) { + self.groupBackgroundColor = groupBackgroundColor + self.groupStrokeColor = groupStrokeColor + self.badgeFillColor = badgeFillColor + self.badgeHighlightColor = badgeHighlightColor + self.badgeStrokeColor = badgeStrokeColor + self.badgeTextColor = badgeTextColor + self.surfaceBaseColor = surfaceBaseColor + self.surfaceBorderColor = surfaceBorderColor + self.surfaceHighlightColor = surfaceHighlightColor + self.surfaceShadowColor = surfaceShadowColor + self.recessColor = recessColor + self.undersideCenterColor = undersideCenterColor + self.undersideEdgeColor = undersideEdgeColor + self.swatchColor = swatchColor + self.textColor = textColor + } } From 46893d960becfe378d0e780d150babe408706b6f Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 27 Aug 2026 22:31:19 +0200 Subject: [PATCH 30/31] refactor(keyboard): split m0116 side seam paths by side --- .../Renderers/M0116/M0116KeycapRenderer.swift | 113 +++++++++++------- 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift index a506c39b..24c1e18c 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/M0116/M0116KeycapRenderer.swift @@ -146,16 +146,8 @@ private extension M0116KeycapRenderer { func frontLipPath(in bodyRect: NSRect, faceRect: NSRect) -> NSBezierPath { let path = NSBezierPath() - let leftSeam = self.sideSeamCurve( - side: .left, - in: bodyRect, - faceRect: faceRect - ) - let rightSeam = self.sideSeamCurve( - side: .right, - in: bodyRect, - faceRect: faceRect - ) + let leftSeam = self.leftSideSeamCurve(in: bodyRect, faceRect: faceRect) + let rightSeam = self.rightSideSeamCurve(in: bodyRect, faceRect: faceRect) let topY = faceRect.minY + M0116KeycapMetrics.frontLipOverlap let leftTopX = faceRect.minX + (M0116KeycapMetrics.faceCornerRadius * 0.45) let rightTopX = faceRect.maxX - (M0116KeycapMetrics.faceCornerRadius * 0.45) @@ -185,16 +177,18 @@ private extension M0116KeycapRenderer { func drawSideSeams(in bodyRect: NSRect, faceRect: NSRect, color: NSColor) { color.setStroke() - for side in [KeycapSide.left, .right] { - let seamPath = self.sideSeamPath(side: side, in: bodyRect, faceRect: faceRect) - seamPath.lineWidth = StrokeWidth.standard - seamPath.stroke() - } + let leftSeamPath = self.leftSideSeamPath(in: bodyRect, faceRect: faceRect) + leftSeamPath.lineWidth = StrokeWidth.standard + leftSeamPath.stroke() + + let rightSeamPath = self.rightSideSeamPath(in: bodyRect, faceRect: faceRect) + rightSeamPath.lineWidth = StrokeWidth.standard + rightSeamPath.stroke() } - func sideSeamPath(side: KeycapSide, in bodyRect: NSRect, faceRect: NSRect) -> NSBezierPath { + func leftSideSeamPath(in bodyRect: NSRect, faceRect: NSRect) -> NSBezierPath { let path = NSBezierPath() - let curve = self.sideSeamCurve(side: side, in: bodyRect, faceRect: faceRect) + let curve = self.leftSideSeamCurve(in: bodyRect, faceRect: faceRect) path.move(to: curve.start) path.line(to: curve.curveStart) @@ -206,33 +200,65 @@ private extension M0116KeycapRenderer { return path } - func sideSeamCurve(side: KeycapSide, in bodyRect: NSRect, faceRect: NSRect) -> SideSeamCurve { - let isLeft = side == .left - let faceX = isLeft ? faceRect.minX : faceRect.maxX - let bodyX = isLeft ? bodyRect.minX : bodyRect.maxX - let start = NSPoint( - x: faceX, - y: faceRect.maxY - (M0116KeycapMetrics.faceCornerRadius * 0.75) - ) - let curveStart = NSPoint( - x: faceX, - y: faceRect.minY + (M0116KeycapMetrics.faceCornerRadius * 0.5) - ) - let end = NSPoint( - x: bodyX, - y: bodyRect.minY + M0116KeycapMetrics.bodyCornerRadius + func rightSideSeamPath(in bodyRect: NSRect, faceRect: NSRect) -> NSBezierPath { + let path = NSBezierPath() + let curve = self.rightSideSeamCurve(in: bodyRect, faceRect: faceRect) + + path.move(to: curve.start) + path.line(to: curve.curveStart) + path.curve( + to: curve.end, + controlPoint1: curve.controlPoint1, + controlPoint2: curve.controlPoint2 ) - let controlPoint1 = NSPoint(x: faceX, y: faceRect.minY - 1) - let controlPoint2 = NSPoint( - x: faceX + ((bodyX - faceX) * 0.65), - y: bodyRect.minY + 2 + return path + } + + func leftSideSeamCurve(in bodyRect: NSRect, faceRect: NSRect) -> SideSeamCurve { + let faceX = faceRect.minX + let bodyX = bodyRect.minX + return SideSeamCurve( + start: NSPoint( + x: faceX, + y: faceRect.maxY - (M0116KeycapMetrics.faceCornerRadius * 0.75) + ), + curveStart: NSPoint( + x: faceX, + y: faceRect.minY + (M0116KeycapMetrics.faceCornerRadius * 0.5) + ), + end: NSPoint( + x: bodyX, + y: bodyRect.minY + M0116KeycapMetrics.bodyCornerRadius + ), + controlPoint1: NSPoint(x: faceX, y: faceRect.minY - 1), + controlPoint2: NSPoint( + x: faceX + ((bodyX - faceX) * 0.65), + y: bodyRect.minY + 2 + ) ) + } + + func rightSideSeamCurve(in bodyRect: NSRect, faceRect: NSRect) -> SideSeamCurve { + let faceX = faceRect.maxX + let bodyX = bodyRect.maxX return SideSeamCurve( - start: start, - curveStart: curveStart, - end: end, - controlPoint1: controlPoint1, - controlPoint2: controlPoint2 + start: NSPoint( + x: faceX, + y: faceRect.maxY - (M0116KeycapMetrics.faceCornerRadius * 0.75) + ), + curveStart: NSPoint( + x: faceX, + y: faceRect.minY + (M0116KeycapMetrics.faceCornerRadius * 0.5) + ), + end: NSPoint( + x: bodyX, + y: bodyRect.minY + M0116KeycapMetrics.bodyCornerRadius + ), + controlPoint1: NSPoint(x: faceX, y: faceRect.minY - 1), + controlPoint2: NSPoint( + x: faceX + ((bodyX - faceX) * 0.65), + y: bodyRect.minY + 2 + ) ) } @@ -375,11 +401,6 @@ private extension M0116KeycapRenderer { } private extension M0116KeycapRenderer { - enum KeycapSide { - case left - case right - } - struct SideSeamCurve { let start: NSPoint let curveStart: NSPoint From c8bd4c57d739a3a17aa904f99de6cc6f8026f4d9 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 27 Aug 2026 22:38:30 +0200 Subject: [PATCH 31/31] test(keyboard): update m0116 settings color expectations --- .../KeycapItemFactory+Keyboard.swift | 2 +- .../KeyboardVisualizerSettingsTests.swift | 3 ++- .../Keycaps/KeycapItemFactoryTests.swift | 23 ++++++++----------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift index ff0e0d1a..9780dc40 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeycapItems/KeycapItemFactory+Keyboard.swift @@ -62,7 +62,7 @@ extension KeycapItemFactory { let identity = KeycapIdentity.keyCode(KeyboardKeyCode.function.rawValue) return KeycapItem( identity: identity, - legend: .function, + legend: palette.style.presentationPolicy.functionLegend, state: KeycapState(isPressed: isPressed), appearance: palette.appearance(for: identity) ) diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift index 24be5be2..37a98851 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift @@ -356,7 +356,8 @@ final class KeyboardVisualizerSettingsTests: XCTestCase { XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.mediaTheme), KeyboardVisualizerTheme.white.rawValue) XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.mouseTheme), KeyboardVisualizerTheme.white.rawValue) XCTAssertEqual(self.store.integer(forKey: KeyboardVisualizerSettingsKeys.groupBackgroundTheme), KeyboardVisualizerTheme.white.rawValue) - XCTAssertEqual(self.settings.appearance.m0116?.shared.textColor, KeyboardVisualizerTheme.white.tokens.textColor) + XCTAssertNotNil(self.settings.appearance.m0116) + XCTAssertNotEqual(self.settings.appearance.m0116?.shared.textColor, KeyboardVisualizerTheme.white.tokens.textColor) } func testM0116StyleRejectsNonWhiteThemeAssignments() { diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 517b9db7..e04c17ec 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -43,24 +43,21 @@ final class KeycapItemFactoryTests: XCTestCase { } func testM0116FunctionKeyMatchesModifierStyle() { - let m0116Items = KeycapItemFactory.modifierItems( - currentFlags: [.function], - releasedFlags: [], + let m0116Item = KeycapItemFactory.functionItem( + isPressed: true, palette: Self.makePalette(style: .m0116, theme: .white) ) - let appleItems = KeycapItemFactory.modifierItems( - currentFlags: [.function], - releasedFlags: [], + let appleItem = KeycapItemFactory.functionItem( + isPressed: true, palette: Self.makePalette(style: .apple, theme: .black) ) - XCTAssertEqual(m0116Items.count, 1) - XCTAssertEqual(m0116Items.first?.identity, .keyCode(KeyboardKeyCode.function.rawValue)) - XCTAssertEqual(m0116Items.first?.symbol, "") - XCTAssertEqual(m0116Items.first?.label, "fn") - XCTAssertNil(m0116Items.first?.sfSymbolName) - XCTAssertEqual(appleItems.first?.sfSymbolName, "globe") - XCTAssertEqual(appleItems.first?.label, "fn") + XCTAssertEqual(m0116Item.identity, .keyCode(KeyboardKeyCode.function.rawValue)) + XCTAssertEqual(m0116Item.symbol, "") + XCTAssertEqual(m0116Item.label, "fn") + XCTAssertNil(m0116Item.sfSymbolName) + XCTAssertEqual(appleItem.sfSymbolName, "globe") + XCTAssertEqual(appleItem.label, "fn") } func testM0116PresentationPolicyHidesCapsLockDot() {