Created
April 19, 2013 09:06
-
-
Save ygmpkk/5419095 to your computer and use it in GitHub Desktop.
python sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Accordion | |
========= | |
.. versionadded:: 1.0.8 | |
.. warning:: | |
This widget is still experimental, and its API is subject to change in a | |
future version. | |
.. image:: images/accordion.jpg | |
:align: right | |
The Accordion widget is a form of menu where the options are stacked either | |
vertically or horizontally, and the item in focus/when touched opens up | |
displaying his content. | |
The :class:`Accordion` will contain one or many :class:`AccordionItem` | |
instances, that will contain one root content widget. You'll have a Tree like | |
this: | |
- Accordion | |
- AccordionItem | |
- YourContent | |
- AccordionItem | |
- BoxLayout | |
- Another user content 1 | |
- Another user content 2 | |
- AccordionItem | |
- Another user content | |
The current implementation divides the :class:`AccordionItem` into two parts: | |
#. One container for the title bar | |
#. One container for the content | |
The title bar is made from a Kv template. We'll see how to create a new template | |
to customize the design of the title bar. | |
.. warning:: | |
If you see message like:: | |
[WARNING] [Accordion] not have enough space for displaying all children | |
[WARNING] [Accordion] need 440px, got 100px | |
[WARNING] [Accordion] layout aborted. | |
That means you have too many children, and there is no more space to | |
display any content. This is "normal", and nothing will be done. Try to | |
increase the space for the accordion, and reduce the number of children. You | |
can also reduce the :attr:`Accordion.min_space`. | |
Simple example | |
-------------- | |
.. include:: ../../examples/widgets/accordion_1.py | |
:literal: | |
Customize the accordion | |
----------------------- | |
You can increase the default size of the title bar:: | |
root = Accordion(min_space=60) | |
Or change the orientation to vertical:: | |
root = Accordion(orientation='vertical') | |
AccordionItem is more configurable, and you can set your own title background | |
when the item is collapsed or opened:: | |
item = AccordionItem(background_normal='image_when_collapsed.png', | |
background_selected='image_when_selected.png') | |
''' | |
__all__ = ('Accordion', 'AccordionItem', 'AccordionException') | |
from kivy.animation import Animation | |
from kivy.uix.floatlayout import FloatLayout | |
from kivy.clock import Clock | |
from kivy.lang import Builder | |
from kivy.properties import (ObjectProperty, StringProperty, | |
BooleanProperty, NumericProperty, | |
ListProperty, OptionProperty, DictProperty) | |
from kivy.uix.widget import Widget | |
from kivy.logger import Logger | |
class AccordionException(Exception): | |
'''AccordionException class. | |
''' | |
pass | |
class AccordionItem(FloatLayout): | |
'''AccordionItem class, that must be used in conjunction with | |
:class:`Accordion` class. See module documentation for more information. | |
''' | |
title = StringProperty('') | |
'''Title string of the item. The title might be used in conjuction with the | |
`AccordionItemTitle` template. If you are using a custom template, you can | |
use that property as a text entry, or not. By default, it's used for the | |
title text. See title_template and the example below. | |
:data:`title` is a :class:`~kivy.properties.StringProperty`, default to '' | |
''' | |
title_template = StringProperty('AccordionItemTitle') | |
'''Template to use for creating the title part of the accordion item. The | |
default template is a simple Label, not customizable (except the text) that | |
supports vertical and horizontal orientation, and different backgrounds for | |
collapse and selected mode. | |
It's better to create and use your own template, if the default template | |
does not suffice. | |
:data:`title` is a :class:`~kivy.properties.StringProperty`, default to | |
'AccordionItemTitle'. The current default template lives in the | |
`kivy/data/style.kv` file. | |
Here is the code if you want to build your own template:: | |
[AccordionItemTitle@Label]: | |
text: ctx.title | |
canvas.before: | |
Color: | |
rgb: 1, 1, 1 | |
BorderImage: | |
source: | |
ctx.item.background_normal \ | |
if ctx.item.collapse \ | |
else ctx.item.background_selected | |
pos: self.pos | |
size: self.size | |
PushMatrix | |
Translate: | |
xy: self.center_x, self.center_y | |
Rotate: | |
angle: 90 if ctx.item.orientation == 'horizontal' else 0 | |
axis: 0, 0, 1 | |
Translate: | |
xy: -self.center_x, -self.center_y | |
canvas.after: | |
PopMatrix | |
''' | |
title_args = DictProperty({}) | |
'''Default arguments that will be pass to the | |
:meth:`kivy.lang.Builder.template` method. | |
:data:`title_args` is a :class:`~kivy.properties.DictProperty`, default to | |
{} | |
''' | |
collapse = BooleanProperty(True) | |
'''Boolean to indicate if the current item is collapsed or not. | |
:data:`collapse` is a :class:`~kivy.properties.BooleanProperty`, default to | |
True | |
''' | |
collapse_alpha = NumericProperty(1.) | |
'''Value between 0 and 1 to indicate how much the item is collasped (1) or | |
selected (0). It's mostly used for animation. | |
:data:`collapse_alpha` is a :class:`~kivy.properties.NumericProperty`, | |
default to 1. | |
''' | |
accordion = ObjectProperty(None) | |
'''Instance of the :class:`Accordion` that the item belongs to. | |
:data:`accordion` is an :class:`~kivy.properties.ObjectProperty`, default to | |
None. | |
''' | |
background_normal = StringProperty( | |
'atlas://data/images/defaulttheme/button') | |
'''Background image of the accordion item used for default graphical | |
representation, when the item is collapsed. | |
:data:`background_normal` is an :class:`~kivy.properties.StringProperty`, | |
default to 'atlas://data/images/defaulttheme/button' | |
''' | |
background_selected = StringProperty( | |
'atlas://data/images/defaulttheme/button_pressed') | |
'''Background image of the accordion item used for default graphical | |
representation, when the item is selected (not collapsed). | |
:data:`background_normal` is an :class:`~kivy.properties.StringProperty`, | |
default to 'atlas://data/images/defaulttheme/button_pressed' | |
''' | |
orientation = OptionProperty('vertical', options=( | |
'horizontal', 'vertical')) | |
'''Link to the :attr:`Accordion.orientation` property. | |
''' | |
min_space = NumericProperty('44dp') | |
'''Link to the :attr:`Accordion.min_space` property. | |
''' | |
content_size = ListProperty([100, 100]) | |
'''(internal) Set by the :class:`Accordion` to the size allocated for the | |
content. | |
''' | |
container = ObjectProperty(None) | |
'''(internal) Property that will be set to the container of children, inside | |
the AccordionItem representation. | |
''' | |
container_title = ObjectProperty(None) | |
'''(internal) Property that will be set to the container of title, inside | |
the AccordionItem representation. | |
''' | |
def __init__(self, **kwargs): | |
self._trigger_title = Clock.create_trigger(self._update_title, -1) | |
self._anim_collapse = None | |
super(AccordionItem, self).__init__(**kwargs) | |
self.bind(title=self._trigger_title, | |
title_template=self._trigger_title, | |
title_args=self._trigger_title) | |
self._trigger_title() | |
def add_widget(self, widget): | |
if self.container is None: | |
return super(AccordionItem, self).add_widget(widget) | |
return self.container.add_widget(widget) | |
def remove_widget(self, widget): | |
if self.container: | |
self.container.remove_widget(widget) | |
super(AccordionItem, self).remove_widget(widget) | |
def on_collapse(self, instance, value): | |
accordion = self.accordion | |
if accordion is None: | |
return | |
if not value: | |
self.accordion.select(self) | |
collapse_alpha = float(value) | |
if self._anim_collapse: | |
self._anim_collapse.stop() | |
self._anim_collapse = None | |
if self.collapse_alpha != collapse_alpha: | |
self._anim_collapse = Animation( | |
collapse_alpha=collapse_alpha, | |
t=accordion.anim_func, | |
d=accordion.anim_duration).start(self) | |
def on_collapse_alpha(self, instance, value): | |
self.accordion._trigger_layout() | |
def on_touch_down(self, touch): | |
if not self.collide_point(*touch.pos): | |
return | |
if self.collapse: | |
self.collapse = False | |
return True | |
else: | |
return super(AccordionItem, self).on_touch_down(touch) | |
def _update_title(self, dt): | |
if not self.container_title: | |
self._trigger_title() | |
return | |
c = self.container_title | |
c.clear_widgets() | |
instance = Builder.template(self.title_template, | |
title=self.title, | |
item=self, | |
**self.title_args) | |
c.add_widget(instance) | |
class Accordion(Widget): | |
'''Accordion class. See module documentation for more information. | |
''' | |
orientation = OptionProperty('horizontal', options=( | |
'horizontal', 'vertical')) | |
'''Orientation of the layout. | |
:data:`orientation` is an :class:`~kivy.properties.OptionProperty`, default | |
to 'horizontal'. Can take a value of 'vertical' or 'horizontal'. | |
''' | |
anim_duration = NumericProperty(.25) | |
'''Duration of the animation in seconds, when a new accordion item is | |
selected. | |
:data:`anim_duration` is a :class:`~kivy.properties.NumericProperty`, | |
default to .25 (250ms) | |
''' | |
anim_func = ObjectProperty('out_expo') | |
'''Easing function to use for the animation. Check | |
:class:`kivy.animation.AnimationTransition` for more information about | |
available animation functions. | |
:data:`anim_func` is a :class:`~kivy.properties.ObjectProperty`, | |
default to 'out_expo'. You can set a string or a function to use as an | |
easing function. | |
''' | |
min_space = NumericProperty('44dp') | |
'''Minimum space to use for title of each item. This value is automatically | |
set on each children, each time the layout happens. | |
:data:`min_space` is a :class:`~kivy.properties.NumericProperty`, default to | |
44 (px). | |
''' | |
def __init__(self, **kwargs): | |
super(Accordion, self).__init__(**kwargs) | |
self._trigger_layout = Clock.create_trigger(self._do_layout, -1) | |
self.bind( | |
orientation=self._trigger_layout, | |
children=self._trigger_layout, | |
size=self._trigger_layout, | |
pos=self._trigger_layout, | |
min_space=self._trigger_layout) | |
def add_widget(self, widget, *largs): | |
if not isinstance(widget, AccordionItem): | |
raise AccordionException('Accordion accept only AccordionItem') | |
widget.accordion = self | |
ret = super(Accordion, self).add_widget(widget, *largs) | |
return ret | |
def select(self, instance): | |
if instance not in self.children: | |
raise AccordionException( | |
'Accordion: instance not found in children') | |
for widget in self.children: | |
if widget == instance: | |
continue | |
widget.collapse = True | |
self._trigger_layout() | |
def _do_layout(self, dt): | |
children = self.children | |
all_collapsed = all(x.collapse for x in children) | |
if all_collapsed: | |
children[0].collapse = False | |
orientation = self.orientation | |
min_space = self.min_space | |
min_space_total = len(children) * self.min_space | |
w, h = self.size | |
x, y = self.pos | |
if orientation == 'horizontal': | |
display_space = self.width - min_space_total | |
else: | |
display_space = self.height - min_space_total | |
if display_space <= 0: | |
Logger.warning('Accordion: not enough space ' | |
'for displaying all children') | |
Logger.warning('Accordion: need %dpx, got %dpx' % ( | |
min_space_total, min_space_total + display_space)) | |
Logger.warning('Accordion: layout aborted.') | |
return | |
if orientation == 'horizontal': | |
children = reversed(children) | |
for child in children: | |
child_space = min_space | |
child_space += display_space * (1 - child.collapse_alpha) | |
child._min_space = min_space | |
child.x = x | |
child.y = y | |
child.orientation = self.orientation | |
if orientation == 'horizontal': | |
child.content_size = display_space, h | |
child.width = child_space | |
child.height = h | |
x += child_space | |
else: | |
child.content_size = w, display_space | |
child.width = w | |
child.height = child_space | |
y += child_space | |
if __name__ == '__main__': | |
from kivy.base import runTouchApp | |
from kivy.uix.button import Button | |
from kivy.uix.boxlayout import BoxLayout | |
from kivy.uix.label import Label | |
acc = Accordion() | |
for x in xrange(10): | |
item = AccordionItem(title='Title %d' % x) | |
if x == 0: | |
item.add_widget(Button(text='Content %d' % x)) | |
elif x == 1: | |
l = BoxLayout(orientation='vertical') | |
l.add_widget(Button(text=str(x), size_hint_y=None, height=35)) | |
l.add_widget(Label(text='Content %d' % x)) | |
item.add_widget(l) | |
else: | |
item.add_widget(Label(text='This is a big content\n' * 20)) | |
acc.add_widget(item) | |
def toggle_layout(*l): | |
o = acc.orientation | |
acc.orientation = 'vertical' if o == 'horizontal' else 'horizontal' | |
btn = Button(text='Toggle layout') | |
btn.bind(on_release=toggle_layout) | |
from kivy.uix.slider import Slider | |
slider = Slider() | |
def update_min_space(instance, value): | |
acc.min_space = value | |
slider.bind(value=update_min_space) | |
root = BoxLayout(spacing=20, padding=20) | |
controls = BoxLayout(orientation='vertical', size_hint_x=.3) | |
controls.add_widget(btn) | |
controls.add_widget(slider) | |
root.add_widget(controls) | |
root.add_widget(acc) | |
runTouchApp(root) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment