Sideband/sbapp/kivymd/uix/tab/tab.py

1973 lines
66 KiB
Python
Executable File

"""
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(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
.. code-block:: kv
<Tab>
MDLabel:
text: root.content_text
pos_hint: {"center_x": .5, "center_y": .5}
All tabs must be contained inside a :class:`~MDTabs` widget:
.. code-block:: kv
Root:
MDTabs:
Tab:
title: "Tab 1"
content_text: f"This is an example text for {self.title}"
Tab:
title: "Tab 2"
content_text: f"This is an example text for {self.title}"
...
Example with tab icon
---------------------
.. tabs::
.. tab:: Declarative KV and imperative python styles
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.icon_definitions import md_icons
KV = '''
MDBoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "Example Tabs"
MDTabs:
id: tabs
on_tab_switch: app.on_tab_switch(*args)
<Tab>
MDIconButton:
id: icon
icon: root.icon
icon_size: "48sp"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Tab(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def on_start(self):
for tab_name in self.icons:
self.root.ids.tabs.add_widget(Tab(icon=tab_name))
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 = instance_tab.icon # get the tab icon
print(f"Welcome to {count_icon}' tab'")
Example().run()
.. tab:: Declarative python styles
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
from kivymd.uix.tab import MDTabsBase, MDTabs
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.icon_definitions import md_icons
from kivymd.uix.toolbar import MDTopAppBar
class Tab(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDBoxLayout(
MDTopAppBar(title="Example Tabs"),
MDTabs(id="tabs"),
orientation="vertical",
)
)
def on_start(self):
self.root.ids.tabs.bind(on_tab_switch=self.on_tab_switch)
for tab_name in self.icons:
self.root.ids.tabs.add_widget(
Tab(
MDIconButton(
icon=tab_name,
icon_size="48sp",
pos_hint={"center_x": .5, "center_y": .5},
),
icon=tab_name,
)
)
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 = instance_tab.icon # get the tab icon
print(f"Welcome to {count_icon}' tab'")
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, the class will send a message
stating that the icon could not be found.
if the tab has no icon, title or tab_label_text, the class will raise a
ValueError.
.. tabs::
.. tab:: Declarative KV and imperative python styles
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.tab import MDTabsBase
KV = '''
MDBoxLayout:
orientation: "vertical"
MDTopAppBar:
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(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def on_start(self):
for i in range(20):
self.root.ids.tabs.add_widget(Tab(title=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()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.label import MDLabel
from kivymd.uix.tab import MDTabsBase, MDTabs
from kivymd.uix.toolbar import MDTopAppBar
class Tab(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDBoxLayout(
MDTopAppBar(title="Example Tabs"),
MDTabs(id="tabs"),
orientation="vertical",
)
)
def on_start(self):
self.root.ids.tabs.bind(on_tab_switch=self.on_tab_switch)
for i in range(20):
self.root.ids.tabs.add_widget(
Tab(
MDLabel(id="label", text="Tab 0", halign="center"),
title=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
------------------------------
.. tabs::
.. tab:: Declarative KV and imperative python styles
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.icon_definitions import md_icons
KV = '''
MDBoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "Example Tabs"
MDTabs:
id: tabs
'''
class Tab(MDFloatLayout, MDTabsBase):
pass
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
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(icon=name_tab, title=name_tab))
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.tab import MDTabsBase, MDTabs
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.icon_definitions import md_icons
from kivymd.uix.toolbar import MDTopAppBar
class Tab(MDFloatLayout, MDTabsBase):
pass
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDBoxLayout(
MDTopAppBar(title="Example Tabs"),
MDTabs(id="tabs"),
orientation="vertical",
)
)
def on_start(self):
for name_tab in list(md_icons.keys())[15:30]:
self.root.ids.tabs.add_widget(Tab(icon=name_tab, title=name_tab))
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-icon-text.gif
:align: center
Dynamic tab management
----------------------
.. tabs::
.. tab:: Declarative KV and imperative python styles
.. code-block:: python
from kivy.lang import Builder
from kivymd.uix.scrollview import MDScrollView
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
KV = '''
MDBoxLayout:
orientation: "vertical"
MDTopAppBar:
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(MDScrollView, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
index = 0
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
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(title=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()[-1]
)
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.uix.button import MDFlatButton
from kivymd.uix.list import MDList
from kivymd.uix.scrollview import MDScrollView
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.tab import MDTabsBase, MDTabs
from kivymd.uix.toolbar import MDTopAppBar
class Tab(MDScrollView, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
index = 0
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDBoxLayout(
MDTopAppBar(title="Example Tabs"),
MDTabs(id="tabs"),
orientation="vertical",
)
)
def on_start(self):
self.add_tab()
def get_tab_list(self, *args):
'''Prints a list of tab objects.'''
print(self.root.ids.tabs.get_tab_list())
def add_tab(self, *args):
self.index += 1
self.root.ids.tabs.add_widget(
Tab(
MDList(
MDBoxLayout(
MDFlatButton(
text="ADD TAB",
on_release=self.add_tab,
),
MDFlatButton(
text="REMOVE LAST TAB",
on_release=self.remove_tab,
),
MDFlatButton(
text="GET TAB LIST",
on_release=self.get_tab_list,
),
adaptive_height=True,
),
),
title=f"{self.index} tab",
)
)
def remove_tab(self, *args):
if self.index > 1:
self.index -= 1
self.root.ids.tabs.remove_widget(
self.root.ids.tabs.get_tab_list()[-1]
)
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:
.. tabs::
.. tab:: Declarative KV and imperative python styles
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.font_definitions import fonts
from kivymd.uix.tab import MDTabsBase
from kivymd.icon_definitions import md_icons
KV = '''
MDBoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "Example Tabs"
MDTabs:
id: tabs
on_ref_press: app.on_ref_press(*args)
<Tab>
MDIconButton:
id: icon
icon: app.icons[0]
icon_size: "48sp"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Tab(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def on_start(self):
for name_tab in self.icons:
self.root.ids.tabs.add_widget(
Tab(
title=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.title == instance_tab_label.text:
instance_tabs.remove_widget(instance_tab_label)
break
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.font_definitions import fonts
from kivymd.uix.tab import MDTabsBase, MDTabs
from kivymd.icon_definitions import md_icons
from kivymd.uix.toolbar import MDTopAppBar
class Tab(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDBoxLayout(
MDTopAppBar(title="Example Tabs"),
MDTabs(id="tabs"),
orientation="vertical",
)
)
def on_start(self):
self.root.ids.tabs.bind(on_ref_press=self.on_ref_press)
for name_tab in self.icons:
self.root.ids.tabs.add_widget(
Tab(
MDIconButton(
icon=self.icons[0],
icon_size="48sp",
pos_hint={"center_x": .5, "center_y": .5}
),
title=(
f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]"
f"{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.title == 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
-------------------------
.. tabs::
.. tab:: Declarative KV and imperative python styles
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.icon_definitions import md_icons
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.tab import MDTabsBase
KV = '''
MDBoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "Example Tabs"
MDTabs:
id: tabs
<Tab>
MDBoxLayout:
orientation: "vertical"
pos_hint: {"center_x": .5, "center_y": .5}
adaptive_size: True
spacing: dp(48)
MDIconButton:
id: icon
icon: "arrow-right"
icon_size: "48sp"
on_release: app.switch_tab_by_name()
MDIconButton:
id: icon2
icon: "page-next"
icon_size: "48sp"
on_release: app.switch_tab_by_object()
'''
class Tab(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
self.iter_list_names = 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(tab_label_text=name_tab))
self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list()))
def switch_tab_by_object(self):
try:
x = next(self.iter_list_objects)
print(f"Switch slide by object, next element to show: [{x}]")
self.root.ids.tabs.switch_tab(x)
except StopIteration:
# reset the iterator an begin again.
self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list()))
self.switch_tab_by_object()
def switch_tab_by_name(self):
'''Switching the tab by name.'''
try:
x = next(self.iter_list_names)
print(f"Switch slide by name, next element to show: [{x}]")
self.root.ids.tabs.switch_tab(x)
except StopIteration:
# Reset the iterator an begin again.
self.iter_list_names = iter(list(self.icons))
self.switch_tab_by_name()
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.icon_definitions import md_icons
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.tab import MDTabsBase, MDTabs
from kivymd.uix.toolbar import MDTopAppBar
class Tab(MDFloatLayout, MDTabsBase):
'''Class implementing content for a tab.'''
class Example(MDApp):
icons = list(md_icons.keys())[15:30]
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
self.iter_list_names = iter(list(self.icons))
return (
MDBoxLayout(
MDTopAppBar(title="Example Tabs"),
MDTabs(id="tabs"),
orientation="vertical",
)
)
def on_start(self):
for name_tab in list(self.icons):
self.root.ids.tabs.add_widget(
Tab(
MDBoxLayout(
MDIconButton(
id="icon",
icon="arrow-right",
icon_size="48sp",
on_release=self.switch_tab_by_name,
),
MDIconButton(
id="icon2",
icon="arrow-left",
icon_size="48sp",
on_release=self.switch_tab_by_object,
),
orientation="vertical",
pos_hint={"center_x": .5, "center_y": .5},
adaptive_size=True,
spacing=dp(48),
),
tab_label_text=name_tab,
)
)
self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list()))
def switch_tab_by_object(self, *args):
try:
x = next(self.iter_list_objects)
print(f"Switch slide by object, next element to show: [{x}]")
self.root.ids.tabs.switch_tab(x)
except StopIteration:
# reset the iterator an begin again.
self.iter_list_objects = iter(
list(self.root.ids.tabs.get_tab_list()))
self.switch_tab_by_object()
def switch_tab_by_name(self, *args):
'''Switching the tab by name.'''
try:
x = next(self.iter_list_names)
print(f"Switch slide by name, next element to show: [{x}]")
self.root.ids.tabs.switch_tab(x)
except StopIteration:
# Reset the iterator an begin again.
self.iter_list_names = iter(list(self.icons))
self.switch_tab_by_name()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switching-tab-by-name.gif
:align: center
"""
__all__ = ("MDTabs", "MDTabsBase")
import os
from typing import Union
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.metrics import dp
from kivy.properties import (
AliasProperty,
BooleanProperty,
BoundedNumericProperty,
ColorProperty,
ListProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
)
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.scrollview import ScrollView
from kivy.utils import boundary
from kivymd import uix_path
from kivymd.font_definitions import fonts, theme_font_styles
from kivymd.icon_definitions import md_icons
from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.uix.behaviors import (
DeclarativeBehavior,
RectangularRippleBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
from kivymd.uix.carousel import MDCarousel
from kivymd.uix.label import MDLabel
with open(os.path.join(uix_path, "tab", "tab.kv"), encoding="utf-8") as kv_file:
Builder.load_string(kv_file.read())
class MDTabsException(Exception):
pass
class MDTabsLabel(ToggleButtonBehavior, RectangularRippleBehavior, MDLabel):
"""This class it represent the label of each tab."""
text_color_normal = ColorProperty(None)
text_color_active = ColorProperty(None)
tab = ObjectProperty()
tab_bar = ObjectProperty()
font_name = StringProperty("Roboto")
def __init__(self, **kwargs):
self.split_str = " ,-"
super().__init__(**kwargs)
self.max_lines = 2
self.size_hint_x = None
self.size_hint_min_x = dp(90)
self.min_space = dp(98)
self.bind(
text=self._update_text_size,
)
def on_release(self) -> None:
try:
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)
except KeyError:
pass
def on_texture(self, instance_tabs_label, texture: Texture) -> None:
# Just save the minimum width of the label based of the content.
if texture:
max_width = dp(360)
min_width = dp(90)
if texture.width > max_width:
self.width = max_width
self.text_size = (max_width, None)
elif texture.width < min_width:
self.width = min_width
else:
self.width = texture.width
def _update_text_size(self, *args):
if not self.tab_bar:
return
if self.tab_bar.parent.allow_stretch is True:
self.text_size = (None, None)
else:
self.width = self.tab_bar.parent.fixed_tab_label_width
self.text_size = (self.width, None)
Clock.schedule_once(self.tab_bar._label_request_indicator_update, 0)
class MDTabsBase:
"""
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.
"""
icon = StringProperty()
"""
This property will set the Tab's Label Icon.
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
title_icon_mode = OptionProperty("Lead", options=["Lead", "Top"])
"""
This property sets the mode in wich the tab's title and icon are shown.
:attr:`title_icon_mode` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Lead'`.
"""
title = StringProperty()
"""
This property will set the Name of the tab.
.. note::
As a side note.
All tabs have set `markup = True`.
Thanks to this, you can use the kivy markup language to set a colorful
and fully customizable tabs titles.
.. warning::
The material design requires that every title label is written in
capital letters, because of this, the `string.upper()` will be applied
to it's contents.
:attr:`title` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
title_is_capital = BooleanProperty(False)
"""
This value controls wether if the title property should be converted to
capital letters.
:attr:`title_is_capital` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
tab_label_text = StringProperty()
"""
This property is the actual title's Label of the tab.
use the property :attr:`icon` and :attr:`title` to set this property
correctly.
This property is kept public for specific and backward compatibility
purposes.
:attr:`tab_label_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 _get_label_font_style(self):
if self.tab_label:
return self.tab_label.font_style
def _set_label_font_style(self, value):
if self.tab_label:
if value in theme_font_styles:
self.tab_label.font_style = value
else:
raise ValueError(
"tab_label_font_style:\n\t"
"font_style not found in theme_font_styles\n\t"
f"font_style = {value}"
)
else:
Clock.schedule_once(lambda x: self._set_label_font_style(value))
return True
tab_label_font_style = AliasProperty(
_get_label_font_style,
_set_label_font_style,
cache=True,
)
"""
:attr:`tab_label_font_style` is an :class:`~kivy.properties.AliasProperty`
that behavies similar to an :class:`~kivy.properties.OptionProperty`.
This property's behavior allows the developer to use any new label style
registered to the app.
This property will affect the Tab's Title Label widget.
"""
def __init__(self, *args, **kwargs):
self.tab_label = MDTabsLabel(tab=self)
super().__init__(*args, **kwargs)
self.bind(
icon=self._update_text,
title=self._update_text,
title_icon_mode=self._update_text,
tab_label_text=self.update_label_text,
title_is_capital=self.update_label_text,
)
Clock.schedule_once(
self._update_text
) # this will ensure the text is correct
def _update_text(self, *args):
# Ensures that the title is in capital letters.
if self.title and self.title_is_capital is True:
if self.title != self.title.upper():
self.title = self.title.upper()
# Avoids event recursion.
return
# Add the icon.
if self.icon and self.icon in md_icons:
self.tab_label_text = f"[size=24sp][font={fonts[-1]['fn_regular']}]{md_icons[self.icon]}[/size][/font]"
if self.title:
self.tab_label_text = (
self.tab_label_text
+ (" " if self.title_icon_mode == "Lead" else "\n")
+ self.title
)
# Add the title.
else:
if self.icon:
Logger.error(
f"{self}: [UID] = [{self.uid}]:\n\t"
f"Icon '{self.icon}' not found in md_icons"
)
if self.title:
self.tab_label_text = self.title
else:
if not self.tab_label_text:
raise ValueError(
f"{self}: [UID] = [{self.uid}]:\n\t"
"No valid Icon was found.\n\t"
"No valid Title was found.\n\t"
f"Icon\t= '{self.icon}'\n\t"
f"Title\t= '{self.title}'\n\t"
)
self.tab_label.padding = dp(16), 0
self.update_label_text(None, self.tab_label_text)
def update_label_text(self, instance_user_tab, text_tab: str) -> None:
self.tab_label.text = text_tab
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):
if self.lock_swiping: # lock a 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: Union[float, None], scroll_y: Union[float, None]
) -> None:
"""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(MDCard):
"""
This class is just a boxlayout that contains the scroll view for tabs.
It is also responsible for resizing the tab shortcut when necessary.
"""
target = ObjectProperty(None, allownone=True)
"""
It 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):
canvas_instructions = self.layout.canvas.before.get_group(
"Indicator_line"
)
return canvas_instructions[0]
indicator = AliasProperty(get_rect_instruction, cache=True)
"""
It is the :class:`~kivy.graphics.vertex_instructions.RoundedRectangle`
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):
super().__init__(**kwargs)
def update_indicator(
self, x: Union[float, int], w: Union[float, int], radius=None
) -> None:
# Update position and size of the indicator.
if self.parent.tab_indicator_type == "line-round":
self.parent._line_x = x
self.parent._line_width = w
self.parent._line_height = self.parent.tab_indicator_height
self.parent._line_radius = self.parent.tab_indicator_height / 2
elif self.parent.tab_indicator_type == "line-rect":
self.parent._line_x = x
self.parent._line_width = w
self.parent._line_height = self.parent.tab_indicator_height
else:
self.indicator.pos = (x, 0)
self.indicator.size = (w, self.parent.tab_indicator_height)
if radius:
self.indicator.radius = radius
def tab_bar_autoscroll(self, instance_tab_label: MDTabsLabel, step: float):
# Automatic scroll animation of the tab bar.
bound_left = self.center_x - self.x
bound_right = self.layout.width - bound_left
dt = instance_tab_label.center_x - bound_left
sx, sy = self.scrollview.convert_distance_to_scroll(dt, 0)
lsx = self.last_scroll_x # ast scroll x of the tab bar
scroll_is_late = lsx < sx # determine scroll direction
dst = abs(lsx - sx) * step # distance to run
if not dst:
return
if scroll_is_late and instance_tab_label.center_x > bound_left:
x = lsx + dst
elif not scroll_is_late and instance_tab_label.center_x < bound_right:
x = lsx - dst
else:
return
x = boundary(x, 0.0, 1.0)
self.scrollview.goto(x, None)
def android_animation(
self, instance_carousel: MDTabsCarousel, offset: Union[float, int]
):
# Try to reproduce the android animation effect.
if offset != 0 and abs(offset) < instance_carousel.width:
forward = offset < 0
offset = abs(offset)
step = offset / float(instance_carousel.width)
indicator_animation = self.parent.tab_indicator_anim
skip_slide = (
instance_carousel.slides[instance_carousel._skip_slide]
if instance_carousel._skip_slide is not None
else None
)
next_slide = (
instance_carousel.next_slide
if forward
else instance_carousel.previous_slide
)
self.target = skip_slide if skip_slide else next_slide
if not self.target:
return
a = instance_carousel.current_slide.tab_label
b = self.target.tab_label
self.tab_bar_autoscroll(b, step)
# Avoids the animation if `indicator_animation` is True.
if indicator_animation is False:
return
gap_x = abs((a.x) - (b.x))
gap_w = (b.width) - (a.width)
if forward:
x_step = a.x + (gap_x * step)
else:
x_step = a.x - gap_x * step
w_step = a.width + (gap_w * step)
self.update_indicator(x_step, w_step)
def _label_request_indicator_update(self, *args):
widget = self.carousel.current_slide.tab_label
self.update_indicator(widget.x, widget.width)
class MDTabs(
DeclarativeBehavior,
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.
"""
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_padding = ListProperty([0, 0, 0, 0])
"""
Padding of the tab bar.
:attr:`tab_padding` is an :class:`~kivy.properties.ListProperty`
and defaults to `[0, 0, 0, 0]`.
"""
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'`.
"""
tab_indicator_type = OptionProperty(
"line", options=["line", "fill", "round", "line-round", "line-rect"]
)
"""
Type of tab indicator. Available options are: `'line'`, `'fill'`,
`'round'`, `'line-rect'` and `'line-round'`.
:attr:`tab_indicator_type` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'line'`.
"""
tab_hint_x = BooleanProperty(False)
"""
This option affects the size of each child. if it's `True`, the size of
each tab will be ignored and will use the size available by the container.
:attr:`tab_hint_x` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
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 `True`, the tab will update dynamically (if :attr:`tab_hint_x` is `True`)
to it's content width, and wrap any text if the widget is wider than `"360dp"`.
If `False`, the tab won't update to it's maximum texture width.
this means that the `fixed_tab_label_width` will be used as the label
width. this will wrap any text inside to fit the fixed value.
:attr:`allow_stretch` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
fixed_tab_label_width = NumericProperty("140dp")
"""
If :attr:`allow_stretch` is `False`, the class will set this value as the
width to all the tabs title label.
:attr:`fixed_tab_label_width` is an :class:`~kivy.properties.NumericProperty`
and defaults to `140dp`.
"""
background_color = ColorProperty(None)
"""
Background color of tabs in ``rgba`` format.
:attr:`background_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
underline_color = ColorProperty([0, 0, 0, 0])
"""
Underline color of tabs in ``rgba`` format.
:attr:`underline_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
text_color_normal = ColorProperty(None)
"""
Text color of the label when it is not selected.
:attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
text_color_active = ColorProperty(None)
"""
Text color of the label when it is selected.
:attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
shadow_softness = NumericProperty(12)
"""
See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_softness`
attribute.
.. versionadded:: 1.1.0
:attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
and defaults to `12`.
"""
shadow_color = ColorProperty([0, 0, 0, 0.6])
"""
See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_color`
attribute.
.. versionadded:: 1.1.0
:attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0.6]`.
"""
shadow_offset = ListProperty((0, 0))
"""
See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_offset`
attribute.
.. versionadded:: 1.1.0
:attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
and defaults to `[0, 0]`.
"""
elevation = NumericProperty(0)
"""
See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.elevation`
attribute.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
indicator_color = ColorProperty(None)
"""
Color indicator in ``rgba`` format.
:attr:`indicator_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
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'`.
"""
ripple_duration = NumericProperty(2)
"""
Ripple duration when long touching to tab.
:attr:`ripple_duration` is an :class:`~kivy.properties.NumericProperty`
and defaults to `2`.
"""
no_ripple_effect = BooleanProperty(True)
"""
Whether to use the ripple effect when tapping on a tab.
:attr:`no_ripple_effect` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
title_icon_mode = OptionProperty("Lead", options=["Lead", "Top"])
"""
This property sets the mode in wich the tab's title and icon are shown.
:attr:`title_icon_mode` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Lead'`.
"""
force_title_icon_mode = BooleanProperty(True)
"""
If this property is se to `True`, it will force the class to update every
tab inside the scroll view to the current `title_icon_mode`
:attr:`force_title_icon_mode` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **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)
self.theme_cls.bind(
primary_palette=self.update_icon_color,
theme_style=self.update_icon_color,
)
self.bind(
force_title_icon_mode=self._parse_icon_mode,
title_icon_mode=self._parse_icon_mode,
)
self.bind(tab_hint_x=self._update_tab_hint_x)
def update_icon_color(
self,
instance_theme_manager: ThemeManager,
name_theme_style_name_palette: str,
) -> None:
"""
Called when the app's color scheme or style has changed
(dark theme/light theme).
"""
for tab_label in self.get_tab_list():
if not self.text_color_normal:
tab_label.text_color_normal = self.theme_cls.text_color
if not self.text_color_active:
tab_label.text_color_active = self.specific_secondary_text_color
def switch_tab(self, name_tab: Union[MDTabsLabel, str], search_by="text"):
"""
This method switch between tabs
name_tab can be either a String or a :class:`~MDTabsBase`.
`search_by` will look up through the properties of every tab.
If the value doesnt match, it will raise a ValueError.
Search_by options:
text : will search by the raw text of the label (`tab_label_text`)
icon : will search by the `icon` property
title : will search by the `title` property
"""
if isinstance(name_tab, str):
if search_by == "title":
for tab_instance in self.tab_bar.parent.carousel.slides:
if tab_instance.title_is_capital is True:
_name_tab = name_tab.upper()
else:
_name_tab = name_tab
if tab_instance.title == _name_tab:
self.carousel.load_slide(tab_instance)
return
# Search by icon.
elif search_by == "icon":
for tab_instance in self.tab_bar.parent.carousel.slides:
if tab_instance.icon == name_tab:
self.carousel.load_slide(tab_instance)
return
# Search by title.
else:
for tab_instance in self.tab_bar.parent.carousel.slides:
if tab_instance.tab_label_text == name_tab:
self.carousel.load_slide(tab_instance)
return
raise ValueError(
"switch_tab:\n\t"
"name_tab not found in the tab list\n\t"
f"search_by = {repr(search_by)} \n\t"
f"name_tab = {repr(name_tab)} \n\t"
)
else:
self.carousel.load_slide(name_tab.tab)
def get_tab_list(self) -> list:
"""Returns a list of :class:`~MDTabsLabel` objects."""
return self.tab_bar.layout.children[::-1]
def get_slides(self) -> list:
"""Returns a list of user tab objects."""
return self.carousel.slides
def get_current_tab(self):
"""
Returns current tab object.
.. versionadded:: 1.0.0
"""
return self.carousel.current_slide
def add_widget(self, widget, index=0, canvas=None):
# You can add only subclass of MDTabsBase.
if not isinstance(widget, (MDTabsBase, MDTabsMain, MDTabsBar)):
raise ValueError(
f"MDTabs[{self.uid}].add_widget:\n\t"
"The widget provided is not a subclass of MDTabsBase."
)
if len(self.children) >= 2:
try:
# FIXME: Can't set the value of the `no_ripple_effect`
# and `ripple_duration` properties for widget.tab_label.
widget.tab_label._no_ripple_effect = self.no_ripple_effect
widget.tab_label.ripple_duration_in_slow = self.ripple_duration
widget.tab_label.group = str(self)
widget.tab_label.tab_bar = self.tab_bar
widget.tab_label.font_name = self.font_name
widget.tab_label.text_color_normal = (
self.text_color_normal
if self.text_color_normal
else self.specific_secondary_text_color
)
widget.tab_label.text_color_active = (
self.text_color_active
if self.text_color_active
else self.specific_text_color
)
self.bind(
allow_stretch=widget.tab_label._update_text_size,
fixed_tab_label_width=widget.tab_label._update_text_size,
font_name=widget.tab_label.setter("font_name"),
text_color_active=widget.tab_label.setter(
"text_color_active"
),
text_color_normal=widget.tab_label.setter(
"text_color_normal"
),
)
Clock.schedule_once(widget.tab_label._update_text_size, 0)
self.tab_bar.layout.add_widget(widget.tab_label)
self.carousel.add_widget(widget)
if self.force_title_icon_mode is True:
widget.title_icon_mode = self.title_icon_mode
Clock.schedule_once(
self.tab_bar._label_request_indicator_update, 0
)
return
except AttributeError:
pass
if isinstance(widget, (MDTabsMain, MDTabsBar)):
return super().add_widget(widget)
def remove_widget(self, widget):
# You can remove only subclass of MDTabsLabel or MDTabsBase.
if not issubclass(widget.__class__, (MDTabsLabel, MDTabsBase)):
raise MDTabsException(
"MDTabs can remove only subclass of MDTabsLabel or MDTabsBase"
)
# If the widget is an instance of MDTabsBase, then the widget is
# set as the widget's tab_label object.
if issubclass(widget.__class__, MDTabsBase):
slide = widget
title_label = widget.tab_label
else:
# We already got the label, so we set the slide reference.
slide = widget.tab
title_label = widget
# Set memory.
# Search object next tab.
# Clean all bindings to allow the widget to be collected.
self.unbind(
allow_stretch=title_label._update_text_size,
fixed_tab_label_width=title_label._update_text_size,
font_name=title_label.setter("font_name"),
text_color_active=title_label.setter("text_color_active"),
text_color_normal=title_label.setter("text_color_normal"),
)
self.carousel.remove_widget(slide)
self.tab_bar.layout.remove_widget(title_label)
# Clean the references.
slide = None
title_label = None
widget = None
def on_slide_progress(self, *args) -> None:
"""
This event is deployed every available frame while the tab is scrolling.
"""
def on_carousel_index(self, instance_tabs_carousel, index: int) -> None:
"""
Called when the Tab index have changed.
This event is deployed by the built in carousel of the class.
"""
# When the index of the carousel change, update tab indicator,
# select the current tab and reset threshold data.
if instance_tabs_carousel.current_slide:
current_tab_label = instance_tabs_carousel.current_slide.tab_label
if current_tab_label.state == "normal":
# current_tab_label._do_press()
current_tab_label.dispatch("on_release")
current_tab_label._release_group(self)
current_tab_label.state = "down"
if self.tab_indicator_type == "round":
self.tab_indicator_height = self.tab_bar_height
if index == 0:
radius = [
0,
self.tab_bar_height / 2,
self.tab_bar_height / 2,
0,
]
self.tab_bar.update_indicator(
current_tab_label.x, current_tab_label.width, radius
)
elif index == len(self.get_tab_list()) - 1:
radius = [
self.tab_bar_height / 2,
0,
0,
self.tab_bar_height / 2,
]
self.tab_bar.update_indicator(
current_tab_label.x, current_tab_label.width, radius
)
else:
radius = [
self.tab_bar_height / 2,
]
self.tab_bar.update_indicator(
current_tab_label.x, current_tab_label.width, radius
)
elif (
self.tab_indicator_type == "fill"
or self.tab_indicator_type == "line-round"
or self.tab_indicator_type == "line-rect"
):
self.tab_indicator_height = self.tab_bar_height
self.tab_bar.update_indicator(
current_tab_label.x, current_tab_label.width
)
else:
self.tab_bar.update_indicator(
current_tab_label.x, current_tab_label.width
)
def on_ref_press(self, *args) -> None:
"""
This event will be launched every time the user press a markup enabled
label with a link or reference inside.
"""
def on_tab_switch(self, *args) -> None:
"""This event is launched every time the current tab is changed."""
def on_size(self, instance_tab, size: list) -> None:
"""Called when the application screen is resized."""
if self.carousel.current_slide:
self._update_indicator(self.carousel.current_slide.tab_label)
def _update_tab_hint_x(self, *args):
if not self.ids.layout.children:
return
if self.tab_hint_x is True:
self.fixed_tab_label_width = self.width // len(
self.ids.layout.children
)
self.allow_stretch = False
else:
self.allow_stretch = True
def _parse_icon_mode(self, *args):
if self.force_title_icon_mode is True:
for slide in self.carousel.slides:
slide.title_icon_mode = self.title_icon_mode
if self.title_icon_mode == "Top":
self.tab_bar_height = dp(72)
else:
self.tab_bar_height = dp(48)
def _carousel_bind(self, interval):
self.carousel.bind(on_slide_progress=self._on_slide_progress)
def _on_slide_progress(self, *args):
self.dispatch("on_slide_progress", args)
def _update_indicator(self, current_tab_label):
def update_indicator(interval):
self.tab_bar.update_indicator(
current_tab_label.x, current_tab_label.width
)
if not current_tab_label:
current_tab_label = self.tab_bar.layout.children[-1]
Clock.schedule_once(update_indicator)
def _update_padding(self, layout, *args):
if self.tab_hint_x is True:
layout.padding = [0, 0]
Clock.schedule_once(self._update_tab_hint_x)
return True
padding = [0, 0]
# FIXME: It's not entirely clear why the `padding = [dp (52), 0]`
# instruction is needed? This creates an extra 52px left padding and
# looks like a bug. This instruction was added by the contributors in
# previous commits and I have not yet figured out why this was done.
# This is more efficient than to use sum([layout.children]).
# width = layout.width - (layout.padding[0] * 2)
# Forces the padding of the tab_bar when the tab_bar is scrollable.
# if width > self.width:
# padding = [dp(52), 0]
# Set the new padding.
layout.padding = padding
# Update the indicator.
if self.carousel.current_slide:
self._update_indicator(self.carousel.current_slide.tab_label)
Clock.schedule_once(
lambda x: setattr(
self.carousel.current_slide.tab_label, "state", "down"
),
-1,
)
return True