Restructured repository
							
								
								
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,3 +1,19 @@ | ||||
| sbapp/.buildozer | ||||
| sbapp/buildozer.spec | ||||
| sbapp/requirements.txt | ||||
| sbapp/venv | ||||
| sbapp/bin | ||||
| sbapp/app_storage | ||||
| sbapp/RNS | ||||
| sbapp/LXMF | ||||
| sbapp/precompiled | ||||
| sbapp/*.DS_Store | ||||
| sbapp/*.pyc | ||||
| sbapp/build | ||||
| sbapp/dist | ||||
| sbapp/docs/build | ||||
| sbapp/sideband*.egg-info | ||||
| 
 | ||||
| .buildozer | ||||
| buildozer.spec | ||||
| requirements.txt | ||||
|  | ||||
							
								
								
									
										44
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						| @ -1,35 +1,21 @@ | ||||
| all: prepare debug | ||||
| devapk: | ||||
|     make -C sbapp devapk | ||||
| 
 | ||||
| prepare: activate | ||||
| 
 | ||||
| clean: | ||||
| 	buildozer android clean | ||||
| 	 | ||||
| activate: | ||||
| 	(. venv/bin/activate) | ||||
| 	(mv setup.py setup.disabled) | ||||
| 
 | ||||
| debug: | ||||
| 	buildozer android debug | ||||
| 
 | ||||
| release: | ||||
| 	buildozer android release | ||||
| 
 | ||||
| postbuild: | ||||
| 	(mv setup.disabled setup.py) | ||||
| 
 | ||||
| apk: prepare release postbuild | ||||
| 
 | ||||
| devapk: prepare debug postbuild | ||||
| apk: | ||||
|     make -C sbapp apk | ||||
| 
 | ||||
| install: | ||||
| 	adb install bin/sideband-0.1.6-arm64-v8a-debug.apk | ||||
| 
 | ||||
| install-release: | ||||
| 	adb install bin/sideband-0.1.6-arm64-v8a-release.apk | ||||
|     make -C sbapp install | ||||
| 
 | ||||
| console: | ||||
| 	(adb logcat | grep python) | ||||
|     make -C sbapp conole | ||||
| 
 | ||||
| getrns: | ||||
| 	(rm ./RNS -r;cp -rv ../Reticulum/RNS ./;rm ./RNS/Utilities/RNS;rm ./RNS/__pycache__ -r) | ||||
| clean: | ||||
|     @echo Cleaning... | ||||
|     -rm -r ./build | ||||
|     -rm -r ./dist | ||||
| 
 | ||||
| build_wheel: | ||||
|     python3 setup.py sdist bdist_wheel | ||||
| 
 | ||||
| release: build_wheel apk | ||||
							
								
								
									
										38
									
								
								sbapp/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,38 @@ | ||||
| all: prepare debug | ||||
| 
 | ||||
| prepare: activate cleanrns getrns | ||||
| 
 | ||||
| clean: | ||||
| 	buildozer android clean | ||||
| 	-(rm ./__pycache__ -r) | ||||
| 	-(rm ./app_storage -r) | ||||
| 	-(rm ./bin -r) | ||||
| 	 | ||||
| activate: | ||||
| 	(. venv/bin/activate) | ||||
| 
 | ||||
| debug: | ||||
| 	buildozer android debug | ||||
| 
 | ||||
| release: | ||||
| 	buildozer android release | ||||
| 
 | ||||
| postbuild: | ||||
| 	cleanrns | ||||
| 	@echo Done | ||||
| 
 | ||||
| apk: prepare release postbuild | ||||
| 
 | ||||
| devapk: prepare debug postbuild | ||||
| 
 | ||||
| install: | ||||
| 	adb install bin/sideband-0.1.6-arm64-v8a-release.apk | ||||
| 
 | ||||
| console: | ||||
| 	(adb logcat | grep python) | ||||
| 
 | ||||
| getrns: | ||||
| 	(cp -rv ../../Reticulum/RNS ./;rm ./RNS/Utilities/RNS;rm ./RNS/__pycache__ -r) | ||||
| 
 | ||||
| cleanrns: | ||||
| 	-(rm ./RNS -r) | ||||
							
								
								
									
										5
									
								
								sbapp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| import os | ||||
| import glob | ||||
| 
 | ||||
| modules = glob.glob(os.path.dirname(__file__)+"/*.py") | ||||
| __all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')] | ||||
| Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB | 
							
								
								
									
										71
									
								
								sbapp/kivymd/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,71 @@ | ||||
| """ | ||||
| KivyMD | ||||
| ====== | ||||
| 
 | ||||
| .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/previous.png | ||||
| 
 | ||||
| Is a collection of Material Design compliant widgets for use with, | ||||
| `Kivy cross-platform graphical framework <http://kivy.org/#home>`_ | ||||
| a framework for cross-platform, touch-enabled graphical applications. | ||||
| The project's goal is to approximate Google's `Material Design spec | ||||
| <https://material.io/design/introduction>`_ as close as possible without | ||||
| sacrificing ease of use or application performance. | ||||
| 
 | ||||
| This library is a fork of the `KivyMD project | ||||
| <https://gitlab.com/kivymd/KivyMD>`_ the author of which stopped supporting | ||||
| this project three years ago. We found the strength and brought this project | ||||
| to a new level. Currently we're in **beta** status, so things are changing | ||||
| all the time and we cannot promise any kind of API stability. | ||||
| However it is safe to vendor now and make use of what's currently available. | ||||
| 
 | ||||
| Join the project! Just fork the project, branch out and submit a pull request | ||||
| when your patch is ready. If any changes are necessary, we'll guide you | ||||
| through the steps that need to be done via PR comments or access to your for | ||||
| may be requested to outright submit them. If you wish to become a project | ||||
| developer (permission to create branches on the project without forking for | ||||
| easier collaboration), have at least one PR approved and ask for it. | ||||
| If you contribute regularly to the project the role may be offered to you | ||||
| without asking too. | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| import kivy | ||||
| from kivy.logger import Logger | ||||
| 
 | ||||
| __version__ = "1.0.0.dev0" | ||||
| """KivyMD version.""" | ||||
| 
 | ||||
| release = False | ||||
| kivy.require("2.0.0") | ||||
| 
 | ||||
| try: | ||||
|     from kivymd._version import __date__, __hash__, __short_hash__ | ||||
| except ImportError: | ||||
|     __hash__ = __short_hash__ = __date__ = "" | ||||
| 
 | ||||
| path = os.path.dirname(__file__) | ||||
| """Path to KivyMD package directory.""" | ||||
| 
 | ||||
| fonts_path = os.path.join(path, f"fonts{os.sep}") | ||||
| """Path to fonts directory.""" | ||||
| 
 | ||||
| images_path = os.path.join(path, f"images{os.sep}") | ||||
| """Path to images directory.""" | ||||
| 
 | ||||
| uix_path = os.path.join(path, "uix") | ||||
| """Path to uix directory.""" | ||||
| 
 | ||||
| _log_message = ( | ||||
|     "KivyMD:" | ||||
|     + (" Release" if release else "") | ||||
|     + f" {__version__}" | ||||
|     + (f", git-{__short_hash__}" if __short_hash__ else "") | ||||
|     + (f", {__date__}" if __date__ else "") | ||||
|     + f' (installed at "{__file__}")' | ||||
| ) | ||||
| Logger.info(_log_message) | ||||
| 
 | ||||
| import kivymd.factory_registers  # NOQA | ||||
| import kivymd.font_definitions  # NOQA | ||||
| from kivymd.tools.packaging.pyinstaller import hooks_path  # NOQA | ||||
							
								
								
									
										5
									
								
								sbapp/kivymd/_version.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| # THIS FILE IS GENERATED FROM KIVYMD SETUP.PY | ||||
| __version__ = '1.0.0.dev0' | ||||
| __hash__ = '68ec8626a93b0e7f69e48d9755c4af70028f66a2' | ||||
| __short_hash__ = '68ec862' | ||||
| __date__ = '2022-07-07' | ||||
							
								
								
									
										133
									
								
								sbapp/kivymd/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,133 @@ | ||||
| """ | ||||
| Themes/Material App | ||||
| =================== | ||||
| 
 | ||||
| This module contains :class:`MDApp` class that is inherited from | ||||
| :class:`~kivy.app.App`. :class:`MDApp` has some properties needed for ``KivyMD`` | ||||
| library (like :attr:`~MDApp.theme_cls`). You can turn on the monitor displaying | ||||
| the current ``FPS`` value in your application: | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     KV = ''' | ||||
|     MDScreen: | ||||
| 
 | ||||
|         MDLabel: | ||||
|             text: "Hello, World!" | ||||
|             halign: "center" | ||||
|     ''' | ||||
| 
 | ||||
|     from kivy.lang import Builder | ||||
| 
 | ||||
|     from kivymd.app import MDApp | ||||
| 
 | ||||
| 
 | ||||
|     class MainApp(MDApp): | ||||
|         def build(self): | ||||
|             return Builder.load_string(KV) | ||||
| 
 | ||||
|         def on_start(self): | ||||
|             self.fps_monitor_start() | ||||
| 
 | ||||
| 
 | ||||
|     MainApp().run() | ||||
| 
 | ||||
| .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fps-monitor.png | ||||
|     :width: 350 px | ||||
|     :align: center | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| __all__ = ("MDApp",) | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| from kivy.app import App | ||||
| from kivy.lang import Builder | ||||
| from kivy.logger import Logger | ||||
| from kivy.properties import ObjectProperty | ||||
| 
 | ||||
| from kivymd.theming import ThemeManager | ||||
| 
 | ||||
| 
 | ||||
| class FpsMonitoring: | ||||
|     """Implements a monitor to display the current FPS in the toolbar.""" | ||||
| 
 | ||||
|     def fps_monitor_start(self) -> None: | ||||
|         """Adds a monitor to the main application window.""" | ||||
| 
 | ||||
|         from kivy.core.window import Window | ||||
| 
 | ||||
|         from kivymd.utils.fpsmonitor import FpsMonitor | ||||
| 
 | ||||
|         monitor = FpsMonitor() | ||||
|         monitor.start() | ||||
|         Window.add_widget(monitor) | ||||
| 
 | ||||
| 
 | ||||
| class MDApp(App, FpsMonitoring): | ||||
|     """ | ||||
|     Application class, see :class:`~kivy.app.App` class documentation for more | ||||
|     information. | ||||
|     """ | ||||
| 
 | ||||
|     theme_cls = ObjectProperty() | ||||
|     """ | ||||
|     Instance of :class:`~ThemeManager` class. | ||||
| 
 | ||||
|     .. Warning:: The :attr:`~theme_cls` attribute is already available | ||||
|         in a class that is inherited from the :class:`~MDApp` class. | ||||
|         The following code will result in an error! | ||||
| 
 | ||||
|     .. code-block:: python | ||||
| 
 | ||||
|         class MainApp(MDApp): | ||||
|             theme_cls = ThemeManager() | ||||
|             theme_cls.primary_palette = "Teal" | ||||
| 
 | ||||
|     .. Note:: Correctly do as shown below! | ||||
| 
 | ||||
|     .. code-block:: python | ||||
| 
 | ||||
|         class MainApp(MDApp): | ||||
|             def build(self): | ||||
|                 self.theme_cls.primary_palette = "Teal" | ||||
| 
 | ||||
|     :attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         super().__init__(**kwargs) | ||||
|         self.theme_cls = ThemeManager() | ||||
| 
 | ||||
|     def load_all_kv_files(self, path_to_directory: str) -> None: | ||||
|         """ | ||||
|         Recursively loads KV files from the selected directory. | ||||
| 
 | ||||
|         .. versionadded:: 1.0.0 | ||||
|         """ | ||||
| 
 | ||||
|         for path_to_dir, dirs, files in os.walk(path_to_directory): | ||||
|             # When using the `load_all_kv_files` method, all KV files | ||||
|             # from the `KivyMD` library were loaded twice, which leads to | ||||
|             # failures when using application built using `PyInstaller`. | ||||
|             if "kivymd" in path_to_directory: | ||||
|                 Logger.critical( | ||||
|                     "KivyMD: " | ||||
|                     "Do not use the word 'kivymd' in the name of the directory " | ||||
|                     "from where you download KV files" | ||||
|                 ) | ||||
|             if ( | ||||
|                 "venv" in path_to_dir | ||||
|                 or ".buildozer" in path_to_dir | ||||
|                 or os.path.join("kivymd") in path_to_dir | ||||
|             ): | ||||
|                 continue | ||||
|             for name_file in files: | ||||
|                 if ( | ||||
|                     os.path.splitext(name_file)[1] == ".kv" | ||||
|                     and name_file != "style.kv"  # if use PyInstaller | ||||
|                     and "__MACOS" not in path_to_dir  # if use Mac OS | ||||
|                 ): | ||||
|                     path_to_kv_file = os.path.join(path_to_dir, name_file) | ||||
|                     Builder.load_file(path_to_kv_file) | ||||
							
								
								
									
										954
									
								
								sbapp/kivymd/color_definitions.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,954 @@ | ||||
| """ | ||||
| Themes/Color Definitions | ||||
| ======================== | ||||
| 
 | ||||
| .. seealso:: | ||||
| 
 | ||||
|    `Material Design spec, The color system <https://material.io/design/color/the-color-system.html>`_ | ||||
| 
 | ||||
|    `Material Design spec, The color tool <https://material.io/resources/color/#!/?view.left=0&view.right=0>`_ | ||||
| 
 | ||||
| Material colors palette to use in :class:`kivymd.theming.ThemeManager`. | ||||
| :data:`~colors` is a dict-in-dict where the first key is a value from | ||||
| :data:`~palette` and the second key is a value from :data:`~hue`. Color is a hex | ||||
| value, a string of 6 characters (0-9, A-F) written in uppercase. | ||||
| 
 | ||||
| For example, ``colors["Red"]["900"]`` is ``"B71C1C"``. | ||||
| """ | ||||
| 
 | ||||
| colors = { | ||||
|     "Red": { | ||||
|         "50": "FFEBEE", | ||||
|         "100": "FFCDD2", | ||||
|         "200": "EF9A9A", | ||||
|         "300": "E57373", | ||||
|         "400": "EF5350", | ||||
|         "500": "F44336", | ||||
|         "600": "E53935", | ||||
|         "700": "D32F2F", | ||||
|         "800": "C62828", | ||||
|         "900": "B71C1C", | ||||
|         "A100": "FF8A80", | ||||
|         "A200": "FF5252", | ||||
|         "A400": "FF1744", | ||||
|         "A700": "D50000", | ||||
|     }, | ||||
|     "Pink": { | ||||
|         "50": "FCE4EC", | ||||
|         "100": "F8BBD0", | ||||
|         "200": "F48FB1", | ||||
|         "300": "F06292", | ||||
|         "400": "EC407A", | ||||
|         "500": "E91E63", | ||||
|         "600": "D81B60", | ||||
|         "700": "C2185B", | ||||
|         "800": "AD1457", | ||||
|         "900": "880E4F", | ||||
|         "A100": "FF80AB", | ||||
|         "A200": "FF4081", | ||||
|         "A400": "F50057", | ||||
|         "A700": "C51162", | ||||
|     }, | ||||
|     "Purple": { | ||||
|         "50": "F3E5F5", | ||||
|         "100": "E1BEE7", | ||||
|         "200": "CE93D8", | ||||
|         "300": "BA68C8", | ||||
|         "400": "AB47BC", | ||||
|         "500": "9C27B0", | ||||
|         "600": "8E24AA", | ||||
|         "700": "7B1FA2", | ||||
|         "800": "6A1B9A", | ||||
|         "900": "4A148C", | ||||
|         "A100": "EA80FC", | ||||
|         "A200": "E040FB", | ||||
|         "A400": "D500F9", | ||||
|         "A700": "AA00FF", | ||||
|     }, | ||||
|     "DeepPurple": { | ||||
|         "50": "EDE7F6", | ||||
|         "100": "D1C4E9", | ||||
|         "200": "B39DDB", | ||||
|         "300": "9575CD", | ||||
|         "400": "7E57C2", | ||||
|         "500": "673AB7", | ||||
|         "600": "5E35B1", | ||||
|         "700": "512DA8", | ||||
|         "800": "4527A0", | ||||
|         "900": "311B92", | ||||
|         "A100": "B388FF", | ||||
|         "A200": "7C4DFF", | ||||
|         "A400": "651FFF", | ||||
|         "A700": "6200EA", | ||||
|     }, | ||||
|     "Indigo": { | ||||
|         "50": "E8EAF6", | ||||
|         "100": "C5CAE9", | ||||
|         "200": "9FA8DA", | ||||
|         "300": "7986CB", | ||||
|         "400": "5C6BC0", | ||||
|         "500": "3F51B5", | ||||
|         "600": "3949AB", | ||||
|         "700": "303F9F", | ||||
|         "800": "283593", | ||||
|         "900": "1A237E", | ||||
|         "A100": "8C9EFF", | ||||
|         "A200": "536DFE", | ||||
|         "A400": "3D5AFE", | ||||
|         "A700": "304FFE", | ||||
|     }, | ||||
|     "Blue": { | ||||
|         "50": "E3F2FD", | ||||
|         "100": "BBDEFB", | ||||
|         "200": "90CAF9", | ||||
|         "300": "64B5F6", | ||||
|         "400": "42A5F5", | ||||
|         "500": "2196F3", | ||||
|         "600": "1E88E5", | ||||
|         "700": "1976D2", | ||||
|         "800": "1565C0", | ||||
|         "900": "0D47A1", | ||||
|         "A100": "82B1FF", | ||||
|         "A200": "448AFF", | ||||
|         "A400": "2979FF", | ||||
|         "A700": "2962FF", | ||||
|     }, | ||||
|     "LightBlue": { | ||||
|         "50": "E1F5FE", | ||||
|         "100": "B3E5FC", | ||||
|         "200": "81D4FA", | ||||
|         "300": "4FC3F7", | ||||
|         "400": "29B6F6", | ||||
|         "500": "03A9F4", | ||||
|         "600": "039BE5", | ||||
|         "700": "0288D1", | ||||
|         "800": "0277BD", | ||||
|         "900": "01579B", | ||||
|         "A100": "80D8FF", | ||||
|         "A200": "40C4FF", | ||||
|         "A400": "00B0FF", | ||||
|         "A700": "0091EA", | ||||
|     }, | ||||
|     "Cyan": { | ||||
|         "50": "E0F7FA", | ||||
|         "100": "B2EBF2", | ||||
|         "200": "80DEEA", | ||||
|         "300": "4DD0E1", | ||||
|         "400": "26C6DA", | ||||
|         "500": "00BCD4", | ||||
|         "600": "00ACC1", | ||||
|         "700": "0097A7", | ||||
|         "800": "00838F", | ||||
|         "900": "006064", | ||||
|         "A100": "84FFFF", | ||||
|         "A200": "18FFFF", | ||||
|         "A400": "00E5FF", | ||||
|         "A700": "00B8D4", | ||||
|     }, | ||||
|     "Teal": { | ||||
|         "50": "E0F2F1", | ||||
|         "100": "B2DFDB", | ||||
|         "200": "80CBC4", | ||||
|         "300": "4DB6AC", | ||||
|         "400": "26A69A", | ||||
|         "500": "009688", | ||||
|         "600": "00897B", | ||||
|         "700": "00796B", | ||||
|         "800": "00695C", | ||||
|         "900": "004D40", | ||||
|         "A100": "A7FFEB", | ||||
|         "A200": "64FFDA", | ||||
|         "A400": "1DE9B6", | ||||
|         "A700": "00BFA5", | ||||
|     }, | ||||
|     "Green": { | ||||
|         "50": "E8F5E9", | ||||
|         "100": "C8E6C9", | ||||
|         "200": "A5D6A7", | ||||
|         "300": "81C784", | ||||
|         "400": "66BB6A", | ||||
|         "500": "4CAF50", | ||||
|         "600": "43A047", | ||||
|         "700": "388E3C", | ||||
|         "800": "2E7D32", | ||||
|         "900": "1B5E20", | ||||
|         "A100": "B9F6CA", | ||||
|         "A200": "69F0AE", | ||||
|         "A400": "00E676", | ||||
|         "A700": "00C853", | ||||
|     }, | ||||
|     "LightGreen": { | ||||
|         "50": "F1F8E9", | ||||
|         "100": "DCEDC8", | ||||
|         "200": "C5E1A5", | ||||
|         "300": "AED581", | ||||
|         "400": "9CCC65", | ||||
|         "500": "8BC34A", | ||||
|         "600": "7CB342", | ||||
|         "700": "689F38", | ||||
|         "800": "558B2F", | ||||
|         "900": "33691E", | ||||
|         "A100": "CCFF90", | ||||
|         "A200": "B2FF59", | ||||
|         "A400": "76FF03", | ||||
|         "A700": "64DD17", | ||||
|     }, | ||||
|     "Lime": { | ||||
|         "50": "F9FBE7", | ||||
|         "100": "F0F4C3", | ||||
|         "200": "E6EE9C", | ||||
|         "300": "DCE775", | ||||
|         "400": "D4E157", | ||||
|         "500": "CDDC39", | ||||
|         "600": "C0CA33", | ||||
|         "700": "AFB42B", | ||||
|         "800": "9E9D24", | ||||
|         "900": "827717", | ||||
|         "A100": "F4FF81", | ||||
|         "A200": "EEFF41", | ||||
|         "A400": "C6FF00", | ||||
|         "A700": "AEEA00", | ||||
|     }, | ||||
|     "Yellow": { | ||||
|         "50": "FFFDE7", | ||||
|         "100": "FFF9C4", | ||||
|         "200": "FFF59D", | ||||
|         "300": "FFF176", | ||||
|         "400": "FFEE58", | ||||
|         "500": "FFEB3B", | ||||
|         "600": "FDD835", | ||||
|         "700": "FBC02D", | ||||
|         "800": "F9A825", | ||||
|         "900": "F57F17", | ||||
|         "A100": "FFFF8D", | ||||
|         "A200": "FFFF00", | ||||
|         "A400": "FFEA00", | ||||
|         "A700": "FFD600", | ||||
|     }, | ||||
|     "Amber": { | ||||
|         "50": "FFF8E1", | ||||
|         "100": "FFECB3", | ||||
|         "200": "FFE082", | ||||
|         "300": "FFD54F", | ||||
|         "400": "FFCA28", | ||||
|         "500": "FFC107", | ||||
|         "600": "FFB300", | ||||
|         "700": "FFA000", | ||||
|         "800": "FF8F00", | ||||
|         "900": "FF6F00", | ||||
|         "A100": "FFE57F", | ||||
|         "A200": "FFD740", | ||||
|         "A400": "FFC400", | ||||
|         "A700": "FFAB00", | ||||
|     }, | ||||
|     "Orange": { | ||||
|         "50": "FFF3E0", | ||||
|         "100": "FFE0B2", | ||||
|         "200": "FFCC80", | ||||
|         "300": "FFB74D", | ||||
|         "400": "FFA726", | ||||
|         "500": "FF9800", | ||||
|         "600": "FB8C00", | ||||
|         "700": "F57C00", | ||||
|         "800": "EF6C00", | ||||
|         "900": "E65100", | ||||
|         "A100": "FFD180", | ||||
|         "A200": "FFAB40", | ||||
|         "A400": "FF9100", | ||||
|         "A700": "FF6D00", | ||||
|     }, | ||||
|     "DeepOrange": { | ||||
|         "50": "FBE9E7", | ||||
|         "100": "FFCCBC", | ||||
|         "200": "FFAB91", | ||||
|         "300": "FF8A65", | ||||
|         "400": "FF7043", | ||||
|         "500": "FF5722", | ||||
|         "600": "F4511E", | ||||
|         "700": "E64A19", | ||||
|         "800": "D84315", | ||||
|         "900": "BF360C", | ||||
|         "A100": "FF9E80", | ||||
|         "A200": "FF6E40", | ||||
|         "A400": "FF3D00", | ||||
|         "A700": "DD2C00", | ||||
|     }, | ||||
|     "Brown": { | ||||
|         "50": "EFEBE9", | ||||
|         "100": "D7CCC8", | ||||
|         "200": "BCAAA4", | ||||
|         "300": "A1887F", | ||||
|         "400": "8D6E63", | ||||
|         "500": "795548", | ||||
|         "600": "6D4C41", | ||||
|         "700": "5D4037", | ||||
|         "800": "4E342E", | ||||
|         "900": "3E2723", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "Gray": { | ||||
|         "50": "FAFAFA", | ||||
|         "100": "F5F5F5", | ||||
|         "200": "EEEEEE", | ||||
|         "300": "E0E0E0", | ||||
|         "400": "BDBDBD", | ||||
|         "500": "9E9E9E", | ||||
|         "600": "757575", | ||||
|         "700": "616161", | ||||
|         "800": "424242", | ||||
|         "900": "212121", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "BlueGray": { | ||||
|         "50": "ECEFF1", | ||||
|         "100": "CFD8DC", | ||||
|         "200": "B0BEC5", | ||||
|         "300": "90A4AE", | ||||
|         "400": "78909C", | ||||
|         "500": "607D8B", | ||||
|         "600": "546E7A", | ||||
|         "700": "455A64", | ||||
|         "800": "37474F", | ||||
|         "900": "263238", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "Light": { | ||||
|         "StatusBar": "E0E0E0", | ||||
|         "AppBar": "F5F5F5", | ||||
|         "Background": "FAFAFA", | ||||
|         "CardsDialogs": "FFFFFF", | ||||
|         "FlatButtonDown": "cccccc", | ||||
|     }, | ||||
|     "Dark": { | ||||
|         "StatusBar": "000000", | ||||
|         "AppBar": "1f1f1f", | ||||
|         "Background": "121212", | ||||
|         "CardsDialogs": "212121", | ||||
|         "FlatButtonDown": "999999", | ||||
|     }, | ||||
| } | ||||
| """ | ||||
| Color palette. Taken from `2014 Material Design color palettes | ||||
| <https://material.io/design/color/the-color-system.html>`_. | ||||
| 
 | ||||
| To demonstrate the shades of the palette, you can run the following code: | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     from kivy.lang import Builder | ||||
|     from kivy.properties import ListProperty, StringProperty | ||||
| 
 | ||||
|     from kivymd.color_definitions import colors | ||||
|     from kivymd.uix.tab import MDTabsBase | ||||
|     from kivymd.uix.boxlayout import MDBoxLayout | ||||
| 
 | ||||
|     demo = ''' | ||||
|     <Root@MDBoxLayout> | ||||
|         orientation: 'vertical' | ||||
| 
 | ||||
|         MDTopAppBar: | ||||
|             title: app.title | ||||
| 
 | ||||
|         MDTabs: | ||||
|             id: android_tabs | ||||
|             on_tab_switch: app.on_tab_switch(*args) | ||||
|             size_hint_y: None | ||||
|             height: "48dp" | ||||
|             tab_indicator_anim: False | ||||
| 
 | ||||
|         RecycleView: | ||||
|             id: rv | ||||
|             key_viewclass: "viewclass" | ||||
|             key_size: "height" | ||||
| 
 | ||||
|             RecycleBoxLayout: | ||||
|                 default_size: None, dp(48) | ||||
|                 default_size_hint: 1, None | ||||
|                 size_hint_y: None | ||||
|                 height: self.minimum_height | ||||
|                 orientation: "vertical" | ||||
| 
 | ||||
| 
 | ||||
|     <ItemColor> | ||||
|         size_hint_y: None | ||||
|         height: "42dp" | ||||
| 
 | ||||
|         MDLabel: | ||||
|             text: root.text | ||||
|             halign: "center" | ||||
| 
 | ||||
| 
 | ||||
|     <Tab> | ||||
|     ''' | ||||
| 
 | ||||
|     from kivy.factory import Factory | ||||
| 
 | ||||
|     from kivymd.app import MDApp | ||||
| 
 | ||||
| 
 | ||||
|     class Tab(MDBoxLayout, MDTabsBase): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
|     class ItemColor(MDBoxLayout): | ||||
|         text = StringProperty() | ||||
|         color = ListProperty() | ||||
| 
 | ||||
| 
 | ||||
|     class Palette(MDApp): | ||||
|         title = "Colors definitions" | ||||
| 
 | ||||
|         def build(self): | ||||
|             Builder.load_string(demo) | ||||
|             self.screen = Factory.Root() | ||||
| 
 | ||||
|             for name_tab in colors.keys(): | ||||
|                 tab = Tab(text=name_tab) | ||||
|                 self.screen.ids.android_tabs.add_widget(tab) | ||||
|             return self.screen | ||||
| 
 | ||||
|         def on_tab_switch( | ||||
|             self, instance_tabs, instance_tab, instance_tabs_label, tab_text | ||||
|         ): | ||||
|             self.screen.ids.rv.data = [] | ||||
|             if not tab_text: | ||||
|                 tab_text = 'Red' | ||||
|             for value_color in colors[tab_text]: | ||||
|                 self.screen.ids.rv.data.append( | ||||
|                     { | ||||
|                         "viewclass": "ItemColor", | ||||
|                         "md_bg_color": colors[tab_text][value_color], | ||||
|                         "text": value_color, | ||||
|                     } | ||||
|                 ) | ||||
| 
 | ||||
|         def on_start(self): | ||||
|             self.on_tab_switch( | ||||
|                 None, | ||||
|                 None, | ||||
|                 None, | ||||
|                 self.screen.ids.android_tabs.ids.layout.children[-1].text, | ||||
|             ) | ||||
| 
 | ||||
| 
 | ||||
|     Palette().run() | ||||
| 
 | ||||
| .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/palette.gif | ||||
|     :align: center | ||||
| """ | ||||
| 
 | ||||
| palette = [ | ||||
|     "Red", | ||||
|     "Pink", | ||||
|     "Purple", | ||||
|     "DeepPurple", | ||||
|     "Indigo", | ||||
|     "Blue", | ||||
|     "LightBlue", | ||||
|     "Cyan", | ||||
|     "Teal", | ||||
|     "Green", | ||||
|     "LightGreen", | ||||
|     "Lime", | ||||
|     "Yellow", | ||||
|     "Amber", | ||||
|     "Orange", | ||||
|     "DeepOrange", | ||||
|     "Brown", | ||||
|     "Gray", | ||||
|     "BlueGray", | ||||
| ] | ||||
| """Valid values for color palette selecting.""" | ||||
| 
 | ||||
| hue = [ | ||||
|     "50", | ||||
|     "100", | ||||
|     "200", | ||||
|     "300", | ||||
|     "400", | ||||
|     "500", | ||||
|     "600", | ||||
|     "700", | ||||
|     "800", | ||||
|     "900", | ||||
|     "A100", | ||||
|     "A200", | ||||
|     "A400", | ||||
|     "A700", | ||||
| ] | ||||
| """Valid values for color hue selecting.""" | ||||
| 
 | ||||
| 
 | ||||
| light_colors = { | ||||
|     "Red": ["50", "100", "200", "300", "A100"], | ||||
|     "Pink": ["50", "100", "200", "A100"], | ||||
|     "Purple": ["50", "100", "200", "A100"], | ||||
|     "DeepPurple": ["50", "100", "200", "A100"], | ||||
|     "Indigo": ["50", "100", "200", "A100"], | ||||
|     "Blue": ["50", "100", "200", "300", "400", "A100"], | ||||
|     "LightBlue": [ | ||||
|         "50", | ||||
|         "100", | ||||
|         "200", | ||||
|         "300", | ||||
|         "400", | ||||
|         "500", | ||||
|         "A100", | ||||
|         "A200", | ||||
|         "A400", | ||||
|     ], | ||||
|     "Cyan": [ | ||||
|         "50", | ||||
|         "100", | ||||
|         "200", | ||||
|         "300", | ||||
|         "400", | ||||
|         "500", | ||||
|         "600", | ||||
|         "A100", | ||||
|         "A200", | ||||
|         "A400", | ||||
|         "A700", | ||||
|     ], | ||||
|     "Teal": ["50", "100", "200", "300", "400", "A100", "A200", "A400", "A700"], | ||||
|     "Green": [ | ||||
|         "50", | ||||
|         "100", | ||||
|         "200", | ||||
|         "300", | ||||
|         "400", | ||||
|         "500", | ||||
|         "A100", | ||||
|         "A200", | ||||
|         "A400", | ||||
|         "A700", | ||||
|     ], | ||||
|     "LightGreen": [ | ||||
|         "50", | ||||
|         "100", | ||||
|         "200", | ||||
|         "300", | ||||
|         "400", | ||||
|         "500", | ||||
|         "600", | ||||
|         "A100", | ||||
|         "A200", | ||||
|         "A400", | ||||
|         "A700", | ||||
|     ], | ||||
|     "Lime": [ | ||||
|         "50", | ||||
|         "100", | ||||
|         "200", | ||||
|         "300", | ||||
|         "400", | ||||
|         "500", | ||||
|         "600", | ||||
|         "700", | ||||
|         "800", | ||||
|         "A100", | ||||
|         "A200", | ||||
|         "A400", | ||||
|         "A700", | ||||
|     ], | ||||
|     "Yellow": [ | ||||
|         "50", | ||||
|         "100", | ||||
|         "200", | ||||
|         "300", | ||||
|         "400", | ||||
|         "500", | ||||
|         "600", | ||||
|         "700", | ||||
|         "800", | ||||
|         "900", | ||||
|         "A100", | ||||
|         "A200", | ||||
|         "A400", | ||||
|         "A700", | ||||
|     ], | ||||
|     "Amber": [ | ||||
|         "50", | ||||
|         "100", | ||||
|         "200", | ||||
|         "300", | ||||
|         "400", | ||||
|         "500", | ||||
|         "600", | ||||
|         "700", | ||||
|         "800", | ||||
|         "900", | ||||
|         "A100", | ||||
|         "A200", | ||||
|         "A400", | ||||
|         "A700", | ||||
|     ], | ||||
|     "Orange": [ | ||||
|         "50", | ||||
|         "100", | ||||
|         "200", | ||||
|         "300", | ||||
|         "400", | ||||
|         "500", | ||||
|         "600", | ||||
|         "700", | ||||
|         "A100", | ||||
|         "A200", | ||||
|         "A400", | ||||
|         "A700", | ||||
|     ], | ||||
|     "DeepOrange": ["50", "100", "200", "300", "400", "A100", "A200"], | ||||
|     "Brown": ["50", "100", "200"], | ||||
|     "Gray": ["50", "100", "200", "300", "400", "500"], | ||||
|     "BlueGray": ["50", "100", "200", "300"], | ||||
|     "Dark": [], | ||||
|     "Light": ["White", "MainBackground", "DialogBackground"], | ||||
| } | ||||
| """Which colors are light. Other are dark.""" | ||||
| 
 | ||||
| text_colors = { | ||||
|     "Red": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "FFFFFF", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "Pink": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "FFFFFF", | ||||
|         "400": "FFFFFF", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "Purple": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "FFFFFF", | ||||
|         "400": "FFFFFF", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "DeepPurple": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "FFFFFF", | ||||
|         "400": "FFFFFF", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "Indigo": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "FFFFFF", | ||||
|         "400": "FFFFFF", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "Blue": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "LightBlue": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "Cyan": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "000000", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "Teal": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "Green": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "LightGreen": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "000000", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "Lime": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "000000", | ||||
|         "700": "000000", | ||||
|         "800": "000000", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "Yellow": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "000000", | ||||
|         "700": "000000", | ||||
|         "800": "000000", | ||||
|         "900": "000000", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "Amber": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "000000", | ||||
|         "700": "000000", | ||||
|         "800": "000000", | ||||
|         "900": "000000", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "Orange": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "000000", | ||||
|         "700": "000000", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "000000", | ||||
|         "A700": "000000", | ||||
|     }, | ||||
|     "DeepOrange": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "000000", | ||||
|         "A200": "000000", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "Brown": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "FFFFFF", | ||||
|         "400": "FFFFFF", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "FFFFFF", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "Gray": { | ||||
|         "50": "FFFFFF", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "000000", | ||||
|         "500": "000000", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "FFFFFF", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
|     "BlueGray": { | ||||
|         "50": "000000", | ||||
|         "100": "000000", | ||||
|         "200": "000000", | ||||
|         "300": "000000", | ||||
|         "400": "FFFFFF", | ||||
|         "500": "FFFFFF", | ||||
|         "600": "FFFFFF", | ||||
|         "700": "FFFFFF", | ||||
|         "800": "FFFFFF", | ||||
|         "900": "FFFFFF", | ||||
|         "A100": "FFFFFF", | ||||
|         "A200": "FFFFFF", | ||||
|         "A400": "FFFFFF", | ||||
|         "A700": "FFFFFF", | ||||
|     }, | ||||
| } | ||||
| """ | ||||
| Text colors generated from :data:`~light_colors`. "000000" for light and | ||||
| "FFFFFF" for dark. | ||||
| 
 | ||||
| How to generate text_colors dict | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|    text_colors = {} | ||||
|    for p in palette: | ||||
|        text_colors[p] = {} | ||||
|        for h in hue: | ||||
|            if h in light_colors[p]: | ||||
|                text_colors[p][h] = "000000" | ||||
|            else: | ||||
|                text_colors[p][h] = "FFFFFF" | ||||
| """ | ||||
| 
 | ||||
| theme_colors = [ | ||||
|     "Primary", | ||||
|     "Secondary", | ||||
|     "Background", | ||||
|     "Surface", | ||||
|     "Error", | ||||
|     "On_Primary", | ||||
|     "On_Secondary", | ||||
|     "On_Background", | ||||
|     "On_Surface", | ||||
|     "On_Error", | ||||
| ] | ||||
| """Valid theme colors.""" | ||||
							
								
								
									
										4
									
								
								sbapp/kivymd/effects/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | ||||
| """ | ||||
| Effects | ||||
| ======= | ||||
| """ | ||||
							
								
								
									
										1
									
								
								sbapp/kivymd/effects/fadingedge/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| from .fadingedge import FadingEdgeEffect | ||||
							
								
								
									
										197
									
								
								sbapp/kivymd/effects/fadingedge/fadingedge.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,197 @@ | ||||
| """ | ||||
| Effects/FadingEdgeEffect | ||||
| ======================== | ||||
| 
 | ||||
| .. versionadded:: 1.0.0 | ||||
| 
 | ||||
| The `FadingEdgeEffect` class implements a fade effect for `KivyMD` widgets: | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     from kivy.lang import Builder | ||||
|     from kivy.uix.scrollview import ScrollView | ||||
| 
 | ||||
|     from kivymd.app import MDApp | ||||
|     from kivymd.effects.fadingedge.fadingedge import FadingEdgeEffect | ||||
|     from kivymd.uix.list import OneLineListItem | ||||
| 
 | ||||
|     KV = ''' | ||||
|     MDScreen: | ||||
| 
 | ||||
|         FadeScrollView: | ||||
|             fade_height: self.height / 2 | ||||
|             fade_color: root.md_bg_color | ||||
| 
 | ||||
|             MDList: | ||||
|                 id: container | ||||
|     ''' | ||||
| 
 | ||||
| 
 | ||||
|     class FadeScrollView(FadingEdgeEffect, ScrollView): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
|     class Test(MDApp): | ||||
|         def build(self): | ||||
|             return Builder.load_string(KV) | ||||
| 
 | ||||
|         def on_start(self): | ||||
|             for i in range(20): | ||||
|                 self.root.ids.container.add_widget( | ||||
|                     OneLineListItem(text=f"Single-line item {i}") | ||||
|                 ) | ||||
| 
 | ||||
| 
 | ||||
|     Test().run() | ||||
| 
 | ||||
| .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fading-edge-effect-white.gif | ||||
|     :align: center | ||||
| 
 | ||||
| .. note:: Use the same color value for the fade_color parameter as for the | ||||
|     parent widget. | ||||
| """ | ||||
| 
 | ||||
| from typing import Union | ||||
| 
 | ||||
| from kivy.clock import Clock | ||||
| from kivy.graphics.context_instructions import Color | ||||
| from kivy.graphics.vertex_instructions import Rectangle | ||||
| from kivy.metrics import dp | ||||
| from kivy.properties import BooleanProperty, ColorProperty, NumericProperty | ||||
| 
 | ||||
| from kivymd.theming import ThemableBehavior | ||||
| 
 | ||||
| __all_ = ("FadingEdgeEffect",) | ||||
| 
 | ||||
| 
 | ||||
| class FadingEdgeEffect(ThemableBehavior): | ||||
|     """ | ||||
|     The class implements the fade effect. | ||||
| 
 | ||||
|     .. versionadded:: 1.0.0 | ||||
|     """ | ||||
| 
 | ||||
|     fade_color = ColorProperty(None) | ||||
|     """ | ||||
|     Fade color. | ||||
| 
 | ||||
|     :attr:`fade_color` is an :class:`~kivy.properties.ColorProperty` | ||||
|     and defaults to `None`. | ||||
|     """ | ||||
| 
 | ||||
|     fade_height = NumericProperty(0) | ||||
|     """ | ||||
|     Fade height. | ||||
| 
 | ||||
|     :attr:`fade_height` is an :class:`~kivy.properties.ColorProperty` | ||||
|     and defaults to `0`. | ||||
|     """ | ||||
| 
 | ||||
|     edge_top = BooleanProperty(True) | ||||
|     """ | ||||
|     Display fade edge top. | ||||
| 
 | ||||
|     :attr:`edge_top` is an :class:`~kivy.properties.BooleanProperty` | ||||
|     and defaults to `True`. | ||||
|     """ | ||||
| 
 | ||||
|     edge_bottom = BooleanProperty(True) | ||||
|     """ | ||||
|     Display fade edge bottom. | ||||
| 
 | ||||
|     :attr:`edge_bottom` is an :class:`~kivy.properties.BooleanProperty` | ||||
|     and defaults to `True`. | ||||
|     """ | ||||
| 
 | ||||
|     _height_segment = 10 | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         super().__init__(**kwargs) | ||||
|         Clock.schedule_once(self.set_fade) | ||||
| 
 | ||||
|     # TODO: Perhaps it would be better if we used a Shader for the fade effect. | ||||
|     #  But, I think the canvas instructions shouldn't affect performance | ||||
|     def set_fade(self, interval: Union[int, float]) -> None: | ||||
|         """Draws a bottom and top fade border on the canvas.""" | ||||
| 
 | ||||
|         fade_color = ( | ||||
|             self.theme_cls.primary_color | ||||
|             if not self.fade_color | ||||
|             else self.fade_color | ||||
|         ) | ||||
|         height_segment = ( | ||||
|             self.fade_height if self.fade_height else dp(100) | ||||
|         ) // self._height_segment | ||||
|         alpha = 1.1 | ||||
| 
 | ||||
|         with self.canvas: | ||||
|             for i in range(self._height_segment): | ||||
|                 alpha -= 0.1 | ||||
| 
 | ||||
|                 Color(rgba=(fade_color[:-1] + [round(alpha, 1)])) | ||||
|                 rectangle_top = ( | ||||
|                     Rectangle( | ||||
|                         pos=(self.x, self.height - (i * height_segment)), | ||||
|                         size=(self.width, height_segment), | ||||
|                     ) | ||||
|                     if self.edge_top | ||||
|                     else None | ||||
|                 ) | ||||
|                 rectangle_bottom = ( | ||||
|                     Rectangle( | ||||
|                         pos=(self.x, i * height_segment), | ||||
|                         size=(self.width, height_segment), | ||||
|                     ) | ||||
|                     if self.edge_bottom | ||||
|                     else None | ||||
|                 ) | ||||
|                 # How I hate lambda functions because of their length :( | ||||
|                 # But I don’t want to call the arguments by short, | ||||
|                 # incomprehensible names 'a', 'b', 'c'. | ||||
|                 self.bind( | ||||
|                     pos=lambda instance_fadind_edge_effect, window_size, rectangle_top=rectangle_top, rectangle_bottom=rectangle_bottom, index=i: self.update_canvas( | ||||
|                         instance_fadind_edge_effect, | ||||
|                         window_size, | ||||
|                         rectangle_top, | ||||
|                         rectangle_bottom, | ||||
|                         index, | ||||
|                     ), | ||||
|                     size=lambda instance_fadind_edge_effect, window_size, rectangle_top=rectangle_top, rectangle_bottom=rectangle_bottom, index=i: self.update_canvas( | ||||
|                         instance_fadind_edge_effect, | ||||
|                         window_size, | ||||
|                         rectangle_top, | ||||
|                         rectangle_bottom, | ||||
|                         index, | ||||
|                     ), | ||||
|                 ) | ||||
| 
 | ||||
|     def update_canvas( | ||||
|         self, | ||||
|         instance_fadind_edge_effect, | ||||
|         size: list[int, int], | ||||
|         rectangle_top: Rectangle, | ||||
|         rectangle_bottom: Rectangle, | ||||
|         index: int, | ||||
|     ) -> None: | ||||
|         """ | ||||
|         Updates the position and size of the fade border on the canvas. | ||||
|         Called when the application screen is resized. | ||||
|         """ | ||||
| 
 | ||||
|         height_segment = ( | ||||
|             self.fade_height if self.fade_height else dp(100) | ||||
|         ) // self._height_segment | ||||
| 
 | ||||
|         if rectangle_top: | ||||
|             rectangle_top.pos = ( | ||||
|                 instance_fadind_edge_effect.x, | ||||
|                 size[1] | ||||
|                 - (index * height_segment - instance_fadind_edge_effect.y), | ||||
|             ) | ||||
|             rectangle_top.size = (size[0], height_segment) | ||||
|         if rectangle_bottom: | ||||
|             rectangle_bottom.pos = ( | ||||
|                 instance_fadind_edge_effect.x, | ||||
|                 index * height_segment + instance_fadind_edge_effect.y, | ||||
|             ) | ||||
|             rectangle_bottom.size = (size[0], height_segment) | ||||
							
								
								
									
										19
									
								
								sbapp/kivymd/effects/roulettescroll/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,19 @@ | ||||
| Copyright (c) 2010-2021 Kivy Team and other contributors | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
							
								
								
									
										58
									
								
								sbapp/kivymd/effects/roulettescroll/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,58 @@ | ||||
| RouletteScrollEffect | ||||
| =================== | ||||
| 
 | ||||
| This is a subclass of `kivy.effects.ScrollEffect` that simulates the | ||||
| motion of a roulette, or a notched wheel (think Wheel of Fortune). It is | ||||
| primarily designed for emulating the effect of the iOS and android date pickers. | ||||
| 
 | ||||
| Usage | ||||
| ----- | ||||
| 
 | ||||
| Here's an example of using `RouletteScrollEffect` for a `kivy.uix.scrollview.ScrollView`: | ||||
| 
 | ||||
| ```python | ||||
|     from kivy.uix.gridlayout import GridLayout | ||||
|     from kivy.uix.button import Button | ||||
|     from kivy.uix.scrollview import ScrollView | ||||
| 
 | ||||
|     # Preparing a `GridLayout` inside a `ScrollView`. | ||||
|     layout = GridLayout(cols=1, padding=10, size_hint=(None, None), width=500) | ||||
|     layout.bind(minimum_height=layout.setter('height')) | ||||
| 
 | ||||
|     for i in range(30): | ||||
|         btn = Button(text=str(i), size=(480, 40), size_hint=(None, None)) | ||||
|         layout.add_widget(btn) | ||||
| 
 | ||||
|     root = ScrollView( | ||||
|         size_hint=(None, None), | ||||
|         size=(500, 320), | ||||
|         pos_hint={'center_x': .5, 'center_y': .5}, | ||||
|         do_scroll_x=False, | ||||
|         ) | ||||
|     root.add_widget(layout) | ||||
| 
 | ||||
|     # Preparation complete. Now add the new scroll effect. | ||||
|     root.effect_y = RouletteScrollEffect(anchor=20, interval=40) | ||||
|     runTouchApp(root) | ||||
| ``` | ||||
| 
 | ||||
| Here the `ScrollView` scrolls through a series of buttons with height `40`. We then attached a `RouletteScrollEffect` with interval 40, | ||||
| corresponding to the button heights. This allows the scrolling to stop at | ||||
| the same offset no matter where it stops. The `RouletteScrollEffect.anchor` | ||||
| adjusts this offset. | ||||
| 
 | ||||
| Customizations | ||||
| -------------- | ||||
| 
 | ||||
| Other settings that can be played with include: | ||||
| 
 | ||||
| - `RouletteScrollEffect.pull_duration` | ||||
| - `RouletteScrollEffect.coasting_alpha` | ||||
| - `RouletteScrollEffect.pull_back_velocity` | ||||
| - `RouletteScrollEffect.terminal_velocity` | ||||
| 
 | ||||
| See their module documentations for details. | ||||
| 
 | ||||
| `RouletteScrollEffect` has one event ``on_coasted_to_stop`` that | ||||
| is fired when the roulette stops, "making a selection". It can be listened to | ||||
| for handling or cleaning up choice making. | ||||
							
								
								
									
										1
									
								
								sbapp/kivymd/effects/roulettescroll/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| from .roulettescroll import RouletteScrollEffect | ||||
							
								
								
									
										251
									
								
								sbapp/kivymd/effects/roulettescroll/roulettescroll.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,251 @@ | ||||
| """ | ||||
| Effects/RouletteScrollEffect | ||||
| ============================ | ||||
| 
 | ||||
| This is a subclass of :class:`kivy.effects.ScrollEffect` that simulates the | ||||
| motion of a roulette, or a notched wheel (think Wheel of Fortune). It is | ||||
| primarily designed for emulating the effect of the iOS and android date pickers. | ||||
| 
 | ||||
| Usage | ||||
| ----- | ||||
| 
 | ||||
| Here's an example of using :class:`RouletteScrollEffect` for a | ||||
| :class:`kivy.uix.scrollview.ScrollView`: | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     from kivy.uix.gridlayout import GridLayout | ||||
|     from kivy.uix.button import Button | ||||
|     from kivy.uix.scrollview import ScrollView | ||||
| 
 | ||||
|     # Preparing a `GridLayout` inside a `ScrollView`. | ||||
|     layout = GridLayout(cols=1, padding=10, size_hint=(None, None), width=500) | ||||
|     layout.bind(minimum_height=layout.setter('height')) | ||||
| 
 | ||||
|     for i in range(30): | ||||
|         btn = Button(text=str(i), size=(480, 40), size_hint=(None, None)) | ||||
|         layout.add_widget(btn) | ||||
| 
 | ||||
|     root = ScrollView( | ||||
|         size_hint=(None, None), | ||||
|         size=(500, 320), | ||||
|         pos_hint={'center_x': .5, 'center_y': .5}, | ||||
|         do_scroll_x=False, | ||||
|         ) | ||||
|     root.add_widget(layout) | ||||
| 
 | ||||
|     # Preparation complete. Now add the new scroll effect. | ||||
|     root.effect_y = RouletteScrollEffect(anchor=20, interval=40) | ||||
|     runTouchApp(root) | ||||
| 
 | ||||
| Here the :class:`ScrollView` scrolls through a series of buttons with height | ||||
| 40. We then attached a :class:`RouletteScrollEffect` with interval 40, | ||||
| corresponding to the button heights. This allows the scrolling to stop at | ||||
| the same offset no matter where it stops. The :attr:`RouletteScrollEffect.anchor` | ||||
| adjusts this offset. | ||||
| 
 | ||||
| Customizations | ||||
| -------------- | ||||
| 
 | ||||
| Other settings that can be played with include: | ||||
| 
 | ||||
| :attr:`RouletteScrollEffect.pull_duration`, | ||||
| :attr:`RouletteScrollEffect.coasting_alpha`, | ||||
| :attr:`RouletteScrollEffect.pull_back_velocity`, and | ||||
| :attr:`RouletteScrollEffect.terminal_velocity`. | ||||
| 
 | ||||
| See their module documentations for details. | ||||
| 
 | ||||
| :class:`RouletteScrollEffect` has one event ``on_coasted_to_stop`` that | ||||
| is fired when the roulette stops, "making a selection". It can be listened to | ||||
| for handling or cleaning up choice making. | ||||
| """ | ||||
| 
 | ||||
| from math import ceil, exp, floor | ||||
| 
 | ||||
| from kivy.animation import Animation | ||||
| from kivy.effects.scroll import ScrollEffect | ||||
| from kivy.properties import AliasProperty, NumericProperty, ObjectProperty | ||||
| 
 | ||||
| __all_ = ("RouletteScrollEffect",) | ||||
| 
 | ||||
| 
 | ||||
| class RouletteScrollEffect(ScrollEffect): | ||||
|     """ | ||||
|     This is a subclass of :class:`kivy.effects.ScrollEffect` that simulates the | ||||
|     motion of a roulette, or a notched wheel (think Wheel of Fortune). It is | ||||
|     primarily designed for emulating the effect of the iOS and android date pickers. | ||||
| 
 | ||||
|     .. versionadded:: 0.104.2 | ||||
|     """ | ||||
| 
 | ||||
|     __events__ = ("on_coasted_to_stop",) | ||||
| 
 | ||||
|     drag_threshold = NumericProperty(0) | ||||
|     """ | ||||
|     Overrides :attr:`ScrollEffect.drag_threshold` to abolish drag threshold. | ||||
| 
 | ||||
|     .. note:: | ||||
|         If using this with a :class:`Roulette` or other :class:`Tickline` | ||||
|         subclasses, what matters is :attr:`Tickline.drag_threshold`, which | ||||
|         is passed to this attribute in the end. | ||||
| 
 | ||||
|     :attr:`drag_threshold` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0`. | ||||
|     """ | ||||
| 
 | ||||
|     min = NumericProperty(-float("inf")) | ||||
|     max = NumericProperty(float("inf")) | ||||
| 
 | ||||
|     interval = NumericProperty(50) | ||||
|     """ | ||||
|     The interval of the values of the "roulette". | ||||
| 
 | ||||
|     :attr:`interval` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `50`. | ||||
|     """ | ||||
| 
 | ||||
|     anchor = NumericProperty(0) | ||||
|     """ | ||||
|     One of the valid stopping values. | ||||
| 
 | ||||
|     :attr:`anchor` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0`. | ||||
|     """ | ||||
| 
 | ||||
|     pull_duration = NumericProperty(0.2) | ||||
|     """ | ||||
|     When movement slows around a stopping value, an animation is used | ||||
|     to pull it toward the nearest value. :attr:`pull_duration` is the duration | ||||
|     used for such an animation. | ||||
| 
 | ||||
|     :attr:`pull_duration` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0.2`. | ||||
|     """ | ||||
| 
 | ||||
|     coasting_alpha = NumericProperty(0.5) | ||||
|     """ | ||||
|     When within :attr:`coasting_alpha` * :attr:`interval` of the | ||||
|     next notch and velocity is below :attr:`terminal_velocity`, | ||||
|     coasting begins and will end on the next notch. | ||||
| 
 | ||||
|     :attr:`coasting_alpha` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0.5`. | ||||
|     """ | ||||
| 
 | ||||
|     pull_back_velocity = NumericProperty("50sp") | ||||
|     """ | ||||
|     The velocity below which the scroll value will be drawn to the | ||||
|     *nearest* notch instead of the *next* notch in the direction travelled. | ||||
| 
 | ||||
|     :attr:`pull_back_velocity` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `50sp`. | ||||
|     """ | ||||
| 
 | ||||
|     _anim = ObjectProperty(None) | ||||
| 
 | ||||
|     def get_term_vel(self): | ||||
|         return ( | ||||
|             exp(self.friction) | ||||
|             * self.interval | ||||
|             * self.coasting_alpha | ||||
|             / self.pull_duration | ||||
|         ) | ||||
| 
 | ||||
|     def set_term_vel(self, val): | ||||
|         self.pull_duration = ( | ||||
|             exp(self.friction) * self.interval * self.coasting_alpha / val | ||||
|         ) | ||||
| 
 | ||||
|     terminal_velocity = AliasProperty( | ||||
|         get_term_vel, | ||||
|         set_term_vel, | ||||
|         bind=["interval", "coasting_alpha", "pull_duration", "friction"], | ||||
|         cache=True, | ||||
|     ) | ||||
|     """ | ||||
|     If velocity falls between :attr:`pull_back_velocity` and | ||||
|     :attr:`terminal velocity` then the movement will start to coast | ||||
|     to the next coming stopping value. | ||||
| 
 | ||||
|     :attr:`terminal_velocity` is computed from a set formula given | ||||
|     :attr:`interval`, :attr:`coasting_alpha`, :attr:`pull_duration`, | ||||
|     and :attr:`friction`. Setting :attr:`terminal_velocity` has the | ||||
|     effect of setting :attr:`pull_duration`. | ||||
|     """ | ||||
| 
 | ||||
|     def start(self, val, t=None): | ||||
|         if self._anim: | ||||
|             self._anim.stop(self) | ||||
|         return ScrollEffect.start(self, val, t=t) | ||||
| 
 | ||||
|     def on_notch(self, *args): | ||||
|         return (self.scroll - self.anchor) % self.interval == 0 | ||||
| 
 | ||||
|     def nearest_notch(self, *args): | ||||
|         interval = float(self.interval) | ||||
|         anchor = self.anchor | ||||
|         n = round((self.scroll - anchor) / interval) | ||||
|         return anchor + n * interval | ||||
| 
 | ||||
|     def next_notch(self, *args): | ||||
|         interval = float(self.interval) | ||||
|         anchor = self.anchor | ||||
|         round_ = ceil if self.velocity > 0 else floor | ||||
|         n = round_((self.scroll - anchor) / interval) | ||||
|         return anchor + n * interval | ||||
| 
 | ||||
|     def near_notch(self, d=0.01): | ||||
|         nearest = self.nearest_notch() | ||||
|         if abs((nearest - self.scroll) / self.interval) % 1 < d: | ||||
|             return nearest | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     def near_next_notch(self, d=None): | ||||
|         d = d or self.coasting_alpha | ||||
|         next_ = self.next_notch() | ||||
|         if abs((next_ - self.scroll) / self.interval) % 1 < d: | ||||
|             return next_ | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     def update_velocity(self, dt): | ||||
|         if self.is_manual: | ||||
|             return | ||||
|         velocity = self.velocity | ||||
|         t_velocity = self.terminal_velocity | ||||
|         next_ = self.near_next_notch() | ||||
|         pull_back_velocity = self.pull_back_velocity | ||||
|         if pull_back_velocity < abs(velocity) < t_velocity and next_: | ||||
|             duration = abs((next_ - self.scroll) / self.velocity) | ||||
|             anim = Animation( | ||||
|                 scroll=next_, | ||||
|                 duration=duration, | ||||
|             ) | ||||
|             self._anim = anim | ||||
|             anim.on_complete = self._coasted_to_stop | ||||
|             anim.start(self) | ||||
|             return | ||||
|         if abs(velocity) < pull_back_velocity and not self.on_notch(): | ||||
|             anim = Animation( | ||||
|                 scroll=self.nearest_notch(), | ||||
|                 duration=self.pull_duration, | ||||
|                 t="in_out_circ", | ||||
|             ) | ||||
|             self._anim = anim | ||||
|             anim.on_complete = self._coasted_to_stop | ||||
|             anim.start(self) | ||||
|         else: | ||||
|             self.velocity -= self.velocity * self.friction | ||||
|             self.apply_distance(self.velocity * dt) | ||||
|             self.trigger_velocity_update() | ||||
| 
 | ||||
|     def on_coasted_to_stop(self, *args): | ||||
|         """ | ||||
|         This event fires when the roulette has stopped, `making a selection`. | ||||
|         """ | ||||
| 
 | ||||
|     def _coasted_to_stop(self, *args): | ||||
|         self.velocity = 0 | ||||
|         self.dispatch("on_coasted_to_stop") | ||||
							
								
								
									
										20
									
								
								sbapp/kivymd/effects/stiffscroll/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | ||||
| The MIT License (MIT) | ||||
| 
 | ||||
| Copyright (c) 2014 LogicalDash | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
| the Software without restriction, including without limitation the rights to | ||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||||
| the Software, and to permit persons to whom the Software is furnished to do so, | ||||
| subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										24
									
								
								sbapp/kivymd/effects/stiffscroll/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | ||||
| stiffscroll | ||||
| =========== | ||||
| 
 | ||||
| A ScrollEffect for use with a Kivy ScrollView. It makes scrolling more | ||||
| laborious as you reach the edge of the scrollable area. | ||||
| 
 | ||||
| A ScrollView constructed with StiffScrollEffect, | ||||
| eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to | ||||
| scroll as you get nearer to its edges. You can scroll all the way to | ||||
| the edge if you want to, but it will take more finger-movement than | ||||
| usual. | ||||
| 
 | ||||
| Unlike DampedScrollEffect, it is impossible to overscroll with | ||||
| StiffScrollEffect. That means you cannot push the contents of the | ||||
| ScrollView far enough to see what's beneath them. This is appropriate | ||||
| if the ScrollView contains, eg., a background image, like a desktop | ||||
| wallpaper. Overscrolling may give the impression that there is some | ||||
| reason to overscroll, even if just to take a peek beneath, and that | ||||
| impression may be misleading. | ||||
| 
 | ||||
| StiffScrollEffect was written by Zachary Spector. His other stuff is at: | ||||
| https://github.com/LogicalDash/ | ||||
| He can be reached, and possibly hired, at: | ||||
| zacharyspector@gmail.com | ||||
							
								
								
									
										1
									
								
								sbapp/kivymd/effects/stiffscroll/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| from .stiffscroll import StiffScrollEffect | ||||
							
								
								
									
										215
									
								
								sbapp/kivymd/effects/stiffscroll/stiffscroll.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,215 @@ | ||||
| """ | ||||
| Effects/StiffScrollEffect | ||||
| ========================= | ||||
| 
 | ||||
| An Effect to be used with ScrollView to prevent scrolling beyond | ||||
| the bounds, but politely. | ||||
| 
 | ||||
| A ScrollView constructed with StiffScrollEffect, | ||||
| eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to | ||||
| scroll as you get nearer to its edges. You can scroll all the way to | ||||
| the edge if you want to, but it will take more finger-movement than | ||||
| usual. | ||||
| 
 | ||||
| Unlike DampedScrollEffect, it is impossible to overscroll with | ||||
| StiffScrollEffect. That means you cannot push the contents of the | ||||
| ScrollView far enough to see what's beneath them. This is appropriate | ||||
| if the ScrollView contains, eg., a background image, like a desktop | ||||
| wallpaper. Overscrolling may give the impression that there is some | ||||
| reason to overscroll, even if just to take a peek beneath, and that | ||||
| impression may be misleading. | ||||
| 
 | ||||
| StiffScrollEffect was written by Zachary Spector. His other stuff is at: | ||||
| https://github.com/LogicalDash/ | ||||
| He can be reached, and possibly hired, at: | ||||
| zacharyspector@gmail.com | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| from time import time | ||||
| 
 | ||||
| from kivy.animation import AnimationTransition | ||||
| from kivy.effects.kinetic import KineticEffect | ||||
| from kivy.properties import NumericProperty, ObjectProperty | ||||
| from kivy.uix.widget import Widget | ||||
| 
 | ||||
| 
 | ||||
| class StiffScrollEffect(KineticEffect): | ||||
|     drag_threshold = NumericProperty("20sp") | ||||
|     """Minimum distance to travel before the movement is considered as a | ||||
|     drag. | ||||
| 
 | ||||
|     :attr:`drag_threshold` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `'20sp'`. | ||||
|     """ | ||||
| 
 | ||||
|     min = NumericProperty(0) | ||||
|     """Minimum boundary to stop the scrolling at. | ||||
| 
 | ||||
|     :attr:`min` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0`. | ||||
|     """ | ||||
| 
 | ||||
|     max = NumericProperty(0) | ||||
|     """Maximum boundary to stop the scrolling at. | ||||
| 
 | ||||
|     :attr:`max` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0`. | ||||
|     """ | ||||
| 
 | ||||
|     max_friction = NumericProperty(1) | ||||
|     """How hard should it be to scroll, at the worst? | ||||
| 
 | ||||
|     :attr:`max_friction` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `1`. | ||||
|     """ | ||||
| 
 | ||||
|     body = NumericProperty(0.7) | ||||
|     """Proportion of the range in which you can scroll unimpeded. | ||||
| 
 | ||||
|     :attr:`body` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0.7`. | ||||
|     """ | ||||
| 
 | ||||
|     scroll = NumericProperty(0.0) | ||||
|     """Computed value for scrolling | ||||
| 
 | ||||
|     :attr:`scroll` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0.0`. | ||||
|     """ | ||||
| 
 | ||||
|     transition_min = ObjectProperty(AnimationTransition.in_cubic) | ||||
|     """The AnimationTransition function to use when adjusting the friction | ||||
|     near the minimum end of the effect. | ||||
| 
 | ||||
|     :attr:`transition_min` is an :class:`~kivy.properties.ObjectProperty` | ||||
|     and defaults to :class:`kivy.animation.AnimationTransition`. | ||||
|     """ | ||||
| 
 | ||||
|     transition_max = ObjectProperty(AnimationTransition.in_cubic) | ||||
|     """The AnimationTransition function to use when adjusting the friction | ||||
|     near the maximum end of the effect. | ||||
| 
 | ||||
|     :attr:`transition_max` is an :class:`~kivy.properties.ObjectProperty` | ||||
|     and defaults to :class:`kivy.animation.AnimationTransition`. | ||||
|     """ | ||||
| 
 | ||||
|     target_widget = ObjectProperty(None, allownone=True, baseclass=Widget) | ||||
|     """The widget to apply the effect to. | ||||
| 
 | ||||
|     :attr:`target_widget` is an :class:`~kivy.properties.ObjectProperty` | ||||
|     and defaults to ``None``. | ||||
|     """ | ||||
| 
 | ||||
|     displacement = NumericProperty(0) | ||||
|     """The absolute distance moved in either direction. | ||||
| 
 | ||||
|     :attr:`displacement` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `0`. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         """Set ``self.base_friction`` to the value of ``self.friction`` just | ||||
|         after instantiation, so that I can reset to that value later. | ||||
|         """ | ||||
| 
 | ||||
|         super().__init__(**kwargs) | ||||
|         self.base_friction = self.friction | ||||
| 
 | ||||
|     def update_velocity(self, dt): | ||||
|         """Before actually updating my velocity, meddle with ``self.friction`` | ||||
|         to make it appropriate to where I'm at, currently. | ||||
|         """ | ||||
| 
 | ||||
|         hard_min = self.min | ||||
|         hard_max = self.max | ||||
|         if hard_min > hard_max: | ||||
|             hard_min, hard_max = hard_max, hard_min | ||||
| 
 | ||||
|         margin = (1.0 - self.body) * (hard_max - hard_min) | ||||
|         soft_min = hard_min + margin | ||||
|         soft_max = hard_max - margin | ||||
| 
 | ||||
|         if self.value < soft_min: | ||||
|             try: | ||||
|                 prop = (soft_min - self.value) / (soft_min - hard_min) | ||||
|                 self.friction = self.base_friction + abs( | ||||
|                     self.max_friction - self.base_friction | ||||
|                 ) * self.transition_min(prop) | ||||
|             except ZeroDivisionError: | ||||
|                 pass | ||||
|         elif self.value > soft_max: | ||||
|             try: | ||||
|                 # normalize how far past soft_max I've gone as a | ||||
|                 # proportion of the distance between soft_max and hard_max | ||||
|                 prop = (self.value - soft_max) / (hard_max - soft_max) | ||||
|                 self.friction = self.base_friction + abs( | ||||
|                     self.max_friction - self.base_friction | ||||
|                 ) * self.transition_min(prop) | ||||
|             except ZeroDivisionError: | ||||
|                 pass | ||||
|         else: | ||||
|             self.friction = self.base_friction | ||||
| 
 | ||||
|         return super().update_velocity(dt) | ||||
| 
 | ||||
|     def on_value(self, *args): | ||||
|         """Prevent moving beyond my bounds, and update ``self.scroll``""" | ||||
| 
 | ||||
|         if self.value > self.min: | ||||
|             self.velocity = 0 | ||||
|             self.scroll = self.min | ||||
|         elif self.value < self.max: | ||||
|             self.velocity = 0 | ||||
|             self.scroll = self.max | ||||
|         else: | ||||
|             self.scroll = self.value | ||||
| 
 | ||||
|     def start(self, val, t=None): | ||||
|         """Start movement with ``self.friction`` = ``self.base_friction``""" | ||||
| 
 | ||||
|         self.is_manual = True | ||||
|         t = t or time() | ||||
|         self.velocity = self.displacement = 0 | ||||
|         self.friction = self.base_friction | ||||
|         self.history = [(t, val)] | ||||
| 
 | ||||
|     def update(self, val, t=None): | ||||
|         """Reduce the impact of whatever change has been made to me, in | ||||
|         proportion with my current friction. | ||||
|         """ | ||||
| 
 | ||||
|         t = t or time() | ||||
|         hard_min = self.min | ||||
|         hard_max = self.max | ||||
|         if hard_min > hard_max: | ||||
|             hard_min, hard_max = hard_max, hard_min | ||||
| 
 | ||||
|         gamut = hard_max - hard_min | ||||
|         margin = (1.0 - self.body) * gamut | ||||
|         soft_min = hard_min + margin | ||||
|         soft_max = hard_max - margin | ||||
|         distance = val - self.history[-1][1] | ||||
|         reach = distance + self.value | ||||
| 
 | ||||
|         if (distance < 0 and reach < soft_min) or ( | ||||
|             distance > 0 and soft_max < reach | ||||
|         ): | ||||
|             distance -= distance * self.friction | ||||
|         self.apply_distance(distance) | ||||
|         self.history.append((t, val)) | ||||
| 
 | ||||
|         if len(self.history) > self.max_history: | ||||
|             self.history.pop(0) | ||||
|         self.displacement += abs(distance) | ||||
|         self.trigger_velocity_update() | ||||
| 
 | ||||
|     def stop(self, val, t=None): | ||||
|         """Work out whether I've been flung.""" | ||||
| 
 | ||||
|         self.is_manual = False | ||||
|         self.displacement += abs(val - self.history[-1][1]) | ||||
|         if self.displacement <= self.drag_threshold: | ||||
|             self.velocity = 0 | ||||
| 
 | ||||
|         return super().stop(val, t) | ||||
							
								
								
									
										110
									
								
								sbapp/kivymd/factory_registers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,110 @@ | ||||
| """ | ||||
| Register KivyMD widgets to use without import. | ||||
| """ | ||||
| 
 | ||||
| from kivy.factory import Factory | ||||
| 
 | ||||
| register = Factory.register | ||||
| register("MDSegmentedControl", module="kivymd.uix.segmentedcontrol") | ||||
| register("MDSegmentedControlItem", module="kivymd.uix.segmentedcontrol") | ||||
| register("MDSliverAppbar", module="kivymd.uix.sliverappbar") | ||||
| register("MDSliverAppbarContent", module="kivymd.uix.sliverappbar") | ||||
| register("MDSliverAppbarHeader", module="kivymd.uix.sliverappbar") | ||||
| register("MDNavigationRail", module="kivymd.uix.navigationrail") | ||||
| register("MDNavigationRailFabButton", module="kivymd.uix.navigationrail") | ||||
| register("MDNavigationRailMenuButton", module="kivymd.uix.navigationrail") | ||||
| register("MDSwiper", module="kivymd.uix.swiper") | ||||
| register("MDCarousel", module="kivymd.uix.carousel") | ||||
| register("MDWidget", module="kivymd.uix.widget") | ||||
| register("MDFloatLayout", module="kivymd.uix.floatlayout") | ||||
| register("MDAnchorLayout", module="kivymd.uix.anchorlayout") | ||||
| register("MDScreen", module="kivymd.uix.screen") | ||||
| register("MDScreenManager", module="kivymd.uix.screenmanager") | ||||
| register("MDRecycleGridLayout", module="kivymd.uix.recyclegridlayout") | ||||
| register("MDBoxLayout", module="kivymd.uix.boxlayout") | ||||
| register("MDRelativeLayout", module="kivymd.uix.relativelayout") | ||||
| register("MDGridLayout", module="kivymd.uix.gridlayout") | ||||
| register("MDStackLayout", module="kivymd.uix.stacklayout") | ||||
| register("MDExpansionPanel", module="kivymd.uix.expansionpanel") | ||||
| register("MDExpansionPanelOneLine", module="kivymd.uix.expansionpanel") | ||||
| register("MDExpansionPanelTwoLine", module="kivymd.uix.expansionpanel") | ||||
| register("MDExpansionPanelThreeLine", module="kivymd.uix.expansionpanel") | ||||
| register("FitImage", module="kivymd.utils.fitimage") | ||||
| register("MDBackdrop", module="kivymd.uix.backdrop") | ||||
| register("MDBanner", module="kivymd.uix.banner") | ||||
| register("MDTooltip", module="kivymd.uix.tooltip") | ||||
| register("MDBottomNavigation", module="kivymd.uix.bottomnavigation") | ||||
| register("MDBottomNavigationItem", module="kivymd.uix.bottomnavigation") | ||||
| register("MDToggleButton", module="kivymd.uix.behaviors.toggle_behavior") | ||||
| register("MDFloatingActionButtonSpeedDial", module="kivymd.uix.button") | ||||
| register("MDIconButton", module="kivymd.uix.button") | ||||
| register("MDRoundImageButton", module="kivymd.uix.button") | ||||
| register("MDFlatButton", module="kivymd.uix.button") | ||||
| register("MDRaisedButton", module="kivymd.uix.button") | ||||
| register("MDFloatingActionButton", module="kivymd.uix.button") | ||||
| register("MDRectangleFlatButton", module="kivymd.uix.button") | ||||
| register("MDTextButton", module="kivymd.uix.button") | ||||
| register("MDCustomRoundIconButton", module="kivymd.uix.button") | ||||
| register("MDRoundFlatButton", module="kivymd.uix.button") | ||||
| register("MDFillRoundFlatButton", module="kivymd.uix.button") | ||||
| register("MDRectangleFlatIconButton", module="kivymd.uix.button") | ||||
| register("MDRoundFlatIconButton", module="kivymd.uix.button") | ||||
| register("MDFillRoundFlatIconButton", module="kivymd.uix.button") | ||||
| register("MDCard", module="kivymd.uix.card") | ||||
| register("MDSeparator", module="kivymd.uix.card") | ||||
| register("MDSelectionList", module="kivymd.uix.selection") | ||||
| register("MDChip", module="kivymd.uix.chip") | ||||
| register("MDChooseChip", module="kivymd.uix.chip") | ||||
| register("MDSmartTile", module="kivymd.uix.imagelist") | ||||
| register("SmartTileWithLabel", module="kivymd.uix.imagelist") | ||||
| register("SmartTileWithStar", module="kivymd.uix.imagelist") | ||||
| register("MDLabel", module="kivymd.uix.label") | ||||
| register("MDIcon", module="kivymd.uix.label") | ||||
| register("MDList", module="kivymd.uix.list") | ||||
| register("ILeftBody", module="kivymd.uix.list") | ||||
| register("ILeftBodyTouch", module="kivymd.uix.list") | ||||
| register("IRightBody", module="kivymd.uix.list") | ||||
| register("IRightBodyTouch", module="kivymd.uix.list") | ||||
| register("ContainerSupport", module="kivymd.uix.list") | ||||
| register("OneLineListItem", module="kivymd.uix.list") | ||||
| register("TwoLineListItem", module="kivymd.uix.list") | ||||
| register("ThreeLineListItem", module="kivymd.uix.list") | ||||
| register("OneLineAvatarListItem", module="kivymd.uix.list") | ||||
| register("TwoLineAvatarListItem", module="kivymd.uix.list") | ||||
| register("ThreeLineAvatarListItem", module="kivymd.uix.list") | ||||
| register("OneLineIconListItem", module="kivymd.uix.list") | ||||
| register("TwoLineIconListItem", module="kivymd.uix.list") | ||||
| register("ThreeLineIconListItem", module="kivymd.uix.list") | ||||
| register("OneLineRightIconListItem", module="kivymd.uix.list") | ||||
| register("TwoLineRightIconListItem", module="kivymd.uix.list") | ||||
| register("ThreeLineRightIconListItem", module="kivymd.uix.list") | ||||
| register("OneLineAvatarIconListItem", module="kivymd.uix.list") | ||||
| register("TwoLineAvatarIconListItem", module="kivymd.uix.list") | ||||
| register("ThreeLineAvatarIconListItem", module="kivymd.uix.list") | ||||
| register("HoverBehavior", module="kivymd.uix.behaviors.hover_behavior") | ||||
| register("FocusBehavior", module="kivymd.uix.behaviors.focus_behavior") | ||||
| register("MagicBehavior", module="kivymd.uix.behaviors.magic_behavior") | ||||
| register("MDNavigationDrawer", module="kivymd.uix.navigationdrawer") | ||||
| register("MDNavigationLayout", module="kivymd.uix.navigationdrawer") | ||||
| register("MDNavigationDrawerMenu", module="kivymd.uix.navigationdrawer") | ||||
| register("MDNavigationDrawerHeader", module="kivymd.uix.navigationdrawer") | ||||
| register("MDNavigationDrawerItem", module="kivymd.uix.navigationdrawer") | ||||
| register("MDNavigationDrawerLabel", module="kivymd.uix.navigationdrawer") | ||||
| register("MDNavigationDrawerDivider", module="kivymd.uix.navigationdrawer") | ||||
| register("MDProgressBar", module="kivymd.uix.progressbar") | ||||
| register("MDScrollViewRefreshLayout", module="kivymd.uix.refreshlayout") | ||||
| register("MDCheckbox", module="kivymd.uix.selectioncontrol") | ||||
| register("MDSwitch", module="kivymd.uix.selectioncontrol") | ||||
| register("MDSlider", module="kivymd.uix.slider") | ||||
| register("MDSpinner", module="kivymd.uix.spinner") | ||||
| register("MDTabs", module="kivymd.uix.tab") | ||||
| register("MDTextField", module="kivymd.uix.textfield") | ||||
| register("MDTextFieldRound", module="kivymd.uix.textfield") | ||||
| register("MDTextFieldRect", module="kivymd.uix.textfield") | ||||
| register("MDToolbar", module="kivymd.uix.toolbar") | ||||
| register("MDTopAppBar", module="kivymd.uix.toolbar") | ||||
| register("MDBottomAppBar", module="kivymd.uix.toolbar") | ||||
| register("MDDropDownItem", module="kivymd.uix.dropdownitem") | ||||
| register("MDCircularLayout", module="kivymd.uix.circularlayout") | ||||
| register("MDHeroFrom", module="kivymd.uix.hero") | ||||
| register("MDHeroTo", module="kivymd.uix.hero") | ||||
							
								
								
									
										69
									
								
								sbapp/kivymd/font_definitions.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,69 @@ | ||||
| """ | ||||
| Themes/Font Definitions | ||||
| ======================= | ||||
| 
 | ||||
| .. seealso:: | ||||
| 
 | ||||
|    `Material Design spec, The type system <https://material.io/design/typography/the-type-system.html>`_ | ||||
| """ | ||||
| 
 | ||||
| from kivy.core.text import LabelBase | ||||
| 
 | ||||
| from kivymd import fonts_path | ||||
| 
 | ||||
| fonts = [ | ||||
|     { | ||||
|         "name": "Roboto", | ||||
|         "fn_regular": fonts_path + "Roboto-Regular.ttf", | ||||
|         "fn_bold": fonts_path + "Roboto-Bold.ttf", | ||||
|         "fn_italic": fonts_path + "Roboto-Italic.ttf", | ||||
|         "fn_bolditalic": fonts_path + "Roboto-BoldItalic.ttf", | ||||
|     }, | ||||
|     { | ||||
|         "name": "RobotoThin", | ||||
|         "fn_regular": fonts_path + "Roboto-Thin.ttf", | ||||
|         "fn_italic": fonts_path + "Roboto-ThinItalic.ttf", | ||||
|     }, | ||||
|     { | ||||
|         "name": "RobotoLight", | ||||
|         "fn_regular": fonts_path + "Roboto-Light.ttf", | ||||
|         "fn_italic": fonts_path + "Roboto-LightItalic.ttf", | ||||
|     }, | ||||
|     { | ||||
|         "name": "RobotoMedium", | ||||
|         "fn_regular": fonts_path + "Roboto-Medium.ttf", | ||||
|         "fn_italic": fonts_path + "Roboto-MediumItalic.ttf", | ||||
|     }, | ||||
|     { | ||||
|         "name": "RobotoBlack", | ||||
|         "fn_regular": fonts_path + "Roboto-Black.ttf", | ||||
|         "fn_italic": fonts_path + "Roboto-BlackItalic.ttf", | ||||
|     }, | ||||
|     { | ||||
|         "name": "Icons", | ||||
|         "fn_regular": fonts_path + "materialdesignicons-webfont.ttf", | ||||
|     }, | ||||
| ] | ||||
| 
 | ||||
| for font in fonts: | ||||
|     LabelBase.register(**font) | ||||
| 
 | ||||
| theme_font_styles = [ | ||||
|     "H1", | ||||
|     "H2", | ||||
|     "H3", | ||||
|     "H4", | ||||
|     "H5", | ||||
|     "H6", | ||||
|     "Subtitle1", | ||||
|     "Subtitle2", | ||||
|     "Body1", | ||||
|     "Body2", | ||||
|     "Button", | ||||
|     "Caption", | ||||
|     "Overline", | ||||
|     "Icon", | ||||
| ] | ||||
| """ | ||||
| .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles-2.png | ||||
| """ | ||||
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-Black.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-BlackItalic.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-Bold.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-BoldItalic.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-Italic.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-Light.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-LightItalic.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-Medium.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-MediumItalic.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-Thin.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/Roboto-ThinItalic.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/fonts/materialdesignicons-webfont.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										7121
									
								
								sbapp/kivymd/icon_definitions.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/alpha_layer.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 553 B | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/black.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 147 B | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/blue.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 147 B | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/firebase-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/folder.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 29 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/green.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 147 B | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/quad_shadow-0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 31 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/quad_shadow-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 31 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/quad_shadow-2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 21 KiB | 
							
								
								
									
										1
									
								
								sbapp/kivymd/images/quad_shadow.atlas
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| {"quad_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "quad_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "quad_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}} | ||||
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/rec_shadow-0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 46 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/rec_shadow-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 43 KiB | 
							
								
								
									
										1
									
								
								sbapp/kivymd/images/rec_shadow.atlas
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| {"rec_shadow-1.png": {"20": [2, 266, 256, 128], "21": [260, 266, 256, 128], "22": [518, 266, 256, 128], "23": [776, 266, 256, 128], "3": [260, 136, 256, 128], "2": [2, 136, 256, 128], "5": [776, 136, 256, 128], "4": [518, 136, 256, 128], "7": [260, 6, 256, 128], "6": [2, 6, 256, 128], "9": [776, 6, 256, 128], "8": [518, 6, 256, 128]}, "rec_shadow-0.png": {"11": [518, 266, 256, 128], "10": [260, 266, 256, 128], "13": [2, 136, 256, 128], "12": [776, 266, 256, 128], "15": [518, 136, 256, 128], "14": [260, 136, 256, 128], "17": [2, 6, 256, 128], "16": [776, 136, 256, 128], "19": [518, 6, 256, 128], "18": [260, 6, 256, 128], "1": [776, 6, 256, 128], "0": [2, 266, 256, 128]}} | ||||
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/rec_st_shadow-0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 30 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/rec_st_shadow-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/rec_st_shadow-2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 28 KiB | 
							
								
								
									
										1
									
								
								sbapp/kivymd/images/rec_st_shadow.atlas
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| {"rec_st_shadow-0.png": {"11": [262, 138, 128, 256], "10": [132, 138, 128, 256], "13": [522, 138, 128, 256], "12": [392, 138, 128, 256], "15": [782, 138, 128, 256], "14": [652, 138, 128, 256], "16": [912, 138, 128, 256], "0": [2, 138, 128, 256]}, "rec_st_shadow-1.png": {"20": [522, 138, 128, 256], "21": [652, 138, 128, 256], "17": [2, 138, 128, 256], "23": [912, 138, 128, 256], "19": [262, 138, 128, 256], "18": [132, 138, 128, 256], "22": [782, 138, 128, 256], "1": [392, 138, 128, 256]}, "rec_st_shadow-2.png": {"3": [132, 138, 128, 256], "2": [2, 138, 128, 256], "5": [392, 138, 128, 256], "4": [262, 138, 128, 256], "7": [652, 138, 128, 256], "6": [522, 138, 128, 256], "9": [912, 138, 128, 256], "8": [782, 138, 128, 256]}} | ||||
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 147 B | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/restdb-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/round_shadow-0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 39 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/round_shadow-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 40 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/round_shadow-2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 26 KiB | 
							
								
								
									
										1
									
								
								sbapp/kivymd/images/round_shadow.atlas
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| {"round_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "round_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "round_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}} | ||||
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/transparent.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 156 B | 
							
								
								
									
										
											BIN
										
									
								
								sbapp/kivymd/images/yellow.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 147 B | 
							
								
								
									
										38
									
								
								sbapp/kivymd/material_resources.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,38 @@ | ||||
| """ | ||||
| Material Resources | ||||
| ================== | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| from kivy.core.window import Window | ||||
| from kivy.metrics import dp | ||||
| from kivy.utils import platform | ||||
| 
 | ||||
| if "KIVY_DOC_INCLUDE" in os.environ: | ||||
|     dp = lambda x: x  # NOQA: F811 | ||||
| 
 | ||||
| # Feel free to override this const if you're designing for a device such as | ||||
| # a GNU/Linux tablet. | ||||
| DEVICE_IOS = platform == "ios" or platform == "macosx" | ||||
| if platform != "android" and platform != "ios": | ||||
|     DEVICE_TYPE = "desktop" | ||||
| elif Window.width >= dp(600) and Window.height >= dp(600): | ||||
|     DEVICE_TYPE = "tablet" | ||||
| else: | ||||
|     DEVICE_TYPE = "mobile" | ||||
| 
 | ||||
| if DEVICE_TYPE == "mobile": | ||||
|     MAX_NAV_DRAWER_WIDTH = dp(300) | ||||
|     HORIZ_MARGINS = dp(16) | ||||
|     STANDARD_INCREMENT = dp(56) | ||||
|     PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT | ||||
|     LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT - dp(8) | ||||
| else: | ||||
|     MAX_NAV_DRAWER_WIDTH = dp(400) | ||||
|     HORIZ_MARGINS = dp(24) | ||||
|     STANDARD_INCREMENT = dp(64) | ||||
|     PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT | ||||
|     LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT | ||||
| 
 | ||||
| TOUCH_TARGET_HEIGHT = dp(48) | ||||
							
								
								
									
										87
									
								
								sbapp/kivymd/tests/pyinstaller/test_pyinstaller_packaging.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,87 @@ | ||||
| """ | ||||
| PyInstaller freezing test | ||||
| ========================= | ||||
| 
 | ||||
| PyInstaller must package KivyMD apps correctly. | ||||
| """ | ||||
| 
 | ||||
| import subprocess | ||||
| 
 | ||||
| from PyInstaller import __main__ as pyi_main | ||||
| 
 | ||||
| 
 | ||||
| def test_datas(tmp_path) -> None: | ||||
|     """Test fonts and images.""" | ||||
| 
 | ||||
|     app_name = "userapp" | ||||
|     workpath = tmp_path / "build" | ||||
|     distpath = tmp_path / "dist" | ||||
|     app = tmp_path / (app_name + ".py") | ||||
|     app.write_text( | ||||
|         """ | ||||
| import os | ||||
| 
 | ||||
| from kivy.core.text import LabelBase | ||||
| 
 | ||||
| import kivymd | ||||
| 
 | ||||
| fonts = os.listdir(kivymd.fonts_path) | ||||
| print(fonts) | ||||
| assert "Roboto-Regular.ttf" in fonts | ||||
| assert "materialdesignicons-webfont.ttf" in fonts | ||||
| print(LabelBase._fonts.keys()) | ||||
| assert "Roboto" in LabelBase._fonts.keys()  # NOQA | ||||
| assert "Icons" in LabelBase._fonts.keys()  # NOQA | ||||
| 
 | ||||
| images = os.listdir(kivymd.images_path) | ||||
| print(images) | ||||
| assert "folder.png" in images | ||||
| assert "rec_shadow.atlas" in images | ||||
| """ | ||||
|     ) | ||||
|     pyi_main.run( | ||||
|         [ | ||||
|             "--workpath", | ||||
|             str(workpath), | ||||
|             "--distpath", | ||||
|             str(distpath), | ||||
|             "--specpath", | ||||
|             str(tmp_path), | ||||
|             str(app), | ||||
|         ] | ||||
|     ) | ||||
|     subprocess.run([str(distpath / app_name / app_name)], check=True) | ||||
| 
 | ||||
| 
 | ||||
| def test_widgets(tmp_path) -> None: | ||||
|     """Test that all widgets are accesible.""" | ||||
| 
 | ||||
|     app_name = "userapp" | ||||
|     workpath = tmp_path / "build" | ||||
|     distpath = tmp_path / "dist" | ||||
|     app = tmp_path / (app_name + ".py") | ||||
|     app.write_text( | ||||
|         """ | ||||
| import os | ||||
| 
 | ||||
| import kivymd  # NOQA | ||||
| __import__("kivymd.uix.label") | ||||
| __import__("kivymd.uix.button") | ||||
| __import__("kivymd.uix.list") | ||||
| __import__("kivymd.uix.navigationdrawer") | ||||
| 
 | ||||
| print(os.listdir(os.path.dirname(kivymd.uix.__path__[0]))) | ||||
| """ | ||||
|     ) | ||||
|     pyi_main.run( | ||||
|         [ | ||||
|             "--workpath", | ||||
|             str(workpath), | ||||
|             "--distpath", | ||||
|             str(distpath), | ||||
|             "--specpath", | ||||
|             str(tmp_path), | ||||
|             str(app), | ||||
|         ] | ||||
|     ) | ||||
|     subprocess.run([str(distpath / app_name / app_name)], check=True) | ||||
							
								
								
									
										21
									
								
								sbapp/kivymd/tests/test_app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,21 @@ | ||||
| from kivy import lang | ||||
| from kivy.clock import Clock | ||||
| from kivy.tests.common import GraphicUnitTest | ||||
| 
 | ||||
| from kivymd.app import MDApp | ||||
| from kivymd.theming import ThemeManager | ||||
| 
 | ||||
| 
 | ||||
| class AppTest(GraphicUnitTest): | ||||
|     def test_start_raw_app(self): | ||||
|         lang._delayed_start = None | ||||
|         a = MDApp() | ||||
|         Clock.schedule_once(a.stop, 0.1) | ||||
|         a.run() | ||||
| 
 | ||||
|     def test_theme_manager_existance(self): | ||||
|         lang._delayed_start = None | ||||
|         a = MDApp() | ||||
|         Clock.schedule_once(a.stop, 0.1) | ||||
|         a.run() | ||||
|         assert isinstance(a.theme_cls, ThemeManager) | ||||
							
								
								
									
										16
									
								
								sbapp/kivymd/tests/test_create_project.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | ||||
| def test_create_project(): | ||||
|     import os | ||||
|     import sys | ||||
| 
 | ||||
|     os.system( | ||||
|         f"{sys.executable} -m kivymd.tools.patterns.create_project " | ||||
|         f"MVC " | ||||
|         f"{os.path.expanduser('~')} " | ||||
|         f"TestProject " | ||||
|         f"{sys.executable} " | ||||
|         f"master " | ||||
|         f"--name_screen TestProjectScreen " | ||||
|         f"--name_database restdb " | ||||
|         f"--use_hotreload yes" | ||||
|     ) | ||||
|     assert os.path.exists(os.path.join(os.path.expanduser("~"), "TestProject")) | ||||
							
								
								
									
										16
									
								
								sbapp/kivymd/tests/test_font_definitions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | ||||
| def test_fonts_registration(): | ||||
|     # This should register fonts: | ||||
|     from kivy.core.text import LabelBase | ||||
| 
 | ||||
|     import kivymd  # NOQA | ||||
| 
 | ||||
|     fonts = [ | ||||
|         "Roboto", | ||||
|         "RobotoThin", | ||||
|         "RobotoLight", | ||||
|         "RobotoMedium", | ||||
|         "RobotoBlack", | ||||
|         "Icons", | ||||
|     ] | ||||
|     for font in fonts: | ||||
|         assert font in LabelBase._fonts.keys() | ||||
							
								
								
									
										10
									
								
								sbapp/kivymd/tests/test_icon_definitions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | ||||
| def test_icons_have_size(): | ||||
|     from kivy.core.text import Label | ||||
| 
 | ||||
|     from kivymd.icon_definitions import md_icons | ||||
| 
 | ||||
|     lbl = Label(font_name="Icons") | ||||
|     for icon_name, icon_value in md_icons.items(): | ||||
|         assert len(icon_value) == 1 | ||||
|         lbl.refresh() | ||||
|         assert lbl.get_extents(icon_value) is not None | ||||
							
								
								
									
										1230
									
								
								sbapp/kivymd/theming.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										90
									
								
								sbapp/kivymd/theming_dynamic_text.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,90 @@ | ||||
| """ | ||||
| Theming Dynamic Text | ||||
| ==================== | ||||
| 
 | ||||
| Two implementations. The first is based on color brightness obtained from- | ||||
| https://www.w3.org/TR/AERT#color-contrast | ||||
| The second is based on relative luminance calculation for sRGB obtained from- | ||||
| https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef | ||||
| and contrast ratio calculation obtained from- | ||||
| https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef | ||||
| 
 | ||||
| Preliminary testing suggests color brightness more closely matches the | ||||
| `Material Design spec` suggested text colors, but the alternative implementation | ||||
| is both newer and the current 'correct' recommendation, so is included here | ||||
| as an option. | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| def _color_brightness(color): | ||||
|     # Implementation of color brightness method | ||||
|     brightness = color[0] * 299 + color[1] * 587 + color[2] * 114 | ||||
|     brightness = brightness | ||||
|     return brightness | ||||
| 
 | ||||
| 
 | ||||
| def _black_or_white_by_color_brightness(color): | ||||
|     if _color_brightness(color) >= 500: | ||||
|         return "black" | ||||
|     else: | ||||
|         return "white" | ||||
| 
 | ||||
| 
 | ||||
| def _normalized_channel(color): | ||||
|     # Implementation of contrast ratio and relative luminance method | ||||
|     if color <= 0.03928: | ||||
|         return color / 12.92 | ||||
|     else: | ||||
|         return ((color + 0.055) / 1.055) ** 2.4 | ||||
| 
 | ||||
| 
 | ||||
| def _luminance(color): | ||||
|     rg = _normalized_channel(color[0]) | ||||
|     gg = _normalized_channel(color[1]) | ||||
|     bg = _normalized_channel(color[2]) | ||||
|     return 0.2126 * rg + 0.7152 * gg + 0.0722 * bg | ||||
| 
 | ||||
| 
 | ||||
| def _black_or_white_by_contrast_ratio(color): | ||||
|     l_color = _luminance(color) | ||||
|     l_black = 0.0 | ||||
|     l_white = 1.0 | ||||
|     b_contrast = (l_color + 0.05) / (l_black + 0.05) | ||||
|     w_contrast = (l_white + 0.05) / (l_color + 0.05) | ||||
|     return "white" if w_contrast >= b_contrast else "black" | ||||
| 
 | ||||
| 
 | ||||
| def get_contrast_text_color(color, use_color_brightness=True): | ||||
|     if use_color_brightness: | ||||
|         contrast_color = _black_or_white_by_color_brightness(color) | ||||
|     else: | ||||
|         contrast_color = _black_or_white_by_contrast_ratio(color) | ||||
|     if contrast_color == "white": | ||||
|         return 1, 1, 1, 1 | ||||
|     else: | ||||
|         return 0, 0, 0, 1 | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     from kivy.utils import get_color_from_hex | ||||
| 
 | ||||
|     from kivymd.color_definitions import colors, text_colors | ||||
| 
 | ||||
|     for c in colors.items(): | ||||
|         if c[0] in ["Light", "Dark"]: | ||||
|             continue | ||||
|         color = c[0] | ||||
|         print(f"For the {color} color palette:") | ||||
|         for name, hex_color in c[1].items(): | ||||
|             if hex_color: | ||||
|                 col = get_color_from_hex(hex_color) | ||||
|                 col_bri = get_contrast_text_color(col) | ||||
|                 con_rat = get_contrast_text_color( | ||||
|                     col, use_color_brightness=False | ||||
|                 ) | ||||
|                 text_color = text_colors[c[0]][name] | ||||
|                 print( | ||||
|                     f"   The {name} hue gives {col_bri} using color " | ||||
|                     f"brightness, {con_rat} using contrast ratio, and " | ||||
|                     f"{text_color} from the MD spec" | ||||
|                 ) | ||||
							
								
								
									
										21
									
								
								sbapp/kivymd/toast/LICENSE
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,21 @@ | ||||
| The MIT License (MIT) | ||||
| 
 | ||||
| Copyright (c) 2013 Brian  - androidtoast library | ||||
| Copyright (c) 2019 Ivanov Yuri - kivytoast library | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
| the Software without restriction, including without limitation the rights to | ||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||||
| the Software, and to permit persons to whom the Software is furnished to do so, | ||||
| subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										41
									
								
								sbapp/kivymd/toast/README.md
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,41 @@ | ||||
| KivyToast | ||||
| ======== | ||||
| 
 | ||||
| A package for working with messages like Toast on Android. It is intended for use in applications written using the Kivy framework. | ||||
| 
 | ||||
| This package is an improved version of the package https://github.com/knappador/kivy-toaster in which human toasts are written, written on Kivy. | ||||
| 
 | ||||
| <img src="https://raw.githubusercontent.com/HeaTTheatR/KivyToast/master/Screenshot.png" align="center"/> | ||||
| 
 | ||||
| The package modules are written using the framework for cross-platform development of <Kivy>. | ||||
| Information about the <Kivy> framework is available at http://kivy.org. | ||||
| 
 | ||||
| An example of usage (note that with this import the native implementation of toasts will be used for the Android platform and implementation on Kivy for others: | ||||
| 
 | ||||
| ```python | ||||
| from toast import toast | ||||
| 
 | ||||
| ... | ||||
| 
 | ||||
| # And then in the code, toasts are available | ||||
| # by calling the toast function: | ||||
| toast ('Your message') | ||||
| ``` | ||||
| 
 | ||||
| To force the Kivy implementation on the Android platform, use the import of the form: | ||||
| 
 | ||||
| ```python | ||||
| from toast.kivytoast import toast | ||||
| ``` | ||||
| 
 | ||||
| PROGRAMMING LANGUAGE | ||||
| -------------------- | ||||
| Python 2.7 + | ||||
| 
 | ||||
| DEPENDENCE | ||||
| ---------- | ||||
| The [Kivy] framework (http://kivy.org/docs/installation/installation.html) | ||||
| 
 | ||||
| LICENSE | ||||
| ------- | ||||
| MIT | ||||
							
								
								
									
										11
									
								
								sbapp/kivymd/toast/__init__.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | ||||
| __all__ = ("toast",) | ||||
| 
 | ||||
| from kivy.utils import platform | ||||
| 
 | ||||
| if platform == "android": | ||||
|     try: | ||||
|         from .androidtoast import toast | ||||
|     except ModuleNotFoundError: | ||||
|         from .kivytoast import toast | ||||
| else: | ||||
|     from .kivytoast import toast | ||||
							
								
								
									
										12
									
								
								sbapp/kivymd/toast/androidtoast/__init__.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,12 @@ | ||||
| """ | ||||
| Toast for Android device | ||||
| ======================== | ||||
| 
 | ||||
| .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toast.png | ||||
|     :align: center | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| __all__ = ("toast",) | ||||
| 
 | ||||
| from .androidtoast import toast | ||||
							
								
								
									
										81
									
								
								sbapp/kivymd/toast/androidtoast/androidtoast.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,81 @@ | ||||
| """ | ||||
| AndroidToast | ||||
| ============ | ||||
| 
 | ||||
| .. rubric:: Native implementation of toast for Android devices. | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     # Will be automatically used native implementation of the toast | ||||
|     # if your application is running on an Android device. | ||||
|     # Otherwise, will be used toast implementation | ||||
|     # from the kivymd/toast/kivytoast package. | ||||
| 
 | ||||
|     from kivy.lang import Builder | ||||
|     from kivy.uix.screenmanager import ScreenManager | ||||
| 
 | ||||
|     from kivymd.toast import toast | ||||
|     from kivymd.app import MDApp | ||||
| 
 | ||||
|     KV = ''' | ||||
|     MDScreen: | ||||
| 
 | ||||
|          MDFlatButton: | ||||
|              text: "My Toast" | ||||
|              pos_hint:{"center_x": .5, "center_y": .5} | ||||
|              on_press: app.show_toast() | ||||
|     ''' | ||||
| 
 | ||||
| 
 | ||||
|     class Test(MDApp): | ||||
|         def build(self): | ||||
|             return Builder.load_string(KV) | ||||
| 
 | ||||
|         def show_toast(self): | ||||
|             toast("Hello World", True, 80, 200, 0) | ||||
| 
 | ||||
| 
 | ||||
|     Test().run() | ||||
| """ | ||||
| 
 | ||||
| __all__ = ("toast",) | ||||
| 
 | ||||
| from kivy import platform | ||||
| 
 | ||||
| if platform != "android": | ||||
|     raise TypeError( | ||||
|         f"{platform.capitalize()} platform does not support Android Toast" | ||||
|     ) | ||||
| 
 | ||||
| from android.runnable import run_on_ui_thread | ||||
| from jnius import autoclass | ||||
| 
 | ||||
| activity = autoclass("org.kivy.android.PythonActivity").mActivity | ||||
| Toast = autoclass("android.widget.Toast") | ||||
| String = autoclass("java.lang.String") | ||||
| 
 | ||||
| 
 | ||||
| @run_on_ui_thread | ||||
| def toast(text, length_long=False, gravity=0, y=0, x=0): | ||||
|     """ | ||||
|     Displays a toast. | ||||
| 
 | ||||
|     :param length_long: the amount of time (in seconds) that the toast is | ||||
|            visible on the screen; | ||||
|     :param text: text to be displayed in the toast; | ||||
|     :param short_duration:  duration of the toast, if `True` the toast | ||||
|            will last 2.3s but if it is `False` the toast will last 3.9s; | ||||
|     :param gravity: refers to the toast position, if it is 80 the toast will | ||||
|            be shown below, if it is 40 the toast will be displayed above; | ||||
|     :param y: refers to the vertical position of the toast; | ||||
|     :param x: refers to the horizontal position of the toast; | ||||
| 
 | ||||
|     Important: if only the text value is specified and the value of | ||||
|     the `gravity`, `y`, `x` parameters is not specified, their values will | ||||
|     be 0 which means that the toast will be shown in the center. | ||||
|     """ | ||||
| 
 | ||||
|     duration = Toast.LENGTH_SHORT if length_long else Toast.LENGTH_LONG | ||||
|     t = Toast.makeText(activity, String(text), duration) | ||||
|     t.setGravity(gravity, x, y) | ||||
|     t.show() | ||||
							
								
								
									
										3
									
								
								sbapp/kivymd/toast/kivytoast/__init__.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,3 @@ | ||||
| __all__ = ("toast",) | ||||
| 
 | ||||
| from .kivytoast import toast | ||||
							
								
								
									
										154
									
								
								sbapp/kivymd/toast/kivytoast/kivytoast.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,154 @@ | ||||
| """ | ||||
| KivyToast | ||||
| ========= | ||||
| 
 | ||||
| .. rubric:: Implementation of toasts for desktop. | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     from kivy.lang import Builder | ||||
| 
 | ||||
|     from kivymd.app import MDApp | ||||
|     from kivymd.toast import toast | ||||
| 
 | ||||
|     KV = ''' | ||||
|     MDScreen: | ||||
| 
 | ||||
|         MDTopAppBar: | ||||
|             title: 'Test Toast' | ||||
|             pos_hint: {'top': 1} | ||||
|             left_action_items: [['menu', lambda x: x]] | ||||
| 
 | ||||
|         MDRaisedButton: | ||||
|             text: 'TEST KIVY TOAST' | ||||
|             pos_hint: {'center_x': .5, 'center_y': .5} | ||||
|             on_release: app.show_toast() | ||||
|     ''' | ||||
| 
 | ||||
| 
 | ||||
|     class Test(MDApp): | ||||
|         def show_toast(self): | ||||
|             '''Displays a toast on the screen.''' | ||||
| 
 | ||||
|             toast('Test Kivy Toast') | ||||
| 
 | ||||
|         def build(self): | ||||
|             return Builder.load_string(KV) | ||||
| 
 | ||||
|     Test().run() | ||||
| """ | ||||
| 
 | ||||
| from typing import List | ||||
| 
 | ||||
| from kivy.animation import Animation | ||||
| from kivy.clock import Clock | ||||
| from kivy.core.window import Window | ||||
| from kivy.lang import Builder | ||||
| from kivy.metrics import dp | ||||
| from kivy.properties import ListProperty, NumericProperty | ||||
| from kivy.uix.label import Label | ||||
| 
 | ||||
| from kivymd.uix.dialog import BaseDialog | ||||
| 
 | ||||
| Builder.load_string( | ||||
|     """ | ||||
| <Toast>: | ||||
|     size_hint: (None, None) | ||||
|     pos_hint: {"center_x": 0.5, "center_y": 0.1} | ||||
|     opacity: 0 | ||||
|     auto_dismiss: True | ||||
|     overlay_color: [0, 0, 0, 0] | ||||
|     canvas: | ||||
|         Color: | ||||
|             rgba: root._md_bg_color | ||||
|         RoundedRectangle: | ||||
|             pos: self.pos | ||||
|             size: self.size | ||||
|             radius: root.radius | ||||
| """ | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| class Toast(BaseDialog): | ||||
|     duration = NumericProperty(2.5) | ||||
|     """ | ||||
|     The amount of time (in seconds) that the toast is visible on the screen. | ||||
| 
 | ||||
|     :attr:`duration` is an :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `2.5`. | ||||
|     """ | ||||
| 
 | ||||
|     _md_bg_color = ListProperty() | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         super().__init__(**kwargs) | ||||
|         self.label_toast = Label(size_hint=(None, None), opacity=0) | ||||
|         self.label_toast.bind(texture_size=self.label_check_texture_size) | ||||
|         self.add_widget(self.label_toast) | ||||
| 
 | ||||
|     def label_check_texture_size( | ||||
|         self, instance_label: Label, texture_size: List[int] | ||||
|     ) -> None: | ||||
|         """ | ||||
|         Resizes the text if the text texture is larger than the screen size. | ||||
|         Sets the size of the toast according to the texture size of the toast | ||||
|         text. | ||||
|         """ | ||||
| 
 | ||||
|         texture_width, texture_height = texture_size | ||||
|         if texture_width > Window.width: | ||||
|             instance_label.text_size = (Window.width - dp(10), None) | ||||
|             instance_label.texture_update() | ||||
|             texture_width, texture_height = instance_label.texture_size | ||||
|         self.size = (texture_width + 25, texture_height + 25) | ||||
| 
 | ||||
|     def toast(self, text_toast: str) -> None: | ||||
|         """Displays a toast.""" | ||||
| 
 | ||||
|         self.label_toast.text = text_toast | ||||
|         self.open() | ||||
| 
 | ||||
|     def on_open(self) -> None: | ||||
|         """Default open event handler.""" | ||||
| 
 | ||||
|         self.fade_in() | ||||
|         Clock.schedule_once(self.fade_out, self.duration) | ||||
| 
 | ||||
|     def fade_in(self) -> None: | ||||
|         """Animation of opening toast on the screen.""" | ||||
| 
 | ||||
|         anim = Animation(opacity=1, duration=0.4) | ||||
|         anim.start(self.label_toast) | ||||
|         anim.start(self) | ||||
| 
 | ||||
|     def fade_out(self, *args) -> None: | ||||
|         """Animation of hiding toast on the screen.""" | ||||
| 
 | ||||
|         anim = Animation(opacity=0, duration=0.4) | ||||
|         anim.bind(on_complete=lambda *x: self.dismiss()) | ||||
|         anim.start(self.label_toast) | ||||
|         anim.start(self) | ||||
| 
 | ||||
|     def on_touch_down(self, touch): | ||||
|         if not self.collide_point(*touch.pos): | ||||
|             if self.auto_dismiss: | ||||
|                 self.fade_out() | ||||
|                 return False | ||||
|         super().on_touch_down(touch) | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| def toast( | ||||
|     text: str = "", background: list = None, duration: float = 2.5 | ||||
| ) -> None: | ||||
|     """ | ||||
|     Displays a toast. | ||||
| 
 | ||||
|     :param text: text to be displayed in the toast; | ||||
|     :param duration: the amount of time (in seconds) that the toast is visible on the screen | ||||
|     :param background: toast background color in ``rgba`` format; | ||||
|     """ | ||||
| 
 | ||||
|     if background is None: | ||||
|         background = [0.2, 0.2, 0.2, 1] | ||||
|     Toast(duration=duration, _md_bg_color=background).toast(text) | ||||
							
								
								
									
										0
									
								
								sbapp/kivymd/tools/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										92
									
								
								sbapp/kivymd/tools/argument_parser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,92 @@ | ||||
| # Copyright (c) 2019-2021 Artem Bulgakov | ||||
| # | ||||
| # This file is distributed under the terms of the same license, | ||||
| # as the Kivy framework. | ||||
| 
 | ||||
| import argparse | ||||
| import sys | ||||
| 
 | ||||
| 
 | ||||
| class ArgumentParserWithHelp(argparse.ArgumentParser): | ||||
|     def parse_args(self, args=None, namespace=None): | ||||
|         # Add help when no arguments specified | ||||
|         if not args and not len(sys.argv) > 1: | ||||
|             self.print_help() | ||||
|             self.exit(1) | ||||
|         return super().parse_args(args, namespace) | ||||
| 
 | ||||
|     def error(self, message): | ||||
|         # Add full help on error | ||||
|         self.print_help() | ||||
|         self.exit(2, f"\nError: {message}\n") | ||||
| 
 | ||||
|     def format_help(self): | ||||
|         # Add subparsers usage and help to full help text | ||||
|         formatter = self._get_formatter() | ||||
| 
 | ||||
|         # Get subparsers | ||||
|         subparsers_actions = [ | ||||
|             action | ||||
|             for action in self._actions | ||||
|             if isinstance(action, argparse._SubParsersAction) | ||||
|         ] | ||||
| 
 | ||||
|         # Description | ||||
|         formatter.add_text(self.description) | ||||
| 
 | ||||
|         # Usage | ||||
|         formatter.add_usage( | ||||
|             self.usage, | ||||
|             self._actions, | ||||
|             self._mutually_exclusive_groups, | ||||
|             prefix="Usage:\n", | ||||
|         ) | ||||
| 
 | ||||
|         # Subparsers usage | ||||
|         for subparsers_action in subparsers_actions: | ||||
|             for choice, subparser in subparsers_action.choices.items(): | ||||
|                 formatter.add_usage( | ||||
|                     subparser.usage, | ||||
|                     subparser._actions, | ||||
|                     subparser._mutually_exclusive_groups, | ||||
|                     prefix="", | ||||
|                 ) | ||||
| 
 | ||||
|         # Positionals, optionals and user-defined groups | ||||
|         for action_group in self._action_groups: | ||||
|             if not any( | ||||
|                 [ | ||||
|                     action in subparsers_actions | ||||
|                     for action in action_group._group_actions | ||||
|                 ] | ||||
|             ): | ||||
|                 formatter.start_section(action_group.title) | ||||
|                 formatter.add_text(action_group.description) | ||||
|                 formatter.add_arguments(action_group._group_actions) | ||||
|                 formatter.end_section() | ||||
|             else: | ||||
|                 # Process subparsers differently | ||||
|                 # Just show list of choices | ||||
|                 formatter.start_section(action_group.title) | ||||
|                 # formatter.add_text(action_group.description) | ||||
|                 for action in action_group._group_actions: | ||||
|                     for choice in action.choices: | ||||
|                         formatter.add_text(choice) | ||||
|                 formatter.end_section() | ||||
| 
 | ||||
|         # Subparsers help | ||||
|         for subparsers_action in subparsers_actions: | ||||
|             for choice, subparser in subparsers_action.choices.items(): | ||||
|                 formatter.start_section(choice) | ||||
|                 for action_group in subparser._action_groups: | ||||
|                     formatter.start_section(action_group.title) | ||||
|                     formatter.add_text(action_group.description) | ||||
|                     formatter.add_arguments(action_group._group_actions) | ||||
|                     formatter.end_section() | ||||
|                 formatter.end_section() | ||||
| 
 | ||||
|         # Epilog | ||||
|         formatter.add_text(self.epilog) | ||||
| 
 | ||||
|         # Determine help from format above | ||||
|         return formatter.format_help() | ||||
							
								
								
									
										0
									
								
								sbapp/kivymd/tools/hotreload/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										549
									
								
								sbapp/kivymd/tools/hotreload/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,549 @@ | ||||
| """ | ||||
| HotReload | ||||
| ========= | ||||
| 
 | ||||
| .. versionadded:: 1.0.0 | ||||
| 
 | ||||
| .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hot-reload.png | ||||
|     :align: center | ||||
| 
 | ||||
| .. rubric:: | ||||
|     Hot reload tool - is a fork of the project https://github.com/tito/kaki | ||||
| 
 | ||||
| .. note:: | ||||
|     Since the project is not developing, we decided to include it in the | ||||
|     KivvMD library and hope that the further development of the hot reload | ||||
|     tool in the KivyMD project will develop faster. | ||||
| 
 | ||||
| .. rubric:: | ||||
|     This library enhance Kivy frameworks with opiniated features such as: | ||||
| 
 | ||||
| - Auto reloading kv or py (watchdog required, limited to some uses cases); | ||||
| - Idle detection support; | ||||
| - Foreground lock (Windows OS only); | ||||
| 
 | ||||
| Usage | ||||
| ----- | ||||
| 
 | ||||
| .. note:: | ||||
|     See `create project with hot reload <https://kivymd.readthedocs.io/en/latest/api/kivymd/tools/patterns/create_project/#create-project-with-hot-reload>`_ | ||||
|     for more information. | ||||
| 
 | ||||
| TODO | ||||
| ---- | ||||
| 
 | ||||
| - Add automatic reloading of Python classes; | ||||
| - Add save application state on reloading; | ||||
| 
 | ||||
| FIXME | ||||
| ----- | ||||
| 
 | ||||
| - On Windows, hot reloading of Python files may not work; | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import traceback | ||||
| from fnmatch import fnmatch | ||||
| from os.path import join, realpath | ||||
| 
 | ||||
| original_argv = sys.argv | ||||
| 
 | ||||
| from kivy.base import ExceptionHandler, ExceptionManager  # NOQA E402 | ||||
| from kivy.clock import Clock, mainthread  # NOQA E402 | ||||
| from kivy.factory import Factory  # NOQA E402 | ||||
| from kivy.lang import Builder  # NOQA E402 | ||||
| from kivy.logger import Logger  # NOQA E402 | ||||
| from kivy.properties import (  # NOQA E402 | ||||
|     BooleanProperty, | ||||
|     DictProperty, | ||||
|     ListProperty, | ||||
|     NumericProperty, | ||||
| ) | ||||
| 
 | ||||
| from kivymd.app import MDApp as BaseApp  # NOQA E402 | ||||
| 
 | ||||
| try: | ||||
|     from monotonic import monotonic | ||||
| except ImportError: | ||||
|     monotonic = None | ||||
| try: | ||||
|     from importlib import reload | ||||
| 
 | ||||
|     PY3 = True | ||||
| except ImportError: | ||||
|     PY3 = False | ||||
| 
 | ||||
| import watchdog  # NOQA | ||||
| 
 | ||||
| 
 | ||||
| class ExceptionClass(ExceptionHandler): | ||||
|     def handle_exception(self, inst): | ||||
|         if isinstance(inst, (KeyboardInterrupt, SystemExit)): | ||||
|             return ExceptionManager.RAISE | ||||
|         app = MDApp.get_running_app() | ||||
|         if not app.DEBUG and not app.RAISE_ERROR: | ||||
|             return ExceptionManager.RAISE | ||||
|         app.set_error(inst, tb=traceback.format_exc()) | ||||
|         return ExceptionManager.PASS | ||||
| 
 | ||||
| 
 | ||||
| ExceptionManager.add_handler(ExceptionClass()) | ||||
| 
 | ||||
| 
 | ||||
| class MDApp(BaseApp): | ||||
|     """HotReload Application class.""" | ||||
| 
 | ||||
|     DEBUG = BooleanProperty("DEBUG" in os.environ) | ||||
|     """ | ||||
|     Control either we activate debugging in the app or not. | ||||
|     Defaults depend if 'DEBUG' exists in os.environ. | ||||
| 
 | ||||
|     :attr:`DEBUG` is a :class:`~kivy.properties.BooleanProperty`. | ||||
|     """ | ||||
| 
 | ||||
|     FOREGROUND_LOCK = BooleanProperty(False) | ||||
|     """ | ||||
|     If `True` it will require the foreground lock on windows. | ||||
| 
 | ||||
|     :attr:`FOREGROUND_LOCK` is a :class:`~kivy.properties.BooleanProperty` | ||||
|     and defaults to `False`. | ||||
|     """ | ||||
| 
 | ||||
|     KV_FILES = ListProperty() | ||||
|     """ | ||||
|     List of KV files under management for auto reloader. | ||||
| 
 | ||||
|     :attr:`KV_FILES` is a :class:`~kivy.properties.ListProperty` | ||||
|     and defaults to `[]`. | ||||
|     """ | ||||
| 
 | ||||
|     KV_DIRS = ListProperty() | ||||
|     """ | ||||
|     List of managed KV directories for autoloader. | ||||
| 
 | ||||
|     :attr:`KV_DIRS` is a :class:`~kivy.properties.ListProperty` | ||||
|     and defaults to `[]`. | ||||
|     """ | ||||
| 
 | ||||
|     AUTORELOADER_PATHS = ListProperty([(".", {"recursive": True})]) | ||||
|     """ | ||||
|     List of path to watch for auto reloading. | ||||
| 
 | ||||
|     :attr:`AUTORELOADER_PATHS` is a :class:`~kivy.properties.ListProperty` | ||||
|     and defaults to `([(".", {"recursive": True})]`. | ||||
|     """ | ||||
| 
 | ||||
|     AUTORELOADER_IGNORE_PATTERNS = ListProperty(["*.pyc", "*__pycache__*"]) | ||||
|     """ | ||||
|     List of extensions to ignore. | ||||
| 
 | ||||
|     :attr:`AUTORELOADER_IGNORE_PATTERNS` is a :class:`~kivy.properties.ListProperty` | ||||
|     and defaults to `['*.pyc', '*__pycache__*']`. | ||||
|     """ | ||||
| 
 | ||||
|     CLASSES = DictProperty() | ||||
|     """ | ||||
|     Factory classes managed by hotreload. | ||||
| 
 | ||||
|     :attr:`CLASSES` is a :class:`~kivy.properties.DictProperty` | ||||
|     and defaults to `{}`. | ||||
|     """ | ||||
| 
 | ||||
|     IDLE_DETECTION = BooleanProperty(False) | ||||
|     """ | ||||
|     Idle detection (if True, event on_idle/on_wakeup will be fired). | ||||
|     Rearming idle can also be done with `rearm_idle()`. | ||||
| 
 | ||||
|     :attr:`IDLE_DETECTION` is a :class:`~kivy.properties.BooleanProperty` | ||||
|     and defaults to `False`. | ||||
|     """ | ||||
| 
 | ||||
|     IDLE_TIMEOUT = NumericProperty(60) | ||||
|     """ | ||||
|     Default idle timeout. | ||||
| 
 | ||||
|     :attr:`IDLE_TIMEOUT` is a :class:`~kivy.properties.NumericProperty` | ||||
|     and defaults to `60`. | ||||
|     """ | ||||
| 
 | ||||
|     RAISE_ERROR = BooleanProperty(True) | ||||
|     """ | ||||
|     Raise error. | ||||
|     When the `DEBUG` is activated, it will raise any error instead | ||||
|     of showing it on the screen. If you still want to show the error | ||||
|     when not in `DEBUG`, put this to `False`. | ||||
| 
 | ||||
|     :attr:`RAISE_ERROR` is a :class:`~kivy.properties.BooleanProperty` | ||||
|     and defaults to `True`. | ||||
|     """ | ||||
| 
 | ||||
|     __events__ = ["on_idle", "on_wakeup"] | ||||
| 
 | ||||
|     def build(self): | ||||
|         if self.DEBUG: | ||||
|             Logger.info("{}: Debug mode activated".format(self.appname)) | ||||
|             self.enable_autoreload() | ||||
|             self.patch_builder() | ||||
|             self.bind_key(32, self.rebuild) | ||||
|         if self.FOREGROUND_LOCK: | ||||
|             self.prepare_foreground_lock() | ||||
| 
 | ||||
|         self.state = None | ||||
|         self.approot = None | ||||
|         self.root = self.get_root() | ||||
|         self.rebuild(first=True) | ||||
| 
 | ||||
|         if self.IDLE_DETECTION: | ||||
|             self.install_idle(timeout=self.IDLE_TIMEOUT) | ||||
| 
 | ||||
|         return super().build() | ||||
| 
 | ||||
|     def get_root(self): | ||||
|         """ | ||||
|         Return a root widget, that will contains your application. | ||||
|         It should not be your application widget itself, as it may | ||||
|         be destroyed and recreated from scratch when reloading. | ||||
| 
 | ||||
|         By default, it returns a RelativeLayout, but it could be | ||||
|         a Viewport. | ||||
|         """ | ||||
| 
 | ||||
|         return Factory.RelativeLayout() | ||||
| 
 | ||||
|     def get_root_path(self): | ||||
|         """Return the root file path.""" | ||||
| 
 | ||||
|         return realpath(os.getcwd()) | ||||
| 
 | ||||
|     def build_app(self, first=False): | ||||
|         """ | ||||
|         Must return your application widget. | ||||
| 
 | ||||
|         If `first` is set, it means that will be your first time ever | ||||
|         that the application is built. Act according to it. | ||||
|         """ | ||||
| 
 | ||||
|         raise NotImplementedError() | ||||
| 
 | ||||
|     def unload_app_dependencies(self): | ||||
|         """ | ||||
|         Called when all the application dependencies must be unloaded. | ||||
|         Usually happen before a reload | ||||
|         """ | ||||
| 
 | ||||
|         for path_to_kv_file in self.KV_FILES: | ||||
|             path_to_kv_file = realpath(path_to_kv_file) | ||||
|             Builder.unload_file(path_to_kv_file) | ||||
| 
 | ||||
|         for name, module in self.CLASSES.items(): | ||||
|             Factory.unregister(name) | ||||
| 
 | ||||
|         for path in self.KV_DIRS: | ||||
|             for path_to_dir, dirs, files in os.walk(path): | ||||
|                 for name_file in files: | ||||
|                     if os.path.splitext(name_file)[1] == ".kv": | ||||
|                         path_to_kv_file = os.path.join(path_to_dir, name_file) | ||||
|                         Builder.unload_file(path_to_kv_file) | ||||
| 
 | ||||
|     def load_app_dependencies(self): | ||||
|         """ | ||||
|         Load all the application dependencies. | ||||
|         This is called before rebuild. | ||||
|         """ | ||||
| 
 | ||||
|         for path_to_kv_file in self.KV_FILES: | ||||
|             path_to_kv_file = realpath(path_to_kv_file) | ||||
|             Builder.load_file(path_to_kv_file) | ||||
| 
 | ||||
|         for name, module in self.CLASSES.items(): | ||||
|             Factory.register(name, module=module) | ||||
| 
 | ||||
|         for path in self.KV_DIRS: | ||||
|             for path_to_dir, dirs, files in os.walk(path): | ||||
|                 for name_file in files: | ||||
|                     if os.path.splitext(name_file)[1] == ".kv": | ||||
|                         path_to_kv_file = os.path.join(path_to_dir, name_file) | ||||
|                         Builder.load_file(path_to_kv_file) | ||||
| 
 | ||||
|     def rebuild(self, *args, **kwargs): | ||||
|         print("{}: Rebuild the application".format(self.appname)) | ||||
|         first = kwargs.get("first", False) | ||||
|         try: | ||||
|             if not first: | ||||
|                 self.unload_app_dependencies() | ||||
| 
 | ||||
|             # In case the loading fail in the middle of building a widget | ||||
|             # there will be existing rules context that will break later | ||||
|             # instanciation. | ||||
|             # Just clean it. | ||||
|             Builder.rulectx = {} | ||||
| 
 | ||||
|             self.load_app_dependencies() | ||||
|             self.set_widget(None) | ||||
|             self.approot = self.build_app() | ||||
|             self.set_widget(self.approot) | ||||
|             self.apply_state(self.state) | ||||
|         except Exception as exc: | ||||
|             import traceback | ||||
| 
 | ||||
|             Logger.exception("{}: Error when building app".format(self.appname)) | ||||
|             self.set_error(repr(exc), traceback.format_exc()) | ||||
|             if not self.DEBUG and self.RAISE_ERROR: | ||||
|                 raise | ||||
| 
 | ||||
|     @mainthread | ||||
|     def set_error(self, exc, tb=None): | ||||
|         print(tb) | ||||
|         from kivy.core.window import Window | ||||
|         from kivy.utils import get_color_from_hex | ||||
| 
 | ||||
|         Window.clearcolor = get_color_from_hex("#e50000") | ||||
|         scroll = Factory.ScrollView(scroll_y=0) | ||||
|         lbl = Factory.Label( | ||||
|             text_size=(Window.width - 100, None), | ||||
|             size_hint_y=None, | ||||
|             text="{}\n\n{}".format(exc, tb or ""), | ||||
|         ) | ||||
|         lbl.bind(texture_size=lbl.setter("size")) | ||||
|         scroll.add_widget(lbl) | ||||
|         self.set_widget(scroll) | ||||
| 
 | ||||
|     def bind_key(self, key, callback): | ||||
|         """Bind a key (keycode) to a callback (cannot be unbind).""" | ||||
| 
 | ||||
|         from kivy.core.window import Window | ||||
| 
 | ||||
|         def _on_keyboard(window, keycode, *args): | ||||
|             if key == keycode: | ||||
|                 return callback() | ||||
| 
 | ||||
|         Window.bind(on_keyboard=_on_keyboard) | ||||
| 
 | ||||
|     @property | ||||
|     def appname(self): | ||||
|         """Return the name of the application class.""" | ||||
| 
 | ||||
|         return self.__class__.__name__ | ||||
| 
 | ||||
|     def enable_autoreload(self): | ||||
|         """ | ||||
|         Enable autoreload manually. It is activated automatically | ||||
|         if "DEBUG" exists in environ. It requires the `watchdog` module. | ||||
|         """ | ||||
| 
 | ||||
|         try: | ||||
|             from watchdog.events import FileSystemEventHandler | ||||
|             from watchdog.observers import Observer | ||||
|         except ImportError: | ||||
|             Logger.warn( | ||||
|                 "{}: Autoreloader is missing watchdog".format(self.appname) | ||||
|             ) | ||||
|             return | ||||
|         Logger.info("{}: Autoreloader activated".format(self.appname)) | ||||
|         rootpath = self.get_root_path() | ||||
|         self.w_handler = handler = FileSystemEventHandler() | ||||
|         handler.dispatch = self._reload_from_watchdog | ||||
|         self._observer = observer = Observer() | ||||
|         for path in self.AUTORELOADER_PATHS: | ||||
|             options = {"recursive": True} | ||||
|             if isinstance(path, (tuple, list)): | ||||
|                 path, options = path | ||||
|             observer.schedule(handler, join(rootpath, path), **options) | ||||
|         observer.start() | ||||
| 
 | ||||
|     def prepare_foreground_lock(self): | ||||
|         """ | ||||
|         Try forcing app to front permanently to avoid windows | ||||
|         pop ups and notifications etc.app. | ||||
| 
 | ||||
|         Requires fake full screen and borderless. | ||||
| 
 | ||||
|         .. note:: | ||||
|             This function is called automatically if `FOREGROUND_LOCK` is set | ||||
|         """ | ||||
| 
 | ||||
|         try: | ||||
|             import ctypes | ||||
| 
 | ||||
|             LSFW_LOCK = 1 | ||||
|             ctypes.windll.user32.LockSetForegroundWindow(LSFW_LOCK) | ||||
|             Logger.info("App: Foreground lock activated") | ||||
|         except Exception: | ||||
|             Logger.warn("App: No foreground lock available") | ||||
| 
 | ||||
|     def set_widget(self, wid): | ||||
|         """ | ||||
|         Clear the root container, and set the new approot widget to `wid`. | ||||
|         """ | ||||
| 
 | ||||
|         self.root.clear_widgets() | ||||
|         self.approot = wid | ||||
|         if wid is None: | ||||
|             return | ||||
|         self.root.add_widget(self.approot) | ||||
|         try: | ||||
|             wid.do_layout() | ||||
|         except Exception: | ||||
|             pass | ||||
| 
 | ||||
|     # State management. | ||||
|     def apply_state(self, state): | ||||
|         """Whatever the current state is, reapply the current state.""" | ||||
| 
 | ||||
|     # Idle management leave. | ||||
|     def install_idle(self, timeout=60): | ||||
|         """ | ||||
|         Install the idle detector. Default timeout is 60s. | ||||
|         Once installed, it will check every second if the idle timer | ||||
|         expired. The timer can be rearm using :func:`rearm_idle`. | ||||
|         """ | ||||
| 
 | ||||
|         if monotonic is None: | ||||
|             Logger.exception( | ||||
|                 "{}: Cannot use idle detector, monotonic is missing".format( | ||||
|                     self.appname | ||||
|                 ) | ||||
|             ) | ||||
|         self.idle_timer = None | ||||
|         self.idle_timeout = timeout | ||||
|         Logger.info( | ||||
|             "{}: Install idle detector, {} seconds".format( | ||||
|                 self.appname, timeout | ||||
|             ) | ||||
|         ) | ||||
|         Clock.schedule_interval(self._check_idle, 1) | ||||
|         self.root.bind( | ||||
|             on_touch_down=self.rearm_idle, on_touch_up=self.rearm_idle | ||||
|         ) | ||||
| 
 | ||||
|     def rearm_idle(self, *args): | ||||
|         """Rearm the idle timer.""" | ||||
| 
 | ||||
|         if not hasattr(self, "idle_timer"): | ||||
|             return | ||||
|         if self.idle_timer is None: | ||||
|             self.dispatch("on_wakeup") | ||||
|         self.idle_timer = monotonic() | ||||
| 
 | ||||
|     # Internals. | ||||
|     def patch_builder(self): | ||||
|         Builder.orig_load_string = Builder.load_string | ||||
|         Builder.load_string = self._builder_load_string | ||||
| 
 | ||||
|     def on_idle(self, *args): | ||||
|         """Event fired when the application enter the idle mode.""" | ||||
| 
 | ||||
|     def on_wakeup(self, *args): | ||||
|         """Event fired when the application leaves idle mode.""" | ||||
| 
 | ||||
|     @mainthread | ||||
|     def _reload_from_watchdog(self, event): | ||||
|         from watchdog.events import FileModifiedEvent | ||||
| 
 | ||||
|         if not isinstance(event, FileModifiedEvent): | ||||
|             return | ||||
| 
 | ||||
|         for pat in self.AUTORELOADER_IGNORE_PATTERNS: | ||||
|             if fnmatch(event.src_path, pat): | ||||
|                 return | ||||
| 
 | ||||
|         if event.src_path.endswith(".py"): | ||||
|             # source changed, reload it | ||||
|             try: | ||||
|                 Builder.unload_file(event.src_path) | ||||
|                 self._reload_py(event.src_path) | ||||
|             except Exception as e: | ||||
|                 import traceback | ||||
| 
 | ||||
|                 self.set_error(repr(e), traceback.format_exc()) | ||||
|                 return | ||||
| 
 | ||||
|         Clock.unschedule(self.rebuild) | ||||
|         Clock.schedule_once(self.rebuild, 0.1) | ||||
| 
 | ||||
|     def _builder_load_string(self, string, **kwargs): | ||||
|         if "filename" not in kwargs: | ||||
|             from inspect import getframeinfo, stack | ||||
| 
 | ||||
|             caller = getframeinfo(stack()[1][0]) | ||||
|             kwargs["filename"] = caller.filename | ||||
|         return Builder.orig_load_string(string, **kwargs) | ||||
| 
 | ||||
|     def _check_idle(self, *args): | ||||
|         if not hasattr(self, "idle_timer"): | ||||
|             return | ||||
|         if self.idle_timer is None: | ||||
|             return | ||||
|         if monotonic() - self.idle_timer > self.idle_timeout: | ||||
|             self.idle_timer = None | ||||
|             self.dispatch("on_idle") | ||||
| 
 | ||||
|     def _reload_py(self, filename): | ||||
|         # We don't have dependency graph yet, so if the module actually exists | ||||
|         # reload it. | ||||
| 
 | ||||
|         filename = realpath(filename) | ||||
|         # Check if it's our own application file. | ||||
|         try: | ||||
|             mod = sys.modules[self.__class__.__module__] | ||||
|             mod_filename = realpath(mod.__file__) | ||||
|         except Exception: | ||||
|             mod_filename = None | ||||
| 
 | ||||
|         # Detect if it's the application class // main. | ||||
|         if mod_filename == filename: | ||||
|             return self._restart_app(mod) | ||||
| 
 | ||||
|         module = self._filename_to_module(filename) | ||||
|         if module in sys.modules: | ||||
|             Logger.debug("{}: Module exist, reload it".format(self.appname)) | ||||
|             Factory.unregister_from_filename(filename) | ||||
|             self._unregister_factory_from_module(module) | ||||
|             reload(sys.modules[module]) | ||||
| 
 | ||||
|     def _unregister_factory_from_module(self, module): | ||||
|         # Check module directly. | ||||
|         to_remove = [ | ||||
|             x for x in Factory.classes if Factory.classes[x]["module"] == module | ||||
|         ] | ||||
|         # Check class name. | ||||
|         for x in Factory.classes: | ||||
|             cls = Factory.classes[x]["cls"] | ||||
|             if not cls: | ||||
|                 continue | ||||
|             if getattr(cls, "__module__", None) == module: | ||||
|                 to_remove.append(x) | ||||
| 
 | ||||
|         for name in set(to_remove): | ||||
|             del Factory.classes[name] | ||||
| 
 | ||||
|     def _filename_to_module(self, filename): | ||||
|         orig_filename = filename | ||||
|         rootpath = self.get_root_path() | ||||
|         if filename.startswith(rootpath): | ||||
|             filename = filename[len(rootpath) :] | ||||
|         if filename.startswith("/"): | ||||
|             filename = filename[1:] | ||||
|         module = filename[:-3].replace("/", ".") | ||||
|         Logger.debug( | ||||
|             "{}: Translated {} to {}".format( | ||||
|                 self.appname, orig_filename, module | ||||
|             ) | ||||
|         ) | ||||
|         return module | ||||
| 
 | ||||
|     def _restart_app(self, mod): | ||||
|         _has_execv = sys.platform != "win32" | ||||
|         cmd = [sys.executable] + original_argv | ||||
|         if not _has_execv: | ||||
|             import subprocess | ||||
| 
 | ||||
|             subprocess.Popen(cmd) | ||||
|             sys.exit(0) | ||||
|         else: | ||||
|             try: | ||||
|                 os.execv(sys.executable, cmd) | ||||
|             except OSError: | ||||
|                 os.spawnv(os.P_NOWAIT, sys.executable, cmd) | ||||
|                 os._exit(0) | ||||
							
								
								
									
										0
									
								
								sbapp/kivymd/tools/packaging/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										72
									
								
								sbapp/kivymd/tools/packaging/pyinstaller/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,72 @@ | ||||
| """ | ||||
| PyInstaller hooks | ||||
| ================= | ||||
| 
 | ||||
| Add ``hookspath=[kivymd.hooks_path]`` to your .spec file. | ||||
| 
 | ||||
| Example of .spec file | ||||
| ===================== | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     # -*- mode: python ; coding: utf-8 -*- | ||||
| 
 | ||||
|     import sys | ||||
|     import os | ||||
| 
 | ||||
|     from kivy_deps import sdl2, glew | ||||
| 
 | ||||
|     from kivymd import hooks_path as kivymd_hooks_path | ||||
| 
 | ||||
|     path = os.path.abspath(".") | ||||
| 
 | ||||
|     a = Analysis( | ||||
|         ["main.py"], | ||||
|         pathex=[path], | ||||
|         hookspath=[kivymd_hooks_path], | ||||
|         win_no_prefer_redirects=False, | ||||
|         win_private_assemblies=False, | ||||
|         cipher=None, | ||||
|         noarchive=False, | ||||
|     ) | ||||
|     pyz = PYZ(a.pure, a.zipped_data, cipher=None) | ||||
| 
 | ||||
|     exe = EXE( | ||||
|         pyz, | ||||
|         a.scripts, | ||||
|         a.binaries, | ||||
|         a.zipfiles, | ||||
|         a.datas, | ||||
|         *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)], | ||||
|         debug=False, | ||||
|         strip=False, | ||||
|         upx=True, | ||||
|         name="app_name", | ||||
|         console=True, | ||||
|     ) | ||||
| """ | ||||
| 
 | ||||
| __all__ = ("hooks_path", "get_hook_dirs", "get_pyinstaller_tests") | ||||
| 
 | ||||
| import os | ||||
| from pathlib import Path | ||||
| 
 | ||||
| import kivymd | ||||
| 
 | ||||
| hooks_path = str(Path(__file__).absolute().parent) | ||||
| """Path to hook directory to use with PyInstaller. | ||||
| See :mod:`kivymd.tools.packaging.pyinstaller` for more information.""" | ||||
| 
 | ||||
| 
 | ||||
| def get_hook_dirs(): | ||||
|     return [hooks_path] | ||||
| 
 | ||||
| 
 | ||||
| def get_pyinstaller_tests(): | ||||
|     return [os.path.join(kivymd.path, "tests", "pyinstaller")] | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     print(hooks_path) | ||||
|     print(get_hook_dirs()) | ||||
|     print(get_pyinstaller_tests()) | ||||
							
								
								
									
										42
									
								
								sbapp/kivymd/tools/packaging/pyinstaller/hook-kivymd.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,42 @@ | ||||
| """ | ||||
| PyInstaller hook for KivyMD | ||||
| =========================== | ||||
| 
 | ||||
| Adds fonts, images and KV files to package. | ||||
| 
 | ||||
| All modules from uix directory are added by Kivy hook. | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| from pathlib import Path | ||||
| 
 | ||||
| import kivymd | ||||
| 
 | ||||
| datas = [ | ||||
|     # Add `.ttf` files from the `kivymd/fonts` directory. | ||||
|     ( | ||||
|         kivymd.fonts_path, | ||||
|         str(Path("kivymd").joinpath(Path(kivymd.fonts_path).name)), | ||||
|     ), | ||||
|     # Add files from the `kivymd/images` directory. | ||||
|     ( | ||||
|         kivymd.images_path, | ||||
|         str(Path("kivymd").joinpath(Path(kivymd.images_path).name)), | ||||
|     ), | ||||
| ] | ||||
| 
 | ||||
| # Add `.kv. files from the `kivymd/uix` directory. | ||||
| for path_to_kv_file in Path(kivymd.uix_path).glob("**/*.kv"): | ||||
|     datas.append( | ||||
|         ( | ||||
|             str(Path(path_to_kv_file).parent.joinpath("*.kv")), | ||||
|             str( | ||||
|                 Path("kivymd").joinpath( | ||||
|                     "uix", | ||||
|                     str(Path(path_to_kv_file).parent).split( | ||||
|                         str(Path("kivymd").joinpath("uix")) + os.sep | ||||
|                     )[1], | ||||
|                 ) | ||||
|             ), | ||||
|         ) | ||||
|     ) | ||||
| @ -0,0 +1,3 @@ | ||||
| %s | ||||
|     def get_view(self) -> %s: | ||||
|         return self.view | ||||
							
								
								
									
										26
									
								
								sbapp/kivymd/tools/patterns/MVC/Makefile.tmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,26 @@ | ||||
