import React from "react"; import { observable, makeObservable } from "mobx"; import { IMessage, MessageType, PropertyType, getParent, makeDerivedClassInfo } from "project-editor/core/object"; import { findBitmap, ProjectType } from "project-editor/project/project"; import { specificGroup } from "project-editor/ui-components/PropertyGrid/groups"; import { LVGLWidget, LVGLScaleWidget } from "./internal"; import { LVGLPropertyType, makeLvglExpressionProperty } from "../expression-property"; import { checkWidgetTypeLvglVersion } from "../widget-common"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { getChildOfObject, Message, propertyNotFoundMessage } from "project-editor/store"; import { getComponentName } from "project-editor/flow/components/components-registry"; import { LV_IMAGE_ALIGN, LV_IMG_SIZE_MODE } from "../lvgl-constants"; import type { LVGLCode } from "project-editor/lvgl/to-lvgl-code"; //////////////////////////////////////////////////////////////////////////////// export class LVGLImageWidget extends LVGLWidget { image: string; setPivot: boolean; pivotX: number; pivotY: number; zoom: number; angle: number; innerAlign: keyof typeof LV_IMAGE_ALIGN; sizeMode: keyof typeof LV_IMG_SIZE_MODE; value: number | string; valueType: LVGLPropertyType; previewValue: string; static classInfo = makeDerivedClassInfo(LVGLWidget.classInfo, { enabledInComponentPalette: (projectType: ProjectType) => projectType === ProjectType.LVGL, componentPaletteGroupName: "!1Basic", label: (widget: LVGLImageWidget) => { let name = getComponentName(widget.type); if (widget.identifier) { return `${name} [${widget.identifier}]`; } if (widget.image) { return `${name}: ${widget.image}`; } return name; }, properties: [ { name: "image", type: PropertyType.ObjectReference, referencedObjectCollectionPath: "bitmaps", propertyGridGroup: specificGroup }, { name: "setPivot", displayName: "Change pivot point (default is center)", type: PropertyType.Boolean, propertyGridGroup: specificGroup }, { name: "pivotX", displayName: "Pivot X", type: PropertyType.Number, propertyGridGroup: specificGroup, disabled: (widget: LVGLImageWidget) => !widget.setPivot }, { name: "pivotY", displayName: "Pivot Y", type: PropertyType.Number, propertyGridGroup: specificGroup, disabled: (widget: LVGLImageWidget) => !widget.setPivot }, { name: "zoom", displayName: "Scale", type: PropertyType.Number, propertyGridGroup: specificGroup, hideInPropertyGrid: (widget: LVGLImageWidget) => widget.isScaleNeedle }, { name: "angle", displayName: "Rotation", type: PropertyType.Number, propertyGridGroup: specificGroup, hideInPropertyGrid: (widget: LVGLImageWidget) => widget.isScaleNeedle }, { name: "innerAlign", type: PropertyType.Enum, enumItems: Object.keys(LV_IMAGE_ALIGN).map(id => ({ id, label: id })), enumDisallowUndefined: true, propertyGridGroup: specificGroup, hideInPropertyGrid: (widget: LVGLImageWidget) => widget.isScaleNeedle || ProjectEditor.getProject(widget).settings.general .lvglVersion.startsWith("8.") }, { name: "sizeMode", type: PropertyType.Enum, enumItems: Object.keys(LV_IMG_SIZE_MODE).map(id => ({ id, label: id })), enumDisallowUndefined: true, propertyGridGroup: specificGroup, hideInPropertyGrid: (widget: LVGLImageWidget) => widget.isScaleNeedle || ProjectEditor.getProject(widget).settings.general .lvglVersion.startsWith("9.") }, ...makeLvglExpressionProperty( "value", "integer", "input", ["literal", "expression"], { propertyGridGroup: specificGroup, hideInPropertyGrid: (widget: LVGLImageWidget) => !widget.isScaleNeedle } ), { name: "previewValue", type: PropertyType.String, disabled: (widget: LVGLImageWidget) => { return widget.valueType == "literal"; }, propertyGridGroup: specificGroup, hideInPropertyGrid: (widget: LVGLImageWidget) => !widget.isScaleNeedle } ], defaultValue: { left: 0, top: 0, width: 100, height: 100, widthUnit: "content", heightUnit: "content", setPivot: false, pivotX: 0, pivotY: 0, zoom: 256, angle: 0, value: 0, valueType: "literal", previewValue: 0 }, beforeLoadHook: ( object: LVGLImageWidget, jsObject: Partial ) => { if (jsObject.innerAlign == undefined) { jsObject.innerAlign = "CENTER"; } if (jsObject.sizeMode == undefined) { jsObject.sizeMode = "VIRTUAL"; } if (jsObject.setPivot == undefined) { jsObject.setPivot = true; } }, icon: ( ), check: (widget: LVGLImageWidget, messages: IMessage[]) => { if (widget.image) { const bitmap = findBitmap( ProjectEditor.getProject(widget), widget.image ); if (!bitmap) { messages.push(propertyNotFoundMessage(widget, "image")); } } if (widget.isScaleNeedle) { checkWidgetTypeLvglVersion(widget, messages, "9."); if (widget.valueType == "literal") { if ( widget.value == undefined || widget.value == null || !Number.isInteger(Number(widget.value)) ) { messages.push( new Message( MessageType.ERROR, `Value must be an integer`, getChildOfObject(widget, "value") ) ); } } } }, 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", oldInitFlags: "PRESS_LOCK|ADV_HITTEST|CLICK_FOCUSABLE|GESTURE_BUBBLE|SNAPPABLE|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_CHAIN", oldDefaultFlags: "PRESS_LOCK|CLICK_FOCUSABLE|GESTURE_BUBBLE|SNAPPABLE|SCROLLABLE|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_CHAIN" } }); override makeEditable() { super.makeEditable(); makeObservable(this, { image: observable, setPivot: observable, pivotX: observable, pivotY: observable, zoom: observable, angle: observable, innerAlign: observable, sizeMode: observable, value: observable, valueType: observable, previewValue: observable }); } get isScaleNeedle(): boolean { const parentChildren = getParent(this); const parentWidget = getParent(parentChildren as any); return parentWidget instanceof LVGLScaleWidget; } override toLVGLCode(code: LVGLCode) { if (this.isScaleNeedle && !code.isV9) { // Scale widget doesn't exist in LVGL version 8.x code.createObject("lv_obj_create"); return; } if (this.isScaleNeedle) { code.createObjectWithoutPosAndSize( code.isV9 ? "lv_image_create" : "lv_img_create" ); } else { code.createObject( code.isV9 ? "lv_image_create" : "lv_img_create" ); } const image = code.image(this.image); if (image) { code.callObjectFunction( code.isV9 ? "lv_image_set_src" : "lv_img_set_src", image ); } if (this.setPivot) { code.callObjectFunction( code.isV9 ? "lv_image_set_pivot" : "lv_img_set_pivot", this.pivotX, this.pivotY ); } if (this.isScaleNeedle) { // scale needle mode if (this.valueType == "literal") { code.postWidgetExecute(() => { if (code.lvglBuild) { code.callFreeFunction( "lv_scale_set_image_needle_value", "parent_obj", "obj", this.value ); } else { const parentObj = code.callFreeFunction( "lv_obj_get_parent", code.objectAccessor ); code.callFreeFunction( "lv_scale_set_image_needle_value", parentObj, code.objectAccessor, this.value ); } }); } if (this.valueType == "expression") { if (code.pageRuntime && code.pageRuntime.isEditor) { const previewValue = Number.parseInt(this.previewValue); if (!isNaN(previewValue)) { code.postWidgetExecute(() => { const parentObj = code.callFreeFunction( "lv_obj_get_parent", code.objectAccessor ); code.callFreeFunction( "lv_scale_set_image_needle_value", parentObj, code.objectAccessor, previewValue ); }); } } code.addToTick("value", () => { const new_val = code.evalIntegerProperty( "int32_t", "new_val", this.value as string, "Failed to evaluate Value in Image widget" ); if (code.lvglBuild) { code.callFreeFunction( "lv_scale_set_image_needle_value", `lv_obj_get_parent(${code.objectAccessor})`, code.objectAccessor, new_val ); } else { const parentObj = code.callFreeFunction( "lv_obj_get_parent", code.objectAccessor ); code.callFreeFunction( "lv_scale_set_image_needle_value", parentObj, code.objectAccessor, new_val ); } }); } } else { if (this.zoom != 256) { code.callObjectFunction( code.isV9 ? "lv_image_set_scale" : "lv_img_set_zoom", this.zoom ); } if (this.angle != 0) { code.callObjectFunction( code.isV9 ? "lv_image_set_rotation" : "lv_img_set_angle", this.angle ); } if (code.isV9) { if (this.innerAlign != "CENTER") { code.callObjectFunction( "lv_image_set_inner_align", code.constant(`LV_IMAGE_ALIGN_${this.innerAlign}`) ); } } else { if (this.sizeMode != "VIRTUAL") { code.callObjectFunction( "lv_img_set_size_mode", code.constant(`LV_IMG_SIZE_MODE_${this.sizeMode}`) ); } } } } }