Files
stickman/docs/specs/phase_17b_use_spots_ui.md
ryan d8b1b1164b Add Use Spot Overlay and Target Capture Bar functionality
- Introduced `StageUseTargetCaptureBar` class for managing user interactions with props and their use spots.
- Implemented UI elements including buttons for changing props and canceling actions.
- Added `PropUseSpotData` class for validation and serialization of prop use spots.
- Created tests for validating use spot data migration, serialization, and rule matching.
- Ensured proper handling of edge cases in use spot data, including unique IDs and finite positions.
2026-10-13 11:23:44 -04:00

110 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 17b — Use spots: UI/UX specification
> **Status:** Approved design; the core UI implementation is present and awaiting in-editor user verification. New rule authoring chooses one named Use spot; saved `spot_scope = "any"` rules remain supported for compatibility, with no new Any-spot authoring button.
> **Source:** [Phase 17 — Props for Storytelling](../plans/phase_17_props_for_storytelling.md).
> **Scope:** Sandbox Stage EDIT, DIRECT, and PLAY presentation for Use spots on placed prop instances. This document defines the child-facing interaction, not the saved-data or reach-solver contracts.
## Child flow
1. In **EDIT**, the child selects one placed prop. The existing right-side card keeps the prop name in its fixed header. A new **Use spots** section explains **Stickmen can touch these places without picking up the prop.** The child presses **+ Add a spot**, moves a hand-shaped preview over the chosen handle, button, or lever, and clicks. The placed pin appears on this copy of the prop. The child may name it, move it, or erase it from a large row in the card.
2. In **DIRECT**, the child chooses a stickman and **Use…** from the same action entry points that offer Pick Up and Set Down. A capture bar below the top toolbar asks the child to choose a prop, then a spot on that prop. Hovering a spot shows its reach result beside the pin. A valid click records the exact prop and spot in the action; **Change spot** lets the child revise it. A blocked choice stays on screen with a short reason at the spot.
3. In **PLAY**, a completed reach gives that spot a short visible pulse while the prop remains in place. A **Used prop** rule can react to one named spot. An attempted interaction that cannot finish shows a brief reason near the target and does not show the success pulse.
## EDIT card and control layout
```text
Existing StageInspectorPanel
├─ Fixed header: prop icon / selected prop name / ✕ Close
└─ ScrollContainer / VBoxContainer
├─ Existing prop role controls
└─ Use spots section: VBoxContainer
├─ Heading / helper / empty-state label
├─ + Add a spot: Button
└─ Spot rows: VBoxContainer
└─ Number + Spot name: LineEdit / Move + Erase: FlowContainer
Stage overlay
├─ Visible selected-prop pins with number and name labels, plus ghost pin in EDIT
└─ Reach status beside the spot in DIRECT; no predicted approach or hand-path lines
DIRECT Use capture bar
├─ Step-aware prompt below the top toolbar and mode pill
└─ Change prop (after prop selection) + ✕ Cancel
```
- Extend the existing selected-prop `StageInspectorPanel` body after the prop role controls. Keep its icon, selected prop name, fixed **✕ Close**, body-only scroll, safe-rectangle fitting, dismissal behavior, and compact **Settings** reopen button. The card applies to a single selected placed prop. With multiple objects selected, keep the current **Select one prop to change its settings.** notice; do not add spots to all selections.
- Use a styled **Use spots** heading, one short helper sentence, a full-width **+ Add a spot** button, and a `VBoxContainer` of spot rows. The empty state says **No Use spots yet. Add one where a stickman should reach.** Rows show a stable number, the spot's editable name, and **Move** and **Erase** buttons. Each control has a pointer target of at least 44 × 44 logical pixels and uses the greater of that size and the applicable theme `button_min_height`. On a narrow card or larger font, the row wraps into a `VBoxContainer` with actions in a `FlowContainer`; it never clips the name or makes buttons smaller.
- New names are **Spot 1**, **Spot 2**, and so on until edited. The child can enter a short name such as **Button** or **Lever**. A blank edited name returns to its existing fallback; duplicate names are allowed but every row and pin retains a visible number so identical names remain distinguishable. Existing numbers stay with their spots when another spot is erased. The name field has the visible label **Spot name** for keyboard and assistive use; pressing Enter or leaving the field saves the edit. Name changes update action and rule summaries without changing which spot they target.
- Selecting a row emphasizes only that pin on the stage and scrolls or pans only enough to reveal it if off screen; it does not silently move the prop. The row's selected border and the pin's number match. The selected-row treatment follows `stage_panels.choice` states; the row's actions follow `stage_panels.action`. A spot row is an individual placed-instance control, not a reusable `.prop` asset setting.
- **+ Add a spot** and **Move** enter a focused stage-placement workflow. Keep the selected prop highlighted, show a cursor-attached pill (**Click a place on this prop** or **Click the spot's new place**), show a numbered pin ghost at the pointer, and provide a prominent **✕ Cancel** control. A valid click commits one change and returns to the card. Clicking outside the selected prop gives **Choose a place on this prop** next to the cursor and keeps placement active. Cancel, Esc, or right-click returns to the card; canceling a move keeps the old position. A spot may sit over a transparent gap within the prop's artwork bounds when it represents a handle; the visible preview and selected prop outline make the intended owner clear. The current EDIT flow uses its existing placement prompt and Cancel control; for future EDIT capture work, use the shared capture-bar pattern in the [Stage panel UI system spec](stage_panel_ui_system.md), also referenced by [AGENTS.md](../../AGENTS.md).
- **Erase** opens a themed confirmation with the spot's name and number: **Erase Button (spot 2)?** with **Keep spot** and **Erase spot** buttons. If an action or rule names that spot, the dialog also says **This spot is used in your story. Those steps will need a new spot.** After a confirmed erase, affected action and rule editors display **Choose a new spot** instead of silently using another spot or the whole prop. An edited draft with a missing spot cannot be committed; PLAY reports the missing target without firing **Used prop**. Canceling the dialog leaves the spot and references intact.
- Moving, naming, and erasing belong to the selected placement and mark the scene as changed. Moving or rotating the prop carries its pins with it. A prop-selection or mode change cancels an active Add/Move preview without committing it. The existing **Objects here** chooser remains available to select a prop covered by another object; once selected, its card rows also provide a reliable way to reach obscured pins.
## Pin appearance and hit targets
- A pin is a compact high-contrast outlined circle with its stable number and an adjacent labeled pill showing the spot name. Its pointer and touch hit region is at least 44 × 44 logical pixels at every stage zoom. Pin and label are both targetable. Color and number/text both carry meaning; do not depend on color alone.
- EDIT pins appear for the selected prop and during Add/Move, then disappear when selection moves elsewhere. DIRECT pins appear only for the prop currently being targeted by a Use action or Used prop rule. PLAY hides authored pins except for the brief completion pulse and a short failure cue. This keeps the movie view clean and distinguishes Use spots from pickup grips.
- Pin drawing sits above selected prop artwork and remains visible if a stickman or front-tier prop overlaps it. A pin has its own hit priority during the focused Use workflow; otherwise normal stage selection and the **Objects here** chooser continue to work. When nearby 44 px hit regions overlap, present named choices rather than guessing. The pin labels are the normal spot-selection affordance; do not add a second always-visible spot picker beside the prop.
- Reusable pickup grip anchors are not shown or edited in this section. Use pins use a numbered outlined circle and adjacent name label; grip anchors keep their existing purpose and appearance. Child-facing copy says **Use spot** and **Pick up** in their respective workflows, never **anchor** or **grip**.
## DIRECT Use action and feedback
- Add **Use…** alongside the existing prop actions in the stickman action menu and Action Editor. In the editor, provide a large **Choose prop and spot** button; after selection, show **Using: Button (spot 2) on Door** with **Change spot** and the existing **Done** / **✕ Cancel** pattern. A new Use action cannot be committed without both a valid prop and an exact spot. Canceling a change to an existing action preserves its previous target.
- Stage capture uses the persistent `StageUseTargetCaptureBar` below the top toolbar and **DIRECTING** pill. It starts with **Choose a prop to use** and **✕ Cancel**. After prop selection, the prompt becomes **Choose a spot on the prop**, with **Change prop** and **✕ Cancel**. If the prop has no spots, the prompt explains **This prop has no Use spots. Add one in EDIT.** The compact dark rounded panel uses a DIRECT amber accent that follows the rounded left edge; its shared visual contract is in the [Stage panel UI system spec](stage_panel_ui_system.md). Changing the prop returns to the first step and preserves the action/rule draft. If the prop is covered, the existing **Objects here** chooser offers prop candidates.
- Hovering a pin shows its reach result beside the spot, without approach or hand-path lines. The stickman remains in its authored position, so a line from a future pose would appear detached. Use DIRECT amber for capture, a green check plus **Ready to use** for a reachable preview, and a red warning plus a specific message at the pin for a known problem, such as **Can't walk there**, **Too far to reach**, or **No room to stand**. Validity uses a glyph and text as well as color.
- A known invalid pin cannot be committed; the child can select another pin or change the prop without losing the action draft. If prior queued actions make the future actor position uncertain, show one amber **?** beside the spot with **We'll check when you play.** and no guide lines. Allow the target to be recorded; PLAY rechecks and reports any failure. The preview is an aid, not a promise that later moving props or changed actions cannot affect the reach.
- The capture bar's visible **✕ Cancel** button is available throughout both capture steps, including rule targeting; **Change prop** appears after a prop is selected. Esc and right-click are optional shortcuts. Cancel returns to the invoking action or rule editor. When editing an existing target, the earlier target remains until a valid replacement is chosen and committed.
## Used prop rule authoring
- Add **Used prop** to the rule trigger choices. The rule builder first shows the acting stickman, then asks the child to choose a prop and one of its labeled Use spots. New rules always name one specific spot; there is no **Any spot on this prop** authoring button. The rule responds only after that Use reach completes. Renaming a spot updates the summary. A missing or erased referenced spot shows **Choose a new spot** in the rule editor and a visible needs-attention cue in the rules list; it never silently broadens the rule to another spot. Saved `spot_scope = "any"` rules remain supported for compatibility and are not offered as a new choice.
- Draw rule links with a quiet gray dashed connector, a green ⚡ trigger badge, and a purple → action badge. This keeps rule links visually distinct from Use reach previews and leaves amber for uncertain reach.
- Selecting a prop with no spots shows **This prop has no Use spots. Add one in EDIT.** and leaves rule target selection active. Rule spot choices do not run reach validation: they describe an event, rather than command a reach. The child can use **Change prop** to replace the target.
- Rule dialogs remain popup workflows and receive `SandboxTheme.control_theme()` explicitly. Their layout may reuse the shared choice visuals and theme tokens where suitable, but the in-stage `StageInspectorPanel` shell is not imposed on a modal editor.
## Theme and accessibility
- Reuse `StageInspectorPanel` for card shell, header, scroll, and action styling. `StagePanelToggleRow` and `StagePanelSwitchVisual` are not needed because Use spots are a collection of named points, not On/Off settings. Extend `stage_panels` tokens for spot row spacing, pin size/hit radius, pin colors, hover/focus rings, name pill, and completion pulse. Keep shared shell, type, and action tokens as the source for sizes, fonts, and surfaces. Use `SandboxTheme` `mode_colors.edit` / `mode_colors.direct` and overlay valid/invalid colors; add named tokens for any new visual state in both the JSON contract and defaults during implementation.
- All card controls, action/rule choices, overlap choices, confirmation buttons, and active Cancel controls meet the 44 × 44 logical-pixel minimum and applicable `button_min_height`. Dynamic rows wrap or scroll at larger fonts. Dialogs and popup windows explicitly use `SandboxTheme.control_theme()`.
- Keyboard users can Tab through Add, name, Move, Erase, Cancel, and Done. Space/Enter activates a focused button; Enter commits a name. A selected card row can invoke **Move** without drag and drop. Focus remains visible over selected styling. Overlap-choice popups remain keyboard-operable. A separate always-visible spot list is not part of the stage targeting UI; if keyboard-only stage target capture is required, it remains a follow-up accessibility gap.
- If a prop or spot disappears before PLAY or while an action is queued, show **Prop is gone** or **Use spot is gone** at the action/actor and mark its queue entry for repair. No completion pulse or **Used prop** rule fires. If reach fails during PLAY, show a short reason at the prop; the child can return to DIRECT and use **Change spot**. Stopping PLAY restores the authored pin presentation and positions.
- The completion cue is a short ring expanding from the used spot, fading after about 0.6 seconds. It signals the completed touch without suggesting an invented door, switch, or light effect. It follows the spot if the prop is moving, does not cover the movie with labels, and can repeat for a later successful interaction. Respect the project's reduced-animation behavior if provided; a brief static highlight can carry the same cue.
### Proposed theme extensions
| Token group | Values to add or reuse |
| --- | --- |
| `stage_panels.spots` | `row_gap`, `pin_visual_size`, `pin_hit_size` (minimum 44), `pin_outline_width`, `focus_ring_width`, `name_pill_padding`, `pin_surface_color`, `pin_outline_color`, `completion_duration` |
| `stage_panels.shell`, `.type`, `.choice`, `.action` | Existing card geometry, section/body/helper fonts, selected/hover/focus surfaces, row and button minima; no copied parallel panel styles |
| `mode_colors.edit`, `mode_colors.direct`, `overlays.valid`, `overlays.invalid` | Existing mode accents and preview state colors; neutral/uncertain state derives from a named Use-preview token if no existing token fits |
The architecture spec may split the new token group into shared row and Use-preview subgroups where another consumer exists. New values must remain theme-owned, including fallback values.
## Exact copy map
| Context | Child-facing copy |
| --- | --- |
| Card section | **Use spots** · **Stickmen can touch these places without picking up the prop.** · **+ Add a spot** |
| Empty card | **No Use spots yet. Add one where a stickman should reach.** |
| Spot row | **Spot name** · **Spot 1** (editable fallback) · **Move** · **Erase** |
| EDIT capture | **Click a place on this prop** · **Click the spot's new place** · **Choose a place on this prop** · **✕ Cancel** |
| Erase dialog | **Erase Button (spot 2)?** · **This spot is used in your story. Those steps will need a new spot.** · **Keep spot** · **Erase spot** |
| DIRECT action/editor | **Use…** · **Choose prop and spot** · **Using: Button (spot 2) on Door** · **Change spot** · **Done** · **✕ Cancel** |
| DIRECT capture bar | **Choose a prop to use** · **Choose a spot on the prop** · **Change prop** · **✕ Cancel** · **This prop has no Use spots. Add one in EDIT.** |
| Reach feedback | **Ready to use** · **Can't walk there** · **Too far to reach** · **No room to stand** · **We'll check when you play.** |
| Rule editor | **Used prop** · **Choose prop** · **Choose a labeled spot** · **Choose a new spot** |
| PLAY failure | **Prop is gone** · **Use spot is gone** · **Can't reach this spot** |
## Acceptance criteria for UI review
1. A child can add, name, move, erase, save, reload, and find at least two Use spots on one placed prop; a second copy of the same asset has independent spots. Moving the prop keeps its pins aligned with the artwork. Pickup grip editing and `.prop` asset controls are unchanged.
2. The selected prop card remains usable on short or narrow stages and with enlarged fonts. Its header and Close stay visible, every spot row and action is reachable, and a covered prop's spots can be selected through **Objects here** and the card list.
3. Add/Move capture shows the correct selected owner, a cursor ghost, a nearby instruction, and a visible Cancel. Invalid clicks leave the draft active; canceling Move restores the prior location. A visually compact pin still has a 44 × 44 hit target across zoom levels.
4. A DIRECT Use action can target either of two named spots on the same prop. The per-spot preview identifies known blocked choices beside the target without drawing detached approach/reach paths, and lets the child correct them. Action Editor and stickman action menu agree on the target and support changing it.
5. A **Used prop** rule names one specific spot and fires only after a completed interaction. A successful PLAY reach has a brief pulse; failures give local feedback and do not imitate completion.
6. Deleting a referenced spot never silently retargets an action or broadens a rule. Repair prompts identify the missing target. Selection, focus, and valid/invalid states remain understandable without color alone, and pointer, touch, and keyboard users can complete the workflow.
## Implementation and verification status
- The core implementation is present: instance pins and labels, EDIT Add/Move capture, the DIRECT Use capture bar, per-spot preview status, Use actions, named-spot rule targeting, runtime completion/failure cues, and saved-scene persistence. The capture bar currently serves DIRECT Use and Use-related rule capture. EDIT spot placement retains its current instruction and Cancel control; future EDIT capture workflows should follow the shared bar pattern linked above.
- This specification records the approved behavior and current presentation. The presence of implementation does not mean the visual details or runtime behavior have been verified in-editor. User verification remains pending.