test-kivy-app/kivy_venv/lib/python3.11/site-packages/kivy/uix/boxlayout.py
2024-09-15 15:12:16 +03:00

332 lines
11 KiB
Python

'''
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.
Kv Example::
BoxLayout:
orientation: 'vertical'
Label:
text: 'this on top'
Label:
text: 'this right aligned'
size_hint_x: None
size: self.texture_size
pos_hint: {'right': 1}
Label:
text: 'this on bottom'
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, ReferenceListProperty)
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'.
'''
minimum_width = NumericProperty(0)
'''Automatically computed minimum width needed to contain all children.
.. versionadded:: 1.10.0
:attr:`minimum_width` is a :class:`~kivy.properties.NumericProperty` and
defaults to 0. It is read only.
'''
minimum_height = NumericProperty(0)
'''Automatically computed minimum height needed to contain all children.
.. versionadded:: 1.10.0
:attr:`minimum_height` is a :class:`~kivy.properties.NumericProperty` and
defaults to 0. It is read only.
'''
minimum_size = ReferenceListProperty(minimum_width, minimum_height)
'''Automatically computed minimum size needed to contain all children.
.. versionadded:: 1.10.0
:attr:`minimum_size` is a
:class:`~kivy.properties.ReferenceListProperty` of
(:attr:`minimum_width`, :attr:`minimum_height`) properties. It is read
only.
'''
def __init__(self, **kwargs):
super(BoxLayout, self).__init__(**kwargs)
update = self._trigger_layout
fbind = self.fbind
fbind('spacing', update)
fbind('padding', update)
fbind('children', update)
fbind('orientation', update)
fbind('parent', update)
fbind('size', update)
fbind('pos', update)
def _iterate_layout(self, sizes):
# optimize layout by preventing looking at the same attribute in a loop
len_children = len(sizes)
padding_left, padding_top, padding_right, padding_bottom = self.padding
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_sum = 0.
has_bound = False
hint = [None] * len_children
# min size from all the None hint, and from those with sh_min
minimum_size_bounded = 0
if orientation == 'horizontal':
minimum_size_y = 0
minimum_size_none = padding_x + spacing * (len_children - 1)
for i, ((w, h), (shw, shh), _, (shw_min, shh_min),
(shw_max, _)) in enumerate(sizes):
if shw is None:
minimum_size_none += w
else:
hint[i] = shw
if shw_min:
has_bound = True
minimum_size_bounded += shw_min
elif shw_max is not None:
has_bound = True
stretch_sum += shw
if shh is None:
minimum_size_y = max(minimum_size_y, h)
elif shh_min:
minimum_size_y = max(minimum_size_y, shh_min)
minimum_size_x = minimum_size_bounded + minimum_size_none
minimum_size_y += padding_y
else:
minimum_size_x = 0
minimum_size_none = padding_y + spacing * (len_children - 1)
for i, ((w, h), (shw, shh), _, (shw_min, shh_min),
(_, shh_max)) in enumerate(sizes):
if shh is None:
minimum_size_none += h
else:
hint[i] = shh
if shh_min:
has_bound = True
minimum_size_bounded += shh_min
elif shh_max is not None:
has_bound = True
stretch_sum += shh
if shw is None:
minimum_size_x = max(minimum_size_x, w)
elif shw_min:
minimum_size_x = max(minimum_size_x, shw_min)
minimum_size_y = minimum_size_bounded + minimum_size_none
minimum_size_x += padding_x
self.minimum_size = minimum_size_x, minimum_size_y
# do not move the w/h get above, it's likely to change on above line
selfx = self.x
selfy = self.y
if orientation == 'horizontal':
stretch_space = max(0.0, self.width - minimum_size_none)
dim = 0
else:
stretch_space = max(0.0, self.height - minimum_size_none)
dim = 1
if has_bound:
# make sure the size_hint_min/max are not violated
if stretch_space < 1e-9:
# there's no space, so just set to min size or zero
stretch_sum = stretch_space = 1.
for i, val in enumerate(sizes):
sh = val[1][dim]
if sh is None:
continue
sh_min = val[3][dim]
if sh_min is not None:
hint[i] = sh_min
else:
hint[i] = 0. # everything else is zero
else:
# hint gets updated in place
self.layout_hint_with_bounds(
stretch_sum, stretch_space, minimum_size_bounded,
(val[3][dim] for val in sizes),
(elem[4][dim] for elem in sizes), hint)
if orientation == 'horizontal':
x = padding_left + selfx
size_y = self.height - padding_y
for i, (sh, ((w, h), (_, shh), pos_hint, _, _)) in enumerate(
zip(reversed(hint), reversed(sizes))):
cy = selfy + padding_bottom
if sh:
w = max(0., stretch_space * sh / stretch_sum)
if shh:
h = max(0, shh * size_y)
for key, value in pos_hint.items():
posy = value * size_y
if key == 'y':
cy += posy
elif key == 'top':
cy += posy - h
elif key == 'center_y':
cy += posy - (h / 2.)
yield len_children - i - 1, x, cy, w, h
x += w + spacing
else:
y = padding_bottom + selfy
size_x = self.width - padding_x
for i, (sh, ((w, h), (shw, _), pos_hint, _, _)) in enumerate(
zip(hint, sizes)):
cx = selfx + padding_left
if sh:
h = max(0., stretch_space * sh / stretch_sum)
if shw:
w = max(0, shw * size_x)
for key, value in pos_hint.items():
posx = value * size_x
if key == 'x':
cx += posx
elif key == 'right':
cx += posx - w
elif key == 'center_x':
cx += posx - (w / 2.)
yield i, cx, y, w, h
y += h + spacing
def do_layout(self, *largs):
children = self.children
if not children:
l, t, r, b = self.padding
self.minimum_size = l + r, t + b
return
for i, x, y, w, h in self._iterate_layout(
[(c.size, c.size_hint, c.pos_hint, c.size_hint_min,
c.size_hint_max) for c in children]):
c = children[i]
c.pos = x, y
shw, shh = c.size_hint
if shw is None:
if shh is not None:
c.height = h
else:
if shh is None:
c.width = w
else:
c.size = (w, h)
def add_widget(self, widget, *args, **kwargs):
widget.fbind('pos_hint', self._trigger_layout)
return super(BoxLayout, self).add_widget(widget, *args, **kwargs)
def remove_widget(self, widget, *args, **kwargs):
widget.funbind('pos_hint', self._trigger_layout)
return super(BoxLayout, self).remove_widget(widget, *args, **kwargs)