@kya-os/cli
Version:
CLI for MCP-I setup and management
254 lines • 7.01 kB
JavaScript
/**
* Scene-Based Animation Engine for Terminal Effects
* Provides sophisticated animation capabilities with scenes, frames, and events
*/
/**
* Sync metric for scene timing
*/
export var SyncMetric;
(function (SyncMetric) {
/** Sync to time */
SyncMetric["TIME"] = "time";
/** Sync to motion distance */
SyncMetric["DISTANCE"] = "distance";
/** Sync to motion steps */
SyncMetric["STEP"] = "step";
})(SyncMetric || (SyncMetric = {}));
/**
* Animation scene - a sequence of frames
*/
export class Scene {
constructor(id) {
this.frames = [];
// @ts-ignore - Used internally
this.currentFrameIndex = 0;
// @ts-ignore - Used internally
this.frameStartTime = 0;
this._isLooping = false;
this._isComplete = false;
/** Sync metric for timing */
this.syncMetric = SyncMetric.TIME;
this.id = id;
}
/**
* Add a frame to the scene
*/
addFrame(symbol, duration, colors) {
this.frames.push({ symbol, duration, colors });
}
/**
* Apply gradient to symbols across multiple frames
*/
applyGradientToSymbols(symbol, duration, gradient, _direction) {
const spectrum = gradient.getSpectrum();
const frameDuration = Math.floor(duration / spectrum.length);
for (const color of spectrum) {
this.addFrame(symbol, frameDuration, { fg: color, bg: null });
}
}
/**
* Set looping behavior
*/
setLooping(isLooping) {
this._isLooping = isLooping;
}
/**
* Get current frame based on elapsed time
*/
getCurrentFrame(elapsedTime) {
if (this.frames.length === 0)
return null;
if (this._isComplete && !this._isLooping) {
return this.frames[this.frames.length - 1];
}
let totalDuration = 0;
let targetTime = elapsedTime;
// Handle looping
if (this._isLooping) {
const sceneDuration = this.frames.reduce((sum, frame) => sum + frame.duration, 0);
targetTime = elapsedTime % sceneDuration;
}
// Find the current frame
for (let i = 0; i < this.frames.length; i++) {
totalDuration += this.frames[i].duration;
if (targetTime < totalDuration) {
this.currentFrameIndex = i;
return this.frames[i];
}
}
// Scene complete
this._isComplete = true;
if (this._isLooping) {
this.currentFrameIndex = 0;
this._isComplete = false;
return this.frames[0];
}
return this.frames[this.frames.length - 1];
}
/**
* Reset the scene
*/
reset() {
this.currentFrameIndex = 0;
this.frameStartTime = 0;
this._isComplete = false;
}
/**
* Check if scene is complete
*/
isComplete() {
return this._isComplete && !this._isLooping;
}
/**
* Get total duration of the scene
*/
getTotalDuration() {
return this.frames.reduce((sum, frame) => sum + frame.duration, 0);
}
/**
* Get frame count
*/
getFrameCount() {
return this.frames.length;
}
/**
* Get current frame index
*/
getCurrentFrameIndex() {
return this.currentFrameIndex;
}
}
/**
* Event types for character animation
*/
export var AnimationEvent;
(function (AnimationEvent) {
AnimationEvent["SCENE_COMPLETE"] = "scene_complete";
AnimationEvent["SCENE_ACTIVATED"] = "scene_activated";
AnimationEvent["FRAME_CHANGE"] = "frame_change";
})(AnimationEvent || (AnimationEvent = {}));
/**
* Character animation manager
*/
export class CharacterAnimation {
constructor(inputColors) {
this.scenes = new Map();
this.activeScene = null;
this.sceneStartTime = 0;
this.eventHandlers = [];
this.inputFgColor = inputColors?.fg;
this.inputBgColor = inputColors?.bg;
}
/**
* Create a new scene
*/
newScene(sceneId) {
const scene = new Scene(sceneId);
this.scenes.set(sceneId, scene);
return scene;
}
/**
* Get a scene by ID
*/
queryScene(sceneId) {
return this.scenes.get(sceneId);
}
/**
* Activate a scene
*/
activateScene(scene) {
if (this.activeScene && this.activeScene !== scene) {
this.triggerEvent(AnimationEvent.SCENE_COMPLETE, this.activeScene.id);
}
this.activeScene = scene;
this.sceneStartTime = Date.now();
scene.reset();
this.triggerEvent(AnimationEvent.SCENE_ACTIVATED, scene.id);
}
/**
* Update animation and get current visual
*/
update() {
if (!this.activeScene)
return null;
const elapsedTime = Date.now() - this.sceneStartTime;
const frame = this.activeScene.getCurrentFrame(elapsedTime);
if (!frame)
return null;
// Check for scene completion
if (this.activeScene.isComplete()) {
this.triggerEvent(AnimationEvent.SCENE_COMPLETE, this.activeScene.id);
}
return {
symbol: frame.symbol,
colors: frame.colors || { fg: this.inputFgColor || null, bg: this.inputBgColor || null },
};
}
/**
* Register an event handler
*/
registerEvent(event, sceneId, callback) {
this.eventHandlers.push({ event, sceneId, callback });
}
/**
* Trigger an event
*/
triggerEvent(event, sceneId) {
for (const handler of this.eventHandlers) {
if (handler.event === event && handler.sceneId === sceneId) {
handler.callback();
}
}
}
/**
* Reset all scenes
*/
reset() {
this.scenes.forEach(scene => scene.reset());
this.activeScene = null;
this.sceneStartTime = 0;
}
}
/**
* Enhanced character with animation capabilities
*/
export class AnimatedCharacter {
constructor(id, symbol, inputColors) {
/** Layer for z-ordering (higher = on top) */
this.layer = 0;
/** Visibility flag */
this.isVisible = true;
this.id = id;
this.originalSymbol = symbol;
this.animation = new CharacterAnimation(inputColors);
this.visual = {
symbol,
colors: {
fg: inputColors?.fg || null,
bg: inputColors?.bg || null,
},
};
}
/**
* Update character animation
*/
update() {
const newVisual = this.animation.update();
if (newVisual) {
this.visual = newVisual;
}
}
/**
* Set character layer
*/
setLayer(layer) {
this.layer = layer;
}
/**
* Set visibility
*/
setVisibility(isVisible) {
this.isVisible = isVisible;
}
}
//# sourceMappingURL=animation-engine.js.map