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