[PR #355] Let visualizers draw keystrokes in a user-chosen font #355

Open
opened 2026-10-02 23:09:26 -06:00 by gitea-mirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/keycastr/keycastr/pull/355
Author: @anandghegde
Created: 9/19/2026
Status: 🔄 Open

Base: main ← Head: feature/selectable-visualizer-font


📝 Commits (1)

  • 39df5e7 Let visualizers draw keystrokes in a user-chosen font

📊 Changes

14 files changed (+444 additions, -28 deletions)

View changed files

📝 keycastr/KCDefaultVisualizer.m (+2 -1)
📝 keycastr/KCDefaultVisualizer.nib/designable.nib (+34 -7)
📝 keycastr/KCDefaultVisualizer.nib/keyedobjects.nib (+0 -0)
➕ keycastr/KCFontPopUpButton.h (+45 -0)
➕ keycastr/KCFontPopUpButton.m (+115 -0)
➕ keycastr/KCVisualizerTests/KCFontSelectionTests.m (+121 -0)
📝 keycastr/KeyCastr.xcodeproj/project.pbxproj (+12 -0)
📝 keycastr/Minimal.xib (+39 -12)
📝 keycastr/MinimalVisualizer.m (+2 -1)
📝 keycastr/NSUserDefaults+Utility.h (+5 -0)
📝 keycastr/NSUserDefaults+Utility.m (+24 -0)
📝 keycastr/Svelte.nib/designable.nib (+29 -2)
📝 keycastr/Svelte.nib/keyedobjects.nib (+0 -0)
📝 keycastr/SvelteVisualizer.m (+16 -5)

📄 Description

Closes #169.

Every bundled visualizer already lets you pick a font size; the typeface has always been hardcoded to the system font. The request in #169 is for fixed-width — I vs l are indistinguishable in the system font, which matters a lot when you're recording a tutorial — but it's just as useful for matching a visualizer to a theme.

What this adds

A Font pop-up in each of the three bundled preference panes, directly above the existing Font Size control (Svelte has no size control, so it sits under "Display All Keystrokes"). Each pane stores its own choice:

Visualizer Key
Default default.fontName
Minimal minimal.fontName
Svelte svelte.fontName

The menu lists every installed family, each drawn in its own typeface, so it reads as a specimen sheet rather than a wall of identical text. The first item is "System Font", which is the default and restores today's behaviour exactly.

How it's put together

KCFontPopUpButton is a small NSPopUpButton subclass that populates itself from NSFontManager and reads/writes whichever defaults key the nib hands it via fontFamilyDefaultsKey (a user-defined runtime attribute). One class covers all three panes, and any future visualizer gets font selection by dropping the control into its nib and setting that one attribute.

Font resolution is -[NSUserDefaults fontForKey:size:bold:], sitting next to the existing colorForKey: / imageForKey: helpers. It always returns something drawable:

  • nothing stored, or the empty string → the system font
  • stored family no longer installed (uninstalled, or disabled in Font Book) → the system font, without clearing the stored value, so reinstalling the font brings the selection back
  • bold requested but the family has no bold face → the family's regular weight, rather than substituting a different family to honour boldness

Minimal is the only visualizer that asks for bold, matching its current boldSystemFontOfSize:.

Two judgement calls worth flagging

Svelte applies the font to the key label only, not the modifier strip above it. That strip is ⌘⌥⇧-style iconography and reads better kept in the system font; a decorative display face makes it much harder to parse at a glance.

Svelte's shrink-to-fit loop gained a minimum size. It previously ran while (size.width > frame.size.width - 10) fontSize -= 1.0; with no floor. That was unreachable with the system font, but a user-selectable wide face makes it reachable, so it now stops at 8pt.

Tests

New KCFontSelectionTests covers the resolution rules (unset, empty, installed, bold fallback, uninstalled family) and an integration test that instantiates the Default and Svelte preference views from their nibs and asserts each one has a font pop-up bound to its own key with a populated menu. That last one is what proves the nib wiring actually survives — the legacy wrapper.nib bundles can't be diffed usefully by eye.

To make the helper testable, NSUserDefaults+Utility.h is now a public header of the KCVisualizer framework.

Minimal isn't in that integration test because only KCDefaultVisualizer.kcplugin and Svelte.kcplugin are copied into the test bundle's plug-ins. Its pane is a plain xib and gets validated when the project builds; I verified all three panes' computed layouts by hand, including re-seating the separator boxes in Default and Minimal, which use autoresizing rather than autolayout and so don't move with the rest of the pane.

Both wrapper.nib bundles were recompiled with ibtool and produce no errors or warnings. The full test suite passes.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/keycastr/keycastr/pull/355 **Author:** [@anandghegde](https://github.com/anandghegde) **Created:** 9/19/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `feature/selectable-visualizer-font` --- ### 📝 Commits (1) - [`39df5e7`](https://github.com/keycastr/keycastr/commit/39df5e7c97d7d809dbec0aa3c7b8424a0a194f0d) Let visualizers draw keystrokes in a user-chosen font ### 📊 Changes **14 files changed** (+444 additions, -28 deletions) <details> <summary>View changed files</summary> 📝 `keycastr/KCDefaultVisualizer.m` (+2 -1) 📝 `keycastr/KCDefaultVisualizer.nib/designable.nib` (+34 -7) 📝 `keycastr/KCDefaultVisualizer.nib/keyedobjects.nib` (+0 -0) ➕ `keycastr/KCFontPopUpButton.h` (+45 -0) ➕ `keycastr/KCFontPopUpButton.m` (+115 -0) ➕ `keycastr/KCVisualizerTests/KCFontSelectionTests.m` (+121 -0) 📝 `keycastr/KeyCastr.xcodeproj/project.pbxproj` (+12 -0) 📝 `keycastr/Minimal.xib` (+39 -12) 📝 `keycastr/MinimalVisualizer.m` (+2 -1) 📝 `keycastr/NSUserDefaults+Utility.h` (+5 -0) 📝 `keycastr/NSUserDefaults+Utility.m` (+24 -0) 📝 `keycastr/Svelte.nib/designable.nib` (+29 -2) 📝 `keycastr/Svelte.nib/keyedobjects.nib` (+0 -0) 📝 `keycastr/SvelteVisualizer.m` (+16 -5) </details> ### 📄 Description Closes #169. Every bundled visualizer already lets you pick a font *size*; the typeface has always been hardcoded to the system font. The request in #169 is for fixed-width — `I` vs `l` are indistinguishable in the system font, which matters a lot when you're recording a tutorial — but it's just as useful for matching a visualizer to a theme. ### What this adds A **Font** pop-up in each of the three bundled preference panes, directly above the existing Font Size control (Svelte has no size control, so it sits under "Display All Keystrokes"). Each pane stores its own choice: | Visualizer | Key | | --- | --- | | Default | `default.fontName` | | Minimal | `minimal.fontName` | | Svelte | `svelte.fontName` | The menu lists every installed family, each drawn in its own typeface, so it reads as a specimen sheet rather than a wall of identical text. The first item is "System Font", which is the default and restores today's behaviour exactly. ### How it's put together `KCFontPopUpButton` is a small `NSPopUpButton` subclass that populates itself from `NSFontManager` and reads/writes whichever defaults key the nib hands it via `fontFamilyDefaultsKey` (a user-defined runtime attribute). One class covers all three panes, and any future visualizer gets font selection by dropping the control into its nib and setting that one attribute. Font resolution is `-[NSUserDefaults fontForKey:size:bold:]`, sitting next to the existing `colorForKey:` / `imageForKey:` helpers. It always returns something drawable: - nothing stored, or the empty string → the system font - stored family no longer installed (uninstalled, or disabled in Font Book) → the system font, **without** clearing the stored value, so reinstalling the font brings the selection back - bold requested but the family has no bold face → the family's regular weight, rather than substituting a different family to honour boldness Minimal is the only visualizer that asks for bold, matching its current `boldSystemFontOfSize:`. ### Two judgement calls worth flagging **Svelte applies the font to the key label only**, not the modifier strip above it. That strip is ⌘⌥⇧-style iconography and reads better kept in the system font; a decorative display face makes it much harder to parse at a glance. **Svelte's shrink-to-fit loop gained a minimum size.** It previously ran `while (size.width > frame.size.width - 10) fontSize -= 1.0;` with no floor. That was unreachable with the system font, but a user-selectable wide face makes it reachable, so it now stops at 8pt. ### Tests New `KCFontSelectionTests` covers the resolution rules (unset, empty, installed, bold fallback, uninstalled family) and an integration test that instantiates the Default and Svelte preference views from their nibs and asserts each one has a font pop-up bound to its own key with a populated menu. That last one is what proves the nib wiring actually survives — the legacy `wrapper.nib` bundles can't be diffed usefully by eye. To make the helper testable, `NSUserDefaults+Utility.h` is now a public header of the `KCVisualizer` framework. Minimal isn't in that integration test because only `KCDefaultVisualizer.kcplugin` and `Svelte.kcplugin` are copied into the test bundle's plug-ins. Its pane is a plain xib and gets validated when the project builds; I verified all three panes' computed layouts by hand, including re-seating the separator boxes in Default and Minimal, which use autoresizing rather than autolayout and so don't move with the rest of the pane. Both `wrapper.nib` bundles were recompiled with `ibtool` and produce no errors or warnings. The full test suite passes. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
github-starred/keycastr#355
No description provided.