From bd9a3e01c03cac32ccda1fc0efe1b8a59d2ae549 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 30 Jul 2026 21:36:14 +0200 Subject: [PATCH 1/3] feat(keyboard): add overlay enable toggle --- .../Keyboard/KeyboardSettingsPane.swift | 24 ++++++++++++++++-- .../KeyboardSettingsPaneViewModel.swift | 7 ++++++ .../Keyboard/KeyboardVisualizer.swift | 22 ++++++++++++++++ .../Keyboard/KeyboardVisualizerSettings.swift | 25 +++++++++++++++++++ .../KeyboardVisualizerSettingsKeys.swift | 1 + .../Keyboard/KeyboardVisualizerWindow.swift | 11 ++++++++ .../Resources/de.lproj/Localizable.strings | 6 +++-- .../Resources/en.lproj/Localizable.strings | 6 +++-- .../Resources/es.lproj/Localizable.strings | 6 +++-- .../Resources/fr.lproj/Localizable.strings | 6 +++-- .../Resources/ja.lproj/Localizable.strings | 6 +++-- .../Resources/uk.lproj/Localizable.strings | 6 +++-- .../zh-Hans.lproj/Localizable.strings | 6 +++-- .../KeyboardVisualizerSettingsTests.swift | 22 ++++++++++++++++ 14 files changed, 138 insertions(+), 16 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift index a48db7a..23da775 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift @@ -22,14 +22,29 @@ struct KeyboardSettingsPane: View { SettingsStack { SettingsSectionView( - title: L10n.KeyboardVisualizer.styleSectionTitle, - subtitle: L10n.KeyboardVisualizer.styleSectionSubtitle + title: L10n.KeyboardVisualizer.generalSectionTitle, + subtitle: L10n.KeyboardVisualizer.generalSectionSubtitle ) { + SettingsControlRow( + title: L10n.KeyboardVisualizer.enabledLabel, + subtitle: L10n.KeyboardVisualizer.enabledSubtitle + ) { + Toggle("", isOn: self.$model.isEnabled) + .labelsHidden() + .accessibilityLabel(L10n.KeyboardVisualizer.enabledLabel) + .toggleStyle(.switch) + .controlSize(.small) + } + + Divider() + self.stylePicker + .disabled(!self.model.isEnabled) Divider() self.baseThemePicker + .disabled(!self.model.isEnabled) } if self.model.usesCustomThemePalette { @@ -91,6 +106,7 @@ struct KeyboardSettingsPane: View { selection: self.$model.mouseTheme ) } + .disabled(!self.model.isEnabled) } SettingsSectionView( @@ -143,6 +159,7 @@ struct KeyboardSettingsPane: View { .toggleStyle(.switch) } } + .disabled(!self.model.isEnabled) SettingsSectionView( title: L10n.KeyboardVisualizer.layoutSectionTitle, @@ -166,6 +183,7 @@ struct KeyboardSettingsPane: View { ) } } + .disabled(!self.model.isEnabled) SettingsSectionView( title: L10n.KeyboardVisualizer.historySectionTitle, @@ -190,6 +208,7 @@ struct KeyboardSettingsPane: View { self.axisPicker } } + .disabled(!self.model.isEnabled) SettingsSectionView( title: L10n.KeyboardVisualizer.timingSectionTitle, @@ -223,6 +242,7 @@ struct KeyboardSettingsPane: View { ) } } + .disabled(!self.model.isEnabled) } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift index eee7f13..97d9edf 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPaneViewModel.swift @@ -21,6 +21,10 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { let scaleRange: ClosedRange = 0.5...2.0 let scaleStep: Double = 0.1 + @Published var isEnabled: Bool { + didSet { self.settings.isEnabled = self.isEnabled } + } + @Published var stackAxis: KeyboardVisualizerStackAxis { didSet { self.settings.stackAxis = self.stackAxis } } @@ -118,6 +122,7 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { init(settings: KeyboardVisualizerSettings) { self.settings = settings + self.isEnabled = settings.isEnabled self.stackAxis = settings.stackAxis self.anchor = settings.anchor self.maxCount = settings.maxCount @@ -143,6 +148,7 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { var previewIdentity: String { [ "\(self.style.rawValue)", + "\(self.isEnabled)", "\(self.theme.rawValue)", self.legendColorMode.rawValue, self.customLegendColor.hexString, @@ -166,6 +172,7 @@ final class KeyboardSettingsPaneViewModel: ObservableObject { var previewRenderSettings: KeyboardVisualizerSettings { let settings = KeyboardVisualizerSettings(store: InMemoryKeyValueStore()) settings.registerDefaults() + settings.isEnabled = self.isEnabled settings.style = self.style settings.theme = self.theme settings.legendColorMode = self.legendColorMode diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift index 6e7121c..876fefc 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift @@ -7,6 +7,7 @@ // import AppKit +import Combine final class KeyboardVisualizer { private static let trackedModifierFlags: NSEvent.ModifierFlags = [.command, .shift, .option, .control, .function] @@ -17,6 +18,8 @@ final class KeyboardVisualizer { private var currentModifierFlags: NSEvent.ModifierFlags = [] private var lastModifierFlags: NSEvent.ModifierFlags = [] private var hasPendingGroupBreak = false + + private var cancellables = Set() convenience init() { self.init(store: UserDefaultsStore()) @@ -29,6 +32,12 @@ final class KeyboardVisualizer { self.visualizerWindow.onGroupRemoved = { [weak self] group in self?.eventCoordinator.removeGroup(group) } + settings.isEnabledChanges + .filter { !$0 } + .sink { [weak self] _ in + self?.clearDisplayState() + } + .store(in: &self.cancellables) } func activate() { @@ -36,6 +45,11 @@ final class KeyboardVisualizer { } func display(_ item: DisplayItem) { + guard self.visualizerSettings.isEnabled else { + self.clearDisplayState() + return + } + if item.kind == .flagsChanged { self.currentModifierFlags = item.modifierFlags self.displayModifierPreview(item.modifierFlags) @@ -186,6 +200,14 @@ final class KeyboardVisualizer { self.hasPendingGroupBreak = false } + private func clearDisplayState() { + self.eventCoordinator.reset() + self.currentModifierFlags = [] + self.lastModifierFlags = [] + self.hasPendingGroupBreak = false + self.visualizerWindow.removeAllGroups() + } + private var currentTrackedFlags: NSEvent.ModifierFlags { self.currentModifierFlags.intersection(Self.trackedModifierFlags) } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift index 89ef654..3d15e46 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift @@ -10,6 +10,7 @@ import AppKit import Combine protocol KeyboardVisualizerSettingsProtocol: AnyObject { + var isEnabled: Bool { get set } var stackAxis: KeyboardVisualizerStackAxis { get set } var maxCount: Int { get set } var fadeDelay: CGFloat { get set } @@ -41,7 +42,14 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS static let maxWindowPadding: CGFloat = Spacing.grid(40) let store: KeyValueStore + private var isEnabledSnapshot: Bool? + private let isEnabledChangesSubject = PassthroughSubject() private let placementChangesSubject = PassthroughSubject() + private var storeChangesCancellable: AnyCancellable? + + var isEnabledChanges: AnyPublisher { + self.isEnabledChangesSubject.eraseToAnyPublisher() + } var placementChanges: AnyPublisher { self.placementChangesSubject.eraseToAnyPublisher() @@ -49,12 +57,22 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS init(store: KeyValueStore = UserDefaultsStore()) { self.store = store + self.storeChangesCancellable = self.store.changes + .sink { [weak self] in + self?.storeDidChange() + } + self.isEnabledSnapshot = self.isEnabled } func registerDefaults() { self.registerStoredDefaults() + self.isEnabledSnapshot = self.isEnabled } + /// Whether the keyboard overlay window should render input events. + @Stored(.bool(KeyboardVisualizerSettingsKeys.isEnabled, default: true)) + var isEnabled: Bool + @Stored(.custom( key: KeyboardVisualizerSettingsKeys.axis, default: .vertical, @@ -265,4 +283,11 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS return self.customLegendColor } } + + private func storeDidChange() { + let isEnabled = self.isEnabled + guard isEnabled != self.isEnabledSnapshot else { return } + self.isEnabledSnapshot = isEnabled + self.isEnabledChangesSubject.send(isEnabled) + } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettingsKeys.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettingsKeys.swift index a2b602f..01ffaf8 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettingsKeys.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettingsKeys.swift @@ -12,6 +12,7 @@ enum KeyboardVisualizerSettingsKeys { static let defaultMaxCount = 1 static let minMaxCount = 1 + static let isEnabled = "keyboard_visualizer.isEnabled" static let axis = "keyboard_visualizer.direction" static let maxCount = "keyboard_visualizer.maxCount" static let fadeDelay = "keyboard_visualizer.fadeDelay" diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerWindow.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerWindow.swift index b578401..bc00d48 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerWindow.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerWindow.swift @@ -71,9 +71,20 @@ final class KeyboardVisualizerWindow: NSWindow { self.scheduleFadeOut(for: groupView) } + func removeAllGroups() { + let removedGroups = self.groupViews + self.groupViews.removeAll() + removedGroups.forEach { groupView in + groupView.removeFromSuperview() + self.onGroupRemoved?(groupView) + } + self.layoutGroups() + } + private func scheduleFadeOut(for groupView: KeyboardVisualizerGroupView) { groupView.scheduleFadeOut { [weak self, weak groupView] in guard let self, let groupView else { return } + guard self.groupViews.contains(where: { $0 === groupView }) else { return } self.groupViews.removeAll { $0 === groupView } groupView.removeFromSuperview() self.onGroupRemoved?(groupView) diff --git a/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings index 3b92723..e095af5 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings @@ -67,8 +67,8 @@ /* Keyboard visualizer settings pane */ "keyboard_visualizer.live_overlay_section_title" = "Vorschau"; "keyboard_visualizer.live_overlay_section_subtitle" = "Diese Vorschau verwendet die aktuellen Einstellungen der Tastaturvisualisierung."; -"keyboard_visualizer.style_section_title" = "Stil"; -"keyboard_visualizer.style_section_subtitle" = "Wähle die allgemeine Form und Oberfläche der Tastenkappen."; +"keyboard_visualizer.general_section_title" = "Allgemein"; +"keyboard_visualizer.general_section_subtitle" = "Steuere Sichtbarkeit und Darstellung der Tastatureinblendung."; "keyboard_visualizer.theme_section_title" = "Thema"; "keyboard_visualizer.theme_section_subtitle" = "Weise jedem Tastentyp eine eigene Farbpalette zu."; "keyboard_visualizer.layout_section_title" = "Layout"; @@ -79,6 +79,8 @@ "keyboard_visualizer.timing_section_subtitle" = "Lege fest, wie lange Tasten sichtbar bleiben und wie schnell sie ausgeblendet werden."; "keyboard_visualizer.filter_section_title" = "Filter"; "keyboard_visualizer.filter_section_subtitle" = "Wähle aus, welche Tastaturereignisse in der Einblendung erscheinen."; +"keyboard_visualizer.enabled_label" = "Aktiviert"; +"keyboard_visualizer.enabled_subtitle" = "Sichtbarkeit der Tastatureinblendung."; "keyboard_visualizer.axis_label" = "Achse"; "keyboard_visualizer.axis_subtitle" = "Richtung, in der aufeinanderfolgende Tastengruppen gestapelt werden."; "keyboard_visualizer.anchor_label" = "Position"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings index 592308c..4f762d3 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings @@ -67,8 +67,8 @@ /* Keyboard visualizer settings pane */ "keyboard_visualizer.live_overlay_section_title" = "Live Overlay"; "keyboard_visualizer.live_overlay_section_subtitle" = "This is a real renderer-backed sample using the current keyboard visualizer settings."; -"keyboard_visualizer.style_section_title" = "Style"; -"keyboard_visualizer.style_section_subtitle" = "Choose the overall keycap construction and finish."; +"keyboard_visualizer.general_section_title" = "General"; +"keyboard_visualizer.general_section_subtitle" = "Control keyboard overlay visibility and appearance."; "keyboard_visualizer.theme_section_title" = "Theme"; "keyboard_visualizer.theme_section_subtitle" = "Give each key type its own color palette."; "keyboard_visualizer.layout_section_title" = "Layout"; @@ -79,6 +79,8 @@ "keyboard_visualizer.timing_section_subtitle" = "Balance readability and responsiveness by controlling how long keys remain visible and how quickly they fade."; "keyboard_visualizer.filter_section_title" = "Filter"; "keyboard_visualizer.filter_section_subtitle" = "Choose which keyboard events appear in the overlay."; +"keyboard_visualizer.enabled_label" = "Enabled"; +"keyboard_visualizer.enabled_subtitle" = "Keyboard overlay visibility."; "keyboard_visualizer.axis_label" = "Axis"; "keyboard_visualizer.axis_subtitle" = "Direction used to stack consecutive key groups."; "keyboard_visualizer.anchor_label" = "Position"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings index f41b60c..ff00119 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings @@ -67,8 +67,8 @@ /* Keyboard visualizer settings pane */ "keyboard_visualizer.live_overlay_section_title" = "Vista previa"; "keyboard_visualizer.live_overlay_section_subtitle" = "Esta muestra utiliza el renderizador y los ajustes actuales del visualizador del teclado."; -"keyboard_visualizer.style_section_title" = "Estilo"; -"keyboard_visualizer.style_section_subtitle" = "Elige el diseño y el acabado general de las teclas."; +"keyboard_visualizer.general_section_title" = "General"; +"keyboard_visualizer.general_section_subtitle" = "Controla la visibilidad y la apariencia de la superposición del teclado."; "keyboard_visualizer.theme_section_title" = "Tema"; "keyboard_visualizer.theme_section_subtitle" = "Asigna una paleta de colores distinta a cada tipo de tecla."; "keyboard_visualizer.layout_section_title" = "Disposición"; @@ -79,6 +79,8 @@ "keyboard_visualizer.timing_section_subtitle" = "Ajusta cuánto tiempo permanecen visibles las teclas y la velocidad con la que se desvanecen."; "keyboard_visualizer.filter_section_title" = "Filtro"; "keyboard_visualizer.filter_section_subtitle" = "Elige qué eventos del teclado aparecen en la superposición."; +"keyboard_visualizer.enabled_label" = "Activado"; +"keyboard_visualizer.enabled_subtitle" = "Visibilidad de la superposición del teclado."; "keyboard_visualizer.axis_label" = "Eje"; "keyboard_visualizer.axis_subtitle" = "Dirección en la que se apilan los grupos de teclas consecutivos."; "keyboard_visualizer.anchor_label" = "Posición"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings index ebe741c..c8e8a47 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings @@ -67,8 +67,8 @@ /* Keyboard visualizer settings pane */ "keyboard_visualizer.live_overlay_section_title" = "Aperçu en direct"; "keyboard_visualizer.live_overlay_section_subtitle" = "Cet aperçu utilise le moteur de rendu et les réglages actuels de visualisation du clavier."; -"keyboard_visualizer.style_section_title" = "Style"; -"keyboard_visualizer.style_section_subtitle" = "Choisissez la forme et la finition générales des touches."; +"keyboard_visualizer.general_section_title" = "Général"; +"keyboard_visualizer.general_section_subtitle" = "Contrôlez la visibilité et l’apparence de la superposition du clavier."; "keyboard_visualizer.theme_section_title" = "Thème"; "keyboard_visualizer.theme_section_subtitle" = "Attribuez une palette de couleurs différente à chaque type de touche."; "keyboard_visualizer.layout_section_title" = "Disposition"; @@ -79,6 +79,8 @@ "keyboard_visualizer.timing_section_subtitle" = "Réglez la durée d’affichage des touches et la vitesse de leur disparition."; "keyboard_visualizer.filter_section_title" = "Filtre"; "keyboard_visualizer.filter_section_subtitle" = "Choisissez les événements du clavier à afficher dans la superposition."; +"keyboard_visualizer.enabled_label" = "Activé"; +"keyboard_visualizer.enabled_subtitle" = "Visibilité de la superposition du clavier."; "keyboard_visualizer.axis_label" = "Axe"; "keyboard_visualizer.axis_subtitle" = "Direction d’empilement des groupes de touches successifs."; "keyboard_visualizer.anchor_label" = "Position"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings index bf27c8e..ac6e740 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings @@ -67,8 +67,8 @@ /* Keyboard visualizer settings pane */ "keyboard_visualizer.live_overlay_section_title" = "ライブプレビュー"; "keyboard_visualizer.live_overlay_section_subtitle" = "現在のキーボード表示設定を使用した実際の表示例です。"; -"keyboard_visualizer.style_section_title" = "スタイル"; -"keyboard_visualizer.style_section_subtitle" = "キーの全体的な形状と仕上げを選択します。"; +"keyboard_visualizer.general_section_title" = "一般"; +"keyboard_visualizer.general_section_subtitle" = "キーボードオーバーレイの表示と外観を設定します。"; "keyboard_visualizer.theme_section_title" = "テーマ"; "keyboard_visualizer.theme_section_subtitle" = "キーの種類ごとにカラーパレットを設定します。"; "keyboard_visualizer.layout_section_title" = "レイアウト"; @@ -79,6 +79,8 @@ "keyboard_visualizer.timing_section_subtitle" = "キーを表示する時間とフェードアウトの速さを設定します。"; "keyboard_visualizer.filter_section_title" = "フィルタ"; "keyboard_visualizer.filter_section_subtitle" = "オーバーレイに表示するキーボードイベントを選択します。"; +"keyboard_visualizer.enabled_label" = "有効"; +"keyboard_visualizer.enabled_subtitle" = "キーボードオーバーレイの表示。"; "keyboard_visualizer.axis_label" = "方向"; "keyboard_visualizer.axis_subtitle" = "連続するキーグループを並べる方向です。"; "keyboard_visualizer.anchor_label" = "位置"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings index e00384c..6b92bb0 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings @@ -67,8 +67,8 @@ /* Keyboard visualizer settings pane */ "keyboard_visualizer.live_overlay_section_title" = "Попередній перегляд"; "keyboard_visualizer.live_overlay_section_subtitle" = "Приклад відображення з поточними параметрами візуалізації клавіатури."; -"keyboard_visualizer.style_section_title" = "Стиль"; -"keyboard_visualizer.style_section_subtitle" = "Виберіть загальний вигляд і оздоблення клавіш."; +"keyboard_visualizer.general_section_title" = "Загальні"; +"keyboard_visualizer.general_section_subtitle" = "Керуйте видимістю та виглядом накладки клавіатури."; "keyboard_visualizer.theme_section_title" = "Тема"; "keyboard_visualizer.theme_section_subtitle" = "Призначте власну палітру кольорів кожному типу клавіш."; "keyboard_visualizer.layout_section_title" = "Розташування"; @@ -79,6 +79,8 @@ "keyboard_visualizer.timing_section_subtitle" = "Налаштуйте час відображення клавіш і швидкість їх зникнення."; "keyboard_visualizer.filter_section_title" = "Фільтр"; "keyboard_visualizer.filter_section_subtitle" = "Виберіть події клавіатури, які з’являтимуться в накладці."; +"keyboard_visualizer.enabled_label" = "Увімкнено"; +"keyboard_visualizer.enabled_subtitle" = "Видимість накладки клавіатури."; "keyboard_visualizer.axis_label" = "Вісь"; "keyboard_visualizer.axis_subtitle" = "Напрямок розміщення послідовних груп клавіш."; "keyboard_visualizer.anchor_label" = "Положення"; 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 1c56e08..787b623 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings @@ -67,8 +67,8 @@ /* Keyboard visualizer settings pane */ "keyboard_visualizer.live_overlay_section_title" = "实时预览"; "keyboard_visualizer.live_overlay_section_subtitle" = "此预览使用当前的键盘可视化设置进行实际渲染。"; -"keyboard_visualizer.style_section_title" = "样式"; -"keyboard_visualizer.style_section_subtitle" = "选择按键的整体结构和表面效果。"; +"keyboard_visualizer.general_section_title" = "通用"; +"keyboard_visualizer.general_section_subtitle" = "控制键盘叠加层的可见性和外观。"; "keyboard_visualizer.theme_section_title" = "主题"; "keyboard_visualizer.theme_section_subtitle" = "为每种按键类型设置专属的调色板。"; "keyboard_visualizer.layout_section_title" = "布局"; @@ -79,6 +79,8 @@ "keyboard_visualizer.timing_section_subtitle" = "设置按键保持可见的时间和淡出的速度。"; "keyboard_visualizer.filter_section_title" = "筛选"; "keyboard_visualizer.filter_section_subtitle" = "选择要在叠加层中显示的键盘事件。"; +"keyboard_visualizer.enabled_label" = "已启用"; +"keyboard_visualizer.enabled_subtitle" = "键盘叠加层可见性。"; "keyboard_visualizer.axis_label" = "方向"; "keyboard_visualizer.axis_subtitle" = "连续按键组的堆叠方向。"; "keyboard_visualizer.anchor_label" = "位置"; 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 102413b..d1588aa 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift @@ -6,6 +6,7 @@ // SPDX-License-Identifier: BSD-3-Clause // +import Combine import XCTest @testable import Keyty @@ -28,6 +29,7 @@ final class KeyboardVisualizerSettingsTests: XCTestCase { func testRegistersDefaults() { settings.registerDefaults() + XCTAssertEqual(store.bool(forKey: KeyboardVisualizerSettingsKeys.isEnabled), true) XCTAssertEqual(store.integer(forKey: KeyboardVisualizerSettingsKeys.axis), KeyboardVisualizerStackAxis.vertical.storedValue) XCTAssertEqual(store.integer(forKey: KeyboardVisualizerSettingsKeys.maxCount), KeyboardVisualizerSettingsKeys.defaultMaxCount) XCTAssertEqual(store.double(forKey: KeyboardVisualizerSettingsKeys.fadeDelay), 2.0, accuracy: 0.0001) @@ -55,6 +57,26 @@ final class KeyboardVisualizerSettingsTests: XCTestCase { XCTAssertEqual(settings.stackAxis, .horizontal) } + func testPersistsIsEnabled() { + settings.isEnabled = false + + XCTAssertFalse(settings.isEnabled) + XCTAssertFalse(store.bool(forKey: KeyboardVisualizerSettingsKeys.isEnabled)) + } + + func testPublishesIsEnabledChanges() { + settings.registerDefaults() + var receivedValues: [Bool] = [] + let cancellable = settings.isEnabledChanges.sink { value in + receivedValues.append(value) + } + + settings.isEnabled = false + + XCTAssertEqual(receivedValues, [false]) + cancellable.cancel() + } + func testPersistsScale() { settings.scale = 1.5 From 4e2fcb0b3939609ddbd47a83d0567b131b78580b Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 30 Jul 2026 21:43:24 +0200 Subject: [PATCH 2/3] refactor(keyboard): share visualizer settings instance --- .../App/Composition/AppServiceContainer.swift | 2 +- .../Keyboard/KeyboardVisualizer.swift | 10 +++++--- .../Keyboard/KeyboardVisualizerSettings.swift | 25 ++++++++----------- .../KeyboardVisualizerSettingsTests.swift | 13 ++++++++++ 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/App/Composition/AppServiceContainer.swift b/Apps/Keyty/Sources/Keyty/App/Composition/AppServiceContainer.swift index 6be7c80..d4c7c6b 100644 --- a/Apps/Keyty/Sources/Keyty/App/Composition/AppServiceContainer.swift +++ b/Apps/Keyty/Sources/Keyty/App/Composition/AppServiceContainer.swift @@ -24,7 +24,7 @@ final class AppServiceContainer { pointerRingSettings: settings.pointerRingSettings, pointerIconSettings: settings.pointerIconSettings ) - let keyboardVisualizer = KeyboardVisualizer(store: settings.store) + let keyboardVisualizer = KeyboardVisualizer(settings: settings.keyboardVisualizerSettings) keyboardVisualizer.activate() let permissionsService = SystemPermissionsService() let captureController = CaptureController( diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift index 876fefc..6431d79 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizer.swift @@ -15,18 +15,20 @@ final class KeyboardVisualizer { private let visualizerSettings: KeyboardVisualizerSettings private let visualizerWindow: KeyboardVisualizerWindow private let eventCoordinator = KeycapEventCoordinator() + private var cancellables = Set() private var currentModifierFlags: NSEvent.ModifierFlags = [] private var lastModifierFlags: NSEvent.ModifierFlags = [] private var hasPendingGroupBreak = false - - private var cancellables = Set() convenience init() { self.init(store: UserDefaultsStore()) } - init(store: KeyValueStore) { - let settings = KeyboardVisualizerSettings(store: store) + convenience init(store: KeyValueStore) { + self.init(settings: KeyboardVisualizerSettings(store: store)) + } + + init(settings: KeyboardVisualizerSettings) { self.visualizerSettings = settings self.visualizerWindow = KeyboardVisualizerWindow(settings: settings) self.visualizerWindow.onGroupRemoved = { [weak self] group in diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift index 3d15e46..657d5cf 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Keyboard/KeyboardVisualizerSettings.swift @@ -42,10 +42,8 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS static let maxWindowPadding: CGFloat = Spacing.grid(40) let store: KeyValueStore - private var isEnabledSnapshot: Bool? private let isEnabledChangesSubject = PassthroughSubject() private let placementChangesSubject = PassthroughSubject() - private var storeChangesCancellable: AnyCancellable? var isEnabledChanges: AnyPublisher { self.isEnabledChangesSubject.eraseToAnyPublisher() @@ -57,21 +55,24 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS init(store: KeyValueStore = UserDefaultsStore()) { self.store = store - self.storeChangesCancellable = self.store.changes - .sink { [weak self] in - self?.storeDidChange() - } - self.isEnabledSnapshot = self.isEnabled } func registerDefaults() { self.registerStoredDefaults() - self.isEnabledSnapshot = self.isEnabled } /// Whether the keyboard overlay window should render input events. @Stored(.bool(KeyboardVisualizerSettingsKeys.isEnabled, default: true)) - var isEnabled: Bool + private var storedIsEnabled: Bool + + var isEnabled: Bool { + get { self.storedIsEnabled } + set { + guard newValue != self.storedIsEnabled else { return } + self.storedIsEnabled = newValue + self.isEnabledChangesSubject.send(newValue) + } + } @Stored(.custom( key: KeyboardVisualizerSettingsKeys.axis, @@ -284,10 +285,4 @@ final class KeyboardVisualizerSettings: KeyboardVisualizerSettingsProtocol, HasS } } - private func storeDidChange() { - let isEnabled = self.isEnabled - guard isEnabled != self.isEnabledSnapshot else { return } - self.isEnabledSnapshot = isEnabled - self.isEnabledChangesSubject.send(isEnabled) - } } 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 d1588aa..226d372 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSettingsTests.swift @@ -77,6 +77,19 @@ final class KeyboardVisualizerSettingsTests: XCTestCase { cancellable.cancel() } + func testDoesNotPublishIsEnabledWhenValueIsUnchanged() { + settings.registerDefaults() + var receivedValues: [Bool] = [] + let cancellable = settings.isEnabledChanges.sink { value in + receivedValues.append(value) + } + + settings.isEnabled = true + + XCTAssertTrue(receivedValues.isEmpty) + cancellable.cancel() + } + func testPersistsScale() { settings.scale = 1.5 From 57b306ab50516fcd339cafdedf2400cb2425c2ec Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Thu, 30 Jul 2026 21:46:13 +0200 Subject: [PATCH 3/3] style(keyboard): normalize settings switch sizes --- .../Features/Settings/Keyboard/KeyboardSettingsPane.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift index 23da775..304dd77 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Settings/Keyboard/KeyboardSettingsPane.swift @@ -121,6 +121,7 @@ struct KeyboardSettingsPane: View { .labelsHidden() .accessibilityLabel(L10n.KeyboardVisualizer.onlyShowModifiedKeystrokesLabel) .toggleStyle(.switch) + .controlSize(.small) } Divider() @@ -133,6 +134,7 @@ struct KeyboardSettingsPane: View { .labelsHidden() .accessibilityLabel(L10n.KeyboardVisualizer.showSpecialKeysLabel) .toggleStyle(.switch) + .controlSize(.small) } Divider() @@ -145,6 +147,7 @@ struct KeyboardSettingsPane: View { .labelsHidden() .accessibilityLabel(L10n.KeyboardVisualizer.showMediaKeyButtonsLabel) .toggleStyle(.switch) + .controlSize(.small) } Divider() @@ -157,6 +160,7 @@ struct KeyboardSettingsPane: View { .labelsHidden() .accessibilityLabel(L10n.KeyboardVisualizer.showMouseEventsLabel) .toggleStyle(.switch) + .controlSize(.small) } } .disabled(!self.model.isEnabled)