'''
Box Layout
==========
.. only:: html
.. image:: images/boxlayout.gif
:align: right
.. only:: latex
.. image:: images/boxlayout.png
:align: right
:class:`BoxLayout` arranges children in a vertical or horizontal box.
To position widgets above/below each other, use a vertical BoxLayout::
layout = BoxLayout(orientation='vertical')
btn1 = Button(text='Hello')
btn2 = Button(text='World')
layout.add_widget(btn1)
layout.add_widget(btn2)
To position widgets next to each other, use a horizontal BoxLayout. In this
example, we use 10 pixel spacing between children; the first button covers
70% of the horizontal space, the second covers 30%::
layout = BoxLayout(spacing=10)
btn1 = Button(text='Hello', size_hint=(.7, 1))
btn2 = Button(text='World', size_hint=(.3, 1))
layout.add_widget(btn1)
layout.add_widget(btn2)
Position hints are partially working, depending on the orientation:
* If the orientation is `vertical`: `x`, `right` and `center_x` will be used.
* If the orientation is `horizontal`: `y`, `top` and `center_y` will be used.
You can check the `examples/widgets/boxlayout_poshint.py` for a live example.
.. note::
The `size_hint` uses the available space after subtracting all the
fixed-size widgets. For example, if you have a layout that is 800px
wide, and add three buttons like this:
btn1 = Button(text='Hello', size=(200, 100), size_hint=(None, None))
btn2 = Button(text='Kivy', size_hint=(.5, 1))
btn3 = Button(text='World', size_hint=(.5, 1))
The first button will be 200px wide as specified, the second and third
will be 300px each, e.g. (800-200) * 0.5
.. versionchanged:: 1.4.1
Added support for `pos_hint`.
'''
__all__ = ('BoxLayout', )
from kivy.uix.layout import Layout
from kivy.properties import (NumericProperty, OptionProperty,
VariableListProperty)
[docs]class BoxLayout(Layout):
'''Box layout class. See module documentation for more information.
'''
spacing = NumericProperty(0)
'''Spacing between children, in pixels.
:attr:`spacing` is a :class:`~kivy.properties.NumericProperty` and defaults
to 0.
'''
padding = VariableListProperty([0, 0, 0, 0])
'''Padding between layout box and children: [padding_left, padding_top,
padding_right, padding_bottom].
padding also accepts a two argument form [padding_horizontal,
padding_vertical] and a one argument form [padding].
.. versionchanged:: 1.7.0
Replaced NumericProperty with VariableListProperty.
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and
defaults to [0, 0, 0, 0].
'''
orientation = OptionProperty('horizontal', options=(
'horizontal', 'vertical'))
'''Orientation of the layout.
:attr:`orientation` is an :class:`~kivy.properties.OptionProperty` and
defaults to 'horizontal'. Can be 'vertical' or 'horizontal'.
'''
def __init__(self, **kwargs):
super(BoxLayout, self).__init__(**kwargs)
self.bind(
spacing=self._trigger_layout,
padding=self._trigger_layout,
children=self._trigger_layout,
orientation=self._trigger_layout,
parent=self._trigger_layout,
size=self._trigger_layout,
pos=self._trigger_layout)
def do_layout(self, *largs):
# optimize layout by preventing looking at the same attribute in a loop
len_children = len(self.children)
if len_children == 0:
return
selfx = self.x
selfy = self.y
selfw = self.width
selfh = self.height
padding_left = self.padding[0]
padding_top = self.padding[1]
padding_right = self.padding[2]
padding_bottom = self.padding[3]
spacing = self.spacing
orientation = self.orientation
padding_x = padding_left + padding_right
padding_y = padding_top + padding_bottom
# calculate maximum space used by size_hint
stretch_weight_x = 0.
stretch_weight_y = 0.
minimum_size_x = padding_x + spacing * (len_children - 1)
minimum_size_y = padding_y + spacing * (len_children - 1)
for w in self.children:
shw = w.size_hint_x
shh = w.size_hint_y
if shw is None:
minimum_size_x += w.width
else:
stretch_weight_x += shw
if shh is None:
minimum_size_y += w.height
else:
stretch_weight_y += shh
if orientation == 'horizontal':
x = padding_left
stretch_space = max(0.0, selfw - minimum_size_x)
for c in reversed(self.children):
shw = c.size_hint_x
shh = c.size_hint_y
w = c.width
h = c.height
cx = selfx + x
cy = selfy + padding_bottom
if shw:
w = stretch_space * shw / stretch_weight_x
if shh:
h = max(0, shh * (selfh - padding_y))
for key, value in c.pos_hint.items():
posy = value * (selfh - padding_y)
if key == 'y':
cy += padding_bottom + posy
elif key == 'top':
cy += padding_bottom + posy - h
elif key == 'center_y':
cy += padding_bottom - h / 2. + posy
c.x = cx
c.y = cy
c.width = w
c.height = h
x += w + spacing
if orientation == 'vertical':
y = padding_bottom
stretch_space = max(0.0, selfh - minimum_size_y)
for c in self.children:
shw = c.size_hint_x
shh = c.size_hint_y
w = c.width
h = c.height
cx = selfx + padding_left
cy = selfy + y
if shh:
h = stretch_space * shh / stretch_weight_y
if shw:
w = max(0, shw * (selfw - padding_x))
for key, value in c.pos_hint.items():
posx = value * (selfw - padding_x)
if key == 'x':
cx += padding_left + posx
elif key == 'right':
cx += padding_left + posx - w
elif key == 'center_x':
cx += padding_left - w / 2. + posx
c.x = cx
c.y = cy
c.width = w
c.height = h
y += h + spacing
def add_widget(self, widget, index=0):
widget.bind(
pos_hint=self._trigger_layout)
return super(BoxLayout, self).add_widget(widget, index)
def remove_widget(self, widget):
widget.unbind(
pos_hint=self._trigger_layout)
return super(BoxLayout, self).remove_widget(widget)