mirror of
https://github.com/esphome/esphome.git
synced 2024-12-12 08:24:55 +01:00
Add support for controlling child alignment in Horizontal and Vertical Stack
This allows the user to left/center/right/stretch items in the VerticalStack and top/center/bototm/stretch items in the HorizontalStack
This commit is contained in:
parent
6574ca68a2
commit
631a969107
7 changed files with 122 additions and 2 deletions
|
@ -11,6 +11,7 @@ static const char *const TAG = "horizontalstack";
|
|||
|
||||
void HorizontalStack::dump_config(int indent_depth, int additional_level_depth) {
|
||||
ESP_LOGCONFIG(TAG, "%*sItem Padding: %i", indent_depth, "", this->item_padding_);
|
||||
ESP_LOGCONFIG(TAG, "%*sChild alignment: %i", indent_depth, "", (int)this->child_align_);
|
||||
ESP_LOGCONFIG(TAG, "%*sChildren: %i", indent_depth, "", this->children_.size());
|
||||
|
||||
for (LayoutItem *child : this->children_) {
|
||||
|
@ -40,8 +41,38 @@ void HorizontalStack::render_internal(display::Display *display, display::Rect b
|
|||
|
||||
for (LayoutItem *item : this->children_) {
|
||||
display::Rect measure = item->measure_item(display);
|
||||
bool align_altered_local_coordinates = false;
|
||||
|
||||
switch (this->child_align_) {
|
||||
case VerticalChildAlign::CENTER_VERTICAL: {
|
||||
align_altered_local_coordinates = true;
|
||||
int adjustment = (bounds.h - measure.h) / 2;
|
||||
display->set_local_coordinates_relative_to_current(0, adjustment);
|
||||
break;
|
||||
}
|
||||
case VerticalChildAlign::BOTTOM: {
|
||||
align_altered_local_coordinates = true;
|
||||
display->set_local_coordinates_relative_to_current(0, bounds.h - measure.h);
|
||||
break;
|
||||
}
|
||||
case VerticalChildAlign::STRETCH_TO_FIT_HEIGHT: {
|
||||
// Items always get the same height as the tallest item
|
||||
measure.h = bounds.h;
|
||||
break;
|
||||
}
|
||||
case VerticalChildAlign::TOP:
|
||||
default: {
|
||||
// No action
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
display->set_local_coordinates_relative_to_current(width_offset, this->item_padding_);
|
||||
item->render(display, measure);
|
||||
if (align_altered_local_coordinates) {
|
||||
// Additional pop of local coords due to alignment
|
||||
display->pop_local_coordinates();
|
||||
}
|
||||
display->pop_local_coordinates();
|
||||
width_offset += measure.w + this->item_padding_;
|
||||
}
|
||||
|
|
|
@ -16,9 +16,11 @@ class HorizontalStack : public ContainerLayoutItem {
|
|||
void dump_config(int indent_depth, int additional_level_depth) override;
|
||||
|
||||
void set_item_padding(int item_padding) { this->item_padding_ = item_padding; };
|
||||
void set_child_align(VerticalChildAlign child_align) { this->child_align_ = child_align; };
|
||||
|
||||
protected:
|
||||
int item_padding_{0};
|
||||
VerticalChildAlign child_align_{VerticalChildAlign::TOP};
|
||||
};
|
||||
|
||||
} // namespace graphical_layout
|
||||
|
|
|
@ -4,10 +4,19 @@ from esphome.const import CONF_ID, CONF_TYPE
|
|||
|
||||
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
|
||||
HorizontalStack = graphical_layout_ns.class_("HorizontalStack")
|
||||
VerticalChildAlign = graphical_layout_ns.enum("VerticalChildAlign", is_class=True)
|
||||
|
||||
CONF_ITEM_PADDING = "item_padding"
|
||||
CONF_HORIZONTAL_STACK = "horizontal_stack"
|
||||
CONF_ITEMS = "items"
|
||||
CONF_CHILD_ALIGN = "child_align"
|
||||
|
||||
VERTICAL_CHILD_ALIGN = {
|
||||
"TOP": VerticalChildAlign.TOP,
|
||||
"CENTER_VERTICAL": VerticalChildAlign.CENTER_VERTICAL,
|
||||
"BOTTOM": VerticalChildAlign.BOTTOM,
|
||||
"STRETCH_TO_FIT_HEIGHT": VerticalChildAlign.STRETCH_TO_FIT_HEIGHT,
|
||||
}
|
||||
|
||||
|
||||
def get_config_schema(base_item_schema, item_type_schema):
|
||||
|
@ -18,6 +27,7 @@ def get_config_schema(base_item_schema, item_type_schema):
|
|||
cv.Required(CONF_ITEMS): cv.All(
|
||||
cv.ensure_list(item_type_schema), cv.Length(min=1)
|
||||
),
|
||||
cv.Optional(CONF_CHILD_ALIGN): cv.enum(VERTICAL_CHILD_ALIGN, upper=True),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -25,10 +35,13 @@ def get_config_schema(base_item_schema, item_type_schema):
|
|||
async def config_to_layout_item(pvariable_builder, item_config, child_item_builder):
|
||||
var = await pvariable_builder(item_config)
|
||||
|
||||
if item_padding_config := item_config[CONF_ITEM_PADDING]:
|
||||
if item_padding_config := item_config.get(CONF_ITEM_PADDING):
|
||||
cg.add(var.set_item_padding(item_padding_config))
|
||||
|
||||
for child_item_config in item_config[CONF_ITEMS]:
|
||||
if child_align := item_config.get(CONF_CHILD_ALIGN):
|
||||
cg.add(var.set_child_align(child_align))
|
||||
|
||||
for child_item_config in item_config.get(CONF_ITEMS):
|
||||
child_item_type = child_item_config[CONF_TYPE]
|
||||
if child_item_type in child_item_builder:
|
||||
child_item_var = await child_item_builder[child_item_type](
|
||||
|
|
|
@ -10,6 +10,36 @@ class Rect;
|
|||
|
||||
namespace graphical_layout {
|
||||
|
||||
/* HorizontalChildAlign is used to control alignment of children horizontally */
|
||||
enum class HorizontalChildAlign {
|
||||
/* Aligns all children to the left of their available width */
|
||||
LEFT = 0x00,
|
||||
|
||||
/* Aligns all children to the center of the available width */
|
||||
CENTER_HORIZONTAL = 0x01,
|
||||
|
||||
/* Aligns all children to the right of the available width */
|
||||
RIGHT = 0x02,
|
||||
|
||||
/* Regardless of the requested size of a child they will be given the entire width of their parent */
|
||||
STRETCH_TO_FIT_WIDTH = 0x03
|
||||
};
|
||||
|
||||
/* VerticalChildAlign is used to control alignment of children vertically */
|
||||
enum class VerticalChildAlign {
|
||||
/* Aligns all children to the top of the available height */
|
||||
TOP = 0x00,
|
||||
|
||||
/* Aligns all children with the center of the available height */
|
||||
CENTER_VERTICAL = 0x01,
|
||||
|
||||
/* Aligns all children to the bottom of the available height*/
|
||||
BOTTOM = 0x02,
|
||||
|
||||
/* Regardless of the requested size of a child they will be given the entire height of their parent */
|
||||
STRETCH_TO_FIT_HEIGHT = 0x03
|
||||
};
|
||||
|
||||
/** LayoutItem is the base from which all items derive from*/
|
||||
class LayoutItem {
|
||||
public:
|
||||
|
|
|
@ -11,6 +11,7 @@ static const char *const TAG = "verticalstack";
|
|||
|
||||
void VerticalStack::dump_config(int indent_depth, int additional_level_depth) {
|
||||
ESP_LOGCONFIG(TAG, "%*sItem Padding: %i", indent_depth, "", this->item_padding_);
|
||||
ESP_LOGCONFIG(TAG, "%*sChild alignment: %i", indent_depth, "", (int)this->child_align_);
|
||||
ESP_LOGCONFIG(TAG, "%*sChildren: %i", indent_depth, "", this->children_.size());
|
||||
|
||||
for (LayoutItem *child : this->children_) {
|
||||
|
@ -38,9 +39,38 @@ void VerticalStack::render_internal(display::Display *display, display::Rect bou
|
|||
|
||||
for (LayoutItem *item : this->children_) {
|
||||
display::Rect measure = item->measure_item(display);
|
||||
bool align_altered_local_coordinates = false;
|
||||
|
||||
switch (this->child_align_) {
|
||||
case HorizontalChildAlign::CENTER_HORIZONTAL: {
|
||||
align_altered_local_coordinates = true;
|
||||
int adjustment = (bounds.w - measure.w) / 2;
|
||||
display->set_local_coordinates_relative_to_current(adjustment, 0);
|
||||
break;
|
||||
}
|
||||
case HorizontalChildAlign::RIGHT: {
|
||||
align_altered_local_coordinates = true;
|
||||
display->set_local_coordinates_relative_to_current(bounds.w - measure.w, 0);
|
||||
break;
|
||||
}
|
||||
case HorizontalChildAlign::STRETCH_TO_FIT_WIDTH: {
|
||||
// Items always get the same width as the widest item
|
||||
measure.w = bounds.w;
|
||||
break;
|
||||
}
|
||||
case HorizontalChildAlign::LEFT:
|
||||
default: {
|
||||
// No action
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
display->set_local_coordinates_relative_to_current(this->item_padding_, height_offset);
|
||||
item->render(display, measure);
|
||||
if (align_altered_local_coordinates) {
|
||||
// Additional pop of local coords due to alignment
|
||||
display->pop_local_coordinates();
|
||||
}
|
||||
display->pop_local_coordinates();
|
||||
height_offset += measure.h + this->item_padding_;
|
||||
}
|
||||
|
|
|
@ -15,9 +15,11 @@ class VerticalStack : public ContainerLayoutItem {
|
|||
void dump_config(int indent_depth, int additional_level_depth) override;
|
||||
|
||||
void set_item_padding(int item_padding) { this->item_padding_ = item_padding; };
|
||||
void set_child_align(HorizontalChildAlign child_align) { this->child_align_ = child_align; };
|
||||
|
||||
protected:
|
||||
int item_padding_{0};
|
||||
HorizontalChildAlign child_align_{HorizontalChildAlign::LEFT};
|
||||
};
|
||||
|
||||
} // namespace graphical_layout
|
||||
|
|
|
@ -4,11 +4,19 @@ from esphome.const import CONF_ID, CONF_TYPE
|
|||
|
||||
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
|
||||
VerticalStack = graphical_layout_ns.class_("VerticalStack")
|
||||
HorizontalChildAlign = graphical_layout_ns.enum("HorizontalChildAlign", is_class=True)
|
||||
|
||||
CONF_ITEM_PADDING = "item_padding"
|
||||
CONF_VERTICAL_STACK = "vertical_stack"
|
||||
CONF_ITEMS = "items"
|
||||
CONF_CHILD_ALIGN = "child_align"
|
||||
|
||||
HORIZONTAL_CHILD_ALIGN = {
|
||||
"LEFT": HorizontalChildAlign.LEFT,
|
||||
"CENTER_HORIZONTAL": HorizontalChildAlign.CENTER_HORIZONTAL,
|
||||
"RIGHT": HorizontalChildAlign.RIGHT,
|
||||
"STRETCH_TO_FIT_WIDTH": HorizontalChildAlign.STRETCH_TO_FIT_WIDTH,
|
||||
}
|
||||
|
||||
def get_config_schema(base_item_schema, item_type_schema):
|
||||
return base_item_schema.extend(
|
||||
|
@ -18,6 +26,7 @@ def get_config_schema(base_item_schema, item_type_schema):
|
|||
cv.Required(CONF_ITEMS): cv.All(
|
||||
cv.ensure_list(item_type_schema), cv.Length(min=1)
|
||||
),
|
||||
cv.Optional(CONF_CHILD_ALIGN): cv.enum(HORIZONTAL_CHILD_ALIGN, upper=True),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -28,6 +37,9 @@ async def config_to_layout_item(pvariable_builder, item_config, child_item_build
|
|||
if item_padding_config := item_config[CONF_ITEM_PADDING]:
|
||||
cg.add(var.set_item_padding(item_padding_config))
|
||||
|
||||
if child_align := item_config.get(CONF_CHILD_ALIGN):
|
||||
cg.add(var.set_child_align(child_align))
|
||||
|
||||
for child_item_config in item_config[CONF_ITEMS]:
|
||||
child_item_type = child_item_config[CONF_TYPE]
|
||||
if child_item_type in child_item_builder:
|
||||
|
|
Loading…
Reference in a new issue