globalforest/kivymd/uix/tab.py

1149 lines
33 KiB
Python
Raw Permalink Normal View History

2020-10-14 00:19:43 -04:00
"""
Components/Tabs
===============
.. seealso::
`Material Design spec, Tabs <https://material.io/components/tabs>`_
.. rubric:: Tabs organize content across different screens, data sets,
and other interactions.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs.png
:align: center
.. Note:: Module provides tabs in the form of icons or text.
Usage
-----
To create a tab, you must create a new class that inherits from the
:class:`~MDTabsBase` class and the `Kivy` container, in which you will create
content for the tab.
.. code-block:: python
class Tab(FloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
.. code-block:: kv
<Tab>:
MDLabel:
text: "Content"
pos_hint: {"center_x": .5, "center_y": .5}
Tabs must be placed in the :class:`~MDTabs` container:
.. code-block:: kv
Root:
MDTabs:
Tab:
text: "Tab 1"
Tab:
text: "Tab 1"
...
Example with tab icon
---------------------
.. code-block:: python
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.icon_definitions import md_icons
KV = '''
BoxLayout:
orientation: "vertical"
MDToolbar:
title: "Example Tabs"
MDTabs:
id: tabs
on_tab_switch: app.on_tab_switch(*args)
<Tab>:
MDIconButton:
id: icon
icon: app.icons[0]
user_font_size: "48sp"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Tab(FloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
return Builder.load_string(KV)
def on_start(self):
for name_tab in self.icons:
self.root.ids.tabs.add_widget(Tab(text=name_tab))
def on_tab_switch(
self, instance_tabs, instance_tab, instance_tab_label, tab_text
):
'''Called when switching tabs.
:type instance_tabs: <kivymd.uix.tab.MDTabs object>;
:param instance_tab: <__main__.Tab object>;
:param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>;
:param tab_text: text or name icon of tab;
'''
count_icon = [k for k, v in md_icons.items() if v == tab_text]
instance_tab.ids.icon.icon = count_icon[0]
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example.gif
:align: center
Example with tab text
---------------------
.. Note:: The :class:`~MDTabsBase` class has an icon parameter and, by default,
tries to find the name of the icon in the file
``kivymd/icon_definitions.py``. If the name of the icon is not found,
then the name of the tab will be plain text, if found, the tab will look
like the corresponding icon.
.. code-block:: python
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
KV = '''
BoxLayout:
orientation: "vertical"
MDToolbar:
title: "Example Tabs"
MDTabs:
id: tabs
on_tab_switch: app.on_tab_switch(*args)
<Tab>:
MDLabel:
id: label
text: "Tab 0"
halign: "center"
'''
class Tab(FloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
for i in range(20):
self.root.ids.tabs.add_widget(Tab(text=f"Tab {i}"))
def on_tab_switch(
self, instance_tabs, instance_tab, instance_tab_label, tab_text
):
'''Called when switching tabs.
:type instance_tabs: <kivymd.uix.tab.MDTabs object>;
:param instance_tab: <__main__.Tab object>;
:param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>;
:param tab_text: text or name icon of tab;
'''
instance_tab.ids.label.text = tab_text
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-text.gif
:align: center
Example with tab icon and text
------------------------------
.. code-block:: python
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.font_definitions import fonts
from kivymd.icon_definitions import md_icons
KV = '''
BoxLayout:
orientation: "vertical"
MDToolbar:
title: "Example Tabs"
MDTabs:
id: tabs
'''
class Tab(FloatLayout, MDTabsBase):
pass
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
for name_tab in list(md_icons.keys())[15:30]:
self.root.ids.tabs.add_widget(
Tab(
text=f"[size=20][font={fonts[-1]['fn_regular']}]{md_icons[name_tab]}[/size][/font] {name_tab}"
)
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-icon-text.png
:align: center
Dynamic tab management
----------------------
.. code-block:: python
from kivy.lang import Builder
from kivy.uix.scrollview import ScrollView
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
KV = '''
BoxLayout:
orientation: "vertical"
MDToolbar:
title: "Example Tabs"
MDTabs:
id: tabs
<Tab>:
MDList:
MDBoxLayout:
adaptive_height: True
MDFlatButton:
text: "ADD TAB"
on_release: app.add_tab()
MDFlatButton:
text: "REMOVE LAST TAB"
on_release: app.remove_tab()
MDFlatButton:
text: "GET TAB LIST"
on_release: app.get_tab_list()
'''
class Tab(ScrollView, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
index = 0
def build(self):
return Builder.load_string(KV)
def on_start(self):
self.add_tab()
def get_tab_list(self):
'''Prints a list of tab objects.'''
print(self.root.ids.tabs.get_tab_list())
def add_tab(self):
self.index += 1
self.root.ids.tabs.add_widget(Tab(text=f"{self.index} tab"))
def remove_tab(self):
if self.index > 1:
self.index -= 1
self.root.ids.tabs.remove_widget(
self.root.ids.tabs.get_tab_list()[0]
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-dynamic-managmant.gif
:align: center
Use on_ref_press method
-----------------------
You can use markup for the text of the tabs and use the ``on_ref_press``
method accordingly:
.. code-block:: python
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivymd.font_definitions import fonts
from kivymd.uix.tab import MDTabsBase
from kivymd.icon_definitions import md_icons
KV = '''
BoxLayout:
orientation: "vertical"
MDToolbar:
title: "Example Tabs"
MDTabs:
id: tabs
on_ref_press: app.on_ref_press(*args)
<Tab>:
MDIconButton:
id: icon
icon: app.icons[0]
user_font_size: "48sp"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Tab(FloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
return Builder.load_string(KV)
def on_start(self):
for name_tab in self.icons:
self.root.ids.tabs.add_widget(
Tab(
text=f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]{md_icons['close']}[/font][/ref] {name_tab}"
)
)
def on_ref_press(
self,
instance_tabs,
instance_tab_label,
instance_tab,
instance_tab_bar,
instance_carousel,
):
'''
The method will be called when the ``on_ref_press`` event
occurs when you, for example, use markup text for tabs.
:param instance_tabs: <kivymd.uix.tab.MDTabs object>
:param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>
:param instance_tab: <__main__.Tab object>
:param instance_tab_bar: <kivymd.uix.tab.MDTabsBar object>
:param instance_carousel: <kivymd.uix.tab.MDTabsCarousel object>
'''
# Removes a tab by clicking on the close icon on the left.
for instance_tab in instance_carousel.slides:
if instance_tab.text == instance_tab_label.text:
instance_tabs.remove_widget(instance_tab_label)
break
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-on-ref-press.gif
:align: center
Switching the tab by name
-------------------------
.. code-block:: python
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.icon_definitions import md_icons
KV = '''
BoxLayout:
orientation: "vertical"
MDToolbar:
title: "Example Tabs"
MDTabs:
id: tabs
<Tab>:
MDIconButton:
id: icon
icon: "arrow-right"
user_font_size: "48sp"
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.switch_tab()
'''
class Tab(FloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
self.iter_list = iter(list(self.icons))
return Builder.load_string(KV)
def on_start(self):
for name_tab in list(self.icons):
self.root.ids.tabs.add_widget(Tab(text=name_tab))
def switch_tab(self):
'''Switching the tab by name.'''
try:
self.root.ids.tabs.switch_tab(next(self.iter_list))
except StopIteration:
pass
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switching-tab-by-name.gif
:align: center
"""
__all__ = ("MDTabs", "MDTabsBase")
from kivy.clock import Clock
from kivy.graphics import Rectangle
from kivy.lang import Builder
from kivy.properties import (
AliasProperty,
BooleanProperty,
BoundedNumericProperty,
ListProperty,
NumericProperty,
ObjectProperty,
StringProperty,
)
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.utils import boundary
from kivymd import fonts_path
from kivymd.icon_definitions import md_icons
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
RectangularElevationBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.carousel import MDCarousel
Builder.load_string(
"""
#:import DampedScrollEffect kivy.effects.dampedscroll.DampedScrollEffect
<MDTabsLabel>
size_hint: None, 1
halign: 'center'
padding: '12dp', 0
group: 'tabs'
font: root.font_name
allow_no_selection: False
markup: True
on_ref_press:
self.tab_bar.parent.dispatch(\
"on_ref_press",
self, \
self.tab, \
self.tab_bar, \
self.tab_bar.parent.carousel)
text_color_normal:
(\
(0, 0, 0, .5) \
if app.theme_cls.theme_style == 'Dark' and not self.text_color_normal \
else (1, 1, 1, .6) \
if app.theme_cls.theme_style == 'Light' and not self.text_color_normal \
else self.text_color_normal \
)
text_color_active:
(\
(0, 0, 0, .75) \
if app.theme_cls.theme_style == 'Dark' and not self.text_color_active \
else (1, 1, 1, 1) \
if app.theme_cls.theme_style == 'Light' and not self.text_color_normal \
else self.text_color_active
)
color:
self.text_color_active if self.state == 'down' \
else self.text_color_normal
on_x: self._trigger_update_tab_indicator()
on_width: self._trigger_update_tab_indicator()
<MDTabsScrollView>
size_hint: 1, 1
do_scroll_y: False
bar_color: 0, 0, 0, 0
bar_inactive_color: 0, 0, 0, 0
bar_width: 0
effect_cls: DampedScrollEffect
<MDTabs>
carousel: carousel
tab_bar: tab_bar
anchor_y: 'top'
background_palette: "Primary"
text_color_normal: self.specific_secondary_text_color
text_color_active: self.specific_text_color
MDTabsMain:
padding: 0, tab_bar.height, 0, 0
MDTabsCarousel:
id: carousel
lock_swiping: root.lock_swiping
ignore_perpendicular_swipes: True
anim_move_duration: root.anim_duration
on_index: root.on_carousel_index(*args)
on__offset: tab_bar.android_animation(*args)
on_slides: self.index = root.default_tab
on_slides: root.on_carousel_index(self, 0)
MDTabsBar:
id: tab_bar
carousel: carousel
scrollview: scrollview
layout: layout
size_hint: 1, None
elevation: root.elevation
height: root.tab_bar_height
md_bg_color: self.theme_cls.primary_color if not root.background_color else root.background_color
MDTabsScrollView:
id: scrollview
on_width: tab_bar._trigger_update_tab_bar()
MDGridLayout:
id: layout
rows: 1
size_hint_y: 1
adaptive_width: True
on_width: tab_bar._trigger_update_tab_bar()
canvas.after:
Color:
rgba: root.theme_cls.accent_color if not root.color_indicator else root.color_indicator
Rectangle:
pos: self.pos
size: 0, root.tab_indicator_height
"""
)
class MDTabsException(Exception):
pass
class MDTabsLabel(ToggleButtonBehavior, Label):
"""This class it represent the label of each tab."""
text_color_normal = ListProperty((1, 1, 1, 1))
text_color_active = ListProperty((1, 1, 1, 1))
tab = ObjectProperty()
tab_bar = ObjectProperty()
font_name = StringProperty("Roboto")
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.min_space = 0
def on_release(self):
self.tab_bar.parent.dispatch("on_tab_switch", self.tab, self, self.text)
# if the label is selected load the relative tab from carousel
if self.state == "down":
self.tab_bar.parent.carousel.load_slide(self.tab)
def on_texture(self, widget, texture):
# just save the minimum width of the label based of the content
if texture:
self.width = texture.width
self.min_space = self.width
def _trigger_update_tab_indicator(self):
# update the position and size of the indicator
# when the label changes size or position
if self.state == "down":
self.tab_bar.update_indicator(self.x, self.width)
class MDTabsBase(Widget):
"""
This class allow you to create a tab.
You must create a new class that inherits from MDTabsBase.
In this way you have total control over the views of your tabbed panel.
"""
text = StringProperty()
"""
It will be the label text of the tab.
:attr:`text` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
tab_label = ObjectProperty()
"""
It is the label object reference of the tab.
:attr:`tab_label` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
def __init__(self, **kwargs):
self.tab_label = MDTabsLabel(tab=self)
super().__init__(**kwargs)
def on_text(self, widget, text):
# Set the icon
if text in md_icons:
self.tab_label.font_name = (
fonts_path + "materialdesignicons-webfont.ttf"
)
self.tab_label.text = md_icons[self.text]
self.tab_label.font_size = "24sp"
# Set the label text
else:
self.tab_label.text = self.text
class MDTabsMain(MDBoxLayout):
"""
This class is just a boxlayout that contain the carousel.
It allows you to have control over the carousel.
"""
class MDTabsCarousel(MDCarousel):
lock_swiping = BooleanProperty(False)
"""
If True - disable switching tabs by swipe.
:attr:`lock_swiping` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
def on_touch_move(self, touch):
# lock a swiping
if self.lock_swiping:
return
if not self.touch_mode_change:
if self.ignore_perpendicular_swipes and self.direction in (
"top",
"bottom",
):
if abs(touch.oy - touch.y) < self.scroll_distance:
if abs(touch.ox - touch.x) > self.scroll_distance:
self._change_touch_mode()
self.touch_mode_change = True
elif self.ignore_perpendicular_swipes and self.direction in (
"right",
"left",
):
if abs(touch.ox - touch.x) < self.scroll_distance:
if abs(touch.oy - touch.y) > self.scroll_distance:
self._change_touch_mode()
self.touch_mode_change = True
if self._get_uid("cavoid") in touch.ud:
return
if self._touch is not touch:
super().on_touch_move(touch)
return self._get_uid() in touch.ud
if touch.grab_current is not self:
return True
ud = touch.ud[self._get_uid()]
direction = self.direction[0]
if ud["mode"] == "unknown":
if direction in "rl":
distance = abs(touch.ox - touch.x)
else:
distance = abs(touch.oy - touch.y)
if distance > self.scroll_distance:
ev = self._change_touch_mode_ev
if ev is not None:
ev.cancel()
ud["mode"] = "scroll"
else:
if direction in "rl":
self._offset += touch.dx
if direction in "tb":
self._offset += touch.dy
return True
class MDTabsScrollView(ScrollView):
"""This class hacked version to fix scroll_x manual setting."""
def goto(self, scroll_x, scroll_y):
"""Update event value along with scroll_*."""
def _update(e, x):
if e:
e.value = (e.max + e.min) * x
if not (scroll_x is None):
self.scroll_x = scroll_x
_update(self.effect_x, scroll_x)
if not (scroll_y is None):
self.scroll_y = scroll_y
_update(self.effect_y, scroll_y)
class MDTabsBar(ThemableBehavior, RectangularElevationBehavior, MDBoxLayout):
"""
This class is just a boxlayout that contains the scroll view for tabs.
He is also responsible for resizing the tab shortcut when necessary.
"""
target = ObjectProperty(None, allownone=True)
"""
Is the carousel reference of the next tab / slide.
When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the
target tab / slide of the carousel.
:attr:`target` is an :class:`~kivy.properties.ObjectProperty`
and default to `None`.
"""
def get_rect_instruction(self):
for i in self.layout.canvas.after.children:
if isinstance(i, Rectangle):
return i
indicator = AliasProperty(get_rect_instruction, cache=True)
"""
Is the Rectangle instruction reference of the tab indicator.
:attr:`indicator` is an :class:`~kivy.properties.AliasProperty`.
"""
def get_last_scroll_x(self):
return self.scrollview.scroll_x
last_scroll_x = AliasProperty(
get_last_scroll_x, bind=("target",), cache=True
)
"""
Is the carousel reference of the next tab/slide.
When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the
target tab/slide of the carousel.
:attr:`last_scroll_x` is an :class:`~kivy.properties.AliasProperty`.
"""
def __init__(self, **kwargs):
self._trigger_update_tab_bar = Clock.schedule_once(
self._update_tab_bar, 0
)
super().__init__(**kwargs)
def _update_tab_bar(self, *args):
if self.parent.allow_stretch:
# update width of the labels when it is needed
width, tabs = self.scrollview.width, self.layout.children
tabs_widths = [t.min_space for t in tabs if t.min_space]
tabs_space = float(sum(tabs_widths))
if not tabs_space:
return
ratio = width / tabs_space
use_ratio = True in (width / len(tabs) < w for w in tabs_widths)
for t in tabs:
t.width = (
t.min_space
if tabs_space > width
else t.min_space * ratio
if use_ratio is True
else width / len(tabs)
)
def update_indicator(self, x, w):
# update position and size of the indicator
self.indicator.pos = (x, 0)
self.indicator.size = (w, self.indicator.size[1])
def tab_bar_autoscroll(self, target, step):
# automatic scroll animation of the tab bar.
bound_left = self.center_x
bound_right = self.layout.width - bound_left
dt = target.center_x - bound_left
sx, sy = self.scrollview.convert_distance_to_scroll(dt, 0)
# last scroll x of the tab bar
lsx = self.last_scroll_x
# determine scroll direction
scroll_is_late = lsx < sx
# distance to run
dst = abs(lsx - sx) * step
if not dst:
return
if scroll_is_late and target.center_x > bound_left:
x = lsx + dst
elif not scroll_is_late and target.center_x < bound_right:
x = lsx - dst
x = boundary(x, 0.0, 1.0)
self.scrollview.goto(x, None)
def android_animation(self, carousel, offset):
# try to reproduce the android animation effect.
if offset != 0 and abs(offset) < carousel.width:
forward = offset < 0
offset = abs(offset)
step = offset / float(carousel.width)
distance = abs(offset - carousel.width)
threshold = self.parent.anim_threshold
breakpoint = carousel.width - (carousel.width * threshold)
traveled = distance / breakpoint if breakpoint else 0
break_step = 1.0 - traveled
indicator_animation = self.parent.tab_indicator_anim
skip_slide = (
carousel.slides[carousel._skip_slide]
if carousel._skip_slide is not None
else None
)
next_slide = (
carousel.next_slide if forward else carousel.previous_slide
)
self.target = skip_slide if skip_slide else next_slide
if not self.target:
return
a = carousel.current_slide.tab_label
b = self.target.tab_label
self.tab_bar_autoscroll(b, step)
if not indicator_animation:
return
if step <= threshold:
if forward:
gap_w = abs((a.x + a.width) - (b.x + b.width))
w_step = a.width + (gap_w * step)
x_step = a.x
else:
gap = abs((a.x - b.x))
x_step = a.x - gap * step
w_step = a.width + gap * step
else:
if forward:
x_step = a.x + abs((a.x - b.x)) * break_step
gap_w = abs((a.x + a.width) - (b.x + b.width))
ind_width = a.width + gap_w * threshold
gap_w = ind_width - b.width
w_step = ind_width - (gap_w * break_step)
else:
x_step = a.x - abs((a.x - b.x)) * threshold
x_step = x_step - abs(x_step - b.x) * break_step
ind_width = (
(a.x + a.width) - x_step if threshold else a.width
)
gap_w = ind_width - b.width
w_step = ind_width - (gap_w * break_step)
w_step = (
w_step
if w_step + x_step <= a.x + a.width
else ind_width
)
self.update_indicator(x_step, w_step)
class MDTabs(ThemableBehavior, SpecificBackgroundColorBehavior, AnchorLayout):
"""
You can use this class to create your own tabbed panel..
:Events:
`on_tab_switch`
Called when switching tabs.
`on_slide_progress`
Called while the slide is scrolling.
`on_ref_press`
The method will be called when the ``on_ref_press`` event
occurs when you, for example, use markup text for tabs.
"""
default_tab = NumericProperty(0)
"""
Index of the default tab.
:attr:`default_tab` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
tab_bar_height = NumericProperty("48dp")
"""
Height of the tab bar.
:attr:`tab_bar_height` is an :class:`~kivy.properties.NumericProperty`
and defaults to `'48dp'`.
"""
tab_indicator_anim = BooleanProperty(False)
"""
Tab indicator animation. If you want use animation set it to ``True``.
:attr:`tab_indicator_anim` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
tab_indicator_height = NumericProperty("2dp")
"""
Height of the tab indicator.
:attr:`tab_indicator_height` is an :class:`~kivy.properties.NumericProperty`
and defaults to `'2dp'`.
"""
anim_duration = NumericProperty(0.2)
"""
Duration of the slide animation.
:attr:`anim_duration` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
anim_threshold = BoundedNumericProperty(
0.8, min=0.0, max=1.0, errorhandler=lambda x: 0.0 if x < 0.0 else 1.0
)
"""
Animation threshold allow you to change the tab indicator animation effect.
:attr:`anim_threshold` is an :class:`~kivy.properties.BoundedNumericProperty`
and defaults to `0.8`.
"""
allow_stretch = BooleanProperty(True)
"""
If False - tabs will not stretch to full screen.
:attr:`allow_stretch` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
background_color = ListProperty()
"""
Background color of tabs in ``rgba`` format.
:attr:`background_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
text_color_normal = ListProperty((1, 1, 1, 1))
"""
Text color of the label when it is not selected.
:attr:`text_color_normal` is an :class:`~kivy.properties.ListProperty`
and defaults to `(1, 1, 1, 1)`.
"""
text_color_active = ListProperty((1, 1, 1, 1))
"""
Text color of the label when it is selected.
:attr:`text_color_active` is an :class:`~kivy.properties.ListProperty`
and defaults to `(1, 1, 1, 1)`.
"""
elevation = NumericProperty(0)
"""
Tab value elevation.
.. seealso::
`Behaviors/Elevation <https://kivymd.readthedocs.io/en/latest/behaviors/elevation/index.html>`_
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
color_indicator = ListProperty()
"""
Color indicator in ``rgba`` format.
:attr:`color_indicator` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
lock_swiping = BooleanProperty(False)
"""
If True - disable switching tabs by swipe.
:attr:`lock_swiping` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
font_name = StringProperty("Roboto")
"""
Font name for tab text.
:attr:`font_name` is an :class:`~kivy.properties.StringProperty`
and defaults to `'Roboto'`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.register_event_type("on_tab_switch")
self.register_event_type("on_ref_press")
self.register_event_type("on_slide_progress")
Clock.schedule_once(self._carousel_bind, 1)
def switch_tab(self, name_tab):
"""Switching the tab by name."""
for instance_tab in self.tab_bar.parent.carousel.slides:
if instance_tab.text == name_tab:
self.tab_bar.parent.carousel.load_slide(instance_tab)
break
def get_tab_list(self):
"""Returns a list of tab objects."""
return self.tab_bar.layout.children
def add_widget(self, widget, index=0, canvas=None):
# You can add only subclass of MDTabsBase.
if len(self.children) >= 2:
try:
widget.tab_label.group = str(self)
widget.tab_label.tab_bar = self.tab_bar
widget.tab_label.text_color_normal = self.text_color_normal
widget.tab_label.text_color_active = self.text_color_active
self.bind(
text_color_normal=widget.tab_label.setter(
"text_color_normal"
)
)
self.bind(
text_color_active=widget.tab_label.setter(
"text_color_active"
)
)
self.bind(font_name=widget.tab_label.setter("font_name"))
self.tab_bar.layout.add_widget(widget.tab_label)
self.carousel.add_widget(widget)
return
except AttributeError:
pass
return super().add_widget(widget)
def remove_widget(self, widget):
# You can remove only subclass of MDTabsLabel.
if not issubclass(widget.__class__, MDTabsLabel):
raise MDTabsException(
"MDTabs can remove only subclass of MDTabsLabel"
)
# The last tab is not deleted.
if len(self.tab_bar.layout.children) == 1:
return
self.tab_bar.layout.remove_widget(widget)
for tab in self.carousel.slides:
if tab.text == widget.text:
self.carousel.remove_widget(tab)
break
def on_slide_progress(self, *args):
"""Called while the slide is scrolling."""
def on_carousel_index(self, carousel, index):
"""Called when the carousel index changes."""
# when the index of the carousel change, update
# tab indicator, select the current tab and reset threshold data.
if carousel.current_slide:
current_tab_label = carousel.current_slide.tab_label
if current_tab_label.state == "normal":
current_tab_label._do_press()
current_tab_label.dispatch("on_release")
self.tab_bar.update_indicator(
current_tab_label.x, current_tab_label.width
)
def on_ref_press(self, *args):
"""The method will be called when the ``on_ref_press`` event
occurs when you, for example, use markup text for tabs."""
def on_tab_switch(self, *args):
"""Called when switching tabs."""
def _carousel_bind(self, i):
self.carousel.bind(on_slide_progress=self._on_slide_progress)
def _on_slide_progress(self, *args):
self.dispatch("on_slide_progress", args)