Version

Quick search

Table Of Contents

Source code for kivy.uix.splitter

'''Splitter
======

.. versionadded:: 1.5.0

.. image:: images/splitter.jpg
    :align: right

The :class:`Splitter` is a widget that helps you re-size its child
widget/layout by letting you re-size it via dragging the boundary or
double tapping the boundary. This widget is similar to the
:class:`~kivy.uix.scrollview.ScrollView` in that it allows only one
child widget.

Usage::

    splitter = Splitter(sizable_from = 'right')
    splitter.add_widget(layout_or_widget_instance)
    splitter.min_size = 100
    splitter.max_size = 250

To change the size of the strip/border used for resizing::

    splitter.strip_size = '10pt'

To change its appearance::

    splitter.strip_cls = your_custom_class

You can also change the appearance of the `strip_cls`, which defaults to
:class:`SplitterStrip`, by overriding the `kv` rule in your app:

.. code-block:: kv

    <SplitterStrip>:
        horizontal: True if self.parent and self.parent.sizable_from[0] \
in ('t', 'b') else False
        background_normal: 'path to normal horizontal image' \
if self.horizontal else 'path to vertical normal image'
        background_down: 'path to pressed horizontal image' \
if self.horizontal else 'path to vertical pressed image'

'''


__all__ = ('Splitter', )

from kivy.factory import Factory
from kivy.uix.button import Button
from kivy.properties import (OptionProperty, NumericProperty, ObjectProperty,
                             ListProperty, BooleanProperty)
from kivy.uix.boxlayout import BoxLayout


class SplitterStrip(Button):
    '''Class used for the graphical representation of a
    :class:`kivy.uix.splitter.SplitterStripe`.
    '''
    pass


