diff --git a/esphome/components/graphical_layout/text_run_panel.cpp b/esphome/components/graphical_layout/text_run_panel.cpp index 7693f46237..53aad2811b 100644 --- a/esphome/components/graphical_layout/text_run_panel.cpp +++ b/esphome/components/graphical_layout/text_run_panel.cpp @@ -1,4 +1,5 @@ #include "text_run_panel.h" +#include #include "esphome/components/display/display.h" #include "esphome/components/display/rect.h" @@ -18,6 +19,7 @@ void TextRunPanel::dump_config(int indent_depth, int additional_level_depth) { ESP_LOGCONFIG(TAG, "%*sMax Width: %i", indent_depth, "", this->max_width_); ESP_LOGCONFIG(TAG, "%*sText Align: %s", indent_depth, "", LOG_STR_ARG(display::text_align_to_string(this->text_align_))); + ESP_LOGCONFIG(TAG, "%*sDraw Partial Lines: %s", indent_depth, "", YESNO(this->draw_partial_lines_)); ESP_LOGCONFIG(TAG, "%*sText Runs: %i", indent_depth, "", this->text_runs_.size()); for (TextRunBase *run : this->text_runs_) { std::string text = run->get_text(); @@ -43,23 +45,39 @@ display::Rect TextRunPanel::measure_item_internal(display::Display *display) { void TextRunPanel::render_internal(display::Display *display, display::Rect bounds) { ESP_LOGD(TAG, "Rendering to (%i, %i)", bounds.w, bounds.h); - CalculatedLayout layout = this->determine_layout_(display, bounds, true); + CalculatedLayout layout = this->determine_layout_(display, bounds, false); + int16_t y_offset = 0; - for (const auto &calculated : layout.runs) { - if (calculated->run->background_color_ != display::COLOR_OFF) { - display->filled_rectangle(calculated->bounds.x, calculated->bounds.y, calculated->bounds.w, calculated->bounds.h, - calculated->run->background_color_); + display::Point offset = display->get_local_coordinates(); + display::Rect clipping_rect = display::Rect(offset.x, offset.y, bounds.w, bounds.h); + display->start_clipping(clipping_rect); + + for (const auto &line : layout.lines) { + if (!this->draw_partial_lines_ && ((y_offset + line->max_height) > bounds.h)) { + ESP_LOGD(TAG, "Line %i would partially render outside of the area, skipping", line->line_number); + continue; } - display->print(calculated->bounds.x, calculated->bounds.y, calculated->run->font_, - calculated->run->foreground_color_, display::TextAlign::TOP_LEFT, calculated->text.c_str()); + + for (const auto &calculated : line->runs) { + if (calculated->run->background_color_ != display::COLOR_OFF) { + display->filled_rectangle(calculated->bounds.x, calculated->bounds.y, calculated->bounds.w, + calculated->bounds.h, calculated->run->background_color_); + } + display->print(calculated->bounds.x, calculated->bounds.y, calculated->run->font_, + calculated->run->foreground_color_, display::TextAlign::TOP_LEFT, calculated->text.c_str()); + } + + if (this->debug_outline_runs_) { + ESP_LOGD(TAG, "Outlining character runs"); + for (const auto &calculated : line->runs) { + display->rectangle(calculated->bounds.x, calculated->bounds.y, calculated->bounds.w, calculated->bounds.h); + } + } + + y_offset += line->max_height; } - if (this->debug_outline_runs_) { - ESP_LOGD(TAG, "Outlining character runs"); - for (const auto &calculated : layout.runs) { - display->rectangle(calculated->bounds.x, calculated->bounds.y, calculated->bounds.w, calculated->bounds.h); - } - } + display->end_clipping(); } std::vector> TextRunPanel::split_runs_into_words_() { @@ -95,7 +113,7 @@ std::vector> TextRunPanel::split_runs_into_wo } std::vector> TextRunPanel::fit_words_to_bounds_( - const std::vector> &runs, display::Rect bounds) { + const std::vector> &runs, display::Rect bounds, bool grow_beyond_bounds_height) { int x_offset = 0; int y_offset = 0; int current_line_number = 0; @@ -114,6 +132,12 @@ std::vector> TextRunPanel::fit_words_to_bounds_( current_line = std::make_shared(current_line_number); lines.push_back(current_line); + + ESP_LOGD(TAG, "Line %i finishes at %i vs available of %i", current_line_number - 1, y_offset, bounds.h); + if (!grow_beyond_bounds_height && y_offset >= bounds.h) { + ESP_LOGD(TAG, "No more text can fit into the available height. Aborting"); + break; + } } // Fits on the line @@ -191,14 +215,13 @@ void TextRunPanel::apply_alignment_to_lines_(std::vector> runs = this->split_runs_into_words_(); - std::vector> lines = this->fit_words_to_bounds_(runs, bounds); + std::vector> lines = this->fit_words_to_bounds_(runs, bounds, grow_beyond_bounds_height); this->apply_alignment_to_lines_(lines, this->text_align_); CalculatedLayout layout; - layout.runs = runs; - layout.line_count = lines.size(); + layout.lines = lines; int y_offset = 0; layout.bounds = display::Rect(0, 0, 0, 0); @@ -208,7 +231,7 @@ CalculatedLayout TextRunPanel::determine_layout_(display::Display *display, disp } layout.bounds.h = y_offset; - ESP_LOGD(TAG, "Text fits on %i lines and its bounds are (%i, %i)", layout.line_count, layout.bounds.w, + ESP_LOGD(TAG, "Text fits on %i lines and its bounds are (%i, %i)", layout.lines.size(), layout.bounds.w, layout.bounds.h); return layout; diff --git a/esphome/components/graphical_layout/text_run_panel.h b/esphome/components/graphical_layout/text_run_panel.h index f607ca271b..29b639d14e 100644 --- a/esphome/components/graphical_layout/text_run_panel.h +++ b/esphome/components/graphical_layout/text_run_panel.h @@ -149,12 +149,6 @@ class CalculatedTextRun { int16_t baseline{0}; }; -struct CalculatedLayout { - std::vector> runs; - display::Rect bounds; - int line_count; -}; - class LineInfo { public: LineInfo(int line_number) { this->line_number = line_number; } @@ -173,6 +167,11 @@ class LineInfo { int16_t max_baseline{0}; }; +struct CalculatedLayout { + std::vector> lines; + display::Rect bounds; +}; + /** The TextRunPanel is a UI item that renders a multiple "runs" of text of independent styling to a display */ class TextRunPanel : public LayoutItem { public: @@ -191,13 +190,15 @@ class TextRunPanel : public LayoutItem { 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_max_width(int max_width) { this->max_width_ = max_width; }; + void set_draw_partial_lines(bool draw_partial_lines) { this->draw_partial_lines_ = draw_partial_lines; }; void set_debug_outline_runs(bool debug_outline_runs) { this->debug_outline_runs_ = debug_outline_runs; }; protected: - CalculatedLayout determine_layout_(display::Display *display, display::Rect bounds, bool apply_alignment); + CalculatedLayout determine_layout_(display::Display *display, display::Rect bounds, bool grow_beyond_bounds_height); std::vector> split_runs_into_words_(); std::vector> fit_words_to_bounds_( - const std::vector> &runs, display::Rect bounds); + const std::vector> &runs, display::Rect bounds, + bool grow_beyond_bounds_height); void apply_alignment_to_lines_(std::vector> &lines, display::TextAlign alignment); std::vector text_runs_; @@ -205,6 +206,7 @@ class TextRunPanel : public LayoutItem { int min_width_{0}; int max_width_{0}; TemplatableValue can_wrap_at_character_{}; + bool draw_partial_lines_{false}; bool debug_outline_runs_{false}; }; diff --git a/esphome/components/graphical_layout/text_run_panel.py b/esphome/components/graphical_layout/text_run_panel.py index c1c1c41317..6857a75251 100644 --- a/esphome/components/graphical_layout/text_run_panel.py +++ b/esphome/components/graphical_layout/text_run_panel.py @@ -36,6 +36,7 @@ CONF_TEXT_SENSOR = "text_sensor" CONF_TEXT_FORMATTER = "text_formatter" CONF_TIME_FORMAT = "time_format" CONF_USE_UTC_TIME = "use_utc_time" +CONF_DRAW_PARTIAL_LINES = "draw_partial_lines" TEXT_ALIGN = { "TOP_LEFT": TextAlign.TOP_LEFT, @@ -110,6 +111,7 @@ def get_config_schema(base_item_schema, item_type_schema): cv.Required(CONF_MAX_WIDTH): cv.int_range(min=0), cv.Optional(CONF_MIN_WIDTH, default=0): cv.int_range(min=0), cv.Optional(CONF_CAN_WRAP_AT_CHARACTER): cv.returning_lambda, + cv.Optional(CONF_DRAW_PARTIAL_LINES, default=False): cv.boolean, cv.Optional(CONF_DEBUG_OUTLINE_RUNS, default=False): cv.boolean, cv.Required(CONF_RUNS): cv.All( cv.ensure_list(RUN_SCHEMA), cv.Length(min=1) @@ -138,6 +140,9 @@ async def config_to_layout_item(pvariable_builder, item_config, child_item_build ) cg.add(var.set_can_wrap_at(can_wrap_at_character)) + draw_partial_lines = item_config[CONF_DRAW_PARTIAL_LINES] + cg.add(var.set_draw_partial_lines(draw_partial_lines)) + debug_outline_runs = item_config[CONF_DEBUG_OUTLINE_RUNS] if debug_outline_runs: cg.add(var.set_debug_outline_runs(debug_outline_runs))