| # FILE TO FIND AND CREATE LOCALIZATION FILES FOR YOUR APPLICATION. \
 | ||||
| \ | ||||
| In this file, you can specify in which files of your project to search for \ | ||||
| localization strings. \ | ||||
| These files should be listed in the below command: \ | ||||
| \ | ||||
| \ | ||||
| xgettext -Lpython --output=messages.pot --from-code=utf-8 \
 | ||||
|     path/to/file-1 \
 | ||||
|     path/to/file-2 \
 | ||||
|     ... | ||||
| 
 | ||||
| .PHONY: po mo | ||||
| 
 | ||||
| po: | ||||
| 	xgettext -Lpython --output=messages.pot --from-code=utf-8 \
 | ||||
|              View/%s/%s.kv \
 | ||||
|              View/%s/%s.py | ||||
| 	msgmerge --update --no-fuzzy-matching --backup=off data/locales/po/en.po messages.pot | ||||
| 	msgmerge --update --no-fuzzy-matching --backup=off data/locales/po/ru.po messages.pot | ||||
| 
 | ||||
| mo: | ||||
| 	mkdir -p data/locales/en/LC_MESSAGES | ||||
| 	mkdir -p data/locales/ru/LC_MESSAGES | ||||
| 	msgfmt -c -o data/locales/en/LC_MESSAGES/%s.mo data/locales/po/en.po | ||||
| 	msgfmt -c -o data/locales/ru/LC_MESSAGES/%s.mo data/locales/po/ru.po | ||||
							
								
								
									
										0
									
								
								sbapp/kivymd/tools/patterns/MVC/Model/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										33
									
								
								sbapp/kivymd/tools/patterns/MVC/Model/base_model.py_tmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,33 @@ | ||||
