Reputation: 2738
I want to create a layout where I have something similar to a BoxLayout to give me the ability to create "rows" in my layout, and in each "row" I want to use something in the sorts of another BoxLayout to create "columns".
The columns don't need to be evenly distributed. For example, I want to create a BoxLayout with one column with a square image, and another occupying the rest of the available width.
See code and screenshot on my gist: https://gist.github.com/MichaelGradek/e5c50038b947352d9e79
I have the basic structure done in the code above, but in addition, I want the BoxLayout's height to adapt to the height of the children.
What would be the best approach to achieve this?
Thanks!
Upvotes: 7
Views: 11274
Reputation: 41
For me, using a GridLayout with height: self.minimum_height, and then setting a manual size (size_hint_y: None and height: some_number) for each child widget, resulted in widgets getting anchored to the bottom of root window. Couldn't really figure out why?
However, using a GridLayout with height: root.height, and then setting a manual size (size_hint_y: None and height: some_number) for each child widget resulted in correct top anchored widgets.
Upvotes: 4
Reputation: 910
Here's some tricks I've figured out for setting GridLayout
height based off children height, however, both require one child per row... so making columns does require adding an internal grid layout.
# kv file or string
<Resizing_GridLayout@GridLayout>:
cols: 1
row_force_default: True
foo: [self.rows_minimum.update({i: x.height}) for i, x in enumerate(reversed(list(self.children)))]
Above could be added to either via Python code or static kv
entries and in my project is at least behaving itself for resizing each row off the contained child widget.
# kv file or string for when above goes funky
<ResizingRow_GridLayout@GridLayout>:
cols: 1
height: sum([c.height for c in self.children])
And for completeness, an example of how to stitch both of'em togeather...
# ... assuming above have been set, bellow maybe within another layout
Resizing_GridLayout:
ResizingRow_GridLayout:
Label:
height: 30
text: 'Label One'
TextInput:
height: 30
multiline: False
write_tab: False
hint_text: 'Insert one liner'
ResizingRow_GridLayout:
Label:
height: 45
text: 'Label two'
Button:
text: 'Button One'
height: 60
GridLayout:
rows: 1
height: 25
Button:
text: 'Button Two'
Button:
text: 'Button three'
modules/adaptive-grid-layout/__init__.py
#!/usr/bin/env python
from collections import OrderedDict
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock
class Adaptive_GridLayout(GridLayout):
"""
Adaptive height and row heights for grid layouts.
Note this should not be used as a root layout and '_refresh_grids_y_dimension()' method should be used by
children widgets that change height to update all attached instances of Adaptive_GridLayout (this layout).
Copyright AGPL-3.0 2019 S0AndS0
"""
def __init__(self, grow_cols = False, grow_rows = False, **kwargs):
super(Adaptive_GridLayout, self).__init__(**kwargs)
self.grow_cols = grow_cols
self.grow_rows = grow_rows
self.trigger_refresh_y_dimension = Clock.create_trigger(lambda _: self._refresh_grids_y_dimension(), 0)
def _yield_tallest_of_each_row(self):
""" Yields tallest child of each row within gridlayout. """
current_tallest = None
for i, c in enumerate(list(reversed(self.children))):
if current_tallest is None:
current_tallest = c
if c.height > current_tallest.height:
current_tallest = c
## Should work around grids without value for 'cols'
if self.cols is None or self.cols is 0:
yield current_tallest
current_tallest = None
## Reached last item of current row... Fizzbuzz!
elif ((i + 1) % self.cols == 0) is True:
yield current_tallest
current_tallest = None
def _calc_child_padding_y(self, child):
""" Returns total padding for a given child. """
## May be faster than asking permission with an if statement as most widgets seem to have padding
try:
child_padding = child.padding
except AttributeError as e:
child_padding = [0]
len_child_padding = len(child_padding)
if len_child_padding is 1:
padding = child_padding[0] * 2
elif len_child_padding is 2:
padding = child_padding[1] * 2
elif len_child_padding > 2:
padding = child_padding[1] + child_padding[3]
else:
padding = 0
return padding
def _calc_min_height(self):
""" Returns total height required to display tallest children of each row plus spacing between widgets. """
min_height = 0
for c in self._yield_tallest_of_each_row():
min_height += c.height + self._calc_child_padding_y(child = c) + self.spacing[1]
return min_height
def _calc_rows_minimum(self):
""" Returns ordered dictionary of how high each row should be to accommodate tallest children of each row. """
rows_minimum = OrderedDict()
for i, c in enumerate(self._yield_tallest_of_each_row()):
rows_minimum.update({i: c.height + self._calc_child_padding_y(child = c)})
return rows_minimum
def _refresh_height(self):
""" Resets 'self.height' using value returned by '_calc_min_height' method. """
self.height = self._calc_min_height()
def _refresh_rows_minimum(self):
""" Resets 'self.rows_minimum' using value returned by '_calc_rows_minimum' method. """
self.rows_minimum = self._calc_rows_minimum()
def _refresh_grids_y_dimension(self):
""" Updates 'height' and 'rows_minimum' first for spawn, then for self, and finally for any progenitors. """
spawn = [x for x in self.walk(restrict = True) if hasattr(x, '_refresh_grids_y_dimension') and x is not self]
for item in spawn:
item._refresh_rows_minimum()
item._refresh_height()
self._refresh_rows_minimum()
self._refresh_height()
progenitors = [x for x in self.walk_reverse() if hasattr(x, '_refresh_grids_y_dimension') and x is not self]
for progenitor in progenitors:
progenitor._refresh_rows_minimum()
progenitor._refresh_height()
def on_children(self, instance, value):
""" If 'grow_cols' or 'grow_rows' is True this will grow layout that way if needed instead of erroring out. """
smax = self.get_max_widgets()
widget_count = len(value)
if smax and widget_count > smax:
increase_by = widget_count - smax
if self.grow_cols is True:
self.cols += increase_by
elif self.grow_rows is True:
self.rows += increase_by
super(Adaptive_GridLayout, self).on_children(instance, value)
def on_parent(self, instance, value):
""" Some adjustments maybe needed to get top row behaving on all platforms. """
self.trigger_refresh_y_dimension()
Above is from a project that I've published, it's pulled from a much larger project and may or may not suite those that want a bit more logic on the Python side for updating dimensions. Check the ReadMe
file for tips on installation within another project.
Upvotes: 0
Reputation: 29450
Don't use a BoxLayout, use a GridLayout with height: self.minimum_height
, and set a manual size (size_hint_y: None
and height: some_number
) for each child widget.
Upvotes: 10