mirror of
https://github.com/esphome/esphome.git
synced 2024-12-12 08:24:55 +01:00
Ensure text does not render beyond the available bounds given to the TextRunPanel
Introduce draw_partial_lines option. If enabled (defaults to false) partial lines of text will be rendered to the screen
This commit is contained in:
parent
e8eabc3cc2
commit
2c52ef30c8
3 changed files with 57 additions and 27 deletions
|
@ -1,4 +1,5 @@
|
||||||
#include "text_run_panel.h"
|
#include "text_run_panel.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "esphome/components/display/display.h"
|
#include "esphome/components/display/display.h"
|
||||||
#include "esphome/components/display/rect.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, "%*sMax Width: %i", indent_depth, "", this->max_width_);
|
||||||
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, "%*sDraw Partial Lines: %s", indent_depth, "", YESNO(this->draw_partial_lines_));
|
||||||
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 (TextRunBase *run : this->text_runs_) {
|
for (TextRunBase *run : this->text_runs_) {
|
||||||
std::string text = run->get_text();
|
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) {
|
void TextRunPanel::render_internal(display::Display *display, display::Rect bounds) {
|
||||||
ESP_LOGD(TAG, "Rendering to (%i, %i)", bounds.w, bounds.h);
|
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) {
|
display::Point offset = display->get_local_coordinates();
|
||||||
if (calculated->run->background_color_ != display::COLOR_OFF) {
|
display::Rect clipping_rect = display::Rect(offset.x, offset.y, bounds.w, bounds.h);
|
||||||
display->filled_rectangle(calculated->bounds.x, calculated->bounds.y, calculated->bounds.w, calculated->bounds.h,
|
display->start_clipping(clipping_rect);
|
||||||
calculated->run->background_color_);
|
|
||||||
|
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_) {
|
display->end_clipping();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::shared_ptr<CalculatedTextRun>> TextRunPanel::split_runs_into_words_() {
|
std::vector<std::shared_ptr<CalculatedTextRun>> TextRunPanel::split_runs_into_words_() {
|
||||||
|
@ -95,7 +113,7 @@ std::vector<std::shared_ptr<CalculatedTextRun>> TextRunPanel::split_runs_into_wo
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::shared_ptr<LineInfo>> TextRunPanel::fit_words_to_bounds_(
|
std::vector<std::shared_ptr<LineInfo>> TextRunPanel::fit_words_to_bounds_(
|
||||||
const std::vector<std::shared_ptr<CalculatedTextRun>> &runs, display::Rect bounds) {
|
const std::vector<std::shared_ptr<CalculatedTextRun>> &runs, display::Rect bounds, bool grow_beyond_bounds_height) {
|
||||||
int x_offset = 0;
|
int x_offset = 0;
|
||||||
int y_offset = 0;
|
int y_offset = 0;
|
||||||
int current_line_number = 0;
|
int current_line_number = 0;
|
||||||
|
@ -114,6 +132,12 @@ std::vector<std::shared_ptr<LineInfo>> TextRunPanel::fit_words_to_bounds_(
|
||||||
current_line = std::make_shared<LineInfo>(current_line_number);
|
current_line = std::make_shared<LineInfo>(current_line_number);
|
||||||
|
|
||||||
lines.push_back(current_line);
|
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
|
// Fits on the line
|
||||||
|
@ -191,14 +215,13 @@ void TextRunPanel::apply_alignment_to_lines_(std::vector<std::shared_ptr<LineInf
|
||||||
}
|
}
|
||||||
|
|
||||||
CalculatedLayout TextRunPanel::determine_layout_(display::Display *display, display::Rect bounds,
|
CalculatedLayout TextRunPanel::determine_layout_(display::Display *display, display::Rect bounds,
|
||||||
bool apply_alignment) {
|
bool grow_beyond_bounds_height) {
|
||||||
std::vector<std::shared_ptr<CalculatedTextRun>> runs = this->split_runs_into_words_();
|
std::vector<std::shared_ptr<CalculatedTextRun>> runs = this->split_runs_into_words_();
|
||||||
std::vector<std::shared_ptr<LineInfo>> lines = this->fit_words_to_bounds_(runs, bounds);
|
std::vector<std::shared_ptr<LineInfo>> lines = this->fit_words_to_bounds_(runs, bounds, grow_beyond_bounds_height);
|
||||||
this->apply_alignment_to_lines_(lines, this->text_align_);
|
this->apply_alignment_to_lines_(lines, this->text_align_);
|
||||||
|
|
||||||
CalculatedLayout layout;
|
CalculatedLayout layout;
|
||||||
layout.runs = runs;
|
layout.lines = lines;
|
||||||
layout.line_count = lines.size();
|
|
||||||
|
|
||||||
int y_offset = 0;
|
int y_offset = 0;
|
||||||
layout.bounds = display::Rect(0, 0, 0, 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;
|
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);
|
layout.bounds.h);
|
||||||
|
|
||||||
return layout;
|
return layout;
|
||||||
|
|
|
@ -149,12 +149,6 @@ class CalculatedTextRun {
|
||||||
int16_t baseline{0};
|
int16_t baseline{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CalculatedLayout {
|
|
||||||
std::vector<std::shared_ptr<CalculatedTextRun>> runs;
|
|
||||||
display::Rect bounds;
|
|
||||||
int line_count;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LineInfo {
|
class LineInfo {
|
||||||
public:
|
public:
|
||||||
LineInfo(int line_number) { this->line_number = line_number; }
|
LineInfo(int line_number) { this->line_number = line_number; }
|
||||||
|
@ -173,6 +167,11 @@ class LineInfo {
|
||||||
int16_t max_baseline{0};
|
int16_t max_baseline{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CalculatedLayout {
|
||||||
|
std::vector<std::shared_ptr<LineInfo>> lines;
|
||||||
|
display::Rect bounds;
|
||||||
|
};
|
||||||
|
|
||||||
/** The TextRunPanel is a UI item that renders a multiple "runs" of text of independent styling to a display */
|
/** The TextRunPanel is a UI item that renders a multiple "runs" of text of independent styling to a display */
|
||||||
class TextRunPanel : public LayoutItem {
|
class TextRunPanel : public LayoutItem {
|
||||||
public:
|
public:
|
||||||
|
@ -191,13 +190,15 @@ class TextRunPanel : public LayoutItem {
|
||||||
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_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; };
|
void set_debug_outline_runs(bool debug_outline_runs) { this->debug_outline_runs_ = debug_outline_runs; };
|
||||||
|
|
||||||
protected:
|
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<std::shared_ptr<CalculatedTextRun>> split_runs_into_words_();
|
std::vector<std::shared_ptr<CalculatedTextRun>> split_runs_into_words_();
|
||||||
std::vector<std::shared_ptr<LineInfo>> fit_words_to_bounds_(
|
std::vector<std::shared_ptr<LineInfo>> fit_words_to_bounds_(
|
||||||
const std::vector<std::shared_ptr<CalculatedTextRun>> &runs, display::Rect bounds);
|
const std::vector<std::shared_ptr<CalculatedTextRun>> &runs, display::Rect bounds,
|
||||||
|
bool grow_beyond_bounds_height);
|
||||||
void apply_alignment_to_lines_(std::vector<std::shared_ptr<LineInfo>> &lines, display::TextAlign alignment);
|
void apply_alignment_to_lines_(std::vector<std::shared_ptr<LineInfo>> &lines, display::TextAlign alignment);
|
||||||
|
|
||||||
std::vector<TextRunBase *> text_runs_;
|
std::vector<TextRunBase *> text_runs_;
|
||||||
|
@ -205,6 +206,7 @@ class TextRunPanel : public LayoutItem {
|
||||||
int min_width_{0};
|
int min_width_{0};
|
||||||
int max_width_{0};
|
int max_width_{0};
|
||||||
TemplatableValue<bool, const CanWrapAtCharacterArguments &> can_wrap_at_character_{};
|
TemplatableValue<bool, const CanWrapAtCharacterArguments &> can_wrap_at_character_{};
|
||||||
|
bool draw_partial_lines_{false};
|
||||||
bool debug_outline_runs_{false};
|
bool debug_outline_runs_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ CONF_TEXT_SENSOR = "text_sensor"
|
||||||
CONF_TEXT_FORMATTER = "text_formatter"
|
CONF_TEXT_FORMATTER = "text_formatter"
|
||||||
CONF_TIME_FORMAT = "time_format"
|
CONF_TIME_FORMAT = "time_format"
|
||||||
CONF_USE_UTC_TIME = "use_utc_time"
|
CONF_USE_UTC_TIME = "use_utc_time"
|
||||||
|
CONF_DRAW_PARTIAL_LINES = "draw_partial_lines"
|
||||||
|
|
||||||
TEXT_ALIGN = {
|
TEXT_ALIGN = {
|
||||||
"TOP_LEFT": TextAlign.TOP_LEFT,
|
"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.Required(CONF_MAX_WIDTH): cv.int_range(min=0),
|
||||||
cv.Optional(CONF_MIN_WIDTH, default=0): 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_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.Optional(CONF_DEBUG_OUTLINE_RUNS, default=False): cv.boolean,
|
||||||
cv.Required(CONF_RUNS): cv.All(
|
cv.Required(CONF_RUNS): cv.All(
|
||||||
cv.ensure_list(RUN_SCHEMA), cv.Length(min=1)
|
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))
|
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]
|
debug_outline_runs = item_config[CONF_DEBUG_OUTLINE_RUNS]
|
||||||
if debug_outline_runs:
|
if debug_outline_runs:
|
||||||
cg.add(var.set_debug_outline_runs(debug_outline_runs))
|
cg.add(var.set_debug_outline_runs(debug_outline_runs))
|
||||||
|
|
Loading…
Reference in a new issue