| # The model implements the observer pattern. This means that the class must | ||||
| # support adding, removing, and alerting observers. In this case, the model is | ||||
| # completely independent of controllers and views. It is important that all | ||||
| # registered observers implement a specific method that will be called by the | ||||
| # model when they are notified (in this case, it is the `model_is_changed` | ||||
| # method). For this, observers must be descendants of an abstract class, | ||||
| # inheriting which, the `model_is_changed` method must be overridden. | ||||
| 
 | ||||
| 
 | ||||
| class BaseScreenModel: | ||||
|     """Implements a base class for model modules.""" | ||||
| 
 | ||||
|     _observers = [] | ||||
| 
 | ||||
|     def add_observer(self, observer) -> None: | ||||
|         self._observers.append(observer) | ||||
| 
 | ||||
|     def remove_observer(self, observer) -> None: | ||||
|         self._observers.remove(observer) | ||||
| 
 | ||||
|     def notify_observers(self, name_screen: str) -> None: | ||||
|         """ | ||||
|         Method that will be called by the observer when the model data changes. | ||||
| 
 | ||||
|         :param name_screen: | ||||
|             name of the view for which the method should be called | ||||
|             :meth:`model_is_changed`. | ||||
|         """ | ||||
| 
 | ||||
|         for observer in self._observers: | ||||
|             if observer.name == name_screen: | ||||
|                 observer.model_is_changed() | ||||
|                 break | ||||
							
								
								
									
										53
									
								
								sbapp/kivymd/tools/patterns/MVC/Model/database_firebase.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,53 @@ | ||||
