From b37ab596c552802f39effe7eb161fdef71d3b149 Mon Sep 17 00:00:00 2001 From: Acbox <850625057@qq.com> Date: Sun, 16 Jun 2024 00:58:27 +0800 Subject: [PATCH] feat: setup grammar sugar --- TODO.md | 7 +- examples/scene1.ts | 299 +++++++++++++++--------------- packages/basic/src/widgets/arc.ts | 9 +- packages/core/src/apiAnimate.ts | 28 --- packages/core/src/index.ts | 2 - packages/core/src/widget.ts | 35 +++- 6 files changed, 187 insertions(+), 193 deletions(-) delete mode 100644 packages/core/src/apiAnimate.ts diff --git a/TODO.md b/TODO.md index d70e6eb7e..a7c59a6bc 100644 --- a/TODO.md +++ b/TODO.md @@ -15,14 +15,11 @@ Please write it according to following rules: | Task | Description | Status | Assigned | Create Date | Done Date | Related Issue | Related Pull Request | Species | | ---------------------------------------------------------- | ----------------------------- | ---------------- | -------------------------- | ----------- | ---------- | ------------- | -------------------- | ------- | -| Color Animation (core) | The Animation that for color. | Intend | @sheepbox8646 | 2024-05-23 | -| Perfect Type Check (core) | ... | Ready | None | 2024-05-23 | -| The Automated Test Scripts (core) | ... | Ready | None | 2024-05-23 | | Layout System (core) | ... | Ready | None | 2024-05-23 | | Failed to create too long video under the local mode (cli) | ... | Ready | None | 2024-5-25 | | Theme (core) | ... | Ready | None | 2024-05-23 | | Documentation for Developer (docs) | ... | Ready | None | 2024-05-23 | -| Playground Sources Preload | ... | Intend | None | 2024-05-25 | +| Playground Sources Preload | ... | WIP | None | 2024-05-25 | | SVG Analyzer (mods) | ... | WIP | @sheepbox8646 @jiwangyihao | 2024-05-23 | | Mod Web Component (mods) | ... | WIP | @justyukie | 2024-05-23 | | | #40 | | Mod Chart (mods) | ... | WIP | @jiwangyihao | 2024-05-23 | @@ -30,6 +27,8 @@ Please write it according to following rules: | Mod Geometry (mods) | ... | WIP | @sheepbox8646 | 2024-05-23 | | Mod Markdown (mods) | ... | WIP | @sheepbox8646 | 2024-05-23 | | Plugin Debug (plugins) | ... | WIP | @sheepbox8646 | 2024-05-23 | +| Perfect Type Check (core) | ... | Done | None | 2024-05-23 | +| Color Animation (core) | The Animation that for color. | Done | @sheepbox8646 | 2024-05-23 | | Unit support for local mode (core) | ... | Done | None | 2024-05-25 | | Widget Lifecycle | ... | Done | None | 2024-05-24 | | Refactor `changeProperty` and `changeStyle` | ... | Done | @wgxh-cli | 2024-05-23 | 2024-05-25 | diff --git a/examples/scene1.ts b/examples/scene1.ts index 3c5efb92f..1da629cf0 100644 --- a/examples/scene1.ts +++ b/examples/scene1.ts @@ -1,157 +1,166 @@ import * as nc from 'newcar' import * as mt from '@newcar/mod-math' -import { Arc } from '@newcar/basic' -await nc.useFont('../Roboto-Regular.ttf') +// await nc.useFont('../Roboto-Regular.ttf') -// remove ck from AnimationContext +// // remove ck from AnimationContext -const show = () => nc.depend<() => boolean, nc.AnimationContext>(ctx => () => { - ctx.widget.show() - return true -}) +// const show = () => nc.depend<() => boolean, nc.AnimationContext>(ctx => () => { +// ctx.widget.show() +// return true +// }) -const withFunction = (f: (widget: T) => boolean) => { - return nc.depend<() => boolean, nc.AnimationContext>(ctx => () => { - return f(ctx.widget) - }) -} +// const withFunction = (f: (widget: T) => boolean) => { +// return nc.depend<() => boolean, nc.AnimationContext>(ctx => () => { +// return f(ctx.widget) +// }) +// } -const withInitial = (f: (widget: T, anim: Anim) => boolean) => { } +// const withInitial = (f: (widget: T, anim: Anim) => boolean) => { } -export default nc.createScene( - new nc.Widget() - .add(new nc.Widget() - .add(new nc.Widget() - .add( - new nc.Text("What is Newcar?", { - style: { - fontSize: 50 - }, - width: 1600, - textAlign: 'center', - y: 450 - }) - .animate(nc.stroke().withAttr({ duration: 3 })) - .animate(nc.move().withAttr({ duration: 1, to: [0, 150], by: nc.easeOutCubic })) - .animate(nc.fadeOut().withAttr({ duration: 0.7 })) - .animate(nc.delay(0.6)) - .animate(withFunction((w) => { - w.text.value = 'This is a circle, you can see it gradually grows.' - w.style.transparency.value = 1 - w.style.interval.value = [0, 0] - return true - })) - .animate(nc.stroke().withAttr({ duration: 3 })) - .animate(nc.delay(4)) - .animate(withFunction((w) => { - w.text.value = 'And it is falling down with bounce' - w.style.transparency.value = 1 - w.style.interval.value = [0, 0] - return true - })) - .animate(nc.stroke().withAttr({ duration: 3 })) - .animate(nc.delay(1)) - .animate(nc.fadeOut().withAttr({ duration: 1 })) +// export default nc.createScene( +// new nc.Widget() +// .add(new nc.Widget() +// .add(new nc.Widget() +// .add( +// new nc.Text("What is Newcar?", { +// style: { +// fontSize: 50 +// }, +// width: 1600, +// textAlign: 'center', +// y: 450 +// }) +// .animate(nc.stroke().withAttr({ duration: 3 })) +// .animate(nc.move().withAttr({ duration: 1, to: [0, 150], by: nc.easeOutCubic })) +// .animate(nc.fadeOut().withAttr({ duration: 0.7 })) +// .animate(nc.delay(0.6)) +// .animate(withFunction((w) => { +// w.text.value = 'This is a circle, you can see it gradually grows.' +// w.style.transparency.value = 1 +// w.style.interval.value = [0, 0] +// return true +// })) +// .animate(nc.stroke().withAttr({ duration: 3 })) +// .animate(nc.delay(4)) +// .animate(withFunction((w) => { +// w.text.value = 'And it is falling down with bounce' +// w.style.transparency.value = 1 +// w.style.interval.value = [0, 0] +// return true +// })) +// .animate(nc.stroke().withAttr({ duration: 3 })) +// .animate(nc.delay(1)) +// .animate(nc.fadeOut().withAttr({ duration: 1 })) + +// ) +// .add(new nc.Arc(Math.PI * 50, 0, 360, { +// x: 800, +// y: 450, +// }) +// .add( +// new mt.MathFunction(x => Math.PI * Math.sin(x / Math.PI), [-4 * Math.PI, 0], { +// style: { +// scaleY: -1 +// }, +// x: Math.PI * 50 +// }).kill() +// .animate(nc.delay(16)) +// .animate(withFunction(w => { +// w.resurrect() +// return true +// })) +// .animate( - ) - .add(new nc.Arc(Math.PI * 50, 0, 360, { - x: 800, - y: 450, - }) - .add( - new mt.MathFunction(x => Math.PI * Math.sin(x / Math.PI), [-4 * Math.PI, 0], { - style: { - scaleY: -1 - }, - x: Math.PI * 50 - }).kill() - .animate(nc.delay(16)) - .animate(withFunction(w => { - w.resurrect() - return true - })) - .animate( +// nc.move().withAttr({ duration: 6, to: [5 * Math.PI * 50, 0] }) +// ) +// ) +// .animate(withFunction(w => { +// w.progress.value = 0 +// return true +// })) +// .animate(nc.delay(7.5)) +// .animate(nc.create().withAttr({ duration: 1 })) +// .animate(nc.delay(3.5)) +// .animate(nc.move().withAttr({ duration: 2, to: [800, 1200], by: nc.easeBounce })) +// .animate(nc.delay(1)) +// .animate(nc.parallel( +// nc.scale().withAttr({ to: [2, 2], duration: 1 }), +// nc.move().withAttr({ to: [1600, 900], duration: 1, by: nc.easeInOutCubic }), +// )) +// .animate(withFunction(w => { +// w.style.fill.value = false +// w.style.border.value = true +// return true +// })) +// .add( +// new mt.NumberPlane([-1000, 1000], [-1000, 1000], { +// style: { +// textsX: false, +// textsY: false +// } +// }).kill() +// .animate(nc.delay(16)) +// .animate(withFunction(w => { +// w.resurrect() +// return true +// })) +// .animate(nc.create().withAttr({ duration: 1, by: nc.easeOutExpo })) +// ) +// .add(...(() => { +// const main = new nc.Line([0, 0], [Math.PI * 50, 0], { +// style: { +// width: 3 +// } +// }).kill() +// .animate(nc.delay(16)) +// .animate(withFunction(w => { +// w.resurrect() +// return true +// })) +// .animate(nc.rotate().withAttr({ duration: 6, to: 720 })) +// return [main] +// })())) - nc.move().withAttr({ duration: 6, to: [5 * Math.PI * 50, 0] }) - ) - ) - .animate(withFunction(w => { - w.progress.value = 0 - return true - })) - .animate(nc.delay(7.5)) - .animate(nc.create().withAttr({ duration: 1 })) - .animate(nc.delay(3.5)) - .animate(nc.move().withAttr({ duration: 2, to: [800, 1200], by: nc.easeBounce })) - .animate(nc.delay(1)) - .animate(nc.parallel( - nc.scale().withAttr({ to: [2, 2], duration: 1 }), - nc.move().withAttr({ to: [1600, 900], duration: 1, by: nc.easeInOutCubic }), - )) - .animate(withFunction(w => { - w.style.fill.value = false - w.style.border.value = true - return true - })) - .add( - new mt.NumberPlane([-1000, 1000], [-1000, 1000], { - style: { - textsX: false, - textsY: false - } - }).kill() - .animate(nc.delay(16)) - .animate(withFunction(w => { - w.resurrect() - return true - })) - .animate(nc.create().withAttr({ duration: 1, by: nc.easeOutExpo })) - ) - .add(...(() => { - const main = new nc.Line([0, 0], [Math.PI * 50, 0], { - style: { - width: 3 - } - }).kill() - .animate(nc.delay(16)) - .animate(withFunction(w => { - w.resurrect() - return true - })) - .animate(nc.rotate().withAttr({ duration: 6, to: 720 })) - return [main] - })())) +// ) - ) +// .add(new nc.Rect(500, 500, { +// style: { +// fillColor: nc.Color.parse('skyblue'), +// blendMode: 'srcIn', +// rotation: 45, +// }, +// x: 600, +// y: 250, +// }).hide() +// .animate(nc.delay(9)) +// .animate(withFunction(w => { +// w.show() +// return true +// })) +// .animate(nc.create().withAttr({ duration: 1 }))) +// .animate(nc.delay(11)) +// .animate(nc.scale().withAttr({ duration: 1, to: [0.5, 0.5] })) +// ) +// .add( +// new nc.Line([0, 700], [1600, 700]).kill() +// .animate(nc.delay(11)) +// .animate(withFunction((w) => { +// w.resurrect() +// return true +// })) +// .animate( +// nc.create().withAttr({ duration: 1 }) +// ) +// ) +// ) - .add(new nc.Rect(500, 500, { - style: { - fillColor: nc.Color.parse('skyblue'), - blendMode: 'srcIn', - rotation: 45, - }, - x: 600, - y: 250, - }).hide() - .animate(nc.delay(9)) - .animate(withFunction(w => { - w.show() - return true - })) - .animate(nc.create().withAttr({ duration: 1 }))) - .animate(nc.delay(11)) - .animate(nc.scale().withAttr({ duration: 1, to: [0.5, 0.5] })) - ) - .add( - new nc.Line([0, 700], [1600, 700]).kill() - .animate(nc.delay(11)) - .animate(withFunction((w) => { - w.resurrect() - return true - })) - .animate( - nc.create().withAttr({ duration: 1 }) - ) - ) -) +export default nc.createScene( + new nc.Rect(500, 500) + .setup(function *() { + // yield 1 + yield nc.move().withAttr({ duration: 1, to: [1000, 1000] }) + }) + .animate(nc.delay(1)) + +) \ No newline at end of file diff --git a/packages/basic/src/widgets/arc.ts b/packages/basic/src/widgets/arc.ts index 1f0c36943..980351946 100644 --- a/packages/basic/src/widgets/arc.ts +++ b/packages/basic/src/widgets/arc.ts @@ -62,13 +62,8 @@ export class Arc extends Path { this.radius.value, this.radius.value, ) + this.path.rewind() + this.path.addArc(this.rect, this.from.value, this.to.value * this.progress.value) }) } - - draw(canvas: Canvas): void { - this.path.rewind() - this.path.addArc(this.rect, this.from.value, this.to.value * this.progress.value) - - super.draw(canvas) - } } diff --git a/packages/core/src/apiAnimate.ts b/packages/core/src/apiAnimate.ts deleted file mode 100644 index 0894f90c8..000000000 --- a/packages/core/src/apiAnimate.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Widget } from './widget' -import type { Animation } from './animation' - -export type AnimateFunction = (animation: Animation, duration: number, params?: Record) => { - animation: Animation - mode: 'async' | 'sync' - duration: number - params: Record - setAsync: () => ReturnType> - setSync: () => ReturnType> -} - -export function animate(animation: Animation, duration: number, params?: Record): ReturnType> { - return { - animation, - duration, - params: params ?? {}, - mode: 'sync', - setAsync() { - this.mode = 'async' - return this - }, - setSync() { - this.mode = 'sync' - return this - }, - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 157939f7a..96e6b92eb 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,8 +6,6 @@ export * from './scene' export * from './widget' export * from './asyncWidget' export * from './global' - -export * from './apiAnimate' export * from './apiUseFont' export * from './apiUseImage' diff --git a/packages/core/src/widget.ts b/packages/core/src/widget.ts index daa590e46..512938f7c 100644 --- a/packages/core/src/widget.ts +++ b/packages/core/src/widget.ts @@ -4,7 +4,6 @@ import type { Anim } from './animation' import type { Event, EventInstance } from './event' import { defineEvent } from './event' import type { WidgetPlugin } from './plugin' -import type { AnimateFunction } from './apiAnimate' import type { ConvertToProp, Reactive, Ref } from './prop' import type { App } from './app' import { changed, reactive, ref } from './prop' @@ -14,7 +13,7 @@ import { RootWidget } from './scene' export type WidgetRange = [number, number, number, number] // export type WidgetInstance = T -export type SetupFunction = (widget: T) => Generator>, void, unknown> +export type SetupFunction = (widget: T) => Generator, void, number | Anim> export type Layout = 'row' | 'column' | 'absolute' | 'mix' export type Status = 'live' | 'dead' @@ -60,7 +59,7 @@ export class Widget { animationInstances: Anim[] = [] eventInstances: EventInstance[] = [] updates: ((elapsed: number, widget: T) => void)[] = [] - setups: Array<{ generator: Generator>, void, unknown>, nextFrame: number }> = [] + setups: Array<{ generator: ReturnType>, nextFrame: number }> = [] key = `widget-${0}-${performance.now()}-${Math.random() .toString(16) .slice(2)}` @@ -142,6 +141,7 @@ export class Widget { this.initialized = true } this.runAnimation(elapsed, ck) + this.processSetups(elapsed, ck) canvas.save() @@ -259,7 +259,7 @@ export class Widget { setup(setupFunc: SetupFunction): this { const generator = setupFunc(this as T) - this.setups.push({ generator, nextFrame: 0 }) + this.setups.push({ generator: generator as any, nextFrame: 0 }) return this } @@ -278,12 +278,33 @@ export class Widget { // compared with the processing of `sync` animation. // When entered next update process, `runAnimation` will run multiple async animations that overlapped on the timeline, // For example, if we have a `move` animation from 1 to 60, and a `scale` animation from 30 to 90, then they will be played at the same time from 30 to 90 - processSetups(elapsed: number) { - // TODO: Rebuild + processSetups(elapsed: number, ck: CanvasKit) { + this.setups.forEach((setup) => { + if (elapsed >= setup.nextFrame) { + // advance the setup + const result = setup.generator.next() + if (!result.done) { + if (typeof result.value === 'number') { + // simply put a delay as long as the value here + setup.nextFrame = elapsed + result.value + } + else if (!isUndefined((result.value as Anim).build)) { + // (result.value as Anim).build({ + // elapsed, ck, widget: this + // })() + this.animationInstances.push(result.value as Anim) + } + } + else { + // Marked done + setup.nextFrame = Number.POSITIVE_INFINITY + } + } + }) // Clean up Generator that has finished. this.setups = this.setups.filter(setup => setup.nextFrame !== Number.POSITIVE_INFINITY) - this.children.forEach(child => child.processSetups(elapsed)) + this.children.forEach(child => child.processSetups(elapsed, ck)) } use(...plugins: WidgetPlugin[]) {