Skip to content

Commit

Permalink
2.0.0 - Material Design 3
Browse files Browse the repository at this point in the history
- fix multiple color scheme generation
- fix the generation of the color scheme according to the new API of the `materialyoucolor` library.
  • Loading branch information
HeaTTheatR committed Jan 16, 2024
1 parent 027c82f commit 2609318
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 93 deletions.
39 changes: 38 additions & 1 deletion kivymd/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,30 @@ def build(self):
return Builder.load_string(KV)
def on_start(self):
super().on_start()
self.fps_monitor_start()
MainApp().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fps-monitor.png
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fps-monitor-dark.png
:width: 350 px
:align: center
.. note::
Note that if you override the built-in on_start method, you will
definitely need to call the super method:
.. code-block:: python
class MainApp(MDApp):
def build(self):
[...]
def on_start(self):
super().on_start()
[...]
"""

__all__ = ("MDApp",)
Expand Down Expand Up @@ -120,6 +135,28 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls = ThemeManager()

def on_start(self):
"""
Event handler for the `on_start` event which is fired after
initialization (after build() has been called) but before the
application has started running.
.. versionadded:: 2.0.0
"""

def on_start(*args):
self.theme_cls.bind(
theme_style=lambda *x: Clock.schedule_once(
self.theme_cls.update_theme_colors, 0.1
),
primary_palette=lambda *x: Clock.schedule_once(
self.theme_cls.set_colors, 0.1
),
)
self.theme_cls.set_colors()

Clock.schedule_once(on_start)

def load_all_kv_files(self, path_to_directory: str) -> None:
"""
Recursively loads KV files from the selected directory.
Expand Down
177 changes: 85 additions & 92 deletions kivymd/theming.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
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.clock import Clock
from kivy.logger import Logger
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.properties import (
Expand All @@ -35,7 +37,6 @@
ObjectProperty,
OptionProperty,
StringProperty,
ListProperty,
)
from kivy.utils import get_color_from_hex, rgba, hex_colormap

Expand All @@ -44,9 +45,17 @@
from kivymd.material_resources import DEVICE_IOS
from kivymd.utils.get_wallpaper import get_wallpaper

from materialyoucolor.utils.theme_utils import theme_from_source_color
from materialyoucolor.utils.image_utils import source_color_from_image
from PIL import Image

from materialyoucolor.utils.color_utils import argb_from_rgb
from materialyoucolor.dynamiccolor.material_dynamic_colors import (
MaterialDynamicColors,
)
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
from materialyoucolor.hct import Hct
from materialyoucolor.quantize import QuantizeCelebi
from materialyoucolor.score.score import Score


