Skip to content

CSE Machine: Added animation for spread instruction #3114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/features/cseMachine/CseMachineAnimation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React from 'react';

import { ArrayAccessAnimation } from './animationComponents/ArrayAccessAnimation';
import { ArrayAssignmentAnimation } from './animationComponents/ArrayAssignmentAnimation';
import { ArraySpreadAnimation } from './animationComponents/ArraySpreadAnimation';
import { AssignmentAnimation } from './animationComponents/AssignmentAnimation';
import { Animatable } from './animationComponents/base/Animatable';
import { lookupBinding } from './animationComponents/base/AnimationUtils';
Expand Down Expand Up @@ -112,6 +113,11 @@ export class CseAnimation {
);
}
break;
case 'SpreadElement':
CseAnimation.animations.push(
new ControlExpansionAnimation(lastControlComponent, CseAnimation.getNewControlItems())
);
break;
case 'AssignmentExpression':
case 'ArrayExpression':
case 'BinaryExpression':
Expand Down Expand Up @@ -276,6 +282,48 @@ export class CseAnimation {
)
);
break;
case InstrType.SPREAD:
const prevcontrol = Layout.previousControlComponent.stackItemComponents;
const control = Layout.controlComponent.stackItemComponents;
const array = Layout.previousStashComponent.stashItemComponents.at(-1)!.arrow!
.target! as ArrayValue;

console.log(array);

let currCallInstr;
let prevCallInstr;

for (let i = 0; control.at(-i) != undefined; i++) {
if (control.at(-i)?.text.includes('call ')) {
// find call instr above
currCallInstr = control.at(-i);
break;
}
}

for (let i = 0; prevcontrol.at(-i) != undefined; i++) {
if (prevcontrol.at(-i)?.text.includes('call ')) {
// find call instr above
prevCallInstr = prevcontrol.at(-i);
break;
}
}

const resultItems =
array.data.length !== 0
? Layout.stashComponent.stashItemComponents.slice(-array.data.length)
: [];

CseAnimation.animations.push(
new ArraySpreadAnimation(
lastControlComponent,
Layout.previousStashComponent.stashItemComponents.at(-1)!,
resultItems!,
currCallInstr!,
prevCallInstr!
)
);
break;
case InstrType.ARRAY_LENGTH:
case InstrType.BREAK:
case InstrType.BREAK_MARKER:
Expand Down
172 changes: 172 additions & 0 deletions src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { Easings } from 'konva/lib/Tween';
import React from 'react';
import { Group } from 'react-konva';

import { ControlItemComponent } from '../components/ControlItemComponent';
import { StashItemComponent } from '../components/StashItemComponent';
import { Visible } from '../components/Visible';
import { ControlStashConfig } from '../CseMachineControlStashConfig';
import {
defaultActiveColor,
defaultDangerColor,
defaultStrokeColor,
getTextWidth
} from '../CseMachineUtils';
import { Animatable, AnimationConfig } from './base/Animatable';
import { AnimatedGenericArrow } from './base/AnimatedGenericArrow';
import { AnimatedTextbox } from './base/AnimatedTextbox';
import { getNodePosition } from './base/AnimationUtils';

