from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_BUTTON, CONF_ID, CONF_TEXT from esphome.core import ID from esphome.cpp_generator import MockObjClass from esphome.cpp_types import nullptr from ..defines import ( CONF_BODY, CONF_BUTTON_STYLE, CONF_BUTTONS, CONF_CLOSE_BUTTON, CONF_HEADER_BUTTONS, CONF_MAIN, CONF_MSGBOXES, CONF_SRC, CONF_TITLE, LV_OBJ_FLAG, TYPE_FLEX, add_warning, literal, ) from ..helpers import add_lv_use from ..lv_validation import lv_bool, lv_image, lv_text, pixels_or_percent from ..lvcode import EVENT_ARG, LambdaContext, LocalVariable, lv, lv_expr, lv_obj from ..schemas import ( STYLE_SCHEMA, STYLED_TEXT_SCHEMA, TEXT_SCHEMA, container_schema, part_schema, ) from ..styles import LVStyle from ..types import LV_EVENT, lv_obj_t from . import Widget, WidgetType, add_widgets, set_obj_properties, widget_to_code from .button import button_spec, lv_button_t from .img import CONF_IMAGE from .label import CONF_LABEL from .obj import obj_spec CONF_MSGBOX = "msgbox" OUTER_STYLE = LVStyle( "msgbox_outer", { "bg_opa": 0.5, "bg_color": "black", "border_width": 0, "pad_all": 0, "radius": 0, }, ) class FooterButtonType(WidgetType): def __init__(self): super().__init__( CONF_BUTTON, lv_button_t, (CONF_MAIN,), TEXT_SCHEMA, is_mock=True ) async def obj_creator(self, parent: MockObjClass, config: dict): return lv_expr.msgbox_add_footer_button(parent, config[CONF_TEXT]) footer_button_spec = FooterButtonType() class HeaderButtonType(WidgetType): def __init__(self): super().__init__( CONF_BUTTON, lv_button_t, (CONF_MAIN,), cv.Schema( { cv.Required(CONF_SRC): lv_image, } ), is_mock=True, ) async def obj_creator(self, parent: MockObjClass, config: dict): return lv_expr.msgbox_add_header_button( parent, await lv_image.process(config[CONF_SRC]) ) header_button_spec = HeaderButtonType() MSGBOX_SCHEMA = container_schema( obj_spec, STYLE_SCHEMA.extend( { cv.GenerateID(CONF_ID): cv.declare_id(lv_obj_t), cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA, cv.Optional(CONF_BODY, default=""): STYLED_TEXT_SCHEMA, cv.Optional(CONF_BUTTONS): cv.ensure_list( container_schema(footer_button_spec) ), cv.Optional(CONF_HEADER_BUTTONS): cv.ensure_list( container_schema(header_button_spec) ), cv.Optional(CONF_CLOSE_BUTTON, default=True): lv_bool, cv.Optional(CONF_BUTTON_STYLE): part_schema(button_spec.parts), } ), ) async def msgbox_to_code(top_layer, conf): """ Construct a message box. This consists of a full-screen translucent background enclosing a centered container with an optional title, body, close button and a set of footer buttons. Header buttons can be added - they can be image buttons only. The body of the message box may have any widgets the user wants to add. :param conf: The config data :return: code to add to the init lambda """ add_lv_use( TYPE_FLEX, CONF_BUTTON, CONF_LABEL, CONF_MSGBOX, CONF_IMAGE, *button_spec.get_uses(), ) if CONF_BUTTON_STYLE in conf: add_warning( "'button_style' for msgbox is deprecated - style the buttons directly." ) messagebox_id = conf[CONF_ID] outer_id = ID(f"{messagebox_id.id}_outer", type=lv_obj_t) outer = cg.Pvariable(outer_id, lv_expr.obj_create(top_layer)) outer_widget = Widget.create(outer_id.id, outer, obj_spec, conf) msgbox = cg.Pvariable(messagebox_id, lv_expr.msgbox_create(outer)) outer_widget.move_to_foreground = True msgbox_widget = Widget.create(messagebox_id, msgbox, obj_spec, conf) msgbox_widget.outer = outer_widget text = await lv_text.process(conf[CONF_BODY].get(CONF_TEXT, "")) title = await lv_text.process(conf[CONF_TITLE].get(CONF_TEXT, "")) close_button = conf[CONF_CLOSE_BUTTON] percent100 = await pixels_or_percent.process(1.0) lv_obj.set_size(outer, percent100, percent100) outer_widget.add_style(await OUTER_STYLE.get_var()) outer_widget.add_flag(LV_OBJ_FLAG.HIDDEN) lv.msgbox_add_title(msgbox, title) lv.msgbox_add_text(msgbox, text) lv_obj.set_style_align(msgbox, literal("LV_ALIGN_CENTER"), 0) await set_obj_properties(msgbox_widget, conf) await add_widgets(msgbox_widget, conf) for button in conf.get(CONF_BUTTONS, ()): await widget_to_code(button, footer_button_spec, msgbox) for button in conf.get(CONF_HEADER_BUTTONS, ()): await widget_to_code(button, header_button_spec, msgbox) async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action: outer_widget.add_flag(LV_OBJ_FLAG.HIDDEN) if close_button: with LocalVariable( "close_btn_", lv_obj_t, lv_expr.msgbox_add_close_button(msgbox) ) as close_btn: lv_obj.remove_event(close_btn, 0) lv_obj.add_event_cb( close_btn, await close_action.get_lambda(), LV_EVENT.CLICKED, nullptr, ) else: lv_obj.add_event_cb( outer, await close_action.get_lambda(), LV_EVENT.CLICKED, nullptr ) async def msgboxes_to_code(lv_component, config): top_layer = lv_expr.disp_get_layer_top(lv_component.get_disp()) for conf in config.get(CONF_MSGBOXES, ()): await msgbox_to_code(top_layer, conf)