class ThemeManager(EventDispatcher, DynamicColor):
primary_palette = OptionProperty(
Expand Down Expand Up @@ -206,6 +215,7 @@ def callback(permission, results):
if all([res for res in results]):
Clock.schedule_once(self.set_dynamic_color)
super().on_start()
if platform == "android":
from android.permissions import Permission, request_permissions
Expand Down Expand Up @@ -354,6 +364,7 @@ def on_start(self):
def on_start(*args):
self.root.md_bg_color = self.theme_cls.backgroundColor
super().on_start()
Clock.schedule_once(on_start)
def switch_theme_style(self, *args):
Expand Down Expand Up @@ -429,6 +440,7 @@ def on_start(self):
def on_start(*args):
self.root.md_bg_color = self.theme_cls.backgroundColor
super().on_start()
Clock.schedule_once(on_start)
Expand Down Expand Up @@ -604,74 +616,43 @@ def build(self):
:attr:`font_styles` is an :class:`~kivy.properties.DictProperty`.
"""

schemes_name_colors = ListProperty()
_size_current_wallpaper = NumericProperty(0)

def __init__(self, **kwargs):
super().__init__(**kwargs)
self._determine_device_orientation(None, Window.size)
Window.bind(size=self._determine_device_orientation)
self.bind(
theme_style=lambda *x: Clock.schedule_once(
self.update_theme_colors, 0.1
),
primary_palette=lambda *x: Clock.schedule_once(
self.set_colors, 0.1
),
)
# Clock.schedule_once(self.sync_theme_styles)
Clock.schedule_once(self.set_colors)

def on_dynamic_color(self, *args) -> None:
"""Fired when the `dynamic_color` value changes."""

self.set_colors()

def set_colors(self, *args) -> None:
"""Calls methods for setting a new color scheme."""
"""Fired methods for setting a new color scheme."""

if not self.dynamic_color:
if not self.primary_palette:
self._set_default_color()
self._set_application_scheme()
else:
self._set_palette_color()
else:
path_to_wallpaper = get_wallpaper(
App.get_running_app().user_data_dir, self.path_to_wallpaper
)
if path_to_wallpaper:
self._set_dynamic_color(path_to_wallpaper)
size_wallpaper = os.path.getsize(path_to_wallpaper)
if size_wallpaper != self._size_current_wallpaper:
self._size_current_wallpaper = os.path.getsize(path_to_wallpaper)
self._set_dynamic_color(path_to_wallpaper)
else:
Logger.info(
"KivyMD: "
f"Color scheme generation. The color scheme of these "
f"wallpapers has already been generated. Skip it."
)
else:
self._set_palette_color()

def on_path_to_wallpaper(self, instance, value) -> None:
"""Fired when the `path_to_wallpaper` value changes."""

if self.dynamic_color:
self.set_colors()

def update_theme_colors(self, *args) -> None:
"""Fired when the `theme_style` value changes."""

color_theme = self.current_color_theme
style_theme = self.theme_style

if "schemes" in color_theme:
self.schemes_name_colors = list(
color_theme["schemes"][style_theme.lower()].props.keys()
)
for color_key in self.schemes_name_colors:
color = color_theme["schemes"][style_theme.lower()].props[
color_key
]
exec(f"self.{color_key}Color = {rgba(color)}")
else:
self.schemes_name_colors = list(
color_theme[style_theme.lower()].keys()
)
for color_key in self.schemes_name_colors:
color = color_theme[style_theme.lower()][color_key]
exec(f"self.{color_key}Color = {rgba(color)}")
self.disabledTextColor = self._get_disabled_hint_text_color()
self._set_application_scheme(self.primary_palette)

def switch_theme(self) -> None:
"""Switches the theme from light to dark."""
Expand All @@ -688,59 +669,71 @@ def sync_theme_styles(self, *args) -> None:
theme_font_styles.append(style)

def _set_dynamic_color(self, path_to_wallpaper: str) -> None:
dynamic_theme = {"schemes":theme_from_source_color(source_color_from_image(path_to_wallpaper), custom_colors=[]).schemes}
self.current_color_theme = dynamic_theme
self.schemes_name_colors = list(
dynamic_theme["schemes"][self.theme_style.lower()].props.keys()
start_time = default_timer()
image = Image.open(path_to_wallpaper)
pixel_len = image.width * image.height
image_data = image.getdata()
pixel_array = [image_data[_] for _ in range(0, pixel_len, 10)]
end_time = default_timer()

Logger.info(
"KivyMD: "
f"Color scheme generation. Creating an array of pixels from a "
f"system wallpaper file - {end_time - start_time} sec."
)

for color_key in self.schemes_name_colors:
color = dynamic_theme["schemes"][self.theme_style.lower()].props[
color_key
]
exec(f"self.{color_key}Color = {rgba(color)}")
self.disabledTextColor = self._get_disabled_hint_text_color()
start_time = default_timer()
colors = QuantizeCelebi(pixel_array, 128)
selected = Score.score(colors)
end_time = default_timer()

def _set_default_color(self) -> None:
# Default blue of Google
default_theme = {"schemes":theme_from_source_color(0xFF4285F4).schemes}
self.current_color_theme = default_theme
self.schemes_name_colors = list(
default_theme[self.theme_style.lower()].keys()
Logger.info(
"KivyMD: "
f"Color scheme generation. Get dominant colors - "
f"{end_time - start_time} sec."
)
self._set_application_scheme(color=selected[0])

def _set_application_scheme(
self, default_color: str = None, color: int = None
) -> None:
# Default blue of Google.
start_time = default_timer()
if not color:
color = get_color_from_hex(
hex_colormap[
"blue" if not default_color else default_color.lower()
]
)
color = argb_from_rgb(*color[:-1])

scheme = SchemeTonalSpot(
Hct.from_int(color), # the color of current theme in int form
False if self.theme_style == "Light" else True, # dark mode
0.0, # contrast level
)
default_theme = {}

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())
default_theme[color_name] = color_value
exec(f"self.{color_name}Color = {color_value}")

for color_key in self.schemes_name_colors:
color = default_theme[self.theme_style.lower()][color_key]
exec(f"self.{color_key}Color = {rgba(color)}")
self.disabledTextColor = self._get_disabled_hint_text_color()
end_time = default_timer()

Logger.info(
"KivyMD: "
f"Color scheme generation. Get a color scheme from an installed "
f"palette - {end_time - start_time} sec."
)

def _set_palette_color(self) -> None:
if not self.primary_palette:
self.primary_palette = "Blue"

color_theme = {"schemes":theme_from_source_color(
[
argb_from_rgb(
*[
int(c * 255)
for c in get_color_from_hex(
hex_colormap[self.primary_palette.lower()]
)
][:3]
)
][0],
fix_if_disliked=True
).schemes}
self.current_color_theme = color_theme
self.schemes_name_colors = list(
color_theme["schemes"][self.theme_style.lower()].props.keys()
)
for color_key in self.schemes_name_colors:
color = color_theme["schemes"][self.theme_style.lower()].props[
color_key
]
exec(f"self.{color_key}Color = {rgba(color)}")
self.disabledTextColor = self._get_disabled_hint_text_color()
self._set_application_scheme(self.primary_palette)


class ThemableBehavior(EventDispatcher):
Expand Down

0 comments on commit 2609318

Please sign in to comment.