first commit

This commit is contained in:
Yura 2024-09-15 15:12:16 +03:00
commit 417e54da96
5696 changed files with 900003 additions and 0 deletions

View 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.
This library is a fork of the `KivyMD project
<https://gitlab.com/kivymd/KivyMD>`_. We found the strength and brought this
project to a new level.
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.2.0"
"""KivyMD version."""
release = False
if "READTHEDOCS" not in os.environ:
kivy.require("2.2.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)
Logger.warning(
"KivyMD: "
"Version 1.2.0 is deprecated and is no longer supported. "
"Use KivyMD version 2.0.0 from the master branch "
"(pip install https://github.com/kivymd/KivyMD/archive/master.zip)"
)
import kivymd.factory_registers # NOQA
import kivymd.font_definitions # NOQA
from kivymd.tools.packaging.pyinstaller import hooks_path # NOQA

View file

@ -0,0 +1,5 @@
# THIS FILE IS GENERATED FROM KIVYMD SETUP.PY
__version__ = '1.2.0'
__hash__ = 'Unknown'
__short_hash__ = 'Unknown'
__date__ = '2024-09-15'

View file

@ -0,0 +1,147 @@
"""
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.clock import Clock
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.properties import ObjectProperty, StringProperty
from kivymd.theming import ThemeManager
class FpsMonitoring:
"""Implements a monitor to display the current FPS in the toolbar."""
def fps_monitor_start(self, anchor: str = "top") -> None:
"""Adds a monitor to the main application window."""
def add_monitor(*args):
from kivy.core.window import Window
from kivymd.utils.fpsmonitor import FpsMonitor
monitor = FpsMonitor(anchor=anchor)
monitor.start()
Window.add_widget(monitor)
Clock.schedule_once(add_monitor)
class MDApp(App, FpsMonitoring):
"""
Application class, see :class:`~kivy.app.App` class documentation for more
information.
"""
icon = StringProperty("kivymd/images/logo/kivymd-icon-512.png")
"""
See :attr:`~kivy.app.App.icon` attribute for more information.
.. versionadded:: 1.1.0
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
adn default to `kivymd/images/logo/kivymd-icon-512.png`.
"""
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)

View 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(title=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],
"title": 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."""

View file

@ -0,0 +1,4 @@
"""
Effects
=======
"""

View file

@ -0,0 +1 @@
from .fadingedge import FadingEdgeEffect

View file

@ -0,0 +1,200 @@
"""
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 dont 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,
),
)
self.update_canvas(
self, self.size, rectangle_top, rectangle_bottom, i
)
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)

View file

@ -0,0 +1 @@
from .roulettescroll import RouletteScrollEffect

View 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")

View file

@ -0,0 +1 @@
from .stiffscroll import StiffScrollEffect

View 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)

View file

@ -0,0 +1,111 @@
"""
Register KivyMD widgets to use without import.
"""
from kivy.factory import Factory
register = Factory.register
register("MDSegmentedButton", module="kivymd.uix.segmentedbutton")
register("MDSegmentedButtonItem", module="kivymd.uix.segmentedbutton")
register("MDScrollView", module="kivymd.uix.scrollview")
register("MDRecycleView", module="kivymd.uix.recycleview")
register("MDResponsiveLayout", module="kivymd.uix.responsivelayout")
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("MDNavigationRailItem", module="kivymd.uix.navigationrail")
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.uix.fitimage")
register("MDBackdrop", module="kivymd.uix.backdrop")
register("MDBanner", module="kivymd.uix.banner")
register("MDTooltip", module="kivymd.uix.tooltip")
register("MDBottomSheet", module="kivymd.uix.bottomsheet")
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("MDSmartTile", 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("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("MDTextFieldRect", module="kivymd.uix.textfield")
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")

View 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
"""

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

View file

@ -0,0 +1,64 @@
"""
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
# Elevation.
SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION = 1
FILE_MANAGER_TOP_APP_BAR_ELEVATION = 1
FLOATING_ACTION_BUTTON_M2_ELEVATION = 1
FLOATING_ACTION_BUTTON_M3_ELEVATION = 0.5
CARD_STYLE_ELEVATED_M3_ELEVATION = 0.5
CARD_STYLE_OUTLINED_FILLED_M3_ELEVATION = 0
DATA_TABLE_ELEVATION = 4
DROP_DOWN_MENU_ELEVATION = 2
TOP_APP_BAR_ELEVATION = 2
SNACK_BAR_ELEVATION = 2
# Shadow softness.
RAISED_BUTTON_SOFTNESS = 4
FLOATING_ACTION_BUTTON_M3_SOFTNESS = 0
DATA_TABLE_SOFTNESS = 12
DROP_DOWN_MENU_SOFTNESS = 6
# Shadow offset.
RAISED_BUTTON_OFFSET = (0, -2)
FLOATING_ACTION_BUTTON_M2_OFFSET = (0, -1)
FLOATING_ACTION_BUTTON_M3_OFFSET = (0, -2)
DATA_TABLE_OFFSET = (0, -2)
DROP_DOWN_MENU_OFFSET = (0, -2)
SNACK_BAR_OFFSET = (0, -2)
TOUCH_TARGET_HEIGHT = dp(48)

File diff suppressed because it is too large Load diff

View 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"
)

View 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

View 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

View 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()

View file

@ -0,0 +1,3 @@
__all__ = ("toast",)
from .kivytoast import toast

View 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), markup=True, 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)

View 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()

View file

@ -0,0 +1,550 @@
"""
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
scroll = Factory.MDScrollView(
scroll_y=0, md_bg_color=get_color_from_hex("#e50000")
)
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)

