Skip to content

Commit

Permalink
Continuing description prototype, with Density, see #941
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanolson committed Nov 15, 2023
1 parent c49a645 commit 2dfa3d8
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 15 deletions.
115 changes: 110 additions & 5 deletions js/DescriptionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,48 @@ import { Node } from '../../scenery/js/imports.js';
import joist from './joist.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import DescriptionRegistry from '../../tandem/js/DescriptionRegistry.js';
import TReadOnlyProperty, { PropertyLazyLinkListener } from '../../axon/js/TReadOnlyProperty.js';
import TReadOnlyProperty, { PropertyLazyLinkListener, PropertyLinkListener, PropertyListener } from '../../axon/js/TReadOnlyProperty.js';
import TEmitter, { TEmitterListener } from '../../axon/js/TEmitter.js';
import { Locale } from './i18n/localeProperty.js';
import TinyProperty from '../../axon/js/TinyProperty.js';
import localeOrderProperty from './i18n/localeOrderProperty.js';

export type DescriptionStrings = {
locale: Locale;
launch( context: DescriptionContext ): void;
};

export type DescriptionLogic = {
launch( context: DescriptionContext, strings: DescriptionStrings ): void;
added( tandemID: string, obj: PhetioObject ): void;
removed( tandemID: string, obj: PhetioObject ): void;
};

