import React from "react"; import { observer } from "mobx-react"; import { autorun, IReactionDisposer, runInAction } from "mobx"; import { ProjectContext } from "project-editor/project/context"; import { QueueTask } from "project-editor/flow/runtime/runtime"; import { EezGuiLiteRuntime } from "project-editor/eez-gui-lite/page-runtime"; import { Widget } from "project-editor/flow/component"; import { evalAssignableExpression, evalExpression } from "project-editor/eez-flow-lite/expression"; import { getClassInfo, type ProjectStore } from "project-editor/store"; import { SetColorThemeActionComponent, ShowPageActionComponent } from "project-editor/flow/components/actions"; import { Page } from "project-editor/features/page/page"; import { SwitchWidget } from "project-editor/flow/components/widgets/eez-gui"; import type { Theme } from "project-editor/features/style/theme"; import { EezFlowLiteWasmRuntime } from "project-editor/eez-flow-lite/wasm-runtime"; export class EezGuiLiteWasmRuntime extends EezFlowLiteWasmRuntime { pageRuntime: EezGuiLiteRuntime; selectedTheme: Theme | undefined; get pointerEvents() { return this.pageRuntime.pointerEvents; } constructor(public projectStore: ProjectStore) { super(projectStore); this.pageRuntime = new EezGuiLiteRuntime(this.selectedPage, this); this.pageRuntime.mount(); } get displayWidth() { return this.pageRuntime.page.width; } get displayHeight() { return this.pageRuntime.page.height; } renderPage(page: Page) { return ; } executeTaskSpecific(task: QueueTask) { const { flowState, connectionLine } = task; const component = connectionLine!.targetComponent; if (component instanceof ShowPageActionComponent) { const page = this.projectStore.project.pages.find( page => page.name == component.page ); if (page) { runInAction(() => { this.selectedPage = page; }); this.pageRuntime.unmount(); this.pageRuntime.page = this.selectedPage; this.pageRuntime.mount(); } } else if (component instanceof SetColorThemeActionComponent) { const themeName = evalExpression( flowState.expressionContext, component, component.theme ); const theme = this.projectStore.project.themes.find( theme => theme.name == themeName ); if (theme) { runInAction(() => (this.selectedTheme = theme)); this.pageRuntime.unmount(); this.pageRuntime.mount(); } } return true; } onEvent(page: Page, widget: Widget, eventType: number) { const flowState = this.flowStates.find( flowState => flowState.flow == page ); if (!flowState) { return; } const classInfo = getClassInfo(widget); if (typeof classInfo.widgetEvents == "function") { const widgetEvents = classInfo.widgetEvents(widget); const eventName = Object.keys(widgetEvents).find( eventName => widgetEvents[eventName].code == eventType ); if (eventName) { if ( eventName == "CLICKED" && widget instanceof SwitchWidget && widget.data != undefined ) { const variable = evalAssignableExpression( flowState.expressionContext, widget, widget.data ); let value = evalExpression( flowState.expressionContext, widget, widget.data ); if (typeof value == "boolean") { flowState.dataContext.set(variable.name, !value); } } const eventHandler = widget.eventHandlers.find( eventHandler => eventHandler.eventName == eventName ); if (eventHandler) { if (eventHandler.handlerType == "flow") { this.addToQueueConnectionLines( flowState, widget, eventName ); } else { const action = this.projectStore.project.actions.find( action => action.name == eventHandler.action ); if (action) { this.executeAction(action, flowState); } } } } } } } //////////////////////////////////////////////////////////////////////////////// const WasmCanvas = observer( class WasmCanvas extends React.Component<{ page: Page; }> { static contextType = ProjectContext; declare context: React.ContextType; canvasRef = React.createRef(); autorunDispose: IReactionDisposer; get wasmRuntime() { return this.context.runtime as EezGuiLiteWasmRuntime; } sendPointerEvent(event: PointerEvent) { if (this.props.page != this.wasmRuntime.pageRuntime.page) { return; } const canvas = this.canvasRef.current; if (!canvas) { return; } const wasmRuntime = this.wasmRuntime; if (!wasmRuntime) { return; } var bbox = canvas.getBoundingClientRect(); const left = wasmRuntime.pageRuntime.page.left; const top = wasmRuntime.pageRuntime.page.top; const width = wasmRuntime.pageRuntime.page.width; const height = wasmRuntime.pageRuntime.page.height; const x = (event.clientX - bbox.left - (wasmRuntime.isDebuggerActive ? 0 : left + (wasmRuntime.displayWidth - width) / 2)) * (canvas.width / bbox.width); const y = (event.clientY - bbox.top - (wasmRuntime.isDebuggerActive ? 0 : top + (wasmRuntime.displayHeight - height) / 2)) * (canvas.height / bbox.height); const pressed = event.buttons == 1; let lastEvent; if (wasmRuntime.pointerEvents.length > 0) { lastEvent = wasmRuntime.pointerEvents[ wasmRuntime.pointerEvents.length - 1 ]; } if (lastEvent && lastEvent.pressed == pressed) { lastEvent.x = x; lastEvent.y = y; } else { wasmRuntime.pointerEvents.push({ x, y, pressed }); } event.preventDefault(); event.stopPropagation(); } onPointerDown = (event: PointerEvent) => { const canvas = this.canvasRef.current; if (!canvas) { return; } canvas.focus(); const wasmRuntime = this.wasmRuntime; if (!wasmRuntime) { return; } canvas.setPointerCapture(event.pointerId); this.sendPointerEvent(event); }; onPointerMove = (event: PointerEvent) => { this.sendPointerEvent(event); }; onPointerUp = (event: PointerEvent) => { const canvas = this.canvasRef.current; if (!canvas) { return; } canvas.releasePointerCapture(event.pointerId); this.sendPointerEvent(event); }; onPointerCancel = (event: PointerEvent) => { const canvas = this.canvasRef.current; if (!canvas) { return; } canvas.releasePointerCapture(event.pointerId); this.sendPointerEvent(event); }; componentDidMount() { const canvasElement = this.canvasRef.current; if (!canvasElement) { return; } const canvas = canvasElement; canvas.addEventListener("pointerdown", this.onPointerDown, true); canvas.addEventListener("pointermove", this.onPointerMove, true); canvas.addEventListener("pointerup", this.onPointerUp, true); canvas.addEventListener( "pointercancel", this.onPointerCancel, true ); canvas.focus(); this.refreshPage(); } componentWillUnmount() { const canvasElement = this.canvasRef.current; if (canvasElement) { const canvas = canvasElement; canvas.removeEventListener( "pointerdown", this.onPointerDown, true ); canvas.removeEventListener( "pointermove", this.onPointerMove, true ); canvas.removeEventListener("pointerup", this.onPointerUp, true); canvas.removeEventListener( "pointercancel", this.onPointerCancel, true ); } if (this.autorunDispose) { this.autorunDispose(); } } componentDidUpdate() { this.refreshPage(); } refreshPage() { if (this.autorunDispose) { this.autorunDispose(); } const canvasElement = this.canvasRef.current; if (!canvasElement) { return; } const canvas = canvasElement; const ctx = canvas.getContext("2d")!; this.autorunDispose = autorun(() => { if (this.wasmRuntime) { const pageImageData = this.wasmRuntime.pageRuntime.pageImageDataMap.get( this.props.page ); if (pageImageData) { this.wasmRuntime.pageRuntime.drawPage( ctx, pageImageData ); } else { ctx.clearRect( 0, 0, this.wasmRuntime.displayWidth, this.wasmRuntime.displayHeight ); } } }); } render() { const wasmRuntime = this.wasmRuntime; return ( ); } } );