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
Expand Up @@ -314,7 +314,7 @@ struct KeyboardSettingsPane: View {
) {
self.colorMenuItem(
title: L10n.KeyboardVisualizer.Color.automatic,
swatchColor: self.model.theme.displayColor,
swatchColor: self.model.style.sanitize(theme: self.model.theme).displayColor,
tag: KeyboardSettingsPaneViewModel.ColorPreset.automaticSelectionID
)

Expand Down Expand Up @@ -353,7 +353,7 @@ struct KeyboardSettingsPane: View {
selection: Binding<KeyboardVisualizerTheme>
) -> some View {
SettingsControlRow(title: title, subtitle: subtitle) {
Picker("", selection: selection) {
Picker("", selection: self.compatibleThemeBinding(selection)) {
ForEach(Array(self.model.allowedThemeSections.enumerated()), id: \.offset) { index, section in
if index > 0 {
Divider()
Expand All @@ -370,6 +370,18 @@ struct KeyboardSettingsPane: View {
}
}

private func compatibleThemeBinding(
_ selection: Binding<KeyboardVisualizerTheme>
) -> Binding<KeyboardVisualizerTheme> {
Binding(
get: { self.model.style.sanitize(theme: selection.wrappedValue) },
set: { theme in
guard self.model.style.allows(theme: theme), self.model.style != .m0116 else { return }
selection.wrappedValue = theme
}
)
}

private func themeRow(_ theme: KeyboardVisualizerTheme) -> some View {
HStack(spacing: Spacing.none) {
SwiftUI.Image(nsImage: theme.displayColor.swatchImage(trailingPadding: Spacing.xxs))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,7 @@ final class KeyboardSettingsPaneViewModel: ObservableObject {
}

@Published var theme: KeyboardVisualizerTheme {
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
}
didSet { self.settings.theme = self.theme }
}

@Published var legendColorMode: KeyboardLegendColorMode {
Expand All @@ -76,76 +68,41 @@ final class KeyboardSettingsPaneViewModel: ObservableObject {
/// Drives the base theme picker. Picking a concrete theme turns custom mode
/// off and sets the base `theme`; picking `.custom` turns custom mode on.
var themeSelection: ThemeSelection {
get { self.usesCustomThemePalette ? .custom : .theme(self.theme) }
get {
self.usesCustomThemePalette
? .custom
: .theme(self.style.sanitize(theme: self.theme))
}
set {
switch newValue {
case .custom:
self.usesCustomThemePalette = true
case .theme(let theme):
guard self.style.allows(theme: theme), self.style != .m0116 else { return }
self.usesCustomThemePalette = false
self.theme = theme
}
}
}

@Published var modifierTheme: KeyboardVisualizerTheme {
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
}
didSet { self.settings.modifierTheme = self.modifierTheme }
}

@Published var specialTheme: KeyboardVisualizerTheme {
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
}
didSet { self.settings.specialTheme = self.specialTheme }
}

@Published var mediaTheme: KeyboardVisualizerTheme {
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
}
didSet { self.settings.mediaTheme = self.mediaTheme }
}

@Published var mouseTheme: KeyboardVisualizerTheme {
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
}
didSet { self.settings.mouseTheme = self.mouseTheme }
}

@Published var groupBackgroundTheme: KeyboardVisualizerTheme {
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
}
didSet { self.settings.groupBackgroundTheme = self.groupBackgroundTheme }
}

@Published var style: KeycapStyle {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS
private var storedTheme: KeyboardVisualizerTheme

var theme: KeyboardVisualizerTheme {
get { self.style.sanitize(theme: self.storedTheme) }
set { self.storedTheme = self.style.sanitize(theme: newValue) }
get { self.storedTheme }
set { self.storedTheme = newValue }
}

/// Whether keycap legend drawing should use the theme-default color or a custom override.
Expand All @@ -218,40 +218,40 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS
private var storedModifierTheme: KeyboardVisualizerTheme

var modifierTheme: KeyboardVisualizerTheme {
get { self.style.sanitize(theme: self.storedModifierTheme) }
set { self.storedModifierTheme = self.style.sanitize(theme: newValue) }
get { self.storedModifierTheme }
set { self.storedModifierTheme = newValue }
}

@Stored(.enum(KeyboardVisualizerSettingsKeys.specialTheme, default: .black))
private var storedSpecialTheme: KeyboardVisualizerTheme

var specialTheme: KeyboardVisualizerTheme {
get { self.style.sanitize(theme: self.storedSpecialTheme) }
set { self.storedSpecialTheme = self.style.sanitize(theme: newValue) }
get { self.storedSpecialTheme }
set { self.storedSpecialTheme = newValue }
}

@Stored(.enum(KeyboardVisualizerSettingsKeys.mediaTheme, default: .black))
private var storedMediaTheme: KeyboardVisualizerTheme

var mediaTheme: KeyboardVisualizerTheme {
get { self.style.sanitize(theme: self.storedMediaTheme) }
set { self.storedMediaTheme = self.style.sanitize(theme: newValue) }
get { self.storedMediaTheme }
set { self.storedMediaTheme = newValue }
}

@Stored(.enum(KeyboardVisualizerSettingsKeys.mouseTheme, default: .black))
private var storedMouseTheme: KeyboardVisualizerTheme

var mouseTheme: KeyboardVisualizerTheme {
get { self.style.sanitize(theme: self.storedMouseTheme) }
set { self.storedMouseTheme = self.style.sanitize(theme: newValue) }
get { self.storedMouseTheme }
set { self.storedMouseTheme = newValue }
}

@Stored(.enum(KeyboardVisualizerSettingsKeys.groupBackgroundTheme, default: .black))
private var storedGroupBackgroundTheme: KeyboardVisualizerTheme

var groupBackgroundTheme: KeyboardVisualizerTheme {
get { self.style.sanitize(theme: self.storedGroupBackgroundTheme) }
set { self.storedGroupBackgroundTheme = self.style.sanitize(theme: newValue) }
get { self.storedGroupBackgroundTheme }
set { self.storedGroupBackgroundTheme = newValue }
}

@Stored(.enum(KeyboardVisualizerSettingsKeys.anchor, default: .default))
Expand Down Expand Up @@ -350,11 +350,7 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS

/// Visual style of the rendered keycaps.
@Stored(.enum(KeyboardVisualizerSettingsKeys.style, default: .default))
var style: KeycapStyle {
didSet {
self.sanitizeThemesForStyle()
}
}
var style: KeycapStyle

/// Whether only keystrokes pressed with modifiers should be rendered.
@Stored(.bool(KeyboardVisualizerSettingsKeys.onlyShowModifiedKeystrokes, default: false))
Expand All @@ -377,19 +373,19 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS
var showMouseEvents: Bool

var themeTokens: KeycapThemeTokens {
self.theme.tokens(legendColorOverride: self.resolvedLegendColorOverride)
self.effectiveTheme(self.theme).tokens(legendColorOverride: self.resolvedLegendColorOverride)
}

var appearance: KeycapAppearance {
self.theme.appearance(for: self.style, legendColorOverride: self.resolvedLegendColorOverride)
self.effectiveTheme(self.theme).appearance(for: self.style, legendColorOverride: self.resolvedLegendColorOverride)
}

/// Resolves per-key-type appearance. When `usesCustomThemePalette` is off, every category
/// (and the group background) collapses to the base `theme` — identical to legacy behavior.
var palette: KeycapThemePalette {
let base = self.theme
let base = self.effectiveTheme(self.theme)
func resolve(_ specific: KeyboardVisualizerTheme) -> KeyboardVisualizerTheme {
self.usesCustomThemePalette ? specific : base
self.usesCustomThemePalette ? self.effectiveTheme(specific) : base
}
return KeycapThemePalette(
style: self.style,
Expand Down Expand Up @@ -423,13 +419,8 @@ 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)
private func effectiveTheme(_ theme: KeyboardVisualizerTheme) -> KeyboardVisualizerTheme {
self.style.sanitize(theme: theme)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,34 @@ extension KeycapContext {
if let appearance = self.appearance.apple {
return appearance
}
return self.settings.theme.appearance(for: .apple).apple!
return self.settings.appearance.apple!
}

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

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

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

var m0116Appearance: KeycapAppearance.M0116 {
if let appearance = self.appearance.m0116 {
return appearance
}
return self.settings.theme.appearance(for: .m0116).m0116!
return self.settings.appearance.m0116!
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ final class KeyboardVisualizerSettingsTests: XCTestCase {
}


func testM0116StyleClampsThemesToWhite() {
func testM0116StyleUsesWhiteWithoutOverwritingSavedThemes() {
self.settings.theme = .black
self.settings.modifierTheme = .purple
self.settings.specialTheme = .rose
Expand All @@ -365,29 +365,19 @@ final class KeyboardVisualizerSettingsTests: XCTestCase {
self.settings.groupBackgroundTheme = .orange
self.settings.style = .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.theme, .black)
XCTAssertEqual(self.settings.modifierTheme, .purple)
XCTAssertEqual(self.settings.specialTheme, .rose)
XCTAssertEqual(self.settings.mediaTheme, .blue)
XCTAssertEqual(self.settings.mouseTheme, .green)
XCTAssertEqual(self.settings.groupBackgroundTheme, .orange)
XCTAssertEqual(self.settings.themeTokens.surfaceBaseColor, KeyboardVisualizerTheme.white.tokens.surfaceBaseColor)
XCTAssertNotNil(self.settings.appearance.m0116)
XCTAssertNotEqual(self.settings.appearance.m0116?.shared.textColor, KeyboardVisualizerTheme.white.tokens.textColor)
}

func testM0116StyleRejectsNonWhiteThemeAssignments() {
self.settings.style = .m0116

self.settings.theme = .black
self.settings.modifierTheme = .purple
self.settings.style = .apple

XCTAssertEqual(self.settings.theme, .white)
XCTAssertEqual(self.settings.modifierTheme, .white)
XCTAssertEqual(self.settings.theme, .black)
XCTAssertEqual(self.settings.modifierTheme, .purple)
}
}
Loading