diff --git a/CLAUDE.md b/CLAUDE.md index cde8c1f..e770f57 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,6 +20,7 @@ Reusable single-screen SceneryStack template. Run `npm run rename` to fork it to | `src/sim-screen/view/SimScreenSummaryContent.ts` | Accessible screen summary (reference a11y pattern) | | `src/sim-screen/view/SimKeyboardHelpContent.ts` | Keyboard-help dialog content | | `src/common/SimPanel.ts` | Pre-themed `Panel` wrapper (uses `SimColors` automatically) | +| `src/common/SimButtonOptions.ts` | Flat button-appearance option bundles + light-control-surface combo-box options | | `src/common/TimeModel.ts` | Composable play/pause + elapsed-time model for animated sims | | `scripts/generate-icons.ts` | PNG icons from `public/icons/icon.svg` | | `scripts/rename-sim.ts` | Automated fork/rename across all files and folders | @@ -58,6 +59,30 @@ export class FrictionModel implements TModel { Wire the view to `TimeControlNode` from `scenerystack/scenery-phet` binding on `model.timer.isPlayingProperty`. +### SimButtonOptions + +SceneryStack's push/round buttons default to a 3-D/beveled look; every button in the sim +should be flat instead. Spread these into the relevant options object: + +```typescript +import { FLAT_RESET_ALL_BUTTON_OPTIONS, FLAT_RECTANGULAR_BUTTON_OPTIONS } from "../../common/SimButtonOptions.js"; + +const resetAllButton = new ResetAllButton({ ...FLAT_RESET_ALL_BUTTON_OPTIONS, listener: () => {...} }); +const exampleButton = new RectangularPushButton({ ...FLAT_RECTANGULAR_BUTTON_OPTIONS, content, listener }); +``` + +`FLAT_PLAY_PAUSE_STEP_BUTTON_OPTIONS` spreads into `TimeControlNode`'s `playPauseStepButtonOptions`; +`TIME_CONTROL_SPEED_RADIO_OPTIONS` fixes `TimeControlNode`'s speed-radio label color, which +otherwise defaults to black text on the sim's dark default-mode panels. `SIM_COMBO_BOX_OPTIONS` +themes a `ComboBox`'s button/list chrome to the light control surface below; pair item labels +with `LIGHT_SURFACE_TEXT_FILL` (not `SimColors.textColorProperty`, which is for panel-fill text). + +`SimColors.ts` backs this with a "light control surfaces" section — +`controlSurfaceColorProperty`, `controlSurfaceDisabledColorProperty`, +`controlSurfaceTextColorProperty` — identical white/dark-text values in both default and +projector profiles, so any component that must stay light regardless of theme (combo boxes, +flat buttons, editable fields) keeps readable contrast automatically. + ## Accessibility This template is the **canonical accessibility reference** for OpenPhysics sims. It ships with diff --git a/src/SimColors.ts b/src/SimColors.ts index 803af54..3c30ad6 100644 --- a/src/SimColors.ts +++ b/src/SimColors.ts @@ -71,6 +71,29 @@ const SimColors = { default: "#e0e0e0", projector: "#1a1a1a", }), + + // ── Light control surfaces ─────────────────────────────────────────────────── + // White chrome (combo boxes, flat push buttons, editable input fields) stays light + // in both profiles; its text stays dark. Same values in default and projector mode, + // but defined here so every color lives in one themeable place. + + /** Fill of light control surfaces: combo-box button/list, editable input fields. */ + controlSurfaceColorProperty: new ProfileColorProperty(SimNamespace, "controlSurface", { + default: "#ffffff", + projector: "#ffffff", + }), + + /** Fill of a disabled control surface (grayed-out editable input field). */ + controlSurfaceDisabledColorProperty: new ProfileColorProperty(SimNamespace, "controlSurfaceDisabled", { + default: "#cccccc", + projector: "#cccccc", + }), + + /** Text on light control surfaces: combo items, flat-button labels, field values, preferences. */ + controlSurfaceTextColorProperty: new ProfileColorProperty(SimNamespace, "controlSurfaceText", { + default: "#1a1a1a", + projector: "#1a1a1a", + }), }; export default SimColors; diff --git a/src/common/SimButtonOptions.ts b/src/common/SimButtonOptions.ts new file mode 100644 index 0000000..adcce51 --- /dev/null +++ b/src/common/SimButtonOptions.ts @@ -0,0 +1,52 @@ +/** + * SimButtonOptions.ts + * + * Shared flat button appearance for the sim. Rectangular and round push buttons + * default to SceneryStack's 3-D appearance; pass these options (or spread them + * into nested button options) for a flat look everywhere. + */ + +import type { PlayPauseStepButtonGroupOptions, TimeControlNodeOptions } from "scenerystack/scenery-phet"; +import { ButtonNode, type ComboBoxOptions } from "scenerystack/sun"; +import SimColors from "../SimColors.js"; + +export const FLAT_BUTTON_APPEARANCE_OPTIONS = { + buttonAppearanceStrategy: ButtonNode.FlatAppearanceStrategy, +} as const; + +/** Text on flat push buttons and combo-box items (always on a light control surface). */ +export const LIGHT_SURFACE_TEXT_FILL = SimColors.controlSurfaceTextColorProperty; + +/** + * Combo-box chrome for panels. Item labels must use {@link LIGHT_SURFACE_TEXT_FILL}, not + * {@link SimColors.textColorProperty} — that color is for labels on the dark panel fill. + */ +export const SIM_COMBO_BOX_OPTIONS = { + buttonFill: SimColors.controlSurfaceColorProperty, + listFill: SimColors.controlSurfaceColorProperty, + buttonStroke: SimColors.panelBorderColorProperty, + listStroke: SimColors.panelBorderColorProperty, +} satisfies Pick; + +/** Options for RectangularPushButton and NumberControl arrow buttons. */ +export const FLAT_RECTANGULAR_BUTTON_OPTIONS = FLAT_BUTTON_APPEARANCE_OPTIONS; + +/** Options for ResetAllButton (extends RoundPushButton). */ +export const FLAT_RESET_ALL_BUTTON_OPTIONS = FLAT_BUTTON_APPEARANCE_OPTIONS; + +/** Nested options for TimeControlNode play / pause / step round buttons. */ +export const FLAT_PLAY_PAUSE_STEP_BUTTON_OPTIONS = { + playPauseButtonOptions: FLAT_BUTTON_APPEARANCE_OPTIONS, + stepForwardButtonOptions: FLAT_BUTTON_APPEARANCE_OPTIONS, + stepBackwardButtonOptions: FLAT_BUTTON_APPEARANCE_OPTIONS, +} satisfies PlayPauseStepButtonGroupOptions; + +/** + * Speed radio labels for TimeControlNode. SceneryStack Text defaults to black, which + * is low-contrast on the sim's dark Default-mode panels. + */ +export const TIME_CONTROL_SPEED_RADIO_OPTIONS = { + speedRadioButtonGroupOptions: { + labelOptions: { fill: SimColors.textColorProperty }, + }, +} satisfies Pick; diff --git a/src/sim-screen/view/SimScreenView.ts b/src/sim-screen/view/SimScreenView.ts index 8c8bb54..a6c191b 100644 --- a/src/sim-screen/view/SimScreenView.ts +++ b/src/sim-screen/view/SimScreenView.ts @@ -24,6 +24,7 @@ import { Node, Rectangle, Text } from "scenerystack/scenery"; import { ResetAllButton } from "scenerystack/scenery-phet"; import type { ScreenViewOptions } from "scenerystack/sim"; import { ScreenView } from "scenerystack/sim"; +import { FLAT_RESET_ALL_BUTTON_OPTIONS } from "../../common/SimButtonOptions.js"; import SimColors from "../../SimColors.js"; import { SCREEN_VIEW_MARGIN } from "../../SimConstants.js"; import type { SimModel } from "../model/SimModel.js"; @@ -67,6 +68,7 @@ export class SimScreenView extends ScreenView { // // const a11y = StringManager.getInstance().getA11yStrings(); // const exampleButton = new RectangularPushButton({ + // ...FLAT_RECTANGULAR_BUTTON_OPTIONS, // flat appearance, not SceneryStack's default 3-D look // content: someIcon, // listener: () => model.doSomething(), // accessibleName: a11y.controls.exampleControlStringProperty, @@ -76,6 +78,7 @@ export class SimScreenView extends ScreenView { // ── Reset All button ────────────────────────────────────────────────────── // Always position at bottom-right (PhET convention). const resetAllButton = new ResetAllButton({ + ...FLAT_RESET_ALL_BUTTON_OPTIONS, listener: () => { model.reset(); this.reset();