Version

Quick search

Table Of Contents

Source code for kivy.uix.stacklayout

'''
Stack Layout
============

.. only:: html

    .. image:: images/stacklayout.gif
        :align: right

.. only:: latex

    .. image:: images/stacklayout.png
        :align: right

.. versionadded:: 1.0.5

The :class:`StackLayout` arranges children vertically or horizontally, as many
as the layout can fit. The size of the individual children widgets do not
have to be uniform.

For example, to display widgets that get progressively larger in width::

    root = StackLayout()
    for i in range(25):
        btn = Button(text=str(i), width=40 + i * 5, size_hint=(None, 0.15))
        root.add_widget(btn)

.. image:: images/stacklayout_sizing.png
    :align: left
'''

__all__ = ('StackLayout', )

from kivy.uix.layout import Layout
from kivy.properties import NumericProperty, OptionProperty, \
    ReferenceListProperty, VariableListProperty


def _compute_size(c, available_size, idx):
    sh_min = c.size_hint_min[idx]
    sh_max = c.size_hint_max[idx]
    val = c.size_hint[idx] * available_size

    if sh_min is not None:
        if sh_max is not None:
            return max(min(sh_max, val), sh_min)
        return max(val, sh_min)
    if sh_max is not None:
        return min(sh_max, val)
    return val


