feat(motion): motion deck for firing clips without changing the selection - #51
Conversation
|
This PR is based on #50 |
|
@AUDOSt0ck1ng this looks good on the vid, I wonder if we could make the cards like a carousel with arrows or highlighted dots instead of scrolling, eg. in case someone has dozens of animations? or what do you think about one single card that can hold all pairs just name + hotkey for each line, not sure, just an idea. Since this was done before 50 was merged, let's merge once this is rebased on Other than that this looks dope and unlocks a bunch of potential new issues towards this direction. Thanks <3 |
`animation.play` takes a `mode`. `select` is the default and behaves as before: it changes what the stage is set to, and that is what config.yaml stores. `once` overlays a single pass and leaves `animationId` alone, so a gesture cannot cost the user the Default sequence or loop they chose. `animation.stop` ends one early. `once` is refused for anything that cannot end on its own — the Default sequence loops for as long as it is selected, so there is no frame at which the selection could come back. The overlay is transient state in AvatarStage, never persisted, and is dropped when the model or the animation catalog changes: the mixer belongs to the VRM and the url belongs to the catalog. Resuming skips a sequence's intro when the selection underneath has not changed, or the avatar would greet you again after every gesture; a pick made while a gesture runs is a fresh start and still plays in full. Also detach a one-shot's `finished` listener whichever generation it belongs to. A superseded one used to return early and leave its closure on the mixer for the life of the model — reachable only from the Animations menu before, reachable from every key press now. This layer is not specific to any one trigger. The Motion Deck is its first consumer, and the local agent bus and keyword triggers are meant to drive the same commands rather than grow their own path to the mixer. Refs #6, #7, ARPAHLS#24 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tion Settings → Motion. The user assembles a list of cards, each holding one clip and any number of chords; pressing a chord — or clicking the card — plays that clip once and hands the stage back to the Animations selection. Refs ARPAHLS#24. Bindings are authored rather than derived. The issue asks for a bind control on every row of the animation catalog, but a table of the catalog is a projection of whichever folder is currently mounted, so it structurally cannot hold a binding for a clip that folder does not contain. An authored list holds cards from several folders at once and does not care which one is mounted; it also gives the agent bus and keyword triggers a stable unit to point at later. Which is why nothing prunes the deck. Switching Directories → Animations replaces the whole catalog, and the autosave would persist a pruned deck within its debounce — "point it at another folder to look" would cost the table with no undo. Cards resolve against the live catalog when drawn and when fired; an unresolved one is shown as unavailable, keeps its chords reserved, and comes back when its folder does. Clear unavailable is the only sweep, and it refuses to run against an empty catalog, since a custom folder is scanned asynchronously and every card is briefly unresolvable at launch. Cards remember their clip's name and re-point themselves when the id misses and exactly one clip matches — a custom folder's ids hash the absolute path, so moving or renaming it invalidates all of them while the files are untouched. A unique match only: two folders can each hold a wave.vrma. Binding a chord that is taken moves it rather than warning or blocking: two cards holding one chord has no defined behaviour, and refusing throws away the key the user just pressed. Chords are keyed off event.code, so the same physical key records and fires under one name. Only single clips can be added, since a sequence has no last frame to hand the stage back on, and keys are live only while the window has focus. The feature is confined to src/motion-deck/, which carries a README with the removal steps; outside it, four lines in userSettings.js and nine in AvatarStage.jsx. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Section 9 said no hotkey was set; Settings → Motion now binds keys, though only while the window has focus. Refs ARPAHLS#24 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6c7c0b0 to
ad29b08
Compare
|
@rosspeili |
Review on ARPAHLS#24 asked for either a carousel or a single card holding every name/hotkey pair. Rows, not a carousel: paging hides the deck behind an interaction, and the panel exists to be read down — name against chord is the only pairing on it worth scanning. A card each spent a bordered block and two stacked lines on what fits in one, so a deck of twenty was taller than the drawer it sits in. The list scrolls itself rather than the drawer, so Add motion stays where it was under a deck of any length, and past five rows the deck grows a filter box for reaching one row to edit without scrolling to it. Filtering never reindexes. Every action addresses a card by its position in the deck, so the filtered list carries the original index rather than its own — otherwise the first row of a filtered view would fire, rebind or delete whichever card happens to sit at position zero. Focusing the filter cancels an armed recording. The keydown listener swallows everything while recording, so a filter box next to the row being bound would otherwise eat the first letter typed and bind it. The unavailable sentence a card could afford does not fit a row: the row carries a badge, and the explanation moves next to Clear unavailable, where it is said once instead of per card. Refs ARPAHLS#24 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The drawer is ~237px wide, which the row layout was written without measuring. Three things came out of measuring it. The bind slot dropped to a second line on every row, however much space was free: a wrap container that is itself a flex item resolves an `auto` basis to min-content — one chip's width — so `flex-basis: max-content` is what makes the chips sit on the name's line. Chips are smaller (10px, 2px 5px) and the bind slot is a bare `+` rather than `+ Key`. At this width the five characters cost more of the name than the word was worth, and the slot reads as one by sitting among the chords; the recording state still spells out "Press a key…". The name gets a 110px floor and the chords give way, rather than the two shrinking together — that split truncated names to "ba…" while the chords wrapped anyway, which lost both. So a clip with two chords, or one long one, is two lines, and a long name still ends in an ellipsis with the full text on hover. One line was the aim, not a requirement worth truncating a name over. Screenshots of the panel for docs/screenshots/, in the 8x settings block. Refs ARPAHLS#24 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two screenshots landed in docs/screenshots/ without anything pointing at them. Both docs now do, the way Directories is shown in each. Section 7 of the walkthrough had no Motion subsection at all — it covered Overlay, Snap, Directories, VRoid Hub and System, and the panel between Directories and VRoid Hub was reachable only through a line in the keyboard notes. It now sits in the drawer's own order. Refs ARPAHLS#24 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This looks really good @AUDOSt0ck1ng, thanks <3 merging as is. |

Settings → Motion. The user assembles a list of cards, each holding one clip and any number of chords; pressing a chord — or clicking the card — plays that clip once and hands the stage back to the Animations selection, without touching
animationId.The design, what it takes from the issue as written, where it diverges, and what switching animation folders does: #24 (comment)
Two commits
feat(stage)—animation.playtakes amode(select/once) andanimation.stopends a one-shot early. Not specific to this feature: the agent bus ([Feature]: Local agent/event bus so external tools can drive the avatar #6) and keyword triggers ([Feature]: Keyword → animation mapping table #7) are meant to drive the same commands. Verified to build and pass on its own.feat(motion)— the deck, confined tosrc/motion-deck/. Outside that folder it is four lines inuserSettings.jsand nine inAvatarStage.jsx; the folder's README carries the removal steps and the decisions worth not re-litigating.Notes for review
Clear unavailableis the only sweep and refuses to run against an empty catalog, since a custom folder is scanned asynchronously and every card is briefly unresolvable at launch.wave.vrma.animationIdvalues and deserves its own change.lint,test(93, of which 21 are new) andbuildpass. Manually tested on the desktop shell: binding and firing, folder switches in both directions, folder rename, reset, and the app's own keyboard not being swallowed while a binding is armed.Refs #24
🤖 Generated with Claude Code