export default class DescriptionContext {

private readonly links: Link[] = [];
private readonly listens: Listen[] = [];
private readonly assignments: Assignment[] = [];

public getOptional( tandemID: string ): PhetioObject | null {
public get( tandemID: string ): PhetioObject | null {
return DescriptionRegistry.map.get( tandemID ) || null;
}

public get( tandemID: string ): PhetioObject {
const obj = this.getOptional( tandemID );
public getRequired( tandemID: string ): PhetioObject {
const obj = this.get( tandemID );

assert && assert( obj !== null );

return obj!;
}

public link( property: TReadOnlyProperty<unknown>, listener: PropertyLinkListener<unknown> ): void {
// TS just... lets us do this?
property.link( listener );

this.links.push( new Link( property, listener ) );
}

public lazyLink( property: TReadOnlyProperty<unknown>, listener: PropertyLazyLinkListener<unknown> ): void {
// TS just... lets us do this?
property.lazyLink( listener );
Expand All @@ -48,6 +69,8 @@ export default class DescriptionContext {
this.links.splice( index, 1 );
}

// TODO: support multilinks https://github.com/phetsims/joist/issues/941

public addListener( emitter: TEmitter<unknown[]>, listener: TEmitterListener<unknown[]> ): void {
emitter.addListener( listener );

Expand Down Expand Up @@ -101,12 +124,94 @@ export default class DescriptionContext {
}
}
}

// What is available and registered
public static readonly stringsMap = new Map<Locale, DescriptionStrings>();
public static readonly logicProperty = new TinyProperty<DescriptionLogic | null>( null );
public static readonly isStartupCompleteProperty = new TinyProperty<boolean>( false );

public static readonly activeStringsProperty = new TinyProperty<DescriptionStrings | null>( null );
public static readonly activeContextProperty = new TinyProperty<DescriptionContext | null>( null );

public static startupComplete(): void {
DescriptionContext.isStartupCompleteProperty.value = true;

localeOrderProperty.link( () => {
this.reload();
} );
}

private static reload(): void {
// If we haven't started up yet, don't do anything (we'll reload when we start up).
if ( !this.isStartupCompleteProperty.value ) {
return;
}

if ( this.activeContextProperty.value ) {
this.activeContextProperty.value.dispose();
}

const logic = this.logicProperty.value;
if ( logic === null ) {
return;
}

const locales = localeOrderProperty.value;

// Search in locale fallback order for the best description strings to use.
this.activeStringsProperty.value = null;
for ( const locale of locales ) {
if ( DescriptionContext.stringsMap.has( locale ) ) {

this.activeStringsProperty.value = DescriptionContext.stringsMap.get( locale )!;
break;
}
}

const strings = this.activeStringsProperty.value;
if ( strings === null ) {
return;
}

this.activeContextProperty.value = new DescriptionContext();

logic.launch( this.activeContextProperty.value, strings );
}

private static needsReloadForLocale( locale: Locale ): boolean {
if ( !DescriptionContext.stringsMap.has( locale ) ) {
// NOTE: We could check to see if it's a "better" locale than our current one
return localeOrderProperty.value.includes( locale );
}

return ( DescriptionContext.stringsMap.get( locale )! ) === this.activeStringsProperty.value;
}

public static registerStrings( strings: DescriptionStrings ): DescriptionStrings {
const needsReload = DescriptionContext.needsReloadForLocale( strings.locale );

DescriptionContext.stringsMap.set( strings.locale, strings );

if ( needsReload ) {
DescriptionContext.reload();
}

return strings;
}

public static registerLogic( logic: DescriptionLogic ): DescriptionLogic {
DescriptionContext.logicProperty.value = logic;

DescriptionContext.reload();

return logic;
}
}

class Link {
public constructor(
public readonly property: TReadOnlyProperty<unknown>,
public readonly listener: PropertyLazyLinkListener<unknown>
public readonly listener: PropertyListener<unknown>
) {}
}

Expand Down
30 changes: 20 additions & 10 deletions js/ScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ class ScreenView extends Node {
protected readonly pdomPlayAreaNode: PlayAreaNode;
protected readonly pdomControlAreaNode: ControlAreaNode;
private readonly pdomScreenSummaryNode: ScreenSummaryNode;
private screenSummaryContent: Node | null;
private readonly pdomParentNode: Node;

// Keep track of the content added to the summary Node, so that if it is set more than once, the previous one can be
// removed. Supports an ES6 getter/setter for this.
private _screenSummaryContent: Node | null = null;

public static readonly DEFAULT_LAYOUT_BOUNDS = DEFAULT_LAYOUT_BOUNDS;

public constructor( providedOptions: ScreenViewOptions ) {
Expand Down Expand Up @@ -118,10 +121,6 @@ class ScreenView extends Node {
// This container has the intro "{{SIM}} is an interactive sim, it changes as you . . ."
this.pdomScreenSummaryNode = new ScreenSummaryNode();

// keep track of the content added to the summary Node, so that if it is set more than once, the previous one can be
// removed.
this.screenSummaryContent = null;

// at the Node from options in the same way that can be done at any time
options.screenSummaryContent && this.setScreenSummaryContent( options.screenSummaryContent );

Expand Down Expand Up @@ -211,17 +210,28 @@ class ScreenView extends Node {
this.visibleBoundsProperty.value = this.parentToLocalBounds( viewBounds );
}

public get screenSummaryContent(): Node | null {
return this._screenSummaryContent;
}

public set screenSummaryContent( node: Node | null ) {
this.setScreenSummaryContent( node );
}

/**
* Set the screen summary Node for the PDOM of this Screen View. Prefer passing in a screen summary Node via
* constructor options, but this method can be used directly when necessary.
*/
public setScreenSummaryContent( node: Node ): void {
assert && assert( node !== this.screenSummaryContent, 'this is already the screen summary Node content' );
public setScreenSummaryContent( node: Node | null ): void {
assert && assert( node !== this._screenSummaryContent, 'this is already the screen summary Node content' );

this.screenSummaryContent && this.pdomScreenSummaryNode.removeChild( this.screenSummaryContent );
this._screenSummaryContent && this.pdomScreenSummaryNode.removeChild( this._screenSummaryContent );

this.screenSummaryContent = node;
this.pdomScreenSummaryNode.addChild( node );
this._screenSummaryContent = node;

if ( node ) {
this.pdomScreenSummaryNode.addChild( node );
}
}

/**
Expand Down

0 comments on commit 2dfa3d8

Please sign in to comment.