Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// AppleKeycapMetrics.swift
// Keyty
//
// SPDX-FileCopyrightText: 2026 Serhii Bykov
// SPDX-License-Identifier: BSD-3-Clause
//

import AppKit

enum AppleKeycapMetrics {
static let height: CGFloat = 74
static let minWidth: CGFloat = 74
static let itemSpacing: CGFloat = 6
static let groupPadding = NSEdgeInsets(top: 10, left: 10, bottom: 18, right: 10)
static let repeatBadgeInset = CGPoint(x: 6, y: 6)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// CommonKeycapMetrics.swift
// Keyty
//
// SPDX-FileCopyrightText: 2026 Serhii Bykov
// SPDX-License-Identifier: BSD-3-Clause
//

import AppKit

enum CommonKeycapMetrics {
static let horizontalPadding: CGFloat = 10
static let symbolFont = NSFont.systemFont(ofSize: 22, weight: .medium)
static let labelFont = NSFont.systemFont(ofSize: 14, weight: .medium)
static let charFont = NSFont.systemFont(ofSize: 24, weight: .medium)

/// Shared keycap widths for left and right variants of each modifier kind.
static let fixedModifierWidths: [KeyboardModifierKey.Kind: CGFloat] = [
.command: 88,
.option: 72,
.shift: 100,
]

/// Fixed widths for individual key identities whose legends need custom sizing.
static let fixedIdentityWidths: [KeycapIdentity: CGFloat] = [
.keyCode(KeyboardKeyCode.tab.rawValue): 112,
.keyCode(KeyboardKeyCode.escape.rawValue): 112,
.keyCode(KeyboardKeyCode.delete.rawValue): 112,
.keyCode(KeyboardKeyCode.forwardDelete.rawValue): 112,
.keyCode(KeyboardKeyCode.returnKey.rawValue): 128,
.keyCode(KeyboardKeyCode.keypadEnter.rawValue): 128,
.keyCode(KeyboardKeyCode.capsLock.rawValue): 144,
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,40 @@
struct KeycapContext {
let item: KeycapItem
let settings: KeyboardVisualizerSettings
}

extension KeycapContext {
var appearance: KeycapAppearance {
item.appearance
self.item.appearance
}
}

extension KeycapContext {
var appleAppearance: KeycapAppearance.Apple {
if let appearance = self.appearance.apple {
return appearance
}
return settings.theme.appearance(for: .apple).apple!
return self.settings.theme.appearance(for: .apple).apple!
}

var pbtAppearance: KeycapAppearance.PBT {
if let appearance = self.appearance.pbt {
return appearance
}
return settings.theme.appearance(for: .pbt).pbt!
return self.settings.theme.appearance(for: .pbt).pbt!
}

var minimalAppearance: KeycapAppearance.Minimal {
if let appearance = self.appearance.minimal {
return appearance
}
return settings.theme.appearance(for: .minimal).minimal!
return self.settings.theme.appearance(for: .minimal).minimal!
}

var retroAppearance: KeycapAppearance.Retro {
if let appearance = self.appearance.retro {
return appearance
}
return settings.theme.appearance(for: .retro).retro!
return self.settings.theme.appearance(for: .retro).retro!
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// KeycapStyle+SizeNormalization.swift
// Keyty
//
// SPDX-FileCopyrightText: 2026 Serhii Bykov
// SPDX-License-Identifier: BSD-3-Clause
//

import AppKit

extension KeycapStyle {
/// Base (unscaled) group-box height for the style: the keycap height plus the
/// top and bottom of its group padding.
var baseGroupHeight: CGFloat {
switch self {
case .apple:
return AppleKeycapMetrics.height
+ AppleKeycapMetrics.groupPadding.top + AppleKeycapMetrics.groupPadding.bottom
case .pbt:
return AppleKeycapMetrics.height + 2 * PBTKeycapMetrics.rim
+ PBTKeycapMetrics.groupPadding.top + PBTKeycapMetrics.groupPadding.bottom
case .minimal:
return MinimalKeycapMetrics.height
+ MinimalKeycapMetrics.groupPadding.top + MinimalKeycapMetrics.groupPadding.bottom
case .retro:
return RetroKeycapMetrics.height
+ RetroKeycapMetrics.groupPadding.top + RetroKeycapMetrics.groupPadding.bottom
}
}

/// Uniform factor that normalizes each style's base box height to Apple's, so the size
/// slider produces a comparable rendered size across all styles at any scale.
var sizeNormalization: CGFloat {
KeycapStyle.apple.baseGroupHeight / baseGroupHeight
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// MinimalKeycapMetrics.swift
// Keyty
//
// SPDX-FileCopyrightText: 2026 Serhii Bykov
// SPDX-License-Identifier: BSD-3-Clause
//

import AppKit

enum MinimalKeycapMetrics {
static let height: CGFloat = 48
static let minWidth: CGFloat = 28
static let horizontalPadding: CGFloat = 4
static let itemSpacing: CGFloat = -8
static let groupPadding = NSEdgeInsets(top: 8, left: 14, bottom: 8, right: 14)
static let repeatBadgeInset = CGPoint(x: 0, y: 0)
static let labelFont = NSFont.systemFont(ofSize: 26, weight: .medium)
static let symbolFont = NSFont.systemFont(ofSize: 36, weight: .medium)
static let imageMaxHeight: CGFloat = 36
static let pressedScale: CGFloat = 0.78
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// PBTKeycapMetrics.swift
// Keyty
//
// SPDX-FileCopyrightText: 2026 Serhii Bykov
// SPDX-License-Identifier: BSD-3-Clause
//

import AppKit

enum PBTKeycapMetrics {
/// Rim added around the content for the PBT style. The dished top surface ends up the
/// same size as an Apple keycap; the body extends beyond it by this amount on each side.
static let rim: CGFloat = 13
static let dishLift: CGFloat = 8
static let cornerRadius: CGFloat = 18
static let dishCornerRadius: CGFloat = 10
static let pressedTravel: CGFloat = 2.5
static let bodyInset: CGFloat = 2
static let groupPadding = NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
static let repeatBadgeInset = CGPoint(x: 6, y: 6)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// RetroKeycapMetrics.swift
// Keyty
//
// SPDX-FileCopyrightText: 2026 Serhii Bykov
// SPDX-License-Identifier: BSD-3-Clause
//

import AppKit

enum RetroKeycapMetrics {
static let height: CGFloat = 96
static let bodyCornerRadius: CGFloat = 24
static let faceCornerRadius: CGFloat = 20
static let faceSideInset: CGFloat = 4
static let extraWidth: CGFloat = 8
static let faceTopInset: CGFloat = 7
static let faceBottomInset: CGFloat = 14
static let minWidth: CGFloat = 96
static let horizontalPadding: CGFloat = 24
static let itemSpacing: CGFloat = 10
static let groupPadding = NSEdgeInsets(top: 12, left: 12, bottom: 16, right: 12)
static let repeatBadgeInset = CGPoint(x: 6, y: 6)
static let pressedTravel: CGFloat = 3
}
Loading