import React from "react"; import { makeObservable, observable } from "mobx"; import { ClassInfo, EezObject, getParent, IMessage, makeDerivedClassInfo, MessageType, PropertyType } from "project-editor/core/object"; import { ProjectType } from "project-editor/project/project"; import { LVGLWidget } from "./internal"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { getChildOfObject, Message, propertyNotSetMessage } from "project-editor/store"; import { specificGroup } from "project-editor/ui-components/PropertyGrid/groups"; import { escapeCString, unescapeCString } from "../widget-common"; import type { LVGLCode } from "project-editor/lvgl/to-lvgl-code"; import { LV_BUTTONMATRIX_CTRL } from "../lvgl-constants"; //////////////////////////////////////////////////////////////////////////////// class LVGLMatrixButton extends EezObject { newLine: boolean; text: string; width: number; ctrlHidden: boolean; ctrlNoRepeat: boolean; ctrlDisabled: boolean; ctrlCheckable: boolean; ctrlChecked: boolean; ctrlClickTrig: boolean; ctrlPopover: boolean; ctrlRecolor: boolean; ctrlCustom1: boolean; ctrlCustom2: boolean; static classInfo: ClassInfo = { properties: [ { name: "newLine", type: PropertyType.Boolean, checkboxStyleSwitch: true }, { name: "text", type: PropertyType.String, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "width", type: PropertyType.Number, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlHidden", displayName: "HIDDEN", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlNoRepeat", displayName: "NO_REPEAT", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlDisabled", displayName: "DISABLED", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlCheckable", displayName: "CHECKABLE", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlChecked", displayName: "CHECKED", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlClickTrig", displayName: "CLICK_TRIG", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlPopover", displayName: "POPOVER", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlRecolor", displayName: "RECOLOR", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine || ProjectEditor.getProject(button).settings.general .lvglVersion.startsWith("9.") }, { name: "ctrlCustom1", displayName: "CUSTOM_1", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine }, { name: "ctrlCustom2", displayName: "CUSTOM_2", type: PropertyType.Boolean, checkboxStyleSwitch: true, hideInPropertyGrid: (button: LVGLMatrixButton) => button.newLine } ], listLabel: (button: LVGLMatrixButton, collapsed: boolean) => { if (button.newLine) { if (collapsed) { return "New line"; } else { return ""; } } let buttonId = 0; const buttons = getParent(button) as LVGLMatrixButton[]; for (const otherButton of buttons) { if (otherButton == button) { break; } if (!otherButton.newLine) { buttonId++; } } if (collapsed) { return ( <> #{buttonId} {button.text} ); } return #{buttonId}; }, defaultValue: { text: "Btn", width: 1 }, check: (button: LVGLMatrixButton, messages: IMessage[]) => { if (!button.newLine) { if (!button.text) { messages.push(propertyNotSetMessage(button, "text")); } if (button.width < 1 || button.width > 7) { messages.push( new Message( MessageType.ERROR, `The width must be in the range of 1..7`, getChildOfObject(button, "width") ) ); } } } }; override makeEditable() { super.makeEditable(); makeObservable(this, { newLine: observable, text: observable, width: observable, ctrlHidden: observable, ctrlNoRepeat: observable, ctrlDisabled: observable, ctrlCheckable: observable, ctrlChecked: observable, ctrlClickTrig: observable, ctrlPopover: observable, ctrlRecolor: observable, ctrlCustom1: observable, ctrlCustom2: observable }); } } export class LVGLButtonMatrixWidget extends LVGLWidget { static classInfo = makeDerivedClassInfo(LVGLWidget.classInfo, { enabledInComponentPalette: (projectType: ProjectType) => projectType === ProjectType.LVGL, componentPaletteGroupName: "!1Basic", properties: [ { name: "buttons", type: PropertyType.Array, typeClass: LVGLMatrixButton, propertyGridGroup: specificGroup, partOfNavigation: false, enumerable: false, defaultValue: [], showArrayCollapsedByDefaultInPropertyGrid: true, hideElementIndexInPropertyGrid: true }, { name: "oneCheck", type: PropertyType.Boolean, propertyGridGroup: specificGroup, checkboxStyleSwitch: true } ], defaultValue: { left: 0, top: 0, width: 240, height: 240, clickableFlag: true, buttons: [], oneCheck: false }, icon: ( ), lvgl: { parts: ["MAIN", "ITEMS"], defaultFlags: "CLICKABLE|CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE" }, overrideEventParamExpressionType: ( widget: LVGLButtonMatrixWidget, eventName: string ) => { // if (eventName == "VALUE_CHANGED") { // return "integer"; // } return undefined; } }); buttons: LVGLMatrixButton[]; oneCheck: boolean; override makeEditable() { super.makeEditable(); makeObservable(this, { buttons: observable, oneCheck: observable }); } override toLVGLCode(code: LVGLCode) { if (code.isV9) { code.createObject("lv_buttonmatrix_create"); } else { code.createObject("lv_btnmatrix_create"); } const buttons = this.buttons ?? []; // buttons map let mapArray; let mapArg; if (code.lvglBuild) { const build = code.lvglBuild; build.blockStart( `static const char *map[${buttons.length + 1}] = {` ); buttons.forEach(button => { build.line( `${escapeCString( button.newLine ? "\n" : button.text ? button.text : " " )},` ); }); build.line(`NULL,`); build.blockEnd(`};`); mapArg = "map"; } else { const runtime = code.pageRuntime!; mapArray = new Uint32Array(buttons.length + 1); for (let i = 0; i < buttons.length; i++) { let button = buttons[i]; mapArray[i] = runtime.wasm.stringToNewUTF8( unescapeCString( button.newLine ? "\n" : button.text ? button.text : " " ) ); } mapArray[buttons.length] = 0; const mapBuffer = runtime.wasm._malloc( mapArray.length * mapArray.BYTES_PER_ELEMENT ); runtime.wasm.HEAPU32.set(mapArray, mapBuffer >> 2); mapArg = mapBuffer; } // buttons ctrl_map let ctrlMapArg; if (code.lvglBuild) { const textButtons = buttons.filter(button => !button.newLine); if ( textButtons.length > 0 && textButtons.find( button => button.width != 1 || button.ctrlHidden || button.ctrlNoRepeat || button.ctrlDisabled || button.ctrlCheckable || button.ctrlChecked || button.ctrlClickTrig || button.ctrlPopover || (code.isV9 ? false : button.ctrlRecolor) || button.ctrlCustom1 || button.ctrlCustom2 ) ) { const build = code.lvglBuild; code.blockStart( `static ${ code.isV9 ? "lv_buttonmatrix_ctrl_t " : "lv_btnmatrix_ctrl_t " }ctrl_map[${textButtons.length}] = {` ); const prefix = code.isV9 ? " | LV_BUTTONMATRIX_CTRL_" : " | LV_BTNMATRIX_CTRL_"; textButtons.forEach(button => { build.line( `${ button.width < 1 ? 1 : button.width > 7 ? 7 : button.width }${button.ctrlHidden ? prefix + "HIDDEN" : ""}${ button.ctrlNoRepeat ? prefix + "NO_REPEAT" : "" }${button.ctrlDisabled ? prefix + "DISABLED" : ""}${ button.ctrlCheckable ? prefix + "CHECKABLE" : "" }${button.ctrlChecked ? prefix + "CHECKED" : ""}${ button.ctrlClickTrig ? prefix + "CLICK_TRIG" : "" }${button.ctrlPopover ? prefix + "POPOVER" : ""}${ !code.isV9 && button.ctrlRecolor ? prefix + "RECOLOR" : "" }${button.ctrlCustom1 ? prefix + "CUSTOM_1" : ""}${ button.ctrlCustom2 ? prefix + "CUSTOM_2" : "" },` ); }); code.blockEnd(`};`); ctrlMapArg = "ctrl_map"; } } else { const runtime = code.pageRuntime!; const textButtons = buttons.filter(button => !button.newLine); let ctrlMapBuffer; if (textButtons.length > 0) { const ctrlMapArray = runtime.isV9 ? new Uint32Array(textButtons.length) : new Uint16Array(textButtons.length); for (let i = 0; i < textButtons.length; i++) { const button = textButtons[i]; let ctrl = button.width; if (ctrl < 1) { ctrl = 1; } if (ctrl > 7) { ctrl = 7; } if (button.ctrlHidden) { ctrl |= LV_BUTTONMATRIX_CTRL.HIDDEN; } if (button.ctrlNoRepeat) { ctrl |= LV_BUTTONMATRIX_CTRL.NO_REPEAT; } if (button.ctrlDisabled) { ctrl |= LV_BUTTONMATRIX_CTRL.DISABLED; } if (button.ctrlCheckable) { ctrl |= LV_BUTTONMATRIX_CTRL.CHECKABLE; } if (button.ctrlChecked) { ctrl |= LV_BUTTONMATRIX_CTRL.CHECKED; } if (button.ctrlClickTrig) { ctrl |= LV_BUTTONMATRIX_CTRL.CLICK_TRIG; } if (button.ctrlPopover) { ctrl |= LV_BUTTONMATRIX_CTRL.POPOVER; } if (!runtime.isV9) { if (button.ctrlRecolor) { ctrl |= LV_BUTTONMATRIX_CTRL.RECOLOR; } } if (button.ctrlCustom1) { ctrl |= LV_BUTTONMATRIX_CTRL.CUSTOM_1; } if (button.ctrlCustom2) { ctrl |= LV_BUTTONMATRIX_CTRL.CUSTOM_2; } ctrlMapArray[i] = ctrl; } ctrlMapBuffer = runtime.wasm._malloc( ctrlMapArray.length * ctrlMapArray.BYTES_PER_ELEMENT ); if (runtime.isV9) { runtime.wasm.HEAPU32.set(ctrlMapArray, ctrlMapBuffer >> 2); } else { runtime.wasm.HEAPU16.set(ctrlMapArray, ctrlMapBuffer >> 1); } } else { ctrlMapBuffer = 0; } ctrlMapArg = ctrlMapBuffer; } if (code.isV9) { code.callObjectFunction("lv_buttonmatrix_set_map", mapArg); if (ctrlMapArg) { code.callObjectFunction( "lv_buttonmatrix_set_ctrl_map", ctrlMapArg ); } if (this.oneCheck) { code.callObjectFunction( "lv_buttonmatrix_set_one_checked", code.constant("true") ); } } else { code.callObjectFunction("lv_btnmatrix_set_map", mapArg); if (ctrlMapArg) { code.callObjectFunction( "lv_btnmatrix_set_ctrl_map", ctrlMapArg ); } if (this.oneCheck) { code.callObjectFunction( "lv_btnmatrix_set_one_checked", code.constant("true") ); } } if (code.pageRuntime) { const runtime = code.pageRuntime!; runtime.addButtonMatrixBuffers(mapArg as number, mapArray! , ctrlMapArg as number); } } }