import fs from "fs"; import { resolve } from "path"; import { NamingConvention, getName, Build, USER_WIDGET_IDENTIFIER_SEPARATOR } from "project-editor/build/helper"; import type { Bitmap } from "project-editor/features/bitmap/bitmap"; import type { Font } from "project-editor/features/font/font"; import { Page } from "project-editor/features/page/page"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { Project } from "project-editor/project/project"; import { Section, getAncestorOfType } from "project-editor/store"; import type { LVGLUserWidgetWidget, LVGLWidget } from "./widgets"; import type { Assets } from "project-editor/build/assets"; import { isDev } from "eez-studio-shared/util-electron"; import { getColorRGB } from "eez-studio-shared/color"; import { writeTextFile, writeBinaryData } from "project-editor/build/build"; import type { LVGLStyle } from "project-editor/lvgl/style"; import { isEnumType, getEnumTypeNameFromType } from "project-editor/features/variable/value-type"; import { IEezObject, MessageType } from "project-editor/core/object"; import { getLvglBitmapSourceFile, getLvglStylePropName } from "project-editor/lvgl/lvgl-versions"; import { sourceRootDir } from "eez-studio-shared/util"; import { getSelectorBuildCode } from "project-editor/lvgl/style-helper"; import type { LVGLGroup } from "./groups"; import { GENERATED_NAME_PREFIX } from "./identifiers"; import { escapeCString, isGeometryControlledByParent } from "./widget-common"; import { BuildLVGLCode } from "project-editor/lvgl/to-lvgl-code"; import { cleanupSourceFile } from "project-editor/build/cleanup-c-source-files"; import { BUILT_IN_FONTS } from "project-editor/lvgl/style-catalog"; import { visitObjects } from "project-editor/core/search"; import { ColorFormat, ColorFormatType } from "project-editor/features/style/color-format"; interface Identifiers { identifiers: string[]; widgetToIdentifier: Map; widgetToAccessor: Map; widgetToIndex: Map; } interface StateVar { id: string; varType: string; varName: string; userWidgetPage: Page | undefined; } interface UpdateColorCallback { object: IEezObject; callback: () => void; } interface UpdateColorCallbackForPage { page: Page; updateColorCallbacks: UpdateColorCallback[]; updateColorCallbackForUserWidgets: UpdateColorCallbackForUserWidget[]; } interface UpdateColorCallbackForUserWidget { lvglUserWidget: LVGLUserWidgetWidget; updateColorsForPage: UpdateColorCallbackForPage; } export class LVGLBuild extends Build { project: Project; toLVGLCode = new BuildLVGLCode(this); styleNames = new Map(); fontNames = new Map(); bitmapNames = new Map(); isFirstPass: boolean; currentPage: Page; lvglObjectsAccessibleFromSourceCode: { fromPage: LVGLWidget[]; fromUserWidgets: Map; } = { fromPage: [], // all pages share the same Set fromUserWidgets: new Map() // different Set for each user widget }; lvglObjectIdentifiers: { fromPage: Identifiers; fromUserWidgets: Map; } = { fromPage: { identifiers: [], widgetToIdentifier: new Map(), widgetToAccessor: new Map(), widgetToIndex: new Map() }, fromUserWidgets: new Map() }; updateColorCallbacks: UpdateColorCallback[] = []; stateVars: Map = new Map(); pagesWithStateVars: Page[] = []; globalVars: { id: string; varType: string; varName: string; isStatic: boolean; }[] = []; objectAccessors: string[] | undefined; tickCallbacks: (() => void)[]; eventHandlers = new Map< LVGLWidget, { eventName: string; callback: () => void }[] >(); postBuildCallbacks: (() => void)[] = []; functions: { [key: string]: { callback: () => void; decl?: string; }; } = {}; animations: Map = new Map(); constructor(public assets: Assets) { super(); this.project = assets.projectStore.project; this.buildStyleNames(); this.buildFontNames(); this.buildBitmapNames(); } async firtsPassStart() { // PASS 1 (find out which LVGL objects are accessible through global objects structure) this.isFirstPass = true; for (const page of this.pages) { if (!page.isUsedAsUserWidget) { this.markObjectAccessibleFromSourceCode(page.lvglScreenWidget!); } } } async firstPassFinish() { await this.buildScreensDef(); const stateVars = new Map(this.stateVars); for (const [key, value] of stateVars) { stateVars.set(key, value.slice()); } const globalVars = this.globalVars.slice(); const objectAccessors = this.objectAccessors?.slice(); const tickCallbacks = this.tickCallbacks.slice(); const eventHandlers = new Map(this.eventHandlers); for (const [key, value] of eventHandlers) { eventHandlers.set(key, value.slice()); } const postBuildCallbacks = this.postBuildCallbacks.slice(); const functions = { ...this.functions }; await this.buildScreensDef(); this.stateVars = stateVars; this.globalVars = globalVars; this.finishStateVars(); this.objectAccessors = objectAccessors; this.tickCallbacks = tickCallbacks; this.eventHandlers = eventHandlers; this.postBuildCallbacks = postBuildCallbacks; this.functions = functions; await this.buildStylesDef(); this.finalizeObjectAccessibleFromSourceCodeTable(); this.isFirstPass = false; this.updateColorCallbacks = []; } markObjectAccessibleFromSourceCode(widget: LVGLWidget) { const page = ProjectEditor.getPage(widget); if (page.isUsedAsUserWidget) { let widgets = this.lvglObjectsAccessibleFromSourceCode.fromUserWidgets.get( page ); if (!widgets) { widgets = []; this.lvglObjectsAccessibleFromSourceCode.fromUserWidgets.set( page, widgets ); } if (!widgets.includes(widget)) { widgets.push(widget); } } else { if ( !this.lvglObjectsAccessibleFromSourceCode.fromPage.includes( widget ) ) { this.lvglObjectsAccessibleFromSourceCode.fromPage.push(widget); } } } finalizeObjectAccessibleFromSourceCodeTable() { let genIndex = 0; function generateUniqueObjectName() { return GENERATED_NAME_PREFIX + genIndex++; } const addPageIdentifiers = ( widgets: LVGLWidget[], pageIdentifiers: Identifiers, prefix: string, isUserWidget: boolean ) => { let startIndex = isUserWidget ? pageIdentifiers.identifiers.length : 0; for (const widget of widgets) { let identifier; if (widget.identifier) { identifier = getName( "", widget.identifier, NamingConvention.UnderscoreLowerCase ); } else { identifier = this.assets.map.lvglWidgetGeneratedIdentifiers[ widget.objID ]; if (!identifier) { identifier = generateUniqueObjectName(); this.assets.map.lvglWidgetGeneratedIdentifiers[ widget.objID ] = identifier; } } pageIdentifiers.widgetToIdentifier.set( widget, prefix + identifier ); pageIdentifiers.widgetToAccessor.set( widget, isUserWidget ? `((lv_obj_t **)&objects)[startWidgetIndex + ${ pageIdentifiers.identifiers.length - startIndex }]` : `objects.${prefix + identifier}` ); pageIdentifiers.widgetToIndex.set( widget, pageIdentifiers.identifiers.length - startIndex ); pageIdentifiers.identifiers.push(prefix + identifier); if (widget instanceof ProjectEditor.LVGLUserWidgetWidgetClass) { const page = widget.userWidgetPage; if (page) { addIdentifiersForUserWidget( prefix + identifier + USER_WIDGET_IDENTIFIER_SEPARATOR, page, pageIdentifiers ); } } } }; const addIdentifiersForUserWidget = ( prefix: string, page: Page, pageIdentifiers: Identifiers ) => { let savedGenIndex = genIndex; genIndex = 0; const widgets = this.lvglObjectsAccessibleFromSourceCode.fromUserWidgets.get( page ); if (widgets) { addPageIdentifiers(widgets, pageIdentifiers, prefix, true); } genIndex = savedGenIndex; }; for (const page of this.pages) { if (!page.isUsedAsUserWidget) { const identifier = getName( "", page.name, NamingConvention.UnderscoreLowerCase ); this.lvglObjectIdentifiers.fromPage.widgetToIdentifier.set( page.lvglScreenWidget!, identifier ); this.lvglObjectIdentifiers.fromPage.widgetToAccessor.set( page.lvglScreenWidget!, `objects.${identifier}` ); this.lvglObjectIdentifiers.fromPage.widgetToIndex.set( page.lvglScreenWidget!, this.lvglObjectIdentifiers.fromPage.identifiers.length ); this.lvglObjectIdentifiers.fromPage.identifiers.push( identifier ); } else { const widgets = this.lvglObjectsAccessibleFromSourceCode.fromUserWidgets.get( page ) ?? []; let pageIdentifiers: Identifiers = { identifiers: [], widgetToIdentifier: new Map(), widgetToAccessor: new Map(), widgetToIndex: new Map() }; addPageIdentifiers(widgets, pageIdentifiers, "", true); this.lvglObjectIdentifiers.fromUserWidgets.set( page, pageIdentifiers ); } } genIndex = 0; const widgets = this.lvglObjectsAccessibleFromSourceCode.fromPage.filter( widget => !this.lvglObjectIdentifiers.fromPage.widgetToIdentifier.get( widget ) ); addPageIdentifiers( widgets, this.lvglObjectIdentifiers.fromPage, "", false ); } isAccessibleFromSourceCode(widget: LVGLWidget) { if (widget.identifier) { return true; } let page = ProjectEditor.getPage(widget); if (page.isUsedAsUserWidget) { return ( this.lvglObjectsAccessibleFromSourceCode.fromUserWidgets .get(page) ?.includes(widget) ?? false ); } return this.lvglObjectsAccessibleFromSourceCode.fromPage.includes( widget ); } get pages() { return this.project._store.lvglIdentifiers.pages; } get userPages() { return this.project._store.lvglIdentifiers.userPages; } get styles() { return this.project._store.lvglIdentifiers.styles; } get fonts() { return this.project._store.lvglIdentifiers.fonts; } get bitmaps() { return this.project._store.lvglIdentifiers.bitmaps; } get isV9() { return this.project.settings.general.lvglVersion.startsWith("9."); } isLVGLVersion(prefixes: string[]): boolean { for (const prefix of prefixes) { if (this.project.settings.general.lvglVersion.startsWith(prefix)) { return true; } } return false; } getStylePropName(stylePropName: string) { return getLvglStylePropName(this.project, stylePropName); } buildStyleNames() { const names = new Set(); for (const style of this.styles) { let name = getName("", style, NamingConvention.UnderscoreLowerCase); // make sure that name is unique if (names.has(name)) { for (let i = 1; ; i++) { const newName = name + i.toString(); if (!names.has(newName)) { name = newName; break; } } } this.styleNames.set(style.objID, name); names.add(name); } } buildFontNames() { const names = new Set(); for (const font of this.fonts) { let name = getName("", font, NamingConvention.UnderscoreLowerCase); // make sure that name is unique if (names.has(name)) { for (let i = 1; ; i++) { const newName = name + i.toString(); if (!names.has(newName)) { name = newName; break; } } } this.fontNames.set(font.objID, name); names.add(name); } } buildBitmapNames() { const names = new Set(); for (const bitmap of this.bitmaps) { let name = getName( "", bitmap, NamingConvention.UnderscoreLowerCase ); // make sure that name is unique if (names.has(name)) { for (let i = 1; ; i++) { const newName = name + i.toString(); if (!names.has(newName)) { name = newName; break; } } } this.bitmapNames.set(bitmap.objID, name); names.add(name); } } getScreenIdentifier(page: Page) { return getName("", page, NamingConvention.UnderscoreLowerCase); } getScreenCreateFunctionName(page: Page) { return page.isUsedAsUserWidget ? `create_user_widget_${this.getScreenIdentifier(page)}` : `create_screen_${this.getScreenIdentifier(page)}`; } getScreenDeleteFunctionName(page: Page) { return `delete_screen_${this.getScreenIdentifier(page)}`; } getScreenTickFunctionName(page: Page) { return page.isUsedAsUserWidget ? `tick_user_widget_${this.getScreenIdentifier(page)}` : `tick_screen_${this.getScreenIdentifier(page)}`; } getActionFunctionName(actionName: string) { return getName( "action_", actionName, NamingConvention.UnderscoreLowerCase ); } getVariableGetterFunctionName(variableName: string) { return getName( "get_var_", variableName, NamingConvention.UnderscoreLowerCase ); } getVariableSetterFunctionName(variableName: string) { return getName( "set_var_", variableName, NamingConvention.UnderscoreLowerCase ); } getPageIdentifiers(object: IEezObject) { const flow = ProjectEditor.getFlow(object); if ( flow instanceof ProjectEditor.PageClass && flow.isUsedAsUserWidget ) { const pageIdentifiers = this.lvglObjectIdentifiers.fromUserWidgets.get(flow); if (!pageIdentifiers) { this.assets.projectStore.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, "Page identifiers not found", object ); } return pageIdentifiers; } return this.lvglObjectIdentifiers.fromPage; } getLvglObjectIdentifierInSourceCode(widget: LVGLWidget) { if (this.isFirstPass) { this.markObjectAccessibleFromSourceCode(widget); return ""; } const pageIdentifiers = this.getPageIdentifiers(widget); if (!pageIdentifiers) { return ""; } const identifier = pageIdentifiers.widgetToIdentifier.get(widget); if (identifier == undefined) { this.assets.projectStore.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, `Widget identifier not found`, widget ); return ""; } return identifier; } getWidgetObjectIndex(widget: LVGLWidget) { if (this.isFirstPass) { this.markObjectAccessibleFromSourceCode(widget); return 0; } const pageIdentifiers = this.getPageIdentifiers(widget); if (!pageIdentifiers) { return 0; } const index = pageIdentifiers.widgetToIndex.get(widget); if (index == undefined) { this.assets.projectStore.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, `Widget index not found`, widget ); return 0; } return index; } getWidgetObjectIndexByName(fromObject: IEezObject, objectName: string) { if (this.isFirstPass) { return 0; } const pageIdentifiers = this.getPageIdentifiers(fromObject); if (!pageIdentifiers) { return 0; } const index = pageIdentifiers.identifiers.indexOf(objectName); if (index == -1) { if (!this.isFirstPass) { this.assets.projectStore.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, `Widget index not found for "${objectName}"`, fromObject ); return 0; } } return index; } getLvglObjectAccessor(widget: LVGLWidget) { if (this.isFirstPass) { this.markObjectAccessibleFromSourceCode(widget); return ""; } const pageIdentifiers = this.getPageIdentifiers(widget); if (!pageIdentifiers) { return "0"; } const accessor = pageIdentifiers.widgetToAccessor.get(widget); if (accessor == undefined) { this.assets.projectStore.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, `Widget accessor not found`, widget ); return ""; } return accessor; } getLvglWidgetAccessorInEventHandler(widgetPath: LVGLWidget[]) { return ( "objects." + widgetPath .map(widget => this.getLvglObjectIdentifierInSourceCode(widget)) .join(USER_WIDGET_IDENTIFIER_SEPARATOR) ); } getEventHandlerCallbackName(widget: LVGLWidget) { const page = ProjectEditor.getPage(widget); return `event_handler_cb_${this.getScreenIdentifier(page)}_${this.getLvglObjectIdentifierInSourceCode(widget)}`; } getCheckedEventHandlerCallbackName(widget: LVGLWidget) { const page = ProjectEditor.getPage(widget); return `event_handler_checked_cb_${this.getScreenIdentifier( page )}_${this.getLvglObjectIdentifierInSourceCode(widget)}`; } getUncheckedEventHandlerCallbackName(widget: LVGLWidget) { const page = ProjectEditor.getPage(widget); return `event_handler_unchecked_cb_${this.getScreenIdentifier( page )}_${this.getLvglObjectIdentifierInSourceCode(widget)}`; } getImageVariableName(bitmap: Bitmap | string) { const IMAGE_PREFIX = "img_"; if (typeof bitmap == "string") { const bitmapobject = this.bitmaps.find( bitmapobject => bitmapobject.name == bitmap ); if (bitmapobject) { this.assets.markBitmapUsed(bitmapobject); } return getName( IMAGE_PREFIX, bitmap, NamingConvention.UnderscoreLowerCase ); } else { this.assets.markBitmapUsed(bitmap); return IMAGE_PREFIX + this.bitmapNames.get(bitmap.objID)!; } } getImageAccessor(bitmap: Bitmap | string) { if (this.project.settings.build.imageExportMode == "binary") { let foundBitmap: Bitmap | undefined; if (typeof bitmap == "string") { foundBitmap = this.bitmaps.find( bitmapobject => bitmapobject.name == bitmap ); } else { foundBitmap = bitmap; } if (foundBitmap) { this.assets.markBitmapUsed(foundBitmap); } let path = this.project.settings.build.fileSystemPath; if (!path.endsWith("/") && !path.endsWith("\\")) { if (path.indexOf("\\") != -1) path += "\\"; else path += "/"; } const output = "ui_image_" + (foundBitmap ? this.bitmapNames.get(foundBitmap.objID)! : bitmap); return escapeCString(`${path}${output}.bin`); } else { return `&${this.getImageVariableName(bitmap)}`; } } getFontVariableName(font: Font) { this.assets.markFontUsed(font); return "ui_font_" + this.fontNames.get(font.objID)!; } getFontAccessor(font: Font) { const variableName = this.getFontVariableName(font); if ( font.lvglUseFreeType || this.project.settings.build.fontExportMode == "binary" ) { return variableName; } return `&${variableName}`; } getAddStyleFunctionName(style: LVGLStyle) { return "add_style_" + this.styleNames.get(style.objID)!; } getRemoveStyleFunctionName(style: LVGLStyle) { return "remove_style_" + this.styleNames.get(style.objID)!; } getInitStyleFunctionName(style: LVGLStyle, part: string, state: string) { return ( "init_style_" + this.styleNames.get(style.objID)! + "_" + part + "_" + (state == "CHECKED|PRESSED" ? "CHECKED_PRESSED" : state) ); } getGetStyleFunctionName(style: LVGLStyle, part: string, state: string) { return ( "get_style_" + this.styleNames.get(style.objID)! + "_" + part + "_" + (state == "CHECKED|PRESSED" ? "CHECKED_PRESSED" : state) ); } getGroupVariableName(group: LVGLGroup) { return `groups.${group.name}`; } getColorAccessor(color: string, themeIndex: string) { const cf = ColorFormat.parse(color, this.project); if (cf.formatType == ColorFormatType.THEME_NAME) { const colorIndex = this.project.colorToIndexMap.get(cf.name); if (colorIndex != undefined) { return { colorAccessor: `lv_color_hex(theme_colors[${themeIndex}][${colorIndex}])`, fromTheme: true }; } } else if (cf.formatType == ColorFormatType.DARKEN || cf.formatType == ColorFormatType.LIGHTEN) { let innerColor; let fromTheme; if (cf.innerColor!.formatType == ColorFormatType.THEME_NAME) { const colorIndex = this.project.colorToIndexMap.get(cf.innerColor!.name); if (colorIndex != undefined) { innerColor = `theme_colors[${themeIndex}][${colorIndex}]`; fromTheme = true; } } if (innerColor == undefined) { innerColor = cf.innerColor!.getHexNumString(); fromTheme = false; } let colorAccessor; let level = cf.levelFormat == "decimal" ? cf.level : Math.min(Math.max(Math.round(cf.level * 255 / 100), 0), 255); if (cf.formatType == ColorFormatType.DARKEN) { colorAccessor = `lv_color_darken(lv_color_hex(${innerColor}), ${level})`; } else { colorAccessor = `lv_color_lighten(lv_color_hex(${innerColor}), ${level})`; } return { colorAccessor, fromTheme } } return { colorAccessor: `lv_color_hex(${cf.getHexNumString()})`, fromTheme: false }; } getColorHexStr(colorValue: string) { const rgb = getColorRGB(colorValue); // result is in BGR format let colorNum = (rgb.b << 0) | (rgb.g << 8) | (rgb.r << 16) | (255 << 24); // signed to unsigned colorNum = colorNum >>> 0; return "0x" + colorNum.toString(16).padStart(8, "0"); } assignToObjectsStruct(objectAccessor: string) { if (this.objectAccessors) { this.objectAccessors.push(objectAccessor); } this.line(`${objectAccessor} = obj;`); } buildColor( object: IEezObject, color: string, getParams: () => T, callback: (color: string, params: T) => void, updateCallback: (color: string, params: T) => void ) { const { colorAccessor, fromTheme } = this.getColorAccessor( color, this.project.projectTypeTraits.hasFlowSupport ? "eez_flow_get_selected_theme_index()" : "active_theme_index" ); const params = getParams(); callback(colorAccessor, params); if (!this.isFirstPass && fromTheme) { this.updateColorCallbacks.push({ object, callback: () => { const { colorAccessor } = this.getColorAccessor( color, "theme_index" ); updateCallback(colorAccessor, params); } }); } } buildColor2( object: IEezObject, color1: string, color2: string, getParams: () => T, callback: (color1: string, color2: string, params: T) => void, updateCallback: (color1: string, color2: string, params: T) => void ) { const { colorAccessor: color1Accessor, fromTheme: color1FromTheme } = this.getColorAccessor( color1, this.project.projectTypeTraits.hasFlowSupport ? "eez_flow_get_selected_theme_index()" : "active_theme_index" ); const { colorAccessor: color2Accessor, fromTheme: color2FromTheme } = this.getColorAccessor( color2, this.project.projectTypeTraits.hasFlowSupport ? "eez_flow_get_selected_theme_index()" : "active_theme_index" ); const params = getParams(); callback(color1Accessor, color2Accessor, params); if (!this.isFirstPass && (color1FromTheme || color2FromTheme)) { this.updateColorCallbacks.push({ object, callback: () => { const { colorAccessor: color1Accessor } = this.getColorAccessor(color1, "theme_index"); const { colorAccessor: color2Accessor } = this.getColorAccessor(color2, "theme_index"); updateCallback(color1Accessor, color2Accessor, params); } }); } } genStateVar(id: string, type: string, prefixName: string) { let pageStateVars = this.stateVars.get(this.currentPage); if (!pageStateVars) { pageStateVars = []; this.stateVars.set(this.currentPage, pageStateVars); } let stateVar = pageStateVars.find(stateVar => stateVar.id == id); if (!stateVar) { let varName: string; if (prefixName.endsWith("!")) { varName = prefixName.slice(0, -1); } else { varName = prefixName; let suffix = 0; while ( pageStateVars.find( pageStateVar => pageStateVar.varName == varName ) ) { suffix += 1; varName = prefixName + suffix; } } stateVar = { id, varType: type, varName, userWidgetPage: undefined }; pageStateVars.push(stateVar); } return `state->${stateVar.varName}`; } assingToStateVar(varName: string, value: string) { this.line(`${varName} = ${value};`); } getStateStructName(page: Page) { const userWidgetPageName = getName( "", page.name, NamingConvention.UnderscoreLowerCase ); return `${page.isUsedAsUserWidget ? "user_widget" : "screen"}_${userWidgetPageName}_state_t`; } getScreenStateVarName(page: Page) { const userWidgetPageName = getName( "", page.name, NamingConvention.UnderscoreLowerCase ); return `screen_${userWidgetPageName}_state`; } getUserWidgetStateVarName(userWidget: LVGLUserWidgetWidget) { const pageStateVars = this.stateVars.get(this.currentPage); if (!pageStateVars) { return ""; } const stateVar = pageStateVars.find( stateVar => stateVar.id == userWidget.objID ); if (!stateVar) { return ""; } return stateVar.varName; } getUserWidgetStateParam(userWidget: LVGLUserWidgetWidget) { const userWidgetStateVarName = this.getUserWidgetStateVarName(userWidget); if (!userWidgetStateVarName) { return ""; } return `, &state->${userWidgetStateVarName}`; } // Ensure that state vars are created in correct order. // A page that is used as user widget in another page // comes before that page. // This is needed to be able to pass state vars as parameters. // Also ensures that state vars are created for all user widgets used in a page. // If a page has state vars, it is added to pagesWithStateVars array. finishStateVars() { const pages: Page[] = []; // ensure that pages are in correct order (a page that is used as user widget in another page // comes before that page) function insertPage(page: Page, usedInPage?: Page) { // find page index let pageIndex = pages.findIndex(p => p == page); if (pageIndex == -1) { // not found, insert at the end pageIndex = pages.length; pages.push(page); } if (!usedInPage) { return; } // find usedInPage index let usedInPageIndex = pages.findIndex(p => p == usedInPage); if (pageIndex < usedInPageIndex) { // already in correct order return; } // remove page from current position pages.splice(pageIndex, 1); // insert before usedInPage pages.splice(usedInPageIndex, 0, page); } const userWidgetHasState = (page: Page) => { if (this.stateVars.get(page)) { return true; } for (const widget of visitObjects(page)) { if (widget instanceof ProjectEditor.LVGLUserWidgetWidgetClass) { if ( widget.userWidgetPage && userWidgetHasState(widget.userWidgetPage) ) { return true; } } } return false; }; let done = false; while (!done) { done = true; for (const page of this.pages) { let pageStateVars = this.stateVars.get(page); if (pageStateVars) { insertPage(page); } for (const widget of visitObjects(page)) { if ( widget instanceof ProjectEditor.LVGLUserWidgetWidgetClass ) { if ( widget.userWidgetPage && userWidgetHasState(widget.userWidgetPage) ) { const userWidgetPageName = getName( "", widget.userWidgetPage.name, NamingConvention.UnderscoreLowerCase ); if (!pageStateVars) { pageStateVars = []; this.stateVars.set(page, pageStateVars); insertPage(page); done = false; } let id = widget.objID; let stateVar = pageStateVars.find( stateVar => stateVar.id == id ); if (!stateVar) { // create state var for user widget let varName: string; if (widget.identifier) { varName = getName( "", widget.identifier, NamingConvention.UnderscoreLowerCase ); } else { let suffix = 1; varName = `${userWidgetPageName}${suffix}_state`; while ( pageStateVars.find( pageStateVar => pageStateVar.varName == varName ) ) { suffix += 1; varName = `${userWidgetPageName}${suffix}_state`; } } pageStateVars.push({ id, varType: this.getStateStructName( widget.userWidgetPage ) + " ", varName, userWidgetPage: widget.userWidgetPage }); insertPage(widget.userWidgetPage, page); } } } } } } this.pagesWithStateVars = pages; } declareGlobalVar( id: string, varType: string, prefixName: string, isStatic: boolean ) { let globalVar = this.globalVars.find(globalVar => globalVar.id == id); if (globalVar) { return globalVar.varName; } let varName: string; if (prefixName.endsWith("!")) { varName = prefixName.slice(0, -1); } else { varName = prefixName; let suffix = 0; while ( this.globalVars.find(globalVar => globalVar.varName == varName) ) { suffix += 1; varName = prefixName + suffix; } } this.globalVars.push({ id, varType, varName, isStatic }); return varName; } addTickCallback(callback: () => void) { this.tickCallbacks.push(callback); } addEventHandler( widget: LVGLWidget, eventName: string, callback: () => void ) { let eventHandlers = this.eventHandlers.get(widget); if (!eventHandlers) { eventHandlers = []; this.eventHandlers.set(widget, eventHandlers); } eventHandlers.push({ eventName, callback }); } buildWidgetAssign(widget: LVGLWidget) { const build = this; if (build.isAccessibleFromSourceCode(widget)) { build.assignToObjectsStruct(build.getLvglObjectAccessor(widget)); } } buildWidgetSetPosAndSize(widget: LVGLWidget) { const build = this; if (widget instanceof ProjectEditor.LVGLScreenWidgetClass) { const page = ProjectEditor.getPage(widget); build.line(`lv_obj_set_pos(obj, ${page.left}, ${page.top});`); build.line(`lv_obj_set_size(obj, ${page.width}, ${page.height});`); } else if (isGeometryControlledByParent(widget)) { // skip } else { let rect = widget.getLvglBuildRect(); build.line(`lv_obj_set_pos(obj, ${rect.left}, ${rect.top});`); build.line(`lv_obj_set_size(obj, ${rect.width}, ${rect.height});`); } } postBuildStart() { this.postBuildCallbacks = []; } postBuildAdd(callback: () => void) { this.postBuildCallbacks.push(callback); } postBuildEnd() { for (const callback of this.postBuildCallbacks) { callback(); } this.postBuildCallbacks = []; } addFunction(name: string, callback: () => void, decl?: string) { this.functions[name] = { callback, decl }; } createAnimation( setDelay: boolean, setRepeatDelay: boolean, setRepeatCount: boolean, delay: number, repeatDelay: number, repeatCount: number ): string { // Create a unique key based on parameters const key = `${setDelay ? delay : "_"}_${setRepeatDelay ? repeatDelay : "_"}_${setRepeatCount ? repeatCount : "_"}`; // Check if animation with same parameters already exists let funcName = this.animations.get(key); if (funcName) { return `${funcName}()`; } // Generate unique function name const animIndex = this.animations.size; // Declare global variables for the animation const animVar = this.declareGlobalVar( `anim_${animIndex}`, "lv_anim_t", `anim`, true ); const animInitializedVar = this.declareGlobalVar( `anim_${animIndex}_initialized`, "bool", `${animVar}_initialized!`, true ); funcName = `get_${animVar}`; this.animations.set(key, funcName); // Add the function that creates the animation const build = this; this.addFunction( funcName, () => { build.blockStart(`lv_anim_t *${funcName}() {`); build.blockStart(`if (!${animInitializedVar}) {`); build.line(`lv_anim_init(&${animVar});`); if (setDelay) { build.line(`lv_anim_set_delay(&${animVar}, ${delay});`); } if (setRepeatDelay) { build.line( `lv_anim_set_repeat_delay(&${animVar}, ${repeatDelay});` ); } if (setRepeatCount) { build.line( `lv_anim_set_repeat_count(&${animVar}, ${repeatCount});` ); } build.line(`${animInitializedVar} = true;`); build.blockEnd(`}`); build.line(`return &${animVar};`); build.blockEnd(`}`); build.line(""); }, `lv_anim_t *${funcName}();` ); return `${funcName}()`; } async buildScreensDecl() { this.startBuild(); const build = this; // screens build.line(""); build.line("// Screens"); build.line(""); // enum ScreensEnum build.blockStart(`enum ScreensEnum {`); const pages = this.pages.filter(page => !page.isUsedAsUserWidget); build.line(`_SCREEN_ID_FIRST = 1,`); for (let i = 0; i < pages.length; i++) { build.line( `SCREEN_ID_${this.getScreenIdentifier(pages[i]).toUpperCase()} = ${i + 1},` ); } build.line(`_SCREEN_ID_LAST = ${pages.length}`); build.blockEnd(`};`); build.line(""); // objects build.blockStart(`typedef struct _objects_t {`); this.lvglObjectIdentifiers.fromPage.identifiers.forEach( (identifier, i) => { build.line(`lv_obj_t *${identifier};`); } ); build.blockEnd(`} objects_t;`); build.line(""); build.line(`extern objects_t objects;`); if (this.pagesWithStateVars.length > 0) { build.line(""); for (const page of this.pagesWithStateVars) { const stateVars = this.stateVars.get(page)!; if (stateVars) { build.blockStart(`typedef struct {`); for (const stateVar of stateVars) { build.line(`${stateVar.varType}${stateVar.varName};`); } build.blockEnd(`} ${this.getStateStructName(page)};`); build.line(""); } } for (const page of this.pagesWithStateVars) { if (!page.isUsedAsUserWidget) { if (this.stateVars.get(page)!) { build.line( `extern ${this.getStateStructName(page)} ${this.getScreenStateVarName(page)};` ); } } } build.line(""); } for (const page of this.pages) { build.line(""); if (page.isUsedAsUserWidget) { if (build.project.projectTypeTraits.hasFlowSupport) { build.line( `void ${this.getScreenCreateFunctionName( page )}(lv_obj_t *parent_obj, void *flowState, int startWidgetIndex${this.stateVars.get(page) ? `, ${this.getStateStructName(page)} *state` : ""});` ); build.line( `void ${this.getScreenTickFunctionName(page)}(void *flowState, int startWidgetIndex${this.stateVars.get(page) ? `, ${this.getStateStructName(page)} *state` : ""});` ); } else { build.line( `void ${this.getScreenCreateFunctionName(page)}(lv_obj_t *parent_obj, int startWidgetIndex${this.stateVars.get(page) ? `, ${this.getStateStructName(page)} *state` : ""});` ); build.line( `void ${this.getScreenTickFunctionName(page)}(int startWidgetIndex${this.stateVars.get(page) ? `, ${this.getStateStructName(page)} *state` : ""});` ); } } else { build.line(`void ${this.getScreenCreateFunctionName(page)}();`); if (build.project.settings.build.screensLifetimeSupport) { build.line( `void ${this.getScreenDeleteFunctionName(page)}();` ); } build.line(`void ${this.getScreenTickFunctionName(page)}();`); } } build.line(""); if (build.project.settings.build.screensLifetimeSupport) { build.line("void create_screen_by_id(enum ScreensEnum screenId);"); build.line("void delete_screen_by_id(enum ScreensEnum screenId);"); } build.line("void tick_screen_by_id(enum ScreensEnum screenId);"); build.line("void tick_screen(int screen_index);"); build.line(""); build.line("void create_screens();"); // groups if (this.project.lvglGroups.groups.length > 0) { build.line(""); build.line("// Groups"); build.line(""); build.blockStart(`typedef struct _groups_t {`); this.project.lvglGroups.groups.forEach(group => { build.line(`lv_group_t *${group.name};`); }); build.blockEnd(`} groups_t;`); build.line(""); build.line(`extern groups_t groups;`); build.line(""); build.line(`void ui_create_groups();`); build.line(""); } // colors & themes if (this.updateColorCallbacks.length > 0) { build.line(""); build.line("// Color themes"); build.line(""); build.blockStart(`enum Themes {`); this.project.themes.forEach(theme => { build.line( `THEME_ID_${getName("", theme.name, NamingConvention.UnderscoreUpperCase)},` ); }); build.blockEnd(`};`); build.blockStart(`enum Colors {`); this.project.colors.forEach(color => { build.line( `COLOR_ID_${getName("", color.name, NamingConvention.UnderscoreUpperCase)},` ); }); build.blockEnd(`};`); build.line("void change_color_theme(uint32_t themeIndex);"); build.line( `extern uint32_t theme_colors[${this.project.themes.length}][${this.project.colors.length}];` ); if (!this.assets.projectStore.projectTypeTraits.hasFlowSupport) { build.line(`extern uint32_t active_theme_index;`); } } // global vars if (this.globalVars.length > 0) { let first = true; for (const globalVar of this.globalVars) { if (!globalVar.isStatic) { if (first) { build.line(""); build.line("// Global state variables"); build.line(""); first = false; } build.line( `extern ${globalVar.varType} ${globalVar.varName};` ); } } } // // Helper functions // if (Object.values(this.functions).length > 0) { let first = true; Object.values(this.functions).forEach(funct => { if (funct.decl) { if (first) { build.line(""); build.line("//"); build.line("// Helper functions"); build.line("//"); build.line(""); first = false; } build.line(funct.decl); } }); } return this.result; } async buildScreensDef() { this.startBuild(); const build = this; build.line(`#include `); build.line(""); build.line(`objects_t objects;`); build.line(""); if (this.assets.projectStore.projectTypeTraits.hasFlowSupport) { const pages = this.pages.filter(page => !page.isUsedAsUserWidget); if (pages.length > 0) { build.line( `static const char *screen_names[] = { ${pages.map(page => `"${page.name}"`).join(", ")} };` ); } if (this.lvglObjectIdentifiers.fromPage.identifiers.length > 0) { build.line( `static const char *object_names[] = { ${this.lvglObjectIdentifiers.fromPage.identifiers .map(identifier => `"${identifier}"`) .join(", ")} };` ); } build.line(""); } build.line(""); if (this.pagesWithStateVars.length > 0) { for (const page of this.pagesWithStateVars) { if (!page.isUsedAsUserWidget) { if (this.stateVars.get(page)) { build.line( `${this.getStateStructName(page)} ${this.getScreenStateVarName(page)};` ); } } } build.line(""); } // global vars if (this.globalVars.length > 0) { build.line(""); build.line("// Global state variables"); build.line(""); for (const globalVar of this.globalVars) { if (globalVar.isStatic) { build.line( `static ${globalVar.varType} ${globalVar.varName};` ); } else { build.line(`${globalVar.varType} ${globalVar.varName};`); } } } // // Helper functions // if (Object.values(this.functions).length > 0) { build.line(""); build.line("//"); build.line("// Helper functions"); build.line("//"); build.line(""); Object.values(this.functions).forEach(funct => funct.callback()); } // // Event handlers // build.line(""); build.line("//"); build.line("// Event handlers"); build.line("//"); build.line(""); build.line(`lv_obj_t *tick_value_change_obj;`); build.line(""); for (const page of this.pages) { page._lvglWidgets.forEach(widget => { const widgetEventHandlers = this.eventHandlers.get(widget); if (widgetEventHandlers) { if ( !build.assets.projectStore.projectTypeTraits .hasFlowSupport ) { const checkedEventHandler = widgetEventHandlers.find( widgetEventHandler => widgetEventHandler.eventName == "CHECKED" ); if (checkedEventHandler) { build.blockStart( `static void ${build.getCheckedEventHandlerCallbackName(widget)}(lv_event_t *e) {` ); checkedEventHandler.callback(); build.blockEnd("}"); build.line(""); } } if ( !build.assets.projectStore.projectTypeTraits .hasFlowSupport ) { const uncheckedEventHandler = widgetEventHandlers.find( widgetEventHandler => widgetEventHandler.eventName == "UNCHECKED" ); if (uncheckedEventHandler) { build.blockStart( `static void ${build.getUncheckedEventHandlerCallbackName(widget)}(lv_event_t *e) {` ); uncheckedEventHandler.callback(); build.blockEnd("}"); build.line(""); } } const otherEventHandlers = build.assets.projectStore .projectTypeTraits.hasFlowSupport ? widgetEventHandlers : widgetEventHandlers.filter( widgetEventHandler => widgetEventHandler.eventName != "CHECKED" && widgetEventHandler.eventName != "UNCHECKED" ); if (otherEventHandlers.length > 0) { build.blockStart( `static void ${build.getEventHandlerCallbackName(widget)}(lv_event_t *e) {` ); build.line( `lv_event_code_t event = lv_event_get_code(e);` ); if ( build.assets.projectStore.projectTypeTraits .hasFlowSupport ) { build.line( `void *flowState = lv_event_get_user_data(e);` ); build.line(`(void)flowState;`); build.line(""); } for (const eventHandler of otherEventHandlers) { this.blockStart( `if (event == LV_EVENT_${eventHandler.eventName == "CHECKED" || eventHandler.eventName == "UNCHECKED" ? "VALUE_CHANGED" : eventHandler.eventName}) {` ); eventHandler.callback(); this.blockEnd("}"); } build.blockEnd("}"); build.line(""); } } }); } // // Screens // build.line(""); build.line("//"); build.line("// Screens"); build.line("//"); build.line(""); for (const page of this.pages) { if (page.isUsedAsUserWidget) { if (build.project.projectTypeTraits.hasFlowSupport) { build.blockStart( `void ${this.getScreenCreateFunctionName( page )}(lv_obj_t *parent_obj, void *flowState, int startWidgetIndex${this.stateVars.get(page) ? `, ${this.getStateStructName(page)} *state` : ""}) {` ); build.line(`(void)flowState;`); } else { build.blockStart( `void ${this.getScreenCreateFunctionName(page)}(lv_obj_t *parent_obj, int startWidgetIndex${this.stateVars.get(page) ? `, ${this.getStateStructName(page)} *state` : ""}) {` ); } build.line(`(void)startWidgetIndex;`); if (this.stateVars.get(page)) { build.line(`(void)state;`); } } else { build.blockStart( `void ${this.getScreenCreateFunctionName(page)}() {` ); if (this.stateVars.get(page)) { build.line( `${this.getStateStructName(page)} *state = &${this.getScreenStateVarName(page)};` ); build.line(`(void)state;`); } } this.objectAccessors = []; this.currentPage = page; this.tickCallbacks = []; page.lvglBuild(this); if ( this.assets.projectStore.projectTypeTraits.hasFlowSupport && build.project.settings.build.screensLifetimeSupport && page.deleteOnScreenUnload ) { build.line(""); build.line( `eez_flow_delete_screen_on_unload(SCREEN_ID_${this.getScreenIdentifier(page).toUpperCase()} - 1);` ); } if (!page.isUsedAsUserWidget) { build.line(""); build.line(`${this.getScreenTickFunctionName(page)}();`); } build.blockEnd("}"); build.line(""); // if ( build.project.settings.build.screensLifetimeSupport && !page.isUsedAsUserWidget ) { build.blockStart( `void ${this.getScreenDeleteFunctionName(page)}() {` ); // delete screen object if (this.isV9) { build.line( `lv_obj_delete(${build.getLvglObjectAccessor(page.lvglScreenWidget!)});` ); } else { build.line( `lv_obj_del(${build.getLvglObjectAccessor(page.lvglScreenWidget!)});` ); } // clean object vars for (const objectAccessor of this.objectAccessors) { build.line(`${objectAccessor} = 0;`); } // clean state vars const stateVars = this.stateVars.get(page); if (stateVars) { function cleanStateVars(page: Page, prefix: string) { const stateVars = build.stateVars.get(page); if (stateVars) { for (const stateVar of stateVars) { if (stateVar.userWidgetPage) { cleanStateVars( stateVar.userWidgetPage, `${prefix}.${stateVar.varName}` ); } else { build.line( `${prefix}.${stateVar.varName} = 0;` ); } } } } cleanStateVars(page, build.getScreenStateVarName(page)); } // delete flow state if (build.project.projectTypeTraits.hasFlowSupport) { build.line( `deletePageFlowState(${build.assets.getFlowIndex(page)});` ); } build.blockEnd("}"); build.line(""); } this.objectAccessors = undefined; // if (page.isUsedAsUserWidget) { if (build.project.projectTypeTraits.hasFlowSupport) { build.blockStart( `void ${this.getScreenTickFunctionName(page)}(void *flowState, int startWidgetIndex${this.stateVars.get(page) ? `, ${this.getStateStructName(page)} *state` : ""}) {` ); build.line(`(void)flowState;`); } else { build.blockStart( `void ${this.getScreenTickFunctionName(page)}(int startWidgetIndex${this.stateVars.get(page) ? `, ${this.getStateStructName(page)} *state` : ""}) {` ); } build.line(`(void)startWidgetIndex;`); if (this.stateVars.get(page)) { build.line(`(void)state;`); } } else { build.blockStart( `void ${this.getScreenTickFunctionName(page)}() {` ); if (this.stateVars.get(page)) { build.line( `${this.getStateStructName(page)} *state = &${this.getScreenStateVarName(page)};` ); build.line(`(void)state;`); } } for (const tickCallback of this.tickCallbacks) { tickCallback(); } build.blockEnd("}"); build.line(""); } if (build.project.settings.build.screensLifetimeSupport) { // build.line(""); build.line("typedef void (*create_screen_func_t)();"); build.blockStart("create_screen_func_t create_screen_funcs[] = {"); for (const page of this.userPages) { build.line(`${this.getScreenCreateFunctionName(page)},`); } build.blockEnd("};"); build.blockStart("void create_screen(int screen_index) {"); build.line("create_screen_funcs[screen_index]();"); build.blockEnd("}"); build.blockStart( "void create_screen_by_id(enum ScreensEnum screenId) {" ); build.line("create_screen_funcs[screenId - 1]();"); build.blockEnd("}"); // build.line(""); build.line("typedef void (*delete_screen_func_t)();"); build.blockStart("delete_screen_func_t delete_screen_funcs[] = {"); for (const page of this.userPages) { build.line(`${this.getScreenDeleteFunctionName(page)},`); } build.blockEnd("};"); build.blockStart("void delete_screen(int screen_index) {"); build.line("delete_screen_funcs[screen_index]();"); build.blockEnd("}"); build.blockStart( "void delete_screen_by_id(enum ScreensEnum screenId) {" ); build.line("delete_screen_funcs[screenId - 1]();"); build.blockEnd("}"); } // build.line(""); build.line("typedef void (*tick_screen_func_t)();"); build.blockStart("tick_screen_func_t tick_screen_funcs[] = {"); for (const page of this.userPages) { build.line(`${this.getScreenTickFunctionName(page)},`); } build.blockEnd("};"); build.blockStart("void tick_screen(int screen_index) {"); build.blockStart(`if (screen_index >= 0 && screen_index < ${this.userPages.length}) {`) build.line("tick_screen_funcs[screen_index]();"); build.blockEnd(`}`) build.blockEnd("}"); build.blockStart("void tick_screen_by_id(enum ScreensEnum screenId) {"); build.line("tick_screen(screenId - 1);"); build.blockEnd(`}`) build.line(""); // // Styles // if ( this.assets.projectStore.projectTypeTraits.hasFlowSupport && this.styles.length > 0 ) { build.line(""); build.line("//"); build.line("// Styles"); build.line("//"); build.line(""); build.line( `static const char *style_names[] = { ${this.styles.map(style => `"${style.name}"`).join(", ")} };` ); build.line(""); build.line( "extern void add_style(lv_obj_t *obj, int32_t styleIndex);" ); build.line( "extern void remove_style(lv_obj_t *obj, int32_t styleIndex);" ); build.line(""); } // // fonts // build.line(""); build.line("//"); build.line("// Fonts"); build.line("//"); build.line(""); { let anyFontDef = false; for (const font of this.fonts) { if ( font.lvglUseFreeType || this.project.settings.build.fontExportMode == "binary" ) { build.line(`lv_font_t *${this.getFontVariableName(font)};`); anyFontDef = true; } } if (anyFontDef) { build.line(""); } } build.blockStart(`ext_font_desc_t fonts[] = {`); for (const font of this.fonts) { build.line( `{ "${font.name}", ${font.lvglUseFreeType || this.project.settings.build.fontExportMode == "binary" ? "NULL" : this.getFontAccessor(font)} },` ); } for (const font of BUILT_IN_FONTS) { build.text(`#if LV_FONT_${font}\n`); build.line(`{ "${font}", &lv_font_${font.toLowerCase()} },`); build.text(`#endif\n`); } build.blockEnd(`};`); build.line(""); // // Themes // if ( !this.assets.projectStore.projectTypeTraits.hasFlowSupport || this.updateColorCallbacks.length > 0 ) { build.line(""); build.line("//"); build.line("// Color themes"); build.line("//"); build.line(""); } if (!this.assets.projectStore.projectTypeTraits.hasFlowSupport) { build.line(`uint32_t active_theme_index = 0;`); } this.buildChangeColorTheme(); if ( this.assets.projectStore.projectTypeTraits.hasFlowSupport && this.updateColorCallbacks.length > 0 ) { build.line( `static const char *theme_names[] = { ${this.project.themes .map(theme => `"${theme.name}"`) .join(", ")} };` ); } if (this.updateColorCallbacks.length > 0) { build.blockStart( `uint32_t theme_colors[${this.project.themes.length}][${this.project.colors.length}] = {` ); this.project.themes.map(theme => { const colors = this.project.colors.map(color => this.getColorHexStr( this.project.getThemeColor(theme.objID, color.objID) ) ); build.line(`{ ${colors.join(", ")} },`); }); build.blockEnd("};"); build.line(""); } // // Groups // if (this.project.lvglGroups.groups.length > 0) { build.line(""); build.line("//"); build.line("// Groups"); build.line("//"); build.line(""); build.line(`groups_t groups;`); build.line("static bool groups_created = false;"); if (this.assets.projectStore.projectTypeTraits.hasFlowSupport) { build.line( `static const char *group_names[] = { ${this.project.lvglGroups.groups .map(group => `"${group.name}"`) .join(", ")} };` ); build.line(""); } build.blockStart("void ui_create_groups() {"); build.blockStart("if (!groups_created) {"); this.project.lvglGroups.groups.forEach(group => { build.line( `${build.getGroupVariableName(group)} = lv_group_create();` ); }); if (this.assets.projectStore.projectTypeTraits.hasFlowSupport) { build.line( "eez_flow_init_groups((lv_group_t **)&groups, sizeof(groups) / sizeof(lv_group_t *));" ); } build.line("groups_created = true;"); build.blockEnd("}"); build.blockEnd("}"); build.line(""); } // // create_screens function // build.line(""); build.line("//"); build.line("//"); build.line("//"); build.line(""); build.blockStart("void create_screens() {"); if ( this.assets.projectStore.projectTypeTraits.hasFlowSupport && this.styles.length > 0 ) { build.line("// Initialize styles"); build.line("eez_flow_init_styles(add_style, remove_style);"); build.line( `eez_flow_init_style_names(style_names, sizeof(style_names) / sizeof(const char *));` ); build.line(""); } { const anyExternalFont = this.fonts.some( font => font.lvglUseFreeType || this.project.settings.build.fontExportMode == "binary" ); if (anyExternalFont) { build.line("// Load external fonts"); let path = this.project.settings.build.fileSystemPath; if (!path.endsWith("/") && !path.endsWith("\\")) { if (path.indexOf("\\") != -1) path += "\\"; else path += "/"; } for ( let fontIndex = 0; fontIndex < this.fonts.length; fontIndex++ ) { const font = this.fonts[fontIndex]; if (font.lvglUseFreeType) { if (this.isV9) { build.blockStart("{"); build.line( `${this.getFontVariableName(font)} = lv_freetype_font_create(${escapeCString( font.lvglFreeTypeFilePath )}, ${font.lvglFreeTypeRenderMode == "OUTLINE" ? "LV_FREETYPE_FONT_RENDER_MODE_OUTLINE" : "LV_FREETYPE_FONT_RENDER_MODE_BITMAP"}, ${ font.source!.size }, ${ font.lvglFreeTypeStyle == "BOLD" ? "LV_FREETYPE_FONT_STYLE_BOLD" : font.lvglFreeTypeStyle == "ITALIC" ? "LV_FREETYPE_FONT_STYLE_ITALIC" : font.lvglFreeTypeStyle == "BOLD_ITALIC" ? "LV_FREETYPE_FONT_STYLE_BOLD | LV_FREETYPE_FONT_STYLE_ITALIC" : "LV_FREETYPE_FONT_STYLE_NORMAL" });` ); build.blockStart( `if (${this.getFontVariableName(font)}) {` ); build.line( `fonts[${fontIndex}].font_ptr = ${this.getFontVariableName(font)};` ); build.unindent(); build.line("} else {"); build.indent(); build.line( `LV_LOG_ERROR("font create failed: ${this.getFontVariableName(font)}");` ); build.blockEnd("}"); build.blockEnd("}"); } else { build.blockStart("{"); build.line(`lv_ft_info_t info;`); build.line( `info.name = ${escapeCString(font.lvglFreeTypeFilePath)};` ); build.line(`info.weight = ${font.source!.size};`); build.line( `info.style = ${ font.lvglFreeTypeStyle == "BOLD" ? "FT_FONT_STYLE_BOLD" : font.lvglFreeTypeStyle == "ITALIC" ? "FT_FONT_STYLE_ITALIC" : font.lvglFreeTypeStyle == "BOLD_ITALIC" ? "FT_FONT_STYLE_BOLD | FT_FONT_STYLE_ITALIC" : "FT_FONT_STYLE_NORMAL" };` ); build.line(`info.mem = 0;`); build.blockStart(`if (lv_ft_font_init(&info)) {`); build.line( `${this.getFontVariableName(font)} = info.font;` ); build.line( `fonts[${fontIndex}].font_ptr = ${this.getFontVariableName(font)};` ); build.unindent(); build.line("} else {"); build.indent(); build.line( `LV_LOG_ERROR("font create failed: ${this.getFontVariableName(font)}");` ); build.blockEnd("}"); build.blockEnd("}"); } } else if ( this.project.settings.build.fontExportMode == "binary" ) { build.blockStart("{"); const output = getName( "ui_font_", font.name || "", NamingConvention.UnderscoreLowerCase ); if (this.isV9) { build.line( `${this.getFontVariableName(font)} = lv_binfont_create(${escapeCString( `${path}${output}.bin` )});` ); } else { build.line( `${this.getFontVariableName(font)} = lv_font_load(${escapeCString(`${path}${output}.bin`)});` ); } build.blockStart( `if (${this.getFontVariableName(font)}) {` ); if (font.lvglFallbackFont) { build.line( `${this.getFontVariableName(font)}->fallback = &${font.lvglFallbackFont};` ); } build.line( `fonts[${fontIndex}].font_ptr = ${this.getFontVariableName(font)};` ); build.unindent(); build.line("} else {"); build.indent(); build.line( `LV_LOG_ERROR("font create failed: ${this.getFontVariableName(font)}");` ); build.blockEnd("}"); build.blockEnd("}"); } } } build.line(""); } if (this.assets.projectStore.projectTypeTraits.hasFlowSupport) { build.line( `eez_flow_init_fonts(fonts, sizeof(fonts) / sizeof(ext_font_desc_t));` ); if (this.updateColorCallbacks.length > 0) { build.line(""); build.line( `eez_flow_init_themes(theme_names, sizeof(theme_names) / sizeof(const char *), change_color_theme, &theme_colors[0][0], sizeof(theme_colors[0]) / sizeof(uint32_t));` ); } build.line(""); } // groups if (this.project.lvglGroups.groups.length > 0) { build.line("// Initialize groups"); } if (this.project.lvglGroups.groups.length > 0) { build.line("ui_create_groups();"); } if ( this.assets.projectStore.projectTypeTraits.hasFlowSupport && this.project.lvglGroups.groups.length > 0 ) { build.line( `eez_flow_init_group_names(group_names, sizeof(group_names) / sizeof(const char *));` ); } build.line(""); build.line("// Set default LVGL theme"); if (this.isV9) { build.line("lv_display_t *dispp = lv_display_get_default();"); } else { build.line("lv_disp_t *dispp = lv_disp_get_default();"); } build.line( `lv_theme_t *theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED), ${ this.project.settings.general.darkTheme ? "true" : "false" }, LV_FONT_DEFAULT);` ); if (this.isV9) { build.line("lv_display_set_theme(dispp, theme);"); } else { build.line("lv_disp_set_theme(dispp, theme);"); } build.line(""); build.line("// Initialize screens"); if (this.assets.projectStore.projectTypeTraits.hasFlowSupport) { if (this.pages.length > 0) { build.line( `eez_flow_init_screen_names(screen_names, sizeof(screen_names) / sizeof(const char *));` ); } if (this.lvglObjectIdentifiers.fromPage.identifiers.length > 0) { build.line( `eez_flow_init_object_names(object_names, sizeof(object_names) / sizeof(const char *));` ); } build.line(""); } if ( this.assets.projectStore.projectTypeTraits.hasFlowSupport && build.project.settings.build.screensLifetimeSupport ) { build.line("eez_flow_set_create_screen_func(create_screen);"); build.line("eez_flow_set_delete_screen_func(delete_screen);"); build.line(""); } build.line("// Create screens"); for (const page of this.userPages) { if ( !build.project.settings.build.screensLifetimeSupport || page.createAtStart ) { build.line(`${this.getScreenCreateFunctionName(page)}();`); } } build.blockEnd("}"); return this.result; } buildChangeColorTheme() { if (this.updateColorCallbacks.length == 0) { return; } // enumerate const updateColorCallbackForPages: UpdateColorCallbackForPage[] = []; const enumPage: ( page: Page ) => UpdateColorCallbackForPage | undefined = (page: Page) => { const updateColorCallbacks = this.updateColorCallbacks.filter( updateColorCallback => { return page == ProjectEditor.getPage(updateColorCallback.object); } ); const lvglUserWidgets = page._lvglWidgets.filter( lvglWidget => lvglWidget instanceof ProjectEditor.LVGLUserWidgetWidgetClass && lvglWidget.userWidgetPage ) as LVGLUserWidgetWidget[]; const updateColorCallbackForUserWidgets: UpdateColorCallbackForUserWidget[] = []; lvglUserWidgets.map(lvglUserWidget => { const updateColorsForPage = enumPage( lvglUserWidget.userWidgetPage! ); if (updateColorsForPage != undefined) { updateColorCallbackForUserWidgets.push({ lvglUserWidget, updateColorsForPage }); } }); if ( updateColorCallbacks.length > 0 || updateColorCallbackForUserWidgets.length > 0 ) { return { page, updateColorCallbacks, updateColorCallbackForUserWidgets }; } return undefined; }; for (const page of this.pages) { if (page.isUsedAsUserWidget) { continue; } const result = enumPage(page); if (result != undefined) { updateColorCallbackForPages.push(result); } } // generate code for change_color_theme const build = this; build.blockStart(`void change_color_theme(uint32_t theme_index) {`); if (!this.assets.projectStore.projectTypeTraits.hasFlowSupport) { build.line("active_theme_index = theme_index;"); build.line(""); } const updateColors = ( updateColorCallbackForPage: UpdateColorCallbackForPage, userWidgetStateVarName?: string, startWidgetIndex?: number ) => { build.blockStart(`{`); const page = updateColorCallbackForPage.page; if (userWidgetStateVarName != undefined) { build.line(`startWidgetIndex = ${startWidgetIndex!};`); if (userWidgetStateVarName) { build.line( `${this.getStateStructName(page)} *state = &parent_state->${userWidgetStateVarName};` ); build.line(`(void)state;`); } } else { if (this.stateVars.get(page)) { build.line( `${this.getStateStructName(page)} *state = &${this.getScreenStateVarName(page)};` ); build.line(`(void)state;`); } } for (const updateColorCallback of updateColorCallbackForPage.updateColorCallbacks) { updateColorCallback.callback(); } if ( updateColorCallbackForPage.updateColorCallbackForUserWidgets .length > 0 ) { build.blockStart(`{`); if ( userWidgetStateVarName == undefined && this.stateVars.get(page) || userWidgetStateVarName ) { build.line( `${this.getStateStructName(page)} *parent_state = state;` ); } if (startWidgetIndex == undefined) { build.line(`int startWidgetIndex;`); build.line(`(void)startWidgetIndex;`); } for (const updateColorCallbackForUserWidget of updateColorCallbackForPage.updateColorCallbackForUserWidgets) { const temp = this.currentPage; this.currentPage = page; const userWidgetStateVarName = this.getUserWidgetStateVarName( updateColorCallbackForUserWidget.lvglUserWidget ); this.currentPage = temp; updateColors( updateColorCallbackForUserWidget.updateColorsForPage, userWidgetStateVarName, (startWidgetIndex ?? 0) + this.getWidgetObjectIndex( updateColorCallbackForUserWidget.lvglUserWidget ) + 1 ); } build.blockEnd("}"); } build.blockEnd("}"); }; for (const updateColorCallbackForPage of updateColorCallbackForPages) { updateColors(updateColorCallbackForPage); } for (const updateColorCallback of this.updateColorCallbacks) { const lvglStyle = getAncestorOfType( updateColorCallback.object, ProjectEditor.LVGLStyleClass.classInfo ); if (lvglStyle) { updateColorCallback.callback(); } } // invalidate all pages build.pages .filter(page => !page.isUsedAsUserWidget) .forEach(page => { const screenIdentifier = "objects." + this.getScreenIdentifier(page); if (this.project.settings.build.screensLifetimeSupport) { build.line( `if (${screenIdentifier}) lv_obj_invalidate(${screenIdentifier});` ); } else { build.line(`lv_obj_invalidate(${screenIdentifier});`); } }); build.blockEnd("}"); } async buildScreensDeclExt() { return ""; } async buildScreensDefExt() { return ""; } async buildImagesDecl() { this.startBuild(); const build = this; if (this.project.settings.build.imageExportMode == "source") { for (const bitmap of this.bitmaps) { build.line( `extern const lv_img_dsc_t ${this.getImageVariableName(bitmap)};` ); } } build.text(` #ifndef EXT_IMG_DESC_T #define EXT_IMG_DESC_T typedef struct _ext_img_desc_t { const char *name; const ${this.project.settings.build.imageExportMode == "binary" ? "void" : "lv_img_dsc_t"} *img_dsc; } ext_img_desc_t; #endif extern const ext_img_desc_t images[${this.bitmaps.length || 1}]; `); return this.result; } async buildImagesDef() { this.startBuild(); const build = this; build.blockStart( `const ext_img_desc_t images[${this.bitmaps.length || 1}] = {` ); if (this.bitmaps.length > 0) { for (const bitmap of this.bitmaps) { build.line( `{ "${bitmap.name}", ${this.getImageAccessor(bitmap)} },` ); } } else { build.line(`0`); } build.blockEnd(`};`); return this.result; } async buildFontsDecl() { this.startBuild(); const build = this; for (const font of this.fonts) { if ( this.project.settings.build.fontExportMode == "binary" || font.lvglUseFreeType ) { build.line( `extern lv_font_t *${this.getFontVariableName(font)};` ); } else { build.line( `extern const lv_font_t ${this.getFontVariableName(font)};` ); } } build.text(` #ifndef EXT_FONT_DESC_T #define EXT_FONT_DESC_T typedef struct _ext_font_desc_t { const char *name; const void *font_ptr; } ext_font_desc_t; #endif extern ext_font_desc_t fonts[]; `); return this.result; } async buildActionsDecl() { this.startBuild(); const build = this; for (const action of this.project.actions) { if ( !this.assets.projectStore.projectTypeTraits.hasFlowSupport || action.implementationType === "native" ) { if (action.userProperties.length > 0) { build.line(""); build.blockStart(`enum {`); for (let i = 0; i < action.userProperties.length; i++) { build.line( `ACTION_${getName( "", action.name, NamingConvention.UnderscoreUpperCase )}_PROPERTY_${getName( "", action.userProperties[i].name, NamingConvention.UnderscoreUpperCase )},` ); } build.blockEnd(`};`); } build.line( `extern void ${this.getActionFunctionName(action.name)}(lv_event_t * e);` ); if (action.userProperties.length > 0) { build.line(""); } } } return this.result; } async buildActionsArrayDef() { if (!this.project.projectTypeTraits.hasFlowSupport) { return ""; } this.startBuild(); const build = this; build.blockStart("ActionExecFunc actions[] = {"); let numActions = 0; for (const action of this.project.actions) { if ( !this.assets.projectStore.projectTypeTraits.hasFlowSupport || action.implementationType === "native" ) { build.line(`${this.getActionFunctionName(action.name)},`); numActions++; } } if (numActions == 0) { build.line("0"); } build.blockEnd(`};`); return this.result; } async buildVariablesDecl() { this.startBuild(); const build = this; for (const variable of this.project.variables.globalVariables) { if ( !this.assets.projectStore.projectTypeTraits.hasFlowSupport || variable.native ) { let nativeType; if (variable.type == "integer") { nativeType = "int32_t "; } else if (variable.type == "float") { nativeType = "float "; } else if (variable.type == "double") { nativeType = "double "; } else if (variable.type == "boolean") { nativeType = "bool "; } else if (variable.type == "string") { nativeType = "const char *"; } else if (isEnumType(variable.type)) { const enumType = getEnumTypeNameFromType(variable.type); nativeType = `${enumType} `; } else { } build.line( `extern ${nativeType}${this.getVariableGetterFunctionName(variable.name)}();` ); build.line( `extern void ${this.getVariableSetterFunctionName(variable.name)}(${nativeType}value);` ); } } return this.result; } async buildNativeVarsTableDef() { if (!this.project.projectTypeTraits.hasFlowSupport) { return ""; } this.startBuild(); const build = this; build.blockStart("native_var_t native_vars[] = {"); build.line("{ NATIVE_VAR_TYPE_NONE, 0, 0 },"); for (const variable of this.project.variables.globalVariables) { if ( !this.assets.projectStore.projectTypeTraits.hasFlowSupport || variable.native ) { build.line( `{ NATIVE_VAR_TYPE_${ isEnumType(variable.type) ? "INTEGER" : variable.type.toUpperCase() }, ${this.getVariableGetterFunctionName(variable.name)}, ${this.getVariableSetterFunctionName( variable.name )} }, ` ); } } build.blockEnd("};"); return this.result; } async buildStylesDef() { this.startBuild(); const build = this; for (const lvglStyle of this.styles) { build.line("// Style: " + lvglStyle.name); const definition = lvglStyle.fullDefinition; if (definition) { Object.keys(definition).forEach(part => { Object.keys(definition[part]).forEach(state => { // build style get function build.line( `lv_style_t *${this.getGetStyleFunctionName(lvglStyle, part, state)}();` ); }); }); } build.line( `void ${this.getAddStyleFunctionName(lvglStyle)}(lv_obj_t *obj);` ); build.line( `void ${this.getRemoveStyleFunctionName(lvglStyle)}(lv_obj_t *obj);` ); build.line(""); } return this.result; } async buildStylesDecl() { this.startBuild(); const build = this; build.line(`#include "ui.h"`); build.line(`#include "screens.h"`); build.line(""); if (this.styles.length > 0) { for (const lvglStyle of this.styles) { build.line("//"); build.line("// Style: " + lvglStyle.name); build.line("//"); build.line(""); const definition = lvglStyle.fullDefinition; if (definition) { Object.keys(definition).forEach(part => { Object.keys(definition[part]).forEach(state => { // build style init function build.blockStart( `void ${this.getInitStyleFunctionName(lvglStyle, part, state)}(lv_style_t *style) {` ); if ( lvglStyle.parentStyle?.fullDefinition?.[part]?.[ state ] ) { build.line( `${this.getInitStyleFunctionName(lvglStyle.parentStyle, part, state)}(style);` ); build.line(""); } if (lvglStyle.definition) { lvglStyle.definition.lvglBuildStyle( build, lvglStyle, part, state ); } build.blockEnd("};"); build.line(""); // build style get function build.blockStart( `lv_style_t *${this.getGetStyleFunctionName(lvglStyle, part, state)}() {` ); build.line("static lv_style_t *style;"); build.blockStart(`if (!style) {`); { if (build.isV9) { build.line( `style = (lv_style_t *)lv_malloc(sizeof(lv_style_t));` ); } else { build.line( `style = (lv_style_t *)lv_mem_alloc(sizeof(lv_style_t));` ); } build.line(`lv_style_init(style);`); build.line( `${this.getInitStyleFunctionName(lvglStyle, part, state)}(style);` ); } build.blockEnd("}"); build.line(`return style;`); build.blockEnd("};"); build.line(""); }); }); } // build style add function build.blockStart( `void ${this.getAddStyleFunctionName(lvglStyle)}(lv_obj_t *obj) {` ); build.line(`(void)obj;`); if (definition) { Object.keys(definition).forEach(part => { Object.keys(definition[part]).forEach(state => { const selectorCode = getSelectorBuildCode( part, state ); build.line( `lv_obj_add_style(obj, ${this.getGetStyleFunctionName( lvglStyle, part, state )}(), ${selectorCode});` ); }); }); } build.blockEnd("};"); build.line(""); // build style remove function build.blockStart( `void ${this.getRemoveStyleFunctionName(lvglStyle)}(lv_obj_t *obj) {` ); build.line(`(void)obj;`); if (definition) { Object.keys(definition).forEach(part => { Object.keys(definition[part]).forEach(state => { const selectorCode = getSelectorBuildCode( part, state ); build.line( `lv_obj_remove_style(obj, ${this.getGetStyleFunctionName( lvglStyle, part, state )}(), ${selectorCode});` ); }); }); } build.blockEnd("};"); build.line(""); } build.line("//"); build.line("//"); build.line("//"); build.line(""); build.blockStart( "void add_style(lv_obj_t *obj, int32_t styleIndex) {" ); build.line("typedef void (*AddStyleFunc)(lv_obj_t *obj);"); build.blockStart("static const AddStyleFunc add_style_funcs[] = {"); for (const lvglStyle of this.styles) { build.line(`${this.getAddStyleFunctionName(lvglStyle)},`); } build.blockEnd("};"); build.line("add_style_funcs[styleIndex](obj);"); build.blockEnd("}"); build.line(""); build.blockStart( "void remove_style(lv_obj_t *obj, int32_t styleIndex) {" ); build.line("typedef void (*RemoveStyleFunc)(lv_obj_t *obj);"); build.blockStart( "static const RemoveStyleFunc remove_style_funcs[] = {" ); for (const lvglStyle of this.styles) { build.line(`${this.getRemoveStyleFunctionName(lvglStyle)},`); } build.blockEnd("};"); build.line("remove_style_funcs[styleIndex](obj);"); build.blockEnd("}"); } return this.result; } async buildEezForLvglCheck() { if (!this.project.projectTypeTraits.hasFlowSupport) { return ""; } this.startBuild(); const build = this; if (this.project.settings.build.generateSourceCodeForEezFramework) { build.line(`#include "eez-flow.h"`); } else { build.line("#if !defined(EEZ_FOR_LVGL)"); build.line(`#warning "EEZ_FOR_LVGL is not enabled"`); build.line(`#define EEZ_FOR_LVGL`); build.line("#endif"); } return this.result; } async buildLoadFirstScreen() { this.startBuild(); const build = this; build.indent(); build.line( `loadScreen(SCREEN_ID_${this.getScreenIdentifier(this.pages[0]).toUpperCase()});` ); build.unindent(); return this.result; } async copyBitmapFiles() { const destinationFolder = this.project.settings.build.destinationFolder; if (!destinationFolder) { return; } await Promise.all( this.bitmaps.map(bitmap => (async () => { const output = "ui_image_" + this.bitmapNames.get(bitmap.objID)!; if ( this.project.settings.build.imageExportMode == "binary" ) { // write BIN file try { let source = (await getLvglBitmapSourceFile( bitmap, this.getImageVariableName(bitmap), true )) as ArrayBuffer; await writeBinaryData( this.project._store.getAbsoluteFilePath( destinationFolder ) + "/" + (this.project.settings.build .separateFolderForImagesAndFonts ? "images/" : "") + output + ".bin", Buffer.from(source) ); } catch (err) { this.project._store.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, `Error generating bitmap file '${output}.bin': ${err}` ); } } else { // write C file try { let source = await getLvglBitmapSourceFile( bitmap, this.getImageVariableName(bitmap) ); source = `#ifdef __has_include #if __has_include("lvgl.h") #ifndef LV_LVGL_H_INCLUDE_SIMPLE #define LV_LVGL_H_INCLUDE_SIMPLE #endif #endif #endif ${source}`; // ensure consistent newlines accross all platforms // (LF only) to avoid unnecessary VCS diffs source = source.replace(/\r\n/g, "\n"); // Windows source = source.replace(/\r/g, "\n"); // old Mac OS await writeTextFile( this.project._store.getAbsoluteFilePath( destinationFolder ) + "/" + (this.project.settings.build .separateFolderForImagesAndFonts ? "images/" : "") + output + ".c", source ); } catch (err) { this.project._store.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, `Error generating bitmap file '${output}.c': ${err}` ); } } })() ) ); } async copyFontFiles() { const destinationFolder = this.project.settings.build.destinationFolder; if (!destinationFolder) { return; } await Promise.all( this.fonts.map(font => (async () => { if ( this.project.settings.build.fontExportMode == "binary" && !font.lvglUseFreeType ) { const lvglBinaryFileBase64 = await font.getLvglBinFileAsync(); const lvglBinaryFile = lvglBinaryFileBase64 ? Buffer.from(lvglBinaryFileBase64, "base64") : undefined; if (lvglBinaryFile) { const output = getName( "ui_font_", font.name || "", NamingConvention.UnderscoreLowerCase ); try { await writeBinaryData( this.project._store.getAbsoluteFilePath( destinationFolder ) + "/" + (this.project.settings.build .separateFolderForImagesAndFonts ? "fonts/" : "") + output + ".bin", lvglBinaryFile ); } catch (err) { this.project._store.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, `Error writing font file '${output}.bin': ${err}` ); } } } else if (!font.lvglUseFreeType) { const lvglSourceFile = await font.getLvglSourceFile(); if (lvglSourceFile) { const output = getName( "ui_font_", font.name || "", NamingConvention.UnderscoreLowerCase ); try { await writeTextFile( this.project._store.getAbsoluteFilePath( destinationFolder ) + "/" + (this.project.settings.build .separateFolderForImagesAndFonts ? "fonts/" : "") + output + ".c", lvglSourceFile ); } catch (err) { this.project._store.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, `Error writing font file '${output}.c': ${err}` ); } } } })() ) ); } } export async function generateSourceCodeForEezFramework( project: Project, destinationFolderPath: string, isUsingCrypyoSha256: boolean ) { try { await fs.promises.rm(destinationFolderPath + "/eez-flow.cpp"); } catch (err) {} try { await fs.promises.rm(destinationFolderPath + "/eez-flow.h"); } catch (err) {} try { await fs.promises.rm(destinationFolderPath + "/eez-flow-lz4.c"); } catch (err) {} try { await fs.promises.rm(destinationFolderPath + "/eez-flow-lz4.h"); } catch (err) {} try { await fs.promises.rm(destinationFolderPath + "/eez-flow-sha256.c"); } catch (err) {} try { await fs.promises.rm(destinationFolderPath + "/eez-flow-sha256.h"); } catch (err) {} if ( !( project.projectTypeTraits.isLVGL && project.projectTypeTraits.hasFlowSupport && project.settings.build.generateSourceCodeForEezFramework ) ) { return; } // post fix structs.h try { let structs_H = await fs.promises.readFile( destinationFolderPath + "/structs.h", "utf-8" ); structs_H = structs_H.replace(`#include \n`, ""); await writeTextFile(destinationFolderPath + "/structs.h", structs_H); } catch (err) {} // post fix ui.h try { let ui_H = await fs.promises.readFile( destinationFolderPath + "/ui.h", "utf-8" ); ui_H = ui_H.replace( `#if defined(EEZ_FOR_LVGL)\n#include \n#endif\n`, "" ); await writeTextFile(destinationFolderPath + "/ui.h", ui_H); } catch (err) {} const eezframeworkAmalgamationPath = isDev ? resolve(`${sourceRootDir()}/../resources/eez-framework-amalgamation`) : process.resourcesPath! + "/eez-framework-amalgamation"; // Copy eez-flow.h (will be modified below) const eezFlowH = await fs.promises.readFile( eezframeworkAmalgamationPath + "/eez-flow.h", "utf-8" ); await writeTextFile(destinationFolderPath + "/eez-flow.h", eezFlowH); let eezH = await fs.promises.readFile( destinationFolderPath + "/eez-flow.h", "utf-8" ); let defines = []; if (project.settings.build.compressFlowDefinition) { const lz4C = await fs.promises.readFile( eezframeworkAmalgamationPath + "/eez-flow-lz4.c", "utf-8" ); await writeTextFile(destinationFolderPath + "/eez-flow-lz4.c", lz4C); const lz4H = await fs.promises.readFile( eezframeworkAmalgamationPath + "/eez-flow-lz4.h", "utf-8" ); await writeTextFile(destinationFolderPath + "/eez-flow-lz4.h", lz4H); } else { eezH = eezH.replace( "#define EEZ_FOR_LVGL_LZ4_OPTION 1", "#define EEZ_FOR_LVGL_LZ4_OPTION 0" ); defines.push("EEZ_FOR_LVGL_LZ4_OPTION=0"); } if (isUsingCrypyoSha256) { const sha256C = await fs.promises.readFile( eezframeworkAmalgamationPath + "/eez-flow-sha256.c", "utf-8" ); await writeTextFile( destinationFolderPath + "/eez-flow-sha256.c", sha256C ); const sha256H = await fs.promises.readFile( eezframeworkAmalgamationPath + "/eez-flow-sha256.h", "utf-8" ); await writeTextFile( destinationFolderPath + "/eez-flow-sha256.h", sha256H ); } else { eezH = eezH.replace( "#define EEZ_FOR_LVGL_SHA256_OPTION 1", "#define EEZ_FOR_LVGL_SHA256_OPTION 0" ); defines.push("EEZ_FOR_LVGL_SHA256_OPTION=0"); } eezH = eezH.replace( "#define EEZ_FLOW_QUEUE_SIZE 1000", "#define EEZ_FLOW_QUEUE_SIZE " + project.settings.build.executionQueueSize ); eezH = eezH.replace( "#define EEZ_FLOW_EVAL_STACK_SIZE 20", "#define EEZ_FLOW_EVAL_STACK_SIZE " + project.settings.build.expressionEvaluatorStackSize ); eezH = eezH.replace( "#include ", `#include <${project.settings.build.lvglInclude}>` ); if (project.settings.build.lvglInclude != "lvgl/lvgl.h") { eezH = eezH.replace( "#include ", ` #ifdef __has_include #if __has_include("lvgl_private.h") #include "lvgl_private.h" #elif __has_include("src/lvgl_private.h") #include "src/lvgl_private.h" #elif __has_include("lvgl/src/lvgl_private.h") #include "lvgl/src/lvgl_private.h" #endif #endif` ); } if (defines.length > 0) { eezH = cleanupSourceFile(eezH, defines); } await writeTextFile(destinationFolderPath + "/eez-flow.h", eezH); // Copy eez-flow.cpp let eezFlowCpp = await fs.promises.readFile( eezframeworkAmalgamationPath + "/eez-flow.cpp", "utf-8" ); if (defines.length > 0) { eezFlowCpp = cleanupSourceFile(eezFlowCpp, defines); } await writeTextFile(destinationFolderPath + "/eez-flow.cpp", eezFlowCpp); project._store.outputSectionsStore.write( Section.OUTPUT, MessageType.INFO, `EEZ Flow engine built` ); }