[docs]class StackLayout(Layout): '''Stack layout class. See module documentation for more information. ''' spacing = VariableListProperty([0, 0], length=2) '''Spacing between children: [spacing_horizontal, spacing_vertical]. spacing also accepts a single argument form [spacing]. :attr:`spacing` is a :class:`~kivy.properties.VariableListProperty` and defaults to [0, 0]. ''' padding = VariableListProperty([0, 0, 0, 0]) '''Padding between the layout box and it's children: [padding_left, padding_top, padding_right, padding_bottom]. padding also accepts a two argument form [padding_horizontal, padding_vertical] and a single argument form [padding]. .. versionchanged:: 1.7.0 Replaced the NumericProperty with a VariableListProperty. :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and defaults to [0, 0, 0, 0]. ''' orientation = OptionProperty('lr-tb', options=( 'lr-tb', 'tb-lr', 'rl-tb', 'tb-rl', 'lr-bt', 'bt-lr', 'rl-bt', 'bt-rl')) '''Orientation of the layout. :attr:`orientation` is an :class:`~kivy.properties.OptionProperty` and defaults to 'lr-tb'. Valid orientations are 'lr-tb', 'tb-lr', 'rl-tb', 'tb-rl', 'lr-bt', 'bt-lr', 'rl-bt' and 'bt-rl'. .. versionchanged:: 1.5.0 :attr:`orientation` now correctly handles all valid combinations of 'lr','rl','tb','bt'. Before this version only 'lr-tb' and 'tb-lr' were supported, and 'tb-lr' was misnamed and placed widgets from bottom to top and from right to left (reversed compared to what was expected). .. note:: 'lr' means Left to Right. 'rl' means Right to Left. 'tb' means Top to Bottom. 'bt' means Bottom to Top. ''' minimum_width = NumericProperty(0) '''Minimum width needed to contain all children. It is automatically set by the layout. .. versionadded:: 1.0.8 :attr:`minimum_width` is a :class:`kivy.properties.NumericProperty` and defaults to 0. ''' minimum_height = NumericProperty(0) '''Minimum height needed to contain all children. It is automatically set by the layout. .. versionadded:: 1.0.8 :attr:`minimum_height` is a :class:`kivy.properties.NumericProperty` and defaults to 0. ''' minimum_size = ReferenceListProperty(minimum_width, minimum_height) '''Minimum size needed to contain all children. It is automatically set by the layout. .. versionadded:: 1.0.8 :attr:`minimum_size` is a :class:`~kivy.properties.ReferenceListProperty` of (:attr:`minimum_width`, :attr:`minimum_height`) properties. ''' def __init__(self, **kwargs): super(StackLayout, self).__init__(**kwargs) trigger = self._trigger_layout fbind = self.fbind fbind('padding', trigger) fbind('spacing', trigger) fbind('children', trigger) fbind('orientation', trigger) fbind('size', trigger) fbind('pos', trigger)
[docs] def do_layout(self, *largs): if not self.children: self.minimum_size = (0., 0.) return # optimize layout by preventing looking at the same attribute in a loop selfpos = self.pos selfsize = self.size orientation = self.orientation.split('-') padding_left = self.padding[0] padding_top = self.padding[1] padding_right = self.padding[2] padding_bottom = self.padding[3] padding_x = padding_left + padding_right padding_y = padding_top + padding_bottom spacing_x, spacing_y = self.spacing # Determine which direction and in what order to place the widgets posattr = [0] * 2 posdelta = [0] * 2 posstart = [0] * 2 for i in (0, 1): posattr[i] = 1 * (orientation[i] in ('tb', 'bt')) k = posattr[i] if orientation[i] == 'lr': # left to right posdelta[i] = 1 posstart[i] = selfpos[k] + padding_left elif orientation[i] == 'bt': # bottom to top posdelta[i] = 1 posstart[i] = selfpos[k] + padding_bottom elif orientation[i] == 'rl': # right to left posdelta[i] = -1 posstart[i] = selfpos[k] + selfsize[k] - padding_right else: # top to bottom posdelta[i] = -1 posstart[i] = selfpos[k] + selfsize[k] - padding_top innerattr, outerattr = posattr ustart, vstart = posstart deltau, deltav = posdelta del posattr, posdelta, posstart u = ustart # inner loop position variable v = vstart # outer loop position variable # space calculation, used for determining when a row or column is full if orientation[0] in ('lr', 'rl'): sv = padding_y # size in v-direction, for minimum_size property su = padding_x # size in h-direction spacing_u = spacing_x spacing_v = spacing_y padding_u = padding_x padding_v = padding_y else: sv = padding_x # size in v-direction, for minimum_size property su = padding_y # size in h-direction spacing_u = spacing_y spacing_v = spacing_x padding_u = padding_y padding_v = padding_x # space calculation, row height or column width, for arranging widgets lv = 0 urev = (deltau < 0) vrev = (deltav < 0) firstchild = self.children[0] sizes = [] lc = [] for c in reversed(self.children): if c.size_hint[outerattr] is not None: c.size[outerattr] = max( 1, _compute_size(c, selfsize[outerattr] - padding_v, outerattr)) # does the widget fit in the row/column? ccount = len(lc) totalsize = availsize = max( 0, selfsize[innerattr] - padding_u - spacing_u * ccount) if not lc: if c.size_hint[innerattr] is not None: childsize = max(1, _compute_size(c, totalsize, innerattr)) else: childsize = max(0, c.size[innerattr]) availsize = selfsize[innerattr] - padding_u - childsize testsizes = [childsize] else: testsizes = [0] * (ccount + 1) for i, child in enumerate(lc): if availsize <= 0: # no space left but we're trying to add another widget. availsize = -1 break if child.size_hint[innerattr] is not None: testsizes[i] = childsize = max( 1, _compute_size(child, totalsize, innerattr)) else: childsize = max(0, child.size[innerattr]) testsizes[i] = childsize availsize -= childsize if c.size_hint[innerattr] is not None: testsizes[-1] = max( 1, _compute_size(c, totalsize, innerattr)) else: testsizes[-1] = max(0, c.size[innerattr]) availsize -= testsizes[-1] # Tiny value added in order to avoid issues with float precision # causing unexpected children reordering when parent resizes. # e.g. if size is 101 and children size_hint_x is 1./5 # 5 children would not fit in one line because 101*(1./5) > 101/5 if (availsize + 1e-10) >= 0 or not lc: # even if there's no space, we always add one widget to a row lc.append(c) sizes = testsizes lv = max(lv, c.size[outerattr]) continue # apply the sizes for i, child in enumerate(lc): if child.size_hint[innerattr] is not None: child.size[innerattr] = sizes[i] # push the line sv += lv + spacing_v for c2 in lc: if urev: u -= c2.size[innerattr] c2.pos[innerattr] = u pos_outer = v if vrev: # v position is actually the top/right side of the widget # when going from high to low coordinate values, # we need to subtract the height/width from the position. pos_outer -= c2.size[outerattr] c2.pos[outerattr] = pos_outer if urev: u -= spacing_u else: u += c2.size[innerattr] + spacing_u v += deltav * lv v += deltav * spacing_v lc = [c] lv = c.size[outerattr] if c.size_hint[innerattr] is not None: sizes = [ max(1, _compute_size(c, selfsize[innerattr] - padding_u, innerattr))] else: sizes = [max(0, c.size[innerattr])] u = ustart if lc: # apply the sizes for i, child in enumerate(lc): if child.size_hint[innerattr] is not None: child.size[innerattr] = sizes[i] # push the last (incomplete) line sv += lv + spacing_v for c2 in lc: if urev: u -= c2.size[innerattr] c2.pos[innerattr] = u pos_outer = v if vrev: pos_outer -= c2.size[outerattr] c2.pos[outerattr] = pos_outer if urev: u -= spacing_u else: u += c2.size[innerattr] + spacing_u self.minimum_size[outerattr] = sv