test-kivy-app/kivy_venv/lib/python3.11/site-packages/kivymd/theming.py

1021 lines
32 KiB
Python
Raw Normal View History

2024-09-15 12:12:16 +00:00
"""
Themes/Theming
==============
.. seealso::
2024-09-15 17:57:02 +00:00
`Material Design spec, Dynamic color <https://m3.material.io/styles/color/dynamic-color/overview>`_
2024-09-15 12:12:16 +00:00
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.
"""
2024-09-15 17:57:02 +00:00
import os.path
from timeit import default_timer
2024-09-15 12:12:16 +00:00
from kivy.app import App
2024-09-15 17:57:02 +00:00
from kivy.logger import Logger
2024-09-15 12:12:16 +00:00
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.properties import (
AliasProperty,
BooleanProperty,
DictProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
)
2024-09-15 17:57:02 +00:00
from kivy import platform
from kivy.utils import get_color_from_hex, rgba, hex_colormap
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
from kivymd.dynamic_color import DynamicColor
2024-09-15 12:12:16 +00:00
from kivymd.font_definitions import theme_font_styles
2024-09-15 17:57:02 +00:00
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
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
class ThemeManager(EventDispatcher, DynamicColor):
primary_palette = OptionProperty(
None,
options=[name_color.capitalize() for name_color in hex_colormap.keys()],
)
2024-09-15 12:12:16 +00:00
"""
The name of the color scheme that the application will use.
All major `material` components will have the color
of the specified color theme.
2024-09-15 17:57:02 +00:00
See :attr:`kivy.utils.hex_colormap` keys for available values.
2024-09-15 12:12:16 +00:00
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:
2024-09-15 17:57:02 +00:00
md_bg_color: self.theme_cls.backgroundColor
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
MDButton:
style: "elevated"
2024-09-15 12:12:16 +00:00
pos_hint: {"center_x": .5, "center_y": .5}
2024-09-15 17:57:02 +00:00
MDButtonIcon:
icon: "plus"
MDButtonText:
text: "Button"
2024-09-15 12:12:16 +00:00
'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
2024-09-15 17:57:02 +00:00
self.theme_cls.primary_palette = "Olive" # "Purple", "Red"
2024-09-15 12:12:16 +00:00
return Builder.load_string(KV)
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
2024-09-15 17:57:02 +00:00
from kivymd.uix.button import MDButton, MDButtonIcon, MDButtonText
2024-09-15 12:12:16 +00:00
from kivymd.uix.screen import MDScreen
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
2024-09-15 17:57:02 +00:00
self.theme_cls.primary_palette = "Olive" # "Purple", "Red"
2024-09-15 12:12:16 +00:00
return (
MDScreen(
2024-09-15 17:57:02 +00:00
MDButton(
MDButtonIcon(
icon="plus",
),
MDButtonText(
text="Button",
),
style="elevated",
2024-09-15 12:12:16 +00:00
pos_hint={"center_x": 0.5, "center_y": 0.5},
2024-09-15 17:57:02 +00:00
),
md_bg_color=self.theme_cls.backgroundColor,
2024-09-15 12:12:16 +00:00
)
)
Example().run()
2024-09-15 17:57:02 +00:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette-m3.png
2024-09-15 12:12:16 +00:00
:align: center
:attr:`primary_palette` is an :class:`~kivy.properties.OptionProperty`
2024-09-15 17:57:02 +00:00
and defaults to `None`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
dynamic_color_quality = NumericProperty(1 if platform == "android" else 10)
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
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.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. warning::
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Remember that by increasing the quality value, you also increase the
generation time of the color scheme.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`dynamic_color_quality` is an :class:`~kivy.properties.NumericProperty`
and defaults to `10` if platform is not Android else `1`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
dynamic_color = BooleanProperty(False)
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Enables or disables dynamic color.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. seealso::
`Material Design spec, Dynamic color <https://m3.material.io/styles/color/dynamic-color/overview>`_
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
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:
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. code-block:: python
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
from kivy import platform
from kivy.lang import Builder
from kivy.clock import Clock
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
from kivymd.app import MDApp
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
KV = '''
MDScreen:
md_bg_color: app.theme_cls.surfaceColor
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
MDButton:
style: "elevated"
pos_hint: {"center_x": .5, "center_y": .5}
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
MDButtonIcon:
icon: "plus"
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
MDButtonText:
text: "Elevated"
'''
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def on_resume(self, *args):
'''Updating the color scheme when the application resumes.'''
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
self.theme_cls.set_colors()
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
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`.
2024-09-15 12:12:16 +00:00
'''
2024-09-15 17:57:02 +00:00
self.theme_cls.dynamic_color = True
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def on_start(self) -> None:
'''
It is fired at the start of the application and requests the
necessary permissions.
'''
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def callback(permission, results):
if all([res for res in results]):
Clock.schedule_once(self.set_dynamic_color)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
if platform == "android":
from android.permissions import Permission, request_permissions
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
permissions = [Permission.READ_EXTERNAL_STORAGE]
request_permissions(permissions, callback)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Example().run()
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`dynamic_color` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
dynamic_scheme_name = OptionProperty("TONAL_SPOT", options=SCHEMES.keys())
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Name of the dynamic scheme. Availabe schemes `TONAL_SPOT`, `SPRITZ`
`VIBRANT`, `EXPRESSIVE`, `FRUIT_SALAD`, `RAINBOW`, `MONOCHROME`, `FIDELITY`
and `CONTENT`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`dynamic_scheme_name` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'TONAL_SPOT'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
dynamic_scheme_contrast = NumericProperty(0.0)
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
The contrast of the generated color scheme.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`dynamic_scheme_contrast` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.0`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
path_to_wallpaper = StringProperty()
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
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.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`path_to_wallpaper` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_style_switch_animation = BooleanProperty(True)
2024-09-15 12:12:16 +00:00
"""
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:
2024-09-15 17:57:02 +00:00
md_bg_color: self.theme_cls.backgroundColor
2024-09-15 12:12:16 +00:00
MDCard:
orientation: "vertical"
padding: 0, 0, 0 , "36dp"
size_hint: .5, .5
2024-09-15 17:57:02 +00:00
style: "elevated"
2024-09-15 12:12:16 +00:00
pos_hint: {"center_x": .5, "center_y": .5}
MDLabel:
text: "Theme style - {}".format(app.theme_cls.theme_style)
halign: "center"
valign: "center"
bold: True
2024-09-15 17:57:02 +00:00
font_style: "Display"
role: "small"
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
MDButton:
2024-09-15 12:12:16 +00:00
on_release: app.switch_theme_style()
pos_hint: {"center_x": .5}
2024-09-15 17:57:02 +00:00
MDButtonText:
text: "Set theme"
2024-09-15 12:12:16 +00:00
'''
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
2024-09-15 17:57:02 +00:00
from kivy.clock import Clock
2024-09-15 12:12:16 +00:00
from kivymd.app import MDApp
2024-09-15 17:57:02 +00:00
from kivymd.uix.button import MDButton, MDButtonText
2024-09-15 12:12:16 +00:00
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",
2024-09-15 17:57:02 +00:00
text="Theme style - {}".format(
self.theme_cls.theme_style),
2024-09-15 12:12:16 +00:00
halign="center",
valign="center",
bold=True,
2024-09-15 17:57:02 +00:00
font_style="Display",
role="small",
2024-09-15 12:12:16 +00:00
),
2024-09-15 17:57:02 +00:00
MDButton(
MDButtonText(
text="Set theme",
),
2024-09-15 12:12:16 +00:00
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},
2024-09-15 17:57:02 +00:00
style="elevated",
2024-09-15 12:12:16 +00:00
)
)
)
2024-09-15 17:57:02 +00:00
def on_start(self):
def on_start(*args):
self.root.md_bg_color = self.theme_cls.backgroundColor
Clock.schedule_once(on_start)
2024-09-15 12:12:16 +00:00
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"
)
2024-09-15 17:57:02 +00:00
self.root.get_ids().label.text = (
2024-09-15 12:12:16 +00:00
"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`
2024-09-15 17:57:02 +00:00
and defaults to `True`.
2024-09-15 12:12:16 +00:00
"""
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.
2024-09-15 17:57:02 +00:00
.. code-block:: python
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
from kivy.clock import Clock
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.button import MDButton, MDButtonText
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
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},
)
)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def on_start(self):
def on_start(*args):
self.root.md_bg_color = self.theme_cls.backgroundColor
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Clock.schedule_once(on_start)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Example().run()
2024-09-15 12:12:16 +00:00
.. 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
2024-09-15 17:57:02 +00:00
def _get_disabled_hint_text_color(self, opposite: bool = False) -> list:
2024-09-15 12:12:16 +00:00
theme_style = self._get_theme_style(opposite)
if theme_style == "Light":
2024-09-15 17:57:02 +00:00
color = get_color_from_hex("000000")
color[3] = 0.38
2024-09-15 12:12:16 +00:00
elif theme_style == "Dark":
2024-09-15 17:57:02 +00:00
color = get_color_from_hex("FFFFFF")
color[3] = 0.50
return color
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
disabled_hint_text_color = AliasProperty(
_get_disabled_hint_text_color, bind=["theme_style"]
)
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
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
2024-09-15 12:12:16 +00:00
.. code-block:: python
2024-09-15 17:57:02 +00:00
from kivy.core.text import LabelBase
2024-09-15 12:12:16 +00:00
from kivy.lang import Builder
2024-09-15 17:57:02 +00:00
from kivy.metrics import sp
2024-09-15 12:12:16 +00:00
from kivymd.app import MDApp
KV = '''
2024-09-15 17:57:02 +00:00
MDScreen:
md_bg_color: self.theme_cls.backgroundColor
MDLabel:
text: "MDLabel"
halign: "center"
font_style: "nasalization"
'''
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
LabelBase.register(
name="nasalization",
fn_regular="nasalization.ttf",
)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
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),
},
}
2024-09-15 12:12:16 +00:00
return Builder.load_string(KV)
2024-09-15 17:57:02 +00:00
Example().run()
2024-09-15 12:12:16 +00:00
.. tab:: Declarative python style
.. code-block:: python
2024-09-15 17:57:02 +00:00
from kivy.core.text import LabelBase
from kivy.metrics import sp
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
2024-09-15 12:12:16 +00:00
from kivymd.app import MDApp
class Example(MDApp):
def build(self):
2024-09-15 17:57:02 +00:00
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),
},
}
2024-09-15 12:12:16 +00:00
return (
2024-09-15 17:57:02 +00:00
MDScreen(
MDLabel(
text="JetBrainsMono",
halign="center",
font_style="nasalization",
)
2024-09-15 12:12:16 +00:00
)
)
Example().run()
2024-09-15 17:57:02 +00:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-font-styles.png
2024-09-15 12:12:16 +00:00
:align: center
2024-09-15 17:57:02 +00:00
:attr:`font_styles` is an :class:`~kivy.properties.DictProperty`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
on_colors = None
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
A Helper function called when colors are changed.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr: `on_colors` defaults to `None`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
_size_current_wallpaper = NumericProperty(0)
_dark_mode = lambda self : False if self.theme_style == "Light" else True
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._determine_device_orientation(None, Window.size)
Window.bind(size=self._determine_device_orientation)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def set_colors(self, *args) -> None:
"""Fired methods for setting a new color scheme."""
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
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()
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def update_theme_colors(self, *args) -> None:
"""Fired when the `theme_style` value changes."""
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
self.set_colors()
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def on_dynamic_scheme_name(self, *args):
self.set_colors()
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def on_dynamic_scheme_contrast(self, *args):
self.set_colors()
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def on_path_to_wallpaper(self, *args):
self.set_colors()
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def switch_theme(self) -> None:
"""Switches the theme from light to dark."""
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
self.theme_style = "Dark" if self.theme_style == "Light" else "Light"
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
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)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
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,
)
)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
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}")
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
self.disabledTextColor = self._get_disabled_hint_text_color()
if self.on_colors:
self.on_colors()
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
def _set_palette_color(self) -> None:
if not self.primary_palette:
self.primary_palette = "Blue"
self._set_application_scheme(self.primary_palette)
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
class ThemableBehavior(EventDispatcher):
theme_cls = ObjectProperty()
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Instance of :class:`~ThemeManager` class.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
device_ios = BooleanProperty(DEVICE_IOS)
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
``True`` if device is ``iOS``.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`device_ios` is an :class:`~kivy.properties.BooleanProperty`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_line_color = OptionProperty("Primary", options=["Primary", "Custom"])
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Line color scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_line_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_bg_color = OptionProperty("Primary", options=["Primary", "Custom"])
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Background color scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_bg_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_shadow_color = OptionProperty(
"Primary", options=["Primary", "Custom"]
2024-09-15 12:12:16 +00:00
)
"""
2024-09-15 17:57:02 +00:00
Elevation color scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_shadow_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_shadow_offset = OptionProperty(
"Primary", options=["Primary", "Custom"]
2024-09-15 12:12:16 +00:00
)
"""
2024-09-15 17:57:02 +00:00
Elevation offset scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_shadow_offset` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_elevation_level = OptionProperty(
"Primary", options=["Primary", "Custom"]
2024-09-15 12:12:16 +00:00
)
"""
2024-09-15 17:57:02 +00:00
Elevation level scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_elevation_level` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_font_size = OptionProperty("Primary", options=["Primary", "Custom"])
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Font size scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_font_size` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_width = OptionProperty("Primary", options=["Primary", "Custom"])
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Widget width scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_width` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_height = OptionProperty("Primary", options=["Primary", "Custom"])
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Widget width scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_height` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_line_height = OptionProperty("Primary", options=["Primary", "Custom"])
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Line height scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
:attr:`theme_line_height` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_font_name = OptionProperty("Primary", options=["Primary", "Custom"])
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Font name scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_font_name` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_shadow_softness = OptionProperty(
"Primary", options=["Primary", "Custom"]
2024-09-15 12:12:16 +00:00
)
"""
2024-09-15 17:57:02 +00:00
Elevation softness scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_shadow_softness` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_focus_color = OptionProperty("Primary", options=["Primary", "Custom"])
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Focus color scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_focus_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_divider_color = OptionProperty(
"Primary", options=["Primary", "Custom"]
2024-09-15 12:12:16 +00:00
)
"""
2024-09-15 17:57:02 +00:00
Divider color scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
.. versionadded:: 2.0.0
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_divider_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
theme_text_color = OptionProperty(
"Primary",
options=[
"Primary",
"Secondary",
"Hint",
"Error",
"Custom",
],
)
2024-09-15 12:12:16 +00:00
"""
2024-09-15 17:57:02 +00:00
Label color scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
`'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
"""
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
theme_icon_color = OptionProperty(
"Primary",
options=[
"Primary",
"Secondary",
"Hint",
"Error",
"Custom",
],
)
"""
Label color scheme name.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
`'Custom'`.
2024-09-15 12:12:16 +00:00
2024-09-15 17:57:02 +00:00
:attr:`theme_icon_color` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'Primary'`.
2024-09-15 12:12:16 +00:00
"""
def __init__(self, **kwargs):
2024-09-15 17:57:02 +00:00
if self.theme_cls is None:
2024-09-15 12:12:16 +00:00
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"
):
2024-09-15 17:57:02 +00:00
for property_name in ["theme_style", "primary_palette"]:
2024-09-15 12:12:16 +00:00
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)