diff --git a/demo/src/Counter.ts b/demo/src/Counter.ts index 5da84dc6..33b1a2f8 100644 --- a/demo/src/Counter.ts +++ b/demo/src/Counter.ts @@ -1,71 +1,83 @@ -import { Image, ViewHolder, VMPanel, ViewModel, Gravity, NativeCall, Text, Color, VLayout, log, logw, loge, Group, LayoutSpec, } from "doric" +import { vlayout, Image, ViewHolder, VMPanel, ViewModel, Gravity, NativeCall, Text, Color, log, logw, loge, Group, LayoutSpec, } from "doric" interface CountModel { count: number } -class CounterView extends ViewHolder { - number = new Text - counter = new Text +class CounterView extends ViewHolder { + + number!: Text + counter!: Text build(root: Group) { - const vlayout = new VLayout - vlayout.width = 200 - vlayout.height = 200 - vlayout.gravity = new Gravity().center() - this.number.textSize = 40 - this.number.layoutConfig = { - alignment: new Gravity().center() - } - this.counter = new Text - this.counter.text = "点击计数" - this.counter.border = { - width: 1, - color: Color.parse('#000000'), - } - this.counter.textSize = 20 - this.counter.corners = 5 - vlayout.space = 20 - vlayout.layoutConfig = { - alignment: new Gravity().center() - } - vlayout.border = { - width: 1, - color: Color.parse("#000000"), - } - this.counter.shadow = { - color: Color.parse("#00ff00"), - opacity: 0.5, - radius: 20, - offsetX: 10, - offsetY: 10, - } - vlayout.shadow = { - color: Color.parse("#ffff00"), - opacity: 0.5, - radius: 20, - offsetX: 10, - offsetY: 10, - } - vlayout.corners = 20 - vlayout.addChild(this.number) - vlayout.addChild(this.counter) - // root.bgColor = Color.parse('#00ff00') - vlayout.bgColor = Color.parse('#ff00ff') - root.addChild(vlayout) - const iv = new Image - // iv.width = iv.height = 100 - iv.imageUrl = "https://misc.aotu.io/ONE-SUNDAY/SteamEngine.png" - //iv.bgColor = Color.parse('#00ff00') - iv.layoutConfig = { - widthSpec: LayoutSpec.WRAP_CONTENT, - heightSpec: LayoutSpec.WRAP_CONTENT, - } - root.addChild(iv) + root.addChild(vlayout([ + () => { + return (new Text).also(it => { + it.textSize = 40 + it.layoutConfig = { + alignment: new Gravity().center() + } + this.number = it + }) + }, + () => { + return (new Text).also(it => { + it.text = "点击计数" + it.textSize = 20 + + it.border = { + width: 1, + color: Color.parse('#000000'), + } + it.corners = 5 + + it.layoutConfig = { + alignment: new Gravity().center() + } + it.shadow = { + color: Color.parse("#00ff00"), + opacity: 0.5, + radius: 20, + offsetX: 10, + offsetY: 10, + } + this.counter = it + }) + }, + ]).also(it => { + it.width = 200 + it.height = 200 + it.space = 20 + it.gravity = new Gravity().center() + it.layoutConfig = { + alignment: new Gravity().center() + } + it.border = { + width: 1, + color: Color.parse("#000000"), + } + it.shadow = { + color: Color.parse("#ffff00"), + opacity: 0.5, + radius: 20, + offsetX: 10, + offsetY: 10, + } + it.corners = 20 + it.bgColor = Color.parse('#ff00ff') + })) + + root.addChild((new Image).also(iv => { + iv.imageUrl = "https://misc.aotu.io/ONE-SUNDAY/SteamEngine.png" + iv.layoutConfig = { + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + })) } - setNumber(n: number) { - this.number.text = n.toString() + bind(state: CountModel) { + this.number.text = `${state.count}` } setCounter(v: Function) { @@ -74,35 +86,33 @@ class CounterView extends ViewHolder { } class CounterVM extends ViewModel { - - binding(v: CounterView, model: CountModel): void { - v.setNumber(model.count) - v.setCounter(() => { - this.getModel().count++ - }) + onAttached(s: CountModel, vh: CounterView): void { + vh.counter.onClick = () => { + this.updateState(state => { + state.count++ + }) + } } } @Entry -class MyPage extends VMPanel{ +class MyPage extends VMPanel{ - getVMClass() { + + getViewHolderClass() { + return CounterView + } + + getViewModelClass() { return CounterVM } - getModel() { + getState(): CountModel { return { - count: 0, - add: function () { - this.count++ - }, + count: 0 } } - getViewHolder() { - return new CounterView - } - @NativeCall log() { diff --git a/demo/src/Snake.ts b/demo/src/Snake.ts index 617d0306..1292cd99 100644 --- a/demo/src/Snake.ts +++ b/demo/src/Snake.ts @@ -1,4 +1,4 @@ -import { loge, log, ViewHolder, Stack, ViewModel, Gravity, Text, Color, HLayout, VLayout, Group, VMPanel, LayoutSpec } from "doric"; +import { loge, log, ViewHolder, Stack, ViewModel, Gravity, Text, Color, HLayout, VLayout, Group, VMPanel, LayoutSpec, vlayout, hlayout } from "doric"; type SnakeNode = { x: number @@ -134,7 +134,8 @@ class SnakeModel { } } -class SnakeView extends ViewHolder { +class SnakeView extends ViewHolder { + panel: Stack = new Stack start: Text = new Text up?: Text @@ -144,79 +145,109 @@ class SnakeView extends ViewHolder { build(root: Group): void { root.bgColor = Color.parse('#000000') - const vlayout = new VLayout - const title = new Text - title.text = "Snake" - title.textSize = 20 - title.textColor = Color.parse("#ffffff") - title.layoutConfig = { - alignment: new Gravity().centerX(), - margin: { - top: 20 + vlayout([ + () => { + return (new Text).also(title => { + title.text = "Snake" + title.textSize = 20 + title.textColor = Color.parse("#ffffff") + title.layoutConfig = { + alignment: new Gravity().centerX(), + margin: { + top: 20 + }, + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + }) }, - widthSpec: LayoutSpec.WRAP_CONTENT, - heightSpec: LayoutSpec.WRAP_CONTENT, - } - vlayout.space = 20 - vlayout.layoutConfig = { - alignment: new Gravity().centerX().top(), - widthSpec: LayoutSpec.WRAP_CONTENT, - heightSpec: LayoutSpec.WRAP_CONTENT, - } - this.panel.bgColor = Color.parse('#00ff00') - vlayout.addChild(title) - vlayout.addChild(this.panel) - root.addChild(vlayout) + () => { + return (new Stack).also(panel => { + panel.bgColor = Color.parse('#00ff00') - const hlayout = new HLayout - hlayout.layoutConfig = { - widthSpec: LayoutSpec.WRAP_CONTENT, - heightSpec: LayoutSpec.WRAP_CONTENT, - } - hlayout.addChild(this.start.also( - it => { - it.text = "Start" - it.textSize = 30 - it.textColor = Color.parse("#ffffff") - it.layoutConfig = { - widthSpec: LayoutSpec.WRAP_CONTENT, - heightSpec: LayoutSpec.WRAP_CONTENT, - } - })) - vlayout.addChild(hlayout) - - - this.up = this.buildController("↑") - this.down = this.buildController("↓") - this.left = this.buildController("←") - this.right = this.buildController("→") - - const controlArea = new VLayout - controlArea.gravity = new Gravity().centerX() - controlArea.space = 10 - controlArea.layoutConfig = { - alignment: new Gravity().centerX(), - widthSpec: LayoutSpec.WRAP_CONTENT, - heightSpec: LayoutSpec.WRAP_CONTENT, - } - const line1 = new HLayout - const line2 = new HLayout - line2.space = 10 - line1.addChild(this.up) - line2.addChild(this.left) - line2.addChild(this.down) - line2.addChild(this.right) - line1.layoutConfig = { - widthSpec: LayoutSpec.WRAP_CONTENT, - heightSpec: LayoutSpec.WRAP_CONTENT, - } - line2.layoutConfig = { - widthSpec: LayoutSpec.WRAP_CONTENT, - heightSpec: LayoutSpec.WRAP_CONTENT, - } - controlArea.addChild(line1) - controlArea.addChild(line2) - vlayout.addChild(controlArea) + }) + }, + () => { + return hlayout([ + () => { + return (new Text).also(it => { + it.text = "Start" + it.textSize = 30 + it.textColor = Color.parse("#ffffff") + it.layoutConfig = { + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + this.start = it + }) + }, + ]).also(it => { + it.layoutConfig = { + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + }) + }, + () => { + return vlayout([ + () => { + return hlayout([ + () => { + return this.buildController("↑").also(it => { + this.up = it + }) + } + ]).also(it => { + it.layoutConfig = { + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + }) + }, + () => { + return hlayout([ + () => { + return this.buildController("←").also(it => { + this.left = it + }) + }, + () => { + return this.buildController("↓").also(it => { + this.down = it + }) + }, + () => { + return this.buildController("→").also(it => { + this.right = it + }) + }, + ]).also(it => { + it.layoutConfig = { + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + it.space = 10 + }) + }, + ]) + .also(controlArea => { + controlArea.gravity = new Gravity().centerX() + controlArea.space = 10 + controlArea.layoutConfig = { + alignment: new Gravity().centerX(), + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + }) + } + ]).also(it => { + it.space = 20 + it.layoutConfig = { + alignment: new Gravity().centerX().top(), + widthSpec: LayoutSpec.WRAP_CONTENT, + heightSpec: LayoutSpec.WRAP_CONTENT, + } + }) } buildController(text: string) { @@ -228,19 +259,54 @@ class SnakeView extends ViewHolder { ret.textAlignment = new Gravity().center() return ret } + + bind(state: SnakeModel): void { + this.panel.width = state.width * 10 + this.panel.height = state.height * 10 + let node: SnakeNode | undefined = state.head + let nodes: SnakeNode[] = [] + while (node != undefined) { + nodes.push(node) + node = node.next + } + nodes.push(state.food) + nodes.forEach((e, index) => { + + let item = this.panel.children[index] + if (item === undefined) { + item = new Stack + item.width = item.height = 10 + this.panel.addChild(item) + } + if (index === nodes.length - 1) { + item.bgColor = Color.parse('#ffff00') + } else { + item.bgColor = Color.parse('#ff0000') + } + item.x = e.x * 10 + item.y = e.y * 10 + }) + + if (nodes.length < this.panel.children.length) { + this.panel.children.length = nodes.length + } + } } class SnakeVM extends ViewModel{ - timerId?: any start = () => { if (this.timerId !== undefined) { clearInterval(this.timerId) } - this.getModel().reset() + this.updateState(it => it.reset()) this.timerId = setInterval(() => { - this.getModel().step() + this.updateState(it => it.step()) + if (this.getState().state === State.fail) { + loge('Game Over') + this.stop() + } }, 500) } @@ -252,56 +318,23 @@ class SnakeVM extends ViewModel{ } left = () => { - this.getModel().direction = Direction.left + this.updateState(it => it.direction = Direction.left) } right = () => { - this.getModel().direction = Direction.right + this.updateState(it => it.direction = Direction.right) } up = () => { - this.getModel().direction = Direction.up + this.updateState(it => it.direction = Direction.up) } down = () => { - this.getModel().direction = Direction.down + this.updateState(it => it.direction = Direction.down) } - binding(v: SnakeView, model: SnakeModel) { - if (model.state === State.fail) { - loge('Game Over') - this.stop() - } + onAttached(state: SnakeModel, v: SnakeView): void { v.start.onClick = this.start - v.panel.width = model.width * 10 - v.panel.height = model.height * 10 - let node: SnakeNode | undefined = model.head - let nodes: SnakeNode[] = [] - while (node != undefined) { - nodes.push(node) - node = node.next - } - nodes.push(model.food) - nodes.forEach((e, index) => { - - let item = v.panel.children[index] - if (item === undefined) { - item = new Stack - item.width = item.height = 10 - v.panel.addChild(item) - } - if (index === nodes.length - 1) { - item.bgColor = Color.parse('#ffff00') - } else { - item.bgColor = Color.parse('#ff0000') - } - item.x = e.x * 10 - item.y = e.y * 10 - }) - - if (nodes.length < v.panel.children.length) { - v.panel.children.length = nodes.length - } if (v.left) { v.left.onClick = this.left } @@ -315,19 +348,17 @@ class SnakeVM extends ViewModel{ v.down.onClick = this.down } } + } @Entry class SnakePanel extends VMPanel{ - - getVMClass() { + getViewModelClass() { return SnakeVM } - - getModel() { + getState(): SnakeModel { return new SnakeModel(35, 35) } - - getViewHolder() { - return new SnakeView + getViewHolderClass() { + return SnakeView } } \ No newline at end of file diff --git a/js-framework/src/vm/mvvm.ts b/js-framework/src/vm/mvvm.ts index e31b4576..da3a85d1 100644 --- a/js-framework/src/vm/mvvm.ts +++ b/js-framework/src/vm/mvvm.ts @@ -43,22 +43,25 @@ export abstract class ViewModel> { attach(view: Group) { this.viewHolder.build(view) + this.viewHolder.bind(this.state) } + + abstract onAttached(state: M, vh: V): void } -export type ViewModelClass = new (m: M, v: ViewHolder) => ViewModel> +export type ViewModelClass> = new (m: M, v: V) => ViewModel -export type ViewHolderClass = new () => ViewHolder +export type ViewHolderClass = new () => V -export abstract class VMPanel extends Panel { +export abstract class VMPanel> extends Panel { - private vm?: ViewModel> - private vh?: ViewHolder + private vm?: ViewModel + private vh?: V - abstract getViewModelClass(): ViewModelClass + abstract getViewModelClass(): ViewModelClass abstract getState(): M - abstract getViewHolderClass(): ViewHolderClass + abstract getViewHolderClass(): ViewHolderClass getViewModel() { return this.vm