Add support for Text Run Panel grabbing run text from a Sensor or Text Sensor

This commit is contained in:
Michael Davidson 2024-01-02 17:37:36 +11:00
parent 4e37a09310
commit c5ed675be5
No known key found for this signature in database
GPG Key ID: B8D1A99712B8B0EB
3 changed files with 134 additions and 22 deletions

View File

@ -19,8 +19,8 @@ void TextRunPanel::dump_config(int indent_depth, int additional_level_depth) {
ESP_LOGCONFIG(TAG, "%*sText Align: %s", indent_depth, "", ESP_LOGCONFIG(TAG, "%*sText Align: %s", indent_depth, "",
LOG_STR_ARG(display::text_align_to_string(this->text_align_))); LOG_STR_ARG(display::text_align_to_string(this->text_align_)));
ESP_LOGCONFIG(TAG, "%*sText Runs: %i", indent_depth, "", this->text_runs_.size()); ESP_LOGCONFIG(TAG, "%*sText Runs: %i", indent_depth, "", this->text_runs_.size());
for (TextRun *run : this->text_runs_) { for (TextRunBase *run : this->text_runs_) {
std::string text = run->text_.value(); std::string text = run->get_text();
ESP_LOGCONFIG(TAG, "%*sText: %s", indent_depth + additional_level_depth, "", text.c_str()); ESP_LOGCONFIG(TAG, "%*sText: %s", indent_depth + additional_level_depth, "", text.c_str());
} }
} }
@ -72,12 +72,12 @@ CalculatedLayout TextRunPanel::determine_layout(display::Display *display, displ
int widest_line = 0; int widest_line = 0;
int line_number = 0; int line_number = 0;
for (TextRun *run : this->text_runs_) { for (TextRunBase *run : this->text_runs_) {
int x1; int x1;
int width; int width;
int height; int height;
int baseline; int baseline;
std::string text = run->text_.value(); std::string text = run->get_text();
run->font_->measure(text.c_str(), &width, &x1, &baseline, &height); run->font_->measure(text.c_str(), &width, &x1, &baseline, &height);

View File

@ -1,10 +1,14 @@
#pragma once #pragma once
#include <utility> #include <utility>
#include <iomanip>
#include <sstream>
#include "esphome/components/graphical_layout/graphical_layout.h" #include "esphome/components/graphical_layout/graphical_layout.h"
#include "esphome/components/font/font.h" #include "esphome/components/font/font.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome { namespace esphome {
namespace graphical_layout { namespace graphical_layout {
@ -25,25 +29,85 @@ struct CanWrapAtCharacterArguments {
char character; char character;
}; };
class TextRun { class TextRunBase {
public: public:
TextRun(TemplatableValue<std::string> text, display::BaseFont *font) { TextRunBase(display::BaseFont *font) {
this->text_ = std::move(text);
this->font_ = font; this->font_ = font;
} }
void set_foreground_color(Color foreground_color) { this->foreground_color_ = foreground_color; } 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_background_color(Color background_color) { this->background_color_ = background_color; }
virtual std::string get_text() = 0;
TemplatableValue<std::string> text_{};
display::BaseFont *font_{nullptr}; display::BaseFont *font_{nullptr};
Color foreground_color_{COLOR_ON}; Color foreground_color_{COLOR_ON};
Color background_color_{COLOR_OFF}; Color background_color_{COLOR_OFF};
}; };
class FormattableTextRun {
public:
template<typename V> void set_text_formatter(V text_formatter) { this->text_formatter_ = text_formatter; };
std::string format_text(std::string string) {
if (this->text_formatter_.has_value()) {
return this->text_formatter_.value(string);
}
return string;
}
protected:
TemplatableValue<std::string, const std::string> text_formatter_{};
};
class TextRun : public TextRunBase, public FormattableTextRun {
public:
TextRun(TemplatableValue<std::string> text, display::BaseFont *font) : TextRunBase(font) {
this->text_ = std::move(text);
}
std::string get_text() override {
return this->format_text(text_.value());
}
protected:
TemplatableValue<std::string> text_{};
};
class SensorTextRun : public TextRunBase, public FormattableTextRun {
public:
SensorTextRun(sensor::Sensor *sensor, display::BaseFont *font) : TextRunBase(font) {
this->sensor_ = sensor;
}
std::string get_text() override {
std::stringstream stream;
stream << std::fixed << std::setprecision(this->sensor_->get_accuracy_decimals()) << this->sensor_->get_state();
return this->format_text(stream.str());
}
protected:
sensor::Sensor *sensor_{nullptr};
};
class TextSensorTextRun : public TextRunBase, public FormattableTextRun {
public:
TextSensorTextRun(text_sensor::TextSensor *text_sensor, display::BaseFont *font) : TextRunBase(font) {
this->text_sensor_ = text_sensor;
}
std::string get_text() override {
return this->format_text(this->text_sensor_->get_state());
}
protected:
text_sensor::TextSensor *text_sensor_{nullptr};
};
class CalculatedTextRun { class CalculatedTextRun {
public: public:
CalculatedTextRun(TextRun *run, std::string text, display::Rect bounds, int16_t baseline, int16_t line_number) { CalculatedTextRun(TextRunBase *run, std::string text, display::Rect bounds, int16_t baseline, int16_t line_number) {
this->run = run; this->run = run;
this->text_ = std::move(text); this->text_ = std::move(text);
this->bounds = bounds; this->bounds = bounds;
@ -53,7 +117,7 @@ class CalculatedTextRun {
std::string text_; std::string text_;
display::Rect bounds; display::Rect bounds;
TextRun *run; TextRunBase *run;
int16_t line_number_; int16_t line_number_;
int16_t baseline; int16_t baseline;
}; };
@ -81,14 +145,14 @@ class TextRunPanel : public LayoutItem {
this->can_wrap_at_character_ = can_wrap_at_character; this->can_wrap_at_character_ = can_wrap_at_character;
}; };
void add_text_run(TextRun *text_run) { this->text_runs_.push_back(text_run); }; void add_text_run(TextRunBase *text_run) { this->text_runs_.push_back(text_run); };
void set_text_align(display::TextAlign text_align) { this->text_align_ = text_align; }; void set_text_align(display::TextAlign text_align) { this->text_align_ = text_align; };
void set_min_width(int min_width) { this->min_width_ = min_width; }; void set_min_width(int min_width) { this->min_width_ = min_width; };
void set_max_width(int max_width) { this->max_width_ = max_width; }; void set_max_width(int max_width) { this->max_width_ = max_width; };
void set_debug_outline_runs(bool debug_outline_runs) { this->debug_outline_runs_ = debug_outline_runs; }; void set_debug_outline_runs(bool debug_outline_runs) { this->debug_outline_runs_ = debug_outline_runs; };
protected: protected:
std::vector<TextRun *> text_runs_; std::vector<TextRunBase *> text_runs_;
display::TextAlign text_align_{display::TextAlign::TOP_LEFT}; display::TextAlign text_align_{display::TextAlign::TOP_LEFT};
int min_width_{0}; int min_width_{0};
int max_width_{0}; int max_width_{0};

View File

@ -1,13 +1,16 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import font, color from esphome.components import font, color, sensor, text_sensor
from esphome.components.display import display_ns from esphome.components.display import display_ns
from esphome.const import CONF_ID, CONF_FOREGROUND_COLOR, CONF_BACKGROUND_COLOR from esphome.const import CONF_ID, CONF_FOREGROUND_COLOR, CONF_BACKGROUND_COLOR, CONF_SENSOR
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout") graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
TextRunPanel = graphical_layout_ns.class_("TextRunPanel") TextRunPanel = graphical_layout_ns.class_("TextRunPanel")
TextAlign = display_ns.enum("TextAlign", is_class=True) TextAlign = display_ns.enum("TextAlign", is_class=True)
TextRun = graphical_layout_ns.class_("TextRun") TextRunBase = graphical_layout_ns.class_("TextRunBase")
TextRun = graphical_layout_ns.class_("TextRun", TextRunBase)
SensorTextRun = graphical_layout_ns.class_("SensorTextRun", TextRunBase)
TextSensorTextRun = graphical_layout_ns.class_("TextSensorTextRun", TextRunBase)
CanWrapAtCharacterArguments = graphical_layout_ns.struct("CanWrapAtCharacterArguments") CanWrapAtCharacterArguments = graphical_layout_ns.struct("CanWrapAtCharacterArguments")
CanWrapAtCharacterArgumentsConstRef = CanWrapAtCharacterArguments.operator( CanWrapAtCharacterArgumentsConstRef = CanWrapAtCharacterArguments.operator(
"const" "const"
@ -22,6 +25,8 @@ CONF_MIN_WIDTH = "min_width"
CONF_RUNS = "runs" CONF_RUNS = "runs"
CONF_CAN_WRAP_AT_CHARACTER = "can_wrap_at_character" CONF_CAN_WRAP_AT_CHARACTER = "can_wrap_at_character"
CONF_DEBUG_OUTLINE_RUNS = "debug_outline_runs" CONF_DEBUG_OUTLINE_RUNS = "debug_outline_runs"
CONF_TEXT_SENSOR = "text_sensor"
CONF_TEXT_FORMATTER = "text_formatter"
TEXT_ALIGN = { TEXT_ALIGN = {
"TOP_LEFT": TextAlign.TOP_LEFT, "TOP_LEFT": TextAlign.TOP_LEFT,
@ -38,16 +43,44 @@ TEXT_ALIGN = {
"BOTTOM_RIGHT": TextAlign.BOTTOM_RIGHT, "BOTTOM_RIGHT": TextAlign.BOTTOM_RIGHT,
} }
RUN_SCHEMA = cv.Schema( BASE_RUN_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(TextRun),
cv.Required(CONF_FONT): cv.use_id(font.Font), cv.Required(CONF_FONT): cv.use_id(font.Font),
cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct), cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct),
cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color.ColorStruct), cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color.ColorStruct),
cv.Required(CONF_TEXT): cv.templatable(cv.string),
} }
) )
SENSOR_TEXT_RUN_SCHEMA = BASE_RUN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(SensorTextRun),
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
cv.Optional(CONF_TEXT_FORMATTER): cv.returning_lambda,
}
)
TEXT_RUN_SCHEMA = BASE_RUN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TextRun),
cv.Required(CONF_TEXT): cv.templatable(cv.string),
cv.Optional(CONF_TEXT_FORMATTER): cv.returning_lambda,
}
)
TEXT_SENSOR_TEXT_RUN_SCHEMA = BASE_RUN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TextSensorTextRun),
cv.Required(CONF_TEXT_SENSOR): cv.use_id(text_sensor.TextSensor),
cv.Optional(CONF_TEXT_FORMATTER): cv.returning_lambda,
}
)
RUN_SCHEMA = cv.Any(
SENSOR_TEXT_RUN_SCHEMA,
TEXT_RUN_SCHEMA,
TEXT_SENSOR_TEXT_RUN_SCHEMA,
)
def get_config_schema(base_item_schema, item_type_schema): def get_config_schema(base_item_schema, item_type_schema):
return base_item_schema.extend( return base_item_schema.extend(
@ -90,12 +123,27 @@ async def config_to_layout_item(pvariable_builder, item_config, child_item_build
cg.add(var.set_debug_outline_runs(debug_outline_runs)) cg.add(var.set_debug_outline_runs(debug_outline_runs))
for run_config in item_config[CONF_RUNS]: for run_config in item_config[CONF_RUNS]:
run_text = await cg.templatable( run = None
run_config[CONF_TEXT], args=[], output_type=cg.std_string
)
run_font = await cg.get_variable(run_config[CONF_FONT]) run_font = await cg.get_variable(run_config[CONF_FONT])
if run_sensor_config := run_config.get(CONF_SENSOR):
sens = await cg.get_variable(run_sensor_config)
run = cg.new_Pvariable(run_config[CONF_ID], sens, run_font)
elif run_text_sensor_config := run_config.get(CONF_TEXT_SENSOR):
text_sens = await cg.get_variable(run_text_sensor_config)
run = cg.new_Pvariable(run_config[CONF_ID], text_sens, run_font)
else:
run_text = await cg.templatable(
run_config[CONF_TEXT], args=[], output_type=cg.std_string
)
run = cg.new_Pvariable(run_config[CONF_ID], run_text, run_font)
run = cg.new_Pvariable(run_config[CONF_ID], run_text, run_font) if run_text_formatter_config := run_config.get(CONF_TEXT_FORMATTER):
run_text_formatter = await cg.process_lambda(
run_text_formatter_config,
[(cg.std_string, "it")],
return_type=cg.std_string,
)
cg.add(run.set_text_formatter(run_text_formatter))
if run_background_color_config := run_config.get(CONF_BACKGROUND_COLOR): if run_background_color_config := run_config.get(CONF_BACKGROUND_COLOR):
run_background_color = await cg.get_variable(run_background_color_config) run_background_color = await cg.get_variable(run_background_color_config)