diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapMetrics.swift new file mode 100644 index 00000000..951c78cc --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapMetrics.swift @@ -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) +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/AppleKeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapRenderer.swift similarity index 100% rename from Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/AppleKeycapRenderer.swift rename to Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Apple/AppleKeycapRenderer.swift diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/CommonKeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/CommonKeycapMetrics.swift new file mode 100644 index 00000000..50747b6b --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/CommonKeycapMetrics.swift @@ -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, + ] +} 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 543923b3..0816cb92 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapContext.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapContext.swift @@ -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! } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapMetrics.swift deleted file mode 100644 index a3c29a04..00000000 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapMetrics.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// KeycapMetrics.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, - ] -} - -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) -} - -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 -} - -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 -} - -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) -} - -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 - } -} 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 new file mode 100644 index 00000000..d342d53e --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/KeycapStyle+SizeNormalization.swift @@ -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 + } +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapMetrics.swift new file mode 100644 index 00000000..6364129b --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapMetrics.swift @@ -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 +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/MinimalKeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapRenderer.swift similarity index 100% rename from Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/MinimalKeycapRenderer.swift rename to Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Minimal/MinimalKeycapRenderer.swift diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapMetrics.swift new file mode 100644 index 00000000..fd70f142 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapMetrics.swift @@ -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) +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBTKeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapRenderer.swift similarity index 100% rename from Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBTKeycapRenderer.swift rename to Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/PBT/PBTKeycapRenderer.swift diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapMetrics.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapMetrics.swift new file mode 100644 index 00000000..5ca5b2a7 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapMetrics.swift @@ -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 +} diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/RetroKeycapRenderer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapRenderer.swift similarity index 100% rename from Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/RetroKeycapRenderer.swift rename to Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/Renderers/Retro/RetroKeycapRenderer.swift