Source code for kivyx.uix.boxlayout

'''
*(Tested on CPython3.9.7 + Kivy2.1.0)*

公式の :external:kivy:doc:`api-kivy.uix.boxlayout` よりも子の並べ方が豊富なBoxLayout。
公式の物は左→右と上→下の二種類の並べ方しかないが、これはそれらに加えて右→左と下→上にも対応する。
'''

__all__ = ('KXBoxLayout', )

from kivy.uix.layout import Layout
from kivy.properties import (
    NumericProperty, OptionProperty, VariableListProperty, AliasProperty, ReferenceListProperty,
)


[docs]class KXBoxLayout(Layout): spacing = NumericProperty(0) '''公式と同じ''' padding = VariableListProperty([0, 0, 0, 0]) '''公式と同じ''' minimum_width = NumericProperty() '''公式と同じ''' minimum_height = NumericProperty() '''公式と同じ''' minimum_size = ReferenceListProperty(minimum_width, minimum_height) '''公式と同じ''' orientation = OptionProperty('lr', options=('lr', 'rl', 'tb', 'bt')) '''子widget達をどのように並べるか。lrは左から右。rlは右から左。tbは上から下。btは下から上。''' is_horizontal = AliasProperty(lambda self: self.orientation in ('lr', 'rl'), bind=('orientation', ), cache=True) ''' 子widget達の並べ方が横方向であるかどうかを表す読み取り専用property。 :attr:`orientation` が ``lr`` / ``rl`` であれば真。そうでなければ偽となる。 ''' def __init__(self, **kwargs): super().__init__(**kwargs) update = self._trigger_layout f = self.fbind f('spacing', update) f('padding', update) f('children', update) f('orientation', update) f('parent', update) f('size', update) f('pos', update) def _iterate_layout(self, sizes): # optimize layout by preventing looking at the same attribute in a loop len_children = len(sizes) indices = tuple(range(len_children)) padding_left, padding_top, padding_right, padding_bottom = self.padding spacing = self.spacing orientation = self.orientation is_horizontal = self.is_horizontal 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 is_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 zip(indices, 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 zip(indices, 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 is_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 zip(indices, 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) zipped_iter = zip(indices, hint, sizes) if (orientation in {'rl', 'tb'}) \ else zip(reversed(indices), reversed(hint), reversed(sizes)) if is_horizontal: x = padding_left + selfx size_y = self.height - padding_y for i, sh, ((w, h), (_, shh), pos_hint, _, _) in zipped_iter: 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 i, 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 zipped_iter: 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().add_widget(widget, *args, **kwargs) def remove_widget(self, widget, *args, **kwargs): widget.funbind('pos_hint', self._trigger_layout) return super().remove_widget(widget, *args, **kwargs)