| from __future__ import annotations | ||||
| 
 | ||||
| import socket | ||||
| 
 | ||||
| import requests | ||||
| from firebase import firebase | ||||
| 
 | ||||
| 
 | ||||
| def get_connect(func, host="8.8.8.8", port=53, timeout=3): | ||||
|     """Checks for an active Internet connection.""" | ||||
| 
 | ||||
|     def wrapped(*args): | ||||
|         try: | ||||
|             socket.setdefaulttimeout(timeout) | ||||
|             socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect( | ||||
|                 (host, port) | ||||
|             ) | ||||
|             return func(*args) | ||||
|         except Exception: | ||||
|             return False | ||||
| 
 | ||||
|     return wrapped | ||||
| 
 | ||||
| 
 | ||||
| class DataBase: | ||||
|     """ | ||||
|     Your methods for working with the database should be implemented in this | ||||
|     class. | ||||
|     """ | ||||
| 
 | ||||
|     name = "Firebase" | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.DATABASE_URL = "https://fir-db73a-default-rtdb.firebaseio.com/" | ||||
|         # Address for users collections. | ||||
|         self.USER_DATA = "Userdata" | ||||
|         # RealTime Database attribute. | ||||
|         self.real_time_firebase = firebase.FirebaseApplication( | ||||
|             self.DATABASE_URL, None | ||||
|         ) | ||||
| 
 | ||||
