diff --git a/src/Const/slide.ts b/src/Const/slide.ts index 3f97774..8c66187 100644 --- a/src/Const/slide.ts +++ b/src/Const/slide.ts @@ -1,2 +1,3 @@ export const DEFAULT_SLIDE_WIDTH = 960 export const DEFAULT_SLIDE_HEIGHT = 540 +export const SLIDE_RATIO = DEFAULT_SLIDE_WIDTH / DEFAULT_SLIDE_HEIGHT diff --git a/src/Kernel/Animation/Animation.ts b/src/Kernel/Animation/Animation.ts new file mode 100644 index 0000000..f0911d3 --- /dev/null +++ b/src/Kernel/Animation/Animation.ts @@ -0,0 +1,14 @@ +import { type Block } from '@BlockHub/Block/Block' + +export class Animation { + constructor( + public type: 'Entrance' | 'Emphasis' | 'Exit', + public name: string, + public block: Block, + public propName: string, + public startValue: any, + public endValue: any, + public duration: number, + public delay: number + ) {} +} diff --git a/src/Kernel/Animation/Timeline.ts b/src/Kernel/Animation/Timeline.ts new file mode 100644 index 0000000..346eb05 --- /dev/null +++ b/src/Kernel/Animation/Timeline.ts @@ -0,0 +1,62 @@ +import { BasicPropName } from '@BlockHub/Block/Block' +import { Animation } from './Animation' + +type TimelineState = 'initial' | 'preview' | 'running' + +export class Timeline { + private _state: TimelineState = 'initial' + private _startTime = 0 + private _animations: Set = new Set() + private _previewAnimations: Set = new Set() + private _runningAnimations: Set = new Set() + + private _tick() { + const animations = this._state === 'running' ? this._runningAnimations : this._previewAnimations + const elapsedTime = Date.now() - this._startTime + for (const animation of animations) { + const { block, propName, startValue, endValue, duration, delay } = animation + let progress + if (elapsedTime < delay) { + progress = 0 + } else if (elapsedTime < delay + duration) { + // time progress -> effect progress + progress = (elapsedTime - delay) / duration + } else { + progress = 1 + animations.delete(animation) + } + block[propName as BasicPropName] = startValue + (endValue - startValue) * progress + } + if (animations.size > 0) { + requestAnimationFrame(this._tick.bind(this)) + } else { + this._state = 'initial' + } + } + + get animations() { + return this._animations + } + + add(animation: Animation) { + this._animations.add(animation) + } + + delete(animation: Animation) { + this._animations.delete(animation) + } + + preview() { + this._state = 'preview' + this._previewAnimations = new Set([...this._animations].slice(-1)) + this._startTime = Date.now() + this._tick() + } + + start() { + this._state = 'running' + this._runningAnimations = new Set([...this._animations]) + this._startTime = Date.now() + this._tick() + } +} diff --git a/src/Kernel/Slide.ts b/src/Kernel/Slide.ts index 5bb1755..e13eae2 100644 --- a/src/Kernel/Slide.ts +++ b/src/Kernel/Slide.ts @@ -2,20 +2,25 @@ import { type Block } from '@BlockHub/Block/Block' import { EventManager } from './EventManager' import { Command } from '@Kernel/HistoryManager' import { history } from '@Kernel/index' +import { Timeline } from './Animation/Timeline' +import { Animation } from './Animation/Animation' export class Slide { static id = 0 private _id: number private _blocks: Array + private _timeline: Timeline events = { blockChange: new EventManager(), + animationChange: new EventManager(), } constructor(blocks: Array = []) { this._id = Slide.id++ this._blocks = blocks + this._timeline = new Timeline() } get id() { @@ -43,7 +48,7 @@ export class Slide { addBlock(block: Block) { const command = new Command( () => this._addBlock(block), - () => this._removeBlock(), + () => this._removeBlock() ) history.exec(command) } @@ -55,4 +60,27 @@ export class Slide { ) history.exec(command) } + + // Animation + get animations() { + return this._timeline.animations + } + + addAnimation(animation: Animation) { + this._timeline.add(animation) + this.events.animationChange.emit() + } + + deleteAnimation(animation: Animation) { + this._timeline.delete(animation) + this.events.animationChange.emit() + } + + previewAnimation() { + this._timeline.preview() + } + + startAnimation() { + this._timeline.start() + } } diff --git a/src/UserInterface/AnimationPane/AnimationPane.vue b/src/UserInterface/AnimationPane/AnimationPane.vue new file mode 100644 index 0000000..d2aaa1b --- /dev/null +++ b/src/UserInterface/AnimationPane/AnimationPane.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/UserInterface/Present/Present.vue b/src/UserInterface/Present/Present.vue index b3701d3..566eed8 100644 --- a/src/UserInterface/Present/Present.vue +++ b/src/UserInterface/Present/Present.vue @@ -6,6 +6,7 @@ import StatusBar from '../StatusBar/StatusBar.vue' import Show from '../Show/Show.vue' import { type SlideMode, slideShowMode, history } from '@Kernel/index' import { ref, onMounted } from 'vue' +import AnimationPane from '../AnimationPane/AnimationPane.vue' const currentMode = ref('edit') slideShowMode.on(async (mode) => { @@ -33,6 +34,7 @@ onMounted(() => {
+
diff --git a/src/UserInterface/Show/Show.vue b/src/UserInterface/Show/Show.vue index 9fc1d3b..ba8dd2d 100644 --- a/src/UserInterface/Show/Show.vue +++ b/src/UserInterface/Show/Show.vue @@ -1,8 +1,9 @@ diff --git a/src/UserInterface/Show/components/Block.vue b/src/UserInterface/Show/components/Block.vue new file mode 100644 index 0000000..44d1e79 --- /dev/null +++ b/src/UserInterface/Show/components/Block.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/UserInterface/Slide/Slide.vue b/src/UserInterface/Slide/Slide.vue index 827db43..8b55452 100644 --- a/src/UserInterface/Slide/Slide.vue +++ b/src/UserInterface/Slide/Slide.vue @@ -48,7 +48,7 @@ toolBox.events.toolChange.on(() => {