[docs]class Splitter(BoxLayout): '''See module documentation. :Events: `on_press`: Fired when the splitter is pressed. `on_release`: Fired when the splitter is released. .. versionchanged:: 1.6.0 Added `on_press` and `on_release` events. ''' border = ListProperty([4, 4, 4, 4]) '''Border used for the :class:`~kivy.graphics.vertex_instructions.BorderImage` graphics instruction. This must be a list of four values: (bottom, right, top, left). Read the BorderImage instructions for more information about how to use it. :attr:`border` is a :class:`~kivy.properties.ListProperty` and defaults to (4, 4, 4, 4). ''' strip_cls = ObjectProperty(SplitterStrip) '''Specifies the class of the resize Strip. :attr:`strip_cls` is an :class:`kivy.properties.ObjectProperty` and defaults to :class:`~kivy.uix.splitter.SplitterStrip`, which is of type :class:`~kivy.uix.button.Button`. .. versionchanged:: 1.8.0 If you set a string, the :class:`~kivy.factory.Factory` will be used to resolve the class. ''' sizable_from = OptionProperty('left', options=( 'left', 'right', 'top', 'bottom')) '''Specifies whether the widget is resizable. Options are: `left`, `right`, `top` or `bottom` :attr:`sizable_from` is an :class:`~kivy.properties.OptionProperty` and defaults to `left`. ''' strip_size = NumericProperty('10pt') '''Specifies the size of resize strip :attr:`strp_size` is a :class:`~kivy.properties.NumericProperty` defaults to `10pt` ''' min_size = NumericProperty('100pt') '''Specifies the minimum size beyond which the widget is not resizable. :attr:`min_size` is a :class:`~kivy.properties.NumericProperty` and defaults to `100pt`. ''' max_size = NumericProperty('500pt') '''Specifies the maximum size beyond which the widget is not resizable. :attr:`max_size` is a :class:`~kivy.properties.NumericProperty` and defaults to `500pt`. ''' _parent_proportion = NumericProperty(0.) '''(internal) Specifies the distance that the slider has travelled across its parent, used to automatically maintain a sensible position if the parent is resized. :attr:`_parent_proportion` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. .. versionadded:: 1.9.0 ''' _bound_parent = ObjectProperty(None, allownone=True) '''(internal) References the widget whose size is currently being tracked by :attr:`_parent_proportion`. :attr:`_bound_parent` is a :class:`~kivy.properties.ObjectProperty` and defaults to None. .. versionadded:: 1.9.0 ''' keep_within_parent = BooleanProperty(False) '''If True, will limit the splitter to stay within its parent widget. :attr:`keep_within_parent` is a :class:`~kivy.properties.BooleanProperty` and defaults to False. .. versionadded:: 1.9.0 ''' rescale_with_parent = BooleanProperty(False) '''If True, will automatically change size to take up the same proportion of the parent widget when it is resized, while staying within :attr:`min_size` and :attr:`max_size`. As long as these attributes can be satisfied, this stops the :class:`Splitter` from exceeding the parent size during rescaling. :attr:`rescale_with_parent` is a :class:`~kivy.properties.BooleanProperty` and defaults to False. .. versionadded:: 1.9.0 ''' __events__ = ('on_press', 'on_release') def __init__(self, **kwargs): self._container = None self._strip = None super(Splitter, self).__init__(**kwargs) do_size = self._do_size fbind = self.fbind fbind('max_size', do_size) fbind('min_size', do_size) fbind('parent', self._rebind_parent) def on_sizable_from(self, instance, sizable_from): if not instance._container: return sup = super(Splitter, instance) _strp = instance._strip if _strp: # remove any previous binds _strp.unbind(on_touch_down=instance.strip_down) _strp.unbind(on_touch_move=instance.strip_move) _strp.unbind(on_touch_up=instance.strip_up) self.unbind(disabled=_strp.setter('disabled')) sup.remove_widget(instance._strip) cls = instance.strip_cls if not isinstance(_strp, cls): if isinstance(cls, str): cls = Factory.get(cls) instance._strip = _strp = cls() sz_frm = instance.sizable_from[0] if sz_frm in ('l', 'r'): _strp.size_hint = None, 1 _strp.width = instance.strip_size instance.orientation = 'horizontal' instance.unbind(strip_size=_strp.setter('width')) instance.bind(strip_size=_strp.setter('width')) else: _strp.size_hint = 1, None _strp.height = instance.strip_size instance.orientation = 'vertical' instance.unbind(strip_size=_strp.setter('height')) instance.bind(strip_size=_strp.setter('height')) index = 1 if sz_frm in ('r', 'b'): index = 0 sup.add_widget(_strp, index) _strp.bind(on_touch_down=instance.strip_down) _strp.bind(on_touch_move=instance.strip_move) _strp.bind(on_touch_up=instance.strip_up) _strp.disabled = self.disabled self.bind(disabled=_strp.setter('disabled'))
[docs] def add_widget(self, widget, index=0, *args, **kwargs): if self._container or not widget: return Exception('Splitter accepts only one Child') self._container = widget sz_frm = self.sizable_from[0] if sz_frm in ('l', 'r'): widget.size_hint_x = 1 else: widget.size_hint_y = 1 index = 0 if sz_frm in ('r', 'b'): index = 1 super(Splitter, self).add_widget(widget, index, *args, **kwargs) self.on_sizable_from(self, self.sizable_from)
[docs] def remove_widget(self, widget, *args, **kwargs): super(Splitter, self).remove_widget(widget, *args, **kwargs) if widget == self._container: self._container = None
[docs] def clear_widgets(self, *args, **kwargs): self.remove_widget(self._container)
def strip_down(self, instance, touch): if not instance.collide_point(*touch.pos): return False touch.grab(self) self.dispatch('on_press') def on_press(self): pass def _rebind_parent(self, instance, new_parent): if self._bound_parent is not None: self._bound_parent.unbind(size=self.rescale_parent_proportion) if self.parent is not None: new_parent.bind(size=self.rescale_parent_proportion) self._bound_parent = new_parent self.rescale_parent_proportion() def rescale_parent_proportion(self, *args): if not self.parent: return if self.rescale_with_parent: parent_proportion = self._parent_proportion if self.sizable_from in ('top', 'bottom'): new_height = parent_proportion * self.parent.height self.height = max(self.min_size, min(new_height, self.max_size)) else: new_width = parent_proportion * self.parent.width self.width = max(self.min_size, min(new_width, self.max_size)) def _do_size(self, instance, value): if self.sizable_from[0] in ('l', 'r'): self.width = max(self.min_size, min(self.width, self.max_size)) else: self.height = max(self.min_size, min(self.height, self.max_size)) @staticmethod def _is_moving(sz_frm, diff, pos, minpos, maxpos): if sz_frm in ('l', 'b'): cmp = minpos else: cmp = maxpos if diff == 0: return False elif diff > 0 and pos <= cmp: return False elif diff < 0 and pos >= cmp: return False return True def strip_move(self, instance, touch): if touch.grab_current is not instance: return False max_size = self.max_size min_size = self.min_size sz_frm = self.sizable_from[0] if sz_frm in ('t', 'b'): diff_y = (touch.dy) self_y = self.y self_top = self.top if not self._is_moving(sz_frm, diff_y, touch.y, self_y, self_top): return if self.keep_within_parent: if sz_frm == 't' and (self_top + diff_y) > self.parent.top: diff_y = self.parent.top - self_top elif sz_frm == 'b' and (self_y + diff_y) < self.parent.y: diff_y = self.parent.y - self_y if sz_frm == 'b': diff_y *= -1 if self.size_hint_y: self.size_hint_y = None if self.height > 0: self.height += diff_y else: self.height = 1 height = self.height self.height = max(min_size, min(height, max_size)) self._parent_proportion = self.height / self.parent.height else: diff_x = (touch.dx) self_x = self.x self_right = self.right if not self._is_moving(sz_frm, diff_x, touch.x, self_x, self_right): return if self.keep_within_parent: if sz_frm == 'l' and (self_x + diff_x) < self.parent.x: diff_x = self.parent.x - self_x elif (sz_frm == 'r' and (self_right + diff_x) > self.parent.right): diff_x = self.parent.right - self_right if sz_frm == 'l': diff_x *= -1 if self.size_hint_x: self.size_hint_x = None if self.width > 0: self.width += diff_x else: self.width = 1 width = self.width self.width = max(min_size, min(width, max_size)) self._parent_proportion = self.width / self.parent.width def strip_up(self, instance, touch): if touch.grab_current is not instance: return if touch.is_double_tap: max_size = self.max_size min_size = self.min_size sz_frm = self.sizable_from[0] s = self.size if sz_frm in ('t', 'b'): if self.size_hint_y: self.size_hint_y = None if s[1] - min_size <= max_size - s[1]: self.height = max_size else: self.height = min_size else: if self.size_hint_x: self.size_hint_x = None if s[0] - min_size <= max_size - s[0]: self.width = max_size else: self.width = min_size touch.ungrab(instance) self.dispatch('on_release') def on_release(self): pass
if __name__ == '__main__': from kivy.app import App from kivy.uix.button import Button from kivy.uix.floatlayout import FloatLayout class SplitterApp(App): def build(self): root = FloatLayout() bx = BoxLayout() bx.add_widget(Button()) bx.add_widget(Button()) bx2 = BoxLayout() bx2.add_widget(Button()) bx2.add_widget(Button()) bx2.add_widget(Button()) spl = Splitter( size_hint=(1, .25), pos_hint={'top': 1}, sizable_from='bottom') spl1 = Splitter( sizable_from='left', size_hint=(None, 1), width=90) spl1.add_widget(Button()) bx.add_widget(spl1) spl.add_widget(bx) spl2 = Splitter(size_hint=(.25, 1)) spl2.add_widget(bx2) spl2.sizable_from = 'right' root.add_widget(spl) root.add_widget(spl2) return root SplitterApp().run()