import React from "react"; import { makeObservable, observable } from "mobx"; import { ClassInfo, EezObject, IMessage, makeDerivedClassInfo, PropertyType } from "project-editor/core/object"; import { IFieldProperties, IWasmFlowRuntime } from "eez-studio-types"; import { findBitmap, ProjectType } from "project-editor/project/project"; import { LVGLWidget } from "./internal"; import { specificGroup } from "project-editor/ui-components/PropertyGrid/groups"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { createObject, propertyNotFoundMessage, propertyNotSetMessage } from "project-editor/store"; import type { LVGLCode } from "project-editor/lvgl/to-lvgl-code"; import { showGenericDialog } from "eez-studio-ui/generic-dialog"; import { Bitmap } from "project-editor/features/bitmap/bitmap"; //////////////////////////////////////////////////////////////////////////////// class LVGLAnimationImage extends EezObject { image: string; static classInfo: ClassInfo = { properties: [ { name: "image", type: PropertyType.ObjectReference, referencedObjectCollectionPath: "bitmaps" } ], listLabel: (animationImage: LVGLAnimationImage, collapsed: boolean) => collapsed ? animationImage.image ? animationImage.image : "Image not set" : "", defaultValue: {}, check: (animationImage: LVGLAnimationImage, messages: IMessage[]) => { if (animationImage.image) { const bitmap = findBitmap( ProjectEditor.getProject(animationImage), animationImage.image ); if (!bitmap) { messages.push( propertyNotFoundMessage(animationImage, "image") ); } } else { messages.push(propertyNotSetMessage(animationImage, "image")); } }, newItem: async (parentObject: LVGLAnimationImage[]) => { const project = ProjectEditor.getProject(parentObject); const bitmaps: Bitmap[] = project._store.lvglIdentifiers.bitmaps.filter( bitmap => !parentObject.find( lvglAnimationImage => lvglAnimationImage.image == bitmap.name ) ); if (bitmaps.length == 0) { return createObject( project._store, {}, LVGLAnimationImage ); } const fields: IFieldProperties[] = []; fields.push({ name: "image", type: "enum", enumItems: bitmaps.map(bitmap => ({ id: bitmap.name, label: bitmap.name })) }); if (parentObject.length == 0) { fields.push({ name: "numImages", displayName: "No. of images", type: "integer" }); } const result = await showGenericDialog({ dialogDefinition: { fields }, values: { image: bitmaps.length > 0 ? bitmaps[0].name : undefined, numImages: 1 }, dialogContext: project }); const objects = []; const numImages = result.values.numImages ?? 1; let bitmapIndex = bitmaps.findIndex( bitmap => bitmap.name == result.values.image ); for (let i = 0; i < numImages; i++, bitmapIndex++) { objects.push( createObject( project._store, { image: bitmapIndex >= 0 && bitmapIndex < bitmaps.length ? bitmaps[bitmapIndex].name : undefined }, LVGLAnimationImage ) ); } if (objects.length > 1) { project._store.addObjects(parentObject, objects); return undefined; } return objects[0]; } }; override makeEditable() { super.makeEditable(); makeObservable(this, { image: observable }); } } //////////////////////////////////////////////////////////////////////////////// export class LVGLAnimationImageWidget extends LVGLWidget { static classInfo = makeDerivedClassInfo(LVGLWidget.classInfo, { enabledInComponentPalette: (projectType: ProjectType) => projectType === ProjectType.LVGL, componentPaletteGroupName: "!1Visualiser", properties: [ { name: "images", type: PropertyType.Array, typeClass: LVGLAnimationImage, propertyGridGroup: specificGroup, partOfNavigation: false, enumerable: false, defaultValue: [] }, { name: "duration", type: PropertyType.Number, propertyGridGroup: specificGroup }, { name: "repeatInfinite", type: PropertyType.Boolean, propertyGridGroup: specificGroup, checkboxStyleSwitch: true }, { name: "repeat", displayName: "Repeat count", type: PropertyType.Number, propertyGridGroup: specificGroup, hideInPropertyGrid: (widget: LVGLAnimationImageWidget) => widget.repeatInfinite } ], defaultValue: { left: 0, top: 0, width: 180, height: 100, duration: 1000, repeatInfinite: true, repeat: 1 }, icon: ( ), lvgl: { parts: ["MAIN"], defaultFlags: "ADV_HITTEST|CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE" } }); images: LVGLAnimationImage[]; duration: number; repeatInfinite: boolean; repeat: number; _bitmapPtrBuffer: number; _bitmapPtrBufferWasm: IWasmFlowRuntime; override makeEditable() { super.makeEditable(); makeObservable(this, { images: observable, duration: observable, repeatInfinite: observable, repeat: observable }); } override toLVGLCode(code: LVGLCode) { code.createObject("lv_animimg_create"); const images = this.images .map(animationImage => code.image(animationImage.image)) .filter(image => image); if (images.length > 0) { let imagesArg; if (code.lvglBuild) { const build = code.lvglBuild; build.blockStart( `static const ${ build.isV9 ? "lv_image_dsc_t" : "lv_img_dsc_t" } *images[${images.length}] = {` ); images.forEach(image => { build.line(`${image},`); }); build.blockEnd("};"); imagesArg = "(const void **)images"; } else { const runtime = code.pageRuntime!; const bitmapPtrArray = new Uint32Array(images.length); for (let i = 0; i < images.length; i++) { bitmapPtrArray[i] = images[i]; } const bitmapPtrBuffer = runtime.wasm._malloc( bitmapPtrArray.length * bitmapPtrArray.BYTES_PER_ELEMENT ); runtime.wasm.HEAPU32.set(bitmapPtrArray, bitmapPtrBuffer >> 2); imagesArg = bitmapPtrBuffer; } code.callObjectFunction( "lv_animimg_set_src", imagesArg, images.length ); if (code.pageRuntime) { const runtime = code.pageRuntime!; if ( this._bitmapPtrBuffer && this._bitmapPtrBufferWasm == runtime.wasm ) { runtime.wasm._free(this._bitmapPtrBuffer); } this._bitmapPtrBuffer = imagesArg as number; this._bitmapPtrBufferWasm = runtime.wasm; } code.callObjectFunction("lv_animimg_set_duration", this.duration); code.callObjectFunction( "lv_animimg_set_repeat_count", this.repeatInfinite ? code.constant("LV_ANIM_REPEAT_INFINITE") : this.repeat ); code.callObjectFunction("lv_animimg_start"); } } }