test-kivy-app/kivy_venv/lib/python3.11/site-packages/kivymd/theming.py
2024-09-15 20:57:02 +03:00

1021 lines
32 KiB
Python

"""
Themes/Theming
==============
.. seealso::
`Material Design spec, Dynamic color <https://m3.material.io/styles/color/dynamic-color/overview>`_
Material App
------------
The main class of your application, which in `Kivy` inherits from the
:class:`~kivy.app.App` class, in `KivyMD` must inherit from the
:class:`~kivymd.app.MDApp` class. The :class:`~kivymd.app.MDApp` class has
properties that allow you to control application properties such as
:attr:`color/style/font` of interface elements and much more.
Control material properties
---------------------------
The main application class inherited from the :class:`~kivymd.app.MDApp` class
has the :attr:`~kivymd.app.MDApp.theme_cls` attribute, with which you control
the material properties of your application.
"""
import os.path
from timeit import default_timer
from kivy.app import App
from kivy.logger import Logger
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.properties import (
AliasProperty,
BooleanProperty,
DictProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
)
from kivy import platform
from kivy.utils import get_color_from_hex, rgba, hex_colormap
from kivymd.dynamic_color import DynamicColor
from kivymd.font_definitions import theme_font_styles
from kivymd.material_resources import DEVICE_IOS
from materialyoucolor.utils.color_utils import argb_from_rgba_01
from materialyoucolor.dynamiccolor.material_dynamic_colors import (
MaterialDynamicColors,
)
from materialyoucolor.utils.platform_utils import SCHEMES, get_dynamic_scheme
from materialyoucolor.hct import Hct
from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer
class ThemeManager(EventDispatcher, DynamicColor):
primary_palette = OptionProperty(
None,
options=[name_color.capitalize() for name_color in hex_colormap.keys()],
)
"""
The name of the color scheme that the application will use.
All major `material` components will have the color
of the specified color theme.
See :attr:`kivy.utils.hex_colormap` keys for available values.
To change the color scheme of an application:
.. tabs::
.. tab:: Imperative python style with KV
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
md_bg_color: self.theme_cls.backgroundColor
MDButton:
style: "elevated"
pos_hint: {"center_x": .5, "center_y": .5}
MDButtonIcon:
icon: "plus"
MDButtonText:
text: "Button"
'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Olive" # "Purple", "Red"
return Builder.load_string(KV)
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.button import MDButton, MDButtonIcon, MDButtonText
from kivymd.uix.screen import MDScreen
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Olive" # "Purple", "Red"
return (
MDScreen(
MDButton(
MDButtonIcon(
icon="plus",
),
MDButtonText(
text="Button",
),
style="elevated",
pos_hint={"center_x": 0.5, "center_y": 0.5},
),
md_bg_color=self.theme_cls.backgroundColor,
)
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette-m3.png
:align: center
:attr:`primary_palette` is an :class:`~kivy.properties.OptionProperty`
and defaults to `None`.
"""
dynamic_color_quality = NumericProperty(1 if platform == "android" else 10)
"""
The quality of the generated color scheme from the system wallpaper.
It is equal to or higher than `1`, with `1` representing the maximum quality.
.. warning::
Remember that by increasing the quality value, you also increase the
generation time of the color scheme.
:attr:`dynamic_color_quality` is an :class:`~kivy.properties.NumericProperty`
and defaults to `10` if platform is not Android else `1`.
"""
dynamic_color = BooleanProperty(False)
"""
Enables or disables dynamic color.
.. versionadded:: 2.0.0
.. seealso::
`Material Design spec, Dynamic color <https://m3.material.io/styles/color/dynamic-color/overview>`_
To build the color scheme of your application from user wallpapers, you
must enable the `READ_EXTERNAL_STORAGE
<https://github.com/Android-for-Python/Android-for-Python-Users?tab=readme-ov-file#storage-permissions>`_
permission if your android version is below 8.1:
.. code-block:: python
from kivy import platform
from kivy.lang import Builder
from kivy.clock import Clock
from kivymd.app import MDApp
KV = '''
MDScreen:
md_bg_color: app.theme_cls.surfaceColor
MDButton:
style: "elevated"
pos_hint: {"center_x": .5, "center_y": .5}
MDButtonIcon:
icon: "plus"
MDButtonText:
text: "Elevated"
'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
def on_resume(self, *args):
'''Updating the color scheme when the application resumes.'''
self.theme_cls.set_colors()
def set_dynamic_color(self, *args) -> None:
'''
When sets the `dynamic_color` value, the self method will be
`called.theme_cls.set_colors()` which will generate a color
scheme from a custom wallpaper if `dynamic_color` is `True`.
'''
self.theme_cls.dynamic_color = True
def on_start(self) -> None:
'''
It is fired at the start of the application and requests the
necessary permissions.
'''
def callback(permission, results):
if all([res for res in results]):
Clock.schedule_once(self.set_dynamic_color)
if platform == "android":
from android.permissions import Permission, request_permissions
permissions = [Permission.READ_EXTERNAL_STORAGE]
request_permissions(permissions, callback)
Example().run()
:attr:`dynamic_color` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
dynamic_scheme_name = OptionProperty("TONAL_SPOT", options=SCHEMES.keys())
"""
Name of the dynamic scheme. Availabe schemes `TONAL_SPOT`, `SPRITZ`
`VIBRANT`, `EXPRESSIVE`, `FRUIT_SALAD`, `RAINBOW`, `MONOCHROME`, `FIDELITY`
and `CONTENT`.
:attr:`dynamic_scheme_name` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'TONAL_SPOT'`.
"""
dynamic_scheme_contrast = NumericProperty(0.0)
"""
The contrast of the generated color scheme.
:attr:`dynamic_scheme_contrast` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.0`.
"""
path_to_wallpaper = StringProperty()
"""
The path to the image to set the color scheme. You can use this option
if you want to use dynamic color on platforms other than the Android
platform.
.. versionadded:: 2.0.0
:attr:`path_to_wallpaper` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
theme_style_switch_animation = BooleanProperty(True)
"""
Animate app colors when switching app color scheme ('Dark/light').
.. versionadded:: 1.1.0
.. tabs::
.. tab:: Declarative KV style
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
md_bg_color: self.theme_cls.backgroundColor
MDCard:
orientation: "vertical"
padding: 0, 0, 0 , "36dp"
size_hint: .5, .5
style: "elevated"
pos_hint: {"center_x": .5, "center_y": .5}
MDLabel:
text: "Theme style - {}".format(app.theme_cls.theme_style)
halign: "center"
valign: "center"
bold: True
font_style: "Display"
role: "small"
MDButton:
on_release: app.switch_theme_style()
pos_hint: {"center_x": .5}
MDButtonText:
text: "Set theme"
'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style_switch_animation = True
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def switch_theme_style(self):
self.theme_cls.primary_palette = (
"Orange" if self.theme_cls.primary_palette == "Red" else "Red"
)
self.theme_cls.theme_style = (
"Dark" if self.theme_cls.theme_style == "Light" else "Light"
)
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivy.clock import Clock
from kivymd.app import MDApp
from kivymd.uix.button import MDButton, MDButtonText
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
class Example(MDApp):
def build(self):
self.theme_cls.theme_style_switch_animation = True
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDCard(
MDLabel(
id="label",
text="Theme style - {}".format(
self.theme_cls.theme_style),
halign="center",
valign="center",
bold=True,
font_style="Display",
role="small",
),
MDButton(
MDButtonText(
text="Set theme",
),
on_release=self.switch_theme_style,
pos_hint={"center_x": 0.5},
),
id="card",
orientation="vertical",
padding=(0, 0, 0, "36dp"),
size_hint=(0.5, 0.5),
pos_hint={"center_x": 0.5, "center_y": 0.5},
style="elevated",
)
)
)
def on_start(self):
def on_start(*args):
self.root.md_bg_color = self.theme_cls.backgroundColor
Clock.schedule_once(on_start)
def switch_theme_style(self, *args):
self.theme_cls.primary_palette = (
"Orange" if self.theme_cls.primary_palette == "Red" else "Red"
)
self.theme_cls.theme_style = (
"Dark" if self.theme_cls.theme_style == "Light" else "Light"
)
self.root.get_ids().label.text = (
"Theme style - {}".format(self.theme_cls.theme_style)
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation.gif
:align: center
:attr:`theme_style_switch_animation` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
theme_style_switch_animation_duration = NumericProperty(0.2)
"""
Duration of the animation of switching the color scheme of the application
("Dark/light").
.. versionadded:: 1.1.0
.. code-block:: python
class Example(MDApp):
def build(self):
self.theme_cls.theme_style_switch_animation = True
self.theme_cls.theme_style_switch_animation_duration = 0.8
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation-duration.gif
:align: center
:attr:`theme_style_switch_animation_duration` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
theme_style = OptionProperty("Light", options=["Light", "Dark"])
"""
App theme style.
.. code-block:: python
from kivy.clock import Clock
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.button import MDButton, MDButtonText
class Example(MDApp):
def build(self):
self.theme_cls.primary_palette = "Orange"
self.theme_cls.theme_style = "Light" # "Dark"
return MDScreen(
MDButton(
MDButtonText(
text="Hello, World",
),
style="outlined",
pos_hint={"center_x": 0.5, "center_y": 0.5},
)
)
def on_start(self):
def on_start(*args):
self.root.md_bg_color = self.theme_cls.backgroundColor
Clock.schedule_once(on_start)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style.png
:align: center
:attr:`theme_style` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Light'`.
"""
def _get_theme_style(self, opposite: bool) -> str:
if opposite:
return "Light" if self.theme_style == "Dark" else "Dark"
else:
return self.theme_style
def _get_disabled_hint_text_color(self, opposite: bool = False) -> list:
theme_style = self._get_theme_style(opposite)
if theme_style == "Light":
color = get_color_from_hex("000000")
color[3] = 0.38
elif theme_style == "Dark":
color = get_color_from_hex("FFFFFF")
color[3] = 0.50
return color
disabled_hint_text_color = AliasProperty(
_get_disabled_hint_text_color, bind=["theme_style"]
)
"""
Color of the disabled text used in the :class:`~kivymd.uix.textfield.MDTextField`.
:attr:`disabled_hint_text_color`
is an :class:`~kivy.properties.AliasProperty` that returns the value
in ``rgba`` format for :attr:`disabled_hint_text_color`,
property is readonly.
"""
def _determine_device_orientation(self, _, window_size) -> None:
if window_size[0] > window_size[1]:
self.device_orientation = "landscape"
elif window_size[1] >= window_size[0]:
self.device_orientation = "portrait"
device_orientation = StringProperty()
"""
Device orientation.
:attr:`device_orientation` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
# Font name, size (sp), always caps, letter spacing (sp).
font_styles = DictProperty(theme_font_styles)
"""
Data of default font styles.
Add custom font
---------------
.. tabs::
.. tab:: Declarative style with KV
.. code-block:: python
from kivy.core.text import LabelBase
from kivy.lang import Builder
from kivy.metrics import sp
from kivymd.app import MDApp
KV = '''
MDScreen:
md_bg_color: self.theme_cls.backgroundColor
MDLabel:
text: "MDLabel"
halign: "center"
font_style: "nasalization"
'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
LabelBase.register(
name="nasalization",
fn_regular="nasalization.ttf",
)
self.theme_cls.font_styles["nasalization"] = {
"large": {
"line-height": 1.64,
"font-name": "nasalization",
"font-size": sp(57),
},
"medium": {
"line-height": 1.52,
"font-name": "nasalization",
"font-size": sp(45),
},
"small": {
"line-height": 1.44,
"font-name": "nasalization",
"font-size": sp(36),
},
}
return Builder.load_string(KV)
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivy.core.text import LabelBase
from kivy.metrics import sp
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
from kivymd.app import MDApp
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
LabelBase.register(
name="nasalization",
fn_regular="/Users/urijivanov/Projects/Dev/MyGithub/Articles/StarTest/data/font/nasalization-rg.ttf",
)
self.theme_cls.font_styles["nasalization"] = {
"large": {
"line-height": 1.64,
"font-name": "nasalization",
"font-size": sp(57),
},
"medium": {
"line-height": 1.52,
"font-name": "nasalization",
"font-size": sp(45),
},
"small": {
"line-height": 1.44,
"font-name": "nasalization",
"font-size": sp(36),
},
}
return (
MDScreen(
MDLabel(
text="JetBrainsMono",
halign="center",
font_style="nasalization",
)
)
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-font-styles.png
:align: center
:attr:`font_styles` is an :class:`~kivy.properties.DictProperty`.
"""
on_colors = None
"""
A Helper function called when colors are changed.
:attr: `on_colors` defaults to `None`.
"""
_size_current_wallpaper = NumericProperty(0)
_dark_mode = lambda self : False if self.theme_style == "Light" else True
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._determine_device_orientation(None, Window.size)
Window.bind(size=self._determine_device_orientation)
def set_colors(self, *args) -> None:
"""Fired methods for setting a new color scheme."""
if not self.dynamic_color:
if not self.primary_palette:
self._set_application_scheme()
else:
self._set_palette_color()
else:
system_scheme = get_dynamic_scheme(
dark_mode=self._dark_mode(),
contrast=self.dynamic_scheme_contrast,
dynamic_color_quality=self.dynamic_color_quality,
fallback_wallpaper_path=self.path_to_wallpaper,
fallback_scheme_name=self.dynamic_scheme_name,
message_logger=Logger.info,
logger_head="KivyMD"
)
if system_scheme:
self._set_color_names(system_scheme)
else:
self._set_application_scheme()
def update_theme_colors(self, *args) -> None:
"""Fired when the `theme_style` value changes."""
self.set_colors()
def on_dynamic_scheme_name(self, *args):
self.set_colors()
def on_dynamic_scheme_contrast(self, *args):
self.set_colors()
def on_path_to_wallpaper(self, *args):
self.set_colors()
def switch_theme(self) -> None:
"""Switches the theme from light to dark."""
self.theme_style = "Dark" if self.theme_style == "Light" else "Light"
def sync_theme_styles(self, *args) -> None:
# Syncs the values from self.font_styles to theme_font_styles
# this will ensure continuity when someone registers a new font_style.
for num, style in enumerate(theme_font_styles):
if style not in self.font_styles:
theme_font_styles.pop(num)
for style in self.font_styles.keys():
theme_font_styles.append(style)
def _set_application_scheme(
self,
color = "blue", # Google default
) -> None:
if not color:
color = "blue"
color = get_color_from_hex(hex_colormap[color.lower()])
color = Hct.from_int(argb_from_rgba_01(color))
color = DislikeAnalyzer.fix_if_disliked(color).to_int()
self._set_color_names(
SCHEMES[self.dynamic_scheme_name](
Hct.from_int(color),
self._dark_mode(),
self.dynamic_scheme_contrast,
)
)
def _set_color_names(self, scheme) -> None:
for color_name in vars(MaterialDynamicColors).keys():
attr = getattr(MaterialDynamicColors, color_name)
if hasattr(attr, "get_hct"):
color_value = rgba(attr.get_hct(scheme).to_rgba())
exec(f"self.{color_name}Color = {color_value}")
self.disabledTextColor = self._get_disabled_hint_text_color()
if self.on_colors:
self.on_colors()
def _set_palette_color(self) -> None:
if not self.primary_palette:
self.primary_palette = "Blue"
self._set_application_scheme(self.primary_palette)
class ThemableBehavior(EventDispatcher):
theme_cls = ObjectProperty()
"""
Instance of :class:`~ThemeManager` class.
:attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`.
"""
device_ios = BooleanProperty(DEVICE_IOS)
"""
``True`` if device is ``iOS``.
:attr:`device_ios` is an :class:`~kivy.properties.BooleanProperty`.
"""
theme_line_color = OptionProperty("Primary", options=["Primary", "Custom"])
"""
Line color scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_line_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_bg_color = OptionProperty("Primary", options=["Primary", "Custom"])
"""
Background color scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_bg_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_shadow_color = OptionProperty(
"Primary", options=["Primary", "Custom"]
)
"""
Elevation color scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_shadow_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_shadow_offset = OptionProperty(
"Primary", options=["Primary", "Custom"]
)
"""
Elevation offset scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_shadow_offset` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_elevation_level = OptionProperty(
"Primary", options=["Primary", "Custom"]
)
"""
Elevation level scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_elevation_level` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_font_size = OptionProperty("Primary", options=["Primary", "Custom"])
"""
Font size scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_font_size` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_width = OptionProperty("Primary", options=["Primary", "Custom"])
"""
Widget width scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_width` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_height = OptionProperty("Primary", options=["Primary", "Custom"])
"""
Widget width scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_height` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_line_height = OptionProperty("Primary", options=["Primary", "Custom"])
"""
Line height scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_line_height` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_font_name = OptionProperty("Primary", options=["Primary", "Custom"])
"""
Font name scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_font_name` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_shadow_softness = OptionProperty(
"Primary", options=["Primary", "Custom"]
)
"""
Elevation softness scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_shadow_softness` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_focus_color = OptionProperty("Primary", options=["Primary", "Custom"])
"""
Focus color scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_focus_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_divider_color = OptionProperty(
"Primary", options=["Primary", "Custom"]
)
"""
Divider color scheme name.
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_divider_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_text_color = OptionProperty(
"Primary",
options=[
"Primary",
"Secondary",
"Hint",
"Error",
"Custom",
],
)
"""
Label color scheme name.
Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
`'Custom'`.
:attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
theme_icon_color = OptionProperty(
"Primary",
options=[
"Primary",
"Secondary",
"Hint",
"Error",
"Custom",
],
)
"""
Label color scheme name.
Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
`'Custom'`.
:attr:`theme_icon_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
def __init__(self, **kwargs):
if self.theme_cls is None:
try:
if not isinstance(
App.get_running_app().property("theme_cls", True),
ObjectProperty,
):
raise ValueError(
"KivyMD: App object must be inherited from "
"`kivymd.app.MDApp`"
)
except AttributeError:
raise ValueError(
"KivyMD: App object must be initialized before loading "
"root widget. See "
"https://github.com/kivymd/KivyMD/wiki/Modules-Material-App#exceptions"
)
self.theme_cls = App.get_running_app().theme_cls
super().__init__(**kwargs)
# Fix circular imports.
from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.label import MDLabel
from kivymd.uix.textfield import MDTextField
self.common_elevation_behavior = CommonElevationBehavior
self.md_label = MDLabel
self.md_textfield = MDTextField
def remove_widget(self, widget) -> None:
if not hasattr(widget, "theme_cls"):
super().remove_widget(widget)
return
callbacks = widget.theme_cls.get_property_observers("theme_style")
for callback in callbacks:
try:
if hasattr(callback, "proxy") and hasattr(
callback.proxy, "theme_cls"
):
for property_name in ["theme_style", "primary_palette"]:
if widget == callback.proxy:
widget.theme_cls.unbind(
**{
property_name: getattr(
callback.proxy, callback.method_name
)
}
)
except ReferenceError:
pass
# Canceling a scheduled method call on_window_touch for MDLabel
# objects.
if (
issubclass(widget.__class__, self.md_label)
and self.md_label.allow_selection
):
Window.unbind(on_touch_down=widget.on_window_touch)
super().remove_widget(widget)