Add customizable mouse-click effects with new "Mouse" preferences tab - #344
Open
prossm wants to merge 1 commit into
Open
Add customizable mouse-click effects with new "Mouse" preferences tab#344prossm wants to merge 1 commit into
prossm wants to merge 1 commit into
Conversation
# What this changes (non-technical)
Adds four new visual effects for mouse clicks, plus a dedicated "Mouse"
tab in Preferences for configuring them. Previously the only mouse-click
indicator was a thin circle whose color was tied to the keystroke
bezel; now click visualization is its own first-class feature.
The new "Mouse" tab (placed between Display and Update in the toolbar)
exposes:
- Mouse Effect: Halo (the original ring), Blur (a soft, expanding
cloud), Halo + Blur (both layered), or Droplet (concentric ripples
that fan outward from the click site, evoking a drop in water).
- Mouse Effect Color 1: drives the halo stroke and droplet rings.
- Mouse Effect Color 2: drives the blur fill (used in Blur and
Halo + Blur).
- Mouse Effect Density: stroke/core thickness, applied per effect.
- Mouse Effect Diameter: base size of the effect at the click point.
- Mouse Effect Duration: how long the animation runs end-to-end.
All numeric controls have both a slider and an editable rounded text
field for precise values. Halo continues to follow the cursor while
the button is held; Blur and Droplet anchor to the click site and run
to completion. Defaults preserve the original Halo look out of the box.
# What this changes (technical)
## KCMouseEventVisualizer.h / .m
Major rewrite of the mouse-click rendering pipeline.
- Exports new defaults keys: kKCMouseEffectKey, kKCMouseEffectColor1Key,
kKCMouseEffectColor2Key, kKCMouseEffectDurationKey,
kKCMouseEffectDensityKey, kKCMouseEffectDiameterKey.
- Adds class method +defaultPreferences returning the registered
defaults dict (consumed by KCAppController.registerDefaults). Default
colors are fully opaque so NSColorWell renders without its
transparency-indicator diagonal.
- Extends the KCMouseDisplayOptionsProvider protocol with
mouseEffectNames and currentMouseEffectName for popup binding.
- KCMouseVisualizerWindow:
- Resized to fit the worst-case (kKCMouseEffectMaxDiameter ×
kKCMouseEffectMaxScale + 40pt) so any in-range diameter at any
scale fits without clipping.
- setIgnoresMouseEvents:YES so the overlay never intercepts clicks.
- KVO observers on the five new defaults keys; observer callback
tears down cached layers via -tearDownLayers, forcing a rebuild
with fresh values on the next event.
- -resolvedEffect, -effectColor1/2, -effectDuration, -effectDensity,
-effectRadius read live from NSUserDefaults each event.
- Per-effect renderers:
- showHaloPersistent / fadeHalo: stroked CAShapeLayer, opacity
animation. Both wrapped in CATransaction setDisableActions:YES to
kill CALayer's implicit 0.25s opacity animation that was producing
a visible double-blink against the explicit fade-out.
- runBlurAnimation: CAGradientLayer (kCAGradientLayerRadial) with a
three-stop gradient — opaque core, opaque core, clear edge — where
the core stop position is derived from density. Bounds explicit,
position centered on cursor; transform.scale + opacity keyframe
run as a CAAnimationGroup.
- runDropletAnimation: kKCDropletRingCount=3 stroked CAShapeLayers
with bounds (diameter × diameter) and explicit position so
anchorPoint (0.5, 0.5) sits on the cursor — fixes a bug where
transform.scale was scaling around the parent layer's (0,0) and
flinging rings up-and-right. Each ring carries a CGShadow glow in
the stroke color (shadowOpacity 0.9, shadowRadius 6) for soft
edges, plus animated lineWidth thinning to dissipate. Rings are
staggered by duration / (ringCount + 1) via CAAnimationGroup
beginTime.
- updateWithMouseEvent: dispatches per effect — halo follows cursor on
drag and fades on mouseUp; blur and droplet trigger once on
mouseDown and ignore drag/up.
## KCAppController.h / .m
- Forward-declares KCMouseEventVisualizer; exposes
-mouseEventVisualizer and -availableMouseEffectNames so the prefs
controller can drive the popup binding.
- registerDefaults merges in [KCMouseEventVisualizer defaultPreferences].
- Lazily registers KCColorValueTransformer (an
NSSecureUnarchiveFromDataTransformer for NSColor) so NSColorWell
bindings can round-trip color data through NSUserDefaultsController.
## KCPrefsWindowController.h / .m
Adds the "Mouse" preference pane programmatically — no nib edits.
- Three earlier attempts to inject controls into the existing Display
tab failed because of entanglement with the static subview's
autoresize masks and the legacy s.height += subview.height * 2.0
hack in -changeVisualizerFrom:to:. The current approach builds a
fully self-contained tab and registers it alongside the nib-loaded
tabs.
- -registerMouseEffectsTab runs from -nudge before the toolbar is
constructed, appends the content view to preferenceViews, and
inserts the "Mouse" identifier into toolbarItemIdentifiers at
count - 1 so the visual order is General | Display | Mouse | Update
while the tag (= preferenceViews index) stays at the end.
- Toolbar icon uses the SF Symbol "computermouse" on macOS 11+ with
NSImageNameComputer as fallback.
- -buildMouseEffectsBoxWithWidth: lays out a 6-row NSBox with absolute
frames (NSGridView with autolayout proved fragile inside the
NSBox + NSAtTop title-bar layout). Content height accounts for the
~24pt title bar reserve via -mouseEffectsContentHeight +
-mouseEffectsBoxHeight.
- -addSliderRowToContent:... helper builds a slider + editable
rounded-bezel NSTextField pair, both bound via NSUserDefaultsController
keypaths with NSContinuouslyUpdatesValueBindingOption so dragging
the slider updates the field live and typing in the field updates
the slider on commit. NSNumberFormatter min/max enforce range.
- Color wells bind via NSValueBinding with
NSValueTransformerNameBindingOption: NSStringFromClass([KCColorValueTransformer class])
so NSData blobs in defaults round-trip to NSColor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hi! KeyCastr user here, first contribution. I built this for my own use and figured I'd offer it upstream. Feel free to incorporate it, or not as you see fit. Full transparency: I did use a coding agent for the work, but it's fully functional.
What this adds
A new "Mouse" preferences tab with four mouse-click visual effects (Halo, Blur, Halo + Blur, Droplet) and per-effect customization for color, density, diameter, and duration. The original Halo behavior is preserved as the default, just with its color decoupled from the keystroke bezel color.
Demo: https://www.loom.com/share/1a43df69bfbf4ce09d79115e62cc0696
Design notes worth flagging for review
mouse.*keys; the existingdefault.bezelColoris no longer read by the mouse visualizer (its color is nowmouse.effectColor1). I didn't add a migration — fresh installs get the new opaque defaults, existing users keep their bezel color for keystrokes and pick a new mouse color on first visit.MainMenu.nibso the binary nib stays untouched. Easier to review as text; downside is it's not editable in Interface Builder.KCMouseEventVisualizerTestsstill pass.Commit
Full technical breakdown is in the single commit message — file-by-file walkthrough of methods touched, KVO wiring, droplet anchor-point fix, etc. Worth reading before reviewing the diff.