Skip to content
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();
}
}