|     @get_connect | ||||
|     def get_data_from_collection(self, name_collection: str) -> dict | bool: | ||||
|         """Returns data of the selected collection from the database.""" | ||||
| 
 | ||||
|         try: | ||||
|             data = self.real_time_firebase.get( | ||||
|                 self.DATABASE_URL, name_collection | ||||
|             ) | ||||
|         except requests.exceptions.ConnectionError: | ||||
|             return False | ||||
| 
 | ||||
|         return data | ||||
							
								
								
									
										134
									
								
								sbapp/kivymd/tools/patterns/MVC/Model/database_restdb.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,134 @@ | ||||
| """ | ||||
| Restdb.io API Wrapper | ||||
| --------------------- | ||||
| 
 | ||||
| This package is an API Wrapper for the website `restdb.io <https://restdb.io>`_, | ||||
| which allows for online databases. | ||||
| """ | ||||
| 
 | ||||
| from __future__ import annotations | ||||
| 
 | ||||
| import json | ||||
| import os | ||||
| import socket | ||||
| 
 | ||||
| import requests | ||||
| 
 | ||||
| 
 | ||||
| def get_connect(func, host="8.8.8.8", port=53, timeout=3): | ||||
|     """Checks for an active Internet connection.""" | ||||
| 
 | ||||
