import React from "react"; import { observable, makeObservable } from "mobx"; import { PropertyType, makeDerivedClassInfo, MessageType, IMessage } from "project-editor/core/object"; import { getChildOfObject, Message } from "project-editor/store"; import { ProjectType } from "project-editor/project/project"; import { specificGroup } from "project-editor/ui-components/PropertyGrid/groups"; import { LV_DIR_BOTTOM, LV_DIR_LEFT, LV_DIR_RIGHT, LV_DIR_TOP } from "project-editor/lvgl/lvgl-constants"; import { LVGLWidget, LVGLTabWidget, LVGLContainerWidget } from "./internal"; import { LVGLPropertyType, makeLvglExpressionProperty } from "../expression-property"; import type { LVGLCode } from "project-editor/lvgl/to-lvgl-code"; //////////////////////////////////////////////////////////////////////////////// const TABVIEW_POSITION = { LEFT: LV_DIR_LEFT, RIGHT: LV_DIR_RIGHT, TOP: LV_DIR_TOP, BOTTOM: LV_DIR_BOTTOM }; export class LVGLTabviewWidget extends LVGLWidget { static classInfo = makeDerivedClassInfo(LVGLWidget.classInfo, { enabledInComponentPalette: (projectType: ProjectType) => projectType === ProjectType.LVGL, componentPaletteGroupName: "!1Basic", properties: [ { name: "tabviewPosition", displayName: "Position", type: PropertyType.Enum, enumItems: Object.keys(TABVIEW_POSITION).map(id => ({ id, label: id })), enumDisallowUndefined: true, propertyGridGroup: specificGroup }, { name: "tabviewSize", displayName: "Size", type: PropertyType.Number, propertyGridGroup: specificGroup }, ...makeLvglExpressionProperty( "selectedTab", "integer", "assignable", ["literal", "expression"], { propertyGridGroup: specificGroup, displayName: "Active tab" } ) ], defaultValue: { left: 0, top: 0, width: 180, height: 100, clickableFlag: true, tabviewPosition: "TOP", tabviewSize: 32, selectedTab: 0, selectedTabType: "literal" }, beforeLoadHook: ( object: LVGLTabviewWidget, jsObject: Partial ) => { if (jsObject.selectedTab == undefined) { jsObject.selectedTab = "0"; } if (jsObject.selectedTabType == undefined) { jsObject.selectedTabType = "literal"; } }, check: (widget: LVGLTabviewWidget, messages: IMessage[]) => { let tabBar = false; let tabContent = false; for (let i = 0; i < widget.children.length; i++) { const childWidget = widget.children[i]; if (childWidget instanceof LVGLContainerWidget) { if (i == 0) { tabBar = true; } else if (i == 1) { tabContent = true; } else { if (tabBar && tabContent) { messages.push( new Message( MessageType.ERROR, `Redundant Container widget`, childWidget ) ); } else { messages.push( new Message( MessageType.ERROR, `Invalid Container widget position`, childWidget ) ); } } } else if (childWidget instanceof LVGLTabWidget) { if (tabContent) { messages.push( new Message( MessageType.ERROR, `Tab should be child of Content container`, childWidget ) ); } } else { messages.push( new Message( MessageType.ERROR, `Tabview child is neither Tab or Container widget`, childWidget ) ); } } if (widget.selectedTabType == "literal") { if ( widget.selectedTab == undefined || widget.selectedTab == null || !Number.isInteger(Number(widget.selectedTab)) ) { messages.push( new Message( MessageType.ERROR, `Min value must be an integer`, getChildOfObject(widget, "selectedTab") ) ); } } }, icon: ( ), lvgl: { parts: ["MAIN"], defaultFlags: "CLICKABLE|CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", oldInitFlags: "PRESS_LOCK|CLICK_FOCUSABLE|GESTURE_BUBBLE|SNAPPABLE|SCROLLABLE|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_CHAIN", oldDefaultFlags: "CLICKABLE|PRESS_LOCK|CLICK_FOCUSABLE|GESTURE_BUBBLE|SNAPPABLE|SCROLLABLE|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_CHAIN" } }); tabviewPosition: keyof typeof TABVIEW_POSITION; tabviewSize: number; selectedTab: number | string; selectedTabType: LVGLPropertyType; _selectedTabIndex: number = 0; get selectedTabIndex() { let numTabs = 0; for (const child of this.children) { if (child instanceof LVGLTabWidget) { numTabs++; } else { for (const child2 of child.children) { if (child2 instanceof LVGLTabWidget) { numTabs++; } } } } if (this._selectedTabIndex < numTabs) { return this._selectedTabIndex; } if (this._selectedTabIndex - 1 > 0) { return this._selectedTabIndex - 1; } return 0; } override makeEditable() { super.makeEditable(); makeObservable(this, { tabviewPosition: observable, tabviewSize: observable, selectedTab: observable, selectedTabType: observable, _selectedTabIndex: observable }); } override toLVGLCode(code: LVGLCode) { const position = this.tabviewPosition ?? "TOP"; const size = this.tabviewSize ?? 32; if (code.isV9) { code.createObject("lv_tabview_create"); code.callObjectFunction( "lv_tabview_set_tab_bar_position", code.constant(`LV_DIR_${position}`) ); code.callObjectFunction("lv_tabview_set_tab_bar_size", size); } else { code.createObject( "lv_tabview_create", code.constant(`LV_DIR_${position}`), size ); } if (code.pageRuntime && code.pageRuntime.isEditor) { const selectedTabIndex = this.selectedTabIndex; code.postPageExecute(() => { if (code.isV9) { code.callObjectFunction( "lv_tabview_set_active", selectedTabIndex, code.constant("LV_ANIM_OFF") ); } else { code.callObjectFunction( "lv_tabview_set_act", selectedTabIndex, code.constant("LV_ANIM_OFF") ); } }); } else { if (this.selectedTabType == "literal") { if (this.selectedTab != 0) { if (code.isV9) { code.callObjectFunction( "lv_tabview_set_active", this.selectedTab, code.constant("LV_ANIM_OFF") ); } else { code.callObjectFunction( "lv_tabview_set_act", this.selectedTab, code.constant("LV_ANIM_OFF") ); } } } else { code.addToTick("selectedTab", () => { const new_val = code.evalIntegerProperty( "int32_t", "new_val", this.selectedTab as string, "Failed to evaluate Active tab in Tabview widget" ); const cur_val = code.callObjectFunctionWithAssignment( "int32_t", "cur_val", code.isV9 ? "lv_tabview_get_tab_active" : "lv_tabview_get_tab_act" ); code.ifNotEqual(new_val, cur_val, () => { code.tickChangeStart(); if (code.isV9) { code.callObjectFunction( "lv_tabview_set_active", new_val, code.constant("LV_ANIM_OFF") ); } else { code.callObjectFunction( "lv_tabview_set_act", new_val, code.constant("LV_ANIM_OFF") ); } code.tickChangeEnd(); }); }); code.addEventHandler( "VALUE_CHANGED", (event, tick_value_change_obj) => { const ta = code.callFreeFunctionWithAssignment( "lv_obj_t *", "ta", code.lv_event_get_target, event ); code.ifNotEqual(tick_value_change_obj, ta, () => { const value = code.callFreeFunctionWithAssignment( "int32_t", "value", code.isV9 ? "lv_tabview_get_tab_active" : "lv_tabview_get_tab_act", ta ); code.assignIntegerProperty( "selectedTab", this.selectedTab as string, value, "Failed to assign Active tab in Tabview widget" ); }); } ); } } } }