124 lines
10 KiB
Markdown
124 lines
10 KiB
Markdown
# Phase 18 — Sandbox Stage palette image icons
|
||
|
||
> **Status:** Implemented and user-verified.
|
||
> **Scope:** The six cards in the Sandbox Stage EDIT mode `PaletteDock` only. The modal Prop and Stickman asset selectors and other editor controls retain their own artwork and behavior.
|
||
|
||
## Goal and approved UI
|
||
|
||
Replace the emoji at the top of each left palette card with the supplied image for that placement tool. Keep the text labels **Ground**, **Ramp**, **Step**, **Prop**, **Stickman**, and **Area** below the artwork. The cards remain recognizable, centered, and usable at the current size; their selection highlight, tooltip, keyboard focus, click, drag, mode visibility, and vertical scrolling retain their current behavior. This is a presentation change to the existing six tools.
|
||
|
||
| Placement ID | Card label | Source PNG after naming | Original supplied filename |
|
||
| --- | --- | --- | --- |
|
||
| `ground` | Ground | `res://resources/icons/ground.png` | `ground.png` |
|
||
| `ramp` | Ramp | `res://resources/icons/ramp.png` | `ramp.png` |
|
||
| `step` | Step | `res://resources/icons/step.png` | `steps.png` |
|
||
| `prop` | Prop | `res://resources/icons/prop.png` | `props.png` |
|
||
| `stickman` | Stickman | `res://resources/icons/stickman.png` | `stickmen.png` |
|
||
| `area` | Area | `res://resources/icons/area.png` | `area.png` |
|
||
|
||
The existing `AssetCard` image area is 64 × 64 px. Each image keeps its aspect ratio inside that area and stays centered above its text label. The button remains the full pointer and focus target; its image and label do not consume input. No extra caption, selector badge, or hover text is added. The current label tooltip remains available.
|
||
|
||
All six original PNG source images are 1254 × 1254 px and must stay at that source resolution after the three file renames. Set Godot's import size limit to **256 px** for these six textures so the runtime texture is smaller while the original source data is preserved. Do not resample or overwrite the PNGs. Regenerate import metadata for the renamed files through Godot's importer; remove stale import metadata for the old names as part of the rename.
|
||
|
||
## Existing hierarchy and data flow
|
||
|
||
```text
|
||
SandboxStage (scripts/editors/stage/sandbox_stage.gd)
|
||
└─ StageChrome : CanvasLayer
|
||
└─ PaletteDock : PanelContainer (EDIT only)
|
||
└─ ScrollContainer
|
||
└─ PaletteCards : VBoxContainer
|
||
├─ Ground AssetCard : Button
|
||
├─ Ramp AssetCard : Button
|
||
├─ Step AssetCard : Button
|
||
├─ Prop AssetCard : Button
|
||
├─ Stickman AssetCard : Button
|
||
└─ Area AssetCard : Button
|
||
└─ VBoxContainer
|
||
├─ TextureRect (64 × 64 image)
|
||
├─ Label (emoji fallback; hidden for these cards)
|
||
└─ Label (tool name)
|
||
```
|
||
|
||
`StageSpawner.get_palette_entries()` supplies the existing palette dictionaries. `StageChrome._build_palette()` resolves an image by the entry's `placement_id` and passes it to `AssetCard.configure()`. The card already shows its `TextureRect` and hides its emoji label when a texture is passed. Its `activated(payload: Dictionary)` and `drag_started(payload: Dictionary)` signals continue through `StageChrome.palette_activated` and `StageChrome.palette_drag_started` to the existing `SandboxStage` placement handlers.
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Registry[StageSpawner palette entry] --> Chrome[StageChrome placement ID lookup]
|
||
PNG[Preloaded palette PNG] --> Chrome
|
||
Chrome -->|entry, Texture2D, theme| Card[AssetCard.configure]
|
||
Card -->|activated Dictionary| Stage[SandboxStage placement handler]
|
||
Card -->|drag_started Dictionary| Stage
|
||
```
|
||
|
||
## Target classes, resources, and contracts
|
||
|
||
| Target | Contract for Phase 18 |
|
||
| --- | --- |
|
||
| `scripts/editors/stage/stage_chrome.gd` — `class_name StageChrome extends CanvasLayer` | Own one fixed `Dictionary` mapping the six placement IDs to preloaded `Texture2D` resources. In `_build_palette(entries: Array[Dictionary]) -> void`, resolve a `Texture2D` by `placement_id` and call the existing `AssetCard.configure(payload: Dictionary, icon_texture: Texture2D, theme: Variant) -> void`. Keep `_palette_buttons: Dictionary`, card signal connections, mode visibility, scroll hierarchy, and placement routing intact. The mapping is presentation data; do not derive game behavior from image paths. |
|
||
| `scripts/shared/ui/asset_card.gd` — `class_name AssetCard extends Button` | Use its existing texture path: 64 × 64 default `thumbnail_size`, `TextureRect.STRETCH_KEEP_ASPECT_CENTERED`, image visibility when the texture is present, and hidden glyph label. No new API or signal is required. Keep its `activated(payload: Dictionary)` and `drag_started(payload: Dictionary)` payload copies and drag threshold. |
|
||
| `scripts/stage/stage_spawner.gd` — `class_name StageSpawner extends RefCounted` | Existing `get_palette_entries() -> Array[Dictionary]` remains the source of placement IDs, labels, categories, and selector flags. Its current emoji `icon: String` value can remain as the generic `AssetCard` fallback; the six Stage palette cards receive image textures and do not render it. |
|
||
| `resources/icons/*.png` and Godot import metadata | Rename the three plural source filenames to their singular placement IDs. Preserve all six 1254 × 1254 PNG source files. Set `process/size_limit=256` for each imported texture, and let Godot regenerate source and imported cache paths for renamed files. |
|
||
| `sandbox_theme.json` and `SandboxTheme` | Keep palette card dimensions, layout, selected style, and scroll behavior. The existing `palette.icon_size` concerns emoji glyph sizing and does not set the 64 × 64 image area; Phase 18 needs no new theme token. |
|
||
|
||
Representative mapping and call shape:
|
||
|
||
```gdscript
|
||
const PALETTE_ICON_TEXTURES: Dictionary = {
|
||
"ground": preload("res://resources/icons/ground.png"),
|
||
"ramp": preload("res://resources/icons/ramp.png"),
|
||
"step": preload("res://resources/icons/step.png"),
|
||
"prop": preload("res://resources/icons/prop.png"),
|
||
"stickman": preload("res://resources/icons/stickman.png"),
|
||
"area": preload("res://resources/icons/area.png"),
|
||
}
|
||
|
||
var placement_id: String = String(entry.get("placement_id", ""))
|
||
var icon_texture: Texture2D = PALETTE_ICON_TEXTURES.get(placement_id) as Texture2D
|
||
card.configure(entry, icon_texture, _stage_theme)
|
||
```
|
||
|
||
The current payload shape and signal values remain unchanged, for example:
|
||
|
||
```gdscript
|
||
{
|
||
"placement_id": "step",
|
||
"kind": "terrain",
|
||
"label": "Step",
|
||
"icon": "🪜", # Unused fallback while an image texture is present.
|
||
"category": "Terrain",
|
||
"opens_selector": false,
|
||
}
|
||
```
|
||
|
||
## Observable acceptance outcomes
|
||
|
||
| User path | Expected outcome |
|
||
| --- | --- |
|
||
| Launch `res://scenes/stage/sandbox_stage.tscn` and enter EDIT. | Six palette cards show their corresponding images and the existing text labels. No emoji is visible on these six cards. Each image is centered, fully visible, and keeps its shape within the 64 × 64 area. |
|
||
| Click each card, including Prop and Stickman. | The same persistent placement tool or asset selector opens as before; the selected card's visual state and tooltip still work. |
|
||
| Drag a card into the stage and release, then try releasing over UI or cancelling. | The existing one-placement drag flow places exactly one item on a valid stage release and none on an invalid or cancelled release. No stray ghost remains. |
|
||
| Switch EDIT → DIRECT or PLAY → EDIT. | The palette follows its existing EDIT-only visibility and the selected placement state still follows the Stage. |
|
||
| Make the window short enough for palette overflow and scroll the dock. | All six cards remain reachable and their image/label pairs remain inside their button bounds. |
|
||
| Inspect the six source PNGs and their Godot imports after the rename. | The source PNGs remain 1254 × 1254; each import has a 256 px size limit and the three plural source filenames are no longer used. |
|
||
|
||
For implementation verification, run a Godot import/syntax check and the focused existing palette regression suite, then inspect the rendered Sandbox Stage at normal and short window heights. A successful import alone cannot establish that each image is visually legible or mapped to the correct card.
|
||
|
||
## Architectural trade-offs
|
||
|
||
- **Local preload map:** The six images are fixed UI presentation assets, so `StageChrome` owns their paths. This keeps the spawn registry independent of artwork paths and uses Godot's resource cache rather than loading on every card creation. A missing preloaded file is detected during import instead of silently shipping an emoji in the dock.
|
||
- **Existing `AssetCard`:** Its texture, glyph fallback, label, and input contracts already meet the UI specification. Reusing them avoids another palette control and keeps the modal asset selector's separate thumbnail workflow intact.
|
||
- **256 px import cap with full-size sources:** The 64 px display still has source resolution for scaling, while import memory is reduced. The original PNGs remain available at 1254 × 1254 for later art or export needs. The cap belongs to import metadata, not the source image bytes.
|
||
|
||
## Implementation and verification
|
||
|
||
Phase 18 is implemented in `scripts/editors/stage/stage_chrome.gd`. The six palette placement IDs map to their corresponding preloaded images; `step.png`, `prop.png`, and `stickman.png` replace the plural filenames. All six source PNGs remain 1254 × 1254, with Godot import size limits set to 256 px. The cards keep their 64 × 64 image areas, labels, and existing interaction paths.
|
||
|
||
| Verification | Result |
|
||
| --- | --- |
|
||
| Godot 4.7.1 headless import | Passed (exit 0); Windows certificate-store and global editor-settings warnings were reported. |
|
||
| `tests/test_phase7_palette.gd` | Passed. |
|
||
| Headless Sandbox Stage launch | Stage loaded successfully. |
|
||
| `git diff --check` | Passed. |
|
||
| Godot Editor visual check | User verified the palette appearance and placement behavior. |
|