|     def wrapped(*args): | ||||
|         try: | ||||
|             socket.setdefaulttimeout(timeout) | ||||
|             socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect( | ||||
|                 (host, port) | ||||
|             ) | ||||
|             return func(*args) | ||||
|         except Exception: | ||||
|             return False | ||||
| 
 | ||||
|     return wrapped | ||||
| 
 | ||||
| 
 | ||||
| class DataBase: | ||||
|     name = "RestDB" | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         database_url = "https://restdbio-5498.restdb.io" | ||||
|         api_key = "7ce258d66f919d3a891d1166558765f0b4dbd" | ||||
| 
 | ||||
|         self.HEADERS = {"x-apikey": api_key, "Content-Type": "application/json"} | ||||
|         # Address for file collections. | ||||
|         self.USER_MEDIA = f"{database_url}/media" | ||||
|         # Address for users collections. | ||||
|         self.USER_DATA = f"{database_url}/rest/userdata" | ||||
| 
 | ||||
|     @get_connect | ||||
|     def upload_file(self, path_to_file: str) -> dict | bool: | ||||
|         """ | ||||
|         Uploads a file to the database. | ||||
|         You can upload a file to the database only from a paid account. | ||||
|         """ | ||||
| 
 | ||||
|         HEADERS = self.HEADERS.copy() | ||||
|         del HEADERS["Content-Type"] | ||||
|         payload = {} | ||||
|         name_file = os.path.split(path_to_file)[1] | ||||
|         files = [("file", (name_file, open(path_to_file, "rb"), name_file))] | ||||
|         response = requests.post( | ||||
|             url=self.USER_MEDIA, | ||||
|             headers=HEADERS, | ||||
|             data=payload, | ||||
|             files=files, | ||||
|         ) | ||||
| 
 | ||||
