Add reusable UI components for Phase 7 UI Polish

- Introduced `EmojiQuickGrid`, `FontPresetPicker`, and `PresetSizeToggle` classes for emoji selection, font presets, and size toggles respectively.
- Implemented configuration methods for each component to handle dynamic content and theming.
- Created tests for the new components to ensure functionality and integration with the existing UI.
- Added a comprehensive architecture specification for Phase 7 UI Polish detailing scope, implementation, and validation strategies.
- Updated existing theme handling to support new UI elements and ensure consistent styling across the application.
This commit is contained in:
2026-09-18 09:42:48 -04:00
parent baa19a9341
commit 1a2a43c805
35 changed files with 1791 additions and 205 deletions
+362
View File
@@ -0,0 +1,362 @@
# Sandbox theme reference
`res://sandbox_theme.json` is the hand-editable presentation configuration for
the Sandbox Stage (`res://scenes/sandbox_stage.tscn`). It is loaded by
`SandboxStage` during `_ready()`, before the code-built UI is created. The
loader is `res://scripts/sandbox_theme.gd` (`class_name SandboxTheme`).
The committed file uses schema `"2.0"`. The theme controls presentation
defaults only; it does not save a scene, placement, or action data.
## Load, merge, and validation rules
The load path is:
```text
res://sandbox_theme.json
│
▼
SandboxTheme.DEFAULT_CONFIG
│ deep-merge file values
▼
SandboxStage._build_ui() and visual overlays
```
- Missing, unreadable, malformed, or non-object JSON uses the built-in default
configuration and emits one warning. The stage continues to run.
- Missing nested keys inherit the built-in value. Unknown keys are retained in
the raw dictionary but have no effect until a consumer is added.
- `stylebox(component)` resolves styles in this order:
`global` → `<section>.style_defaults` → the component object. For example,
`palette` is resolved from `global`, then `palette.style_defaults`, then
`palette`.
- Colors must be strings in `#RRGGBB` or `#RRGGBBAA` form. Invalid colors use
the requested fallback and emit a warning.
- Numeric accessors accept finite integers/floats and clamp them to the range
requested by the consumer. Invalid or non-finite numbers use that consumer's
fallback. Size arrays are accepted when they contain two numeric values;
individual controls apply their own minimum sizes.
- Font paths are `res://` resource paths. A missing resource, or a resource
that is not a `Font`, uses `ThemeDB.fallback_font` and emits a warning.
- The theme is loaded once per Sandbox Stage instance. Restart the scene (F6)
to see edits; there is no live file watcher or reload button.
The live grid size, snap toggle, and grid visibility are stored in
`user://sandbox_settings.json`. `grid.snap_size` supplies the initial grid-size
default; it does not overwrite a value already saved in user settings.
## Schema overview
Every current top-level block is listed below. The values shown are the
defaults in the committed `sandbox_theme.json` (not necessarily the fallback
used by an older caller).
### Current consumer status
The schema intentionally includes forward-looking values for the reusable
widget and speech work. The current Sandbox runtime applies these groups:
- `global` style values used by resolved styleboxes and the selector control
theme (background/border/radius/padding); `global.text_color` and
`global.muted_text_color` are available presentation values but are not
applied to every existing label automatically.
- `fonts.ui_font`, `emoji_font`, bold/italic variants, the nested `sizes`
entries for action popups, tooltips, status pills, queue/rule/action editors,
panel rows/titles, `file_menu`, `file_menu_popup`, and `mode_switcher`, plus
the related `flags`.
- `mode_colors.edit`, `direct`, `play`, and `guide`; `grid.snap_size` as the
first-run grid default; dialog sizes/fonts; `chrome.mode_switcher` width;
palette width/card size/drag threshold/style; selector cell and thumbnail
sizes; and all `overlays` values.
These declared values currently remain reserved or require a legacy adapter:
- `chrome.top_bar.height`, `status_bar.height`, `mode_frame`, `mode_badge`,
and `tooltip` describe the intended chrome contract, but the corresponding
layout/style constants are still authored in `SandboxStage`.
- `popups.action_menu.font_size` and `popups.asset_selector.backdrop_alpha`
are reserved component overrides. The active action-menu size comes from
`fonts.sizes.action_popup`; selector `cell_size` and `thumbnail_size` are
active.
- `palette.icon_size` and `palette.selected_border_width` are reserved for
the palette card renderer; the current card has its own glyph/border values.
- `fonts.sizes.assignment_badge`, `rule_label`, `asset_label`, and
`asset_badge` are part of the resolved schema, but the current direct call
into `StageDirectorVisuals` still reads the legacy flat badge/label names.
Use the legacy names documented below when changing those director visuals.
- `fonts.presets` and every `widgets` list are exposed through reusable
controls, but the current Sandbox Speak action does not instantiate those
controls yet. They are future speech-customization data.
### `version`
| Key | Format | Current value | Notes |
|---|---|---:|---|
| `version` | string | `"2.0"` | Use `"2.0"` when using the nested schema below. |
### `global`
Global style values are inherited by `SandboxTheme.stylebox()` and the default
control theme.
| Key | Format | Default | Purpose |
|---|---|---:|---|
| `font_preset` | string | `"ui"` | Named base-font selection for future/general UI use. |
| `font_size` | integer | `18` | Default `Control` font size. |
| `text_color`, `muted_text_color` | color string | `#F5F7FA`, `#AAB2C0` | Primary and secondary text colors. |
| `bg_color`, `surface_color` | color string | `#171B24F2`, `#252B38FA` | Global and raised-surface backgrounds. |
| `border_color` | color string | `#FFFFFF33` | Default border color. |
| `border_width` | integer | `1` | Default border width. |
| `corner_radius` | integer | `10` | Default corner radius. |
| `padding`, `spacing` | integer | `8`, `8` | Default style padding and layout spacing. |
### `fonts`
Font paths may be empty (`""`) to use the engine fallback. `presets` is an
object of arbitrary preset IDs; each entry has a display `label` and a `path`.
The committed file provides `ui`, `comic`, `pixel`, and `scary` entries.
| Key | Format | Default | Consumer |
|---|---|---:|---|
| `ui_font`, `emoji_font` | `res://` path or empty | `""` | Base UI and emoji-capable stage/director controls. |
| `ui_font_bold`, `ui_font_italic` | `res://` path or empty | `""` | Optional style variants; bold can be generated from the base font when no dedicated path is supplied. |
| `presets` | object `{id:{label,path}}` | see JSON | `font_presets()` and future font selectors. |
| `sizes.action_popup` | integer | `24` | Director/action popup text. |
| `sizes.action_popup_emoji` | integer | `22` | Effective popup size when a popup font is active (Godot popup menus have one font size). |
| `sizes.assignment_badge` | integer | `20` | Reserved resolved-theme value; current director visuals use legacy `fonts.assignment_badge_font_size`. |
| `sizes.rule_label` | integer | `18` | Reserved resolved-theme value; current director visuals use legacy `fonts.rule_label_font_size`. |
| `sizes.status_pill` | integer | `18` | Mode badge/pill. |
| `sizes.tooltip` | integer | `18` | Cursor-attached targeting tooltip. |
| `sizes.file_menu` | integer | `22` | `File` button in the top bar. |
| `sizes.file_menu_popup` | integer | `26` | File menu and Open Recent popup rows. |
| `sizes.mode_switcher` | integer | `24` | Edit/Direct/Play buttons. |
| `sizes.queue_panel`, `sizes.rule_panel` | integer | `22`, `22` | Queue and rule editor panels. |
| `sizes.action_editor`, `sizes.rule_editor` | integer | `22`, `22` | Action and rule editor controls. |
| `sizes.panel_row`, `sizes.panel_title` | integer | `20`, `24` | Editor row text and panel titles. |
| `sizes.asset_label`, `sizes.asset_badge` | integer | `18`, `14` | Reserved asset-card text sizing. |
| `flags.panel_title_bold` | boolean | `true` | Bold queue/rule panel titles. |
| `flags.rule_label_bold` | boolean | `false` | Bold director rule labels. |
| `flags.badge_bold` | boolean | `true` | Bold director badges. |
| `flags.action_popup_bold`, `flags.action_popup_italic` | boolean | `false`, `false` | Popup style flags. |
### `mode_colors`
Each value is a color string. These accents are used by the mode badge/frame,
active mode controls, targeting feedback, and related stage chrome.
| Key | Default | Purpose |
|---|---|---|
| `edit` | `#22C6FF` | Edit-mode accent. |
| `direct` | `#FFB300` | Direct-mode accent. |
| `play` | `#33DD77` | Play-mode accent. |
| `guide` | `#22C6FF` | Terrain painting guide line. |
### `grid`
| Key | Format | Default | Purpose |
|---|---|---:|---|
| `snap_size` | finite number | `15.0` | Initial grid size, clamped to the stage's supported range. |
### `chrome`
`style_defaults` is the optional common style object for chrome components. Its
style keys follow the `global` style formats (`bg_color`, `border_color`,
`border_width`, `corner_radius`, `padding`).
| Key | Format | Default | Purpose |
|---|---|---:|---|
| `style_defaults` | style object | `{ "bg_color":"#171B24F2", "corner_radius":10 }` | Shared chrome style base. |
| `top_bar.height` | number | `64` | Top navigation bar height. |
| `status_bar.height` | number | `38` | Bottom status bar height. |
| `mode_switcher.segment_min_width` | number | `124` | Minimum width of each Edit/Direct/Play segment. |
| `mode_frame.border_width` | integer | `3` | Colored stage frame width. |
| `mode_badge.corner_radius` | integer | `12` | Mode badge corner radius. |
| `mode_badge.padding` | number | `10` | Mode badge padding. |
| `tooltip.bg_color` | color | `#101010E6` | Targeting tooltip background. |
| `tooltip.border_width` | integer | `1` | Targeting tooltip border. |
| `dialogs.queue_size` | `[width,height]` | `[680,520]` | Queue editor window minimum size. |
| `dialogs.rule_size` | `[width,height]` | `[720,560]` | Rule editor window minimum size. |
| `dialogs.action_size` | `[width,height]` | `[560,390]` | Action editor window minimum size. |
| `dialogs.confirm_size` | `[width,height]` | `[620,300]` | Rename, unsaved, missing-assets, and other confirmation/error windows. |
| `dialogs.font_size` | integer | `22` | Dialog controls/buttons. |
| `dialogs.message_font_size` | integer | `26` | Centered AcceptDialog message text. |
### `popups`
`style_defaults` accepts the normal style keys and is inherited by popup
components.
| Key | Format | Default | Purpose |
|---|---|---:|---|
| `style_defaults` | style object | bg `#202633FA`, border `#FFFFFF33`, radius `12`, padding `12` | Common popup surface. |
| `action_menu.font_size` | integer | `24` | Reserved action-menu component override; use `fonts.sizes.action_popup` for the active menu. |
| `dialog` | object | `{}` | Reserved per-dialog popup overrides. |
| `asset_selector.backdrop_alpha` | finite number | `0.55` | Reserved selector backdrop opacity. |
| `asset_selector.cell_size` | `[width,height]` | `[190,220]` | Prop/stickman selector cell size. |
| `asset_selector.thumbnail_size` | `[width,height]` | `[156,156]` | Prop/stickman selector thumbnail size. |
### `palette`
`style_defaults` uses normal style keys and styles the stage placement palette.
| Key | Format | Default | Purpose |
|---|---|---:|---|
| `style_defaults` | style object | bg `#252B38FA`, radius `10`, padding `6` | Palette surface/card base. |
| `dock_width` | number | `116` | Palette width in Edit mode and stage-frame inset. |
| `card_size` | `[width,height]` | `[96,94]` | Placement-card minimum size; the card keeps room for its label. |
| `icon_size` | number | `30` | Reserved palette icon/glyph size. |
| `drag_threshold` | number | `8` | Pointer distance before a card becomes a drag. |
| `selected_border_width` | integer | `3` | Reserved selected palette card border. |
### `overlays`
`style_defaults` controls dashed overlay geometry in world pixels; colors are
RGBA strings.
| Key | Format | Default | Purpose |
|---|---|---:|---|
| `style_defaults.line_width` | number | `2` | Dashed trajectory/guide width. |
| `style_defaults.dash_length` | number | `8` | Dash length. |
| `style_defaults.dash_gap` | number | `5` | Gap length. |
| `terrain_guide.color` | color | `#22C6FF` | Terrain paint guide. |
| `valid.color` | color | `#4DE66680` | Valid director target/placement feedback. |
| `same_type.color` | color | `#FFFFFF26` | Same-type placement feedback. |
| `invalid.color` | color | `#FF4D4D80` | Invalid target/placement feedback. |
### `widgets`
These lists feed reusable controls (`EmojiQuickGrid`, `FontPresetPicker`, and
`PresetSizeToggle`). They are data sources, not speech-action persistence.
| Key | Format | Default | Purpose |
|---|---|---|---|
| `swatches` | array of color strings | white, black, cyan, amber, green, red | Color selector entries. |
| `size_presets` | array of `{id,label,value}` objects | Small/Medium/HUGE (`20/28/40`) | Size selector entries; `value` is an integer-like size. |
| `emoji` | array of strings | eight emoji entries | Emoji quick-grid entries. |
## Practical edits
Make the top navigation easier to read:
```json
{
"fonts": {
"sizes": {
"file_menu": 26,
"file_menu_popup": 30,
"mode_switcher": 28
}
}
}
```
Change the mode frame and make the stage colors more distinct:
```json
{
"mode_colors": {
"edit": "#4DD9FF",
"direct": "#FF8A3D",
"play": "#65E68A",
"guide": "#4DD9FF"
},
"chrome": { "mode_frame": { "border_width": 4 } }
}
```
Give selectors larger cards and thumbnails:
```json
{
"popups": {
"asset_selector": {
"cell_size": [220, 260],
"thumbnail_size": [190, 190]
}
}
}
```
Make kid-facing editors and confirmations larger:
```json
{
"chrome": {
"dialogs": {
"queue_size": [760, 600],
"rule_size": [800, 640],
"font_size": 26,
"message_font_size": 30
}
},
"fonts": {
"sizes": {
"queue_panel": 24,
"rule_panel": 24,
"action_editor": 24,
"rule_editor": 24,
"panel_row": 22,
"panel_title": 28
}
}
}
```
## Legacy v1 files
Older Phase 4b/3c theme files used flat keys such as
`fonts.action_popup_font_size`, `fonts.queue_panel_font_size`,
`fonts.panel_title_font_size`, and mode keys such as
`mode_colors.edit_accent` and `mode_colors.guide_line`. They remain accepted.
The loader aliases the legacy mode names and selected flat font-size names into
the nested structure, while `SandboxStage` retains the old precedence for a
file that does not declare version `"2.0"`.
The complete legacy names still read by the current runtime are:
```text
fonts.action_popup_font_size fonts.action_popup_emoji_size
fonts.assignment_badge_font_size fonts.assignment_badge_radius
fonts.rule_label_font_size fonts.status_pill_font_size
fonts.tooltip_font_size fonts.queue_panel_font_size
fonts.rule_panel_font_size fonts.action_editor_font_size
fonts.rule_editor_font_size fonts.panel_row_font_size
fonts.panel_title_font_size fonts.panel_title_bold
fonts.rule_label_bold fonts.badge_bold
fonts.action_popup = { size, bold, italic }
mode_colors.edit_accent mode_colors.direct_accent
mode_colors.play_accent mode_colors.guide_line
```
For a migration, copy the values into the nested keys in this document and set
`"version": "2.0"`. Keep only one spelling of each setting so the result is
easy to maintain.
## Fonts, emoji, and troubleshooting
- Use project-relative paths such as `res://fonts/kids_font.ttf`; do not use a
machine-specific absolute path.
- If text falls back to the Godot default, check the path, import status, and
that the resource is actually a `Font`/font file. Restart the Sandbox after
correcting it.
- Emoji availability depends on the selected font and platform glyph support.
A missing `emoji_font` is non-fatal; the normal UI fallback remains usable.
- If a color or number is ignored, check JSON commas/quotes, use a supported
color form, and keep numbers finite. The warning identifies the affected key.
- If a selector or dialog looks cramped, increase its size array and its
related font size together; restart F6 to reload the file.
## Speech customization status
`fonts.presets` (`ui`, `comic`, `pixel`, `scary`), `widgets.size_presets`,
`widgets.swatches`, and `widgets.emoji` are the reusable data contract for a
future speech-customization UI. They are intentionally available now so a
future Speak dialog can consume the same theme.
The current Sandbox speech action still stores and renders text through the
existing `StickmanSpeechBubble`; these theme lists do not yet change saved
speech actions, bubble shape, bubble colors, or bubble font at runtime. Do not
assume that changing a preset changes an existing speech action. Treat
speech-specific fields under `popups.dialog` or future bubble sections as
reserved until that feature is implemented.