Add Stage Surface Color Dialog and Background Ground Functionality
- Implemented StageSurfaceColorDialog for custom color selection with live preview. - Created StageBackgroundGround class for non-walkable scenery behind the set. - Introduced StageRectSurface for shared rectangular geometry and tint preview. - Added StageSurfacePreview for rendering surface previews. - Developed comprehensive tests for surface color selection, rendering, and serialization.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# Phase 19 follow-up — Background ground and wall color: architecture specification
|
||||
|
||||
> **Status:** Proposed for final sign-off. Implements the accepted [product brief](../plans/phase_19_background_ground_and_wall_color.md) and [UI/UX specification](phase_19_background_ground_and_wall_color_ui_ux.md). This is a Stage follow-up; the existing walkable Ground, Prop physics, and artwork formats retain their contracts.
|
||||
|
||||
## Architectural decision
|
||||
|
||||
Use one small rectangular-surface contract for two distinct inert scenery nodes: `StageWallPanel` and `StageBackgroundGround`. Keep separate scene object types, finish allowlists, draw bands, and erase commands. A shared renderer draws the same material in the chooser, placement ghost, selected-piece preview, and finished scene. `SandboxStage` owns selection, mutations, dirty state, and dialogs; visual controls emit intent only.
|
||||
|
||||
The chosen order is **Sky → Background ground → wall panels → Far artwork → Near artwork → walkable Ground → On the floor artwork → action elements**. Add `StageDrawOrder.BACKGROUND_GROUND = -50` below the existing `WALL = -40`; the Sky stays in its existing background layer. Background ground remains a direct `World` child and has no `CollisionObject2D`, navigation region, support, pickup, Use target, or event identity. Its only Stage behavior outside drawing is Scenery editing.
|
||||
|
||||
```text
|
||||
SandboxStage (scene and input owner)
|
||||
├─ Sky CanvasLayer / StageSkyView
|
||||
├─ World : Node2D
|
||||
│ ├─ StageBackgroundGround : StageRectSurface : Node2D z = -50
|
||||
│ ├─ StageWallPanel : StageRectSurface : Node2D z = -40
|
||||
│ ├─ StageSceneryArtwork (Far / Near / Floor)
|
||||
│ └─ TerrainBlock (walkable Ground; existing collision/navigation)
|
||||
├─ StageSceneryController (placement and transform snapshots)
|
||||
├─ StageSelection / StageGizmos (Scenery scope)
|
||||
└─ UI CanvasLayer
|
||||
├─ AssetSelector (bounded gallery and setup view)
|
||||
├─ StageInspectorPanel (selected Scenery)
|
||||
├─ Stage capture bar
|
||||
└─ themed color and erase dialogs
|
||||
```
|
||||
|
||||
## Typed nodes and API contracts
|
||||
|
||||
| Target | Contract and responsibility |
|
||||
| --- | --- |
|
||||
| New `scripts/stage/scenery/stage_rect_surface.gd`, `class_name StageRectSurface extends Node2D` | Holds `panel_size: Vector2`, `finish_id: StringName`, `tint: Color`, and a transient preview-color flag/value. `func get_local_bounds() -> Rect2` returns a centered positive rectangle. `func set_preview_tint(color: Color) -> void`, `func clear_preview_tint() -> void`, and `func get_render_tint() -> Color` affect drawing only; `tint` remains the saved value. Do not add collision or asset-path state. |
|
||||
| Existing `scripts/stage/scenery/stage_wall_panel.gd`, `class_name StageWallPanel extends StageRectSurface` | Preserve `func configure(new_size: Vector2, finish: StringName, color: Color) -> void` and `motif_origin_offset: Vector2` for erased fragments. Validate only `finish_wall_paint`, `finish_wall_brick`, `finish_wall_wood`; set wall draw order. `_draw()` calls `StageSurfaceRenderer.draw_wall(...)` with `get_render_tint()`. Wall-area subtraction still tests the concrete wall type. |
|
||||
| New `scripts/stage/scenery/stage_background_ground.gd`, `class_name StageBackgroundGround extends StageRectSurface` | `func configure(new_size: Vector2, finish: StringName, color: Color) -> void`; allow only `finish_background_grass`, `finish_background_concrete`, `finish_background_carpet`, set the backdrop draw order, and draw a bounded world-anchored motif. Grass/Concrete ignore Color controls but retain `tint` while the child switches looks; Carpet uses it. Its `get_local_bounds()` supports Stage selection and gizmos. |
|
||||
| Existing `scripts/stage/scenery/stage_surface_renderer.gd`, `class_name StageSurfaceRenderer extends RefCounted` | Add `BACKGROUND_FINISHES: Array[StringName]`, `is_background_finish(id: StringName) -> bool`, `default_tint(finish: StringName) -> Color`, and `draw_background(canvas: CanvasItem, rect: Rect2, finish: StringName, tint: Color, world_origin: Vector2, drawing_scale: Vector2 = Vector2.ONE, cull_to_viewport: bool = true) -> void`. Keep `draw_wall` and `draw_ground`; route preview and Stage through the same finish functions. The default tint is finish-specific, never the old universal green wall value. |
|
||||
| Existing `scripts/stage/stage_draw_order.gd`, `class_name StageDrawOrder extends RefCounted` | Add `BACKGROUND_GROUND: int = -50`. Existing semantic bands and terrain ordering remain stable. |
|
||||
| Existing `scripts/editors/stage/stage_scenery_controller.gd`, `class_name StageSceneryController extends Node` | Add `begin_background_ground(finish: StringName, initial_world_position: Vector2, tint: Color) -> void`. Reuse the existing wall rectangle drag/two-corner, snap, minimum-size, ghost, repeated-placement, and cancel lifecycle through a shared rectangular placement helper. Accept both concrete rectangle types for single and group move/resize; preserve root scale at 1 and change `panel_size`. Keep wall-area eraser restricted to `StageWallPanel`. Existing `placement_committed(node: Node2D)` and `document_change_requested()` signal contracts remain. |
|
||||
| Existing `scripts/stage/stage_selection.gd`, `class_name StageSelection extends RefCounted`, and `scripts/stage/stage_gizmos.gd` | Include `StageBackgroundGround` only in `SelectionScope.SCENERY`, use its transformed `get_local_bounds()` for hit tests and handles, and include it in box selection. `hit_test_all()` and **Pieces here** list overlapping wall, artwork, and backdrop pieces, frontmost first. Ordinary ACTION selection still excludes all scenery surfaces. |
|
||||
| Existing `scripts/editors/stage/stage_scene_adapter.gd`, `class_name StageSceneAdapter extends RefCounted`, and `scripts/stage/stage_scene_io.gd`, `class_name StageSceneIO extends RefCounted` | Add explicit `background_ground` encode/decode/validation and an old-scene color migration (below). Do not route decorative ground through `TerrainBlock` or Ground finish serialization. |
|
||||
| Existing `scripts/editors/stage/sandbox_stage.gd`, `class_name SandboxStage extends Node2D` | Owns selected-piece Look/Color/Erase handlers, color-dialog session, shared recent colors, confirmation target, dirty-state transition, and chooser-to-controller handoff. Refresh widgets with no-signal setters. It must include Background ground in all Scenery-only membership, selection, deletion, save, and mode gates. |
|
||||
|
||||
Keep exact concrete-type checks wherever behavior truly differs, especially wall-area erase. For common rectangle handling, use `node is StageRectSurface` or a small typed helper instead of another list of exact-script comparisons. Do not broaden physics or action selection by using a generic `Node2D` group.
|
||||
|
||||
### Style and color rules
|
||||
|
||||
The selected `Color` is a material base tone. Paint fills directly. Brick uses that tone for brick faces and derives bounded light/dark face variants; mortar remains a legible neutral with contrast against the chosen face. Wood derives plank, grain, highlight, and seam tones from the chosen base while preserving the plank pattern. Carpet derives restrained lighter/darker fibers from its chosen tone. Very light, dark, and saturated colors must still show texture; use a deterministic luminance-aware palette transform with bounded contrast, rather than `CanvasItem.modulate` or a flat overlay. Grass and Concrete have designed palettes and no Color control in this follow-up. All four colorable looks can use imaginative hues.
|
||||
|
||||
New choices use opaque colors. Preserve any valid eight-digit color already stored in an older scene until the user edits it. `StageSurfaceRenderer.default_tint()` supplies visually natural Brick and Wood defaults plus usable Paint and Carpet defaults. Switching Look retains the piece's saved `tint`; switching from Carpet to Grass and back reveals the same Carpet color. In the chooser, each new look starts at its own default until the child explicitly chooses a color; after that, a chosen color follows the child between colorable looks. Merely opening the chooser or drawing with an untouched default does not add history.
|
||||
|
||||
### World-anchored pattern and drawing cost
|
||||
|
||||
Map every motif sample to an integer world-grid cell, seeded only from cell coordinates and finish ID. Clip motif geometry to the panel rectangle and to the visible local viewport rectangle. Moving or resizing a piece changes which world cells are visible; it never scales or restarts a texture from that piece's top-left corner. Adjacent same-finish pieces therefore share pitch and phase. Keep a stable world-space pitch at normal zoom. If a distant zoom needs a coarser level of detail, derive the level from **camera zoom/viewport transform**, not individual panel size; every piece at the same zoom must choose the same level. At small projected size, draw the material base and a restrained subset of motifs. Cap visible-cell work per draw and avoid a full-panel loop for broad backgrounds.
|
||||
|
||||
The existing `StageSurfaceRenderer` brick/wood detail functions currently adapt to the visible rectangle; the follow-up must make any detail-level decision common across adjacent pieces. Existing walkable Ground's grass is concentrated at its top edge; Background ground Grass needs deterministic interior tufts/color variation distributed over its whole rectangle. Concrete needs restrained joints/speckle rather than reusing Sidewalk unchanged. Carpet uses low-contrast short fibers or weave marks at world scale. The setup preview renders a representative world-sized sample with the same renderer, fitted into its card/preview `Control`; it does not stretch an image of the final rectangle. Decorative preview controls ignore pointer input. The Stage ghost can use the same node with alpha modulation and the existing EDIT outline/corner overlay.
|
||||
|
||||
## Scene schema and compatibility
|
||||
|
||||
Advance `StageSceneIO.FILE_VERSION` from `"1.5"` to `"1.6"`; retain reads of v1.0–v1.5. New pieces use a distinct type:
|
||||
|
||||
```json
|
||||
{
|
||||
"file_id": "background_ground_1",
|
||||
"type": "background_ground",
|
||||
"position": {"x": 420.0, "y": 180.0},
|
||||
"rotation": 0.0,
|
||||
"size": {"x": 800.0, "y": 360.0},
|
||||
"finish_id": "finish_background_carpet",
|
||||
"color": "#8B537BFF"
|
||||
}
|
||||
```
|
||||
|
||||
`size` must be finite and positive, `finish_id` must be one of the three background IDs, and `color` must pass the existing `StageSceneIO` hex parser. Store Color for every piece, including Grass/Concrete, so changing Look preserves it. Serialize the live `tint`, never the transient preview tint. Preserve `World` child order when collecting/restoring so overlaps of pieces in the same draw band stay visually stable; the distinct draw bands determine inter-category order. Background pieces need no `scale` field: a resize edits `size`, retaining world pattern pitch. Old `wall_panel` entries keep their existing fields, including optional `motif_origin_offset`; Paint colors remain intact.
|
||||
|
||||
For v1.5 and earlier documents, migrate a Brick or Wood wall's color **only when its saved value is the old default `#5A9D70` or the color is absent** (case-insensitive, optional `#`, with opaque `FF` accepted). Replace that placeholder with the corresponding natural `default_tint()` before validating/loading, because old Brick/Wood drawing ignored the saved tint. Preserve Paint and every non-placeholder Brick/Wood color exactly, including alpha. Never run this rule on v1.6: a child may deliberately choose the same green for Brick or Wood. Missing Paint color keeps its old default. Migration changes the in-memory document; the new version and color are written on its next normal save. Do not alter source files merely on open. Invalid enum/color/size must remain a load error under existing validation rules, and a failed load must not replace the current scene.
|
||||
|
||||
The Stage's `user://sandbox_settings.json` gains `recent_surface_colors: Array[String]`, independent of `.scene` files. Canonicalize to uppercase opaque `#RRGGBB` for new history entries; validate on load, discard malformed/duplicate entries, keep at most eight newest first, and tolerate the key being absent. A valid preset/recent press or confirmed custom color moves it to the front. A canceled dialog, opening a picker, switching Look, and placing an untouched default do not change history. Extend the existing settings load/save methods so saving grid/snap/recent scenes also retains this list. A history write never marks the scene dirty or recolors placed pieces; a placed-piece confirmed color change marks that scene dirty once.
|
||||
|
||||
## UI composition and signal flow
|
||||
|
||||
`AssetSelector` keeps the existing bounded `PopupPanel`, fixed title/Close, tab shell, and scroll body. For Scenery Backgrounds, its first view shows Sky, Wall panel, and Background ground cards; the latter two navigate to an internal setup view. `StageAssetBrowser` supplies IDs/labels, while the chooser owns transient finish/color choice and emits a typed-intent payload through its existing `item_selected(entry: Dictionary)` signal only when **Draw** is pressed. Suggested payload:
|
||||
|
||||
```gdscript
|
||||
{
|
||||
"background_kind": "background_ground", # or "wall"
|
||||
"finish_id": "finish_background_carpet", # String
|
||||
"color": Color("#8B537B"), # in-memory Color, never a saved Variant
|
||||
}
|
||||
```
|
||||
|
||||
Sky continues its immediate-selection route. Choosing a look or color in setup updates only setup state; no Stage tool starts and no existing piece changes. `SandboxStage` closes the chooser, calls the correct `StageSceneryController.begin_*` function with the exact chosen finish/color, and displays the patterned ghost. The chooser's setup view uses native focusable look buttons, one shared wrapping Color control, a representative renderer preview, Back, and Draw inside the bounded scrolling area. It does not inherit `StageInspectorPanel`, whose layout and dismissal rules serve selected Stage objects.
|
||||
|
||||
For selected pieces, extend the **existing** Scenery `StageInspectorPanel` body. Use its fixed header, safe rectangle, scroll position, `style_action_button`, and compact Settings reopen behavior. `StagePanelChoiceGroup` owns **Look** choices and emits `choice_selected(id: StringName)`; its cue controls are render-only. Add one shared `scripts/shared/stage_ui/stage_surface_color_controls.gd`, `class_name StageSurfaceColorControls extends VBoxContainer`, for current color, up to eight recent swatches, presets, and **More colors…**. Its typed contract is `signal color_selected(color: Color)`, `signal custom_color_requested()`, `func configure(selected: Color, recents: Array[Color], presets: Array[Color], stage_theme: SandboxTheme) -> void`, and `func set_selected_no_signal(color: Color) -> void`. Extend the existing `ColorSwatchSelector` from a single row to a wrapping `FlowContainer`, preserving `configure(colors: Array[Color], selected: Color, theme: Variant)`, `set_selected`, and `color_selected(color: Color)`. Refresh both recent and preset groups without emission. `StagePanelToggleRow` is not used because no surface option is boolean. No second inspector shell or choice styling is introduced.
|
||||
|
||||
The custom color dialog uses a native `ColorPicker` in a themed, screen-bounded dialog with its own representative material preview, **Use color**, and **✕ Cancel**. It adopts `SandboxTheme.control_theme()` and popup/dialog tokens, not the Stage inspector hierarchy. For a selected piece, `SandboxStage` holds a provisional session `{target: WeakRef, original_color: Color, tentative_color: Color}`. `ColorPicker.color_changed(color: Color)` calls `target.set_preview_tint(color)` and redraws the dialog preview; it does **not** change `target.tint`, recent history, scene dirty state, or serialized snapshots. **Use color** validates the target and commits `tint = tentative_color`, clears the preview override, records history, and marks the scene dirty once if the color actually changed. **Cancel**, dialog close, Esc, target deletion, scene replacement, and mode switch clear the preview override without a document edit. Because `StageSceneAdapter.collect()` reads `tint`, ordinary Save/autosave cannot serialize a tentative color. For chooser use, the session targets only setup state and its dialog preview; Cancel restores the prior chooser color. A preset/recent press is already confirmed: update the one selected piece immediately, record history, mark dirty once if changed, and refresh the card without sending another choice signal.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Chooser[Scenery setup: Look + Color] -->|Draw payload| Stage[SandboxStage]
|
||||
Stage -->|begin_wall / begin_background_ground| Draft[StageSceneryController ghost]
|
||||
Draft -->|valid release| Piece[World surface node]
|
||||
Piece -->|document_change_requested| Dirty[Scene dirty + save]
|
||||
Piece -->|Edit Scenery select| Card[StageInspectorPanel]
|
||||
Card -->|Look or confirmed swatch| Stage
|
||||
Card -->|More colors| Dialog[Provisional ColorPicker]
|
||||
Dialog -->|color_changed| Render[Transient render tint]
|
||||
Dialog -->|Use color| Stage
|
||||
Dialog -->|Cancel| Restore[Clear render tint]
|
||||
```
|
||||
|
||||
### Erase and selection rules
|
||||
|
||||
Background ground is erasable. In **Edit Scenery**, select one Background ground piece and press **Erase background ground**. The shared confirmation asks **“Erase this piece?”** with **Keep it** and **Erase**. The Delete/Backspace path for a selected Scenery surface calls the same confirmation; it must not fall through to `SandboxStage.delete_selected()`'s immediate generic deletion. Confirmation captures the intended node as a weak reference, checks it is still a live `World` child in Edit Scenery, erases only that node, clears selection/hover, and marks the scene dirty once. Keep it, Cancel, Esc, or closing the confirmation leaves the piece and dirty state unchanged. Wall panels use the corresponding **Erase wall panel** action; **Erase wall area** remains a separate wall-only rectangle subtraction and never cuts Background ground. A group delete, if exposed, must present its member count and follow the existing Phase 19 group confirmation boundary.
|
||||
|
||||
The backdrop's broad area must not capture normal EDIT clicks. In Scenery scope, a higher draw band wins a click; a smaller visible item at equal depth wins, and **Pieces here** lists all hits so a covered background remains reachable. Scenery box selection and group move/resize include Background ground; group preview restores exact size/position on Cancel and commits once on Keep. Selection outlines and corner hit regions follow the existing minimum 44 × 44 logical-pixel Stage gizmo contract. Mode/tool switches cancel an unfinished placement or provisional edit before changing scope. The capture bar reuses the Stage's existing EDIT capture pattern, puts prompt and visible **Done / ✕ Cancel** together below the mode pill, and names the non-walkable role.
|
||||
|
||||
## Theme, focus, and responsive layout
|
||||
|
||||
The selected-piece card adopts `stage_panels.shell`, `.type`, `.choice`, and `.action` from `SandboxTheme.DEFAULT_CONFIG` and `sandbox_theme.json`; add narrowly scoped swatch/preview tokens only for values those groups cannot express. The chooser and color dialog use `popups.asset_selector` / `popups.dialog` and `SandboxTheme.control_theme()`. Create local StyleBox copies for selected/hover/focus states; do not mutate the cached shared `Theme`. Every swatch/button target is at least 44 × 44 logical pixels, with text or a color value as well as its fill. Keep native focus and Space/Enter activation, separate focus ring and selection border, and restore focus to the dialog opener on close.
|
||||
|
||||
Fit both popup windows to the usable display rectangle, accounting for the current root window and OS usable screen area. Keep chooser title/Close and inspector header/Close fixed while their contents scroll. `FlowContainer` wraps colors and look cards before horizontal clipping; Draw and dialog actions remain reachable when fonts grow or the display is short. The inspector uses `set_safe_rect()` and preserves scroll position during a Look/Color change. The Stage capture bar stays below the mode pill and does not cover it.
|
||||
|
||||
## Implementation sequence and repeatable behavior review
|
||||
|
||||
1. Add the scene v1.6 migration/validation and `background_ground` adapter path; preserve old-scene visuals and verify malformed entries are rejected before replacing the live scene.
|
||||
2. Add the common rectangle contract, draw band, and deterministic material rendering. Connect renderer samples to chooser cards, setup preview, ghost, and live nodes.
|
||||
3. Extend Scenery placement, selection, gizmos, group transforms, inspector, erase confirmation, and dirty/save boundaries. Keep wall-area erase concrete-wall-only.
|
||||
4. Extend the shared swatch control and user-settings history, then wire the bounded chooser and provisional custom-color dialog.
|
||||
|
||||
| Child path / runtime scenario | Required observation |
|
||||
| --- | --- |
|
||||
| Scenery → Backgrounds → Wall panel → Brick → default Draw; repeat with chosen color | Both ghosts and placed walls show brick mortar/detail and the chosen hue. The default looks like natural brick. Drawing with an untouched default adds no recent color. |
|
||||
| Scenery → Backgrounds → Background ground → Grass → draw broad rectangle; place a walkable Ground strip in front; PLAY | Grass detail appears across the backdrop, buildings/trees appear in front, and the stickman walks only on the separate Ground strip. Backdrop adds no collision/navigation/event target. |
|
||||
| Draw adjacent Grass or Carpet pieces; move/resize one; zoom in/out | Motif pitch does not stretch and matching neighbors align at the same zoom. A large partly off-screen piece draws only bounded visible detail. |
|
||||
| Edit Scenery → select one wall → Look/Color; select one Carpet → Color | Only the selected piece changes. Changing Carpet to Grass and back restores its saved carpet color. Inspector scroll and selection remain stable; chooser/ghost/Stage material character agrees. |
|
||||
| More colors… on selected Brick/Carpet → change picker several times → Cancel; repeat → Use color | Stage and dialog preview each tentative color. Cancel restores original and adds no recent or scene edit, even if autosave occurs. Use color commits once and moves that color to the front of recents. |
|
||||
| Select eight colors across Paint, Brick, Wood, Carpet; reopen scene and restart Stage | One shared newest-first, deduplicated eight-color list survives restart. Each saved piece keeps its own color; history changes do not recolor another piece. |
|
||||
| Load a v1.5 scene with old green Brick/Wood and Paint; load another with a non-placeholder Brick color; save/reopen | Placeholder Brick/Wood take natural defaults, Paint and the non-placeholder color are preserved, and v1.6 save/reopen preserves the result. An intentionally green v1.6 Brick remains green. |
|
||||
| Edit Scenery → select Background ground through its exposed area or Pieces here → resize/group preview/Cancel/Keep | It is selectable only in Scenery scope, handles remain reachable, Cancel restores exact geometry, Keep commits one change, and normal EDIT clicks still reach interactive objects. |
|
||||
| Select Background ground → Erase background ground → Keep it; then Delete → Erase | Keep it leaves the scene unchanged. Erase removes exactly the captured piece after confirmation and leaves walkable Ground intact. Wall-area eraser cannot cut Background ground. |
|
||||
| Narrow/short display with larger fonts; chooser setup and custom color dialog | Close, Back, Draw, More colors, Use color/Cancel, inspector Close, and capture-bar Cancel remain visible or scroll-reachable by touch and keyboard; no UI spills below the usable screen or covers the mode pill. |
|
||||
|
||||
These scenarios are product behavior checks. They do not require a new test suite by themselves; implementers should use the project’s existing verification routes where needed to establish the behavior, especially scene loading, renderer output, and live selection.
|
||||
|
||||
## Trade-offs and limits
|
||||
|
||||
- A shared rectangle base avoids repeated size, bounds, and transient-preview behavior. Distinct concrete nodes and scene types keep semantic draw order, finish validation, and wall-only erasure explicit. A single `TerrainBlock` or one material-kind enum would blur physics and editing boundaries.
|
||||
- World-grid procedural motifs keep resizing and adjoining pieces coherent without texture assets. Visible-region culling and zoom-consistent detail control limit draw work; if many giant overlapping surfaces later become costly, cache only measured hot paths rather than introducing a render-texture cache now.
|
||||
- Scene v1.6 migration is necessary because v1.5 stored unused Brick/Wood green. It changes only the known placeholder on older documents; ambiguous intentional uses of that exact placeholder cannot be distinguished retrospectively. All v1.6 colors are authoritative.
|
||||
- A transient render tint keeps custom-color preview visible while the saved model and autosave remain stable. It avoids copying live Stage nodes into a modal overlay or treating tentative input as an undoable document edit.
|
||||
- Recent colors are per-user settings; placed colors are document data. Keeping these sources separate makes scene sharing and restart behavior predictable.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Phase 19 follow-up — Background ground and wall color: UI/UX specification
|
||||
|
||||
> **Status:** UI/UX handoff for the accepted [Background Ground and Wall Color brief](../plans/phase_19_background_ground_and_wall_color.md). This extends the [approved Phase 19 Scenery UI specification](phase_19_scenery_backgrounds_ui_ux.md); its existing Sky, artwork, Edit Scenery, and placement behavior still applies.
|
||||
|
||||
## Child promise
|
||||
|
||||
A child can choose what a wall or distant ground area looks like, see that look before drawing, and change one placed piece later. **Background ground is scenery:** it looks like ground, stays behind the set, and does not make a walking path. The separate **Ground** palette tool remains the way to make a path that stickmen can walk on.
|
||||
|
||||
The Scenery chooser supplies the same material and color choices for new pieces that the selected-piece card supplies for placed pieces. The color shown in the chooser, its preview, the placement ghost, and the finished piece must agree. Paint, Brick, Wood, and Carpet share eight recently used colors. Grass and Concrete have designed looks and no Color control in this follow-up.
|
||||
|
||||
## Flow and navigation
|
||||
|
||||
```text
|
||||
Scenery → Backgrounds
|
||||
Sky → existing immediate Sky cards
|
||||
Wall panel → Paint / Brick / Wood → choose color → Draw wall panel
|
||||
Background ground → Grass / Concrete / Carpet → choose color for Carpet → Draw background ground
|
||||
|
||||
Edit Scenery → select one piece → Look / Color where available / Erase
|
||||
```
|
||||
|
||||
- **Backgrounds** first shows three large, illustrated choices in this order: **Sky**, **Wall panel**, **Background ground**. Wall panel and Background ground open a setup view *inside the existing bounded Scenery chooser*. An explicit **‹ Back** returns to the three choices; the fixed **✕ Close** dismisses the chooser. Sky keeps its approved immediate-selection behavior.
|
||||
- The setup view shows one clearly selected look, a large representative preview, the Color control when that look supports color, a short role helper, and a large **Draw wall panel** or **Draw background ground** action. Entering the view selects the natural default look and its default color. **Draw** is available immediately: a child can use the default without opening Color.
|
||||
- Choosing a look updates the preview in place; it does not start placement or close the chooser. Wall look choices are **Paint**, **Brick**, **Wood**. Background ground choices are **Grass**, **Concrete**, **Carpet**. Before a child chooses a color, each new look uses its believable finish-specific default; after the child chooses a color, switching looks keeps that chosen color. Grass and Concrete show their designed appearance; Carpet begins with a usable default fabric color.
|
||||
- The chooser closes only after **Draw**. The Stage then arms the corresponding rectangle tool with exactly the chosen look and color. Returning to Scenery later may show the last choice for convenience, but opening the chooser must not modify an existing piece or silently start a tool.
|
||||
- Artwork keeps the existing **Artwork** tab. **Edit placed scenery** remains reachable from the chooser. The chooser retains a fixed title/Close row, a scrollable content area, and its existing bounded screen layout. The setup view does not become an in-stage inspector.
|
||||
|
||||
## Setup view layout and controls
|
||||
|
||||
Use the existing Scenery chooser shell, card treatment, and theme. The contents of its scroll area should follow this structure:
|
||||
|
||||
```text
|
||||
ScrollContainer
|
||||
└─ VBoxContainer
|
||||
├─ Back + category heading
|
||||
├─ Look heading
|
||||
├─ adaptive look cards (one native Button per look)
|
||||
├─ large live material preview
|
||||
├─ Color section (Paint / Brick / Wood / Carpet only)
|
||||
│ ├─ selected-color swatch/button
|
||||
│ ├─ Recently used + wrapping swatches (up to eight)
|
||||
│ ├─ Colors + wrapping preset swatches
|
||||
│ └─ More colors… button
|
||||
├─ role helper
|
||||
└─ Draw action
|
||||
```
|
||||
|
||||
Look cards show material detail at card size, a stable name, a clear selected border and fill, and a separate keyboard focus ring. The large preview uses the same drawing rules as the placed surface at an appropriate scale; Brick mortar, Wood grain, Grass interior detail, Concrete treatment, and Carpet fabric remain visible. Preview decorations ignore pointer input. A selected color swatch shows the actual color as well as a text label or readable color value, so selection never depends on color alone.
|
||||
|
||||
The color row uses two wrapping groups: **Recently used** first, then **Colors** for common presets. Eight recents fit across multiple rows on narrow displays; the group never expands the popup horizontally. An empty history shows **“Colors you use will appear here”** rather than eight blank targets. Duplicate colors appear only once in the history. Each swatch is a single focusable button with a visible selected state, focus ring, and a readable name or color value in its accessible/tooltip text. Touch and click targets are at least 44 × 44 logical pixels. Extend the existing shared `ColorSwatchSelector` or extract a common swatch control instead of building a second swatch style for the chooser and inspector.
|
||||
|
||||
Choosing a recent or preset swatch changes the chosen color and preview immediately. That confirmed choice moves to the front of the shared recent list. The history is shared by Wall Paint, Brick, Wood, and Background ground Carpet, persists across scenes and restarts, and changes no already placed pieces. Merely opening the chooser or drawing with an untouched default color does not create a recent entry.
|
||||
|
||||
**More colors…** opens a bounded, themed color dialog. It shows the color picker and a live preview of the *current look* with the tentative color. The actions are **Use color** and **✕ Cancel**. **Use color** confirms the new color, closes the dialog, updates the setup preview, and adds that color to Recently used. **Cancel**, the dialog Close control, or Esc restores the color held before the dialog opened and leaves history unchanged. The dialog receives `SandboxTheme.control_theme()`, uses popup/dialog tokens, and clamps to the usable display area; its controls scroll if necessary while both actions stay reachable. Returning from the dialog restores focus to **More colors…** or the color control.
|
||||
|
||||
## Drawing on the Stage
|
||||
|
||||
- Drawing a wall keeps the existing **Draw wall panel** rectangle behavior. Drawing Background ground uses the same drag or two-tap corner interaction, snap feedback, minimum valid size cue, and repeated-placement tool lifecycle. The background surface is finite; releasing a valid rectangle places one piece, with **Done** and **✕ Cancel** remaining available for another placement.
|
||||
- The Stage shows a translucent, **patterned** ghost under the cursor before the first corner and a continuously updating patterned rectangle while dragging. The ghost uses the selected material and color. The motif stays at a stable Stage scale as the rectangle changes size; resizing reveals more of it instead of stretching it. A bright EDIT outline and corner reticle make the intended area visible over any Sky. Invalid drafts use the existing red outline and a plain instruction: **“Make this area bigger.”**
|
||||
- Keep the existing screen-space capture-bar position below the toolbar and EDIT mode pill, using the shared Stage capture-bar pattern and `mode_colors.edit` accent. The bar names the current action and keeps **Done** and **✕ Cancel** together. A short pointer pill follows the cursor: **“Drag to draw wall”** or **“Drag to draw background ground.”** The prompt stays near the working point; the header and status bar do not carry essential instructions.
|
||||
- The chooser and capture bar both explain the role in words. The Background ground card and setup helper say **“Decorative only · Stickmen don’t walk here.”** Its capture bar can shorten this to **“Draw background ground · Stickmen don’t walk here.”** The Wall panel helper says **“Looks like a wall · Stickmen can pass through.”** These messages remain visible without hover and do not rely on color or icons.
|
||||
- Cancel during a draft discards the draft. A completed piece stays when placement ends, matching existing Scenery behavior; after one placement, the capture bar includes **“Placed pieces stay.”** Esc and right-click remain shortcuts, while the visible buttons serve touch and keyboard users. Switching mode or tool removes the unfinished ghost.
|
||||
|
||||
## Editing a placed piece
|
||||
|
||||
**Edit Scenery** is the only ordinary selection mode for broad wall and Background ground pieces. A regular EDIT click still favors characters, Props, and walkable Ground. In Edit Scenery, clicking an exposed part or edge of a background piece selects it; a smaller visible item under the pointer wins over a broad background. The existing **Pieces here** chooser lists all matching pieces with large labeled rows when they overlap, so a covered Background ground or wall remains selectable. Background ground joins Scenery box selection and multi-piece movement/resizing under the existing group preview rules.
|
||||
|
||||
One selected piece uses the existing `StageInspectorPanel` shell: fixed name/icon/Close header, scrollable body, safe-screen fitting, and the compact **Settings** button to reopen a dismissed card. The body uses `StagePanelChoiceGroup` for **Look**, stable choice labels, and preview cues. It uses the same wrapping Color control as the chooser, with the selected piece's saved color as the starting value. Theme and minimum heights come from `stage_panels` and `SandboxTheme`; the card is not a separate visual system.
|
||||
|
||||
| Selected piece | Header and controls |
|
||||
| --- | --- |
|
||||
| Wall panel | **Wall panel**; **Look:** Paint / Brick / Wood; **Color**; **Erase wall area**; **Erase wall panel**. |
|
||||
| Background ground | **Background ground**; **Look:** Grass / Concrete / Carpet; **Color** only for Carpet; **Erase background ground**. A short helper repeats **“Decorative only · Stickmen don’t walk here.”** |
|
||||
|
||||
Changing Look or choosing a recent/preset color updates **only the selected piece** immediately. A Look change preserves that piece's stored color, including when changing away from and back to Carpet; this keeps Look and Color independent. A custom color dialog previews its tentative result on that selected piece while open; **Use color** commits it and adds it to history, while **✕ Cancel** restores the piece's prior color and leaves history unchanged. The color dialog's preview and the Stage use the same material drawing rules. The card remains open, keeps its scroll position, and refreshes choices without emitting a second edit.
|
||||
|
||||
The existing large move/resize handles, dark-backed selection outline, button size steps where applicable, and single-piece release-to-commit behavior apply to Background ground as they do to wall panels. Moving or resizing preserves motif scale and alignment with adjacent matching pieces. **Erase** asks **“Erase this piece?”** with **Keep it** and **Erase** before removing one piece; the Delete key follows the same safeguard. **Erase wall area** affects wall panels only and retains the approved cutout behavior. Group edits and their **Keep changes / ✕ Cancel** boundary remain as specified for Phase 19.
|
||||
|
||||
## Theme, responsiveness, and input
|
||||
|
||||
- The Scenery chooser and color dialog use `SandboxTheme.control_theme()` and existing `popups.asset_selector` / `popups.dialog` presentation rules. The inspector uses `stage_panels.shell`, `.type`, `.choice`, and `.action`; EDIT accents come from `mode_colors.edit`. Introduce focused token entries for swatch spacing/selection, look preview sizing, and dialog actions only where existing tokens do not cover them. Add matching entries to `SandboxTheme.DEFAULT_CONFIG` and `sandbox_theme.json`; avoid fixed UI colors or font sizes in the new controls.
|
||||
- Layout uses `VBoxContainer`/`HBoxContainer` for sections and `FlowContainer` for swatches and any wrapping look choices. The title and Close stay fixed while content scrolls. Card columns and swatches wrap before they clip; labels wrap; the Draw button stays in the scroll order and reachable on short screens. The chooser and color dialog fit inside the unobstructed display, including raised font sizes. No selected-piece card or capture bar covers the mode pill.
|
||||
- Every action, swatch, card, and handle has a pointer hit region of at least 44 × 44 logical pixels; button and row heights respect theme `button_min_height` or `stage_panels` minimum target tokens. Text, outline, and focus signal choices in addition to color. Tab order follows Back → Look → Color → Draw → Close in the setup view, with Close always reachable. Space/Enter activates focused choices. The dialog traps focus while open, and closing it returns focus to its opener; closing the chooser returns focus to Scenery. Touch can use two taps for rectangle corners and can always reach Cancel without Esc.
|
||||
- Opening a selected-piece card from the keyboard places focus at its first useful control. Closing it keeps the piece selected, shows the compact Settings reopen action, and returns focus to the Stage or the opener. Scrolling the card must not drag the piece underneath.
|
||||
|
||||
## Copy map
|
||||
|
||||
| Where | Exact child-facing text |
|
||||
| --- | --- |
|
||||
| Backgrounds category | **Wall panel**; **Background ground** |
|
||||
| Background ground role | **Decorative only · Stickmen don’t walk here.** |
|
||||
| Wall role | **Looks like a wall · Stickmen can pass through.** |
|
||||
| Setup sections | **Look**; **Color**; **Recently used**; **Colors**; **More colors…** |
|
||||
| Setup primary action | **Draw wall panel** / **Draw background ground** |
|
||||
| Stage pointer pill | **Drag to draw wall** / **Drag to draw background ground** |
|
||||
| Too-small draft | **Make this area bigger.** |
|
||||
| Selected-piece actions | **Erase wall area**; **Erase wall panel**; **Erase background ground** |
|
||||
| Color dialog | **Use color**; **✕ Cancel** |
|
||||
|
||||
## Observable acceptance outcomes
|
||||
|
||||
1. From Scenery → Backgrounds, a child can choose Wall panel or Background ground, inspect the natural default, and press Draw without visiting Color. The placed preview and final piece show the same material and color.
|
||||
2. Color is available for Paint, Brick, Wood, and Carpet in both setup and selected-piece editing. Brick mortar, Wood grain, and Carpet texture stay recognizable after choosing a light, dark, or saturated color. Grass detail reads inside a large rectangle at normal Stage zoom.
|
||||
3. After choosing eight distinct colors, the history shows newest first in both flows, wraps on a narrow screen, survives a scene change and restart, and has no duplicate. Canceling a tentative custom color changes neither saved color nor history.
|
||||
4. A child can change one selected wall or Background ground piece without changing its neighbors. Changing Look preserves its stored color; selecting Carpet again restores its prior Carpet color. A canceled custom-color preview restores the selected piece.
|
||||
5. A broad Background ground is selectable in Edit Scenery, including through **Pieces here** when covered, and stays out of ordinary EDIT selection. Move, resize, erase, and multi-piece edit controls remain accessible. The separate Ground palette remains clearly marked as walkable.
|
||||
6. On a short or narrow display with larger fonts, chooser title/Close, color dialog actions, inspector header/Close, capture-bar Cancel, and every swatch remain reachable by touch and keyboard. No control spills below the visible screen or covers the mode pill.
|
||||
|
||||
## Remaining UI decisions
|
||||
|
||||
The accepted flow leaves no blocking user-facing choice. During architecture, settle the exact shared swatch-control API and token names, the setup view's retained-choice lifetime, and how the color dialog provides a provisional Stage preview without committing until **Use color**. These choices must preserve the observable behavior above.
|
||||
Reference in New Issue
Block a user