diff --git a/esphome/components/graphical_layout/text_panel.cpp b/esphome/components/graphical_layout/text_panel.cpp index 71c5caec20..47884a4b34 100644 --- a/esphome/components/graphical_layout/text_panel.cpp +++ b/esphome/components/graphical_layout/text_panel.cpp @@ -8,6 +8,8 @@ namespace esphome { namespace graphical_layout { static const char *const TAG = "textpanel"; +static const int TEXT_ALIGN_X_MASK = (int)display::TextAlign::RIGHT | (int)display::TextAlign::CENTER_HORIZONTAL; +static const int TEXT_ALIGN_Y_MASK = (int)display::TextAlign::BOTTOM | (int)display::TextAlign::BASELINE | (int)display::TextAlign::CENTER_VERTICAL; void TextPanel::dump_config(int indent_depth, int additional_level_depth) { ESP_LOGCONFIG(TAG, "%*sText: %s", indent_depth, "", this->text_.c_str()); @@ -18,15 +20,61 @@ display::Rect TextPanel::measure_item_internal(display::Display *display) { int y1; int width; int height; - - display->get_text_bounds(0, 0, this->text_.c_str(), this->font_, display::TextAlign::TOP_LEFT, &x1, &y1, &width, + display->get_text_bounds(0, 0, this->text_.c_str(), this->font_, this->text_align_, &x1, &y1, &width, &height); - return display::Rect(0, 0, width, height); } void TextPanel::render_internal(display::Display *display, display::Rect bounds) { - display->print(0, 0, this->font_, this->foreground_color_, display::TextAlign::TOP_LEFT, this->text_.c_str()); + int width, height, x_offset, baseline; + + this->font_->measure(this->text_.c_str(), &width, &x_offset, &baseline, &height); + + const auto x_align = display::TextAlign(int(this->text_align_) & TEXT_ALIGN_X_MASK); + const auto y_align = display::TextAlign(int(this->text_align_) & TEXT_ALIGN_Y_MASK); + + display::Rect text_bounds(0, 0, bounds.w, bounds.h); + + switch (x_align) { + case display::TextAlign::RIGHT: { + bounds.x = bounds.w - width; + break; + } + case display::TextAlign::CENTER_HORIZONTAL: { + bounds.x = (bounds.w - width) / 2; + break; + } + case display::TextAlign::LEFT: + default: { + // LEFT + bounds.x = 0; + break; + } + } + + switch (y_align) { + case display::TextAlign::BOTTOM: { + bounds.y = bounds.h - height; + break; + } + case display::TextAlign::BASELINE: { + bounds.y = (bounds.h - height) + baseline; + break; + } + case display::TextAlign::CENTER_VERTICAL: { + bounds.y = (bounds.h - height) / 2; + break; + } + case display::TextAlign::TOP: + default: { + bounds.y = 0; + break; + } + } + + auto rendered_alignment = display::TextAlign::TOP_LEFT; + display->print(bounds.x, bounds.y, this->font_, this->foreground_color_, rendered_alignment, this->text_.c_str()); + } } // namespace graphical_layout diff --git a/esphome/components/graphical_layout/text_panel.h b/esphome/components/graphical_layout/text_panel.h index 8a5c1e7315..66610630e3 100644 --- a/esphome/components/graphical_layout/text_panel.h +++ b/esphome/components/graphical_layout/text_panel.h @@ -23,11 +23,13 @@ class TextPanel : public LayoutItem { void set_font(display::BaseFont *font) { this->font_ = font; }; void set_foreground_color(Color foreground_color) { this->foreground_color_ = foreground_color; }; void set_background_color(Color background_color) { this->background_color_ = background_color; }; + void set_text_align(display::TextAlign text_align) { this->text_align_ = text_align; }; protected: int item_padding_{0}; std::string text_{}; display::BaseFont *font_{nullptr}; + display::TextAlign text_align_{display::TextAlign::TOP_LEFT}; Color foreground_color_{COLOR_ON}; Color background_color_{COLOR_OFF}; }; diff --git a/esphome/components/graphical_layout/text_panel.py b/esphome/components/graphical_layout/text_panel.py index 5a22f9d346..f646343a7e 100644 --- a/esphome/components/graphical_layout/text_panel.py +++ b/esphome/components/graphical_layout/text_panel.py @@ -1,9 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import font, color +from esphome.components.display import display_ns graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout") TextPanel = graphical_layout_ns.class_("TextPanel") +TextAlign = display_ns.enum("TextAlign", is_class=True) CONF_ITEM_PADDING = "item_padding" CONF_TEXT_PANEL = "text_panel" @@ -11,8 +13,24 @@ CONF_FONT = "font" CONF_FOREGROUND_COLOR = "foreground_color" CONF_BACKGROUND_COLOR = "background_color" CONF_TEXT = "text" +CONF_TEXT_ALIGN = "text_align" +TEXT_ALIGN = { + "TOP_LEFT": TextAlign.TOP_LEFT, + "TOP_CENTER": TextAlign.TOP_CENTER, + "TOP_RIGHT": TextAlign.TOP_RIGHT, + "CENTER_LEFT": TextAlign.CENTER_LEFT, + "CENTER": TextAlign.CENTER, + "CENTER_RIGHT": TextAlign.CENTER_RIGHT, + "BASELINE_LEFT": TextAlign.BASELINE_LEFT, + "BASELINE_CENTER": TextAlign.BASELINE_CENTER, + "BASELINE_RIGHT": TextAlign.BASELINE_RIGHT, + "BOTTOM_LEFT": TextAlign.BOTTOM_LEFT, + "BOTTOM_CENTER": TextAlign.BOTTOM_CENTER, + "BOTTOM_RIGHT": TextAlign.BOTTOM_RIGHT, +} + def get_config_schema(base_item_schema, item_type_schema): return base_item_schema.extend( { @@ -22,6 +40,7 @@ def get_config_schema(base_item_schema, item_type_schema): cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct), cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color.ColorStruct), cv.Required(CONF_TEXT): cv.templatable(cv.string), + cv.Optional(CONF_TEXT_ALIGN): cv.enum(TEXT_ALIGN, upper=True) } ) @@ -46,4 +65,7 @@ async def config_to_layout_item(pvariable_builder, item_config, child_item_build text = await cg.templatable(item_config[CONF_TEXT], args=[], output_type=str) cg.add(var.set_text(text)) + if text_align := item_config.get(CONF_TEXT_ALIGN): + cg.add(var.set_text_align(text_align)) + return var