From b18e217313862cd21b2a57f83b49939daf95605b Mon Sep 17 00:00:00 2001 From: Michael Davidson Date: Mon, 1 Jan 2024 10:23:17 +1100 Subject: [PATCH] Make margin, borders, and padding individually settable (top vs left vs bottom vs right) Can still take an int to set all the properties equally --- .../components/graphical_layout/__init__.py | 69 ++++++++++++++++--- .../fixed_dimension_panel.cpp | 4 +- .../graphical_layout/layout_item.cpp | 50 ++++++++------ .../components/graphical_layout/layout_item.h | 59 ++++++++++++++-- 4 files changed, 143 insertions(+), 39 deletions(-) diff --git a/esphome/components/graphical_layout/__init__.py b/esphome/components/graphical_layout/__init__.py index 46910315e8..a10f09703c 100644 --- a/esphome/components/graphical_layout/__init__.py +++ b/esphome/components/graphical_layout/__init__.py @@ -24,13 +24,32 @@ CONF_LAYOUT = "layout" CONF_MARGIN = "margin" CONF_PADDING = "padding" CONF_BORDER_COLOR = "border_color" +CONF_LEFT = "left" +CONF_TOP = "top" +CONF_RIGHT = "right" +CONF_BOTTOM = "bottom" + +DIMENSION_SCHEMA = cv.Schema( + { + cv.Optional(CONF_LEFT, default=0): cv.int_range(min=0), + cv.Optional(CONF_TOP, default=0): cv.int_range(min=0), + cv.Optional(CONF_RIGHT, default=0): cv.int_range(min=0), + cv.Optional(CONF_BOTTOM, default=0): cv.int_range(min=0), + } +) BASE_ITEM_SCHEMA = cv.Schema( { - cv.Optional(CONF_MARGIN, default=0): cv.int_range(min=0), - cv.Optional(CONF_BORDER, default=0): cv.int_range(min=0), + cv.Optional(CONF_MARGIN, default=0): cv.Any( + DIMENSION_SCHEMA, cv.int_range(min=0) + ), + cv.Optional(CONF_BORDER, default=0): cv.Any( + DIMENSION_SCHEMA, cv.int_range(min=0) + ), cv.Optional(CONF_BORDER_COLOR): cv.use_id(color.ColorStruct), - cv.Optional(CONF_PADDING, default=0): cv.int_range(min=0), + cv.Optional(CONF_PADDING, default=0): cv.Any( + DIMENSION_SCHEMA, cv.int_range(min=0) + ), } ) @@ -79,22 +98,52 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) +async def extract_dimension_expression(value_config, individual_set, single_set): + if value_config is not None: + if not isinstance(value_config, int): + # Handle individual dimensions + left = value_config.get(CONF_LEFT) + top = value_config.get(CONF_TOP) + right = value_config.get(CONF_RIGHT) + bottom = value_config.get(CONF_BOTTOM) + + individual_set(left, top, right, bottom) + else: + template = await cg.templatable(value_config, args=[], output_type=int) + single_set(template) + + async def build_layout_item_pvariable(config): var = cg.new_Pvariable(config[CONF_ID]) - margin = await cg.templatable(config[CONF_MARGIN], args=[], output_type=int) - cg.add(var.set_margin(margin)) + await extract_dimension_expression( + config.get(CONF_MARGIN), + lambda left, top, right, bottom: cg.add( + var.set_margin(left, top, right, bottom) + ), + lambda margin: cg.add(var.set_margin(margin)), + ) - border = await cg.templatable(config[CONF_BORDER], args=[], output_type=int) - cg.add(var.set_border(border)) + await extract_dimension_expression( + config.get(CONF_BORDER), + lambda left, top, right, bottom: cg.add( + var.set_border(left, top, right, bottom) + ), + lambda border: cg.add(var.set_border(border)), + ) + + await extract_dimension_expression( + config.get(CONF_PADDING), + lambda left, top, right, bottom: cg.add( + var.set_padding(left, top, right, bottom) + ), + lambda padding: cg.add(var.set_padding(padding)), + ) if border_color_config := config.get(CONF_BORDER_COLOR): border_color = await cg.get_variable(border_color_config) cg.add(var.set_border_color(border_color)) - padding = await cg.templatable(config[CONF_PADDING], args=[], output_type=int) - cg.add(var.set_padding(padding)) - return var diff --git a/esphome/components/graphical_layout/fixed_dimension_panel.cpp b/esphome/components/graphical_layout/fixed_dimension_panel.cpp index 634acfbb98..20d92ac4ee 100644 --- a/esphome/components/graphical_layout/fixed_dimension_panel.cpp +++ b/esphome/components/graphical_layout/fixed_dimension_panel.cpp @@ -37,7 +37,7 @@ display::Rect FixedDimensionPanel::measure_item_internal(display::Display *displ if (this->unset_width_uses_display_width_) { rect.w = display->get_width(); // We need to account for our own padding + margin + border - rect.w -= (this->margin_ + this->border_ + this->padding_) * 2; + rect.w -= this->margin_.horizontal() + this->border_.horizontal() + this->padding_.horizontal(); } else { rect.w = child_size.w; } @@ -47,7 +47,7 @@ display::Rect FixedDimensionPanel::measure_item_internal(display::Display *displ if (this->unset_height_uses_display_height_) { rect.h = display->get_height(); // We need to account for our own padding + margin + border - rect.h -= (this->margin_ + this->border_ + this->padding_) * 2; + rect.h -= this->margin_.vertical() + this->border_.vertical() + this->padding_.vertical(); } else { rect.h = child_size.h; } diff --git a/esphome/components/graphical_layout/layout_item.cpp b/esphome/components/graphical_layout/layout_item.cpp index 3085eafee9..c4e717be5a 100644 --- a/esphome/components/graphical_layout/layout_item.cpp +++ b/esphome/components/graphical_layout/layout_item.cpp @@ -13,38 +13,43 @@ const Color COLOR_ON(255, 255, 255, 255); display::Rect LayoutItem::measure_item(display::Display *display) { display::Rect inner_size = this->measure_item_internal(display); - int margin_border_padding = this->margin_ + this->border_ + this->padding_; - - return display::Rect(0, 0, (margin_border_padding * 2) + inner_size.w, (margin_border_padding * 2) + inner_size.h); + return display::Rect(0, 0, this->margin_.horizontal() + this->border_.horizontal() + this->padding_.horizontal() + inner_size.w, this->margin_.vertical() + this->border_.vertical() + this->padding_.vertical() + inner_size.h); } void LayoutItem::render(display::Display *display, display::Rect bounds) { // Margin - display->set_local_coordinates_relative_to_current(this->margin_, this->margin_); + display->set_local_coordinates_relative_to_current(this->margin_.left, this->margin_.top); // Border - if (this->border_ > 0) { - display::Rect border_bounds(0, 0, bounds.w - (this->margin_ * 2), bounds.h - (this->margin_ * 2)); - if (this->border_ == 1) { + if (this->border_.any()) { + display::Rect border_bounds(0, 0, bounds.w - this->margin_.horizontal(), bounds.h - this->margin_.vertical()); + if (this->border_.equals(1)) { // Single pixel border use the native function display->rectangle(0, 0, border_bounds.w, border_bounds.h, this->border_color_); } else { // Thicker border need to do mutiple filled rects - // Top rectangle - display->filled_rectangle(border_bounds.x, border_bounds.y, border_bounds.w, this->border_); - // Bottom rectangle - display->filled_rectangle(border_bounds.x, border_bounds.h - this->border_, border_bounds.w, this->border_); - // Left rectangle - display->filled_rectangle(border_bounds.x, border_bounds.y, this->border_, border_bounds.h); - // Right rectangle - display->filled_rectangle(border_bounds.w - this->border_, border_bounds.y, this->border_, border_bounds.h); + // Top Rectangle + if (this->border_.top > 0) { + display->filled_rectangle(border_bounds.x, border_bounds.y, border_bounds.w, this->border_.top); + } + // Left Rectangle + if (this->border_.left > 0) { + display->filled_rectangle(border_bounds.x, border_bounds.y + this->border_.top, this->border_.left, border_bounds.h - this->border_.bottom - this->border_.top); + } + // Bottom Rectangle + if (this->border_.bottom > 0) { + display->filled_rectangle(border_bounds.x, border_bounds.h - this->border_.bottom, border_bounds.w, this->border_.bottom); + } + // Right Rectangle + if (this->border_.right > 0) { + display->filled_rectangle(border_bounds.w - this->border_.right, border_bounds.y + this->border_.top, this->border_.right, border_bounds.h - this->border_.bottom - this->border_.top); + } } } // Padding - display->set_local_coordinates_relative_to_current(this->border_ + this->padding_, this->border_ + this->padding_); - int margin_border_padding_offset = (this->margin_ + this->border_ + this->padding_) * 2; - display::Rect internal_bounds(0, 0, bounds.w - margin_border_padding_offset, bounds.h - margin_border_padding_offset); + display->set_local_coordinates_relative_to_current(this->border_.left + this->padding_.left, this->border_.top + this->padding_.top); + display::Rect internal_bounds(0, 0, bounds.w - this->margin_.horizontal() - this->border_.horizontal() - this->padding_.horizontal(), bounds.h - this->margin_.vertical() - this->border_.vertical() - this->padding_.vertical()); // Rendering this->render_internal(display, internal_bounds); @@ -59,11 +64,14 @@ void LayoutItem::render(display::Display *display, display::Rect bounds) { } void LayoutItem::dump_config_base_properties(const char *tag, int indent_depth) { - ESP_LOGCONFIG(tag, "%*sMargin: %i", indent_depth, "", this->margin_); - ESP_LOGCONFIG(tag, "%*sBorder: %i", indent_depth, "", this->border_); + ESP_LOGCONFIG(tag, "%*sMargin: : (L: %i, T: %i, R: %i, B: %i)", indent_depth, "", this->margin_.left, + this->margin_.top, this->margin_.right, this->margin_.bottom); + ESP_LOGCONFIG(tag, "%*sBorder: (L: %i, T: %i, R: %i, B: %i)", indent_depth, "", this->border_.left, + this->border_.top, this->border_.right, this->border_.bottom); ESP_LOGCONFIG(tag, "%*sBorder Color: (R: %i, G: %i, B: %i)", indent_depth, "", this->border_color_.r, this->border_color_.g, this->border_color_.b); - ESP_LOGCONFIG(tag, "%*sPadding: %i", indent_depth, "", this->padding_); + ESP_LOGCONFIG(tag, "%*sPadding: : (L: %i, T: %i, R: %i, B: %i)", indent_depth, "", this->padding_.left, + this->padding_.top, this->padding_.right, this->padding_.bottom); } const LogString *horizontal_child_align_to_string(HorizontalChildAlign align) { diff --git a/esphome/components/graphical_layout/layout_item.h b/esphome/components/graphical_layout/layout_item.h index ee6793cc1a..f51b9c4d3c 100644 --- a/esphome/components/graphical_layout/layout_item.h +++ b/esphome/components/graphical_layout/layout_item.h @@ -44,6 +44,43 @@ enum class VerticalChildAlign { STRETCH_TO_FIT_HEIGHT = 0x03 }; +struct Dimension { + Dimension() {}; + Dimension(int16_t padding) { + this->left = padding; + this->top = padding; + this->right = padding; + this->bottom = padding; + } + Dimension(int16_t left, int16_t top, int16_t right, int16_t bottom) { + this->left = left; + this->top = top; + this->right = right; + this->bottom = bottom; + } + + /* Gets the total padding for the horizontal direction (left + right) */ + inline int16_t horizontal() const { return this->left + this->right; }; + /* Gets the total padding for the vertical direction (top + bottom) */ + inline int16_t vertical() const { return this->top + this->bottom; }; + + /* Returns true if any value is set to a non-zero value*/ + inline bool any() const { + return this->left > 0 || this->top > 0 || this->right > 0 || this->bottom > 0; + }; + + /* Returns true if all dimensions are equal to the value */ + inline bool equals(int16_t value) const { + return this->left == value && this->top == value && this->right == value + && this->bottom == value; + } + + int16_t left{0}; + int16_t top{0}; + int16_t right{0}; + int16_t bottom{0}; +}; + /** LayoutItem is the base from which all items derive from*/ class LayoutItem { public: @@ -99,15 +136,25 @@ class LayoutItem { */ virtual void setup_complete(){}; - void set_margin(int margin) { this->margin_ = margin; }; - void set_padding(int padding) { this->padding_ = padding; }; - void set_border(int border) { this->border_ = border; }; + void set_margin(int margin) { this->margin_ = Dimension(margin); }; + void set_margin(int left, int top, int right, int bottom) { + this->margin_ = Dimension(left, top, right, bottom); + } + void set_padding(int padding) { this->padding_ = Dimension(padding); }; + void set_padding(int left, int top, int right, int bottom) { + this->padding_ = Dimension(left, top, right, bottom); + } + void set_border(int border) { this->border_ = Dimension(border); }; + void set_border(int left, int top, int right, int bottom) { + this->border_ = Dimension(left, top, right, bottom); + } void set_border_color(Color color) { this->border_color_ = color; }; + protected: - int margin_{0}; - int padding_{0}; - int border_{0}; + Dimension margin_{}; + Dimension padding_{}; + Dimension border_{}; Color border_color_{COLOR_ON}; };