/**
* Adapted from InstructionApplicationAnimation, but changed resultAnimation to [], among others
*/
export class ArraySpreadAnimation extends Animatable {
private controlInstrAnimation: AnimatedTextbox; // the array literal control item
private stashItemAnimation: AnimatedTextbox;
private resultAnimations: AnimatedTextbox[];
private arrowAnimation?: AnimatedGenericArrow<StashItemComponent, Visible>;
private currCallInstrAnimation: AnimatedTextbox;
private prevCallInstrAnimation: AnimatedTextbox;

private endX: number;

constructor(
private controlInstrItem: ControlItemComponent,
private stashItem: StashItemComponent,
private resultItems: StashItemComponent[],
private currCallInstrItem: ControlItemComponent,
private prevCallInstrItem: ControlItemComponent
) {
super();

console.log(stashItem);
console.log(resultItems);

this.endX = stashItem!.x() + stashItem!.width();
this.controlInstrAnimation = new AnimatedTextbox(
controlInstrItem.text,
getNodePosition(controlInstrItem),
{ rectProps: { stroke: defaultActiveColor() } }
);
this.stashItemAnimation = new AnimatedTextbox(stashItem.text, getNodePosition(stashItem), {
rectProps: {
stroke: defaultDangerColor()
}
});

// call instr above
this.prevCallInstrAnimation = new AnimatedTextbox(
this.prevCallInstrItem.text,
{ ...getNodePosition(this.prevCallInstrItem), opacity: 0 },
{ rectProps: { stroke: defaultActiveColor() } }
);

this.currCallInstrAnimation = new AnimatedTextbox(
this.currCallInstrItem.text,
{ ...getNodePosition(this.currCallInstrItem), opacity: 0 },
{ rectProps: { stroke: defaultActiveColor() } }
);

this.resultAnimations = resultItems.map(item => {
return new AnimatedTextbox(item.text, {
...getNodePosition(item),
opacity: 0
});
});
if (stashItem.arrow) {
this.arrowAnimation = new AnimatedGenericArrow(stashItem.arrow, { opacity: 0 });
}
}

draw(): React.ReactNode {
return (
<Group ref={this.ref} key={Animatable.key--}>
{this.controlInstrAnimation.draw()}
{this.stashItemAnimation.draw()}
{this.currCallInstrAnimation.draw()}
{this.prevCallInstrAnimation.draw()}
{this.resultAnimations.map(a => a.draw())}
{this.arrowAnimation?.draw()}
</Group>
);
}

async animate(animationConfig?: AnimationConfig) {
this.resultItems?.map(a => a.ref.current?.hide());
this.resultItems?.map(a => a.arrow?.ref.current?.hide());
const minInstrWidth =
getTextWidth(this.controlInstrItem.text) + ControlStashConfig.ControlItemTextPadding * 2;
const resultX = (idx: number) => this.resultItems[idx]?.x() ?? this.stashItem.x();
const resultY = this.resultItems[0]?.y() ?? this.stashItem.y();
const startX = resultX(0);
const fadeDuration = ((animationConfig?.duration ?? 1) * 3) / 4;
const fadeInDelay = (animationConfig?.delay ?? 0) + (animationConfig?.duration ?? 1) / 4;

// Move spread instruction next to stash item (array pointer)
await Promise.all([
// Show change in call arity
this.prevCallInstrAnimation.animateTo(
{ scaleX: 1.1, scaleY: 1.1, opacity: 0 },
{ duration: 0.3, easing: Easings.StrongEaseOut }
),

this.currCallInstrAnimation.animateTo(
{ opacity: 1 },
{ duration: 0.3, easing: Easings.StrongEaseOut }
),

...this.resultAnimations.flatMap(a => [
a.animateTo(
{ x: startX + (this.endX - startX) / 2 - this.resultItems[0]?.width() / 2 },
{ duration: 0 }
)
]),
this.controlInstrAnimation.animateRectTo({ stroke: defaultStrokeColor() }, animationConfig),
this.controlInstrAnimation.animateTo(
{
x: startX,
y: resultY + (this.resultItems[0]?.height() ?? this.stashItem.height()),
width: minInstrWidth
},
animationConfig
),
this.stashItemAnimation.animateRectTo({ stroke: defaultDangerColor() }, animationConfig)
]);

animationConfig = { ...animationConfig, delay: 0 };
// Merge all elements together to form the result
await Promise.all([
this.controlInstrAnimation.animateTo({ x: resultX(0), y: resultY }, animationConfig),
this.controlInstrAnimation.animateTo(
{ opacity: 0 },
{ ...animationConfig, duration: fadeDuration }
),
this.stashItemAnimation.animateTo({ x: resultX(0) }, animationConfig),
this.stashItemAnimation.animateTo(
{ opacity: 0 },
{ ...animationConfig, duration: fadeDuration }
),

...this.resultAnimations.flatMap((a, idx) => [
a.animateTo({ x: resultX(idx) }, animationConfig),
a.animateRectTo({ stroke: defaultDangerColor() }, animationConfig),
a.animateTo(
{ opacity: 1 },
{ ...animationConfig, duration: fadeDuration, delay: fadeInDelay }
)
])
]);

this.destroy();
}

destroy() {
this.ref.current?.hide();
this.resultItems.map(a => a.ref.current?.show());
this.resultItems.map(a => a.arrow?.ref.current?.show());
this.controlInstrAnimation.destroy();
this.stashItemAnimation.destroy();
this.resultAnimations.map(a => a.destroy());
this.arrowAnimation?.destroy();
}
}