Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #549 from kivy/uix-tabbedpanel

Uix tabbedpanel
  • Loading branch information...
commit 7a9c791455f27819b55ed2211f46997c9bcaf9f6 2 parents c9fe0d9 + dc1d190
@tito tito authored
View
49 examples/widgets/tabbed_panel_showcase.py
@@ -16,12 +16,12 @@ class StandingHeader(TabbedPanelHeader):
pass
-class ClosableHeader(TabbedPanelHeader):
+class CloseableHeader(TabbedPanelHeader):
pass
Factory.register('StandingHeader', cls = StandingHeader)
-Factory.register('ClosableHeader', cls = ClosableHeader)
+Factory.register('CloseableHeader', cls = CloseableHeader)
from kivy.lang import Builder
@@ -44,7 +44,7 @@ class ClosableHeader(TabbedPanelHeader):
size_hint: None, None
size: lbl.size
center_x: root.center_x
- center_y: root.center_y + (lbl.height/4)
+ center_y: root.center_y
Label:
id: lbl
text: root.text
@@ -55,7 +55,8 @@ class ClosableHeader(TabbedPanelHeader):
size_hint: (.45, .45)
pos_hint: {'center_x': .25, 'y': .55}
#replace the default tab with our custom tab
- default_tab: def_tab
+ default_tab_cls: sh.__class__
+ default_tab_content: default_content
tab_width: 40
tab_height: 70
FloatLayout:
@@ -74,17 +75,14 @@ class ClosableHeader(TabbedPanelHeader):
size: self.parent.size
source: 'data/images/image-loading.gif'
StandingHeader:
- id: def_tab
- content: default_content
- text: 'Default tab'
- StandingHeader:
+ id: sh
content: tab_2_content
text: 'tab 2'
StandingHeader:
content: tab_3_content
text: 'tab 3'
-<ClosableHeader>
+<CloseableHeader>
color: 0,0,0,0
# variable tab_width
text: 'tabx'
@@ -100,8 +98,8 @@ class ClosableHeader(TabbedPanelHeader):
text: root.text
BoxLayout:
size_hint: None, 1
- width: 22
orientation: 'vertical'
+ width: 22
Widget:
Button:
border: 0,0,0,0
@@ -114,13 +112,14 @@ class ClosableHeader(TabbedPanelHeader):
tab_pos: 'top_right'
size_hint: (.45, .45)
pos_hint: {'center_x': .75, 'y': .55}
+ #replace the default tab with our custom tab
default_tab: def_tab
#allow variable tab width
tab_width: None
FloatLayout:
RstDocument:
id: default_content
- text: '\\n'.join(("Closable tabs", "-------------",\
+ text: '\\n'.join(("Closeable tabs", "---------------",\
"- The tabs above are also scrollable",\
"- Tabs in \\'%s\\' position" %root.tab_pos))
Image:
@@ -138,33 +137,33 @@ class ClosableHeader(TabbedPanelHeader):
BubbleButton:
text: 'Press set this tab as default'
on_release: root.default_tab = tab3
- ClosableHeader:
+ CloseableHeader:
id: def_tab
text: 'default tab'
content:default_content
panel: root
- ClosableHeader:
+ CloseableHeader:
text: 'tab2'
content: tab_2_content
panel: root
- ClosableHeader:
+ CloseableHeader:
id: tab3
text: 'tab3'
content: tab_3_content
panel: root
- ClosableHeader:
+ CloseableHeader:
panel: root
- ClosableHeader:
+ CloseableHeader:
panel: root
- ClosableHeader:
+ CloseableHeader:
panel: root
- ClosableHeader:
+ CloseableHeader:
panel: root
- ClosableHeader:
+ CloseableHeader:
panel: root
- ClosableHeader:
+ CloseableHeader:
panel: root
- ClosableHeader:
+ CloseableHeader:
panel: root
<PanelbLeft>
@@ -259,9 +258,7 @@ class ClosableHeader(TabbedPanelHeader):
class Tp(TabbedPanel):
#override tab switching method to animate on tab switch
- def switch_to(self, header, *args):
- if header.content is None:
- return
+ def switch_to(self, header):
anim = Animation(color=(1, 1, 1, 0), d =.24, t = 'in_out_quad')
def start_anim(_anim, child, in_complete, *lt):
@@ -280,7 +277,7 @@ def _on_complete(*lt):
anim.bind(on_complete = _on_complete)
if self.content:
- start_anim(anim, self.content.children[0], False)
+ start_anim(anim, self.current_tab.content, False)
else:
_on_complete()
@@ -292,7 +289,7 @@ class PanelLeft(Tp):
class PanelRight(Tp):
def add_header(self):
- self.add_widget(ClosableHeader(panel = self))
+ self.add_widget(CloseableHeader(panel = self))
class PanelbLeft(Tp):
View
2  examples/widgets/tabbedpanel.py
@@ -7,9 +7,11 @@
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
+from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
+
<Test>:
size_hint: .5, .5
pos_hint: {'center_x': .5, 'center_y': .5}
View
162 kivy/uix/tabbedpanel.py
@@ -30,11 +30,6 @@
tab_pos = 'top_mid'
-By default, the widgets added to a tabbed panel are orderd horizontally, as in
-a Boxlayout. You can change that by::
-
- orientation = 'vertical'
-
An individual tab is called a TabbedPanelHeader. It is a special button
containing a content property. You add the TabbedPanelHeader first, and set its
content separately::
@@ -113,7 +108,7 @@
'''
__all__ = ('TabbedPanel', 'TabbedPanelContent', 'TabbedPanelHeader',
- 'TabbedPanelStrip')
+ 'TabbedPanelStrip', 'TabbedPanelException')
from functools import partial
from kivy.clock import Clock
@@ -123,13 +118,21 @@
from kivy.uix.scatter import Scatter
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
+from kivy.uix.floatlayout import FloatLayout
from kivy.logger import Logger
from kivy.properties import ObjectProperty, StringProperty, OptionProperty, \
ListProperty, NumericProperty, AliasProperty
+class TabbedPanelException(Exception):
+ '''The TabbedPanelException class.
+ '''
+ pass
+
+
class TabbedPanelHeader(ToggleButton):
- '''A button intended to be used as a Heading/Tab for TabbedPanel widget.
+ '''A Base for implementing a Tabbed Panel Head. A button intended to be
+ used as a Heading/Tab for TabbedPanel widget.
You can use this TabbedPanelHeader widget to add a new tab to TabbedPanel.
'''
@@ -137,7 +140,8 @@ class TabbedPanelHeader(ToggleButton):
content = ObjectProperty(None)
'''Content to be loaded when this tab header is selected.
- :data:`content` is a :class:`~kivy.properties.ObjectProperty`
+ :data:`content` is a :class:`~kivy.properties.ObjectProperty` default
+ to None .
'''
# only allow selecting the tab if not already selected
@@ -151,8 +155,6 @@ def on_touch_down(self, touch):
super(TabbedPanelHeader, self).on_touch_down(touch)
def on_release(self, *l):
- if not self.content:
- return
parent = self.parent
while parent is not None and not isinstance(parent, TabbedPanel):
parent = parent.parent
@@ -163,12 +165,18 @@ def on_release(self, *l):
class TabbedPanelStrip(GridLayout):
'''A strip intended to be used as background for Heading/Tab.
- See module documentation for details.
'''
tabbed_panel = ObjectProperty(None)
+ '''link to the panel that tab strip is a part of.
+ :data:`tabbed_panel` is a :class:`~kivy.properties.ObjectProperty` default
+ to None .
+ '''
-class TabbedPanelContent(GridLayout):
+
+class TabbedPanelContent(FloatLayout):
+ '''The TabbedPanelContent class.
+ '''
pass
@@ -202,13 +210,25 @@ class TabbedPanel(GridLayout):
default to 'atlas://data/images/defaulttheme/tab'.
'''
+ def get_current_tab(self):
+ return self._current_tab
+
+ current_tab = AliasProperty(get_current_tab, None)
+ '''Links to the currently select or active tab.
+
+ .. versionadded:: 1.4.0
+
+ :data:`current_tab` is a :class:`~kivy.AliasProperty`, read-only.
+ '''
+
tab_pos = OptionProperty('top_left',
options=('left_top', 'left_mid', 'left_bottom', 'top_left',
'top_mid', 'top_right', 'right_top', 'right_mid',
'right_bottom', 'bottom_left', 'bottom_mid', 'bottom_right'))
'''Specifies the position of the tabs relative to the content.
- Can be one of: left_top, left_mid, left_bottom top_left, top_mid, top_right
- right_top, right_mid, right_bottom bottom_left, bottom_mid, bottom_right.
+ Can be one of: `left_top`, `left_mid`, `left_bottom`, `top_left`,
+ `top_mid`, `top_right`, `right_top`, `right_mid`, `right_bottom`,
+ `bottom_left`, `bottom_mid`, `bottom_right`.
:data:`tab_pos` is a :class:`~kivy.properties.OptionProperty`,
default to 'bottom_mid'.
@@ -218,14 +238,14 @@ class TabbedPanel(GridLayout):
'''Specifies the height of the tab header.
:data:`tab_height` is a :class:`~kivy.properties.NumericProperty`,
- default to '20'.
+ default to 40.
'''
tab_width = NumericProperty(100, allownone=True)
'''Specifies the width of the tab header.
:data:`tab_width` is a :class:`~kivy.properties.NumericProperty`,
- default to '100'.
+ default to 100.
'''
default_tab_text = StringProperty('Default tab')
@@ -235,6 +255,18 @@ class TabbedPanel(GridLayout):
default to 'default tab'.
'''
+ default_tab_cls = ObjectProperty(TabbedPanelHeader)
+ '''Specifies the class to use for the styling of the default tab.
+
+ .. versionadded:: 1.4.0
+
+ .. warning::
+ `default_tab_cls` should be subclassed from `TabbedPanelHeader`
+
+ :data:`default_tab_cls` is a :class:`~kivy.properties.ObjectProperty`,
+ default to `TabbedPanelHeader`.
+ '''
+
def get_tab_list(self):
if self._tab_strip:
return self._tab_strip.children
@@ -258,13 +290,16 @@ def get_def_tab(self):
return self._default_tab
def set_def_tab(self, new_tab):
+ if not issubclass(new_tab.__class__, TabbedPanelHeader):
+ raise TabbedPanelException('`default_tab_class` should be\
+ subclassed from `TabbedPanelHeader`')
if self._default_tab == new_tab:
return
oltab = self._default_tab
self._default_tab = new_tab
- if self._original_tab == oltab:
+ if hasattr(self, '_original_tab') and self._original_tab == oltab:
self.remove_widget(oltab)
- self._origina_tab = None
+ self._original_tab = None
self.switch_to(new_tab)
new_tab.state = 'down'
@@ -290,15 +325,6 @@ def set_def_tab_content(self, *l):
:data:`default_tab_content` is a :class:`~kivy.properties.AliasProperty`
'''
- orientation = OptionProperty('horizontal',
- options=('horizontal', 'vertical'))
- '''This specifies the manner in which the children of the panel content
- are arranged. Can be one of 'vertical', 'horizontal'.
-
- :data:`orientation` is a :class:`~kivy.properties.OptionProperty`,
- default to 'horizontal'.
- '''
-
def __init__(self, **kwargs):
self._tab_layout = GridLayout(rows = 1)
self._bk_img = Image(
@@ -307,31 +333,56 @@ def __init__(self, **kwargs):
self._tab_strip = _tabs = TabbedPanelStrip(tabbed_panel=self,
rows=1, cols=99, size_hint=(None, None),\
height=self.tab_height, width=self.tab_width)
- self._original_tab = self._default_tab = default_tab = \
- TabbedPanelHeader(text=self.default_tab_text,
- height=self.tab_height, state='down',
- width=self.tab_width)
- _tabs.add_widget(default_tab)
- default_tab.group = '__tab%r__' %_tabs.uid
- self._partial_update_scrollview = None
+ self._partial_update_scrollview = None
self.content = content = TabbedPanelContent()
+
+ self._current_tab = default_tab = self._original_tab\
+ = self._default_tab = TabbedPanelHeader()
super(TabbedPanel, self).__init__(**kwargs)
- self.add_widget(content)
+
+ content = self._default_tab.content
+ cls = self.default_tab_cls
+ if not issubclass(cls, TabbedPanelHeader):
+ raise TabbedPanelException('`default_tab_class` should be\
+ subclassed from `TabbedPanelHeader`')
+
+ if cls != type(default_tab):
+ self._default_tab = cls()
+ default_tab = self.default_tab
+ default_tab.text = self.default_tab_text
+ default_tab.height = self.tab_height
+ default_tab.group = '__tab%r__' %_tabs.uid
+ default_tab.state = 'down'
+ default_tab.width = self.tab_width if self.tab_width else 100
+ default_tab.content = content
+
+ tl = self.tab_list
+ if default_tab not in tl:
+ _tabs.add_widget(default_tab, len(tl))
+
+ if default_tab.content:
+ self.clear_widgets()
+ self.add_widget(self.default_tab.content)
+ else:
+ Clock.schedule_once(self._load_default_tab_content)
+ self._current_tab = default_tab
self.on_tab_pos()
- #make default tab the active tab
- Clock.schedule_once(partial(self.switch_to, self._default_tab))
- def on_default_tab_text(self, *l):
+ def _load_default_tab_content(self, dt):
+ self.switch_to(self.default_tab)
+
+ def on_default_tab_text(self, *args):
self._default_tab.text = self.default_tab_text
- def switch_to(self, header, *dt):
+ def switch_to(self, header):
'''Switch to a specific panel header.
'''
header_content = header.content
+ self._current_tab = header
+ self.clear_widgets()
if header_content is None:
return
- self.clear_widgets()
# if content has a previous parent remove it from that parent
parent = header_content.parent
if parent:
@@ -346,11 +397,11 @@ def add_widget(self, widget, index=0):
super(TabbedPanel, self).add_widget(widget, index)
elif isinstance(widget, TabbedPanelHeader):
self_tabs = self._tab_strip
- self_tab_width = self.tab_width
self_tabs.add_widget(widget)
widget.group = '__tab%r__' % self_tabs.uid
self.on_tab_width()
else:
+ widget.pos_hint = {'x': 0, 'top': 1}
content.add_widget(widget, index)
def remove_widget(self, widget):
@@ -392,7 +443,7 @@ def clear_tabs(self, *l):
def reposition_tabs(self, *l):
Clock.unschedule(self.on_tab_pos)
- Clock.schedule_once(self.on_tab_pos)
+ Clock.schedule_once(self.on_tab_pos, 0)
def on_background_image(self, *l):
self._bk_img.source = self.background_image
@@ -402,17 +453,6 @@ def on_background_color(self, *l):
return
self._bk_img.color = self.background_color
- def on_orientation(self, *l):
- content = self.content
- if not content:
- return
- if self.orientation[0] == 'v':
- content.cols = 1
- content.rows = 99
- else:
- content.cols = 99
- content.rows = 1
-
def on_tab_width(self, *l):
Clock.unschedule(self.update_tab_width)
Clock.schedule_once(self.update_tab_width, 0)
@@ -430,7 +470,7 @@ def update_tab_width(self, *l):
# size_hint_x: x/.xyz
tab.size_hint_x = 1
#drop to default tab_width
- tsw = 100 * len(self._tab_strip.children)
+ tsw += 100
else:
# size_hint_x: None
tsw += tab.width
@@ -540,13 +580,12 @@ def on_tab_pos(self, *l):
if tab_pos[lentab_pos-4:] == '_top':
#on positions 'left_top' and 'right_top'
- sctr.bind(pos = Clock.schedule_once(
- partial(self._update_top, sctr, 'top', None), -1))
+ sctr.bind(pos = partial(self._update_top, sctr, 'top', None))
tab_list = (sctr, )
elif tab_pos[lentab_pos-4:] == '_mid':
#calculate top of scatter
- sctr.bind(pos = Clock.schedule_once(
- partial(self._update_top, sctr, 'mid', scrl_v.width), -1))
+ sctr.bind(pos = partial(self._update_top, sctr, 'mid',
+ scrl_v.width))
tab_list = (Widget(), sctr, Widget())
elif tab_pos[lentab_pos-7:] == '_bottom':
tab_list = (Widget(), Widget(), sctr)
@@ -566,7 +605,12 @@ def on_tab_pos(self, *l):
for widg in widget_list:
add(widg)
- def _update_top(self, sctr, top, scrl_v_width, dt):
+ def _update_top(self, *args):
+ sctr, top, scrl_v_width, x, y = args
+ Clock.unschedule(partial(self._updt_top, sctr, top, scrl_v_width))
+ Clock.schedule_once(partial(self._updt_top, sctr, top, scrl_v_width), 0)
+
+ def _updt_top(self, sctr, top, scrl_v_width, *args):
if top[0] == 't':
sctr.top = self.top
else:
Please sign in to comment.
Something went wrong with that request. Please try again.