|         if response.status_code == 201: | ||||
|             # { | ||||
|             #     "msg":"OK", | ||||
|             #     "uploadid": "ed1bca42334f68d873161641144e57b7", | ||||
|             #     "ids": ["62614fb90f9df71600284aa7"], | ||||
|             # } | ||||
|             json = response.json() | ||||
|             if "msg" in json and json["msg"] == "OK": | ||||
|                 return json | ||||
|         else: | ||||
|             return False | ||||
| 
 | ||||
|     @get_connect | ||||
|     def get_data_from_collection(self, collection_address: str) -> bool | list: | ||||
|         """Returns data of the selected collection from the database.""" | ||||
| 
 | ||||
|         response = requests.get(url=collection_address, headers=self.HEADERS) | ||||
|         if response.status_code != 200: | ||||
|             return False | ||||
|         else: | ||||
|             return response.json() | ||||
| 
 | ||||
|     @get_connect | ||||
|     def delete_doc_from_collection(self, collection_address: str) -> bool: | ||||
|         """ | ||||
|         Delete data of the selected collection from the database. | ||||
| 
 | ||||
|         :param collection_address: "database_url/id_collection". | ||||
|         """ | ||||
| 
 | ||||
|         response = requests.delete(collection_address, headers=self.HEADERS) | ||||
|         if response.status_code == 200: | ||||
|             return True | ||||
|         else: | ||||
|             return False | ||||
| 
 | ||||
