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) {
|
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, "%*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());
|
ESP_LOGCONFIG(TAG, "%*sChildren: %i", indent_depth, "", this->children_.size());
|
||||||
|
|
||||||
for (LayoutItem *child : this->children_) {
|
for (LayoutItem *child : this->children_) {
|
||||||
|
@ -40,8 +41,38 @@ void HorizontalStack::render_internal(display::Display *display, display::Rect b
|
||||||
|
|
||||||
for (LayoutItem *item : this->children_) {
|
for (LayoutItem *item : this->children_) {
|
||||||
display::Rect measure = item->measure_item(display);
|
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_);
|
display->set_local_coordinates_relative_to_current(width_offset, this->item_padding_);
|
||||||
item->render(display, measure);
|
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();
|
display->pop_local_coordinates();
|
||||||
width_offset += measure.w + this->item_padding_;
|
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 dump_config(int indent_depth, int additional_level_depth) override;
|
||||||
|
|
||||||
void set_item_padding(int item_padding) { this->item_padding_ = item_padding; };
|
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:
|
protected:
|
||||||
int item_padding_{0};
|
int item_padding_{0};
|
||||||
|
VerticalChildAlign child_align_{VerticalChildAlign::TOP};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace graphical_layout
|
} // namespace graphical_layout
|
||||||
|
|
|
@ -4,10 +4,19 @@ from esphome.const import CONF_ID, CONF_TYPE
|
||||||
|
|
||||||
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
|
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
|
||||||
HorizontalStack = graphical_layout_ns.class_("HorizontalStack")
|
HorizontalStack = graphical_layout_ns.class_("HorizontalStack")
|
||||||
|
VerticalChildAlign = graphical_layout_ns.enum("VerticalChildAlign", is_class=True)
|
||||||
|
|
||||||
CONF_ITEM_PADDING = "item_padding"
|
CONF_ITEM_PADDING = "item_padding"
|
||||||
CONF_HORIZONTAL_STACK = "horizontal_stack"
|
CONF_HORIZONTAL_STACK = "horizontal_stack"
|
||||||
CONF_ITEMS = "items"
|
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):
|
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.Required(CONF_ITEMS): cv.All(
|
||||||
cv.ensure_list(item_type_schema), cv.Length(min=1)
|
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):
|
async def config_to_layout_item(pvariable_builder, item_config, child_item_builder):
|
||||||
var = await pvariable_builder(item_config)
|
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))
|
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]
|
child_item_type = child_item_config[CONF_TYPE]
|
||||||
if child_item_type in child_item_builder:
|
if child_item_type in child_item_builder:
|
||||||
child_item_var = await child_item_builder[child_item_type](
|
child_item_var = await child_item_builder[child_item_type](
|
||||||
|
|
|
@ -10,6 +10,36 @@ class Rect;
|
||||||
|
|
||||||
namespace graphical_layout {
|
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*/
|
/** LayoutItem is the base from which all items derive from*/
|
||||||
class LayoutItem {
|
class LayoutItem {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -11,6 +11,7 @@ static const char *const TAG = "verticalstack";
|
||||||
|
|
||||||
void VerticalStack::dump_config(int indent_depth, int additional_level_depth) {
|
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, "%*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());
|
ESP_LOGCONFIG(TAG, "%*sChildren: %i", indent_depth, "", this->children_.size());
|
||||||
|
|
||||||
for (LayoutItem *child : this->children_) {
|
for (LayoutItem *child : this->children_) {
|
||||||
|
@ -38,9 +39,38 @@ void VerticalStack::render_internal(display::Display *display, display::Rect bou
|
||||||
|
|
||||||
for (LayoutItem *item : this->children_) {
|
for (LayoutItem *item : this->children_) {
|
||||||
display::Rect measure = item->measure_item(display);
|
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);
|
display->set_local_coordinates_relative_to_current(this->item_padding_, height_offset);
|
||||||
item->render(display, measure);
|
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();
|
display->pop_local_coordinates();
|
||||||
height_offset += measure.h + this->item_padding_;
|
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 dump_config(int indent_depth, int additional_level_depth) override;
|
||||||
|
|
||||||
void set_item_padding(int item_padding) { this->item_padding_ = item_padding; };
|
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:
|
protected:
|
||||||
int item_padding_{0};
|
int item_padding_{0};
|
||||||
|
HorizontalChildAlign child_align_{HorizontalChildAlign::LEFT};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace graphical_layout
|
} // namespace graphical_layout
|
||||||
|
|
|
@ -4,11 +4,19 @@ from esphome.const import CONF_ID, CONF_TYPE
|
||||||
|
|
||||||
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
|
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
|
||||||
VerticalStack = graphical_layout_ns.class_("VerticalStack")
|
VerticalStack = graphical_layout_ns.class_("VerticalStack")
|
||||||
|
HorizontalChildAlign = graphical_layout_ns.enum("HorizontalChildAlign", is_class=True)
|
||||||
|
|
||||||
CONF_ITEM_PADDING = "item_padding"
|
CONF_ITEM_PADDING = "item_padding"
|
||||||
CONF_VERTICAL_STACK = "vertical_stack"
|
CONF_VERTICAL_STACK = "vertical_stack"
|
||||||
CONF_ITEMS = "items"
|
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):
|
def get_config_schema(base_item_schema, item_type_schema):
|
||||||
return base_item_schema.extend(
|
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.Required(CONF_ITEMS): cv.All(
|
||||||
cv.ensure_list(item_type_schema), cv.Length(min=1)
|
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]:
|
if item_padding_config := item_config[CONF_ITEM_PADDING]:
|
||||||
cg.add(var.set_item_padding(item_padding_config))
|
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]:
|
for child_item_config in item_config[CONF_ITEMS]:
|
||||||
child_item_type = child_item_config[CONF_TYPE]
|
child_item_type = child_item_config[CONF_TYPE]
|
||||||
if child_item_type in child_item_builder:
|
if child_item_type in child_item_builder:
|
||||||
|
|
Loading…
Reference in a new issue