Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ fn a_cubic_bezier_slot_carries_its_control_points_into_the_spec() {

#[test]
fn the_registry_carries_every_slot_and_the_slide_property() {
// `neomacs-effect-set` validates a property key against the value already
// `neomacs--effect-set` validates a property key against the value already
// stored, so a property missing from the serialized shape makes switching
// it from Lisp unreachable. Walking every name of the config struct and
// round-tripping one value through the registry proves the opposite.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2648,7 +2648,7 @@ fn every_effect_in_the_registry_can_report_its_values() {
// The registry is pure serde reflection over `VisualConfig`, and a property
// that serializes to an object (anything but the `Duration` special case)
// makes `effect_values` fail for the *whole* effect — which takes
// `neomacs-effect-get` down for it and `neomacs-effects-apply`, which walks
// `neomacs-effect-get` down for it and `neomacs--effects-apply`, which walks
// every effect, down entirely. That failure is invisible from the Rust side
// until someone asks from Lisp, so it is asserted here instead.
let config = VisualConfig::default();
Expand Down Expand Up @@ -2730,7 +2730,7 @@ fn every_window_animation_slot_reaches_the_registry_as_scalars() {
// break if a slot stored a `MotionSpec` directly: every variant but
// `Instant` serializes to an object, which the registry cannot represent,
// so `neomacs-effect-get` would fail for the slot — and
// `neomacs-effects-apply`, which walks every effect, with it.
// `neomacs--effects-apply`, which walks every effect, with it.
assert!(sprung.effect_values("window-open").is_ok());
}

Expand Down
2 changes: 1 addition & 1 deletion crates/neomacs-display-protocol/src/motion_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl<'de> serde::Deserialize<'de> for DevicePixels {
/// that type is a *registry* field on four different configs, and the effect
/// registry can only carry scalar property values. A data-carrying easing
/// variant would serialize to an object and break `neomacs-effect-get` for
/// whichever slot selected it -- and `neomacs-effects-apply`, which walks every
/// whichever slot selected it -- and `neomacs--effects-apply`, which walks every
/// effect, along with it. Keeping `TransitionEasing` fieldless makes that
/// impossible by construction rather than by discipline.
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2648,7 +2648,7 @@ fn every_effect_in_the_registry_can_report_its_values() {
// The registry is pure serde reflection over `VisualConfig`, and a property
// that serializes to an object (anything but the `Duration` special case)
// makes `effect_values` fail for the *whole* effect — which takes
// `neomacs-effect-get` down for it and `neomacs-effects-apply`, which walks
// `neomacs-effect-get` down for it and `neomacs--effects-apply`, which walks
// every effect, down entirely. That failure is invisible from the Rust side
// until someone asks from Lisp, so it is asserted here instead.
let config = VisualConfig::default();
Expand Down Expand Up @@ -2730,7 +2730,7 @@ fn every_window_animation_slot_reaches_the_registry_as_scalars() {
// break if a slot stored a `MotionSpec` directly: every variant but
// `Instant` serializes to an object, which the registry cannot represent,
// so `neomacs-effect-get` would fail for the slot — and
// `neomacs-effects-apply`, which walks every effect, with it.
// `neomacs--effects-apply`, which walks every effect, with it.
assert!(sprung.effect_values("window-open").is_ok());
}

Expand Down
18 changes: 12 additions & 6 deletions crates/neomacs-display-protocol/src/visual_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,12 @@ impl Default for BufferTransitionConfig {
// Off by default: switching buffers repaints instantly, matching
// stock Emacs, the same choice ScrollTransitionConfig makes below.
// Opt in at runtime with
// (setq neomacs-buffer-transition-enabled t)
// or via the `neomacs-effects' profile. The effect/duration/easing
// below are the values used once it is enabled.
// (setopt neomacs-buffer-transition-enabled t)
// or through Customize. A plain `setq' after `neomacs-effects.el'
// has loaded never reaches the renderer; a value set before that
// load is applied by the `defcustom'.
// The effect/duration/easing below are the values used once it is
// enabled.
enabled: false,
duration: Duration::from_millis(200),
effect: TransitionEffect::Slide,
Expand All @@ -118,9 +121,12 @@ impl Default for ScrollTransitionConfig {
Self {
// Off by default: C-v/M-v (and other scrolls) update instantly,
// matching stock Emacs. Opt in at runtime with
// (setq neomacs-scroll-transition-enabled t)
// or via the `neomacs-effects' profile. The effect/duration/easing
// below are the values used once it is enabled.
// (setopt neomacs-scroll-transition-enabled t)
// or through Customize. A plain `setq' after `neomacs-effects.el'
// has loaded never reaches the renderer; a value set before that
// load is applied by the `defcustom'.
// The effect/duration/easing below are the values used once it is
// enabled.
enabled: false,
duration: Duration::from_millis(150),
effect: TransitionEffect::Slide,
Expand Down
2 changes: 1 addition & 1 deletion crates/neomacs-display-protocol/src/window_animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! The effect registry reflects [`crate::visual_config::VisualConfig`] through
//! serde and can carry only scalar property values. Every `MotionSpec` variant
//! but `Instant` serializes to an *object*, so storing one here would make
//! `neomacs-effect-get` fail for the slot -- and `neomacs-effects-apply`, which
//! `neomacs-effect-get` fail for the slot -- and `neomacs--effects-apply`, which
//! walks every effect, with it. [`WindowAnimation::motion`] converts to the
//! form the compositor samples.
//!
Expand Down
45 changes: 26 additions & 19 deletions docs/animations.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,30 @@ orientation, and direction are independent typed settings:
## Configuration

```elisp
;; One typed, name-based profile configures effects and animation policy.
(neomacs-effects-apply
'((cursor-motion :enabled t :speed 15.0
:style critically-damped-spring :duration 0.15
:trail-size 0.7)
(buffer-transition :enabled t :duration 0.2
:effect slide :easing ease-out-quad
:axis auto :direction forward)
(scroll-transition :enabled t :duration 0.15
:effect page-curl :easing spring)))

;; Incremental changes use the same names and properties.
(neomacs-effect-set 'cursor-motion
:speed 20.0 :style 'linear :duration 0.1)
(neomacs-effect-set 'buffer-transition
:effect 'page-curl
:axis 'vertical :direction 'backward)
(neomacs-effect-set 'scroll-transition
:effect 'wobbly :easing 'ease-out-quad)
;; Options reach the renderer through their :set function. setopt and
;; Customize do that; a setq after neomacs-effects.el has loaded does not.
(setopt neomacs-cursor-motion-enabled t
neomacs-cursor-motion-speed 15.0
neomacs-cursor-motion-style 'critically-damped-spring
neomacs-cursor-motion-duration 0.15
neomacs-cursor-motion-trail-size 0.7
neomacs-buffer-transition-enabled t
neomacs-buffer-transition-duration 0.2
neomacs-buffer-transition-effect 'slide
neomacs-buffer-transition-easing 'ease-out-quad
neomacs-buffer-transition-axis 'auto
neomacs-buffer-transition-direction 'forward
neomacs-scroll-transition-enabled t
neomacs-scroll-transition-duration 0.15
neomacs-scroll-transition-effect 'page-curl
neomacs-scroll-transition-easing 'spring)

(setopt neomacs-cursor-motion-speed 20.0
neomacs-cursor-motion-style 'linear
neomacs-cursor-motion-duration 0.1
neomacs-buffer-transition-effect 'page-curl
neomacs-buffer-transition-axis 'vertical
neomacs-buffer-transition-direction 'backward
neomacs-scroll-transition-effect 'wobbly
neomacs-scroll-transition-easing 'ease-out-quad)
```
2 changes: 1 addition & 1 deletion docs/design/display-crate-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Submodule charters — a new file needs one of these to belong:
6. **A config value that Lisp can set must be scalar.** The effect registry is
serde reflection over `VisualConfig` and carries only scalars plus
`Duration`; a field that serializes to an object breaks
`neomacs-effects-apply` for every effect, not just its own.
`neomacs--effects-apply` for every effect, not just its own.
6. **Lint suppressions are per item, never per crate.** A `#![allow(...)]` in a
`lib.rs` covers code nobody has read yet, so the count it hides drifts:
`neomacs-layout-engine`'s crate-wide `clippy::too_many_arguments` allow was
Expand Down
9 changes: 5 additions & 4 deletions docs/display-engine/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,11 @@ region, excluding mode-lines and fringes.
```
Elisp Evaluator / protocol Render thread
───── ──────────────────── ─────────────
(neomacs-effect-set validate named properties ConfigCommand::
'cursor-glow against VisualConfig SetVisualConfig(snapshot)
:enabled t apply to cloned snapshot ↓
:radius 48) commit atomically effect/cursor/transition state
(setopt option :set calls ConfigCommand::
neomacs-effect- neomacs--effect-set SetVisualConfig(snapshot)
cursor-glow validate named properties ↓
'(:enabled t against VisualConfig effect/cursor/transition state
:radius 48)) commit atomically
```

Each effect keeps its own typed property schema. Profiles validate completely
Expand Down
112 changes: 72 additions & 40 deletions docs/display-engine/EFFECTS.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,80 @@
# Renderer effects

Neomacs exposes renderer effects through one name-based Elisp interface. An
effect has its own typed property set; unrelated effects do not pretend to
share one universal options struct.
Renderer effects are user options. Behavioural effects — cursor motion,
buffer and scroll transitions, window and child-frame animation — are one
option per property. A decorative effect is one option, `neomacs-effect-NAME`,
whose value is a plist of that effect's properties. `setopt` and Customize
are the interface: both run the option's `:set` function, and that is what
reaches the renderer. A plain `setq` after `neomacs-effects.el` has loaded
changes the variable only. A value already set when that file loads is picked
up by the `defcustom`.

```elisp
(neomacs-effect-set 'cursor-glow
:enabled t
:color "#66CCFF"
:radius 48)
(setopt neomacs-effect-cursor-glow
'(:enabled t :color "#66CCFF" :radius 48))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

(neomacs-effect-get 'cursor-glow)
(neomacs-effect-reset 'cursor-glow)
(neomacs-effect-names)
```

A gallery assignment replaces the option's plist. The renderer merges the
properties it is given, so a later `(setopt neomacs-effect-cursor-glow
'(:radius 40))` leaves glow enabled on screen while the option no longer
records `:enabled`. Saving that value and starting again applies only
`:radius` onto the defaults, and the enablement is gone. Include every
property that should persist, or merge onto the current plist first.

`neomacs-effect-set`, `neomacs-effect-reset`, and `neomacs-effects-apply` are
not defined.

`neomacs-effect-names` accepts an optional discovery scope: `shader` lists
renderer shader configs, `cursor` lists configs valid in a buffer-local cursor
profile, and `behavior` lists cursor/window animation policy. With no scope it
returns the complete `VisualConfig` registry.

The same interface owns animation behavior that used to be split across
The same options own animation behavior that used to be split across
positional setters:

```elisp
(neomacs-effect-set 'cursor-motion
:enabled t :speed 2.4
:style 'critically-damped-spring
:duration 0.15 :trail-size 0.7)
(neomacs-effect-set 'cursor-color-cycle :enabled t :fps 24)
(neomacs-effect-set 'cursor-blink :enabled t :interval 0.5)
(neomacs-effect-set 'cursor-size-transition :enabled t :duration 0.15)
(neomacs-effect-set 'buffer-transition
:enabled t :duration 0.2
:effect 'slide :easing 'ease-out-quad
:axis 'auto :direction 'forward)
(neomacs-effect-set 'scroll-transition
:enabled t :duration 0.15
:effect 'page-curl :easing 'spring)
(setopt neomacs-cursor-motion-enabled t
neomacs-cursor-motion-speed 2.4
neomacs-cursor-motion-style 'critically-damped-spring
neomacs-cursor-motion-duration 0.15
neomacs-cursor-motion-trail-size 0.7)
(setopt neomacs-effect-cursor-color-cycle '(:enabled t :fps 24))
(setopt neomacs-cursor-blink-enabled t
neomacs-cursor-blink-interval 0.5)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
(setopt neomacs-cursor-size-transition-enabled t
neomacs-cursor-size-transition-duration 0.15)
```

`neomacs-cursor-blink-enabled` and `neomacs-cursor-blink-interval` are not
what a session keeps. After the first frame, `neomacs--setup-cursor-blink`
copies `blink-cursor-mode` and `blink-cursor-interval` into the renderer
with `neomacs--effect-set`, and a later `blink-cursor-mode` change does
that again. The option variables stay as they were, so a `setopt` of them
in an init file can disappear from the screen. Set `blink-cursor-mode` and
`blink-cursor-interval` for the blink that should persist.

```elisp
(setopt neomacs-buffer-transition-enabled t
neomacs-buffer-transition-duration 0.2
neomacs-buffer-transition-effect 'slide
neomacs-buffer-transition-easing 'ease-out-quad
neomacs-buffer-transition-axis 'auto
neomacs-buffer-transition-direction 'forward)
(setopt neomacs-scroll-transition-enabled t
neomacs-scroll-transition-duration 0.15
neomacs-scroll-transition-effect 'page-curl
neomacs-scroll-transition-easing 'spring)
```

Unknown effects, unknown properties, invalid colors, wrong value types, and
non-integral values for integer properties signal an Elisp error. Property
names use Lisp kebab-case and map to the Rust config field with the same name
`neomacs--effect-set` rejects an unknown effect, an unknown property, an
invalid color, a wrong value type, and a non-integral value for an integer
property. An option's `:set` function stores the Lisp value first and
ignores that signal, so a rejected value changes the option and leaves the
renderer as it was. Property names use Lisp kebab-case and map to the Rust
config field with the same name
in snake_case. Colors use sRGB `#RRGGBB` or `#RRGGBBAA` strings and are
converted to and from the renderer's linear color storage at the protocol
boundary. Time fields whose names end in `-ms` are milliseconds, while Rust
Expand Down Expand Up @@ -80,25 +112,25 @@ Other motion styles remain available: `exponential`, `critically-damped-spring`,
`ease-in-out-cubic`. The plain `ease-out-expo` style moves one rectangle;
`neovide` independently animates its corners.

### Applying profiles
### Applying several effects

`neomacs-effects-apply` replaces the complete profile, starting from the
Rust-defined defaults and applying every entry before publishing anything:
Set each option. There is no public command that replaces the whole profile.
`neomacs--effects-apply` is internal: it rebuilds the renderer from the Rust
defaults plus the profile it is given, and it does not write the options, so
calling it directly leaves Customize showing the previous values. An invalid
entry makes that subr signal and publish nothing.

```elisp
(neomacs-effects-apply
'((cursor-glow :enabled t :color "#66CCFF" :radius 48)
(rain-effect :enabled t :drop-count 30 :speed 120.0)))
(setopt neomacs-effect-cursor-glow
'(:enabled t :color "#66CCFF" :radius 48)
neomacs-effect-rain-effect
'(:enabled t :drop-count 30 :speed 120.0))
```

If any entry is invalid, neither evaluator state nor renderer state changes.
An empty profile restores every Rust default. `neomacs-effects`, the Custom
option in `term/neo-win.el`, has the same format.

`neomacs-cursor-effect` accepts one cursor-effect entry or a list of entries
in this format as a buffer-local variable or window parameter. The layout
engine validates it through the same protocol registry used by global
configuration.
`neomacs-cursor-effect`, in `term/neo-win.el`, accepts one
`(EFFECT :PROPERTY VALUE ...)` entry or a list of them as a buffer-local
variable or window parameter. The layout engine validates it through the
same protocol registry used by the options above.

## Ownership and update flow

Expand Down
11 changes: 7 additions & 4 deletions lisp/neomacs-effects.el
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@

;; Every renderer effect is configured through `defcustom', the way
;; `blink-cursor-interval' and friends are: one option per property, with a
;; type, a docstring and a group. There is no public setter function. Setting
;; an option with `setq' or through \\[customize] is the whole interface.
;; type, a docstring and a group. There is no public setter function: `setopt'
;; and \\[customize] are the whole interface, because an option reaches the
;; renderer through its `:set' function. As with any GNU option that has one, a
;; plain `setq' once this file is loaded changes the variable and nothing else;
;; a value already set when this file loads is picked up by the `defcustom'.
;;
;; The properties themselves are defined in Rust and published by
;; `neomacs-effect-schema', which reports each property's kind, its bounds and,
Expand Down Expand Up @@ -534,7 +537,7 @@ decorative."
"Curve shape for a buffer switch."
:type '(choice (const linear) (const ease-out-quad) (const ease-out-cubic)
(const ease-out-expo) (const ease-in-out-cubic)
(const spring) (const cubic-bezier))
(const spring))
:group 'neomacs-transitions
:set (lambda (symbol value)
(set-default symbol value)
Expand Down Expand Up @@ -594,7 +597,7 @@ When nil the viewport jumps to its new position on the next frame."
"Curve shape for a scroll."
:type '(choice (const linear) (const ease-out-quad) (const ease-out-cubic)
(const ease-out-expo) (const ease-in-out-cubic)
(const spring) (const cubic-bezier))
(const spring))
:group 'neomacs-transitions
:set (lambda (symbol value)
(set-default symbol value)
Expand Down
2 changes: 1 addition & 1 deletion lisp/term/neo-win.el
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ Applied at the render level."

(defvar-local neomacs-cursor-effect nil
"Per-buffer cursor effect profile.
The value is one entry or a list of entries in `neomacs-effects' format.
The value is one (EFFECT :PROPERTY VALUE ...) entry or a list of them.
Only cursor effects are used when rendering this buffer's cursor.")

;; Provide the feature
Expand Down
Loading