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 (
);
}
}
);