diff --git a/src/components/output/OutputView.svelte b/src/components/output/OutputView.svelte index 08886fba0..679e3c870 100644 --- a/src/components/output/OutputView.svelte +++ b/src/components/output/OutputView.svelte @@ -38,7 +38,7 @@ import Pointer from '../../input/Pointer'; import Place from '../../output/Place'; import moveOutput, { addStageContent } from '../palette/editOutput'; - import { getPlace } from '../../output/getPlace'; + import { getOrCreatePlace } from '../../output/getOrCreatePlace'; import { SvelteComponent, afterUpdate, beforeUpdate } from 'svelte'; import Placement from '../../input/Placement'; import { toExpression } from '../../parser/parseExpression'; @@ -446,7 +446,7 @@ ? renderedFocus : // If there's selected output, it's the first output selected, and it has a place $selectedOutput && $selectedOutput.length > 0 - ? getPlace( + ? getOrCreatePlace( project, $locale, $selectedOutput[0], diff --git a/src/components/palette/BindColor.svelte b/src/components/palette/BindColor.svelte index feef58817..36e9bd5e0 100644 --- a/src/components/palette/BindColor.svelte +++ b/src/components/palette/BindColor.svelte @@ -9,6 +9,7 @@ import type OutputProperty from '../../edit/OutputProperty'; import { getProject, getSelectedOutput } from '../project/Contexts'; import { Projects } from '../../db/Database'; + import type Bind from '../../nodes/Bind'; export let property: OutputProperty; export let values: OutputPropertyValueSet; @@ -17,9 +18,15 @@ let project = getProject(); let selectedOutput = getSelectedOutput(); - $: lightness = getColorValue('lightness') ?? 0; - $: chroma = getColorValue('chroma') ?? 0; - $: hue = getColorValue('hue') ?? 0; + $: lightness = $project + ? getColorValue($project.shares.output.Color.inputs[0]) ?? 0 + : 0; + $: chroma = $project + ? getColorValue($project.shares.output.Color.inputs[1]) ?? 0 + : 0; + $: hue = $project + ? getColorValue($project.shares.output.Color.inputs[1]) ?? 0 + : 0; // Whenever the slider value changes, revise the Evaluates to match the new value. function handleChange(l: number, c: number, h: number) { @@ -51,19 +58,20 @@ ); } - function getColorValue(name: string) { + function getColorValue(bind: Bind) { if ($project === undefined) return undefined; // The value of this facet on every value selected. const facets = values.values.map((val) => { if ($project && val.expression instanceof Evaluate) { const mapping = val.expression.getMappingFor( - name, + bind, $project.getNodeContext(val.expression) ); const number = mapping && mapping.given instanceof NumberLiteral ? mapping.given.getValue().toNumber() * - (name === 'lightness' && mapping.given.isPercent() + (bind === $project.shares.output.Color.inputs[0] && + mapping.given.isPercent() ? 0.01 : 1) : undefined; diff --git a/src/components/palette/PlaceEditor.svelte b/src/components/palette/PlaceEditor.svelte index f00d6e93a..c3d858be2 100644 --- a/src/components/palette/PlaceEditor.svelte +++ b/src/components/palette/PlaceEditor.svelte @@ -1,5 +1,4 @@
- {project.shares.output.Place.names.getSymbolicName()}{#each [getFirstName($locale.output.Place.x.names), getFirstName($locale.output.Place.y.names), getFirstName($locale.output.Place.z.names)] as dimension, index} - {@const given = place?.getMappingFor( + {project.shares.output.Place.names.getSymbolicName()}{#each project.shares.output.Place.inputs as dimension, index} + {@const given = place?.getInput( dimension, project.getNodeContext(place) - )?.given} + )} {@const value = given instanceof Expression ? getNumber(given) : undefined} @@ -70,7 +70,7 @@ text={`${value}`} validator={valid} {editable} - placeholder={getFirstName(dimension)} + placeholder={dimension.names.getNames()[0]} description={$locale.ui.palette.field.coordinate} changed={(value) => handleChange(dimension, value)} bind:view={views[index]} diff --git a/src/components/palette/VelocityEditor.svelte b/src/components/palette/VelocityEditor.svelte index 1672a4700..c79a34236 100644 --- a/src/components/palette/VelocityEditor.svelte +++ b/src/components/palette/VelocityEditor.svelte @@ -1,5 +1,4 @@
- {project.shares.output.Velocity.names.getSymbolicName()}{#each project.shares.output.Velocity.inputs.map( (input) => input.getPreferredName($locales) ) as dimension, index} - {@const mapping = velocity?.getMappingFor( + {project.shares.output.Velocity.names.getSymbolicName()}{#each project.shares.output.Velocity.inputs as dimension, index} + {@const given = velocity?.getInput( dimension, project.getNodeContext(velocity) )} - {@const given = mapping?.given} {@const value = given instanceof Expression ? getNumber(given) : undefined} @@ -73,7 +68,7 @@ text={`${value}`} validator={valid} {editable} - placeholder={getFirstName(dimension)} + placeholder={dimension.names.getNames()[0]} description={$locale.ui.palette.field.coordinate} changed={(value) => handleChange(dimension, index, value)} bind:view={views[index]} diff --git a/src/components/palette/editOutput.ts b/src/components/palette/editOutput.ts index 39ee83132..f09bfb166 100644 --- a/src/components/palette/editOutput.ts +++ b/src/components/palette/editOutput.ts @@ -20,6 +20,7 @@ import { STAGE_SYMBOL, } from '../../parser/Symbols'; import { toExpression } from '../../parser/parseExpression'; +import { getPlaceExpression } from '../../output/getOrCreatePlace'; export function getNumber(given: Expression): number | undefined { const measurement = @@ -54,65 +55,83 @@ export default function moveOutput( evaluates.map((evaluate) => { const ctx = project.getNodeContext(evaluate); - const given = evaluate.getMappingFor('place', ctx); + const given = getPlaceExpression(project, evaluate, ctx); const place = - given && - given.given instanceof Evaluate && - given.given.is(PlaceType, ctx) - ? given.given - : given && - given.given instanceof Bind && - given.given.value instanceof Evaluate && - given.given.value.is(PlaceType, ctx) - ? given.given.value + given instanceof Evaluate && given.is(PlaceType, ctx) + ? given : undefined; - const x = place?.getMappingFor('x', ctx)?.given; - const y = place?.getMappingFor('y', ctx)?.given; - const z = place?.getMappingFor('z', ctx)?.given; + const x = place?.getInput( + project.shares.output.Place.inputs[0], + ctx + ); + const y = place?.getInput( + project.shares.output.Place.inputs[1], + ctx + ); + const z = place?.getInput( + project.shares.output.Place.inputs[2], + ctx + ); const xValue = x instanceof Expression ? getNumber(x) : undefined; const yValue = y instanceof Expression ? getNumber(y) : undefined; const zValue = z instanceof Expression ? getNumber(z) : undefined; + const bind = evaluate.is(project.shares.output.Phrase, ctx) + ? project.shares.output.Phrase.inputs[3] + : evaluate.is(project.shares.output.Group, ctx) + ? project.shares.output.Phrase.inputs[4] + : undefined; + return [ evaluate, - evaluate.withBindAs( - 'place', - Evaluate.make( - Reference.make( - PlaceType.names.getPreferredNameString(locales), - PlaceType - ), - [ - // If coordinate is computed, and not a literal, don't change it. - x instanceof Expression && xValue === undefined - ? x - : NumberLiteral.make( - relative - ? new Decimal(xValue ?? 0) - .add(horizontal) - .toNumber() - : horizontal, - Unit.create(['m']) - ), - y instanceof Expression && yValue === undefined - ? y - : NumberLiteral.make( - relative - ? new Decimal(yValue ?? 0) - .add(vertical) - .toNumber() - : vertical, - Unit.create(['m']) + bind === undefined + ? evaluate + : evaluate.withBindAs( + bind, + Evaluate.make( + Reference.make( + PlaceType.names.getPreferredNameString( + locales ), - z instanceof Expression && zValue !== undefined - ? z - : NumberLiteral.make(0, Unit.create(['m'])), - ] - ), - ctx - ), + PlaceType + ), + [ + // If coordinate is computed, and not a literal, don't change it. + x instanceof Expression && + xValue === undefined + ? x + : NumberLiteral.make( + relative + ? new Decimal(xValue ?? 0) + .add(horizontal) + .toNumber() + : horizontal, + Unit.create(['m']) + ), + y instanceof Expression && + yValue === undefined + ? y + : NumberLiteral.make( + relative + ? new Decimal(yValue ?? 0) + .add(vertical) + .toNumber() + : vertical, + Unit.create(['m']) + ), + z instanceof Expression && + zValue !== undefined + ? z + : NumberLiteral.make( + 0, + Unit.create(['m']) + ), + ] + ), + ctx + ), ]; }) ); @@ -231,12 +250,12 @@ export function addStageContent( if (stage) { const context = project.getNodeContext(stage); - const list = stage.getExpressionFor( - StageType.inputs[0].getNames()[0], - context - ); - if (list instanceof ListLiteral) { - reviseContent(database, project, list, [...list.values, content]); + const content = stage.getInput(StageType.inputs[0], context); + if (content instanceof ListLiteral) { + reviseContent(database, project, content, [ + ...content.values, + content, + ]); } } } diff --git a/src/edit/OutputExpression.ts b/src/edit/OutputExpression.ts index 350ca0d8a..c20971806 100644 --- a/src/edit/OutputExpression.ts +++ b/src/edit/OutputExpression.ts @@ -166,10 +166,18 @@ export default class OutputExpression { } withPropertyUnset(name: string): Evaluate { - return this.node.withBindAs( - name, - undefined, - this.project.getNodeContext(this.node) - ); + // Find the bind corresponding to the given name. + + const context = this.project.getNodeContext(this.node); + const fun = this.node.getFunction(context); + const bind = fun?.inputs.find((bind) => bind.hasName(name)); + + return bind + ? this.node.withBindAs( + bind, + undefined, + this.project.getNodeContext(this.node) + ) + : this.node; } } diff --git a/src/models/Project.ts b/src/models/Project.ts index 84e4c4077..3de4f455c 100644 --- a/src/models/Project.ts +++ b/src/models/Project.ts @@ -806,14 +806,22 @@ export default class Project { name: string, value: Expression | undefined ): [Evaluate, Evaluate | undefined][] { - return evaluates.map((evaluate) => [ - evaluate, - evaluate.withBindAs( - name, - value?.clone(), - this.getNodeContext(evaluate) - ), - ]); + return evaluates.map((evaluate) => { + // Find the bind corresponding to the name. + const context = this.getNodeContext(evaluate); + const fun = evaluate.getFunction(context); + const bind = fun?.inputs.find((bind) => bind.hasName(name)); + return bind + ? [ + evaluate, + evaluate.withBindAs( + bind, + value?.clone(), + this.getNodeContext(evaluate) + ), + ] + : [evaluate, evaluate]; + }); } /** Get all the languages used in the project */ diff --git a/src/nodes/Evaluate.ts b/src/nodes/Evaluate.ts index a411df248..ff6af9698 100644 --- a/src/nodes/Evaluate.ts +++ b/src/nodes/Evaluate.ts @@ -355,16 +355,16 @@ export default class Evaluate extends Expression { } /** - * Given a name and an expression, create a new evaluate that binds this name to this value instead of its current binding, + * Given a bind of the function being evaluated and an expression, create a new evaluate that binds this name to this value instead of its current binding, * and if there is no current binding, create one. */ withBindAs( - name: string, + bind: Bind, expression: Expression | undefined, context: Context, named = true ): Evaluate { - const mapping = this.getMappingFor(name, context); + const mapping = this.getMappingFor(bind, context); if (mapping === undefined) return this; // If we'replacing with nothing @@ -385,7 +385,7 @@ export default class Evaluate extends Expression { named ? Bind.make( undefined, - Names.make([name]), + Names.make([bind.getNames()[0]]), undefined, expression ) @@ -407,23 +407,12 @@ export default class Evaluate extends Expression { ); } - getExpressionFor(name: string, context: Context) { - const mapping = this.getMappingFor(name, context); - return mapping === undefined - ? undefined - : mapping.given instanceof Bind - ? mapping.given.value - : mapping.given; - } - - getMappingFor(name: string, context: Context) { + getMappingFor(bind: Bind, context: Context) { // Figure out what the current mapping is. const mappings = this.getInputMapping(context); // Find the bind. - return mappings?.inputs.find((input) => - input.expected.names.hasName(name) - ); + return mappings?.inputs.find((input) => input.expected === bind); } computeConflicts(context: Context): Conflict[] { diff --git a/src/output/getPlace.ts b/src/output/getOrCreatePlace.ts similarity index 51% rename from src/output/getPlace.ts rename to src/output/getOrCreatePlace.ts index 22dc65531..10c6e54c9 100644 --- a/src/output/getPlace.ts +++ b/src/output/getOrCreatePlace.ts @@ -5,18 +5,33 @@ import Evaluate from '../nodes/Evaluate'; import evaluateCode from '../runtime/evaluate'; import { toPlace } from './Place'; -export function getPlace( +export function getPlaceExpression( + project: Project, + evaluate: Evaluate, + context: Context +) { + return ( + evaluate.getInput(project.shares.output.Phrase.inputs[3], context) ?? + evaluate.getInput(project.shares.output.Group.inputs[4], context) + ); +} + +export function getOrCreatePlace( project: Project, locale: Locale, evaluate: Evaluate, context: Context ) { - const place = evaluate.getExpressionFor('place', context); + const place = getPlaceExpression(project, evaluate, context); if (place instanceof Evaluate) { + // Only return a place if it's a Place creator. if ( place.is(project.shares.output.Place, project.getNodeContext(place)) ) return toPlace(evaluateCode(place.toWordplay(), [], locale)); else return undefined; - } else return toPlace(evaluateCode('Place()', [], locale)); + } else + return toPlace( + evaluateCode(`${project.shares.output.Place.names.getNames()[0]}()`) + ); }