"Expose current playback, track, seek, repeat, and error state through native controls and menus. A submitted command is not a visible state change until the engine acknowledges it."
The Synthesis of the Whole Track
Everything so far — a command is a request, a seek is a state machine, explicit intent beats memory — points at one design stance for the entire user interface: every control is a mirror of the engine, never a store of your intentions. The pause menu item's checkmark, the subtitle track's dot, the A-B loop chip, the position in the overlay — none of them remember what you clicked. All of them read what the engine reports.
This inverts the ordinary way UIs are built. Normally a control both triggers an action and records its own new state. Ashen Reel splits those: a control triggers a request, and its displayed state comes back from the engine through observed properties. The control fires and forgets; the mirror reflects.
AppKit makes this natural: menu validation runs right before a menu is shown, so you set each item's state from the engine at display time.
Why This Is the Feature, Not the Polish
It's tempting to file 'the checkmark matches reality' under polish. It isn't — for a player joined to Recall, it's the product. When Dad jumps to a remembered second, the overlay must show the second he actually landed on, the menu must show the track actually playing, the loop chip must show the markers the engine actually holds. A UI that mirrors the engine can be trusted as a readout of what's true; a UI that stores its own guesses is just a hopeful sticky note. The whole track was building one habit — observe, don't assume — and this is where it becomes something the user can feel: a player whose face never lies.
Code
// AppKit calls this right before showing the menu -- the perfect moment to
// re-derive each item's state from the engine, not from what we last set.
extension PlayerController: NSMenuItemValidation {
func validateMenuItem(_ item: NSMenuItem) -> Bool {
switch item.action {
case #selector(togglePause(_:)):
item.state = engine.isPaused ? .on : .off // observed truth
case #selector(toggleSubtitles(_:)):
item.state = engine.subtitlesVisible ? .on : .off // observed truth
case #selector(cycleAudioTrack(_:)):
item.title = "Audio: \(engine.currentAudioTrackName)" // observed truth
default:
break
}
return engine.isMediaLoaded // controls disable honestly when nothing's open
}
}
// Not one line reads a variable we set when the user clicked. It's all engine.External links
Exercise
Hint
Progress
Comments 0
🔔 Reply notifications (sign in)No comments yet — be the first.