View 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())

View 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],
)
),
)
)

View 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

View 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

View file

@ -0,0 +1,12 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-27 18:54+0300\n"
"PO-Revision-Date: 2019-09-22 23:12+0300\n"
"Last-Translator: KivyMD library https://github.com/kivymd/KivyMD\n"
"Language-Team: KivyMD library https://github.com/kivymd/KivyMD\n"
"Language: Russian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

View file

@ -0,0 +1,12 @@
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-27 18:54+0300\n"
"PO-Revision-Date: 2019-09-22 23:12+0300\n"
"Last-Translator: KivyMD library https://github.com/kivymd/KivyMD\n"
"Language-Team: KivyMD library https://github.com/kivymd/KivyMD\n"
"Language: English\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

View file

@ -0,0 +1 @@
# This package is for additional application modules.

View file

@ -0,0 +1,49 @@
import gettext
from kivy.lang import Observable
class Translation(Observable):
"""Original source - https://github.com/tito/kivy-gettext-example."""
observers = []
def __init__(self, defaultlang, domian, resource_dir):
super().__init__()
self.ugettext = None
self.lang = defaultlang
self.domian = domian
self.resource_dir = resource_dir
self.switch_lang(self.lang)
def _(self, text):
return self.ugettext(text)
def fbind(self, name, func, args, **kwargs):
if name == "_":
self.observers.append((func, args, kwargs))
else:
return super().fbind(name, func, *args, **kwargs)
def funbind(self, name, func, args, **kwargs):
if name == "_":
key = (func, args, kwargs)
if key in self.observers:
self.observers.remove(key)
else:
return super().funbind(name, func, *args, **kwargs)
def switch_lang(self, lang):
locales = gettext.translation(
self.domian, self.resource_dir, languages=[lang]
)
try:
self.ugettext = locales.ugettext
except AttributeError:
self.ugettext = locales.gettext
for func, largs, kwargs in self.observers:
try:
func(largs, None, None)
except ReferenceError:
pass

View file

@ -0,0 +1,18 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-27 18:54+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

Some files were not shown because too many files have changed in this diff Show more