|     @get_connect | ||||
|     def add_doc_to_collection( | ||||
|         self, data: dict, collection_address: str | ||||
|     ) -> bool: | ||||
|         """Add collection to the database.""" | ||||
| 
 | ||||
|         response = requests.post( | ||||
|             url=collection_address, | ||||
|             data=json.dumps(data), | ||||
|             headers=self.HEADERS, | ||||
|         ) | ||||
|         if response.status_code == 201: | ||||
|             if "_id" in response.json(): | ||||
|                 return response.json() | ||||
|         else: | ||||
|             return False | ||||
| 
 | ||||
|     @get_connect | ||||
|     def edit_data( | ||||
|         self, collection: dict, collection_address: str, collection_id: str | ||||
|     ) -> bool: | ||||
|         """Modifies data in a collection of data in a database.""" | ||||
| 
 | ||||
|         response = requests.put( | ||||
|             url=f"{collection_address}/{collection_id}", | ||||
|             headers=self.HEADERS, | ||||
|             json=collection, | ||||
|         ) | ||||
|         if response.status_code == 200: | ||||
|             if "_id" in response.json(): | ||||
|                 return True | ||||
|         else: | ||||
|             return False | ||||
| @ -0,0 +1 @@ | ||||
| %s | ||||
							
								
								
									
										0
									
								
								sbapp/kivymd/tools/patterns/MVC/Utility/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										16
									
								
								sbapp/kivymd/tools/patterns/MVC/Utility/observer.py_tmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | ||||
| # Of course, "very flexible Python" allows you to do without an abstract | ||||
| # superclass at all or use the clever exception `NotImplementedError`. In my | ||||
| # opinion, this can negatively affect the architecture of the application. | ||||
| # I would like to point out that using Kivy, one could use the on-signaling | ||||
| # model. In this case, when the state changes, the model will send a signal | ||||
| # that can be received by all attached observers. This approach seems less | ||||
| # universal - you may want to use a different library in the future. | ||||
| 
 | ||||
| 
 | ||||
| class Observer: | ||||
|     """Abstract superclass for all observers.""" | ||||
| 
 | ||||
|     def model_is_changed(self): | ||||
|         """ | ||||
|         The method that will be called on the observer when the model changes. | ||||
|         """ | ||||
| @ -0,0 +1,72 @@ | ||||
| #:import images_path kivymd.images_path | ||||
| #:import colors kivymd.color_definitions.colors | ||||
| #:import get_color_from_hex kivy.utils.get_color_from_hex | ||||
| 
 | ||||
| 
 | ||||
| <%s> | ||||
| 
 | ||||
|     FitImage: | ||||
|         source: | ||||
|             ( \ | ||||
|             f"{images_path}restdb-logo.png" \ | ||||
|             if root.model.database.name == "RestDB" else \ | ||||
|             f"{images_path}firebase-logo.png" \ | ||||
|             ) \ | ||||
|             if hasattr(root.model, "database") else \ | ||||
|             f"{images_path}transparent.png" | ||||
| 
 | ||||
|     MDBoxLayout: | ||||
|         orientation: "vertical" | ||||
| 
 | ||||
|         MDToolbar: | ||||
|             id: toolbar | ||||
|             title: "%s" | ||||
|             right_action_items: [["web", lambda x: %s]] | ||||
|             md_bg_color: | ||||
|                 ( \ | ||||
|                 get_color_from_hex(colors["Yellow"]["700"]) \ | ||||
|                 if root.model.database.name == "Firebase" else \ | ||||
|                 get_color_from_hex(colors["Blue"]["300"]) \ | ||||
|                 ) \ | ||||
|                 if hasattr(root.model, "database") else \ | ||||
|                 app.theme_cls.primary_color | ||||
| 
 | ||||
|         MDFloatLayout: | ||||
| 
 | ||||
|             MDBoxLayout: | ||||
|                 orientation: "vertical" | ||||
|                 adaptive_height: True | ||||
|                 size_hint_x: None | ||||
|                 width: root.width - dp(72) | ||||
|                 radius: 12 | ||||
|                 padding: "12dp" | ||||
|                 md_bg_color: 1, 1, 1, .5 | ||||
|                 pos_hint: {"center_x": .5, "center_y": .5} | ||||
| 
 | ||||
|                 MDLabel: | ||||
|                     id: prev_label | ||||
|                     text: %s | ||||
|                     font_style: "H6" | ||||
|                     adaptive_height: True | ||||
|                     halign: "center" | ||||
|                     color: 1, 1, 1, 1 | ||||
| 
 | ||||
|                 MDBoxLayout: | ||||
|                     orientation: "vertical" | ||||
|                     adaptive_height: True | ||||
|                     padding: "50dp" | ||||
|                     spacing: "20dp" | ||||
| 
 | ||||
|                     MDTextField: | ||||
|                         hint_text: %s | ||||
|                         on_text: root.controller.set_user_data("login", self.text) | ||||
| 
 | ||||
|                     MDTextField: | ||||
|                         hint_text: %s | ||||
|                         on_text: root.controller.set_user_data("password", self.text) | ||||
| 
 | ||||
|     MDFillRoundFlatButton: | ||||
|         text: %s | ||||
|         on_release: root.controller.on_tap_button_login() | ||||
|         pos_hint: {"center_x": .5, "center_y": .1} | ||||
|         md_bg_color: toolbar.md_bg_color | ||||
| @ -0,0 +1,15 @@ | ||||
| %s | ||||
| from View.base_screen import BaseScreenView | ||||
| 
 | ||||
| 
 | ||||
| class %s(BaseScreenView): | ||||
|     """Implements the login start screen in the user application.""" | ||||
| %s | ||||
|     def model_is_changed(self) -> None: | ||||
|         """ | ||||
|         Called whenever any change has occurred in the data model. | ||||
|         The view in this method tracks these changes and updates the UI | ||||
|         according to these changes. | ||||
|         """ | ||||
| 
 | ||||
|         %s | ||||
							
								
								
									
										47
									
								
								sbapp/kivymd/tools/patterns/MVC/View/base_screen.py_tmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,47 @@ | ||||
| from kivy.properties import ObjectProperty | ||||
| 
 | ||||
| from kivymd.app import MDApp | ||||
| from kivymd.theming import ThemableBehavior | ||||
| from kivymd.uix.screen import MDScreen | ||||
| 
 | ||||
| from Utility.observer import Observer | ||||
| 
 | ||||
| 
 | ||||
| class BaseScreenView(ThemableBehavior, MDScreen, Observer): | ||||
|     """ | ||||
|     A base class that implements a visual representation of the model data | ||||
|     :class:`~Model.%s.%s`. | ||||
|     The view class must be inherited from this class. | ||||
|     """ | ||||
| 
 | ||||
|     controller = ObjectProperty() | ||||
|     """ | ||||
|     Controller object - :class:`~Controller.%s.%s`. | ||||
| 
 | ||||
|     :attr:`controller` is an :class:`~kivy.properties.ObjectProperty` | ||||
|     and defaults to `None`. | ||||
|     """ | ||||
| 
 | ||||
|     model = ObjectProperty() | ||||
|     """ | ||||
|     Model object - :class:`~Model.%s.%s`. | ||||
| 
 | ||||
|     :attr:`model` is an :class:`~kivy.properties.ObjectProperty` | ||||
|     and defaults to `None`. | ||||
|     """ | ||||
| 
 | ||||
|     manager_screens = ObjectProperty() | ||||
|     """ | ||||
|     Screen manager object - :class:`~kivy.uix.screenmanager.ScreenManager`. | ||||
| 
 | ||||
|     :attr:`manager_screens` is an :class:`~kivy.properties.ObjectProperty` | ||||
|     and defaults to `None`. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, **kw): | ||||
|         super().__init__(**kw) | ||||
|         # Often you need to get access to the application object from the view | ||||
|         # class. You can do this using this attribute. | ||||
|         self.app = MDApp.get_running_app() | ||||
|         # Adding a view class as observer. | ||||
|         self.model.add_observer(self) | ||||
							
								
								
									
										13
									
								
								sbapp/kivymd/tools/patterns/MVC/View/screens.py_tmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | ||||
| # The screens dictionary contains the objects of the models and controllers | ||||
| # of the screens of the application. | ||||
| 
 | ||||
| from Model.%s import %s | ||||
| 
 | ||||
| from Controller.%s import %s | ||||
| 
 | ||||
| screens = { | ||||
|     %s: { | ||||
|         "model": %s, | ||||
|         "controller": %s, | ||||
|     }, | ||||
| } | ||||