""" This is the base of the import tree for LVGL. It contains constant definitions used elsewhere. Constants already defined in esphome.const are not duplicated here and must be imported where used. """ import logging from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS from esphome.core import ID, Lambda from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_types import uint32 from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from .helpers import requires_component LOGGER = logging.getLogger(__name__) lvgl_ns = cg.esphome_ns.namespace("lvgl") lv_defines = {} # Dict of #defines to provide as build flags def add_define(macro, value="1"): if macro in lv_defines and lv_defines[macro] != value: LOGGER.error( "Redefinition of %s - was %s now %s", macro, lv_defines[macro], value ) lv_defines[macro] = value def literal(arg) -> MockObj: if isinstance(arg, str): return MockObj(arg) return arg def static_cast(type, value): return literal(f"static_cast<{type}>({value})") def call_lambda(lamb: LambdaExpression): expr = lamb.content.strip() if expr.startswith("return") and expr.endswith(";"): return expr[6:][:-1].strip() return f"{lamb}()" class LValidator: """ A validator for a particular type used in LVGL. Usable in configs as a validator, also has `process()` to convert a value during code generation """ def __init__(self, validator, rtype, retmapper=None, requires=None): self.validator = validator self.rtype = rtype self.retmapper = retmapper self.requires = requires def __call__(self, value): if self.requires: value = requires_component(self.requires)(value) if isinstance(value, cv.Lambda): return cv.returning_lambda(value) return self.validator(value) async def process(self, value, args=()): if value is None: return None if isinstance(value, Lambda): return cg.RawExpression( call_lambda( await cg.process_lambda(value, args, return_type=self.rtype) ) ) if self.retmapper is not None: return self.retmapper(value) if isinstance(value, ID): return await cg.get_variable(value) if isinstance(value, list): value = [ await cg.get_variable(x) if isinstance(x, ID) else x for x in value ] return cg.safe_exp(value) class LvConstant(LValidator): """ Allow one of a list of choices, mapped to upper case, and prepend the choice with the prefix. It's also permitted to include the prefix in the value The property `one_of` has the single case validator, and `several_of` allows a list of constants. """ def __init__(self, prefix: str, *choices): self.prefix = prefix self.choices = choices prefixed_choices = [prefix + v for v in choices] prefixed_validator = cv.one_of(*prefixed_choices, upper=True) @schema_extractor("one_of") def validator(value): if value == SCHEMA_EXTRACT: return self.choices if isinstance(value, str) and value.startswith(self.prefix): return prefixed_validator(value) return self.prefix + cv.one_of(*choices, upper=True)(value) super().__init__(validator, rtype=uint32) self.retmapper = self.mapper self.one_of = LValidator(validator, uint32, retmapper=self.mapper) self.several_of = LValidator( cv.ensure_list(self.one_of), uint32, retmapper=self.mapper ) def mapper(self, value): if not isinstance(value, list): value = [value] return literal( "|".join( [ str(v) if str(v).startswith(self.prefix) else self.prefix + str(v) for v in value ] ).upper() ) def extend(self, *choices): """ Extend an LVconstant with additional choices. :param choices: The extra choices :return: A new LVConstant instance """ return LvConstant(self.prefix, *(self.choices + choices)) # Parts CONF_MAIN = "main" CONF_SCROLLBAR = "scrollbar" CONF_INDICATOR = "indicator" CONF_KNOB = "knob" CONF_SELECTED = "selected" CONF_TICKS = "ticks" CONF_CURSOR = "cursor" CONF_TEXTAREA_PLACEHOLDER = "textarea_placeholder" # Layout types TYPE_FLEX = "flex" TYPE_GRID = "grid" TYPE_NONE = "none" DIRECTIONS = LvConstant("LV_DIR_", "LEFT", "RIGHT", "BOTTOM", "TOP") LV_FONTS = list(f"montserrat_{s}" for s in range(8, 50, 2)) + [ "dejavu_16_persian_hebrew", "simsun_16_cjk", "unscii_8", "unscii_16", ] LV_EVENT_MAP = { "PRESS": "PRESSED", "SHORT_CLICK": "SHORT_CLICKED", "LONG_PRESS": "LONG_PRESSED", "LONG_PRESS_REPEAT": "LONG_PRESSED_REPEAT", "CLICK": "CLICKED", "RELEASE": "RELEASED", "SCROLL_BEGIN": "SCROLL_BEGIN", "SCROLL_END": "SCROLL_END", "SCROLL": "SCROLL", "FOCUS": "FOCUSED", "DEFOCUS": "DEFOCUSED", "READY": "READY", "CANCEL": "CANCEL", "ALL_EVENTS": "ALL", "CHANGE": "VALUE_CHANGED", "GESTURE": "GESTURE", } LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) SWIPE_TRIGGERS = tuple( f"on_swipe_{x.lower()}" for x in DIRECTIONS.choices + ("up", "down") ) LV_ANIM = LvConstant( "LV_SCR_LOAD_ANIM_", "NONE", "OVER_LEFT", "OVER_RIGHT", "OVER_TOP", "OVER_BOTTOM", "MOVE_LEFT", "MOVE_RIGHT", "MOVE_TOP", "MOVE_BOTTOM", "FADE_IN", "FADE_OUT", "OUT_LEFT", "OUT_RIGHT", "OUT_TOP", "OUT_BOTTOM", ) LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER") LV_DITHER = LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF") LV_LOG_LEVELS = { "VERBOSE": "TRACE", "DEBUG": "TRACE", "INFO": "INFO", "WARN": "WARN", "ERROR": "ERROR", "NONE": "NONE", } LV_LONG_MODES = LvConstant( "LV_LABEL_LONG_", "WRAP", "DOT", "SCROLL", "SCROLL_CIRCULAR", "CLIP", ) STATES = ( # default state not included here "checked", "focused", "focus_key", "edited", "hovered", "pressed", "scrolled", "disabled", "user_1", "user_2", "user_3", "user_4", ) PARTS = ( CONF_MAIN, CONF_SCROLLBAR, CONF_INDICATOR, CONF_KNOB, CONF_SELECTED, CONF_ITEMS, CONF_TICKS, CONF_CURSOR, CONF_TEXTAREA_PLACEHOLDER, ) KEYBOARD_MODES = LvConstant( "LV_KEYBOARD_MODE_", "TEXT_LOWER", "TEXT_UPPER", "SPECIAL", "NUMBER", ) ROLLER_MODES = LvConstant("LV_ROLLER_MODE_", "NORMAL", "INFINITE") TILE_DIRECTIONS = DIRECTIONS.extend("HOR", "VER", "ALL") CHILD_ALIGNMENTS = LvConstant( "LV_ALIGN_", "TOP_LEFT", "TOP_MID", "TOP_RIGHT", "LEFT_MID", "CENTER", "RIGHT_MID", "BOTTOM_LEFT", "BOTTOM_MID", "BOTTOM_RIGHT", ) SIBLING_ALIGNMENTS = LvConstant( "LV_ALIGN_", "OUT_LEFT_TOP", "OUT_TOP_LEFT", "OUT_TOP_MID", "OUT_TOP_RIGHT", "OUT_RIGHT_TOP", "OUT_LEFT_MID", "OUT_RIGHT_MID", "OUT_LEFT_BOTTOM", "OUT_BOTTOM_LEFT", "OUT_BOTTOM_MID", "OUT_BOTTOM_RIGHT", "OUT_RIGHT_BOTTOM", ) ALIGN_ALIGNMENTS = CHILD_ALIGNMENTS.extend(*SIBLING_ALIGNMENTS.choices) FLEX_FLOWS = LvConstant( "LV_FLEX_FLOW_", "ROW", "COLUMN", "ROW_WRAP", "COLUMN_WRAP", "ROW_REVERSE", "COLUMN_REVERSE", "ROW_WRAP_REVERSE", "COLUMN_WRAP_REVERSE", ) OBJ_FLAGS = ( "hidden", "clickable", "click_focusable", "checkable", "scrollable", "scroll_elastic", "scroll_momentum", "scroll_one", "scroll_chain_hor", "scroll_chain_ver", "scroll_chain", "scroll_on_focus", "scroll_with_arrow", "snappable", "press_lock", "event_bubble", "gesture_bubble", "adv_hittest", "ignore_layout", "floating", "overflow_visible", "layout_1", "layout_2", "widget_1", "widget_2", "user_1", "user_2", "user_3", "user_4", ) ARC_MODES = LvConstant("LV_ARC_MODE_", "NORMAL", "REVERSE", "SYMMETRICAL") BAR_MODES = LvConstant("LV_BAR_MODE_", "NORMAL", "SYMMETRICAL", "RANGE") BUTTONMATRIX_CTRLS = LvConstant( "LV_BTNMATRIX_CTRL_", "HIDDEN", "NO_REPEAT", "DISABLED", "CHECKABLE", "CHECKED", "CLICK_TRIG", "POPOVER", "RECOLOR", "CUSTOM_1", "CUSTOM_2", ) LV_BASE_ALIGNMENTS = ( "START", "CENTER", "END", ) LV_CELL_ALIGNMENTS = LvConstant( "LV_GRID_ALIGN_", *LV_BASE_ALIGNMENTS, ) LV_GRID_ALIGNMENTS = LV_CELL_ALIGNMENTS.extend( "STRETCH", "SPACE_EVENLY", "SPACE_AROUND", "SPACE_BETWEEN", ) LV_FLEX_ALIGNMENTS = LvConstant( "LV_FLEX_ALIGN_", *LV_BASE_ALIGNMENTS, "SPACE_EVENLY", "SPACE_AROUND", "SPACE_BETWEEN", ) LV_MENU_MODES = LvConstant( "LV_MENU_HEADER_", "TOP_FIXED", "TOP_UNFIXED", "BOTTOM_FIXED", ) LV_CHART_TYPES = ( "NONE", "LINE", "BAR", "SCATTER", ) LV_CHART_AXES = ( "PRIMARY_Y", "SECONDARY_Y", "PRIMARY_X", "SECONDARY_X", ) CONF_ACCEPTED_CHARS = "accepted_chars" CONF_ADJUSTABLE = "adjustable" CONF_ALIGN = "align" CONF_ALIGN_TO = "align_to" CONF_ANIMATED = "animated" CONF_ANIMATION = "animation" CONF_ANTIALIAS = "antialias" CONF_ARC_LENGTH = "arc_length" CONF_AUTO_START = "auto_start" CONF_BACKGROUND_STYLE = "background_style" CONF_BUTTON_STYLE = "button_style" CONF_DECIMAL_PLACES = "decimal_places" CONF_COLUMN = "column" CONF_DIGITS = "digits" CONF_DISP_BG_COLOR = "disp_bg_color" CONF_DISP_BG_IMAGE = "disp_bg_image" CONF_DISP_BG_OPA = "disp_bg_opa" CONF_BODY = "body" CONF_BUTTONS = "buttons" CONF_BYTE_ORDER = "byte_order" CONF_CHANGE_RATE = "change_rate" CONF_CLOSE_BUTTON = "close_button" CONF_COLOR_DEPTH = "color_depth" CONF_CONTROL = "control" CONF_DEFAULT_FONT = "default_font" CONF_DEFAULT_GROUP = "default_group" CONF_DIR = "dir" CONF_DISPLAYS = "displays" CONF_EDITING = "editing" CONF_ENCODERS = "encoders" CONF_END_ANGLE = "end_angle" CONF_END_VALUE = "end_value" CONF_ENTER_BUTTON = "enter_button" CONF_ENTRIES = "entries" CONF_FLAGS = "flags" CONF_FLEX_FLOW = "flex_flow" CONF_FLEX_ALIGN_MAIN = "flex_align_main" CONF_FLEX_ALIGN_CROSS = "flex_align_cross" CONF_FLEX_ALIGN_TRACK = "flex_align_track" CONF_FLEX_GROW = "flex_grow" CONF_FREEZE = "freeze" CONF_FULL_REFRESH = "full_refresh" CONF_GRADIENTS = "gradients" CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos" CONF_GRID_CELL_COLUMN_POS = "grid_cell_column_pos" CONF_GRID_CELL_ROW_SPAN = "grid_cell_row_span" CONF_GRID_CELL_COLUMN_SPAN = "grid_cell_column_span" CONF_GRID_CELL_X_ALIGN = "grid_cell_x_align" CONF_GRID_CELL_Y_ALIGN = "grid_cell_y_align" CONF_GRID_COLUMN_ALIGN = "grid_column_align" CONF_GRID_COLUMNS = "grid_columns" CONF_GRID_ROW_ALIGN = "grid_row_align" CONF_GRID_ROWS = "grid_rows" CONF_HEADER_MODE = "header_mode" CONF_HOME = "home" CONF_INITIAL_FOCUS = "initial_focus" CONF_KEY_CODE = "key_code" CONF_KEYPADS = "keypads" CONF_LAYOUT = "layout" CONF_LEFT_BUTTON = "left_button" CONF_LINE_WIDTH = "line_width" CONF_LOG_LEVEL = "log_level" CONF_LONG_PRESS_TIME = "long_press_time" CONF_LONG_PRESS_REPEAT_TIME = "long_press_repeat_time" CONF_LVGL_ID = "lvgl_id" CONF_LONG_MODE = "long_mode" CONF_MSGBOXES = "msgboxes" CONF_OBJ = "obj" CONF_ONE_CHECKED = "one_checked" CONF_ONE_LINE = "one_line" CONF_ON_PAUSE = "on_pause" CONF_ON_RESUME = "on_resume" CONF_ON_SELECT = "on_select" CONF_OPA = "opa" CONF_NEXT = "next" CONF_PAD_ROW = "pad_row" CONF_PAD_COLUMN = "pad_column" CONF_PAGE = "page" CONF_PAGE_WRAP = "page_wrap" CONF_PASSWORD_MODE = "password_mode" CONF_PIVOT_X = "pivot_x" CONF_PIVOT_Y = "pivot_y" CONF_PLACEHOLDER_TEXT = "placeholder_text" CONF_POINTS = "points" CONF_PREVIOUS = "previous" CONF_REPEAT_COUNT = "repeat_count" CONF_RECOLOR = "recolor" CONF_RESUME_ON_INPUT = "resume_on_input" CONF_RIGHT_BUTTON = "right_button" CONF_ROLLOVER = "rollover" CONF_ROOT_BACK_BTN = "root_back_btn" CONF_ROWS = "rows" CONF_SCALE_LINES = "scale_lines" CONF_SCROLLBAR_MODE = "scrollbar_mode" CONF_SELECTED_INDEX = "selected_index" CONF_SELECTED_TEXT = "selected_text" CONF_SHOW_SNOW = "show_snow" CONF_SPIN_TIME = "spin_time" CONF_SRC = "src" CONF_START_ANGLE = "start_angle" CONF_START_VALUE = "start_value" CONF_STATES = "states" CONF_STYLE = "style" CONF_STYLES = "styles" CONF_STYLE_DEFINITIONS = "style_definitions" CONF_STYLE_ID = "style_id" CONF_SKIP = "skip" CONF_SYMBOL = "symbol" CONF_TAB_ID = "tab_id" CONF_TABS = "tabs" CONF_TIME_FORMAT = "time_format" CONF_TILE = "tile" CONF_TILE_ID = "tile_id" CONF_TILES = "tiles" CONF_TITLE = "title" CONF_TOP_LAYER = "top_layer" CONF_TOUCHSCREENS = "touchscreens" CONF_TRANSPARENCY_KEY = "transparency_key" CONF_THEME = "theme" CONF_UPDATE_ON_RELEASE = "update_on_release" CONF_VISIBLE_ROW_COUNT = "visible_row_count" CONF_WIDGET = "widget" CONF_WIDGETS = "widgets" CONF_ZOOM = "zoom" # Keypad keys LV_KEYS = LvConstant( "LV_KEY_", "UP", "DOWN", "RIGHT", "LEFT", "ESC", "DEL", "BACKSPACE", "ENTER", "NEXT", "PREV", "HOME", "END", ) DEFAULT_ESPHOME_FONT = "esphome_lv_default_font" def join_enums(enums, prefix=""): enums = list(enums) enums.sort() # If a prefix is provided, prepend each constant with the prefix, and assume that all the constants are within the # same namespace, otherwise cast to int to avoid triggering warnings about mixing enum types. if prefix: return literal("|".join(f"{prefix}{e.upper()}" for e in enums)) return literal("|".join(f"(int){e.upper()}" for e in enums))