diff --git a/kivymd/__init__.py b/kivymd/__init__.py deleted file mode 100644 index 977a1d5..0000000 --- a/kivymd/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -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 `_ -a framework for cross-platform, touch-enabled graphical applications. -The project's goal is to approximate Google's `Material Design spec -`_ as close as possible without -sacrificing ease of use or application performance. - -This library is a fork of the `KivyMD project -`_ 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__ = "0.104.2.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.""" - -_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 diff --git a/kivymd/_version.py b/kivymd/_version.py deleted file mode 100644 index a1660d7..0000000 --- a/kivymd/_version.py +++ /dev/null @@ -1,5 +0,0 @@ -# THIS FILE IS GENERATED FROM KIVYMD SETUP.PY -__version__ = "0.104.2.dev0" -__hash__ = "Unknown" -__short_hash__ = "Unknown" -__date__ = "2021-04-17" diff --git a/kivymd/app.py b/kivymd/app.py deleted file mode 100644 index a108a4e..0000000 --- a/kivymd/app.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -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 = ''' - Screen: - - 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",) - -from kivy.app import App -from kivy.properties import ObjectProperty - -from kivymd.theming import ThemeManager - - -class FpsMonitoring: - """Adds a monitor to display the current FPS in the toolbar.""" - - def fps_monitor_start(self): - from kivy.core.window import Window - - from kivymd.utils.fpsmonitor import FpsMonitor - - monitor = FpsMonitor() - monitor.start() - Window.add_widget(monitor) - - -class MDApp(App, FpsMonitoring): - 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() diff --git a/kivymd/color_definitions.py b/kivymd/color_definitions.py deleted file mode 100644 index c3afb98..0000000 --- a/kivymd/color_definitions.py +++ /dev/null @@ -1,947 +0,0 @@ -""" -Themes/Color Definitions -======================== - -.. seealso:: - - `Material Design spec, The color system `_ - - `Material Design spec, The color tool `_ - -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 -`_. - -To demonstrate the shades of the palette, you can run the following code: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.boxlayout import BoxLayout - from kivy.utils import get_color_from_hex - from kivy.properties import ListProperty, StringProperty - - from kivymd.color_definitions import colors - from kivymd.uix.tab import MDTabsBase - - demo = ''' - - orientation: 'vertical' - - MDToolbar: - 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 - - ScrollView: - - MDList: - id: box - - - : - size_hint_y: None - height: "42dp" - - canvas: - Color: - rgba: root.color - Rectangle: - size: self.size - pos: self.pos - - MDLabel: - text: root.text - halign: "center" - - - : - ''' - - from kivy.factory import Factory - from kivymd.app import MDApp - - - class Tab(BoxLayout, MDTabsBase): - pass - - - class ItemColor(BoxLayout): - 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.box.clear_widgets() - for value_color in colors[tab_text]: - self.screen.ids.box.add_widget( - ItemColor( - color=get_color_from_hex(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.""" diff --git a/kivymd/factory_registers.py b/kivymd/factory_registers.py deleted file mode 100644 index b94b069..0000000 --- a/kivymd/factory_registers.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -Register KivyMD widgets to use without import -""" - -from kivy.factory import Factory - -r = Factory.register -r("MDNavigationRail", module="kivymd.uix.navigationrail") -r("MDSwiper", module="kivymd.uix.swiper") -r("MDCarousel", module="kivymd.uix.carousel") -r("MDFloatLayout", module="kivymd.uix.floatlayout") -r("MDScreen", module="kivymd.uix.screen") -r("MDBoxLayout", module="kivymd.uix.boxlayout") -r("MDRelativeLayout", module="kivymd.uix.relativelayout") -r("MDGridLayout", module="kivymd.uix.gridlayout") -r("MDStackLayout", module="kivymd.uix.stacklayout") -r("MDExpansionPanel", module="kivymd.uix.expansionpanel") -r("MDExpansionPanelOneLine", module="kivymd.uix.expansionpanel") -r("MDExpansionPanelTwoLine", module="kivymd.uix.expansionpanel") -r("MDExpansionPanelThreeLine", module="kivymd.uix.expansionpanel") -r("FitImage", module="kivymd.utils.fitimage") -r("MDBackdrop", module="kivymd.uix.backdrop") -r("MDBanner", module="kivymd.uix.banner") -r("MDTooltip", module="kivymd.uix.tooltip") -r("MDBottomNavigation", module="kivymd.uix.bottomnavigation") -r("MDBottomNavigationItem", module="kivymd.uix.bottomnavigation") -r("MDToggleButton", module="kivymd.uix.behaviors.toggle_behavior") -r("MDFloatingActionButtonSpeedDial", module="kivymd.uix.button") -r("MDIconButton", module="kivymd.uix.button") -r("MDRoundImageButton", module="kivymd.uix.button") -r("MDFlatButton", module="kivymd.uix.button") -r("MDRaisedButton", module="kivymd.uix.button") -r("MDFloatingActionButton", module="kivymd.uix.button") -r("MDRectangleFlatButton", module="kivymd.uix.button") -r("MDTextButton", module="kivymd.uix.button") -r("MDCustomRoundIconButton", module="kivymd.uix.button") -r("MDRoundFlatButton", module="kivymd.uix.button") -r("MDFillRoundFlatButton", module="kivymd.uix.button") -r("MDRectangleFlatIconButton", module="kivymd.uix.button") -r("MDRoundFlatIconButton", module="kivymd.uix.button") -r("MDFillRoundFlatIconButton", module="kivymd.uix.button") -r("MDCard", module="kivymd.uix.card") -r("MDSeparator", module="kivymd.uix.card") -r("MDSelectionList", module="kivymd.uix.selection") -r("MDChip", module="kivymd.uix.chip") -r("MDChooseChip", module="kivymd.uix.chip") -r("SmartTile", module="kivymd.uix.imagelist") -r("SmartTileWithLabel", module="kivymd.uix.imagelist") -r("SmartTileWithStar", module="kivymd.uix.imagelist") -r("MDLabel", module="kivymd.uix.label") -r("MDIcon", module="kivymd.uix.label") -r("MDList", module="kivymd.uix.list") -r("ILeftBody", module="kivymd.uix.list") -r("ILeftBodyTouch", module="kivymd.uix.list") -r("IRightBody", module="kivymd.uix.list") -r("IRightBodyTouch", module="kivymd.uix.list") -r("ContainerSupport", module="kivymd.uix.list") -r("OneLineListItem", module="kivymd.uix.list") -r("TwoLineListItem", module="kivymd.uix.list") -r("ThreeLineListItem", module="kivymd.uix.list") -r("OneLineAvatarListItem", module="kivymd.uix.list") -r("TwoLineAvatarListItem", module="kivymd.uix.list") -r("ThreeLineAvatarListItem", module="kivymd.uix.list") -r("OneLineIconListItem", module="kivymd.uix.list") -r("TwoLineIconListItem", module="kivymd.uix.list") -r("ThreeLineIconListItem", module="kivymd.uix.list") -r("OneLineRightIconListItem", module="kivymd.uix.list") -r("TwoLineRightIconListItem", module="kivymd.uix.list") -r("ThreeLineRightIconListItem", module="kivymd.uix.list") -r("OneLineAvatarIconListItem", module="kivymd.uix.list") -r("TwoLineAvatarIconListItem", module="kivymd.uix.list") -r("ThreeLineAvatarIconListItem", module="kivymd.uix.list") -r("HoverBehavior", module="kivymd.uix.behaviors.hover_behavior") -r("FocusBehavior", module="kivymd.uix.behaviors.focus_behavior") -r("MagicBehavior", module="kivymd.uix.behaviors.magic_behavior") -r("MDNavigationDrawer", module="kivymd.uix.navigationdrawer") -r("MDNavigationLayout", module="kivymd.uix.navigationdrawer") -r("MDProgressBar", module="kivymd.uix.progressbar") -r("MDScrollViewRefreshLayout", module="kivymd.uix.refreshlayout") -r("MDCheckbox", module="kivymd.uix.selectioncontrol") -r("MDSwitch", module="kivymd.uix.selectioncontrol") -r("MDSlider", module="kivymd.uix.slider") -r("MDSpinner", module="kivymd.uix.spinner") -r("MDTabs", module="kivymd.uix.tab") -r("MDTextField", module="kivymd.uix.textfield") -r("MDTextFieldRound", module="kivymd.uix.textfield") -r("MDTextFieldRect", module="kivymd.uix.textfield") -r("MDToolbar", module="kivymd.uix.toolbar") -r("MDBottomAppBar", module="kivymd.uix.toolbar") -r("MDDropDownItem", module="kivymd.uix.dropdownitem") -r("MDCircularLayout", module="kivymd.uix.circularlayout") diff --git a/kivymd/font_definitions.py b/kivymd/font_definitions.py deleted file mode 100644 index e7cc8e2..0000000 --- a/kivymd/font_definitions.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Themes/Font Definitions -======================= - -.. seealso:: - - `Material Design spec, The type system `_ -""" - -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 -""" diff --git a/kivymd/fonts/Roboto-Black.ttf b/kivymd/fonts/Roboto-Black.ttf deleted file mode 100644 index 689fe5c..0000000 Binary files a/kivymd/fonts/Roboto-Black.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-BlackItalic.ttf b/kivymd/fonts/Roboto-BlackItalic.ttf deleted file mode 100644 index 0b4e0ee..0000000 Binary files a/kivymd/fonts/Roboto-BlackItalic.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-Bold.ttf b/kivymd/fonts/Roboto-Bold.ttf deleted file mode 100644 index d3f01ad..0000000 Binary files a/kivymd/fonts/Roboto-Bold.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-BoldItalic.ttf b/kivymd/fonts/Roboto-BoldItalic.ttf deleted file mode 100644 index 41cc1e7..0000000 Binary files a/kivymd/fonts/Roboto-BoldItalic.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-Italic.ttf b/kivymd/fonts/Roboto-Italic.ttf deleted file mode 100644 index 6a1cee5..0000000 Binary files a/kivymd/fonts/Roboto-Italic.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-Light.ttf b/kivymd/fonts/Roboto-Light.ttf deleted file mode 100644 index 219063a..0000000 Binary files a/kivymd/fonts/Roboto-Light.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-LightItalic.ttf b/kivymd/fonts/Roboto-LightItalic.ttf deleted file mode 100644 index 0e81e87..0000000 Binary files a/kivymd/fonts/Roboto-LightItalic.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-Medium.ttf b/kivymd/fonts/Roboto-Medium.ttf deleted file mode 100644 index 1a7f3b0..0000000 Binary files a/kivymd/fonts/Roboto-Medium.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-MediumItalic.ttf b/kivymd/fonts/Roboto-MediumItalic.ttf deleted file mode 100644 index 0030295..0000000 Binary files a/kivymd/fonts/Roboto-MediumItalic.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-Regular.ttf b/kivymd/fonts/Roboto-Regular.ttf deleted file mode 100644 index 2c97eea..0000000 Binary files a/kivymd/fonts/Roboto-Regular.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-Thin.ttf b/kivymd/fonts/Roboto-Thin.ttf deleted file mode 100644 index b74a4fd..0000000 Binary files a/kivymd/fonts/Roboto-Thin.ttf and /dev/null differ diff --git a/kivymd/fonts/Roboto-ThinItalic.ttf b/kivymd/fonts/Roboto-ThinItalic.ttf deleted file mode 100644 index dd0ddb8..0000000 Binary files a/kivymd/fonts/Roboto-ThinItalic.ttf and /dev/null differ diff --git a/kivymd/fonts/materialdesignicons-webfont.ttf b/kivymd/fonts/materialdesignicons-webfont.ttf deleted file mode 100644 index 5545629..0000000 Binary files a/kivymd/fonts/materialdesignicons-webfont.ttf and /dev/null differ diff --git a/kivymd/icon_definitions.py b/kivymd/icon_definitions.py deleted file mode 100644 index a36ea60..0000000 --- a/kivymd/icon_definitions.py +++ /dev/null @@ -1,6081 +0,0 @@ -""" -Themes/Icon Definitions -======================= - -.. seealso:: - - `Material Design Icons `_ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/material-icons.png - :align: center - -List of icons from materialdesignicons.com. These expanded material design -icons are maintained by Austin Andrews (Templarian on Github). - -LAST UPDATED: Version 5.9.55 - -To preview the icons and their names, you can use the following application: ----------------------------------------------------------------------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - from kivy.uix.screenmanager import Screen - - from kivymd.icon_definitions import md_icons - from kivymd.app import MDApp - from kivymd.uix.list import OneLineIconListItem - - - Builder.load_string( - ''' - #:import images_path kivymd.images_path - - - : - - IconLeftWidget: - icon: root.icon - - - : - - MDBoxLayout: - orientation: 'vertical' - spacing: dp(10) - padding: dp(20) - - MDBoxLayout: - adaptive_height: True - - MDIconButton: - icon: 'magnify' - - MDTextField: - id: search_field - hint_text: 'Search icon' - on_text: root.set_list_md_icons(self.text, True) - - RecycleView: - id: rv - key_viewclass: 'viewclass' - key_size: 'height' - - RecycleBoxLayout: - padding: dp(10) - default_size: None, dp(48) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - orientation: 'vertical' - ''' - ) - - - class CustomOneLineIconListItem(OneLineIconListItem): - icon = StringProperty() - - - class PreviousMDIcons(Screen): - - def set_list_md_icons(self, text="", search=False): - '''Builds a list of icons for the screen MDIcons.''' - - def add_icon_item(name_icon): - self.ids.rv.data.append( - { - "viewclass": "CustomOneLineIconListItem", - "icon": name_icon, - "text": name_icon, - "callback": lambda x: x, - } - ) - - self.ids.rv.data = [] - for name_icon in md_icons.keys(): - if search: - if text in name_icon: - add_icon_item(name_icon) - else: - add_icon_item(name_icon) - - - class MainApp(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = PreviousMDIcons() - - def build(self): - return self.screen - - def on_start(self): - self.screen.set_list_md_icons() - - - MainApp().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icons-previous.gif - :align: center - -""" - -# THIS DICTIONARY WAS GENERATED BY ``kivymd/tools/update_icons.py`` -md_icons = { - "ab-testing": "\U000F01C9", - "abacus": "\U000F16E0", - "abjad-arabic": "\U000F1328", - "abjad-hebrew": "\U000F1329", - "abugida-devanagari": "\U000F132A", - "abugida-thai": "\U000F132B", - "access-point": "\U000F0003", - "access-point-check": "\U000F1538", - "access-point-minus": "\U000F1539", - "access-point-network": "\U000F0002", - "access-point-network-off": "\U000F0BE1", - "access-point-off": "\U000F1511", - "access-point-plus": "\U000F153A", - "access-point-remove": "\U000F153B", - "account": "\U000F0004", - "account-alert": "\U000F0005", - "account-alert-outline": "\U000F0B50", - "account-arrow-left": "\U000F0B51", - "account-arrow-left-outline": "\U000F0B52", - "account-arrow-right": "\U000F0B53", - "account-arrow-right-outline": "\U000F0B54", - "account-box": "\U000F0006", - "account-box-multiple": "\U000F0934", - "account-box-multiple-outline": "\U000F100A", - "account-box-outline": "\U000F0007", - "account-cancel": "\U000F12DF", - "account-cancel-outline": "\U000F12E0", - "account-cash": "\U000F1097", - "account-cash-outline": "\U000F1098", - "account-check": "\U000F0008", - "account-check-outline": "\U000F0BE2", - "account-child": "\U000F0A89", - "account-child-circle": "\U000F0A8A", - "account-child-outline": "\U000F10C8", - "account-circle": "\U000F0009", - "account-circle-outline": "\U000F0B55", - "account-clock": "\U000F0B56", - "account-clock-outline": "\U000F0B57", - "account-cog": "\U000F1370", - "account-cog-outline": "\U000F1371", - "account-convert": "\U000F000A", - "account-convert-outline": "\U000F1301", - "account-cowboy-hat": "\U000F0E9B", - "account-details": "\U000F0631", - "account-details-outline": "\U000F1372", - "account-edit": "\U000F06BC", - "account-edit-outline": "\U000F0FFB", - "account-group": "\U000F0849", - "account-group-outline": "\U000F0B58", - "account-hard-hat": "\U000F05B5", - "account-heart": "\U000F0899", - "account-heart-outline": "\U000F0BE3", - "account-key": "\U000F000B", - "account-key-outline": "\U000F0BE4", - "account-lock": "\U000F115E", - "account-lock-outline": "\U000F115F", - "account-minus": "\U000F000D", - "account-minus-outline": "\U000F0AEC", - "account-multiple": "\U000F000E", - "account-multiple-check": "\U000F08C5", - "account-multiple-check-outline": "\U000F11FE", - "account-multiple-minus": "\U000F05D3", - "account-multiple-minus-outline": "\U000F0BE5", - "account-multiple-outline": "\U000F000F", - "account-multiple-plus": "\U000F0010", - "account-multiple-plus-outline": "\U000F0800", - "account-multiple-remove": "\U000F120A", - "account-multiple-remove-outline": "\U000F120B", - "account-music": "\U000F0803", - "account-music-outline": "\U000F0CE9", - "account-network": "\U000F0011", - "account-network-outline": "\U000F0BE6", - "account-off": "\U000F0012", - "account-off-outline": "\U000F0BE7", - "account-outline": "\U000F0013", - "account-plus": "\U000F0014", - "account-plus-outline": "\U000F0801", - "account-question": "\U000F0B59", - "account-question-outline": "\U000F0B5A", - "account-reactivate": "\U000F152B", - "account-reactivate-outline": "\U000F152C", - "account-remove": "\U000F0015", - "account-remove-outline": "\U000F0AED", - "account-search": "\U000F0016", - "account-search-outline": "\U000F0935", - "account-settings": "\U000F0630", - "account-settings-outline": "\U000F10C9", - "account-star": "\U000F0017", - "account-star-outline": "\U000F0BE8", - "account-supervisor": "\U000F0A8B", - "account-supervisor-circle": "\U000F0A8C", - "account-supervisor-circle-outline": "\U000F14EC", - "account-supervisor-outline": "\U000F112D", - "account-switch": "\U000F0019", - "account-switch-outline": "\U000F04CB", - "account-tie": "\U000F0CE3", - "account-tie-outline": "\U000F10CA", - "account-tie-voice": "\U000F1308", - "account-tie-voice-off": "\U000F130A", - "account-tie-voice-off-outline": "\U000F130B", - "account-tie-voice-outline": "\U000F1309", - "account-voice": "\U000F05CB", - "adjust": "\U000F001A", - "adobe": "\U000F0936", - "adobe-acrobat": "\U000F0F9D", - "air-conditioner": "\U000F001B", - "air-filter": "\U000F0D43", - "air-horn": "\U000F0DAC", - "air-humidifier": "\U000F1099", - "air-humidifier-off": "\U000F1466", - "air-purifier": "\U000F0D44", - "airbag": "\U000F0BE9", - "airballoon": "\U000F001C", - "airballoon-outline": "\U000F100B", - "airplane": "\U000F001D", - "airplane-landing": "\U000F05D4", - "airplane-off": "\U000F001E", - "airplane-takeoff": "\U000F05D5", - "airport": "\U000F084B", - "alarm": "\U000F0020", - "alarm-bell": "\U000F078E", - "alarm-check": "\U000F0021", - "alarm-light": "\U000F078F", - "alarm-light-off": "\U000F171E", - "alarm-light-off-outline": "\U000F171F", - "alarm-light-outline": "\U000F0BEA", - "alarm-multiple": "\U000F0022", - "alarm-note": "\U000F0E71", - "alarm-note-off": "\U000F0E72", - "alarm-off": "\U000F0023", - "alarm-panel": "\U000F15C4", - "alarm-panel-outline": "\U000F15C5", - "alarm-plus": "\U000F0024", - "alarm-snooze": "\U000F068E", - "album": "\U000F0025", - "alert": "\U000F0026", - "alert-box": "\U000F0027", - "alert-box-outline": "\U000F0CE4", - "alert-circle": "\U000F0028", - "alert-circle-check": "\U000F11ED", - "alert-circle-check-outline": "\U000F11EE", - "alert-circle-outline": "\U000F05D6", - "alert-decagram": "\U000F06BD", - "alert-decagram-outline": "\U000F0CE5", - "alert-minus": "\U000F14BB", - "alert-minus-outline": "\U000F14BE", - "alert-octagon": "\U000F0029", - "alert-octagon-outline": "\U000F0CE6", - "alert-octagram": "\U000F0767", - "alert-octagram-outline": "\U000F0CE7", - "alert-outline": "\U000F002A", - "alert-plus": "\U000F14BA", - "alert-plus-outline": "\U000F14BD", - "alert-remove": "\U000F14BC", - "alert-remove-outline": "\U000F14BF", - "alert-rhombus": "\U000F11CE", - "alert-rhombus-outline": "\U000F11CF", - "alien": "\U000F089A", - "alien-outline": "\U000F10CB", - "align-horizontal-center": "\U000F11C3", - "align-horizontal-left": "\U000F11C2", - "align-horizontal-right": "\U000F11C4", - "align-vertical-bottom": "\U000F11C5", - "align-vertical-center": "\U000F11C6", - "align-vertical-top": "\U000F11C7", - "all-inclusive": "\U000F06BE", - "allergy": "\U000F1258", - "alpha": "\U000F002B", - "alpha-a": "\U000F0AEE", - "alpha-a-box": "\U000F0B08", - "alpha-a-box-outline": "\U000F0BEB", - "alpha-a-circle": "\U000F0BEC", - "alpha-a-circle-outline": "\U000F0BED", - "alpha-b": "\U000F0AEF", - "alpha-b-box": "\U000F0B09", - "alpha-b-box-outline": "\U000F0BEE", - "alpha-b-circle": "\U000F0BEF", - "alpha-b-circle-outline": "\U000F0BF0", - "alpha-c": "\U000F0AF0", - "alpha-c-box": "\U000F0B0A", - "alpha-c-box-outline": "\U000F0BF1", - "alpha-c-circle": "\U000F0BF2", - "alpha-c-circle-outline": "\U000F0BF3", - "alpha-d": "\U000F0AF1", - "alpha-d-box": "\U000F0B0B", - "alpha-d-box-outline": "\U000F0BF4", - "alpha-d-circle": "\U000F0BF5", - "alpha-d-circle-outline": "\U000F0BF6", - "alpha-e": "\U000F0AF2", - "alpha-e-box": "\U000F0B0C", - "alpha-e-box-outline": "\U000F0BF7", - "alpha-e-circle": "\U000F0BF8", - "alpha-e-circle-outline": "\U000F0BF9", - "alpha-f": "\U000F0AF3", - "alpha-f-box": "\U000F0B0D", - "alpha-f-box-outline": "\U000F0BFA", - "alpha-f-circle": "\U000F0BFB", - "alpha-f-circle-outline": "\U000F0BFC", - "alpha-g": "\U000F0AF4", - "alpha-g-box": "\U000F0B0E", - "alpha-g-box-outline": "\U000F0BFD", - "alpha-g-circle": "\U000F0BFE", - "alpha-g-circle-outline": "\U000F0BFF", - "alpha-h": "\U000F0AF5", - "alpha-h-box": "\U000F0B0F", - "alpha-h-box-outline": "\U000F0C00", - "alpha-h-circle": "\U000F0C01", - "alpha-h-circle-outline": "\U000F0C02", - "alpha-i": "\U000F0AF6", - "alpha-i-box": "\U000F0B10", - "alpha-i-box-outline": "\U000F0C03", - "alpha-i-circle": "\U000F0C04", - "alpha-i-circle-outline": "\U000F0C05", - "alpha-j": "\U000F0AF7", - "alpha-j-box": "\U000F0B11", - "alpha-j-box-outline": "\U000F0C06", - "alpha-j-circle": "\U000F0C07", - "alpha-j-circle-outline": "\U000F0C08", - "alpha-k": "\U000F0AF8", - "alpha-k-box": "\U000F0B12", - "alpha-k-box-outline": "\U000F0C09", - "alpha-k-circle": "\U000F0C0A", - "alpha-k-circle-outline": "\U000F0C0B", - "alpha-l": "\U000F0AF9", - "alpha-l-box": "\U000F0B13", - "alpha-l-box-outline": "\U000F0C0C", - "alpha-l-circle": "\U000F0C0D", - "alpha-l-circle-outline": "\U000F0C0E", - "alpha-m": "\U000F0AFA", - "alpha-m-box": "\U000F0B14", - "alpha-m-box-outline": "\U000F0C0F", - "alpha-m-circle": "\U000F0C10", - "alpha-m-circle-outline": "\U000F0C11", - "alpha-n": "\U000F0AFB", - "alpha-n-box": "\U000F0B15", - "alpha-n-box-outline": "\U000F0C12", - "alpha-n-circle": "\U000F0C13", - "alpha-n-circle-outline": "\U000F0C14", - "alpha-o": "\U000F0AFC", - "alpha-o-box": "\U000F0B16", - "alpha-o-box-outline": "\U000F0C15", - "alpha-o-circle": "\U000F0C16", - "alpha-o-circle-outline": "\U000F0C17", - "alpha-p": "\U000F0AFD", - "alpha-p-box": "\U000F0B17", - "alpha-p-box-outline": "\U000F0C18", - "alpha-p-circle": "\U000F0C19", - "alpha-p-circle-outline": "\U000F0C1A", - "alpha-q": "\U000F0AFE", - "alpha-q-box": "\U000F0B18", - "alpha-q-box-outline": "\U000F0C1B", - "alpha-q-circle": "\U000F0C1C", - "alpha-q-circle-outline": "\U000F0C1D", - "alpha-r": "\U000F0AFF", - "alpha-r-box": "\U000F0B19", - "alpha-r-box-outline": "\U000F0C1E", - "alpha-r-circle": "\U000F0C1F", - "alpha-r-circle-outline": "\U000F0C20", - "alpha-s": "\U000F0B00", - "alpha-s-box": "\U000F0B1A", - "alpha-s-box-outline": "\U000F0C21", - "alpha-s-circle": "\U000F0C22", - "alpha-s-circle-outline": "\U000F0C23", - "alpha-t": "\U000F0B01", - "alpha-t-box": "\U000F0B1B", - "alpha-t-box-outline": "\U000F0C24", - "alpha-t-circle": "\U000F0C25", - "alpha-t-circle-outline": "\U000F0C26", - "alpha-u": "\U000F0B02", - "alpha-u-box": "\U000F0B1C", - "alpha-u-box-outline": "\U000F0C27", - "alpha-u-circle": "\U000F0C28", - "alpha-u-circle-outline": "\U000F0C29", - "alpha-v": "\U000F0B03", - "alpha-v-box": "\U000F0B1D", - "alpha-v-box-outline": "\U000F0C2A", - "alpha-v-circle": "\U000F0C2B", - "alpha-v-circle-outline": "\U000F0C2C", - "alpha-w": "\U000F0B04", - "alpha-w-box": "\U000F0B1E", - "alpha-w-box-outline": "\U000F0C2D", - "alpha-w-circle": "\U000F0C2E", - "alpha-w-circle-outline": "\U000F0C2F", - "alpha-x": "\U000F0B05", - "alpha-x-box": "\U000F0B1F", - "alpha-x-box-outline": "\U000F0C30", - "alpha-x-circle": "\U000F0C31", - "alpha-x-circle-outline": "\U000F0C32", - "alpha-y": "\U000F0B06", - "alpha-y-box": "\U000F0B20", - "alpha-y-box-outline": "\U000F0C33", - "alpha-y-circle": "\U000F0C34", - "alpha-y-circle-outline": "\U000F0C35", - "alpha-z": "\U000F0B07", - "alpha-z-box": "\U000F0B21", - "alpha-z-box-outline": "\U000F0C36", - "alpha-z-circle": "\U000F0C37", - "alpha-z-circle-outline": "\U000F0C38", - "alphabet-aurebesh": "\U000F132C", - "alphabet-cyrillic": "\U000F132D", - "alphabet-greek": "\U000F132E", - "alphabet-latin": "\U000F132F", - "alphabet-piqad": "\U000F1330", - "alphabet-tengwar": "\U000F1337", - "alphabetical": "\U000F002C", - "alphabetical-off": "\U000F100C", - "alphabetical-variant": "\U000F100D", - "alphabetical-variant-off": "\U000F100E", - "altimeter": "\U000F05D7", - "amazon": "\U000F002D", - "amazon-alexa": "\U000F08C6", - "ambulance": "\U000F002F", - "ammunition": "\U000F0CE8", - "ampersand": "\U000F0A8D", - "amplifier": "\U000F0030", - "amplifier-off": "\U000F11B5", - "anchor": "\U000F0031", - "android": "\U000F0032", - "android-auto": "\U000F0A8E", - "android-debug-bridge": "\U000F0033", - "android-messages": "\U000F0D45", - "android-studio": "\U000F0034", - "angle-acute": "\U000F0937", - "angle-obtuse": "\U000F0938", - "angle-right": "\U000F0939", - "angular": "\U000F06B2", - "angularjs": "\U000F06BF", - "animation": "\U000F05D8", - "animation-outline": "\U000F0A8F", - "animation-play": "\U000F093A", - "animation-play-outline": "\U000F0A90", - "ansible": "\U000F109A", - "antenna": "\U000F1119", - "anvil": "\U000F089B", - "apache-kafka": "\U000F100F", - "api": "\U000F109B", - "api-off": "\U000F1257", - "apple": "\U000F0035", - "apple-airplay": "\U000F001F", - "apple-finder": "\U000F0036", - "apple-icloud": "\U000F0038", - "apple-ios": "\U000F0037", - "apple-keyboard-caps": "\U000F0632", - "apple-keyboard-command": "\U000F0633", - "apple-keyboard-control": "\U000F0634", - "apple-keyboard-option": "\U000F0635", - "apple-keyboard-shift": "\U000F0636", - "apple-safari": "\U000F0039", - "application": "\U000F0614", - "application-cog": "\U000F1577", - "application-export": "\U000F0DAD", - "application-import": "\U000F0DAE", - "application-settings": "\U000F1555", - "approximately-equal": "\U000F0F9E", - "approximately-equal-box": "\U000F0F9F", - "apps": "\U000F003B", - "apps-box": "\U000F0D46", - "arch": "\U000F08C7", - "archive": "\U000F003C", - "archive-alert": "\U000F14FD", - "archive-alert-outline": "\U000F14FE", - "archive-arrow-down": "\U000F1259", - "archive-arrow-down-outline": "\U000F125A", - "archive-arrow-up": "\U000F125B", - "archive-arrow-up-outline": "\U000F125C", - "archive-outline": "\U000F120E", - "arm-flex": "\U000F0FD7", - "arm-flex-outline": "\U000F0FD6", - "arrange-bring-forward": "\U000F003D", - "arrange-bring-to-front": "\U000F003E", - "arrange-send-backward": "\U000F003F", - "arrange-send-to-back": "\U000F0040", - "arrow-all": "\U000F0041", - "arrow-bottom-left": "\U000F0042", - "arrow-bottom-left-bold-outline": "\U000F09B7", - "arrow-bottom-left-thick": "\U000F09B8", - "arrow-bottom-left-thin-circle-outline": "\U000F1596", - "arrow-bottom-right": "\U000F0043", - "arrow-bottom-right-bold-outline": "\U000F09B9", - "arrow-bottom-right-thick": "\U000F09BA", - "arrow-bottom-right-thin-circle-outline": "\U000F1595", - "arrow-collapse": "\U000F0615", - "arrow-collapse-all": "\U000F0044", - "arrow-collapse-down": "\U000F0792", - "arrow-collapse-horizontal": "\U000F084C", - "arrow-collapse-left": "\U000F0793", - "arrow-collapse-right": "\U000F0794", - "arrow-collapse-up": "\U000F0795", - "arrow-collapse-vertical": "\U000F084D", - "arrow-decision": "\U000F09BB", - "arrow-decision-auto": "\U000F09BC", - "arrow-decision-auto-outline": "\U000F09BD", - "arrow-decision-outline": "\U000F09BE", - "arrow-down": "\U000F0045", - "arrow-down-bold": "\U000F072E", - "arrow-down-bold-box": "\U000F072F", - "arrow-down-bold-box-outline": "\U000F0730", - "arrow-down-bold-circle": "\U000F0047", - "arrow-down-bold-circle-outline": "\U000F0048", - "arrow-down-bold-hexagon-outline": "\U000F0049", - "arrow-down-bold-outline": "\U000F09BF", - "arrow-down-box": "\U000F06C0", - "arrow-down-circle": "\U000F0CDB", - "arrow-down-circle-outline": "\U000F0CDC", - "arrow-down-drop-circle": "\U000F004A", - "arrow-down-drop-circle-outline": "\U000F004B", - "arrow-down-thick": "\U000F0046", - "arrow-down-thin-circle-outline": "\U000F1599", - "arrow-expand": "\U000F0616", - "arrow-expand-all": "\U000F004C", - "arrow-expand-down": "\U000F0796", - "arrow-expand-horizontal": "\U000F084E", - "arrow-expand-left": "\U000F0797", - "arrow-expand-right": "\U000F0798", - "arrow-expand-up": "\U000F0799", - "arrow-expand-vertical": "\U000F084F", - "arrow-horizontal-lock": "\U000F115B", - "arrow-left": "\U000F004D", - "arrow-left-bold": "\U000F0731", - "arrow-left-bold-box": "\U000F0732", - "arrow-left-bold-box-outline": "\U000F0733", - "arrow-left-bold-circle": "\U000F004F", - "arrow-left-bold-circle-outline": "\U000F0050", - "arrow-left-bold-hexagon-outline": "\U000F0051", - "arrow-left-bold-outline": "\U000F09C0", - "arrow-left-box": "\U000F06C1", - "arrow-left-circle": "\U000F0CDD", - "arrow-left-circle-outline": "\U000F0CDE", - "arrow-left-drop-circle": "\U000F0052", - "arrow-left-drop-circle-outline": "\U000F0053", - "arrow-left-right": "\U000F0E73", - "arrow-left-right-bold": "\U000F0E74", - "arrow-left-right-bold-outline": "\U000F09C1", - "arrow-left-thick": "\U000F004E", - "arrow-left-thin-circle-outline": "\U000F159A", - "arrow-right": "\U000F0054", - "arrow-right-bold": "\U000F0734", - "arrow-right-bold-box": "\U000F0735", - "arrow-right-bold-box-outline": "\U000F0736", - "arrow-right-bold-circle": "\U000F0056", - "arrow-right-bold-circle-outline": "\U000F0057", - "arrow-right-bold-hexagon-outline": "\U000F0058", - "arrow-right-bold-outline": "\U000F09C2", - "arrow-right-box": "\U000F06C2", - "arrow-right-circle": "\U000F0CDF", - "arrow-right-circle-outline": "\U000F0CE0", - "arrow-right-drop-circle": "\U000F0059", - "arrow-right-drop-circle-outline": "\U000F005A", - "arrow-right-thick": "\U000F0055", - "arrow-right-thin-circle-outline": "\U000F1598", - "arrow-split-horizontal": "\U000F093B", - "arrow-split-vertical": "\U000F093C", - "arrow-top-left": "\U000F005B", - "arrow-top-left-bold-outline": "\U000F09C3", - "arrow-top-left-bottom-right": "\U000F0E75", - "arrow-top-left-bottom-right-bold": "\U000F0E76", - "arrow-top-left-thick": "\U000F09C4", - "arrow-top-left-thin-circle-outline": "\U000F1593", - "arrow-top-right": "\U000F005C", - "arrow-top-right-bold-outline": "\U000F09C5", - "arrow-top-right-bottom-left": "\U000F0E77", - "arrow-top-right-bottom-left-bold": "\U000F0E78", - "arrow-top-right-thick": "\U000F09C6", - "arrow-top-right-thin-circle-outline": "\U000F1594", - "arrow-up": "\U000F005D", - "arrow-up-bold": "\U000F0737", - "arrow-up-bold-box": "\U000F0738", - "arrow-up-bold-box-outline": "\U000F0739", - "arrow-up-bold-circle": "\U000F005F", - "arrow-up-bold-circle-outline": "\U000F0060", - "arrow-up-bold-hexagon-outline": "\U000F0061", - "arrow-up-bold-outline": "\U000F09C7", - "arrow-up-box": "\U000F06C3", - "arrow-up-circle": "\U000F0CE1", - "arrow-up-circle-outline": "\U000F0CE2", - "arrow-up-down": "\U000F0E79", - "arrow-up-down-bold": "\U000F0E7A", - "arrow-up-down-bold-outline": "\U000F09C8", - "arrow-up-drop-circle": "\U000F0062", - "arrow-up-drop-circle-outline": "\U000F0063", - "arrow-up-thick": "\U000F005E", - "arrow-up-thin-circle-outline": "\U000F1597", - "arrow-vertical-lock": "\U000F115C", - "artstation": "\U000F0B5B", - "aspect-ratio": "\U000F0A24", - "assistant": "\U000F0064", - "asterisk": "\U000F06C4", - "at": "\U000F0065", - "atlassian": "\U000F0804", - "atm": "\U000F0D47", - "atom": "\U000F0768", - "atom-variant": "\U000F0E7B", - "attachment": "\U000F0066", - "audio-video": "\U000F093D", - "audio-video-off": "\U000F11B6", - "augmented-reality": "\U000F0850", - "auto-download": "\U000F137E", - "auto-fix": "\U000F0068", - "auto-upload": "\U000F0069", - "autorenew": "\U000F006A", - "av-timer": "\U000F006B", - "aws": "\U000F0E0F", - "axe": "\U000F08C8", - "axis": "\U000F0D48", - "axis-arrow": "\U000F0D49", - "axis-arrow-info": "\U000F140E", - "axis-arrow-lock": "\U000F0D4A", - "axis-lock": "\U000F0D4B", - "axis-x-arrow": "\U000F0D4C", - "axis-x-arrow-lock": "\U000F0D4D", - "axis-x-rotate-clockwise": "\U000F0D4E", - "axis-x-rotate-counterclockwise": "\U000F0D4F", - "axis-x-y-arrow-lock": "\U000F0D50", - "axis-y-arrow": "\U000F0D51", - "axis-y-arrow-lock": "\U000F0D52", - "axis-y-rotate-clockwise": "\U000F0D53", - "axis-y-rotate-counterclockwise": "\U000F0D54", - "axis-z-arrow": "\U000F0D55", - "axis-z-arrow-lock": "\U000F0D56", - "axis-z-rotate-clockwise": "\U000F0D57", - "axis-z-rotate-counterclockwise": "\U000F0D58", - "babel": "\U000F0A25", - "baby": "\U000F006C", - "baby-bottle": "\U000F0F39", - "baby-bottle-outline": "\U000F0F3A", - "baby-buggy": "\U000F13E0", - "baby-carriage": "\U000F068F", - "baby-carriage-off": "\U000F0FA0", - "baby-face": "\U000F0E7C", - "baby-face-outline": "\U000F0E7D", - "backburger": "\U000F006D", - "backspace": "\U000F006E", - "backspace-outline": "\U000F0B5C", - "backspace-reverse": "\U000F0E7E", - "backspace-reverse-outline": "\U000F0E7F", - "backup-restore": "\U000F006F", - "bacteria": "\U000F0ED5", - "bacteria-outline": "\U000F0ED6", - "badge-account": "\U000F0DA7", - "badge-account-alert": "\U000F0DA8", - "badge-account-alert-outline": "\U000F0DA9", - "badge-account-horizontal": "\U000F0E0D", - "badge-account-horizontal-outline": "\U000F0E0E", - "badge-account-outline": "\U000F0DAA", - "badminton": "\U000F0851", - "bag-carry-on": "\U000F0F3B", - "bag-carry-on-check": "\U000F0D65", - "bag-carry-on-off": "\U000F0F3C", - "bag-checked": "\U000F0F3D", - "bag-personal": "\U000F0E10", - "bag-personal-off": "\U000F0E11", - "bag-personal-off-outline": "\U000F0E12", - "bag-personal-outline": "\U000F0E13", - "bag-suitcase": "\U000F158B", - "bag-suitcase-off": "\U000F158D", - "bag-suitcase-off-outline": "\U000F158E", - "bag-suitcase-outline": "\U000F158C", - "baguette": "\U000F0F3E", - "balloon": "\U000F0A26", - "ballot": "\U000F09C9", - "ballot-outline": "\U000F09CA", - "ballot-recount": "\U000F0C39", - "ballot-recount-outline": "\U000F0C3A", - "bandage": "\U000F0DAF", - "bandcamp": "\U000F0675", - "bank": "\U000F0070", - "bank-check": "\U000F1655", - "bank-minus": "\U000F0DB0", - "bank-off": "\U000F1656", - "bank-off-outline": "\U000F1657", - "bank-outline": "\U000F0E80", - "bank-plus": "\U000F0DB1", - "bank-remove": "\U000F0DB2", - "bank-transfer": "\U000F0A27", - "bank-transfer-in": "\U000F0A28", - "bank-transfer-out": "\U000F0A29", - "barcode": "\U000F0071", - "barcode-off": "\U000F1236", - "barcode-scan": "\U000F0072", - "barley": "\U000F0073", - "barley-off": "\U000F0B5D", - "barn": "\U000F0B5E", - "barrel": "\U000F0074", - "baseball": "\U000F0852", - "baseball-bat": "\U000F0853", - "baseball-diamond": "\U000F15EC", - "baseball-diamond-outline": "\U000F15ED", - "bash": "\U000F1183", - "basket": "\U000F0076", - "basket-fill": "\U000F0077", - "basket-minus": "\U000F1523", - "basket-minus-outline": "\U000F1524", - "basket-off": "\U000F1525", - "basket-off-outline": "\U000F1526", - "basket-outline": "\U000F1181", - "basket-plus": "\U000F1527", - "basket-plus-outline": "\U000F1528", - "basket-remove": "\U000F1529", - "basket-remove-outline": "\U000F152A", - "basket-unfill": "\U000F0078", - "basketball": "\U000F0806", - "basketball-hoop": "\U000F0C3B", - "basketball-hoop-outline": "\U000F0C3C", - "bat": "\U000F0B5F", - "battery": "\U000F0079", - "battery-10": "\U000F007A", - "battery-10-bluetooth": "\U000F093E", - "battery-20": "\U000F007B", - "battery-20-bluetooth": "\U000F093F", - "battery-30": "\U000F007C", - "battery-30-bluetooth": "\U000F0940", - "battery-40": "\U000F007D", - "battery-40-bluetooth": "\U000F0941", - "battery-50": "\U000F007E", - "battery-50-bluetooth": "\U000F0942", - "battery-60": "\U000F007F", - "battery-60-bluetooth": "\U000F0943", - "battery-70": "\U000F0080", - "battery-70-bluetooth": "\U000F0944", - "battery-80": "\U000F0081", - "battery-80-bluetooth": "\U000F0945", - "battery-90": "\U000F0082", - "battery-90-bluetooth": "\U000F0946", - "battery-alert": "\U000F0083", - "battery-alert-bluetooth": "\U000F0947", - "battery-alert-variant": "\U000F10CC", - "battery-alert-variant-outline": "\U000F10CD", - "battery-bluetooth": "\U000F0948", - "battery-bluetooth-variant": "\U000F0949", - "battery-charging": "\U000F0084", - "battery-charging-10": "\U000F089C", - "battery-charging-100": "\U000F0085", - "battery-charging-20": "\U000F0086", - "battery-charging-30": "\U000F0087", - "battery-charging-40": "\U000F0088", - "battery-charging-50": "\U000F089D", - "battery-charging-60": "\U000F0089", - "battery-charging-70": "\U000F089E", - "battery-charging-80": "\U000F008A", - "battery-charging-90": "\U000F008B", - "battery-charging-high": "\U000F12A6", - "battery-charging-low": "\U000F12A4", - "battery-charging-medium": "\U000F12A5", - "battery-charging-outline": "\U000F089F", - "battery-charging-wireless": "\U000F0807", - "battery-charging-wireless-10": "\U000F0808", - "battery-charging-wireless-20": "\U000F0809", - "battery-charging-wireless-30": "\U000F080A", - "battery-charging-wireless-40": "\U000F080B", - "battery-charging-wireless-50": "\U000F080C", - "battery-charging-wireless-60": "\U000F080D", - "battery-charging-wireless-70": "\U000F080E", - "battery-charging-wireless-80": "\U000F080F", - "battery-charging-wireless-90": "\U000F0810", - "battery-charging-wireless-alert": "\U000F0811", - "battery-charging-wireless-outline": "\U000F0812", - "battery-heart": "\U000F120F", - "battery-heart-outline": "\U000F1210", - "battery-heart-variant": "\U000F1211", - "battery-high": "\U000F12A3", - "battery-low": "\U000F12A1", - "battery-medium": "\U000F12A2", - "battery-minus": "\U000F008C", - "battery-negative": "\U000F008D", - "battery-off": "\U000F125D", - "battery-off-outline": "\U000F125E", - "battery-outline": "\U000F008E", - "battery-plus": "\U000F008F", - "battery-positive": "\U000F0090", - "battery-unknown": "\U000F0091", - "battery-unknown-bluetooth": "\U000F094A", - "battlenet": "\U000F0B60", - "beach": "\U000F0092", - "beaker": "\U000F0CEA", - "beaker-alert": "\U000F1229", - "beaker-alert-outline": "\U000F122A", - "beaker-check": "\U000F122B", - "beaker-check-outline": "\U000F122C", - "beaker-minus": "\U000F122D", - "beaker-minus-outline": "\U000F122E", - "beaker-outline": "\U000F0690", - "beaker-plus": "\U000F122F", - "beaker-plus-outline": "\U000F1230", - "beaker-question": "\U000F1231", - "beaker-question-outline": "\U000F1232", - "beaker-remove": "\U000F1233", - "beaker-remove-outline": "\U000F1234", - "bed": "\U000F02E3", - "bed-double": "\U000F0FD4", - "bed-double-outline": "\U000F0FD3", - "bed-empty": "\U000F08A0", - "bed-king": "\U000F0FD2", - "bed-king-outline": "\U000F0FD1", - "bed-outline": "\U000F0099", - "bed-queen": "\U000F0FD0", - "bed-queen-outline": "\U000F0FDB", - "bed-single": "\U000F106D", - "bed-single-outline": "\U000F106E", - "bee": "\U000F0FA1", - "bee-flower": "\U000F0FA2", - "beehive-off-outline": "\U000F13ED", - "beehive-outline": "\U000F10CE", - "beekeeper": "\U000F14E2", - "beer": "\U000F0098", - "beer-outline": "\U000F130C", - "bell": "\U000F009A", - "bell-alert": "\U000F0D59", - "bell-alert-outline": "\U000F0E81", - "bell-cancel": "\U000F13E7", - "bell-cancel-outline": "\U000F13E8", - "bell-check": "\U000F11E5", - "bell-check-outline": "\U000F11E6", - "bell-circle": "\U000F0D5A", - "bell-circle-outline": "\U000F0D5B", - "bell-minus": "\U000F13E9", - "bell-minus-outline": "\U000F13EA", - "bell-off": "\U000F009B", - "bell-off-outline": "\U000F0A91", - "bell-outline": "\U000F009C", - "bell-plus": "\U000F009D", - "bell-plus-outline": "\U000F0A92", - "bell-remove": "\U000F13EB", - "bell-remove-outline": "\U000F13EC", - "bell-ring": "\U000F009E", - "bell-ring-outline": "\U000F009F", - "bell-sleep": "\U000F00A0", - "bell-sleep-outline": "\U000F0A93", - "beta": "\U000F00A1", - "betamax": "\U000F09CB", - "biathlon": "\U000F0E14", - "bicycle": "\U000F109C", - "bicycle-basket": "\U000F1235", - "bicycle-electric": "\U000F15B4", - "bicycle-penny-farthing": "\U000F15E9", - "bike": "\U000F00A3", - "bike-fast": "\U000F111F", - "billboard": "\U000F1010", - "billiards": "\U000F0B61", - "billiards-rack": "\U000F0B62", - "binoculars": "\U000F00A5", - "bio": "\U000F00A6", - "biohazard": "\U000F00A7", - "bird": "\U000F15C6", - "bitbucket": "\U000F00A8", - "bitcoin": "\U000F0813", - "black-mesa": "\U000F00A9", - "blender": "\U000F0CEB", - "blender-software": "\U000F00AB", - "blinds": "\U000F00AC", - "blinds-open": "\U000F1011", - "block-helper": "\U000F00AD", - "blogger": "\U000F00AE", - "blood-bag": "\U000F0CEC", - "bluetooth": "\U000F00AF", - "bluetooth-audio": "\U000F00B0", - "bluetooth-connect": "\U000F00B1", - "bluetooth-off": "\U000F00B2", - "bluetooth-settings": "\U000F00B3", - "bluetooth-transfer": "\U000F00B4", - "blur": "\U000F00B5", - "blur-linear": "\U000F00B6", - "blur-off": "\U000F00B7", - "blur-radial": "\U000F00B8", - "bolnisi-cross": "\U000F0CED", - "bolt": "\U000F0DB3", - "bomb": "\U000F0691", - "bomb-off": "\U000F06C5", - "bone": "\U000F00B9", - "book": "\U000F00BA", - "book-account": "\U000F13AD", - "book-account-outline": "\U000F13AE", - "book-alert": "\U000F167C", - "book-alert-outline": "\U000F167D", - "book-alphabet": "\U000F061D", - "book-arrow-down": "\U000F167E", - "book-arrow-down-outline": "\U000F167F", - "book-arrow-left": "\U000F1680", - "book-arrow-left-outline": "\U000F1681", - "book-arrow-right": "\U000F1682", - "book-arrow-right-outline": "\U000F1683", - "book-arrow-up": "\U000F1684", - "book-arrow-up-outline": "\U000F1685", - "book-cancel": "\U000F1686", - "book-cancel-outline": "\U000F1687", - "book-check": "\U000F14F3", - "book-check-outline": "\U000F14F4", - "book-clock": "\U000F1688", - "book-clock-outline": "\U000F1689", - "book-cog": "\U000F168A", - "book-cog-outline": "\U000F168B", - "book-cross": "\U000F00A2", - "book-edit": "\U000F168C", - "book-edit-outline": "\U000F168D", - "book-education": "\U000F16C9", - "book-education-outline": "\U000F16CA", - "book-information-variant": "\U000F106F", - "book-lock": "\U000F079A", - "book-lock-open": "\U000F079B", - "book-lock-open-outline": "\U000F168E", - "book-lock-outline": "\U000F168F", - "book-marker": "\U000F1690", - "book-marker-outline": "\U000F1691", - "book-minus": "\U000F05D9", - "book-minus-multiple": "\U000F0A94", - "book-minus-multiple-outline": "\U000F090B", - "book-minus-outline": "\U000F1692", - "book-multiple": "\U000F00BB", - "book-multiple-outline": "\U000F0436", - "book-music": "\U000F0067", - "book-music-outline": "\U000F1693", - "book-off": "\U000F1694", - "book-off-outline": "\U000F1695", - "book-open": "\U000F00BD", - "book-open-blank-variant": "\U000F00BE", - "book-open-outline": "\U000F0B63", - "book-open-page-variant": "\U000F05DA", - "book-open-page-variant-outline": "\U000F15D6", - "book-open-variant": "\U000F14F7", - "book-outline": "\U000F0B64", - "book-play": "\U000F0E82", - "book-play-outline": "\U000F0E83", - "book-plus": "\U000F05DB", - "book-plus-multiple": "\U000F0A95", - "book-plus-multiple-outline": "\U000F0ADE", - "book-plus-outline": "\U000F1696", - "book-refresh": "\U000F1697", - "book-refresh-outline": "\U000F1698", - "book-remove": "\U000F0A97", - "book-remove-multiple": "\U000F0A96", - "book-remove-multiple-outline": "\U000F04CA", - "book-remove-outline": "\U000F1699", - "book-search": "\U000F0E84", - "book-search-outline": "\U000F0E85", - "book-settings": "\U000F169A", - "book-settings-outline": "\U000F169B", - "book-sync": "\U000F169C", - "book-sync-outline": "\U000F16C8", - "book-variant": "\U000F00BF", - "book-variant-multiple": "\U000F00BC", - "bookmark": "\U000F00C0", - "bookmark-check": "\U000F00C1", - "bookmark-check-outline": "\U000F137B", - "bookmark-minus": "\U000F09CC", - "bookmark-minus-outline": "\U000F09CD", - "bookmark-multiple": "\U000F0E15", - "bookmark-multiple-outline": "\U000F0E16", - "bookmark-music": "\U000F00C2", - "bookmark-music-outline": "\U000F1379", - "bookmark-off": "\U000F09CE", - "bookmark-off-outline": "\U000F09CF", - "bookmark-outline": "\U000F00C3", - "bookmark-plus": "\U000F00C5", - "bookmark-plus-outline": "\U000F00C4", - "bookmark-remove": "\U000F00C6", - "bookmark-remove-outline": "\U000F137A", - "bookshelf": "\U000F125F", - "boom-gate": "\U000F0E86", - "boom-gate-alert": "\U000F0E87", - "boom-gate-alert-outline": "\U000F0E88", - "boom-gate-down": "\U000F0E89", - "boom-gate-down-outline": "\U000F0E8A", - "boom-gate-outline": "\U000F0E8B", - "boom-gate-up": "\U000F0E8C", - "boom-gate-up-outline": "\U000F0E8D", - "boombox": "\U000F05DC", - "boomerang": "\U000F10CF", - "bootstrap": "\U000F06C6", - "border-all": "\U000F00C7", - "border-all-variant": "\U000F08A1", - "border-bottom": "\U000F00C8", - "border-bottom-variant": "\U000F08A2", - "border-color": "\U000F00C9", - "border-horizontal": "\U000F00CA", - "border-inside": "\U000F00CB", - "border-left": "\U000F00CC", - "border-left-variant": "\U000F08A3", - "border-none": "\U000F00CD", - "border-none-variant": "\U000F08A4", - "border-outside": "\U000F00CE", - "border-right": "\U000F00CF", - "border-right-variant": "\U000F08A5", - "border-style": "\U000F00D0", - "border-top": "\U000F00D1", - "border-top-variant": "\U000F08A6", - "border-vertical": "\U000F00D2", - "bottle-soda": "\U000F1070", - "bottle-soda-classic": "\U000F1071", - "bottle-soda-classic-outline": "\U000F1363", - "bottle-soda-outline": "\U000F1072", - "bottle-tonic": "\U000F112E", - "bottle-tonic-outline": "\U000F112F", - "bottle-tonic-plus": "\U000F1130", - "bottle-tonic-plus-outline": "\U000F1131", - "bottle-tonic-skull": "\U000F1132", - "bottle-tonic-skull-outline": "\U000F1133", - "bottle-wine": "\U000F0854", - "bottle-wine-outline": "\U000F1310", - "bow-tie": "\U000F0678", - "bowl": "\U000F028E", - "bowl-mix": "\U000F0617", - "bowl-mix-outline": "\U000F02E4", - "bowl-outline": "\U000F02A9", - "bowling": "\U000F00D3", - "box": "\U000F00D4", - "box-cutter": "\U000F00D5", - "box-cutter-off": "\U000F0B4A", - "box-shadow": "\U000F0637", - "boxing-glove": "\U000F0B65", - "braille": "\U000F09D0", - "brain": "\U000F09D1", - "bread-slice": "\U000F0CEE", - "bread-slice-outline": "\U000F0CEF", - "bridge": "\U000F0618", - "briefcase": "\U000F00D6", - "briefcase-account": "\U000F0CF0", - "briefcase-account-outline": "\U000F0CF1", - "briefcase-check": "\U000F00D7", - "briefcase-check-outline": "\U000F131E", - "briefcase-clock": "\U000F10D0", - "briefcase-clock-outline": "\U000F10D1", - "briefcase-download": "\U000F00D8", - "briefcase-download-outline": "\U000F0C3D", - "briefcase-edit": "\U000F0A98", - "briefcase-edit-outline": "\U000F0C3E", - "briefcase-minus": "\U000F0A2A", - "briefcase-minus-outline": "\U000F0C3F", - "briefcase-off": "\U000F1658", - "briefcase-off-outline": "\U000F1659", - "briefcase-outline": "\U000F0814", - "briefcase-plus": "\U000F0A2B", - "briefcase-plus-outline": "\U000F0C40", - "briefcase-remove": "\U000F0A2C", - "briefcase-remove-outline": "\U000F0C41", - "briefcase-search": "\U000F0A2D", - "briefcase-search-outline": "\U000F0C42", - "briefcase-upload": "\U000F00D9", - "briefcase-upload-outline": "\U000F0C43", - "briefcase-variant": "\U000F1494", - "briefcase-variant-off": "\U000F165A", - "briefcase-variant-off-outline": "\U000F165B", - "briefcase-variant-outline": "\U000F1495", - "brightness-1": "\U000F00DA", - "brightness-2": "\U000F00DB", - "brightness-3": "\U000F00DC", - "brightness-4": "\U000F00DD", - "brightness-5": "\U000F00DE", - "brightness-6": "\U000F00DF", - "brightness-7": "\U000F00E0", - "brightness-auto": "\U000F00E1", - "brightness-percent": "\U000F0CF2", - "broadcast": "\U000F1720", - "broadcast-off": "\U000F1721", - "broom": "\U000F00E2", - "brush": "\U000F00E3", - "bucket": "\U000F1415", - "bucket-outline": "\U000F1416", - "buddhism": "\U000F094B", - "buffer": "\U000F0619", - "buffet": "\U000F0578", - "bug": "\U000F00E4", - "bug-check": "\U000F0A2E", - "bug-check-outline": "\U000F0A2F", - "bug-outline": "\U000F0A30", - "bugle": "\U000F0DB4", - "bulldozer": "\U000F0B22", - "bullet": "\U000F0CF3", - "bulletin-board": "\U000F00E5", - "bullhorn": "\U000F00E6", - "bullhorn-outline": "\U000F0B23", - "bullseye": "\U000F05DD", - "bullseye-arrow": "\U000F08C9", - "bulma": "\U000F12E7", - "bunk-bed": "\U000F1302", - "bunk-bed-outline": "\U000F0097", - "bus": "\U000F00E7", - "bus-alert": "\U000F0A99", - "bus-articulated-end": "\U000F079C", - "bus-articulated-front": "\U000F079D", - "bus-clock": "\U000F08CA", - "bus-double-decker": "\U000F079E", - "bus-marker": "\U000F1212", - "bus-multiple": "\U000F0F3F", - "bus-school": "\U000F079F", - "bus-side": "\U000F07A0", - "bus-stop": "\U000F1012", - "bus-stop-covered": "\U000F1013", - "bus-stop-uncovered": "\U000F1014", - "butterfly": "\U000F1589", - "butterfly-outline": "\U000F158A", - "cable-data": "\U000F1394", - "cached": "\U000F00E8", - "cactus": "\U000F0DB5", - "cake": "\U000F00E9", - "cake-layered": "\U000F00EA", - "cake-variant": "\U000F00EB", - "calculator": "\U000F00EC", - "calculator-variant": "\U000F0A9A", - "calculator-variant-outline": "\U000F15A6", - "calendar": "\U000F00ED", - "calendar-account": "\U000F0ED7", - "calendar-account-outline": "\U000F0ED8", - "calendar-alert": "\U000F0A31", - "calendar-arrow-left": "\U000F1134", - "calendar-arrow-right": "\U000F1135", - "calendar-blank": "\U000F00EE", - "calendar-blank-multiple": "\U000F1073", - "calendar-blank-outline": "\U000F0B66", - "calendar-check": "\U000F00EF", - "calendar-check-outline": "\U000F0C44", - "calendar-clock": "\U000F00F0", - "calendar-clock-outline": "\U000F16E1", - "calendar-cursor": "\U000F157B", - "calendar-edit": "\U000F08A7", - "calendar-end": "\U000F166C", - "calendar-export": "\U000F0B24", - "calendar-heart": "\U000F09D2", - "calendar-import": "\U000F0B25", - "calendar-lock": "\U000F1641", - "calendar-lock-outline": "\U000F1642", - "calendar-minus": "\U000F0D5C", - "calendar-month": "\U000F0E17", - "calendar-month-outline": "\U000F0E18", - "calendar-multiple": "\U000F00F1", - "calendar-multiple-check": "\U000F00F2", - "calendar-multiselect": "\U000F0A32", - "calendar-outline": "\U000F0B67", - "calendar-plus": "\U000F00F3", - "calendar-question": "\U000F0692", - "calendar-range": "\U000F0679", - "calendar-range-outline": "\U000F0B68", - "calendar-refresh": "\U000F01E1", - "calendar-refresh-outline": "\U000F0203", - "calendar-remove": "\U000F00F4", - "calendar-remove-outline": "\U000F0C45", - "calendar-search": "\U000F094C", - "calendar-star": "\U000F09D3", - "calendar-start": "\U000F166D", - "calendar-sync": "\U000F0E8E", - "calendar-sync-outline": "\U000F0E8F", - "calendar-text": "\U000F00F5", - "calendar-text-outline": "\U000F0C46", - "calendar-today": "\U000F00F6", - "calendar-week": "\U000F0A33", - "calendar-week-begin": "\U000F0A34", - "calendar-weekend": "\U000F0ED9", - "calendar-weekend-outline": "\U000F0EDA", - "call-made": "\U000F00F7", - "call-merge": "\U000F00F8", - "call-missed": "\U000F00F9", - "call-received": "\U000F00FA", - "call-split": "\U000F00FB", - "camcorder": "\U000F00FC", - "camcorder-off": "\U000F00FF", - "camera": "\U000F0100", - "camera-account": "\U000F08CB", - "camera-burst": "\U000F0693", - "camera-control": "\U000F0B69", - "camera-enhance": "\U000F0101", - "camera-enhance-outline": "\U000F0B6A", - "camera-flip": "\U000F15D9", - "camera-flip-outline": "\U000F15DA", - "camera-front": "\U000F0102", - "camera-front-variant": "\U000F0103", - "camera-gopro": "\U000F07A1", - "camera-image": "\U000F08CC", - "camera-iris": "\U000F0104", - "camera-metering-center": "\U000F07A2", - "camera-metering-matrix": "\U000F07A3", - "camera-metering-partial": "\U000F07A4", - "camera-metering-spot": "\U000F07A5", - "camera-off": "\U000F05DF", - "camera-outline": "\U000F0D5D", - "camera-party-mode": "\U000F0105", - "camera-plus": "\U000F0EDB", - "camera-plus-outline": "\U000F0EDC", - "camera-rear": "\U000F0106", - "camera-rear-variant": "\U000F0107", - "camera-retake": "\U000F0E19", - "camera-retake-outline": "\U000F0E1A", - "camera-switch": "\U000F0108", - "camera-switch-outline": "\U000F084A", - "camera-timer": "\U000F0109", - "camera-wireless": "\U000F0DB6", - "camera-wireless-outline": "\U000F0DB7", - "campfire": "\U000F0EDD", - "cancel": "\U000F073A", - "candle": "\U000F05E2", - "candycane": "\U000F010A", - "cannabis": "\U000F07A6", - "cannabis-off": "\U000F166E", - "caps-lock": "\U000F0A9B", - "car": "\U000F010B", - "car-2-plus": "\U000F1015", - "car-3-plus": "\U000F1016", - "car-arrow-left": "\U000F13B2", - "car-arrow-right": "\U000F13B3", - "car-back": "\U000F0E1B", - "car-battery": "\U000F010C", - "car-brake-abs": "\U000F0C47", - "car-brake-alert": "\U000F0C48", - "car-brake-hold": "\U000F0D5E", - "car-brake-parking": "\U000F0D5F", - "car-brake-retarder": "\U000F1017", - "car-child-seat": "\U000F0FA3", - "car-clutch": "\U000F1018", - "car-cog": "\U000F13CC", - "car-connected": "\U000F010D", - "car-convertible": "\U000F07A7", - "car-coolant-level": "\U000F1019", - "car-cruise-control": "\U000F0D60", - "car-defrost-front": "\U000F0D61", - "car-defrost-rear": "\U000F0D62", - "car-door": "\U000F0B6B", - "car-door-lock": "\U000F109D", - "car-electric": "\U000F0B6C", - "car-electric-outline": "\U000F15B5", - "car-emergency": "\U000F160F", - "car-esp": "\U000F0C49", - "car-estate": "\U000F07A8", - "car-hatchback": "\U000F07A9", - "car-info": "\U000F11BE", - "car-key": "\U000F0B6D", - "car-lifted-pickup": "\U000F152D", - "car-light-dimmed": "\U000F0C4A", - "car-light-fog": "\U000F0C4B", - "car-light-high": "\U000F0C4C", - "car-limousine": "\U000F08CD", - "car-multiple": "\U000F0B6E", - "car-off": "\U000F0E1C", - "car-outline": "\U000F14ED", - "car-parking-lights": "\U000F0D63", - "car-pickup": "\U000F07AA", - "car-seat": "\U000F0FA4", - "car-seat-cooler": "\U000F0FA5", - "car-seat-heater": "\U000F0FA6", - "car-settings": "\U000F13CD", - "car-shift-pattern": "\U000F0F40", - "car-side": "\U000F07AB", - "car-sports": "\U000F07AC", - "car-tire-alert": "\U000F0C4D", - "car-traction-control": "\U000F0D64", - "car-turbocharger": "\U000F101A", - "car-wash": "\U000F010E", - "car-windshield": "\U000F101B", - "car-windshield-outline": "\U000F101C", - "carabiner": "\U000F14C0", - "caravan": "\U000F07AD", - "card": "\U000F0B6F", - "card-account-details": "\U000F05D2", - "card-account-details-outline": "\U000F0DAB", - "card-account-details-star": "\U000F02A3", - "card-account-details-star-outline": "\U000F06DB", - "card-account-mail": "\U000F018E", - "card-account-mail-outline": "\U000F0E98", - "card-account-phone": "\U000F0E99", - "card-account-phone-outline": "\U000F0E9A", - "card-bulleted": "\U000F0B70", - "card-bulleted-off": "\U000F0B71", - "card-bulleted-off-outline": "\U000F0B72", - "card-bulleted-outline": "\U000F0B73", - "card-bulleted-settings": "\U000F0B74", - "card-bulleted-settings-outline": "\U000F0B75", - "card-minus": "\U000F1600", - "card-minus-outline": "\U000F1601", - "card-off": "\U000F1602", - "card-off-outline": "\U000F1603", - "card-outline": "\U000F0B76", - "card-plus": "\U000F11FF", - "card-plus-outline": "\U000F1200", - "card-remove": "\U000F1604", - "card-remove-outline": "\U000F1605", - "card-search": "\U000F1074", - "card-search-outline": "\U000F1075", - "card-text": "\U000F0B77", - "card-text-outline": "\U000F0B78", - "cards": "\U000F0638", - "cards-club": "\U000F08CE", - "cards-diamond": "\U000F08CF", - "cards-diamond-outline": "\U000F101D", - "cards-heart": "\U000F08D0", - "cards-outline": "\U000F0639", - "cards-playing-outline": "\U000F063A", - "cards-spade": "\U000F08D1", - "cards-variant": "\U000F06C7", - "carrot": "\U000F010F", - "cart": "\U000F0110", - "cart-arrow-down": "\U000F0D66", - "cart-arrow-right": "\U000F0C4E", - "cart-arrow-up": "\U000F0D67", - "cart-check": "\U000F15EA", - "cart-minus": "\U000F0D68", - "cart-off": "\U000F066B", - "cart-outline": "\U000F0111", - "cart-plus": "\U000F0112", - "cart-remove": "\U000F0D69", - "cart-variant": "\U000F15EB", - "case-sensitive-alt": "\U000F0113", - "cash": "\U000F0114", - "cash-100": "\U000F0115", - "cash-check": "\U000F14EE", - "cash-lock": "\U000F14EA", - "cash-lock-open": "\U000F14EB", - "cash-marker": "\U000F0DB8", - "cash-minus": "\U000F1260", - "cash-multiple": "\U000F0116", - "cash-plus": "\U000F1261", - "cash-refund": "\U000F0A9C", - "cash-register": "\U000F0CF4", - "cash-remove": "\U000F1262", - "cash-usd": "\U000F1176", - "cash-usd-outline": "\U000F0117", - "cassette": "\U000F09D4", - "cast": "\U000F0118", - "cast-audio": "\U000F101E", - "cast-connected": "\U000F0119", - "cast-education": "\U000F0E1D", - "cast-off": "\U000F078A", - "castle": "\U000F011A", - "cat": "\U000F011B", - "cctv": "\U000F07AE", - "ceiling-light": "\U000F0769", - "cellphone": "\U000F011C", - "cellphone-android": "\U000F011D", - "cellphone-arrow-down": "\U000F09D5", - "cellphone-basic": "\U000F011E", - "cellphone-charging": "\U000F1397", - "cellphone-cog": "\U000F0951", - "cellphone-dock": "\U000F011F", - "cellphone-erase": "\U000F094D", - "cellphone-information": "\U000F0F41", - "cellphone-iphone": "\U000F0120", - "cellphone-key": "\U000F094E", - "cellphone-link": "\U000F0121", - "cellphone-link-off": "\U000F0122", - "cellphone-lock": "\U000F094F", - "cellphone-message": "\U000F08D3", - "cellphone-message-off": "\U000F10D2", - "cellphone-nfc": "\U000F0E90", - "cellphone-nfc-off": "\U000F12D8", - "cellphone-off": "\U000F0950", - "cellphone-play": "\U000F101F", - "cellphone-screenshot": "\U000F0A35", - "cellphone-settings": "\U000F0123", - "cellphone-sound": "\U000F0952", - "cellphone-text": "\U000F08D2", - "cellphone-wireless": "\U000F0815", - "celtic-cross": "\U000F0CF5", - "centos": "\U000F111A", - "certificate": "\U000F0124", - "certificate-outline": "\U000F1188", - "chair-rolling": "\U000F0F48", - "chair-school": "\U000F0125", - "charity": "\U000F0C4F", - "chart-arc": "\U000F0126", - "chart-areaspline": "\U000F0127", - "chart-areaspline-variant": "\U000F0E91", - "chart-bar": "\U000F0128", - "chart-bar-stacked": "\U000F076A", - "chart-bell-curve": "\U000F0C50", - "chart-bell-curve-cumulative": "\U000F0FA7", - "chart-box": "\U000F154D", - "chart-box-outline": "\U000F154E", - "chart-box-plus-outline": "\U000F154F", - "chart-bubble": "\U000F05E3", - "chart-donut": "\U000F07AF", - "chart-donut-variant": "\U000F07B0", - "chart-gantt": "\U000F066C", - "chart-histogram": "\U000F0129", - "chart-line": "\U000F012A", - "chart-line-stacked": "\U000F076B", - "chart-line-variant": "\U000F07B1", - "chart-multiline": "\U000F08D4", - "chart-multiple": "\U000F1213", - "chart-pie": "\U000F012B", - "chart-ppf": "\U000F1380", - "chart-sankey": "\U000F11DF", - "chart-sankey-variant": "\U000F11E0", - "chart-scatter-plot": "\U000F0E92", - "chart-scatter-plot-hexbin": "\U000F066D", - "chart-timeline": "\U000F066E", - "chart-timeline-variant": "\U000F0E93", - "chart-timeline-variant-shimmer": "\U000F15B6", - "chart-tree": "\U000F0E94", - "chat": "\U000F0B79", - "chat-alert": "\U000F0B7A", - "chat-alert-outline": "\U000F12C9", - "chat-minus": "\U000F1410", - "chat-minus-outline": "\U000F1413", - "chat-outline": "\U000F0EDE", - "chat-plus": "\U000F140F", - "chat-plus-outline": "\U000F1412", - "chat-processing": "\U000F0B7B", - "chat-processing-outline": "\U000F12CA", - "chat-question": "\U000F1738", - "chat-question-outline": "\U000F1739", - "chat-remove": "\U000F1411", - "chat-remove-outline": "\U000F1414", - "chat-sleep": "\U000F12D1", - "chat-sleep-outline": "\U000F12D2", - "check": "\U000F012C", - "check-all": "\U000F012D", - "check-bold": "\U000F0E1E", - "check-box-multiple-outline": "\U000F0C51", - "check-box-outline": "\U000F0C52", - "check-circle": "\U000F05E0", - "check-circle-outline": "\U000F05E1", - "check-decagram": "\U000F0791", - "check-decagram-outline": "\U000F1740", - "check-network": "\U000F0C53", - "check-network-outline": "\U000F0C54", - "check-outline": "\U000F0855", - "check-underline": "\U000F0E1F", - "check-underline-circle": "\U000F0E20", - "check-underline-circle-outline": "\U000F0E21", - "checkbook": "\U000F0A9D", - "checkbox-blank": "\U000F012E", - "checkbox-blank-circle": "\U000F012F", - "checkbox-blank-circle-outline": "\U000F0130", - "checkbox-blank-off": "\U000F12EC", - "checkbox-blank-off-outline": "\U000F12ED", - "checkbox-blank-outline": "\U000F0131", - "checkbox-intermediate": "\U000F0856", - "checkbox-marked": "\U000F0132", - "checkbox-marked-circle": "\U000F0133", - "checkbox-marked-circle-outline": "\U000F0134", - "checkbox-marked-outline": "\U000F0135", - "checkbox-multiple-blank": "\U000F0136", - "checkbox-multiple-blank-circle": "\U000F063B", - "checkbox-multiple-blank-circle-outline": "\U000F063C", - "checkbox-multiple-blank-outline": "\U000F0137", - "checkbox-multiple-marked": "\U000F0138", - "checkbox-multiple-marked-circle": "\U000F063D", - "checkbox-multiple-marked-circle-outline": "\U000F063E", - "checkbox-multiple-marked-outline": "\U000F0139", - "checkerboard": "\U000F013A", - "checkerboard-minus": "\U000F1202", - "checkerboard-plus": "\U000F1201", - "checkerboard-remove": "\U000F1203", - "cheese": "\U000F12B9", - "cheese-off": "\U000F13EE", - "chef-hat": "\U000F0B7C", - "chemical-weapon": "\U000F013B", - "chess-bishop": "\U000F085C", - "chess-king": "\U000F0857", - "chess-knight": "\U000F0858", - "chess-pawn": "\U000F0859", - "chess-queen": "\U000F085A", - "chess-rook": "\U000F085B", - "chevron-double-down": "\U000F013C", - "chevron-double-left": "\U000F013D", - "chevron-double-right": "\U000F013E", - "chevron-double-up": "\U000F013F", - "chevron-down": "\U000F0140", - "chevron-down-box": "\U000F09D6", - "chevron-down-box-outline": "\U000F09D7", - "chevron-down-circle": "\U000F0B26", - "chevron-down-circle-outline": "\U000F0B27", - "chevron-left": "\U000F0141", - "chevron-left-box": "\U000F09D8", - "chevron-left-box-outline": "\U000F09D9", - "chevron-left-circle": "\U000F0B28", - "chevron-left-circle-outline": "\U000F0B29", - "chevron-right": "\U000F0142", - "chevron-right-box": "\U000F09DA", - "chevron-right-box-outline": "\U000F09DB", - "chevron-right-circle": "\U000F0B2A", - "chevron-right-circle-outline": "\U000F0B2B", - "chevron-triple-down": "\U000F0DB9", - "chevron-triple-left": "\U000F0DBA", - "chevron-triple-right": "\U000F0DBB", - "chevron-triple-up": "\U000F0DBC", - "chevron-up": "\U000F0143", - "chevron-up-box": "\U000F09DC", - "chevron-up-box-outline": "\U000F09DD", - "chevron-up-circle": "\U000F0B2C", - "chevron-up-circle-outline": "\U000F0B2D", - "chili-hot": "\U000F07B2", - "chili-medium": "\U000F07B3", - "chili-mild": "\U000F07B4", - "chili-off": "\U000F1467", - "chip": "\U000F061A", - "christianity": "\U000F0953", - "christianity-outline": "\U000F0CF6", - "church": "\U000F0144", - "cigar": "\U000F1189", - "cigar-off": "\U000F141B", - "circle": "\U000F0765", - "circle-box": "\U000F15DC", - "circle-box-outline": "\U000F15DD", - "circle-double": "\U000F0E95", - "circle-edit-outline": "\U000F08D5", - "circle-expand": "\U000F0E96", - "circle-half": "\U000F1395", - "circle-half-full": "\U000F1396", - "circle-medium": "\U000F09DE", - "circle-multiple": "\U000F0B38", - "circle-multiple-outline": "\U000F0695", - "circle-off-outline": "\U000F10D3", - "circle-outline": "\U000F0766", - "circle-slice-1": "\U000F0A9E", - "circle-slice-2": "\U000F0A9F", - "circle-slice-3": "\U000F0AA0", - "circle-slice-4": "\U000F0AA1", - "circle-slice-5": "\U000F0AA2", - "circle-slice-6": "\U000F0AA3", - "circle-slice-7": "\U000F0AA4", - "circle-slice-8": "\U000F0AA5", - "circle-small": "\U000F09DF", - "circular-saw": "\U000F0E22", - "city": "\U000F0146", - "city-variant": "\U000F0A36", - "city-variant-outline": "\U000F0A37", - "clipboard": "\U000F0147", - "clipboard-account": "\U000F0148", - "clipboard-account-outline": "\U000F0C55", - "clipboard-alert": "\U000F0149", - "clipboard-alert-outline": "\U000F0CF7", - "clipboard-arrow-down": "\U000F014A", - "clipboard-arrow-down-outline": "\U000F0C56", - "clipboard-arrow-left": "\U000F014B", - "clipboard-arrow-left-outline": "\U000F0CF8", - "clipboard-arrow-right": "\U000F0CF9", - "clipboard-arrow-right-outline": "\U000F0CFA", - "clipboard-arrow-up": "\U000F0C57", - "clipboard-arrow-up-outline": "\U000F0C58", - "clipboard-check": "\U000F014E", - "clipboard-check-multiple": "\U000F1263", - "clipboard-check-multiple-outline": "\U000F1264", - "clipboard-check-outline": "\U000F08A8", - "clipboard-clock": "\U000F16E2", - "clipboard-clock-outline": "\U000F16E3", - "clipboard-edit": "\U000F14E5", - "clipboard-edit-outline": "\U000F14E6", - "clipboard-file": "\U000F1265", - "clipboard-file-outline": "\U000F1266", - "clipboard-flow": "\U000F06C8", - "clipboard-flow-outline": "\U000F1117", - "clipboard-list": "\U000F10D4", - "clipboard-list-outline": "\U000F10D5", - "clipboard-minus": "\U000F1618", - "clipboard-minus-outline": "\U000F1619", - "clipboard-multiple": "\U000F1267", - "clipboard-multiple-outline": "\U000F1268", - "clipboard-off": "\U000F161A", - "clipboard-off-outline": "\U000F161B", - "clipboard-outline": "\U000F014C", - "clipboard-play": "\U000F0C59", - "clipboard-play-multiple": "\U000F1269", - "clipboard-play-multiple-outline": "\U000F126A", - "clipboard-play-outline": "\U000F0C5A", - "clipboard-plus": "\U000F0751", - "clipboard-plus-outline": "\U000F131F", - "clipboard-pulse": "\U000F085D", - "clipboard-pulse-outline": "\U000F085E", - "clipboard-remove": "\U000F161C", - "clipboard-remove-outline": "\U000F161D", - "clipboard-search": "\U000F161E", - "clipboard-search-outline": "\U000F161F", - "clipboard-text": "\U000F014D", - "clipboard-text-multiple": "\U000F126B", - "clipboard-text-multiple-outline": "\U000F126C", - "clipboard-text-off": "\U000F1620", - "clipboard-text-off-outline": "\U000F1621", - "clipboard-text-outline": "\U000F0A38", - "clipboard-text-play": "\U000F0C5B", - "clipboard-text-play-outline": "\U000F0C5C", - "clipboard-text-search": "\U000F1622", - "clipboard-text-search-outline": "\U000F1623", - "clippy": "\U000F014F", - "clock": "\U000F0954", - "clock-alert": "\U000F0955", - "clock-alert-outline": "\U000F05CE", - "clock-check": "\U000F0FA8", - "clock-check-outline": "\U000F0FA9", - "clock-digital": "\U000F0E97", - "clock-end": "\U000F0151", - "clock-fast": "\U000F0152", - "clock-in": "\U000F0153", - "clock-out": "\U000F0154", - "clock-outline": "\U000F0150", - "clock-start": "\U000F0155", - "clock-time-eight": "\U000F1446", - "clock-time-eight-outline": "\U000F1452", - "clock-time-eleven": "\U000F1449", - "clock-time-eleven-outline": "\U000F1455", - "clock-time-five": "\U000F1443", - "clock-time-five-outline": "\U000F144F", - "clock-time-four": "\U000F1442", - "clock-time-four-outline": "\U000F144E", - "clock-time-nine": "\U000F1447", - "clock-time-nine-outline": "\U000F1453", - "clock-time-one": "\U000F143F", - "clock-time-one-outline": "\U000F144B", - "clock-time-seven": "\U000F1445", - "clock-time-seven-outline": "\U000F1451", - "clock-time-six": "\U000F1444", - "clock-time-six-outline": "\U000F1450", - "clock-time-ten": "\U000F1448", - "clock-time-ten-outline": "\U000F1454", - "clock-time-three": "\U000F1441", - "clock-time-three-outline": "\U000F144D", - "clock-time-twelve": "\U000F144A", - "clock-time-twelve-outline": "\U000F1456", - "clock-time-two": "\U000F1440", - "clock-time-two-outline": "\U000F144C", - "close": "\U000F0156", - "close-box": "\U000F0157", - "close-box-multiple": "\U000F0C5D", - "close-box-multiple-outline": "\U000F0C5E", - "close-box-outline": "\U000F0158", - "close-circle": "\U000F0159", - "close-circle-multiple": "\U000F062A", - "close-circle-multiple-outline": "\U000F0883", - "close-circle-outline": "\U000F015A", - "close-network": "\U000F015B", - "close-network-outline": "\U000F0C5F", - "close-octagon": "\U000F015C", - "close-octagon-outline": "\U000F015D", - "close-outline": "\U000F06C9", - "close-thick": "\U000F1398", - "closed-caption": "\U000F015E", - "closed-caption-outline": "\U000F0DBD", - "cloud": "\U000F015F", - "cloud-alert": "\U000F09E0", - "cloud-braces": "\U000F07B5", - "cloud-check": "\U000F0160", - "cloud-check-outline": "\U000F12CC", - "cloud-circle": "\U000F0161", - "cloud-download": "\U000F0162", - "cloud-download-outline": "\U000F0B7D", - "cloud-lock": "\U000F11F1", - "cloud-lock-outline": "\U000F11F2", - "cloud-off-outline": "\U000F0164", - "cloud-outline": "\U000F0163", - "cloud-print": "\U000F0165", - "cloud-print-outline": "\U000F0166", - "cloud-question": "\U000F0A39", - "cloud-refresh": "\U000F052A", - "cloud-search": "\U000F0956", - "cloud-search-outline": "\U000F0957", - "cloud-sync": "\U000F063F", - "cloud-sync-outline": "\U000F12D6", - "cloud-tags": "\U000F07B6", - "cloud-upload": "\U000F0167", - "cloud-upload-outline": "\U000F0B7E", - "clover": "\U000F0816", - "coach-lamp": "\U000F1020", - "coat-rack": "\U000F109E", - "code-array": "\U000F0168", - "code-braces": "\U000F0169", - "code-braces-box": "\U000F10D6", - "code-brackets": "\U000F016A", - "code-equal": "\U000F016B", - "code-greater-than": "\U000F016C", - "code-greater-than-or-equal": "\U000F016D", - "code-json": "\U000F0626", - "code-less-than": "\U000F016E", - "code-less-than-or-equal": "\U000F016F", - "code-not-equal": "\U000F0170", - "code-not-equal-variant": "\U000F0171", - "code-parentheses": "\U000F0172", - "code-parentheses-box": "\U000F10D7", - "code-string": "\U000F0173", - "code-tags": "\U000F0174", - "code-tags-check": "\U000F0694", - "codepen": "\U000F0175", - "coffee": "\U000F0176", - "coffee-maker": "\U000F109F", - "coffee-off": "\U000F0FAA", - "coffee-off-outline": "\U000F0FAB", - "coffee-outline": "\U000F06CA", - "coffee-to-go": "\U000F0177", - "coffee-to-go-outline": "\U000F130E", - "coffin": "\U000F0B7F", - "cog": "\U000F0493", - "cog-box": "\U000F0494", - "cog-clockwise": "\U000F11DD", - "cog-counterclockwise": "\U000F11DE", - "cog-off": "\U000F13CE", - "cog-off-outline": "\U000F13CF", - "cog-outline": "\U000F08BB", - "cog-refresh": "\U000F145E", - "cog-refresh-outline": "\U000F145F", - "cog-sync": "\U000F1460", - "cog-sync-outline": "\U000F1461", - "cog-transfer": "\U000F105B", - "cog-transfer-outline": "\U000F105C", - "cogs": "\U000F08D6", - "collage": "\U000F0640", - "collapse-all": "\U000F0AA6", - "collapse-all-outline": "\U000F0AA7", - "color-helper": "\U000F0179", - "comma": "\U000F0E23", - "comma-box": "\U000F0E2B", - "comma-box-outline": "\U000F0E24", - "comma-circle": "\U000F0E25", - "comma-circle-outline": "\U000F0E26", - "comment": "\U000F017A", - "comment-account": "\U000F017B", - "comment-account-outline": "\U000F017C", - "comment-alert": "\U000F017D", - "comment-alert-outline": "\U000F017E", - "comment-arrow-left": "\U000F09E1", - "comment-arrow-left-outline": "\U000F09E2", - "comment-arrow-right": "\U000F09E3", - "comment-arrow-right-outline": "\U000F09E4", - "comment-bookmark": "\U000F15AE", - "comment-bookmark-outline": "\U000F15AF", - "comment-check": "\U000F017F", - "comment-check-outline": "\U000F0180", - "comment-edit": "\U000F11BF", - "comment-edit-outline": "\U000F12C4", - "comment-eye": "\U000F0A3A", - "comment-eye-outline": "\U000F0A3B", - "comment-flash": "\U000F15B0", - "comment-flash-outline": "\U000F15B1", - "comment-minus": "\U000F15DF", - "comment-minus-outline": "\U000F15E0", - "comment-multiple": "\U000F085F", - "comment-multiple-outline": "\U000F0181", - "comment-off": "\U000F15E1", - "comment-off-outline": "\U000F15E2", - "comment-outline": "\U000F0182", - "comment-plus": "\U000F09E5", - "comment-plus-outline": "\U000F0183", - "comment-processing": "\U000F0184", - "comment-processing-outline": "\U000F0185", - "comment-question": "\U000F0817", - "comment-question-outline": "\U000F0186", - "comment-quote": "\U000F1021", - "comment-quote-outline": "\U000F1022", - "comment-remove": "\U000F05DE", - "comment-remove-outline": "\U000F0187", - "comment-search": "\U000F0A3C", - "comment-search-outline": "\U000F0A3D", - "comment-text": "\U000F0188", - "comment-text-multiple": "\U000F0860", - "comment-text-multiple-outline": "\U000F0861", - "comment-text-outline": "\U000F0189", - "compare": "\U000F018A", - "compare-horizontal": "\U000F1492", - "compare-vertical": "\U000F1493", - "compass": "\U000F018B", - "compass-off": "\U000F0B80", - "compass-off-outline": "\U000F0B81", - "compass-outline": "\U000F018C", - "compass-rose": "\U000F1382", - "concourse-ci": "\U000F10A0", - "connection": "\U000F1616", - "console": "\U000F018D", - "console-line": "\U000F07B7", - "console-network": "\U000F08A9", - "console-network-outline": "\U000F0C60", - "consolidate": "\U000F10D8", - "contactless-payment": "\U000F0D6A", - "contactless-payment-circle": "\U000F0321", - "contactless-payment-circle-outline": "\U000F0408", - "contacts": "\U000F06CB", - "contacts-outline": "\U000F05B8", - "contain": "\U000F0A3E", - "contain-end": "\U000F0A3F", - "contain-start": "\U000F0A40", - "content-copy": "\U000F018F", - "content-cut": "\U000F0190", - "content-duplicate": "\U000F0191", - "content-paste": "\U000F0192", - "content-save": "\U000F0193", - "content-save-alert": "\U000F0F42", - "content-save-alert-outline": "\U000F0F43", - "content-save-all": "\U000F0194", - "content-save-all-outline": "\U000F0F44", - "content-save-cog": "\U000F145B", - "content-save-cog-outline": "\U000F145C", - "content-save-edit": "\U000F0CFB", - "content-save-edit-outline": "\U000F0CFC", - "content-save-move": "\U000F0E27", - "content-save-move-outline": "\U000F0E28", - "content-save-off": "\U000F1643", - "content-save-off-outline": "\U000F1644", - "content-save-outline": "\U000F0818", - "content-save-settings": "\U000F061B", - "content-save-settings-outline": "\U000F0B2E", - "contrast": "\U000F0195", - "contrast-box": "\U000F0196", - "contrast-circle": "\U000F0197", - "controller-classic": "\U000F0B82", - "controller-classic-outline": "\U000F0B83", - "cookie": "\U000F0198", - "cookie-alert": "\U000F16D0", - "cookie-alert-outline": "\U000F16D1", - "cookie-check": "\U000F16D2", - "cookie-check-outline": "\U000F16D3", - "cookie-clock": "\U000F16E4", - "cookie-clock-outline": "\U000F16E5", - "cookie-cog": "\U000F16D4", - "cookie-cog-outline": "\U000F16D5", - "cookie-edit": "\U000F16E6", - "cookie-edit-outline": "\U000F16E7", - "cookie-lock": "\U000F16E8", - "cookie-lock-outline": "\U000F16E9", - "cookie-minus": "\U000F16DA", - "cookie-minus-outline": "\U000F16DB", - "cookie-off": "\U000F16EA", - "cookie-off-outline": "\U000F16EB", - "cookie-outline": "\U000F16DE", - "cookie-plus": "\U000F16D6", - "cookie-plus-outline": "\U000F16D7", - "cookie-refresh": "\U000F16EC", - "cookie-refresh-outline": "\U000F16ED", - "cookie-remove": "\U000F16D8", - "cookie-remove-outline": "\U000F16D9", - "cookie-settings": "\U000F16DC", - "cookie-settings-outline": "\U000F16DD", - "coolant-temperature": "\U000F03C8", - "copyright": "\U000F05E6", - "cordova": "\U000F0958", - "corn": "\U000F07B8", - "corn-off": "\U000F13EF", - "cosine-wave": "\U000F1479", - "counter": "\U000F0199", - "cow": "\U000F019A", - "cpu-32-bit": "\U000F0EDF", - "cpu-64-bit": "\U000F0EE0", - "crane": "\U000F0862", - "creation": "\U000F0674", - "creative-commons": "\U000F0D6B", - "credit-card": "\U000F0FEF", - "credit-card-check": "\U000F13D0", - "credit-card-check-outline": "\U000F13D1", - "credit-card-clock": "\U000F0EE1", - "credit-card-clock-outline": "\U000F0EE2", - "credit-card-marker": "\U000F06A8", - "credit-card-marker-outline": "\U000F0DBE", - "credit-card-minus": "\U000F0FAC", - "credit-card-minus-outline": "\U000F0FAD", - "credit-card-multiple": "\U000F0FF0", - "credit-card-multiple-outline": "\U000F019C", - "credit-card-off": "\U000F0FF1", - "credit-card-off-outline": "\U000F05E4", - "credit-card-outline": "\U000F019B", - "credit-card-plus": "\U000F0FF2", - "credit-card-plus-outline": "\U000F0676", - "credit-card-refresh": "\U000F1645", - "credit-card-refresh-outline": "\U000F1646", - "credit-card-refund": "\U000F0FF3", - "credit-card-refund-outline": "\U000F0AA8", - "credit-card-remove": "\U000F0FAE", - "credit-card-remove-outline": "\U000F0FAF", - "credit-card-scan": "\U000F0FF4", - "credit-card-scan-outline": "\U000F019D", - "credit-card-search": "\U000F1647", - "credit-card-search-outline": "\U000F1648", - "credit-card-settings": "\U000F0FF5", - "credit-card-settings-outline": "\U000F08D7", - "credit-card-sync": "\U000F1649", - "credit-card-sync-outline": "\U000F164A", - "credit-card-wireless": "\U000F0802", - "credit-card-wireless-off": "\U000F057A", - "credit-card-wireless-off-outline": "\U000F057B", - "credit-card-wireless-outline": "\U000F0D6C", - "cricket": "\U000F0D6D", - "crop": "\U000F019E", - "crop-free": "\U000F019F", - "crop-landscape": "\U000F01A0", - "crop-portrait": "\U000F01A1", - "crop-rotate": "\U000F0696", - "crop-square": "\U000F01A2", - "crosshairs": "\U000F01A3", - "crosshairs-gps": "\U000F01A4", - "crosshairs-off": "\U000F0F45", - "crosshairs-question": "\U000F1136", - "crown": "\U000F01A5", - "crown-outline": "\U000F11D0", - "cryengine": "\U000F0959", - "crystal-ball": "\U000F0B2F", - "cube": "\U000F01A6", - "cube-off": "\U000F141C", - "cube-off-outline": "\U000F141D", - "cube-outline": "\U000F01A7", - "cube-scan": "\U000F0B84", - "cube-send": "\U000F01A8", - "cube-unfolded": "\U000F01A9", - "cup": "\U000F01AA", - "cup-off": "\U000F05E5", - "cup-off-outline": "\U000F137D", - "cup-outline": "\U000F130F", - "cup-water": "\U000F01AB", - "cupboard": "\U000F0F46", - "cupboard-outline": "\U000F0F47", - "cupcake": "\U000F095A", - "curling": "\U000F0863", - "currency-bdt": "\U000F0864", - "currency-brl": "\U000F0B85", - "currency-btc": "\U000F01AC", - "currency-cny": "\U000F07BA", - "currency-eth": "\U000F07BB", - "currency-eur": "\U000F01AD", - "currency-eur-off": "\U000F1315", - "currency-gbp": "\U000F01AE", - "currency-ils": "\U000F0C61", - "currency-inr": "\U000F01AF", - "currency-jpy": "\U000F07BC", - "currency-krw": "\U000F07BD", - "currency-kzt": "\U000F0865", - "currency-mnt": "\U000F1512", - "currency-ngn": "\U000F01B0", - "currency-php": "\U000F09E6", - "currency-rial": "\U000F0E9C", - "currency-rub": "\U000F01B1", - "currency-sign": "\U000F07BE", - "currency-try": "\U000F01B2", - "currency-twd": "\U000F07BF", - "currency-usd": "\U000F01C1", - "currency-usd-circle": "\U000F116B", - "currency-usd-circle-outline": "\U000F0178", - "currency-usd-off": "\U000F067A", - "current-ac": "\U000F1480", - "current-dc": "\U000F095C", - "cursor-default": "\U000F01C0", - "cursor-default-click": "\U000F0CFD", - "cursor-default-click-outline": "\U000F0CFE", - "cursor-default-gesture": "\U000F1127", - "cursor-default-gesture-outline": "\U000F1128", - "cursor-default-outline": "\U000F01BF", - "cursor-move": "\U000F01BE", - "cursor-pointer": "\U000F01BD", - "cursor-text": "\U000F05E7", - "dance-ballroom": "\U000F15FB", - "dance-pole": "\U000F1578", - "data-matrix": "\U000F153C", - "data-matrix-edit": "\U000F153D", - "data-matrix-minus": "\U000F153E", - "data-matrix-plus": "\U000F153F", - "data-matrix-remove": "\U000F1540", - "data-matrix-scan": "\U000F1541", - "database": "\U000F01BC", - "database-alert": "\U000F163A", - "database-alert-outline": "\U000F1624", - "database-arrow-down": "\U000F163B", - "database-arrow-down-outline": "\U000F1625", - "database-arrow-left": "\U000F163C", - "database-arrow-left-outline": "\U000F1626", - "database-arrow-right": "\U000F163D", - "database-arrow-right-outline": "\U000F1627", - "database-arrow-up": "\U000F163E", - "database-arrow-up-outline": "\U000F1628", - "database-check": "\U000F0AA9", - "database-check-outline": "\U000F1629", - "database-clock": "\U000F163F", - "database-clock-outline": "\U000F162A", - "database-cog": "\U000F164B", - "database-cog-outline": "\U000F164C", - "database-edit": "\U000F0B86", - "database-edit-outline": "\U000F162B", - "database-export": "\U000F095E", - "database-export-outline": "\U000F162C", - "database-import": "\U000F095D", - "database-import-outline": "\U000F162D", - "database-lock": "\U000F0AAA", - "database-lock-outline": "\U000F162E", - "database-marker": "\U000F12F6", - "database-marker-outline": "\U000F162F", - "database-minus": "\U000F01BB", - "database-minus-outline": "\U000F1630", - "database-off": "\U000F1640", - "database-off-outline": "\U000F1631", - "database-outline": "\U000F1632", - "database-plus": "\U000F01BA", - "database-plus-outline": "\U000F1633", - "database-refresh": "\U000F05C2", - "database-refresh-outline": "\U000F1634", - "database-remove": "\U000F0D00", - "database-remove-outline": "\U000F1635", - "database-search": "\U000F0866", - "database-search-outline": "\U000F1636", - "database-settings": "\U000F0D01", - "database-settings-outline": "\U000F1637", - "database-sync": "\U000F0CFF", - "database-sync-outline": "\U000F1638", - "death-star": "\U000F08D8", - "death-star-variant": "\U000F08D9", - "deathly-hallows": "\U000F0B87", - "debian": "\U000F08DA", - "debug-step-into": "\U000F01B9", - "debug-step-out": "\U000F01B8", - "debug-step-over": "\U000F01B7", - "decagram": "\U000F076C", - "decagram-outline": "\U000F076D", - "decimal": "\U000F10A1", - "decimal-comma": "\U000F10A2", - "decimal-comma-decrease": "\U000F10A3", - "decimal-comma-increase": "\U000F10A4", - "decimal-decrease": "\U000F01B6", - "decimal-increase": "\U000F01B5", - "delete": "\U000F01B4", - "delete-alert": "\U000F10A5", - "delete-alert-outline": "\U000F10A6", - "delete-circle": "\U000F0683", - "delete-circle-outline": "\U000F0B88", - "delete-clock": "\U000F1556", - "delete-clock-outline": "\U000F1557", - "delete-empty": "\U000F06CC", - "delete-empty-outline": "\U000F0E9D", - "delete-forever": "\U000F05E8", - "delete-forever-outline": "\U000F0B89", - "delete-off": "\U000F10A7", - "delete-off-outline": "\U000F10A8", - "delete-outline": "\U000F09E7", - "delete-restore": "\U000F0819", - "delete-sweep": "\U000F05E9", - "delete-sweep-outline": "\U000F0C62", - "delete-variant": "\U000F01B3", - "delta": "\U000F01C2", - "desk": "\U000F1239", - "desk-lamp": "\U000F095F", - "deskphone": "\U000F01C3", - "desktop-classic": "\U000F07C0", - "desktop-mac": "\U000F01C4", - "desktop-mac-dashboard": "\U000F09E8", - "desktop-tower": "\U000F01C5", - "desktop-tower-monitor": "\U000F0AAB", - "details": "\U000F01C6", - "dev-to": "\U000F0D6E", - "developer-board": "\U000F0697", - "deviantart": "\U000F01C7", - "devices": "\U000F0FB0", - "diabetes": "\U000F1126", - "dialpad": "\U000F061C", - "diameter": "\U000F0C63", - "diameter-outline": "\U000F0C64", - "diameter-variant": "\U000F0C65", - "diamond": "\U000F0B8A", - "diamond-outline": "\U000F0B8B", - "diamond-stone": "\U000F01C8", - "dice-1": "\U000F01CA", - "dice-1-outline": "\U000F114A", - "dice-2": "\U000F01CB", - "dice-2-outline": "\U000F114B", - "dice-3": "\U000F01CC", - "dice-3-outline": "\U000F114C", - "dice-4": "\U000F01CD", - "dice-4-outline": "\U000F114D", - "dice-5": "\U000F01CE", - "dice-5-outline": "\U000F114E", - "dice-6": "\U000F01CF", - "dice-6-outline": "\U000F114F", - "dice-d10": "\U000F1153", - "dice-d10-outline": "\U000F076F", - "dice-d12": "\U000F1154", - "dice-d12-outline": "\U000F0867", - "dice-d20": "\U000F1155", - "dice-d20-outline": "\U000F05EA", - "dice-d4": "\U000F1150", - "dice-d4-outline": "\U000F05EB", - "dice-d6": "\U000F1151", - "dice-d6-outline": "\U000F05ED", - "dice-d8": "\U000F1152", - "dice-d8-outline": "\U000F05EC", - "dice-multiple": "\U000F076E", - "dice-multiple-outline": "\U000F1156", - "digital-ocean": "\U000F1237", - "dip-switch": "\U000F07C1", - "directions": "\U000F01D0", - "directions-fork": "\U000F0641", - "disc": "\U000F05EE", - "disc-alert": "\U000F01D1", - "disc-player": "\U000F0960", - "discord": "\U000F066F", - "dishwasher": "\U000F0AAC", - "dishwasher-alert": "\U000F11B8", - "dishwasher-off": "\U000F11B9", - "disqus": "\U000F01D2", - "distribute-horizontal-center": "\U000F11C9", - "distribute-horizontal-left": "\U000F11C8", - "distribute-horizontal-right": "\U000F11CA", - "distribute-vertical-bottom": "\U000F11CB", - "distribute-vertical-center": "\U000F11CC", - "distribute-vertical-top": "\U000F11CD", - "diving-flippers": "\U000F0DBF", - "diving-helmet": "\U000F0DC0", - "diving-scuba": "\U000F0DC1", - "diving-scuba-flag": "\U000F0DC2", - "diving-scuba-tank": "\U000F0DC3", - "diving-scuba-tank-multiple": "\U000F0DC4", - "diving-snorkel": "\U000F0DC5", - "division": "\U000F01D4", - "division-box": "\U000F01D5", - "dlna": "\U000F0A41", - "dna": "\U000F0684", - "dns": "\U000F01D6", - "dns-outline": "\U000F0B8C", - "do-not-disturb": "\U000F0698", - "do-not-disturb-off": "\U000F0699", - "dock-bottom": "\U000F10A9", - "dock-left": "\U000F10AA", - "dock-right": "\U000F10AB", - "dock-top": "\U000F1513", - "dock-window": "\U000F10AC", - "docker": "\U000F0868", - "doctor": "\U000F0A42", - "dog": "\U000F0A43", - "dog-service": "\U000F0AAD", - "dog-side": "\U000F0A44", - "dog-side-off": "\U000F16EE", - "dolby": "\U000F06B3", - "dolly": "\U000F0E9E", - "domain": "\U000F01D7", - "domain-off": "\U000F0D6F", - "domain-plus": "\U000F10AD", - "domain-remove": "\U000F10AE", - "dome-light": "\U000F141E", - "domino-mask": "\U000F1023", - "donkey": "\U000F07C2", - "door": "\U000F081A", - "door-closed": "\U000F081B", - "door-closed-lock": "\U000F10AF", - "door-open": "\U000F081C", - "doorbell": "\U000F12E6", - "doorbell-video": "\U000F0869", - "dot-net": "\U000F0AAE", - "dots-grid": "\U000F15FC", - "dots-hexagon": "\U000F15FF", - "dots-horizontal": "\U000F01D8", - "dots-horizontal-circle": "\U000F07C3", - "dots-horizontal-circle-outline": "\U000F0B8D", - "dots-square": "\U000F15FD", - "dots-triangle": "\U000F15FE", - "dots-vertical": "\U000F01D9", - "dots-vertical-circle": "\U000F07C4", - "dots-vertical-circle-outline": "\U000F0B8E", - "douban": "\U000F069A", - "download": "\U000F01DA", - "download-box": "\U000F1462", - "download-box-outline": "\U000F1463", - "download-circle": "\U000F1464", - "download-circle-outline": "\U000F1465", - "download-lock": "\U000F1320", - "download-lock-outline": "\U000F1321", - "download-multiple": "\U000F09E9", - "download-network": "\U000F06F4", - "download-network-outline": "\U000F0C66", - "download-off": "\U000F10B0", - "download-off-outline": "\U000F10B1", - "download-outline": "\U000F0B8F", - "drag": "\U000F01DB", - "drag-horizontal": "\U000F01DC", - "drag-horizontal-variant": "\U000F12F0", - "drag-variant": "\U000F0B90", - "drag-vertical": "\U000F01DD", - "drag-vertical-variant": "\U000F12F1", - "drama-masks": "\U000F0D02", - "draw": "\U000F0F49", - "drawing": "\U000F01DE", - "drawing-box": "\U000F01DF", - "dresser": "\U000F0F4A", - "dresser-outline": "\U000F0F4B", - "drone": "\U000F01E2", - "dropbox": "\U000F01E3", - "drupal": "\U000F01E4", - "duck": "\U000F01E5", - "dumbbell": "\U000F01E6", - "dump-truck": "\U000F0C67", - "ear-hearing": "\U000F07C5", - "ear-hearing-off": "\U000F0A45", - "earth": "\U000F01E7", - "earth-arrow-right": "\U000F1311", - "earth-box": "\U000F06CD", - "earth-box-minus": "\U000F1407", - "earth-box-off": "\U000F06CE", - "earth-box-plus": "\U000F1406", - "earth-box-remove": "\U000F1408", - "earth-minus": "\U000F1404", - "earth-off": "\U000F01E8", - "earth-plus": "\U000F1403", - "earth-remove": "\U000F1405", - "egg": "\U000F0AAF", - "egg-easter": "\U000F0AB0", - "egg-off": "\U000F13F0", - "egg-off-outline": "\U000F13F1", - "egg-outline": "\U000F13F2", - "eiffel-tower": "\U000F156B", - "eight-track": "\U000F09EA", - "eject": "\U000F01EA", - "eject-outline": "\U000F0B91", - "electric-switch": "\U000F0E9F", - "electric-switch-closed": "\U000F10D9", - "electron-framework": "\U000F1024", - "elephant": "\U000F07C6", - "elevation-decline": "\U000F01EB", - "elevation-rise": "\U000F01EC", - "elevator": "\U000F01ED", - "elevator-down": "\U000F12C2", - "elevator-passenger": "\U000F1381", - "elevator-up": "\U000F12C1", - "ellipse": "\U000F0EA0", - "ellipse-outline": "\U000F0EA1", - "email": "\U000F01EE", - "email-alert": "\U000F06CF", - "email-alert-outline": "\U000F0D42", - "email-box": "\U000F0D03", - "email-check": "\U000F0AB1", - "email-check-outline": "\U000F0AB2", - "email-edit": "\U000F0EE3", - "email-edit-outline": "\U000F0EE4", - "email-lock": "\U000F01F1", - "email-mark-as-unread": "\U000F0B92", - "email-minus": "\U000F0EE5", - "email-minus-outline": "\U000F0EE6", - "email-multiple": "\U000F0EE7", - "email-multiple-outline": "\U000F0EE8", - "email-newsletter": "\U000F0FB1", - "email-off": "\U000F13E3", - "email-off-outline": "\U000F13E4", - "email-open": "\U000F01EF", - "email-open-multiple": "\U000F0EE9", - "email-open-multiple-outline": "\U000F0EEA", - "email-open-outline": "\U000F05EF", - "email-outline": "\U000F01F0", - "email-plus": "\U000F09EB", - "email-plus-outline": "\U000F09EC", - "email-receive": "\U000F10DA", - "email-receive-outline": "\U000F10DB", - "email-remove": "\U000F1661", - "email-remove-outline": "\U000F1662", - "email-search": "\U000F0961", - "email-search-outline": "\U000F0962", - "email-send": "\U000F10DC", - "email-send-outline": "\U000F10DD", - "email-sync": "\U000F12C7", - "email-sync-outline": "\U000F12C8", - "email-variant": "\U000F05F0", - "ember": "\U000F0B30", - "emby": "\U000F06B4", - "emoticon": "\U000F0C68", - "emoticon-angry": "\U000F0C69", - "emoticon-angry-outline": "\U000F0C6A", - "emoticon-confused": "\U000F10DE", - "emoticon-confused-outline": "\U000F10DF", - "emoticon-cool": "\U000F0C6B", - "emoticon-cool-outline": "\U000F01F3", - "emoticon-cry": "\U000F0C6C", - "emoticon-cry-outline": "\U000F0C6D", - "emoticon-dead": "\U000F0C6E", - "emoticon-dead-outline": "\U000F069B", - "emoticon-devil": "\U000F0C6F", - "emoticon-devil-outline": "\U000F01F4", - "emoticon-excited": "\U000F0C70", - "emoticon-excited-outline": "\U000F069C", - "emoticon-frown": "\U000F0F4C", - "emoticon-frown-outline": "\U000F0F4D", - "emoticon-happy": "\U000F0C71", - "emoticon-happy-outline": "\U000F01F5", - "emoticon-kiss": "\U000F0C72", - "emoticon-kiss-outline": "\U000F0C73", - "emoticon-lol": "\U000F1214", - "emoticon-lol-outline": "\U000F1215", - "emoticon-neutral": "\U000F0C74", - "emoticon-neutral-outline": "\U000F01F6", - "emoticon-outline": "\U000F01F2", - "emoticon-poop": "\U000F01F7", - "emoticon-poop-outline": "\U000F0C75", - "emoticon-sad": "\U000F0C76", - "emoticon-sad-outline": "\U000F01F8", - "emoticon-sick": "\U000F157C", - "emoticon-sick-outline": "\U000F157D", - "emoticon-tongue": "\U000F01F9", - "emoticon-tongue-outline": "\U000F0C77", - "emoticon-wink": "\U000F0C78", - "emoticon-wink-outline": "\U000F0C79", - "engine": "\U000F01FA", - "engine-off": "\U000F0A46", - "engine-off-outline": "\U000F0A47", - "engine-outline": "\U000F01FB", - "epsilon": "\U000F10E0", - "equal": "\U000F01FC", - "equal-box": "\U000F01FD", - "equalizer": "\U000F0EA2", - "equalizer-outline": "\U000F0EA3", - "eraser": "\U000F01FE", - "eraser-variant": "\U000F0642", - "escalator": "\U000F01FF", - "escalator-box": "\U000F1399", - "escalator-down": "\U000F12C0", - "escalator-up": "\U000F12BF", - "eslint": "\U000F0C7A", - "et": "\U000F0AB3", - "ethereum": "\U000F086A", - "ethernet": "\U000F0200", - "ethernet-cable": "\U000F0201", - "ethernet-cable-off": "\U000F0202", - "ev-plug-ccs1": "\U000F1519", - "ev-plug-ccs2": "\U000F151A", - "ev-plug-chademo": "\U000F151B", - "ev-plug-tesla": "\U000F151C", - "ev-plug-type1": "\U000F151D", - "ev-plug-type2": "\U000F151E", - "ev-station": "\U000F05F1", - "evernote": "\U000F0204", - "excavator": "\U000F1025", - "exclamation": "\U000F0205", - "exclamation-thick": "\U000F1238", - "exit-run": "\U000F0A48", - "exit-to-app": "\U000F0206", - "expand-all": "\U000F0AB4", - "expand-all-outline": "\U000F0AB5", - "expansion-card": "\U000F08AE", - "expansion-card-variant": "\U000F0FB2", - "exponent": "\U000F0963", - "exponent-box": "\U000F0964", - "export": "\U000F0207", - "export-variant": "\U000F0B93", - "eye": "\U000F0208", - "eye-check": "\U000F0D04", - "eye-check-outline": "\U000F0D05", - "eye-circle": "\U000F0B94", - "eye-circle-outline": "\U000F0B95", - "eye-minus": "\U000F1026", - "eye-minus-outline": "\U000F1027", - "eye-off": "\U000F0209", - "eye-off-outline": "\U000F06D1", - "eye-outline": "\U000F06D0", - "eye-plus": "\U000F086B", - "eye-plus-outline": "\U000F086C", - "eye-remove": "\U000F15E3", - "eye-remove-outline": "\U000F15E4", - "eye-settings": "\U000F086D", - "eye-settings-outline": "\U000F086E", - "eyedropper": "\U000F020A", - "eyedropper-minus": "\U000F13DD", - "eyedropper-off": "\U000F13DF", - "eyedropper-plus": "\U000F13DC", - "eyedropper-remove": "\U000F13DE", - "eyedropper-variant": "\U000F020B", - "face": "\U000F0643", - "face-agent": "\U000F0D70", - "face-mask": "\U000F1586", - "face-mask-outline": "\U000F1587", - "face-outline": "\U000F0B96", - "face-profile": "\U000F0644", - "face-profile-woman": "\U000F1076", - "face-recognition": "\U000F0C7B", - "face-shimmer": "\U000F15CC", - "face-shimmer-outline": "\U000F15CD", - "face-woman": "\U000F1077", - "face-woman-outline": "\U000F1078", - "face-woman-shimmer": "\U000F15CE", - "face-woman-shimmer-outline": "\U000F15CF", - "facebook": "\U000F020C", - "facebook-gaming": "\U000F07DD", - "facebook-messenger": "\U000F020E", - "facebook-workplace": "\U000F0B31", - "factory": "\U000F020F", - "family-tree": "\U000F160E", - "fan": "\U000F0210", - "fan-alert": "\U000F146C", - "fan-auto": "\U000F171D", - "fan-chevron-down": "\U000F146D", - "fan-chevron-up": "\U000F146E", - "fan-minus": "\U000F1470", - "fan-off": "\U000F081D", - "fan-plus": "\U000F146F", - "fan-remove": "\U000F1471", - "fan-speed-1": "\U000F1472", - "fan-speed-2": "\U000F1473", - "fan-speed-3": "\U000F1474", - "fast-forward": "\U000F0211", - "fast-forward-10": "\U000F0D71", - "fast-forward-30": "\U000F0D06", - "fast-forward-5": "\U000F11F8", - "fast-forward-60": "\U000F160B", - "fast-forward-outline": "\U000F06D2", - "fax": "\U000F0212", - "feather": "\U000F06D3", - "feature-search": "\U000F0A49", - "feature-search-outline": "\U000F0A4A", - "fedora": "\U000F08DB", - "fencing": "\U000F14C1", - "ferris-wheel": "\U000F0EA4", - "ferry": "\U000F0213", - "file": "\U000F0214", - "file-account": "\U000F073B", - "file-account-outline": "\U000F1028", - "file-alert": "\U000F0A4B", - "file-alert-outline": "\U000F0A4C", - "file-cabinet": "\U000F0AB6", - "file-cad": "\U000F0EEB", - "file-cad-box": "\U000F0EEC", - "file-cancel": "\U000F0DC6", - "file-cancel-outline": "\U000F0DC7", - "file-certificate": "\U000F1186", - "file-certificate-outline": "\U000F1187", - "file-chart": "\U000F0215", - "file-chart-outline": "\U000F1029", - "file-check": "\U000F0216", - "file-check-outline": "\U000F0E29", - "file-clock": "\U000F12E1", - "file-clock-outline": "\U000F12E2", - "file-cloud": "\U000F0217", - "file-cloud-outline": "\U000F102A", - "file-code": "\U000F022E", - "file-code-outline": "\U000F102B", - "file-cog": "\U000F107B", - "file-cog-outline": "\U000F107C", - "file-compare": "\U000F08AA", - "file-delimited": "\U000F0218", - "file-delimited-outline": "\U000F0EA5", - "file-document": "\U000F0219", - "file-document-edit": "\U000F0DC8", - "file-document-edit-outline": "\U000F0DC9", - "file-document-multiple": "\U000F1517", - "file-document-multiple-outline": "\U000F1518", - "file-document-outline": "\U000F09EE", - "file-download": "\U000F0965", - "file-download-outline": "\U000F0966", - "file-edit": "\U000F11E7", - "file-edit-outline": "\U000F11E8", - "file-excel": "\U000F021B", - "file-excel-box": "\U000F021C", - "file-excel-box-outline": "\U000F102C", - "file-excel-outline": "\U000F102D", - "file-export": "\U000F021D", - "file-export-outline": "\U000F102E", - "file-eye": "\U000F0DCA", - "file-eye-outline": "\U000F0DCB", - "file-find": "\U000F021E", - "file-find-outline": "\U000F0B97", - "file-hidden": "\U000F0613", - "file-image": "\U000F021F", - "file-image-outline": "\U000F0EB0", - "file-import": "\U000F0220", - "file-import-outline": "\U000F102F", - "file-key": "\U000F1184", - "file-key-outline": "\U000F1185", - "file-link": "\U000F1177", - "file-link-outline": "\U000F1178", - "file-lock": "\U000F0221", - "file-lock-outline": "\U000F1030", - "file-move": "\U000F0AB9", - "file-move-outline": "\U000F1031", - "file-multiple": "\U000F0222", - "file-multiple-outline": "\U000F1032", - "file-music": "\U000F0223", - "file-music-outline": "\U000F0E2A", - "file-outline": "\U000F0224", - "file-pdf": "\U000F0225", - "file-pdf-box": "\U000F0226", - "file-pdf-box-outline": "\U000F0FB3", - "file-pdf-outline": "\U000F0E2D", - "file-percent": "\U000F081E", - "file-percent-outline": "\U000F1033", - "file-phone": "\U000F1179", - "file-phone-outline": "\U000F117A", - "file-plus": "\U000F0752", - "file-plus-outline": "\U000F0EED", - "file-powerpoint": "\U000F0227", - "file-powerpoint-box": "\U000F0228", - "file-powerpoint-box-outline": "\U000F1034", - "file-powerpoint-outline": "\U000F1035", - "file-presentation-box": "\U000F0229", - "file-question": "\U000F086F", - "file-question-outline": "\U000F1036", - "file-refresh": "\U000F0918", - "file-refresh-outline": "\U000F0541", - "file-remove": "\U000F0B98", - "file-remove-outline": "\U000F1037", - "file-replace": "\U000F0B32", - "file-replace-outline": "\U000F0B33", - "file-restore": "\U000F0670", - "file-restore-outline": "\U000F1038", - "file-search": "\U000F0C7C", - "file-search-outline": "\U000F0C7D", - "file-send": "\U000F022A", - "file-send-outline": "\U000F1039", - "file-settings": "\U000F1079", - "file-settings-outline": "\U000F107A", - "file-star": "\U000F103A", - "file-star-outline": "\U000F103B", - "file-swap": "\U000F0FB4", - "file-swap-outline": "\U000F0FB5", - "file-sync": "\U000F1216", - "file-sync-outline": "\U000F1217", - "file-table": "\U000F0C7E", - "file-table-box": "\U000F10E1", - "file-table-box-multiple": "\U000F10E2", - "file-table-box-multiple-outline": "\U000F10E3", - "file-table-box-outline": "\U000F10E4", - "file-table-outline": "\U000F0C7F", - "file-tree": "\U000F0645", - "file-tree-outline": "\U000F13D2", - "file-undo": "\U000F08DC", - "file-undo-outline": "\U000F103C", - "file-upload": "\U000F0A4D", - "file-upload-outline": "\U000F0A4E", - "file-video": "\U000F022B", - "file-video-outline": "\U000F0E2C", - "file-word": "\U000F022C", - "file-word-box": "\U000F022D", - "file-word-box-outline": "\U000F103D", - "file-word-outline": "\U000F103E", - "film": "\U000F022F", - "filmstrip": "\U000F0230", - "filmstrip-box": "\U000F0332", - "filmstrip-box-multiple": "\U000F0D18", - "filmstrip-off": "\U000F0231", - "filter": "\U000F0232", - "filter-menu": "\U000F10E5", - "filter-menu-outline": "\U000F10E6", - "filter-minus": "\U000F0EEE", - "filter-minus-outline": "\U000F0EEF", - "filter-off": "\U000F14EF", - "filter-off-outline": "\U000F14F0", - "filter-outline": "\U000F0233", - "filter-plus": "\U000F0EF0", - "filter-plus-outline": "\U000F0EF1", - "filter-remove": "\U000F0234", - "filter-remove-outline": "\U000F0235", - "filter-variant": "\U000F0236", - "filter-variant-minus": "\U000F1112", - "filter-variant-plus": "\U000F1113", - "filter-variant-remove": "\U000F103F", - "finance": "\U000F081F", - "find-replace": "\U000F06D4", - "fingerprint": "\U000F0237", - "fingerprint-off": "\U000F0EB1", - "fire": "\U000F0238", - "fire-alert": "\U000F15D7", - "fire-extinguisher": "\U000F0EF2", - "fire-hydrant": "\U000F1137", - "fire-hydrant-alert": "\U000F1138", - "fire-hydrant-off": "\U000F1139", - "fire-off": "\U000F1722", - "fire-truck": "\U000F08AB", - "firebase": "\U000F0967", - "firefox": "\U000F0239", - "fireplace": "\U000F0E2E", - "fireplace-off": "\U000F0E2F", - "firework": "\U000F0E30", - "firework-off": "\U000F1723", - "fish": "\U000F023A", - "fish-off": "\U000F13F3", - "fishbowl": "\U000F0EF3", - "fishbowl-outline": "\U000F0EF4", - "fit-to-page": "\U000F0EF5", - "fit-to-page-outline": "\U000F0EF6", - "flag": "\U000F023B", - "flag-checkered": "\U000F023C", - "flag-minus": "\U000F0B99", - "flag-minus-outline": "\U000F10B2", - "flag-outline": "\U000F023D", - "flag-plus": "\U000F0B9A", - "flag-plus-outline": "\U000F10B3", - "flag-remove": "\U000F0B9B", - "flag-remove-outline": "\U000F10B4", - "flag-triangle": "\U000F023F", - "flag-variant": "\U000F0240", - "flag-variant-outline": "\U000F023E", - "flare": "\U000F0D72", - "flash": "\U000F0241", - "flash-alert": "\U000F0EF7", - "flash-alert-outline": "\U000F0EF8", - "flash-auto": "\U000F0242", - "flash-circle": "\U000F0820", - "flash-off": "\U000F0243", - "flash-outline": "\U000F06D5", - "flash-red-eye": "\U000F067B", - "flashlight": "\U000F0244", - "flashlight-off": "\U000F0245", - "flask": "\U000F0093", - "flask-empty": "\U000F0094", - "flask-empty-minus": "\U000F123A", - "flask-empty-minus-outline": "\U000F123B", - "flask-empty-off": "\U000F13F4", - "flask-empty-off-outline": "\U000F13F5", - "flask-empty-outline": "\U000F0095", - "flask-empty-plus": "\U000F123C", - "flask-empty-plus-outline": "\U000F123D", - "flask-empty-remove": "\U000F123E", - "flask-empty-remove-outline": "\U000F123F", - "flask-minus": "\U000F1240", - "flask-minus-outline": "\U000F1241", - "flask-off": "\U000F13F6", - "flask-off-outline": "\U000F13F7", - "flask-outline": "\U000F0096", - "flask-plus": "\U000F1242", - "flask-plus-outline": "\U000F1243", - "flask-remove": "\U000F1244", - "flask-remove-outline": "\U000F1245", - "flask-round-bottom": "\U000F124B", - "flask-round-bottom-empty": "\U000F124C", - "flask-round-bottom-empty-outline": "\U000F124D", - "flask-round-bottom-outline": "\U000F124E", - "fleur-de-lis": "\U000F1303", - "flip-horizontal": "\U000F10E7", - "flip-to-back": "\U000F0247", - "flip-to-front": "\U000F0248", - "flip-vertical": "\U000F10E8", - "floor-lamp": "\U000F08DD", - "floor-lamp-dual": "\U000F1040", - "floor-lamp-variant": "\U000F1041", - "floor-plan": "\U000F0821", - "floppy": "\U000F0249", - "floppy-variant": "\U000F09EF", - "flower": "\U000F024A", - "flower-outline": "\U000F09F0", - "flower-poppy": "\U000F0D08", - "flower-tulip": "\U000F09F1", - "flower-tulip-outline": "\U000F09F2", - "focus-auto": "\U000F0F4E", - "focus-field": "\U000F0F4F", - "focus-field-horizontal": "\U000F0F50", - "focus-field-vertical": "\U000F0F51", - "folder": "\U000F024B", - "folder-account": "\U000F024C", - "folder-account-outline": "\U000F0B9C", - "folder-alert": "\U000F0DCC", - "folder-alert-outline": "\U000F0DCD", - "folder-clock": "\U000F0ABA", - "folder-clock-outline": "\U000F0ABB", - "folder-cog": "\U000F107F", - "folder-cog-outline": "\U000F1080", - "folder-download": "\U000F024D", - "folder-download-outline": "\U000F10E9", - "folder-edit": "\U000F08DE", - "folder-edit-outline": "\U000F0DCE", - "folder-google-drive": "\U000F024E", - "folder-heart": "\U000F10EA", - "folder-heart-outline": "\U000F10EB", - "folder-home": "\U000F10B5", - "folder-home-outline": "\U000F10B6", - "folder-image": "\U000F024F", - "folder-information": "\U000F10B7", - "folder-information-outline": "\U000F10B8", - "folder-key": "\U000F08AC", - "folder-key-network": "\U000F08AD", - "folder-key-network-outline": "\U000F0C80", - "folder-key-outline": "\U000F10EC", - "folder-lock": "\U000F0250", - "folder-lock-open": "\U000F0251", - "folder-marker": "\U000F126D", - "folder-marker-outline": "\U000F126E", - "folder-move": "\U000F0252", - "folder-move-outline": "\U000F1246", - "folder-multiple": "\U000F0253", - "folder-multiple-image": "\U000F0254", - "folder-multiple-outline": "\U000F0255", - "folder-multiple-plus": "\U000F147E", - "folder-multiple-plus-outline": "\U000F147F", - "folder-music": "\U000F1359", - "folder-music-outline": "\U000F135A", - "folder-network": "\U000F0870", - "folder-network-outline": "\U000F0C81", - "folder-open": "\U000F0770", - "folder-open-outline": "\U000F0DCF", - "folder-outline": "\U000F0256", - "folder-plus": "\U000F0257", - "folder-plus-outline": "\U000F0B9D", - "folder-pound": "\U000F0D09", - "folder-pound-outline": "\U000F0D0A", - "folder-refresh": "\U000F0749", - "folder-refresh-outline": "\U000F0542", - "folder-remove": "\U000F0258", - "folder-remove-outline": "\U000F0B9E", - "folder-search": "\U000F0968", - "folder-search-outline": "\U000F0969", - "folder-settings": "\U000F107D", - "folder-settings-outline": "\U000F107E", - "folder-star": "\U000F069D", - "folder-star-multiple": "\U000F13D3", - "folder-star-multiple-outline": "\U000F13D4", - "folder-star-outline": "\U000F0B9F", - "folder-swap": "\U000F0FB6", - "folder-swap-outline": "\U000F0FB7", - "folder-sync": "\U000F0D0B", - "folder-sync-outline": "\U000F0D0C", - "folder-table": "\U000F12E3", - "folder-table-outline": "\U000F12E4", - "folder-text": "\U000F0C82", - "folder-text-outline": "\U000F0C83", - "folder-upload": "\U000F0259", - "folder-upload-outline": "\U000F10ED", - "folder-zip": "\U000F06EB", - "folder-zip-outline": "\U000F07B9", - "font-awesome": "\U000F003A", - "food": "\U000F025A", - "food-apple": "\U000F025B", - "food-apple-outline": "\U000F0C84", - "food-croissant": "\U000F07C8", - "food-drumstick": "\U000F141F", - "food-drumstick-off": "\U000F1468", - "food-drumstick-off-outline": "\U000F1469", - "food-drumstick-outline": "\U000F1420", - "food-fork-drink": "\U000F05F2", - "food-halal": "\U000F1572", - "food-kosher": "\U000F1573", - "food-off": "\U000F05F3", - "food-steak": "\U000F146A", - "food-steak-off": "\U000F146B", - "food-turkey": "\U000F171C", - "food-variant": "\U000F025C", - "food-variant-off": "\U000F13E5", - "foot-print": "\U000F0F52", - "football": "\U000F025D", - "football-australian": "\U000F025E", - "football-helmet": "\U000F025F", - "forklift": "\U000F07C9", - "form-dropdown": "\U000F1400", - "form-select": "\U000F1401", - "form-textarea": "\U000F1095", - "form-textbox": "\U000F060E", - "form-textbox-lock": "\U000F135D", - "form-textbox-password": "\U000F07F5", - "format-align-bottom": "\U000F0753", - "format-align-center": "\U000F0260", - "format-align-justify": "\U000F0261", - "format-align-left": "\U000F0262", - "format-align-middle": "\U000F0754", - "format-align-right": "\U000F0263", - "format-align-top": "\U000F0755", - "format-annotation-minus": "\U000F0ABC", - "format-annotation-plus": "\U000F0646", - "format-bold": "\U000F0264", - "format-clear": "\U000F0265", - "format-color-fill": "\U000F0266", - "format-color-highlight": "\U000F0E31", - "format-color-marker-cancel": "\U000F1313", - "format-color-text": "\U000F069E", - "format-columns": "\U000F08DF", - "format-float-center": "\U000F0267", - "format-float-left": "\U000F0268", - "format-float-none": "\U000F0269", - "format-float-right": "\U000F026A", - "format-font": "\U000F06D6", - "format-font-size-decrease": "\U000F09F3", - "format-font-size-increase": "\U000F09F4", - "format-header-1": "\U000F026B", - "format-header-2": "\U000F026C", - "format-header-3": "\U000F026D", - "format-header-4": "\U000F026E", - "format-header-5": "\U000F026F", - "format-header-6": "\U000F0270", - "format-header-decrease": "\U000F0271", - "format-header-equal": "\U000F0272", - "format-header-increase": "\U000F0273", - "format-header-pound": "\U000F0274", - "format-horizontal-align-center": "\U000F061E", - "format-horizontal-align-left": "\U000F061F", - "format-horizontal-align-right": "\U000F0620", - "format-indent-decrease": "\U000F0275", - "format-indent-increase": "\U000F0276", - "format-italic": "\U000F0277", - "format-letter-case": "\U000F0B34", - "format-letter-case-lower": "\U000F0B35", - "format-letter-case-upper": "\U000F0B36", - "format-letter-ends-with": "\U000F0FB8", - "format-letter-matches": "\U000F0FB9", - "format-letter-starts-with": "\U000F0FBA", - "format-line-spacing": "\U000F0278", - "format-line-style": "\U000F05C8", - "format-line-weight": "\U000F05C9", - "format-list-bulleted": "\U000F0279", - "format-list-bulleted-square": "\U000F0DD0", - "format-list-bulleted-triangle": "\U000F0EB2", - "format-list-bulleted-type": "\U000F027A", - "format-list-checkbox": "\U000F096A", - "format-list-checks": "\U000F0756", - "format-list-numbered": "\U000F027B", - "format-list-numbered-rtl": "\U000F0D0D", - "format-list-text": "\U000F126F", - "format-overline": "\U000F0EB3", - "format-page-break": "\U000F06D7", - "format-paint": "\U000F027C", - "format-paragraph": "\U000F027D", - "format-pilcrow": "\U000F06D8", - "format-quote-close": "\U000F027E", - "format-quote-close-outline": "\U000F11A8", - "format-quote-open": "\U000F0757", - "format-quote-open-outline": "\U000F11A7", - "format-rotate-90": "\U000F06AA", - "format-section": "\U000F069F", - "format-size": "\U000F027F", - "format-strikethrough": "\U000F0280", - "format-strikethrough-variant": "\U000F0281", - "format-subscript": "\U000F0282", - "format-superscript": "\U000F0283", - "format-text": "\U000F0284", - "format-text-rotation-angle-down": "\U000F0FBB", - "format-text-rotation-angle-up": "\U000F0FBC", - "format-text-rotation-down": "\U000F0D73", - "format-text-rotation-down-vertical": "\U000F0FBD", - "format-text-rotation-none": "\U000F0D74", - "format-text-rotation-up": "\U000F0FBE", - "format-text-rotation-vertical": "\U000F0FBF", - "format-text-variant": "\U000F0E32", - "format-text-variant-outline": "\U000F150F", - "format-text-wrapping-clip": "\U000F0D0E", - "format-text-wrapping-overflow": "\U000F0D0F", - "format-text-wrapping-wrap": "\U000F0D10", - "format-textbox": "\U000F0D11", - "format-textdirection-l-to-r": "\U000F0285", - "format-textdirection-r-to-l": "\U000F0286", - "format-title": "\U000F05F4", - "format-underline": "\U000F0287", - "format-vertical-align-bottom": "\U000F0621", - "format-vertical-align-center": "\U000F0622", - "format-vertical-align-top": "\U000F0623", - "format-wrap-inline": "\U000F0288", - "format-wrap-square": "\U000F0289", - "format-wrap-tight": "\U000F028A", - "format-wrap-top-bottom": "\U000F028B", - "forum": "\U000F028C", - "forum-outline": "\U000F0822", - "forward": "\U000F028D", - "forwardburger": "\U000F0D75", - "fountain": "\U000F096B", - "fountain-pen": "\U000F0D12", - "fountain-pen-tip": "\U000F0D13", - "freebsd": "\U000F08E0", - "frequently-asked-questions": "\U000F0EB4", - "fridge": "\U000F0290", - "fridge-alert": "\U000F11B1", - "fridge-alert-outline": "\U000F11B2", - "fridge-bottom": "\U000F0292", - "fridge-industrial": "\U000F15EE", - "fridge-industrial-alert": "\U000F15EF", - "fridge-industrial-alert-outline": "\U000F15F0", - "fridge-industrial-off": "\U000F15F1", - "fridge-industrial-off-outline": "\U000F15F2", - "fridge-industrial-outline": "\U000F15F3", - "fridge-off": "\U000F11AF", - "fridge-off-outline": "\U000F11B0", - "fridge-outline": "\U000F028F", - "fridge-top": "\U000F0291", - "fridge-variant": "\U000F15F4", - "fridge-variant-alert": "\U000F15F5", - "fridge-variant-alert-outline": "\U000F15F6", - "fridge-variant-off": "\U000F15F7", - "fridge-variant-off-outline": "\U000F15F8", - "fridge-variant-outline": "\U000F15F9", - "fruit-cherries": "\U000F1042", - "fruit-cherries-off": "\U000F13F8", - "fruit-citrus": "\U000F1043", - "fruit-citrus-off": "\U000F13F9", - "fruit-grapes": "\U000F1044", - "fruit-grapes-outline": "\U000F1045", - "fruit-pineapple": "\U000F1046", - "fruit-watermelon": "\U000F1047", - "fuel": "\U000F07CA", - "fullscreen": "\U000F0293", - "fullscreen-exit": "\U000F0294", - "function": "\U000F0295", - "function-variant": "\U000F0871", - "furigana-horizontal": "\U000F1081", - "furigana-vertical": "\U000F1082", - "fuse": "\U000F0C85", - "fuse-alert": "\U000F142D", - "fuse-blade": "\U000F0C86", - "fuse-off": "\U000F142C", - "gamepad": "\U000F0296", - "gamepad-circle": "\U000F0E33", - "gamepad-circle-down": "\U000F0E34", - "gamepad-circle-left": "\U000F0E35", - "gamepad-circle-outline": "\U000F0E36", - "gamepad-circle-right": "\U000F0E37", - "gamepad-circle-up": "\U000F0E38", - "gamepad-down": "\U000F0E39", - "gamepad-left": "\U000F0E3A", - "gamepad-right": "\U000F0E3B", - "gamepad-round": "\U000F0E3C", - "gamepad-round-down": "\U000F0E3D", - "gamepad-round-left": "\U000F0E3E", - "gamepad-round-outline": "\U000F0E3F", - "gamepad-round-right": "\U000F0E40", - "gamepad-round-up": "\U000F0E41", - "gamepad-square": "\U000F0EB5", - "gamepad-square-outline": "\U000F0EB6", - "gamepad-up": "\U000F0E42", - "gamepad-variant": "\U000F0297", - "gamepad-variant-outline": "\U000F0EB7", - "gamma": "\U000F10EE", - "gantry-crane": "\U000F0DD1", - "garage": "\U000F06D9", - "garage-alert": "\U000F0872", - "garage-alert-variant": "\U000F12D5", - "garage-open": "\U000F06DA", - "garage-open-variant": "\U000F12D4", - "garage-variant": "\U000F12D3", - "gas-cylinder": "\U000F0647", - "gas-station": "\U000F0298", - "gas-station-off": "\U000F1409", - "gas-station-off-outline": "\U000F140A", - "gas-station-outline": "\U000F0EB8", - "gate": "\U000F0299", - "gate-and": "\U000F08E1", - "gate-arrow-right": "\U000F1169", - "gate-nand": "\U000F08E2", - "gate-nor": "\U000F08E3", - "gate-not": "\U000F08E4", - "gate-open": "\U000F116A", - "gate-or": "\U000F08E5", - "gate-xnor": "\U000F08E6", - "gate-xor": "\U000F08E7", - "gatsby": "\U000F0E43", - "gauge": "\U000F029A", - "gauge-empty": "\U000F0873", - "gauge-full": "\U000F0874", - "gauge-low": "\U000F0875", - "gavel": "\U000F029B", - "gender-female": "\U000F029C", - "gender-male": "\U000F029D", - "gender-male-female": "\U000F029E", - "gender-male-female-variant": "\U000F113F", - "gender-non-binary": "\U000F1140", - "gender-transgender": "\U000F029F", - "gentoo": "\U000F08E8", - "gesture": "\U000F07CB", - "gesture-double-tap": "\U000F073C", - "gesture-pinch": "\U000F0ABD", - "gesture-spread": "\U000F0ABE", - "gesture-swipe": "\U000F0D76", - "gesture-swipe-down": "\U000F073D", - "gesture-swipe-horizontal": "\U000F0ABF", - "gesture-swipe-left": "\U000F073E", - "gesture-swipe-right": "\U000F073F", - "gesture-swipe-up": "\U000F0740", - "gesture-swipe-vertical": "\U000F0AC0", - "gesture-tap": "\U000F0741", - "gesture-tap-box": "\U000F12A9", - "gesture-tap-button": "\U000F12A8", - "gesture-tap-hold": "\U000F0D77", - "gesture-two-double-tap": "\U000F0742", - "gesture-two-tap": "\U000F0743", - "ghost": "\U000F02A0", - "ghost-off": "\U000F09F5", - "ghost-off-outline": "\U000F165C", - "ghost-outline": "\U000F165D", - "gif": "\U000F0D78", - "gift": "\U000F0E44", - "gift-off": "\U000F16EF", - "gift-off-outline": "\U000F16F0", - "gift-open": "\U000F16F1", - "gift-open-outline": "\U000F16F2", - "gift-outline": "\U000F02A1", - "git": "\U000F02A2", - "github": "\U000F02A4", - "gitlab": "\U000F0BA0", - "glass-cocktail": "\U000F0356", - "glass-cocktail-off": "\U000F15E6", - "glass-flute": "\U000F02A5", - "glass-mug": "\U000F02A6", - "glass-mug-off": "\U000F15E7", - "glass-mug-variant": "\U000F1116", - "glass-mug-variant-off": "\U000F15E8", - "glass-pint-outline": "\U000F130D", - "glass-stange": "\U000F02A7", - "glass-tulip": "\U000F02A8", - "glass-wine": "\U000F0876", - "glasses": "\U000F02AA", - "globe-light": "\U000F12D7", - "globe-model": "\U000F08E9", - "gmail": "\U000F02AB", - "gnome": "\U000F02AC", - "go-kart": "\U000F0D79", - "go-kart-track": "\U000F0D7A", - "gog": "\U000F0BA1", - "gold": "\U000F124F", - "golf": "\U000F0823", - "golf-cart": "\U000F11A4", - "golf-tee": "\U000F1083", - "gondola": "\U000F0686", - "goodreads": "\U000F0D7B", - "google": "\U000F02AD", - "google-ads": "\U000F0C87", - "google-analytics": "\U000F07CC", - "google-assistant": "\U000F07CD", - "google-cardboard": "\U000F02AE", - "google-chrome": "\U000F02AF", - "google-circles": "\U000F02B0", - "google-circles-communities": "\U000F02B1", - "google-circles-extended": "\U000F02B2", - "google-circles-group": "\U000F02B3", - "google-classroom": "\U000F02C0", - "google-cloud": "\U000F11F6", - "google-controller": "\U000F02B4", - "google-controller-off": "\U000F02B5", - "google-downasaur": "\U000F1362", - "google-drive": "\U000F02B6", - "google-earth": "\U000F02B7", - "google-fit": "\U000F096C", - "google-glass": "\U000F02B8", - "google-hangouts": "\U000F02C9", - "google-home": "\U000F0824", - "google-keep": "\U000F06DC", - "google-lens": "\U000F09F6", - "google-maps": "\U000F05F5", - "google-my-business": "\U000F1048", - "google-nearby": "\U000F02B9", - "google-photos": "\U000F06DD", - "google-play": "\U000F02BC", - "google-plus": "\U000F02BD", - "google-podcast": "\U000F0EB9", - "google-spreadsheet": "\U000F09F7", - "google-street-view": "\U000F0C88", - "google-translate": "\U000F02BF", - "gradient": "\U000F06A0", - "grain": "\U000F0D7C", - "graph": "\U000F1049", - "graph-outline": "\U000F104A", - "graphql": "\U000F0877", - "grass": "\U000F1510", - "grave-stone": "\U000F0BA2", - "grease-pencil": "\U000F0648", - "greater-than": "\U000F096D", - "greater-than-or-equal": "\U000F096E", - "grid": "\U000F02C1", - "grid-large": "\U000F0758", - "grid-off": "\U000F02C2", - "grill": "\U000F0E45", - "grill-outline": "\U000F118A", - "group": "\U000F02C3", - "guitar-acoustic": "\U000F0771", - "guitar-electric": "\U000F02C4", - "guitar-pick": "\U000F02C5", - "guitar-pick-outline": "\U000F02C6", - "guy-fawkes-mask": "\U000F0825", - "hail": "\U000F0AC1", - "hair-dryer": "\U000F10EF", - "hair-dryer-outline": "\U000F10F0", - "halloween": "\U000F0BA3", - "hamburger": "\U000F0685", - "hammer": "\U000F08EA", - "hammer-screwdriver": "\U000F1322", - "hammer-wrench": "\U000F1323", - "hand": "\U000F0A4F", - "hand-heart": "\U000F10F1", - "hand-heart-outline": "\U000F157E", - "hand-left": "\U000F0E46", - "hand-okay": "\U000F0A50", - "hand-peace": "\U000F0A51", - "hand-peace-variant": "\U000F0A52", - "hand-pointing-down": "\U000F0A53", - "hand-pointing-left": "\U000F0A54", - "hand-pointing-right": "\U000F02C7", - "hand-pointing-up": "\U000F0A55", - "hand-right": "\U000F0E47", - "hand-saw": "\U000F0E48", - "hand-wash": "\U000F157F", - "hand-wash-outline": "\U000F1580", - "hand-water": "\U000F139F", - "handball": "\U000F0F53", - "handcuffs": "\U000F113E", - "handshake": "\U000F1218", - "handshake-outline": "\U000F15A1", - "hanger": "\U000F02C8", - "hard-hat": "\U000F096F", - "harddisk": "\U000F02CA", - "harddisk-plus": "\U000F104B", - "harddisk-remove": "\U000F104C", - "hat-fedora": "\U000F0BA4", - "hazard-lights": "\U000F0C89", - "hdr": "\U000F0D7D", - "hdr-off": "\U000F0D7E", - "head": "\U000F135E", - "head-alert": "\U000F1338", - "head-alert-outline": "\U000F1339", - "head-check": "\U000F133A", - "head-check-outline": "\U000F133B", - "head-cog": "\U000F133C", - "head-cog-outline": "\U000F133D", - "head-dots-horizontal": "\U000F133E", - "head-dots-horizontal-outline": "\U000F133F", - "head-flash": "\U000F1340", - "head-flash-outline": "\U000F1341", - "head-heart": "\U000F1342", - "head-heart-outline": "\U000F1343", - "head-lightbulb": "\U000F1344", - "head-lightbulb-outline": "\U000F1345", - "head-minus": "\U000F1346", - "head-minus-outline": "\U000F1347", - "head-outline": "\U000F135F", - "head-plus": "\U000F1348", - "head-plus-outline": "\U000F1349", - "head-question": "\U000F134A", - "head-question-outline": "\U000F134B", - "head-remove": "\U000F134C", - "head-remove-outline": "\U000F134D", - "head-snowflake": "\U000F134E", - "head-snowflake-outline": "\U000F134F", - "head-sync": "\U000F1350", - "head-sync-outline": "\U000F1351", - "headphones": "\U000F02CB", - "headphones-bluetooth": "\U000F0970", - "headphones-box": "\U000F02CC", - "headphones-off": "\U000F07CE", - "headphones-settings": "\U000F02CD", - "headset": "\U000F02CE", - "headset-dock": "\U000F02CF", - "headset-off": "\U000F02D0", - "heart": "\U000F02D1", - "heart-box": "\U000F02D2", - "heart-box-outline": "\U000F02D3", - "heart-broken": "\U000F02D4", - "heart-broken-outline": "\U000F0D14", - "heart-circle": "\U000F0971", - "heart-circle-outline": "\U000F0972", - "heart-cog": "\U000F1663", - "heart-cog-outline": "\U000F1664", - "heart-flash": "\U000F0EF9", - "heart-half": "\U000F06DF", - "heart-half-full": "\U000F06DE", - "heart-half-outline": "\U000F06E0", - "heart-minus": "\U000F142F", - "heart-minus-outline": "\U000F1432", - "heart-multiple": "\U000F0A56", - "heart-multiple-outline": "\U000F0A57", - "heart-off": "\U000F0759", - "heart-off-outline": "\U000F1434", - "heart-outline": "\U000F02D5", - "heart-plus": "\U000F142E", - "heart-plus-outline": "\U000F1431", - "heart-pulse": "\U000F05F6", - "heart-remove": "\U000F1430", - "heart-remove-outline": "\U000F1433", - "heart-settings": "\U000F1665", - "heart-settings-outline": "\U000F1666", - "helicopter": "\U000F0AC2", - "help": "\U000F02D6", - "help-box": "\U000F078B", - "help-circle": "\U000F02D7", - "help-circle-outline": "\U000F0625", - "help-network": "\U000F06F5", - "help-network-outline": "\U000F0C8A", - "help-rhombus": "\U000F0BA5", - "help-rhombus-outline": "\U000F0BA6", - "hexadecimal": "\U000F12A7", - "hexagon": "\U000F02D8", - "hexagon-multiple": "\U000F06E1", - "hexagon-multiple-outline": "\U000F10F2", - "hexagon-outline": "\U000F02D9", - "hexagon-slice-1": "\U000F0AC3", - "hexagon-slice-2": "\U000F0AC4", - "hexagon-slice-3": "\U000F0AC5", - "hexagon-slice-4": "\U000F0AC6", - "hexagon-slice-5": "\U000F0AC7", - "hexagon-slice-6": "\U000F0AC8", - "hexagram": "\U000F0AC9", - "hexagram-outline": "\U000F0ACA", - "high-definition": "\U000F07CF", - "high-definition-box": "\U000F0878", - "highway": "\U000F05F7", - "hiking": "\U000F0D7F", - "hinduism": "\U000F0973", - "history": "\U000F02DA", - "hockey-puck": "\U000F0879", - "hockey-sticks": "\U000F087A", - "hololens": "\U000F02DB", - "home": "\U000F02DC", - "home-account": "\U000F0826", - "home-alert": "\U000F087B", - "home-alert-outline": "\U000F15D0", - "home-analytics": "\U000F0EBA", - "home-assistant": "\U000F07D0", - "home-automation": "\U000F07D1", - "home-circle": "\U000F07D2", - "home-circle-outline": "\U000F104D", - "home-city": "\U000F0D15", - "home-city-outline": "\U000F0D16", - "home-currency-usd": "\U000F08AF", - "home-edit": "\U000F1159", - "home-edit-outline": "\U000F115A", - "home-export-outline": "\U000F0F9B", - "home-flood": "\U000F0EFA", - "home-floor-0": "\U000F0DD2", - "home-floor-1": "\U000F0D80", - "home-floor-2": "\U000F0D81", - "home-floor-3": "\U000F0D82", - "home-floor-a": "\U000F0D83", - "home-floor-b": "\U000F0D84", - "home-floor-g": "\U000F0D85", - "home-floor-l": "\U000F0D86", - "home-floor-negative-1": "\U000F0DD3", - "home-group": "\U000F0DD4", - "home-heart": "\U000F0827", - "home-import-outline": "\U000F0F9C", - "home-lightbulb": "\U000F1251", - "home-lightbulb-outline": "\U000F1252", - "home-lock": "\U000F08EB", - "home-lock-open": "\U000F08EC", - "home-map-marker": "\U000F05F8", - "home-minus": "\U000F0974", - "home-minus-outline": "\U000F13D5", - "home-modern": "\U000F02DD", - "home-outline": "\U000F06A1", - "home-plus": "\U000F0975", - "home-plus-outline": "\U000F13D6", - "home-remove": "\U000F1247", - "home-remove-outline": "\U000F13D7", - "home-roof": "\U000F112B", - "home-search": "\U000F13B0", - "home-search-outline": "\U000F13B1", - "home-thermometer": "\U000F0F54", - "home-thermometer-outline": "\U000F0F55", - "home-variant": "\U000F02DE", - "home-variant-outline": "\U000F0BA7", - "hook": "\U000F06E2", - "hook-off": "\U000F06E3", - "hops": "\U000F02DF", - "horizontal-rotate-clockwise": "\U000F10F3", - "horizontal-rotate-counterclockwise": "\U000F10F4", - "horse": "\U000F15BF", - "horse-human": "\U000F15C0", - "horse-variant": "\U000F15C1", - "horseshoe": "\U000F0A58", - "hospital": "\U000F0FF6", - "hospital-box": "\U000F02E0", - "hospital-box-outline": "\U000F0FF7", - "hospital-building": "\U000F02E1", - "hospital-marker": "\U000F02E2", - "hot-tub": "\U000F0828", - "hours-24": "\U000F1478", - "hubspot": "\U000F0D17", - "hulu": "\U000F0829", - "human": "\U000F02E6", - "human-baby-changing-table": "\U000F138B", - "human-cane": "\U000F1581", - "human-capacity-decrease": "\U000F159B", - "human-capacity-increase": "\U000F159C", - "human-child": "\U000F02E7", - "human-edit": "\U000F14E8", - "human-female": "\U000F0649", - "human-female-boy": "\U000F0A59", - "human-female-dance": "\U000F15C9", - "human-female-female": "\U000F0A5A", - "human-female-girl": "\U000F0A5B", - "human-greeting": "\U000F064A", - "human-greeting-proximity": "\U000F159D", - "human-handsdown": "\U000F064B", - "human-handsup": "\U000F064C", - "human-male": "\U000F064D", - "human-male-boy": "\U000F0A5C", - "human-male-child": "\U000F138C", - "human-male-female": "\U000F02E8", - "human-male-girl": "\U000F0A5D", - "human-male-height": "\U000F0EFB", - "human-male-height-variant": "\U000F0EFC", - "human-male-male": "\U000F0A5E", - "human-pregnant": "\U000F05CF", - "human-queue": "\U000F1571", - "human-scooter": "\U000F11E9", - "human-wheelchair": "\U000F138D", - "humble-bundle": "\U000F0744", - "hvac": "\U000F1352", - "hvac-off": "\U000F159E", - "hydraulic-oil-level": "\U000F1324", - "hydraulic-oil-temperature": "\U000F1325", - "hydro-power": "\U000F12E5", - "ice-cream": "\U000F082A", - "ice-cream-off": "\U000F0E52", - "ice-pop": "\U000F0EFD", - "id-card": "\U000F0FC0", - "identifier": "\U000F0EFE", - "ideogram-cjk": "\U000F1331", - "ideogram-cjk-variant": "\U000F1332", - "iframe": "\U000F0C8B", - "iframe-array": "\U000F10F5", - "iframe-array-outline": "\U000F10F6", - "iframe-braces": "\U000F10F7", - "iframe-braces-outline": "\U000F10F8", - "iframe-outline": "\U000F0C8C", - "iframe-parentheses": "\U000F10F9", - "iframe-parentheses-outline": "\U000F10FA", - "iframe-variable": "\U000F10FB", - "iframe-variable-outline": "\U000F10FC", - "image": "\U000F02E9", - "image-album": "\U000F02EA", - "image-area": "\U000F02EB", - "image-area-close": "\U000F02EC", - "image-auto-adjust": "\U000F0FC1", - "image-broken": "\U000F02ED", - "image-broken-variant": "\U000F02EE", - "image-edit": "\U000F11E3", - "image-edit-outline": "\U000F11E4", - "image-filter-black-white": "\U000F02F0", - "image-filter-center-focus": "\U000F02F1", - "image-filter-center-focus-strong": "\U000F0EFF", - "image-filter-center-focus-strong-outline": "\U000F0F00", - "image-filter-center-focus-weak": "\U000F02F2", - "image-filter-drama": "\U000F02F3", - "image-filter-frames": "\U000F02F4", - "image-filter-hdr": "\U000F02F5", - "image-filter-none": "\U000F02F6", - "image-filter-tilt-shift": "\U000F02F7", - "image-filter-vintage": "\U000F02F8", - "image-frame": "\U000F0E49", - "image-minus": "\U000F1419", - "image-move": "\U000F09F8", - "image-multiple": "\U000F02F9", - "image-multiple-outline": "\U000F02EF", - "image-off": "\U000F082B", - "image-off-outline": "\U000F11D1", - "image-outline": "\U000F0976", - "image-plus": "\U000F087C", - "image-remove": "\U000F1418", - "image-search": "\U000F0977", - "image-search-outline": "\U000F0978", - "image-size-select-actual": "\U000F0C8D", - "image-size-select-large": "\U000F0C8E", - "image-size-select-small": "\U000F0C8F", - "image-text": "\U000F160D", - "import": "\U000F02FA", - "inbox": "\U000F0687", - "inbox-arrow-down": "\U000F02FB", - "inbox-arrow-down-outline": "\U000F1270", - "inbox-arrow-up": "\U000F03D1", - "inbox-arrow-up-outline": "\U000F1271", - "inbox-full": "\U000F1272", - "inbox-full-outline": "\U000F1273", - "inbox-multiple": "\U000F08B0", - "inbox-multiple-outline": "\U000F0BA8", - "inbox-outline": "\U000F1274", - "inbox-remove": "\U000F159F", - "inbox-remove-outline": "\U000F15A0", - "incognito": "\U000F05F9", - "incognito-circle": "\U000F1421", - "incognito-circle-off": "\U000F1422", - "incognito-off": "\U000F0075", - "infinity": "\U000F06E4", - "information": "\U000F02FC", - "information-outline": "\U000F02FD", - "information-variant": "\U000F064E", - "instagram": "\U000F02FE", - "instrument-triangle": "\U000F104E", - "invert-colors": "\U000F0301", - "invert-colors-off": "\U000F0E4A", - "iobroker": "\U000F12E8", - "ip": "\U000F0A5F", - "ip-network": "\U000F0A60", - "ip-network-outline": "\U000F0C90", - "ipod": "\U000F0C91", - "islam": "\U000F0979", - "island": "\U000F104F", - "iv-bag": "\U000F10B9", - "jabber": "\U000F0DD5", - "jeepney": "\U000F0302", - "jellyfish": "\U000F0F01", - "jellyfish-outline": "\U000F0F02", - "jira": "\U000F0303", - "jquery": "\U000F087D", - "jsfiddle": "\U000F0304", - "judaism": "\U000F097A", - "jump-rope": "\U000F12FF", - "kabaddi": "\U000F0D87", - "kangaroo": "\U000F1558", - "karate": "\U000F082C", - "keg": "\U000F0305", - "kettle": "\U000F05FA", - "kettle-alert": "\U000F1317", - "kettle-alert-outline": "\U000F1318", - "kettle-off": "\U000F131B", - "kettle-off-outline": "\U000F131C", - "kettle-outline": "\U000F0F56", - "kettle-pour-over": "\U000F173C", - "kettle-steam": "\U000F1319", - "kettle-steam-outline": "\U000F131A", - "kettlebell": "\U000F1300", - "key": "\U000F0306", - "key-arrow-right": "\U000F1312", - "key-chain": "\U000F1574", - "key-chain-variant": "\U000F1575", - "key-change": "\U000F0307", - "key-link": "\U000F119F", - "key-minus": "\U000F0308", - "key-outline": "\U000F0DD6", - "key-plus": "\U000F0309", - "key-remove": "\U000F030A", - "key-star": "\U000F119E", - "key-variant": "\U000F030B", - "key-wireless": "\U000F0FC2", - "keyboard": "\U000F030C", - "keyboard-backspace": "\U000F030D", - "keyboard-caps": "\U000F030E", - "keyboard-close": "\U000F030F", - "keyboard-esc": "\U000F12B7", - "keyboard-f1": "\U000F12AB", - "keyboard-f10": "\U000F12B4", - "keyboard-f11": "\U000F12B5", - "keyboard-f12": "\U000F12B6", - "keyboard-f2": "\U000F12AC", - "keyboard-f3": "\U000F12AD", - "keyboard-f4": "\U000F12AE", - "keyboard-f5": "\U000F12AF", - "keyboard-f6": "\U000F12B0", - "keyboard-f7": "\U000F12B1", - "keyboard-f8": "\U000F12B2", - "keyboard-f9": "\U000F12B3", - "keyboard-off": "\U000F0310", - "keyboard-off-outline": "\U000F0E4B", - "keyboard-outline": "\U000F097B", - "keyboard-return": "\U000F0311", - "keyboard-settings": "\U000F09F9", - "keyboard-settings-outline": "\U000F09FA", - "keyboard-space": "\U000F1050", - "keyboard-tab": "\U000F0312", - "keyboard-variant": "\U000F0313", - "khanda": "\U000F10FD", - "kickstarter": "\U000F0745", - "klingon": "\U000F135B", - "knife": "\U000F09FB", - "knife-military": "\U000F09FC", - "koala": "\U000F173F", - "kodi": "\U000F0314", - "kubernetes": "\U000F10FE", - "label": "\U000F0315", - "label-multiple": "\U000F1375", - "label-multiple-outline": "\U000F1376", - "label-off": "\U000F0ACB", - "label-off-outline": "\U000F0ACC", - "label-outline": "\U000F0316", - "label-percent": "\U000F12EA", - "label-percent-outline": "\U000F12EB", - "label-variant": "\U000F0ACD", - "label-variant-outline": "\U000F0ACE", - "ladder": "\U000F15A2", - "ladybug": "\U000F082D", - "lambda": "\U000F0627", - "lamp": "\U000F06B5", - "lamps": "\U000F1576", - "lan": "\U000F0317", - "lan-check": "\U000F12AA", - "lan-connect": "\U000F0318", - "lan-disconnect": "\U000F0319", - "lan-pending": "\U000F031A", - "language-c": "\U000F0671", - "language-cpp": "\U000F0672", - "language-csharp": "\U000F031B", - "language-css3": "\U000F031C", - "language-fortran": "\U000F121A", - "language-go": "\U000F07D3", - "language-haskell": "\U000F0C92", - "language-html5": "\U000F031D", - "language-java": "\U000F0B37", - "language-javascript": "\U000F031E", - "language-kotlin": "\U000F1219", - "language-lua": "\U000F08B1", - "language-markdown": "\U000F0354", - "language-markdown-outline": "\U000F0F5B", - "language-php": "\U000F031F", - "language-python": "\U000F0320", - "language-r": "\U000F07D4", - "language-ruby": "\U000F0D2D", - "language-ruby-on-rails": "\U000F0ACF", - "language-rust": "\U000F1617", - "language-swift": "\U000F06E5", - "language-typescript": "\U000F06E6", - "language-xaml": "\U000F0673", - "laptop": "\U000F0322", - "laptop-chromebook": "\U000F0323", - "laptop-mac": "\U000F0324", - "laptop-off": "\U000F06E7", - "laptop-windows": "\U000F0325", - "laravel": "\U000F0AD0", - "laser-pointer": "\U000F1484", - "lasso": "\U000F0F03", - "lastpass": "\U000F0446", - "latitude": "\U000F0F57", - "launch": "\U000F0327", - "lava-lamp": "\U000F07D5", - "layers": "\U000F0328", - "layers-minus": "\U000F0E4C", - "layers-off": "\U000F0329", - "layers-off-outline": "\U000F09FD", - "layers-outline": "\U000F09FE", - "layers-plus": "\U000F0E4D", - "layers-remove": "\U000F0E4E", - "layers-search": "\U000F1206", - "layers-search-outline": "\U000F1207", - "layers-triple": "\U000F0F58", - "layers-triple-outline": "\U000F0F59", - "lead-pencil": "\U000F064F", - "leaf": "\U000F032A", - "leaf-maple": "\U000F0C93", - "leaf-maple-off": "\U000F12DA", - "leaf-off": "\U000F12D9", - "leak": "\U000F0DD7", - "leak-off": "\U000F0DD8", - "led-off": "\U000F032B", - "led-on": "\U000F032C", - "led-outline": "\U000F032D", - "led-strip": "\U000F07D6", - "led-strip-variant": "\U000F1051", - "led-variant-off": "\U000F032E", - "led-variant-on": "\U000F032F", - "led-variant-outline": "\U000F0330", - "leek": "\U000F117D", - "less-than": "\U000F097C", - "less-than-or-equal": "\U000F097D", - "library": "\U000F0331", - "library-shelves": "\U000F0BA9", - "license": "\U000F0FC3", - "lifebuoy": "\U000F087E", - "light-switch": "\U000F097E", - "lightbulb": "\U000F0335", - "lightbulb-cfl": "\U000F1208", - "lightbulb-cfl-off": "\U000F1209", - "lightbulb-cfl-spiral": "\U000F1275", - "lightbulb-cfl-spiral-off": "\U000F12C3", - "lightbulb-group": "\U000F1253", - "lightbulb-group-off": "\U000F12CD", - "lightbulb-group-off-outline": "\U000F12CE", - "lightbulb-group-outline": "\U000F1254", - "lightbulb-multiple": "\U000F1255", - "lightbulb-multiple-off": "\U000F12CF", - "lightbulb-multiple-off-outline": "\U000F12D0", - "lightbulb-multiple-outline": "\U000F1256", - "lightbulb-off": "\U000F0E4F", - "lightbulb-off-outline": "\U000F0E50", - "lightbulb-on": "\U000F06E8", - "lightbulb-on-outline": "\U000F06E9", - "lightbulb-outline": "\U000F0336", - "lighthouse": "\U000F09FF", - "lighthouse-on": "\U000F0A00", - "lightning-bolt": "\U000F140B", - "lightning-bolt-outline": "\U000F140C", - "lingerie": "\U000F1476", - "link": "\U000F0337", - "link-box": "\U000F0D1A", - "link-box-outline": "\U000F0D1B", - "link-box-variant": "\U000F0D1C", - "link-box-variant-outline": "\U000F0D1D", - "link-lock": "\U000F10BA", - "link-off": "\U000F0338", - "link-plus": "\U000F0C94", - "link-variant": "\U000F0339", - "link-variant-minus": "\U000F10FF", - "link-variant-off": "\U000F033A", - "link-variant-plus": "\U000F1100", - "link-variant-remove": "\U000F1101", - "linkedin": "\U000F033B", - "linux": "\U000F033D", - "linux-mint": "\U000F08ED", - "lipstick": "\U000F13B5", - "list-status": "\U000F15AB", - "litecoin": "\U000F0A61", - "loading": "\U000F0772", - "location-enter": "\U000F0FC4", - "location-exit": "\U000F0FC5", - "lock": "\U000F033E", - "lock-alert": "\U000F08EE", - "lock-alert-outline": "\U000F15D1", - "lock-check": "\U000F139A", - "lock-check-outline": "\U000F16A8", - "lock-clock": "\U000F097F", - "lock-minus": "\U000F16A9", - "lock-minus-outline": "\U000F16AA", - "lock-off": "\U000F1671", - "lock-off-outline": "\U000F1672", - "lock-open": "\U000F033F", - "lock-open-alert": "\U000F139B", - "lock-open-alert-outline": "\U000F15D2", - "lock-open-check": "\U000F139C", - "lock-open-check-outline": "\U000F16AB", - "lock-open-minus": "\U000F16AC", - "lock-open-minus-outline": "\U000F16AD", - "lock-open-outline": "\U000F0340", - "lock-open-plus": "\U000F16AE", - "lock-open-plus-outline": "\U000F16AF", - "lock-open-remove": "\U000F16B0", - "lock-open-remove-outline": "\U000F16B1", - "lock-open-variant": "\U000F0FC6", - "lock-open-variant-outline": "\U000F0FC7", - "lock-outline": "\U000F0341", - "lock-pattern": "\U000F06EA", - "lock-plus": "\U000F05FB", - "lock-plus-outline": "\U000F16B2", - "lock-question": "\U000F08EF", - "lock-remove": "\U000F16B3", - "lock-remove-outline": "\U000F16B4", - "lock-reset": "\U000F0773", - "lock-smart": "\U000F08B2", - "locker": "\U000F07D7", - "locker-multiple": "\U000F07D8", - "login": "\U000F0342", - "login-variant": "\U000F05FC", - "logout": "\U000F0343", - "logout-variant": "\U000F05FD", - "longitude": "\U000F0F5A", - "looks": "\U000F0344", - "lotion": "\U000F1582", - "lotion-outline": "\U000F1583", - "lotion-plus": "\U000F1584", - "lotion-plus-outline": "\U000F1585", - "loupe": "\U000F0345", - "lumx": "\U000F0346", - "lungs": "\U000F1084", - "magnet": "\U000F0347", - "magnet-on": "\U000F0348", - "magnify": "\U000F0349", - "magnify-close": "\U000F0980", - "magnify-minus": "\U000F034A", - "magnify-minus-cursor": "\U000F0A62", - "magnify-minus-outline": "\U000F06EC", - "magnify-plus": "\U000F034B", - "magnify-plus-cursor": "\U000F0A63", - "magnify-plus-outline": "\U000F06ED", - "magnify-remove-cursor": "\U000F120C", - "magnify-remove-outline": "\U000F120D", - "magnify-scan": "\U000F1276", - "mail": "\U000F0EBB", - "mailbox": "\U000F06EE", - "mailbox-open": "\U000F0D88", - "mailbox-open-outline": "\U000F0D89", - "mailbox-open-up": "\U000F0D8A", - "mailbox-open-up-outline": "\U000F0D8B", - "mailbox-outline": "\U000F0D8C", - "mailbox-up": "\U000F0D8D", - "mailbox-up-outline": "\U000F0D8E", - "manjaro": "\U000F160A", - "map": "\U000F034D", - "map-check": "\U000F0EBC", - "map-check-outline": "\U000F0EBD", - "map-clock": "\U000F0D1E", - "map-clock-outline": "\U000F0D1F", - "map-legend": "\U000F0A01", - "map-marker": "\U000F034E", - "map-marker-alert": "\U000F0F05", - "map-marker-alert-outline": "\U000F0F06", - "map-marker-check": "\U000F0C95", - "map-marker-check-outline": "\U000F12FB", - "map-marker-circle": "\U000F034F", - "map-marker-distance": "\U000F08F0", - "map-marker-down": "\U000F1102", - "map-marker-left": "\U000F12DB", - "map-marker-left-outline": "\U000F12DD", - "map-marker-minus": "\U000F0650", - "map-marker-minus-outline": "\U000F12F9", - "map-marker-multiple": "\U000F0350", - "map-marker-multiple-outline": "\U000F1277", - "map-marker-off": "\U000F0351", - "map-marker-off-outline": "\U000F12FD", - "map-marker-outline": "\U000F07D9", - "map-marker-path": "\U000F0D20", - "map-marker-plus": "\U000F0651", - "map-marker-plus-outline": "\U000F12F8", - "map-marker-question": "\U000F0F07", - "map-marker-question-outline": "\U000F0F08", - "map-marker-radius": "\U000F0352", - "map-marker-radius-outline": "\U000F12FC", - "map-marker-remove": "\U000F0F09", - "map-marker-remove-outline": "\U000F12FA", - "map-marker-remove-variant": "\U000F0F0A", - "map-marker-right": "\U000F12DC", - "map-marker-right-outline": "\U000F12DE", - "map-marker-star": "\U000F1608", - "map-marker-star-outline": "\U000F1609", - "map-marker-up": "\U000F1103", - "map-minus": "\U000F0981", - "map-outline": "\U000F0982", - "map-plus": "\U000F0983", - "map-search": "\U000F0984", - "map-search-outline": "\U000F0985", - "mapbox": "\U000F0BAA", - "margin": "\U000F0353", - "marker": "\U000F0652", - "marker-cancel": "\U000F0DD9", - "marker-check": "\U000F0355", - "mastodon": "\U000F0AD1", - "material-design": "\U000F0986", - "material-ui": "\U000F0357", - "math-compass": "\U000F0358", - "math-cos": "\U000F0C96", - "math-integral": "\U000F0FC8", - "math-integral-box": "\U000F0FC9", - "math-log": "\U000F1085", - "math-norm": "\U000F0FCA", - "math-norm-box": "\U000F0FCB", - "math-sin": "\U000F0C97", - "math-tan": "\U000F0C98", - "matrix": "\U000F0628", - "medal": "\U000F0987", - "medal-outline": "\U000F1326", - "medical-bag": "\U000F06EF", - "meditation": "\U000F117B", - "memory": "\U000F035B", - "menu": "\U000F035C", - "menu-down": "\U000F035D", - "menu-down-outline": "\U000F06B6", - "menu-left": "\U000F035E", - "menu-left-outline": "\U000F0A02", - "menu-open": "\U000F0BAB", - "menu-right": "\U000F035F", - "menu-right-outline": "\U000F0A03", - "menu-swap": "\U000F0A64", - "menu-swap-outline": "\U000F0A65", - "menu-up": "\U000F0360", - "menu-up-outline": "\U000F06B7", - "merge": "\U000F0F5C", - "message": "\U000F0361", - "message-alert": "\U000F0362", - "message-alert-outline": "\U000F0A04", - "message-arrow-left": "\U000F12F2", - "message-arrow-left-outline": "\U000F12F3", - "message-arrow-right": "\U000F12F4", - "message-arrow-right-outline": "\U000F12F5", - "message-bookmark": "\U000F15AC", - "message-bookmark-outline": "\U000F15AD", - "message-bulleted": "\U000F06A2", - "message-bulleted-off": "\U000F06A3", - "message-cog": "\U000F06F1", - "message-cog-outline": "\U000F1172", - "message-draw": "\U000F0363", - "message-flash": "\U000F15A9", - "message-flash-outline": "\U000F15AA", - "message-image": "\U000F0364", - "message-image-outline": "\U000F116C", - "message-lock": "\U000F0FCC", - "message-lock-outline": "\U000F116D", - "message-minus": "\U000F116E", - "message-minus-outline": "\U000F116F", - "message-off": "\U000F164D", - "message-off-outline": "\U000F164E", - "message-outline": "\U000F0365", - "message-plus": "\U000F0653", - "message-plus-outline": "\U000F10BB", - "message-processing": "\U000F0366", - "message-processing-outline": "\U000F1170", - "message-question": "\U000F173A", - "message-question-outline": "\U000F173B", - "message-reply": "\U000F0367", - "message-reply-outline": "\U000F173D", - "message-reply-text": "\U000F0368", - "message-reply-text-outline": "\U000F173E", - "message-settings": "\U000F06F0", - "message-settings-outline": "\U000F1171", - "message-text": "\U000F0369", - "message-text-clock": "\U000F1173", - "message-text-clock-outline": "\U000F1174", - "message-text-lock": "\U000F0FCD", - "message-text-lock-outline": "\U000F1175", - "message-text-outline": "\U000F036A", - "message-video": "\U000F036B", - "meteor": "\U000F0629", - "metronome": "\U000F07DA", - "metronome-tick": "\U000F07DB", - "micro-sd": "\U000F07DC", - "microphone": "\U000F036C", - "microphone-minus": "\U000F08B3", - "microphone-off": "\U000F036D", - "microphone-outline": "\U000F036E", - "microphone-plus": "\U000F08B4", - "microphone-settings": "\U000F036F", - "microphone-variant": "\U000F0370", - "microphone-variant-off": "\U000F0371", - "microscope": "\U000F0654", - "microsoft": "\U000F0372", - "microsoft-access": "\U000F138E", - "microsoft-azure": "\U000F0805", - "microsoft-azure-devops": "\U000F0FD5", - "microsoft-bing": "\U000F00A4", - "microsoft-dynamics-365": "\U000F0988", - "microsoft-edge": "\U000F01E9", - "microsoft-edge-legacy": "\U000F1250", - "microsoft-excel": "\U000F138F", - "microsoft-internet-explorer": "\U000F0300", - "microsoft-office": "\U000F03C6", - "microsoft-onedrive": "\U000F03CA", - "microsoft-onenote": "\U000F0747", - "microsoft-outlook": "\U000F0D22", - "microsoft-powerpoint": "\U000F1390", - "microsoft-sharepoint": "\U000F1391", - "microsoft-teams": "\U000F02BB", - "microsoft-visual-studio": "\U000F0610", - "microsoft-visual-studio-code": "\U000F0A1E", - "microsoft-windows": "\U000F05B3", - "microsoft-windows-classic": "\U000F0A21", - "microsoft-word": "\U000F1392", - "microsoft-xbox": "\U000F05B9", - "microsoft-xbox-controller": "\U000F05BA", - "microsoft-xbox-controller-battery-alert": "\U000F074B", - "microsoft-xbox-controller-battery-charging": "\U000F0A22", - "microsoft-xbox-controller-battery-empty": "\U000F074C", - "microsoft-xbox-controller-battery-full": "\U000F074D", - "microsoft-xbox-controller-battery-low": "\U000F074E", - "microsoft-xbox-controller-battery-medium": "\U000F074F", - "microsoft-xbox-controller-battery-unknown": "\U000F0750", - "microsoft-xbox-controller-menu": "\U000F0E6F", - "microsoft-xbox-controller-off": "\U000F05BB", - "microsoft-xbox-controller-view": "\U000F0E70", - "microsoft-yammer": "\U000F0789", - "microwave": "\U000F0C99", - "microwave-off": "\U000F1423", - "middleware": "\U000F0F5D", - "middleware-outline": "\U000F0F5E", - "midi": "\U000F08F1", - "midi-port": "\U000F08F2", - "mine": "\U000F0DDA", - "minecraft": "\U000F0373", - "mini-sd": "\U000F0A05", - "minidisc": "\U000F0A06", - "minus": "\U000F0374", - "minus-box": "\U000F0375", - "minus-box-multiple": "\U000F1141", - "minus-box-multiple-outline": "\U000F1142", - "minus-box-outline": "\U000F06F2", - "minus-circle": "\U000F0376", - "minus-circle-multiple": "\U000F035A", - "minus-circle-multiple-outline": "\U000F0AD3", - "minus-circle-off": "\U000F1459", - "minus-circle-off-outline": "\U000F145A", - "minus-circle-outline": "\U000F0377", - "minus-network": "\U000F0378", - "minus-network-outline": "\U000F0C9A", - "minus-thick": "\U000F1639", - "mirror": "\U000F11FD", - "mixed-martial-arts": "\U000F0D8F", - "mixed-reality": "\U000F087F", - "molecule": "\U000F0BAC", - "molecule-co": "\U000F12FE", - "molecule-co2": "\U000F07E4", - "monitor": "\U000F0379", - "monitor-cellphone": "\U000F0989", - "monitor-cellphone-star": "\U000F098A", - "monitor-clean": "\U000F1104", - "monitor-dashboard": "\U000F0A07", - "monitor-edit": "\U000F12C6", - "monitor-eye": "\U000F13B4", - "monitor-lock": "\U000F0DDB", - "monitor-multiple": "\U000F037A", - "monitor-off": "\U000F0D90", - "monitor-screenshot": "\U000F0E51", - "monitor-share": "\U000F1483", - "monitor-speaker": "\U000F0F5F", - "monitor-speaker-off": "\U000F0F60", - "monitor-star": "\U000F0DDC", - "moon-first-quarter": "\U000F0F61", - "moon-full": "\U000F0F62", - "moon-last-quarter": "\U000F0F63", - "moon-new": "\U000F0F64", - "moon-waning-crescent": "\U000F0F65", - "moon-waning-gibbous": "\U000F0F66", - "moon-waxing-crescent": "\U000F0F67", - "moon-waxing-gibbous": "\U000F0F68", - "moped": "\U000F1086", - "moped-electric": "\U000F15B7", - "moped-electric-outline": "\U000F15B8", - "moped-outline": "\U000F15B9", - "more": "\U000F037B", - "mother-heart": "\U000F1314", - "mother-nurse": "\U000F0D21", - "motion": "\U000F15B2", - "motion-outline": "\U000F15B3", - "motion-pause": "\U000F1590", - "motion-pause-outline": "\U000F1592", - "motion-play": "\U000F158F", - "motion-play-outline": "\U000F1591", - "motion-sensor": "\U000F0D91", - "motion-sensor-off": "\U000F1435", - "motorbike": "\U000F037C", - "motorbike-electric": "\U000F15BA", - "mouse": "\U000F037D", - "mouse-bluetooth": "\U000F098B", - "mouse-move-down": "\U000F1550", - "mouse-move-up": "\U000F1551", - "mouse-move-vertical": "\U000F1552", - "mouse-off": "\U000F037E", - "mouse-variant": "\U000F037F", - "mouse-variant-off": "\U000F0380", - "move-resize": "\U000F0655", - "move-resize-variant": "\U000F0656", - "movie": "\U000F0381", - "movie-check": "\U000F16F3", - "movie-check-outline": "\U000F16F4", - "movie-cog": "\U000F16F5", - "movie-cog-outline": "\U000F16F6", - "movie-edit": "\U000F1122", - "movie-edit-outline": "\U000F1123", - "movie-filter": "\U000F1124", - "movie-filter-outline": "\U000F1125", - "movie-minus": "\U000F16F7", - "movie-minus-outline": "\U000F16F8", - "movie-off": "\U000F16F9", - "movie-off-outline": "\U000F16FA", - "movie-open": "\U000F0FCE", - "movie-open-check": "\U000F16FB", - "movie-open-check-outline": "\U000F16FC", - "movie-open-cog": "\U000F16FD", - "movie-open-cog-outline": "\U000F16FE", - "movie-open-edit": "\U000F16FF", - "movie-open-edit-outline": "\U000F1700", - "movie-open-minus": "\U000F1701", - "movie-open-minus-outline": "\U000F1702", - "movie-open-off": "\U000F1703", - "movie-open-off-outline": "\U000F1704", - "movie-open-outline": "\U000F0FCF", - "movie-open-play": "\U000F1705", - "movie-open-play-outline": "\U000F1706", - "movie-open-plus": "\U000F1707", - "movie-open-plus-outline": "\U000F1708", - "movie-open-remove": "\U000F1709", - "movie-open-remove-outline": "\U000F170A", - "movie-open-settings": "\U000F170B", - "movie-open-settings-outline": "\U000F170C", - "movie-open-star": "\U000F170D", - "movie-open-star-outline": "\U000F170E", - "movie-outline": "\U000F0DDD", - "movie-play": "\U000F170F", - "movie-play-outline": "\U000F1710", - "movie-plus": "\U000F1711", - "movie-plus-outline": "\U000F1712", - "movie-remove": "\U000F1713", - "movie-remove-outline": "\U000F1714", - "movie-roll": "\U000F07DE", - "movie-search": "\U000F11D2", - "movie-search-outline": "\U000F11D3", - "movie-settings": "\U000F1715", - "movie-settings-outline": "\U000F1716", - "movie-star": "\U000F1717", - "movie-star-outline": "\U000F1718", - "mower": "\U000F166F", - "mower-bag": "\U000F1670", - "muffin": "\U000F098C", - "multiplication": "\U000F0382", - "multiplication-box": "\U000F0383", - "mushroom": "\U000F07DF", - "mushroom-off": "\U000F13FA", - "mushroom-off-outline": "\U000F13FB", - "mushroom-outline": "\U000F07E0", - "music": "\U000F075A", - "music-accidental-double-flat": "\U000F0F69", - "music-accidental-double-sharp": "\U000F0F6A", - "music-accidental-flat": "\U000F0F6B", - "music-accidental-natural": "\U000F0F6C", - "music-accidental-sharp": "\U000F0F6D", - "music-box": "\U000F0384", - "music-box-multiple": "\U000F0333", - "music-box-multiple-outline": "\U000F0F04", - "music-box-outline": "\U000F0385", - "music-circle": "\U000F0386", - "music-circle-outline": "\U000F0AD4", - "music-clef-alto": "\U000F0F6E", - "music-clef-bass": "\U000F0F6F", - "music-clef-treble": "\U000F0F70", - "music-note": "\U000F0387", - "music-note-bluetooth": "\U000F05FE", - "music-note-bluetooth-off": "\U000F05FF", - "music-note-eighth": "\U000F0388", - "music-note-eighth-dotted": "\U000F0F71", - "music-note-half": "\U000F0389", - "music-note-half-dotted": "\U000F0F72", - "music-note-off": "\U000F038A", - "music-note-off-outline": "\U000F0F73", - "music-note-outline": "\U000F0F74", - "music-note-plus": "\U000F0DDE", - "music-note-quarter": "\U000F038B", - "music-note-quarter-dotted": "\U000F0F75", - "music-note-sixteenth": "\U000F038C", - "music-note-sixteenth-dotted": "\U000F0F76", - "music-note-whole": "\U000F038D", - "music-note-whole-dotted": "\U000F0F77", - "music-off": "\U000F075B", - "music-rest-eighth": "\U000F0F78", - "music-rest-half": "\U000F0F79", - "music-rest-quarter": "\U000F0F7A", - "music-rest-sixteenth": "\U000F0F7B", - "music-rest-whole": "\U000F0F7C", - "mustache": "\U000F15DE", - "nail": "\U000F0DDF", - "nas": "\U000F08F3", - "nativescript": "\U000F0880", - "nature": "\U000F038E", - "nature-people": "\U000F038F", - "navigation": "\U000F0390", - "navigation-outline": "\U000F1607", - "near-me": "\U000F05CD", - "necklace": "\U000F0F0B", - "needle": "\U000F0391", - "netflix": "\U000F0746", - "network": "\U000F06F3", - "network-off": "\U000F0C9B", - "network-off-outline": "\U000F0C9C", - "network-outline": "\U000F0C9D", - "network-strength-1": "\U000F08F4", - "network-strength-1-alert": "\U000F08F5", - "network-strength-2": "\U000F08F6", - "network-strength-2-alert": "\U000F08F7", - "network-strength-3": "\U000F08F8", - "network-strength-3-alert": "\U000F08F9", - "network-strength-4": "\U000F08FA", - "network-strength-4-alert": "\U000F08FB", - "network-strength-off": "\U000F08FC", - "network-strength-off-outline": "\U000F08FD", - "network-strength-outline": "\U000F08FE", - "new-box": "\U000F0394", - "newspaper": "\U000F0395", - "newspaper-minus": "\U000F0F0C", - "newspaper-plus": "\U000F0F0D", - "newspaper-variant": "\U000F1001", - "newspaper-variant-multiple": "\U000F1002", - "newspaper-variant-multiple-outline": "\U000F1003", - "newspaper-variant-outline": "\U000F1004", - "nfc": "\U000F0396", - "nfc-search-variant": "\U000F0E53", - "nfc-tap": "\U000F0397", - "nfc-variant": "\U000F0398", - "nfc-variant-off": "\U000F0E54", - "ninja": "\U000F0774", - "nintendo-game-boy": "\U000F1393", - "nintendo-switch": "\U000F07E1", - "nintendo-wii": "\U000F05AB", - "nintendo-wiiu": "\U000F072D", - "nix": "\U000F1105", - "nodejs": "\U000F0399", - "noodles": "\U000F117E", - "not-equal": "\U000F098D", - "not-equal-variant": "\U000F098E", - "note": "\U000F039A", - "note-minus": "\U000F164F", - "note-minus-outline": "\U000F1650", - "note-multiple": "\U000F06B8", - "note-multiple-outline": "\U000F06B9", - "note-outline": "\U000F039B", - "note-plus": "\U000F039C", - "note-plus-outline": "\U000F039D", - "note-remove": "\U000F1651", - "note-remove-outline": "\U000F1652", - "note-search": "\U000F1653", - "note-search-outline": "\U000F1654", - "note-text": "\U000F039E", - "note-text-outline": "\U000F11D7", - "notebook": "\U000F082E", - "notebook-check": "\U000F14F5", - "notebook-check-outline": "\U000F14F6", - "notebook-edit": "\U000F14E7", - "notebook-edit-outline": "\U000F14E9", - "notebook-minus": "\U000F1610", - "notebook-minus-outline": "\U000F1611", - "notebook-multiple": "\U000F0E55", - "notebook-outline": "\U000F0EBF", - "notebook-plus": "\U000F1612", - "notebook-plus-outline": "\U000F1613", - "notebook-remove": "\U000F1614", - "notebook-remove-outline": "\U000F1615", - "notification-clear-all": "\U000F039F", - "npm": "\U000F06F7", - "nuke": "\U000F06A4", - "null": "\U000F07E2", - "numeric": "\U000F03A0", - "numeric-0": "\U000F0B39", - "numeric-0-box": "\U000F03A1", - "numeric-0-box-multiple": "\U000F0F0E", - "numeric-0-box-multiple-outline": "\U000F03A2", - "numeric-0-box-outline": "\U000F03A3", - "numeric-0-circle": "\U000F0C9E", - "numeric-0-circle-outline": "\U000F0C9F", - "numeric-1": "\U000F0B3A", - "numeric-1-box": "\U000F03A4", - "numeric-1-box-multiple": "\U000F0F0F", - "numeric-1-box-multiple-outline": "\U000F03A5", - "numeric-1-box-outline": "\U000F03A6", - "numeric-1-circle": "\U000F0CA0", - "numeric-1-circle-outline": "\U000F0CA1", - "numeric-10": "\U000F0FE9", - "numeric-10-box": "\U000F0F7D", - "numeric-10-box-multiple": "\U000F0FEA", - "numeric-10-box-multiple-outline": "\U000F0FEB", - "numeric-10-box-outline": "\U000F0F7E", - "numeric-10-circle": "\U000F0FEC", - "numeric-10-circle-outline": "\U000F0FED", - "numeric-2": "\U000F0B3B", - "numeric-2-box": "\U000F03A7", - "numeric-2-box-multiple": "\U000F0F10", - "numeric-2-box-multiple-outline": "\U000F03A8", - "numeric-2-box-outline": "\U000F03A9", - "numeric-2-circle": "\U000F0CA2", - "numeric-2-circle-outline": "\U000F0CA3", - "numeric-3": "\U000F0B3C", - "numeric-3-box": "\U000F03AA", - "numeric-3-box-multiple": "\U000F0F11", - "numeric-3-box-multiple-outline": "\U000F03AB", - "numeric-3-box-outline": "\U000F03AC", - "numeric-3-circle": "\U000F0CA4", - "numeric-3-circle-outline": "\U000F0CA5", - "numeric-4": "\U000F0B3D", - "numeric-4-box": "\U000F03AD", - "numeric-4-box-multiple": "\U000F0F12", - "numeric-4-box-multiple-outline": "\U000F03B2", - "numeric-4-box-outline": "\U000F03AE", - "numeric-4-circle": "\U000F0CA6", - "numeric-4-circle-outline": "\U000F0CA7", - "numeric-5": "\U000F0B3E", - "numeric-5-box": "\U000F03B1", - "numeric-5-box-multiple": "\U000F0F13", - "numeric-5-box-multiple-outline": "\U000F03AF", - "numeric-5-box-outline": "\U000F03B0", - "numeric-5-circle": "\U000F0CA8", - "numeric-5-circle-outline": "\U000F0CA9", - "numeric-6": "\U000F0B3F", - "numeric-6-box": "\U000F03B3", - "numeric-6-box-multiple": "\U000F0F14", - "numeric-6-box-multiple-outline": "\U000F03B4", - "numeric-6-box-outline": "\U000F03B5", - "numeric-6-circle": "\U000F0CAA", - "numeric-6-circle-outline": "\U000F0CAB", - "numeric-7": "\U000F0B40", - "numeric-7-box": "\U000F03B6", - "numeric-7-box-multiple": "\U000F0F15", - "numeric-7-box-multiple-outline": "\U000F03B7", - "numeric-7-box-outline": "\U000F03B8", - "numeric-7-circle": "\U000F0CAC", - "numeric-7-circle-outline": "\U000F0CAD", - "numeric-8": "\U000F0B41", - "numeric-8-box": "\U000F03B9", - "numeric-8-box-multiple": "\U000F0F16", - "numeric-8-box-multiple-outline": "\U000F03BA", - "numeric-8-box-outline": "\U000F03BB", - "numeric-8-circle": "\U000F0CAE", - "numeric-8-circle-outline": "\U000F0CAF", - "numeric-9": "\U000F0B42", - "numeric-9-box": "\U000F03BC", - "numeric-9-box-multiple": "\U000F0F17", - "numeric-9-box-multiple-outline": "\U000F03BD", - "numeric-9-box-outline": "\U000F03BE", - "numeric-9-circle": "\U000F0CB0", - "numeric-9-circle-outline": "\U000F0CB1", - "numeric-9-plus": "\U000F0FEE", - "numeric-9-plus-box": "\U000F03BF", - "numeric-9-plus-box-multiple": "\U000F0F18", - "numeric-9-plus-box-multiple-outline": "\U000F03C0", - "numeric-9-plus-box-outline": "\U000F03C1", - "numeric-9-plus-circle": "\U000F0CB2", - "numeric-9-plus-circle-outline": "\U000F0CB3", - "numeric-negative-1": "\U000F1052", - "numeric-positive-1": "\U000F15CB", - "nut": "\U000F06F8", - "nutrition": "\U000F03C2", - "nuxt": "\U000F1106", - "oar": "\U000F067C", - "ocarina": "\U000F0DE0", - "oci": "\U000F12E9", - "ocr": "\U000F113A", - "octagon": "\U000F03C3", - "octagon-outline": "\U000F03C4", - "octagram": "\U000F06F9", - "octagram-outline": "\U000F0775", - "odnoklassniki": "\U000F03C5", - "offer": "\U000F121B", - "office-building": "\U000F0991", - "office-building-marker": "\U000F1520", - "office-building-marker-outline": "\U000F1521", - "office-building-outline": "\U000F151F", - "oil": "\U000F03C7", - "oil-lamp": "\U000F0F19", - "oil-level": "\U000F1053", - "oil-temperature": "\U000F0FF8", - "omega": "\U000F03C9", - "one-up": "\U000F0BAD", - "onepassword": "\U000F0881", - "opacity": "\U000F05CC", - "open-in-app": "\U000F03CB", - "open-in-new": "\U000F03CC", - "open-source-initiative": "\U000F0BAE", - "openid": "\U000F03CD", - "opera": "\U000F03CE", - "orbit": "\U000F0018", - "orbit-variant": "\U000F15DB", - "order-alphabetical-ascending": "\U000F020D", - "order-alphabetical-descending": "\U000F0D07", - "order-bool-ascending": "\U000F02BE", - "order-bool-ascending-variant": "\U000F098F", - "order-bool-descending": "\U000F1384", - "order-bool-descending-variant": "\U000F0990", - "order-numeric-ascending": "\U000F0545", - "order-numeric-descending": "\U000F0546", - "origin": "\U000F0B43", - "ornament": "\U000F03CF", - "ornament-variant": "\U000F03D0", - "outdoor-lamp": "\U000F1054", - "overscan": "\U000F1005", - "owl": "\U000F03D2", - "pac-man": "\U000F0BAF", - "package": "\U000F03D3", - "package-down": "\U000F03D4", - "package-up": "\U000F03D5", - "package-variant": "\U000F03D6", - "package-variant-closed": "\U000F03D7", - "page-first": "\U000F0600", - "page-last": "\U000F0601", - "page-layout-body": "\U000F06FA", - "page-layout-footer": "\U000F06FB", - "page-layout-header": "\U000F06FC", - "page-layout-header-footer": "\U000F0F7F", - "page-layout-sidebar-left": "\U000F06FD", - "page-layout-sidebar-right": "\U000F06FE", - "page-next": "\U000F0BB0", - "page-next-outline": "\U000F0BB1", - "page-previous": "\U000F0BB2", - "page-previous-outline": "\U000F0BB3", - "pail": "\U000F1417", - "pail-minus": "\U000F1437", - "pail-minus-outline": "\U000F143C", - "pail-off": "\U000F1439", - "pail-off-outline": "\U000F143E", - "pail-outline": "\U000F143A", - "pail-plus": "\U000F1436", - "pail-plus-outline": "\U000F143B", - "pail-remove": "\U000F1438", - "pail-remove-outline": "\U000F143D", - "palette": "\U000F03D8", - "palette-advanced": "\U000F03D9", - "palette-outline": "\U000F0E0C", - "palette-swatch": "\U000F08B5", - "palette-swatch-outline": "\U000F135C", - "palm-tree": "\U000F1055", - "pan": "\U000F0BB4", - "pan-bottom-left": "\U000F0BB5", - "pan-bottom-right": "\U000F0BB6", - "pan-down": "\U000F0BB7", - "pan-horizontal": "\U000F0BB8", - "pan-left": "\U000F0BB9", - "pan-right": "\U000F0BBA", - "pan-top-left": "\U000F0BBB", - "pan-top-right": "\U000F0BBC", - "pan-up": "\U000F0BBD", - "pan-vertical": "\U000F0BBE", - "panda": "\U000F03DA", - "pandora": "\U000F03DB", - "panorama": "\U000F03DC", - "panorama-fisheye": "\U000F03DD", - "panorama-horizontal": "\U000F03DE", - "panorama-vertical": "\U000F03DF", - "panorama-wide-angle": "\U000F03E0", - "paper-cut-vertical": "\U000F03E1", - "paper-roll": "\U000F1157", - "paper-roll-outline": "\U000F1158", - "paperclip": "\U000F03E2", - "parachute": "\U000F0CB4", - "parachute-outline": "\U000F0CB5", - "parking": "\U000F03E3", - "party-popper": "\U000F1056", - "passport": "\U000F07E3", - "passport-biometric": "\U000F0DE1", - "pasta": "\U000F1160", - "patio-heater": "\U000F0F80", - "patreon": "\U000F0882", - "pause": "\U000F03E4", - "pause-circle": "\U000F03E5", - "pause-circle-outline": "\U000F03E6", - "pause-octagon": "\U000F03E7", - "pause-octagon-outline": "\U000F03E8", - "paw": "\U000F03E9", - "paw-off": "\U000F0657", - "paw-off-outline": "\U000F1676", - "paw-outline": "\U000F1675", - "pdf-box": "\U000F0E56", - "peace": "\U000F0884", - "peanut": "\U000F0FFC", - "peanut-off": "\U000F0FFD", - "peanut-off-outline": "\U000F0FFF", - "peanut-outline": "\U000F0FFE", - "pen": "\U000F03EA", - "pen-lock": "\U000F0DE2", - "pen-minus": "\U000F0DE3", - "pen-off": "\U000F0DE4", - "pen-plus": "\U000F0DE5", - "pen-remove": "\U000F0DE6", - "pencil": "\U000F03EB", - "pencil-box": "\U000F03EC", - "pencil-box-multiple": "\U000F1144", - "pencil-box-multiple-outline": "\U000F1145", - "pencil-box-outline": "\U000F03ED", - "pencil-circle": "\U000F06FF", - "pencil-circle-outline": "\U000F0776", - "pencil-lock": "\U000F03EE", - "pencil-lock-outline": "\U000F0DE7", - "pencil-minus": "\U000F0DE8", - "pencil-minus-outline": "\U000F0DE9", - "pencil-off": "\U000F03EF", - "pencil-off-outline": "\U000F0DEA", - "pencil-outline": "\U000F0CB6", - "pencil-plus": "\U000F0DEB", - "pencil-plus-outline": "\U000F0DEC", - "pencil-remove": "\U000F0DED", - "pencil-remove-outline": "\U000F0DEE", - "pencil-ruler": "\U000F1353", - "penguin": "\U000F0EC0", - "pentagon": "\U000F0701", - "pentagon-outline": "\U000F0700", - "pentagram": "\U000F1667", - "percent": "\U000F03F0", - "percent-outline": "\U000F1278", - "periodic-table": "\U000F08B6", - "perspective-less": "\U000F0D23", - "perspective-more": "\U000F0D24", - "pharmacy": "\U000F03F1", - "phone": "\U000F03F2", - "phone-alert": "\U000F0F1A", - "phone-alert-outline": "\U000F118E", - "phone-bluetooth": "\U000F03F3", - "phone-bluetooth-outline": "\U000F118F", - "phone-cancel": "\U000F10BC", - "phone-cancel-outline": "\U000F1190", - "phone-check": "\U000F11A9", - "phone-check-outline": "\U000F11AA", - "phone-classic": "\U000F0602", - "phone-classic-off": "\U000F1279", - "phone-dial": "\U000F1559", - "phone-dial-outline": "\U000F155A", - "phone-forward": "\U000F03F4", - "phone-forward-outline": "\U000F1191", - "phone-hangup": "\U000F03F5", - "phone-hangup-outline": "\U000F1192", - "phone-in-talk": "\U000F03F6", - "phone-in-talk-outline": "\U000F1182", - "phone-incoming": "\U000F03F7", - "phone-incoming-outline": "\U000F1193", - "phone-lock": "\U000F03F8", - "phone-lock-outline": "\U000F1194", - "phone-log": "\U000F03F9", - "phone-log-outline": "\U000F1195", - "phone-message": "\U000F1196", - "phone-message-outline": "\U000F1197", - "phone-minus": "\U000F0658", - "phone-minus-outline": "\U000F1198", - "phone-missed": "\U000F03FA", - "phone-missed-outline": "\U000F11A5", - "phone-off": "\U000F0DEF", - "phone-off-outline": "\U000F11A6", - "phone-outgoing": "\U000F03FB", - "phone-outgoing-outline": "\U000F1199", - "phone-outline": "\U000F0DF0", - "phone-paused": "\U000F03FC", - "phone-paused-outline": "\U000F119A", - "phone-plus": "\U000F0659", - "phone-plus-outline": "\U000F119B", - "phone-remove": "\U000F152F", - "phone-remove-outline": "\U000F1530", - "phone-return": "\U000F082F", - "phone-return-outline": "\U000F119C", - "phone-ring": "\U000F11AB", - "phone-ring-outline": "\U000F11AC", - "phone-rotate-landscape": "\U000F0885", - "phone-rotate-portrait": "\U000F0886", - "phone-settings": "\U000F03FD", - "phone-settings-outline": "\U000F119D", - "phone-voip": "\U000F03FE", - "pi": "\U000F03FF", - "pi-box": "\U000F0400", - "pi-hole": "\U000F0DF1", - "piano": "\U000F067D", - "pickaxe": "\U000F08B7", - "picture-in-picture-bottom-right": "\U000F0E57", - "picture-in-picture-bottom-right-outline": "\U000F0E58", - "picture-in-picture-top-right": "\U000F0E59", - "picture-in-picture-top-right-outline": "\U000F0E5A", - "pier": "\U000F0887", - "pier-crane": "\U000F0888", - "pig": "\U000F0401", - "pig-variant": "\U000F1006", - "pig-variant-outline": "\U000F1678", - "piggy-bank": "\U000F1007", - "piggy-bank-outline": "\U000F1679", - "pill": "\U000F0402", - "pillar": "\U000F0702", - "pin": "\U000F0403", - "pin-off": "\U000F0404", - "pin-off-outline": "\U000F0930", - "pin-outline": "\U000F0931", - "pine-tree": "\U000F0405", - "pine-tree-box": "\U000F0406", - "pine-tree-fire": "\U000F141A", - "pinterest": "\U000F0407", - "pinwheel": "\U000F0AD5", - "pinwheel-outline": "\U000F0AD6", - "pipe": "\U000F07E5", - "pipe-disconnected": "\U000F07E6", - "pipe-leak": "\U000F0889", - "pipe-wrench": "\U000F1354", - "pirate": "\U000F0A08", - "pistol": "\U000F0703", - "piston": "\U000F088A", - "pitchfork": "\U000F1553", - "pizza": "\U000F0409", - "play": "\U000F040A", - "play-box": "\U000F127A", - "play-box-multiple": "\U000F0D19", - "play-box-multiple-outline": "\U000F13E6", - "play-box-outline": "\U000F040B", - "play-circle": "\U000F040C", - "play-circle-outline": "\U000F040D", - "play-network": "\U000F088B", - "play-network-outline": "\U000F0CB7", - "play-outline": "\U000F0F1B", - "play-pause": "\U000F040E", - "play-protected-content": "\U000F040F", - "play-speed": "\U000F08FF", - "playlist-check": "\U000F05C7", - "playlist-edit": "\U000F0900", - "playlist-minus": "\U000F0410", - "playlist-music": "\U000F0CB8", - "playlist-music-outline": "\U000F0CB9", - "playlist-play": "\U000F0411", - "playlist-plus": "\U000F0412", - "playlist-remove": "\U000F0413", - "playlist-star": "\U000F0DF2", - "plex": "\U000F06BA", - "plus": "\U000F0415", - "plus-box": "\U000F0416", - "plus-box-multiple": "\U000F0334", - "plus-box-multiple-outline": "\U000F1143", - "plus-box-outline": "\U000F0704", - "plus-circle": "\U000F0417", - "plus-circle-multiple": "\U000F034C", - "plus-circle-multiple-outline": "\U000F0418", - "plus-circle-outline": "\U000F0419", - "plus-minus": "\U000F0992", - "plus-minus-box": "\U000F0993", - "plus-minus-variant": "\U000F14C9", - "plus-network": "\U000F041A", - "plus-network-outline": "\U000F0CBA", - "plus-one": "\U000F041B", - "plus-outline": "\U000F0705", - "plus-thick": "\U000F11EC", - "podcast": "\U000F0994", - "podium": "\U000F0D25", - "podium-bronze": "\U000F0D26", - "podium-gold": "\U000F0D27", - "podium-silver": "\U000F0D28", - "point-of-sale": "\U000F0D92", - "pokeball": "\U000F041D", - "pokemon-go": "\U000F0A09", - "poker-chip": "\U000F0830", - "polaroid": "\U000F041E", - "police-badge": "\U000F1167", - "police-badge-outline": "\U000F1168", - "poll": "\U000F041F", - "poll-box": "\U000F0420", - "poll-box-outline": "\U000F127B", - "polo": "\U000F14C3", - "polymer": "\U000F0421", - "pool": "\U000F0606", - "popcorn": "\U000F0422", - "post": "\U000F1008", - "post-outline": "\U000F1009", - "postage-stamp": "\U000F0CBB", - "pot": "\U000F02E5", - "pot-mix": "\U000F065B", - "pot-mix-outline": "\U000F0677", - "pot-outline": "\U000F02FF", - "pot-steam": "\U000F065A", - "pot-steam-outline": "\U000F0326", - "pound": "\U000F0423", - "pound-box": "\U000F0424", - "pound-box-outline": "\U000F117F", - "power": "\U000F0425", - "power-cycle": "\U000F0901", - "power-off": "\U000F0902", - "power-on": "\U000F0903", - "power-plug": "\U000F06A5", - "power-plug-off": "\U000F06A6", - "power-plug-off-outline": "\U000F1424", - "power-plug-outline": "\U000F1425", - "power-settings": "\U000F0426", - "power-sleep": "\U000F0904", - "power-socket": "\U000F0427", - "power-socket-au": "\U000F0905", - "power-socket-de": "\U000F1107", - "power-socket-eu": "\U000F07E7", - "power-socket-fr": "\U000F1108", - "power-socket-it": "\U000F14FF", - "power-socket-jp": "\U000F1109", - "power-socket-uk": "\U000F07E8", - "power-socket-us": "\U000F07E9", - "power-standby": "\U000F0906", - "powershell": "\U000F0A0A", - "prescription": "\U000F0706", - "presentation": "\U000F0428", - "presentation-play": "\U000F0429", - "pretzel": "\U000F1562", - "printer": "\U000F042A", - "printer-3d": "\U000F042B", - "printer-3d-nozzle": "\U000F0E5B", - "printer-3d-nozzle-alert": "\U000F11C0", - "printer-3d-nozzle-alert-outline": "\U000F11C1", - "printer-3d-nozzle-outline": "\U000F0E5C", - "printer-alert": "\U000F042C", - "printer-check": "\U000F1146", - "printer-eye": "\U000F1458", - "printer-off": "\U000F0E5D", - "printer-pos": "\U000F1057", - "printer-search": "\U000F1457", - "printer-settings": "\U000F0707", - "printer-wireless": "\U000F0A0B", - "priority-high": "\U000F0603", - "priority-low": "\U000F0604", - "professional-hexagon": "\U000F042D", - "progress-alert": "\U000F0CBC", - "progress-check": "\U000F0995", - "progress-clock": "\U000F0996", - "progress-close": "\U000F110A", - "progress-download": "\U000F0997", - "progress-question": "\U000F1522", - "progress-upload": "\U000F0998", - "progress-wrench": "\U000F0CBD", - "projector": "\U000F042E", - "projector-screen": "\U000F042F", - "projector-screen-outline": "\U000F1724", - "propane-tank": "\U000F1357", - "propane-tank-outline": "\U000F1358", - "protocol": "\U000F0FD8", - "publish": "\U000F06A7", - "pulse": "\U000F0430", - "pump": "\U000F1402", - "pumpkin": "\U000F0BBF", - "purse": "\U000F0F1C", - "purse-outline": "\U000F0F1D", - "puzzle": "\U000F0431", - "puzzle-check": "\U000F1426", - "puzzle-check-outline": "\U000F1427", - "puzzle-edit": "\U000F14D3", - "puzzle-edit-outline": "\U000F14D9", - "puzzle-heart": "\U000F14D4", - "puzzle-heart-outline": "\U000F14DA", - "puzzle-minus": "\U000F14D1", - "puzzle-minus-outline": "\U000F14D7", - "puzzle-outline": "\U000F0A66", - "puzzle-plus": "\U000F14D0", - "puzzle-plus-outline": "\U000F14D6", - "puzzle-remove": "\U000F14D2", - "puzzle-remove-outline": "\U000F14D8", - "puzzle-star": "\U000F14D5", - "puzzle-star-outline": "\U000F14DB", - "qi": "\U000F0999", - "qqchat": "\U000F0605", - "qrcode": "\U000F0432", - "qrcode-edit": "\U000F08B8", - "qrcode-minus": "\U000F118C", - "qrcode-plus": "\U000F118B", - "qrcode-remove": "\U000F118D", - "qrcode-scan": "\U000F0433", - "quadcopter": "\U000F0434", - "quality-high": "\U000F0435", - "quality-low": "\U000F0A0C", - "quality-medium": "\U000F0A0D", - "quora": "\U000F0D29", - "rabbit": "\U000F0907", - "racing-helmet": "\U000F0D93", - "racquetball": "\U000F0D94", - "radar": "\U000F0437", - "radiator": "\U000F0438", - "radiator-disabled": "\U000F0AD7", - "radiator-off": "\U000F0AD8", - "radio": "\U000F0439", - "radio-am": "\U000F0CBE", - "radio-fm": "\U000F0CBF", - "radio-handheld": "\U000F043A", - "radio-off": "\U000F121C", - "radio-tower": "\U000F043B", - "radioactive": "\U000F043C", - "radioactive-off": "\U000F0EC1", - "radiobox-blank": "\U000F043D", - "radiobox-marked": "\U000F043E", - "radiology-box": "\U000F14C5", - "radiology-box-outline": "\U000F14C6", - "radius": "\U000F0CC0", - "radius-outline": "\U000F0CC1", - "railroad-light": "\U000F0F1E", - "rake": "\U000F1544", - "raspberry-pi": "\U000F043F", - "ray-end": "\U000F0440", - "ray-end-arrow": "\U000F0441", - "ray-start": "\U000F0442", - "ray-start-arrow": "\U000F0443", - "ray-start-end": "\U000F0444", - "ray-start-vertex-end": "\U000F15D8", - "ray-vertex": "\U000F0445", - "react": "\U000F0708", - "read": "\U000F0447", - "receipt": "\U000F0449", - "record": "\U000F044A", - "record-circle": "\U000F0EC2", - "record-circle-outline": "\U000F0EC3", - "record-player": "\U000F099A", - "record-rec": "\U000F044B", - "rectangle": "\U000F0E5E", - "rectangle-outline": "\U000F0E5F", - "recycle": "\U000F044C", - "recycle-variant": "\U000F139D", - "reddit": "\U000F044D", - "redhat": "\U000F111B", - "redo": "\U000F044E", - "redo-variant": "\U000F044F", - "reflect-horizontal": "\U000F0A0E", - "reflect-vertical": "\U000F0A0F", - "refresh": "\U000F0450", - "refresh-circle": "\U000F1377", - "regex": "\U000F0451", - "registered-trademark": "\U000F0A67", - "reiterate": "\U000F1588", - "relation-many-to-many": "\U000F1496", - "relation-many-to-one": "\U000F1497", - "relation-many-to-one-or-many": "\U000F1498", - "relation-many-to-only-one": "\U000F1499", - "relation-many-to-zero-or-many": "\U000F149A", - "relation-many-to-zero-or-one": "\U000F149B", - "relation-one-or-many-to-many": "\U000F149C", - "relation-one-or-many-to-one": "\U000F149D", - "relation-one-or-many-to-one-or-many": "\U000F149E", - "relation-one-or-many-to-only-one": "\U000F149F", - "relation-one-or-many-to-zero-or-many": "\U000F14A0", - "relation-one-or-many-to-zero-or-one": "\U000F14A1", - "relation-one-to-many": "\U000F14A2", - "relation-one-to-one": "\U000F14A3", - "relation-one-to-one-or-many": "\U000F14A4", - "relation-one-to-only-one": "\U000F14A5", - "relation-one-to-zero-or-many": "\U000F14A6", - "relation-one-to-zero-or-one": "\U000F14A7", - "relation-only-one-to-many": "\U000F14A8", - "relation-only-one-to-one": "\U000F14A9", - "relation-only-one-to-one-or-many": "\U000F14AA", - "relation-only-one-to-only-one": "\U000F14AB", - "relation-only-one-to-zero-or-many": "\U000F14AC", - "relation-only-one-to-zero-or-one": "\U000F14AD", - "relation-zero-or-many-to-many": "\U000F14AE", - "relation-zero-or-many-to-one": "\U000F14AF", - "relation-zero-or-many-to-one-or-many": "\U000F14B0", - "relation-zero-or-many-to-only-one": "\U000F14B1", - "relation-zero-or-many-to-zero-or-many": "\U000F14B2", - "relation-zero-or-many-to-zero-or-one": "\U000F14B3", - "relation-zero-or-one-to-many": "\U000F14B4", - "relation-zero-or-one-to-one": "\U000F14B5", - "relation-zero-or-one-to-one-or-many": "\U000F14B6", - "relation-zero-or-one-to-only-one": "\U000F14B7", - "relation-zero-or-one-to-zero-or-many": "\U000F14B8", - "relation-zero-or-one-to-zero-or-one": "\U000F14B9", - "relative-scale": "\U000F0452", - "reload": "\U000F0453", - "reload-alert": "\U000F110B", - "reminder": "\U000F088C", - "remote": "\U000F0454", - "remote-desktop": "\U000F08B9", - "remote-off": "\U000F0EC4", - "remote-tv": "\U000F0EC5", - "remote-tv-off": "\U000F0EC6", - "rename-box": "\U000F0455", - "reorder-horizontal": "\U000F0688", - "reorder-vertical": "\U000F0689", - "repeat": "\U000F0456", - "repeat-off": "\U000F0457", - "repeat-once": "\U000F0458", - "replay": "\U000F0459", - "reply": "\U000F045A", - "reply-all": "\U000F045B", - "reply-all-outline": "\U000F0F1F", - "reply-circle": "\U000F11AE", - "reply-outline": "\U000F0F20", - "reproduction": "\U000F045C", - "resistor": "\U000F0B44", - "resistor-nodes": "\U000F0B45", - "resize": "\U000F0A68", - "resize-bottom-right": "\U000F045D", - "responsive": "\U000F045E", - "restart": "\U000F0709", - "restart-alert": "\U000F110C", - "restart-off": "\U000F0D95", - "restore": "\U000F099B", - "restore-alert": "\U000F110D", - "rewind": "\U000F045F", - "rewind-10": "\U000F0D2A", - "rewind-30": "\U000F0D96", - "rewind-5": "\U000F11F9", - "rewind-60": "\U000F160C", - "rewind-outline": "\U000F070A", - "rhombus": "\U000F070B", - "rhombus-medium": "\U000F0A10", - "rhombus-medium-outline": "\U000F14DC", - "rhombus-outline": "\U000F070C", - "rhombus-split": "\U000F0A11", - "rhombus-split-outline": "\U000F14DD", - "ribbon": "\U000F0460", - "rice": "\U000F07EA", - "rickshaw": "\U000F15BB", - "rickshaw-electric": "\U000F15BC", - "ring": "\U000F07EB", - "rivet": "\U000F0E60", - "road": "\U000F0461", - "road-variant": "\U000F0462", - "robber": "\U000F1058", - "robot": "\U000F06A9", - "robot-angry": "\U000F169D", - "robot-angry-outline": "\U000F169E", - "robot-confused": "\U000F169F", - "robot-confused-outline": "\U000F16A0", - "robot-dead": "\U000F16A1", - "robot-dead-outline": "\U000F16A2", - "robot-excited": "\U000F16A3", - "robot-excited-outline": "\U000F16A4", - "robot-happy": "\U000F1719", - "robot-happy-outline": "\U000F171A", - "robot-industrial": "\U000F0B46", - "robot-love": "\U000F16A5", - "robot-love-outline": "\U000F16A6", - "robot-mower": "\U000F11F7", - "robot-mower-outline": "\U000F11F3", - "robot-off": "\U000F16A7", - "robot-off-outline": "\U000F167B", - "robot-outline": "\U000F167A", - "robot-vacuum": "\U000F070D", - "robot-vacuum-variant": "\U000F0908", - "rocket": "\U000F0463", - "rocket-launch": "\U000F14DE", - "rocket-launch-outline": "\U000F14DF", - "rocket-outline": "\U000F13AF", - "rodent": "\U000F1327", - "roller-skate": "\U000F0D2B", - "roller-skate-off": "\U000F0145", - "rollerblade": "\U000F0D2C", - "rollerblade-off": "\U000F002E", - "rollupjs": "\U000F0BC0", - "roman-numeral-1": "\U000F1088", - "roman-numeral-10": "\U000F1091", - "roman-numeral-2": "\U000F1089", - "roman-numeral-3": "\U000F108A", - "roman-numeral-4": "\U000F108B", - "roman-numeral-5": "\U000F108C", - "roman-numeral-6": "\U000F108D", - "roman-numeral-7": "\U000F108E", - "roman-numeral-8": "\U000F108F", - "roman-numeral-9": "\U000F1090", - "room-service": "\U000F088D", - "room-service-outline": "\U000F0D97", - "rotate-3d": "\U000F0EC7", - "rotate-3d-variant": "\U000F0464", - "rotate-left": "\U000F0465", - "rotate-left-variant": "\U000F0466", - "rotate-orbit": "\U000F0D98", - "rotate-right": "\U000F0467", - "rotate-right-variant": "\U000F0468", - "rounded-corner": "\U000F0607", - "router": "\U000F11E2", - "router-network": "\U000F1087", - "router-wireless": "\U000F0469", - "router-wireless-off": "\U000F15A3", - "router-wireless-settings": "\U000F0A69", - "routes": "\U000F046A", - "routes-clock": "\U000F1059", - "rowing": "\U000F0608", - "rss": "\U000F046B", - "rss-box": "\U000F046C", - "rss-off": "\U000F0F21", - "rug": "\U000F1475", - "rugby": "\U000F0D99", - "ruler": "\U000F046D", - "ruler-square": "\U000F0CC2", - "ruler-square-compass": "\U000F0EBE", - "run": "\U000F070E", - "run-fast": "\U000F046E", - "rv-truck": "\U000F11D4", - "sack": "\U000F0D2E", - "sack-percent": "\U000F0D2F", - "safe": "\U000F0A6A", - "safe-square": "\U000F127C", - "safe-square-outline": "\U000F127D", - "safety-goggles": "\U000F0D30", - "sail-boat": "\U000F0EC8", - "sale": "\U000F046F", - "salesforce": "\U000F088E", - "sass": "\U000F07EC", - "satellite": "\U000F0470", - "satellite-uplink": "\U000F0909", - "satellite-variant": "\U000F0471", - "sausage": "\U000F08BA", - "saw-blade": "\U000F0E61", - "sawtooth-wave": "\U000F147A", - "saxophone": "\U000F0609", - "scale": "\U000F0472", - "scale-balance": "\U000F05D1", - "scale-bathroom": "\U000F0473", - "scale-off": "\U000F105A", - "scan-helper": "\U000F13D8", - "scanner": "\U000F06AB", - "scanner-off": "\U000F090A", - "scatter-plot": "\U000F0EC9", - "scatter-plot-outline": "\U000F0ECA", - "school": "\U000F0474", - "school-outline": "\U000F1180", - "scissors-cutting": "\U000F0A6B", - "scooter": "\U000F15BD", - "scooter-electric": "\U000F15BE", - "scoreboard": "\U000F127E", - "scoreboard-outline": "\U000F127F", - "screen-rotation": "\U000F0475", - "screen-rotation-lock": "\U000F0478", - "screw-flat-top": "\U000F0DF3", - "screw-lag": "\U000F0DF4", - "screw-machine-flat-top": "\U000F0DF5", - "screw-machine-round-top": "\U000F0DF6", - "screw-round-top": "\U000F0DF7", - "screwdriver": "\U000F0476", - "script": "\U000F0BC1", - "script-outline": "\U000F0477", - "script-text": "\U000F0BC2", - "script-text-key": "\U000F1725", - "script-text-key-outline": "\U000F1726", - "script-text-outline": "\U000F0BC3", - "script-text-play": "\U000F1727", - "script-text-play-outline": "\U000F1728", - "sd": "\U000F0479", - "seal": "\U000F047A", - "seal-variant": "\U000F0FD9", - "search-web": "\U000F070F", - "seat": "\U000F0CC3", - "seat-flat": "\U000F047B", - "seat-flat-angled": "\U000F047C", - "seat-individual-suite": "\U000F047D", - "seat-legroom-extra": "\U000F047E", - "seat-legroom-normal": "\U000F047F", - "seat-legroom-reduced": "\U000F0480", - "seat-outline": "\U000F0CC4", - "seat-passenger": "\U000F1249", - "seat-recline-extra": "\U000F0481", - "seat-recline-normal": "\U000F0482", - "seatbelt": "\U000F0CC5", - "security": "\U000F0483", - "security-network": "\U000F0484", - "seed": "\U000F0E62", - "seed-off": "\U000F13FD", - "seed-off-outline": "\U000F13FE", - "seed-outline": "\U000F0E63", - "seesaw": "\U000F15A4", - "segment": "\U000F0ECB", - "select": "\U000F0485", - "select-all": "\U000F0486", - "select-color": "\U000F0D31", - "select-compare": "\U000F0AD9", - "select-drag": "\U000F0A6C", - "select-group": "\U000F0F82", - "select-inverse": "\U000F0487", - "select-marker": "\U000F1280", - "select-multiple": "\U000F1281", - "select-multiple-marker": "\U000F1282", - "select-off": "\U000F0488", - "select-place": "\U000F0FDA", - "select-search": "\U000F1204", - "selection": "\U000F0489", - "selection-drag": "\U000F0A6D", - "selection-ellipse": "\U000F0D32", - "selection-ellipse-arrow-inside": "\U000F0F22", - "selection-marker": "\U000F1283", - "selection-multiple": "\U000F1285", - "selection-multiple-marker": "\U000F1284", - "selection-off": "\U000F0777", - "selection-search": "\U000F1205", - "semantic-web": "\U000F1316", - "send": "\U000F048A", - "send-check": "\U000F1161", - "send-check-outline": "\U000F1162", - "send-circle": "\U000F0DF8", - "send-circle-outline": "\U000F0DF9", - "send-clock": "\U000F1163", - "send-clock-outline": "\U000F1164", - "send-lock": "\U000F07ED", - "send-lock-outline": "\U000F1166", - "send-outline": "\U000F1165", - "serial-port": "\U000F065C", - "server": "\U000F048B", - "server-minus": "\U000F048C", - "server-network": "\U000F048D", - "server-network-off": "\U000F048E", - "server-off": "\U000F048F", - "server-plus": "\U000F0490", - "server-remove": "\U000F0491", - "server-security": "\U000F0492", - "set-all": "\U000F0778", - "set-center": "\U000F0779", - "set-center-right": "\U000F077A", - "set-left": "\U000F077B", - "set-left-center": "\U000F077C", - "set-left-right": "\U000F077D", - "set-merge": "\U000F14E0", - "set-none": "\U000F077E", - "set-right": "\U000F077F", - "set-split": "\U000F14E1", - "set-square": "\U000F145D", - "set-top-box": "\U000F099F", - "settings-helper": "\U000F0A6E", - "shaker": "\U000F110E", - "shaker-outline": "\U000F110F", - "shape": "\U000F0831", - "shape-circle-plus": "\U000F065D", - "shape-outline": "\U000F0832", - "shape-oval-plus": "\U000F11FA", - "shape-plus": "\U000F0495", - "shape-polygon-plus": "\U000F065E", - "shape-rectangle-plus": "\U000F065F", - "shape-square-plus": "\U000F0660", - "shape-square-rounded-plus": "\U000F14FA", - "share": "\U000F0496", - "share-all": "\U000F11F4", - "share-all-outline": "\U000F11F5", - "share-circle": "\U000F11AD", - "share-off": "\U000F0F23", - "share-off-outline": "\U000F0F24", - "share-outline": "\U000F0932", - "share-variant": "\U000F0497", - "share-variant-outline": "\U000F1514", - "shark-fin": "\U000F1673", - "shark-fin-outline": "\U000F1674", - "sheep": "\U000F0CC6", - "shield": "\U000F0498", - "shield-account": "\U000F088F", - "shield-account-outline": "\U000F0A12", - "shield-account-variant": "\U000F15A7", - "shield-account-variant-outline": "\U000F15A8", - "shield-airplane": "\U000F06BB", - "shield-airplane-outline": "\U000F0CC7", - "shield-alert": "\U000F0ECC", - "shield-alert-outline": "\U000F0ECD", - "shield-bug": "\U000F13DA", - "shield-bug-outline": "\U000F13DB", - "shield-car": "\U000F0F83", - "shield-check": "\U000F0565", - "shield-check-outline": "\U000F0CC8", - "shield-cross": "\U000F0CC9", - "shield-cross-outline": "\U000F0CCA", - "shield-edit": "\U000F11A0", - "shield-edit-outline": "\U000F11A1", - "shield-half": "\U000F1360", - "shield-half-full": "\U000F0780", - "shield-home": "\U000F068A", - "shield-home-outline": "\U000F0CCB", - "shield-key": "\U000F0BC4", - "shield-key-outline": "\U000F0BC5", - "shield-link-variant": "\U000F0D33", - "shield-link-variant-outline": "\U000F0D34", - "shield-lock": "\U000F099D", - "shield-lock-outline": "\U000F0CCC", - "shield-off": "\U000F099E", - "shield-off-outline": "\U000F099C", - "shield-outline": "\U000F0499", - "shield-plus": "\U000F0ADA", - "shield-plus-outline": "\U000F0ADB", - "shield-refresh": "\U000F00AA", - "shield-refresh-outline": "\U000F01E0", - "shield-remove": "\U000F0ADC", - "shield-remove-outline": "\U000F0ADD", - "shield-search": "\U000F0D9A", - "shield-star": "\U000F113B", - "shield-star-outline": "\U000F113C", - "shield-sun": "\U000F105D", - "shield-sun-outline": "\U000F105E", - "shield-sync": "\U000F11A2", - "shield-sync-outline": "\U000F11A3", - "ship-wheel": "\U000F0833", - "shoe-ballet": "\U000F15CA", - "shoe-cleat": "\U000F15C7", - "shoe-formal": "\U000F0B47", - "shoe-heel": "\U000F0B48", - "shoe-print": "\U000F0DFA", - "shoe-sneaker": "\U000F15C8", - "shopping": "\U000F049A", - "shopping-music": "\U000F049B", - "shopping-outline": "\U000F11D5", - "shopping-search": "\U000F0F84", - "shore": "\U000F14F9", - "shovel": "\U000F0710", - "shovel-off": "\U000F0711", - "shower": "\U000F09A0", - "shower-head": "\U000F09A1", - "shredder": "\U000F049C", - "shuffle": "\U000F049D", - "shuffle-disabled": "\U000F049E", - "shuffle-variant": "\U000F049F", - "shuriken": "\U000F137F", - "sigma": "\U000F04A0", - "sigma-lower": "\U000F062B", - "sign-caution": "\U000F04A1", - "sign-direction": "\U000F0781", - "sign-direction-minus": "\U000F1000", - "sign-direction-plus": "\U000F0FDC", - "sign-direction-remove": "\U000F0FDD", - "sign-pole": "\U000F14F8", - "sign-real-estate": "\U000F1118", - "sign-text": "\U000F0782", - "signal": "\U000F04A2", - "signal-2g": "\U000F0712", - "signal-3g": "\U000F0713", - "signal-4g": "\U000F0714", - "signal-5g": "\U000F0A6F", - "signal-cellular-1": "\U000F08BC", - "signal-cellular-2": "\U000F08BD", - "signal-cellular-3": "\U000F08BE", - "signal-cellular-outline": "\U000F08BF", - "signal-distance-variant": "\U000F0E64", - "signal-hspa": "\U000F0715", - "signal-hspa-plus": "\U000F0716", - "signal-off": "\U000F0783", - "signal-variant": "\U000F060A", - "signature": "\U000F0DFB", - "signature-freehand": "\U000F0DFC", - "signature-image": "\U000F0DFD", - "signature-text": "\U000F0DFE", - "silo": "\U000F0B49", - "silverware": "\U000F04A3", - "silverware-clean": "\U000F0FDE", - "silverware-fork": "\U000F04A4", - "silverware-fork-knife": "\U000F0A70", - "silverware-spoon": "\U000F04A5", - "silverware-variant": "\U000F04A6", - "sim": "\U000F04A7", - "sim-alert": "\U000F04A8", - "sim-alert-outline": "\U000F15D3", - "sim-off": "\U000F04A9", - "sim-off-outline": "\U000F15D4", - "sim-outline": "\U000F15D5", - "simple-icons": "\U000F131D", - "sina-weibo": "\U000F0ADF", - "sine-wave": "\U000F095B", - "sitemap": "\U000F04AA", - "size-l": "\U000F13A6", - "size-m": "\U000F13A5", - "size-s": "\U000F13A4", - "size-xl": "\U000F13A7", - "size-xs": "\U000F13A3", - "size-xxl": "\U000F13A8", - "size-xxs": "\U000F13A2", - "size-xxxl": "\U000F13A9", - "skate": "\U000F0D35", - "skateboard": "\U000F14C2", - "skew-less": "\U000F0D36", - "skew-more": "\U000F0D37", - "ski": "\U000F1304", - "ski-cross-country": "\U000F1305", - "ski-water": "\U000F1306", - "skip-backward": "\U000F04AB", - "skip-backward-outline": "\U000F0F25", - "skip-forward": "\U000F04AC", - "skip-forward-outline": "\U000F0F26", - "skip-next": "\U000F04AD", - "skip-next-circle": "\U000F0661", - "skip-next-circle-outline": "\U000F0662", - "skip-next-outline": "\U000F0F27", - "skip-previous": "\U000F04AE", - "skip-previous-circle": "\U000F0663", - "skip-previous-circle-outline": "\U000F0664", - "skip-previous-outline": "\U000F0F28", - "skull": "\U000F068C", - "skull-crossbones": "\U000F0BC6", - "skull-crossbones-outline": "\U000F0BC7", - "skull-outline": "\U000F0BC8", - "skull-scan": "\U000F14C7", - "skull-scan-outline": "\U000F14C8", - "skype": "\U000F04AF", - "skype-business": "\U000F04B0", - "slack": "\U000F04B1", - "slash-forward": "\U000F0FDF", - "slash-forward-box": "\U000F0FE0", - "sleep": "\U000F04B2", - "sleep-off": "\U000F04B3", - "slide": "\U000F15A5", - "slope-downhill": "\U000F0DFF", - "slope-uphill": "\U000F0E00", - "slot-machine": "\U000F1114", - "slot-machine-outline": "\U000F1115", - "smart-card": "\U000F10BD", - "smart-card-outline": "\U000F10BE", - "smart-card-reader": "\U000F10BF", - "smart-card-reader-outline": "\U000F10C0", - "smog": "\U000F0A71", - "smoke-detector": "\U000F0392", - "smoking": "\U000F04B4", - "smoking-off": "\U000F04B5", - "smoking-pipe": "\U000F140D", - "smoking-pipe-off": "\U000F1428", - "snail": "\U000F1677", - "snake": "\U000F150E", - "snapchat": "\U000F04B6", - "snowboard": "\U000F1307", - "snowflake": "\U000F0717", - "snowflake-alert": "\U000F0F29", - "snowflake-melt": "\U000F12CB", - "snowflake-off": "\U000F14E3", - "snowflake-variant": "\U000F0F2A", - "snowman": "\U000F04B7", - "soccer": "\U000F04B8", - "soccer-field": "\U000F0834", - "social-distance-2-meters": "\U000F1579", - "social-distance-6-feet": "\U000F157A", - "sofa": "\U000F04B9", - "sofa-outline": "\U000F156D", - "sofa-single": "\U000F156E", - "sofa-single-outline": "\U000F156F", - "solar-panel": "\U000F0D9B", - "solar-panel-large": "\U000F0D9C", - "solar-power": "\U000F0A72", - "soldering-iron": "\U000F1092", - "solid": "\U000F068D", - "sony-playstation": "\U000F0414", - "sort": "\U000F04BA", - "sort-alphabetical-ascending": "\U000F05BD", - "sort-alphabetical-ascending-variant": "\U000F1148", - "sort-alphabetical-descending": "\U000F05BF", - "sort-alphabetical-descending-variant": "\U000F1149", - "sort-alphabetical-variant": "\U000F04BB", - "sort-ascending": "\U000F04BC", - "sort-bool-ascending": "\U000F1385", - "sort-bool-ascending-variant": "\U000F1386", - "sort-bool-descending": "\U000F1387", - "sort-bool-descending-variant": "\U000F1388", - "sort-calendar-ascending": "\U000F1547", - "sort-calendar-descending": "\U000F1548", - "sort-clock-ascending": "\U000F1549", - "sort-clock-ascending-outline": "\U000F154A", - "sort-clock-descending": "\U000F154B", - "sort-clock-descending-outline": "\U000F154C", - "sort-descending": "\U000F04BD", - "sort-numeric-ascending": "\U000F1389", - "sort-numeric-ascending-variant": "\U000F090D", - "sort-numeric-descending": "\U000F138A", - "sort-numeric-descending-variant": "\U000F0AD2", - "sort-numeric-variant": "\U000F04BE", - "sort-reverse-variant": "\U000F033C", - "sort-variant": "\U000F04BF", - "sort-variant-lock": "\U000F0CCD", - "sort-variant-lock-open": "\U000F0CCE", - "sort-variant-remove": "\U000F1147", - "soundcloud": "\U000F04C0", - "source-branch": "\U000F062C", - "source-branch-check": "\U000F14CF", - "source-branch-minus": "\U000F14CB", - "source-branch-plus": "\U000F14CA", - "source-branch-refresh": "\U000F14CD", - "source-branch-remove": "\U000F14CC", - "source-branch-sync": "\U000F14CE", - "source-commit": "\U000F0718", - "source-commit-end": "\U000F0719", - "source-commit-end-local": "\U000F071A", - "source-commit-local": "\U000F071B", - "source-commit-next-local": "\U000F071C", - "source-commit-start": "\U000F071D", - "source-commit-start-next-local": "\U000F071E", - "source-fork": "\U000F04C1", - "source-merge": "\U000F062D", - "source-pull": "\U000F04C2", - "source-repository": "\U000F0CCF", - "source-repository-multiple": "\U000F0CD0", - "soy-sauce": "\U000F07EE", - "soy-sauce-off": "\U000F13FC", - "spa": "\U000F0CD1", - "spa-outline": "\U000F0CD2", - "space-invaders": "\U000F0BC9", - "space-station": "\U000F1383", - "spade": "\U000F0E65", - "sparkles": "\U000F1545", - "speaker": "\U000F04C3", - "speaker-bluetooth": "\U000F09A2", - "speaker-multiple": "\U000F0D38", - "speaker-off": "\U000F04C4", - "speaker-wireless": "\U000F071F", - "speedometer": "\U000F04C5", - "speedometer-medium": "\U000F0F85", - "speedometer-slow": "\U000F0F86", - "spellcheck": "\U000F04C6", - "spider": "\U000F11EA", - "spider-thread": "\U000F11EB", - "spider-web": "\U000F0BCA", - "spirit-level": "\U000F14F1", - "spoon-sugar": "\U000F1429", - "spotify": "\U000F04C7", - "spotlight": "\U000F04C8", - "spotlight-beam": "\U000F04C9", - "spray": "\U000F0665", - "spray-bottle": "\U000F0AE0", - "sprinkler": "\U000F105F", - "sprinkler-variant": "\U000F1060", - "sprout": "\U000F0E66", - "sprout-outline": "\U000F0E67", - "square": "\U000F0764", - "square-circle": "\U000F1500", - "square-edit-outline": "\U000F090C", - "square-medium": "\U000F0A13", - "square-medium-outline": "\U000F0A14", - "square-off": "\U000F12EE", - "square-off-outline": "\U000F12EF", - "square-outline": "\U000F0763", - "square-root": "\U000F0784", - "square-root-box": "\U000F09A3", - "square-rounded": "\U000F14FB", - "square-rounded-outline": "\U000F14FC", - "square-small": "\U000F0A15", - "square-wave": "\U000F147B", - "squeegee": "\U000F0AE1", - "ssh": "\U000F08C0", - "stack-exchange": "\U000F060B", - "stack-overflow": "\U000F04CC", - "stackpath": "\U000F0359", - "stadium": "\U000F0FF9", - "stadium-variant": "\U000F0720", - "stairs": "\U000F04CD", - "stairs-box": "\U000F139E", - "stairs-down": "\U000F12BE", - "stairs-up": "\U000F12BD", - "stamper": "\U000F0D39", - "standard-definition": "\U000F07EF", - "star": "\U000F04CE", - "star-box": "\U000F0A73", - "star-box-multiple": "\U000F1286", - "star-box-multiple-outline": "\U000F1287", - "star-box-outline": "\U000F0A74", - "star-check": "\U000F1566", - "star-check-outline": "\U000F156A", - "star-circle": "\U000F04CF", - "star-circle-outline": "\U000F09A4", - "star-cog": "\U000F1668", - "star-cog-outline": "\U000F1669", - "star-face": "\U000F09A5", - "star-four-points": "\U000F0AE2", - "star-four-points-outline": "\U000F0AE3", - "star-half": "\U000F0246", - "star-half-full": "\U000F04D0", - "star-minus": "\U000F1564", - "star-minus-outline": "\U000F1568", - "star-off": "\U000F04D1", - "star-off-outline": "\U000F155B", - "star-outline": "\U000F04D2", - "star-plus": "\U000F1563", - "star-plus-outline": "\U000F1567", - "star-remove": "\U000F1565", - "star-remove-outline": "\U000F1569", - "star-settings": "\U000F166A", - "star-settings-outline": "\U000F166B", - "star-shooting": "\U000F1741", - "star-shooting-outline": "\U000F1742", - "star-three-points": "\U000F0AE4", - "star-three-points-outline": "\U000F0AE5", - "state-machine": "\U000F11EF", - "steam": "\U000F04D3", - "steering": "\U000F04D4", - "steering-off": "\U000F090E", - "step-backward": "\U000F04D5", - "step-backward-2": "\U000F04D6", - "step-forward": "\U000F04D7", - "step-forward-2": "\U000F04D8", - "stethoscope": "\U000F04D9", - "sticker": "\U000F1364", - "sticker-alert": "\U000F1365", - "sticker-alert-outline": "\U000F1366", - "sticker-check": "\U000F1367", - "sticker-check-outline": "\U000F1368", - "sticker-circle-outline": "\U000F05D0", - "sticker-emoji": "\U000F0785", - "sticker-minus": "\U000F1369", - "sticker-minus-outline": "\U000F136A", - "sticker-outline": "\U000F136B", - "sticker-plus": "\U000F136C", - "sticker-plus-outline": "\U000F136D", - "sticker-remove": "\U000F136E", - "sticker-remove-outline": "\U000F136F", - "stocking": "\U000F04DA", - "stomach": "\U000F1093", - "stop": "\U000F04DB", - "stop-circle": "\U000F0666", - "stop-circle-outline": "\U000F0667", - "store": "\U000F04DC", - "store-24-hour": "\U000F04DD", - "store-minus": "\U000F165E", - "store-outline": "\U000F1361", - "store-plus": "\U000F165F", - "store-remove": "\U000F1660", - "storefront": "\U000F07C7", - "storefront-outline": "\U000F10C1", - "stove": "\U000F04DE", - "strategy": "\U000F11D6", - "stretch-to-page": "\U000F0F2B", - "stretch-to-page-outline": "\U000F0F2C", - "string-lights": "\U000F12BA", - "string-lights-off": "\U000F12BB", - "subdirectory-arrow-left": "\U000F060C", - "subdirectory-arrow-right": "\U000F060D", - "submarine": "\U000F156C", - "subtitles": "\U000F0A16", - "subtitles-outline": "\U000F0A17", - "subway": "\U000F06AC", - "subway-alert-variant": "\U000F0D9D", - "subway-variant": "\U000F04DF", - "summit": "\U000F0786", - "sunglasses": "\U000F04E0", - "surround-sound": "\U000F05C5", - "surround-sound-2-0": "\U000F07F0", - "surround-sound-2-1": "\U000F1729", - "surround-sound-3-1": "\U000F07F1", - "surround-sound-5-1": "\U000F07F2", - "surround-sound-5-1-2": "\U000F172A", - "surround-sound-7-1": "\U000F07F3", - "svg": "\U000F0721", - "swap-horizontal": "\U000F04E1", - "swap-horizontal-bold": "\U000F0BCD", - "swap-horizontal-circle": "\U000F0FE1", - "swap-horizontal-circle-outline": "\U000F0FE2", - "swap-horizontal-variant": "\U000F08C1", - "swap-vertical": "\U000F04E2", - "swap-vertical-bold": "\U000F0BCE", - "swap-vertical-circle": "\U000F0FE3", - "swap-vertical-circle-outline": "\U000F0FE4", - "swap-vertical-variant": "\U000F08C2", - "swim": "\U000F04E3", - "switch": "\U000F04E4", - "sword": "\U000F04E5", - "sword-cross": "\U000F0787", - "syllabary-hangul": "\U000F1333", - "syllabary-hiragana": "\U000F1334", - "syllabary-katakana": "\U000F1335", - "syllabary-katakana-halfwidth": "\U000F1336", - "symbol": "\U000F1501", - "symfony": "\U000F0AE6", - "sync": "\U000F04E6", - "sync-alert": "\U000F04E7", - "sync-circle": "\U000F1378", - "sync-off": "\U000F04E8", - "tab": "\U000F04E9", - "tab-minus": "\U000F0B4B", - "tab-plus": "\U000F075C", - "tab-remove": "\U000F0B4C", - "tab-unselected": "\U000F04EA", - "table": "\U000F04EB", - "table-account": "\U000F13B9", - "table-alert": "\U000F13BA", - "table-arrow-down": "\U000F13BB", - "table-arrow-left": "\U000F13BC", - "table-arrow-right": "\U000F13BD", - "table-arrow-up": "\U000F13BE", - "table-border": "\U000F0A18", - "table-cancel": "\U000F13BF", - "table-chair": "\U000F1061", - "table-check": "\U000F13C0", - "table-clock": "\U000F13C1", - "table-cog": "\U000F13C2", - "table-column": "\U000F0835", - "table-column-plus-after": "\U000F04EC", - "table-column-plus-before": "\U000F04ED", - "table-column-remove": "\U000F04EE", - "table-column-width": "\U000F04EF", - "table-edit": "\U000F04F0", - "table-eye": "\U000F1094", - "table-eye-off": "\U000F13C3", - "table-furniture": "\U000F05BC", - "table-headers-eye": "\U000F121D", - "table-headers-eye-off": "\U000F121E", - "table-heart": "\U000F13C4", - "table-key": "\U000F13C5", - "table-large": "\U000F04F1", - "table-large-plus": "\U000F0F87", - "table-large-remove": "\U000F0F88", - "table-lock": "\U000F13C6", - "table-merge-cells": "\U000F09A6", - "table-minus": "\U000F13C7", - "table-multiple": "\U000F13C8", - "table-network": "\U000F13C9", - "table-of-contents": "\U000F0836", - "table-off": "\U000F13CA", - "table-picnic": "\U000F1743", - "table-plus": "\U000F0A75", - "table-refresh": "\U000F13A0", - "table-remove": "\U000F0A76", - "table-row": "\U000F0837", - "table-row-height": "\U000F04F2", - "table-row-plus-after": "\U000F04F3", - "table-row-plus-before": "\U000F04F4", - "table-row-remove": "\U000F04F5", - "table-search": "\U000F090F", - "table-settings": "\U000F0838", - "table-split-cell": "\U000F142A", - "table-star": "\U000F13CB", - "table-sync": "\U000F13A1", - "table-tennis": "\U000F0E68", - "tablet": "\U000F04F6", - "tablet-android": "\U000F04F7", - "tablet-cellphone": "\U000F09A7", - "tablet-dashboard": "\U000F0ECE", - "tablet-ipad": "\U000F04F8", - "taco": "\U000F0762", - "tag": "\U000F04F9", - "tag-arrow-down": "\U000F172B", - "tag-arrow-down-outline": "\U000F172C", - "tag-arrow-left": "\U000F172D", - "tag-arrow-left-outline": "\U000F172E", - "tag-arrow-right": "\U000F172F", - "tag-arrow-right-outline": "\U000F1730", - "tag-arrow-up": "\U000F1731", - "tag-arrow-up-outline": "\U000F1732", - "tag-faces": "\U000F04FA", - "tag-heart": "\U000F068B", - "tag-heart-outline": "\U000F0BCF", - "tag-minus": "\U000F0910", - "tag-minus-outline": "\U000F121F", - "tag-multiple": "\U000F04FB", - "tag-multiple-outline": "\U000F12F7", - "tag-off": "\U000F1220", - "tag-off-outline": "\U000F1221", - "tag-outline": "\U000F04FC", - "tag-plus": "\U000F0722", - "tag-plus-outline": "\U000F1222", - "tag-remove": "\U000F0723", - "tag-remove-outline": "\U000F1223", - "tag-text": "\U000F1224", - "tag-text-outline": "\U000F04FD", - "tailwind": "\U000F13FF", - "tank": "\U000F0D3A", - "tanker-truck": "\U000F0FE5", - "tape-drive": "\U000F16DF", - "tape-measure": "\U000F0B4D", - "target": "\U000F04FE", - "target-account": "\U000F0BD0", - "target-variant": "\U000F0A77", - "taxi": "\U000F04FF", - "tea": "\U000F0D9E", - "tea-outline": "\U000F0D9F", - "teach": "\U000F0890", - "teamviewer": "\U000F0500", - "telegram": "\U000F0501", - "telescope": "\U000F0B4E", - "television": "\U000F0502", - "television-ambient-light": "\U000F1356", - "television-box": "\U000F0839", - "television-classic": "\U000F07F4", - "television-classic-off": "\U000F083A", - "television-clean": "\U000F1110", - "television-guide": "\U000F0503", - "television-off": "\U000F083B", - "television-pause": "\U000F0F89", - "television-play": "\U000F0ECF", - "television-stop": "\U000F0F8A", - "temperature-celsius": "\U000F0504", - "temperature-fahrenheit": "\U000F0505", - "temperature-kelvin": "\U000F0506", - "tennis": "\U000F0DA0", - "tennis-ball": "\U000F0507", - "tent": "\U000F0508", - "terraform": "\U000F1062", - "terrain": "\U000F0509", - "test-tube": "\U000F0668", - "test-tube-empty": "\U000F0911", - "test-tube-off": "\U000F0912", - "text": "\U000F09A8", - "text-account": "\U000F1570", - "text-box": "\U000F021A", - "text-box-check": "\U000F0EA6", - "text-box-check-outline": "\U000F0EA7", - "text-box-minus": "\U000F0EA8", - "text-box-minus-outline": "\U000F0EA9", - "text-box-multiple": "\U000F0AB7", - "text-box-multiple-outline": "\U000F0AB8", - "text-box-outline": "\U000F09ED", - "text-box-plus": "\U000F0EAA", - "text-box-plus-outline": "\U000F0EAB", - "text-box-remove": "\U000F0EAC", - "text-box-remove-outline": "\U000F0EAD", - "text-box-search": "\U000F0EAE", - "text-box-search-outline": "\U000F0EAF", - "text-recognition": "\U000F113D", - "text-search": "\U000F13B8", - "text-shadow": "\U000F0669", - "text-short": "\U000F09A9", - "text-subject": "\U000F09AA", - "text-to-speech": "\U000F050A", - "text-to-speech-off": "\U000F050B", - "texture": "\U000F050C", - "texture-box": "\U000F0FE6", - "theater": "\U000F050D", - "theme-light-dark": "\U000F050E", - "thermometer": "\U000F050F", - "thermometer-alert": "\U000F0E01", - "thermometer-chevron-down": "\U000F0E02", - "thermometer-chevron-up": "\U000F0E03", - "thermometer-high": "\U000F10C2", - "thermometer-lines": "\U000F0510", - "thermometer-low": "\U000F10C3", - "thermometer-minus": "\U000F0E04", - "thermometer-off": "\U000F1531", - "thermometer-plus": "\U000F0E05", - "thermostat": "\U000F0393", - "thermostat-box": "\U000F0891", - "thought-bubble": "\U000F07F6", - "thought-bubble-outline": "\U000F07F7", - "thumb-down": "\U000F0511", - "thumb-down-outline": "\U000F0512", - "thumb-up": "\U000F0513", - "thumb-up-outline": "\U000F0514", - "thumbs-up-down": "\U000F0515", - "ticket": "\U000F0516", - "ticket-account": "\U000F0517", - "ticket-confirmation": "\U000F0518", - "ticket-confirmation-outline": "\U000F13AA", - "ticket-outline": "\U000F0913", - "ticket-percent": "\U000F0724", - "ticket-percent-outline": "\U000F142B", - "tie": "\U000F0519", - "tilde": "\U000F0725", - "timelapse": "\U000F051A", - "timeline": "\U000F0BD1", - "timeline-alert": "\U000F0F95", - "timeline-alert-outline": "\U000F0F98", - "timeline-check": "\U000F1532", - "timeline-check-outline": "\U000F1533", - "timeline-clock": "\U000F11FB", - "timeline-clock-outline": "\U000F11FC", - "timeline-help": "\U000F0F99", - "timeline-help-outline": "\U000F0F9A", - "timeline-minus": "\U000F1534", - "timeline-minus-outline": "\U000F1535", - "timeline-outline": "\U000F0BD2", - "timeline-plus": "\U000F0F96", - "timeline-plus-outline": "\U000F0F97", - "timeline-remove": "\U000F1536", - "timeline-remove-outline": "\U000F1537", - "timeline-text": "\U000F0BD3", - "timeline-text-outline": "\U000F0BD4", - "timer": "\U000F13AB", - "timer-10": "\U000F051C", - "timer-3": "\U000F051D", - "timer-off": "\U000F13AC", - "timer-off-outline": "\U000F051E", - "timer-outline": "\U000F051B", - "timer-sand": "\U000F051F", - "timer-sand-empty": "\U000F06AD", - "timer-sand-full": "\U000F078C", - "timetable": "\U000F0520", - "toaster": "\U000F1063", - "toaster-off": "\U000F11B7", - "toaster-oven": "\U000F0CD3", - "toggle-switch": "\U000F0521", - "toggle-switch-off": "\U000F0522", - "toggle-switch-off-outline": "\U000F0A19", - "toggle-switch-outline": "\U000F0A1A", - "toilet": "\U000F09AB", - "toolbox": "\U000F09AC", - "toolbox-outline": "\U000F09AD", - "tools": "\U000F1064", - "tooltip": "\U000F0523", - "tooltip-account": "\U000F000C", - "tooltip-check": "\U000F155C", - "tooltip-check-outline": "\U000F155D", - "tooltip-edit": "\U000F0524", - "tooltip-edit-outline": "\U000F12C5", - "tooltip-image": "\U000F0525", - "tooltip-image-outline": "\U000F0BD5", - "tooltip-minus": "\U000F155E", - "tooltip-minus-outline": "\U000F155F", - "tooltip-outline": "\U000F0526", - "tooltip-plus": "\U000F0BD6", - "tooltip-plus-outline": "\U000F0527", - "tooltip-remove": "\U000F1560", - "tooltip-remove-outline": "\U000F1561", - "tooltip-text": "\U000F0528", - "tooltip-text-outline": "\U000F0BD7", - "tooth": "\U000F08C3", - "tooth-outline": "\U000F0529", - "toothbrush": "\U000F1129", - "toothbrush-electric": "\U000F112C", - "toothbrush-paste": "\U000F112A", - "torch": "\U000F1606", - "tortoise": "\U000F0D3B", - "toslink": "\U000F12B8", - "tournament": "\U000F09AE", - "tow-truck": "\U000F083C", - "tower-beach": "\U000F0681", - "tower-fire": "\U000F0682", - "toy-brick": "\U000F1288", - "toy-brick-marker": "\U000F1289", - "toy-brick-marker-outline": "\U000F128A", - "toy-brick-minus": "\U000F128B", - "toy-brick-minus-outline": "\U000F128C", - "toy-brick-outline": "\U000F128D", - "toy-brick-plus": "\U000F128E", - "toy-brick-plus-outline": "\U000F128F", - "toy-brick-remove": "\U000F1290", - "toy-brick-remove-outline": "\U000F1291", - "toy-brick-search": "\U000F1292", - "toy-brick-search-outline": "\U000F1293", - "track-light": "\U000F0914", - "trackpad": "\U000F07F8", - "trackpad-lock": "\U000F0933", - "tractor": "\U000F0892", - "tractor-variant": "\U000F14C4", - "trademark": "\U000F0A78", - "traffic-cone": "\U000F137C", - "traffic-light": "\U000F052B", - "train": "\U000F052C", - "train-car": "\U000F0BD8", - "train-car-passenger": "\U000F1733", - "train-car-passenger-door": "\U000F1734", - "train-car-passenger-door-open": "\U000F1735", - "train-car-passenger-variant": "\U000F1736", - "train-variant": "\U000F08C4", - "tram": "\U000F052D", - "tram-side": "\U000F0FE7", - "transcribe": "\U000F052E", - "transcribe-close": "\U000F052F", - "transfer": "\U000F1065", - "transfer-down": "\U000F0DA1", - "transfer-left": "\U000F0DA2", - "transfer-right": "\U000F0530", - "transfer-up": "\U000F0DA3", - "transit-connection": "\U000F0D3C", - "transit-connection-horizontal": "\U000F1546", - "transit-connection-variant": "\U000F0D3D", - "transit-detour": "\U000F0F8B", - "transit-skip": "\U000F1515", - "transit-transfer": "\U000F06AE", - "transition": "\U000F0915", - "transition-masked": "\U000F0916", - "translate": "\U000F05CA", - "translate-off": "\U000F0E06", - "transmission-tower": "\U000F0D3E", - "trash-can": "\U000F0A79", - "trash-can-outline": "\U000F0A7A", - "tray": "\U000F1294", - "tray-alert": "\U000F1295", - "tray-full": "\U000F1296", - "tray-minus": "\U000F1297", - "tray-plus": "\U000F1298", - "tray-remove": "\U000F1299", - "treasure-chest": "\U000F0726", - "tree": "\U000F0531", - "tree-outline": "\U000F0E69", - "trello": "\U000F0532", - "trending-down": "\U000F0533", - "trending-neutral": "\U000F0534", - "trending-up": "\U000F0535", - "triangle": "\U000F0536", - "triangle-outline": "\U000F0537", - "triangle-wave": "\U000F147C", - "triforce": "\U000F0BD9", - "trophy": "\U000F0538", - "trophy-award": "\U000F0539", - "trophy-broken": "\U000F0DA4", - "trophy-outline": "\U000F053A", - "trophy-variant": "\U000F053B", - "trophy-variant-outline": "\U000F053C", - "truck": "\U000F053D", - "truck-check": "\U000F0CD4", - "truck-check-outline": "\U000F129A", - "truck-delivery": "\U000F053E", - "truck-delivery-outline": "\U000F129B", - "truck-fast": "\U000F0788", - "truck-fast-outline": "\U000F129C", - "truck-outline": "\U000F129D", - "truck-trailer": "\U000F0727", - "trumpet": "\U000F1096", - "tshirt-crew": "\U000F0A7B", - "tshirt-crew-outline": "\U000F053F", - "tshirt-v": "\U000F0A7C", - "tshirt-v-outline": "\U000F0540", - "tumble-dryer": "\U000F0917", - "tumble-dryer-alert": "\U000F11BA", - "tumble-dryer-off": "\U000F11BB", - "tune": "\U000F062E", - "tune-variant": "\U000F1542", - "tune-vertical": "\U000F066A", - "tune-vertical-variant": "\U000F1543", - "turkey": "\U000F171B", - "turnstile": "\U000F0CD5", - "turnstile-outline": "\U000F0CD6", - "turtle": "\U000F0CD7", - "twitch": "\U000F0543", - "twitter": "\U000F0544", - "twitter-retweet": "\U000F0547", - "two-factor-authentication": "\U000F09AF", - "typewriter": "\U000F0F2D", - "ubisoft": "\U000F0BDA", - "ubuntu": "\U000F0548", - "ufo": "\U000F10C4", - "ufo-outline": "\U000F10C5", - "ultra-high-definition": "\U000F07F9", - "umbraco": "\U000F0549", - "umbrella": "\U000F054A", - "umbrella-closed": "\U000F09B0", - "umbrella-closed-outline": "\U000F13E2", - "umbrella-closed-variant": "\U000F13E1", - "umbrella-outline": "\U000F054B", - "undo": "\U000F054C", - "undo-variant": "\U000F054D", - "unfold-less-horizontal": "\U000F054E", - "unfold-less-vertical": "\U000F0760", - "unfold-more-horizontal": "\U000F054F", - "unfold-more-vertical": "\U000F0761", - "ungroup": "\U000F0550", - "unicode": "\U000F0ED0", - "unicorn": "\U000F15C2", - "unicorn-variant": "\U000F15C3", - "unicycle": "\U000F15E5", - "unity": "\U000F06AF", - "unreal": "\U000F09B1", - "untappd": "\U000F0551", - "update": "\U000F06B0", - "upload": "\U000F0552", - "upload-lock": "\U000F1373", - "upload-lock-outline": "\U000F1374", - "upload-multiple": "\U000F083D", - "upload-network": "\U000F06F6", - "upload-network-outline": "\U000F0CD8", - "upload-off": "\U000F10C6", - "upload-off-outline": "\U000F10C7", - "upload-outline": "\U000F0E07", - "usb": "\U000F0553", - "usb-flash-drive": "\U000F129E", - "usb-flash-drive-outline": "\U000F129F", - "usb-port": "\U000F11F0", - "valve": "\U000F1066", - "valve-closed": "\U000F1067", - "valve-open": "\U000F1068", - "van-passenger": "\U000F07FA", - "van-utility": "\U000F07FB", - "vanish": "\U000F07FC", - "vanish-quarter": "\U000F1554", - "vanity-light": "\U000F11E1", - "variable": "\U000F0AE7", - "variable-box": "\U000F1111", - "vector-arrange-above": "\U000F0554", - "vector-arrange-below": "\U000F0555", - "vector-bezier": "\U000F0AE8", - "vector-circle": "\U000F0556", - "vector-circle-variant": "\U000F0557", - "vector-combine": "\U000F0558", - "vector-curve": "\U000F0559", - "vector-difference": "\U000F055A", - "vector-difference-ab": "\U000F055B", - "vector-difference-ba": "\U000F055C", - "vector-ellipse": "\U000F0893", - "vector-intersection": "\U000F055D", - "vector-line": "\U000F055E", - "vector-link": "\U000F0FE8", - "vector-point": "\U000F055F", - "vector-polygon": "\U000F0560", - "vector-polyline": "\U000F0561", - "vector-polyline-edit": "\U000F1225", - "vector-polyline-minus": "\U000F1226", - "vector-polyline-plus": "\U000F1227", - "vector-polyline-remove": "\U000F1228", - "vector-radius": "\U000F074A", - "vector-rectangle": "\U000F05C6", - "vector-selection": "\U000F0562", - "vector-square": "\U000F0001", - "vector-triangle": "\U000F0563", - "vector-union": "\U000F0564", - "vhs": "\U000F0A1B", - "vibrate": "\U000F0566", - "vibrate-off": "\U000F0CD9", - "video": "\U000F0567", - "video-3d": "\U000F07FD", - "video-3d-off": "\U000F13D9", - "video-3d-variant": "\U000F0ED1", - "video-4k-box": "\U000F083E", - "video-account": "\U000F0919", - "video-box": "\U000F00FD", - "video-box-off": "\U000F00FE", - "video-check": "\U000F1069", - "video-check-outline": "\U000F106A", - "video-high-definition": "\U000F152E", - "video-image": "\U000F091A", - "video-input-antenna": "\U000F083F", - "video-input-component": "\U000F0840", - "video-input-hdmi": "\U000F0841", - "video-input-scart": "\U000F0F8C", - "video-input-svideo": "\U000F0842", - "video-minus": "\U000F09B2", - "video-minus-outline": "\U000F02BA", - "video-off": "\U000F0568", - "video-off-outline": "\U000F0BDB", - "video-outline": "\U000F0BDC", - "video-plus": "\U000F09B3", - "video-plus-outline": "\U000F01D3", - "video-stabilization": "\U000F091B", - "video-switch": "\U000F0569", - "video-switch-outline": "\U000F0790", - "video-vintage": "\U000F0A1C", - "video-wireless": "\U000F0ED2", - "video-wireless-outline": "\U000F0ED3", - "view-agenda": "\U000F056A", - "view-agenda-outline": "\U000F11D8", - "view-array": "\U000F056B", - "view-array-outline": "\U000F1485", - "view-carousel": "\U000F056C", - "view-carousel-outline": "\U000F1486", - "view-column": "\U000F056D", - "view-column-outline": "\U000F1487", - "view-comfy": "\U000F0E6A", - "view-comfy-outline": "\U000F1488", - "view-compact": "\U000F0E6B", - "view-compact-outline": "\U000F0E6C", - "view-dashboard": "\U000F056E", - "view-dashboard-outline": "\U000F0A1D", - "view-dashboard-variant": "\U000F0843", - "view-dashboard-variant-outline": "\U000F1489", - "view-day": "\U000F056F", - "view-day-outline": "\U000F148A", - "view-grid": "\U000F0570", - "view-grid-outline": "\U000F11D9", - "view-grid-plus": "\U000F0F8D", - "view-grid-plus-outline": "\U000F11DA", - "view-headline": "\U000F0571", - "view-list": "\U000F0572", - "view-list-outline": "\U000F148B", - "view-module": "\U000F0573", - "view-module-outline": "\U000F148C", - "view-parallel": "\U000F0728", - "view-parallel-outline": "\U000F148D", - "view-quilt": "\U000F0574", - "view-quilt-outline": "\U000F148E", - "view-sequential": "\U000F0729", - "view-sequential-outline": "\U000F148F", - "view-split-horizontal": "\U000F0BCB", - "view-split-vertical": "\U000F0BCC", - "view-stream": "\U000F0575", - "view-stream-outline": "\U000F1490", - "view-week": "\U000F0576", - "view-week-outline": "\U000F1491", - "vimeo": "\U000F0577", - "violin": "\U000F060F", - "virtual-reality": "\U000F0894", - "virus": "\U000F13B6", - "virus-outline": "\U000F13B7", - "vk": "\U000F0579", - "vlc": "\U000F057C", - "voice-off": "\U000F0ED4", - "voicemail": "\U000F057D", - "volleyball": "\U000F09B4", - "volume-high": "\U000F057E", - "volume-low": "\U000F057F", - "volume-medium": "\U000F0580", - "volume-minus": "\U000F075E", - "volume-mute": "\U000F075F", - "volume-off": "\U000F0581", - "volume-plus": "\U000F075D", - "volume-source": "\U000F1120", - "volume-variant-off": "\U000F0E08", - "volume-vibrate": "\U000F1121", - "vote": "\U000F0A1F", - "vote-outline": "\U000F0A20", - "vpn": "\U000F0582", - "vuejs": "\U000F0844", - "vuetify": "\U000F0E6D", - "walk": "\U000F0583", - "wall": "\U000F07FE", - "wall-sconce": "\U000F091C", - "wall-sconce-flat": "\U000F091D", - "wall-sconce-flat-variant": "\U000F041C", - "wall-sconce-round": "\U000F0748", - "wall-sconce-round-variant": "\U000F091E", - "wallet": "\U000F0584", - "wallet-giftcard": "\U000F0585", - "wallet-membership": "\U000F0586", - "wallet-outline": "\U000F0BDD", - "wallet-plus": "\U000F0F8E", - "wallet-plus-outline": "\U000F0F8F", - "wallet-travel": "\U000F0587", - "wallpaper": "\U000F0E09", - "wan": "\U000F0588", - "wardrobe": "\U000F0F90", - "wardrobe-outline": "\U000F0F91", - "warehouse": "\U000F0F81", - "washing-machine": "\U000F072A", - "washing-machine-alert": "\U000F11BC", - "washing-machine-off": "\U000F11BD", - "watch": "\U000F0589", - "watch-export": "\U000F058A", - "watch-export-variant": "\U000F0895", - "watch-import": "\U000F058B", - "watch-import-variant": "\U000F0896", - "watch-variant": "\U000F0897", - "watch-vibrate": "\U000F06B1", - "watch-vibrate-off": "\U000F0CDA", - "water": "\U000F058C", - "water-alert": "\U000F1502", - "water-alert-outline": "\U000F1503", - "water-boiler": "\U000F0F92", - "water-boiler-alert": "\U000F11B3", - "water-boiler-off": "\U000F11B4", - "water-check": "\U000F1504", - "water-check-outline": "\U000F1505", - "water-minus": "\U000F1506", - "water-minus-outline": "\U000F1507", - "water-off": "\U000F058D", - "water-off-outline": "\U000F1508", - "water-outline": "\U000F0E0A", - "water-percent": "\U000F058E", - "water-percent-alert": "\U000F1509", - "water-plus": "\U000F150A", - "water-plus-outline": "\U000F150B", - "water-polo": "\U000F12A0", - "water-pump": "\U000F058F", - "water-pump-off": "\U000F0F93", - "water-remove": "\U000F150C", - "water-remove-outline": "\U000F150D", - "water-well": "\U000F106B", - "water-well-outline": "\U000F106C", - "watering-can": "\U000F1481", - "watering-can-outline": "\U000F1482", - "watermark": "\U000F0612", - "wave": "\U000F0F2E", - "waveform": "\U000F147D", - "waves": "\U000F078D", - "waze": "\U000F0BDE", - "weather-cloudy": "\U000F0590", - "weather-cloudy-alert": "\U000F0F2F", - "weather-cloudy-arrow-right": "\U000F0E6E", - "weather-fog": "\U000F0591", - "weather-hail": "\U000F0592", - "weather-hazy": "\U000F0F30", - "weather-hurricane": "\U000F0898", - "weather-lightning": "\U000F0593", - "weather-lightning-rainy": "\U000F067E", - "weather-night": "\U000F0594", - "weather-night-partly-cloudy": "\U000F0F31", - "weather-partly-cloudy": "\U000F0595", - "weather-partly-lightning": "\U000F0F32", - "weather-partly-rainy": "\U000F0F33", - "weather-partly-snowy": "\U000F0F34", - "weather-partly-snowy-rainy": "\U000F0F35", - "weather-pouring": "\U000F0596", - "weather-rainy": "\U000F0597", - "weather-snowy": "\U000F0598", - "weather-snowy-heavy": "\U000F0F36", - "weather-snowy-rainy": "\U000F067F", - "weather-sunny": "\U000F0599", - "weather-sunny-alert": "\U000F0F37", - "weather-sunny-off": "\U000F14E4", - "weather-sunset": "\U000F059A", - "weather-sunset-down": "\U000F059B", - "weather-sunset-up": "\U000F059C", - "weather-tornado": "\U000F0F38", - "weather-windy": "\U000F059D", - "weather-windy-variant": "\U000F059E", - "web": "\U000F059F", - "web-box": "\U000F0F94", - "web-clock": "\U000F124A", - "webcam": "\U000F05A0", - "webcam-off": "\U000F1737", - "webhook": "\U000F062F", - "webpack": "\U000F072B", - "webrtc": "\U000F1248", - "wechat": "\U000F0611", - "weight": "\U000F05A1", - "weight-gram": "\U000F0D3F", - "weight-kilogram": "\U000F05A2", - "weight-lifter": "\U000F115D", - "weight-pound": "\U000F09B5", - "whatsapp": "\U000F05A3", - "wheel-barrow": "\U000F14F2", - "wheelchair-accessibility": "\U000F05A4", - "whistle": "\U000F09B6", - "whistle-outline": "\U000F12BC", - "white-balance-auto": "\U000F05A5", - "white-balance-incandescent": "\U000F05A6", - "white-balance-iridescent": "\U000F05A7", - "white-balance-sunny": "\U000F05A8", - "widgets": "\U000F072C", - "widgets-outline": "\U000F1355", - "wifi": "\U000F05A9", - "wifi-alert": "\U000F16B5", - "wifi-arrow-down": "\U000F16B6", - "wifi-arrow-left": "\U000F16B7", - "wifi-arrow-left-right": "\U000F16B8", - "wifi-arrow-right": "\U000F16B9", - "wifi-arrow-up": "\U000F16BA", - "wifi-arrow-up-down": "\U000F16BB", - "wifi-cancel": "\U000F16BC", - "wifi-check": "\U000F16BD", - "wifi-cog": "\U000F16BE", - "wifi-lock": "\U000F16BF", - "wifi-lock-open": "\U000F16C0", - "wifi-marker": "\U000F16C1", - "wifi-minus": "\U000F16C2", - "wifi-off": "\U000F05AA", - "wifi-plus": "\U000F16C3", - "wifi-refresh": "\U000F16C4", - "wifi-remove": "\U000F16C5", - "wifi-settings": "\U000F16C6", - "wifi-star": "\U000F0E0B", - "wifi-strength-1": "\U000F091F", - "wifi-strength-1-alert": "\U000F0920", - "wifi-strength-1-lock": "\U000F0921", - "wifi-strength-1-lock-open": "\U000F16CB", - "wifi-strength-2": "\U000F0922", - "wifi-strength-2-alert": "\U000F0923", - "wifi-strength-2-lock": "\U000F0924", - "wifi-strength-2-lock-open": "\U000F16CC", - "wifi-strength-3": "\U000F0925", - "wifi-strength-3-alert": "\U000F0926", - "wifi-strength-3-lock": "\U000F0927", - "wifi-strength-3-lock-open": "\U000F16CD", - "wifi-strength-4": "\U000F0928", - "wifi-strength-4-alert": "\U000F0929", - "wifi-strength-4-lock": "\U000F092A", - "wifi-strength-4-lock-open": "\U000F16CE", - "wifi-strength-alert-outline": "\U000F092B", - "wifi-strength-lock-open-outline": "\U000F16CF", - "wifi-strength-lock-outline": "\U000F092C", - "wifi-strength-off": "\U000F092D", - "wifi-strength-off-outline": "\U000F092E", - "wifi-strength-outline": "\U000F092F", - "wifi-sync": "\U000F16C7", - "wikipedia": "\U000F05AC", - "wind-turbine": "\U000F0DA5", - "window-close": "\U000F05AD", - "window-closed": "\U000F05AE", - "window-closed-variant": "\U000F11DB", - "window-maximize": "\U000F05AF", - "window-minimize": "\U000F05B0", - "window-open": "\U000F05B1", - "window-open-variant": "\U000F11DC", - "window-restore": "\U000F05B2", - "window-shutter": "\U000F111C", - "window-shutter-alert": "\U000F111D", - "window-shutter-open": "\U000F111E", - "windsock": "\U000F15FA", - "wiper": "\U000F0AE9", - "wiper-wash": "\U000F0DA6", - "wizard-hat": "\U000F1477", - "wordpress": "\U000F05B4", - "wrap": "\U000F05B6", - "wrap-disabled": "\U000F0BDF", - "wrench": "\U000F05B7", - "wrench-outline": "\U000F0BE0", - "xamarin": "\U000F0845", - "xamarin-outline": "\U000F0846", - "xing": "\U000F05BE", - "xml": "\U000F05C0", - "xmpp": "\U000F07FF", - "y-combinator": "\U000F0624", - "yahoo": "\U000F0B4F", - "yeast": "\U000F05C1", - "yin-yang": "\U000F0680", - "yoga": "\U000F117C", - "youtube": "\U000F05C3", - "youtube-gaming": "\U000F0848", - "youtube-studio": "\U000F0847", - "youtube-subscription": "\U000F0D40", - "youtube-tv": "\U000F0448", - "yurt": "\U000F1516", - "z-wave": "\U000F0AEA", - "zend": "\U000F0AEB", - "zigbee": "\U000F0D41", - "zip-box": "\U000F05C4", - "zip-box-outline": "\U000F0FFA", - "zip-disk": "\U000F0A23", - "zodiac-aquarius": "\U000F0A7D", - "zodiac-aries": "\U000F0A7E", - "zodiac-cancer": "\U000F0A7F", - "zodiac-capricorn": "\U000F0A80", - "zodiac-gemini": "\U000F0A81", - "zodiac-leo": "\U000F0A82", - "zodiac-libra": "\U000F0A83", - "zodiac-pisces": "\U000F0A84", - "zodiac-sagittarius": "\U000F0A85", - "zodiac-scorpio": "\U000F0A86", - "zodiac-taurus": "\U000F0A87", - "zodiac-virgo": "\U000F0A88", - "blank": " ", -} diff --git a/kivymd/images/folder.png b/kivymd/images/folder.png deleted file mode 100644 index 58fed42..0000000 Binary files a/kivymd/images/folder.png and /dev/null differ diff --git a/kivymd/images/quad_shadow-0.png b/kivymd/images/quad_shadow-0.png deleted file mode 100644 index 5d64fde..0000000 Binary files a/kivymd/images/quad_shadow-0.png and /dev/null differ diff --git a/kivymd/images/quad_shadow-1.png b/kivymd/images/quad_shadow-1.png deleted file mode 100644 index c0f1e22..0000000 Binary files a/kivymd/images/quad_shadow-1.png and /dev/null differ diff --git a/kivymd/images/quad_shadow-2.png b/kivymd/images/quad_shadow-2.png deleted file mode 100644 index 44619e5..0000000 Binary files a/kivymd/images/quad_shadow-2.png and /dev/null differ diff --git a/kivymd/images/quad_shadow.atlas b/kivymd/images/quad_shadow.atlas deleted file mode 100644 index 68e0aad..0000000 --- a/kivymd/images/quad_shadow.atlas +++ /dev/null @@ -1 +0,0 @@ -{"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]}} \ No newline at end of file diff --git a/kivymd/images/rec_shadow-0.png b/kivymd/images/rec_shadow-0.png deleted file mode 100644 index f02b919..0000000 Binary files a/kivymd/images/rec_shadow-0.png and /dev/null differ diff --git a/kivymd/images/rec_shadow-1.png b/kivymd/images/rec_shadow-1.png deleted file mode 100644 index f752fd2..0000000 Binary files a/kivymd/images/rec_shadow-1.png and /dev/null differ diff --git a/kivymd/images/rec_shadow.atlas b/kivymd/images/rec_shadow.atlas deleted file mode 100644 index 71b0e9d..0000000 --- a/kivymd/images/rec_shadow.atlas +++ /dev/null @@ -1 +0,0 @@ -{"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]}} \ No newline at end of file diff --git a/kivymd/images/rec_st_shadow-0.png b/kivymd/images/rec_st_shadow-0.png deleted file mode 100644 index 887327d..0000000 Binary files a/kivymd/images/rec_st_shadow-0.png and /dev/null differ diff --git a/kivymd/images/rec_st_shadow-1.png b/kivymd/images/rec_st_shadow-1.png deleted file mode 100644 index 759ee65..0000000 Binary files a/kivymd/images/rec_st_shadow-1.png and /dev/null differ diff --git a/kivymd/images/rec_st_shadow-2.png b/kivymd/images/rec_st_shadow-2.png deleted file mode 100644 index e9fdacc..0000000 Binary files a/kivymd/images/rec_st_shadow-2.png and /dev/null differ diff --git a/kivymd/images/rec_st_shadow.atlas b/kivymd/images/rec_st_shadow.atlas deleted file mode 100644 index d4c24ab..0000000 --- a/kivymd/images/rec_st_shadow.atlas +++ /dev/null @@ -1 +0,0 @@ -{"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]}} \ No newline at end of file diff --git a/kivymd/images/round_shadow-0.png b/kivymd/images/round_shadow-0.png deleted file mode 100644 index 26d9840..0000000 Binary files a/kivymd/images/round_shadow-0.png and /dev/null differ diff --git a/kivymd/images/round_shadow-1.png b/kivymd/images/round_shadow-1.png deleted file mode 100644 index d0f4c0f..0000000 Binary files a/kivymd/images/round_shadow-1.png and /dev/null differ diff --git a/kivymd/images/round_shadow-2.png b/kivymd/images/round_shadow-2.png deleted file mode 100644 index d5feef2..0000000 Binary files a/kivymd/images/round_shadow-2.png and /dev/null differ diff --git a/kivymd/images/round_shadow.atlas b/kivymd/images/round_shadow.atlas deleted file mode 100644 index f25016d..0000000 --- a/kivymd/images/round_shadow.atlas +++ /dev/null @@ -1 +0,0 @@ -{"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]}} \ No newline at end of file diff --git a/kivymd/images/transparent.png b/kivymd/images/transparent.png deleted file mode 100644 index effe1c2..0000000 Binary files a/kivymd/images/transparent.png and /dev/null differ diff --git a/kivymd/material_resources.py b/kivymd/material_resources.py deleted file mode 100644 index 0a1b811..0000000 --- a/kivymd/material_resources.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -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) diff --git a/kivymd/stiffscroll/__init__.py b/kivymd/stiffscroll/__init__.py deleted file mode 100644 index 72a63e9..0000000 --- a/kivymd/stiffscroll/__init__.py +++ /dev/null @@ -1,215 +0,0 @@ -""" -Stiff Scroll Effect -=================== - -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) diff --git a/kivymd/stiffscroll/__pycache__/__init__.cpython-38.pyc b/kivymd/stiffscroll/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index dd79eef..0000000 Binary files a/kivymd/stiffscroll/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/theming.py b/kivymd/theming.py deleted file mode 100644 index e0c25c6..0000000 --- a/kivymd/theming.py +++ /dev/null @@ -1,1140 +0,0 @@ -""" -Themes/Theming -============== - -.. seealso:: - - `Material Design spec, Material theming `_ - -Material App ------------- - -The main class of your application, which in `Kivy` inherits from the App class, -in `KivyMD` must inherit from the `MDApp` class. The `MDApp` class has -properties that allow you to control application properties -such as :attr:`color/style/font` of interface elements and much more. - -Control material properties ---------------------------- - -The main application class inherited from the `MDApp` class has the :attr:`theme_cls` -attribute, with which you control the material properties of your application. - -Changing the theme colors -------------------------- - -The standard theme_cls is designed to provide the standard themes and colors as -defined by Material Design. - -We do not recommend that you change this. - -However, if you do need to change the standard colors, for instance to meet branding -guidelines, you can do this by overloading the `color_definitions.py` object. - -* Create a custom color defintion object. This should have the same format as the `colors `_ object in `color_definitions.py` and contain definitions for at least the primary color, the accent color and the Light and Dark backgrounds. - -.. note:: - - Your custom colors *must* use the names of the `existing colors as - defined in the palette `_ - e.g. You can have `Blue` but you cannot have `NavyBlue`. - - -* Add the custom theme to the MDApp as shown in the following snippet. - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import ObjectProperty - - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase - from kivymd.icon_definitions import md_icons - - colors = { - "Teal": { - "50": "e4f8f9", - "100": "bdedf0", - "200": "97e2e8", - "300": "79d5de", - "400": "6dcbd6", - "500": "6ac2cf", - "600": "63b2bc", - "700": "5b9ca3", - "800": "54888c", - "900": "486363", - "A100": "bdedf0", - "A200": "97e2e8", - "A400": "6dcbd6", - "A700": "5b9ca3", - }, - "Blue": { - "50": "e3f3f8", - "100": "b9e1ee", - "200": "91cee3", - "300": "72bad6", - "400": "62acce", - "500": "589fc6", - "600": "5191b8", - "700": "487fa5", - "800": "426f91", - "900": "35506d", - "A100": "b9e1ee", - "A200": "91cee3", - "A400": "62acce", - "A700": "487fa5", - }, - "Light": { - "StatusBar": "E0E0E0", - "AppBar": "F5F5F5", - "Background": "FAFAFA", - "CardsDialogs": "FFFFFF", - "FlatButtonDown": "cccccc", - }, - "Dark": { - "StatusBar": "000000", - "AppBar": "212121", - "Background": "303030", - "CardsDialogs": "424242", - "FlatButtonDown": "999999", - } - } - - - KV = ''' - BoxLayout: - orientation: "vertical" - MDToolbar: - title: "Example Tabs" - MDTabs: - id: tabs - - - - - MDIconButton: - id: icon - icon: root.icon - user_font_size: "48sp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - icon = ObjectProperty() - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.colors = colors - self.theme_cls.primary_palette = "Blue" - self.theme_cls.accent_palette = "Teal" - return Builder.load_string(KV) - - def on_start(self): - for name_tab in self.icons: - tab = Tab(text="This is " + name_tab, icon=name_tab) - self.root.ids.tabs.add_widget(tab) - - - Example().run() - -This will change the theme colors to your custom defintion. In all other respects, -the theming stays as documented. - - -""" - -from kivy.app import App -from kivy.atlas import Atlas -from kivy.clock import Clock -from kivy.core.window import Window -from kivy.event import EventDispatcher -from kivy.metrics import dp -from kivy.properties import ( - AliasProperty, - BooleanProperty, - ColorProperty, - DictProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.utils import get_color_from_hex - -from kivymd import images_path -from kivymd.color_definitions import colors, hue, palette -from kivymd.font_definitions import theme_font_styles -from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE - - -class ThemeManager(EventDispatcher): - primary_palette = OptionProperty("Blue", options=palette) - """ - The name of the color scheme that the application will use. - All major `material` components will have the color - of the specified color theme. - - Available options are: `'Red'`, `'Pink'`, `'Purple'`, `'DeepPurple'`, - `'Indigo'`, `'Blue'`, `'LightBlue'`, `'Cyan'`, `'Teal'`, `'Green'`, - `'LightGreen'`, `'Lime'`, `'Yellow'`, `'Amber'`, `'Orange'`, `'DeepOrange'`, - `'Brown'`, `'Gray'`, `'BlueGray'`. - - To change the color scheme of an application: - - .. code-block:: python - - from kivy.uix.screenmanager import Screen - - from kivymd.app import MDApp - from kivymd.uix.button import MDRectangleFlatButton - - - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Green" # "Purple", "Red" - - screen = Screen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen - - - MainApp().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png - - :attr:`primary_palette` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Blue'`. - """ - - primary_hue = OptionProperty("500", options=hue) - """ - The color hue of the application. - - Available options are: `'50'`, `'100'`, `'200'`, `'300'`, `'400'`, `'500'`, - `'600'`, `'700'`, `'800'`, `'900'`, `'A100'`, `'A200'`, `'A400'`, `'A700'`. - - To change the hue color scheme of an application: - - .. code-block:: python - - from kivy.uix.screenmanager import Screen - - from kivymd.app import MDApp - from kivymd.uix.button import MDRectangleFlatButton - - - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Green" # "Purple", "Red" - self.theme_cls.primary_hue = "200" # "500" - - screen = Screen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen - - - MainApp().run() - - With a value of ``self.theme_cls.primary_hue = "500"``: - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png - - With a value of ``self.theme_cls.primary_hue = "200"``: - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-hue.png - - :attr:`primary_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'500'`. - """ - - primary_light_hue = OptionProperty("200", options=hue) - """ - Hue value for :attr:`primary_light`. - - :attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'200'`. - """ - - primary_dark_hue = OptionProperty("700", options=hue) - """ - Hue value for :attr:`primary_dark`. - - :attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'700'`. - """ - - def _get_primary_color(self): - return get_color_from_hex( - self.colors[self.primary_palette][self.primary_hue] - ) - - primary_color = AliasProperty( - _get_primary_color, bind=("primary_palette", "primary_hue") - ) - """ - The color of the current application theme in ``rgba`` format. - - :attr:`primary_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value of the current application theme, property is readonly. - """ - - def _get_primary_light(self): - return get_color_from_hex( - self.colors[self.primary_palette][self.primary_light_hue] - ) - - primary_light = AliasProperty( - _get_primary_light, bind=("primary_palette", "primary_light_hue") - ) - """ - Colors of the current application color theme in ``rgba`` format - (in lighter color). - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - - KV = ''' - Screen: - - MDRaisedButton: - text: "primary_light" - pos_hint: {"center_x": 0.5, "center_y": 0.7} - md_bg_color: app.theme_cls.primary_light - - MDRaisedButton: - text: "primary_color" - pos_hint: {"center_x": 0.5, "center_y": 0.5} - - MDRaisedButton: - text: "primary_dark" - pos_hint: {"center_x": 0.5, "center_y": 0.3} - md_bg_color: app.theme_cls.primary_dark - ''' - - - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Green" - return Builder.load_string(KV) - - - MainApp().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-colors-light-dark.png - :align: center - - :attr:`primary_light` is an :class:`~kivy.properties.AliasProperty` that - returns the value of the current application theme (in lighter color), - property is readonly. - """ - - def _get_primary_dark(self): - return get_color_from_hex( - self.colors[self.primary_palette][self.primary_dark_hue] - ) - - primary_dark = AliasProperty( - _get_primary_dark, bind=("primary_palette", "primary_dark_hue") - ) - """ - Colors of the current application color theme - in ``rgba`` format (in darker color). - - :attr:`primary_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value of the current application theme (in darker color), - property is readonly. - """ - - accent_palette = OptionProperty("Amber", options=palette) - """ - The application color palette used for items such as the tab indicator - in the :attr:`MDTabsBar` class and so on... - - The image below shows the color schemes with the values - ``self.theme_cls.accent_palette = 'Blue'``, ``Red'`` and​​ ``Yellow'``: - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-palette.png - - :attr:`accent_palette` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Amber'`. - """ - - accent_hue = OptionProperty("500", options=hue) - """Similar to :attr:`primary_hue`, - but returns a value for :attr:`accent_palette`. - - :attr:`accent_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'500'`. - """ - - accent_light_hue = OptionProperty("200", options=hue) - """ - Hue value for :attr:`accent_light`. - - :attr:`accent_light_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'200'`. - """ - - accent_dark_hue = OptionProperty("700", options=hue) - """ - Hue value for :attr:`accent_dark`. - - :attr:`accent_dark_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'700'`. - """ - - def _get_accent_color(self): - return get_color_from_hex( - self.colors[self.accent_palette][self.accent_hue] - ) - - accent_color = AliasProperty( - _get_accent_color, bind=["accent_palette", "accent_hue"] - ) - """Similar to :attr:`primary_color`, - but returns a value for :attr:`accent_color`. - - :attr:`accent_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_color`, - property is readonly. - """ - - def _get_accent_light(self): - return get_color_from_hex( - self.colors[self.accent_palette][self.accent_light_hue] - ) - - accent_light = AliasProperty( - _get_accent_light, bind=["accent_palette", "accent_light_hue"] - ) - """Similar to :attr:`primary_light`, - but returns a value for :attr:`accent_light`. - - :attr:`accent_light` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_light`, - property is readonly. - """ - - def _get_accent_dark(self): - return get_color_from_hex( - self.colors[self.accent_palette][self.accent_dark_hue] - ) - - accent_dark = AliasProperty( - _get_accent_dark, bind=["accent_palette", "accent_dark_hue"] - ) - """Similar to :attr:`primary_dark`, - but returns a value for :attr:`accent_dark`. - - :attr:`accent_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_dark`, - property is readonly. - """ - - theme_style = OptionProperty("Light", options=["Light", "Dark"]) - """App theme style. - - .. code-block:: python - - from kivy.uix.screenmanager import Screen - - from kivymd.app import MDApp - from kivymd.uix.button import MDRectangleFlatButton - - - class MainApp(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" # "Light" - - screen = Screen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen - - - MainApp().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style.png - - :attr:`theme_style` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Light'`. - """ - - def _get_theme_style(self, opposite): - if opposite: - return "Light" if self.theme_style == "Dark" else "Dark" - else: - return self.theme_style - - def _get_bg_darkest(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - return get_color_from_hex(self.colors["Light"]["StatusBar"]) - elif theme_style == "Dark": - return get_color_from_hex(self.colors["Dark"]["StatusBar"]) - - bg_darkest = AliasProperty(_get_bg_darkest, bind=["theme_style"]) - """ - Similar to :attr:`bg_dark`, - but the color values ​​are a tone lower (darker) than :attr:`bg_dark`. - - .. code-block:: python - - KV = ''' - : - bg: 0, 0, 0, 0 - - canvas: - Color: - rgba: root.bg - Rectangle: - pos: self.pos - size: self.size - - BoxLayout: - - Box: - bg: app.theme_cls.bg_light - Box: - bg: app.theme_cls.bg_normal - Box: - bg: app.theme_cls.bg_dark - Box: - bg: app.theme_cls.bg_darkest - ''' - - from kivy.lang import Builder - - from kivymd.app import MDApp - - - class MainApp(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" # "Light" - return Builder.load_string(KV) - - - MainApp().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bg-normal-dark-darkest.png - - :attr:`bg_darkest` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`bg_darkest`, - property is readonly. - """ - - def _get_op_bg_darkest(self): - return self._get_bg_darkest(True) - - opposite_bg_darkest = AliasProperty( - _get_op_bg_darkest, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`bg_darkest`. - - :attr:`opposite_bg_darkest` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_bg_darkest`, - property is readonly. - """ - - def _get_bg_dark(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - return get_color_from_hex(self.colors["Light"]["AppBar"]) - elif theme_style == "Dark": - return get_color_from_hex(self.colors["Dark"]["AppBar"]) - - bg_dark = AliasProperty(_get_bg_dark, bind=["theme_style"]) - """ - Similar to :attr:`bg_normal`, - but the color values ​​are one tone lower (darker) than :attr:`bg_normal`. - - :attr:`bg_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`bg_dark`, - property is readonly. - """ - - def _get_op_bg_dark(self): - return self._get_bg_dark(True) - - opposite_bg_dark = AliasProperty(_get_op_bg_dark, bind=["theme_style"]) - """ - The opposite value of color in the :attr:`bg_dark`. - - :attr:`opposite_bg_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`opposite_bg_dark`, - property is readonly. - """ - - def _get_bg_normal(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - return get_color_from_hex(self.colors["Light"]["Background"]) - elif theme_style == "Dark": - return get_color_from_hex(self.colors["Dark"]["Background"]) - - bg_normal = AliasProperty(_get_bg_normal, bind=["theme_style"]) - """ - Similar to :attr:`bg_light`, - but the color values ​​are one tone lower (darker) than :attr:`bg_light`. - - :attr:`bg_normal` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`bg_normal`, - property is readonly. - """ - - def _get_op_bg_normal(self): - return self._get_bg_normal(True) - - opposite_bg_normal = AliasProperty(_get_op_bg_normal, bind=["theme_style"]) - """ - The opposite value of color in the :attr:`bg_normal`. - - :attr:`opposite_bg_normal` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_bg_normal`, - property is readonly. - """ - - def _get_bg_light(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - return get_color_from_hex(self.colors["Light"]["CardsDialogs"]) - elif theme_style == "Dark": - return get_color_from_hex(self.colors["Dark"]["CardsDialogs"]) - - bg_light = AliasProperty(_get_bg_light, bind=["theme_style"]) - """" - Depending on the style of the theme (`'Dark'` or `'Light`') - that the application uses, :attr:`bg_light` contains the color value - in ``rgba`` format for the widgets background. - - :attr:`bg_light` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`bg_light`, - property is readonly. - """ - - def _get_op_bg_light(self): - return self._get_bg_light(True) - - opposite_bg_light = AliasProperty(_get_op_bg_light, bind=["theme_style"]) - """ - The opposite value of color in the :attr:`bg_light`. - - :attr:`opposite_bg_light` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_bg_light`, - property is readonly. - """ - - def _get_divider_color(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - color[3] = 0.12 - return color - - divider_color = AliasProperty(_get_divider_color, bind=["theme_style"]) - """ - Color for dividing lines such as :class:`~kivymd.uix.card.MDSeparator`. - - :attr:`divider_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`divider_color`, - property is readonly. - """ - - def _get_op_divider_color(self): - return self._get_divider_color(True) - - opposite_divider_color = AliasProperty( - _get_op_divider_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`divider_color`. - - :attr:`opposite_divider_color` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_divider_color`, - property is readonly. - """ - - def _get_text_color(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - color[3] = 0.87 - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - return color - - text_color = AliasProperty(_get_text_color, bind=["theme_style"]) - """ - Color of the text used in the :class:`~kivymd.uix.label.MDLabel`. - - :attr:`text_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`text_color`, - property is readonly. - """ - - def _get_op_text_color(self): - return self._get_text_color(True) - - opposite_text_color = AliasProperty( - _get_op_text_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`text_color`. - - :attr:`opposite_text_color` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_text_color`, - property is readonly. - """ - - def _get_secondary_text_color(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - color[3] = 0.54 - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - color[3] = 0.70 - return color - - secondary_text_color = AliasProperty( - _get_secondary_text_color, bind=["theme_style"] - ) - """ - The color for the secondary text that is used in classes - from the module :class:`~kivymd/uix/list.TwoLineListItem`. - - :attr:`secondary_text_color` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`secondary_text_color`, - property is readonly. - """ - - def _get_op_secondary_text_color(self): - return self._get_secondary_text_color(True) - - opposite_secondary_text_color = AliasProperty( - _get_op_secondary_text_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`secondary_text_color`. - - :attr:`opposite_secondary_text_color` - is an :class:`~kivy.properties.AliasProperty` that returns the value - in ``rgba`` format for :attr:`opposite_secondary_text_color`, - property is readonly. - """ - - def _get_icon_color(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - color[3] = 0.54 - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - return color - - icon_color = AliasProperty(_get_icon_color, bind=["theme_style"]) - """ - Color of the icon used in the :class:`~kivymd.uix.button.MDIconButton`. - - :attr:`icon_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`icon_color`, - property is readonly. - """ - - def _get_op_icon_color(self): - return self._get_icon_color(True) - - opposite_icon_color = AliasProperty( - _get_op_icon_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`icon_color`. - - :attr:`opposite_icon_color` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_icon_color`, - property is readonly. - """ - - def _get_disabled_hint_text_color(self, opposite=False): - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - color[3] = 0.38 - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - color[3] = 0.50 - return color - - disabled_hint_text_color = AliasProperty( - _get_disabled_hint_text_color, bind=["theme_style"] - ) - """ - Color of the disabled text used in the :class:`~kivymd.uix.textfield.MDTextField`. - - :attr:`disabled_hint_text_color` - is an :class:`~kivy.properties.AliasProperty` that returns the value - in ``rgba`` format for :attr:`disabled_hint_text_color`, - property is readonly. - """ - - def _get_op_disabled_hint_text_color(self): - return self._get_disabled_hint_text_color(True) - - opposite_disabled_hint_text_color = AliasProperty( - _get_op_disabled_hint_text_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`disabled_hint_text_color`. - - :attr:`opposite_disabled_hint_text_color` - is an :class:`~kivy.properties.AliasProperty` that returns the value - in ``rgba`` format for :attr:`opposite_disabled_hint_text_color`, - property is readonly. - """ - - # Hardcoded because muh standard - def _get_error_color(self): - return get_color_from_hex(self.colors["Red"]["A700"]) - - error_color = AliasProperty(_get_error_color, bind=["theme_style"]) - """ - Color of the error text used - in the :class:`~kivymd.uix.textfield.MDTextField`. - - :attr:`error_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`error_color`, - property is readonly. - """ - - def _get_ripple_color(self): - return self._ripple_color - - def _set_ripple_color(self, value): - self._ripple_color = value - - _ripple_color = ColorProperty(get_color_from_hex(colors["Gray"]["400"])) - """Private value.""" - - ripple_color = AliasProperty( - _get_ripple_color, _set_ripple_color, bind=["_ripple_color"] - ) - """ - Color of ripple effects. - - :attr:`ripple_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`ripple_color`, - property is readonly. - """ - - def _determine_device_orientation(self, _, window_size): - if window_size[0] > window_size[1]: - self.device_orientation = "landscape" - elif window_size[1] >= window_size[0]: - self.device_orientation = "portrait" - - device_orientation = StringProperty("") - """ - Device orientation. - - :attr:`device_orientation` is an :class:`~kivy.properties.StringProperty`. - """ - - def _get_standard_increment(self): - if DEVICE_TYPE == "mobile": - if self.device_orientation == "landscape": - return dp(48) - else: - return dp(56) - else: - return dp(64) - - standard_increment = AliasProperty( - _get_standard_increment, bind=["device_orientation"] - ) - """ - Value of standard increment. - - :attr:`standard_increment` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`standard_increment`, - property is readonly. - """ - - def _get_horizontal_margins(self): - if DEVICE_TYPE == "mobile": - return dp(16) - else: - return dp(24) - - horizontal_margins = AliasProperty(_get_horizontal_margins) - """ - Value of horizontal margins. - - :attr:`horizontal_margins` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`horizontal_margins`, - property is readonly. - """ - - def on_theme_style(self, instance, value): - if ( - hasattr(App.get_running_app(), "theme_cls") - and App.get_running_app().theme_cls == self - ): - self.set_clearcolor_by_theme_style(value) - - set_clearcolor = BooleanProperty(True) - - def set_clearcolor_by_theme_style(self, theme_style): - if not self.set_clearcolor: - return - Window.clearcolor = get_color_from_hex( - self.colors[theme_style]["Background"] - ) - - # font name, size (sp), always caps, letter spacing (sp) - font_styles = DictProperty( - { - "H1": ["RobotoLight", 96, False, -1.5], - "H2": ["RobotoLight", 60, False, -0.5], - "H3": ["Roboto", 48, False, 0], - "H4": ["Roboto", 34, False, 0.25], - "H5": ["Roboto", 24, False, 0], - "H6": ["RobotoMedium", 20, False, 0.15], - "Subtitle1": ["Roboto", 16, False, 0.15], - "Subtitle2": ["RobotoMedium", 14, False, 0.1], - "Body1": ["Roboto", 16, False, 0.5], - "Body2": ["Roboto", 14, False, 0.25], - "Button": ["RobotoMedium", 14, True, 1.25], - "Caption": ["Roboto", 12, False, 0.4], - "Overline": ["Roboto", 10, True, 1.5], - "Icon": ["Icons", 24, False, 0], - } - ) - """ - Data of default font styles. - - Add custom font: - - .. code-block:: python - - KV = ''' - Screen: - - MDLabel: - text: "JetBrainsMono" - halign: "center" - font_style: "JetBrainsMono" - ''' - - from kivy.core.text import LabelBase - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.font_definitions import theme_font_styles - - - class MainApp(MDApp): - def build(self): - LabelBase.register( - name="JetBrainsMono", - fn_regular="JetBrainsMono-Regular.ttf") - - theme_font_styles.append('JetBrainsMono') - self.theme_cls.font_styles["JetBrainsMono"] = [ - "JetBrainsMono", - 16, - False, - 0.15, - ] - return Builder.load_string(KV) - - - MainApp().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles.png - - :attr:`font_styles` is an :class:`~kivy.properties.DictProperty`. - """ - - def set_colors( - self, - primary_palette, - primary_hue, - primary_light_hue, - primary_dark_hue, - accent_palette, - accent_hue, - accent_light_hue, - accent_dark_hue, - ): - """ - Courtesy method to allow all of the theme color attributes to be set in one call. - - :attr:`set_colors` allows all of the following to be set in one method call: - - * primary palette color, - * primary hue, - * primary light hue, - * primary dark hue, - * accent palette color, - * accent hue, - * accent ligth hue, and - * accent dark hue. - - Note that all values *must* be provided. If you only want to set one or two values - use the appropriate method call for that. - - .. code-block:: python - - from kivy.uix.screenmanager import Screen - - from kivymd.app import MDApp - from kivymd.uix.button import MDRectangleFlatButton - - - class MainApp(MDApp): - def build(self): - self.theme_cls.set_colors( - "Blue", "600", "50", "800", "Teal", "600", "100", "800" - ) - - screen = Screen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen - - - MainApp().run() - - """ - self.primary_palette = primary_palette - self.primary_hue = primary_hue - self.primary_light_hue = primary_light_hue - self.primary_dark_hue = primary_dark_hue - self.accent_palette = accent_palette - self.accent_hue = accent_hue - self.accent_light_hue = accent_light_hue - self.accent_dark_hue = accent_dark_hue - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.rec_shadow = Atlas(f"{images_path}rec_shadow.atlas") - self.rec_st_shadow = Atlas(f"{images_path}rec_st_shadow.atlas") - self.quad_shadow = Atlas(f"{images_path}quad_shadow.atlas") - self.round_shadow = Atlas(f"{images_path}round_shadow.atlas") - Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style)) - self._determine_device_orientation(None, Window.size) - Window.bind(size=self._determine_device_orientation) - self.bind(font_styles=self.sync_theme_styles) - self.colors = colors - Clock.schedule_once(self.sync_theme_styles) - - def sync_theme_styles(self, *args): - # Syncs the values from self.font_styles to theme_font_styles - # this will ensure continuity when someone registers a new font_style. - for num, style in enumerate(theme_font_styles): - if style not in self.font_styles: - theme_font_styles.pop(num) - for style in self.font_styles.keys(): - theme_font_styles.append(style) - - -class ThemableBehavior(EventDispatcher): - theme_cls = ObjectProperty() - """ - Instance of :class:`~ThemeManager` class. - - :attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`. - """ - - device_ios = BooleanProperty(DEVICE_IOS) - """ - ``True`` if device is ``iOS``. - - :attr:`device_ios` is an :class:`~kivy.properties.BooleanProperty`. - """ - - widget_style = OptionProperty( - "android", options=["android", "ios", "desktop"] - ) - """ - Allows to set one of the three style properties for the widget: - `'android'`, `'ios'`, `'desktop'`. - - For example, for the class :class:`~kivymd.uix.selectioncontrol.MDSwitch` - has two styles - `'android'` and `'ios'`: - - .. code-block:: kv - - MDSwitch: - widget_style: "ios" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-switch-ios.gif - :align: center - - .. code-block:: kv - - MDSwitch: - widget_style: "android" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-switch-android.gif - :align: center - - :attr:`widget_style` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'android'`. - """ - - opposite_colors = BooleanProperty(False) - - def __init__(self, **kwargs): - if self.theme_cls is not None: - pass - else: - try: - if not isinstance( - App.get_running_app().property("theme_cls", True), - ObjectProperty, - ): - raise ValueError( - "KivyMD: App object must be inherited from " - "`kivymd.app.MDApp`" - ) - except AttributeError: - raise ValueError( - "KivyMD: App object must be initialized before loading " - "root widget. See " - "https://github.com/kivymd/KivyMD/wiki/Modules-Material-App#exceptions" - ) - self.theme_cls = App.get_running_app().theme_cls - super().__init__(**kwargs) diff --git a/kivymd/theming_dynamic_text.py b/kivymd/theming_dynamic_text.py deleted file mode 100644 index 64f9921..0000000 --- a/kivymd/theming_dynamic_text.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -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" - ) diff --git a/kivymd/toast/__init__.py b/kivymd/toast/__init__.py deleted file mode 100644 index 2b823a6..0000000 --- a/kivymd/toast/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -__all__ = ("toast",) - -from kivy.utils import platform - -if platform == "android": - try: - from .androidtoast import toast - except BaseException: - from .kivytoast import toast -else: - from .kivytoast import toast diff --git a/kivymd/toast/__pycache__/__init__.cpython-38.pyc b/kivymd/toast/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index ee95c36..0000000 Binary files a/kivymd/toast/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/toast/androidtoast/__init__.py b/kivymd/toast/androidtoast/__init__.py deleted file mode 100644 index 300dd2d..0000000 --- a/kivymd/toast/androidtoast/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -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 diff --git a/kivymd/toast/androidtoast/__pycache__/__init__.cpython-38.pyc b/kivymd/toast/androidtoast/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 7a5e0f3..0000000 Binary files a/kivymd/toast/androidtoast/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/toast/androidtoast/__pycache__/androidtoast.cpython-38.pyc b/kivymd/toast/androidtoast/__pycache__/androidtoast.cpython-38.pyc deleted file mode 100644 index 79b3a58..0000000 Binary files a/kivymd/toast/androidtoast/__pycache__/androidtoast.cpython-38.pyc and /dev/null differ diff --git a/kivymd/toast/androidtoast/androidtoast.py b/kivymd/toast/androidtoast/androidtoast.py deleted file mode 100644 index 0686aa5..0000000 --- a/kivymd/toast/androidtoast/androidtoast.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -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 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 80the 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() diff --git a/kivymd/toast/kivytoast/__init__.py b/kivymd/toast/kivytoast/__init__.py deleted file mode 100644 index 0d9c204..0000000 --- a/kivymd/toast/kivytoast/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -__all__ = ("toast",) - -from .kivytoast import toast diff --git a/kivymd/toast/kivytoast/__pycache__/__init__.cpython-38.pyc b/kivymd/toast/kivytoast/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index ab40f59..0000000 Binary files a/kivymd/toast/kivytoast/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/toast/kivytoast/__pycache__/kivytoast.cpython-38.pyc b/kivymd/toast/kivytoast/__pycache__/kivytoast.cpython-38.pyc deleted file mode 100644 index b11a7f4..0000000 Binary files a/kivymd/toast/kivytoast/__pycache__/kivytoast.cpython-38.pyc and /dev/null differ diff --git a/kivymd/toast/kivytoast/kivytoast.py b/kivymd/toast/kivytoast/kivytoast.py deleted file mode 100644 index 911c17c..0000000 --- a/kivymd/toast/kivytoast/kivytoast.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -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: - - MDToolbar: - 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 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( - """ -: - 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, texture_size): - texture_width, texture_height = texture_size - if texture_width > Window.width: - instance.text_size = (Window.width - dp(10), None) - instance.texture_update() - texture_width, texture_height = instance.texture_size - self.size = (texture_width + 25, texture_height + 25) - - def toast(self, text_toast): - self.label_toast.text = text_toast - self.open() - - def on_open(self): - self.fade_in() - Clock.schedule_once(self.fade_out, self.duration) - - def fade_in(self): - anim = Animation(opacity=1, duration=0.4) - anim.start(self.label_toast) - anim.start(self) - - def fade_out(self, *args): - 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="", background=[0.2, 0.2, 0.2, 1], duration=2.5): - """Displays a toast. - - :attr duration: the amount of time (in seconds) that the toast is visible on the screen - :type duration: float - - :attr background: color ``rgba`` in Kivy format - :type background: list - """ - - Toast(duration=duration, _md_bg_color=background).toast(text) diff --git a/kivymd/tools/__init__.py b/kivymd/tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kivymd/tools/__pycache__/__init__.cpython-38.pyc b/kivymd/tools/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 84e747f..0000000 Binary files a/kivymd/tools/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/tools/packaging/__init__.py b/kivymd/tools/packaging/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kivymd/tools/packaging/__pycache__/__init__.cpython-38.pyc b/kivymd/tools/packaging/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index ae3d81a..0000000 Binary files a/kivymd/tools/packaging/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/tools/packaging/pyinstaller/__init__.py b/kivymd/tools/packaging/pyinstaller/__init__.py deleted file mode 100644 index 47db41b..0000000 --- a/kivymd/tools/packaging/pyinstaller/__init__.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -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()) diff --git a/kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-38.pyc b/kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index eaa527d..0000000 Binary files a/kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-38.pyc b/kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-38.pyc deleted file mode 100644 index 38d5da7..0000000 Binary files a/kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-38.pyc and /dev/null differ diff --git a/kivymd/tools/packaging/pyinstaller/hook-kivymd.py b/kivymd/tools/packaging/pyinstaller/hook-kivymd.py deleted file mode 100644 index b2ca8f5..0000000 --- a/kivymd/tools/packaging/pyinstaller/hook-kivymd.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -PyInstaller hook for KivyMD -=========================== - -Adds fonts and images to package. - -All modules from uix directory are added by Kivy hook. -""" - -from pathlib import Path - -import kivymd - -datas = [ - ( - kivymd.fonts_path, - str(Path("kivymd").joinpath(Path(kivymd.fonts_path).name)), - ), - ( - kivymd.images_path, - str(Path("kivymd").joinpath(Path(kivymd.images_path).name)), - ), -] diff --git a/kivymd/uix/__init__.py b/kivymd/uix/__init__.py deleted file mode 100644 index e995df5..0000000 --- a/kivymd/uix/__init__.py +++ /dev/null @@ -1,84 +0,0 @@ -from kivy.properties import BooleanProperty -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.label import Label -from kivy.uix.screenmanager import Screen - -from kivymd.uix.behaviors import SpecificBackgroundColorBehavior - - -class MDAdaptiveWidget(SpecificBackgroundColorBehavior): - adaptive_height = BooleanProperty(False) - """ - If `True`, the following properties will be applied to the widget: - - .. code-block:: kv - - size_hint_y: None - height: self.minimum_height - - :attr:`adaptive_height` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - adaptive_width = BooleanProperty(False) - """ - If `True`, the following properties will be applied to the widget: - - .. code-block:: kv - - size_hint_x: None - width: self.minimum_width - - :attr:`adaptive_width` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - adaptive_size = BooleanProperty(False) - """ - If `True`, the following properties will be applied to the widget: - - .. code-block:: kv - - size_hint: None, None - size: self.minimum_size - - :attr:`adaptive_size` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - def on_adaptive_height(self, instance, value): - self.size_hint_y = None - if issubclass(self.__class__, Label): - self.bind( - texture_size=lambda *x: self.setter("height")( - self, self.texture_size[1] - ) - ) - else: - if not isinstance(self, (FloatLayout, Screen)): - self.bind(minimum_height=self.setter("height")) - - def on_adaptive_width(self, instance, value): - self.size_hint_x = None - if issubclass(self.__class__, Label): - self.bind( - texture_size=lambda *x: self.setter("width")( - self, self.texture_size[0] - ) - ) - else: - if not isinstance(self, (FloatLayout, Screen)): - self.bind(minimum_width=self.setter("width")) - - def on_adaptive_size(self, instance, value): - self.size_hint = (None, None) - if issubclass(self.__class__, Label): - self.text_size = (None, None) - self.bind( - texture_size=lambda *x: self.setter("size")( - self, self.texture_size - ) - ) - else: - if not isinstance(self, (FloatLayout, Screen)): - self.bind(minimum_size=self.setter("size")) diff --git a/kivymd/uix/__pycache__/__init__.cpython-38.pyc b/kivymd/uix/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 54dbffc..0000000 Binary files a/kivymd/uix/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/backdrop.cpython-38.pyc b/kivymd/uix/__pycache__/backdrop.cpython-38.pyc deleted file mode 100644 index 4c9ceaf..0000000 Binary files a/kivymd/uix/__pycache__/backdrop.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/banner.cpython-38.pyc b/kivymd/uix/__pycache__/banner.cpython-38.pyc deleted file mode 100644 index 1fc17d0..0000000 Binary files a/kivymd/uix/__pycache__/banner.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/bottomnavigation.cpython-38.pyc b/kivymd/uix/__pycache__/bottomnavigation.cpython-38.pyc deleted file mode 100644 index fb60521..0000000 Binary files a/kivymd/uix/__pycache__/bottomnavigation.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/bottomsheet.cpython-38.pyc b/kivymd/uix/__pycache__/bottomsheet.cpython-38.pyc deleted file mode 100644 index 815ca9a..0000000 Binary files a/kivymd/uix/__pycache__/bottomsheet.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/boxlayout.cpython-38.pyc b/kivymd/uix/__pycache__/boxlayout.cpython-38.pyc deleted file mode 100644 index 9a3cd52..0000000 Binary files a/kivymd/uix/__pycache__/boxlayout.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/button.cpython-38.pyc b/kivymd/uix/__pycache__/button.cpython-38.pyc deleted file mode 100644 index 009ab7d..0000000 Binary files a/kivymd/uix/__pycache__/button.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/card.cpython-38.pyc b/kivymd/uix/__pycache__/card.cpython-38.pyc deleted file mode 100644 index 6ee2874..0000000 Binary files a/kivymd/uix/__pycache__/card.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/carousel.cpython-38.pyc b/kivymd/uix/__pycache__/carousel.cpython-38.pyc deleted file mode 100644 index 796a078..0000000 Binary files a/kivymd/uix/__pycache__/carousel.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/chip.cpython-38.pyc b/kivymd/uix/__pycache__/chip.cpython-38.pyc deleted file mode 100644 index b56e96c..0000000 Binary files a/kivymd/uix/__pycache__/chip.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/circularlayout.cpython-38.pyc b/kivymd/uix/__pycache__/circularlayout.cpython-38.pyc deleted file mode 100644 index 3d13ff8..0000000 Binary files a/kivymd/uix/__pycache__/circularlayout.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/datatables.cpython-38.pyc b/kivymd/uix/__pycache__/datatables.cpython-38.pyc deleted file mode 100644 index 1c9b733..0000000 Binary files a/kivymd/uix/__pycache__/datatables.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/dialog.cpython-38.pyc b/kivymd/uix/__pycache__/dialog.cpython-38.pyc deleted file mode 100644 index 0abf527..0000000 Binary files a/kivymd/uix/__pycache__/dialog.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/dropdownitem.cpython-38.pyc b/kivymd/uix/__pycache__/dropdownitem.cpython-38.pyc deleted file mode 100644 index 3d9aa2f..0000000 Binary files a/kivymd/uix/__pycache__/dropdownitem.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/expansionpanel.cpython-38.pyc b/kivymd/uix/__pycache__/expansionpanel.cpython-38.pyc deleted file mode 100644 index ccdf07d..0000000 Binary files a/kivymd/uix/__pycache__/expansionpanel.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/filemanager.cpython-38.pyc b/kivymd/uix/__pycache__/filemanager.cpython-38.pyc deleted file mode 100644 index 1da65f5..0000000 Binary files a/kivymd/uix/__pycache__/filemanager.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/floatlayout.cpython-38.pyc b/kivymd/uix/__pycache__/floatlayout.cpython-38.pyc deleted file mode 100644 index cc06efd..0000000 Binary files a/kivymd/uix/__pycache__/floatlayout.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/gridlayout.cpython-38.pyc b/kivymd/uix/__pycache__/gridlayout.cpython-38.pyc deleted file mode 100644 index c7c69d5..0000000 Binary files a/kivymd/uix/__pycache__/gridlayout.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/imagelist.cpython-38.pyc b/kivymd/uix/__pycache__/imagelist.cpython-38.pyc deleted file mode 100644 index 145c3c1..0000000 Binary files a/kivymd/uix/__pycache__/imagelist.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/label.cpython-38.pyc b/kivymd/uix/__pycache__/label.cpython-38.pyc deleted file mode 100644 index be07243..0000000 Binary files a/kivymd/uix/__pycache__/label.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/list.cpython-38.pyc b/kivymd/uix/__pycache__/list.cpython-38.pyc deleted file mode 100644 index 9e56aa9..0000000 Binary files a/kivymd/uix/__pycache__/list.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/menu.cpython-38.pyc b/kivymd/uix/__pycache__/menu.cpython-38.pyc deleted file mode 100644 index e4cd8a1..0000000 Binary files a/kivymd/uix/__pycache__/menu.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/navigationdrawer.cpython-38.pyc b/kivymd/uix/__pycache__/navigationdrawer.cpython-38.pyc deleted file mode 100644 index d7234c0..0000000 Binary files a/kivymd/uix/__pycache__/navigationdrawer.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/navigationrail.cpython-38.pyc b/kivymd/uix/__pycache__/navigationrail.cpython-38.pyc deleted file mode 100644 index 0032eda..0000000 Binary files a/kivymd/uix/__pycache__/navigationrail.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/picker.cpython-38.pyc b/kivymd/uix/__pycache__/picker.cpython-38.pyc deleted file mode 100644 index 0c871c2..0000000 Binary files a/kivymd/uix/__pycache__/picker.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/progressbar.cpython-38.pyc b/kivymd/uix/__pycache__/progressbar.cpython-38.pyc deleted file mode 100644 index fab69be..0000000 Binary files a/kivymd/uix/__pycache__/progressbar.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/refreshlayout.cpython-38.pyc b/kivymd/uix/__pycache__/refreshlayout.cpython-38.pyc deleted file mode 100644 index 135ac8f..0000000 Binary files a/kivymd/uix/__pycache__/refreshlayout.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/relativelayout.cpython-38.pyc b/kivymd/uix/__pycache__/relativelayout.cpython-38.pyc deleted file mode 100644 index 1d45217..0000000 Binary files a/kivymd/uix/__pycache__/relativelayout.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/screen.cpython-38.pyc b/kivymd/uix/__pycache__/screen.cpython-38.pyc deleted file mode 100644 index d766c65..0000000 Binary files a/kivymd/uix/__pycache__/screen.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/selection.cpython-38.pyc b/kivymd/uix/__pycache__/selection.cpython-38.pyc deleted file mode 100644 index 0e95043..0000000 Binary files a/kivymd/uix/__pycache__/selection.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/selectioncontrol.cpython-38.pyc b/kivymd/uix/__pycache__/selectioncontrol.cpython-38.pyc deleted file mode 100644 index 9a6cd2b..0000000 Binary files a/kivymd/uix/__pycache__/selectioncontrol.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/slider.cpython-38.pyc b/kivymd/uix/__pycache__/slider.cpython-38.pyc deleted file mode 100644 index 5bf467b..0000000 Binary files a/kivymd/uix/__pycache__/slider.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/snackbar.cpython-38.pyc b/kivymd/uix/__pycache__/snackbar.cpython-38.pyc deleted file mode 100644 index 3ffa7fb..0000000 Binary files a/kivymd/uix/__pycache__/snackbar.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/spinner.cpython-38.pyc b/kivymd/uix/__pycache__/spinner.cpython-38.pyc deleted file mode 100644 index 260aee9..0000000 Binary files a/kivymd/uix/__pycache__/spinner.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/stacklayout.cpython-38.pyc b/kivymd/uix/__pycache__/stacklayout.cpython-38.pyc deleted file mode 100644 index c83e8df..0000000 Binary files a/kivymd/uix/__pycache__/stacklayout.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/swiper.cpython-38.pyc b/kivymd/uix/__pycache__/swiper.cpython-38.pyc deleted file mode 100644 index 3d7b4e5..0000000 Binary files a/kivymd/uix/__pycache__/swiper.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/tab.cpython-38.pyc b/kivymd/uix/__pycache__/tab.cpython-38.pyc deleted file mode 100644 index 4c4f6ae..0000000 Binary files a/kivymd/uix/__pycache__/tab.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/taptargetview.cpython-38.pyc b/kivymd/uix/__pycache__/taptargetview.cpython-38.pyc deleted file mode 100644 index 4e7388c..0000000 Binary files a/kivymd/uix/__pycache__/taptargetview.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/textfield.cpython-38.pyc b/kivymd/uix/__pycache__/textfield.cpython-38.pyc deleted file mode 100644 index f9eb776..0000000 Binary files a/kivymd/uix/__pycache__/textfield.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/toolbar.cpython-38.pyc b/kivymd/uix/__pycache__/toolbar.cpython-38.pyc deleted file mode 100644 index 72b85d1..0000000 Binary files a/kivymd/uix/__pycache__/toolbar.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/__pycache__/tooltip.cpython-38.pyc b/kivymd/uix/__pycache__/tooltip.cpython-38.pyc deleted file mode 100644 index adcdff2..0000000 Binary files a/kivymd/uix/__pycache__/tooltip.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/backdrop.py b/kivymd/uix/backdrop.py deleted file mode 100644 index 6b27cc1..0000000 --- a/kivymd/uix/backdrop.py +++ /dev/null @@ -1,417 +0,0 @@ -""" -Components/Backdrop -=================== - -.. seealso:: - - `Material Design spec, Backdrop `_ - -.. rubric:: Skeleton layout for using :class:`~MDBackdrop`: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.png - :align: center - -Usage ------ - -.. code-block:: kv - - : - - MDBackdrop: - - MDBackdropBackLayer: - - ContentForBackdropBackLayer: - - MDBackdropFrontLayer: - - ContentForBackdropFrontLayer: - -Example -------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.screenmanager import Screen - - from kivymd.app import MDApp - - # Your layouts. - Builder.load_string( - ''' - #:import Window kivy.core.window.Window - #:import IconLeftWidget kivymd.uix.list.IconLeftWidget - - - - icon: "android" - - IconLeftWidget: - icon: root.icon - - - - backdrop: None - text: "Lower the front layer" - secondary_text: " by 50 %" - icon: "transfer-down" - on_press: root.backdrop.open(-Window.height / 2) - pos_hint: {"top": 1} - _no_ripple_effect: True - - - - size_hint: .8, .8 - source: "data/logo/kivy-icon-512.png" - pos_hint: {"center_x": .5, "center_y": .6} - ''' - ) - - # Usage example of MDBackdrop. - Builder.load_string( - ''' - - - MDBackdrop: - id: backdrop - left_action_items: [['menu', lambda x: self.open()]] - title: "Example Backdrop" - radius_left: "25dp" - radius_right: "0dp" - header_text: "Menu:" - - MDBackdropBackLayer: - MyBackdropBackLayer: - id: backlayer - - MDBackdropFrontLayer: - MyBackdropFrontLayer: - backdrop: backdrop - ''' - ) - - - class ExampleBackdrop(Screen): - pass - - - class TestBackdrop(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def build(self): - return ExampleBackdrop() - - - TestBackdrop().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.gif - :width: 280 px - :align: center - -.. Note:: `See full example `_ -""" - -__all__ = ( - "MDBackdropToolbar", - "MDBackdropFrontLayer", - "MDBackdropBackLayer", - "MDBackdrop", -) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - StringProperty, -) -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.floatlayout import FloatLayout - -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import FakeRectangularElevationBehavior -from kivymd.uix.card import MDCard -from kivymd.uix.toolbar import MDToolbar - -Builder.load_string( - """ - - - canvas: - Color: - rgba: - root.theme_cls.primary_color if not root.back_layer_color \ - else root.back_layer_color - Rectangle: - pos: self.pos - size: self.size - - MDBackdropToolbar: - id: toolbar - title: root.title - elevation: 0 - md_bg_color: - root.theme_cls.primary_color if not root.back_layer_color \ - else root.back_layer_color - left_action_items: root.left_action_items - right_action_items: root.right_action_items - pos_hint: {'top': 1} - - _BackLayer: - id: back_layer - y: -toolbar.height - padding: 0, 0, 0, toolbar.height + dp(10) - - _FrontLayer: - id: _front_layer - md_bg_color: 0, 0, 0, 0 - orientation: "vertical" - size_hint_y: None - height: root.height - toolbar.height - padding: root.padding - - canvas: - Color: - rgba: - root.theme_cls.bg_normal if not root.front_layer_color \ - else root.front_layer_color - RoundedRectangle: - pos: self.pos - size: self.size - radius: - [ - (root.radius_left, root.radius_left), - (root.radius_right, root.radius_right), - (0, 0), - (0, 0) - ] - - OneLineListItem: - id: header_button - text: root.header_text - divider: None - _no_ripple_effect: True - on_press: root.open() - - BoxLayout: - id: front_layer - padding: 0, 0, 0, "10dp" -""" -) - - -class MDBackdrop(ThemableBehavior, FloatLayout): - """ - :Events: - :attr:`on_open` - When the front layer drops. - :attr:`on_close` - When the front layer rises. - """ - - padding = ListProperty([0, 0, 0, 0]) - """ - Padding for contents of the front layer. - - :attr:`padding` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - left_action_items = ListProperty() - """ - The icons and methods left of the :class:`kivymd.uix.toolbar.MDToolbar` - in back layer. For more information, see the :class:`kivymd.uix.toolbar.MDToolbar` module - and :attr:`left_action_items` parameter. - - :attr:`left_action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - right_action_items = ListProperty() - """ - Works the same way as :attr:`left_action_items`. - - :attr:`right_action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - title = StringProperty() - """ - See the :class:`kivymd.uix.toolbar.MDToolbar.title` parameter. - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - back_layer_color = ColorProperty(None) - """ - Background color of back layer. - - :attr:`back_layer_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - front_layer_color = ColorProperty(None) - """ - Background color of front layer. - - :attr:`front_layer_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - radius_left = NumericProperty("16dp") - """ - The value of the rounding radius of the upper left corner - of the front layer. - - :attr:`radius_left` is an :class:`~kivy.properties.NumericProperty` - and defaults to `16dp`. - """ - - radius_right = NumericProperty("16dp") - """ - The value of the rounding radius of the upper right corner - of the front layer. - - :attr:`radius_right` is an :class:`~kivy.properties.NumericProperty` - and defaults to `16dp`. - """ - - header = BooleanProperty(True) - """ - Whether to use a header above the contents of the front layer. - - :attr:`header` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - header_text = StringProperty("Header") - """ - Text of header. - - :attr:`header_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Header'`. - """ - - close_icon = StringProperty("close") - """ - The name of the icon that will be installed on the toolbar - on the left when opening the front layer. - - :attr:`close_icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'close'`. - """ - - _open_icon = "" - _front_layer_open = False - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_open") - self.register_event_type("on_close") - Clock.schedule_once( - lambda x: self.on_left_action_items(self, self.left_action_items) - ) - - def on_open(self): - """When the front layer drops.""" - - def on_close(self): - """When the front layer rises.""" - - def on_left_action_items(self, instance, value): - if value: - self.left_action_items = [value[0]] - else: - self.left_action_items = [["menu", lambda x: self.open()]] - self._open_icon = self.left_action_items[0][0] - - def on_header(self, instance, value): - if not value: - self.ids._front_layer.remove_widget(self.ids.header_button) - - def open(self, open_up_to=0): - """ - Opens the front layer. - - :open_up_to: - the height to which the front screen will be lowered; - if equal to zero - falls to the bottom of the screen; - """ - - self.animtion_icon_menu() - if self._front_layer_open: - self.close() - return - - if open_up_to: - if open_up_to < ( - self.ids.header_button.height - self.ids._front_layer.height - ): - y = self.ids.header_button.height - self.ids._front_layer.height - elif open_up_to > 0: - y = 0 - else: - y = open_up_to - else: - y = self.ids.header_button.height - self.ids._front_layer.height - - Animation(y=y, d=0.2, t="out_quad").start(self.ids._front_layer) - self._front_layer_open = True - self.dispatch("on_open") - - def close(self): - """Opens the front layer.""" - - Animation(y=0, d=0.2, t="out_quad").start(self.ids._front_layer) - self._front_layer_open = False - self.dispatch("on_close") - - def animtion_icon_menu(self): - icon_menu = self.ids.toolbar.ids.left_actions.children[0] - anim = Animation(opacity=0, d=0.2, t="out_quad") - anim.bind(on_complete=self.animtion_icon_close) - anim.start(icon_menu) - - def animtion_icon_close(self, instance_animation, instance_icon_menu): - instance_icon_menu.icon = ( - self.close_icon - if instance_icon_menu.icon == self._open_icon - else self._open_icon - ) - Animation(opacity=1, d=0.2).start(instance_icon_menu) - - def add_widget(self, widget, index=0, canvas=None): - if widget.__class__ in (MDBackdropToolbar, _BackLayer, _FrontLayer): - return super().add_widget(widget) - else: - if widget.__class__ is MDBackdropBackLayer: - self.ids.back_layer.add_widget(widget) - elif widget.__class__ is MDBackdropFrontLayer: - self.ids.front_layer.add_widget(widget) - - -class MDBackdropToolbar(MDToolbar): - pass - - -class MDBackdropFrontLayer(BoxLayout): - pass - - -class MDBackdropBackLayer(BoxLayout): - pass - - -class _BackLayer(BoxLayout): - pass - - -class _FrontLayer(MDCard, FakeRectangularElevationBehavior): - pass diff --git a/kivymd/uix/banner.py b/kivymd/uix/banner.py deleted file mode 100644 index 0e9df94..0000000 --- a/kivymd/uix/banner.py +++ /dev/null @@ -1,455 +0,0 @@ -""" -Components/Banner -================= - -.. seealso:: - - `Material Design spec, Banner `_ - -.. rubric:: A banner displays a prominent message and related optional actions. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner.png - :align: center - -Usage -===== - -.. code-block:: python - - from kivy.lang import Builder - from kivy.factory import Factory - - from kivymd.app import MDApp - - Builder.load_string(''' - - - MDBanner: - id: banner - text: ["One line string text example without actions."] - # The widget that is under the banner. - # It will be shifted down to the height of the banner. - over_widget: screen - vertical_pad: toolbar.height - - MDToolbar: - id: toolbar - title: "Example Banners" - elevation: 10 - pos_hint: {'top': 1} - - BoxLayout: - id: screen - orientation: "vertical" - size_hint_y: None - height: Window.height - toolbar.height - - OneLineListItem: - text: "Banner without actions" - on_release: banner.show() - - Widget: - ''') - - - class Test(MDApp): - def build(self): - return Factory.ExampleBanner() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-example-1.gif - :align: center - -.. rubric:: Banner type. - -By default, the banner is of the type ``'one-line'``: - -.. code-block:: kv - - MDBanner: - text: ["One line string text example without actions."] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-one-line.png - :align: center - -To use a two-line banner, specify the ``'two-line'`` :attr:`MDBanner.type` for the banner -and pass the list of two lines to the :attr:`MDBanner.text` parameter: - -.. code-block:: kv - - MDBanner: - type: "two-line" - text: - ["One line string text example without actions.", "This is the second line of the banner message."] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-two-line.png - :align: center - -Similarly, create a three-line banner: - -.. code-block:: kv - - MDBanner: - type: "three-line" - text: - ["One line string text example without actions.", "This is the second line of the banner message.", "and this is the third line of the banner message."] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-three-line.png - :align: center - -To add buttons to any type of banner, -use the :attr:`MDBanner.left_action` and :attr:`MDBanner.right_action` parameters, -which should take a list ['Button name', function]: - -.. code-block:: kv - - MDBanner: - text: ["One line string text example without actions."] - left_action: ["CANCEL", lambda x: None] - -Or two buttons: - -.. code-block:: kv - - MDBanner: - text: ["One line string text example without actions."] - left_action: ["CANCEL", lambda x: None] - right_action: ["CLOSE", lambda x: None] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-actions.png - :align: center - -If you want to use the icon on the left in the banner, -add the prefix `'-icon'` to the banner type: - -.. code-block:: kv - - MDBanner: - type: "one-line-icon" - icon: f"{images_path}/kivymd.png" - text: ["One line string text example without actions."] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-icon.png - :align: center - -.. Note:: `See full example `_ -""" - -__all__ = ("MDBanner",) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.widget import Widget - -from kivymd.uix.button import MDFlatButton -from kivymd.uix.card import MDCard -from kivymd.uix.list import ( - OneLineAvatarListItem, - OneLineListItem, - ThreeLineAvatarListItem, - ThreeLineListItem, - TwoLineAvatarListItem, - TwoLineListItem, -) - -Builder.load_string( - """ -#:import Window kivy.core.window.Window -#:import Clock kivy.clock.Clock - - - - text: root.text_message[0] - secondary_text: root.text_message[1] - tertiary_text: root.text_message[2] - divider: None - _no_ripple_effect: True - - ImageLeftWidget: - source: root.icon - - - - text: root.text_message[0] - secondary_text: root.text_message[1] - divider: None - _no_ripple_effect: True - - ImageLeftWidget: - source: root.icon - - - - text: root.text_message[0] - divider: None - _no_ripple_effect: True - - ImageLeftWidget: - source: root.icon - - - - text: root.text_message[0] - secondary_text: root.text_message[1] - tertiary_text: root.text_message[2] - divider: None - _no_ripple_effect: True - - - - text: root.text_message[0] - secondary_text: root.text_message[1] - divider: None - _no_ripple_effect: True - - - - text: root.text_message[0] - divider: None - _no_ripple_effect: True - - - - size_hint_y: None - height: self.minimum_height - banner_y: 0 - orientation: "vertical" - y: Window.height - self.banner_y - - canvas: - Color: - rgba: 0, 0, 0, 0 - Rectangle: - pos: self.pos - size: self.size - - BoxLayout: - id: container_message - size_hint_y: None - height: self.minimum_height - - BoxLayout: - size_hint: None, None - size: self.minimum_size - pos_hint: {"right": 1} - padding: 0, 0, "8dp", "8dp" - spacing: "8dp" - - BoxLayout: - id: left_action_box - size_hint: None, None - size: self.minimum_size - - BoxLayout: - id: right_action_box - size_hint: None, None - size: self.minimum_size -""" -) - - -class MDBanner(MDCard): - vertical_pad = NumericProperty(dp(68)) - """ - Indent the banner at the top of the screen. - - :attr:`vertical_pad` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(68)`. - """ - - opening_transition = StringProperty("in_quad") - """ - The name of the animation transition. - - :attr:`opening_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `'in_quad'`. - """ - - icon = StringProperty("data/logo/kivy-icon-128.png") - """Icon banner. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'data/logo/kivy-icon-128.png'`. - """ - - over_widget = ObjectProperty() - """ - The widget that is under the banner. - It will be shifted down to the height of the banner. - - :attr:`over_widget` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - text = ListProperty() - """List of lines for banner text. - Must contain no more than three lines for a - `'one-line'`, `'two-line'` and `'three-line'` banner, respectively. - - :attr:`text` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - left_action = ListProperty() - """The action of banner. - - To add one action, make a list [`'name_action'`, callback] - where `'name_action'` is a string that corresponds to an action name and - ``callback`` is the function called on a touch release event. - - :attr:`left_action` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - right_action = ListProperty() - """Works the same way as :attr:`left_action`. - - :attr:`right_action` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - type = OptionProperty( - "one-line", - options=[ - "one-line", - "two-line", - "three-line", - "one-line-icon", - "two-line-icon", - "three-line-icon", - ], - allownone=True, - ) - """Banner type. . Available options are: (`"one-line"`, `"two-line"`, - `"three-line"`, `"one-line-icon"`, `"two-line-icon"`, `"three-line-icon"`). - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'one-line'`. - """ - - _type_message = None - _progress = False - - def add_actions_buttons(self, box, data): - if data: - name_action_button, function_action_button = data - action_button = MDFlatButton( - text=f"[b]{name_action_button}[/b]", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - on_release=function_action_button, - ) - action_button.markup = True - box.add_widget(action_button) - - def set_left_action(self): - self.add_actions_buttons(self.ids.left_action_box, self.left_action) - - def set_right_action(self): - self.add_actions_buttons(self.ids.right_action_box, self.right_action) - - def set_type_banner(self): - self._type_message = { - "three-line-icon": ThreeLineIconBanner, - "two-line-icon": TwoLineIconBanner, - "one-line-icon": OneLineIconBanner, - "three-line": ThreeLineBanner, - "two-line": TwoLineBanner, - "one-line": OneLineBanner, - }[self.type] - - def add_banner_to_container(self): - self.ids.container_message.add_widget( - self._type_message(text_message=self.text, icon=self.icon) - ) - - def show(self): - def show(interval): - self.set_type_banner() - self.set_left_action() - self.set_right_action() - self.add_banner_to_container() - Clock.schedule_once(self.animation_display_banner, 0.1) - - if self._progress: - return - self._progress = True - if self.ids.container_message.children: - self.hide() - Clock.schedule_once(show, 0.7) - - def animation_display_banner(self, i): - Animation( - banner_y=self.height + self.vertical_pad, - d=0.15, - t=self.opening_transition, - ).start(self) - anim = Animation( - y=self.over_widget.y - self.height, - d=0.15, - t=self.opening_transition, - ) - anim.bind(on_complete=self._reset_progress) - anim.start(self.over_widget) - - def hide(self): - def hide(interval): - anim = Animation(banner_y=0, d=0.15) - anim.bind(on_complete=self._remove_banner) - anim.start(self) - Animation(y=self.over_widget.y + self.height, d=0.15).start( - self.over_widget - ) - - Clock.schedule_once(hide, 0.5) - - def _remove_banner(self, *args): - self.ids.container_message.clear_widgets() - self.ids.left_action_box.clear_widgets() - self.ids.right_action_box.clear_widgets() - - def _reset_progress(self, *args): - self._progress = False - - -class BaseBanner(Widget): - text_message = ListProperty(["", "", ""]) - icon = StringProperty() - - def on_touch_down(self, touch): - self.parent.parent.hide() - - -class ThreeLineIconBanner(ThreeLineAvatarListItem, BaseBanner): - pass - - -class TwoLineIconBanner(TwoLineAvatarListItem, BaseBanner): - pass - - -class OneLineIconBanner(OneLineAvatarListItem, BaseBanner): - pass - - -class ThreeLineBanner(ThreeLineListItem, BaseBanner): - pass - - -class TwoLineBanner(TwoLineListItem, BaseBanner): - pass - - -class OneLineBanner(OneLineListItem, BaseBanner): - pass diff --git a/kivymd/uix/behaviors/__init__.py b/kivymd/uix/behaviors/__init__.py deleted file mode 100644 index 639acff..0000000 --- a/kivymd/uix/behaviors/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Behaviors -========= - -Modules and classes implementing various behaviors for buttons etc. -""" - -# flake8: NOQA -from .hover_behavior import HoverBehavior # isort:skip -from .backgroundcolor_behavior import ( - BackgroundColorBehavior, - SpecificBackgroundColorBehavior, -) -from .elevation import ( - CircularElevationBehavior, - CommonElevationBehavior, - FakeCircularElevationBehavior, - FakeRectangularElevationBehavior, - ObservableShadow, - RectangularElevationBehavior, - RoundedRectangularElevationBehavior, -) -from .focus_behavior import FocusBehavior -from .magic_behavior import MagicBehavior -from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior -from .touch_behavior import TouchBehavior diff --git a/kivymd/uix/behaviors/__pycache__/__init__.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index e72c45e..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-38.pyc deleted file mode 100644 index 719af71..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/__pycache__/elevation.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/elevation.cpython-38.pyc deleted file mode 100644 index e74e371..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/elevation.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-38.pyc deleted file mode 100644 index f71c4e5..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-38.pyc deleted file mode 100644 index 69ce6b6..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-38.pyc deleted file mode 100644 index f78f90b..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-38.pyc deleted file mode 100644 index 3920cfd..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-38.pyc deleted file mode 100644 index 329f629..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-38.pyc b/kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-38.pyc deleted file mode 100644 index 7dd4b09..0000000 Binary files a/kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-38.pyc and /dev/null differ diff --git a/kivymd/uix/behaviors/backgroundcolor_behavior.py b/kivymd/uix/behaviors/backgroundcolor_behavior.py deleted file mode 100644 index 41e9751..0000000 --- a/kivymd/uix/behaviors/backgroundcolor_behavior.py +++ /dev/null @@ -1,219 +0,0 @@ -""" -Behaviors/Background Color -========================== - -.. note:: The following classes are intended for in-house use of the library. -""" - -__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior") - -from kivy.lang import Builder -from kivy.properties import ( - BoundedNumericProperty, - ColorProperty, - ListProperty, - NumericProperty, - OptionProperty, - ReferenceListProperty, - StringProperty, - VariableListProperty, -) -from kivy.utils import get_color_from_hex - -from kivymd.color_definitions import hue, palette, text_colors - -from .elevation import CommonElevationBehavior - -Builder.load_string( - """ -#:import RelativeLayout kivy.uix.relativelayout.RelativeLayout - - - - canvas.before: - PushMatrix - Rotate: - angle: self.angle - origin: self._background_origin - Color: - rgba: self.md_bg_color - RoundedRectangle: - group: "Background_instruction" - size: self.size - pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0) - radius: root.radius - source: root.background - PopMatrix -""", - filename="BackgroundColorBehavior.kv", -) - - -class BackgroundColorBehavior(CommonElevationBehavior): - background = StringProperty() - """ - Background image path. - - :attr:`background` is a :class:`~kivy.properties.StringProperty` - and defaults to `None`. - """ - - r = BoundedNumericProperty(1.0, min=0.0, max=1.0) - """ - The value of ``red`` in the ``rgba`` palette. - - :attr:`r` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `1.0`. - """ - - g = BoundedNumericProperty(1.0, min=0.0, max=1.0) - """ - The value of ``green`` in the ``rgba`` palette. - - :attr:`g` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `1.0`. - """ - - b = BoundedNumericProperty(1.0, min=0.0, max=1.0) - """ - The value of ``blue`` in the ``rgba`` palette. - - :attr:`b` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `1.0`. - """ - - a = BoundedNumericProperty(0.0, min=0.0, max=1.0) - """ - The value of ``alpha channel`` in the ``rgba`` palette. - - :attr:`a` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `0.0`. - """ - - radius = VariableListProperty([0], length=4) - """ - Canvas radius. - - .. code-block:: python - - # Top left corner slice. - MDBoxLayout: - md_bg_color: app.theme_cls.primary_color - radius: [25, 0, 0, 0] - - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - md_bg_color = ReferenceListProperty(r, g, b, a) - """ - The background color of the widget (:class:`~kivy.uix.widget.Widget`) - that will be inherited from the :attr:`BackgroundColorBehavior` class. - - For example: - - .. code-block:: kv - - Widget: - canvas: - Color: - rgba: 0, 1, 1, 1 - Rectangle: - size: self.size - pos: self.pos - - similar to code: - - .. code-block:: kv - - - md_bg_color: 0, 1, 1, 1 - - :attr:`md_bg_color` is an :class:`~kivy.properties.ReferenceListProperty` - and defaults to :attr:`r`, :attr:`g`, :attr:`b`, :attr:`a`. - """ - - angle = NumericProperty(0) - background_origin = ListProperty(None) - - _background_x = NumericProperty(0) - _background_y = NumericProperty(0) - _background_origin = ReferenceListProperty( - _background_x, - _background_y, - ) - - def __init__(self, **kwarg): - super().__init__(**kwarg) - self.bind(pos=self.update_background_origin) - - def update_background_origin(self, *args): - if self.background_origin: - self._background_origin = self.background_origin - else: - self._background_origin = self.center - - -class SpecificBackgroundColorBehavior(BackgroundColorBehavior): - background_palette = OptionProperty( - "Primary", options=["Primary", "Accent", *palette] - ) - """ - See :attr:`kivymd.color_definitions.palette`. - - :attr:`background_palette` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Primary'`. - """ - - background_hue = OptionProperty("500", options=hue) - """ - See :attr:`kivymd.color_definitions.hue`. - - :attr:`background_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'500'`. - """ - - specific_text_color = ColorProperty([0, 0, 0, 0.87]) - """ - :attr:`specific_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.87]`. - """ - - specific_secondary_text_color = ColorProperty([0, 0, 0, 0.87]) - """ - :attr:`specific_secondary_text_color`is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.87]`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - if hasattr(self, "theme_cls"): - self.theme_cls.bind( - primary_palette=self._update_specific_text_color - ) - self.theme_cls.bind(accent_palette=self._update_specific_text_color) - self.theme_cls.bind(theme_style=self._update_specific_text_color) - self.bind(background_hue=self._update_specific_text_color) - self.bind(background_palette=self._update_specific_text_color) - self._update_specific_text_color(None, None) - - def _update_specific_text_color(self, instance, value): - if hasattr(self, "theme_cls"): - palette = { - "Primary": self.theme_cls.primary_palette, - "Accent": self.theme_cls.accent_palette, - }.get(self.background_palette, self.background_palette) - else: - palette = {"Primary": "Blue", "Accent": "Amber"}.get( - self.background_palette, self.background_palette - ) - color = get_color_from_hex(text_colors[palette][self.background_hue]) - secondary_color = color[:] - # Check for black text (need to adjust opacity). - if (color[0] + color[1] + color[2]) == 0: - color[3] = 0.87 - secondary_color[3] = 0.54 - else: - secondary_color[3] = 0.7 - self.specific_text_color = color - self.specific_secondary_text_color = secondary_color diff --git a/kivymd/uix/behaviors/elevation.py b/kivymd/uix/behaviors/elevation.py deleted file mode 100644 index 7b09781..0000000 --- a/kivymd/uix/behaviors/elevation.py +++ /dev/null @@ -1,1489 +0,0 @@ -""" -Behaviors/Elevation -=================== - -.. seealso:: - - `Material Design spec, Elevation `_ - -.. rubric:: Elevation is the relative distance between two surfaces along the z-axis. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-previous.png - :align: center - -There are 5 classes in KivyMD that can simulate shadow: - - #. :class:`~FakeRectangularElevationBehavior` - #. :class:`~FakeCircularElevationBehavior` - - #. :class:`~RectangularElevationBehavior` - #. :class:`~CircularElevationBehavior` - #. :class:`~RoundedRectangularElevationBehavior` - -By default, KivyMD widgets use the elevation behavior implemented in classes -:class:`~FakeRectangularElevationBehavior` and :class:`~FakeCircularElevationBehavior` -for cast shadows. These classes use the old method of rendering shadows and it -doesn't look very aesthetically pleasing. Shadows are harsh, no softness: - -The :class:`~RectangularElevationBehavior`, :class:`~CircularElevationBehavior`, -:class:`~RoundedRectangularElevationBehavior` classes use the new shadow -rendering algorithm, based on textures creation using the `Pillow` library. -It looks very aesthetically pleasing and beautiful. - -.. warning:: Remember that :class:`~RectangularElevationBehavior`, - :class:`~CircularElevationBehavior`, :class:`~RoundedRectangularElevationBehavior` - classes require a lot of resources from the device on which your application will run, - so you should not use these classes on mobile devices. - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.widget import Widget - - from kivymd.app import MDApp - from kivymd.uix.card import MDCard - from kivymd.uix.behaviors import RectangularElevationBehavior - from kivymd.uix.boxlayout import MDBoxLayout - - KV = ''' - - adaptive_size: True - orientation: "vertical" - spacing: "36dp" - - - - size_hint: None, None - size: 100, 100 - md_bg_color: 0, 0, 1, 1 - elevation: 36 - pos_hint: {'center_x': .5} - - - MDFloatLayout: - - MDBoxLayout: - adaptive_size: True - pos_hint: {'center_x': .5, 'center_y': .5} - spacing: "56dp" - - Box: - - MDLabel: - text: "Deprecated shadow rendering" - adaptive_size: True - - DeprecatedShadowWidget: - - MDLabel: - text: "Doesn't require a lot of resources" - adaptive_size: True - - Box: - - MDLabel: - text: "New shadow rendering" - adaptive_size: True - - NewShadowWidget: - - MDLabel: - text: "It takes a lot of resources" - adaptive_size: True - ''' - - - class BaseShadowWidget(Widget): - pass - - - class DeprecatedShadowWidget(MDCard, BaseShadowWidget): - '''Deprecated shadow rendering. Doesn't require a lot of resources.''' - - - class NewShadowWidget(RectangularElevationBehavior, BaseShadowWidget, MDBoxLayout): - '''New shadow rendering. It takes a lot of resources.''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-differential.png - :align: center - - -For example, let's create an button with a rectangular elevation effect: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.behaviors import ButtonBehavior - - from kivymd.app import MDApp - from kivymd.uix.behaviors import ( - RectangularRippleBehavior, - BackgroundColorBehavior, - FakeRectangularElevationBehavior, - ) - - KV = ''' - : - size_hint: None, None - size: "250dp", "50dp" - - - MDScreen: - - # With elevation effect - RectangularElevationButton: - pos_hint: {"center_x": .5, "center_y": .6} - elevation: 18 - - # Without elevation effect - RectangularElevationButton: - pos_hint: {"center_x": .5, "center_y": .4} - ''' - - - class RectangularElevationButton( - RectangularRippleBehavior, - FakeRectangularElevationBehavior, - ButtonBehavior, - BackgroundColorBehavior, - ): - md_bg_color = [0, 0, 1, 1] - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.gif - :align: center - -Similarly, create a circular button: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.behaviors import ButtonBehavior - - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.app import MDApp - from kivymd.uix.behaviors import ( - CircularRippleBehavior, - FakeCircularElevationBehavior, - ) - - KV = ''' - : - size_hint: None, None - size: "100dp", "100dp" - radius: self.size[0] / 2 - md_bg_color: 0, 0, 1, 1 - - MDIcon: - icon: "hand-heart" - halign: "center" - valign: "center" - size: root.size - pos: root.pos - font_size: root.size[0] * .6 - theme_text_color: "Custom" - text_color: [1] * 4 - - - MDScreen: - - CircularElevationButton: - pos_hint: {"center_x": .5, "center_y": .6} - elevation: 24 - ''' - - - class CircularElevationButton( - FakeCircularElevationBehavior, - CircularRippleBehavior, - ButtonBehavior, - MDBoxLayout, - ): - pass - - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-fake-elevation.png - :align: center - -Animating the elevation ------------------------ - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - from kivy.properties import ObjectProperty - from kivy.uix.behaviors import ButtonBehavior - - from kivymd.app import MDApp - from kivymd.theming import ThemableBehavior - from kivymd.uix.behaviors import FakeRectangularElevationBehavior, RectangularRippleBehavior - from kivymd.uix.boxlayout import MDBoxLayout - - KV = ''' - MDFloatLayout: - - ElevatedWidget: - pos_hint: {'center_x': .5, 'center_y': .5} - size_hint: None, None - size: 100, 100 - md_bg_color: 0, 0, 1, 1 - ''' - - - class ElevatedWidget( - ThemableBehavior, - FakeRectangularElevationBehavior, - RectangularRippleBehavior, - ButtonBehavior, - MDBoxLayout, - ): - shadow_animation = ObjectProperty() - - def on_press(self, *args): - if self.shadow_animation: - Animation.cancel_all(self, "_elevation") - self.shadow_animation = Animation(_elevation=self.elevation + 10, d=0.4) - self.shadow_animation.start(self) - - def on_release(self, *args): - if self.shadow_animation: - Animation.cancel_all(self, "_elevation") - self.shadow_animation = Animation(_elevation=self.elevation, d=0.1) - self.shadow_animation.start(self) - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-animation-effect.gif - :align: center - -Lighting position ------------------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.card import MDCard - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.behaviors import RectangularElevationBehavior - - KV = ''' - MDScreen: - - ShadowCard: - pos_hint: {'center_x': .5, 'center_y': .5} - size_hint: None, None - size: 100, 100 - shadow_pos: -10 + slider.value, -10 + slider.value - elevation: 24 - md_bg_color: 1, 1, 1, 1 - - MDSlider: - id: slider - max: 20 - size_hint_x: .6 - pos_hint: {'center_x': .5, 'center_y': .3} - ''' - - - class ShadowCard(RectangularElevationBehavior, MDBoxLayout): - pass - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-pos.gif - :align: center -""" - -__all__ = ( - "CommonElevationBehavior", - "RectangularElevationBehavior", - "CircularElevationBehavior", - "RoundedRectangularElevationBehavior", - "ObservableShadow", - "FakeRectangularElevationBehavior", - "FakeCircularElevationBehavior", -) - -from io import BytesIO -from weakref import WeakMethod, ref - -from kivy.clock import Clock -from kivy.core.image import Image as CoreImage -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - AliasProperty, - BooleanProperty, - BoundedNumericProperty, - ListProperty, - NumericProperty, - ObjectProperty, - ReferenceListProperty, - StringProperty, - VariableListProperty, -) -from kivy.uix.widget import Widget -from PIL import Image, ImageDraw, ImageFilter - -from kivymd.app import MDApp - -Builder.load_string( - """ -#:import InstructionGroup kivy.graphics.instructions.InstructionGroup - - - - canvas.before: - # SOFT SHADOW - PushMatrix - Rotate: - angle: self.angle - origin: self._shadow_origin - Color: - group: "soft_shadow" - rgba: root.soft_shadow_cl - Rectangle: - group: "soft_shadow" - texture: self._soft_shadow_texture - size: self.soft_shadow_size - pos: self.soft_shadow_pos - PopMatrix - - # HARD SHADOW - PushMatrix - Rotate: - angle: self.angle - origin: self.center - Color: - group: "hard_shadow" - rgba: root.hard_shadow_cl - Rectangle: - group: "hard_shadow" - texture: self.hard_shadow_texture - size: self.hard_shadow_size - pos: self.hard_shadow_pos - PopMatrix - Color: - group: "shadow" - a: 1 -""", - filename="CommonElevationBehavior.kv", -) - - -class CommonElevationBehavior(Widget): - """Common base class for rectangular and circular elevation behavior.""" - - elevation = BoundedNumericProperty(0, min=0, errorvalue=0) - """ - Elevation of the widget. - - .. note:: - Although, this value does not represent the current elevation of the - widget. :attr:`~CommonElevationBehavior._elevation` can be used to - animate the current elevation and come back using the - :attr:`~CommonElevationBehavior.elevation` property directly. - - For example: - - .. code-block:: python - - from kivy.lang import Builder - from kivy.uix.behaviors import ButtonBehavior - - from kivymd.app import MDApp - from kivymd.uix.behaviors import CircularElevationBehavior, CircularRippleBehavior - from kivymd.uix.boxlayout import MDBoxLayout - - KV = ''' - #:import Animation kivy.animation.Animation - - - - size_hint: [None, None] - elevation: 6 - animation_: None - md_bg_color: [1] * 4 - on_size: - self.radius = [self.height / 2] * 4 - on_press: - if self.animation_: \ - self.animation_.cancel(self); \ - self.animation_ = Animation(_elevation=self.elevation + 6, d=0.08); \ - self.animation_.start(self) - on_release: - if self.animation_: \ - self.animation_.cancel(self); \ - self.animation_ = Animation(_elevation = self.elevation, d=0.08); \ - self.animation_.start(self) - - MDFloatLayout: - - WidgetWithShadow: - size: [root.size[1] / 2] * 2 - pos_hint: {"center": [0.5, 0.5]} - ''' - - - class WidgetWithShadow( - CircularElevationBehavior, - CircularRippleBehavior, - ButtonBehavior, - MDBoxLayout, - ): - def __init__(self, **kwargs): - # always set the elevation before the super().__init__ call - # self.elevation = 6 - super().__init__(**kwargs) - - def on_size(self, *args): - self.radius = [self.size[0] / 2] - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - """ - - # Shadow rendering properties. - # Shadow rotation memory - SHARED ACROSS OTHER CLASSES. - angle = NumericProperty(0) - """ - Angle of rotation in degrees of the current shadow. - This value is shared across different widgets. - - .. note:: - This value will affect both, hard and soft shadows. - Each shadow has his own origin point that's computed every time the - elevation changes. - - .. warning:: - Do not add `PushMatrix` inside the canvas before and add `PopMatrix` - in the next layer, this will cause visual errors, because the stack - used will clip the push and pop matrix already inside the canvas.before - canvas layer. - - Incorrect: - - .. code-block:: kv - - - canvas.before: - PushMatrix - [...] - canvas: - PopMatrix - - Correct: - - .. code-block:: kv - - - canvas.before: - PushMatrix - [...] - PopMatrix - - - - :attr:`angle` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - radius = VariableListProperty([0]) - """ - Radius of the corners of the shadow. - This values represents each corner of the shadow, starting from `top-left` - corner and going clockwise. - - .. code-block:: python - - radius = [ - "top-left", - "top-right", - "bottom-right", - "bottom-left", - ] - - This value can be expanded thus allowing this settings to be valid: - - .. code-block:: python - - widget.radius=[0] # Translates to [0, 0, 0, 0] - widget.radius=[10, 3] # Translates to [10, 3, 10, 3] - widget.radius=[7.0, 8.7, 1.5, 3.0] # Translates to [7, 8, 1, 3] - - .. note:: - This value will affect both, hard and soft shadows. - This value only affects :class:`~RoundedRectangularElevationBehavior` - for now, but can be stored and used by custom shadow draw functions. - - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - # Position of the shadow. - _shadow_origin_x = NumericProperty(0) - """ - Shadow origin `x` position for the rotation origin. - - Managed by `_shadow_origin`. - - :attr:`_shadow_origin_x` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - - .. note:: - This property is automatically processed. by _shadow_origin. - """ - - _shadow_origin_y = NumericProperty(0) - """ - Shadow origin y position for the rotation origin. - - Managed by :attr:`_shadow_origin`. - - :attr:`_shadow_origin_y` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - - .. note:: - This property is automatically processed. - """ - - _shadow_origin = ReferenceListProperty(_shadow_origin_x, _shadow_origin_y) - """ - Soft shadow rotation origin point. - - :attr:`_shadow_origin` is an :class:`~kivy.properties.ReferenceListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed and relative to the canvas center. - """ - - _shadow_pos = ListProperty([0, 0]) # custom offset - """ - Soft shadow origin point. - - :attr:`_shadow_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed and relative to the widget's - canvas center. - """ - - shadow_pos = ListProperty([0, 0]) # bottom left corner - """ - Custom shadow origin point. If this property is set, :attr:`_shadow_pos` - will be ommited. - - This property allows users to fake light source. - - :attr:`shadow_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - this value overwrite the :attr:`_shadow_pos` processing. - """ - - # Shadow Group shared memory - __shadow_groups = {"global": []} - - shadow_group = StringProperty("global") - """ - Widget's shadow group. - By default every widget with a shadow is saved inside the memory - :attr:`__shadow_groups` as a weakref. This means that you can have multiple - light sources, one for every shadow group. - - To fake a light source use :attr:`force_shadow_pos`. - - :attr:`shadow_group` is an :class:`~kivy.properties.StringProperty` - and defaults to `"global"`. - """ - - _elevation = BoundedNumericProperty(0, min=0, errorvalue=0) - """ - Current elevation of the widget. - - .. warning:: - This property is the current elevation of the widget, do not - use this property directly, instead, use :class:`~CommonElevationBehavior` - elevation. - - :attr:`_elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - # soft shadow - _soft_shadow_texture = ObjectProperty() - """ - Texture of the soft shadow texture for the canvas. - - :attr:`_soft_shadow_texture` is an :class:`~kivy.core.image.Image` - and defaults to `None`. - - .. note:: - This property is automatically processed. - """ - - soft_shadow_size = ListProperty([0, 0]) - """ - Size of the soft shadow texture over the canvas. - - :attr:`soft_shadow_size` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed. - """ - - soft_shadow_pos = ListProperty([0, 0]) - """ - Position of the hard shadow texture over the canvas. - - :attr:`soft_shadow_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed. - """ - - soft_shadow_cl = ListProperty([0, 0, 0, 0.50]) - """ - Color of the soft shadow. - - :attr:`soft_shadow_cl` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0, 0, 0.15]`. - """ - - # hard shadow - hard_shadow_texture = ObjectProperty() - """ - Texture of the hard shadow texture for the canvas. - - :attr:`hard_shadow_texture` is an :class:`~kivy.core.image.Image` - and defaults to `None`. - - .. note:: - This property is automatically processed when elevation is changed. - """ - - hard_shadow_size = ListProperty([0, 0]) - """ - Size of the hard shadow texture over the canvas. - - :attr:`hard_shadow_size` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed when elevation is changed. - """ - - hard_shadow_pos = ListProperty([0, 0]) - """ - Position of the hard shadow texture over the canvas. - - :attr:`hard_shadow_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - - .. note:: - This property is automatically processed when elevation is changed. - """ - - hard_shadow_cl = ListProperty([0, 0, 0, 0.15]) - """ - Color of the hard shadow. - - .. note:: - :attr:`hard_shadow_cl` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0, 0, 0.15]`. - """ - - # Shared property for some calculations. - # This values are used to improve the gaussain blur and avoid that - # the blur goes outside the texture. - hard_shadow_offset = BoundedNumericProperty( - 2, min=0, errorhandler=lambda x: 0 if x < 0 else x - ) - """ - This value sets a special offset to the shadow canvas, this offset allows a - correct draw of the canvas size. allowing the effect to correctly blur the - image in the given space. - - :attr:`hard_shadow_offset` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `2`. - """ - - soft_shadow_offset = BoundedNumericProperty( - 4, min=0, errorhandler=lambda x: 0 if x < 0 else x - ) - """ - This value sets a special offset to the shadow canvas, this offset allows a - correct draw of the canvas size. allowing the effect to correctly blur the - image in the given space. - - :attr:`soft_shadow_offset` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `4`. - """ - - draw_shadow = ObjectProperty(None) - """ - This property controls the draw call of the context. - - This property is automatically set to :attr:`__draw_shadow__` inside the - `super().__init__ call.` unless the property is different of None. - - To set a different drawing instruction function, set this property before the - `super(),__init__` call inside the `__init__` definition of the new class. - - You can use the source for this classes as example of how to draw over - with the context: - - Real time shadows: - #. :class:`~RectangularElevationBehavior` - #. :class:`~CircularElevationBehavior` - #. :class:`~RoundedRectangularElevationBehavior` - #. :class:`~ObservableShadow` - - - Fake shadows (d`ont use this property): - #. :class:`~FakeRectangularElevationBehavior` - #. :class:`~FakeCircularElevationBehavior` - - :attr:`draw_shadow` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - - .. note:: If this property is left to `None` the - :class:`~CommonElevationBehavior` will set to a function that will - raise a `NotImplementedError` inside `super().__init__`. - - Follow the next example to set a new draw instruction for the class - inside `__init__`: - - .. code-block:: python - - class RoundedRectangularElevationBehavior(CommonElevationBehavior): - ''' - Shadow class for the RoundedRectangular shadow behavior. - Controls the size and position of the shadow. - ''' - - def __init__(self, **kwargs): - self._draw_shadow = WeakMethod(self.__draw_shadow__) - super().__init__(**kwargs) - - def __draw_shadow__(self, origin, end, context=None): - context.draw(...) - - Context is a `Pillow` `ImageDraw` class. For more information check the - [Pillow official documentation](https://github.com/python-pillow/Pillow/). - """ - - # All classes that uses a fake shadow shall set this value as `True` - # for performance. - _fake_elevation = BooleanProperty(False) - - def __init__(self, **kwargs): - if self.draw_shadow is None: - self.draw_shadow = WeakMethod(self.__draw_shadow__) - self.prev_shadow_group = None - im = BytesIO() - Image.new("RGBA", (4, 4), color=(0, 0, 0, 0)).save(im, format="png") - im.seek(0) - # Setting a empty image as texture, improves performance. - self._soft_shadow_texture = self.hard_shadow_texture = CoreImage( - im, ext="png" - ).texture - Clock.schedule_once(self.shadow_preset, -1) - self.on_shadow_group(self, self.shadow_group) - - self.bind( - pos=self._update_shadow, - size=self._update_shadow, - radius=self._update_shadow, - ) - super().__init__(**kwargs) - - def on_shadow_group(self, instance, value): - """ - This function controls the shadow group of the widget. - Do not use Directly to change the group. instead, use the shadow_group - :attr:`property`. - """ - - groups = CommonElevationBehavior.__shadow_groups - if self.prev_shadow_group: - group = groups[self.prev_shadow_group] - for widget in group[:]: - if widget() is self: - group.remove(widget) - group = self.prev_shadow_group = self.shadow_group - if group not in groups: - groups[group] = [] - r = ref(self, CommonElevationBehavior._clear_shadow_groups) - groups[group].append(r) - - @staticmethod - def _clear_shadow_groups(wk): - # auto flush the element when the weak reference have been deleted - groups = CommonElevationBehavior.__shadow_groups - for group in list(groups.values()): - if not group: - break - if wk in group: - group.remove(wk) - break - - def force_shadow_pos(self, shadow_pos): - """ - This property forces the shadow position in every widget inside the - widget. The argument :attr:`shadow_pos` is expected as a - or . - """ - - if self.shadow_group is None: - return - group = CommonElevationBehavior.__shadow_groups[self.shadow_group] - for wk in group[:]: - widget = wk() - if widget is None: - group.remove(wk) - widget.shadow_pos = shadow_pos - del group - - def update_group_property(self, property_name, value): - """ - This functions allows to change properties of every widget inside the - shadow group. - """ - - if self.shadow_group is None: - return - group = CommonElevationBehavior.__shadow_groups[self.shadow_group] - for wk in group[:]: - widget = wk() - if widget is None: - group.remove(wk) - setattr(widget, property_name, value) - del group - - def shadow_preset(self, *args): - """ - This function is meant to set the default configuration of the - elevation. - - After a new instance is created, the elevation property will be launched - and thus this function will update the elevation if the KV lang have not - done it already. - - Works similar to an `__after_init__` call inside a widget. - """ - - if self.elevation is None: - self.elevation = 10 - if self._fake_elevation is False: - self._update_shadow(self, self.elevation) - self.bind( - pos=self._update_shadow, - size=self._update_shadow, - _elevation=self._update_shadow, - ) - - def on_elevation(self, instance, value): - """ - Elevation event that sets the current elevation value to `_elevation`. - """ - - if value is not None: - self._elevation = value - - def _set_soft_shadow_a(self, value): - value = 0 if value < 0 else (1 if value > 1 else value) - self.soft_shadow_cl[-1] = value - return True - - def _set_hard_shadow_a(self, value): - value = 0 if value < 0 else (1 if value > 1 else value) - self.hard_shadow_cl[-1] = value - return True - - def _get_soft_shadow_a(self): - return self.soft_shadow_cl[-1] - - def _get_hard_shadow_a(self): - return self.hard_shadow_cl[-1] - - _soft_shadow_a = AliasProperty( - _get_soft_shadow_a, _set_soft_shadow_a, bind=["soft_shadow_cl"] - ) - _hard_shadow_a = AliasProperty( - _get_hard_shadow_a, _set_hard_shadow_a, bind=["hard_shadow_cl"] - ) - - def on_disabled(self, instance, value): - """ - This function hides the shadow when the widget is disabled. - It sets the shadow to `0`. - """ - - if self.disabled is True: - self._elevation = 0 - else: - self._elevation = 0 if self.elevation is None else self.elevation - self._update_shadow(self, self._elevation) - try: - super().on_disabled(instance, value) - except Exception: - pass - - def _update_elevation(self, instance, value): - self._elevation = value - self._update_shadow(instance, value) - - def _update_shadow_pos(self, instance, value): - if self._elevation > 0: - self.hard_shadow_pos = [ - self.x - dp(self.hard_shadow_offset), # + self.shadow_pos[0], - self.y - dp(self.hard_shadow_offset), # + self.shadow_pos[1], - ] - if self.shadow_pos == [0, 0]: - self.soft_shadow_pos = [ - self.x - + self._shadow_pos[0] - - self._elevation - - dp(self.soft_shadow_offset), - self.y - + self._shadow_pos[1] - - self._elevation - - dp(self.soft_shadow_offset), - ] - else: - self.soft_shadow_pos = [ - self.x - + self.shadow_pos[0] - - self._elevation - - dp(self.soft_shadow_offset), - self.y - + self.shadow_pos[1] - - self._elevation - - dp(self.soft_shadow_offset), - ] - self._shadow_origin = [ - self.soft_shadow_pos[0] + self.soft_shadow_size[0] / 2, - self.soft_shadow_pos[1] + self.soft_shadow_size[1] / 2, - ] - - def on__shadow_pos(self, ins, val): - """ - Updates the shadow with the computed value. - - Call this function every time you need to force a shadow update. - """ - - self._update_shadow_pos(ins, val) - - def on_shadow_pos(self, ins, val): - """ - Updates the shadow with the fixed value. - - Call this function every time you need to force a shadow update. - """ - - self._update_shadow_pos(ins, val) - - def _update_shadow(self, instance, value): - self._update_shadow_pos(instance, value) - if self._elevation > 0 and self._fake_elevation is False: - # dynamic elevation position for the shadow - if self.shadow_pos == [0, 0]: - self._shadow_pos = [0, -self._elevation * 0.4] - - # HARD Shadow - offset = int(dp(self.hard_shadow_offset)) - size = [ - int(self.size[0] + (offset * 2)), - int(self.size[1] + (offset * 2)), - ] - im = BytesIO() - # context - img = Image.new("RGBA", tuple(size), color=(0, 0, 0, 0)) - # draw context - shadow = ImageDraw.Draw(img) - self.draw_shadow()( - [offset, offset], - [ - int(size[0] - 1 - offset), - int(size[1] - 1 - offset), - ], - context=shadow - # context=ref(shadow) - ) - img = img.filter( - ImageFilter.GaussianBlur( - radius=int(dp(1 + self.hard_shadow_offset / 3)) - ) - ) - img.save(im, format="png") - im.seek(0) - self.hard_shadow_size = size - self.hard_shadow_texture = CoreImage(im, ext="png").texture - - # soft shadow - if self.soft_shadow_cl[-1] > 0: - offset = dp(self.soft_shadow_offset) - size = [ - int(self.size[0] + dp(self._elevation * 2) + (offset * 2)), - int(self.size[1] + dp(self._elevation * 2) + (offset * 2)), - # ((self._elevation)*2) + x + (offset*2)) for x in self.size - ] - im = BytesIO() - img = Image.new("RGBA", tuple(size), color=((0,) * 4)) - shadow = ImageDraw.Draw(img) - _offset = int(dp(self._elevation + offset)) - self.draw_shadow()( - [ - _offset, - _offset, - ], - [int(size[0] - _offset - 1), int(size[1] - _offset - 1)], - context=shadow - # context=ref(shadow) - ) - img = img.filter( - ImageFilter.GaussianBlur(radius=self._elevation // 2) - ) - shadow = ImageDraw.Draw(img) - img.save(im, format="png") - im.seek(0) - self.soft_shadow_size = size - self._soft_shadow_texture = CoreImage(im, ext="png").texture - else: - im = BytesIO() - Image.new("RGBA", (4, 4), color=(0, 0, 0, 0)).save(im, format="png") - im.seek(0) - self._soft_shadow_texture = self.hard_shadow_texture = CoreImage( - im, ext="png" - ).texture - return - - def __draw_shadow__(self, origin, end, context=None): - raise NotImplementedError( - "KivyMD:\n" - "If you see this error, this means that either youre using " - "`CommonElevationBehavio`r directly or your 'shader' dont have a " - "`_draw_shadow` instruction, remember to overwrite this function" - "to draw over the image context. the figure you would like." - ) - - -class RectangularElevationBehavior(CommonElevationBehavior): - """ - Base class for a rectangular elevation behavior. - """ - - def __init__(self, **kwargs): - self.draw_shadow = WeakMethod(self.__draw_shadow__) - super().__init__(**kwargs) - - def __draw_shadow__(self, origin, end, context=None): - context.rectangle(origin + end, fill=tuple([255] * 4)) - - -class CircularElevationBehavior(CommonElevationBehavior): - """ - Base class for a circular elevation behavior. - """ - - def __init__(self, **kwargs): - self.draw_shadow = WeakMethod(self.__draw_shadow__) - super().__init__(**kwargs) - - def __draw_shadow__(self, origin, end, context=None): - context.ellipse(origin + end, fill=tuple([255] * 4)) - - -class RoundedRectangularElevationBehavior(CommonElevationBehavior): - """ - Base class for rounded rectangular elevation behavior. - """ - - def __init__(self, **kwargs): - self.bind( - radius=self._update_shadow, - ) - self.draw_shadow = WeakMethod(self.__draw_shadow__) - super().__init__(**kwargs) - - def __draw_shadow__(self, origin, end, context=None): - if self.radius == [0, 0, 0, 0]: - context.rectangle(origin + end, fill=tuple([255] * 4)) - else: - radius = [x * 2 for x in self.radius] - context.pieslice( - [ - origin[0], - origin[1], - origin[0] + radius[0], - origin[1] + radius[0], - ], - 180, - 270, - fill=(255, 255, 255, 255), - ) - context.pieslice( - [ - end[0] - radius[1], - origin[1], - end[0], - origin[1] + radius[1], - ], - 270, - 360, - fill=(255, 255, 255, 255), - ) - context.pieslice( - [ - end[0] - radius[2], - end[1] - radius[2], - end[0], - end[1], - ], - 0, - 90, - fill=(255, 255, 255, 255), - ) - context.pieslice( - [ - origin[0], - end[1] - radius[3], - origin[0] + radius[3], - end[1], - ], - 90, - 180, - fill=(255, 255, 255, 255), - ) - if all((x == self.radius[0] for x in self.radius)): - radius = int(self.radius[0]) - context.rectangle( - [ - origin[0] + radius, - origin[1], - end[0] - radius, - end[1], - ], - fill=(255,) * 4, - ) - context.rectangle( - [ - origin[0], - origin[1] + radius, - end[0], - end[1] - radius, - ], - fill=(255,) * 4, - ) - else: - radius = [ - max((self.radius[0], self.radius[1])), - max((self.radius[1], self.radius[2])), - max((self.radius[2], self.radius[3])), - max((self.radius[3], self.radius[0])), - ] - context.rectangle( - [ - origin[0] + self.radius[0], - origin[1], - end[0] - self.radius[1], - end[1] - radius[2], - ], - fill=(255,) * 4, - ) - context.rectangle( - [ - origin[0] + radius[3], - origin[1] + self.radius[1], - end[0], - end[1] - self.radius[2], - ], - fill=(255,) * 4, - ) - context.rectangle( - [ - origin[0] + self.radius[3], - origin[1] + radius[0], - end[0] - self.radius[2], - end[1], - ], - fill=(255,) * 4, - ) - context.rectangle( - [ - origin[0], - origin[1] + self.radius[0], - end[0] - radius[2], - end[1] - self.radius[3], - ], - fill=(255,) * 4, - ) - - -class ObservableShadow(CommonElevationBehavior): - """ - ObservableShadow is real time shadow render that it's intended to only - render a partial shadow of widgets based upon on the window observable - area, this is meant to improve the performance of bigger widgets. - - .. warning:: - This is an empty class, the name has been reserved for future use. - if you include this clas in your object, you wil get a - `NotImplementedError`. - """ - - def __init__(self, **kwargs): - # self._shadow = MDApp.get_running_app().theme_cls.round_shadow - # self._fake_elevation=True - raise NotImplementedError( - "ObservableShadow:\n\t" "This class is in current development" - ) - super().__init__(**kwargs) - - -class FakeRectangularElevationBehavior(CommonElevationBehavior): - """ - `FakeRectangularElevationBehavio`r is a shadow mockup for widgets. Improves - performance using cached images inside `kivymd.images` dir - - This class cast a fake Rectangular shadow behaind the widget. - - You can either use this behavior to overwrite the elevation of a prefab - widget, or use it directly inside a new widget class definition. - - Use this class as follows for new widgets: - - .. code-block:: python - - class NewWidget( - ThemableBehavior, - FakeCircularElevationBehavior, - SpecificBackgroundColorBehavior, - # here you add the other front end classes for the widget front_end, - ): - [...] - - With this method each class can draw it's content in the canvas in the - correct order, avoiding some visual errors. - - `FakeCircularElevationBehavior` will load prefabricated textures to - optimize loading times. - - Also, this class allows you to overwrite real time shadows, in the sence that - if you are using a standard widget, like a button, MDCard or Toolbar, you can - include this class after the base class to optimize the loading times. - - As an example of this flexibility: - - .. code-block:: python - - class Custom_rectangular_Card( - MDCard, - FakeRectangularElevationBehavior - ): - [...] - - .. note:: About rounded corners: - be careful, since this behavior is a mockup and will not draw any - rounded corners. - """ - - def __init__(self, **kwargs): - # self._shadow = MDApp.get_running_app().theme_cls.round_shadow - self.draw_shadow = WeakMethod(self.__draw_shadow__) - self._fake_elevation = True - self._update_shadow(self, self.elevation) - super().__init__(**kwargs) - - def _update_shadow(self, *args): - if self._elevation > 0: - # Set shadow size. - ratio = self.width / (self.height if self.height != 0 else 1) - if -2 < ratio < 2: - self._shadow = MDApp.get_running_app().theme_cls.quad_shadow - width = soft_width = self.width * 1.9 - height = soft_height = self.height * 1.9 - elif ratio <= -2: - self._shadow = MDApp.get_running_app().theme_cls.rec_st_shadow - ratio = abs(ratio) - if ratio > 5: - ratio = ratio * 22 - else: - ratio = ratio * 11.5 - width = soft_width = self.width * 1.9 - height = self.height + dp(ratio) - soft_height = ( - self.height + dp(ratio) + dp(self._elevation) * 0.5 - ) - else: - self._shadow = MDApp.get_running_app().theme_cls.quad_shadow - width = soft_width = self.width * 1.8 - height = soft_height = self.height * 1.8 - - self.soft_shadow_size = (soft_width, soft_height) - self.hard_shadow_size = (width, height) - # Set ``soft_shadow`` parameters. - self.hard_shadow_pos = self.soft_shadow_pos = ( - self.center_x - soft_width / 2, - self.center_y - soft_height / 2 - dp(self._elevation * 0.5), - ) - # Set transparency - self._soft_shadow_a = 0.1 * 1.05 ** self._elevation - self._hard_shadow_a = 0.4 * 0.8 ** self._elevation - t = int(round(self._elevation)) - if 0 < t <= 23: - self._soft_shadow_texture = ( - self._hard_shadow_texture - ) = self._shadow.textures[str(t)] - else: - self._soft_shadow_texture = ( - self._hard_shadow_texture - ) = self._shadow.textures["23"] - else: - self._soft_shadow_a = 0 - self._hard_shadow_a = 0 - - def __draw_shadow__(self, origin, end, context=None): - pass - - -class FakeCircularElevationBehavior(CommonElevationBehavior): - """ - `FakeCircularElevationBehavior` is a shadow mockup for widgets. Improves - performance using cached images inside `kivymd.images` dir - - This class cast a fake elliptic shadow behaind the widget. - - You can either use this behavior to overwrite the elevation of a prefab - widget, or use it directly inside a new widget class definition. - - Use this class as follows for new widgets: - - .. code-block:: python - - class NewWidget( - ThemableBehavior, - FakeCircularElevationBehavior, - SpecificBackgroundColorBehavior, - # here you add the other front end classes for the widget front_end, - ): - [...] - - With this method each class can draw it's content in the canvas in the - correct order, avoiding some visual errors. - - `FakeCircularElevationBehavior` will load prefabricated textures to optimize - loading times. - - Also, this class allows you to overwrite real time shadows, in the sence that - if you are using a standard widget, like a button, MDCard or Toolbar, you can - include this class afher the base class to optimize the loading times. - - As an example of this flexibility: - - .. code-block:: python - - class Custom_Circular_Card( - MDCard, - FakeCircularElevationBehavior - ): - [...] - - .. note:: About rounded corners: - be careful, since this behavior is a mockup and will not draw any rounded - corners. only perfect ellipses. - """ - - def __init__(self, **kwargs): - self._shadow = MDApp.get_running_app().theme_cls.round_shadow - self.draw_shadow = WeakMethod(self.__draw_shadow__) - self._fake_elevation = True - self._update_shadow(self, self.elevation) - super().__init__(**kwargs) - - def _update_shadow(self, *args): - if self._elevation > 0: - # set shadow size - width = self.width * 2 - height = self.height * 2 - - x = self.center_x - width / 2 - self.soft_shadow_size = (width, height) - self.hard_shadow_size = (width, height) - # set ``soft_shadow`` parameters - y = self.center_y - height / 2 - dp(0.5 * self._elevation) - self.soft_shadow_pos = (x, y) - - # set ``hard_shadow`` parameters - y = self.center_y - height / 2 - dp(0.5 * self._elevation) - self.hard_shadow_pos = (x, y) - - # shadow transparency - self._soft_shadow_a = 0.1 * 1.05 ** self._elevation - self._hard_shadow_a = 0.4 * 0.8 ** self._elevation - t = int(round(self._elevation)) - if 0 < t <= 23: - if hasattr(self, "_shadow"): - self._soft_shadow_texture = ( - self._hard_shadow_texture - ) = self._shadow.textures[str(t)] - else: - self._soft_shadow_texture = ( - self._hard_shadow_texture - ) = self._shadow.textures["23"] - else: - self._soft_shadow_a = 0 - self._hard_shadow_a = 0 - - def __draw_shadow__(self, origin, end, context=None): - pass diff --git a/kivymd/uix/behaviors/focus_behavior.py b/kivymd/uix/behaviors/focus_behavior.py deleted file mode 100644 index 15d3169..0000000 --- a/kivymd/uix/behaviors/focus_behavior.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Behaviors/Focus -=============== - -.. rubric:: Changing the background color when the mouse is on the widget. - -To apply focus behavior, you must create a new class that is inherited from the -widget to which you apply the behavior and from the :class:`FocusBehavior` class. - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.behaviors import RectangularElevationBehavior, FocusBehavior - from kivymd.uix.boxlayout import MDBoxLayout - - KV = ''' - MDScreen: - md_bg_color: 1, 1, 1, 1 - - FocusWidget: - size_hint: .5, .3 - pos_hint: {"center_x": .5, "center_y": .5} - md_bg_color: app.theme_cls.bg_light - - MDLabel: - text: "Label" - theme_text_color: "Primary" - pos_hint: {"center_y": .5} - halign: "center" - ''' - - - class FocusWidget(MDBoxLayout, RectangularElevationBehavior, FocusBehavior): - pass - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-widget.gif - :align: center - -Color change at focus/defocus - -.. code-block:: kv - - FocusWidget: - focus_color: 1, 0, 1, 1 - unfocus_color: 0, 0, 1, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-defocus-color.gif - :align: center -""" - -__all__ = ("FocusBehavior",) - -from kivy.app import App -from kivy.properties import BooleanProperty, ColorProperty -from kivy.uix.behaviors import ButtonBehavior - -from kivymd.uix.behaviors import HoverBehavior - - -class FocusBehavior(HoverBehavior, ButtonBehavior): - - focus_behavior = BooleanProperty(True) - """ - Using focus when hovering over a widget. - - :attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - focus_color = ColorProperty(None) - """ - The color of the widget when the mouse enters the bbox of the widget. - - :attr:`focus_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - unfocus_color = ColorProperty(None) - """ - The color of the widget when the mouse exits the bbox widget. - - :attr:`unfocus_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - def on_enter(self): - """Called when mouse enter the bbox of the widget.""" - - if hasattr(self, "md_bg_color") and self.focus_behavior: - if hasattr(self, "theme_cls") and not self.focus_color: - self.md_bg_color = self.theme_cls.bg_normal - else: - if not self.focus_color: - self.md_bg_color = App.get_running_app().theme_cls.bg_normal - else: - self.md_bg_color = self.focus_color - - def on_leave(self): - """Called when the mouse exit the widget.""" - - if hasattr(self, "md_bg_color") and self.focus_behavior: - if hasattr(self, "theme_cls") and not self.unfocus_color: - self.md_bg_color = self.theme_cls.bg_light - else: - if not self.unfocus_color: - self.md_bg_color = App.get_running_app().theme_cls.bg_light - else: - self.md_bg_color = self.unfocus_color diff --git a/kivymd/uix/behaviors/hover_behavior.py b/kivymd/uix/behaviors/hover_behavior.py deleted file mode 100644 index 40f519e..0000000 --- a/kivymd/uix/behaviors/hover_behavior.py +++ /dev/null @@ -1,235 +0,0 @@ -""" -Behaviors/Hover -=============== - -.. rubric:: Changing when the mouse is on the widget and the widget is visible. - -To apply hover behavior, you must create a new class that is inherited from the -widget to which you apply the behavior and from the :attr:`HoverBehavior` class. - -In `KV file`: - -.. code-block:: kv - - - -In `python file`: - -.. code-block:: python - - class HoverItem(MDBoxLayout, ThemableBehavior, HoverBehavior): - '''Custom item implementing hover behavior.''' - -After creating a class, you must define two methods for it: -:attr:`HoverBehavior.on_enter` and :attr:`HoverBehavior.on_leave`, which will be automatically called -when the mouse cursor is over the widget and when the mouse cursor goes beyond -the widget. - -.. note:: - - :class:`~HoverBehavior` will by default check to see if the current Widget is visible (i.e. not covered by a modal or popup and not a part of a Relative Layout, MDTab or Carousel that is not currently visible etc) and will only issue events if the widget is visible. - - To get the legacy behavior that the events are always triggered, you can set `detect_visible` on the Widget to `False`. - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.behaviors import HoverBehavior - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.theming import ThemableBehavior - - KV = ''' - Screen - - MDBoxLayout: - id: box - pos_hint: {'center_x': .5, 'center_y': .5} - size_hint: .8, .8 - md_bg_color: app.theme_cls.bg_darkest - ''' - - - class HoverItem(MDBoxLayout, ThemableBehavior, HoverBehavior): - '''Custom item implementing hover behavior.''' - - def on_enter(self, *args): - '''The method will be called when the mouse cursor - is within the borders of the current widget.''' - - self.md_bg_color = (1, 1, 1, 1) - - def on_leave(self, *args): - '''The method will be called when the mouse cursor goes beyond - the borders of the current widget.''' - - self.md_bg_color = self.theme_cls.bg_darkest - - - class Test(MDApp): - def build(self): - self.screen = Builder.load_string(KV) - for i in range(5): - self.screen.ids.box.add_widget(HoverItem()) - return self.screen - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hover-behavior.gif - :width: 250 px - :align: center -""" - -__all__ = ("HoverBehavior",) - -from kivy.core.window import Window -from kivy.properties import BooleanProperty, ObjectProperty -from kivy.uix.widget import Widget - - -class HoverBehavior(object): - """ - :Events: - :attr:`on_enter` - Called when mouse enters the bbox of the widget AND the widget is visible - :attr:`on_leave` - Called when the mouse exits the widget AND the widget is visible - """ - - hovering = BooleanProperty(False) - """ - `True`, if the mouse cursor is within the borders of the widget. - - Note that this is set and cleared even if the widget is not visible - - :attr:`hover` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - hover_visible = BooleanProperty(False) - """ - `True` if hovering is True AND is the current widget is visible - - :attr:`hover_visible` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - enter_point = ObjectProperty(allownone=True) - """ - Holds the last position where the mouse pointer crossed into the Widget - if the Widget is visible and is currently in a hovering state - - :attr:`enter_point` is a :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - detect_visible = BooleanProperty(True) - """ - Should this widget perform the visibility check? - - :attr:`detect_visible` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - def __init__(self, **kwargs): - self.register_event_type("on_enter") - self.register_event_type("on_leave") - Window.bind(mouse_pos=self.on_mouse_update) - super(HoverBehavior, self).__init__(**kwargs) - - def on_mouse_update(self, *args): - # If the Widget currently has no parent, do nothing - if not self.get_root_window(): - return - pos = args[1] - # - # is the pointer in the same position as the widget? - # If not - then issue an on_exit event if needed - # - if not self.collide_point(*self.to_widget(*pos)): - self.hovering = False - self.enter_point = None - if self.hover_visible: - self.hover_visible = False - self.dispatch("on_leave") - return - - # - # The pointer is in the same position as the widget - # - - if self.hovering: - # - # nothing to do here. Not - this does not handle the case where - # a popup comes over an existing hover event. - # This seems reasonable - # - return - - # - # Otherwise - set the hovering attribute - # - self.hovering = True - - # - # We need to traverse the tree to see if the Widget is visible - # - # This is a two stage process: - # - first go up the tree to the root Window. - # At each stage - check that the Widget is actually visible - # - Second - At the root Window check that there is not another branch - # covering the Widget - # - - self.hover_visible = True - if self.detect_visible: - widget: Widget = self - while True: - # Walk up the Widget tree from the target Widget - parent = widget.parent - try: - # See if the mouse point collides with the parent - # using both local and glabal coordinates to cover absoluet and relative layouts - pinside = parent.collide_point( - *parent.to_widget(*pos) - ) or parent.collide_point(*pos) - except Exception: - # The collide_point will error when you reach the root Window - break - if not pinside: - self.hover_visible = False - break - # Iterate upwards - widget = parent - - # - # parent = root window - # widget = first Widget on the current branch - # - - children = parent.children - for child in children: - # For each top level widget - check if is current branch - # If it is - then break. - # If not then - since we start at 0 - this widget is visible - # - # Check to see if it should take the hover - # - if child == widget: - # this means that the current widget is visible - break - if child.collide_point(*pos): - # this means that the current widget is covered by a modal or popup - self.hover_visible = False - break - if self.hover_visible: - self.enter_point = pos - self.dispatch("on_enter") - - def on_enter(self): - """Called when mouse enters the bbox of the widget AND the widget is visible.""" - - def on_leave(self): - """Called when the mouse exits the widget AND the widget is visible.""" diff --git a/kivymd/uix/behaviors/magic_behavior.py b/kivymd/uix/behaviors/magic_behavior.py deleted file mode 100644 index 0061bf5..0000000 --- a/kivymd/uix/behaviors/magic_behavior.py +++ /dev/null @@ -1,192 +0,0 @@ -""" -Behaviors/Magic -=============== - -.. rubric:: Magical effects for buttons. - -.. warning:: Magic effects do not work correctly with `KivyMD` buttons! - -To apply magic effects, you must create a new class that is inherited from the -widget to which you apply the effect and from the :attr:`MagicBehavior` class. - -In `KV file`: - -.. code-block:: kv - - - -In `python file`: - -.. code-block:: python - - class MagicButton(MagicBehavior, MDRectangleFlatButton): - pass - -.. rubric:: The :attr:`MagicBehavior` class provides five effects: - -- :attr:`MagicBehavior.wobble` -- :attr:`MagicBehavior.grow` -- :attr:`MagicBehavior.shake` -- :attr:`MagicBehavior.twist` -- :attr:`MagicBehavior.shrink` - -Example: - -.. code-block:: python - - from kivymd.app import MDApp - from kivy.lang import Builder - - KV = ''' - #:import MagicBehavior kivymd.uix.behaviors.MagicBehavior - - - - - - FloatLayout: - - MagicButton: - text: "WOBBLE EFFECT" - on_release: self.wobble() - pos_hint: {"center_x": .5, "center_y": .3} - - MagicButton: - text: "GROW EFFECT" - on_release: self.grow() - pos_hint: {"center_x": .5, "center_y": .4} - - MagicButton: - text: "SHAKE EFFECT" - on_release: self.shake() - pos_hint: {"center_x": .5, "center_y": .5} - - MagicButton: - text: "TWIST EFFECT" - on_release: self.twist() - pos_hint: {"center_x": .5, "center_y": .6} - - MagicButton: - text: "SHRINK EFFECT" - on_release: self.shrink() - pos_hint: {"center_x": .5, "center_y": .7} - ''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/magic-button.gif - :width: 250 px - :align: center -""" - -__all__ = ("MagicBehavior",) - -from kivy.animation import Animation -from kivy.lang import Builder -from kivy.properties import NumericProperty - -Builder.load_string( - """ - - translate_x: 0 - translate_y: 0 - scale_x: 1 - scale_y: 1 - rotate: 0 - - canvas.before: - PushMatrix - Translate: - x: self.translate_x or 0 - y: self.translate_y or 0 - Rotate: - origin: self.center - angle: self.rotate or 0 - Scale: - origin: self.center - x: self.scale_x or 1 - y: self.scale_y or 1 - canvas.after: - PopMatrix -""" -) - - -class MagicBehavior: - - magic_speed = NumericProperty(1) - """ - Animation playback speed. - - :attr:`magic_speed` is a :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - def grow(self): - """Grow effect animation.""" - - ( - Animation( - scale_x=1.2, - scale_y=1.2, - t="out_quad", - d=0.03 / self.magic_speed, - ) - + Animation( - scale_x=1, scale_y=1, t="out_elastic", d=0.4 / self.magic_speed - ) - ).start(self) - - def shake(self): - """Shake effect animation.""" - - ( - Animation(translate_x=50, t="out_quad", d=0.02 / self.magic_speed) - + Animation( - translate_x=0, t="out_elastic", d=0.5 / self.magic_speed - ) - ).start(self) - - def wobble(self): - """Wobble effect animation.""" - - ( - ( - Animation(scale_y=0.7, t="out_quad", d=0.03 / self.magic_speed) - & Animation( - scale_x=1.4, t="out_quad", d=0.03 / self.magic_speed - ) - ) - + ( - Animation(scale_y=1, t="out_elastic", d=0.5 / self.magic_speed) - & Animation( - scale_x=1, t="out_elastic", d=0.4 / self.magic_speed - ) - ) - ).start(self) - - def twist(self): - """Twist effect animation.""" - - ( - Animation(rotate=25, t="out_quad", d=0.05 / self.magic_speed) - + Animation(rotate=0, t="out_elastic", d=0.5 / self.magic_speed) - ).start(self) - - def shrink(self): - """Shrink effect animation.""" - - Animation( - scale_x=0.95, scale_y=0.95, t="out_quad", d=0.1 / self.magic_speed - ).start(self) - - def on_touch_up(self, *args): - Animation.stop_all(self) - return super().on_touch_up(*args) diff --git a/kivymd/uix/behaviors/ripple_behavior.py b/kivymd/uix/behaviors/ripple_behavior.py deleted file mode 100644 index c782277..0000000 --- a/kivymd/uix/behaviors/ripple_behavior.py +++ /dev/null @@ -1,414 +0,0 @@ -""" -Behaviors/Ripple -================ - -.. rubric:: Classes implements a circular and rectangular ripple effects. - -To create a widget with сircular ripple effect, you must create a new class -that inherits from the :class:`~CircularRippleBehavior` class. - -For example, let's create an image button with a circular ripple effect: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.behaviors import ButtonBehavior - from kivy.uix.image import Image - - from kivymd.app import MDApp - from kivymd.uix.behaviors import CircularRippleBehavior - - KV = ''' - #:import images_path kivymd.images_path - - - Screen: - - CircularRippleButton: - source: f"{images_path}/kivymd.png" - size_hint: None, None - size: "250dp", "250dp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class CircularRippleButton(CircularRippleBehavior, ButtonBehavior, Image): - def __init__(self, **kwargs): - self.ripple_scale = 0.85 - super().__init__(**kwargs) - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-ripple-effect.gif - :align: center - -To create a widget with rectangular ripple effect, you must create a new class -that inherits from the :class:`~RectangularRippleBehavior` class: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.behaviors import ButtonBehavior - - from kivymd.app import MDApp - from kivymd.uix.behaviors import RectangularRippleBehavior, BackgroundColorBehavior - - KV = ''' - Screen: - - RectangularRippleButton: - size_hint: None, None - size: "250dp", "50dp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class RectangularRippleButton( - RectangularRippleBehavior, ButtonBehavior, BackgroundColorBehavior - ): - md_bg_color = [0, 0, 1, 1] - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-ripple-effect.gif - :align: center -""" - -__all__ = ( - "CommonRipple", - "RectangularRippleBehavior", - "CircularRippleBehavior", -) - -from kivy.animation import Animation -from kivy.graphics import ( - Color, - Ellipse, - StencilPop, - StencilPush, - StencilUnUse, - StencilUse, -) -from kivy.graphics.vertex_instructions import RoundedRectangle -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - StringProperty, -) - - -class CommonRipple(object): - """Base class for ripple effect.""" - - ripple_rad_default = NumericProperty(1) - """ - Default value of the ripple effect radius. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-rad-default.gif - :align: center - - :attr:`ripple_rad_default` is an :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - ripple_color = ColorProperty(None) - """ - Ripple color in ``rgba`` format. - - :attr:`ripple_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - ripple_alpha = NumericProperty(0.5) - """ - Alpha channel values for ripple effect. - - :attr:`ripple_alpha` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.5`. - """ - - ripple_scale = NumericProperty(None) - """ - Ripple effect scale. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-scale-1.gif - :align: center - - :attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty` - and defaults to `None`. - """ - - ripple_duration_in_fast = NumericProperty(0.3) - """ - Ripple duration when touching to widget. - - :attr:`ripple_duration_in_fast` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.3`. - """ - - ripple_duration_in_slow = NumericProperty(2) - """ - Ripple duration when long touching to widget. - - :attr:`ripple_duration_in_slow` is an :class:`~kivy.properties.NumericProperty` - and defaults to `2`. - """ - - ripple_duration_out = NumericProperty(0.3) - """ - The duration of the disappearance of the wave effect. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-duration-out.gif - :align: center - - :attr:`ripple_duration_out` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.3`. - """ - - ripple_func_in = StringProperty("out_quad") - """ - Type of animation for ripple in effect. - - :attr:`ripple_func_in` is an :class:`~kivy.properties.StringProperty` - and defaults to `'out_quad'`. - """ - - ripple_func_out = StringProperty("out_quad") - """ - Type of animation for ripple out effect. - - :attr:`ripple_func_out` is an :class:`~kivy.properties.StringProperty` - and defaults to `'ripple_func_out'`. - """ - - _ripple_rad = NumericProperty() - _doing_ripple = BooleanProperty(False) - _finishing_ripple = BooleanProperty(False) - _fading_out = BooleanProperty(False) - _no_ripple_effect = BooleanProperty(False) - _round_rad = ListProperty([0, 0, 0, 0]) - - def lay_canvas_instructions(self): - raise NotImplementedError - - def start_ripple(self): - if not self._doing_ripple: - self._doing_ripple = True - anim = Animation( - _ripple_rad=self.finish_rad, - t="linear", - duration=self.ripple_duration_in_slow, - ) - anim.bind(on_complete=self.fade_out) - anim.start(self) - - def finish_ripple(self): - if self._doing_ripple and not self._finishing_ripple: - self._finishing_ripple = True - self._doing_ripple = False - Animation.cancel_all(self, "_ripple_rad") - anim = Animation( - _ripple_rad=self.finish_rad, - t=self.ripple_func_in, - duration=self.ripple_duration_in_fast, - ) - anim.bind(on_complete=self.fade_out) - anim.start(self) - - def fade_out(self, *args): - rc = self.ripple_color - if not self._fading_out: - self._fading_out = True - Animation.cancel_all(self, "ripple_color") - anim = Animation( - ripple_color=[rc[0], rc[1], rc[2], 0.0], - t=self.ripple_func_out, - duration=self.ripple_duration_out, - ) - anim.bind(on_complete=self.anim_complete) - anim.start(self) - - def anim_complete(self, *args): - self._doing_ripple = False - self._finishing_ripple = False - self._fading_out = False - self.canvas.after.remove_group("circular_ripple_behavior") - self.canvas.after.remove_group("rectangular_ripple_behavior") - - def on_touch_down(self, touch): - super().on_touch_down(touch) - if touch.is_mouse_scrolling: - return False - if not self.collide_point(touch.x, touch.y): - return False - - if not self.disabled: - if self._doing_ripple: - Animation.cancel_all( - self, "_ripple_rad", "ripple_color", "rect_color" - ) - self.anim_complete() - self._ripple_rad = self.ripple_rad_default - self.ripple_pos = (touch.x, touch.y) - - if self.ripple_color: - pass - elif hasattr(self, "theme_cls"): - self.ripple_color = self.theme_cls.ripple_color - else: - # If no theme, set Gray 300. - self.ripple_color = [ - 0.8784313725490196, - 0.8784313725490196, - 0.8784313725490196, - self.ripple_alpha, - ] - self.ripple_color[3] = self.ripple_alpha - self.lay_canvas_instructions() - self.finish_rad = max(self.width, self.height) * self.ripple_scale - self.start_ripple() - - def on_touch_move(self, touch, *args): - if not self.collide_point(touch.x, touch.y): - if not self._finishing_ripple and self._doing_ripple: - self.finish_ripple() - return super().on_touch_move(touch, *args) - - def on_touch_up(self, touch): - if self.collide_point(touch.x, touch.y) and self._doing_ripple: - self.finish_ripple() - return super().on_touch_up(touch) - - def _set_ellipse(self, instance, value): - self.ellipse.size = (self._ripple_rad, self._ripple_rad) - - # Adjust ellipse pos here - - def _set_color(self, instance, value): - self.col_instruction.a = value[3] - - -class RectangularRippleBehavior(CommonRipple): - """Class implements a rectangular ripple effect.""" - - ripple_scale = NumericProperty(2.75) - """ - See :class:`~CommonRipple.ripple_scale`. - - :attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty` - and defaults to `2.75`. - """ - - def lay_canvas_instructions(self): - if self._no_ripple_effect: - return - with self.canvas.after: - if hasattr(self, "radius"): - self._round_rad = self.radius - StencilPush(group="rectangular_ripple_behavior") - RoundedRectangle( - pos=self.pos, - size=self.size, - radius=self._round_rad, - group="rectangular_ripple_behavior", - ) - StencilUse(group="rectangular_ripple_behavior") - self.col_instruction = Color( - rgba=self.ripple_color, group="rectangular_ripple_behavior" - ) - self.ellipse = Ellipse( - size=(self._ripple_rad, self._ripple_rad), - pos=( - self.ripple_pos[0] - self._ripple_rad / 2.0, - self.ripple_pos[1] - self._ripple_rad / 2.0, - ), - group="rectangular_ripple_behavior", - ) - StencilUnUse(group="rectangular_ripple_behavior") - RoundedRectangle( - pos=self.pos, - size=self.size, - radius=self._round_rad, - group="rectangular_ripple_behavior", - ) - StencilPop(group="rectangular_ripple_behavior") - self.bind(ripple_color=self._set_color, _ripple_rad=self._set_ellipse) - - def _set_ellipse(self, instance, value): - super()._set_ellipse(instance, value) - self.ellipse.pos = ( - self.ripple_pos[0] - self._ripple_rad / 2.0, - self.ripple_pos[1] - self._ripple_rad / 2.0, - ) - - -class CircularRippleBehavior(CommonRipple): - """Class implements a circular ripple effect.""" - - ripple_scale = NumericProperty(1) - """ - See :class:`~CommonRipple.ripple_scale`. - - :attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - def lay_canvas_instructions(self): - if self._no_ripple_effect: - return - with self.canvas.after: - StencilPush(group="circular_ripple_behavior") - self.stencil = Ellipse( - size=( - self.width * self.ripple_scale, - self.height * self.ripple_scale, - ), - pos=( - self.center_x - (self.width * self.ripple_scale) / 2, - self.center_y - (self.height * self.ripple_scale) / 2, - ), - group="circular_ripple_behavior", - ) - StencilUse(group="circular_ripple_behavior") - self.col_instruction = Color(rgba=self.ripple_color) - self.ellipse = Ellipse( - size=(self._ripple_rad, self._ripple_rad), - pos=( - self.center_x - self._ripple_rad / 2.0, - self.center_y - self._ripple_rad / 2.0, - ), - group="circular_ripple_behavior", - ) - StencilUnUse(group="circular_ripple_behavior") - Ellipse( - pos=self.pos, size=self.size, group="circular_ripple_behavior" - ) - StencilPop(group="circular_ripple_behavior") - self.bind( - ripple_color=self._set_color, _ripple_rad=self._set_ellipse - ) - - def _set_ellipse(self, instance, value): - super()._set_ellipse(instance, value) - if self.ellipse.size[0] > self.width * 0.6 and not self._fading_out: - self.fade_out() - self.ellipse.pos = ( - self.center_x - self._ripple_rad / 2.0, - self.center_y - self._ripple_rad / 2.0, - ) diff --git a/kivymd/uix/behaviors/toggle_behavior.py b/kivymd/uix/behaviors/toggle_behavior.py deleted file mode 100644 index 054849c..0000000 --- a/kivymd/uix/behaviors/toggle_behavior.py +++ /dev/null @@ -1,202 +0,0 @@ -""" -Behaviors/ToggleButton -====================== - -This behavior must always be inherited after the button's Widget class since it -works with the inherited properties of the button class. - -example: - -.. code-block:: python - - class MyToggleButtonWidget(MDFlatButton, MDToggleButton): - # [...] - pass - - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.behaviors.toggle_behavior import MDToggleButton - from kivymd.uix.button import MDRectangleFlatButton - - KV = ''' - Screen: - - MDBoxLayout: - adaptive_size: True - pos_hint: {"center_x": .5, "center_y": .5} - - MyToggleButton: - text: "Show ads" - group: "x" - - MyToggleButton: - text: "Do not show ads" - group: "x" - - MyToggleButton: - text: "Does not matter" - group: "x" - ''' - - - class MyToggleButton(MDRectangleFlatButton, MDToggleButton): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.background_down = self.theme_cls.primary_light - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif - :align: center - -.. code-block:: python - - class MyToggleButton(MDFillRoundFlatButton, MDToggleButton): - def __init__(self, **kwargs): - self.background_down = MDApp.get_running_app().theme_cls.primary_dark - super().__init__(**kwargs) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-2.gif - :align: center - -You can inherit the ``MyToggleButton`` class only from the following classes ----------------------------------------------------------------------------- - -- :class:`~kivymd.uix.button.MDRaisedButton` -- :class:`~kivymd.uix.button.MDFlatButton` -- :class:`~kivymd.uix.button.MDRectangleFlatButton` -- :class:`~kivymd.uix.button.MDRectangleFlatIconButton` -- :class:`~kivymd.uix.button.MDRoundFlatButton` -- :class:`~kivymd.uix.button.MDRoundFlatIconButton` -- :class:`~kivymd.uix.button.MDFillRoundFlatButton` -- :class:`~kivymd.uix.button.MDFillRoundFlatIconButton` -""" - -__all__ = ("MDToggleButton",) - -from kivy.properties import BooleanProperty, ColorProperty -from kivy.uix.behaviors import ToggleButtonBehavior - -from kivymd.uix.button import ( - MDFillRoundFlatButton, - MDFillRoundFlatIconButton, - MDFlatButton, - MDRaisedButton, - MDRectangleFlatButton, - MDRectangleFlatIconButton, - MDRoundFlatButton, - MDRoundFlatIconButton, -) - - -class MDToggleButton(ToggleButtonBehavior): - background_normal = ColorProperty(None) - """ - Color of the button in ``rgba`` format for the 'normal' state. - - :attr:`background_normal` is a :class:`~kivy.properties.ColorProperty` - and is defaults to `None`. - """ - - background_down = ColorProperty(None) - """ - Color of the button in ``rgba`` format for the 'down' state. - - :attr:`background_down` is a :class:`~kivy.properties.ColorProperty` - and is defaults to `None`. - """ - - font_color_normal = ColorProperty(None) - """ - Color of the font's button in ``rgba`` format for the 'normal' state. - - :attr:`font_color_normal` is a :class:`~kivy.properties.ColorProperty` - and is defaults to `None`. - """ - - font_color_down = ColorProperty([1, 1, 1, 1]) - """ - Color of the font's button in ``rgba`` format for the 'down' state. - - :attr:`font_color_down` is a :class:`~kivy.properties.ColorProperty` - and is defaults to `[1, 1, 1, 1]`. - """ - - __is_filled = BooleanProperty(False) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - classinfo = ( - MDRaisedButton, - MDFlatButton, - MDRectangleFlatButton, - MDRectangleFlatIconButton, - MDRoundFlatButton, - MDRoundFlatIconButton, - MDFillRoundFlatButton, - MDFillRoundFlatIconButton, - ) - # Do the object inherited from the "supported" buttons? - if not issubclass(self.__class__, classinfo): - raise ValueError( - f"Class {self.__class__} must be inherited from one of the classes in the list {classinfo}" - ) - if ( - not self.background_normal - ): # This means that if the value == [] or None will return True. - # If the object inherits from buttons with background: - if isinstance( - self, - ( - MDRaisedButton, - MDFillRoundFlatButton, - MDFillRoundFlatIconButton, - ), - ): - self.__is_filled = True - self.background_normal = self.theme_cls.primary_color - # If not the background_normal must be the same as the inherited one: - else: - self.background_normal = self.md_bg_color[:] - # If no background_down is setted: - if ( - not self.background_down - ): # This means that if the value == [] or None will return True. - self.background_down = ( - self.theme_cls.primary_dark - ) # get the primary_color dark from theme_cls - if not self.font_color_normal: - self.font_color_normal = self.theme_cls.primary_color - # Alternative to bind the function to the property. - # self.bind(state=self._update_bg) - self.fbind("state", self._update_bg) - - def _update_bg(self, ins, val): - """Updates the color of the background.""" - - if val == "down": - self.md_bg_color = self.background_down - if ( - self.__is_filled is False - ): # If the background is transparent, and the button it toggled, - # the font color must be withe [1, 1, 1, 1]. - self.text_color = self.font_color_down - if self.group: - self._release_group(self) - else: - self.md_bg_color = self.background_normal - if ( - self.__is_filled is False - ): # If the background is transparent, the font color must be the - # primary color. - self.text_color = self.font_color_normal diff --git a/kivymd/uix/behaviors/touch_behavior.py b/kivymd/uix/behaviors/touch_behavior.py deleted file mode 100644 index ce15987..0000000 --- a/kivymd/uix/behaviors/touch_behavior.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -Behaviors/Touch -=============== - -.. rubric:: Provides easy access to events. - -The following events are available: - -- on_long_touch -- on_double_tap -- on_triple_tap - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.behaviors import TouchBehavior - from kivymd.uix.button import MDRaisedButton - - KV = ''' - Screen: - - MyButton: - text: "PRESS ME" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class MyButton(MDRaisedButton, TouchBehavior): - def on_long_touch(self, *args): - print(" event") - - def on_double_tap(self, *args): - print(" event") - - def on_triple_tap(self, *args): - print(" event") - - - class MainApp(MDApp): - def build(self): - return Builder.load_string(KV) - - - MainApp().run() -""" - -__all__ = ("TouchBehavior",) - -from functools import partial - -from kivy.clock import Clock -from kivy.properties import NumericProperty - - -class TouchBehavior: - duration_long_touch = NumericProperty(0.4) - """ - Time for a long touch. - - :attr:`duration_long_touch` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.4`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.bind( - on_touch_down=self.create_clock, on_touch_up=self.delete_clock - ) - - def create_clock(self, widget, touch, *args): - if self.collide_point(touch.x, touch.y): - callback = partial(self.on_long_touch, touch) - Clock.schedule_once(callback, self.duration_long_touch) - touch.ud["event"] = callback - - def delete_clock(self, widget, touch, *args): - if self.collide_point(touch.x, touch.y): - try: - Clock.unschedule(touch.ud["event"]) - except KeyError: - pass - - if touch.is_double_tap: - self.on_double_tap(touch, *args) - if touch.is_triple_tap: - self.on_triple_tap(touch, *args) - - def on_long_touch(self, touch, *args): - """Called when the widget is pressed for a long time.""" - - def on_double_tap(self, touch, *args): - """Called by double clicking on the widget.""" - - def on_triple_tap(self, touch, *args): - """Called by triple clicking on the widget.""" diff --git a/kivymd/uix/bottomnavigation.py b/kivymd/uix/bottomnavigation.py deleted file mode 100644 index e09f527..0000000 --- a/kivymd/uix/bottomnavigation.py +++ /dev/null @@ -1,649 +0,0 @@ -""" -Components/Bottom Navigation -============================ - -.. seealso:: - - `Material Design spec, Bottom navigation `_ - -.. rubric:: Bottom navigation bars allow movement between primary destinations in an app: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.png - :align: center - -Usage ------ - -.. code-block:: kv - - >: - - MDBottomNavigation: - - MDBottomNavigationItem: - name: "screen 1" - - YourContent: - - MDBottomNavigationItem: - name: "screen 2" - - YourContent: - - MDBottomNavigationItem: - name: "screen 3" - - YourContent: - -For ease of understanding, this code works like this: - -.. code-block:: kv - - >: - - ScreenManager: - - Screen: - name: "screen 1" - - YourContent: - - Screen: - name: "screen 2" - - YourContent: - - Screen: - name: "screen 3" - - YourContent: - -Example -------- - -.. code-block:: python - - from kivymd.app import MDApp - from kivy.lang import Builder - - - class Test(MDApp): - - def build(self): - self.theme_cls.primary_palette = "Gray" - return Builder.load_string( - ''' - BoxLayout: - orientation:'vertical' - - MDToolbar: - title: 'Bottom navigation' - md_bg_color: .2, .2, .2, 1 - specific_text_color: 1, 1, 1, 1 - - MDBottomNavigation: - panel_color: .2, .2, .2, 1 - - MDBottomNavigationItem: - name: 'screen 1' - text: 'Python' - icon: 'language-python' - - MDLabel: - text: 'Python' - halign: 'center' - - MDBottomNavigationItem: - name: 'screen 2' - text: 'C++' - icon: 'language-cpp' - - MDLabel: - text: 'I programming of C++' - halign: 'center' - - MDBottomNavigationItem: - name: 'screen 3' - text: 'JS' - icon: 'language-javascript' - - MDLabel: - text: 'JS' - halign: 'center' - ''' - ) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif - :align: center - -.. rubric:: :class:`~MDBottomNavigationItem` provides the following events for use: - -.. code-block:: python - - __events__ = ( - "on_tab_touch_down", - "on_tab_touch_move", - "on_tab_touch_up", - "on_tab_press", - "on_tab_release", - ) - -.. seealso:: - - See :class:`~MDTab.__events__` - -.. code-block:: kv - - Root: - - MDBottomNavigation: - - MDBottomNavigationItem: - on_tab_touch_down: print("on_tab_touch_down") - on_tab_touch_move: print("on_tab_touch_move") - on_tab_touch_up: print("on_tab_touch_up") - on_tab_press: print("on_tab_press") - on_tab_release: print("on_tab_release") - - YourContent: - -How to automatically switch a tab? ----------------------------------- - -Use method :attr:`~MDBottomNavigation.switch_tab` which takes as argument -the name of the tab you want to switch to. - -How to change icon color? -------------------------- - -.. code-block:: kv - - MDBottomNavigation: - text_color_active: 1, 0, 1, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_active.png - -.. code-block:: kv - - MDBottomNavigation: - text_color_normal: 1, 0, 1, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_normal.png - -.. seealso:: - - `See Tab auto switch example `_ - - `See full example `_ -""" - -__all__ = ( - "TabbedPanelBase", - "MDBottomNavigationItem", - "MDBottomNavigation", - "MDTab", -) - -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 sp -from kivy.properties import ( - BooleanProperty, - ListProperty, - NumericProperty, - ObjectProperty, - StringProperty, -) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.screenmanager import Screen, ScreenManagerException - -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import FakeRectangularElevationBehavior -from kivymd.uix.behaviors.backgroundcolor_behavior import ( - BackgroundColorBehavior, - SpecificBackgroundColorBehavior, -) - -Builder.load_string( - """ -#:import sm kivy.uix.screenmanager -#:import Window kivy.core.window.Window - - - - id: panel - orientation: "vertical" - height: dp(56) # Spec - - ScreenManager: - id: tab_manager - transition: sm.FadeTransition(duration=.2) - current: root.current - screens: root.tabs - - MDBottomNavigationBar: - id: bottom_panel - size_hint_y: None - height: dp(56) - md_bg_color: root.theme_cls.bg_dark if not root.panel_color else root.panel_color - - BoxLayout: - id: tab_bar - pos_hint: {"center_x": .5, "center_y": .5} - height: dp(56) - size_hint: None, None - - - - canvas: - Color: - rgba: root.panel_color - Rectangle: - size: self.size - pos: self.pos - - width: - root.panel.width / len(root.panel.ids.tab_manager.screens) \ - if len(root.panel.ids.tab_manager.screens) != 0 else root.panel.width - padding: (dp(12), dp(12)) - on_press: self.tab.dispatch("on_tab_press") - on_release: self.tab.dispatch("on_tab_release") - on_touch_down: self.tab.dispatch("on_tab_touch_down", *args) - on_touch_move: self.tab.dispatch("on_tab_touch_move", *args) - on_touch_up: self.tab.dispatch("on_tab_touch_up", *args) - - RelativeLayout: - id: item_container - - MDIcon: - id: _label_icon - icon: root.tab.icon - size_hint_x: None - text_size: (None, root.height) - height: self.texture_size[1] - theme_text_color: "Custom" - text_color: root._text_color_normal - opposite_colors: root.opposite_colors - pos: [self.pos[0], self.pos[1]] - font_size: dp(24) - pos_hint: {"center_x": .5} - y: item_container.height - dp(8) - - - MDLabel: - id: _label - text: root.tab.text - font_style: "Button" - size_hint_x: None - text_size: None, root.height - adaptive_height: True - theme_text_color: "Custom" - text_color: root._text_color_normal - opposite_colors: root.opposite_colors - font_size: root._label_font_size - pos_hint: {"center_x": .5} - y: -dp(8) - - - - canvas: - Color: - rgba: root.theme_cls.bg_normal - Rectangle: - size: root.size -""" -) - - -class MDBottomNavigationHeader(ThemableBehavior, ButtonBehavior, AnchorLayout): - opposite_colors = BooleanProperty(True) - - panel_color = ListProperty([1, 1, 1, 0]) - """Panel color of bottom navigation. - - :attr:`panel_color` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1, 0]`. - """ - - tab = ObjectProperty() - """ - :attr:`tab` is an :class:`~MDBottomNavigationItem` - and defaults to `None`. - """ - - panel = ObjectProperty() - """ - :attr:`panel` is an :class:`~MDBottomNavigation` - and defaults to `None`. - """ - - active = BooleanProperty(False) - - text = StringProperty() - """ - :attr:`text` is an :class:`~MDTab.text` - and defaults to `''`. - """ - - text_color_normal = ListProperty([1, 1, 1, 1]) - """ - Text color of the label when it is not selected. - - :attr:`text_color_normal` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - text_color_active = ListProperty([1, 1, 1, 1]) - """ - Text color of the label when it is selected. - - :attr:`text_color_active` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - _label = ObjectProperty() - _label_font_size = NumericProperty("12sp") - _text_color_normal = ListProperty([1, 1, 1, 1]) - _text_color_active = ListProperty([1, 1, 1, 1]) - - def __init__(self, panel, height, tab): - self.panel = panel - self.height = height - self.tab = tab - super().__init__() - self._text_color_normal = ( - self.theme_cls.disabled_hint_text_color - if self.text_color_normal == [1, 1, 1, 1] - else self.text_color_normal - ) - self._label = self.ids._label - self._label_font_size = sp(12) - self.theme_cls.bind(disabled_hint_text_color=self._update_theme_style) - self.active = False - - def on_press(self): - Animation(_label_font_size=sp(14), d=0.1).start(self) - Animation( - _text_color_normal=self.theme_cls.primary_color - if self.text_color_active == [1, 1, 1, 1] - else self.text_color_active, - d=0.1, - ).start(self) - - def _update_theme_style(self, instance, color): - """Called when the application theme style changes (White/Black).""" - - if not self.active: - self._text_color_normal = ( - color - if self.text_color_normal == [1, 1, 1, 1] - else self.text_color_normal - ) - - -class MDTab(Screen, ThemableBehavior): - """A tab is simply a screen with meta information - that defines the content that goes in the tab header. - """ - - __events__ = ( - "on_tab_touch_down", - "on_tab_touch_move", - "on_tab_touch_up", - "on_tab_press", - "on_tab_release", - ) - """Events provided.""" - - text = StringProperty() - """Tab header text. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon = StringProperty("checkbox-blank-circle") - """Tab header icon. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank-circle'`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.index = 0 - self.parent_widget = None - self.register_event_type("on_tab_touch_down") - self.register_event_type("on_tab_touch_move") - self.register_event_type("on_tab_touch_up") - self.register_event_type("on_tab_press") - self.register_event_type("on_tab_release") - - def on_tab_touch_down(self, *args): - pass - - def on_tab_touch_move(self, *args): - pass - - def on_tab_touch_up(self, *args): - pass - - def on_tab_press(self, *args): - par = self.parent_widget - if par.previous_tab is not self: - if par.previous_tab.index > self.index: - par.ids.tab_manager.transition.direction = "right" - elif par.previous_tab.index < self.index: - par.ids.tab_manager.transition.direction = "left" - par.ids.tab_manager.current = self.name - par.previous_tab = self - - def on_tab_release(self, *args): - pass - - def __repr__(self): - return f"" - - -class MDBottomNavigationItem(MDTab): - header = ObjectProperty() - """ - :attr:`header` is an :class:`~MDBottomNavigationHeader` - and defaults to `None`. - """ - - def on_tab_press(self, *args): - par = self.parent_widget - par.ids.tab_manager.current = self.name - if par.previous_tab is not self: - Animation(_label_font_size=sp(12), d=0.1).start( - par.previous_tab.header - ) - Animation( - _text_color_normal=par.previous_tab.header.text_color_normal - if par.previous_tab.header.text_color_normal != [1, 1, 1, 1] - else self.theme_cls.disabled_hint_text_color, - d=0.1, - ).start(par.previous_tab.header) - par.previous_tab.header.active = False - self.header.active = True - par.previous_tab = self - - def on_leave(self, *args): - pass - - -class TabbedPanelBase( - ThemableBehavior, SpecificBackgroundColorBehavior, BoxLayout -): - """A class that contains all variables a :class:`~kivy.properties.TabPannel` - must have. It is here so I (zingballyhoo) don't get mad about - the :class:`~kivy.properties.TabbedPannels` not being DRY. - - """ - - current = StringProperty(None) - """Current tab name. - - :attr:`current` is an :class:`~kivy.properties.StringProperty` - and defaults to `None`. - """ - - previous_tab = ObjectProperty() - """ - :attr:`previous_tab` is an :class:`~MDTab` and defaults to `None`. - """ - - panel_color = ListProperty() - """Panel color of bottom navigation. - - :attr:`panel_color` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - tabs = ListProperty() - - -class MDBottomNavigation(TabbedPanelBase): - """A bottom navigation that is implemented by delegating - all items to a ScreenManager.""" - - first_widget = ObjectProperty() - """ - :attr:`first_widget` is an :class:`~MDBottomNavigationItem` - and defaults to `None`. - """ - - tab_header = ObjectProperty() - """ - :attr:`tab_header` is an :class:`~MDBottomNavigationHeader` - and defaults to `None`. - """ - - text_color_normal = ListProperty([1, 1, 1, 1]) - """ - Text color of the label when it is not selected. - - :attr:`text_color_normal` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - text_color_active = ListProperty([1, 1, 1, 1]) - """ - Text color of the label when it is selected. - - :attr:`text_color_active` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.previous_tab = None - self.widget_index = 0 - Window.bind(on_resize=self.on_resize) - Clock.schedule_once(lambda x: self.on_resize(), 0) - - def on_panel_color(self, instance, value): - self.tab_header.panel_color = value - - def on_text_color_normal(self, instance, value): - for tab in self.ids.tab_bar.children: - if not tab.active: - tab._text_color_normal = value - - def on_text_color_active(self, instance, value): - for tab in self.ids.tab_bar.children: - tab.text_color_active = value - if tab.active: - tab._text_color_normal = value - - def switch_tab(self, name_tab): - """Switching the tab by name.""" - - if not self.ids.tab_manager.has_screen(name_tab): - raise ScreenManagerException(f"No Screen with name '{name_tab}'.") - self.ids.tab_manager.get_screen(name_tab).dispatch("on_tab_press") - count_index_screen = [ - self.ids.tab_manager.screens.index(screen) - for screen in self.ids.tab_manager.screens - if screen.name == name_tab - ][0] - numbers_screens = list(range(len(self.ids.tab_manager.screens))) - numbers_screens.reverse() - self.ids.tab_bar.children[ - numbers_screens.index(count_index_screen) - ].dispatch("on_press") - - def refresh_tabs(self): - """Refresh all tabs.""" - - if not self.ids: - return - tab_bar = self.ids.tab_bar - tab_bar.clear_widgets() - tab_manager = self.ids.tab_manager - for tab in tab_manager.screens: - self.tab_header = MDBottomNavigationHeader( - tab=tab, panel=self, height=tab_bar.height - ) - tab.header = self.tab_header - tab_bar.add_widget(self.tab_header) - if tab is self.first_widget: - self.tab_header._text_color_normal = ( - self.theme_cls.primary_color - ) - self.tab_header._label_font_size = sp(14) - self.tab_header.active = True - else: - self.tab_header._label_font_size = sp(12) - - def on_size(self, *args): - self.on_resize() - - def on_resize(self, instance=None, width=None, do_again=True): - """Called when the application window is resized.""" - - full_width = 0 - for tab in self.ids.tab_manager.screens: - full_width += tab.header.width - tab.header.text_color_normal = self.text_color_normal - self.ids.tab_bar.width = full_width - if do_again: - Clock.schedule_once(lambda x: self.on_resize(do_again=False), 0.1) - - def add_widget(self, widget, **kwargs): - if isinstance(widget, MDBottomNavigationItem): - self.widget_index += 1 - widget.index = self.widget_index - widget.parent_widget = self - self.ids.tab_manager.add_widget(widget) - if self.widget_index == 1: - self.previous_tab = widget - self.first_widget = widget - self.refresh_tabs() - else: - super().add_widget(widget) - - def remove_widget(self, widget): - if isinstance(widget, MDBottomNavigationItem): - self.ids.tab_manager.remove_widget(widget) - self.refresh_tabs() - else: - super().remove_widget(widget) - - -class MDBottomNavigationBar( - ThemableBehavior, - BackgroundColorBehavior, - FakeRectangularElevationBehavior, - FloatLayout, -): - pass diff --git a/kivymd/uix/bottomsheet.py b/kivymd/uix/bottomsheet.py deleted file mode 100644 index b3e8c78..0000000 --- a/kivymd/uix/bottomsheet.py +++ /dev/null @@ -1,589 +0,0 @@ -""" -Components/Bottom Sheet -======================= - -.. seealso:: - - `Material Design spec, Sheets: bottom `_ - -.. rubric:: Bottom sheets are surfaces containing supplementary content that are anchored to the bottom of the screen. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet.png - :align: center - -Two classes are available to you :class:`~MDListBottomSheet` and :class:`~MDGridBottomSheet` -for standard bottom sheets dialogs: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/grid-list-bottomsheets.png - :align: center - -Usage :class:`~MDListBottomSheet` -================================= - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.toast import toast - from kivymd.uix.bottomsheet import MDListBottomSheet - from kivymd.app import MDApp - - KV = ''' - Screen: - - MDToolbar: - title: "Example BottomSheet" - pos_hint: {"top": 1} - elevation: 10 - - MDRaisedButton: - text: "Open list bottom sheet" - on_release: app.show_example_list_bottom_sheet() - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - def callback_for_menu_items(self, *args): - toast(args[0]) - - def show_example_list_bottom_sheet(self): - bottom_sheet_menu = MDListBottomSheet() - for i in range(1, 11): - bottom_sheet_menu.add_item( - f"Standart Item {i}", - lambda x, y=i: self.callback_for_menu_items( - f"Standart Item {y}" - ), - ) - bottom_sheet_menu.open() - - - Example().run() - -The :attr:`~MDListBottomSheet.add_item` method of the :class:`~MDListBottomSheet` -class takes the following arguments: - -``text`` - element text; - -``callback`` - function that will be called when clicking on an item; - -There is also an optional argument ``icon``, -which will be used as an icon to the left of the item: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-list-bottomsheets.png - :align: center - -.. rubric:: Using the :class:`~MDGridBottomSheet` class is similar - to using the :class:`~MDListBottomSheet` class: - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.toast import toast - from kivymd.uix.bottomsheet import MDGridBottomSheet - from kivymd.app import MDApp - - KV = ''' - Screen: - - MDToolbar: - title: 'Example BottomSheet' - pos_hint: {"top": 1} - elevation: 10 - - MDRaisedButton: - text: "Open grid bottom sheet" - on_release: app.show_example_grid_bottom_sheet() - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - def callback_for_menu_items(self, *args): - toast(args[0]) - - def show_example_grid_bottom_sheet(self): - bottom_sheet_menu = MDGridBottomSheet() - data = { - "Facebook": "facebook-box", - "YouTube": "youtube", - "Twitter": "twitter-box", - "Da Cloud": "cloud-upload", - "Camera": "camera", - } - for item in data.items(): - bottom_sheet_menu.add_item( - item[0], - lambda x, y=item[0]: self.callback_for_menu_items(y), - icon_src=item[1], - ) - bottom_sheet_menu.open() - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/grid-bottomsheet.png - :align: center - -.. rubric:: You can use custom content for bottom sheet dialogs: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.factory import Factory - - from kivymd.uix.bottomsheet import MDCustomBottomSheet - from kivymd.app import MDApp - - KV = ''' - - on_press: app.custom_sheet.dismiss() - icon: "" - - IconLeftWidget: - icon: root.icon - - - : - orientation: "vertical" - size_hint_y: None - height: "400dp" - - MDToolbar: - title: 'Custom bottom sheet:' - - ScrollView: - - MDGridLayout: - cols: 1 - adaptive_height: True - - ItemForCustomBottomSheet: - icon: "page-previous" - text: "Preview" - - ItemForCustomBottomSheet: - icon: "exit-to-app" - text: "Exit" - - - Screen: - - MDToolbar: - title: 'Example BottomSheet' - pos_hint: {"top": 1} - elevation: 10 - - MDRaisedButton: - text: "Open custom bottom sheet" - on_release: app.show_example_custom_bottom_sheet() - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Example(MDApp): - custom_sheet = None - - def build(self): - return Builder.load_string(KV) - - def show_example_custom_bottom_sheet(self): - self.custom_sheet = MDCustomBottomSheet(screen=Factory.ContentCustomSheet()) - self.custom_sheet.open() - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-bottomsheet.png - :align: center - -.. note:: When you use the :attr:`~MDCustomBottomSheet` class, you must specify - the height of the user-defined content exactly, otherwise ``dp(100)`` - heights will be used for your ``ContentCustomSheet`` class: - -.. code-block:: kv - - : - orientation: "vertical" - size_hint_y: None - height: "400dp" - -.. note:: The height of the bottom sheet dialog will never exceed half - the height of the screen! -""" - -__all__ = ( - "MDGridBottomSheet", - "GridBottomSheetItem", - "MDListBottomSheet", - "MDCustomBottomSheet", - "MDBottomSheet", -) - -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 ( - BooleanProperty, - ColorProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.gridlayout import GridLayout -from kivy.uix.modalview import ModalView -from kivy.uix.scrollview import ScrollView - -from kivymd import images_path -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import BackgroundColorBehavior -from kivymd.uix.label import MDIcon -from kivymd.uix.list import ILeftBody, OneLineIconListItem, OneLineListItem - -Builder.load_string( - """ -#:import Window kivy.core.window.Window - - -: - - MDGridLayout: - id: box_sheet_list - cols: 1 - adaptive_height: True - padding: 0, 0, 0, "96dp" - - - - md_bg_color: root.value_transparent - _upper_padding: _upper_padding - _gl_content: _gl_content - _position_content: Window.height - - MDBoxLayout: - orientation: "vertical" - padding: 0, 1, 0, 0 - - BsPadding: - id: _upper_padding - size_hint_y: None - height: root.height - min(root.width * 9 / 16, root._gl_content.height) - on_release: root.dismiss() - - BottomSheetContent: - id: _gl_content - size_hint_y: None - cols: 1 - md_bg_color: 0, 0, 0, 0 - - canvas: - Color: - rgba: root.theme_cls.bg_normal if not root.bg_color else root.bg_color - RoundedRectangle: - pos: self.pos - size: self.size - radius: - [ - (root.radius, root.radius) if root.radius_from == "top_left" or root.radius_from == "top" else (0, 0), - (root.radius, root.radius) if root.radius_from == "top_right" or root.radius_from == "top" else (0, 0), - (root.radius, root.radius) if root.radius_from == "bottom_right" or root.radius_from == "bottom" else (0, 0), - (root.radius, root.radius) if root.radius_from == "bottom_left" or root.radius_from == "bottom" else (0, 0) - ] -""" -) - - -class SheetList(ScrollView): - pass - - -class BsPadding(ButtonBehavior, FloatLayout): - pass - - -class BottomSheetContent(BackgroundColorBehavior, GridLayout): - pass - - -class MDBottomSheet(ThemableBehavior, ModalView): - background = f"{images_path}transparent.png" - """Private attribute.""" - - duration_opening = NumericProperty(0.15) - """ - The duration of the bottom sheet dialog opening animation. - - :attr:`duration_opening` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.15`. - """ - - duration_closing = NumericProperty(0.15) - """ - The duration of the bottom sheet dialog closing animation. - - :attr:`duration_closing` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.15`. - """ - - radius = NumericProperty(25) - """ - The value of the rounding of the corners of the dialog. - - :attr:`radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `25`. - """ - - radius_from = OptionProperty( - None, - options=[ - "top_left", - "top_right", - "top", - "bottom_right", - "bottom_left", - "bottom", - ], - allownone=True, - ) - """ - Sets which corners to cut from the dialog. Available options are: - (`"top_left"`, `"top_right"`, `"top"`, `"bottom_right"`, `"bottom_left"`, `"bottom"`). - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-radius-from.png - :align: center - - :attr:`radius_from` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None`. - """ - - animation = BooleanProperty(False) - """ - Whether to use animation for opening and closing of the bottomsheet or not. - - :attr:`animation` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - bg_color = ColorProperty(None) - """ - Dialog background color in ``rgba`` format. - - :attr:`bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. - """ - - value_transparent = ColorProperty([0, 0, 0, 0.8]) - """ - Background transparency value when opening a dialog. - - :attr:`value_transparent` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.8]`. - """ - - _upper_padding = ObjectProperty() - _gl_content = ObjectProperty() - _position_content = NumericProperty() - - def open(self, *args): - super().open(*args) - - def add_widget(self, widget, index=0, canvas=None): - super().add_widget(widget, index, canvas) - - def dismiss(self, *args, **kwargs): - def dismiss(*args): - self.dispatch("on_pre_dismiss") - self._gl_content.clear_widgets() - self._real_remove_widget() - self.dispatch("on_dismiss") - - if self.animation: - a = Animation(height=0, d=self.duration_closing) - a.bind(on_complete=dismiss) - a.start(self._gl_content) - else: - dismiss() - - def resize_content_layout(self, content, layout, interval=0): - if not layout.ids.get("box_sheet_list"): - _layout = layout - else: - _layout = layout.ids.box_sheet_list - - if _layout.height > Window.height / 2: - height = Window.height / 2 - else: - height = _layout.height - - if self.animation: - Animation(height=height, d=self.duration_opening).start(_layout) - Animation(height=height, d=self.duration_opening).start(content) - else: - layout.height = height - content.height = height - - -Builder.load_string( - """ - - halign: "center" - theme_text_color: "Primary" - valign: "middle" -""" -) - - -class ListBottomSheetIconLeft(ILeftBody, MDIcon): - pass - - -class MDCustomBottomSheet(MDBottomSheet): - screen = ObjectProperty() - """ - Custom content. - - :attr:`screen` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._gl_content.add_widget(self.screen) - Clock.schedule_once( - lambda x: self.resize_content_layout(self._gl_content, self.screen), - 0, - ) - - -class MDListBottomSheet(MDBottomSheet): - sheet_list = ObjectProperty() - """ - :attr:`sheet_list` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.sheet_list = SheetList(size_hint_y=None) - self._gl_content.add_widget(self.sheet_list) - Clock.schedule_once( - lambda x: self.resize_content_layout( - self._gl_content, self.sheet_list - ), - 0, - ) - - def add_item(self, text, callback, icon=None): - """ - :arg text: element text; - :arg callback: function that will be called when clicking on an item; - :arg icon: which will be used as an icon to the left of the item; - """ - - if icon: - item = OneLineIconListItem(text=text, on_release=callback) - item.add_widget(ListBottomSheetIconLeft(icon=icon)) - else: - item = OneLineListItem(text=text, on_release=callback) - item.bind(on_release=lambda x: self.dismiss()) - self.sheet_list.ids.box_sheet_list.add_widget(item) - - -Builder.load_string( - """ - - orientation: "vertical" - padding: 0, dp(24), 0, 0 - size_hint_y: None - size: dp(64), dp(96) - - AnchorLayout: - anchor_x: "center" - - MDIconButton: - icon: root.source - user_font_size: root.icon_size - on_release: root.dispatch("on_release") - - MDLabel: - font_style: "Caption" - theme_text_color: "Secondary" - text: root.caption - halign: "center" -""" -) - - -class GridBottomSheetItem(ButtonBehavior, BoxLayout): - source = StringProperty() - """ - Icon path if you use a local image or icon name - if you use icon names from a file ``kivymd/icon_definitions.py``. - - :attr:`source` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - caption = StringProperty() - """ - Item text. - - :attr:`caption` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon_size = NumericProperty("24sp") - """ - Icon size. - - :attr:`caption` is an :class:`~kivy.properties.StringProperty` - and defaults to `'24sp'`. - """ - - -class MDGridBottomSheet(MDBottomSheet): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - self.sheet_list = SheetList(size_hint_y=None) - self.sheet_list.ids.box_sheet_list.cols = 3 - self.sheet_list.ids.box_sheet_list.padding = (dp(16), 0, dp(16), dp(96)) - self._gl_content.add_widget(self.sheet_list) - Clock.schedule_once( - lambda x: self.resize_content_layout( - self._gl_content, self.sheet_list - ), - 0, - ) - - def add_item(self, text, callback, icon_src): - """ - :arg text: element text; - :arg callback: function that will be called when clicking on an item; - :arg icon_src: icon item; - """ - - def tap_on_item(instance): - callback(instance) - self.dismiss() - - item = GridBottomSheetItem( - caption=text, on_release=tap_on_item, source=icon_src - ) - if len(self._gl_content.children) % 3 == 0: - self._gl_content.height += dp(96) - self.sheet_list.ids.box_sheet_list.add_widget(item) diff --git a/kivymd/uix/boxlayout.py b/kivymd/uix/boxlayout.py deleted file mode 100644 index 2fe38da..0000000 --- a/kivymd/uix/boxlayout.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Components/Box Layout -===================== - -:class:`~kivy.uix.boxlayout.BoxLayout` class equivalent. Simplifies working -with some widget properties. For example: - -BoxLayout ---------- - -.. code-block:: - - BoxLayout: - size_hint_y: None - height: self.minimum_height - - canvas: - Color: - rgba: app.theme_cls.primary_color - Rectangle: - pos: self.pos - size: self.size - -MDBoxLayout ------------ - -.. code-block:: - - MDBoxLayout: - adaptive_height: True - md_bg_color: app.theme_cls.primary_color - -Available options are: ----------------------- - -- adaptive_height_ -- adaptive_width_ -- adaptive_size_ - -.. adaptive_height: -adaptive_height ---------------- - -.. code-block:: kv - - adaptive_height: True - -Equivalent - -.. code-block:: kv - - size_hint_y: None - height: self.minimum_height - -.. adaptive_width: -adaptive_width --------------- - -.. code-block:: kv - - adaptive_width: True - -Equivalent - -.. code-block:: kv - - size_hint_x: None - height: self.minimum_width - -.. adaptive_size: -adaptive_size -------------- - -.. code-block:: kv - - adaptive_size: True - -Equivalent - -.. code-block:: kv - - size_hint: None, None - size: self.minimum_size -""" - -from kivy.uix.boxlayout import BoxLayout - -from kivymd.uix import MDAdaptiveWidget - - -class MDBoxLayout(BoxLayout, MDAdaptiveWidget): - pass diff --git a/kivymd/uix/button.py b/kivymd/uix/button.py deleted file mode 100644 index 07f1165..0000000 --- a/kivymd/uix/button.py +++ /dev/null @@ -1,2071 +0,0 @@ -""" -Components/Button -================= - -.. seealso:: - - `Material Design spec, Buttons `_ - - `Material Design spec, Buttons: floating action button `_ - -.. rubric:: Buttons allow users to take actions, and make choices, - with a single tap. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/buttons.png - :align: center - -`KivyMD` provides the following button classes for use: - -- MDIconButton_ -- MDFloatingActionButton_ -- MDFlatButton_ -- MDRaisedButton_ -- MDRectangleFlatButton_ -- MDRectangleFlatIconButton_ -- MDRoundFlatButton_ -- MDRoundFlatIconButton_ -- MDFillRoundFlatButton_ -- MDFillRoundFlatIconButton_ -- MDTextButton_ -- MDFloatingActionButtonSpeedDial_ - -.. MDIconButton: -MDIconButton ------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.gif - :align: center - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDIconButton: - icon: "language-python" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -The :class:`~MDIconButton.icon` parameter must have the name of the icon -from ``kivymd/icon_definitions.py`` file. - -You can also use custom icons: - -.. code-block:: kv - - MDIconButton: - icon: "data/logo/kivy-icon-256.png" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.gif - :align: center - -By default, :class:`~MDIconButton` button has a size ``(dp(48), dp (48))``. -Use :class:`~BaseButton.user_font_size` attribute to resize the button: - -.. code-block:: kv - - MDIconButton: - icon: "android" - user_font_size: "64sp" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.gif - :align: center - -By default, the color of :class:`~MDIconButton` -(depending on the style of the application) is black or white. -You can change the color of :class:`~MDIconButton` as the text color -of :class:`~kivymd.uix.label.MDLabel`: - -.. code-block:: kv - - MDIconButton: - icon: "android" - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-theme-text-color.png - :align: center - -.. MDFloatingActionButton: -MDFloatingActionButton ----------------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button.png - :align: center - -The above parameters for :class:`~MDIconButton` apply -to :class:`~MDFloatingActionButton`. - -To change :class:`~MDFloatingActionButton` background, use the -``md_bg_color`` parameter: - -.. code-block:: kv - - MDFloatingActionButton: - icon: "android" - md_bg_color: app.theme_cls.primary_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-md-bg-color.png - :align: center - -The length of the shadow is controlled by the ``elevation_normal`` parameter: - -.. code-block:: kv - - MDFloatingActionButton: - icon: "android" - elevation_normal: 12 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-elevation-normal.png - :align: center - - -.. MDFlatButton: -MDFlatButton ------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button.gif - :align: center - -To change the text color of: class:`~MDFlatButton` use the ``text_color`` parameter: - -.. code-block:: kv - - MDFlatButton: - text: "MDFLATBUTTON" - theme_text_color: "Custom" - text_color: 0, 0, 1, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button-text-color.png - :align: center - -Or use markup: - -.. code-block:: kv - - MDFlatButton: - text: "[color=#00ffcc]MDFLATBUTTON[/color]" - -To specify the font size and font name, use the parameters as in the usual -`Kivy` buttons: - -.. code-block:: kv - - MDFlatButton: - text: "MDFLATBUTTON" - font_size: "18sp" - font_name: "path/to/font" - -.. MDRaisedButton: -MDRaisedButton --------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.gif - :align: center - -This button is similar to the :class:`~MDFlatButton` button except that you -can set the background color for :class:`~MDRaisedButton`: - -.. code-block:: kv - - MDRaisedButton: - text: "MDRAISEDBUTTON" - md_bg_color: 1, 0, 1, 1 - - -.. MDRectangleFlatButton: -MDRectangleFlatButton ---------------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button.gif - :align: center - -.. code-block:: kv - - MDRectangleFlatButton: - text: "MDRECTANGLEFLATBUTTON" - theme_text_color: "Custom" - text_color: 1, 0, 0, 1 - line_color: 0, 0, 1, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button-md-bg-color.png - :align: center - -.. MDRectangleFlatIconButton: -MDRectangleFlatIconButton -------------------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button.png - :align: center - -Button parameters :class:`~MDRectangleFlatButton` are the same as -button :class:`~MDRectangleFlatButton`: - -.. code-block:: kv - - MDRectangleFlatIconButton: - icon: "android" - text: "MDRECTANGLEFLATICONBUTTON" - theme_text_color: "Custom" - text_color: 0, 0, 1, 1 - line_color: 1, 0, 1, 1 - icon_color: 1, 0, 0, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-custom.png - :align: center - -Without border --------------- - -.. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatIconButton - - - class Example(MDApp): - def build(self): - screen = MDScreen() - screen.add_widget( - MDRectangleFlatIconButton( - text="MDRectangleFlatIconButton", - icon="language-python", - line_color=(0, 0, 0, 0), - pos_hint={"center_x": .5, "center_y": .5}, - ) - ) - return screen - - - Example().run() - -.. code-block:: kv - - MDRectangleFlatIconButton: - text: "MDRectangleFlatIconButton" - icon: "language-python" - line_color: 0, 0, 0, 0 - pos_hint: {"center_x": .5, "center_y": .5} - -.. MDRoundFlatButton: -MDRoundFlatButton ------------------ - -.. code-block:: kv - - MDRoundFlatButton: - text: "MDROUNDFLATBUTTON" - text_color: 0, 1, 0, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-button-text-color.png - :align: center - -.. MDRoundFlatIconButton: -MDRoundFlatIconButton ---------------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png - :align: center - -Button parameters :class:`~MDRoundFlatIconButton` are the same as -button :class:`~MDRoundFlatButton`: - -.. code-block:: kv - - MDRoundFlatIconButton: - icon: "android" - text: "MDROUNDFLATICONBUTTON" - -.. MDFillRoundFlatButton: -MDFillRoundFlatButton ---------------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-button.png - :align: center - -Button parameters :class:`~MDFillRoundFlatButton` are the same as -button :class:`~MDRaisedButton`. - -.. MDFillRoundFlatIconButton: -MDFillRoundFlatIconButton -------------------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-icon-button.png - :align: center - -Button parameters :class:`~MDFillRoundFlatIconButton` are the same as -button :class:`~MDRaisedButton`. - -.. note:: Notice that the width of the :class:`~MDFillRoundFlatIconButton` - button matches the size of the button text. - -.. MDTextButton: -MDTextButton ------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png - :align: center - -.. code-block:: kv - - MDTextButton: - text: "MDTEXTBUTTON" - custom_color: 0, 1, 0, 1 - -.. MDFloatingActionButtonSpeedDial: -MDFloatingActionButtonSpeedDial -------------------------------- - -.. Note:: See the full list of arguments in the class - :class:`~MDFloatingActionButtonSpeedDial`. - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDFloatingActionButtonSpeedDial: - data: app.data - root_button_anim: True - ''' - - - class Example(MDApp): - data = { - 'Python': 'language-python', - 'PHP': 'language-php', - 'C++': 'language-cpp', - } - - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial.gif - :align: center - -Or without KV Language: - -.. code-block:: python - - from kivymd.uix.screen import MDScreen - from kivymd.app import MDApp - from kivymd.uix.button import MDFloatingActionButtonSpeedDial - - - class Example(MDApp): - data = { - 'Python': 'language-python', - 'PHP': 'language-php', - 'C++': 'language-cpp', - } - - def build(self): - screen = MDScreen() - speed_dial = MDFloatingActionButtonSpeedDial() - speed_dial.data = self.data - speed_dial.root_button_anim = True - screen.add_widget(speed_dial) - return screen - - - Example().run() - -You can use various types of animation of labels for buttons on the stack: - -.. code-block:: kv - - MDFloatingActionButtonSpeedDial: - hint_animation: True - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint.gif - :align: center - -You can set your color values ​​for background, text of buttons etc: - -.. code-block:: kv - - MDFloatingActionButtonSpeedDial: - bg_hint_color: app.theme_cls.primary_light - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint-color.png - :align: center - -.. seealso:: - - `See full example `_ -""" - -__all__ = ( - "MDIconButton", - "MDFloatingActionButton", - "MDFlatButton", - "MDRaisedButton", - "MDRectangleFlatButton", - "MDRectangleFlatIconButton", - "MDRoundFlatButton", - "MDRoundFlatIconButton", - "MDFillRoundFlatButton", - "MDFillRoundFlatIconButton", - "MDTextButton", - "MDFloatingActionButtonSpeedDial", -) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.core.window import Window -from kivy.graphics.context_instructions import Color -from kivy.graphics.stencil_instructions import ( - StencilPop, - StencilPush, - StencilUnUse, - StencilUse, -) -from kivy.graphics.vertex_instructions import Ellipse, RoundedRectangle -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - BoundedNumericProperty, - ColorProperty, - DictProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.floatlayout import FloatLayout - -from kivymd.color_definitions import text_colors -from kivymd.font_definitions import theme_font_styles -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import ( - BackgroundColorBehavior, - CircularElevationBehavior, - CircularRippleBehavior, - CommonElevationBehavior, - FakeRectangularElevationBehavior, - RectangularRippleBehavior, -) -from kivymd.uix.label import MDLabel -from kivymd.uix.tooltip import MDTooltip - -Builder.load_string( - """ - - canvas: - Clear - Color: - rgba: - self.md_bg_color if not self.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - RoundedRectangle: - size: self.size - pos: self.pos - radius: [root._radius, ] - - lbl_txt: lbl_txt - size_hint: None, None - height: dp(20) + lbl_txt.texture_size[1] - width: lbl_txt.texture_size[0] + dp(24) - - MDLabel: - id: lbl_txt - text: root.text - font_size: root.font_size - font_style: root.font_style - adaptive_size: True - -text_size: None, None - theme_text_color: root.theme_text_color - text_color: root.text_color - markup: True - disabled: root.disabled - opposite_colors: root.opposite_colors - font_name: root.font_name if root.font_name else self.font_name - - - - canvas: - Clear - Color: - rgba: - (self.md_bg_color if root.icon in md_icons else (0, 0, 0, 0)) \ - if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - Ellipse: - size: self.size - pos: self.pos - source: self.source if hasattr(self, "source") else "" - - size: "48dp", "48dp" - lbl_txt: lbl_txt - padding: "12dp" if root.icon in md_icons else (0, 0, 0, 0) - - MDIcon: - id: lbl_txt - icon: root.icon - font_size: root.user_font_size if root.user_font_size else self.font_size - font_name: root.font_name if root.font_name else self.font_name - theme_text_color: root.theme_text_color - text_color: - root.text_color if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - disabled: root.disabled - valign: "middle" - halign: "center" - opposite_colors: root.opposite_colors - - - - canvas.before: - Clear - Color: - rgba: - (root.theme_cls.primary_color if not root.line_color else root.line_color) \ - if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - Line: - width: root.line_width - rounded_rectangle: - (self.x, self.y, self.width, self.height,\ - root._radius, root._radius, root._radius, root._radius,\ - self.height) - - theme_text_color: "Custom" - - - - canvas.before: - Clear - Color: - rgba: - (root.theme_cls.primary_color if root.md_bg_color == [0.0, 0.0, 0.0, 0.0] \ - else root.md_bg_color) if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - RoundedRectangle: - size: self.size - pos: self.pos - radius: [root._radius, ] - - line_width: 0.001 - theme_text_color: "Custom" - - - - canvas.before: - Clear - Color: - rgba: - (root.theme_cls.primary_color if root.md_bg_color == [0.0, 0.0, 0.0, 0.0] \ - else root.md_bg_color) if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - RoundedRectangle: - size: self.size - pos: self.pos - radius: [root._radius, ] - - line_width: 0.001 - theme_text_color: "Custom" - - - - canvas.before: - Clear - Color: - rgba: - (root.theme_cls.primary_color if not root.line_color else root.line_color) \ - if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - Line: - width: root.line_width - rectangle: (self.x, self.y, self.width, self.height) - - theme_text_color: "Custom" - - - - canvas.before: - Clear - Color: - rgba: - (root.theme_cls.primary_color if not root.line_color else root.line_color) \ - if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - Line: - width: root.line_width - rectangle: (self.x, self.y, self.width, self.height) - - size_hint_x: None - width: root.lbl_txt.width + lbl_ic.width * 2 + box.spacing - - MDBoxLayout: - id: box - size_hint: None, None - size: root.size - padding: "10dp", 0, 0, 0 - spacing: "8dp" - - MDIcon: - id: lbl_ic - icon: root.icon - theme_text_color: "Custom" - text_color: - (root.theme_cls.primary_color if not root.icon_color else root.icon_color) \ - if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - size_hint_x: None - width: self.texture_size[0] - - MDLabel: - id: lbl - text: root.text - font_size: root.font_size - font_style: root.font_style - adaptive_size: True - -text_size: None, None - theme_text_color: "Custom" - text_color: root.text_color - markup: True - disabled: root.disabled - opposite_colors: root.opposite_colors - font_name: root.font_name if root.font_name else self.font_name - pos_hint: {"center_y": .5} - - - - canvas.before: - Clear - Color: - rgba: - (root.theme_cls.primary_color if not root.line_color else root.line_color) \ - if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - Line: - width: root.line_width - rounded_rectangle: - (self.x, self.y, self.width, self.height,\ - root._radius, root._radius, root._radius, root._radius,\ - self.height) - - size_hint_x: None - width: root.lbl_txt.width + lbl_ic.width * 2 + box.spacing - - MDBoxLayout: - id: box - size_hint: None, None - size: root.size - padding: "10dp", 0, 0, 0 - spacing: "8dp" - - MDIcon: - id: lbl_ic - icon: root.icon - theme_text_color: "Custom" - text_color: - (root.theme_cls.primary_color if not root.icon_color else root.icon_color) \ - if not root.disabled else \ - (root.md_bg_color_disabled if root.md_bg_color_disabled \ - else root.theme_cls.disabled_hint_text_color) - size_hint_x: None - width: self.texture_size[0] - - MDLabel: - id: lbl - text: root.text - font_size: root.font_size - font_style: root.font_style - adaptive_size: True - -text_size: None, None - theme_text_color: root.theme_text_color - text_color: root.text_color - markup: True - disabled: root.disabled - opposite_colors: root.opposite_colors - font_name: root.font_name if root.font_name else self.font_name - pos_hint: {"center_y": .5} - - - - on_size: root.set_size(0) - - - - theme_text_color: "Custom" - # theme_text_color: root.theme_text_color - # opposite_colors: root.opposite_colors - on_size: root.set_size(0) - - - - adaptive_size: True - color: root.theme_cls.primary_color if not root.color else root.color - opacity: 1 - - - - theme_text_color: "Custom" - md_bg_color: self.theme_cls.primary_color - - canvas.before: - Color: - rgba: - self.theme_cls.primary_color \ - if not self._bg_color else self._bg_color - RoundedRectangle: - pos: - (self.x - self._canvas_width + dp(1.5)) + self._padding_right / 2, \ - self.y - self._padding_right / 2 + dp(1.5) - size: - self.width + self._canvas_width - dp(3), \ - self.height + self._padding_right - dp(3) - radius: [self.height / 2] - - - - theme_text_color: "Custom" - md_bg_color: self.theme_cls.primary_color - - canvas.before: - PushMatrix - Rotate: - angle: self._angle - axis: (0, 0, 1) - origin: self.center - canvas.after: - PopMatrix - - - - size_hint: None, None - padding: "8dp", "4dp", "8dp", "4dp" - height: label.texture_size[1] + self.padding[1] * 2 - width: label.texture_size[0] + self.padding[0] * 2 - elevation: 10 - - canvas: - Color: - rgba: - self.theme_cls.primary_color \ - if not root.bg_color else root.bg_color - RoundedRectangle: - pos: self.pos - size: self.size - radius: [5] - - Label: - id: label - markup: True - text: root.text - size_hint: None, None - size: self.texture_size - color: root.theme_cls.text_color if not root.text_color else root.text_color -""" -) - - -class BaseButton(ThemableBehavior, ButtonBehavior, AnchorLayout): - """Base class for all buttons.""" - - text = StringProperty(" ") - """ - Button text. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `' '`. - """ - - theme_text_color = OptionProperty( - "Primary", - options=[ - "Primary", - "Secondary", - "Hint", - "Error", - "Custom", - "ContrastParentBackground", - ], - ) - """ - Button text type. Available options are: (`"Primary"`, `"Secondary"`, - `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`). - - :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Primary'`. - """ - - text_color = ColorProperty(None) - """ - Button text color in (r, g, b, a) format. - - :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - font_name = StringProperty() - """ - Button text font name. - - :attr:`font_name` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - font_size = NumericProperty("14sp") - """ - Button text font size. - - :attr:`font_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `14sp`. - """ - - user_font_size = NumericProperty(0) - """ - Custom font size for :class:`~MDIconButton`. - - :attr:`user_font_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - font_style = OptionProperty("Body1", options=theme_font_styles) - """ - Button text font style. - - Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`, - `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`, - `'Caption'`, `'Overline'`, `'Icon'`. - - :attr:`font_style` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Body1'`. - """ - - md_bg_color = ColorProperty([1.0, 1.0, 1.0, 0]) - """ - Button background color. - - :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1.0, 1.0, 1.0, 0]`. - """ - - md_bg_color_disabled = ColorProperty(None) - """ - Disabled button text color. - - :attr:`md_bg_color_disabled` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _radius = NumericProperty(0) - _md_bg_color = ColorProperty(None) # last current button color - - def __init__(self, **kwargs): - super().__init__(**kwargs) - if not self.md_bg_color_disabled: - self.md_bg_color_disabled = self.theme_cls.disabled_hint_text_color - self.theme_cls.bind(primary_palette=self.update_md_bg_color) - self.theme_cls.bind(theme_style=self.update_text_color) - Clock.schedule_once(self.set_md_bg_color) - if not self.text_color: - self.text_color = self.theme_cls.text_color - - def update_text_color(self, instance, value): - pass - - def set_md_bg_color(self, interval): - """Checks if a value is set for the `md_bg_color` parameter.""" - - if self.md_bg_color == [1.0, 1.0, 1.0, 0]: - self.md_bg_color = self.theme_cls.primary_color - if not self.md_bg_color_disabled: - self.md_bg_color_disabled = self.theme_cls.disabled_hint_text_color - - def update_md_bg_color(self, instance, value): - """Called when the application color palette changes.""" - - self.md_bg_color = self.theme_cls._get_primary_color() - self.update_text_color(instance, value) - - def on_disabled(self, instance, value): - """ - Sets the color of the button at the moment of setting the parameter - `disabled`. - """ - - # Sets button background color (disabled). - if value: - if not self.md_bg_color_disabled: - self.md_bg_color_disabled = ( - self.theme_cls.disabled_hint_text_color - ) - self.md_bg_color = self.md_bg_color_disabled - # Sets last current button background color. - else: - self.md_bg_color = ( - self._md_bg_color - if self._md_bg_color - else self.theme_cls.primary_color - ) - - def on_md_bg_color(self, instance, value): - """Sets last current button background color.""" - - if self.md_bg_color != self.md_bg_color_disabled: - self._md_bg_color = value - - -class BasePressedButton(BaseButton): - """ - Base class for those button which fade to a background color on press. - """ - - animation_fade_bg = None - current_md_bg_color = ColorProperty(None) - - def on_touch_down(self, touch): - if touch.is_mouse_scrolling: - return False - elif not self.collide_point(touch.x, touch.y): - return False - elif self in touch.ud: - return False - elif self.disabled: - return False - else: - if self.md_bg_color == [0.0, 0.0, 0.0, 0.0]: - self.current_md_bg_color = self.md_bg_color - self.animation_fade_bg = Animation( - duration=0.5, md_bg_color=[0.0, 0.0, 0.0, 0.1] - ) - self.animation_fade_bg.start(self) - return super().on_touch_down(touch) - - def on_touch_up(self, touch): - if ( - self.collide_point(touch.x, touch.y) - and self.animation_fade_bg - and not self.disabled - ): - self.animation_fade_bg.stop_property(self, "md_bg_color") - Animation( - duration=0.05, md_bg_color=self.current_md_bg_color - ).start(self) - return super().on_touch_up(touch) - - -class BaseRectangularButton( - RectangularRippleBehavior, - FakeRectangularElevationBehavior, - BackgroundColorBehavior, - BasePressedButton, - BaseButton, -): - """Base class for all rectangular buttons.""" - - width = BoundedNumericProperty( - 88, min=88, max=None, errorhandler=lambda x: 88 - ) - - -class BaseFlatButton(BaseRectangularButton): - def update_md_bg_color(self, instance, value): - """Called when the application color palette changes.""" - - self.text_color = self.theme_cls.primary_color - - def set_text_color(self, interval): - """Sets the text color if no custom value is specified.""" - - if self.text_color in ([0.0, 0.0, 0.0, 0.87], [1, 0, 1, 1]): - self.theme_text_color = "Custom" - self.text_color = self.theme_cls.primary_color - - def on_md_bg_color(self, instance, value): - """ - We override this method, thus prohibiting setting the background color - for the button. - - Allows to set the background color only in the rangefrom - [0.0, 0.0, 0.0, 0.0] to [0.0, 0.0, 0.0, 0.1]. This color is set in - the :class:`BasePressedButton` class when the button is pressed and - Ignore other custom colors. - """ - - if value[:-1] != [0.0, 0.0, 0.0]: - self.md_bg_color = [0.0, 0.0, 0.0, 0.0] - - def on_disabled(self, instance, value): - if value and not self.disabled: - self.md_bg_color = ( - self.md_bg_color_disabled - if self.md_bg_color_disabled - else self.theme_cls.disabled_hint_text_color - ) - else: - self.md_bg_color = [0.0, 0.0, 0.0, 0.0] - - def on_elevation(self, instance, value): - """ - We are overriding this method to not allow set the `elevation` value - for this type of button. - """ - self.elevation = 0 - self._elevation = 0 - super().on_elevation(instance, value) - - -class BaseElevationButton(CommonElevationBehavior, BaseButton): - """ - Base class for raised buttons. - Implements elevation behavior as well as the recommended down/disabled - colors for raised buttons. - - The minimum elevation for any raised button is `"1dp"`, - by default, set to `"2dp"`. - - the _elevation_raised is automatically computed and is set to - self.elevation + 6 each time self.elevation is updated. - - """ - - _elevation_raised = NumericProperty() - _anim_raised = None - - def __init__(self, **kwargs): - if self.elevation == 0: - self.elevation = 2 - super().__init__(**kwargs) - self.on_elevation(self, self.elevation) - - def on_elevation(self, instance, value): - super().on_elevation(instance, value) - self._elevation_raised = self.elevation + 6 - self._anim_raised = Animation(_elevation=self._elevation_raised, d=0.15) - - def on__elevation_raised(self, instance, value): - Animation.cancel_all(self, "_elevation") - self._anim_raised = Animation(_elevation=self._elevation_raised, d=0.15) - - def on_disabled(self, instance, value): - if self.disabled is True: - Animation.cancel_all(self, "_elevation") - super().on_disabled(instance, value) - - def on_touch_down(self, touch): - if not self.disabled: - if touch.is_mouse_scrolling: - return False - if not self.collide_point(touch.x, touch.y): - return False - if self in touch.ud: - return False - if self._anim_raised: - self._anim_raised.start(self) - return super().on_touch_down(touch) - - def on_touch_up(self, touch): - if not self.disabled: - if touch.grab_current is not self: - if isinstance(self, MDFloatingActionButton): - self.stop_elevation_anim() - return super().on_touch_up(touch) - self.stop_elevation_anim() - return super().on_touch_up(touch) - - def stop_elevation_anim(self): - Animation.cancel_all(self, "_elevation") - self._elevation = self.elevation - - -class BaseCircularElevationButton( - CircularElevationBehavior, BaseElevationButton, BaseButton -): - pass - - -class BaseRoundButton(CircularRippleBehavior, BaseButton): - """ - Base class for all round buttons, bringing in the appropriate - on-touch behavior - """ - - md_bg_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_text) - Clock.schedule_once(self.set_text_color) - - def set_text_color(self, interval): - if not self.text_color: - self.text_color = self.theme_cls._get_text_color() - - def set_text(self, interval): - self.text = "" - - -class BaseRectangleFlatButton(BaseFlatButton, BaseElevationButton): - line_width = NumericProperty(1) - """ - Line width for button border. - - :attr:`line_width` is an :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - line_color = ColorProperty(None) - """ - Line color for button border. - - :attr:`line_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - md_bg_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_text_color) - - -class MDRaisedButton(BaseRectangularButton, BaseElevationButton): - theme_text_color = StringProperty("Custom") - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.update_text_color) - - def update_text_color(self, *args): - if self.text_color in ( - [0.0, 0.0, 0.0, 0.87], - [0.0, 0.0, 0.0, 1.0], - [1.0, 1.0, 1.0, 1.0], - ): - self.text_color = text_colors[self.theme_cls.primary_palette][ - self.theme_cls.primary_hue - ] - - -class MDFlatButton(BaseFlatButton): - md_bg_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) - - -class MDRectangleFlatButton(BaseRectangleFlatButton): - # TODO: If the user has set a custom text color, then when the application - # color palette changes, the text color will also change to the current - # color of the color scheme. Do need to preserve custom text colors when - # changing the application palette? - pass - - -class MDRectangleFlatIconButton(BaseRectangleFlatButton): - icon = StringProperty("android") - """ - Button icon. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'android'`. - """ - - icon_color = ColorProperty(None) - """ - Button icon color. - - :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.remove_label) - Clock.schedule_once(self.set_icon_color) - - def update_md_bg_color(self, instance, value): - self.text_color = self.theme_cls.primary_color - self.icon_color = self.theme_cls.primary_color - - def set_icon_color(self, interval): - """Sets the icon color if no custom value is specified.""" - - if not self.icon_color: - self.icon_color = self.theme_cls.primary_color - - def remove_label(self, interval): - self.remove_widget(self.ids.lbl_txt) - - -class MDRoundFlatButton(MDFlatButton): - line_width = NumericProperty(1) - """ - Line width for button border. - - :attr:`line_width` is an :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - line_color = ColorProperty(None) - """ - Line color for button border. - - :attr:`line_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _radius = NumericProperty(18) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_text_color) - - def lay_canvas_instructions(self): - with self.canvas.after: - StencilPush() - RoundedRectangle( - size=self.size, pos=self.pos, radius=[self._radius] - ) - StencilUse() - self.col_instruction = Color(rgba=self.ripple_color) - self.ellipse = Ellipse( - size=(self._ripple_rad, self._ripple_rad), - pos=( - self.ripple_pos[0] - self._ripple_rad / 2.0, - self.ripple_pos[1] - self._ripple_rad / 2.0, - ), - ) - StencilUnUse() - RoundedRectangle( - size=self.size, pos=self.pos, radius=[self._radius] - ) - StencilPop() - self.bind(ripple_color=self._set_color, _ripple_rad=self._set_ellipse) - - -class MDRoundFlatIconButton(MDRoundFlatButton): - icon = StringProperty("android") - """ - Button icon. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'android'`. - """ - - icon_color = ColorProperty(None) - """ - Button icon color. - - :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.remove_label) - Clock.schedule_once(self.set_icon_color) - - def set_icon_color(self, interval): - """Sets the icon color if no custom value is specified.""" - - if not self.icon_color: - self.icon_color = self.theme_cls.primary_color - - def update_md_bg_color(self, instance, value): - self.text_color = self.theme_cls.primary_color - self.icon_color = self.theme_cls.primary_color - - # From Python code. - def on_icon_color(self, instance, value): - def set_icon_color(interval): - self.icon_color = value - - Clock.schedule_once(set_icon_color) - - def remove_label(self, interval): - self.remove_widget(self.ids.lbl_txt) - - -class MDFillRoundFlatButton(MDRoundFlatButton): - opposite_colors = BooleanProperty(True) - - def __init__(self, **kwargs): - # Some blatant shit :( - super().__init__(**kwargs) - self.md_bg_color = self.theme_cls.primary_color - super().__init__(**kwargs) - Clock.schedule_once(self.set_text_color) - - def set_text_color(self, interval): - if self.text_color in ( - [0.0, 0.0, 0.0, 0.87], - [0.0, 0.0, 0.0, 1.0], - [1.0, 1.0, 1.0, 1.0], - ): - self.text_color = text_colors[self.theme_cls.primary_palette][ - self.theme_cls.primary_hue - ] - - def update_md_bg_color(self, instance, value): - self.md_bg_color = self.theme_cls.primary_color - self.set_text_color(0) - - def on_md_bg_color(self, instance, value): - self.set_text_color(0) - - -class MDFillRoundFlatIconButton(MDRoundFlatIconButton): - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.update_text_color) - Clock.schedule_once(self.update_icon_color) - - def set_md_bg_color(self, interval): - if self.md_bg_color == [0.0, 0.0, 0.0, 0.0]: - self.md_bg_color = self.theme_cls.primary_color - - def on_md_bg_color(self, instance, value): - self.md_bg_color = value - if self.md_bg_color != self.md_bg_color_disabled: - self._md_bg_color = value - - def update_md_bg_color(self, instance, value): - """Called when the application color palette changes.""" - - self.md_bg_color = self.theme_cls._get_primary_color() - self.update_text_color(instance, value) - if self.icon_color in ( - [0.0, 0.0, 0.0, 0.87], - [0.0, 0.0, 0.0, 1.0], - [1.0, 1.0, 1.0, 1.0], - ): - self.icon_color = text_colors[self.theme_cls.primary_palette][ - self.theme_cls.primary_hue - ] - - def update_text_color(self, *args): - if self.text_color in ( - [0.0, 0.0, 0.0, 0.87], - [0.0, 0.0, 0.0, 1.0], - [1.0, 1.0, 1.0, 1.0], - ): - self.text_color = text_colors[self.theme_cls.primary_palette][ - self.theme_cls.primary_hue - ] - - def set_text_color(self, interval): - pass - - def update_icon_color(self, interval): - if not self.icon_color: - self.icon_color = text_colors[self.theme_cls.primary_palette][ - self.theme_cls.primary_hue - ] - - def on_disabled(self, instance, value): - if value: - if not self.md_bg_color_disabled: - self.md_bg_color_disabled = ( - self.theme_cls.disabled_hint_text_color - ) - self.md_bg_color = self.md_bg_color_disabled - else: - if self._md_bg_color: - self.md_bg_color = self._md_bg_color - - def set_icon_color(self, interval): - pass - - -class MDIconButton(BaseRoundButton, BasePressedButton): - icon = StringProperty("checkbox-blank-circle") - """ - Button icon. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank-circle'`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.theme_cls.bind(primary_palette=self.update_md_bg_color) - Clock.schedule_once(self.set_size) - self.on_md_bg_color(self, [0.0, 0.0, 0.0, 0.0]) - - def set_size(self, interval): - """ - Sets the custom icon size if the value of the `user_font_size` - attribute is not zero. Otherwise, the icon size is set to `(48, 48)`. - """ - - self.width = ( - "48dp" if not self.user_font_size else dp(self.user_font_size + 23) - ) - self.height = ( - "48dp" if not self.user_font_size else dp(self.user_font_size + 23) - ) - - def update_md_bg_color(self, instance, value): - if self.md_bg_color != [0.0, 0.0, 0.0, 0.0]: - self.md_bg_color = self.theme_cls._get_primary_color() - - -class MDFloatingActionButton( - BaseRoundButton, BasePressedButton, BaseCircularElevationButton -): - icon = StringProperty("android") - """ - Button icon. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'android'`. - """ - - # FIXME: The `opposite_colors` parameter does not work for this type - # of buttons - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.theme_cls.bind(primary_palette=self.update_md_bg_color) - Clock.schedule_once(self.set_md_bg_color) - Clock.schedule_once(self.set_size) - Clock.schedule_once(self.update_text_color) - - def update_text_color(self, *args): - if self.text_color in ( - [0.0, 0.0, 0.0, 0.87], - [0.0, 0.0, 0.0, 1.0], - [1.0, 1.0, 1.0, 1.0], - ): - self.text_color = text_colors[self.theme_cls.primary_palette][ - self.theme_cls.primary_hue - ] - - def set_md_bg_color(self, interval): - if self.md_bg_color == [0.0, 0.0, 0.0, 0.0]: - self.md_bg_color = self.theme_cls.primary_color - - def set_size(self, interval): - self.width = "56dp" - self.height = "56dp" - - def on_touch_down(self, touch): - super(MDFloatingActionButton, self).on_touch_down(touch) - if self.collide_point(touch.x, touch.y): - return True - - def on_touch_move(self, touch): - super(MDFloatingActionButton, self).on_touch_move(touch) - if self.collide_point(touch.x, touch.y): - return True - - def on_touch_up(self, touch): - super(MDFloatingActionButton, self).on_touch_up(touch) - if self.collide_point(touch.x, touch.y): - return True - - -class MDTextButton(ButtonBehavior, MDLabel): - color = ColorProperty(None) - """ - Button color in (r, g, b, a) format. - - :attr:`color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - color_disabled = ColorProperty(None) - """ - Button color disabled in (r, g, b, a) format. - - :attr:`color_disabled` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _color = ColorProperty(None) # last current button text color - - def animation_label(self): - def set_default_state_label(*args): - Animation(opacity=1, d=0.1, t="in_out_cubic").start(self) - - anim = Animation(opacity=0.5, d=0.2, t="in_out_cubic") - anim.bind(on_complete=set_default_state_label) - anim.start(self) - - def on_press(self, *args): - self.animation_label() - return super().on_press(*args) - - def on_md_bg_color(self, instance, value): - self.md_bg_color = [0.0, 0.0, 0.0, 0.0] - - def on_disabled(self, instance, value): - if value: - if not self.color_disabled: - self.color_disabled = self.theme_cls.disabled_hint_text_color - self._color = self.color - self.text_color = self.color_disabled - else: - self.text_color = self._color - - -# SpeedDial classes - - -class BaseFloatingRootButton(MDFloatingActionButton): - _angle = NumericProperty(0) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.elevation = 5 - - -class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip): - _canvas_width = NumericProperty(0) - _padding_right = NumericProperty(0) - _bg_color = ColorProperty(None) - - def set_size(self, interval): - self.width = "46dp" - self.height = "46dp" - - -class BaseFloatingLabel( - ThemableBehavior, FakeRectangularElevationBehavior, BoxLayout -): - text = StringProperty() - text_color = ColorProperty(None) - bg_color = ColorProperty(None) - - -class MDFloatingBottomButton(BaseFloatingBottomButton): - pass - - -class MDFloatingRootButton(BaseFloatingRootButton): - pass - - -class MDFloatingLabel(BaseFloatingLabel): - pass - - -class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout): - """ - :Events: - :attr:`on_open` - Called when a stack is opened. - :attr:`on_close` - Called when a stack is closed. - """ - - icon = StringProperty("plus") - """ - Root button icon name. - - :attr:`icon` is a :class:`~kivy.properties.StringProperty` - and defaults to `'plus'`. - """ - - anchor = OptionProperty("right", option=["right"]) - """ - Stack anchor. Available options are: `'right'`. - - :attr:`anchor` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'right'`. - """ - - callback = ObjectProperty(lambda x: None) - """ - Custom callback. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - callback: app.callback - - .. code-block:: python - - def callback(self, instance): - print(instance.icon) - - - :attr:`callback` is a :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - label_text_color = ColorProperty([0, 0, 0, 1]) - """ - Floating text color in (r, g, b, a) format. - - :attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 1]`. - """ - - data = DictProperty() - """ - Must be a dictionary - - .. code-block:: python - - { - 'name-icon': 'Text label', - ..., - ..., - } - """ - - right_pad = BooleanProperty(True) - """ - If `True`, the button will increase on the right side by 2.5 pixels - if the :attr:`~hint_animation` parameter equal to `True`. - - .. rubric:: False - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad.gif - :align: center - - .. rubric:: True - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad-true.gif - :align: center - - :attr:`right_pad` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - root_button_anim = BooleanProperty(False) - """ - If ``True`` then the root button will rotate 45 degrees when the stack - is opened. - - :attr:`root_button_anim` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - opening_transition = StringProperty("out_cubic") - """ - The name of the stack opening animation type. - - :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - closing_transition = StringProperty("out_cubic") - """ - The name of the stack closing animation type. - - :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - opening_transition_button_rotation = StringProperty("out_cubic") - """ - The name of the animation type to rotate the root button when opening the - stack. - - :attr:`opening_transition_button_rotation` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - closing_transition_button_rotation = StringProperty("out_cubic") - """ - The name of the animation type to rotate the root button when closing the - stack. - - :attr:`closing_transition_button_rotation` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - opening_time = NumericProperty(0.5) - """ - Time required for the stack to go to: attr:`state` `'open'`. - - :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - closing_time = NumericProperty(0.2) - """ - Time required for the stack to go to: attr:`state` `'close'`. - - :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - opening_time_button_rotation = NumericProperty(0.2) - """ - Time required to rotate the root button 45 degrees during the stack - opening animation. - - :attr:`opening_time_button_rotation` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - closing_time_button_rotation = NumericProperty(0.2) - """ - Time required to rotate the root button 0 degrees during the stack - closing animation. - - :attr:`closing_time_button_rotation` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - state = OptionProperty("close", options=("close", "open")) - """ - Indicates whether the stack is closed or open. - Available options are: `'close'`, `'open'`. - - :attr:`state` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'close'`. - """ - - bg_color_root_button = ColorProperty(None) - """ - Root button color in (r, g, b, a) format. - - :attr:`bg_color_root_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. - """ - - bg_color_stack_button = ColorProperty(None) - """ - The color of the buttons in the stack (r, g, b, a) format. - - :attr:`bg_color_stack_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. - """ - - color_icon_stack_button = ColorProperty(None) - """ - The color icon of the buttons in the stack (r, g, b, a) format. - - :attr:`color_icon_stack_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. - """ - - color_icon_root_button = ColorProperty(None) - """ - The color icon of the root button (r, g, b, a) format. - - :attr:`color_icon_root_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. - """ - - bg_hint_color = ColorProperty(None) - """ - Background color for the text of the buttons in the stack (r, g, b, a) format. - - :attr:`bg_hint_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - hint_animation = BooleanProperty(False) - """ - Whether to use button extension animation to display text labels. - - :attr:`hint_animation` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - _label_pos_y_set = False - _anim_buttons_data = {} - _anim_labels_data = {} - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_open") - self.register_event_type("on_close") - Window.bind(on_resize=self._update_pos_buttons) - - def on_open(self, *args): - """Called when a stack is opened.""" - - def on_close(self, *args): - """Called when a stack is closed.""" - - def on_leave(self, instance): - """Called when the mouse cursor goes outside the button of stack.""" - - if self.state == "open": - for widget in self.children: - if isinstance(widget, MDFloatingLabel) and self.hint_animation: - Animation.cancel_all(widget) - if self.data[instance.icon] == widget.text: - Animation( - _canvas_width=0, - _padding_right=0, - d=self.opening_time, - t=self.opening_transition, - ).start(instance) - if self.hint_animation: - Animation( - opacity=0, d=0.1, t=self.opening_transition - ).start(widget) - break - - def on_enter(self, instance): - """Called when the mouse cursor is over a button from the stack.""" - - if self.state == "open": - for widget in self.children: - if isinstance(widget, MDFloatingLabel) and self.hint_animation: - widget._elevation = 0 - if self.data[instance.icon] == widget.text: - Animation( - _canvas_width=widget.width + dp(24), - _padding_right=dp(5) if self.right_pad else 0, - d=self.opening_time, - t=self.opening_transition, - ).start(instance) - if self.hint_animation: - Animation( - opacity=1, - d=self.opening_time, - t=self.opening_transition, - ).start(widget) - break - - def on_data(self, instance, value): - """Creates a stack of buttons.""" - - # FIXME: Don't know how to fix AttributeError error: - # File "kivymd/uix/button.py", line 1597, in on_data - # self.add_widget(bottom_button) - # File "kivy/uix/floatlayout.py", line 140, in add_widget - # return super(FloatLayout, self).add_widget(widget, index, canvas) - # File "kivy/uix/layout.py", line 97, in add_widget - # return super(Layout, self).add_widget(widget, index, canvas) - # File "kivy/uix/widget.py", line 629, in add_widget - # canvas.add(widget.canvas) - # AttributeError: 'NoneType' object has no attribute 'add' - super().__init__() - - self.clear_widgets() - self._anim_buttons_data = {} - self._anim_labels_data = {} - self._label_pos_y_set = False - - # Bottom buttons. - for name, name_icon in value.items(): - bottom_button = MDFloatingBottomButton( - icon=name_icon, - on_enter=self.on_enter, - on_leave=self.on_leave, - opacity=0, - ) - bottom_button.bind( - on_release=lambda x=bottom_button: self.callback(x) - ) - self.set_pos_bottom_buttons(bottom_button) - self.add_widget(bottom_button) - # Labels. - floating_text = name - if floating_text: - label = MDFloatingLabel(text=floating_text, opacity=0) - label.text_color = self.label_text_color - self.add_widget(label) - # Top root button. - root_button = MDFloatingRootButton(on_release=self.open_stack) - root_button.icon = self.icon - self.set_pos_root_button(root_button) - self.add_widget(root_button) - - def on_icon(self, instance, value): - self._get_count_widget(MDFloatingRootButton).icon = value - - def on_label_text_color(self, instance, value): - for widget in self.children: - if isinstance(widget, MDFloatingLabel): - widget.text_color = value - - def on_color_icon_stack_button(self, instance, value): - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - widget.text_color = value - - def on_hint_animation(self, instance, value): - for widget in self.children: - if isinstance(widget, MDFloatingLabel): - widget.bg_color = (0, 0, 0, 0) - - def on_bg_hint_color(self, instance, value): - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - widget._bg_color = value - - def on_color_icon_root_button(self, instance, value): - self._get_count_widget(MDFloatingRootButton).text_color = value - - def on_bg_color_stack_button(self, instance, value): - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - widget.md_bg_color = value - - def on_bg_color_root_button(self, instance, value): - self._get_count_widget(MDFloatingRootButton).md_bg_color = value - - def set_pos_labels(self, widget): - """Sets the position of the floating labels.""" - - if self.anchor == "right": - widget.x = Window.width - widget.width - dp(86) - - def set_pos_root_button(self, instance): - """Sets the position of the root button.""" - - if self.anchor == "right": - instance.y = dp(20) - instance.x = Window.width - (dp(56) + dp(20)) - - def set_pos_bottom_buttons(self, instance): - """Sets the position of the bottom buttons in a stack.""" - - if self.anchor == "right": - if self.state != "open": - instance.y = instance.height / 2 - instance.x = Window.width - (instance.height + instance.width / 2) - - def open_stack(self, instance): - """Opens a button stack.""" - - for widget in self.children: - if isinstance(widget, MDFloatingLabel): - Animation.cancel_all(widget) - - if self.state != "open": - y = 0 - label_position = dp(56) - anim_buttons_data = {} - anim_labels_data = {} - - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - # Sets new button positions. - y += dp(56) - widget.y = widget.y * 2 + y - if not self._anim_buttons_data: - anim_buttons_data[widget] = Animation( - opacity=1, - d=self.opening_time, - t=self.opening_transition, - ) - elif isinstance(widget, MDFloatingLabel): - # Sets new labels positions. - label_position += dp(56) - # Sets the position of signatures only once. - if not self._label_pos_y_set: - widget.y = widget.y * 2 + label_position - widget.x = Window.width - widget.width - dp(86) - if not self._anim_labels_data: - anim_labels_data[widget] = Animation( - opacity=1, d=self.opening_time - ) - elif ( - isinstance(widget, MDFloatingRootButton) - and self.root_button_anim - ): - # Rotates the root button 45 degrees. - Animation( - _angle=-45, - d=self.opening_time_button_rotation, - t=self.opening_transition_button_rotation, - ).start(widget) - - if anim_buttons_data: - self._anim_buttons_data = anim_buttons_data - if anim_labels_data and not self.hint_animation: - self._anim_labels_data = anim_labels_data - - self.state = "open" - self.dispatch("on_open") - self.do_animation_open_stack(self._anim_buttons_data) - self.do_animation_open_stack(self._anim_labels_data) - if not self._label_pos_y_set: - self._label_pos_y_set = True - else: - self.close_stack() - - def do_animation_open_stack(self, anim_data): - def on_progress(animation, widget, value): - if value >= 0.1: - animation_open_stack() - - def animation_open_stack(*args): - try: - widget = next(widgets_list) - animation = anim_data[widget] - animation.bind(on_progress=on_progress) - animation.start(widget) - except StopIteration: - pass - - widgets_list = iter(list(anim_data.keys())) - animation_open_stack() - - def close_stack(self): - """Closes the button stack.""" - - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - Animation( - y=widget.height / 2, - d=self.closing_time, - t=self.closing_transition, - opacity=0, - ).start(widget) - elif isinstance(widget, MDFloatingLabel): - Animation(opacity=0, d=0.1).start(widget) - elif ( - isinstance(widget, MDFloatingRootButton) - and self.root_button_anim - ): - Animation( - _angle=0, - d=self.closing_time_button_rotation, - t=self.closing_transition_button_rotation, - ).start(widget) - self.state = "close" - self.dispatch("on_close") - - def _update_pos_buttons(self, instance, width, height): - # Updates button positions when resizing screen. - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - self.set_pos_bottom_buttons(widget) - elif isinstance(widget, MDFloatingRootButton): - self.set_pos_root_button(widget) - elif isinstance(widget, MDFloatingLabel): - self.set_pos_labels(widget) - - def _get_count_widget(self, instance): - widget = None - for widget in self.children: - if isinstance(widget, instance): - break - return widget diff --git a/kivymd/uix/card.py b/kivymd/uix/card.py deleted file mode 100644 index ead4e0e..0000000 --- a/kivymd/uix/card.py +++ /dev/null @@ -1,882 +0,0 @@ -""" -Components/Card -=============== - -.. seealso:: - - `Material Design spec, Cards `_ - -.. rubric:: Cards contain content and actions about a single subject. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/cards.gif - :align: center - -`KivyMD` provides the following card classes for use: - -- MDCard_ -- MDCardSwipe_ - -.. Note:: :class:`~MDCard` inherited from - :class:`~kivy.uix.boxlayout.BoxLayout`. You can use all parameters and - attributes of the :class:`~kivy.uix.boxlayout.BoxLayout` class in the - :class:`~MDCard` class. - -.. MDCard: -MDCard ------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDCard: - size_hint: None, None - size: "280dp", "180dp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class TestCard(MDApp): - def build(self): - return Builder.load_string(KV) - - - TestCard().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card.png - :align: center - -Add content to card: --------------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDCard: - orientation: "vertical" - padding: "8dp" - size_hint: None, None - size: "280dp", "180dp" - pos_hint: {"center_x": .5, "center_y": .5} - - MDLabel: - text: "Title" - theme_text_color: "Secondary" - size_hint_y: None - height: self.texture_size[1] - - MDSeparator: - height: "1dp" - - MDLabel: - text: "Body" - ''' - - - class TestCard(MDApp): - def build(self): - return Builder.load_string(KV) - - - TestCard().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-content.png - :align: center - -.. MDCardSwipe: -MDCardSwipe ------------ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDCardSwipe.gif - :align: center - -To create a card with `swipe-to-delete` behavior, you must create a new class -that inherits from the :class:`~MDCardSwipe` class: - - -.. code-block:: kv - - : - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - - MDCardSwipeFrontBox: - - OneLineListItem: - id: content - text: root.text - _no_ripple_effect: True - -.. code-block:: python - - class SwipeToDeleteItem(MDCardSwipe): - text = StringProperty() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/map-mdcard-swipr.png - :align: center - -End full code -------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.card import MDCardSwipe - - KV = ''' - : - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - # Content under the card. - - MDCardSwipeFrontBox: - - # Content of card. - OneLineListItem: - id: content - text: root.text - _no_ripple_effect: True - - - MDScreen: - - MDBoxLayout: - orientation: "vertical" - spacing: "10dp" - - MDToolbar: - elevation: 10 - title: "MDCardSwipe" - - ScrollView: - scroll_timeout : 100 - - MDList: - id: md_list - padding: 0 - ''' - - - class SwipeToDeleteItem(MDCardSwipe): - '''Card with `swipe-to-delete` behavior.''' - - text = StringProperty() - - - class TestCard(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - - def build(self): - return self.screen - - def on_start(self): - '''Creates a list of cards.''' - - for i in range(20): - self.screen.ids.md_list.add_widget( - SwipeToDeleteItem(text=f"One-line item {i}") - ) - - - TestCard().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-mdcard-swipe.gif - :align: center - -Binding a swipe to one of the sides of the screen -------------------------------------------------- - -.. code-block:: kv - - : - # By default, the parameter is "left" - anchor: "right" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdcard-swipe-anchor-right.gif - :align: center - - -.. Note:: You cannot use the left and right swipe at the same time. - -Swipe behavior --------------- - -.. code-block:: kv - - : - # By default, the parameter is "hand" - type_swipe: "hand" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hand-mdcard-swipe.gif - :align: center - -.. code-block:: kv - - : - type_swipe: "auto" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/auto-mdcard-swipe.gif - :align: center - -Removing an item using the ``type_swipe = "auto"`` parameter ------------------------------------------------------------- - -The map provides the :attr:`MDCardSwipe.on_swipe_complete` event. -You can use this event to remove items from a list: - -.. code-block:: kv - - : - on_swipe_complete: app.on_swipe_complete(root) - -.. code-block:: python - - def on_swipe_complete(self, instance): - self.screen.ids.md_list.remove_widget(instance) - -End full code -------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.card import MDCardSwipe - - KV = ''' - : - size_hint_y: None - height: content.height - type_swipe: "auto" - on_swipe_complete: app.on_swipe_complete(root) - - MDCardSwipeLayerBox: - - MDCardSwipeFrontBox: - - OneLineListItem: - id: content - text: root.text - _no_ripple_effect: True - - - MDScreen: - - MDBoxLayout: - orientation: "vertical" - spacing: "10dp" - - MDToolbar: - elevation: 10 - title: "MDCardSwipe" - - ScrollView: - - MDList: - id: md_list - padding: 0 - ''' - - - class SwipeToDeleteItem(MDCardSwipe): - text = StringProperty() - - - class TestCard(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - - def build(self): - return self.screen - - def on_swipe_complete(self, instance): - self.screen.ids.md_list.remove_widget(instance) - - def on_start(self): - for i in range(20): - self.screen.ids.md_list.add_widget( - SwipeToDeleteItem(text=f"One-line item {i}") - ) - - - TestCard().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/autodelete-mdcard-swipe.gif - :align: center - -Add content to the bottom layer of the card -------------------------------------------- - -To add content to the bottom layer of the card, -use the :class:`~MDCardSwipeLayerBox` class. - -.. code-block:: kv - - : - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - icon: "trash-can" - pos_hint: {"center_y": .5} - on_release: app.remove_item(root) - -End full code -------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.card import MDCardSwipe - - KV = ''' - : - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - icon: "trash-can" - pos_hint: {"center_y": .5} - on_release: app.remove_item(root) - - MDCardSwipeFrontBox: - - OneLineListItem: - id: content - text: root.text - _no_ripple_effect: True - - - MDScreen: - - MDBoxLayout: - orientation: "vertical" - spacing: "10dp" - - MDToolbar: - elevation: 10 - title: "MDCardSwipe" - - ScrollView: - - MDList: - id: md_list - padding: 0 - ''' - - - class SwipeToDeleteItem(MDCardSwipe): - text = StringProperty() - - - class TestCard(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - - def build(self): - return self.screen - - def remove_item(self, instance): - self.screen.ids.md_list.remove_widget(instance) - - def on_start(self): - for i in range(20): - self.screen.ids.md_list.add_widget( - SwipeToDeleteItem(text=f"One-line item {i}") - ) - - - TestCard().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/handdelete-mdcard-swipe.gif - :align: center - -Focus behavior --------------- - -.. code-block:: kv - - MDCard: - focus_behavior: True - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif - :align: center - -Ripple behavior ---------------- - -.. code-block:: kv - - MDCard: - ripple_behavior: True - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-behavior.gif - :align: center - -End full code -------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - - icon: "star" - on_release: self.icon = "star-outline" if self.icon == "star" else "star" - - - MDScreen: - - MDCard: - orientation: "vertical" - size_hint: .5, None - height: box_top.height + box_bottom.height - focus_behavior: True - ripple_behavior: True - pos_hint: {"center_x": .5, "center_y": .5} - - MDBoxLayout: - id: box_top - spacing: "20dp" - adaptive_height: True - - FitImage: - source: "/Users/macbookair/album.jpeg" - size_hint: .3, None - height: text_box.height - - MDBoxLayout: - id: text_box - orientation: "vertical" - adaptive_height: True - spacing: "10dp" - padding: 0, "10dp", "10dp", "10dp" - - MDLabel: - text: "Ride the Lightning" - theme_text_color: "Primary" - font_style: "H5" - bold: True - size_hint_y: None - height: self.texture_size[1] - - MDLabel: - text: "July 27, 1984" - size_hint_y: None - height: self.texture_size[1] - theme_text_color: "Primary" - - MDSeparator: - - MDBoxLayout: - id: box_bottom - adaptive_height: True - padding: "10dp", 0, 0, 0 - - MDLabel: - text: "Rate this album" - size_hint_y: None - height: self.texture_size[1] - pos_hint: {"center_y": .5} - theme_text_color: "Primary" - - StarButton: - StarButton: - StarButton: - StarButton: - StarButton: - ''' - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Test().run() -""" - -__all__ = ( - "MDCard", - "MDCardSwipe", - "MDCardSwipeFrontBox", - "MDCardSwipeLayerBox", - "MDSeparator", -) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - NumericProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.relativelayout import RelativeLayout -from kivy.utils import get_color_from_hex - -from kivymd.color_definitions import colors -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import ( - BackgroundColorBehavior, - FakeRectangularElevationBehavior, - FocusBehavior, - RectangularRippleBehavior, -) -from kivymd.uix.boxlayout import MDBoxLayout - -Builder.load_string( - """ -: - canvas.before: - Color: - rgba: app.theme_cls.divider_color - Rectangle: - size: self.size - pos: self.pos - - - - canvas.before: - Color: - rgba: self.md_bg_color - RoundedRectangle: - size: self.size - pos: self.pos - radius: root.radius - source: root.background - - - - md_bg_color: self.theme_cls.divider_color if not root.color else root.color -""" -) - - -class MDSeparator(ThemableBehavior, MDBoxLayout): - """A separator line.""" - - color = ColorProperty(None) - """Separator color in ``rgba`` format. - - :attr:`color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.on_orientation() - - def on_orientation(self, *args): - self.size_hint = ( - (1, None) if self.orientation == "horizontal" else (None, 1) - ) - if self.orientation == "horizontal": - self.height = dp(1) - else: - self.width = dp(1) - - -class MDCard( - ThemableBehavior, - FakeRectangularElevationBehavior, - BackgroundColorBehavior, - RectangularRippleBehavior, - FocusBehavior, - BoxLayout, -): - - focus_behavior = BooleanProperty(False) - """ - Using focus when hovering over a card. - - :attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - ripple_behavior = BooleanProperty(False) - """ - Use ripple effect for card. - - :attr:`ripple_behavior` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - elevation = NumericProperty(None, allownone=True) - """ - Elevation value. - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to 1. - """ - - _bg_color_map = ( - get_color_from_hex(colors["Light"]["CardsDialogs"]), - get_color_from_hex(colors["Dark"]["CardsDialogs"]), - [1.0, 1.0, 1.0, 0.0], - ) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.theme_cls.bind(theme_style=self.update_md_bg_color) - Clock.schedule_once(lambda x: self._on_elevation(self.elevation)) - Clock.schedule_once( - lambda x: self.on_ripple_behavior(0, self.ripple_behavior) - ) - self.update_md_bg_color(self, self.theme_cls.theme_style) - - def update_md_bg_color(self, instance, value): - if self.md_bg_color in self._bg_color_map: - self.md_bg_color = get_color_from_hex(colors[value]["CardsDialogs"]) - - def on_ripple_behavior(self, instance, value): - self._no_ripple_effect = False if value else True - - def _on_elevation(self, value): - if value is None: - self.elevation = 6 - else: - self.elevation = value - - -class MDCardSwipe(RelativeLayout): - """ - :Events: - :attr:`on_swipe_complete` - Called when a swipe of card is completed. - """ - - open_progress = NumericProperty(0.0) - """ - Percent of visible part of side panel. The percent is specified as a - floating point number in the range 0-1. 0.0 if panel is closed and 1.0 if - panel is opened. - - :attr:`open_progress` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.0`. - """ - - opening_transition = StringProperty("out_cubic") - """ - The name of the animation transition type to use when animating to - the :attr:`state` `'opened'`. - - :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - closing_transition = StringProperty("out_sine") - """ - The name of the animation transition type to use when animating to - the :attr:`state` 'closed'. - - :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_sine'`. - """ - - anchor = OptionProperty("left", options=("left", "right")) - """ - Anchoring screen edge for card. Available options are: `'left'`, `'right'`. - - :attr:`anchor` is a :class:`~kivy.properties.OptionProperty` - and defaults to `left`. - """ - - swipe_distance = NumericProperty(50) - """ - The distance of the swipe with which the movement of navigation drawer - begins. - - :attr:`swipe_distance` is a :class:`~kivy.properties.NumericProperty` - and defaults to `50`. - """ - - opening_time = NumericProperty(0.2) - """ - The time taken for the card to slide to the :attr:`state` `'open'`. - - :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - state = OptionProperty("closed", options=("closed", "opened")) - """ - Detailed state. Sets before :attr:`state`. Bind to :attr:`state` instead - of :attr:`status`. Available options are: `'closed'`, `'opened'`. - - :attr:`status` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'closed'`. - """ - - max_swipe_x = NumericProperty(0.3) - """ - If, after the events of :attr:`~on_touch_up` card position exceeds this - value - will automatically execute the method :attr:`~open_card`, - and if not - will automatically be :attr:`~close_card` method. - - :attr:`max_swipe_x` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.3`. - """ - - max_opened_x = NumericProperty("100dp") - """ - The value of the position the card shifts to when :attr:`~type_swipe` - s set to `'hand'`. - - :attr:`max_opened_x` is a :class:`~kivy.properties.NumericProperty` - and defaults to `100dp`. - """ - - type_swipe = OptionProperty("hand", options=("auto", "hand")) - """ - Type of card opening when swipe. Shift the card to the edge or to - a set position :attr:`~max_opened_x`. Available options are: - `'auto'`, `'hand'`. - - :attr:`type_swipe` is a :class:`~kivy.properties.OptionProperty` - and defaults to `auto`. - """ - - _opens_process = False - _to_closed = True - - def __init__(self, **kw): - self.register_event_type("on_swipe_complete") - super().__init__(**kw) - - def _on_swipe_complete(self, *args): - self.dispatch("on_swipe_complete") - - def add_widget(self, widget, index=0, canvas=None): - if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)): - return super().add_widget(widget) - - def on_swipe_complete(self, *args): - """Called when a swipe of card is completed.""" - - def on_anchor(self, instance, value): - if value == "right": - self.open_progress = 1.0 - else: - self.open_progress = 0.0 - - def on_open_progress(self, instance, value): - if self.anchor == "left": - self.children[0].x = self.width * value - else: - self.children[0].x = self.width * value - self.width - - def on_touch_move(self, touch): - if self.collide_point(touch.x, touch.y): - expr = ( - touch.x < self.swipe_distance - if self.anchor == "left" - else touch.x > self.width - self.swipe_distance - ) - if expr and not self._opens_process: - self._opens_process = True - self._to_closed = False - if self._opens_process: - self.open_progress = max( - min(self.open_progress + touch.dx / self.width, 2.5), 0 - ) - return super().on_touch_move(touch) - - def on_touch_up(self, touch): - if self.collide_point(touch.x, touch.y): - if not self._to_closed: - self._opens_process = False - self.complete_swipe() - return super().on_touch_up(touch) - - def on_touch_down(self, touch): - if self.collide_point(touch.x, touch.y): - if self.state == "opened": - self._to_closed = True - self.close_card() - return super().on_touch_down(touch) - - def complete_swipe(self): - expr = ( - self.open_progress <= self.max_swipe_x - if self.anchor == "left" - else self.open_progress >= self.max_swipe_x - ) - if expr: - self.close_card() - else: - self.open_card() - - def open_card(self): - if self.type_swipe == "hand": - swipe_x = ( - self.max_opened_x - if self.anchor == "left" - else -self.max_opened_x - ) - else: - swipe_x = self.width if self.anchor == "left" else 0 - anim = Animation( - x=swipe_x, t=self.opening_transition, d=self.opening_time - ) - anim.bind(on_complete=self._on_swipe_complete) - anim.start(self.children[0]) - self.state = "opened" - - def close_card(self): - anim = Animation(x=0, t=self.closing_transition, d=self.opening_time) - anim.bind(on_complete=self._reset_open_progress) - anim.start(self.children[0]) - self.state = "closed" - - def _reset_open_progress(self, *args): - self.open_progress = 0.0 if self.anchor == "left" else 1.0 - self._to_closed = False - self.dispatch("on_swipe_complete") - - -class MDCardSwipeFrontBox(MDCard): - pass - - -class MDCardSwipeLayerBox(BoxLayout): - pass diff --git a/kivymd/uix/carousel.py b/kivymd/uix/carousel.py deleted file mode 100644 index c119515..0000000 --- a/kivymd/uix/carousel.py +++ /dev/null @@ -1,212 +0,0 @@ -""" -Components/Carousel -===================== - -:class:`~kivy.uix.boxlayout.Carousel` class equivalent. Simplifies working -with some widget properties. For example: - - -Carousel ---------- - -.. code-block:: python - - kv=''' - YourCarousel: - BoxLayout: - [...] - BoxLayout: - [...] - BoxLayout: - [...] - ''' - builder.load_string(kv) - - class YourCarousel(Carousel): - def __init__(self,*kwargs): - self.register_event_type("on_slide_progress") - self.register_event_type("on_slide_complete") - - def on_touch_down(self, *args): - ["Code to detect when the slide changes"] - - def on_touch_up(self, *args): - ["Code to detect when the slide changes"] - - def Calculate_slide_pos(self, *args): - ["Code to calculate the current position of the slide"] - - def do_custom_animation(self, *args) - ["Code to recreate an animation"] - - -MDCarousel ------------ - -.. code-block:: kv - - MDCarousel: - on_slide_progress: - do_something() - on_slide_complete: - do_something() - -""" -# TODO: Add documentation. - -from kivy.animation import Animation -from kivy.uix.carousel import Carousel - - -class MDCarousel(Carousel): - """ - based on kivy's carousel. - - .. seealso:: - `kivy.uix.carousel.Carousel `_ - """ - - _scrolling = False - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_slide_progress") - self.register_event_type("on_slide_complete") - - def on_slide_progress(self, *args): - """ - Event launched when the Slide animation is progress. - rememebr to bind and unbid to this method. - """ - pass - - def on_slide_complete(self, *args): - """ - Event launched when the Slide animation is complete. - rememebr to bind and unbid to this method. - """ - pass - - def _position_visible_slides(self, *args): - slides, index = self.slides, self.index - no_of_slides = len(slides) - 1 - if not slides: - return - x, y, width, height = self.x, self.y, self.width, self.height - _offset, direction = self._offset, self.direction - _prev, _next, _current = self._prev, self._next, self._current - get_slide_container = self.get_slide_container - last_slide = get_slide_container(slides[-1]) - first_slide = get_slide_container(slides[0]) - skip_next = False - _loop = self.loop - - if direction[0] in ["r", "l"]: - xoff = x + _offset - x_prev = {"l": xoff + width, "r": xoff - width} - x_next = {"l": xoff - width, "r": xoff + width} - if _prev: - _prev.pos = (x_prev[direction[0]], y) - elif _loop and _next and index == 0: - if (_offset > 0 and direction[0] == "r") or ( - _offset < 0 and direction[0] == "l" - ): - last_slide.pos = (x_prev[direction[0]], y) - skip_next = True - if _current: - _current.pos = (xoff, y) - - if self._scrolling: - self.dispatch("on_slide_progress", (xoff, y)) - - if skip_next: - return - if _next: - _next.pos = (x_next[direction[0]], y) - elif _loop and _prev and index == no_of_slides: - if (_offset < 0 and direction[0] == "r") or ( - _offset > 0 and direction[0] == "l" - ): - first_slide.pos = (x_next[direction[0]], y) - if direction[0] in ["t", "b"]: - yoff = y + _offset - y_prev = {"t": yoff - height, "b": yoff + height} - y_next = {"t": yoff + height, "b": yoff - height} - if _prev: - _prev.pos = (x, y_prev[direction[0]]) - elif _loop and _next and index == 0: - if (_offset > 0 and direction[0] == "t") or ( - _offset < 0 and direction[0] == "b" - ): - last_slide.pos = (x, y_prev[direction[0]]) - skip_next = True - if _current: - _current.pos = (x, yoff) - if skip_next: - return - if _next: - _next.pos = (x, y_next[direction[0]]) - elif _loop and _prev and index == no_of_slides: - if (_offset < 0 and direction[0] == "t") or ( - _offset > 0 and direction[0] == "b" - ): - first_slide.pos = (x, y_next[direction[0]]) - - def on_touch_down(self, touch): - self._scrolling = True - return super().on_touch_down(touch) - - def on_touch_up(self, touch): - self._scrolling = False - return super().on_touch_up(touch) - - def _start_animation(self, *args, **kwargs): - # compute target offset for ease back, next or prev - new_offset = 0 - direction = kwargs.get("direction", self.direction)[0] - is_horizontal = direction in "rl" - extent = self.width if is_horizontal else self.height - min_move = kwargs.get("min_move", self.min_move) - _offset = kwargs.get("offset", self._offset) - - if _offset < min_move * -extent: - new_offset = -extent - elif _offset > min_move * extent: - new_offset = extent - - # if new_offset is 0, it wasnt enough to go next/prev - dur = self.anim_move_duration - if new_offset == 0: - dur = self.anim_cancel_duration - - # detect edge cases if not looping - len_slides = len(self.slides) - index = self.index - if not self.loop or len_slides == 1: - is_first = index == 0 - is_last = index == len_slides - 1 - if direction in "rt": - towards_prev = new_offset > 0 - towards_next = new_offset < 0 - else: - towards_prev = new_offset < 0 - towards_next = new_offset > 0 - if (is_first and towards_prev) or (is_last and towards_next): - new_offset = 0 - - anim = Animation(_offset=new_offset, d=dur, t=self.anim_type) - anim.cancel_all(self) - - def _cmp(*args): - self.dispatch( - "on_slide_complete", - self.previous_slide, - self.current_slide, - self.next_slide, - ) - if self._skip_slide is not None: - self.index = self._skip_slide - self._skip_slide = None - - anim.bind(on_complete=_cmp) - anim.start(self) diff --git a/kivymd/uix/chip.py b/kivymd/uix/chip.py deleted file mode 100644 index 75a4389..0000000 --- a/kivymd/uix/chip.py +++ /dev/null @@ -1,305 +0,0 @@ -""" -Components/Chip -=============== - -.. seealso:: - - `Material Design spec, Chips `_ - -.. rubric:: Chips are compact elements that represent an input, attribute, or action. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chips.png - :align: center - -Usage ------ - -.. code-block:: kv - - MDChip: - text: 'Coffee' - color: .4470588235118, .1960787254902, 0, 1 - icon: 'coffee' - on_release: app.callback_for_menu_items(self) - -The user function takes two arguments - the object and the text of the chip: - -.. code-block:: python - - def callback_for_menu_items(self, instance): - print(instance) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ordinary-chip.png - :align: center - -Use custom icon ---------------- - -.. code-block:: kv - - MDChip: - text: 'Kivy' - icon: 'data/logo/kivy-icon-256.png' - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-custom-icon.png - :align: center - -Use without icon ----------------- - -.. code-block:: kv - - MDChip: - text: 'Without icon' - icon: '' - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-without-icon.png - :align: center - -Chips with check ----------------- - -.. code-block:: kv - - MDChip: - text: 'Check with icon' - icon: 'city' - check: True - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-check-icon.gif - :align: center - -Choose chip ------------ - -.. code-block:: kv - - MDChooseChip: - - MDChip: - text: 'Earth' - icon: 'earth' - selected_chip_color: .21176470535294, .098039627451, 1, 1 - - MDChip: - text: 'Face' - icon: 'face' - selected_chip_color: .21176470535294, .098039627451, 1, 1 - - MDChip: - text: 'Facebook' - icon: 'facebook' - selected_chip_color: .21176470535294, .098039627451, 1, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-shoose-icon.gif - :align: center - -.. Note:: `See full example `_ -""" - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp, sp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - StringProperty, -) -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.boxlayout import BoxLayout - -from kivymd.theming import ThemableBehavior -from kivymd.uix.label import MDIcon -from kivymd.uix.stacklayout import MDStackLayout - -Builder.load_string( - """ -#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE - - - - adaptive_height: True - spacing: "5dp" - - - - size_hint: None, None - height: "26dp" - padding: 0, 0, "8dp", 0 - width: - self.minimum_width - (dp(10) if DEVICE_TYPE == "desktop" else dp(20)) \ - if root.icon != 'checkbox-blank-circle' else self.minimum_width - - canvas: - Color: - rgba: root.theme_cls.primary_color if not root.color else root.color - RoundedRectangle: - pos: self.pos - size: self.size - radius: root.radius - - MDBoxLayout: - id: box_check - adaptive_size: True - pos_hint: {'center_y': .5} - padding: "8dp", 0, 0, 0 - - MDBoxLayout: - adaptive_width: True - padding: dp(10) - - Label: - id: label - text: root.text - size_hint_x: None - width: self.texture_size[0] - color: root.text_color if root.text_color else (root.theme_cls.text_color) - markup: True - - MDIcon: - id: icon - icon: root.icon - size_hint: None, None - size: "26dp", "26dp" - font_size: "20sp" - theme_text_color: "Custom" - text_color: root.icon_color if root.icon_color else (root.theme_cls.text_color) -""" -) - - -class MDChip(ThemableBehavior, ButtonBehavior, BoxLayout): - text = StringProperty() - """ - Chip text. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon = StringProperty("checkbox-blank-circle") - """ - Chip icon. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank-circle'`. - """ - - color = ColorProperty(None) - """ - Chip color in ``rgba`` format. - - :attr:`color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_color = ColorProperty(None) - """ - Chip's text color in ``rgba`` format. - - :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - icon_color = ColorProperty(None) - """ - Chip's icon color in ``rgba`` format. - - :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - check = BooleanProperty(False) - """ - If True, a checkmark is added to the left when touch to the chip. - - :attr:`check` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - radius = ListProperty( - [ - dp(12), - ] - ) - """ - Corner radius values. - - :attr:`radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `'[dp(12),]'`. - """ - - selected_chip_color = ColorProperty(None) - """ - The color of the chip that is currently selected in ``rgba`` format. - - :attr:`selected_chip_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _color = ColorProperty(None) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_color) - - def set_color(self, interval): - if not self.color: - self.color = self.theme_cls.primary_color - else: - self._color = self.color - - def on_icon(self, instance, value): - def remove_icon(interval): - self.remove_widget(self.ids.icon) - - if value == "": - self.icon = "checkbox-blank-circle" - Clock.schedule_once(remove_icon) - - def on_touch_down(self, touch): - if self.collide_point(*touch.pos): - self.dispatch("on_press") - self.dispatch("on_release") - md_choose_chip = self.parent - - if self.selected_chip_color: - Animation( - color=self.theme_cls.primary_dark - if not self.selected_chip_color - else self.selected_chip_color, - d=0.3, - ).start(self) - - if issubclass(md_choose_chip.__class__, MDChooseChip): - for chip in md_choose_chip.children: - if chip is not self: - chip.color = ( - self.theme_cls.primary_color - if not chip._color - else chip._color - ) - else: - chip.color = self.theme_cls.primary_color - - if self.check: - if not len(self.ids.box_check.children): - self.ids.box_check.add_widget( - MDIcon( - icon="check", - size_hint=(None, None), - size=("26dp", "26dp"), - font_size=sp(20), - ) - ) - else: - check = self.ids.box_check.children[0] - self.ids.box_check.remove_widget(check) - - -class MDChooseChip(MDStackLayout): - def add_widget(self, widget, index=0, canvas=None): - if isinstance(widget, MDChip): - return super().add_widget(widget) diff --git a/kivymd/uix/circularlayout.py b/kivymd/uix/circularlayout.py deleted file mode 100644 index 7c232bf..0000000 --- a/kivymd/uix/circularlayout.py +++ /dev/null @@ -1,208 +0,0 @@ -""" -Components/CircularLayout -========================= - -CircularLayout is a special layout that places widgets around a circle. - -MDCircularLayout ----------------- - -.. rubric:: Usage - -.. code-block:: - - from kivy.lang.builder import Builder - from kivy.uix.label import Label - - from kivymd.app import MDApp - - kv = ''' - Screen: - MDCircularLayout: - id: container - pos_hint: {"center_x": .5, "center_y": .5} - row_spacing: min(self.size)*0.1 - ''' - - - class Main(MDApp): - def build(self): - return Builder.load_string(kv) - - def on_start(self): - for x in range(1, 49): - self.root.ids.container.add_widget( - Label(text=f"{x}", color=[0, 0, 0, 1]) - ) - - - Main().run() - -""" - -__all__ = ("MDCircularLayout",) - -from math import atan2, cos, degrees, radians, sin - -from kivy.properties import BooleanProperty, NumericProperty - -from kivymd.uix.floatlayout import MDFloatLayout - - -class MDCircularLayout(MDFloatLayout): - - degree_spacing = NumericProperty(30) - """ - The space between children in degree. - - :attr:`degree_spacing` is an :class:`~kivy.properties.NumericProperty` - and defaults to `30`. - """ - - circular_radius = NumericProperty(None, allownone=True) - """ - Radius of circle. Radius will be the greatest value in the layout if `circular_radius` if not specified. - - :attr:`circular_radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `None`. - """ - - start_from = NumericProperty(60) - """ - The positon of first child in degree. - - :attr:`start_from` is an :class:`~kivy.properties.NumericProperty` - and defaults to `60`. - """ - - max_degree = NumericProperty(360) - """ - Maximum range in degree allowed for each row of widgets before jumping to the next row. - - :attr:`max_degree` is an :class:`~kivy.properties.NumericProperty` - and defaults to `360`. - """ - - circular_padding = NumericProperty("25dp") - """ - Padding between outer widgets and the edge of the biggest circle. - - :attr:`circular_padding` is an :class:`~kivy.properties.NumericProperty` - and defaults to `25dp`. - """ - - row_spacing = NumericProperty("50dp") - """ - Space between each row of widget. - - :attr:`row_spacing` is an :class:`~kivy.properties.NumericProperty` - and defaults to `50dp`. - """ - - clockwise = BooleanProperty(True) - """ - Direction of widgets in circular direction. - - :attr:`clockwise` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.bind( - row_spacing=self._update_layout, - ) - - def _update_layout(self, *args): - for index, child in enumerate(reversed(self.children)): - pos = self._point_on_circle( - self._calculate_radius(index), - self._calculate_degree(index), - ) - child.center = pos - - def do_layout(self, *largs, **kwargs): - self._update_layout() - return super().do_layout(*largs, **kwargs) - - def _max_per_row(self): - return int(self.max_degree / self.degree_spacing) - - def _calculate_radius(self, index): - """ - calculates the radius for given index - - """ - - idx = int(index / self._max_per_row()) - - if not self.circular_radius: - init_radius = ( - min([self.width / 2, self.height / 2]) - self.circular_padding - ) - else: - init_radius = self.circular_radius - - if idx != 0: - space = self.row_spacing * idx - init_radius -= space - - return init_radius - - def _calculate_degree(self, index): - """ calculates the angle for given index""" - if self.clockwise: - degree = self.start_from - index * self.degree_spacing - else: - degree = self.start_from + index * self.degree_spacing - - return degree - - def remove_widget(self, widget, **kwargs): - super().remove_widget(widget, **kwargs) - self._update_layout() - - def _point_on_circle(self, radius, degree): - angle = radians(degree) - center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2] - x = center[0] + (radius * cos(angle)) - y = center[1] + (radius * sin(angle)) - return [x, y] - - def get_angle(self, pos): - """ - Returns the angle of given pos - """ - - center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2] - (dx, dy) = (center[0] - pos[0], center[1] - pos[1]) - angle = degrees(atan2(float(dy), float(dx))) - angle += 180 - return angle - - -if __name__ == "__main__": - from kivy.lang.builder import Builder - from kivy.uix.label import Label - - from kivymd.app import MDApp - - kv = """ -Screen: - MDCircularLayout: - id: container - pos_hint: {"center_x": .5, "center_y": .5} - row_spacing: min(self.size)*0.1 - """ - - class Main(MDApp): - def build(self): - return Builder.load_string(kv) - - def on_start(self): - for x in range(1, 49): - self.root.ids.container.add_widget( - Label(text=f"{x}", color=[0, 0, 0, 1]) - ) - - Main().run() diff --git a/kivymd/uix/datatables.py b/kivymd/uix/datatables.py deleted file mode 100644 index fe74fe3..0000000 --- a/kivymd/uix/datatables.py +++ /dev/null @@ -1,1644 +0,0 @@ -""" -Components/DataTables -===================== - -.. seealso:: - - `Material Design spec, DataTables `_ - -.. rubric:: Data tables display sets of data across rows and columns. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-previous.png - :align: center - -Warnings ---------- - -.. warning:: - - Data tables are still far from perfect. The class is in constant change, - because of optimizations and bug fixes. - - If you find a bug or have an improvement you want to share, take some time - and share your discoveries with us over the main git repo. - - Any help is well appreciated. - -.. warning:: - - In versions prior to Kivy 2.1-dev0 exists an error in which is the table - has only one row in the current page, the table will only render one - column instead of the whole row. - -.. note:: - - MDDataTable allows developers to sort the data provided by column. This - happens thanks to the use of an external function that you can bind while - you're defining the table columns. - - Be aware that the sorting function must return a 2 value list in the - format of: - - `[Index, Sorted_Row_Data]` - - This is because the index list is needed to allow MDDataTable to keep track - of the selected rows. and, after the data is sorted, update the row - checkboxes. - -""" - -# Special thanks for the info - -# https://stackoverflow.com/questions/50219281/python-how-to-add-vertical-scroll-in-recycleview - -__all__ = ("MDDataTable",) - -from collections import defaultdict - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - DictProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.behaviors import ButtonBehavior, FocusBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.recyclegridlayout import RecycleGridLayout -from kivy.uix.recycleview import RecycleView -from kivy.uix.recycleview.layout import LayoutSelectionBehavior -from kivy.uix.recycleview.views import RecycleDataViewBehavior -from kivy.uix.scrollview import ScrollView - -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import HoverBehavior -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDIconButton -from kivymd.uix.menu import MDDropdownMenu -from kivymd.uix.tooltip import MDTooltip - -Builder.load_string( - """ -#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE -#:import StiffScrollEffect kivymd.stiffscroll.StiffScrollEffect - - - - orientation: "vertical" - - canvas.before: - Color: - rgba: - (\ - root.theme_cls.bg_darkest \ - if root.theme_cls.theme_style == "Light" else \ - root.theme_cls.bg_light \ - ) \ - if self.selected else root.theme_cls.bg_normal - Rectangle: - pos: self.pos - size: self.size - - on_press: if DEVICE_TYPE != "desktop": root.table.on_mouse_select(self) - on_enter: if DEVICE_TYPE == "desktop": root.table.on_mouse_select(self) - - MDBoxLayout: - id: box - padding: "8dp", "8dp", 0, "8dp" - spacing: "16dp" - - MDCheckbox: - id: check - size_hint: None, None - size: 0, 0 - opacity: 0 - - MDBoxLayout: - id: inner_box - - MDIcon: - id: icon - size_hint: None, None - pos_hint: {"center_y": 0.5} - size: ("24dp", "24dp") if root.icon else (0, 0) - icon: root.icon if root.icon else "" - theme_text_color: "Custom" - text_color: - root.icon_color if root.icon_color else \ - root.theme_cls.primary_color - - MDLabel: - id: label - text: " " + root.text - markup: True - color: - (1, 1, 1, 1) \ - if root.theme_cls.theme_style == "Dark" else \ - (0, 0, 0, 1) - - MDSeparator: - - - - orientation: "vertical" - size_hint_y: None - height: self.minimum_height - spacing: "4dp" - tooltip_text: root.text - - BoxLayout: - id: box - size_hint_y: None - height: lbl.height - - MDLabel: - id: lbl - text: " " + root.text - size_hint_y: None - height: self.texture_size[1] - bold: True - markup: True - color: - (1, 1, 1, 1) \ - if root.theme_cls.theme_style == "Dark" else \ - (0, 0, 0, 1) - - MDSeparator: - id: separator - - - - id: sort_btn - icon: "arrow-up" - pos_hint: {"center_y": 0.5} - #ripple_scale: .65 - size: [dp(24), dp(0)] - theme_text_color: "Custom" - text_color: self.theme_cls.secondary_text_color - opacity: 0 - - - - bar_width: 0 - do_scroll: False - size_hint: 1, None - height: header.height - - MDGridLayout: - id: header - rows: 1 - cols_minimum: root.cols_minimum - adaptive_size: True - padding: 0, "8dp", 0, 0 - - MDBoxLayout: - orientation: "vertical" - - MDBoxLayout: - id: box - padding: "8dp", "8dp", "4dp", 0 - spacing: "16dp" - - MDCheckbox: - id: check - size_hint: None, None - size: 0, 0 - opacity: 0 - on_release: root.table_data.select_all(self.state) - - CellHeader: - id: first_cell - - MDSeparator: - - - - data: root.recycle_data - data_first_cells: root.data_first_cells - key_viewclass: "viewclass" - # effect_cls: StiffScrollEffect - - TableRecycleGridLayout: - id: row_controller - key_selection: "selectable" - cols: root.total_col_headings - cols_minimum: root.cols_minimum - default_size: None, dp(52) - default_size_hint: 1, None - size_hint: None, None - height: self.minimum_height - width: self.minimum_width - multiselect: True - touch_multiselect: True - - - - adaptive_height: True - spacing: "8dp" - - MDLabel: - text: "Rows per page" - shorten: True - halign: "right" - font_style: "Caption" - color: - (1, 1, 1, 1) \ - if root.theme_cls.theme_style == "Dark" else \ - (0, 0, 0, 1) - - MDDropDownItem: - id: drop_item - pos_hint: {'center_y': .5} - text: str(root.table_data.rows_num) - font_size: "14sp" - on_release: root.table_data.open_pagination_menu() - - Widget: - size_hint_x: None - width: "32dp" if DEVICE_TYPE != "mobile" else "8dp" - - MDLabel: - id: label_rows_per_page - text: f"1-{root.table_data.rows_num} of {len(root.table_data.row_data)}" - size_hint: None, None - size: self.texture_size - -text_size: None, None - pos_hint: {"center_y": .5} - font_style: "Caption" - color: - (1, 1, 1, 1) \ - if root.theme_cls.theme_style == "Dark" else \ - (0, 0, 0, 1) - - MDIconButton: - id: button_back - icon: "chevron-left" - user_font_size: "20sp" if DEVICE_TYPE != "mobile" else "16dp" - ripple_scale: .5 if DEVICE_TYPE == "mobile" else 1 - pos_hint: {'center_y': .5} - disabled: True - md_bg_color_disabled: 0, 0, 0, 0 - on_release: root.table_data.set_next_row_data_parts("back") - - MDIconButton: - id: button_forward - icon: "chevron-right" - user_font_size: "20sp" if DEVICE_TYPE != "mobile" else "16dp" - ripple_scale: .5 if DEVICE_TYPE == "mobile" else 1 - pos_hint: {'center_y': .5} - disabled: True - md_bg_color_disabled: 0, 0, 0, 0 - on_release: root.table_data.set_next_row_data_parts("forward") - - - - - MDCard: - id: container - orientation: "vertical" - elevation: root.elevation - padding: "24dp", "24dp", "8dp", "8dp" -""" -) - - -class TableRecycleGridLayout( - FocusBehavior, LayoutSelectionBehavior, RecycleGridLayout -): - selected_row = NumericProperty(0) - table_data = ObjectProperty(None) - - def get_nodes(self): - nodes = self.get_selectable_nodes() - if self.nodes_order_reversed: - nodes = nodes[::-1] - if not nodes: - return None, None - - selected = self.selected_nodes - if not selected: # nothing selected, select the first - self.selected_row = 0 - self.select_row(nodes) - return None, None - - if len(nodes) == 1: # the only selectable node is selected already - return None, None - - index = selected[-1] - if index > len(nodes): - last = len(nodes) - else: - last = nodes.index(index) - self.clear_selection() - return last, nodes - - def select_next(self, instance): - """Select next row.""" - - self.table_data = instance - last, nodes = self.get_nodes() - if not nodes: - return - - if last == len(nodes) - 1: - self.selected_row = nodes[0] - else: - self.selected_row = nodes[last + 1] - - self.selected_row += self.table_data.total_col_headings - self.select_row(nodes) - - def select_current(self, instance): - """Select current row.""" - - self.table_data = instance - last, nodes = self.get_nodes() - if not nodes: - return - - self.select_row(nodes) - - def select_row(self, nodes): - col = self.table_data.recycle_data[self.selected_row]["range"] - for x in range(col[0], col[1] + 1): - self.select_node(nodes[x]) - - -class CellRow( - ThemableBehavior, - RecycleDataViewBehavior, - HoverBehavior, - ButtonBehavior, - BoxLayout, -): - text = StringProperty() # row text - table = ObjectProperty() # - index = None - icon = StringProperty() - icon_copy = icon - icon_color = ColorProperty(None) - selected = BooleanProperty(False) - selectable = BooleanProperty(True) - - def __init__(self, **kwargs): - super(CellRow, self).__init__(**kwargs) - self.ids.check.bind(active=self.select_check) - self.ids.check.bind(active=self.notify_checkbox_click) - - def notify_checkbox_click(self, instance, active): - self.table.get_select_row(self.index) - - def on_icon(self, instance, value): - self.icon_copy = value - - def on_table(self, instance, table): - """Sets padding/spacing to zero if no checkboxes are used for rows.""" - - if not table.check: - self.ids.box.padding = 0 - self.ids.box.spacing = 0 - - def refresh_view_attrs(self, table_data, index, data): - """ - Called by the :class:`RecycleAdapter` when the view is initially - populated with the values from the `data` dictionary for this item. - - Any pos or size info should be removed because they are set - subsequently with :attr:`refresh_view_layout`. - - :Parameters: - - `table_data`: :class:`TableData` instance - The :class:`TableData` that caused the update. - `data`: dict - The data dict used to populate this view. - """ - - self.index = index - return super().refresh_view_attrs(table_data, index, data) - - def on_touch_down(self, touch): - if super().on_touch_down(touch): - if self.table._parent: - self.table._parent.dispatch("on_row_press", self) - return True - - def apply_selection(self, table_data, index, is_selected): - """Called when list items of table appear on the screen.""" - - self.selected = is_selected - - # Fixes cloning of icons. - ic = table_data.recycle_data[index].get("icon", None) - cell_row_obj = table_data.view_adapter.get_visible_view(index) - - if not ic: - cell_row_obj.icon = "" - else: - cell_row_obj.icon = cell_row_obj.icon_copy - - # Set checkboxes. - if table_data.check: - if self.index in table_data.data_first_cells: - self.ids.check.size = (dp(32), dp(32)) - self.ids.check.opacity = 1 - self.ids.box.spacing = dp(16) - self.ids.box.padding[0] = dp(8) - else: - self.ids.check.size = (0, 0) - self.ids.check.opacity = 0 - self.ids.box.spacing = 0 - self.ids.box.padding[0] = 0 - - # Set checkboxes state. - if table_data._rows_number in table_data.current_selection_check: - for index in table_data.current_selection_check[ - table_data._rows_number - ]: - if ( - self.index - in table_data.current_selection_check[ - table_data._rows_number - ] - ): - self.change_check_state_no_notif("down") - else: - self.change_check_state_no_notif("normal") - else: - self.change_check_state_no_notif("normal") - - def change_check_state_no_notif(self, new_state): - checkbox = self.ids.check - checkbox.unbind(active=self.notify_checkbox_click) - checkbox.state = new_state - checkbox.bind(active=self.notify_checkbox_click) - - def _check_all(self, state): - """Checks if all checkboxes are in same state""" - - if state == "down" and self.table.check_all(state): - self.table.table_header.ids.check.state = "down" - else: - self.table.table_header.ids.check.state = "normal" - - def select_check(self, instance, active): - """Called upon activation/deactivation of the checkbox.""" - - if active: - if ( - self.table._rows_number - not in self.table.current_selection_check - ): - self.table.current_selection_check[self.table._rows_number] = [] - if ( - self.index - not in self.table.current_selection_check[ - self.table._rows_number - ] - ): - self.table.current_selection_check[ - self.table._rows_number - ].append(self.index) - else: - if self.table._rows_number in self.table.current_selection_check: - if ( - self.index - in self.table.current_selection_check[ - self.table._rows_number - ] - and not active - ): - self.table.current_selection_check[ - self.table._rows_number - ].remove(self.index) - - -class SortButton(MDIconButton): - pass - - -class CellHeader(MDTooltip, BoxLayout): - text = StringProperty() # column text - sort_action = ObjectProperty() - table_data = ObjectProperty() - is_sorted = BooleanProperty(False) - sorted_order = StringProperty() - - def __init__(self, *args, **kwargs): - super().__init__(**kwargs) - - if self.sort_action: - box = self.ids.box - ib = SortButton() - ib.bind(on_release=self._sort_release) - - if self.is_sorted: - ib.icon = ( - "arrow-down" if self.sorted_order == "ASC" else "arrow-up" - ) - ib.size = [dp(24), dp(24)] - ib.opacity = 1 - else: - self.bind(on_enter=self.set_sort_btn) - self.bind(on_leave=self.set_sort_btn) - - box.add_widget(ib, index=1) - - def _sort_release(self, inst): - inst.icon = "arrow-down" if inst.icon == "arrow-up" else "arrow-up" - - if not self.parent.parent.col_with_sort: - c = self.parent.children - col_with_sort = [ - each - for each in c - if each.ids.get("box", None) and len(each.ids.box.children) == 2 - ] - self.parent.parent.col_with_sort = col_with_sort - else: - col_with_sort = self.parent.parent.col_with_sort - - for each in col_with_sort: - if each == self: - self.unbind(on_enter=self.set_sort_btn) - self.unbind(on_leave=self.set_sort_btn) - else: - btn = each.ids.box.children[-1] - btn.size = [dp(24), dp(0)] - btn.opacity = 0 - each.bind(on_enter=each.set_sort_btn) - each.bind(on_leave=each.set_sort_btn) - - if self.sort_action: - if not self.table_data: - th = self.parent.parent - self.table_data = th.table_data - - indices, sorted_data = self.sort_action(self.table_data.row_data) - - if not sorted_data: - return - - if inst.icon == "arrow-down": - sorted_data = sorted_data[::-1] - indices = indices[::-1] - - self.table_data.row_data = sorted_data - self.table_data.on_rows_num(self, self.table_data.rows_num) - self.restore_checks(dict(zip(indices, range(len(indices))))) - self.table_data.set_next_row_data_parts("reset") - self.table_data.cell_row_obj_dict = {} - self.table_data.table_header.ids.check.state = "normal" - - def restore_checks(self, indices): - curr_checks = self.table_data.current_selection_check - rows_num = self.table_data.rows_num - columns = self.table_data.total_col_headings - - new_checks = defaultdict(list) - for i, x in enumerate(curr_checks): - for j, y in enumerate(curr_checks[x]): - new_page = (indices[y // columns + x * rows_num]) // rows_num - new_indice = ( - (indices[y // columns + x * rows_num]) % rows_num - ) * columns - new_checks[new_page].append(new_indice) - self.table_data.current_selection_check = dict(new_checks) - - def set_sort_btn(self, instance): - btn = instance.ids.box.children[-1] - if btn.opacity: - btn.size = [dp(24), dp(0)] - btn.opacity = 0 - else: - btn.size = [dp(24), dp(24)] - btn.opacity = 1 - - -class TableHeader(ScrollView): - table_data = ObjectProperty() # - column_data = ListProperty() # MDDataTable.column_data - col_headings = ListProperty() # column names list - sorted_on = StringProperty() - sorted_order = StringProperty() - # kivy.uix.gridlayout.GridLayout.cols_minimum - cols_minimum = DictProperty() - col_with_sort = [] # store cols which contain sort functions - - def __init__(self, **kwargs): - super().__init__(**kwargs) - # Create cells. - for i, col_heading in enumerate(self.column_data): - self.cols_minimum[i] = col_heading[1] * 5 - self.col_headings.append(col_heading[0]) - if i: - self.ids.header.add_widget( - ( - CellHeader( - text=col_heading[0], - sort_action=col_heading[2], - width=self.cols_minimum[i], - table_data=self.table_data, - is_sorted=(col_heading[0] == self.sorted_on), - sorted_order=self.sorted_order, - ) - if len(col_heading) == 3 - else CellHeader( - text=col_heading[0], - width=self.cols_minimum[i], - table_data=self.table_data, - ) - ) - ) - else: - # Sets the text in the first cell. - self.ids.first_cell.text = col_heading[0] - self.ids.first_cell.ids.separator.height = 0 - self.ids.first_cell.width = self.cols_minimum[i] - - def on_table_data(self, instance, value): - """Sets the checkbox in the first cell.""" - - if self.table_data.check: - self.ids.check.size = (dp(32), dp(32)) - self.ids.check.opacity = 1 - else: - self.ids.box.padding[0] = 0 - self.ids.box.spacing = 0 - - -class TableData(RecycleView): - recycle_data = ListProperty() # kivy.uix.recycleview.RecycleView.data - data_first_cells = ListProperty() # list of first row cells - row_data = ListProperty() # MDDataTable.row_data - total_col_headings = NumericProperty(0) # TableHeader.col_headings - cols_minimum = DictProperty() # TableHeader.cols_minimum - table_header = ObjectProperty() # - pagination_menu = ObjectProperty() # - pagination = ObjectProperty() # - check = ObjectProperty() # MDDataTable.check - rows_num = NumericProperty() # number of rows displayed on the table page - # Open or close the menu for selecting the number of rows displayed - # on the table page. - pagination_menu_open = BooleanProperty(False) - # List of indexes of marked checkboxes. - current_selection_check = DictProperty() - cell_row_obj_dict = {} - - _parent = ObjectProperty() - _rows_number = NumericProperty(0) - _rows_num = NumericProperty() - _current_value = NumericProperty(1) - _to_value = NumericProperty() - _row_data_parts = ListProperty() - - def __init__(self, table_header, **kwargs): - super().__init__(**kwargs) - self.table_header = table_header - self.total_col_headings = len(table_header.col_headings) - self.cols_minimum = table_header.cols_minimum - self.set_row_data() - Clock.schedule_once(self.set_default_first_row, 0) - - def get_select_row(self, index): - """Returns the current row with all elements.""" - - row = [] - for data in self.recycle_data: - if index in data["range"]: - row.append(data["text"]) - self._parent.dispatch("on_check_press", row) - self._get_row_checks() # update the dict - - def set_default_first_row(self, dt): - """Set default first row as selected.""" - - self.ids.row_controller.select_next(self) - - def set_row_data(self): - data = [] - low = 0 - high = self.total_col_headings - 1 - self.recycle_data = [] - self.data_first_cells = [] - - if self._row_data_parts: - # for row in self.row_data: - for row in self._row_data_parts[self._rows_number]: - for i in range(len(row)): - data.append([row[i], row[0], [low, high]]) - low += self.total_col_headings - high += self.total_col_headings - - for j, x in enumerate(data): - if x[0] == x[1]: - self.data_first_cells.append(x[2][0]) - self.recycle_data.append( - { - "text": str(x[0]), - "Index": str(j), - "range": x[2], - "selectable": True, - "viewclass": "CellRow", - "table": self, - } - ) - else: - r_data = { - "Index": str(j), - "range": x[2], - "selectable": True, - "viewclass": "CellRow", - "table": self, - } - - if ( - isinstance(x[0], tuple) or isinstance(x[0], list) - ) and len(x[0]) == 3: - r_data["icon"] = x[0][0] - r_data["icon_color"] = x[0][1] - r_data["text"] = str(x[0][2]) - - self.recycle_data.append(r_data) - - elif ( - isinstance(x[0], tuple) or isinstance(x[0], list) - ) and len(x[0]) == 2: - r_data["icon"] = x[0][0] - r_data["text"] = str(x[0][1]) - - self.recycle_data.append(r_data) - - else: - r_data["text"] = str(x[0]) - self.recycle_data.append(r_data) - - if not self.table_header.column_data: - raise ValueError("Set value for column_data in class TableData") - self.data_first_cells.append(self.table_header.column_data[0][0]) - - def set_text_from_of(self, direction): - """Sets the text of the numbers of displayed pages in table.""" - - if self.pagination: - if direction == "reset": - self._current_value = 1 - self._to_value = len(self._row_data_parts[self._rows_number]) - elif direction == "forward": - if ( - len(self._row_data_parts[self._rows_number]) - < self._to_value - ): - self._current_value = self._current_value + self.rows_num - else: - self._current_value = self._current_value + len( - self._row_data_parts[self._rows_number] - ) - self._to_value = self._to_value + len( - self._row_data_parts[self._rows_number] - ) - if direction == "back": - self._current_value = self._current_value - len( - self._row_data_parts[self._rows_number] - ) - self._to_value = self._to_value - len( - self._row_data_parts[self._rows_number + 1] - ) - if direction == "increment": - self._current_value = 1 - self._to_value = self.rows_num + self._current_value - 1 - - self.pagination.ids.label_rows_per_page.text = ( - f"{self._current_value}-{self._to_value} " - f"of {len(self.row_data)}" - ) - - def select_all(self, state): - """Sets the checkboxes of all rows to the active/inactive position.""" - - for i in range(0, len(self.recycle_data), self.total_col_headings): - cell_row_obj = cell_row_obj = self.view_adapter.get_visible_view(i) - self.cell_row_obj_dict[i] = cell_row_obj - self.on_mouse_select(cell_row_obj) - cell_row_obj.ids.check.state = state - - if state == "down": - # select all checks on all pages - rows_num = self.rows_num - columns = self.total_col_headings - full_pages = len(self.row_data) // self.rows_num - left_over_rows = len(self.row_data) % self.rows_num - - new_checks = {} - for page in range(full_pages): - new_checks[page] = list(range(0, rows_num * columns, columns)) - - if left_over_rows: - new_checks[full_pages] = list( - range(0, left_over_rows * columns, columns) - ) - - self.current_selection_check = new_checks - return - - # resets all checks on all pages - self.current_selection_check = {} - - def check_all(self, state): - """Checks if checkboxes of all rows are in the same state""" - - tmp = [] - for i in range(0, len(self.recycle_data), self.total_col_headings): - if self.cell_row_obj_dict.get(i, None): - cell_row_obj = self.cell_row_obj_dict[i] - else: - cell_row_obj = self.view_adapter.get_visible_view(i) - if cell_row_obj: - self.cell_row_obj_dict[i] = cell_row_obj - if cell_row_obj: - tmp.append(cell_row_obj.ids.check.state == state) - return all(tmp) - - def _get_row_checks(self): - """ - Returns all rows that are checked - """ - - tmp = [] - for i in range(0, len(self.recycle_data), self.total_col_headings): - if self.cell_row_obj_dict.get(i, None): - cell_row_obj = self.cell_row_obj_dict[i] - else: - cell_row_obj = self.view_adapter.get_visible_view(i) - if cell_row_obj: - self.cell_row_obj_dict[i] = cell_row_obj - - if cell_row_obj and cell_row_obj.ids.check.state == "down": - idx = cell_row_obj.index - row = [] - for data in self.recycle_data: - if idx in data["range"]: - row.append(data["text"]) - - tmp.append(row) - return tmp - - def close_pagination_menu(self, *args): - """Called when the pagination menu window is closed.""" - - self.pagination_menu_open = False - - def open_pagination_menu(self): - """Open pagination menu window.""" - - if self.pagination_menu.items: - self.pagination_menu_open = True - self.pagination_menu.open() - - def set_number_displayed_lines(self, text_item): - """ - Called when the user sets the number of pages displayed - in the table. - """ - - self.rows_num = int(text_item) - self.set_next_row_data_parts("reset") - self.set_text_from_of("reset") - - def set_next_row_data_parts(self, direction): - """Called when switching the pages of the table.""" - - if direction == "reset": - self._rows_number = 0 - self.pagination.ids.button_back.disabled = True - self.pagination.ids.button_forward.disabled = False - elif direction == "forward": - self._rows_number += 1 - self.pagination.ids.button_back.disabled = False - elif direction == "back": - self._rows_number -= 1 - self.pagination.ids.button_forward.disabled = False - - self.set_row_data() - self.set_text_from_of(direction) - - if self._to_value == len(self.row_data): - self.pagination.ids.button_forward.disabled = True - if self._current_value == 1: - self.pagination.ids.button_back.disabled = True - - def on_mouse_select(self, instance): - """Called on the ``on_enter`` event of the :class:`~CellRow` class.""" - - if not self.pagination_menu_open: - if self.ids.row_controller.selected_row != instance.index: - self.ids.row_controller.selected_row = instance.index - self.ids.row_controller.select_current(self) - - def on_rows_num(self, instance, value): - if not self._to_value: - self._to_value = value - - self._rows_number = 0 - self._row_data_parts = list( - self._split_list_into_equal_parts(self.row_data, value) - ) - - def on_pagination(self, instance, value): - if self._to_value < len(self.row_data): - self.pagination.ids.button_forward.disabled = False - - def _split_list_into_equal_parts(self, lst, parts): - for i in range(0, len(lst), parts): - yield lst[i : i + parts] - - # def on_pagination(self, instance_table, instance_pagination): - # if len(self._row_data_parts) <= self._to_value: - # instance_pagination.ids.button_forward.disabled = True - - -class TablePagination(ThemableBehavior, MDBoxLayout): - """Pagination Container.""" - - table_data = ObjectProperty() # - - -class MDDataTable(ThemableBehavior, AnchorLayout): - """ - :Events: - :attr:`on_row_press` - Called when a table row is clicked. - :attr:`on_check_press` - Called when the check box in the table row is checked. - - .. rubric:: Use events as follows - - .. code-block:: python - - from kivy.metrics import dp - from kivy.uix.anchorlayout import AnchorLayout - from kivy.lang import Builder - from kivy.logger import Logger - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - - kv = ''' - BoxLayout: - orientation: "vertical" - BoxLayout: - id:button_tab - size_hint_y:None - height: dp(48) - - MDFlatButton: - text: "Hello <3" - on_release: - app.update_row_data() - - BoxLayout: - id:body - - ''' - - class Example(MDApp): - def build(self): - self.data_tables = MDDataTable( - # MDDataTable allows the use of size_hint - size_hint=(0.8, 0.7), - use_pagination=True, - check=True, - column_data=[ - ("No.", dp(30)), - ("Status", dp(30)), - ("Signal Name", dp(60), self.sort_on_signal), - ("Severity", dp(30)), - ("Stage", dp(30)), - ("Schedule", dp(30), self.sort_on_schedule), - ("Team Lead", dp(30), self.sort_on_team) - ], - row_data=[ - ("1", ("alert", [255 / 256, 165 / 256, 0, 1], "No Signal"), - "Astrid: NE shared managed", "Medium", "Triaged", "0:33", - "Chase Nguyen"), - ("2", ("alert-circle", [1, 0, 0, 1], "Offline"), - "Cosmo: prod shared ares", "Huge", "Triaged", "0:39", - "Brie Furman"), - ("3", ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online"), "Phoenix: prod shared lyra-lists", "Minor", - "Not Triaged", "3:12", "Jeremy lake"), - ("4", ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online"), "Sirius: NW prod shared locations", - "Negligible", - "Triaged", "13:18", "Angelica Howards"), - ("5", ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online"), "Sirius: prod independent account", - "Negligible", - "Triaged", "22:06", "Diane Okuma"), - - ], - sorted_on="Schedule", - sorted_order="ASC", - elevation=2 - ) - self.data_tables.bind(on_row_press=self.on_row_press) - self.data_tables.bind(on_check_press=self.on_check_press) - root = Builder.load_string(kv) - root.ids.body.add_widget(self.data_tables) - return root - - def update_row_data(self, *dt): - self.data_tables.row_data = [ - ( - "21", - ("alert", [255 / 256, 165 / 256, 0, 1], "No Signal"), - "Astrid: NE shared managed", - "Medium", - "Triaged", - "0:33", - "Chase Nguyen" - ), - ("32", ("alert-circle", [1, 0, 0, 1], "Offline"), - "Cosmo: prod shared ares", "Huge", "Triaged", "0:39", - "Brie Furman"), - ("43", ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online"), "Phoenix: prod shared lyra-lists", "Minor", - "Not Triaged", "3:12", "Jeremy lake"), - ("54", ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online"), "Sirius: NW prod shared locations", - "Negligible", - "Triaged", "13:18", "Angelica Howards"), - ("85", ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online"), "Sirius: prod independent account", - "Negligible", - "Triaged", "22:06", "Diane Okuma"), - ("85", ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online"), "Sirius: prod independent account", - "Negligible", - "Triaged", "22:06", "John Sakura"), - ] - - - def on_row_press(self, instance_table, instance_row): - '''Called when a table row is clicked.''' - - print(instance_table, instance_row) - - def on_check_press(self, instance_table, current_row): - '''Called when the check box in the table row is checked.''' - - print(instance_table, current_row) - - # Sorting Methods: - # Since the # 914 Pull request, the sorting method requires you to sort - # out the indexes of each data value for the support of selections - - # The most common method to do this is with the use of the bult-in function - # zip and enimerate, see the example below for more info. - - # the result given by these funcitons must be a list in the format of - # [Indexes, Sorted_Row_Data] - - - def sort_on_signal(self, data): - return zip( - *sorted( - enumerate(data), - key=lambda l: l[1][2] - ) - ) - - def sort_on_schedule(self, data): - return zip( - *sorted( - enumerate(data), - key=lambda l: sum( - [int(l[1][-2].split(":")[0])*60, - int(l[1][-2].split(":")[1])] - ) - ) - ) - - def sort_on_team(self, data): - return zip( - *sorted( - enumerate(data), - key=lambda l: l[1][-1] - ) - ) - - Example().run() - """ - - column_data = ListProperty() - """ - Data for header columns. - - .. code-block:: python - - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - from kivy.uix.anchorlayout import AnchorLayout - - - class Example(MDApp): - def build(self): - layout = AnchorLayout() - self.data_tables = MDDataTable( - size_hint=(0.7, 0.6), - use_pagination=True, - check=True, - - # name column, width column, sorting function column(optional) - column_data=[ - ("No.", dp(30)), - ("Status", dp(30)), - ("Signal Name", dp(60)), - ("Severity", dp(30)), - ("Stage", dp(30)), - ("Schedule", dp(30), lambda *args: print("Sorted using Schedule")), - ("Team Lead", dp(30)), - ], - ) - layout.add_widget(self.data_tables) - return layout - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-column-data.png - :align: center - - :attr:`column_data` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - - .. note:: - The functions which will be called for sorting must accept a data argument and return the sorted data. - - Incoming data format will be similar to the provided row_data except that it'll be all list instead of tuple like below. - Any icon provided initially will also be there in this data so handle accordingly. - - .. code-block:: python - - [ - ['1', ['icon', 'No Signal'], 'Astrid: NE shared managed', 'Medium', 'Triaged', '0:33', 'Chase Nguyen'], - ['2', 'Offline', 'Cosmo: prod shared ares', 'Huge', 'Triaged', '0:39', 'Brie Furman'], - ['3', 'Online', 'Phoenix: prod shared lyra-lists', 'Minor', 'Not Triaged', '3:12', 'Jeremy lake'], - ['4', 'Online', 'Sirius: NW prod shared locations', 'Negligible', 'Triaged', '13:18', 'Angelica Howards'], - ['5', 'Online', 'Sirius: prod independent account', 'Negligible', 'Triaged', '22:06', 'Diane Okuma'] - ] - - You must sort inner lists in ascending order and return the sorted data in the same format. - """ - - row_data = ListProperty() - """ - Data for rows. To add icon in addition to a row data, include a tuple with - This property stores the row data used to display each row in the DataTable - To show an icon inside a column in a row, use the folowing format in the - row's columns. - - Format: - - `("MDicon-name", [icon color in rgba], "Column Value")` - - Example: - - .. code-block:: python - [...] - row_data = [ - - # row 1 - [ - "value 1", - "value 2", - # the third value will have an icon inside the box - ["home", [128/255, 48/255, 76/255, 1], "Offie" ] - ], - - # row 2 - [ - "value 1", - "value 2", - # the third value will have an icon inside the box - ["git", [1, 0.1, 0.1, 1], "Git Repo" ] - ] - ] - - For a more complex example see below. - - .. code-block:: python - - from kivy.metrics import dp - from kivy.uix.anchorlayout import AnchorLayout - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - - - class Example(MDApp): - def build(self): - layout = AnchorLayout() - data_tables = MDDataTable( - size_hint=(0.9, 0.6), - column_data=[ - ("Column 1", dp(20)), - ("Column 2", dp(30)), - ("Column 3", dp(50), self.sort_on_col_3), - ("Column 4", dp(30)), - ("Column 5", dp(30)), - ("Column 6", dp(30)), - ("Column 7", dp(30), self.sort_on_col_2), - ], - row_data=[ - # The number of elements must match the length - # of the `column_data` list. - ( - "1", - ("alert", [255 / 256, 165 / 256, 0, 1], "No Signal"), - "Astrid: NE shared managed", - "Medium", - "Triaged", - "0:33", - "Chase Nguyen", - ), - ( - "2", - ("alert-circle", [1, 0, 0, 1], "Offline"), - "Cosmo: prod shared ares", - "Huge", - "Triaged", - "0:39", - "Brie Furman", - ), - ( - "3", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Phoenix: prod shared lyra-lists", - "Minor", - "Not Triaged", - "3:12", - "Jeremy lake", - ), - ( - "4", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Sirius: NW prod shared locations", - "Negligible", - "Triaged", - "13:18", - "Angelica Howards", - ), - ( - "5", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Sirius: prod independent account", - "Negligible", - "Triaged", - "22:06", - "Diane Okuma", - ), - ], - ) - layout.add_widget(data_tables) - return layout - - def sort_on_col_3(self, data): - return zip( - *sorted( - enumerate(data), - key=lambda l: l[1][3] - ) - ) - - def sort_on_col_2(self, data): - return zip( - *sorted( - enumerate(data), - key=lambda l: l[1][-1] - ) - ) - - Example().run() - - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-row-data.png - :align: center - - :attr:`row_data` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - sorted_on = StringProperty() - """ - Column name upon which the data is already sorted. - - If the table data is showing an already sorted data then this can be used - to indicate upon which column the data is sorted. - - :attr:`sorted_on` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - sorted_order = OptionProperty("ASC", options=["ASC", "DSC"]) - """ - Order of already sorted data. Must be one of `'ASC'` for ascending or - `'DSC'` for descending order. - - :attr:`sorted_order` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'ASC'`. - """ - - check = BooleanProperty(False) - """ - Use or not use checkboxes for rows. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.gif - :align: center - - :attr:`check` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - use_pagination = BooleanProperty(False) - """ - Use page pagination for table or not. - - .. code-block:: python - - from kivy.metrics import dp - from kivy.uix.anchorlayout import AnchorLayout - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - - - class Example(MDApp): - def build(self): - layout = AnchorLayout() - data_tables = MDDataTable( - size_hint=(0.9, 0.6), - use_pagination=True, - column_data=[ - ("No.", dp(30)), - ("Column 1", dp(30)), - ("Column 2", dp(30)), - ("Column 3", dp(30)), - ("Column 4", dp(30)), - ("Column 5", dp(30)), - ], - row_data=[ - (f"{i + 1}", "1", "2", "3", "4", "5") for i in range(50) - ], - ) - layout.add_widget(data_tables) - return layout - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination.png - :align: center - - :attr:`use_pagination` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - elevation = NumericProperty(8) - """ - Table elevation. - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `8`. - """ - - rows_num = NumericProperty(5) - """ - The number of rows displayed on one page of the table. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination.gif - :align: center - - :attr:`rows_num` is an :class:`~kivy.properties.NumericProperty` - and defaults to `10`. - """ - - pagination_menu_pos = OptionProperty("center", options=["center", "auto"]) - """ - Menu position for selecting the number of displayed rows. - Available options are `'center'`, `'auto'`. - - .. rubric:: Center - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-center.png - :align: center - - .. rubric:: Auto - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-auto.png - :align: center - - :attr:`pagination_menu_pos` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'center'`. - """ - - pagination_menu_height = NumericProperty("140dp") - """ - Menu height for selecting the number of displayed rows. - - .. rubric:: 140dp - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-140.png - :align: center - - .. rubric:: 240dp - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-240.png - :align: center - - :attr:`pagination_menu_height` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'140dp'`. - """ - - background_color = ColorProperty([0, 0, 0, 0]) - """ - Background color in the format (r, g, b, a). - See :attr:`~kivy.uix.modalview.ModalView.background_color`. - - Use markup strings - ------------------ - - .. code-block:: python - - from kivy.metrics import dp - from kivy.uix.anchorlayout import AnchorLayout - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - - - class Example(MDApp): - def build(self): - layout = AnchorLayout() - data_tables = MDDataTable( - size_hint=(0.9, 0.6), - use_pagination=True, - column_data=[ - ("No.", dp(30)), - ("Column 1", dp(30)), - ("[color=#52251B]Column 2[/color]", dp(30)), - ("Column 3", dp(30)), - ("[size=24][color=#C042B8]Column 4[/color][/size]", dp(30)), - ("Column 5", dp(30)), - ], - row_data=[ - ( - f"{i + 1}", - "[color=#297B50]1[/color]", - "[color=#C552A1]2[/color]", - "[color=#6C9331]3[/color]", - "4", - "5", - ) - for i in range(50) - ], - ) - layout.add_widget(data_tables) - return layout - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/datatables-use-markup-strings.png - :align: center - - :attr:`background_color` is a :class:`~kivy.properties.ColorProperty` and - defaults to [0, 0, 0, 0]. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.header = TableHeader( - column_data=self.column_data, - sorted_on=self.sorted_on, - sorted_order=self.sorted_order, - ) - self.table_data = TableData( - self.header, - row_data=self.row_data, - check=self.check, - rows_num=self.rows_num, - _parent=self, - ) - self.register_event_type("on_row_press") - self.register_event_type("on_check_press") - self.pagination = TablePagination(table_data=self.table_data) - self.table_data.pagination = self.pagination - self.header.table_data = self.table_data - self.table_data.fbind("scroll_x", self._scroll_with_header) - self.ids.container.add_widget(self.header) - self.ids.container.add_widget(self.table_data) - if self.use_pagination: - self.ids.container.add_widget(self.pagination) - Clock.schedule_once(self.create_pagination_menu, 0.5) - self.bind(row_data=self.update_row_data) - - def update_row_data(self, instance, value): - """ - Called when a the widget data must be updated. - - Remember that this is a heavy function. since the whole data set must - be updated. you can get better results calling this metod with in a - coroutine. - """ - - self.table_data.row_data = value - self.table_data.on_rows_num(self, self.table_data.rows_num) - # Set cursors to 0 - self.table_data._rows_number = 0 - self.table_data._current_value = 1 - - if len(value) < self.table_data.rows_num: - self.table_data._to_value = len(value) - self.table_data.pagination.ids.button_forward.disabled = True - else: - self.table_data._to_value = self.table_data.rows_num - self.table_data.pagination.ids.button_forward.disabled = False - - self.table_data.set_next_row_data_parts("") - self.pagination.ids.button_back.disabled = True - Clock.schedule_once(self.create_pagination_menu, 0.5) - - def on_row_press(self, *args): - """Called when a table row is clicked.""" - - def on_check_press(self, *args): - """Called when the check box in the table row is checked.""" - - def get_row_checks(self): - """Returns all rows that are checked.""" - - return self.table_data._get_row_checks() - - def create_pagination_menu(self, interval): - menu_items = [ - { - "text": f"{i}", - "viewclass": "OneLineListItem", - "height": dp(56), - "on_release": lambda x=f"{i}": self.table_data.set_number_displayed_lines( - x - ), - } - for i in range( - self.rows_num, len(self.row_data) // 2, self.rows_num - ) - ] - pagination_menu = MDDropdownMenu( - caller=self.pagination.ids.drop_item, - items=menu_items, - position=self.pagination_menu_pos, - max_height=self.pagination_menu_height, - width_mult=2, - ) - pagination_menu.bind( - on_dismiss=self.table_data.close_pagination_menu, - ) - self.table_data.pagination_menu = pagination_menu - - def _scroll_with_header(self, instance, value): - self.header.scroll_x = value diff --git a/kivymd/uix/dialog.py b/kivymd/uix/dialog.py deleted file mode 100644 index 269c2ff..0000000 --- a/kivymd/uix/dialog.py +++ /dev/null @@ -1,624 +0,0 @@ -""" -Components/Dialog -================= - -.. seealso:: - - `Material Design spec, Dialogs `_ - - -.. rubric:: Dialogs inform users about a task and can contain critical - information, require decisions, or involve multiple tasks. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialogs.png - :align: center - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.button import MDFlatButton - from kivymd.uix.dialog import MDDialog - - KV = ''' - MDFloatLayout: - - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_alert_dialog() - ''' - - - class Example(MDApp): - dialog = None - - def build(self): - return Builder.load_string(KV) - - def show_alert_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - text="Discard draft?", - buttons=[ - MDFlatButton( - text="CANCEL", text_color=self.theme_cls.primary_color - ), - MDFlatButton( - text="DISCARD", text_color=self.theme_cls.primary_color - ), - ], - ) - self.dialog.open() - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/alert-dialog.png - :align: center -""" - -__all__ = ("MDDialog",) - -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 ( - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.modalview import ModalView - -from kivymd.material_resources import DEVICE_TYPE -from kivymd.theming import ThemableBehavior -from kivymd.uix.button import BaseButton -from kivymd.uix.card import MDSeparator -from kivymd.uix.list import BaseListItem - -Builder.load_string( - """ -#:import images_path kivymd.images_path - - - - background: '{}/transparent.png'.format(images_path) - - canvas.before: - PushMatrix - RoundedRectangle: - pos: self.pos - size: self.size - radius: root.radius - Scale: - origin: self.center - x: root._scale_x - y: root._scale_y - canvas.after: - PopMatrix - - - - - MDCard: - id: container - orientation: "vertical" - size_hint_y: None - height: self.minimum_height - elevation: 24 - padding: "24dp", "24dp", "8dp", "8dp" - radius: root.radius - md_bg_color: - root.theme_cls.bg_dark \ - if not root.md_bg_color else root.md_bg_color - - MDLabel: - id: title - text: root.title - font_style: "H6" - bold: True - markup: True - size_hint_y: None - height: self.texture_size[1] - valign: "top" - - BoxLayout: - id: spacer_top_box - size_hint_y: None - height: root._spacer_top - - MDLabel: - id: text - text: root.text - font_style: "Body1" - theme_text_color: "Custom" - text_color: root.theme_cls.disabled_hint_text_color - size_hint_y: None - height: self.texture_size[1] - markup: True - - ScrollView: - id: scroll - size_hint_y: None - height: root._scroll_height - - MDGridLayout: - id: box_items - adaptive_height: True - cols: 1 - - BoxLayout: - id: spacer_bottom_box - size_hint_y: None - height: self.minimum_height - - AnchorLayout: - id: root_button_box - size_hint_y: None - height: "52dp" - anchor_x: "right" - - MDBoxLayout: - id: button_box - adaptive_size: True - spacing: "8dp" -""" -) - - -class BaseDialog(ThemableBehavior, ModalView): - radius = ListProperty([7, 7, 7, 7]) - """ - Dialog corners rounding value. - - .. code-block:: python - - [...] - self.dialog = MDDialog( - text="Oops! Something seems to have gone wrong!", - radius=[20, 7, 20, 7], - ) - [...] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-radius.png - :align: center - - :attr:`radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[7, 7, 7, 7]`. - """ - - _scale_x = NumericProperty(1) - _scale_y = NumericProperty(1) - - -class MDDialog(BaseDialog): - title = StringProperty() - """ - Title dialog. - - .. code-block:: python - - [...] - self.dialog = MDDialog( - title="Reset settings?", - buttons=[ - MDFlatButton( - text="CANCEL", text_color=self.theme_cls.primary_color - ), - MDFlatButton( - text="ACCEPT", text_color=self.theme_cls.primary_color - ), - ], - ) - [...] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-title.png - :align: center - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - text = StringProperty() - """ - Text dialog. - - .. code-block:: python - - [...] - self.dialog = MDDialog( - title="Reset settings?", - text="This will reset your device to its default factory settings.", - buttons=[ - MDFlatButton( - text="CANCEL", text_color=self.theme_cls.primary_color - ), - MDFlatButton( - text="ACCEPT", text_color=self.theme_cls.primary_color - ), - ], - ) - [...] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-text.png - :align: center - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - buttons = ListProperty() - """ - List of button objects for dialog. - Objects must be inherited from :class:`~kivymd.uix.button.BaseButton` class. - - .. code-block:: python - - [...] - self.dialog = MDDialog( - text="Discard draft?", - buttons=[ - MDFlatButton(text="CANCEL"), MDRaisedButton(text="DISCARD"), - ], - ) - [...] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-buttons.png - :align: center - - :attr:`buttons` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - items = ListProperty() - """ - List of items objects for dialog. - Objects must be inherited from :class:`~kivymd.uix.list.BaseListItem` class. - - With type 'simple' - ~~~~~~~~~~~~~~~~~~ - - .. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.dialog import MDDialog - from kivymd.uix.list import OneLineAvatarListItem - - KV = ''' - - - ImageLeftWidget: - source: root.source - - - MDFloatLayout: - - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_simple_dialog() - ''' - - - class Item(OneLineAvatarListItem): - divider = None - source = StringProperty() - - - class Example(MDApp): - dialog = None - - def build(self): - return Builder.load_string(KV) - - def show_simple_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - title="Set backup account", - type="simple", - items=[ - Item(text="user01@gmail.com", source="user-1.png"), - Item(text="user02@gmail.com", source="user-2.png"), - Item(text="Add account", source="add-icon.png"), - ], - ) - self.dialog.open() - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-items.png - :align: center - - With type 'confirmation' - ~~~~~~~~~~~~~~~~~~~~~~~~ - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.button import MDFlatButton - from kivymd.uix.dialog import MDDialog - from kivymd.uix.list import OneLineAvatarIconListItem - - KV = ''' - - on_release: root.set_icon(check) - - CheckboxLeftWidget: - id: check - group: "check" - - - MDFloatLayout: - - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_confirmation_dialog() - ''' - - - class ItemConfirm(OneLineAvatarIconListItem): - divider = None - - def set_icon(self, instance_check): - instance_check.active = True - check_list = instance_check.get_widgets(instance_check.group) - for check in check_list: - if check != instance_check: - check.active = False - - - class Example(MDApp): - dialog = None - - def build(self): - return Builder.load_string(KV) - - def show_confirmation_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - title="Phone ringtone", - type="confirmation", - items=[ - ItemConfirm(text="Callisto"), - ItemConfirm(text="Luna"), - ItemConfirm(text="Night"), - ItemConfirm(text="Solo"), - ItemConfirm(text="Phobos"), - ItemConfirm(text="Diamond"), - ItemConfirm(text="Sirena"), - ItemConfirm(text="Red music"), - ItemConfirm(text="Allergio"), - ItemConfirm(text="Magic"), - ItemConfirm(text="Tic-tac"), - ], - buttons=[ - MDFlatButton( - text="CANCEL", text_color=self.theme_cls.primary_color - ), - MDFlatButton( - text="OK", text_color=self.theme_cls.primary_color - ), - ], - ) - self.dialog.open() - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-confirmation.png - :align: center - - :attr:`items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - width_offset = NumericProperty(dp(48)) - """ - Dialog offset from device width. - - :attr:`width_offset` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(48)`. - """ - - type = OptionProperty( - "alert", options=["alert", "simple", "confirmation", "custom"] - ) - """ - Dialog type. - Available option are `'alert'`, `'simple'`, `'confirmation'`, `'custom'`. - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'alert'`. - """ - - content_cls = ObjectProperty() - """ - Custom content class. - - .. code-block:: python - - from kivy.lang import Builder - from kivy.uix.boxlayout import BoxLayout - - from kivymd.app import MDApp - from kivymd.uix.button import MDFlatButton - from kivymd.uix.dialog import MDDialog - - KV = ''' - - orientation: "vertical" - spacing: "12dp" - size_hint_y: None - height: "120dp" - - MDTextField: - hint_text: "City" - - MDTextField: - hint_text: "Street" - - - MDFloatLayout: - - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_confirmation_dialog() - ''' - - - class Content(BoxLayout): - pass - - - class Example(MDApp): - dialog = None - - def build(self): - return Builder.load_string(KV) - - def show_confirmation_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - title="Address:", - type="custom", - content_cls=Content(), - buttons=[ - MDFlatButton( - text="CANCEL", text_color=self.theme_cls.primary_color - ), - MDFlatButton( - text="OK", text_color=self.theme_cls.primary_color - ), - ], - ) - self.dialog.open() - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png - :align: center - - :attr:`content_cls` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `'None'`. - """ - - md_bg_color = ColorProperty(None) - """ - Background color in the format (r, g, b, a). - - :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _scroll_height = NumericProperty("28dp") - _spacer_top = NumericProperty("24dp") - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Window.bind(on_resize=self.update_width) - - if self.size_hint == [1, 1] and ( - DEVICE_TYPE == "desktop" or DEVICE_TYPE == "tablet" - ): - self.size_hint = (None, None) - self.width = min(dp(560), Window.width - self.width_offset) - elif self.size_hint == [1, 1] and DEVICE_TYPE == "mobile": - self.size_hint = (None, None) - self.width = min(dp(280), Window.width - self.width_offset) - - if not self.title: - self._spacer_top = 0 - - if not self.buttons: - self.ids.root_button_box.height = 0 - else: - self.create_buttons() - - update_height = False - if self.type in ("simple", "confirmation"): - if self.type == "confirmation": - self.ids.spacer_top_box.add_widget(MDSeparator()) - self.ids.spacer_bottom_box.add_widget(MDSeparator()) - self.create_items() - if self.type == "custom": - if self.content_cls: - self.ids.container.remove_widget(self.ids.scroll) - self.ids.container.remove_widget(self.ids.text) - self.ids.spacer_top_box.add_widget(self.content_cls) - self.ids.spacer_top_box.padding = (0, "24dp", "16dp", 0) - update_height = True - if self.type == "alert": - self.ids.scroll.bar_width = 0 - - if update_height: - Clock.schedule_once(self.update_height) - - def update_width(self, *args): - self.width = max( - self.height + self.width_offset, - min( - dp(560) if DEVICE_TYPE != "mobile" else dp(280), - Window.width - self.width_offset, - ), - ) - - def update_height(self, *args): - self._spacer_top = self.content_cls.height + dp(24) - - def on_open(self): - # TODO: Add scrolling text. - self.height = self.ids.container.height - - def get_normal_height(self): - return ( - (Window.height * 80 / 100) - - self._spacer_top - - dp(52) - - self.ids.container.padding[1] - - self.ids.container.padding[-1] - - 100 - ) - - def edit_padding_for_item(self, instance_item): - instance_item.ids._left_container.x = 0 - instance_item._txt_left_pad = "56dp" - - def create_items(self): - if not self.text: - self.ids.container.remove_widget(self.ids.text) - height = 0 - else: - height = self.ids.text.height - - for item in self.items: - if issubclass(item.__class__, BaseListItem): - height += item.height # calculate height contents - self.edit_padding_for_item(item) - self.ids.box_items.add_widget(item) - - if height > Window.height: - self.ids.scroll.height = self.get_normal_height() - else: - self.ids.scroll.height = height - - def create_buttons(self): - for button in self.buttons: - if issubclass(button.__class__, BaseButton): - self.ids.button_box.add_widget(button) diff --git a/kivymd/uix/dropdownitem.py b/kivymd/uix/dropdownitem.py deleted file mode 100644 index b6341ef..0000000 --- a/kivymd/uix/dropdownitem.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -Components/Dropdown Item -======================== - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dropdown-item.png - :align: center - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - Screen - - MDDropDownItem: - id: drop_item - pos_hint: {'center_x': .5, 'center_y': .5} - text: 'Item' - on_release: self.set_item("New Item") - ''' - - - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - - def build(self): - return self.screen - - - Test().run() - -.. seealso:: - - `Work with the class MDDropdownMenu see here `_ -""" - -__all__ = ("MDDropDownItem",) - -from kivy.lang import Builder -from kivy.properties import NumericProperty, StringProperty -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.widget import Widget - -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import FakeRectangularElevationBehavior -from kivymd.uix.boxlayout import MDBoxLayout - -Builder.load_string( - """ -<_Triangle>: - canvas: - Color: - rgba: root.theme_cls.text_color - Triangle: - points: - [ \ - self.right-dp(14), self.y+dp(7), \ - self.right-dp(7), self.y+dp(7), \ - self.right-dp(7), self.y+dp(14) \ - ] - - - - orientation: "vertical" - adaptive_size: True - spacing: "5dp" - padding: "5dp", "5dp", "5dp", 0 - - MDBoxLayout: - adaptive_size: True - spacing: "10dp" - - Label: - id: label_item - size_hint: None, None - size: self.texture_size - color: root.theme_cls.text_color - font_size: root.font_size - - - _Triangle: - size_hint: None, None - size: "20dp", "20dp" - - MDSeparator: -""" -) - - -class _Triangle(ThemableBehavior, Widget): - pass - - -class MDDropDownItem( - ThemableBehavior, - FakeRectangularElevationBehavior, - ButtonBehavior, - MDBoxLayout, -): - text = StringProperty() - """ - Text item. - - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - current_item = StringProperty() - """ - Current name item. - - :attr:`current_item` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - font_size = NumericProperty("16sp") - """ - Item font size. - - :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` - and defaults to `'16sp'`. - """ - - def on_text(self, instance, value): - self.ids.label_item.text = value - - def set_item(self, name_item): - """Sets new text for an item.""" - - self.ids.label_item.text = name_item - self.current_item = name_item diff --git a/kivymd/uix/expansionpanel.py b/kivymd/uix/expansionpanel.py deleted file mode 100644 index e35bdef..0000000 --- a/kivymd/uix/expansionpanel.py +++ /dev/null @@ -1,463 +0,0 @@ -""" -Components/Expansion Panel -========================== - -.. seealso:: - - `Material Design spec, Expansion panel `_ - -.. rubric:: Expansion panels contain creation flows and allow lightweight editing of an element. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.png - :align: center - -Usage ------ - -.. code-block:: python - - self.add_widget( - MDExpansionPanel( - icon="logo.png", # panel icon - content=Content(), # panel content - panel_cls=MDExpansionPanelOneLine(text="Secondary text"), # panel class - ) - ) - -To use :class:`~MDExpansionPanel` you must pass one of the following classes -to the :attr:`~MDExpansionPanel.panel_cls` parameter: - -- :class:`~MDExpansionPanelOneLine` -- :class:`~MDExpansionPanelTwoLine` -- :class:`~MDExpansionPanelThreeLine` - -These classes are inherited from the following classes: - -- :class:`~kivymd.uix.list.OneLineAvatarIconListItem` -- :class:`~kivymd.uix.list.TwoLineAvatarIconListItem` -- :class:`~kivymd.uix.list.ThreeLineAvatarIconListItem` - -.. code-block:: python - - self.root.ids.box.add_widget( - MDExpansionPanel( - icon="logo.png", - content=Content(), - panel_cls=MDExpansionPanelThreeLine( - text="Text", - secondary_text="Secondary text", - tertiary_text="Tertiary text", - ) - ) - ) - -Example -------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelThreeLine - from kivymd import images_path - - KV = ''' - - adaptive_height: True - - TwoLineIconListItem: - text: "(050)-123-45-67" - secondary_text: "Mobile" - - IconLeftWidget: - icon: 'phone' - - - ScrollView: - - MDGridLayout: - id: box - cols: 1 - adaptive_height: True - ''' - - - class Content(MDBoxLayout): - '''Custom content.''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(10): - self.root.ids.box.add_widget( - MDExpansionPanel( - icon=f"{images_path}kivymd.png", - content=Content(), - panel_cls=MDExpansionPanelThreeLine( - text="Text", - secondary_text="Secondary text", - tertiary_text="Tertiary text", - ) - ) - ) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.gif - :align: center - -Two events are available for :class:`~MDExpansionPanel` -------------------------------------------------------- - -- :attr:`~MDExpansionPanel.on_open` -- :attr:`~MDExpansionPanel.on_close` - -.. code-block:: kv - - MDExpansionPanel: - on_open: app.on_panel_open(args) - on_close: app.on_panel_close(args) - -The user function takes one argument - the object of the panel: - -.. code-block:: python - - def on_panel_open(self, instance_panel): - print(instance_panel) - -.. seealso:: `See Expansion panel example `_ - - `Expansion panel and MDCard `_ -""" - -__all__ = ( - "MDExpansionPanel", - "MDExpansionPanelOneLine", - "MDExpansionPanelTwoLine", - "MDExpansionPanelThreeLine", - "MDExpansionPanelLabel", -) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import NumericProperty, ObjectProperty, StringProperty -from kivy.uix.relativelayout import RelativeLayout -from kivy.uix.widget import WidgetException - -import kivymd.material_resources as m_res -from kivymd.icon_definitions import md_icons -from kivymd.uix.button import MDIconButton -from kivymd.uix.list import ( - IconLeftWidget, - ImageLeftWidget, - IRightBodyTouch, - OneLineAvatarIconListItem, - ThreeLineAvatarIconListItem, - TwoLineAvatarIconListItem, - TwoLineListItem, -) - -Builder.load_string( - """ -: - icon: "chevron-right" - disabled: True - md_bg_color_disabled: 0, 0, 0, 0 - - canvas.before: - PushMatrix - Rotate: - angle: self._angle - axis: (0, 0, 1) - origin: self.center - canvas.after: - PopMatrix - - - - size_hint_y: None - # height: dp(68) -""" -) - - -class MDExpansionChevronRight(IRightBodyTouch, MDIconButton): - """Chevron icon on the right panel.""" - - _angle = NumericProperty(0) - - -class MDExpansionPanelOneLine(OneLineAvatarIconListItem): - """Single line panel.""" - - -class MDExpansionPanelTwoLine(TwoLineAvatarIconListItem): - """Two-line panel.""" - - -class MDExpansionPanelThreeLine(ThreeLineAvatarIconListItem): - """Three-line panel.""" - - -class MDExpansionPanelLabel(TwoLineListItem): - """ - Label panel. - - ..warning:: This class is created for use in the - :class:`~kivymd.uix.stepper.MDStepperVertical` and - :class:`~kivymd.uix.stepper.MDStepper` classes, and has not - been tested for use outside of these classes. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_paddings) - - def set_paddings(self, interval): - self._txt_bot_pad = dp(36) - self._txt_left_pad = dp(0) - - -class MDExpansionPanel(RelativeLayout): - """ - :Events: - :attr:`on_open` - Called when a panel is opened. - :attr:`on_close` - Called when a panel is closed. - """ - - content = ObjectProperty() - """ - Content of panel. Must be `Kivy` widget. - - :attr:`content` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - icon = StringProperty() - """ - Icon of panel. - - Icon Should be either be a path to an image or - a logo name in :class:`~kivymd.icon_definitions.md_icons` - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - opening_transition = StringProperty("out_cubic") - """ - The name of the animation transition type to use when animating to - the :attr:`state` `'open'`. - - :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - opening_time = NumericProperty(0.2) - """ - The time taken for the panel to slide to the :attr:`state` `'open'`. - - :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - closing_transition = StringProperty("out_sine") - """ - The name of the animation transition type to use when animating to - the :attr:`state` 'close'. - - :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_sine'`. - """ - - closing_time = NumericProperty(0.2) - """ - The time taken for the panel to slide to the :attr:`state` `'close'`. - - :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - panel_cls = ObjectProperty() - """ - Panel object. The object must be one of the classes - :class:`~MDExpansionPanelOneLine`, :class:`~MDExpansionPanelTwoLine` or - :class:`~MDExpansionPanelThreeLine`. - - :attr:`panel_cls` is a :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - _state = StringProperty("close") - _anim_playing = False - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_open") - self.register_event_type("on_close") - - if self.panel_cls and isinstance( - self.panel_cls, - ( - MDExpansionPanelOneLine, - MDExpansionPanelTwoLine, - MDExpansionPanelThreeLine, - MDExpansionPanelLabel, - ), - ): - self.panel_cls.pos_hint = {"top": 1} - self.panel_cls._no_ripple_effect = True - self.panel_cls.bind( - on_release=lambda x: self.check_open_panel(self.panel_cls) - ) - if not isinstance(self.panel_cls, MDExpansionPanelLabel): - self.chevron = MDExpansionChevronRight() - self.panel_cls.add_widget(self.chevron) - if self.icon: - if self.icon in md_icons.keys(): - self.panel_cls.add_widget( - IconLeftWidget( - icon=self.icon, - pos_hint={"center_y": 0.5}, - ) - ) - else: - self.panel_cls.add_widget( - ImageLeftWidget( - source=self.icon, pos_hint={"center_y": 0.5} - ) - ) - else: - self.panel_cls.remove_widget( - self.panel_cls.ids._left_container - ) - self.panel_cls._txt_left_pad = 0 - else: - # if no icon - self.panel_cls._txt_left_pad = m_res.HORIZ_MARGINS - self.add_widget(self.panel_cls) - else: - raise ValueError( - "KivyMD: `panel_cls` object must be must be one of the " - "objects from the list\n" - "[MDExpansionPanelOneLine, MDExpansionPanelTwoLine, " - "MDExpansionPanelThreeLine]" - ) - - def on_open(self, *args): - """Called when a panel is opened.""" - - def on_close(self, *args): - """Called when a panel is closed.""" - - def check_open_panel(self, instance): - """ - Called when you click on the panel. Called methods to open or close - a panel. - """ - - press_current_panel = False - for panel in self.parent.children: - if isinstance(panel, MDExpansionPanel): - if len(panel.children) == 2: - if instance is panel.children[1]: - press_current_panel = True - panel.remove_widget(panel.children[0]) - if not isinstance(self.panel_cls, MDExpansionPanelLabel): - chevron = panel.children[0].children[0].children[0] - self.set_chevron_up(chevron) - self.close_panel(panel, press_current_panel) - self.dispatch("on_close") - break - if not press_current_panel: - self.set_chevron_down() - - def set_chevron_down(self): - """Sets the chevron down.""" - - if not isinstance(self.panel_cls, MDExpansionPanelLabel): - Animation(_angle=-90, d=self.opening_time).start(self.chevron) - self.open_panel() - self.dispatch("on_open") - - def set_chevron_up(self, instance_chevron): - """Sets the chevron up.""" - - if not isinstance(self.panel_cls, MDExpansionPanelLabel): - Animation(_angle=0, d=self.closing_time).start(instance_chevron) - - def close_panel(self, instance_panel, press_current_panel): - """Method closes the panel.""" - - if self._anim_playing: - return - - if press_current_panel: - self._anim_playing = True - - self._state = "close" - - anim = Animation( - height=self.panel_cls.height, - d=self.closing_time, - t=self.closing_transition, - ) - anim.bind(on_complete=self._disable_anim) - anim.start(instance_panel) - - def open_panel(self, *args): - """Method opens a panel.""" - - if self._anim_playing: - return - - self._anim_playing = True - self._state = "open" - - anim = Animation( - height=self.content.height + self.height, - d=self.opening_time, - t=self.opening_transition, - ) - anim.bind(on_complete=self._add_content) - anim.bind(on_complete=self._disable_anim) - anim.start(self) - - def get_state(self): - """Returns the state of panel. Can be `close` or `open` .""" - return self._state - - def add_widget(self, widget, index=0, canvas=None): - if isinstance( - widget, - ( - MDExpansionPanelOneLine, - MDExpansionPanelTwoLine, - MDExpansionPanelThreeLine, - MDExpansionPanelLabel, - ), - ): - self.height = widget.height - return super().add_widget(widget) - - def _disable_anim(self, *args): - self._anim_playing = False - - def _add_content(self, *args): - if self.content: - try: - if isinstance(self.panel_cls, MDExpansionPanelLabel): - self.content.y = dp(36) - self.add_widget(self.content) - except WidgetException: - pass diff --git a/kivymd/uix/filemanager.py b/kivymd/uix/filemanager.py deleted file mode 100644 index 81ebc23..0000000 --- a/kivymd/uix/filemanager.py +++ /dev/null @@ -1,663 +0,0 @@ -""" -Components/File Manager -======================= - -A simple manager for selecting directories and files. - -Usage ------ - -.. code-block:: python - - path = '/' # path to the directory that will be opened in the file manager - file_manager = MDFileManager( - exit_manager=self.exit_manager, # function called when the user reaches directory tree root - select_path=self.select_path, # function called when selecting a file/directory - ) - file_manager.show(path) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager.png - :align: center - -.. warning:: Be careful! To use the `/` path on Android devices, you need - special permissions. Therefore, you are likely to get an error. - -Or with ``preview`` mode: - -.. code-block:: python - - file_manager = MDFileManager( - exit_manager=self.exit_manager, - select_path=self.select_path, - preview=True, - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-previous.png - :align: center - -.. warning:: The `preview` mode is intended only for viewing images and will - not display other types of files. - -Example -------- - -.. code-block:: python - - from kivy.core.window import Window - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.filemanager import MDFileManager - from kivymd.toast import toast - - - KV = ''' - BoxLayout: - orientation: 'vertical' - - MDToolbar: - title: "MDFileManager" - left_action_items: [['menu', lambda x: None]] - elevation: 10 - - FloatLayout: - - MDRoundFlatIconButton: - text: "Open manager" - icon: "folder" - pos_hint: {'center_x': .5, 'center_y': .6} - on_release: app.file_manager_open() - ''' - - - class Example(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - Window.bind(on_keyboard=self.events) - self.manager_open = False - self.file_manager = MDFileManager( - exit_manager=self.exit_manager, - select_path=self.select_path, - preview=True, - ) - - def build(self): - return Builder.load_string(KV) - - def file_manager_open(self): - self.file_manager.show('/') # output manager to the screen - self.manager_open = True - - def select_path(self, path): - '''It will be called when you click on the file name - or the catalog selection button. - - :type path: str; - :param path: path to the selected directory or file; - ''' - - self.exit_manager() - toast(path) - - def exit_manager(self, *args): - '''Called when the user reaches the root of the directory tree.''' - - self.manager_open = False - self.file_manager.close() - - def events(self, instance, keyboard, keycode, text, modifiers): - '''Called when buttons are pressed on the mobile device.''' - - if keyboard in (1001, 27): - if self.manager_open: - self.file_manager.back() - return True - - - Example().run() -""" - -__all__ = ("MDFileManager",) - -import locale -import os - -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.modalview import ModalView - -from kivymd import images_path -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import CircularRippleBehavior -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.floatlayout import MDFloatLayout -from kivymd.uix.list import BaseListItem, ContainerSupport -from kivymd.uix.relativelayout import MDRelativeLayout -from kivymd.utils.fitimage import FitImage - -ACTIVITY_MANAGER = """ -#:import os os - - - - icon: "folder" - path: "" - background_normal: "" - background_down: "" - dir_or_file_name: "" - _selected: False - events_callback: lambda x: None - orientation: "vertical" - - ModifiedOneLineIconListItem: - text: root.dir_or_file_name - bg_color: self.theme_cls.bg_darkest if root._selected else self.theme_cls.bg_normal - on_release: root.events_callback(root.path, root) - - IconLeftWidget: - icon: root.icon - theme_text_color: "Custom" - text_color: self.theme_cls.primary_color - - MDSeparator: - - - - size_hint_y: None - height: self.texture_size[1] - shorten: True - shorten_from: "center" - halign: "center" - text_size: self.width, None - - - - name: "" - path: "" - realpath: "" - type: "folder" - events_callback: lambda x: None - _selected: False - orientation: "vertical" - size_hint_y: None - hright: root.height - padding: dp(20) - - IconButton: - mipmap: True - source: root.path - bg_color: app.theme_cls.bg_darkest if root._selected else app.theme_cls.bg_normal - on_release: - root.events_callback(\ - os.path.join(root.path if root.type != "folder" else root.realpath, \ - root.name), root) - - LabelContent: - text: root.name - - - - anchor_x: "right" - anchor_y: "bottom" - size_hint_y: None - height: dp(56) - padding: dp(10) - - MDFloatingActionButton: - size_hint: None, None - size:dp(56), dp(56) - icon: root.icon - opposite_colors: True - elevation: 8 - on_release: root.callback() - md_bg_color: root.md_bg_color - - - - md_bg_color: root.theme_cls.bg_normal - - MDBoxLayout: - orientation: "vertical" - spacing: dp(5) - - MDToolbar: - id: toolbar - title: root.current_path - right_action_items: [["close-box", lambda x: root.exit_manager(1)]] - left_action_items: [["chevron-left", lambda x: root.back()]] - elevation: 10 - - RecycleView: - id: rv - key_viewclass: "viewclass" - key_size: "height" - bar_width: dp(4) - bar_color: root.theme_cls.primary_color - - RecycleGridLayout: - padding: "10dp" - spacing: "2dp" - cols: 3 if root.preview else 1 - default_size: None, dp(48) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - - - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height / 2 - self.height / 2 - size: dp(48), dp(48) -""" - - -class BodyManagerWithPreview(MDBoxLayout): - """Base class for folder icons and thumbnails images in ``preview`` mode.""" - - -class IconButton(CircularRippleBehavior, ButtonBehavior, FitImage): - """Folder icons/thumbnails images in ``preview`` mode.""" - - -class FloatButton(AnchorLayout): - callback = ObjectProperty() - md_bg_color = ColorProperty([1, 1, 1, 1]) - icon = StringProperty() - - -class ModifiedOneLineIconListItem(ContainerSupport, BaseListItem): - _txt_left_pad = NumericProperty("72dp") - _txt_top_pad = NumericProperty("16dp") - _txt_bot_pad = NumericProperty("15dp") - _num_lines = 1 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(48) - - -class MDFileManager(ThemableBehavior, MDRelativeLayout): - icon = StringProperty("check") - """ - The icon that will be used on the directory selection button. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `check`. - """ - - icon_folder = StringProperty(f"{images_path}folder.png") - """ - The icon that will be used for folder icons when using ``preview = True``. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `check`. - """ - - exit_manager = ObjectProperty(lambda x: None) - """ - Function called when the user reaches directory tree root. - - :attr:`exit_manager` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `lambda x: None`. - """ - - select_path = ObjectProperty(lambda x: None) - """ - Function, called when selecting a file/directory. - - :attr:`select_path` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `lambda x: None`. - """ - - ext = ListProperty() - """ - List of file extensions to be displayed in the manager. - For example, `['.py', '.kv']` - will filter out all files, - except python scripts and Kv Language. - - :attr:`ext` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - search = OptionProperty("all", options=["all", "dirs", "files"]) - """ - It can take the values 'all' 'dirs' 'files' - display only directories - or only files or both them. By default, it displays folders, and files. - Available options are: `'all'`, `'dirs'`, `'files'`. - - :attr:`search` is an :class:`~kivy.properties.OptionProperty` - and defaults to `all`. - """ - - current_path = StringProperty(os.getcwd()) - """ - Current directory. - - :attr:`current_path` is an :class:`~kivy.properties.StringProperty` - and defaults to `/`. - """ - - use_access = BooleanProperty(True) - """ - Show access to files and directories. - - :attr:`use_access` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - preview = BooleanProperty(False) - """ - Shows only image previews. - - :attr:`preview` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - show_hidden_files = BooleanProperty(False) - """ - Shows hidden files. - - :attr:`show_hidden_files` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - sort_by = OptionProperty( - "name", options=["nothing", "name", "date", "size", "type"] - ) - """ - It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files by option - By default, sort by name. - Available options are: `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`. - - :attr:`sort_by` is an :class:`~kivy.properties.OptionProperty` - and defaults to `name`. - """ - - sort_by_desc = BooleanProperty(False) - """ - Sort by descending. - - :attr:`sort_by_desc` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - selector = OptionProperty("any", options=["any", "file", "folder", "multi"]) - """ - It can take the values 'any' 'file' 'folder' 'multi' - By default, any. - Available options are: `'any'`, `'file'`, `'folder'`, `'multi'`. - - :attr:`selector` is an :class:`~kivy.properties.OptionProperty` - and defaults to `any`. - """ - - selection = ListProperty() - """ - Contains the list of files that are currently selected. - - :attr:`selection` is a read-only :class:`~kivy.properties.ListProperty` and - defaults to `[]`. - """ - - _window_manager = None - _window_manager_open = False - - def __init__(self, **kwargs): - super().__init__(**kwargs) - toolbar_label = self.ids.toolbar.children[1].children[0] - toolbar_label.font_style = "Subtitle1" - if ( - self.selector == "any" - or self.selector == "multi" - or self.selector == "folder" - ): - self.add_widget( - FloatButton( - callback=self.select_directory_on_press_button, - md_bg_color=self.theme_cls.primary_color, - icon=self.icon, - ) - ) - - if self.preview: - self.ext = [".png", ".jpg", ".jpeg"] - - def __sort_files(self, files): - def sort_by_name(files): - files.sort(key=locale.strxfrm) - files.sort(key=str.casefold) - return files - - if self.sort_by == "name": - sorted_files = sort_by_name(files) - elif self.sort_by == "date": - _files = sort_by_name(files) - _sorted_files = [os.path.join(self.current_path, f) for f in _files] - _sorted_files.sort(key=os.path.getmtime, reverse=True) - sorted_files = [os.path.basename(f) for f in _sorted_files] - elif self.sort_by == "size": - _files = sort_by_name(files) - _sorted_files = [os.path.join(self.current_path, f) for f in _files] - _sorted_files.sort(key=os.path.getsize, reverse=True) - sorted_files = [os.path.basename(f) for f in _sorted_files] - elif self.sort_by == "type": - _files = sort_by_name(files) - sorted_files = sorted( - _files, - key=lambda f: (os.path.splitext(f)[1], os.path.splitext(f)[0]), - ) - else: - sorted_files = files - - if self.sort_by_desc: - sorted_files.reverse() - - return sorted_files - - def show(self, path): - """Forms the body of a directory tree. - - :param path: - The path to the directory that will be opened in the file manager. - """ - - self.current_path = path - self.selection = [] - dirs, files = self.get_content() - manager_list = [] - - if dirs == [] and files == []: # selected directory - pass - elif not dirs and not files: # directory is unavailable - return - - if self.preview: - for name_dir in self.__sort_files(dirs): - manager_list.append( - { - "viewclass": "BodyManagerWithPreview", - "path": self.icon_folder, - "realpath": os.path.join(path), - "type": "folder", - "name": name_dir, - "events_callback": self.select_dir_or_file, - "height": dp(150), - "_selected": False, - } - ) - for name_file in self.__sort_files(files): - if ( - os.path.splitext(os.path.join(path, name_file))[1] - in self.ext - ): - manager_list.append( - { - "viewclass": "BodyManagerWithPreview", - "path": os.path.join(path, name_file), - "name": name_file, - "type": "files", - "events_callback": self.select_dir_or_file, - "height": dp(150), - "_selected": False, - } - ) - else: - for name in self.__sort_files(dirs): - _path = os.path.join(path, name) - access_string = self.get_access_string(_path) - if "r" not in access_string: - icon = "folder-lock" - else: - icon = "folder" - - manager_list.append( - { - "viewclass": "BodyManager", - "path": _path, - "icon": icon, - "dir_or_file_name": name, - "events_callback": self.select_dir_or_file, - "_selected": False, - } - ) - for name in self.__sort_files(files): - if self.ext and os.path.splitext(name)[1] not in self.ext: - continue - - manager_list.append( - { - "viewclass": "BodyManager", - "path": name, - "icon": "file-outline", - "dir_or_file_name": os.path.split(name)[1], - "events_callback": self.select_dir_or_file, - "_selected": False, - } - ) - self.ids.rv.data = manager_list - - if not self._window_manager: - self._window_manager = ModalView( - size_hint=self.size_hint, auto_dismiss=False - ) - self._window_manager.add_widget(self) - if not self._window_manager_open: - self._window_manager.open() - self._window_manager_open = True - - def get_access_string(self, path): - access_string = "" - if self.use_access: - access_data = {"r": os.R_OK, "w": os.W_OK, "x": os.X_OK} - for access in access_data.keys(): - access_string += ( - access if os.access(path, access_data[access]) else "-" - ) - return access_string - - def get_content(self): - """Returns a list of the type [[Folder List], [file list]].""" - - try: - files = [] - dirs = [] - - for content in os.listdir(self.current_path): - if os.path.isdir(os.path.join(self.current_path, content)): - if self.search == "all" or self.search == "dirs": - if (not self.show_hidden_files) and ( - content.startswith(".") - ): - continue - else: - dirs.append(content) - - else: - if self.search == "all" or self.search == "files": - if len(self.ext) != 0: - try: - files.append( - os.path.join(self.current_path, content) - ) - except IndexError: - pass - else: - if ( - not self.show_hidden_files - and content.startswith(".") - ): - continue - else: - files.append(content) - - return dirs, files - - except OSError: - return None, None - - def close(self): - """Closes the file manager window.""" - - self._window_manager.dismiss() - self._window_manager_open = False - - def select_dir_or_file(self, path, widget): - """Called by tap on the name of the directory or file.""" - - if os.path.isfile(os.path.join(self.current_path, path)): - if self.selector == "multi": - file_path = os.path.join(self.current_path, path) - if file_path in self.selection: - widget._selected = False - self.selection.remove(file_path) - else: - widget._selected = True - self.selection.append(file_path) - elif self.selector == "folder": - return - else: - self.select_path(os.path.join(self.current_path, path)) - - else: - self.current_path = path - self.show(path) - - def back(self): - """Returning to the branch down in the directory tree.""" - - path, end = os.path.split(self.current_path) - - if not end: - self.close() - self.exit_manager(1) - - else: - self.show(path) - - def select_directory_on_press_button(self, *args): - """Called when a click on a floating button.""" - - if self.selector == "multi": - if len(self.selection) > 0: - self.select_path(self.selection) - else: - if self.selector == "folder" or self.selector == "any": - self.select_path(self.current_path) - - -Builder.load_string(ACTIVITY_MANAGER) diff --git a/kivymd/uix/floatlayout.py b/kivymd/uix/floatlayout.py deleted file mode 100644 index e6eb2e4..0000000 --- a/kivymd/uix/floatlayout.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Components/Float Layout -======================= - -:class:`~kivy.uix.floatlayout.FloatLayout` class equivalent. Simplifies working -with some widget properties. For example: - -FloatLayout ------------ - -.. code-block:: - - FloatLayout: - canvas: - Color: - rgba: app.theme_cls.primary_color - RoundedRectangle: - pos: self.pos - size: self.size - radius: [25, 0, 0, 0] - -MDFloatLayout -------------- - -.. code-block:: - - MDFloatLayout: - radius: [25, 0, 0, 0] - md_bg_color: app.theme_cls.primary_color - -.. Warning:: For a :class:`~kivy.uix.floatlayout.FloatLayout`, the - ``minimum_size`` attributes are always 0, so you cannot use - ``adaptive_size`` and related options. -""" - -from kivy.uix.floatlayout import FloatLayout - -from kivymd.uix import MDAdaptiveWidget - - -class MDFloatLayout(FloatLayout, MDAdaptiveWidget): - pass diff --git a/kivymd/uix/gridlayout.py b/kivymd/uix/gridlayout.py deleted file mode 100644 index 7b9cddc..0000000 --- a/kivymd/uix/gridlayout.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Components/Grid Layout -====================== - -:class:`~kivy.uix.gridlayout.GridLayout` class equivalent. Simplifies working -with some widget properties. For example: - -GridLayout ----------- - -.. code-block:: - - GridLayout: - size_hint_y: None - height: self.minimum_height - - canvas: - Color: - rgba: app.theme_cls.primary_color - Rectangle: - pos: self.pos - size: self.size - -MDGridLayout ------------- - -.. code-block:: - - MDGridLayout: - adaptive_height: True - md_bg_color: app.theme_cls.primary_color - -Available options are: ----------------------- - -- adaptive_height_ -- adaptive_width_ -- adaptive_size_ - -.. adaptive_height: -adaptive_height ---------------- - -.. code-block:: kv - - adaptive_height: True - -Equivalent - -.. code-block:: kv - - size_hint_y: None - height: self.minimum_height - -.. adaptive_width: -adaptive_width --------------- - -.. code-block:: kv - - adaptive_width: True - -Equivalent - -.. code-block:: kv - - size_hint_x: None - height: self.minimum_width - -.. adaptive_size: -adaptive_size -------------- - -.. code-block:: kv - - adaptive_size: True - -Equivalent - -.. code-block:: kv - - size_hint: None, None - size: self.minimum_size -""" - -from kivy.uix.gridlayout import GridLayout - -from kivymd.uix import MDAdaptiveWidget - - -class MDGridLayout(GridLayout, MDAdaptiveWidget): - pass diff --git a/kivymd/uix/imagelist.py b/kivymd/uix/imagelist.py deleted file mode 100644 index d9ad7d6..0000000 --- a/kivymd/uix/imagelist.py +++ /dev/null @@ -1,311 +0,0 @@ -""" -Components/Image List -===================== - -.. seealso:: - - `Material Design spec, Image lists `_ - -.. rubric:: Image lists display a collection of images in an organized grid. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/image-list.png - :align: center - -`KivyMD` provides the following tile classes for use: - -- SmartTileWithStar_ -- SmartTileWithLabel_ - -.. SmartTileWithStar: -SmartTileWithStar ------------------ - -.. code-block:: - - from kivymd.app import MDApp - from kivy.lang import Builder - - KV = ''' - - size_hint_y: None - height: "240dp" - - - ScrollView: - - MDGridLayout: - cols: 3 - adaptive_height: True - padding: dp(4), dp(4) - spacing: dp(4) - - MyTile: - stars: 5 - source: "cat-1.jpg" - - MyTile: - stars: 5 - source: "cat-2.jpg" - - MyTile: - stars: 5 - source: "cat-3.jpg" - ''' - - - class MyApp(MDApp): - def build(self): - return Builder.load_string(KV) - - - MyApp().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/SmartTileWithStar.gif - :align: center - -.. SmartTileWithLabel: -SmartTileWithLabel ------------------- - -.. code-block:: python - - from kivymd.app import MDApp - from kivy.lang import Builder - - KV = ''' - - size_hint_y: None - height: "240dp" - - - ScrollView: - - MDGridLayout: - cols: 3 - adaptive_height: True - padding: dp(4), dp(4) - spacing: dp(4) - - MyTile: - source: "cat-1.jpg" - text: "[size=26]Cat 1[/size]\\n[size=14]cat-1.jpg[/size]" - - MyTile: - source: "cat-2.jpg" - text: "[size=26]Cat 2[/size]\\n[size=14]cat-2.jpg[/size]" - tile_text_color: app.theme_cls.accent_color - - MyTile: - source: "cat-3.jpg" - text: "[size=26][color=#ffffff]Cat 3[/color][/size]\\n[size=14]cat-3.jpg[/size]" - tile_text_color: app.theme_cls.accent_color - ''' - - - class MyApp(MDApp): - def build(self): - return Builder.load_string(KV) - - - MyApp().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/SmartTileWithLabel.png - :align: center -""" - -__all__ = ("SmartTile", "SmartTileWithLabel", "SmartTileWithStar") - -from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ColorProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.behaviors import ButtonBehavior - -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import RectangularRippleBehavior -from kivymd.uix.button import MDIconButton -from kivymd.uix.floatlayout import MDFloatLayout - -Builder.load_string( - """ - - _img_widget: img - _img_overlay: img_overlay - _box_overlay: box - - FitImage: - id: img - source: root.source - x: root.x - y: root.y if root.overlap or root.box_position == 'header' else box.top - - BoxLayout: - id: img_overlay - size_hint: img.size_hint - size: img.size - pos: img.pos - - MDBoxLayout: - id: box - md_bg_color: root.box_color - size_hint_y: None - height: "68dp" if root.lines == 2 else "48dp" - x: root.x - y: root.y if root.box_position == 'footer' else root.y + root.height - self.height - - - - _img_widget: img - _img_overlay: img_overlay - _box_overlay: box - _box_label: boxlabel - - FitImage: - id: img - source: root.source - x: root.x - y: root.y if root.overlap or root.box_position == 'header' else box.top - - BoxLayout: - id: img_overlay - size_hint: img.size_hint - size: img.size - pos: img.pos - - MDBoxLayout: - id: box - padding: "5dp", 0, 0, 0 - md_bg_color: root.box_color - adaptive_height: True - x: root.x - y: root.y if root.box_position == 'footer' else root.y + root.height - self.height - - MDLabel: - id: boxlabel - font_style: root.font_style - size_hint_y: None - height: self.texture_size[1] - text: root.text - color: root.tile_text_color - markup: True -""" -) - - -class SmartTile( - ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, MDFloatLayout -): - """ - A tile for more complex needs. - - Includes an image, a container to place overlays and a box that can act - as a header or a footer, as described in the Material Design specs. - """ - - box_color = ColorProperty((0, 0, 0, 0.5)) - """ - Sets the color and opacity for the information box. - - :attr:`box_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `(0, 0, 0, 0.5)`. - """ - - box_position = OptionProperty("footer", options=["footer", "header"]) - """ - Determines wether the information box acts as a header or footer to the - image. Available are options: `'footer'`, `'header'`. - - :attr:`box_position` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'footer'`. - """ - - lines = OptionProperty(1, options=[1, 2]) - """ - Number of lines in the `header/footer`. As per `Material Design specs`, - only 1 and 2 are valid values. Available are options: ``1``, ``2``. - - :attr:`lines` is a :class:`~kivy.properties.OptionProperty` - and defaults to `1`. - """ - - overlap = BooleanProperty(True) - """ - Determines if the `header/footer` overlaps on top of the image or not. - - :attr:`overlap` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - source = StringProperty() - """ - Path to tile image. See :attr:`~kivy.uix.image.Image.source`. - - :attr:`source` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - _img_widget = ObjectProperty() - _img_overlay = ObjectProperty() - _box_overlay = ObjectProperty() - _box_label = ObjectProperty() - - def reload(self): - self._img_widget.reload() - - -class SmartTileWithLabel(SmartTile): - font_style = StringProperty("Caption") - """ - Tile font style. - - :attr:`font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Caption'`. - """ - - tile_text_color = ColorProperty((1, 1, 1, 1)) - """ - Tile text color in ``rgba`` format. - - :attr:`tile_text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `(1, 1, 1, 1)`. - """ - - text = StringProperty() - """ - Determines the text for the box `footer/header`. - - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - _box_label = ObjectProperty() - - -class SmartTileWithStar(SmartTileWithLabel): - stars = NumericProperty(1) - """ - Tile stars. - - :attr:`stars` is a :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - def on_stars(self, *args): - for star in range(self.stars): - self.ids.box.add_widget( - _Star( - icon="star-outline", - theme_text_color="Custom", - text_color=(1, 1, 1, 1), - ) - ) - - -class _Star(MDIconButton): - def on_touch_down(self, touch): - return True diff --git a/kivymd/uix/label.py b/kivymd/uix/label.py deleted file mode 100644 index c7c372c..0000000 --- a/kivymd/uix/label.py +++ /dev/null @@ -1,405 +0,0 @@ -""" -Components/Label -================ - -.. rubric:: The :class:`MDLabel` widget is for rendering text. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label.png - :align: center - -- MDLabel_ -- MDIcon_ - -.. MDLabel: -MDLabel -------- - -Class :class:`MDLabel` inherited from the :class:`~kivy.uix.label.Label` class -but for :class:`MDLabel` the ``text_size`` parameter is ``(self.width, None)`` -and default is positioned on the left: - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - Screen: - - BoxLayout: - orientation: "vertical" - - MDToolbar: - title: "MDLabel" - - MDLabel: - text: "MDLabel" - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-left.png - :align: center - -.. Note:: See :attr:`~kivy.uix.label.Label.halign` - and :attr:`~kivy.uix.label.Label.valign` attributes - of the :class:`~kivy.uix.label.Label` class - -.. code-block:: kv - - MDLabel: - text: "MDLabel" - halign: "center" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-center.png - :align: center - -:class:`~MDLabel` color: ------------------------- - -:class:`~MDLabel` provides standard color themes for label color management: - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.label import MDLabel - - KV = ''' - Screen: - - BoxLayout: - id: box - orientation: "vertical" - - MDToolbar: - title: "MDLabel" - ''' - - - class Test(MDApp): - def build(self): - screen = Builder.load_string(KV) - # Names of standard color themes. - for name_theme in [ - "Primary", - "Secondary", - "Hint", - "Error", - "ContrastParentBackground", - ]: - screen.ids.box.add_widget( - MDLabel( - text=name_theme, - halign="center", - theme_text_color=name_theme, - ) - ) - return screen - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-theme-text-color.png - :align: center - -To use a custom color for :class:`~MDLabel`, use a theme `'Custom'`. -After that, you can specify the desired color in the ``rgba`` format -in the ``text_color`` parameter: - -.. code-block:: kv - - MDLabel: - text: "Custom color" - halign: "center" - theme_text_color: "Custom" - text_color: 0, 0, 1, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-custom-color.png - :align: center - -:class:`~MDLabel` provides standard font styles for labels. To do this, -specify the name of the desired style in the :attr:`~MDLabel.font_style` -parameter: - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.label import MDLabel - from kivymd.font_definitions import theme_font_styles - - - KV = ''' - Screen: - - BoxLayout: - orientation: "vertical" - - MDToolbar: - title: "MDLabel" - - ScrollView: - - MDList: - id: box - ''' - - - class Test(MDApp): - def build(self): - screen = Builder.load_string(KV) - # Names of standard font styles. - for name_style in theme_font_styles[:-1]: - screen.ids.box.add_widget( - MDLabel( - text=f"{name_style} style", - halign="center", - font_style=name_style, - ) - ) - return screen - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style.gif - :align: center - -.. MDIcon: -MDIcon -------- - -You can use labels to display material design icons using the -:class:`~MDIcon` class. - -.. seealso:: - - `Material Design Icons `_ - - `Material Design Icon Names `_ - -The :class:`~MDIcon` class is inherited from -:class:`~MDLabel` and has the same parameters. - -.. Warning:: For the :class:`~MDIcon` class, you cannot use ``text`` - and ``font_style`` options! - -.. code-block:: kv - - MDIcon: - halign: "center" - icon: "language-python" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon.png - :align: center -""" - -__all__ = ("MDLabel", "MDIcon") - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import sp -from kivy.properties import ( - AliasProperty, - BooleanProperty, - ColorProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.label import Label - -from kivymd.theming import ThemableBehavior -from kivymd.theming_dynamic_text import get_contrast_text_color -from kivymd.uix import MDAdaptiveWidget - -__MDLabel_colors__ = { - "Primary": "text_color", - "Secondary": "secondary_text_color", - "Hint": "disabled_hint_text_color", - "Error": "error_color", - "OP": { - "primary": "opposite_text_color", - "Secondary": "opposite_secondary_text_color", - "Hint": "opposite_disabled_hint_text_color", - }, -} - -Builder.load_string( - """ -#:import md_icons kivymd.icon_definitions.md_icons - - - - disabled_color: self.theme_cls.disabled_hint_text_color - text_size: self.width, None - - -: - font_style: "Icon" - text: u"{}".format(md_icons[self.icon]) if self.icon in md_icons else "" - source: None if self.icon in md_icons else self.icon - canvas: - Color: - rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0) - Rectangle: - source: self.source if self.source else None - pos: self.pos - size: self.size -""" -) - - -class MDLabel(ThemableBehavior, Label, MDAdaptiveWidget): - font_style = StringProperty("Body1") - """ - Label font style. - - Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`, `'H6'`, - `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`, - `'Caption'`, `'Overline'`, `'Icon'`. - - :attr:`font_style` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Body1'`. - """ - - _capitalizing = BooleanProperty(False) - - def _get_text(self): - if self._capitalizing: - return self._text.upper() - return self._text - - def _set_text(self, value): - self._text = value - - _text = StringProperty() - - text = AliasProperty(_get_text, _set_text, bind=["_text", "_capitalizing"]) - """Text of the label.""" - - theme_text_color = OptionProperty( - "Primary", - allownone=True, - options=[ - "Primary", - "Secondary", - "Hint", - "Error", - "Custom", - "ContrastParentBackground", - ], - ) - """ - Label color scheme name. - - Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`, - `'Custom'`, `'ContrastParentBackground'`. - - :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None`. - """ - - text_color = ColorProperty(None) - """Label text color in ``rgba`` format. - - :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - _text_color_str = StringProperty() - - parent_background = ColorProperty(None) - can_capitalize = BooleanProperty(True) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.bind( - font_style=self.update_font_style, - can_capitalize=self.update_font_style, - ) - self.on_theme_text_color(None, self.theme_text_color) - self.update_font_style() - self.on_opposite_colors(None, self.opposite_colors) - Clock.schedule_once(self.check_font_styles) - self.theme_cls.bind(theme_style=self._do_update_theme_color) - - def check_font_styles(self, *dt): - if self.font_style not in list(self.theme_cls.font_styles.keys()): - raise ValueError( - f"MDLabel.font_style is set to an invalid option '{self.font_style}'." - f"Must be one of: {list(self.theme_cls.font_styles)}" - ) - else: - return True - - def update_font_style(self, *args): - if self.check_font_styles() is True: - font_info = self.theme_cls.font_styles[self.font_style] - self.font_name = font_info[0] - self.font_size = sp(font_info[1]) - if font_info[2] and self.can_capitalize: - self._capitalizing = True - else: - self._capitalizing = False - - # TODO: Add letter spacing change - # self.letter_spacing = font_info[3] - - def on_theme_text_color(self, instance, value): - op = self.opposite_colors - if op: - self._text_color_str = __MDLabel_colors__.get("OP", "").get( - value, "" - ) - else: - self._text_color_str = __MDLabel_colors__.get(value, "") - if self._text_color_str: - self._do_update_theme_color() - else: - # 'Custom' and 'ContrastParentBackground' lead here, as well as the - # generic None value it's not yet been set - self._text_color_str = "" - if value == "Custom" and self.text_color: - self.color = self.text_color - elif value == "ContrastParentBackground" and self.parent_background: - self.color = get_contrast_text_color(self.parent_background) - else: - self.color = [0, 0, 0, 1] - - def _do_update_theme_color(self, *arguments): - if self._text_color_str: - self.color = getattr(self.theme_cls, self._text_color_str) - - def on_text_color(self, *args): - if self.theme_text_color == "Custom": - self.color = self.text_color - - def on_opposite_colors(self, instance, value): - self.on_theme_text_color(self, self.theme_text_color) - - -class MDIcon(MDLabel): - icon = StringProperty("android") - """ - Label icon name. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'android'`. - """ - - source = StringProperty(None, allownone=True) - """ - Path to icon. - - :attr:`source` is an :class:`~kivy.properties.StringProperty` - and defaults to `None`. - """ diff --git a/kivymd/uix/list.py b/kivymd/uix/list.py deleted file mode 100644 index 9d29fb8..0000000 --- a/kivymd/uix/list.py +++ /dev/null @@ -1,1027 +0,0 @@ -""" -Components/List -=============== - -.. seealso:: - - `Material Design spec, Lists `_ - -.. rubric:: Lists are continuous, vertical indexes of text or images. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists.png - :align: center - -The class :class:`~MDList` in combination with a :class:`~BaseListItem` like -:class:`~OneLineListItem` will create a list that expands as items are added to -it, working nicely with `Kivy's` :class:`~kivy.uix.scrollview.ScrollView`. - -Due to the variety in sizes and controls in the `Material Design spec`, -this module suffers from a certain level of complexity to keep the widgets -compliant, flexible and performant. - -For this `KivyMD` provides list items that try to cover the most common usecases, -when those are insufficient, there's a base class called :class:`~BaseListItem` -which you can use to create your own list items. This documentation will only -cover the provided ones, for custom implementations please refer to this -module's source code. - -`KivyMD` provides the following list items classes for use: - -Text only ListItems -------------------- - -- OneLineListItem_ -- TwoLineListItem_ -- ThreeLineListItem_ - -ListItems with widget containers --------------------------------- - -These widgets will take other widgets that inherit from :class:`~ILeftBody`, -:class:`ILeftBodyTouch`, :class:`~IRightBody` or :class:`~IRightBodyTouch` and -put them in their corresponding container. - -As the name implies, :class:`~ILeftBody` and :class:`~IRightBody` will signal -that the widget goes into the left or right container, respectively. - -:class:`~ILeftBodyTouch` and :class:`~IRightBodyTouch` do the same thing, -except these widgets will also receive touch events that occur within their -surfaces. - -`KivyMD` provides base classes such as :class:`~ImageLeftWidget`, -:class:`~ImageRightWidget`, :class:`~IconRightWidget`, :class:`~IconLeftWidget`, -based on the above classes. - -.. rubric:: Allows the use of items with custom widgets on the left. - -- OneLineAvatarListItem_ -- TwoLineAvatarListItem_ -- ThreeLineAvatarListItem_ -- OneLineIconListItem_ -- TwoLineIconListItem_ -- ThreeLineIconListItem_ - -.. rubric:: It allows the use of elements with custom widgets on the left - and the right. - -- OneLineAvatarIconListItem_ -- TwoLineAvatarIconListItem_ -- ThreeLineAvatarIconListItem_ - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.list import OneLineListItem - - KV = ''' - ScrollView: - - MDList: - id: container - ''' - - - 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/lists.gif - :align: center - -Events of List --------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - ScrollView: - - MDList: - - OneLineAvatarIconListItem: - on_release: print("Click!") - - IconLeftWidget: - icon: "github" - - OneLineAvatarIconListItem: - on_release: print("Click 2!") - - IconLeftWidget: - icon: "gitlab" - ''' - - - class MainApp(MDApp): - def build(self): - return Builder.load_string(KV) - - - MainApp().run() - -.. OneLineListItem: -OneLineListItem ---------------- - -.. code-block:: kv - - OneLineListItem: - text: "Single-line item" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineListItem.png - :align: center - -.. TwoLineListItem: -TwoLineListItem ---------------- - -.. code-block:: kv - - TwoLineListItem: - text: "Two-line item" - secondary_text: "Secondary text here" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineListItem.png - :align: center - -.. ThreeLineListItem: -ThreeLineListItem ------------------ - -.. code-block:: kv - - ThreeLineListItem: - text: "Three-line item" - secondary_text: "This is a multi-line label where you can" - tertiary_text: "fit more text than usual" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineListItem.png - :align: center - -.. OneLineAvatarListItem: -OneLineAvatarListItem ---------------------- - -.. code-block:: kv - - OneLineAvatarListItem: - text: "Single-line item with avatar" - - ImageLeftWidget: - source: "data/logo/kivy-icon-256.png" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists-map.png - :align: center - -.. TwoLineAvatarListItem: -TwoLineAvatarListItem ---------------------- - -.. code-block:: kv - - TwoLineAvatarListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" - - ImageLeftWidget: - source: "data/logo/kivy-icon-256.png" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarListItem.png - :align: center - - -.. ThreeLineAvatarListItem: -ThreeLineAvatarListItem ------------------------ - -.. code-block:: kv - - ThreeLineAvatarListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" - - ImageLeftWidget: - source: "data/logo/kivy-icon-256.png" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarListItem.png - :align: center - -.. OneLineIconListItem: -OneLineIconListItem -------------------- - -.. code-block:: kv - - OneLineIconListItem: - text: "Single-line item with avatar" - - IconLeftWidget: - icon: "language-python" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineIconListItem.png - :align: center - -.. TwoLineIconListItem: -TwoLineIconListItem -------------------- - -.. code-block:: kv - - TwoLineIconListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" - - IconLeftWidget: - icon: "language-python" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineIconListItem.png - :align: center - -.. ThreeLineIconListItem: -ThreeLineIconListItem ---------------------- - -.. code-block:: kv - - ThreeLineIconListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" - - IconLeftWidget: - icon: "language-python" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineIconListItem.png - :align: center - -.. OneLineAvatarIconListItem: -OneLineAvatarIconListItem -------------------------- - -.. code-block:: kv - - OneLineAvatarIconListItem: - text: "One-line item with avatar" - - IconLeftWidget: - icon: "plus" - - IconRightWidget: - icon: "minus" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarIconListItem.png - :align: center - -.. TwoLineAvatarIconListItem: -TwoLineAvatarIconListItem -------------------------- - -.. code-block:: kv - - TwoLineAvatarIconListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" - - IconLeftWidget: - icon: "plus" - - IconRightWidget: - icon: "minus" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarIconListItem.png - :align: center - -.. ThreeLineAvatarIconListItem: -ThreeLineAvatarIconListItem ---------------------------- - -.. code-block:: kv - - ThreeLineAvatarIconListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" - - IconLeftWidget: - icon: "plus" - - IconRightWidget: - icon: "minus" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarIconListItem.png - :align: center - -Custom list item ----------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem - from kivymd.uix.selectioncontrol import MDCheckbox - from kivymd.icon_definitions import md_icons - - - KV = ''' - : - - IconLeftWidget: - icon: root.icon - - RightCheckbox: - - - BoxLayout: - - ScrollView: - - MDList: - id: scroll - ''' - - - class ListItemWithCheckbox(OneLineAvatarIconListItem): - '''Custom list item.''' - - icon = StringProperty("android") - - - class RightCheckbox(IRightBodyTouch, MDCheckbox): - '''Custom right container.''' - - - class MainApp(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_start(self): - icons = list(md_icons.keys()) - for i in range(30): - self.root.ids.scroll.add_widget( - ListItemWithCheckbox(text=f"Item {i}", icon=icons[i]) - ) - - - MainApp().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-item.png - :align: center - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.list import IRightBodyTouch - - KV = ''' - OneLineAvatarIconListItem: - text: "One-line item with avatar" - on_size: - self.ids._right_container.width = container.width - self.ids._right_container.x = container.width - - IconLeftWidget: - icon: "cog" - - Container: - id: container - - MDIconButton: - icon: "minus" - - MDIconButton: - icon: "plus" - ''' - - - class Container(IRightBodyTouch, MDBoxLayout): - adaptive_width = True - - - class MainApp(MDApp): - def build(self): - return Builder.load_string(KV) - - - MainApp().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-right-container.png - :align: center -""" - -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.floatlayout import FloatLayout - -import kivymd.material_resources as m_res -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import RectangularRippleBehavior -from kivymd.uix.button import MDIconButton -from kivymd.uix.gridlayout import MDGridLayout -from kivymd.uix.selectioncontrol import MDCheckbox -from kivymd.utils.fitimage import FitImage - -Builder.load_string( - """ -#:import m_res kivymd.material_resources - - - - cols: 1 - adaptive_height: True - padding: 0, self._list_vertical_padding - - - - size_hint_y: None - canvas: - Color: - rgba: - self.theme_cls.divider_color if root.divider is not None\ - else (0, 0, 0, 0) - Line: - points: (root.x ,root.y, root.x+self.width, root.y)\ - if root.divider == 'Full' else\ - (root.x+root._txt_left_pad, root.y,\ - root.x+self.width-root._txt_left_pad-root._txt_right_pad,\ - root.y) - Color: - rgba: root.bg_color if root.bg_color else (0, 0, 0, 0) - Rectangle: - pos: self.pos - size: self.size - - BoxLayout: - id: _text_container - orientation: 'vertical' - pos: root.pos - padding: - root._txt_left_pad, root._txt_top_pad,\ - root._txt_right_pad, root._txt_bot_pad - - MDLabel: - id: _lbl_primary - text: root.text - font_style: root.font_style - theme_text_color: root.theme_text_color - text_color: root.text_color - size_hint_y: None - height: self.texture_size[1] - markup: True - shorten_from: 'right' - shorten: True - - MDLabel: - id: _lbl_secondary - text: '' if root._num_lines == 1 else root.secondary_text - font_style: root.secondary_font_style - theme_text_color: root.secondary_theme_text_color - text_color: root.secondary_text_color - size_hint_y: None - height: 0 if root._num_lines == 1 else self.texture_size[1] - shorten: True - shorten_from: 'right' - markup: True - - MDLabel: - id: _lbl_tertiary - text: '' if root._num_lines == 1 else root.tertiary_text - font_style: root.tertiary_font_style - theme_text_color: root.tertiary_theme_text_color - text_color: root.tertiary_text_color - size_hint_y: None - height: 0 if root._num_lines == 1 else self.texture_size[1] - shorten: True - shorten_from: 'right' - markup: True - - - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height/2 - self.height/2 - size: dp(40), dp(40) - - - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height - root._txt_top_pad - self.height - dp(5) - size: dp(40), dp(40) - - - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height/2 - self.height/2 - size: dp(48), dp(48) - - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height - root._txt_top_pad - self.height - dp(5) - size: dp(48), dp(48) - - - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height/2 - self.height/2 - size: dp(48), dp(48) - - - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height/2 - self.height/2 - size: dp(48), dp(48) - - - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height/2 - self.height/2 - size: dp(48), dp(48) - - - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height/2 - self.height/2 - size: dp(48), dp(48) - - - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height - root._txt_top_pad - self.height - dp(5) - size: dp(48), dp(48) -""" -) - - -class MDList(MDGridLayout): - """ListItem container. Best used in conjunction with a - :class:`kivy.uix.ScrollView`. - - When adding (or removing) a widget, it will resize itself to fit its - children, plus top and bottom paddings as described by the `MD` spec. - """ - - _list_vertical_padding = NumericProperty("8dp") - - def add_widget(self, widget, index=0, canvas=None): - super().add_widget(widget, index, canvas) - self.height += widget.height - - def remove_widget(self, widget): - super().remove_widget(widget) - self.height -= widget.height - - -class BaseListItem( - ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, FloatLayout -): - """ - Base class to all ListItems. Not supposed to be instantiated on its own. - """ - - text = StringProperty() - """ - Text shown in the first line. - - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - text_color = ColorProperty(None) - """ - Text color in ``rgba`` format used if :attr:`~theme_text_color` is set - to `'Custom'`. - - :attr:`text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - font_style = StringProperty("Subtitle1") - """ - Text font style. See ``kivymd.font_definitions.py``. - - :attr:`font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Subtitle1'`. - """ - - theme_text_color = StringProperty("Primary", allownone=True) - """ - Theme text color in ``rgba`` format for primary text. - - :attr:`theme_text_color` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Primary'`. - """ - - secondary_text = StringProperty() - """ - Text shown in the second line. - - :attr:`secondary_text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - tertiary_text = StringProperty() - """ - The text is displayed on the third line. - - :attr:`tertiary_text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - secondary_text_color = ColorProperty(None) - """ - Text color in ``rgba`` format used for secondary text - if :attr:`~secondary_theme_text_color` is set to `'Custom'`. - - :attr:`secondary_text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - tertiary_text_color = ColorProperty(None) - """ - Text color in ``rgba`` format used for tertiary text - if :attr:`~tertiary_theme_text_color` is set to 'Custom'. - - :attr:`tertiary_text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - secondary_theme_text_color = StringProperty("Secondary", allownone=True) - """ - Theme text color for secondary text. - - :attr:`secondary_theme_text_color` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Secondary'`. - """ - - tertiary_theme_text_color = StringProperty("Secondary", allownone=True) - """ - Theme text color for tertiary text. - - :attr:`tertiary_theme_text_color` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Secondary'`. - """ - - secondary_font_style = StringProperty("Body1") - """ - Font style for secondary line. See ``kivymd.font_definitions.py``. - - :attr:`secondary_font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Body1'`. - """ - - tertiary_font_style = StringProperty("Body1") - """ - Font style for tertiary line. See ``kivymd.font_definitions.py``. - - :attr:`tertiary_font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Body1'`. - """ - - divider = OptionProperty( - "Full", options=["Full", "Inset", None], allownone=True - ) - """ - Divider mode. Available options are: `'Full'`, `'Inset'` - and default to `'Full'`. - - :attr:`divider` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'Full'`. - """ - - bg_color = ColorProperty(None) - """ - Background color for menu item. - - :attr:`bg_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _txt_left_pad = NumericProperty("16dp") - _txt_top_pad = NumericProperty() - _txt_bot_pad = NumericProperty() - _txt_right_pad = NumericProperty(m_res.HORIZ_MARGINS) - _num_lines = 3 - _no_ripple_effect = BooleanProperty(False) - - -class ILeftBody: - """ - Pseudo-interface for widgets that go in the left container for - ListItems that support it. - - Implements nothing and requires no implementation, for annotation only. - """ - - pass - - -class ILeftBodyTouch: - """ - Same as :class:`~ILeftBody`, but allows the widget to receive touch - events instead of triggering the ListItem's ripple effect. - """ - - pass - - -class IRightBody: - """ - Pseudo-interface for widgets that go in the right container for - ListItems that support it. - - Implements nothing and requires no implementation, for annotation only. - """ - - pass - - -class IRightBodyTouch: - """ - Same as :class:`~IRightBody`, but allows the widget to receive touch - events instead of triggering the ``ListItem``'s ripple effect - """ - - pass - - -class ContainerSupport: - """ - Overrides ``add_widget`` in a ``ListItem`` to include support - for ``I*Body`` widgets when the appropiate containers are present. - """ - - _touchable_widgets = ListProperty() - - def add_widget(self, widget, index=0): - if issubclass(widget.__class__, ILeftBody): - self.ids._left_container.add_widget(widget) - elif issubclass(widget.__class__, ILeftBodyTouch): - self.ids._left_container.add_widget(widget) - self._touchable_widgets.append(widget) - elif issubclass(widget.__class__, IRightBody): - self.ids._right_container.add_widget(widget) - elif issubclass(widget.__class__, IRightBodyTouch): - self.ids._right_container.add_widget(widget) - self._touchable_widgets.append(widget) - else: - return super().add_widget(widget) - - def remove_widget(self, widget): - super().remove_widget(widget) - if widget in self._touchable_widgets: - self._touchable_widgets.remove(widget) - - def on_touch_down(self, touch): - if self.propagate_touch_to_touchable_widgets(touch, "down"): - return - super().on_touch_down(touch) - - def on_touch_move(self, touch, *args): - if self.propagate_touch_to_touchable_widgets(touch, "move", *args): - return - super().on_touch_move(touch, *args) - - def on_touch_up(self, touch): - if self.propagate_touch_to_touchable_widgets(touch, "up"): - return - super().on_touch_up(touch) - - def propagate_touch_to_touchable_widgets(self, touch, touch_event, *args): - triggered = False - for i in self._touchable_widgets: - if i.collide_point(touch.x, touch.y): - triggered = True - if touch_event == "down": - i.on_touch_down(touch) - elif touch_event == "move": - i.on_touch_move(touch, *args) - elif touch_event == "up": - i.on_touch_up(touch) - return triggered - - -class OneLineListItem(BaseListItem): - """A one line list item.""" - - _txt_top_pad = NumericProperty("16dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) - _height = NumericProperty() - _num_lines = 1 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(48) if not self._height else self._height - - -class TwoLineListItem(BaseListItem): - """A two line list item.""" - - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) - _height = NumericProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(72) if not self._height else self._height - - -class ThreeLineListItem(BaseListItem): - """A three line list item.""" - - _txt_top_pad = NumericProperty("16dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) - _height = NumericProperty() - _num_lines = 3 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(88) if not self._height else self._height - - -class OneLineAvatarListItem(ContainerSupport, BaseListItem): - _txt_left_pad = NumericProperty("72dp") - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("19dp") # dp(24) - dp(5) - _height = NumericProperty() - _num_lines = 1 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(56) if not self._height else self._height - - -class TwoLineAvatarListItem(OneLineAvatarListItem): - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) - _height = NumericProperty() - _num_lines = 2 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(72) if not self._height else self._height - - -class ThreeLineAvatarListItem(ContainerSupport, ThreeLineListItem): - _txt_left_pad = NumericProperty("72dp") - - -class OneLineIconListItem(ContainerSupport, OneLineListItem): - _txt_left_pad = NumericProperty("72dp") - - -class TwoLineIconListItem(OneLineIconListItem): - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) - _height = NumericProperty() - _num_lines = 2 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(72) if not self._height else self._height - - -class ThreeLineIconListItem(ContainerSupport, ThreeLineListItem): - _txt_left_pad = NumericProperty("72dp") - - -class OneLineRightIconListItem(ContainerSupport, OneLineListItem): - # dp(40) = dp(16) + dp(24): - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class TwoLineRightIconListItem(OneLineRightIconListItem): - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") # dp(20) - dp(5) - _height = NumericProperty() - _num_lines = 2 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(72) if not self._height else self._height - - -class ThreeLineRightIconListItem(ContainerSupport, ThreeLineListItem): - # dp(40) = dp(16) + dp(24): - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class OneLineAvatarIconListItem(OneLineAvatarListItem): - # dp(40) = dp(16) + dp(24): - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class TwoLineAvatarIconListItem(TwoLineAvatarListItem): - # dp(40) = dp(16) + dp(24): - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class ThreeLineAvatarIconListItem(ThreeLineAvatarListItem): - # dp(40) = dp(16) + dp(24): - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class ImageLeftWidget(ILeftBody, FitImage): - pass - - -class ImageRightWidget(IRightBodyTouch, FitImage): - pass - - -class IconRightWidget(IRightBodyTouch, MDIconButton): - pass - - -class IconLeftWidget(ILeftBodyTouch, MDIconButton): - pass - - -class CheckboxLeftWidget(ILeftBodyTouch, MDCheckbox): - pass diff --git a/kivymd/uix/menu.py b/kivymd/uix/menu.py deleted file mode 100644 index 8287d35..0000000 --- a/kivymd/uix/menu.py +++ /dev/null @@ -1,792 +0,0 @@ -""" -Components/Menu -=============== - -.. seealso:: - - `Material Design spec, Menus `_ - -.. rubric:: Menus display a list of choices on temporary surfaces. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-previous.png - :align: center - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.menu import MDDropdownMenu - - KV = ''' - MDScreen: - - MDRaisedButton: - id: button - text: "PRESS ME" - pos_hint: {"center_x": .5, "center_y": .5} - on_release: app.menu.open() - ''' - - - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - menu_items = [ - { - "text": f"Item {i}", - "viewclass": "OneLineListItem", - "on_release": lambda x=f"Item {i}": self.menu_callback(x), - } for i in range(5) - ] - self.menu = MDDropdownMenu( - caller=self.screen.ids.button, - items=menu_items, - width_mult=4, - ) - - def menu_callback(self, text_item): - print(text_item) - - def build(self): - return self.screen - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-usage.gif - :align: center - -.. Warning:: Do not create the :class:`~MDDropdownMenu` object when you open - the menu window. Because on a mobile device this one will be very slow! - -Wrong ------ - -.. code-block:: python - - menu = MDDropdownMenu(caller=self.screen.ids.button, items=menu_items) - menu.open() - -Customization of menu item --------------------------- - -Menu items are created in the same way as items for the :class:`~kivy.uix.recycleview.RecycleView` class. - -.. code-block:: python - - from kivy.lang import Builder - from kivy.metrics import dp - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem - from kivymd.uix.menu import MDDropdownMenu - - KV = ''' - - disabled: True - adaptive_size: True - pos_hint: {"center_y": .5} - - MDIconButton: - icon: root.icon - user_font_size: "16sp" - md_bg_color_disabled: 0, 0, 0, 0 - - MDLabel: - text: root.text - font_style: "Caption" - adaptive_size: True - pos_hint: {"center_y": .5} - - - - - IconLeftWidget: - icon: root.left_icon - - RightContentCls: - id: container - icon: root.right_icon - text: root.right_text - - - MDScreen: - - MDRaisedButton: - id: button - text: "PRESS ME" - pos_hint: {"center_x": .5, "center_y": .5} - on_release: app.menu.open() - ''' - - - class RightContentCls(IRightBodyTouch, MDBoxLayout): - icon = StringProperty() - text = StringProperty() - - - class Item(OneLineAvatarIconListItem): - left_icon = StringProperty() - right_icon = StringProperty() - right_text = StringProperty() - - - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - menu_items = [ - { - "text": f"Item {i}", - "right_text": f"R+{i}", - "right_icon": "apple-keyboard-command", - "left_icon": "git", - "viewclass": "Item", - "height": dp(54), - "on_release": lambda x=f"Item {i}": self.menu_callback(x), - } for i in range(5) - ] - self.menu = MDDropdownMenu( - caller=self.screen.ids.button, - items=menu_items, - width_mult=4, - ) - - def menu_callback(self, text_item): - print(text_item) - - def build(self): - return self.screen - - - Test().run() - - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-right.gif - :align: center - -Menu with MDToolbar -------------------- - -The :class:`~MDDropdownMenu` works well with the standard -:class:`~kivymd.uix.toolbar.MDToolbar`. Since the buttons on the Toolbar are created -by the MDToolbar component, it is necessary to pass the button as an argument to -the callback using `lambda x: app.callback(x)`. - -.. note:: This example uses drop down menus for both the righthand and - lefthand menus (i.e both the 'triple bar' and 'triple dot' menus) to - illustrate that it is possible. A better solution for the 'triple bar' menu - would probably have been :class:`~kivymd.uix.MDNavigationDrawer`. - - -.. code-block:: python - - from kivy.lang import Builder - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.menu import MDDropdownMenu - from kivymd.uix.snackbar import Snackbar - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "MDToolbar" - left_action_items: [["menu", lambda x: app.callback(x)]] - right_action_items: [["dots-vertical", lambda x: app.callback(x)]] - - MDLabel: - text: "Content" - halign: "center" - ''' - - - class Test(MDApp): - def build(self): - menu_items = [ - { - "viewclass": "OneLineListItem", - "text": f"Item {i}", - "height": dp(56), - "on_release": lambda x=f"Item {i}": self.menu_callback(x), - } for i in range(5) - ] - self.menu = MDDropdownMenu( - items=menu_items, - width_mult=4, - ) - return Builder.load_string(KV) - - def callback(self, button): - self.menu.caller = button - self.menu.open() - - def menu_callback(self, text_item): - self.menu.dismiss() - Snackbar(text=text_item).open() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-menu.gif - :align: center - -Position menu -============= - -Bottom position ---------------- - -.. seealso:: - - :attr:`~MDDropdownMenu.position` - -.. code-block:: python - - from kivy.lang import Builder - from kivy.metrics import dp - from kivy.properties import StringProperty - - from kivymd.uix.list import OneLineIconListItem - from kivymd.app import MDApp - from kivymd.uix.menu import MDDropdownMenu - - KV = ''' - - - IconLeftWidget: - icon: root.icon - - - MDScreen - - MDTextField: - id: field - pos_hint: {'center_x': .5, 'center_y': .6} - size_hint_x: None - width: "200dp" - hint_text: "Password" - on_focus: if self.focus: app.menu.open() - ''' - - - class IconListItem(OneLineIconListItem): - icon = StringProperty() - - - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - menu_items = [ - { - "viewclass": "IconListItem", - "icon": "git", - "height": dp(56), - "text": f"Item {i}", - "on_release": lambda x=f"Item {i}": self.set_item(x), - } for i in range(5)] - self.menu = MDDropdownMenu( - caller=self.screen.ids.field, - items=menu_items, - position="bottom", - width_mult=4, - ) - - def set_item(self, text__item): - self.screen.ids.field.text = text__item - self.menu.dismiss() - - def build(self): - return self.screen - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-position.gif - :align: center - -Center position ---------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.metrics import dp - from kivy.properties import StringProperty - - from kivymd.uix.list import OneLineIconListItem - from kivymd.app import MDApp - from kivymd.uix.menu import MDDropdownMenu - - KV = ''' - - - IconLeftWidget: - icon: root.icon - - - MDScreen - - MDDropDownItem: - id: drop_item - pos_hint: {'center_x': .5, 'center_y': .5} - text: 'Item 0' - on_release: app.menu.open() - ''' - - - class IconListItem(OneLineIconListItem): - icon = StringProperty() - - - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - menu_items = [ - { - "viewclass": "IconListItem", - "icon": "git", - "text": f"Item {i}", - "height": dp(56), - "on_release": lambda x=f"Item {i}": self.set_item(x), - } for i in range(5) - ] - self.menu = MDDropdownMenu( - caller=self.screen.ids.drop_item, - items=menu_items, - position="center", - width_mult=4, - ) - self.menu.bind() - - def set_item(self, text_item): - self.screen.ids.drop_item.set_item(text_item) - self.menu.dismiss() - - def build(self): - return self.screen - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-position-center.gif - :align: center -""" - -__all__ = ("MDDropdownMenu",) - -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 ( - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.recycleview import RecycleView - -import kivymd.material_resources as m_res -from kivymd.theming import ThemableBehavior - -Builder.load_string( - """ -#:import STD_INC kivymd.material_resources.STANDARD_INCREMENT - - - - adaptive_width: True - - - - - IconLeftWidget: - id: icon_widget - icon: root.icon - - - - size_hint: None, None - width: root.width_mult * STD_INC - bar_width: 0 - key_viewclass: "viewclass" - key_size: "height" - - RecycleBoxLayout: - padding: 0, "4dp", 0, "4dp" - default_size: None, dp(48) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - - - - - MDCard: - id: card - elevation: 10 - size_hint: None, None - size: md_menu.size - pos: md_menu.pos - opacity: md_menu.opacity - radius: root.radius - md_bg_color: - root.background_color \ - if root.background_color else root.theme_cls.bg_dark - - MDMenu: - id: md_menu - drop_cls: root - width_mult: root.width_mult - size_hint: None, None - size: 0, 0 - opacity: 0 -""" -) - - -class MDMenu(RecycleView): - width_mult = NumericProperty(1) - """ - See :attr:`~MDDropdownMenu.width_mult`. - """ - - drop_cls = ObjectProperty() - """ - See :class:`~MDDropdownMenu` class. - """ - - -class MDDropdownMenu(ThemableBehavior, FloatLayout): - """ - :Events: - `on_release` - The method that will be called when you click menu items. - """ - - items = ListProperty() - """ - See :attr:`~kivy.uix.recycleview.RecycleView.data`. - - :attr:`items` is a :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - width_mult = NumericProperty(1) - """ - This number multiplied by the standard increment (56dp on mobile, - 64dp on desktop, determines the width of the menu items. - - If the resulting number were to be too big for the application Window, - the multiplier will be adjusted for the biggest possible one. - - :attr:`width_mult` is a :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - max_height = NumericProperty() - """ - The menu will grow no bigger than this number. Set to 0 for no limit. - - :attr:`max_height` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - border_margin = NumericProperty("4dp") - """ - Margin between Window border and menu. - - :attr:`border_margin` is a :class:`~kivy.properties.NumericProperty` - and defaults to `4dp`. - """ - - ver_growth = OptionProperty(None, allownone=True, options=["up", "down"]) - """ - Where the menu will grow vertically to when opening. Set to None to let - the widget pick for you. Available options are: `'up'`, `'down'`. - - :attr:`ver_growth` is a :class:`~kivy.properties.OptionProperty` - and defaults to `None`. - """ - - hor_growth = OptionProperty(None, allownone=True, options=["left", "right"]) - """ - Where the menu will grow horizontally to when opening. Set to None to let - the widget pick for you. Available options are: `'left'`, `'right'`. - - :attr:`hor_growth` is a :class:`~kivy.properties.OptionProperty` - and defaults to `None`. - """ - - background_color = ColorProperty(None) - """ - Color of the background of the menu. - - :attr:`background_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - opening_transition = StringProperty("out_cubic") - """ - Type of animation for opening a menu window. - - :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - opening_time = NumericProperty(0.2) - """ - Menu window opening animation time and you can set it to 0 - if you don't want animation of menu opening. - - :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - caller = ObjectProperty() - """ - The widget object that caller the menu window. - - :attr:`caller` is a :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - position = OptionProperty("auto", options=["auto", "center", "bottom"]) - """ - Menu window position relative to parent element. - Available options are: `'auto'`, `'center'`, `'bottom'`. - - :attr:`position` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'auto'`. - """ - - radius = ListProperty( - [ - dp(7), - ] - ) - """ - Menu radius. - - :attr:`radius` is a :class:`~kivy.properties.ListProperty` - and defaults to `'[dp(7),]'`. - """ - - _start_coords = [] - _calculate_complete = False - _calculate_process = False - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Window.bind(on_resize=self.check_position_caller) - Window.bind(on_maximize=self.set_menu_properties) - Window.bind(on_restore=self.set_menu_properties) - self.register_event_type("on_dismiss") - self.menu = self.ids.md_menu - self.target_height = 0 - - def check_position_caller(self, instance, width, height): - self.set_menu_properties(0) - - def set_menu_properties(self, interval=0): - """Sets the size and position for the menu window.""" - - if self.caller: - self.ids.md_menu.data = self.items - # We need to pick a starting point, see how big we need to be, - # and where to grow to. - self._start_coords = self.caller.to_window( - self.caller.center_x, self.caller.center_y - ) - self.target_width = self.width_mult * m_res.STANDARD_INCREMENT - - # If we're wider than the Window... - if self.target_width > Window.width: - # ...reduce our multiplier to max allowed. - self.target_width = ( - int(Window.width / m_res.STANDARD_INCREMENT) - * m_res.STANDARD_INCREMENT - ) - - # Set the target_height of the menu depending on the size of - # each MDMenuItem or MDMenuItemIcon. - self.target_height = 0 - for item in self.ids.md_menu.data: - self.target_height += item.get("height", dp(72)) - - # If we're over max_height... - if 0 < self.max_height < self.target_height: - self.target_height = self.max_height - - # Establish vertical growth direction. - if self.ver_growth is not None: - ver_growth = self.ver_growth - else: - # If there's enough space below us: - if ( - self.target_height - <= self._start_coords[1] - self.border_margin - ): - ver_growth = "down" - # if there's enough space above us: - elif ( - self.target_height - < Window.height - self._start_coords[1] - self.border_margin - ): - ver_growth = "up" - # Otherwise, let's pick the one with more space and adjust ourselves. - else: - # If there"s more space below us: - if ( - self._start_coords[1] - >= Window.height - self._start_coords[1] - ): - ver_growth = "down" - self.target_height = ( - self._start_coords[1] - self.border_margin - ) - # If there's more space above us: - else: - ver_growth = "up" - self.target_height = ( - Window.height - - self._start_coords[1] - - self.border_margin - ) - - if self.hor_growth is not None: - hor_growth = self.hor_growth - else: - # If there's enough space to the right: - if ( - self.target_width - <= Window.width - self._start_coords[0] - self.border_margin - ): - hor_growth = "right" - # if there's enough space to the left: - elif ( - self.target_width - < self._start_coords[0] - self.border_margin - ): - hor_growth = "left" - # Otherwise, let's pick the one with more space and adjust ourselves. - else: - # if there"s more space to the right: - if ( - Window.width - self._start_coords[0] - >= self._start_coords[0] - ): - hor_growth = "right" - self.target_width = ( - Window.width - - self._start_coords[0] - - self.border_margin - ) - # if there"s more space to the left: - else: - hor_growth = "left" - self.target_width = ( - self._start_coords[0] - self.border_margin - ) - - if ver_growth == "down": - self.tar_y = self._start_coords[1] - self.target_height - else: # should always be "up" - self.tar_y = self._start_coords[1] - - if hor_growth == "right": - self.tar_x = self._start_coords[0] - else: # should always be "left" - self.tar_x = self._start_coords[0] - self.target_width - self._calculate_complete = True - - def open(self): - """Animate the opening of a menu window.""" - - def open(interval): - if not self._calculate_complete: - return - if self.position == "auto": - self.menu.pos = self._start_coords - anim = Animation( - x=self.tar_x, - y=self.tar_y, - width=self.target_width, - height=self.target_height, - duration=self.opening_time, - opacity=1, - transition=self.opening_transition, - ) - anim.start(self.menu) - else: - if self.position == "center": - self.menu.pos = ( - self._start_coords[0] - self.target_width / 2, - self._start_coords[1] - self.target_height / 2, - ) - elif self.position == "bottom": - self.menu.pos = ( - self._start_coords[0] - self.target_width / 2, - self.caller.pos[1] - self.target_height, - ) - anim = Animation( - width=self.target_width, - height=self.target_height, - duration=self.opening_time, - opacity=1, - transition=self.opening_transition, - ) - anim.start(self.menu) - Window.add_widget(self) - Clock.unschedule(open) - self._calculate_process = False - - self.set_menu_properties() - if not self._calculate_process: - self._calculate_process = True - Clock.schedule_interval(open, 0) - - def on_touch_down(self, touch): - if not self.menu.collide_point(*touch.pos): - self.dispatch("on_dismiss") - return True - super().on_touch_down(touch) - return True - - def on_touch_move(self, touch): - super().on_touch_move(touch) - return True - - def on_touch_up(self, touch): - super().on_touch_up(touch) - return True - - def on_dismiss(self): - """Called when the menu is closed.""" - - Window.remove_widget(self) - self.menu.width = 0 - self.menu.height = 0 - self.menu.opacity = 0 - - def dismiss(self): - """Closes the menu.""" - - self.on_dismiss() diff --git a/kivymd/uix/navigationdrawer.py b/kivymd/uix/navigationdrawer.py deleted file mode 100644 index 30f5ddc..0000000 --- a/kivymd/uix/navigationdrawer.py +++ /dev/null @@ -1,746 +0,0 @@ -""" -Components/Navigation Drawer -============================ - -.. seealso:: - - `Material Design spec, Navigation drawer `_ - -.. rubric:: Navigation drawers provide access to destinations in your app. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.png - :align: center - -When using the class :class:`~MDNavigationDrawer` skeleton of your `KV` markup -should look like this: - -.. code-block:: kv - - Root: - - MDNavigationLayout: - - ScreenManager: - - Screen_1: - - Screen_2: - - MDNavigationDrawer: - # This custom rule should implement what will be appear in your MDNavigationDrawer - ContentNavigationDrawer - -A simple example: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.boxlayout import BoxLayout - - from kivymd.app import MDApp - - KV = ''' - Screen: - - MDNavigationLayout: - - ScreenManager: - - Screen: - - BoxLayout: - orientation: 'vertical' - - MDToolbar: - title: "Navigation Drawer" - elevation: 10 - left_action_items: [['menu', lambda x: nav_drawer.set_state("open")]] - - Widget: - - - MDNavigationDrawer: - id: nav_drawer - - ContentNavigationDrawer: - ''' - - - class ContentNavigationDrawer(BoxLayout): - pass - - - class TestNavigationDrawer(MDApp): - def build(self): - return Builder.load_string(KV) - - - TestNavigationDrawer().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.gif - :align: center - -.. Note:: :class:`~MDNavigationDrawer` is an empty - :class:`~kivymd.uix.card.MDCard` panel. - -Let's extend the ``ContentNavigationDrawer`` class from the above example and -create content for our :class:`~MDNavigationDrawer` panel: - -.. code-block:: kv - - # Menu item in the DrawerList list. - : - theme_text_color: "Custom" - on_release: self.parent.set_color_item(self) - - IconLeftWidget: - id: icon - icon: root.icon - theme_text_color: "Custom" - text_color: root.text_color - -.. code-block:: python - - class ItemDrawer(OneLineIconListItem): - icon = StringProperty() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-item.png - :align: center - -Top of ``ContentNavigationDrawer`` and ``DrawerList`` for menu items: - -.. code-block:: kv - - : - orientation: "vertical" - padding: "8dp" - spacing: "8dp" - - AnchorLayout: - anchor_x: "left" - size_hint_y: None - height: avatar.height - - Image: - id: avatar - size_hint: None, None - size: "56dp", "56dp" - source: "kivymd.png" - - MDLabel: - text: "KivyMD library" - font_style: "Button" - size_hint_y: None - height: self.texture_size[1] - - MDLabel: - text: "kivydevelopment@gmail.com" - font_style: "Caption" - size_hint_y: None - height: self.texture_size[1] - - ScrollView: - - DrawerList: - id: md_list - -.. code-block:: python - - class ContentNavigationDrawer(BoxLayout): - pass - - - class DrawerList(ThemableBehavior, MDList): - def set_color_item(self, instance_item): - '''Called when tap on a menu item.''' - - # Set the color of the icon and text for the menu item. - for item in self.children: - if item.text_color == self.theme_cls.primary_color: - item.text_color = self.theme_cls.text_color - break - instance_item.text_color = self.theme_cls.primary_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-top.png - :align: center - -Create a menu list for ``ContentNavigationDrawer``: - -.. code-block:: python - - def on_start(self): - icons_item = { - "folder": "My files", - "account-multiple": "Shared with me", - "star": "Starred", - "history": "Recent", - "checkbox-marked": "Shared with me", - "upload": "Upload", - } - for icon_name in icons_item.keys(): - self.root.ids.content_drawer.ids.md_list.add_widget( - ItemDrawer(icon=icon_name, text=icons_item[icon_name]) - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-work.gif - :align: center - -Switching screens in the ``ScreenManager`` and using the common ``MDToolbar`` ------------------------------------------------------------------------------ - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.boxlayout import BoxLayout - from kivy.properties import ObjectProperty - - from kivymd.app import MDApp - - KV = ''' - : - - ScrollView: - - MDList: - - OneLineListItem: - text: "Screen 1" - on_press: - root.nav_drawer.set_state("close") - root.screen_manager.current = "scr 1" - - OneLineListItem: - text: "Screen 2" - on_press: - root.nav_drawer.set_state("close") - root.screen_manager.current = "scr 2" - - - Screen: - - MDToolbar: - id: toolbar - pos_hint: {"top": 1} - elevation: 10 - title: "MDNavigationDrawer" - left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]] - - MDNavigationLayout: - x: toolbar.height - - ScreenManager: - id: screen_manager - - Screen: - name: "scr 1" - - MDLabel: - text: "Screen 1" - halign: "center" - - Screen: - name: "scr 2" - - MDLabel: - text: "Screen 2" - halign: "center" - - MDNavigationDrawer: - id: nav_drawer - - ContentNavigationDrawer: - screen_manager: screen_manager - nav_drawer: nav_drawer - ''' - - - class ContentNavigationDrawer(BoxLayout): - screen_manager = ObjectProperty() - nav_drawer = ObjectProperty() - - - class TestNavigationDrawer(MDApp): - def build(self): - return Builder.load_string(KV) - - - TestNavigationDrawer().run() - -NavigationDrawer with type ``standard`` ---------------------------------------- - -You can use the ``standard`` behavior type for the NavigationDrawer: - -.. code-block:: kv - - MDNavigationDrawer: - type: "standard" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standard.gif - :align: center - -.. seealso:: - - `Full example of Components-Navigation-Drawer `_ -""" - -__all__ = ("MDNavigationLayout", "MDNavigationDrawer") - -from kivy.animation import Animation, AnimationTransition -from kivy.core.window import Window -from kivy.graphics.context_instructions import Color -from kivy.graphics.vertex_instructions import Rectangle -from kivy.lang import Builder -from kivy.properties import ( - AliasProperty, - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.screenmanager import ScreenManager - -from kivymd.uix.behaviors import FakeRectangularElevationBehavior -from kivymd.uix.card import MDCard -from kivymd.uix.toolbar import MDToolbar - -Builder.load_string( - """ -#:import Window kivy.core.window.Window - - -: - size_hint_x: None - width: Window.width - dp(56) if Window.width <= dp(376) else dp(320) - x: - (self.width * (self.open_progress - 1)) \ - if self.anchor == "left" \ - else (Window.width - self.width * self.open_progress) - - canvas: - Clear - Color: - rgba: self.md_bg_color - RoundedRectangle: - size: self.size - pos: self.pos - source: root.background - radius: root._radius - md_bg_color: self.theme_cls.bg_light -""" -) - - -class NavigationDrawerContentError(Exception): - pass - - -class MDNavigationLayout(FloatLayout): - _scrim_color = ObjectProperty(None) - _scrim_rectangle = ObjectProperty(None) - - _screen_manager = ObjectProperty(None) - _navigation_drawer = ObjectProperty(None) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.bind(width=self.update_pos) - - def update_pos(self, *args): - drawer = self._navigation_drawer - manager = self._screen_manager - if not drawer or not manager: - return - if drawer.type == "standard": - manager.size_hint_x = None - if drawer.anchor == "left": - manager.x = drawer.width + drawer.x - manager.width = self.width - manager.x - else: - manager.x = 0 - manager.width = drawer.x - elif drawer.type == "modal": - manager.size_hint_x = None - manager.x = 0 - if drawer.anchor == "left": - manager.width = self.width - manager.x - else: - manager.width = self.width - - def add_scrim(self, widget): - with widget.canvas.after: - self._scrim_color = Color(rgba=[0, 0, 0, 0]) - self._scrim_rectangle = Rectangle(pos=widget.pos, size=widget.size) - widget.bind( - pos=self.update_scrim_rectangle, - size=self.update_scrim_rectangle, - ) - - def update_scrim_rectangle(self, *args): - self._scrim_rectangle.pos = self.pos - self._scrim_rectangle.size = self.size - - def add_widget(self, widget, index=0, canvas=None): - """ - Only two layouts are allowed: - :class:`~kivy.uix.screenmanager.ScreenManager` and - :class:`~MDNavigationDrawer`. - """ - - if not isinstance( - widget, (MDNavigationDrawer, ScreenManager, MDToolbar) - ): - raise NavigationDrawerContentError( - "The MDNavigationLayout must contain " - "only `MDNavigationDrawer` and `ScreenManager`" - ) - if isinstance(widget, ScreenManager): - self._screen_manager = widget - self.add_scrim(widget) - if isinstance(widget, MDNavigationDrawer): - self._navigation_drawer = widget - widget.bind( - x=self.update_pos, width=self.update_pos, anchor=self.update_pos - ) - if len(self.children) > 3: - raise NavigationDrawerContentError( - "The MDNavigationLayout must contain " - "only `MDNavigationDrawer` and `ScreenManager`" - ) - return super().add_widget(widget) - - -class MDNavigationDrawer(MDCard, FakeRectangularElevationBehavior): - type = OptionProperty("modal", options=("standard", "modal")) - """ - Type of drawer. Modal type will be on top of screen. Standard type will be - at left or right of screen. Also it automatically disables - :attr:`close_on_click` and :attr:`enable_swiping` to prevent closing - drawer for standard type. - - :attr:`type` is a :class:`~kivy.properties.OptionProperty` - and defaults to `modal`. - """ - - anchor = OptionProperty("left", options=("left", "right")) - """ - Anchoring screen edge for drawer. Set it to `'right'` for right-to-left - languages. Available options are: `'left'`, `'right'`. - - :attr:`anchor` is a :class:`~kivy.properties.OptionProperty` - and defaults to `left`. - """ - - close_on_click = BooleanProperty(True) - """ - Close when click on scrim or keyboard escape. It automatically sets to - False for "standard" type. - - :attr:`close_on_click` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - state = OptionProperty("close", options=("close", "open")) - """ - Indicates if panel closed or opened. Sets after :attr:`status` change. - Available options are: `'close'`, `'open'`. - - :attr:`state` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'close'`. - """ - - status = OptionProperty( - "closed", - options=( - "closed", - "opening_with_swipe", - "opening_with_animation", - "opened", - "closing_with_swipe", - "closing_with_animation", - ), - ) - """ - Detailed state. Sets before :attr:`state`. Bind to :attr:`state` instead - of :attr:`status`. Available options are: `'closed'`, - `'opening_with_swipe'`, `'opening_with_animation'`, `'opened'`, - `'closing_with_swipe'`, `'closing_with_animation'`. - - :attr:`status` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'closed'`. - """ - - open_progress = NumericProperty(0.0) - """ - Percent of visible part of side panel. The percent is specified as a - floating point number in the range 0-1. 0.0 if panel is closed and 1.0 if - panel is opened. - - :attr:`open_progress` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.0`. - """ - - enable_swiping = BooleanProperty(True) - """ - Allow to open or close navigation drawer with swipe. It automatically - sets to False for "standard" type. - - :attr:`enable_swiping` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - swipe_distance = NumericProperty(10) - """ - The distance of the swipe with which the movement of navigation drawer - begins. - - :attr:`swipe_distance` is a :class:`~kivy.properties.NumericProperty` - and defaults to `10`. - """ - - swipe_edge_width = NumericProperty(20) - """ - The size of the area in px inside which should start swipe to drag - navigation drawer. - - :attr:`swipe_edge_width` is a :class:`~kivy.properties.NumericProperty` - and defaults to `20`. - """ - - scrim_color = ColorProperty([0, 0, 0, 0.5]) - """ - Color for scrim. Alpha channel will be multiplied with - :attr:`_scrim_alpha`. Set fourth channel to 0 if you want to disable - scrim. - - :attr:`scrim_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.5]`. - """ - - _radius = ListProperty([0, 0, 0, 0]) - - def _get_scrim_alpha(self): - _scrim_alpha = 0 - if self.type == "modal": - _scrim_alpha = self._scrim_alpha_transition(self.open_progress) - if ( - isinstance(self.parent, MDNavigationLayout) - and self.parent._scrim_color - ): - self.parent._scrim_color.rgba = self.scrim_color[:3] + [ - self.scrim_color[3] * _scrim_alpha - ] - return _scrim_alpha - - _scrim_alpha = AliasProperty( - _get_scrim_alpha, - None, - bind=("_scrim_alpha_transition", "open_progress", "scrim_color"), - ) - """ - Multiplier for alpha channel of :attr:`scrim_color`. For internal - usage only. - """ - - scrim_alpha_transition = StringProperty("linear") - """ - The name of the animation transition type to use for changing - :attr:`scrim_alpha`. - - :attr:`scrim_alpha_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'linear'`. - """ - - def _get_scrim_alpha_transition(self): - return getattr(AnimationTransition, self.scrim_alpha_transition) - - _scrim_alpha_transition = AliasProperty( - _get_scrim_alpha_transition, - None, - bind=("scrim_alpha_transition",), - cache=True, - ) - - opening_transition = StringProperty("out_cubic") - """ - The name of the animation transition type to use when animating to - the :attr:`state` `'open'`. - - :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - opening_time = NumericProperty(0.2) - """ - The time taken for the panel to slide to the :attr:`state` `'open'`. - - :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - closing_transition = StringProperty("out_sine") - """The name of the animation transition type to use when animating to - the :attr:`state` 'close'. - - :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_sine'`. - """ - - closing_time = NumericProperty(0.2) - """ - The time taken for the panel to slide to the :attr:`state` `'close'`. - - :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.bind( - open_progress=self.update_status, - status=self.update_status, - state=self.update_status, - ) - Window.bind(on_keyboard=self._handle_keyboard) - - def set_state(self, new_state="toggle", animation=True): - """Change state of the side panel. - New_state can be one of `"toggle"`, `"open"` or `"close"`. - """ - - if new_state == "toggle": - new_state = "close" if self.state == "open" else "open" - - if new_state == "open": - Animation.cancel_all(self, "open_progress") - self.status = "opening_with_animation" - if animation: - Animation( - open_progress=1.0, - d=self.opening_time * (1 - self.open_progress), - t=self.opening_transition, - ).start(self) - else: - self.open_progress = 1 - else: # "close" - Animation.cancel_all(self, "open_progress") - self.status = "closing_with_animation" - if animation: - Animation( - open_progress=0.0, - d=self.closing_time * self.open_progress, - t=self.closing_transition, - ).start(self) - else: - self.open_progress = 0 - - def update_status(self, *_): - status = self.status - if status == "closed": - self.state = "close" - elif status == "opened": - self.state = "open" - elif self.open_progress == 1 and status == "opening_with_animation": - self.status = "opened" - self.state = "open" - elif self.open_progress == 0 and status == "closing_with_animation": - self.status = "closed" - self.state = "close" - elif status in ( - "opening_with_swipe", - "opening_with_animation", - "closing_with_swipe", - "closing_with_animation", - ): - pass - if self.status == "closed": - self.opacity = 0 - else: - self.opacity = 1 - - def get_dist_from_side(self, x): - if self.anchor == "left": - return 0 if x < 0 else x - return 0 if x > Window.width else Window.width - x - - def on_touch_down(self, touch): - if self.status == "closed": - return False - elif self.status == "opened": - for child in self.children[:]: - if child.dispatch("on_touch_down", touch): - return True - if self.type == "standard" and not self.collide_point( - touch.ox, touch.oy - ): - return False - return True - - def on_touch_move(self, touch): - if self.enable_swiping: - if self.status == "closed": - if ( - self.get_dist_from_side(touch.ox) <= self.swipe_edge_width - and abs(touch.x - touch.ox) > self.swipe_distance - ): - self.status = "opening_with_swipe" - elif self.status == "opened": - if abs(touch.x - touch.ox) > self.swipe_distance: - self.status = "closing_with_swipe" - - if self.status in ("opening_with_swipe", "closing_with_swipe"): - self.open_progress = max( - min( - self.open_progress - + (touch.dx if self.anchor == "left" else -touch.dx) - / self.width, - 1, - ), - 0, - ) - return True - return super().on_touch_move(touch) - - def on_touch_up(self, touch): - if self.status == "opening_with_swipe": - if self.open_progress > 0.5: - self.set_state("open", animation=True) - else: - self.set_state("close", animation=True) - elif self.status == "closing_with_swipe": - if self.open_progress < 0.5: - self.set_state("close", animation=True) - else: - self.set_state("open", animation=True) - elif self.status == "opened": - if self.close_on_click and not self.collide_point( - touch.ox, touch.oy - ): - self.set_state("close", animation=True) - elif self.type == "standard" and not self.collide_point( - touch.ox, touch.oy - ): - return False - elif self.status == "closed": - return False - return True - - def on_radius(self, instance, value): - self._radius = value - - def on_type(self, *args): - if self.type == "standard": - self.enable_swiping = False - self.close_on_click = False - else: - self.enable_swiping = True - self.close_on_click = True - - def _handle_keyboard(self, window, key, *largs): - if key == 27 and self.status == "opened" and self.close_on_click: - self.set_state("close") - return True diff --git a/kivymd/uix/navigationrail.py b/kivymd/uix/navigationrail.py deleted file mode 100644 index 5f449ef..0000000 --- a/kivymd/uix/navigationrail.py +++ /dev/null @@ -1,873 +0,0 @@ -""" -Components/Navigation Rail -========================== - -.. seealso:: - - `Material Design spec, Navigation rail `_ - -.. rubric:: Navigation rails provide ergonomic movement between primary destinations in apps. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail.png - :align: center - -Usage -===== - -.. code-block:: kv - - MDNavigationRail: - - MDNavigationRailItem: - - MDNavigationRailItem: - - MDNavigationRailItem: - -.. code-block:: python - - from kivy.factory import Factory - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - #:import get_color_from_hex kivy.utils.get_color_from_hex - - - - size_hint_y: None - height: "240dp" - - - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "MDNavigationRail" - md_bg_color: rail.md_bg_color - - MDBoxLayout: - - MDNavigationRail: - id: rail - md_bg_color: get_color_from_hex("#344954") - color_normal: get_color_from_hex("#718089") - color_active: get_color_from_hex("#f3ab44") - - MDNavigationRailItem: - icon: "language-cpp" - text: "C++" - - MDNavigationRailItem: - icon: "language-python" - text: "Python" - - MDNavigationRailItem: - icon: "language-swift" - text: "Swift" - - MDBoxLayout: - padding: "24dp" - - ScrollView: - - MDList: - id: box - cols: 3 - spacing: "12dp" - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(9): - tile = Factory.MyTile(source="cpp.png") - tile.stars = 5 - self.root.ids.box.add_widget(tile) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-usage.png - :align: center - -""" - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.behaviors import ButtonBehavior - -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import CircularRippleBehavior, HoverBehavior -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDFloatingActionButton -from kivymd.uix.card import MDCard -from kivymd.uix.label import MDLabel - -Builder.load_string( - """ -#:import StiffScrollEffect kivymd.stiffscroll.StiffScrollEffect - - - - id: title_box - padding: "12dp", "8dp", "8dp", "8dp" - spacing: "16dp" - - MDIconButton: - id: title_icon - icon: root.icon - pos_hint: {"center_x": .5, "center_y": .5} - on_release: - root.navigation_rail.rail_state = "open" \ - if root.navigation_rail.rail_state == "close" else "close" - - MDLabel: - id: lbl_title - text: root.title - size_hint: None, None - -text_size: dp(140), None - size: dp(140), self.texture_size[1] - pos_hint: {"center_y": .5} - opacity: 0 - markup: True - - MDIconButton: - id: icon_settings - opacity: 0 - opposite_colors: True - icon: "cog" - pos_hint: {"center_y": .5} - - - - theme_text_color: "Custom" - - canvas.before: - Color: - rgba: root.md_bg_color - RoundedRectangle: - pos: dp(8), self.y - self._padding_right / 2 + dp(1.5) - size: - self.width + self._canvas_width - dp(3), \ - self.height + self._padding_right - dp(3) - radius: [self.height / 2] - - Color: - rgba: 1, 1, 1, self._alpha - Rectangle: - texture: self._lbl_text.texture - size: self._lbl_text.texture_size - pos: self.width + dp(8), self.y + dp(18) - - - - spacing: "8dp" - padding: "16dp" - - - - orientation: "vertical" - spacing: "12dp" - adaptive_height: True - - MDIcon: - id: btn_icon - icon: root.icon - font_size: "24dp" - halign: "center" - theme_text_color: "Custom" - text_color: - root.theme_cls.text_color \ - if not root._color_normal else root._color_normal - pos_hint: {"center_y": .5} - - MDLabel: - id: lbl_text - text: root.text - size_hint_y: None - height: self.texture_size[1] - halign: "center" - markup: True - theme_text_color: "Custom" - text_color: - btn_icon.text_color \ - if root.navigation_rail.visible == "Persistent" else (0, 0, 0, 0) - - - - orientation: "vertical" - padding: 0, "8dp", 0, "8dp" - elevation: root.elevation - - MDBoxLayout: - id: box_title - size_hint: None, None - size: root.width * 2, "56dp" - - ScrollView: - effect_cls: StiffScrollEffect - bar_width: 0 - - MDList: - id: box - spacing: "8dp" -""" -) - -__all__ = ("MDNavigationRail",) - - -class BaseNavigationRailBoxItem(MDBoxLayout): - """The base class to which the menu items will be added - (:class:`~MDNavigationRailItem` classes).""" - - -class BaseNavigationRailFloatingButton(MDFloatingActionButton): - """Class for a custom MDFloatingActionButton that can stretch.""" - - text = StringProperty() - """ - The text will be placed to the left of the button icon.. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - _canvas_width = NumericProperty(0) - _alpha = NumericProperty(0) - _padding_right = NumericProperty(0) - _lbl_text = MDLabel(markup=True) - - def on_text(self, instance, value): - self._lbl_text.text = value - - -class NavigationRailTitle(MDBoxLayout): - navigation_rail = ObjectProperty() - """ - `:class:`~kivymd.uix.navigationrail.MDNavigationRail` object. - - :attr:`navigation_rail` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - icon = StringProperty("menu") - """ - Icon item. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'blank'`. - """ - - title = StringProperty("Rail") - """ - Text title. - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Rail'`. - """ - - -class MDNavigationRailItem( - ThemableBehavior, - HoverBehavior, - CircularRippleBehavior, - ButtonBehavior, - BaseNavigationRailBoxItem, -): - icon = StringProperty("checkbox-blank") - """ - Icon item. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank'`. - """ - - text = StringProperty() - """ - Text item. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - navigation_rail = ObjectProperty() - """ - `:class:`~kivymd.uix.navigationrail.MDNavigationRail` object. - - :attr:`navigation_rail` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - color_normal = ColorProperty(None) - """See :attr:`~MDNavigationRail.color_normal` attribute.""" - - color_active = ColorProperty(None) - """See :attr:`~MDNavigationRail.color_active` attribute.""" - - _color_normal = ColorProperty(None) - _color_active = ColorProperty(None) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._no_ripple_effect = True - Clock.schedule_once(self.set_width) - - def set_width(self, interval): - """Sets the size of the menu item.""" - - self.size_hint = (None, None) - self.size = (self.navigation_rail.width, self.navigation_rail.width) - - def on_enter(self): - if self.navigation_rail.use_hover_behavior: - Animation( - md_bg_color=self.theme_cls.bg_darkest - if not self.navigation_rail.hover_bg - else self.navigation_rail.hover_bg, - t=self.navigation_rail.color_transition, - d=self.navigation_rail.color_change_duration, - ).start(self) - - def on_leave(self): - if self.navigation_rail.use_hover_behavior: - Animation( - md_bg_color=self.theme_cls.bg_light - if self.navigation_rail.md_bg_color == self.theme_cls.bg_light - else self.navigation_rail.md_bg_color, - t=self.navigation_rail.color_transition, - d=self.navigation_rail.color_change_duration, - ).start(self) - - def on_release(self): - self.navigation_rail.set_color_menu_item(self) - self.navigation_rail.dispatch("on_item_switch", self) - - def on_visible(self, instance, value): - if value in ("Selected", "Unlabeled"): - Clock.schedule_once(self.set_item_label_transparency) - - def set_item_label_transparency(self, interval): - self.ids.lbl_text.text_color = (0, 0, 0, 0) - - -class MDNavigationRail(MDCard): - """ - :Events: - `on_item_switch` - Called when the menu item is switched. - `on_open` - Called when a rail is opened. - `on_close` - Called when a rail is closed. - `on_action_button` - """ - - use_hover_behavior = BooleanProperty(False) - """ - Whether to use the HoverBehavior effect for menu items. - - .. code-block:: kv - - MDNavigationRail: - use_hover_behavior: True - hover_bg: 0, 0, 0, .2 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-hover-behavior.gif - :align: center - - :attr:`use_hover_behavior` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - hover_bg = ColorProperty(None) - """ - The background color for the menu item. - Used when :attr:`use_hover_behavior` parameter is `True`. - """ - - use_resizeable = BooleanProperty(False) - """ - Allows you to change the width of the rail (open/close). - - .. code-block:: python - - from kivy.factory import Factory - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - #:import get_color_from_hex kivy.utils.get_color_from_hex - - - - size_hint_y: None - height: "240dp" - - - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "MDNavigationRail" - md_bg_color: rail.md_bg_color - left_action_items: [["menu", lambda x: app.rail_open()]] - - MDBoxLayout: - - MDNavigationRail: - id: rail - md_bg_color: get_color_from_hex("#344954") - color_normal: get_color_from_hex("#718089") - color_active: get_color_from_hex("#f3ab44") - use_resizeable: True - - MDNavigationRailItem: - icon: "language-cpp" - text: "C++" - - MDNavigationRailItem: - icon: "language-java" - text: "Java" - - MDNavigationRailItem: - icon: "language-swift" - text: "Swift" - - MDBoxLayout: - padding: "24dp" - - ScrollView: - - MDList: - id: box - cols: 3 - spacing: "12dp" - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def rail_open(self): - if self.root.ids.rail.rail_state == "open": - self.root.ids.rail.rail_state = "close" - else: - self.root.ids.rail.rail_state = "open" - - def on_start(self): - for i in range(9): - tile = Factory.MyTile(source="kitten.png") - tile.stars = 5 - self.root.ids.box.add_widget(tile) - - - Test().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-use-resizeable.gif - :align: center - - :attr:`use_resizeable` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - use_title = BooleanProperty(False) - """ - Whether to use an additional panel at the top of the rail. - - .. code-block:: kv - - MDNavigationRail: - use_resizeable: True - use_title: True - icon_title: "logo.png" - text_title: "[b][color=#ffffff]Example[/color][/b]" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-use-title.gif - :align: center - - :attr:`use_title` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - icon_title = StringProperty("menu") - """ - Icon (name or path to `png` file) for :class:`~NavigationRailTitle` class. - - :attr:`icon_title` is an :class:`~kivy.properties.StringProperty` - and defaults to `'menu'`. - """ - - text_title = StringProperty("Rail") - """ - Text title for :class:`~NavigationRailTitle` class. - - :attr:`text_title` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Rail'`. - """ - - use_action_button = BooleanProperty(False) - """ - Should :class:`~MDFloatingActionButton` button be used. - - .. code-block:: kv - - MDNavigationRail: - use_action_button: True - action_text_button: "COMPOSE" - on_action_button: print(args) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-use-action-button.gif - :align: center - - :attr:`use_action_button` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - action_icon_button = StringProperty("plus") - """ - Icon of :attr:`~use_action_button`. - - :attr:`action_icon_button` is an :class:`~kivy.properties.StringProperty` - and defaults to `'plus'`. - """ - - action_text_button = StringProperty() - """ - Text of :attr:`~use_action_button`. - - :attr:`action_text_button` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - action_color_button = ColorProperty(None) - """ - Text of :attr:`~use_action_button`. - - :attr:`action_color_button` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - color_normal = ColorProperty(None) - """ - Color normal of item menu. - - :attr:`color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - color_active = ColorProperty(None) - """ - Color active of item menu. - - :attr:`color_active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - visible = OptionProperty( - "Persistent", options=["Selected", "Persistent", "Unlabeled"] - ) - """ - Item label visible type. - Available options are: `'Selected'`, `'Persistent'`, `'Unlabeled'`. - - Persistent - ~~~~~~~~~~ - - .. code-block:: kv - - MDNavigationRail: - visible: "Persistent" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-visible-persistent.gif - :align: center - - Selected - ~~~~~~~~ - - .. code-block:: kv - - MDNavigationRail: - visible: "Selected" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-visible-selected.gif - :align: center - - Unlabeled - ~~~~~~~~~ - - .. code-block:: kv - - MDNavigationRail: - visible: "Unlabeled" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-visible-unlabeled.gif - :align: center - - :attr:`visible` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Persistent'`. - """ - - color_transition = StringProperty("linear") - """ - Animation type when changing the color of a menu item. - - :attr:`color_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'linear'`. - """ - - color_change_duration = NumericProperty(0.2) - """ - Animation duration when changing menu item color. - - :attr:`color_change_duration` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - rail_state = OptionProperty("close", options=("close", "open")) - """ - Closed or open rails. - - :attr:`rail_state` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'close'`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.floating_action_button = None - self.elevation = 0 - - self.register_event_type("on_action_button") - self.register_event_type("on_item_switch") - self.register_event_type("on_open") - self.register_event_type("on_close") - - Clock.schedule_once(self.set_items_visible) - Clock.schedule_once(self.set_width) - Clock.schedule_once(self.set_action_icon_button) - Clock.schedule_once(self.set_action_text_button) - Clock.schedule_once(self.set_box_title_size) - Clock.schedule_once(self.set_action_color_button) - Clock.schedule_once(self.set_items_color) - - def anim_color_normal(self, item): - color = ( - self.theme_cls.text_color - if not item.color_normal - else item.color_normal - ) - Animation( - _color_normal=color, - t=self.color_transition, - d=self.color_change_duration, - ).start(item) - if item.visible == "Selected": - Animation( - text_color=(0, 0, 0, 0), - t=self.color_transition, - d=self.color_change_duration, - ).start(item.ids.lbl_text) - - def anim_color_active(self, item): - color = ( - self.theme_cls.primary_color - if not item.color_active - else item.color_active - ) - Animation( - _color_normal=color, - t=self.color_transition, - d=self.color_change_duration, - ).start(item) - if item.visible == "Selected": - item.ids.lbl_text.text_color = item._color_normal - Animation( - text_color=color, - t=self.color_transition, - d=self.color_change_duration, - ).start(item.ids.lbl_text) - - def item_switch(self, instance_item): - instance_item.dispatch("on_release") - - def add_widget(self, widget, index=0, canvas=None): - if isinstance(widget, MDNavigationRailItem): - widget.navigation_rail = self - self.bind(color_normal=widget.setter("color_normal")) - self.bind(color_normal=widget.setter("_color_normal")) - self.bind(color_active=widget.setter("color_active")) - self.ids.box.add_widget(widget) - else: - return super().add_widget(widget) - - def open(self): - def set_opacity_title_component(*args): - if self.use_title: - Animation(opacity=1, d=0.2).start( - self.ids.box_title.children[0].ids.lbl_title - ) - Animation(opacity=1, d=0.2).start( - self.ids.box_title.children[0].ids.icon_settings - ) - - if self.use_resizeable: - anim = Animation(width=self.width * 4, d=0.2) - anim.bind(on_complete=set_opacity_title_component) - anim.start(self) - - if self.floating_action_button: - Animation( - _canvas_width=self.floating_action_button.width + dp(124), - _padding_right=dp(8), - _alpha=1, - d=0.2, - ).start(self.floating_action_button) - self.dispatch("on_open") - - def close(self): - if self.use_resizeable: - Animation(width=self.width / 4, d=0.2).start(self) - if self.use_title: - Animation(opacity=0, d=0.2).start( - self.ids.box_title.children[0].ids.lbl_title - ) - Animation(opacity=0, d=0.02).start( - self.ids.box_title.children[0].ids.icon_settings - ) - if self.floating_action_button: - Animation( - _canvas_width=0, - _padding_right=0, - d=0.2, - _alpha=0, - ).start(self.floating_action_button) - self.dispatch("on_close") - - def on_rail_state(self, instance, value): - if value == "open": - self.open() - elif value == "close": - self.close() - - def on_item_switch(self, instance_item): - """Called when the menu item is switched.""" - - def on_open(self): - """Called when a rail is opened.""" - - def on_close(self): - """Called when a rail is closed.""" - - def on_action_button(self, floating_action_button): - """Called when the `MDFloatingActionButton` is pressed.""" - - def on_visible(self, instance, value): - for widget in self.ids.box.children: - if isinstance(widget, MDNavigationRailItem): - widget.visible = value - - def on_use_title(self, instance, value): - def add_title(interval): - if value: - self.ids.box_title.add_widget( - NavigationRailTitle( - navigation_rail=self, - icon=self.icon_title, - title=self.text_title, - ) - ) - - Clock.schedule_once(add_title) - - def on_use_resizeable(self, instance, value): - def add_item(interval): - if value: - for widget in self.ids.box.children: - if isinstance(widget, MDNavigationRailItem): - widget.orientation = "horizontal" - self.ids.box.spacing = "24dp" - self.use_hover_behavior = False - widget._no_ripple_effect = True - widget.size_hint_x = None - widget.width = self.width * 2 - - Clock.schedule_once(add_item) - - def on_use_action_button(self, instance, value): - if value: - rail_box = BaseNavigationRailBoxItem( - size_hint=(None, None), - width=self.width, - padding=dp(8), - ) - self.floating_action_button = BaseNavigationRailFloatingButton( - pos_hint={"top": 1}, - ) - self.floating_action_button.bind( - on_release=self.press_floating_action_button - ) - rail_box.height = self.floating_action_button.height + dp(16) - rail_box.add_widget(self.floating_action_button) - self.ids.box.add_widget(rail_box) - self.ids.box.children.reverse() - - def press_floating_action_button(self, floating_action_button): - self.dispatch("on_action_button", floating_action_button) - - def set_action_color_button(self, interval): - if self.floating_action_button and self.action_color_button: - self.floating_action_button.md_bg_color = self.action_color_button - - def set_width(self, interval): - self.size_hint_x = None - self.width = dp(72) - - def set_box_title_size(self, interval): - if not self.use_title: - self.remove_widget(self.ids.box_title) - - def set_action_icon_button(self, interval): - if self.floating_action_button: - self.floating_action_button.icon = self.action_icon_button - - def set_action_text_button(self, interval): - if self.floating_action_button: - self.floating_action_button.text = self.action_text_button - - def set_color_menu_item(self, instance_item): - for item in self.ids.box.children: - if isinstance(item, MDNavigationRailItem): - if instance_item is item: - self.anim_color_active(item) - else: - self.anim_color_normal(item) - - def set_items_color(self, interval): - if not self.color_normal: - self.color_normal = self.theme_cls.text_color - if not self.color_active: - self.color_active = self.theme_cls.bg_light - - def set_items_visible(self, interval): - # If the user does not set the `visible` parameter. - if self.visible == "Persistent": - for widget in self.ids.box.children: - if isinstance(widget, MDNavigationRailItem): - widget.visible = "Persistent" diff --git a/kivymd/uix/picker.py b/kivymd/uix/picker.py deleted file mode 100644 index 40d093a..0000000 --- a/kivymd/uix/picker.py +++ /dev/null @@ -1,3117 +0,0 @@ -""" -Components/Pickers -================== - -.. seealso:: - - `Material Design spec, Time picker `_ - - `Material Design spec, Date picker `_ - -.. rubric:: Includes date, time and color picker. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/picker-previous.png - :align: center - -`KivyMD` provides the following classes for use: - -- MDTimePicker_ -- MDDatePicker_ -- MDThemePicker_ - -.. MDTimePicker: -MDTimePicker ------------- - -.. rubric:: Usage - -.. code-block:: - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.picker import MDTimePicker - - KV = ''' - MDFloatLayout: - - MDRaisedButton: - text: "Open time picker" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_time_picker() - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def show_time_picker(self): - '''Open time picker dialog.''' - - time_dialog = MDTimePicker() - time_dialog.open() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDTimePicker.png - :align: center - -Binding method returning set time ---------------------------------- - -.. code-block:: python - - def show_time_picker(self): - time_dialog = MDTimePicker() - time_dialog.bind(time=self.get_time) - time_dialog.open() - - def get_time(self, instance, time): - ''' - The method returns the set time. - - :type instance: - :type time: - ''' - - return time - -Open time dialog with the specified time ----------------------------------------- - -Use the :attr:`~MDTimePicker.set_time` method of the -:class:`~MDTimePicker.` class. - -.. code-block:: python - - def show_time_picker(self): - from datetime import datetime - - # Must be a datetime object - previous_time = datetime.strptime("03:20:00", '%H:%M:%S').time() - time_dialog = MDTimePicker() - time_dialog.set_time(previous_time) - time_dialog.open() - -.. note:: For customization of the :class:`~MDTimePicker` class, see the - documentation in the :class:`~BaseDialogPicker` class. - -.. MDDatePicker: -MDDatePicker ------------- - -.. warning:: The widget is under testing. Therefore, we would be grateful if - you would let us know about the bugs found. - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.picker import MDDatePicker - - KV = ''' - MDFloatLayout: - - MDToolbar: - title: "MDDatePicker" - pos_hint: {"top": 1} - elevation: 10 - - MDRaisedButton: - text: "Open time picker" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_date_picker() - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_save(self, instance, value, date_range): - ''' - Events called when the "OK" dialog box button is clicked. - - :type instance: ; - - :param value: selected date; - :type value: ; - - :param date_range: list of 'datetime.date' objects in the selected range; - :type date_range: ; - ''' - - print(instance, value, date_range) - - def on_cancel(self, instance, value): - '''Events called when the "CANCEL" dialog box button is clicked.''' - - def show_date_picker(self): - date_dialog = MDDatePicker() - date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel) - date_dialog.open() - - - Test().run() - - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDDatePicker.gif - :align: center - -Open date dialog with the specified date ----------------------------------------- - -.. code-block:: python - - def show_date_picker(self): - date_dialog = MDDatePicker(year=1983, month=4, day=12) - date_dialog.open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/previous-date.png - :align: center - -You can set the time interval from and to the set date. All days of the week -that are not included in this range will have the status `disabled`. - -.. code-block:: python - - def show_date_picker(self): - date_dialog = MDDatePicker( - min_date=datetime.date(2021, 2, 15), - max_date=datetime.date(2021, 3, 27), - ) - date_dialog.open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/range-date.gif - :align: center - -The range of available dates can be changed in the picker dialog: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/change-range-date.gif - :align: center - -Select year ------------ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/select-year-date.gif - :align: center - -.. warning:: The list of years when opening is not automatically set - to the current year. - -You can set the range of years using the :attr:`~kivymd.uix.picker.MDDatePicker.min_year` and -:attr:`~kivymd.uix.picker.MDDatePicker.max_year` attributes: - -.. code-block:: python - - def show_date_picker(self): - date_dialog = MDDatePicker(min_year=2021, max_year=2030) - date_dialog.open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/min-max-year-date.png - :align: center - -Set and select a date range ---------------------------- - -.. code-block:: python - - def show_date_picker(self): - date_dialog = MDDatePicker(mode="range") - date_dialog.open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/set-select-range-date.gif - :align: center - -.. MDThemePicker: -MDThemePicker -------------- - -.. code-block:: python - - def show_theme_picker(self): - theme_dialog = MDThemePicker() - theme_dialog.open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDThemePicker.gif - :align: center -""" - -__all__ = ("MDTimePicker", "MDDatePicker", "MDThemePicker", "BaseDialogPicker") - -import calendar -import datetime -import re -from datetime import date - -from kivy import Logger -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.event import EventDispatcher -from kivy.factory import Factory -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.behaviors import ButtonBehavior, FocusBehavior -from kivy.uix.recyclegridlayout import RecycleGridLayout -from kivy.uix.recycleview.layout import LayoutSelectionBehavior -from kivy.uix.recycleview.views import RecycleDataViewBehavior -from kivy.utils import get_color_from_hex -from kivy.vector import Vector - -from kivymd.color_definitions import colors, palette -from kivymd.theming import ThemableBehavior -from kivymd.toast import toast -from kivymd.uix.behaviors import ( - CircularRippleBehavior, - FakeRectangularElevationBehavior, - SpecificBackgroundColorBehavior, -) -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDIconButton -from kivymd.uix.circularlayout import MDCircularLayout -from kivymd.uix.dialog import BaseDialog -from kivymd.uix.label import MDLabel -from kivymd.uix.relativelayout import MDRelativeLayout -from kivymd.uix.textfield import MDTextField -from kivymd.uix.tooltip import MDTooltip - -Builder.load_string( - """ -#:import os os -#:import date datetime.date -#:import calendar calendar -#:import platform platform -#:import Clock kivy.clock.Clock -#:import images_path kivymd.images_path - - - - on_enter: - self.tooltip_text = "" if self.owner \ - and self.owner._input_date_dialog_open \ - or self.owner._select_year_dialog_open \ - else self.hint_text - - - - - - - _calendar_layout: _calendar_layout - size_hint: None, None - size: - (dp(328), dp(512) - root._shift_dialog_height) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(528), dp(328) - root._shift_dialog_height) - - MDRelativeLayout: - id: container - background: os.path.join(images_path, "transparent.png") - - canvas: - Color: - rgb: - app.theme_cls.primary_color \ - if not root.primary_color else root.primary_color - RoundedRectangle: - size: - (dp(328), dp(120)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(168), dp(328) - root._shift_dialog_height) - pos: - (0, root.height - dp(120)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (0, 0) - radius: - (root.radius[0], root.radius[1], dp(0), dp(0)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (root.radius[0], dp(0), dp(0), root.radius[3]) - Color: - rgba: - app.theme_cls.bg_normal \ - if not root.accent_color else root.accent_color - RoundedRectangle: - size: - (dp(328), dp(512) - dp(120) - root._shift_dialog_height) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(360), dp(328) - root._shift_dialog_height) - pos: - (0, 0) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(168), 0) - radius: - (dp(0), dp(0), root.radius[2], root.radius[3]) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(0), root.radius[1], root.radius[2], dp(0)) - - MDLabel: - id: label_title - font_style: "Body2" - bold: True - theme_text_color: "Custom" - size_hint_x: None - width: root.width - adaptive_height: True - text: root.title - font_name: root.font_name - pos: - (dp(24), root.height - self.height - dp(18)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(24), root.height - self.height - dp(24)) - text_color: - root.specific_text_color \ - if not root.text_toolbar_color else root.text_toolbar_color - - MDLabel: - id: label_full_date - font_style: "H4" - theme_text_color: "Custom" - size_hint_x: None - width: root.width - adaptive_height: True - font_name: root.font_name - markup: True - pos: - (dp(24), root.height - dp(120) + dp(18)) \ - if root.theme_cls.device_orientation == "portrait" \ - else \ - ( \ - dp(24) if not root._input_date_dialog_open else dp(168) + dp(24), \ - root.height - self.height - dp(96) \ - ) - text: - root.set_text_full_date(root.sel_year, root.sel_month, root.sel_day, \ - root.theme_cls.device_orientation) - text_color: - ( \ - root.specific_text_color \ - if not root.text_toolbar_color else root.text_toolbar_color \ - ) \ - if root.theme_cls.device_orientation == "portrait" \ - else \ - ( \ - ( \ - self.theme_cls.primary_color \ - if not root.primary_color else root.primary_color \ - ) \ - if root._input_date_dialog_open \ - else \ - ( \ - root.specific_text_color \ - if not root.text_toolbar_color else root.text_toolbar_color \ - ) \ - ) - - RecycleView: - id: _year_layout - key_viewclass: "viewclass" - size_hint: None, None - size: _calendar_layout.size - pos: _calendar_layout.pos - disabled: True - - canvas.before: - PushMatrix - Scale: - x: root._scale_year_layout - y: root._scale_year_layout - origin: self.center - canvas.after: - PopMatrix - - SelectYearList: - cols: 3 - default_size: dp(170), dp(36) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - - MDIconButton: - id: edit_icon - icon: "pencil" - user_font_size: "24sp" - theme_text_color: "Custom" - on_release: - root.transformation_to_dialog_input_date() \ - if not root._input_date_dialog_open else \ - Clock.schedule_once(root.transformation_from_dialog_input_date, .15) - x: - (root.width - self.width - dp(12)) \ - if root.theme_cls.device_orientation == "portrait" \ - else dp(12) - y: - (root.height - dp(120) + dp(12)) \ - if root.theme_cls.device_orientation == "portrait" \ - else dp(12) - text_color: - root.specific_text_color \ - if not root.text_toolbar_color else root.text_toolbar_color - - MDLabel: - id: label_month_selector - font_style: "Body2" - -text_size: None, None - theme_text_color: "Custom" - adaptive_size: True - text: calendar.month_name[root.month].capitalize() + " " + str(root.year) - font_name: root.font_name - pos: - (dp(24), root.height - dp(120) - self.height - dp(20)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(168) + dp(24), label_title.y) - text_color: - app.theme_cls.text_color \ - if not root.text_color else root.text_color - - DatePickerIconTooltipButton: - id: triangle - owner: root - icon: "menu-down" - ripple_scale: .5 - theme_text_color: "Custom" - hint_text: "Choose year" - on_release: - root.transformation_to_dialog_select_year() \ - if not root._select_year_dialog_open else \ - root.transformation_from_dialog_select_year() - pos: - (label_month_selector.width + dp(14), root.height - dp(123) - self.height) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(180) + label_month_selector.width, label_title.y - dp(14)) - text_color: - app.theme_cls.text_color \ - if not root.text_color else root.text_color - md_bg_color_disabled: 0, 0, 0, 0 - - DatePickerIconTooltipButton: - id: chevron_left - owner: root - icon: "chevron-left" - theme_text_color: "Secondary" - on_release: root.change_month("prev") - theme_text_color: "Custom" - hint_text: "Previous month" - x: - dp(228) if root.theme_cls.device_orientation == "portrait" \ - else dp(418) - y: - root.height - dp(120) - self.height / 2 - dp(30) \ - if root.theme_cls.device_orientation == "portrait" \ - else dp(272) - text_color: - app.theme_cls.text_color \ - if not root.text_color else root.text_color - - DatePickerIconTooltipButton: - id: chevron_right - owner: root - icon: "chevron-right" - theme_text_color: "Secondary" - on_release: root.change_month("next") - theme_text_color: "Custom" - hint_text: "Next month" - x: - dp(272) if root.theme_cls.device_orientation == "portrait" \ - else dp(464) - y: - root.height - dp(120) - self.height / 2 - dp(30) \ - if root.theme_cls.device_orientation == "portrait" \ - else dp(272) - text_color: - app.theme_cls.text_color \ - if not root.text_color else root.text_color - - # TODO: Replace the GridLayout with a RecycleView - # if it improves performance. - GridLayout: - id: _calendar_layout - cols: 7 - size_hint: None, None - size: - (dp(44 * 7), dp(40 * 7)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(46 * 7), dp(32 * 7)) - col_default_width: - dp(42) if root.theme_cls.device_orientation == "portrait" \ - else dp(39) - padding: - (dp(2), 0) if root.theme_cls.device_orientation == "portrait" \ - else (dp(7), 0) - spacing: - (dp(2), 0) if root.theme_cls.device_orientation == "portrait" \ - else (dp(7), 0) - pos: - (dp(10), dp(56)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(168) + dp(20), dp(44)) - - canvas.before: - PushMatrix - Scale: - x: root._scale_calendar_layout - y: root._scale_calendar_layout - origin: self.center - canvas.after: - PopMatrix - - MDFlatButton: - id: ok_button - width: dp(32) - pos: root.width - self.width, dp(10) - text: "OK" - theme_text_color: "Custom" - font_name: root.font_name - text_color: - root.theme_cls.primary_color \ - if not root.text_button_color else root.text_button_color - on_release: - root.dispatch(\ - "on_save", \ - date(root.sel_year, root.sel_month, root.sel_day), \ - root._date_range \ - ) - - MDFlatButton: - id: cancel_button - text: "CANCEL" - on_release: root.dispatch("on_cancel", None) - theme_text_color: "Custom" - pos: root.width - self.width - ok_button.width - dp(10), dp(10) - font_name: root.font_name - text_color: - root.theme_cls.primary_color \ - if not root.text_button_color else root.text_button_color - - - - size_hint: None, None - size: - (dp(42), dp(42)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(32), dp(32)) - disabled: True - - canvas: - Color: - rgba: - ( \ - ( \ - self.theme_cls.primary_color if not root.owner.selector_color \ - else root.owner.selector_color \ - ) \ - if root.is_selected and not self.disabled \ - else (0, 0, 0, 0) \ - ) \ - if self.owner.mode != "range" else \ - ( \ - ( \ - self.theme_cls.primary_color if not root.owner.selector_color \ - else root.owner.selector_color \ - ) \ - if root.is_selected and not self.disabled \ - and (self.owner.mode == "range" and self.owner._start_range_date) \ - else (0, 0, 0, 0) \ - ) - Ellipse: - size: - (dp(42), dp(42)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(32), dp(32)) - pos: self.pos - - # Fill marking the available dates of the range, if using the `range` mode - # or use `min_date/max_date`. - canvas.before: - Color: - rgba: - (\ - self.owner.selector_color[:-1] + [.3] \ - if self.owner.selector_color \ - else self.theme_cls.primary_color[:-1] + [.3] \ - ) \ - if not self.disabled \ - and self.text \ - and self.check_date(self.owner.year, self.owner.month, int(self.text)) \ - else (0, 0, 0, 0) - RoundedRectangle: - size: - (dp(44), dp(32)) \ - if root.theme_cls.device_orientation == "portrait" \ - else \ - (dp(32), dp(28)) \ - if self.index in [6, 13, 20, 27, 30] or self.owner._date_range \ - and self.text and self.owner._date_range[-1] == date( \ - self.current_year, \ - self.current_month, \ - int(self.text) \ - ) \ - else (dp(46), dp(28)) - pos: - (self.x - dp(1.5), self.y + dp(5)) \ - if root.theme_cls.device_orientation == "portrait" else \ - (self.x, self.y + 1) - radius: - [0, 0, 0, 0] if not self.owner._date_range else \ - ( \ - [self.width / 2, 0, 0, self.width / 2] \ - if self.text and self.owner._date_range[0] == date( \ - self.current_year, \ - self.current_month, \ - int(self.text) \ - ) \ - or (self.index in [0, 7, 14, 21, 28] and root.is_selected) \ - else \ - ( \ - [0, 0, 0, 0] if self.text \ - and self.owner._date_range[-1] != date( \ - self.current_year, \ - self.current_month, \ - int(self.text) \ - ) \ - and self.index not in [6, 13, 20, 27, 30] \ - else [0, self.width / 2, self.width, 0] \ - if root.is_selected or self.text \ - and self.owner._date_range[-1] == date( \ - self.current_year, \ - self.current_month, \ - int(self.text) \ - ) \ - else [0, 0, 0, 0]) \ - ) - - # Circle marking the beginning and end of the date range if the "range" - # mode is used. - Color: - rgba: - [0, 0, 0, 0] if not self.owner._date_range else \ - ( - ( \ - self.theme_cls.primary_color if not root.owner.selector_color \ - else root.owner.selector_color \ - ) \ - if self.text and self.owner._date_range[0] == date( \ - self.current_year, \ - self.current_month, \ - int(self.text) \ - ) \ - or \ - self.text and self.owner._date_range[-1] == date( \ - self.current_year, \ - self.current_month, \ - int(self.text) \ - ) \ - else (0, 0, 0, 0) \ - ) - Ellipse: - size: - (dp(42), dp(42)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(32), dp(32)) - pos: self.pos - - MDLabel: - font_style: "Caption" - size_hint_x: None - halign: "center" - text: root.text - font_name: root.owner.font_name - theme_text_color: "Custom" - text_color: - ( \ - root.theme_cls.primary_color \ - if not root.owner.text_current_color \ - else root.owner.text_current_color \ - ) \ - if root.is_today and not root.is_selected \ - else ( \ - ( \ - root.theme_cls.text_color \ - if not root.is_selected or root.owner.mode == "range" \ - else (1, 1, 1, 1) \ - ) \ - if not root.owner.text_color \ - else \ - ( \ - root.owner.text_color \ - if not root.is_selected else (1, 1, 1, 1)) \ - ) - - - - font_style: "Caption" - theme_text_color: "Custom" - size_hint: None, None - text_size: self.size - halign: "center" - valign: - "middle" if root.theme_cls.device_orientation == "portrait" \ - else "center" - size: - (dp(40), dp(40)) if root.theme_cls.device_orientation == "portrait" \ - else (dp(32), dp(32)) - text_color: - app.theme_cls.disabled_hint_text_color \ - if not root.owner.text_weekday_color else root.owner.text_weekday_color - - - - font_style: "Caption" - size_hint_x: None - valign: "middle" - halign: "center" - text: root.text - theme_text_color: "Custom" - on_text: root.font_name = root.owner.font_name - - canvas.before: - Color: - rgba: - root.selected_color if root.selected_color \ - else self.theme_cls.primary_color - RoundedRectangle: - pos: self.x + dp(12), self.y - size: self.width - dp(24), self.height - radius: [root.height / 2, ] - - - - adaptive_height: True - size_hint_x: None - spacing: dp(8) - width: - self.owner.width - dp(48) \ - if root.owner.theme_cls.device_orientation == "portrait" \ - else self.owner.width - dp(168) - dp(48) - y: - self.owner.height - dp(123) - self.height - dp(20) \ - if root.owner.theme_cls.device_orientation == "portrait" \ - else self.owner.height - self.height - dp(24) - x: - dp(24) if root.owner.theme_cls.device_orientation == "portrait" \ - else dp(168) + dp(24) - - - - mode: "fill" - opacity: 0 - hint_text: "dd/mm/yyyy" - input_filter: root.input_filter - do_backspace: root.do_backspace - fill_color: - (0, 0, 0, .15) \ - if not self.owner.input_field_background_color \ - else root.owner.input_field_background_color -""" -) - - -class BaseDialogPicker( - BaseDialog, - FakeRectangularElevationBehavior, - SpecificBackgroundColorBehavior, -): - """ - Base class for :attr:`~kivymd.uix.picker.MDDatePicker` and - :attr:`~kivymd.uix.picker.MDTimePicker` classes. - - :Events: - `on_save` - Events called when the "OK" dialog box button is clicked. - `on_cancel` - Events called when the "CANCEL" dialog box button is clicked. - """ - - title_input = StringProperty("INPUT DATE") - """ - Dialog title fot input date. - - :attr:`title_input` is an :class:`~kivy.properties.StringProperty` - and defaults to `INPUT DATE`. - """ - - title = StringProperty("SELECT DATE") - """ - Dialog title fot select date. - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `SELECT DATE`. - """ - - radius = ListProperty([7, 7, 7, 7]) - """ - Radius list for the four corners of the dialog. - - :attr:`radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[7, 7, 7, 7]`. - """ - - primary_color = ColorProperty(None) - """ - Background color of toolbar. - - .. code-block:: python - - MDDatePicker(primary_color=get_color_from_hex("#72225b") - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-color-date.png - :align: center - - :attr:`primary_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - accent_color = ColorProperty(None) - """ - Background color of calendar/clock face. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-color-date.png - :align: center - - :attr:`accent_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - selector_color = ColorProperty(None) - """ - Background color of the selected day of the month or hour. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selector-color-date.png - :align: center - - :attr:`selector_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_toolbar_color = ColorProperty(None) - """ - Color of labels for text on a toolbar. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-toolbar-color-date.png - :align: center - - :attr:`text_toolbar_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_color = ColorProperty(None) - """ - Color of text labels in calendar/clock face. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-color-date.png - :align: center - - :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_current_color = ColorProperty(None) - """ - Color of the text of the current day of the month/hour. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-current-color-date.png - :align: center - - :attr:`text_current_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_button_color = ColorProperty(None) - """ - Text button color. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - text_button_color=(1, 1, 1, .5), - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-button-color-date.png - :align: center - - :attr:`text_button_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - input_field_background_color = ColorProperty(None) - """ - Background color of input fields. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - input_field_background_color=(1, 1, 1, 0.2), - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png - :align: center - - :attr:`input_field_background_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - input_field_text_color = ColorProperty(None) - """ - Text color of input fields. - - Background color of input fields. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - input_field_background_color=(1, 1, 1, 0.2), - input_field_text_color=(1, 1, 1, 1), - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png - :align: center - - :attr:`input_field_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - font_name = StringProperty("Roboto") - """ - Font name for dialog window text. - - .. code-block:: python - - MDDatePicker( - primary_color=get_color_from_hex("#72225b"), - accent_color=get_color_from_hex("#5d1a4a"), - selector_color=get_color_from_hex("#e93f39"), - text_toolbar_color=get_color_from_hex("#cccccc"), - text_color=("#ffffff"), - text_current_color=get_color_from_hex("#e93f39"), - input_field_background_color=(1, 1, 1, 0.2), - input_field_text_color=(1, 1, 1, 1), - font_name="Weather.ttf", - - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-name-date.png - :align: center - - :attr:`font_name` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_save") - self.register_event_type("on_cancel") - - def on_save(self, *args): - """Events called when the "OK" dialog box button is clicked.""" - - self.dismiss() - - def on_cancel(self, *args): - """Events called when the "CANCEL" dialog box button is clicked.""" - - self.dismiss() - - -class DatePickerBaseTooltip(MDTooltip): - owner = ObjectProperty() - hint_text = StringProperty() - - -class DatePickerIconTooltipButton(MDIconButton, DatePickerBaseTooltip): - pass - - -class DatePickerWeekdayLabel(MDLabel, DatePickerBaseTooltip): - pass - - -class DatePickerTypeDateError(Exception): - pass - - -class DatePickerEnterDataField(MDTextField): - """Implements date input in 01/01/2021 format.""" - - owner = ObjectProperty() - _backspace = False - _date = "" - - def isnumeric(self, value): - """ - We are forced to create a custom method because if we set the ``int`` - value for the ``input_filter`` parameter of the text field, then the - ``-`` character is still available for keyboard input. Apparently, this - is a Kivy bug. - """ - - try: - int(value) - return True - except ValueError: - return False - - def do_backspace(self, *args): - """Prevent deleting text from the middle of a line of a text field.""" - - self._backspace = True - self.text = self.text[:-1] - self._date = self.text - self._backspace = False - - def input_filter(self, value, boolean): - """Date validity check in dd/mm/yyyy format.""" - - cursor = self.cursor[0] - if len(self.text) == 10: - return - if self.isnumeric(value): - self._date += value - value = int(value) - # checking a valid value for the number of days in a month - if cursor == 0: # first value - if self.owner.sel_month == 2: - valid_value = 2 - else: - valid_value = 3 - if value > valid_value: - self._date = self._date[:-1] - return - # check there is a day number in the month - if cursor == 1: - days_of_month = [] - for _date in self.owner.calendar.itermonthdates( - self.owner.sel_year, self.owner.sel_month - ): - if _date.month == self.owner.sel_month: - days_of_month.append(_date.day) - if not int(self._date[:2]) in days_of_month: - self._date = self._date[:-1] - return - # checking the allowed value of the number of months - elif self.cursor[0] == 2: - if int(value) > 1: - self._date = self._date[:-1] - return - elif self.cursor[0] == 4: - if int(self._date[-2:]) not in list(range(1, 13)): - self._date = self._date[:-1] - return - # checking the valid year value - elif self.cursor[0] == 6: - if not int(value): - self._date = self._date[:-1] - return - return str(value) - - def on_text(self, instance_field, value): - if value != "" and not value.isspace() and not self._backspace: - if len(value) <= 1 and instance_field.focus: - instance_field.text = value - self._set_pos_cursor() - elif len(value) == 3: - start = instance_field.text[:-1] - end = instance_field.text[-1] - instance_field.text = f"{start}/{end}" - self._set_pos_cursor() - elif len(value) == 5: - instance_field.text += "/" - self._set_pos_cursor() - if not self.owner.min_date and not self.owner.max_date: - self.owner.update_text_full_date(self._get_list_date()) - - def _get_list_date(self): - """ - Returns a list as `[dd, mm, yyyy]` from a text fied for entering a date. - """ - - return [d for d in self.text.split("/") if d] - - def _set_pos_cursor(self): - def set_pos_cursor(pos_corsor, interval=0.5): - self.cursor = (pos_corsor, 0) - - if self.focus: - Clock.schedule_once(lambda x: set_pos_cursor(len(self.text)), 0.1) - - -class DatePickerDatePickerEnterDataFieldContainer(MDBoxLayout): - owner = ObjectProperty() - - -class SelectYearList(FocusBehavior, LayoutSelectionBehavior, RecycleGridLayout): - """A class that implements a list for choosing a year.""" - - -class DatePickerDaySelectableItem( - ThemableBehavior, CircularRippleBehavior, ButtonBehavior, AnchorLayout -): - """A class that implements a list for choosing a day.""" - - text = StringProperty() - owner = ObjectProperty() - is_today = BooleanProperty(False) - is_selected = BooleanProperty(False) - current_month = NumericProperty() - current_year = NumericProperty() - index = NumericProperty(0) - - def check_date(self, year, month, day): - try: - return date(year, month, day) in self.owner._date_range - except ValueError as error: - if str(error) == "day is out of range for month": - return False - - def on_release(self): - if ( - self.owner.mode == "range" - and self.owner._end_range_date - and self.owner._start_range_date - ): - return - if ( - not self.owner._input_date_dialog_open - and not self.owner._select_year_dialog_open - ): - if self.owner.mode == "range" and not self.owner._start_range_date: - self.owner._start_range_date = date( - self.current_year, self.current_month, int(self.text) - ) - self.owner.min_date = self.owner._start_range_date - elif ( - self.owner.mode == "range" - and not self.owner._end_range_date - and self.owner._start_range_date - ): - self.owner._end_range_date = date( - self.current_year, self.current_month, int(self.text) - ) - if self.owner._end_range_date <= self.owner.min_date: - toast(self.owner.date_range_text_error) - Logger.error( - "`Data Picker: max_date` value cannot be less than " - "or equal to 'min_date' value." - ) - self.owner._start_range_date = 0 - self.owner._end_range_date = 0 - return - self.owner.max_date = self.owner._end_range_date - self.owner.update_calendar_for_date_range() - - self.owner.set_selected_widget(self) - - -class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel): - """Implements an item for a pick list of the year.""" - - index = None - selected = BooleanProperty(False) - selectable = BooleanProperty(True) - selected_color = ColorProperty([0, 0, 0, 0]) - owner = ObjectProperty() - - def refresh_view_attrs(self, rv, index, data): - self.index = index - return super().refresh_view_attrs(rv, index, data) - - def on_touch_down(self, touch): - if super().on_touch_down(touch): - return True - if self.collide_point(*touch.pos) and self.selectable: - self.owner.year = int(self.text) - # self.owner.sel_year = self.owner.year - self.owner.ids.label_full_date.text = self.owner.set_text_full_date( - self.owner.sel_year, - self.owner.sel_month, - self.owner.sel_day, - self.owner.theme_cls.device_orientation, - ) - return self.parent.select_with_touch(self.index, touch) - - def apply_selection(self, table_data, index, is_selected): - self.selected = is_selected - if is_selected: - self.selected_color = ( - self.owner.selector_color - if self.owner.selector_color - else self.theme_cls.primary_color - ) - self.text_color = (1, 1, 1, 1) - else: - if int(self.text) == self.owner.sel_year: - self.text_color = ( - self.theme_cls.primary_color - if not self.owner.text_current_color - else self.owner.text_current_color - ) - self.selected_color = [0, 0, 0, 0] - self.text_color = (0, 0, 0, 1) - - -# TODO: Add the feature to embed the `MDDatePicker` class in other layouts -# and not use it as a modal dialog. -class MDDatePicker(BaseDialogPicker): - text_weekday_color = ColorProperty(None) - """ - Text color of weekday names. - - :attr:`text_weekday_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - day = NumericProperty() - """ - The day of the month to be opened by default. If not specified, - the current number will be used. - - :attr:`day` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - month = NumericProperty() - """ - The number of month to be opened by default. If not specified, - the current number will be used. - - :attr:`month` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - year = NumericProperty() - """ - The year of month to be opened by default. If not specified, - the current number will be used. - - :attr:`year` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - min_year = NumericProperty(1914) - """ - The year of month to be opened by default. If not specified, - the current number will be used. - - :attr:`min_year` is an :class:`~kivy.properties.NumericProperty` - and defaults to `1914`. - """ - - max_year = NumericProperty(2121) - """ - The year of month to be opened by default. If not specified, - the current number will be used. - - :attr:`max_year` is an :class:`~kivy.properties.NumericProperty` - and defaults to `2121`. - """ - - mode = OptionProperty("picker", options=["picker", "range"]) - """ - Dialog type:`'picker'` type allows you to select one date; - `'range'` type allows to set a range of dates from which the - user can select a date. - Available options are: [`'picker'`, `'range'`]. - - :attr:`mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `picker`. - """ - - min_date = ObjectProperty() - """ - The minimum value of the date range for the `'mode`' parameter. - Must be an object . - - :attr:`min_date` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - max_date = ObjectProperty() - """ - The minimum value of the date range for the `'mode`' parameter. - Must be an object . - - :attr:`max_date` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - date_range_text_error = StringProperty("Error date range") - """ - Error text that will be shown on the screen in the form of a toast if the - minimum date range exceeds the maximum. - - :attr:`date_range_text_error` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Error date range'`. - """ - - sel_year = NumericProperty() - sel_month = NumericProperty() - sel_day = NumericProperty() - - _calendar_layout = ObjectProperty() - _calendar_list = None - _enter_data_field = None - _enter_data_field_two = None - _enter_data_field_container = None - _date_range = [] - _sel_day_widget = ObjectProperty() - _scale_calendar_layout = NumericProperty(1) - _scale_year_layout = NumericProperty(0) - _shift_dialog_height = NumericProperty(0) - _input_date_dialog_open = BooleanProperty(False) - _select_year_dialog_open = False - _start_range_date = 0 - _end_range_date = 0 - - def __init__( - self, - year=None, - month=None, - day=None, - firstweekday=0, - **kwargs, - ): - self.today = date.today() - self.calendar = calendar.Calendar(firstweekday) - self.sel_year = year if year else self.today.year - self.sel_month = month if month else self.today.month - self.sel_day = day if day else self.today.day - self.month = self.sel_month - self.year = self.sel_year - self.day = self.sel_day - self._current_selected_date = ( - self.sel_day, - self.sel_month, - self.sel_year, - ) - super().__init__(**kwargs) - self.theme_cls.bind(device_orientation=self.on_device_orientation) - - if self.max_date and self.min_date: - if self.min_date and not isinstance(self.min_date, date): - raise DatePickerTypeDateError( - "'min_date' must be of class " - ) - if self.max_date and not isinstance(self.max_date, date): - raise DatePickerTypeDateError( - "'max_date' must be of class " - ) - self.compare_date_range() - self._date_range = self.get_date_range() - - self.generate_list_widgets_days() - self.update_calendar(self.sel_year, self.sel_month) - - if ( - not self.max_date - and not self.min_date - and not self._date_range - and self.mode != "range" - ): - # Mark the current day. - self.set_month_day(self.sel_day) - self._sel_day_widget.dispatch("on_release") - - def on_device_orientation(self, instance, value): - if self._input_date_dialog_open: - if value == "portrait": - self._shift_dialog_height = dp(250) - if value == "landscape": - self._shift_dialog_height = dp(138) - - def transformation_from_dialog_select_year(self): - self.ids.chevron_left.disabled = False - self.ids.chevron_right.disabled = False - self.ids._year_layout.disabled = True - self.ids.triangle.disabled = False - self._select_year_dialog_open = False - self.ids.triangle.icon = "menu-down" - - Animation(opacity=1, d=0.15).start(self.ids.chevron_left) - Animation(opacity=1, d=0.15).start(self.ids.chevron_right) - Animation(_scale_year_layout=0, d=0.15).start(self) - Animation( - _shift_dialog_height=dp(0), _scale_calendar_layout=1, d=0.15 - ).start(self) - - self._calendar_layout.clear_widgets() - self.generate_list_widgets_days() - self.update_calendar(self.year, self.month) - - if self.mode != "range": - self.set_month_day(self.day) - self._sel_day_widget.dispatch("on_release") - - def transformation_to_dialog_select_year(self): - def disabled_chevron_buttons(*args): - self.ids.chevron_left.disabled = True - self.ids.chevron_right.disabled = True - - self._select_year_dialog_open = True - self.ids._year_layout.disabled = False - self._scale_calendar_layout = 0 - Animation(opacity=0, d=0.15).start(self.ids.chevron_left) - Animation(opacity=0, d=0.15).start(self.ids.chevron_right) - anim = Animation(_scale_year_layout=1, d=0.15) - anim.bind(on_complete=disabled_chevron_buttons) - anim.start(self) - self.ids.triangle.icon = "menu-up" - self.generate_list_widgets_years() - self.set_position_to_current_year() - - def transformation_to_dialog_input_date(self): - def set_date_to_input_field(): - if not self._enter_data_field_two: - # Date of current day. - self._enter_data_field.text = ( - f"{'' if self.sel_day >= 10 else '0'}" - f"{self.sel_day}/" - f"{'' if self.sel_month >= 10 else '0'}" - f"{self.sel_month}/{self.sel_year}" - ) - else: - # Range start date. - self._enter_data_field.text = ( - f"{'' if self.min_date.day >= 10 else '0'}" - f"{self.min_date.day}/" - f"{'' if self.min_date.month >= 10 else '0'}" - f"{self.min_date.month}/{self.min_date.year}" - ) - - def set_date_to_input_field_two(): - # Range end date. - self._enter_data_field_two.text = ( - f"{'' if self.max_date.day >= 10 else '0'}" - f"{self.max_date.day}/" - f"{'' if self.max_date.month >= 10 else '0'}" - f"{self.max_date.month}/{self.max_date.year}" - ) - - self.ids.triangle.disabled = True - if self._select_year_dialog_open: - self.transformation_from_dialog_select_year() - self._input_date_dialog_open = True - - self._enter_data_field_container = ( - DatePickerDatePickerEnterDataFieldContainer(owner=self) - ) - self._enter_data_field = self.get_field() - if self.min_date and self.max_date: - self._enter_data_field_two = self.get_field() - set_date_to_input_field_two() - set_date_to_input_field() - self._enter_data_field_container.add_widget(self._enter_data_field) - if self._enter_data_field_two: - self._enter_data_field_container.add_widget( - self._enter_data_field_two - ) - - self.ids.container.add_widget(self._enter_data_field_container) - self.ids.edit_icon.icon = "calendar" - self.ids.label_title.text = self.title_input - - Animation( - _shift_dialog_height=dp(250) - if self.theme_cls.device_orientation == "portrait" - else dp(138), - _scale_calendar_layout=0, - d=0.15, - ).start(self) - Animation( - opacity=0, - d=0.15 if self.theme_cls.device_orientation == "portrait" else 0, - ).start(self.ids.chevron_left) - Animation( - opacity=0, - d=0.15 if self.theme_cls.device_orientation == "portrait" else 0, - ).start(self.ids.chevron_right) - Animation(opacity=0, d=0.15).start(self.ids.label_month_selector) - Animation(opacity=0, d=0.15).start(self.ids.triangle) - Animation(opacity=1, d=0.15).start(self._enter_data_field) - if self._enter_data_field_two: - Animation(opacity=1, d=0.15).start(self._enter_data_field_two) - self.ids.label_full_date.text = self.set_text_full_date( - self.sel_year, - self.sel_month, - self.sel_day, - self.theme_cls.device_orientation, - ) - - def transformation_from_dialog_input_date(self, interval): - self._input_date_dialog_open = False - self.ids.label_full_date.text = self.set_text_full_date( - self.sel_year, - self.sel_month, - self.sel_day, - self.theme_cls.device_orientation, - ) - self.ids.triangle.disabled = False - self.ids.container.remove_widget(self._enter_data_field_container) - Animation( - _shift_dialog_height=dp(0), _scale_calendar_layout=1, d=0.15 - ).start(self) - Animation( - opacity=1, - d=0.15 if self.theme_cls.device_orientation == "portrait" else 0.65, - ).start(self.ids.chevron_left) - Animation( - opacity=1, - d=0.15 if self.theme_cls.device_orientation == "portrait" else 0.65, - ).start(self.ids.chevron_right) - Animation(opacity=1, d=0.15).start(self.ids.label_month_selector) - Animation(opacity=1, d=0.15).start(self.ids.triangle) - Animation(opacity=0, d=0.15).start(self._enter_data_field) - self.ids.edit_icon.icon = "pencil" - self.ids.label_title.text = self.title - - if not self.min_date and not self.max_date: - list_date = self._enter_data_field._get_list_date() - if len(list_date) == 3 and len(list_date[2]) == 4: - # self._sel_day_widget.is_selected = False - self.update_calendar(int(list_date[2]), int(list_date[1])) - self.set_month_day(int(list_date[0])) - # self._sel_day_widget.dispatch("on_release") - if self.mode != "range": - self._sel_day_widget.is_selected = False - self._sel_day_widget.dispatch("on_release") - elif self.min_date and self.max_date: - list_min_date = self._enter_data_field._get_list_date() - list_max_date = self._enter_data_field_two._get_list_date() - - if len(list_min_date) == 3 and len(list_min_date[2]) == 4: - self.min_date = date( - int(list_min_date[2]), - int(list_min_date[1]), - int(list_min_date[0]), - ) - if len(list_max_date) == 3 and len(list_max_date[2]) == 4: - self.max_date = date( - int(list_max_date[2]), - int(list_max_date[1]), - int(list_max_date[0]), - ) - - self.update_calendar_for_date_range() - self.ids.label_full_date.text = self.set_text_full_date( - int(list_max_date[2]), - int(list_max_date[1]), - int(list_max_date[0]), - self.theme_cls.device_orientation, - ) - - def compare_date_range(self): - # TODO: Add behavior if the minimum date range exceeds the maximum - # date range. Use toast? - if self.max_date <= self.min_date: - raise DatePickerTypeDateError( - "`max_date` value cannot be less than or equal " - "to 'min_date' value" - ) - - def update_calendar_for_date_range(self): - # self.compare_date_range() - self._date_range = self.get_date_range() - self._calendar_layout.clear_widgets() - self.generate_list_widgets_days() - self.update_calendar(self.year, self.month) - - def update_text_full_date(self, list_date): - """ - Updates the title of the week, month and number day name - in an open date input dialog. - """ - - if len(list_date) == 1 and len(list_date[0]) == 2: - self.ids.label_full_date.text = self.set_text_full_date( - self.sel_year, - self.sel_month, - list_date[0], - self.theme_cls.device_orientation, - ) - if len(list_date) == 2 and len(list_date[1]) == 2: - self.ids.label_full_date.text = self.set_text_full_date( - self.sel_year, - int(list_date[1]), - int(list_date[0]), - self.theme_cls.device_orientation, - ) - if len(list_date) == 3 and len(list_date[2]) == 4: - self.ids.label_full_date.text = self.set_text_full_date( - int(list_date[2]), - int(list_date[1]), - int(list_date[0]), - self.theme_cls.device_orientation, - ) - - def update_calendar(self, year, month): - try: - dates = [x for x in self.calendar.itermonthdates(year, month)] - except ValueError as e: - if str(e) == "year is out of range": - pass - else: - self.year = year - self.month = month - for idx in range(len(self._calendar_list)): - self._calendar_list[idx].current_month = int(self.month) - self._calendar_list[idx].current_year = int(self.year) - - # Dates of the month not in the range 1-31. - if idx >= len(dates) or dates[idx].month != month: - # self._calendar_list[idx].disabled = True - self._calendar_list[idx].text = "" - # Dates of the month in the range 1-31. - else: - self._calendar_list[idx].disabled = False - self._calendar_list[idx].text = str(dates[idx].day) - self._calendar_list[idx].is_today = dates[idx] == self.today - # The marked date widget has a True value in the `is_selected` - # attribute. In the KV file it is checked if the date widget - # (DatePickerDaySelectableItem) has the `is_selected = False` - # attribute value, then the date widget is not highlighted. - if ( - 0 - if not self._calendar_list[idx].text - else int(self._calendar_list[idx].text), - self._calendar_list[idx].current_month, - self._calendar_list[idx].current_year, - ) == self._current_selected_date: - self._calendar_list[idx].is_selected = True - else: - self._calendar_list[idx].is_selected = False - # Dates outside the set range - disabled. - if ( - self.mode == "picker" - and self._date_range - and self._calendar_list[idx].text - ) or ( - self.mode == "range" - and self._start_range_date - and self._end_range_date - and self._calendar_list[idx].text - ): - if ( - date( - self._calendar_list[idx].current_year, - self._calendar_list[idx].current_month, - int(self._calendar_list[idx].text), - ) - not in self._date_range - ): - self._calendar_list[idx].disabled = True - - def get_field(self): - """Creates and returns a text field object used to enter dates.""" - - field = DatePickerEnterDataField(owner=self) - field.color_mode = "custom" - field.line_color_focus = ( - self.theme_cls.primary_color - if not self.input_field_text_color - else self.input_field_text_color - ) - field.current_hint_text_color = field.line_color_focus - field._current_hint_text_color = field.line_color_focus - return field - - def get_date_range(self): - date_range = [ - self.min_date + datetime.timedelta(days=x) - for x in range((self.max_date - self.min_date).days + 1) - ] - return date_range - - def set_text_full_date(self, year, month, day, orientation): - """ - Returns a string of type "Tue, Feb 2" or "Tue,\nFeb 2" for a date - choose and a string like "Feb 15 - Mar 23" or "Feb 15,\nMar 23" for - a date range. - """ - if 12 < int(month) < 0: - raise ValueError( - "set_text_full_date:\n\t" f"Month [{month}] out of range." - ) - if int(day) > calendar.monthrange(int(year), (month))[1]: - raise ValueError( - "set_text_full_date:\n\t" - f"Day [{day}] out of range for the month {month}" - ) - date = datetime.date(int(year), int(month), int(day)) - separator = ( - "\n" - if (orientation == "landscape" and not self._input_date_dialog_open) - else " " - ) - - if self.mode == "picker": - if not self.min_date and not self.max_date: - return ( - date.strftime("%a,").capitalize() - + separator - + date.strftime("%b ").capitalize() - + str(day).lstrip("0") - ) - else: - return ( - self.min_date.strftime("%b ").capitalize() - + str(self.min_date.day).lstrip("0") - + ( - " - " - if orientation == "portrait" - else ( - ",\n" if not self._input_date_dialog_open else ", " - ) - ) - + self.max_date.strftime("%b ").capitalize() - + str(self.max_date.day).lstrip("0") - ) - elif self.mode == "range": - if self._start_range_date and self._end_range_date: - if ( - orientation == "landscape" - and "-" in self.ids.label_full_date.text - ): - return ( - self.ids.label_full_date.text.split("-")[0].strip() - + (",\n" if not self._input_date_dialog_open else " - ") - + date.strftime("%b ").capitalize() - + str(day).lstrip("0") - ) - else: - if ( - orientation == "landscape" - and "," in self.ids.label_full_date.text - ): - return ( - self.ids.label_full_date.text.split(",")[0].strip() - + ( - ",\n" - if not self._input_date_dialog_open - else "-" - ) - + date.strftime("%b ").capitalize() - + str(day).lstrip("0") - ) - if ( - orientation == "portrait" - and "," in self.ids.label_full_date.text - ): - return ( - self.ids.label_full_date.text.split(",")[0].strip() - + "-" - + date.strftime("%b ").capitalize() - + str(day).lstrip("0") - ) - if ( - orientation == "portrait" - and "-" in self.ids.label_full_date.text - ): - return ( - self.ids.label_full_date.text.split("-")[0].strip() - + " - " - + date.strftime("%b ").capitalize() - + str(day).lstrip("0") - ) - elif self._start_range_date and not self._end_range_date: - return ( - ( - date.strftime("%b ").capitalize() - + str(day).lstrip("0") - + " - End" - ) - if orientation != "landscape" - else ( - date.strftime("%b ").capitalize() - + str(day).lstrip("0") - + "{}End".format( - ",\n" if not self._input_date_dialog_open else " - " - ) - ) - ) - elif not self._start_range_date and not self._end_range_date: - return ( - "Start - End" - if orientation != "landscape" - else "Start{}End".format( - ",\n" if not self._input_date_dialog_open else " - " - ) - ) - - def set_selected_widget(self, widget): - if self._sel_day_widget: - self._sel_day_widget.is_selected = False - - widget.is_selected = True - self.sel_month = int(self.month) - self.sel_year = int(self.year) - self.sel_day = int(widget.text) - self._current_selected_date = ( - self.sel_day, - self.sel_month, - self.sel_year, - ) - self._sel_day_widget = widget - - def set_month_day(self, day): - for idx in range(len(self._calendar_list)): - if str(day) == str(self._calendar_list[idx].text): - self._sel_day_widget = self._calendar_list[idx] - self.sel_day = int(self._calendar_list[idx].text) - if self._sel_day_widget: - self._sel_day_widget.is_selected = False - self._sel_day_widget = self._calendar_list[idx] - - def set_position_to_current_year(self): - # TODO: Add the feature to set the position of the list of years - # for the current year. This is not currently possible because the - # ``RecycleView`` class does not support this functionality. - # There is a solution to this problem - # - https://github.com/Bakterija/log_fruit/blob/dev/src/app_modules/widgets/app_recycleview/recycleview.py. - # But I have not been able to get it to work. - pass - - def generate_list_widgets_years(self): - for i, number_year in enumerate(range(self.min_year, self.max_year)): - self.ids._year_layout.data.append( - { - "owner": self, - "text": str(number_year), - "index": i, - "selectable": True, - "viewclass": "DatePickerYearSelectableItem", - } - ) - - def generate_list_widgets_days(self): - calendar_list = [] - - for day in self.calendar.iterweekdays(): - weekday_label = DatePickerWeekdayLabel( - text=calendar.day_name[day][0].upper(), - owner=self, - hint_text=calendar.day_name[day], - ) - weekday_label.font_name = self.font_name - self._calendar_layout.add_widget(weekday_label) - for i, j in enumerate(range(6 * 7)): # 6 weeks, 7 days a week - day_selectable_item = DatePickerDaySelectableItem( - index=i, - owner=self, - current_month=int(self.month), - current_year=int(self.year), - ) - calendar_list.append(day_selectable_item) - self._calendar_layout.add_widget(day_selectable_item) - self._calendar_list = calendar_list - - def change_month(self, operation): - """ - Called when "chevron-left" and "chevron-right" buttons are pressed. - Switches the calendar to the previous/next month. - """ - - operation = 1 if operation == "next" else -1 - month = ( - 12 - if self.month + operation == 0 - else 1 - if self.month + operation == 13 - else self.month + operation - ) - year = ( - self.year - 1 - if self.month + operation == 0 - else self.year + 1 - if self.month + operation == 13 - else self.year - ) - self.update_calendar(year, month) - if self.sel_day: - x = calendar.monthrange(year, month)[1] - if x < self.sel_day: - self.sel_day = ( - x if year <= self.sel_year and month <= self.sel_year else 1 - ) - - -Builder.load_string( - """ -: - theme_text_color: "Custom" - font_size: dp(10) - halign: "left" - valign: "bottom" - adaptive_size: True - - - - halign: "center" - valign: "center" - theme_text_color: "Custom" - - - - size_hint: None, None - - canvas.before: - Color: - rgba: root.border_color - RoundedRectangle: - pos: self.pos - size: self.size - radius: [root.border_radius, ] - - #AM - Color: - rgba: root._am_bg_color - RoundedRectangle: - pos: - [ \ - self.pos[0] + root.border_width, \ - self.pos[1] + self.height/2 + self.border_width * 0.5 \ - ] if self.orientation == "vertical" else \ - [ \ - self.pos[0] + root.border_width, \ - self.pos[1] + root.border_width \ - ] - size: - [ \ - self.size[0] - root.border_width * 2, \ - self.size[1] / 2 - self.border_width * 1.5 \ - ] if self.orientation == "vertical" else \ - [ \ - self.size[0] / 2 - root.border_width * 1.5, \ - self.size[1] - root.border_width * 2 \ - ] - radius: [root.border_radius, root.border_radius, 0, 0] \ - if self.orientation == "vertical" else \ - [root.border_radius, 0, 0, root.border_radius] - - #PM - Color: - rgba: root._pm_bg_color - RoundedRectangle: - pos: - [ \ - self.pos[0] + root.border_width, \ - self.pos[1] + self.border_width \ - ] if self.orientation == "vertical" else \ - [ \ - self.pos[0] + root.size[0] / 2 + root.border_width / 2, \ - self.pos[1] + root.border_width \ - ] - size: - [ \ - self.size[0] - root.border_width * 2, \ - self.size[1] / 2 - self.border_width * 1.5 \ - ] if self.orientation == "vertical" else \ - [ \ - self.size[0] / 2 - root.border_width * 1.5, \ - self.size[1] - root.border_width * 2 \ - ] - radius: [0, 0, root.border_radius, root.border_radius] \ - if self.orientation == "vertical" else \ - [0 ,root.border_radius, root.border_radius, 0] - - # AM - AmPmSelectorLabel: - text: "AM" - on_release: root.selected = "am" - text_color: root.text_color - - AmPmSelectorLabel: - text: "PM" - on_release: root.selected = "pm" - text_color: root.text_color - - - - size_hint: None, 1 - width: dp(96) - mode: "fill" - active_line: False - font_size: dp(56) - line_color_normal: 0, 0, 0, 0 - on_text: root.on_text - radius: [dp(10), ] - - - - size_hint: None, None - _hour: hour - _minute: minute - - TimeInputTextField: - id: hour - num_type: "hour" - pos: 0, 0 - text_color: root.text_color - disabled: root.disabled - on_text: root.dispatch("on_time_input") - radius: root.hour_radius - on_select: - root.dispatch("on_hour_select") - root.state = "hour" - fill_color: - [*root.bg_color_active[:3], 0.5] \ - if root.state == "hour" else [*root.bg_color[:3], 0.5] - - - MDLabel: - text: ":" - size_hint: None, None - size: dp(24), dp(80) - halign: "center" - valign: "center" - font_size: dp(50) - pos: dp(96), 0 - theme_text_color: "Custom" - text_color: root.text_color - - TimeInputTextField: - id: minute - num_type: "minute" - pos: dp(120), 0 - text_color: root.text_color - disabled: root.disabled - on_text: root.dispatch("on_time_input") - radius: root.minute_radius - on_select: - root.dispatch("on_minute_select") - root.state = "minute" - fill_color: - [*root.bg_color_active[:3], 0.5] \ - if root.state == "minute" else [*root.bg_color[:3], 0.5] - - - - circular_padding: dp(28) - size_hint: None, None - size: [dp(256), dp(256)] - row_spacing: dp(40) - - canvas.before: - PushMatrix - Scale: - origin: self.scale_origin - x: root.scale - y: root.scale - Color: - rgba: root.bg_color - Ellipse: - size: self.size - pos: self.pos - PushMatrix - Scale: - origin: self.center - x: root.content_scale - y: root.content_scale - Color: - rgb: root.selector_color - a: 0 if self.selector_pos == [0, 0] else 1 - Ellipse: - size: self.selector_size, self.selector_size - pos: - [self.selector_pos[0] - self.selector_size / 2, \ - self.selector_pos[1] - self.selector_size / 2] - Ellipse: - size: dp(10), dp(10) - pos: [self.center[0] - dp(5), self.center[1] - dp(5)] - Line: - points: [self.center, self.selector_pos] - width: dp(1) - canvas.after: - PopMatrix - PopMatrix - - - - halign: "center" - valign: "center" - adaptive_size: True - theme_text_color: "Custom" - - - - auto_dismiss: True - size_hint: None, None - _time_input: _time_input - _selector: _selector - _am_pm_selector: _am_pm_selector - _minute_label: _minute_label - _hour_label: _hour_label - - MDRelativeLayout: - canvas.before: - Color: - rgba: - root.primary_color \ - if root.primary_color \ - else root.theme_cls.bg_normal - - RoundedRectangle: - size: self.size - radius: root.radius - - MDLabel: - id: label_title - font_style: "Body2" - bold: True - theme_text_color: "Custom" - size_hint_x: None - width: root.width - adaptive_height: True - text: root.title - font_name: root.font_name - pos: (dp(24), root.height - self.height - dp(18)) - text_color: - root.text_toolbar_color if root.text_toolbar_color \ - else root.theme_cls.text_color - - TimeInput: - id: _time_input - bg_color: - root.accent_color if root.accent_color else \ - root.theme_cls.primary_light - bg_color_active: - root.selector_color if root.selector_color \ - else root.theme_cls.primary_color - text_color: - root.input_field_text_color if root.input_field_text_color else \ - root.theme_cls.text_color - on_time_input: root._get_time_input(*self.get_time()) - on_hour_select: _selector.switch_mode("hour") - on_minute_select: _selector.switch_mode("minute") - minute_radius: root.minute_radius - hour_radius: root.hour_radius - - TimeInputLabel: - id: _hour_label - text: "Hour" - opacity: 0 - text_color: - root.text_toolbar_color if root.text_toolbar_color else \ - root.theme_cls.secondary_text_color - - TimeInputLabel: - id: _minute_label - text: "Minute" - opacity: 0 - text_color: - root.text_toolbar_color if root.text_toolbar_color else \ - root.theme_cls.secondary_text_color - - AmPmSelector: - id: _am_pm_selector - border_color: - root.accent_color if root.accent_color else \ - root.theme_cls.primary_color - border_radius: root.am_pm_radius - bg_color: - root.primary_color if root.primary_color else \ - root.theme_cls.bg_normal - border_width: root.am_pm_border_width - bg_color_active: - root.selector_color if root.selector_color else \ - root.theme_cls.primary_light - text_color: - root.input_field_text_color if root.input_field_text_color else \ - root.theme_cls.text_color - on_selected: root._get_am_pm(self.selected) - - CircularSelector: - id: _selector - text_color: - root.text_color if root.text_color else \ - root.theme_cls.text_color - bg_color: - root.accent_color if root.accent_color else \ - root.theme_cls.primary_light - selector_color: - root.selector_color if root.selector_color else \ - root.theme_cls.primary_color - font_name: root.font_name - on_selector_change: root._get_dial_time(_selector) - - MDIconButton: - id: input_clock_switch - icon: "keyboard" - pos: dp(12), dp(8) - theme_text_color: "Custom" - user_font_size: "24dp" - on_release: root._switch_input() - text_color: - root.text_toolbar_color if root.text_toolbar_color else \ - root.theme_cls.secondary_text_color - - MDFlatButton: - id: cancel_button - text: "CANCEL" - on_release: root.dispatch("on_cancel", None) - theme_text_color: "Custom" - pos: root.width - self.width - ok_button.width - dp(10), dp(10) - font_name: root.font_name - text_color: - root.theme_cls.primary_color \ - if not root.text_button_color else root.text_button_color - - MDFlatButton: - id: ok_button - width: dp(32) - pos: root.width - self.width, dp(10) - text: "OK" - theme_text_color: "Custom" - font_name: root.font_name - text_color: - root.theme_cls.primary_color \ - if not root.text_button_color else root.text_button_color - on_release: root.dispatch("on_save", root._get_data()) -""", - filename="picker.kv", -) - - -class AmPmSelectorLabel(ButtonBehavior, MDLabel): - pass - - -class AmPmSelector(ThemableBehavior, MDBoxLayout, EventDispatcher): - border_radius = NumericProperty() - border_color = ColorProperty() - bg_color = ColorProperty() - bg_color_active = ColorProperty() - border_width = NumericProperty() - am = ObjectProperty() - pm = ObjectProperty() - text_color = ColorProperty() - selected = StringProperty() - _am_bg_color = ColorProperty() - _pm_bg_color = ColorProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.bind(selected=self._upadte_color) - Clock.schedule_once(self._upadte_color) - - def _upadte_color(self, *args): - bg_color = self.bg_color_active - if self.selected == "am": - self._am_bg_color = bg_color - self._pm_bg_color = self.bg_color - elif self.selected == "pm": - self._am_bg_color = self.bg_color - self._pm_bg_color = bg_color - - -class TimeInputTextField(MDTextField): - num_type = OptionProperty("hour", options=["hour", "minute"]) - hour_regx = "^[0-9]$|^0[1-9]$|^1[0-2]$" - minute_regx = "^[0-9]$|^0[0-9]$|^[1-5][0-9]$" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.on_text) - self.register_event_type("on_select") - self.bind(text_color=self.setter("_current_hint_text_color")) - self.bind(text_color=self.setter("current_hint_text_color")) - - def validate_time(self, s): - reg = self.hour_regx if self.num_type == "hour" else self.minute_regx - return re.match(reg, s) - - def insert_text(self, s, from_undo=False): - text = self.text.strip() - current_string = "".join([text, s]) - if not self.validate_time(current_string): - s = "" - return super().insert_text(s, from_undo=from_undo) - - def on_text(self, *args): - """ - Texts should be center aligned. now we are setting the padding of text - to somehow make them aligned. - """ - - if not self.text: - self.text = " " - - self._refresh_text(self.text) - max_size = max(self._lines_rects, key=lambda r: r.size[0]).size - dx = (self.width - max_size[0]) / 2.0 - dy = (self.height - max_size[1]) / 2.0 - self.padding = [dx, dy, dx, dy] - - if len(self.text) > 1: - self.text = self.text.replace(" ", "") - - def on_focus(self, *args): - super().on_focus(*args) - if self.text.strip(): - if ( - not self.focus - and int(self.text) == 0 - and self.num_type == "hour" - ): - self.text = "12" - else: - self.text = " 12" if self.num_type == "hour" else " 00" - - def on_select(self, *args): - pass - - def on_touch_down(self, touch): - if self.collide_point(*touch.pos): - self.dispatch("on_select") - super().on_touch_down(touch) - - -class TimeInput(MDRelativeLayout, EventDispatcher): - bg_color = ColorProperty() - bg_color_active = ColorProperty() - text_color = ColorProperty() - disabled = BooleanProperty(True) - minute_radius = ListProperty([0, 0, 0, 0]) - hour_radius = ListProperty([0, 0, 0, 0]) - state = StringProperty("hour") - _hour = ObjectProperty() - _minute = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_time_input") - self.register_event_type("on_hour_select") - self.register_event_type("on_minute_select") - - def set_time(self, time_list): - hour, minute = time_list - self._hour.text = hour - self._minute.text = minute - - def get_time(self): - hour = self._hour.text.strip() - minute = self._minute.text.strip() - return [hour, minute] - - def _update_padding(self, *args): - self._hour.on_text() - self._minute.on_text() - - def on_time_input(self, *args): - pass - - def on_minute_select(self, *args): - pass - - def on_hour_select(self, *args): - pass - - -class SelectorLabel(MDLabel): - pass - - -class CircularSelector(MDCircularLayout, EventDispatcher): - mode = OptionProperty("hour", options=["hour", "minute"]) # and military - text_color = ColorProperty() - selected_hour = StringProperty("12") - selected_minute = StringProperty("0") - selector_size = NumericProperty("48dp") - selector_pos = ListProperty([0, 0]) - selector_color = ColorProperty() - bg_color = ColorProperty() - font_name = StringProperty() - _centers_pos = ListProperty() - scale = NumericProperty(1) - content_scale = NumericProperty(1) - t = StringProperty("out_quad") - d = NumericProperty(0.2) - scale_origin = ListProperty([100, 100]) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.bind( - mode=self._update_labels, - selected_hour=self.update_time, - selected_minute=self.update_time, - ) - Clock.schedule_once(lambda x: self._update_labels(animate=False)) - self.register_event_type("on_selector_change") - - def do_layout(self, *largs, **kwargs): - self.update_time() - return super().do_layout(*largs, **kwargs) - - def _update_labels(self, animate=True, *args): - """ - This method builds the selector based on current mode which currently - can be hour or minute. - """ - - if self.mode == "hour": - param = (1, 12) - self.degree_spacing = 30 - self.start_from = 60 - elif self.mode == "minute": - param = (0, 59, 5) - self.degree_spacing = 6 - self.start_from = 90 - elif self.mode == "military": - param = (1, 24) - self.degree_spacing = 30 - self.start_from = 90 - if animate: - anim = Animation(content_scale=0, t=self.t, d=self.d) - anim.bind(on_complete=lambda *args: self._add_items(*param)) - anim.start(self) - else: - self._add_items(*param) - - def _add_items(self, start, end, step=1): - """ - Adds all number in range `[start, end + 1]` to the circular layout with - the specified step. Step means that all widgets will be added to layout - but sets the opacity for skipped widgets to `0` because we are using - the label's text as a reference to the selected number so we have to - add these to layout. - """ - - self.clear_widgets() - i = 0 - for x in range(start, end + 1): - label = SelectorLabel( - text=f"{x}", - ) - if i % step != 0: - label.opacity = 0 - self.bind( - text_color=label.setter("text_color"), - font_name=label.setter("font_name"), - ) - self.add_widget(label) - i += 1 - Clock.schedule_once(self.update_time) - Clock.schedule_once(self._get_centers, 0.1) - anim = Animation(content_scale=1, t=self.t, d=self.d) - anim.start(self) - - def _get_centers(self, *args): - """ - Returns a list of all center. we use this for positioning the selector - indicator. - """ - - self._centers_pos = [] - for child in self.children: - self._centers_pos.append(child.center) - - def _get_closest_widget(self, pos): - """ - Returns the nearest widget to the given position. we use this to create - the magnetic effect. - """ - - distance = [Vector(pos).distance(point) for point in self._centers_pos] - if not distance: - return False - index = distance.index(min(distance)) - return self.children[index] - - def on_touch_down(self, touch): - if self.collide_point(*touch.pos): - touch.grab(self) - closest_wid = self._get_closest_widget(touch.pos) - self.set_time(closest_wid.text) - return True - - def on_touch_move(self, touch): - if touch.grab_current == self: - closest_wid = self._get_closest_widget(touch.pos) - self.set_time(closest_wid.text) - - def on_touch_up(self, touch): - if touch.grab_current is self: - touch.ungrab(self) - return True - - def set_selector(self, selected): - """ - Sets the selector's position towards the given text. - """ - - widget = None - for wid in self.children: - wid.text_color = self.text_color - if wid.text == selected: - widget = wid - if not widget: - return False - self.selector_pos = widget.center - widget.text_color = [1, 1, 1, 1] - self.dispatch("on_selector_change") - return True - - def set_time(self, selected): - if self.mode == "hour": - self.selected_hour = selected - elif self.mode == "minute": - self.selected_minute = selected - - def update_time(self, *args): - if self.mode == "hour": - self.set_selector(self.selected_hour) - elif self.mode == "minute": - self.set_selector(self.selected_minute) - - def get_selected(self): - return self.selected - - def on_selector_change(self, *args): - pass - - def switch_mode(self, mode): - if mode != self.mode: - self.mode = mode - - -class MDTimePicker(BaseDialogPicker): - hour = StringProperty("12") - """ - Current hour - - :attr:`hour` is an :class:`~kivy.properties.StringProperty` - and defaults to `'12'`. - """ - - minute = StringProperty("0") - """ - Current minute - - :attr:`minute` is an :class:`~kivy.properties.StringProperty` - and defaults to `0`. - """ - - minute_radius = ListProperty( - [ - dp(5), - ] - ) - """ - Radius of the minute input field. - - :attr:`minute_radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[dp(5),]`. - """ - - hour_radius = ListProperty( - [ - dp(5), - ] - ) - """ - Radius of the hour input field. - - :attr:`hour_radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[dp(5),]`. - """ - - am_pm_radius = NumericProperty("5dp") - """ - Radius of the AM/PM selector. - - :attr:`am_pm_radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(5)`. - """ - - am_pm_border_width = NumericProperty("1dp") - """ - Width of the AM/PM selector's borders. - - :attr:`am_pm_border_width` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(1)`. - """ - - am_pm = OptionProperty("am", options=["am", "pm"]) - """ - Current AM/PM mode. - - :attr:`am_pm` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'am'`. - """ - - animation_duration = NumericProperty(0.3) - """ - Duration of the animations. - - :attr:`animation_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - animation_transition = StringProperty("out_quad") - """ - Transition type of the animations. - - :attr:`animation_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `'out_quad'`. - """ - - time = ObjectProperty(allownone=True) - """ - Returns the current time object. - - :attr:`time` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - _state = StringProperty() - _selector = ObjectProperty() - _time_input = ObjectProperty() - _am_pm_selector = ObjectProperty() - _hour_label = ObjectProperty() - _minute_label = ObjectProperty() - _anim_playing = BooleanProperty(False) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.bind( - hour=self._set_current_time, - minute=self._set_current_time, - am_pm=self._set_current_time, - ) - self.theme_cls.bind(device_orientation=self._check_orienation) - self.title = "SELECT TIME" - # default time - self.set_time(datetime.time(hour=12, minute=0)) - self._check_orienation() - - def _get_dial_time(self, instance): - mode = instance.mode - if mode == "hour": - self.hour = instance.selected_hour - elif mode == "minute": - self.minute = instance.selected_minute - else: - raise Exception("invalid mode for MDTimePicker: " % mode) - self._set_time_input(self.hour, self.minute) - - def _set_dial_time(self, hour, minute): - self._selector.selected_minute = minute - self._selector.selected_hour = hour - - def _get_time_input(self, hour, minute): - if hour: - self.hour = f"{int(hour):01d}" - if minute: - self.minute = f"{int(minute):01d}" - self._set_dial_time(self.hour, self.minute) - - def _set_time_input(self, hour, minute): - hour = f"{int(hour):02d}" - minute = f"{int(minute):02d}" - if self._state != "input": - self._time_input.set_time([hour, minute]) - - def _get_am_pm(self, selected): - self.am_pm = selected - - def _set_am_pm(self, selected): - self._am_pm_selector.mode = self.am_pm - self._am_pm_selector.selected = self.am_pm - - def set_time(self, time_obj): - """ - Manually set time dialog with the specified time. - """ - - hour = time_obj.hour - minute = time_obj.minute - if hour > 12: - hour -= 12 - mode = "pm" - else: - mode = "am" - hour = str(hour) - minute = str(minute) - self._set_time_input(hour, minute) - self._set_dial_time(hour, minute) - self._set_am_pm(mode) - - def get_state(self): - """ - Returns the current state of TimePicker. - Can be one of `portrait`, `landscape` or `input`. - """ - - return self._state - - def _get_data(self): - try: - result = datetime.datetime.strptime( - f"{int(self.hour):02d}:{int(self.minute):02d} {self.am_pm}", - "%I:%M %p", - ).time() - return result - except ValueError: - return None # hour is zero - - def _check_orienation(self, *args, do_anim=False): - orientation = self.theme_cls.device_orientation - if self._state != "input" and orientation != self._state: - self._update_pos_size(orientation, anim=do_anim) - - def _update_pos_size(self, orientation, anim=False): - d = self.animation_duration - # time input - time_input_pos = ( - [dp(24), dp(368)] - if orientation == "portrait" - else ( - [dp(24), dp(178)] - if orientation == "landscape" - else [dp(24), dp(96)] - ) - ) - if anim: - _time_input = Animation( - pos=time_input_pos, - d=d, - t=self.animation_transition, # 80 - 8, - ) - _time_input.start(self._time_input) - else: - self._time_input.pos = time_input_pos - - self._time_input.disabled = False if orientation == "input" else True - self._time_input.size = ( - [dp(216), dp(62)] if orientation == "input" else [dp(216), dp(72)] - ) - Clock.schedule_once(self._time_input._update_padding) - - # circular selector - if orientation == "input": - if self.theme_cls.device_orientation == "portrait": - selector_pos = [dp(34), dp(-256)] - self._selector.scale_origin = [dp(162), dp(200)] - else: - selector_pos = [dp(324), dp(-19)] - self._selector.scale_origin = [dp(292), dp(109)] - elif orientation == "portrait": - self._selector.pos = selector_pos = [dp(36), dp(76)] - else: - self._selector.pos = selector_pos = [dp(304), dp(76)] - - Animation( - pos=selector_pos, - scale=0 if orientation == "input" else 1, - opacity=0 if orientation == "input" else 1, - d=d, - t=self.animation_transition, - ).start(self._selector) - - # AM/PM selector - am_pm_pos = ( - [dp(252), dp(368)] - if orientation == "portrait" - else ( - [dp(24), dp(126)] - if orientation == "landscape" - else [dp(252), dp(96)] - ) - ) - am_pm_size = ( - [dp(52), dp(80)] - if orientation == "portrait" - else ( - [dp(216), dp(40)] - if orientation == "landscape" - else [dp(48), dp(70)] - ) - ) - if anim: - Animation( - pos=am_pm_pos, - size=am_pm_size, - d=d, - t=self.animation_transition, - ).start(self._am_pm_selector) - else: - self._am_pm_selector.pos = am_pm_pos - self._am_pm_selector.size = am_pm_size - - self._am_pm_selector.orientation = ( - "horizontal" if orientation == "landscape" else "vertical" - ) - - # MDTimePicker - time_picker_size = ( - [dp(328), dp(500)] - if orientation == "portrait" - else ( - [dp(584), dp(368)] - if orientation == "landscape" - else [dp(324), dp(218)] - ) - ) - if anim: - Animation( - size=time_picker_size, - d=d, - t=self.animation_transition, - ).start(self) - else: - self.size = time_picker_size - - # minute label - Animation( - pos=[dp(144), dp(76)], - opacity=1 if orientation == "input" else 0, - d=d, - t=self.animation_transition, - ).start(self._minute_label) - - # hour label - Animation( - pos=[dp(24), dp(76)], - opacity=1 if orientation == "input" else 0, - d=d, - t=self.animation_transition, - ).start(self._hour_label) - - self._state = orientation - self.ids.input_clock_switch.icon = ( - "clock-time-four-outline" if orientation == "input" else "keyboard" - ) - - def _set_current_time(self, *args): - self.time = self._get_data() - - def _switch_input(self): - self._update_pos_size( - self.theme_cls.device_orientation - if self._state == "input" - else "input", - anim=True, - ) - - -Builder.load_string( - """ - - md_bg_color: app.theme_cls.bg_normal - - - - canvas: - Color: - rgba: root.rgb_hex(root.color_name) - Ellipse: - size: self.size - pos: self.pos - - - - on_release: app.theme_cls.accent_palette = root.color_name - - - - on_release: app.theme_cls.primary_palette = root.color_name - - - - size_hint: None, None - size: "284dp", "400dp" - - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "Change theme" - - MDTabs: - on_tab_switch: root.on_tab_switch(*args) - - Tab: - id: theme_tab - text: "Theme" - - MDGridLayout: - id: primary_box - adaptive_size: True - spacing: "8dp" - padding: "12dp" - pos_hint: {"center_x": .5, "top": 1} - cols: 5 - rows: 4 - - MDFlatButton: - text: "CLOSE" - pos: root.width - self.width - 10, 10 - on_release: root.dismiss() - - Tab: - text: "Accent" - - MDGridLayout: - id: accent_box - adaptive_size: True - spacing: "8dp" - padding: "12dp" - pos_hint: {"center_x": .5, "top": 1} - cols: 5 - rows: 4 - - MDFlatButton: - text: "CLOSE" - pos: root.width - self.width - 10, 10 - on_release: root.dismiss() - - Tab: - text: "Style" - - MDGridLayout: - adaptive_size: True - spacing: "8dp" - pos_hint: {"center_x": .5, "center_y": .5} - cols: 2 - rows: 1 - - MDIconButton: - canvas: - Color: - rgba: 1, 1, 1, 1 - Ellipse: - size: self.size - pos: self.pos - Color: - rgba: 0, 0, 0, 1 - Line: - width: 1. - circle: (self.center_x, self.center_y, sp(62)) - - user_font_size: "100sp" - on_release: app.theme_cls.theme_style = "Light" - - MDIconButton: - canvas: - Color: - rgba: 0, 0, 0, 1 - Ellipse: - size: self.size - pos: self.pos - - on_release: app.theme_cls.theme_style = "Dark" - user_font_size: "100sp" - - MDFlatButton: - text: "CLOSE" - pos: root.width - self.width - 10, 10 - on_release: root.dismiss() -""" -) - - -class ColorSelector(MDIconButton): - color_name = OptionProperty("Indigo", options=palette) - - def rgb_hex(self, col): - return get_color_from_hex(colors[col][self.theme_cls.accent_hue]) - - -class MDThemePicker( - BaseDialog, - SpecificBackgroundColorBehavior, - FakeRectangularElevationBehavior, -): - def on_open(self): - self.on_tab_switch(None, self.ids.theme_tab, None, None) - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): - if instance_tab.text == "Theme": - if not self.ids.primary_box.children: - for name_palette in palette: - self.ids.primary_box.add_widget( - Factory.PrimaryColorSelector(color_name=name_palette) - ) - if instance_tab.text == "Accent": - if not self.ids.accent_box.children: - for name_palette in palette: - self.ids.accent_box.add_widget( - Factory.AccentColorSelector(color_name=name_palette) - ) diff --git a/kivymd/uix/progressbar.py b/kivymd/uix/progressbar.py deleted file mode 100644 index 337ad68..0000000 --- a/kivymd/uix/progressbar.py +++ /dev/null @@ -1,313 +0,0 @@ -""" -Components/Progress Bar -======================= - -.. seealso:: - - `Material Design spec, Progress indicators `_ - -.. rubric:: Progress indicators express an unspecified wait time or display - the length of a process. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-preview.png - :align: center - -`KivyMD` provides the following bars classes for use: - -- MDProgressBar_ -- Determinate_ -- Indeterminate_ - -.. MDProgressBar: -MDProgressBar -------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - BoxLayout: - padding: "10dp" - - MDProgressBar: - value: 50 - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar.png - :align: center - -Vertical orientation --------------------- - -.. code-block:: kv - - MDProgressBar: - orientation: "vertical" - value: 50 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-vertical.png - :align: center - -With custom color ------------------ - -.. code-block:: kv - - MDProgressBar: - value: 50 - color: app.theme_cls.accent_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-custom-color.png - :align: center - -.. Indeterminate: -Indeterminate -------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - - KV = ''' - Screen: - - MDProgressBar: - id: progress - pos_hint: {"center_y": .6} - type: "indeterminate" - - MDRaisedButton: - text: "STOP" if app.state == "start" else "START" - pos_hint: {"center_x": .5, "center_y": .45} - on_press: app.state = "stop" if app.state == "start" else "start" - ''' - - - class Test(MDApp): - state = StringProperty("stop") - - def build(self): - return Builder.load_string(KV) - - def on_state(self, instance, value): - { - "start": self.root.ids.progress.start, - "stop": self.root.ids.progress.stop, - }.get(value)() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/indeterminate-progress-bar.gif - :align: center - -.. Determinate: -Determinate ------------ - -.. code-block:: kv - - MDProgressBar: - type: "determinate" - running_duration: 1 - catching_duration: 1.5 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/determinate-progress-bar.gif - :align: center -""" - -__all__ = ("MDProgressBar",) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ColorProperty, - NumericProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.progressbar import ProgressBar - -from kivymd.theming import ThemableBehavior - -Builder.load_string( - """ - - canvas: - Clear - Color: - rgba: self.theme_cls.divider_color - Rectangle: - size: - (self.width, dp(4)) \ - if self.orientation == "horizontal" \ - else (dp(4), self.height) - pos: - (self.x, self.center_y - dp(4)) \ - if self.orientation == "horizontal" \ - else (self.center_x - dp(4),self.y) - Color: - rgba: - self.theme_cls.primary_color if not self.color else self.color - Rectangle: - size: - (self.width * self.value_normalized, sp(4)) \ - if self.orientation == "horizontal" \ - else (sp(4), self.height * self.value_normalized) - pos: - (self.width * (1 - self.value_normalized) + self.x \ - if self.reversed else self.x + self._x, self.center_y - dp(4)) \ - if self.orientation == "horizontal" \ - else (self.center_x - dp(4),self.height \ - * (1 - self.value_normalized) + self.y if self.reversed \ - else self.y) -""" -) - - -class MDProgressBar(ThemableBehavior, ProgressBar): - reversed = BooleanProperty(False) - """Reverse the direction the progressbar moves. - - :attr:`reversed` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - orientation = OptionProperty( - "horizontal", options=["horizontal", "vertical"] - ) - """Orientation of progressbar. Available options are: `'horizontal '`, - `'vertical'`. - - :attr:`orientation` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'horizontal'`. - """ - - color = ColorProperty(None) - """ - Progress bar color in ``rgba`` format. - - :attr:`color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - running_transition = StringProperty("in_cubic") - """Running transition. - - :attr:`running_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `'in_cubic'`. - """ - - catching_transition = StringProperty("out_quart") - """Catching transition. - - :attr:`catching_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `'out_quart'`. - """ - - running_duration = NumericProperty(0.5) - """Running duration. - - :attr:`running_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.5`. - """ - - catching_duration = NumericProperty(0.8) - """Catching duration. - - :attr:`running_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.8`. - """ - - type = OptionProperty( - None, options=["indeterminate", "determinate"], allownone=True - ) - """Type of progressbar. Available options are: `'indeterminate '`, - `'determinate'`. - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None`. - """ - - _x = NumericProperty(0) - - def __init__(self, **kwargs): - self.catching_anim = None - self.running_anim = None - super().__init__(**kwargs) - - def start(self): - """Start animation.""" - - if self.type in ("indeterminate", "determinate"): - Clock.schedule_once(self._set_default_value) - if not self.catching_anim and not self.running_anim: - if self.type == "indeterminate": - self._create_indeterminate_animations() - else: - self._create_determinate_animations() - self.running_away() - - def stop(self): - """Stop animation.""" - - Animation.cancel_all(self) - self._set_default_value(0) - - def running_away(self, *args): - self._set_default_value(0) - self.running_anim.start(self) - - def catching_up(self, *args): - if self.type == "indeterminate": - self.reversed = True - self.catching_anim.start(self) - - def _create_determinate_animations(self): - self.running_anim = Animation( - value=100, - opacity=1, - t=self.running_transition, - d=self.running_duration, - ) - self.running_anim.bind(on_complete=self.catching_up) - self.catching_anim = Animation( - opacity=0, - t=self.catching_transition, - d=self.catching_duration, - ) - self.catching_anim.bind(on_complete=self.running_away) - - def _create_indeterminate_animations(self): - self.running_anim = Animation( - _x=self.width / 2, - value=50, - t=self.running_transition, - d=self.running_duration, - ) - self.running_anim.bind(on_complete=self.catching_up) - self.catching_anim = Animation( - value=0, t=self.catching_transition, d=self.catching_duration - ) - self.catching_anim.bind(on_complete=self.running_away) - - def _set_default_value(self, interval): - self._x = 0 - self.value = 0 - self.reversed = False diff --git a/kivymd/uix/refreshlayout.py b/kivymd/uix/refreshlayout.py deleted file mode 100644 index 71e883e..0000000 --- a/kivymd/uix/refreshlayout.py +++ /dev/null @@ -1,223 +0,0 @@ -""" -Components/Refresh Layout -========================= - -Example -------- - -.. code-block:: python - - from kivymd.app import MDApp - from kivy.clock import Clock - from kivy.lang import Builder - from kivy.factory import Factory - from kivy.properties import StringProperty - - from kivymd.uix.button import MDIconButton - from kivymd.icon_definitions import md_icons - from kivymd.uix.list import ILeftBodyTouch, OneLineIconListItem - from kivymd.theming import ThemeManager - from kivymd.utils import asynckivy - - Builder.load_string(''' - - text: root.text - - IconLeftSampleWidget: - icon: root.icon - - - - - BoxLayout: - orientation: 'vertical' - - MDToolbar: - title: app.title - md_bg_color: app.theme_cls.primary_color - background_palette: 'Primary' - elevation: 10 - left_action_items: [['menu', lambda x: x]] - - MDScrollViewRefreshLayout: - id: refresh_layout - refresh_callback: app.refresh_callback - root_layout: root - - MDGridLayout: - id: box - adaptive_height: True - cols: 1 - ''') - - - class IconLeftSampleWidget(ILeftBodyTouch, MDIconButton): - pass - - - class ItemForList(OneLineIconListItem): - icon = StringProperty() - - - class Example(MDApp): - title = 'Example Refresh Layout' - screen = None - x = 0 - y = 15 - - def build(self): - self.screen = Factory.Example() - self.set_list() - - return self.screen - - def set_list(self): - async def set_list(): - names_icons_list = list(md_icons.keys())[self.x:self.y] - for name_icon in names_icons_list: - await asynckivy.sleep(0) - self.screen.ids.box.add_widget( - ItemForList(icon=name_icon, text=name_icon)) - asynckivy.start(set_list()) - - def refresh_callback(self, *args): - '''A method that updates the state of your application - while the spinner remains on the screen.''' - - def refresh_callback(interval): - self.screen.ids.box.clear_widgets() - if self.x == 0: - self.x, self.y = 15, 30 - else: - self.x, self.y = 0, 15 - self.set_list() - self.screen.ids.refresh_layout.refresh_done() - self.tick = 0 - - Clock.schedule_once(refresh_callback, 1) - - - Example().run() -""" - -from kivy.animation import Animation -from kivy.core.window import Window -from kivy.effects.dampedscroll import DampedScrollEffect -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ColorProperty, NumericProperty, ObjectProperty -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.scrollview import ScrollView - -from kivymd.theming import ThemableBehavior - -Builder.load_string( - """ -#:import Window kivy.core.window.Window - - - - - AnchorLayout: - id: body_spinner - size_hint: None, None - size: dp(46), dp(46) - y: Window.height - pos_hint: {'center_x': .5} - anchor_x: 'center' - anchor_y: 'center' - - canvas: - Clear - Color: - rgba: root.theme_cls.primary_dark - Ellipse: - pos: self.pos - size: self.size - - MDSpinner: - id: spinner - size_hint: None, None - size: dp(30), dp(30) - color: 1, 1, 1, 1 -""" -) - - -class _RefreshScrollEffect(DampedScrollEffect): - """This class is simply based on DampedScrollEffect. - If you need any documentation please look at kivy.effects.dampedscrolleffect. - """ - - min_scroll_to_reload = NumericProperty("-100dp") - """Minimum overscroll value to reload.""" - - def on_overscroll(self, scrollview, overscroll): - if overscroll < self.min_scroll_to_reload: - scroll_view = self.target_widget.parent - scroll_view._did_overscroll = True - return True - else: - return False - - -class MDScrollViewRefreshLayout(ScrollView): - root_layout = ObjectProperty() - """The spinner will be attached to this layout.""" - - def __init__(self, **kargs): - super().__init__(**kargs) - self.effect_cls = _RefreshScrollEffect - self._work_spinnrer = False - self._did_overscroll = False - self.refresh_spinner = None - - def on_touch_up(self, *args): - if self._did_overscroll and not self._work_spinnrer: - if self.refresh_callback: - self.refresh_callback() - if not self.refresh_spinner: - self.refresh_spinner = RefreshSpinner(_refresh_layout=self) - self.root_layout.add_widget(self.refresh_spinner) - self.refresh_spinner.start_anim_spinner() - self._work_spinnrer = True - self._did_overscroll = False - return True - - return super().on_touch_up(*args) - - def refresh_done(self): - if self.refresh_spinner: - self.refresh_spinner.hide_anim_spinner() - - -class RefreshSpinner(ThemableBehavior, FloatLayout): - spinner_color = ColorProperty([1, 1, 1, 1]) - - _refresh_layout = ObjectProperty() - """kivymd.refreshlayout.MDScrollViewRefreshLayout object.""" - - def start_anim_spinner(self): - spinner = self.ids.body_spinner - Animation( - y=spinner.y - self.theme_cls.standard_increment * 2 + dp(10), - d=0.8, - t="out_elastic", - ).start(spinner) - - def hide_anim_spinner(self): - spinner = self.ids.body_spinner - anim = Animation(y=Window.height, d=0.8, t="out_elastic") - anim.bind(on_complete=self.set_spinner) - anim.start(spinner) - - def set_spinner(self, *args): - body_spinner = self.ids.body_spinner - body_spinner.size = (dp(46), dp(46)) - body_spinner.y = Window.height - body_spinner.opacity = 1 - spinner = self.ids.spinner - spinner.size = (dp(30), dp(30)) - spinner.opacity = 1 - self._refresh_layout._work_spinnrer = False - self._refresh_layout._did_overscroll = False diff --git a/kivymd/uix/relativelayout.py b/kivymd/uix/relativelayout.py deleted file mode 100644 index 38b230b..0000000 --- a/kivymd/uix/relativelayout.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -Components/Relative Layout -========================== - -:class:`~kivy.uix.relativelayout.RelativeLayout` class equivalent. Simplifies working -with some widget properties. For example: - -RelativeLayout --------------- - -.. code-block:: - - RelativeLayout: - canvas: - Color: - rgba: app.theme_cls.primary_color - RoundedRectangle: - pos: (0, 0) - size: self.size - radius: [25, ] - -MDRelativeLayout ----------------- - -.. code-block:: - - MDRelativeLayout: - radius: [25, ] - md_bg_color: app.theme_cls.primary_color -""" - -from kivy.uix.relativelayout import RelativeLayout - -from kivymd.uix import MDAdaptiveWidget - - -class MDRelativeLayout(RelativeLayout, MDAdaptiveWidget): - pass diff --git a/kivymd/uix/screen.py b/kivymd/uix/screen.py deleted file mode 100644 index 2add537..0000000 --- a/kivymd/uix/screen.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -Components/Screen -================= - -:class:`~kivy.uix.screenmanager.Screen` class equivalent. Simplifies working -with some widget properties. For example: - -Screen ------- - -.. code-block:: - - Screen: - canvas: - Color: - rgba: app.theme_cls.primary_color - RoundedRectangle: - pos: self.pos - size: self.size - radius: [25, 0, 0, 0] - -MDScreen --------- - -.. code-block:: - - MDScreen: - radius: [25, 0, 0, 0] - md_bg_color: app.theme_cls.primary_color -""" - -from kivy.uix.screenmanager import Screen - -from kivymd.uix import MDAdaptiveWidget - - -class MDScreen(Screen, MDAdaptiveWidget): - pass diff --git a/kivymd/uix/selection.py b/kivymd/uix/selection.py deleted file mode 100644 index 61ffe81..0000000 --- a/kivymd/uix/selection.py +++ /dev/null @@ -1,631 +0,0 @@ -""" -Components/Selection -==================== - -.. seealso:: - - `Material Design spec, Banner `_ - -.. rubric:: Selection refers to how users indicate specific items they intend to take action on. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-previous.png - :align: center - -Entering selection mode ------------------------ - -To select an item and enter selection mode, long press the item: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/enter-selection-mode.gif - :align: center - -Exiting selection mode ----------------------- - -To exit selection mode, tap each selected item until they’re all deselected: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/exit-selection-mode.gif - :align: center - -Larger selections ------------------ - -.. note:: This feature is missing yet. - -Events ------- - -.. code-block:: python - - def on_selected(self, instance_selection_list, instance_selection_item): - '''Called when a list item is selected.''' - - def on_unselected(self, instance_selection_list, instance_selection_item): - '''Called when a list item is unselected.''' - -Example with TwoLineAvatarListItem ----------------------------------- - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - from kivy.utils import get_color_from_hex - - from kivymd.app import MDApp - from kivymd.uix.list import TwoLineAvatarListItem - - KV = ''' - - text: "Two-line item with avatar" - secondary_text: "Secondary text here" - _no_ripple_effect: True - - ImageLeftWidget: - source: "data/logo/kivy-icon-256.png" - - - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - id: toolbar - title: "Inbox" - left_action_items: [["menu"]] - right_action_items: [["magnify"], ["dots-vertical"]] - md_bg_color: 0, 0, 0, 1 - - MDBoxLayout: - padding: "24dp", "8dp", 0, "8dp" - adaptive_size: True - - MDLabel: - text: "Today" - adaptive_size: True - - ScrollView: - - MDSelectionList: - id: selection_list - spacing: "12dp" - overlay_color: app.overlay_color[:-1] + [.2] - icon_bg_color: app.overlay_color - on_selected: app.on_selected(*args) - on_unselected: app.on_unselected(*args) - on_selected_mode: app.set_selection_mode(*args) - ''' - - - class MyItem(TwoLineAvatarListItem): - pass - - - class Example(MDApp): - overlay_color = get_color_from_hex("#6042e4") - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(10): - self.root.ids.selection_list.add_widget(MyItem()) - - def set_selection_mode(self, instance_selection_list, mode): - if mode: - md_bg_color = self.overlay_color - left_action_items = [ - [ - "close", - lambda x: self.root.ids.selection_list.unselected_all(), - ] - ] - right_action_items = [["trash-can"], ["dots-vertical"]] - else: - md_bg_color = (0, 0, 0, 1) - left_action_items = [["menu"]] - right_action_items = [["magnify"], ["dots-vertical"]] - self.root.ids.toolbar.title = "Inbox" - - Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar) - self.root.ids.toolbar.left_action_items = left_action_items - self.root.ids.toolbar.right_action_items = right_action_items - - def on_selected(self, instance_selection_list, instance_selection_item): - self.root.ids.toolbar.title = str( - len(instance_selection_list.get_selected_list_items()) - ) - - def on_unselected(self, instance_selection_list, instance_selection_item): - if instance_selection_list.get_selected_list_items(): - self.root.ids.toolbar.title = str( - len(instance_selection_list.get_selected_list_items()) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-example-with-listItem.gif - :align: center - -Example with FitImage ---------------------- - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - from kivy.utils import get_color_from_hex - - from kivymd.app import MDApp - from kivymd.utils.fitimage import FitImage - - KV = ''' - MDBoxLayout: - orientation: "vertical" - md_bg_color: app.theme_cls.bg_light - - MDToolbar: - id: toolbar - title: "Inbox" - left_action_items: [["menu"]] - right_action_items: [["magnify"], ["dots-vertical"]] - md_bg_color: app.theme_cls.bg_light - specific_text_color: 0, 0, 0, 1 - - MDBoxLayout: - padding: "24dp", "8dp", 0, "8dp" - adaptive_size: True - - MDLabel: - text: "Today" - adaptive_size: True - - ScrollView: - - MDSelectionList: - id: selection_list - padding: "24dp", 0, "24dp", "24dp" - cols: 3 - spacing: "12dp" - overlay_color: app.overlay_color[:-1] + [.2] - icon_bg_color: app.overlay_color - progress_round_color: app.progress_round_color - on_selected: app.on_selected(*args) - on_unselected: app.on_unselected(*args) - on_selected_mode: app.set_selection_mode(*args) - ''' - - - class Example(MDApp): - overlay_color = get_color_from_hex("#6042e4") - progress_round_color = get_color_from_hex("#ef514b") - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(10): - self.root.ids.selection_list.add_widget( - FitImage( - source="image.png", - size_hint_y=None, - height="240dp", - ) - ) - - def set_selection_mode(self, instance_selection_list, mode): - if mode: - md_bg_color = self.overlay_color - left_action_items = [ - [ - "close", - lambda x: self.root.ids.selection_list.unselected_all(), - ] - ] - right_action_items = [["trash-can"], ["dots-vertical"]] - else: - md_bg_color = (1, 1, 1, 1) - left_action_items = [["menu"]] - right_action_items = [["magnify"], ["dots-vertical"]] - self.root.ids.toolbar.title = "Inbox" - - Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar) - self.root.ids.toolbar.left_action_items = left_action_items - self.root.ids.toolbar.right_action_items = right_action_items - - def on_selected(self, instance_selection_list, instance_selection_item): - self.root.ids.toolbar.title = str( - len(instance_selection_list.get_selected_list_items()) - ) - - def on_unselected(self, instance_selection_list, instance_selection_item): - if instance_selection_list.get_selected_list_items(): - self.root.ids.toolbar.title = str( - len(instance_selection_list.get_selected_list_items()) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-example-with-fitimage.gif - :align: center -""" - -__all__ = ("MDSelectionList",) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.graphics.context_instructions import Color -from kivy.graphics.vertex_instructions import ( - Ellipse, - RoundedRectangle, - SmoothLine, -) -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - StringProperty, -) - -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import TouchBehavior -from kivymd.uix.button import MDIconButton -from kivymd.uix.list import MDList -from kivymd.uix.relativelayout import MDRelativeLayout - -Builder.load_string( - """ - - theme_text_color: "Custom" - text_color: self.icon_check_color - - canvas.before: - PushMatrix - Scale: - x: root.scale - y: root.scale - z: root.scale - origin: self.center - canvas.after: - PopMatrix - - - - md_bg_color: root.overlay_color if root.selected else (0, 0, 0, 0) -""" -) - - -class SelectionIconCheck(MDIconButton): - scale = NumericProperty(0) - icon_check_color = ColorProperty([0, 0, 0, 1]) - - -class SelectionItem(ThemableBehavior, MDRelativeLayout, TouchBehavior): - selected = BooleanProperty(False) - owner = ObjectProperty() - instance_item = ObjectProperty() - instance_icon = ObjectProperty() - overlay_color = ColorProperty([0, 0, 0, 0.2]) - progress_round_size = NumericProperty(dp(46)) - progress_round_color = ColorProperty(None) - - _progress_round = NumericProperty(0) - _progress_line_end = NumericProperty(0) - _progress_animation = BooleanProperty(False) - _touch_long = BooleanProperty(False) - _instance_progress_inner_circle_color = ObjectProperty() - _instance_progress_inner_circle_ellipse = ObjectProperty() - _instance_progress_inner_outer_color = ObjectProperty() - _instance_progress_inner_outer_line = ObjectProperty() - _instance_overlay_color = ObjectProperty() - _instance_overlay_rounded_rec = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_progress_round) - - def set_progress_round(self, interval): - with self.canvas.after: - self._instance_progress_inner_circle_color = Color( - rgba=(0, 0, 0, 0) - ) - self._instance_progress_inner_circle_ellipse = Ellipse( - size=self.get_progress_round_size(), - pos=self.get_progress_round_pos(), - ) - self.bind( - pos=self.update_progress_inner_circle_ellipse, - size=self.update_progress_inner_circle_ellipse, - ) - # FIXME: Radius value is not displayed. - self._instance_overlay_color = Color(rgba=(0, 0, 0, 0)) - self._instance_overlay_rounded_rec = RoundedRectangle( - size=self.size, - pos=self.pos, - radius=self.instance_item.radius - if hasattr(self.instance_item, "radius") - else [ - 0, - ], - ) - self.bind( - pos=self.update_overlay_rounded_rec, - size=self.update_overlay_rounded_rec, - ) - self._instance_progress_inner_outer_color = Color(rgba=(0, 0, 0, 0)) - self._instance_progress_inner_outer_line = SmoothLine( - width=dp(4), - circle=[ - self.center_x, - self.center_y, - self.progress_round_size * 0.58, - 0, - 0, - ], - ) - - def do_selected_item(self, *args): - Animation(scale=1, d=0.2).start(self.instance_icon) - self.selected = True - self._progress_animation = False - self._instance_overlay_color.rgba = self.get_overlay_color() - self.owner.dispatch("on_selected", self) - - def do_unselected_item(self): - Animation(scale=0, d=0.2).start(self.instance_icon) - self.selected = False - self._instance_overlay_color.rgba = self.get_overlay_color() - self.owner.dispatch("on_unselected", self) - - def do_animation_progress_line(self, animation, instance, value): - self._instance_progress_inner_outer_line.circle = ( - self.center_x, - self.center_y, - self.progress_round_size * 0.58, - 0, - 360 * value, - ) - - def update_overlay_rounded_rec(self, *args): - self._instance_overlay_rounded_rec.size = self.size - self._instance_overlay_rounded_rec.pos = self.pos - - def update_progress_inner_circle_ellipse(self, *args): - self._instance_progress_inner_circle_ellipse.size = ( - self.get_progress_round_size() - ) - self._instance_progress_inner_circle_ellipse.pos = ( - self.get_progress_round_pos() - ) - - def reset_progress_animation(self): - Animation.cancel_all(self) - self._progress_animation = False - self._instance_progress_inner_circle_color.rgba = (0, 0, 0, 0) - self._instance_progress_inner_outer_color.rgba = (0, 0, 0, 0) - self._instance_progress_inner_outer_line.circle = [ - self.center_x, - self.center_y, - self.progress_round_size * 0.58, - 0, - 0, - ] - self._progress_line_end = 0 - - def get_overlay_color(self): - return self.overlay_color if self.selected else (0, 0, 0, 0) - - def get_progress_round_pos(self): - return ( - self.center_x - self.progress_round_size / 2, - self.center_y - self.progress_round_size / 2, - ) - - def get_progress_round_size(self): - return self.progress_round_size, self.progress_round_size - - def get_progress_round_color(self): - return ( - self.theme_cls.primary_color - if not self.progress_round_color - else self.progress_round_color - ) - - def get_progress_line_color(self): - return ( - self.theme_cls.primary_color[:-1] + [0.5] - if not self.progress_round_color - else self.progress_round_color[:-1] + [0.5] - ) - - def on_long_touch(self, *args): - if not self.owner.get_selected(): - self._touch_long = True - self._progress_animation = True - - def on_touch_up(self, touch): - if self.collide_point(*touch.pos): - if self._touch_long: - self._touch_long = False - return super().on_touch_up(touch) - - def on_touch_down(self, touch): - if self.collide_point(*touch.pos): - if self.selected: - self.do_unselected_item() - else: - if self.owner.selected_mode: - self.do_selected_item() - return super().on_touch_down(touch) - - def on__touch_long(self, instance, value): - if not value: - self.reset_progress_animation() - - def on__progress_animation(self, instance, value): - if value: - anim = Animation(_progress_line_end=360, d=1, t="in_out_quad") - anim.bind( - on_progress=self.do_animation_progress_line, - on_complete=self.do_selected_item, - ) - anim.start(self) - self._instance_progress_inner_outer_color.rgba = ( - self.get_progress_line_color() - ) - self._instance_progress_inner_circle_color.rgba = ( - self.get_progress_round_color() - ) - else: - self.reset_progress_animation() - - -class MDSelectionList(MDList): - """ - :Events: - `on_selected` - Called when a list item is selected. - `on_unselected` - Called when a list item is unselected. - """ - - selected_mode = BooleanProperty(False) - """ - List item selection mode. If `True` when clicking on a list item, it will - be selected. - - :attr:`selected_mode` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - icon = StringProperty("check") - """ - Name of the icon with which the selected list item will be marked. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'check'`. - """ - - icon_pos = ListProperty() - """ - The position of the icon that will mark the selected list item. - - :attr:`icon_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - icon_bg_color = ColorProperty([1, 1, 1, 1]) - """ - Background color of the icon that will mark the selected list item. - - :attr:`icon_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - icon_check_color = ColorProperty([0, 0, 0, 1]) - """ - Color of the icon that will mark the selected list item. - - :attr:`icon_check_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - overlay_color = ColorProperty([0, 0, 0, 0.2]) - """ - The overlay color of the selected list item.. - - :attr:`overlay_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.2]]`. - """ - - progress_round_size = NumericProperty(dp(46)) - """ - Size of the spinner for switching of `selected_mode` mode. - - :attr:`progress_round_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(46)`. - """ - - progress_round_color = ColorProperty(None) - """ - Color of the spinner for switching of `selected_mode` mode. - - :attr:`progress_round_color` is an :class:`~kivy.properties.NumericProperty` - and defaults to `None`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_selected") - self.register_event_type("on_unselected") - - def add_widget(self, widget, index=0, canvas=None): - selection_icon = SelectionIconCheck( - icon=self.icon, - md_bg_color=self.icon_bg_color, - icon_check_color=self.icon_check_color, - ) - container = SelectionItem( - size_hint=(1, None), - height=widget.height, - instance_item=widget, - instance_icon=selection_icon, - overlay_color=self.overlay_color, - progress_round_size=self.progress_round_size, - progress_round_color=self.progress_round_color, - owner=self, - ) - container.add_widget(widget) - - if not self.icon_pos: - pos = ( - dp(12), - container.height / 2 - selection_icon.height / 2, - ) - else: - pos = self.icon_pos - selection_icon.pos = pos - container.add_widget(selection_icon) - return super().add_widget(container, index, canvas) - - def get_selected(self): - selected = False - for item in self.children: - if item.selected: - selected = True - break - return selected - - def get_selected_list_items(self): - selected_list_items = [] - for item in self.children: - if item.selected: - selected_list_items.append(item) - return selected_list_items - - def unselected_all(self): - for item in self.children: - item.do_unselected_item() - self.selected_mode = False - - def selected_all(self): - for item in self.children: - item.do_selected_item() - self.selected_mode = True - - def on_selected(self, *args): - """Called when a list item is selected.""" - - if not self.selected_mode: - self.selected_mode = True - - def on_unselected(self, *args): - """Called when a list item is unselected.""" - - self.selected_mode = self.get_selected() diff --git a/kivymd/uix/selectioncontrol.py b/kivymd/uix/selectioncontrol.py deleted file mode 100644 index 92d530e..0000000 --- a/kivymd/uix/selectioncontrol.py +++ /dev/null @@ -1,610 +0,0 @@ -""" -Components/Selection Controls -============================= - -.. seealso:: - - `Material Design spec, Selection controls `_ - -.. rubric:: Selection controls allow the user to select options. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-controll.png - :align: center - -`KivyMD` provides the following selection controls classes for use: - -- MDCheckbox_ -- MDSwitch_ - -.. MDCheckbox: -MDCheckbox ----------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - - KV = ''' - MDFloatLayout: - - MDCheckbox: - size_hint: None, None - size: "48dp", "48dp" - pos_hint: {'center_x': .5, 'center_y': .5} - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox.gif - :align: center - -.. Note:: Be sure to specify the size of the checkbox. By default, it is - ``(dp(48), dp(48))``, but the ripple effect takes up all the available - space. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-no-size.gif - :align: center - -Control state -------------- - -.. code-block:: kv - - MDCheckbox: - on_active: app.on_checkbox_active(*args) - -.. code-block:: python - - def on_checkbox_active(self, checkbox, value): - if value: - print('The checkbox', checkbox, 'is active', 'and', checkbox.state, 'state') - else: - print('The checkbox', checkbox, 'is inactive', 'and', checkbox.state, 'state') - -MDCheckbox with group ---------------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - : - group: 'group' - size_hint: None, None - size: dp(48), dp(48) - - - MDFloatLayout: - - Check: - active: True - pos_hint: {'center_x': .4, 'center_y': .5} - - Check: - pos_hint: {'center_x': .6, 'center_y': .5} - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-group.gif - :align: center - -.. MDSwitch: -MDSwitch --------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDFloatLayout: - - MDSwitch: - pos_hint: {'center_x': .5, 'center_y': .5} - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-switch.gif - :align: center - -.. Note:: For :class:`~MDSwitch` size is not required. By default it is - ``(dp(36), dp(48))``, but you can increase the width if you want. - -.. code-block:: kv - - MDSwitch: - width: dp(64) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-switch_width.png - :align: center - -.. Note:: Control state of :class:`~MDSwitch` same way as in - :class:`~MDCheckbox`. -""" - -__all__ = ("MDCheckbox", "MDSwitch") - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp, sp -from kivy.properties import ( - AliasProperty, - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.behaviors import ButtonBehavior, ToggleButtonBehavior -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.widget import Widget -from kivy.utils import get_color_from_hex - -from kivymd.color_definitions import colors -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import ( - CircularRippleBehavior, - FakeCircularElevationBehavior, -) -from kivymd.uix.label import MDIcon - -Builder.load_string( - """ - - canvas: - Clear - Color: - rgba: self.color - Rectangle: - texture: self.texture - size: self.texture_size - pos: - int(self.center_x - self.texture_size[0] / 2.),\ - int(self.center_y - self.texture_size[1] / 2.) - - color: self._current_color - halign: 'center' - valign: 'middle' - - - - color: 1, 1, 1, 1 - canvas: - Color: - rgba: self.color - Ellipse: - size: self.size - pos: self.pos - - - - canvas.before: - Color: - rgba: - self._track_color_disabled if self.disabled else \ - ( \ - self._track_color_active \ - if self.active else self._track_color_normal \ - ) - RoundedRectangle: - size: - (self.width + dp(14), dp(28)) \ - if root.widget_style == "ios" else \ - (self.width - dp(8), dp(16)) - pos: - (self.x - dp(2), self.center_y - dp(14)) \ - if root.widget_style == "ios" else \ - (self.x + dp(8), self.center_y - dp(8)) - radius: - [dp(14)] if root.widget_style == "ios" else [dp(7)] - Color: - rgba: - ( \ - self.theme_cls.disabled_hint_text_color[:-1] + [.2] \ - if not root.active else (0, 0, 0, 0) \ - ) \ - if root.widget_style == "ios" else (0, 0, 0, 0) - Line: - width: 1 - rounded_rectangle: - ( \ - self.x - dp(2), self.center_y - dp(14), self.width + dp(14), \ - dp(28), dp(14), dp(14), dp(14), dp(14), dp(28) \ - ) \ - if root.widget_style == "ios" else \ - (1, 1, 1, 1, 1, 1, 1, 1, 1) - - Thumb: - id: thumb - size_hint: None, None - size: dp(24), dp(24) - pos: root.pos[0] + root._thumb_pos[0], root.pos[1] + root._thumb_pos[1] - color: - root.thumb_color_disabled if root.disabled else \ - (root.thumb_color_down if root.active else root.thumb_color) - elevation: 8 if root.active else 5 - on_release: setattr(root, "active", not root.active) -""" -) - - -class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon): - active = BooleanProperty(False) - """ - Indicates if the checkbox is active or inactive. - - :attr:`active` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - checkbox_icon_normal = StringProperty("checkbox-blank-outline") - """ - Background icon of the checkbox used for the default graphical - representation when the checkbox is not pressed. - - :attr:`checkbox_icon_normal` is a :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank-outline'`. - """ - - checkbox_icon_down = StringProperty("checkbox-marked") - """ - Background icon of the checkbox used for the default graphical - representation when the checkbox is pressed. - - :attr:`checkbox_icon_down` is a :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-marked'`. - """ - - radio_icon_normal = StringProperty("checkbox-blank-circle-outline") - """ - Background icon (when using the ``group`` option) of the checkbox used for - the default graphical representation when the checkbox is not pressed. - - :attr:`radio_icon_normal` is a :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank-circle-outline'`. - """ - - radio_icon_down = StringProperty("checkbox-marked-circle") - """ - Background icon (when using the ``group`` option) of the checkbox used for - the default graphical representation when the checkbox is pressed. - - :attr:`radio_icon_down` is a :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-marked-circle'`. - """ - - selected_color = ColorProperty(None) - """ - Selected color in ``rgba`` format. - - :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - unselected_color = ColorProperty(None) - """ - Unelected color in ``rgba`` format. - - :attr:`unselected_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - disabled_color = ColorProperty(None) - """ - Disabled color in ``rgba`` format. - - :attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _current_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) - - def __init__(self, **kwargs): - self.check_anim_out = Animation(font_size=0, duration=0.1, t="out_quad") - self.check_anim_in = Animation( - font_size=sp(24), duration=0.1, t="out_quad" - ) - super().__init__(**kwargs) - self.selected_color = self.theme_cls.primary_color - self.unselected_color = self.theme_cls.secondary_text_color - self.disabled_color = self.theme_cls.divider_color - self._current_color = self.unselected_color - self.check_anim_out.bind( - on_complete=lambda *x: self.check_anim_in.start(self) - ) - self.bind( - checkbox_icon_normal=self.update_icon, - checkbox_icon_down=self.update_icon, - radio_icon_normal=self.update_icon, - radio_icon_down=self.update_icon, - group=self.update_icon, - selected_color=self.update_color, - unselected_color=self.update_color, - disabled_color=self.update_color, - disabled=self.update_color, - state=self.update_color, - ) - self.theme_cls.bind(primary_color=self.update_primary_color) - self.theme_cls.bind(theme_style=self.update_primary_color) - self.update_icon() - self.update_color() - - def update_primary_color(self, instance, value): - if value in ("Dark", "Light"): - if not self.disabled: - self.color = self.theme_cls.primary_color - else: - self.color = self.disabled_color - else: - self.selected_color = value - - def update_icon(self, *args): - if self.state == "down": - self.icon = ( - self.radio_icon_down if self.group else self.checkbox_icon_down - ) - else: - self.icon = ( - self.radio_icon_normal - if self.group - else self.checkbox_icon_normal - ) - - def update_color(self, *args): - if self.disabled: - self._current_color = self.disabled_color - elif self.state == "down": - self._current_color = self.selected_color - else: - self._current_color = self.unselected_color - - def on_state(self, *args): - if self.state == "down": - self.check_anim_in.cancel(self) - self.check_anim_out.start(self) - self.update_icon() - if self.group: - self._release_group(self) - self.active = True - else: - self.check_anim_in.cancel(self) - if not self.group: - self.check_anim_out.start(self) - self.update_icon() - self.active = False - - def on_active(self, *args): - self.state = "down" if self.active else "normal" - - -class Thumb( - FakeCircularElevationBehavior, - CircularRippleBehavior, - ButtonBehavior, - Widget, -): - ripple_scale = NumericProperty(2) - """ - See :attr:`~kivymd.uix.behaviors.ripplebehavior.CommonRipple.ripple_scale`. - - :attr:`ripple_scale` is a :class:`~kivy.properties.NumericProperty` - and defaults to `2`. - """ - - def _set_ellipse(self, instance, value): - self.ellipse.size = (self._ripple_rad, self._ripple_rad) - if self.ellipse.size[0] > self.width * 1.5 and not self._fading_out: - self.fade_out() - self.ellipse.pos = ( - self.center_x - self._ripple_rad / 2.0, - self.center_y - self._ripple_rad / 2.0, - ) - self.stencil.pos = ( - self.center_x - (self.width * self.ripple_scale) / 2, - self.center_y - (self.height * self.ripple_scale) / 2, - ) - - -class MDSwitch(ThemableBehavior, ButtonBehavior, FloatLayout): - active = BooleanProperty(False) - """ - Indicates if the switch is active or inactive. - - :attr:`active` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - _thumb_color = ColorProperty(get_color_from_hex(colors["Gray"]["50"])) - - def _get_thumb_color(self): - return self._thumb_color - - def _set_thumb_color(self, color, alpha=None): - if len(color) == 2: - self._thumb_color = get_color_from_hex(colors[color[0]][color[1]]) - if alpha: - self._thumb_color[3] = alpha - elif len(color) == 4: - self._thumb_color = color - - thumb_color = AliasProperty( - _get_thumb_color, _set_thumb_color, bind=["_thumb_color"] - ) - """ - Get thumb color ``rgba`` format. - - :attr:`thumb_color` is an :class:`~kivy.properties.AliasProperty` - and property is readonly. - """ - - _thumb_color_down = ColorProperty([1, 1, 1, 1]) - - def _get_thumb_color_down(self): - return self._thumb_color_down - - def _set_thumb_color_down(self, color, alpha=None): - if len(color) == 2: - self._thumb_color_down = get_color_from_hex( - colors[color[0]][color[1]] - ) - if alpha: - self._thumb_color_down[3] = alpha - else: - self._thumb_color_down[3] = 1 - elif len(color) == 4: - self._thumb_color_down = color - - _thumb_color_disabled = ColorProperty( - get_color_from_hex(colors["Gray"]["400"]) - ) - - thumb_color_disabled = get_color_from_hex(colors["Gray"]["800"]) - """ - Get thumb color disabled ``rgba`` format. - - :attr:`thumb_color_disabled` is an :class:`~kivy.properties.AliasProperty` - and property is readonly. - """ - - def _get_thumb_color_disabled(self): - return self._thumb_color_disabled - - def _set_thumb_color_disabled(self, color, alpha=None): - if len(color) == 2: - self._thumb_color_disabled = get_color_from_hex( - colors[color[0]][color[1]] - ) - if alpha: - self._thumb_color_disabled[3] = alpha - elif len(color) == 4: - self._thumb_color_disabled = color - - thumb_color_down = AliasProperty( - _get_thumb_color_disabled, - _set_thumb_color_disabled, - bind=["_thumb_color_disabled"], - ) - """ - Get thumb color down ``rgba`` format. - - :attr:`thumb_color_down` is an :class:`~kivy.properties.AliasProperty` - and property is readonly. - """ - - theme_thumb_color = OptionProperty("Primary", options=["Primary", "Custom"]) - """ - Thumb color scheme name - - :attr:`theme_thumb_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `Primary`. - """ - - theme_thumb_down_color = OptionProperty( - "Primary", options=["Primary", "Custom"] - ) - """ - Thumb Down color scheme name - - :attr:`theme_thumb_down_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `Primary`. - """ - - _track_color_active = ColorProperty([0, 0, 0, 0]) - _track_color_normal = ColorProperty([0, 0, 0, 0]) - _track_color_disabled = ColorProperty([0, 0, 0, 0]) - _thumb_pos = ListProperty([0, 0]) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.theme_cls.bind( - theme_style=self._set_colors, - primary_color=self._set_colors, - primary_palette=self._set_colors, - ) - self.bind(active=self._update_thumb_pos) - Clock.schedule_once(self._set_colors) - self.size_hint = (None, None) - self.size = (dp(36), dp(48)) - - def _set_colors(self, *args): - self._track_color_normal = self.theme_cls.disabled_hint_text_color - if self.theme_cls.theme_style == "Dark": - - if self.theme_thumb_down_color == "Primary": - self._track_color_active = self.theme_cls.primary_color - else: - self._track_color_active = self.thumb_color_down - - self._track_color_active[3] = 0.5 - self._track_color_disabled = get_color_from_hex("FFFFFF") - self._track_color_disabled[3] = 0.1 - - if self.theme_thumb_color == "Primary": - self.thumb_color = get_color_from_hex(colors["Gray"]["400"]) - - if self.theme_thumb_down_color == "Primary": - self.thumb_color_down = get_color_from_hex( - colors[self.theme_cls.primary_palette]["200"] - ) - else: - if self.theme_thumb_down_color == "Primary": - self._track_color_active = get_color_from_hex( - colors[self.theme_cls.primary_palette]["200"] - ) - else: - self._track_color_active = self.thumb_color_down - - self._track_color_active[3] = 0.5 - self._track_color_disabled = self.theme_cls.disabled_hint_text_color - - if self.theme_thumb_down_color == "Primary": - self.thumb_color_down = self.theme_cls.primary_color - - if self.theme_thumb_color == "Primary": - self.thumb_color = get_color_from_hex(colors["Gray"]["50"]) - - def _update_thumb_pos(self, *args, animation=True): - if self.active: - _thumb_pos = (self.width - dp(14), self.height / 2 - dp(12)) - else: - _thumb_pos = (0, self.height / 2 - dp(12)) - Animation.cancel_all(self, "_thumb_pos") - if animation: - Animation(_thumb_pos=_thumb_pos, duration=0.2, t="out_quad").start( - self - ) - else: - self._thumb_pos = _thumb_pos - - def on_size(self, *args): - self._update_thumb_pos(animation=False) diff --git a/kivymd/uix/slider.py b/kivymd/uix/slider.py deleted file mode 100644 index 4d1c8f4..0000000 --- a/kivymd/uix/slider.py +++ /dev/null @@ -1,328 +0,0 @@ -""" -Components/Slider -================= - -.. seealso:: - - `Material Design spec, Sliders `_ - -.. rubric:: Sliders allow users to make selections from a range of values. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider.png - :align: center - -With value hint ---------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - Screen - - MDSlider: - min: 0 - max: 100 - value: 40 - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider-1.gif - :align: center - -Without value hint ------------------- - -.. code-block:: kv - - MDSlider: - min: 0 - max: 100 - value: 40 - hint: False - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider-2.gif - :align: center - -Without custom color --------------------- - -.. code-block:: kv - - MDSlider: - min: 0 - max: 100 - value: 40 - hint: False - color: app.theme_cls.accent_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider-3.png - :align: center -""" - -__all__ = ("MDSlider",) - -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, -) -from kivy.uix.slider import Slider -from kivy.utils import get_color_from_hex - -from kivymd.color_definitions import colors -from kivymd.theming import ThemableBehavior - -Builder.load_string( - """ -#:import images_path kivymd.images_path -#:import Thumb kivymd.uix.selectioncontrol.Thumb - - - - id: slider - canvas: - Clear - Color: - rgba: - self._track_color_disabled if self.disabled \ - else (self._track_color_active if self.active \ - else self._track_color_normal) - Rectangle: - size: - (self.width - self.padding * 2 - self._offset[0], dp(4)) if \ - self.orientation == "horizontal" \ - else (dp(4),self.height - self.padding*2 - self._offset[1]) - pos: - (self.x + self.padding + self._offset[0], self.center_y - dp(4)) \ - if self.orientation == "horizontal" else \ - (self.center_x - dp(4), self.y + self.padding + self._offset[1]) - - # If 0 draw circle - Color: - rgba: - (0, 0, 0, 0) if not self._is_off \ - else (self._track_color_disabled if self.disabled \ - else (self._track_color_active \ - if self.active else self._track_color_normal)) - Line: - width: 2 - circle: - (self.x + self.padding + dp(3), self.center_y - dp(2), 8 \ - if self.active else 6 ) if self.orientation == "horizontal" \ - else (self.center_x - dp(2), self.y + self.padding + dp(3), 8 \ - if self.active else 6) - - Color: - rgba: - (0, 0, 0, 0) if self._is_off \ - else (self.color if not self.disabled \ - else self._track_color_disabled) - Rectangle: - size: - ((self.width - self.padding * 2) * self.value_normalized, sp(4)) \ - if slider.orientation == "horizontal" else (sp(4), \ - (self.height - self.padding * 2) * self.value_normalized) - pos: - (self.x + self.padding, self.center_y - dp(4)) \ - if self.orientation == "horizontal" \ - else (self.center_x - dp(4), self.y + self.padding) - - Thumb: - id: thumb - size_hint: None, None - size: - (dp(12), dp(12)) if root.disabled else ((dp(24), dp(24)) \ - if root.active else (dp(16), dp(16))) - pos: - (slider.value_pos[0] - dp(8), slider.center_y - thumb.height / 2 - dp(2)) \ - if slider.orientation == "horizontal" \ - else (slider.center_x - thumb.width / 2 - dp(2), \ - slider.value_pos[1] - dp(8)) - color: - (0, 0, 0, 0) if slider._is_off else (root._track_color_disabled \ - if root.disabled else root.color) - elevation: - 0 if slider._is_off else (4 if root.active else 2) - - MDCard: - id: hint_box - size_hint: None, None - md_bg_color: (1, 1, 1, 1) if not root.hint_bg_color else slider.hint_bg_color - elevation: 0 - opacity: 1 if slider.active else 0 - background: f"{images_path}transparent.png" - radius: [slider.hint_radius,] - size: - (dp(12), dp(12)) if root.disabled else ((dp(28), dp(28)) \ - if root.active else (dp(20), dp(20))) - pos: - (slider.value_pos[0] - dp(9), slider.center_y - hint_box.height / 2 + dp(30)) \ - if slider.orientation == "horizontal" \ - else (slider.center_x - hint_box.width / 2 + dp(30), \ - slider.value_pos[1] - dp(8)) - - MDLabel: - text: str(int(slider.value)) - font_style: "Caption" - halign: "center" - theme_text_color: "Custom" - text_color: - (root.color if root.active else (0, 0, 0, 0)) \ - if not slider.hint_text_color else slider.hint_text_color -""" -) - - -class MDSlider(ThemableBehavior, Slider): - active = BooleanProperty(False) - """ - If the slider is clicked. - - :attr:`active` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - hint = BooleanProperty(True) - """ - If True, then the current value is displayed above the slider. - - :attr:`hint` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - hint_bg_color = ColorProperty(None) - """ - Hint rectangle color in ``rgba`` format. - - :attr:`hint_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - hint_text_color = ColorProperty(None) - """ - Hint text color in ``rgba`` format. - - :attr:`hint_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - hint_radius = NumericProperty(4) - """ - Hint radius. - - :attr:`hint_radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `4`. - """ - - show_off = BooleanProperty(True) - """ - Show the `'off'` ring when set to minimum value. - - :attr:`show_off` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - color = ColorProperty([0, 0, 0, 0]) - """ - Color slider in ``rgba`` format. - - :attr:`color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _track_color_active = ColorProperty([0, 0, 0, 0]) - _track_color_normal = ColorProperty([0, 0, 0, 0]) - _track_color_disabled = ColorProperty([0, 0, 0, 0]) - _thumb_pos = ListProperty([0, 0]) - _thumb_color_disabled = ColorProperty( - get_color_from_hex(colors["Gray"]["400"]) - ) - # Internal state of ring - _is_off = BooleanProperty(False) - # Internal adjustment to reposition sliders for ring - _offset = ListProperty((0, 0)) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.theme_cls.bind( - theme_style=self._set_colors, - primary_color=self._set_colors, - primary_palette=self._set_colors, - ) - self._set_colors() - - def on_hint(self, instance, value): - if not value: - self.remove_widget(self.ids.hint_box) - - def on_value_normalized(self, *args): - """When the ``value == min`` set it to `'off'` state and make slider - a ring. - """ - - self._update_is_off() - - def on_show_off(self, *args): - self._update_is_off() - - def on__is_off(self, *args): - self._update_offset() - - def on_active(self, *args): - self._update_offset() - - def on_touch_down(self, touch): - if super().on_touch_down(touch): - self.active = True - - def on_touch_up(self, touch): - if super().on_touch_up(touch): - self.active = False - - def _update_offset(self): - """Offset is used to shift the sliders so the background color - shows through the off circle. - """ - - d = 2 if self.active else 0 - self._offset = (dp(11 + d), dp(11 + d)) if self._is_off else (0, 0) - - def _update_is_off(self): - self._is_off = self.show_off and (self.value_normalized == 0) - - def _set_colors(self, *args): - if self.theme_cls.theme_style == "Dark": - self._track_color_normal = get_color_from_hex("FFFFFF") - self._track_color_normal[3] = 0.3 - self._track_color_active = self._track_color_normal - self._track_color_disabled = self._track_color_normal - if self.color == [0, 0, 0, 0]: - self.color = get_color_from_hex( - colors[self.theme_cls.primary_palette]["200"] - ) - self.thumb_color_disabled = get_color_from_hex( - colors["Gray"]["800"] - ) - else: - self._track_color_normal = get_color_from_hex("000000") - self._track_color_normal[3] = 0.26 - self._track_color_active = get_color_from_hex("000000") - self._track_color_active[3] = 0.38 - self._track_color_disabled = get_color_from_hex("000000") - self._track_color_disabled[3] = 0.26 - if self.color == [0, 0, 0, 0]: - self.color = self.theme_cls.primary_color diff --git a/kivymd/uix/snackbar.py b/kivymd/uix/snackbar.py deleted file mode 100644 index f3bf3f8..0000000 --- a/kivymd/uix/snackbar.py +++ /dev/null @@ -1,643 +0,0 @@ -""" -Components/Snackbar -=================== - -.. seealso:: - - `Material Design spec, Snackbars `_ - -.. rubric:: Snackbars provide brief messages about app processes at the bottom - of the screen. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar.png - :align: center - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - #:import Snackbar kivymd.uix.snackbar.Snackbar - - - Screen: - - MDRaisedButton: - text: "Create simple snackbar" - on_release: Snackbar(text="This is a snackbar!").open() - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-simple.gif - :align: center - -Usage with snackbar_x, snackbar_y ---------------------------------- - -.. code-block:: python - - Snackbar( - text="This is a snackbar!", - snackbar_x="10dp", - snackbar_y="10dp", - size_hint_x=( - Window.width - (dp(10) * 2) - ) / Window.width - ).open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-padding.gif - :align: center - -Control width -------------- - -.. code-block:: python - - Snackbar( - text="This is a snackbar!", - snackbar_x="10dp", - snackbar_y="10dp", - size_hint_x=.5 - ).open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-percent-width.png - :align: center - -Custom text color ------------------ - -.. code-block:: python - - Snackbar( - text="[color=#ddbb34]This is a snackbar![/color]", - snackbar_y="10dp", - snackbar_y="10dp", - size_hint_x=.7 - ).open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-custom-color.png - :align: center - -Usage with button ------------------ - -.. code-block:: python - - snackbar = Snackbar( - text="This is a snackbar!", - snackbar_x="10dp", - snackbar_y="10dp", - ) - snackbar.size_hint_x = ( - Window.width - (snackbar.snackbar_x * 2) - ) / Window.width - snackbar.buttons = [ - MDFlatButton( - text="UPDATE", - text_color=(1, 1, 1, 1), - on_release=snackbar.dismiss, - ), - MDFlatButton( - text="CANCEL", - text_color=(1, 1, 1, 1), - on_release=snackbar.dismiss, - ), - ] - snackbar.open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-button.png - :align: center - -Using a button with custom color --------------------------------- - -.. code-block:: python - - Snackbar( - ... - bg_color=(0, 0, 1, 1), - ).open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-button-custom-color.png - :align: center - -Custom usage ------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.animation import Animation - from kivy.clock import Clock - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.snackbar import Snackbar - - - KV = ''' - Screen: - - MDFloatingActionButton: - id: button - x: root.width - self.width - dp(10) - y: dp(10) - on_release: app.snackbar_show() - ''' - - - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - self.snackbar = None - self._interval = 0 - - def build(self): - return self.screen - - def wait_interval(self, interval): - self._interval += interval - if self._interval > self.snackbar.duration + 0.5: - anim = Animation(y=dp(10), d=.2) - anim.start(self.screen.ids.button) - Clock.unschedule(self.wait_interval) - self._interval = 0 - self.snackbar = None - - def snackbar_show(self): - if not self.snackbar: - self.snackbar = Snackbar(text="This is a snackbar!") - self.snackbar.open() - anim = Animation(y=dp(72), d=.2) - anim.bind(on_complete=lambda *args: Clock.schedule_interval( - self.wait_interval, 0)) - anim.start(self.screen.ids.button) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-custom-usage.gif - :align: center - -Custom Snackbar ---------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.core.window import Window - from kivy.properties import StringProperty, NumericProperty - - from kivymd.app import MDApp - from kivymd.uix.button import MDFlatButton - from kivymd.uix.snackbar import BaseSnackbar - - KV = ''' - - - MDIconButton: - pos_hint: {'center_y': .5} - icon: root.icon - opposite_colors: True - - MDLabel: - id: text_bar - size_hint_y: None - height: self.texture_size[1] - text: root.text - font_size: root.font_size - theme_text_color: 'Custom' - text_color: get_color_from_hex('ffffff') - shorten: True - shorten_from: 'right' - pos_hint: {'center_y': .5} - - - Screen: - - MDRaisedButton: - text: "SHOW" - pos_hint: {"center_x": .5, "center_y": .45} - on_press: app.show() - ''' - - - class CustomSnackbar(BaseSnackbar): - text = StringProperty(None) - icon = StringProperty(None) - font_size = NumericProperty("15sp") - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def show(self): - snackbar = CustomSnackbar( - text="This is a snackbar!", - icon="information", - snackbar_x="10dp", - snackbar_y="10dp", - buttons=[MDFlatButton(text="ACTION", text_color=(1, 1, 1, 1))] - ) - snackbar.size_hint_x = ( - Window.width - (snackbar.snackbar_x * 2) - ) / Window.width - snackbar.open() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-custom.png - :align: center -""" - -__all__ = ("Snackbar",) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.core.window import Window -from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - OptionProperty, - StringProperty, -) - -from kivymd.uix.button import BaseButton -from kivymd.uix.card import MDCard - -Builder.load_string( - """ -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import window kivy.core.window - - - size_hint_y: None - height: "58dp" - spacing: "10dp" - padding: "10dp", "10dp", "10dp", "10dp" - md_bg_color: get_color_from_hex("323232") if not root.bg_color else root.bg_color - radius: root.radius - elevation: 11 if root.padding else 0 - - canvas: - Color: - rgba: self.md_bg_color - RoundedRectangle: - size: self.size - pos: self.pos - radius: self.radius - - - - MDLabel: - id: text_bar - size_hint_y: None - height: self.texture_size[1] - text: root.text - font_size: root.font_size - theme_text_color: "Custom" - text_color: get_color_from_hex("ffffff") - shorten: True - shorten_from: "right" - markup: True - pos_hint: {"center_y": .5} -""" -) - - -class BaseSnackbar(MDCard): - """ - :Events: - :attr:`on_open` - Called when a dialog is opened. - :attr:`on_dismiss` - When the front layer rises. - - Abstract base class for all Snackbars. - This class handles sizing, positioning, shape and events for Snackbars - - All Snackbars will be made off of this `BaseSnackbar`. - - `BaseSnackbar` will always try to fill the remainder of the screen with - your Snackbar. - - To make your Snackbar dynamic and symetric with snackbar_x. - - Set size_hint_x like below: - - .. code-block:: python - - size_hint_z = ( - Window.width - (snackbar_x * 2) - ) / Window.width - """ - - duration = NumericProperty(3) - """ - The amount of time that the snackbar will stay on screen for. - - :attr:`duration` is a :class:`~kivy.properties.NumericProperty` - and defaults to `3`. - """ - - auto_dismiss = BooleanProperty(True) - """ - Whether to use automatic closing of the snackbar or not. - - :attr:`auto_dismiss` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `'True'`. - """ - - bg_color = ColorProperty(None) - """ - Snackbar background. - - :attr:`bg_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - buttons = ListProperty() - """ - Snackbar buttons. - - :attr:`buttons` is a :class:`~kivy.properties.ListProperty` - and defaults to `'[]'` - """ - - radius = ListProperty([5, 5, 5, 5]) - """ - Snackbar radius. - - :attr:`radius` is a :class:`~kivy.properties.ListProperty` - and defaults to `'[5, 5, 5, 5]'` - """ - - snackbar_animation_dir = OptionProperty( - "Bottom", - options=["Top", "Bottom", "Left", "Right"], - ) - """ - Snackbar animation direction. - - Available options are: `"Top"`, `"Bottom"`, `"Left"`, `"Right"` - - :attr:`snackbar_animation_dir` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Bottom'`. - """ - - snackbar_x = NumericProperty("0dp") - """ - The snackbar x position in the screen - - :attr:`snackbar_x` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0dp`. - """ - - snackbar_y = NumericProperty("0dp") - """ - The snackbar x position in the screen - - :attr:`snackbar_y` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0dp`. - """ - - _interval = 0 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_open") - self.register_event_type("on_dismiss") - - def dismiss(self, *args): - """Dismiss the snackbar.""" - - def dismiss(interval): - if self.snackbar_animation_dir == "Top": - anim = Animation(y=(Window.height + self.height), d=0.2) - elif self.snackbar_animation_dir == "Left": - anim = Animation(x=-self.width, d=0.2) - elif self.snackbar_animation_dir == "Right": - anim = Animation(x=Window.width, d=0.2) - else: - anim = Animation(y=-self.height, d=0.2) - - anim.bind( - on_complete=lambda *args: Window.parent.remove_widget(self) - ) - anim.start(self) - - Clock.schedule_once(dismiss, 0.5) - self.dispatch("on_dismiss") - - def open(self): - """Show the snackbar.""" - - def wait_interval(interval): - self._interval += interval - if self._interval > self.duration: - self.dismiss() - Clock.unschedule(wait_interval) - self._interval = 0 - - for c in Window.parent.children: - if isinstance(c, BaseSnackbar): - return - - if self.snackbar_y > (Window.height - self.height): - self.snackbar_y = Window.height - self.height - - self._calc_radius() - - if self.size_hint_x == 1: - self.size_hint_x = (Window.width - self.snackbar_x) / Window.width - - if ( - self.snackbar_animation_dir == "Top" - or self.snackbar_animation_dir == "Bottom" - ): - self.x = self.snackbar_x - - if self.snackbar_animation_dir == "Top": - self.y = Window.height + self.height - else: - self.y = -self.height - - Window.parent.add_widget(self) - - if self.snackbar_animation_dir == "Top": - anim = Animation( - y=self.snackbar_y - if self.snackbar_y != 0 - else Window.height - self.height, - d=0.2, - ) - else: - anim = Animation( - y=self.snackbar_y if self.snackbar_y != 0 else 0, d=0.2 - ) - - elif ( - self.snackbar_animation_dir == "Left" - or self.snackbar_animation_dir == "Right" - ): - self.y = self.snackbar_y - - if self.snackbar_animation_dir == "Left": - self.x = -Window.width - else: - self.x = Window.width - - Window.parent.add_widget(self) - anim = Animation( - x=self.snackbar_x if self.snackbar_x != 0 else 0, d=0.2 - ) - - if self.auto_dismiss: - anim.bind( - on_complete=lambda *args: Clock.schedule_interval( - wait_interval, 0 - ) - ) - anim.start(self) - self.dispatch("on_open") - - def on_open(self, *args): - """Called when a dialog is opened.""" - - def on_dismiss(self, *args): - """Called when the dialog is closed.""" - - def on_buttons(self, instance, value): - def on_buttons(interval): - for button in value: - if issubclass(button.__class__, (BaseButton,)): - self.add_widget(button) - else: - raise ValueError( - f"The {button} object must be inherited from the base class " - ) - - Clock.schedule_once(on_buttons) - - def _calc_radius(self): - if ( - self.snackbar_animation_dir == "Top" - or self.snackbar_animation_dir == "Bottom" - ): - - if self.snackbar_y == 0 and self.snackbar_x == 0: - - if self.size_hint_x == 1: - self.radius = [0, 0, 0, 0] - else: - if self.snackbar_animation_dir == "Top": - self.radius = [0, 0, self.radius[2], 0] - else: - self.radius = [0, self.radius[1], 0, 0] - - elif self.snackbar_y != 0 and self.snackbar_x == 0: - - if self.size_hint_x == 1: - self.radius = [0, 0, 0, 0] - else: - if self.snackbar_y >= Window.height - self.height: - self.radius = [0, 0, self.radius[2], 0] - else: - self.radius = [0, self.radius[1], self.radius[2], 0] - - elif self.snackbar_y == 0 and self.snackbar_x != 0: - - if self.size_hint_x == 1: - if self.snackbar_animation_dir == "Top": - self.radius = [0, 0, 0, self.radius[3]] - else: - self.radius = [self.radius[0], 0, 0, 0] - else: - if self.snackbar_animation_dir == "Top": - self.radius = [0, 0, self.radius[2], self.radius[3]] - else: - self.radius = [self.radius[0], self.radius[1], 0, 0] - - else: # self.snackbar_y != 0 and self.snackbar_x != 0 - - if self.size_hint_x == 1: - self.radius = [self.radius[0], 0, 0, self.radius[3]] - elif self.snackbar_y >= Window.height - self.height: - self.radius = [0, 0, self.radius[2], self.radius[3]] - - elif ( - self.snackbar_animation_dir == "Left" - or self.snackbar_animation_dir == "Right" - ): - - if self.snackbar_y == 0 and self.snackbar_x == 0: - - if self.size_hint_x == 1: - self.radius = [0, 0, 0, 0] - else: - self.radius = [0, self.radius[1], 0, 0] - - elif self.snackbar_y != 0 and self.snackbar_x == 0: - - if self.size_hint_x == 1: - self.radius = [0, 0, 0, 0] - else: - self.radius = [0, self.radius[1], self.radius[2], 0] - - elif self.snackbar_y == 0 and self.snackbar_x != 0: - - if self.size_hint_x == 1: - self.radius = [self.radius[0], 0, 0, 0] - else: - self.radius = [self.radius[0], self.radius[1], 0, 0] - - else: # self.snackbar_y != 0 and self.snackbar_x != 0 - - if self.size_hint_x == 1: - if self.snackbar_y >= Window.height - self.height: - self.radius = [0, 0, 0, self.radius[3]] - else: - self.radius = [self.radius[0], 0, 0, self.radius[3]] - elif self.snackbar_y >= Window.height - self.height: - self.radius = [0, 0, self.radius[2], self.radius[3]] - - -class Snackbar(BaseSnackbar): - """ - Snackbar inherits all its functionality from `BaseSnackbar` - """ - - text = StringProperty() - """ - The text that will appear in the snackbar. - - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - font_size = NumericProperty("15sp") - """ - The font size of the text that will appear in the snackbar. - - :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` and - defaults to `'15sp'`. - """ diff --git a/kivymd/uix/spinner.py b/kivymd/uix/spinner.py deleted file mode 100644 index 3192858..0000000 --- a/kivymd/uix/spinner.py +++ /dev/null @@ -1,334 +0,0 @@ -""" -Components/Spinner -================== - -.. seealso:: - - `Material Design spec, Menus `_ - -.. rubric:: Circular progress indicator in Google's Material Design. - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - Screen: - - MDSpinner: - size_hint: None, None - size: dp(46), dp(46) - pos_hint: {'center_x': .5, 'center_y': .5} - active: True if check.active else False - - MDCheckbox: - id: check - size_hint: None, None - size: dp(48), dp(48) - pos_hint: {'center_x': .5, 'center_y': .4} - active: True - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner.gif - :align: center - -Spinner palette ---------------- - -.. code-block:: kv - - MDSpinner: - # The number of color values ​​can be any. - palette: - [0.28627450980392155, 0.8431372549019608, 0.596078431372549, 1], \ - [0.3568627450980392, 0.3215686274509804, 0.8666666666666667, 1], \ - [0.8862745098039215, 0.36470588235294116, 0.592156862745098, 1], \ - [0.8784313725490196, 0.9058823529411765, 0.40784313725490196, 1], - -.. code-block:: python - - MDSpinner( - size_hint=(None, None), - size=(dp(46), dp(46)), - pos_hint={'center_x': .5, 'center_y': .5}, - active=True, - palette=[ - [0.28627450980392155, 0.8431372549019608, 0.596078431372549, 1], - [0.3568627450980392, 0.3215686274509804, 0.8666666666666667, 1], - [0.8862745098039215, 0.36470588235294116, 0.592156862745098, 1], - [0.8784313725490196, 0.9058823529411765, 0.40784313725490196, 1], - ] - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner-palette.gif - :align: center - -Determinate mode ----------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDSpinner: - size_hint: None, None - size: dp(48), dp(48) - pos_hint: {'center_x': .5, 'center_y': .5} - determinate: True - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner-determinate.gif - :align: center -""" - -__all__ = ("MDSpinner",) - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, -) -from kivy.uix.widget import Widget - -from kivymd.theming import ThemableBehavior - -Builder.load_string( - """ - - canvas.before: - PushMatrix - Rotate: - angle: self._rotation_angle - origin: self.center - canvas: - Color: - rgba: self.color if self.color else self.theme_cls.primary_color - a: self._alpha - SmoothLine: - circle: self.center_x, self.center_y, self.width / 2, \ - self._angle_start, self._angle_end - cap: 'square' - width: root.line_width - canvas.after: - PopMatrix - -""" -) - - -class MDSpinner(ThemableBehavior, Widget): - """ - :class:`MDSpinner` is an implementation of the circular progress - indicator in `Google's Material Design`. - - It can be used either as an indeterminate indicator that loops while - the user waits for something to happen, or as a determinate indicator. - - Set :attr:`determinate` to **True** to activate determinate mode, and - :attr:`determinate_time` to set the duration of the animation. - - :Events: - `on_determinate_complete` - The event is called at the end of the spinner loop in the - `determinate = True` mode. - """ - - determinate = BooleanProperty(False) - """ - Determinate value. - - :attr:`determinate` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - determinate_time = NumericProperty(2) - """ - Determinate time value. - - :attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `2`. - """ - - line_width = NumericProperty(dp(2.25)) - """ - Progress line width of spinner. - - :attr:`line_width` is a :class:`~kivy.properties.NumericProperty` - and defaults to `dp(2.25)`. - """ - - active = BooleanProperty(True) - """ - Use :attr:`active` to start or stop the spinner. - - :attr:`active` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - color = ColorProperty(None, allownone=True) - """ - Spinner color. - - :attr:`color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - palette = ListProperty() - """ - A set of colors. Changes with each completed spinner cycle. - - :attr:`palette` is a :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - _alpha = NumericProperty(0) - _rotation_angle = NumericProperty(360) - _angle_start = NumericProperty(0) - _angle_end = NumericProperty(0) - _palette = [] - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - if not self.color: - self.color = self.theme_cls.primary_color - - if self.color == self.theme_cls.primary_color: - self.theme_cls.bind(primary_color=self._update_color) - - self._alpha_anim_in = Animation(_alpha=1, duration=0.8, t="out_quad") - self._alpha_anim_out = Animation(_alpha=0, duration=0.3, t="out_quad") - self._alpha_anim_out.bind( - on_complete=self._reset, - on_progress=self._on_determinate_progress, - ) - - self.register_event_type("on_determinate_complete") - Clock.schedule_once(self.check_determinate) - - def on__rotation_angle(self, *args): - if self._rotation_angle == 0: - self._rotation_angle = 360 - if not self.determinate: - _rot_anim = Animation(_rotation_angle=0, duration=2) - _rot_anim.start(self) - elif self._rotation_angle == 360: - if self._palette: - try: - Animation(color=next(self._palette), duration=2).start(self) - except StopIteration: - self._palette = iter(self.palette) - Animation(color=next(self._palette), duration=2).start(self) - - def on_palette(self, instance, value): - self._palette = iter(value) - - def on_active(self, *args): - self._reset() - if self.active: - self.check_determinate() - - def on_determinate_complete(self, *args): - """ - The event is called at the end of the spinner loop in the - `determinate = True` mode. - """ - - def check_determinate(self, *args): - if self.active: - if self.determinate: - self._start_determinate() - else: - self._start_loop() - - def _update_color(self, *args): - self.color = self.theme_cls.primary_color - - def _start_determinate(self, *args): - self._alpha_anim_in.start(self) - Animation( - _rotation_angle=0, - duration=self.determinate_time * 0.7, - t="out_quad", - ).start(self) - - _angle_start_anim = Animation( - _angle_end=360, duration=self.determinate_time, t="in_out_quad" - ) - _angle_start_anim.bind( - on_complete=lambda *x: self._alpha_anim_out.start(self) - ) - - _angle_start_anim.start(self) - - def _start_loop(self, *args): - if self._alpha == 0: - _rot_anim = Animation(_rotation_angle=0, duration=2, t="linear") - _rot_anim.start(self) - - self._alpha = 1 - self._alpha_anim_in.start(self) - _angle_start_anim = Animation( - _angle_end=self._angle_end + 270, duration=0.6, t="in_out_cubic" - ) - _angle_start_anim.bind(on_complete=self._anim_back) - _angle_start_anim.start(self) - - def _anim_back(self, *args): - _angle_back_anim = Animation( - _angle_start=self._angle_end - 8, duration=0.6, t="in_out_cubic" - ) - _angle_back_anim.bind(on_complete=self._start_loop) - - _angle_back_anim.start(self) - - def _reset(self, *args): - Animation.cancel_all( - self, - "_angle_start", - "_rotation_angle", - "_angle_end", - "_alpha", - "color", - ) - self._angle_start = 0 - self._angle_end = 0 - self._rotation_angle = 360 - self._alpha = 0 - - def _on_determinate_progress( - self, instance_animation, instance_spinner, value - ): - if value == 1: - self.dispatch("on_determinate_complete") diff --git a/kivymd/uix/stacklayout.py b/kivymd/uix/stacklayout.py deleted file mode 100644 index 4782302..0000000 --- a/kivymd/uix/stacklayout.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Components/StackLayout -====================== - -:class:`~kivy.uix.stacklayout.StackLayout` class equivalent. Simplifies working -with some widget properties. For example: - -StackLayout ------------ - -.. code-block:: - - StackLayout: - size_hint_y: None - height: self.minimum_height - - canvas: - Color: - rgba: app.theme_cls.primary_color - Rectangle: - pos: self.pos - size: self.size - -MDStackLayout -------------- - -.. code-block:: - - MDStackLayout: - adaptive_height: True - md_bg_color: app.theme_cls.primary_color - -Available options are: ----------------------- - -- adaptive_height_ -- adaptive_width_ -- adaptive_size_ - -.. adaptive_height: -adaptive_height ---------------- - -.. code-block:: kv - - adaptive_height: True - -Equivalent - -.. code-block:: kv - - size_hint_y: None - height: self.minimum_height - -.. adaptive_width: -adaptive_width --------------- - -.. code-block:: kv - - adaptive_width: True - -Equivalent - -.. code-block:: kv - - size_hint_x: None - width: self.minimum_width - -.. adaptive_size: -adaptive_size -------------- - -.. code-block:: kv - - adaptive_size: True - -Equivalent - -.. code-block:: kv - - size_hint: None, None - size: self.minimum_size -""" - -from kivy.uix.stacklayout import StackLayout - -from kivymd.uix import MDAdaptiveWidget - - -class MDStackLayout(StackLayout, MDAdaptiveWidget): - pass diff --git a/kivymd/uix/swiper.py b/kivymd/uix/swiper.py deleted file mode 100644 index 376eed0..0000000 --- a/kivymd/uix/swiper.py +++ /dev/null @@ -1,575 +0,0 @@ -""" -Components/MDSwiper -=================== - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdswiper-preview.gif - :align: center - -Usage -===== - -.. code-block:: kv - - MDSwiper: - - MDSwiperItem: - - MDSwiperItem: - - MDSwiperItem: - -Example -======= - -.. code-block:: python - - from kivymd.app import MDApp - from kivy.lang.builder import Builder - - kv = ''' - - - FitImage: - source: "guitar.png" - radius: [20,] - - MDScreen: - - MDToolbar: - id: toolbar - title: "MDSwiper" - elevation: 10 - pos_hint: {"top": 1} - - MDSwiper: - size_hint_y: None - height: root.height - toolbar.height - dp(40) - y: root.height - self.height - toolbar.height - dp(20) - - MySwiper: - - MySwiper: - - MySwiper: - - MySwiper: - - MySwiper: - ''' - - - class Main(MDApp): - def build(self): - return Builder.load_string(kv) - - Main().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdswiper-example.gif - :align: center - -.. warning:: - The width of :class:`MDSwiperItem` is adjusted automatically. Consider changing - that by :attr:`~MDSwiperItem.width_mult`. - -.. warning:: - The width of :class:`MDSwiper` is automatically adjusted according to the width of the window. - -.. rubric:: :class:`~MDSwiper` provides the following events for use: - -.. code-block:: python - - __events__ = ( - "on_swipe", - "on_pre_swipe", - "on_overswipe_right", - "on_overswipe_left", - "on_swipe_left", - "on_swipe_right" - ) - -.. code-block:: kv - - MDSwiper: - on_swipe: print("on_swipe") - on_pre_swipe: print("on_pre_swipe") - on_overswipe_right: print("on_overswipe_right") - on_overswipe_left: print("on_overswipe_left") - on_swipe_left: print("on_swipe_left") - on_swipe_right: print("on_swipe_right") - -Example -======= - -.. code-block:: python - - from kivy.lang.builder import Builder - - from kivymd.app import MDApp - - kv = ''' - - - - - - RelativeLayout: - - FitImage: - source: "guitar.png" - radius: [20,] - - MDBoxLayout: - adaptive_height: True - spacing: "12dp" - - MagicButton: - id: icon - icon: "weather-sunny" - user_font_size: "56sp" - opposite_colors: True - - MDLabel: - text: "MDLabel" - font_style: "H5" - size_hint_y: None - height: self.texture_size[1] - pos_hint: {"center_y": .5} - opposite_colors: True - - - MDScreen: - - MDToolbar: - id: toolbar - title: "MDSwiper" - elevation: 10 - pos_hint: {"top": 1} - - MDSwiper: - size_hint_y: None - height: root.height - toolbar.height - dp(40) - y: root.height - self.height - toolbar.height - dp(20) - on_swipe: self.get_current_item().ids.icon.shake() - - MySwiper: - - MySwiper: - - MySwiper: - - MySwiper: - - MySwiper: - ''' - - - class Main(MDApp): - def build(self): - return Builder.load_string(kv) - - - Main().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdswiper-on-swipe.gif - :align: center - -How to automatically switch a SwiperItem? -========================================= - -Use method :attr:`~MDSwiper.set_current` which takes the index of :class:`MDSwiperItem` as argument. - -Example -======= - -.. code-block:: kv - - MDSwiper: - id: swiper - - MDSwiperItem: # First widget with index 0 - - MDSwiperItem: # Second widget with index 1 - - MDRaisedButton: - text: "Go to Second" - on_release: swiper.set_current(1) -""" - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.core.window import Window -from kivy.effects.dampedscroll import DampedScrollEffect -from kivy.event import EventDispatcher -from kivy.lang.builder import Builder -from kivy.properties import ( - BooleanProperty, - NumericProperty, - ObjectProperty, - StringProperty, -) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.scrollview import ScrollView -from kivy.utils import platform - -__all__ = ("MDSwiperItem", "MDSwiper") - -Builder.load_string( - """ - - do_scroll_y: False - bar_width: 0 - - MDBoxLayout: - id: anchor_scroll - adaptive_width: True - padding: [root.items_spacing, 0 ] - - - - size_hint: None, None - - -<_ItemsBox> - size_hint_x: None - anchor_x: "center" - anchor_y: "center" -""" -) - - -class _ScrollViewHardStop(DampedScrollEffect): - def stop(self, val, t=None): - return super().stop(val, t=0.01) - - -class _ItemsBox(AnchorLayout): - _root = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self._update) - Window.bind(on_resize=self._set_size) - - def _update(self, *args): - self._set_size() - - def _set_size(self, *args): - window_size = Window.size - self.size = [ - window_size[0] - - self._root.items_spacing * self._root.width_mult * 2, - self._root.height, - ] - - -class MDSwiperItem(BoxLayout): - """ - :class:`MDSwiperItem` is a :class:`BoxLayout` but it's size is adjusted - automatically. - """ - - _root = ObjectProperty() - _selected = False - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self._set_size) - Window.bind(on_resize=self._set_size) - - def _set_size(self, *args): - Clock.schedule_once(lambda x: self._root._reset_size()) - if self._selected: - self._selected_size() - else: - self._dismiss_size() - - def _selected_size(self): - size = [ - Window.size[0] - - self._root.items_spacing * self._root.width_mult * 2, - self._root.height, - ] - anim = Animation( - size=size, d=self._root.size_duration, t=self._root.size_transition - ) - anim.start(self) - - def _dismiss_size(self): - size = [ - Window.size[0] - - self._root.items_spacing * (1 + self._root.width_mult) * 2, - self._root.height - self._root.items_spacing * 2, - ] - anim = Animation( - size=size, d=self._root.size_duration, t=self._root.size_transition - ) - anim.start(self) - - -class MDSwiper(ScrollView, EventDispatcher): - items_spacing = NumericProperty("20dp") - """ - The space between each :class:`MDSwiperItem`. - - :attr:`items_spacing` is an :class:`~kivy.properties.NumericProperty` - and defaults to `20dp`. - """ - - transition_duration = NumericProperty(0.2) - """ - Duration of switching between :class:`MDSwiperItem`. - - :attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - size_duration = NumericProperty(0.2) - """ - Duration of changing the size of :class:`MDSwiperItem`. - - :attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - size_transition = StringProperty("out_quad") - """ - The type of animation used for changing the size of :class:`MDSwiperItem`. - - :attr:`size_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `out_quad`. - """ - - swipe_transition = StringProperty("out_quad") - """ - The type of animation used for swiping. - - :attr:`swipe_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `out_quad`. - """ - - swipe_distance = NumericProperty("70dp") - """ - Distance to move before swiping the :class:`MDSwiperItem`. - - :attr:`swipe_distance` is an :class:`~kivy.properties.NumericProperty` - and defaults to `70dp`. - """ - - width_mult = NumericProperty(3) - """ - This number is multiplied by :attr:`items_spacing` x2 and - then subtracted from the width of window to specify the width of - :class:`MDSwiperItem`. So by decreasing the :attr:`width_mult` the width - of :class:`MDSwiperItem` increases and vice versa. - - :attr:`width_mult` is an :class:`~kivy.properties.NumericProperty` - and defaults to `3`. - """ - - swipe_on_scroll = BooleanProperty(True) - """ - Wheter to swipe on mouse wheel scrolling or not. - - :attr:`swipe_on_scroll` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - _selected = 0 - _start_touch_x = None - - __events__ = ( - "on_swipe", - "on_pre_swipe", - "on_overswipe_right", - "on_overswipe_left", - "on_swipe_left", - "on_swipe_right", - ) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_swipe") - self.register_event_type("on_pre_swipe") - self.register_event_type("on_overswipe_right") - self.register_event_type("on_overswipe_left") - self.register_event_type("on_swipe_left") - self.register_event_type("on_swipe_right") - - self.effect_cls = _ScrollViewHardStop - - def add_widget(self, widget, index=0): - if issubclass(widget.__class__, MDSwiperItem): - widget._root = self - items_box = _ItemsBox(_root=self) - items_box.add_widget(widget) - self.ids.anchor_scroll.add_widget(items_box) - return - else: - return super().add_widget(widget, index=index) - - def remove_widget(self, widget): - if not issubclass(widget.__class__, MDSwiperItem): - return - - for item_box in self.ids.anchor_scroll.children: - if widget in item_box.children: - return self.ids.anchor_scroll.remove_widget(item_box) - - def set_current(self, index): - """Switch to given :class:`MDSwiperItem` index.""" - - self._selected = index - self.dispatch("on_pre_swipe") - self._reset_size() - self.dispatch("on_swipe") - - def get_current_index(self): - """Returns the current :class:`MDSwiperItem` index.""" - - return self._selected - - def get_current_item(self): - """Returns the current :class:`MDSwiperItem` instance.""" - - return list(reversed(self.ids.anchor_scroll.children))[ - self._selected - ].children[0] - - def get_items(self): - """Returns the list of :class:`MDSwiperItem` children. - - .. note:: - - Use `get_items()` to get the list of children instead of - `MDSwiper.children`. - - """ - - children = list(reversed(self.ids.anchor_scroll.children)) - items = [item.children[0] for item in children] - return items - - def _reset_size(self, *args): - children = list(reversed(self.ids.anchor_scroll.children)) - if not children: - return - - child = children[self._selected] - total_width = self.ids.anchor_scroll.width - Window.width - - if self.get_current_index() == 0: - view_x = child.x - self.items_spacing - elif self.get_current_index() == len(children) - 1: - view_x = ( - child.x - - self.items_spacing * self.width_mult - - self.items_spacing * 2 - ) - else: - view_x = child.x - self.items_spacing * self.width_mult - - anim = Animation( - scroll_x=view_x / total_width, - d=self.transition_duration, - t=self.swipe_transition, - ) - anim.start(self) - - for widget in children: - widget.children[0]._dismiss_size() - widget.children[0]._selected = False - - child.children[0]._selected_size() - child.children[0]._selected = True - - def on_swipe(self): - pass - - def on_pre_swipe(self): - pass - - def on_overswipe_right(self): - pass - - def on_overswipe_left(self): - pass - - def on_swipe_left(self): - pass - - def on_swipe_right(self): - pass - - def swipe_left(self): - previous_index = self._selected - 1 - if previous_index == -1: - self.set_current(0) - self.dispatch("on_overswipe_left") - else: - self.set_current(previous_index) - self.dispatch("on_swipe_left") - - def swipe_right(self): - next_index = self._selected + 1 - last_index = len(self.ids.anchor_scroll.children) - 1 - if next_index == last_index + 1: - self.set_current(last_index) - self.dispatch("on_overswipe_right") - else: - self.set_current(next_index) - self.dispatch("on_swipe_right") - - def on_scroll_start(self, touch, check_children=True): - - if platform in ["ios", "android"]: - return super().on_scroll_start(touch) - - # on touch pad events - if touch.button == "scrollright": - self.swipe_left() - elif touch.button == "scrollleft": - self.swipe_right() - return super().on_scroll_start(touch) - - def on_touch_down(self, touch): - super().on_touch_down(touch) - - if not self.collide_point(touch.pos[0], touch.pos[1]): - return - - if platform not in ["ios", "android"] and self.swipe_on_scroll: - if touch.button == "scrolldown": - self.swipe_right() - elif touch.button == "scrollup": - self.swipe_left() - else: - self._start_touch_x = touch.pos[0] - else: - self._start_touch_x = touch.pos[0] - - def on_touch_up(self, touch): - super().on_touch_up(touch) - if not self._start_touch_x: - return - - if self._start_touch_x: - touch_x_diff = abs(self._start_touch_x - touch.pos[0]) - else: - return - - if touch_x_diff <= self.swipe_distance: - if touch_x_diff == 0: - return - self._reset_size() - return - - # swipe to left - if self._start_touch_x < touch.pos[0]: - self.swipe_left() - # swipe to right - else: - self.swipe_right() - - self._start_touch_x = None - return diff --git a/kivymd/uix/tab.py b/kivymd/uix/tab.py deleted file mode 100644 index 3a7d106..0000000 --- a/kivymd/uix/tab.py +++ /dev/null @@ -1,1628 +0,0 @@ -""" -Components/Tabs -=============== - -.. seealso:: - - `Material Design spec, Tabs `_ - -.. rubric:: Tabs organize content across different screens, data sets, - and other interactions. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs.png - :align: center - -.. Note:: Module provides tabs in the form of icons or text. - -Usage ------ - -To create a tab, you must create a new class that inherits from the -:class:`~MDTabsBase` class and the `Kivy` container, in which you will create -content for the tab. - -.. code-block:: python - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - content_text = StringProperty("") - -.. code-block:: kv - - - content_text - MDLabel: - text: root.content_text - pos_hint: {"center_x": .5, "center_y": .5} - -All tabs must be contained inside a :class:`~MDTabs` widget: - -.. code-block:: kv - - Root: - - MDTabs: - - Tab: - title: "Tab 1" - content_text: f"This is an example text for {self.title}" - - Tab: - title: "Tab 2" - content_text: f"This is an example text for {self.title}" - - ... - -Example with tab icon ---------------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.icon_definitions import md_icons - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "Example Tabs" - - MDTabs: - id: tabs - on_tab_switch: app.on_tab_switch(*args) - - - - - MDIconButton: - id: icon - icon: root.icon - user_font_size: "48sp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for tab_name in self.icons: - self.root.ids.tabs.add_widget(Tab(icon=tab_name)) - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): - ''' - Called when switching tabs. - - :type instance_tabs: ; - :param instance_tab: <__main__.Tab object>; - :param instance_tab_label: ; - :param tab_text: text or name icon of tab; - ''' - # get the tab icon. - count_icon = instance_tab.icon - # print it on shell/bash. - print(f"Welcome to {count_icon}' tab'") - - - Example().run() - - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example.gif - :align: center - -Example with tab text ---------------------- - -.. Note:: The :class:`~MDTabsBase` class has an icon parameter and, by default, - tries to find the name of the icon in the file - ``kivymd/icon_definitions.py``. - - If the name of the icon is not found, the class will send a message - stating that the icon could not be found. - - if the tab has no icon, title or tab_label_text, the class will raise a - ValueError. - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "Example Tabs" - - MDTabs: - id: tabs - on_tab_switch: app.on_tab_switch(*args) - - - - - MDLabel: - id: label - text: "Tab 0" - halign: "center" - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(20): - self.root.ids.tabs.add_widget(Tab(title=f"Tab {i}")) - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): - '''Called when switching tabs. - - :type instance_tabs: ; - :param instance_tab: <__main__.Tab object>; - :param instance_tab_label: ; - :param tab_text: text or name icon of tab; - ''' - - instance_tab.ids.label.text = tab_text - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-text.gif - :align: center - -Example with tab icon and text ------------------------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.icon_definitions import md_icons - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "Example Tabs" - - MDTabs: - id: tabs - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - pass - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for name_tab in list(md_icons.keys())[15:30]: - self.root.ids.tabs.add_widget(Tab(icon=name_tab, title=name_tab)) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-icon-text.png - :align: center - -Dynamic tab management ----------------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.scrollview import ScrollView - - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "Example Tabs" - - MDTabs: - id: tabs - - - - - MDList: - - MDBoxLayout: - adaptive_height: True - - MDFlatButton: - text: "ADD TAB" - on_release: app.add_tab() - - MDFlatButton: - text: "REMOVE LAST TAB" - on_release: app.remove_tab() - - MDFlatButton: - text: "GET TAB LIST" - on_release: app.get_tab_list() - ''' - - - class Tab(ScrollView, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - index = 0 - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - self.add_tab() - - def get_tab_list(self): - '''Prints a list of tab objects.''' - - print(self.root.ids.tabs.get_tab_list()) - - def add_tab(self): - self.index += 1 - self.root.ids.tabs.add_widget(Tab(text=f"{self.index} tab")) - - def remove_tab(self): - if self.index > 1: - self.index -= 1 - self.root.ids.tabs.remove_widget( - self.root.ids.tabs.get_tab_list()[-1] - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-dynamic-managmant.gif - :align: center - -Use on_ref_press method ------------------------ - -You can use markup for the text of the tabs and use the ``on_ref_press`` -method accordingly: - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.font_definitions import fonts - from kivymd.uix.tab import MDTabsBase - from kivymd.icon_definitions import md_icons - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "Example Tabs" - - MDTabs: - id: tabs - on_ref_press: app.on_ref_press(*args) - - - - - MDIconButton: - id: icon - icon: app.icons[0] - user_font_size: "48sp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for name_tab in self.icons: - self.root.ids.tabs.add_widget( - Tab( - text=f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]{md_icons['close']}[/font][/ref] {name_tab}" - ) - ) - - def on_ref_press( - self, - instance_tabs, - instance_tab_label, - instance_tab, - instance_tab_bar, - instance_carousel, - ): - ''' - The method will be called when the ``on_ref_press`` event - occurs when you, for example, use markup text for tabs. - - :param instance_tabs: - :param instance_tab_label: - :param instance_tab: <__main__.Tab object> - :param instance_tab_bar: - :param instance_carousel: - ''' - - # Removes a tab by clicking on the close icon on the left. - for instance_tab in instance_carousel.slides: - if instance_tab.text == instance_tab_label.text: - instance_tabs.remove_widget(instance_tab_label) - break - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-on-ref-press.gif - :align: center - -Switching the tab by name -------------------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivymd.app import MDApp - from kivymd.icon_definitions import md_icons - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "Example Tabs" - - MDTabs: - id: tabs - - - - MDBoxLayout: - orientation: "vertical" - pos_hint: {"center_x": .5, "center_y": .5} - size_hint: None, None - spacing: dp(48) - MDIconButton: - id: icon - icon: "arrow-right" - user_font_size: "48sp" - - on_release: - app.switch_tab_by_name() - - MDIconButton: - id: icon2 - icon: "page-next" - user_font_size: "48sp" - - on_release: - app.switch_tab_by_object() - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.iter_list_names = iter(list(self.icons)) - root = Builder.load_string(KV) - return root - - def on_start(self): - for name_tab in list(self.icons): - self.root.ids.tabs.add_widget(Tab(tab_label_text=name_tab)) - self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) - - def switch_tab_by_object(self): - try: - x = next(self.iter_list_objects) - print(f"Switch slide by object, \n\tnex element to show: [{x}]") - self.root.ids.tabs.switch_tab(x) - except StopIteration: - # reset the iterator an begin again. - self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) - self.switch_tab_by_object() - pass - - def switch_tab_by_name(self): - '''Switching the tab by name.''' - - try: - x = next(self.iter_list_names) - print(f"Switch slide by name, \n\tnex element to show: [{x}]") - self.root.ids.tabs.switch_tab(x) - except StopIteration: - # reset the iterator an begin again. - self.iter_list_names = iter(list(self.icons)) - self.switch_tab_by_name() - pass - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switching-tab-by-name.gif - :align: center -""" - -__all__ = ("MDTabs", "MDTabsBase") - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.logger import Logger -from kivy.metrics import dp -from kivy.properties import ( - AliasProperty, - BooleanProperty, - BoundedNumericProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.behaviors import ToggleButtonBehavior -from kivy.uix.scrollview import ScrollView -from kivy.uix.widget import Widget -from kivy.utils import boundary - -from kivymd.font_definitions import fonts, theme_font_styles -from kivymd.icon_definitions import md_icons -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import ( - FakeRectangularElevationBehavior, - RectangularRippleBehavior, - SpecificBackgroundColorBehavior, -) -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.carousel import MDCarousel -from kivymd.uix.label import MDLabel - -Builder.load_string( - """ -#:import DampedScrollEffect kivy.effects.dampedscroll.DampedScrollEffect - - - - _set_start_tab: False - size_hint: None, 1 - halign: "center" - valign: "center" - group: "tabs" - font: root.font_name - allow_no_selection: False - markup: True - on_width: - if not self._set_start_tab: \ - self.tab_bar.parent._update_indicator( \ - self.tab_bar.parent.carousel.current_slide.tab_label); \ - self._set_start_tab = True - on_tab_bar: - self.text_size = (None, None) \ - if self.tab_bar.parent.allow_stretch else (self.width, None) - on_ref_press: - self.tab_bar.parent.dispatch( \ - "on_ref_press", - self, \ - self.tab, \ - self.tab_bar, \ - self.tab_bar.parent.carousel) - color: - ( \ - self.text_color_active \ - if self.text_color_active else self.specific_secondary_text_color \ - ) \ - if self.state == "down" else \ - ( \ - self.text_color_normal \ - if self.text_color_normal else self.theme_cls.text_color \ - ) - - - - size_hint: 1, 1 - do_scroll_y: False - bar_color: 0, 0, 0, 0 - bar_inactive_color: 0, 0, 0, 0 - bar_width: 0 - effect_cls: DampedScrollEffect - - - - carousel: carousel - tab_bar: tab_bar - anchor_y: "top" - background_palette: "Primary" - - _line_x: 0 - _line_width: 0 - _line_height: 0 - _line_radius: 0 - - on_size: - root._update_padding(layout) - - MDTabsMain: - padding: 0, tab_bar.height, 0, 0 - - MDTabsCarousel: - id: carousel - lock_swiping: root.lock_swiping - ignore_perpendicular_swipes: True - anim_move_duration: root.anim_duration - on_index: root.on_carousel_index(*args) - on__offset: tab_bar.android_animation(*args) - - MDTabsBar: - id: tab_bar - padding: root.tab_padding - carousel: carousel - scrollview: scrollview - layout: layout - size_hint: 1, None - elevation: root.elevation - height: root.tab_bar_height - md_bg_color: - self.theme_cls.primary_color \ - if not root.background_color else \ - root.background_color - - MDTabsScrollView: - id: scrollview - do_scroll_x: False if layout.width <= self.width else True - - MDGridLayout: - id: layout - rows: 1 - size_hint_y: 1 - adaptive_width: True - on_size: root._update_padding(layout) - - canvas.before: - Color: - rgba: root.underline_color - Line: - width: dp(2) - rectangle: [0, 0, layout.width, dp(2)] - Color: - rgba: - root.theme_cls.accent_color \ - if not root.indicator_color else \ - root.indicator_color - RoundedRectangle: - group: "Indicator_line" - pos: self.pos - size: 0, root.tab_indicator_height - radius: [0,] - Line: - width: dp(2) - rounded_rectangle: - [ \ - root._line_x, \ - self.pos[1], \ - root._line_width, \ - root._line_height, \ - root._line_radius \ - ] -""" -) - - -class MDTabsException(Exception): - pass - - -class MDTabsLabel(ToggleButtonBehavior, RectangularRippleBehavior, MDLabel): - """This class it represent the label of each tab.""" - - text_color_normal = ColorProperty(None) - text_color_active = ColorProperty(None) - tab = ObjectProperty() - tab_bar = ObjectProperty() - font_name = StringProperty("Roboto") - - def __init__(self, **kwargs): - self.split_str = " ,-" - super().__init__(**kwargs) - self.max_lines = 2 - self.size_hint_x = None - self.size_hint_min_x = dp(90) - self.min_space = dp(98) - self.bind( - text=self._update_text_size, - ) - - def on_release(self): - self.tab_bar.parent.dispatch("on_tab_switch", self.tab, self, self.text) - # If the label is selected load the relative tab from carousel. - if self.state == "down": - self.tab_bar.parent.carousel.load_slide(self.tab) - - def on_texture(self, widget, texture): - # Just save the minimum width of the label based of the content. - if texture: - max_width = dp(360) - min_width = dp(90) - if texture.width > max_width: - self.width = max_width - self.text_size = (max_width, None) - elif texture.width < min_width: - self.width = min_width - else: - self.width = texture.width - - def _update_text_size(self, *args): - if not self.tab_bar: - return - if self.tab_bar.parent.allow_stretch is True: - self.text_size = (None, None) - else: - self.width = self.tab_bar.parent.fixed_tab_label_width - self.text_size = (self.width, None) - Clock.schedule_once(self.tab_bar._label_request_indicator_update, 0) - - -class MDTabsBase(Widget): - """ - This class allow you to create a tab. - You must create a new class that inherits from MDTabsBase. - In this way you have total control over the views of your tabbed panel. - """ - - icon = StringProperty() - """ - This property will set the Tab's Label Icon. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - title_icon_mode = OptionProperty("Lead", options=["Lead", "Top"]) - """ - This property sets the mode in wich the tab's title and icon are shown. - - :attr:`title_icon_mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Lead'`. - """ - - title = StringProperty() - """ - This property will set the Name of the tab. - - .. note:: - As a side note. - - All tabs have set `markup = True`. - Thanks to this, you can use the kivy markup language to set a colorful - and fully customizable tabs titles. - - .. warning:: - The material design requires that every title label is written in - capital letters, because of this, the `string.upper()` will be applied - to it's contents. - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - title_is_capital = BooleanProperty(False) - """ - This value controls wether if the title property should be converted to - capital letters. - - :attr:`title_is_capital` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - text = StringProperty(deprecated=True) - """ - This property is the actual title of the tab. - use the property :attr:`icon` and :attr:`title` to set this property - correctly. - - This property is kept public for specific and backward compatibility - purposes. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - - .. warning:: - This property is deprecated, use :attr:`tab_label_text` instead. - """ - - tab_label_text = StringProperty() - """ - This property is the actual title's Label of the tab. - use the property :attr:`icon` and :attr:`title` to set this property - correctly. - - This property is kept public for specific and backward compatibility - purposes. - - :attr:`tab_label_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - tab_label = ObjectProperty() - """ - It is the label object reference of the tab. - - :attr:`tab_label` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - def _get_label_font_style(self): - if self.tab_label: - return self.tab_label.font_style - - def _set_label_font_style(self, value): - if self.tab_label: - if value in theme_font_styles: - self.tab_label.font_style = value - else: - raise ValueError( - "tab_label_font_style:\n\t" - "font_style not found in theme_font_styles\n\t" - f"font_style = {value}" - ) - else: - Clock.schedule_once(lambda x: self._set_label_font_style(value)) - return True - - tab_label_font_style = AliasProperty( - _get_label_font_style, - _set_label_font_style, - cache=True, - ) - """ - :attr:`tab_label_font_style` is an :class:`~kivy.properties.AliasProperty` - that behavies similar to an :class:`~kivy.properties.OptionProperty`. - - This property's behavior allows the developer to use any new label style - registered to the app. - - This property will affect the Tab's Title Label widget. - """ - - def __init__(self, **kwargs): - self.tab_label = MDTabsLabel(tab=self) - super().__init__(**kwargs) - self.bind( - icon=self._update_text, - title=self._update_text, - title_icon_mode=self._update_text, - text=self.update_label_text, - tab_label_text=self.update_label_text, - title_is_capital=self.update_label_text, - ) - Clock.schedule_once( - self._update_text - ) # this will ensure the text is correct - - def _update_text(self, *args): - # Ensures that the title is in capital letters. - if self.title and self.title_is_capital is True: - if self.title != self.title.upper(): - self.title = self.title.upper() - # Avoids event recursion. - return - # Add the icon. - if self.icon and self.icon in md_icons: - self.tab_label_text = f"[size=24sp][font={fonts[-1]['fn_regular']}]{md_icons[self.icon]}[/size][/font]" - if self.title: - self.tab_label_text = ( - self.text - + (" " if self.title_icon_mode == "Lead" else "\n") - + self.title - ) - # Add the title. - else: - if self.icon: - Logger.error( - f"{self}: [UID] = [{self.uid}]:\n\t" - f"Icon '{self.icon}' not found in md_icons" - ) - if self.title: - self.tab_label_text = self.title - else: - if not self.tab_label_text: - raise ValueError( - f"{self}: [UID] = [{self.uid}]:\n\t" - "No valid Icon was found.\n\t" - "No valid Title was found.\n\t" - f"Icon\t= '{self.icon}'\n\t" - f"Title\t= '{self.title}'\n\t" - ) - - self.tab_label.padding = dp(16), 0 - self.update_label_text(None, self.tab_label_text) - - def update_label_text(self, widget, value): - self.tab_label.text = self.text = self.tab_label_text - - def on_text(self, widget, text): - self.tab_label_text = self.text - - -class MDTabsMain(MDBoxLayout): - """ - This class is just a boxlayout that contain the carousel. - It allows you to have control over the carousel. - """ - - -class MDTabsCarousel(MDCarousel): - lock_swiping = BooleanProperty(False) - """ - If True - disable switching tabs by swipe. - - :attr:`lock_swiping` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - def on_touch_move(self, touch): - if self.lock_swiping: # lock a swiping - return - if not self.touch_mode_change: - if self.ignore_perpendicular_swipes and self.direction in ( - "top", - "bottom", - ): - if abs(touch.oy - touch.y) < self.scroll_distance: - if abs(touch.ox - touch.x) > self.scroll_distance: - self._change_touch_mode() - self.touch_mode_change = True - elif self.ignore_perpendicular_swipes and self.direction in ( - "right", - "left", - ): - if abs(touch.ox - touch.x) < self.scroll_distance: - if abs(touch.oy - touch.y) > self.scroll_distance: - self._change_touch_mode() - self.touch_mode_change = True - - if self._get_uid("cavoid") in touch.ud: - return - if self._touch is not touch: - super().on_touch_move(touch) - return self._get_uid() in touch.ud - if touch.grab_current is not self: - return True - - ud = touch.ud[self._get_uid()] - direction = self.direction[0] - - if ud["mode"] == "unknown": - if direction in "rl": - distance = abs(touch.ox - touch.x) - else: - distance = abs(touch.oy - touch.y) - if distance > self.scroll_distance: - ev = self._change_touch_mode_ev - if ev is not None: - ev.cancel() - ud["mode"] = "scroll" - else: - if direction in "rl": - self._offset += touch.dx - if direction in "tb": - self._offset += touch.dy - return True - - -class MDTabsScrollView(ScrollView): - """This class hacked version to fix scroll_x manual setting.""" - - def goto(self, scroll_x, scroll_y): - """Update event value along with scroll_*.""" - - def _update(e, x): - if e: - e.value = (e.max + e.min) * x - - if not (scroll_x is None): - self.scroll_x = scroll_x - _update(self.effect_x, scroll_x) - - if not (scroll_y is None): - self.scroll_y = scroll_y - _update(self.effect_y, scroll_y) - - -class MDTabsBar( - ThemableBehavior, FakeRectangularElevationBehavior, MDBoxLayout -): - """ - This class is just a boxlayout that contains the scroll view for tabs. - It is also responsible for resizing the tab shortcut when necessary. - """ - - target = ObjectProperty(None, allownone=True) - """ - It is the carousel reference of the next tab / slide. - When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the - target tab / slide of the carousel. - - :attr:`target` is an :class:`~kivy.properties.ObjectProperty` - and default to `None`. - """ - - def get_rect_instruction(self): - canvas_instructions = self.layout.canvas.before.get_group( - "Indicator_line" - ) - return canvas_instructions[0] - - indicator = AliasProperty(get_rect_instruction, cache=True) - """ - It is the :class:`~kivy.graphics.vertex_instructions.RoundedRectangle` - instruction reference of the tab indicator. - - :attr:`indicator` is an :class:`~kivy.properties.AliasProperty`. - """ - - def get_last_scroll_x(self): - return self.scrollview.scroll_x - - last_scroll_x = AliasProperty( - get_last_scroll_x, bind=("target",), cache=True - ) - """ - Is the carousel reference of the next tab/slide. - When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the - target tab/slide of the carousel. - - :attr:`last_scroll_x` is an :class:`~kivy.properties.AliasProperty`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def update_indicator(self, x, w, radius=None): - # Update position and size of the indicator. - if self.parent.tab_indicator_type == "line-round": - self.parent._line_x = x - self.parent._line_width = w - self.parent._line_height = self.parent.tab_indicator_height - self.parent._line_radius = self.parent.tab_indicator_height / 2 - elif self.parent.tab_indicator_type == "line-rect": - self.parent._line_x = x - self.parent._line_width = w - self.parent._line_height = self.parent.tab_indicator_height - else: - self.indicator.pos = (x, 0) - self.indicator.size = (w, self.parent.tab_indicator_height) - if radius: - self.indicator.radius = radius - - def tab_bar_autoscroll(self, target, step): - # Automatic scroll animation of the tab bar. - bound_left = self.center_x - self.x - bound_right = self.layout.width - bound_left - dt = target.center_x - bound_left - sx, sy = self.scrollview.convert_distance_to_scroll(dt, 0) - lsx = self.last_scroll_x # ast scroll x of the tab bar - scroll_is_late = lsx < sx # determine scroll direction - dst = abs(lsx - sx) * step # distance to run - - if not dst: - return - if scroll_is_late and target.center_x > bound_left: - x = lsx + dst - elif not scroll_is_late and target.center_x < bound_right: - x = lsx - dst - else: - return - x = boundary(x, 0.0, 1.0) - self.scrollview.goto(x, None) - - def android_animation(self, carousel, offset): - # Try to reproduce the android animation effect. - if offset != 0 and abs(offset) < carousel.width: - forward = offset < 0 - offset = abs(offset) - step = offset / float(carousel.width) - indicator_animation = self.parent.tab_indicator_anim - - skip_slide = ( - carousel.slides[carousel._skip_slide] - if carousel._skip_slide is not None - else None - ) - next_slide = ( - carousel.next_slide if forward else carousel.previous_slide - ) - self.target = skip_slide if skip_slide else next_slide - - if not self.target: - return - - a = carousel.current_slide.tab_label - b = self.target.tab_label - self.tab_bar_autoscroll(b, step) - - # Avoids the animation if `indicator_animation` is True. - if indicator_animation is False: - return - gap_x = abs((a.x) - (b.x)) - gap_w = (b.width) - (a.width) - if forward: - x_step = a.x + (gap_x * step) - else: - x_step = a.x - gap_x * step - w_step = a.width + (gap_w * step) - self.update_indicator(x_step, w_step) - - def _label_request_indicator_update(self, *args): - widget = self.carousel.current_slide.tab_label - self.update_indicator(widget.x, widget.width) - - -class MDTabs(ThemableBehavior, SpecificBackgroundColorBehavior, AnchorLayout): - """ - You can use this class to create your own tabbed panel. - - :Events: - `on_tab_switch` - Called when switching tabs. - `on_slide_progress` - Called while the slide is scrolling. - `on_ref_press` - The method will be called when the ``on_ref_press`` event - occurs when you, for example, use markup text for tabs. - """ - - default_tab = NumericProperty(0) - """ - Index of the default tab. - - :attr:`default_tab` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - tab_bar_height = NumericProperty("48dp") - """ - Height of the tab bar. - - :attr:`tab_bar_height` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'48dp'`. - """ - - tab_padding = ListProperty([0, 0, 0, 0]) - """ - Padding of the tab bar. - - :attr:`tab_padding` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - tab_indicator_anim = BooleanProperty(False) - """ - Tab indicator animation. If you want use animation set it to ``True``. - - :attr:`tab_indicator_anim` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - tab_indicator_height = NumericProperty("2dp") - """ - Height of the tab indicator. - - :attr:`tab_indicator_height` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'2dp'`. - """ - - tab_indicator_type = OptionProperty( - "line", options=["line", "fill", "round", "line-round", "line-rect"] - ) - """ - Type of tab indicator. Available options are: `'line'`, `'fill'`, - `'round'`, `'line-rect'` and `'line-round'`. - - :attr:`tab_indicator_type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'line'`. - """ - - tab_hint_x = BooleanProperty(False) - """ - This option affects the size of each child. if it's `True`, the size of - each tab will be ignored and will use the size available by the container. - - :attr:`tab_hint_x` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - anim_duration = NumericProperty(0.2) - """ - Duration of the slide animation. - - :attr:`anim_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - anim_threshold = BoundedNumericProperty( - 0.8, min=0.0, max=1.0, errorhandler=lambda x: 0.0 if x < 0.0 else 1.0 - ) - """ - Animation threshold allow you to change the tab indicator animation effect. - - :attr:`anim_threshold` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `0.8`. - """ - - allow_stretch = BooleanProperty(True) - """ - If `True`, the tab will update dynamically (if :attr:`tab_hint_x` is `True`) - to it's content width, and wrap any text if the widget is wider than `"360dp"`. - - If `False`, the tab won't update to it's maximum texture width. - this means that the `fixed_tab_label_width` will be used as the label - width. this will wrap any text inside to fit the fixed value. - - :attr:`allow_stretch` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - fixed_tab_label_width = NumericProperty("140dp") - """ - If :attr:`allow_stretch` is `False`, the class will set this value as the - width to all the tabs title label. - - :attr:`fixed_tab_label_width` is an :class:`~kivy.properties.NumericProperty` - and defaults to `140dp`. - """ - - background_color = ColorProperty(None) - """ - Background color of tabs in ``rgba`` format. - - :attr:`background_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - underline_color = ColorProperty([0, 0, 0, 0]) - """ - Underline color of tabs in ``rgba`` format. - - :attr:`underline_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - text_color_normal = ColorProperty(None) - """ - Text color of the label when it is not selected. - - :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_color_active = ColorProperty(None) - """ - Text color of the label when it is selected. - - :attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - elevation = NumericProperty(0) - """ - Tab value elevation. - - .. seealso:: - - `Behaviors/Elevation `_ - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - indicator_color = ColorProperty(None) - """ - Color indicator in ``rgba`` format. - - :attr:`indicator_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - lock_swiping = BooleanProperty(False) - """ - If True - disable switching tabs by swipe. - - :attr:`lock_swiping` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - font_name = StringProperty("Roboto") - """ - Font name for tab text. - - :attr:`font_name` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - ripple_duration = NumericProperty(2) - """ - Ripple duration when long touching to tab. - - :attr:`ripple_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `2`. - """ - - no_ripple_effect = BooleanProperty(True) - """ - Whether to use the ripple effect when tapping on a tab. - - :attr:`no_ripple_effect` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - title_icon_mode = OptionProperty("Lead", options=["Lead", "Top"]) - """ - This property sets the mode in wich the tab's title and icon are shown. - - :attr:`title_icon_mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Lead'`. - """ - - force_title_icon_mode = BooleanProperty(True) - """ - If this property is se to `True`, it will force the class to update every - tab inside the scroll view to the current `title_icon_mode` - - :attr:`force_title_icon_mode` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_tab_switch") - self.register_event_type("on_ref_press") - self.register_event_type("on_slide_progress") - Clock.schedule_once(self._carousel_bind, 1) - self.theme_cls.bind( - primary_palette=self.update_icon_color, - theme_style=self.update_icon_color, - ) - self.bind( - force_title_icon_mode=self._parse_icon_mode, - title_icon_mode=self._parse_icon_mode, - ) - self.bind(tab_hint_x=self._update_tab_hint_x) - - def _update_tab_hint_x(self, *args): - if not self.ids.layout.children: - return - if self.tab_hint_x is True: - self.fixed_tab_label_width = self.width // len( - self.ids.layout.children - ) - self.allow_stretch = False - else: - self.allow_stretch = True - - def _parse_icon_mode(self, *args): - if self.force_title_icon_mode is True: - for slide in self.carousel.slides: - slide.title_icon_mode = self.title_icon_mode - if self.title_icon_mode == "Top": - self.tab_bar_height = dp(72) - else: - self.tab_bar_height = dp(48) - - def update_icon_color(self, instance, value): - for tab_label in self.get_tab_list(): - if not self.text_color_normal: - tab_label.text_color_normal = self.theme_cls.text_color - if not self.text_color_active: - tab_label.text_color_active = self.specific_secondary_text_color - - def switch_tab(self, name_tab, search_by="text"): - """ - This funciont switch between tabs - name_tab can be either a String or a MDTabsBase. - - `search_by` will look up through the properties of every tab. - - If the value doesnt match, it will raise a ValueError. - - Search_by options: - text : will search by the raw text of the label (`tab_label_text`) - icon : will search by the `icon` property - title : will search by the `title` property - """ - - if isinstance(name_tab, str): - if search_by == "title": - for tab_instance in self.tab_bar.parent.carousel.slides: - if tab_instance.title_is_capital is True: - _name_tab = name_tab.upper() - else: - _name_tab = name_tab - if tab_instance.title == _name_tab: - self.carousel.load_slide(tab_instance) - return - # Search by icon. - elif search_by == "icon": - for tab_instance in self.tab_bar.parent.carousel.slides: - if tab_instance.icon == name_tab: - self.carousel.load_slide(tab_instance) - return - # Search by title. - else: - for tab_instance in self.tab_bar.parent.carousel.slides: - if tab_instance.tab_label_text == name_tab: - self.carousel.load_slide(tab_instance) - return - raise ValueError( - "switch_tab:\n\t" - "name_tab not found in the tab list\n\t" - f"search_by = {repr(search_by)} \n\t" - f"name_tab = {repr(name_tab)} \n\t" - ) - else: - self.carousel.load_slide(name_tab.tab) - - def get_tab_list(self): - """Returns a list of tab objects.""" - - return self.tab_bar.layout.children[::-1] - - def get_slides(self): - return self.carousel.slides - - def add_widget(self, widget, index=0, canvas=None): - # You can add only subclass of MDTabsBase. - if not isinstance(widget, (MDTabsBase, MDTabsMain, MDTabsBar)): - raise ValueError( - f"MDTabs[{self.uid}].add_widget:\n\t" - "The widget provided is not a subclass of MDTabsBase." - ) - if len(self.children) >= 2: - try: - # FIXME: Can't set the value of the `no_ripple_effect` - # and `ripple_duration` properties for widget.tab_label. - widget.tab_label._no_ripple_effect = self.no_ripple_effect - widget.tab_label.ripple_duration_in_slow = self.ripple_duration - widget.tab_label.group = str(self) - widget.tab_label.tab_bar = self.tab_bar - widget.tab_label.font_name = self.font_name - widget.tab_label.text_color_normal = ( - self.text_color_normal - if self.text_color_normal - else self.specific_secondary_text_color - ) - widget.tab_label.text_color_active = ( - self.text_color_active - if self.text_color_active - else self.specific_text_color - ) - self.bind( - allow_stretch=widget.tab_label._update_text_size, - fixed_tab_label_width=widget.tab_label._update_text_size, - font_name=widget.tab_label.setter("font_name"), - text_color_active=widget.tab_label.setter( - "text_color_active" - ), - text_color_normal=widget.tab_label.setter( - "text_color_normal" - ), - ) - Clock.schedule_once(widget.tab_label._update_text_size, 0) - self.tab_bar.layout.add_widget(widget.tab_label) - self.carousel.add_widget(widget) - if self.force_title_icon_mode is True: - widget.title_icon_mode = self.title_icon_mode - Clock.schedule_once( - self.tab_bar._label_request_indicator_update, 0 - ) - return - except AttributeError: - pass - if isinstance(widget, (MDTabsMain, MDTabsBar)): - return super().add_widget(widget) - - def remove_widget(self, widget): - if len(self.carousel.slides) < 2: - return - # You can remove only subclass of MDTabsLabel or MDTabsBase. - if not issubclass(widget.__class__, (MDTabsLabel, MDTabsBase)): - raise MDTabsException( - "MDTabs can remove only subclass of MDTabsLabel or MDTabsBase" - ) - # If the widget is an instance of MDTabsBase, then the widget is - # set as the widget's tab_label object. - if issubclass(widget.__class__, MDTabsBase): - slide = widget - title_label = widget.tab_label - else: - # We already got the label, so we set the slide reference. - slide = widget.tab - title_label = widget - # Set memory. - # Search object next tab. - # Clean all bindings to allow the widget to be collected. - self.unbind( - allow_stretch=title_label._update_text_size, - fixed_tab_label_width=title_label._update_text_size, - font_name=title_label.setter("font_name"), - text_color_active=title_label.setter("text_color_active"), - text_color_normal=title_label.setter("text_color_normal"), - ) - self.carousel.remove_widget(slide) - self.tab_bar.layout.remove_widget(title_label) - # Clean the references. - slide = None - title_label = None - widget = None - - def on_slide_progress(self, *args): - """ - This event is deployed every available frame while the tab is scrolling. - """ - - def on_carousel_index(self, carousel, index): - """Called when the Tab index have changed. - - This event is deployed by the built in carousel of the class. - """ - - # When the index of the carousel change, update tab indicator, - # select the current tab and reset threshold data. - if carousel.current_slide: - current_tab_label = carousel.current_slide.tab_label - if current_tab_label.state == "normal": - # current_tab_label._do_press() - current_tab_label.dispatch("on_release") - current_tab_label._release_group(self) - current_tab_label.state = "down" - - if self.tab_indicator_type == "round": - self.tab_indicator_height = self.tab_bar_height - if index == 0: - radius = [ - 0, - self.tab_bar_height / 2, - self.tab_bar_height / 2, - 0, - ] - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width, radius - ) - elif index == len(self.get_tab_list()) - 1: - radius = [ - self.tab_bar_height / 2, - 0, - 0, - self.tab_bar_height / 2, - ] - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width, radius - ) - else: - radius = [ - self.tab_bar_height / 2, - ] - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width, radius - ) - elif ( - self.tab_indicator_type == "fill" - or self.tab_indicator_type == "line-round" - or self.tab_indicator_type == "line-rect" - ): - self.tab_indicator_height = self.tab_bar_height - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width - ) - else: - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width - ) - - def on_ref_press(self, *args): - """ - This event will be launched every time the user press a markup enabled - label with a link or reference inside. - """ - - def on_tab_switch(self, *args): - """This event is launched every time the current tab is changed.""" - - def on_size(self, *args): - if self.carousel.current_slide: - self._update_indicator(self.carousel.current_slide.tab_label) - - def _carousel_bind(self, interval): - self.carousel.bind(on_slide_progress=self._on_slide_progress) - - def _on_slide_progress(self, *args): - self.dispatch("on_slide_progress", args) - - def _update_indicator(self, current_tab_label): - def update_indicator(interval): - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width - ) - - if not current_tab_label: - current_tab_label = self.tab_bar.layout.children[-1] - Clock.schedule_once(update_indicator) - - def _update_padding(self, layout, *args): - if self.tab_hint_x is True: - layout.padding = [0, 0] - Clock.schedule_once(self._update_tab_hint_x) - return True - padding = [0, 0] - # This is more efficient than to use sum([layout.children]). - width = layout.width - (layout.padding[0] * 2) - # Forces the padding of the tab_bar when the tab_bar is scrollable. - if width > self.width: - padding = [dp(52), 0] - # Set the new padding. - layout.padding = padding - # Update the indicator. - if self.carousel.current_slide: - self._update_indicator(self.carousel.current_slide.tab_label) - Clock.schedule_once( - lambda x: setattr( - self.carousel.current_slide.tab_label, "state", "down" - ), - -1, - ) - return True diff --git a/kivymd/uix/taptargetview.py b/kivymd/uix/taptargetview.py deleted file mode 100644 index 48105ce..0000000 --- a/kivymd/uix/taptargetview.py +++ /dev/null @@ -1,849 +0,0 @@ -""" -Components/TapTargetView -======================== - -.. seealso:: - - `TapTargetView, GitHub `_ - - `TapTargetView, Material archive `_ - -.. rubric:: Provide value and improve engagement by introducing users to new - features and functionality at relevant moments. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-previous.gif - :align: center - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.taptargetview import MDTapTargetView - - KV = ''' - Screen: - - MDFloatingActionButton: - id: button - icon: "plus" - pos: 10, 10 - on_release: app.tap_target_start() - ''' - - - class TapTargetViewDemo(MDApp): - def build(self): - screen = Builder.load_string(KV) - self.tap_target_view = MDTapTargetView( - widget=screen.ids.button, - title_text="This is an add button", - description_text="This is a description of the button", - widget_position="left_bottom", - ) - - return screen - - def tap_target_start(self): - if self.tap_target_view.state == "close": - self.tap_target_view.start() - else: - self.tap_target_view.stop() - - - TapTargetViewDemo().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-usage.gif - :align: center - -Widget position ---------------- - -Sets the position of the widget relative to the floating circle. - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="right", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="left", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="top", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-top.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="bottom", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-bottom.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="left_top", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left_top.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="right_top", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right_top.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="left_bottom", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left_bottom.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="right_bottom", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right_bottom.png - :align: center - -If you use ``the widget_position = "center"`` parameter then you must -definitely specify the :attr:`~MDTapTargetView.title_position`. - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="center", - title_position="left_top", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-center.png - :align: center - -Text options ------------- - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - title_text="Title text", - description_text="Description text", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-text.png - :align: center - - -You can use the following options to control font size, color, and boldness: - -- :attr:`~MDTapTargetView.title_text_size` -- :attr:`~MDTapTargetView.title_text_color` -- :attr:`~MDTapTargetView.title_text_bold` -- :attr:`~MDTapTargetView.description_text_size` -- :attr:`~MDTapTargetView.description_text_color` -- :attr:`~MDTapTargetView.description_text_bold` - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - title_text="Title text", - title_text_size="36sp", - description_text="Description text", - description_text_color=[1, 0, 0, 1] - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-text-option.png - :align: center - -But you can also use markup to set these values. - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - title_text="[size=36]Title text[/size]", - description_text="[color=#ff0000ff]Description text[/color]", - ) - -Events control --------------- - -.. code-block:: python - - self.tap_target_view.bind(on_open=self.on_open, on_close=self.on_close) - -.. code-block:: python - - def on_open(self, instance_tap_target_view): - '''Called at the time of the start of the widget opening animation.''' - - print("Open", instance_tap_target_view) - - def on_close(self, instance_tap_target_view): - '''Called at the time of the start of the widget closed animation.''' - - print("Close", instance_tap_target_view) - -.. Note:: See other parameters in the :class:`~MDTapTargetView` class. -""" - -from kivy.animation import Animation -from kivy.event import EventDispatcher -from kivy.graphics import Color, Ellipse, Rectangle -from kivy.logger import Logger -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.label import Label - -from kivymd.theming import ThemableBehavior - - -class MDTapTargetView(ThemableBehavior, EventDispatcher): - """Rough try to mimic the working of Android's TapTargetView. - - :Events: - :attr:`on_open` - Called at the time of the start of the widget opening animation. - :attr:`on_close` - Called at the time of the start of the widget closed animation. - """ - - widget = ObjectProperty() - """ - Widget to add ``TapTargetView`` upon. - - :attr:`widget` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - outer_radius = NumericProperty(dp(200)) - """ - Radius for outer circle. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-outer-radius.png - :align: center - - :attr:`outer_radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(200)`. - """ - - outer_circle_color = ListProperty() - """ - Color for the outer circle in ``rgb`` format. - - .. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - outer_circle_color=(1, 0, 0) - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-outer-circle-color.png - :align: center - - :attr:`outer_circle_color` is an :class:`~kivy.properties.ListProperty` - and defaults to ``theme_cls.primary_color``. - """ - - outer_circle_alpha = NumericProperty(0.96) - """ - Alpha value for outer circle. - - :attr:`outer_circle_alpha` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.96`. - """ - - target_radius = NumericProperty(dp(45)) - """ - Radius for target circle. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-target-radius.png - :align: center - - :attr:`target_radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(45)`. - """ - - target_circle_color = ListProperty([1, 1, 1]) - """ - Color for target circle in ``rgb`` format. - - .. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - target_circle_color=(1, 0, 0) - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-target-circle-color.png - :align: center - - :attr:`target_circle_color` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1]`. - """ - - title_text = StringProperty() - """ - Title to be shown on the view. - - :attr:`title_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - title_text_size = NumericProperty(dp(25)) - """ - Text size for title. - - :attr:`title_text_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(25)`. - """ - - title_text_color = ListProperty([1, 1, 1, 1]) - """ - Text color for title. - - :attr:`title_text_color` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - title_text_bold = BooleanProperty(True) - """ - Whether title should be bold. - - :attr:`title_text_bold` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - description_text = StringProperty() - """ - Description to be shown below the title (keep it short). - - :attr:`description_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - description_text_size = NumericProperty(dp(20)) - """ - Text size for description text. - - :attr:`description_text_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(20)`. - """ - - description_text_color = ListProperty([0.9, 0.9, 0.9, 1]) - """ - Text size for description text. - - :attr:`description_text_color` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0.9, 0.9, 0.9, 1]`. - """ - - description_text_bold = BooleanProperty(False) - """ - Whether description should be bold. - - :attr:`description_text_bold` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - draw_shadow = BooleanProperty(False) - """ - Whether to show shadow. - - :attr:`draw_shadow` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - cancelable = BooleanProperty(False) - """ - Whether clicking outside the outer circle dismisses the view. - - :attr:`cancelable` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - widget_position = OptionProperty( - "left", - options=[ - "left", - "right", - "top", - "bottom", - "left_top", - "right_top", - "left_bottom", - "right_bottom", - "center", - ], - ) - """ - Sets the position of the widget on the :attr:`~outer_circle`. Available options are - `'left`', `'right`', `'top`', `'bottom`', `'left_top`', `'right_top`', - `'left_bottom`', `'right_bottom`', `'center`'. - - :attr:`widget_position` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'left'`. - """ - - title_position = OptionProperty( - "auto", - options=[ - "auto", - "left", - "right", - "top", - "bottom", - "left_top", - "right_top", - "left_bottom", - "right_bottom", - ], - ) - """ - Sets the position of :attr`~title_text` on the outer circle. Only works if - :attr`~widget_position` is set to `'center'`. In all other cases, it - calculates the :attr`~title_position` itself. - Must be set to other than `'auto`' when :attr`~widget_position` is set - to `'center`'. - - Available options are `'auto'`, `'left`', `'right`', `'top`', `'bottom`', - `'left_top`', `'right_top`', `'left_bottom`', `'right_bottom`', `'center`'. - - :attr:`title_position` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'auto'`. - """ - - stop_on_outer_touch = BooleanProperty(False) - """ - Whether clicking on outer circle stops the animation. - - :attr:`stop_on_outer_touch` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - stop_on_target_touch = BooleanProperty(True) - """ - Whether clicking on target circle should stop the animation. - - :attr:`stop_on_target_touch` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - state = OptionProperty("close", options=["close", "open"]) - """ - State of :class:`~MDTapTargetView`. - - :attr:`state` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'close'`. - """ - - _outer_radius = NumericProperty(0) - _target_radius = NumericProperty(0) - - def __init__(self, **kwargs): - self.ripple_max_dist = dp(90) - self.on_outer_radius(self, self.outer_radius) - self.on_target_radius(self, self.target_radius) - self.anim_ripple = None - - self.core_title_text = Label( - markup=True, size_hint=(None, None), bold=self.title_text_bold - ) - self.core_title_text.bind( - texture_size=self.core_title_text.setter("size") - ) - self.core_description_text = Label(markup=True, size_hint=(None, None)) - self.core_description_text.bind( - texture_size=self.core_description_text.setter("size") - ) - - super().__init__(**kwargs) - self.register_event_type("on_outer_touch") - self.register_event_type("on_target_touch") - self.register_event_type("on_outside_click") - self.register_event_type("on_open") - self.register_event_type("on_close") - - if not self.outer_circle_color: - self.outer_circle_color = self.theme_cls.primary_color[:-1] - - def _initialize(self): - setattr(self.widget, "_outer_radius", 0) - setattr(self.widget, "_target_radius", 0) - setattr(self.widget, "target_ripple_radius", 0) - setattr(self.widget, "target_ripple_alpha", 0) - - # Bind some function on widget event when this function is called - # instead of when the class itself is initialized to prevent all - # widgets of all instances to get bind at once and start messing up. - self.widget.bind(on_touch_down=self._some_func) - - def _draw_canvas(self): - _pos = self._ttv_pos() - self.widget.canvas.before.clear() - - with self.widget.canvas.before: - # Outer circle. - Color( - *self.outer_circle_color, - self.outer_circle_alpha, - group="ttv_group", - ) - _rad1 = self.widget._outer_radius - Ellipse(size=(_rad1, _rad1), pos=_pos[0], group="ttv_group") - - # Title text. - Color(*self.title_text_color, group="ttv_group") - Rectangle( - size=self.core_title_text.texture.size, - texture=self.core_title_text.texture, - pos=_pos[1], - group="ttv_group", - ) - - # Description text. - Color(*self.description_text_color, group="ttv_group") - Rectangle( - size=self.core_description_text.texture.size, - texture=self.core_description_text.texture, - pos=( - _pos[1][0], - _pos[1][1] - self.core_description_text.size[1] - 5, - ), - group="ttv_group", - ) - - # Target circle. - Color(*self.target_circle_color, group="ttv_group") - _rad2 = self.widget._target_radius - Ellipse( - size=(_rad2, _rad2), - pos=( - self.widget.x - (_rad2 / 2 - self.widget.size[0] / 2), - self.widget.y - (_rad2 / 2 - self.widget.size[0] / 2), - ), - group="ttv_group", - ) - - # Target ripple. - Color( - *self.target_circle_color, - self.widget.target_ripple_alpha, - group="ttv_group", - ) - _rad3 = self.widget.target_ripple_radius - Ellipse( - size=(_rad3, _rad3), - pos=( - self.widget.x - (_rad3 / 2 - self.widget.size[0] / 2), - self.widget.y - (_rad3 / 2 - self.widget.size[0] / 2), - ), - group="ttv_group", - ) - - def stop(self, *args): - """Starts widget close animation.""" - - # It needs a better implementation. - if self.anim_ripple is not None: - self.anim_ripple.unbind(on_complete=self._repeat_ripple) - self.core_title_text.opacity = 0 - self.core_description_text.opacity = 0 - anim = Animation( - d=0.15, - t="in_cubic", - **dict( - zip( - ["_outer_radius", "_target_radius", "target_ripple_radius"], - [0, 0, 0], - ) - ), - ) - anim.bind(on_complete=self._after_stop) - anim.start(self.widget) - - def _after_stop(self, *args): - self.widget.canvas.before.remove_group("ttv_group") - args[0].stop_all(self.widget) - elev = getattr(self.widget, "elevation", None) - - if elev: - self._fix_elev() - self.dispatch("on_close") - - # Don't forget to unbind the function or it'll mess - # up with other next bindings. - self.widget.unbind(on_touch_down=self._some_func) - self.state = "close" - - def _fix_elev(self): - with self.widget.canvas.before: - Color(a=self.widget._soft_shadow_a) - Rectangle( - texture=self.widget._soft_shadow_texture, - size=self.widget._soft_shadow_size, - pos=self.widget._soft_shadow_pos, - ) - Color(a=self.widget._hard_shadow_a) - Rectangle( - texture=self.widget._hard_shadow_texture, - size=self.widget._hard_shadow_size, - pos=self.widget._hard_shadow_pos, - ) - Color(a=1) - - def start(self, *args): - """Starts widget opening animation.""" - - self._initialize() - self._animate_outer() - self.state = "open" - self.core_title_text.opacity = 1 - self.core_description_text.opacity = 1 - self.dispatch("on_open") - - def _animate_outer(self): - anim = Animation( - d=0.2, - t="out_cubic", - **dict( - zip( - ["_outer_radius", "_target_radius"], - [self._outer_radius, self._target_radius], - ) - ), - ) - anim.cancel_all(self.widget) - anim.bind(on_progress=lambda x, y, z: self._draw_canvas()) - anim.bind(on_complete=self._animate_ripple) - anim.start(self.widget) - setattr(self.widget, "target_ripple_radius", self._target_radius) - setattr(self.widget, "target_ripple_alpha", 1) - - def _animate_ripple(self, *args): - self.anim_ripple = Animation( - d=1, - t="in_cubic", - target_ripple_radius=self._target_radius + self.ripple_max_dist, - target_ripple_alpha=0, - ) - self.anim_ripple.stop_all(self.widget) - self.anim_ripple.bind(on_progress=lambda x, y, z: self._draw_canvas()) - self.anim_ripple.bind(on_complete=self._repeat_ripple) - self.anim_ripple.start(self.widget) - - def _repeat_ripple(self, *args): - setattr(self.widget, "target_ripple_radius", self._target_radius) - setattr(self.widget, "target_ripple_alpha", 1) - self._animate_ripple() - - def on_open(self, *args): - """Called at the time of the start of the widget opening animation.""" - - def on_close(self, *args): - """Called at the time of the start of the widget closed animation.""" - - def on_draw_shadow(self, instance, value): - Logger.warning( - "The shadow adding method will be implemented in future versions" - ) - - def on_description_text(self, instance, value): - self.core_description_text.text = value - - def on_description_text_size(self, instance, value): - self.core_description_text.font_size = value - - def on_description_text_bold(self, instance, value): - self.core_description_text.bold = value - - def on_title_text(self, instance, value): - self.core_title_text.text = value - - def on_title_text_size(self, instance, value): - self.core_title_text.font_size = value - - def on_title_text_bold(self, instance, value): - self.core_title_text.bold = value - - def on_outer_radius(self, instance, value): - self._outer_radius = self.outer_radius * 2 - - def on_target_radius(self, instance, value): - self._target_radius = self.target_radius * 2 - - def on_target_touch(self): - if self.stop_on_target_touch: - self.stop() - - def on_outer_touch(self): - if self.stop_on_outer_touch: - self.stop() - - def on_outside_click(self): - if self.cancelable: - self.stop() - - def _some_func(self, wid, touch): - """ - This function decides which one to dispatch based on the touch - position. - """ - - if self._check_pos_target(touch.pos): - self.dispatch("on_target_touch") - elif self._check_pos_outer(touch.pos): - self.dispatch("on_outer_touch") - else: - self.dispatch("on_outside_click") - - def _check_pos_outer(self, pos): - """ - Checks if a given `pos` coordinate is within the :attr:`~outer_radius`. - """ - - cx = self.circ_pos[0] + self._outer_radius / 2 - cy = self.circ_pos[1] + self._outer_radius / 2 - r = self._outer_radius / 2 - h, k = pos - - lhs = (cx - h) ** 2 + (cy - k) ** 2 - rhs = r ** 2 - if lhs <= rhs: - return True - return False - - def _check_pos_target(self, pos): - """ - Checks if a given `pos` coordinate is within the - :attr:`~target_radius`. - """ - - cx = self.widget.pos[0] + self.widget.width / 2 - cy = self.widget.pos[1] + self.widget.height / 2 - r = self._target_radius / 2 - h, k = pos - - lhs = (cx - h) ** 2 + (cy - k) ** 2 - rhs = r ** 2 - if lhs <= rhs: - return True - return False - - def _ttv_pos(self): - """ - Calculates the `pos` value for outer circle and text - based on the position provided. - - :returns: A tuple containing pos for the circle and text. - """ - - _rad1 = self.widget._outer_radius - _center_x = self.widget.x - (_rad1 / 2 - self.widget.size[0] / 2) - _center_y = self.widget.y - (_rad1 / 2 - self.widget.size[0] / 2) - - if self.widget_position == "left": - circ_pos = (_center_x + _rad1 / 3, _center_y) - title_pos = (_center_x + _rad1 / 1.4, _center_y + _rad1 / 1.4) - elif self.widget_position == "right": - circ_pos = (_center_x - _rad1 / 3, _center_y) - title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 1.4) - elif self.widget_position == "top": - circ_pos = (_center_x, _center_y - _rad1 / 3) - title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4) - elif self.widget_position == "bottom": - circ_pos = (_center_x, _center_y + _rad1 / 3) - title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 1.2) - # Corner ones need to be at a little smaller distance - # than edge ones that's why _rad1/4. - elif self.widget_position == "left_top": - circ_pos = (_center_x + _rad1 / 4, _center_y - _rad1 / 4) - title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 4) - elif self.widget_position == "right_top": - circ_pos = (_center_x - _rad1 / 4, _center_y - _rad1 / 4) - title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 4) - elif self.widget_position == "left_bottom": - circ_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4) - title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.2) - elif self.widget_position == "right_bottom": - circ_pos = (_center_x - _rad1 / 4, _center_y + _rad1 / 4) - title_pos = (_center_x, _center_y + _rad1 / 1.2) - else: - # Center. - circ_pos = (_center_x, _center_y) - - if self.title_position == "auto": - raise ValueError( - "widget_position='center' requires title_position to be set." - ) - elif self.title_position == "left": - title_pos = (_center_x + _rad1 / 10, _center_y + _rad1 / 2) - elif self.title_position == "right": - title_pos = (_center_x + _rad1 / 1.6, _center_y + _rad1 / 2) - elif self.title_position == "top": - title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 1.3) - elif self.title_position == "bottom": - title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 4) - elif self.title_position == "left_top": - title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 1.4) - elif self.title_position == "right_top": - title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.3) - elif self.title_position == "left_bottom": - title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 4) - elif self.title_position == "right_bottom": - title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 3.5) - else: - raise ValueError( - f"'{self.title_position}'" - f"is not a valid value for title_position" - ) - - self.circ_pos = circ_pos - return circ_pos, title_pos diff --git a/kivymd/uix/textfield.py b/kivymd/uix/textfield.py deleted file mode 100644 index a768d39..0000000 --- a/kivymd/uix/textfield.py +++ /dev/null @@ -1,1484 +0,0 @@ -""" -Components/Text Field -===================== - -.. seealso:: - - `Material Design spec, Text fields `_ - -.. rubric:: Text fields let users enter and edit text. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields.png - :align: center - -`KivyMD` provides the following field classes for use: - -- MDTextField_ -- MDTextFieldRound_ -- MDTextFieldRect_ - -.. Note:: :class:`~MDTextField` inherited from - :class:`~kivy.uix.textinput.TextInput`. Therefore, most parameters and all - events of the :class:`~kivy.uix.textinput.TextInput` class are also - available in the :class:`~MDTextField` class. - -.. MDTextField: -MDTextField ------------ - - -:class:`~MDTextField` can be with helper text and without. - -Without helper text mode ------------------------- - -.. code-block:: kv - - MDTextField: - hint_text: "No helper text" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-no-helper-mode.gif - :align: center - -Helper text mode on ``on_focus`` event --------------------------------------- - -.. code-block:: kv - - MDTextField: - hint_text: "Helper text on focus" - helper_text: "This will disappear when you click off" - helper_text_mode: "on_focus" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-on-focus.gif - :align: center - -Persistent helper text mode ---------------------------- - -.. code-block:: kv - - MDTextField: - hint_text: "Persistent helper text" - helper_text: "Text is always here" - helper_text_mode: "persistent" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-persistent.gif - :align: center - -Helper text mode `'on_error'` ------------------------------ - -To display an error in a text field when using the -``helper_text_mode: "on_error"`` parameter, set the `"error"` text field -parameter to `True`: - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - BoxLayout: - padding: "10dp" - - MDTextField: - id: text_field_error - hint_text: "Helper text on error (press 'Enter')" - helper_text: "There will always be a mistake" - helper_text_mode: "on_error" - pos_hint: {"center_y": .5} - ''' - - - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) - - def build(self): - self.screen.ids.text_field_error.bind( - on_text_validate=self.set_error_message, - on_focus=self.set_error_message, - ) - return self.screen - - def set_error_message(self, instance_textfield): - self.screen.ids.text_field_error.error = True - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-on-error.gif - :align: center - -Helper text mode `'on_error'` (with required) ---------------------------------------------- - -.. code-block:: kv - - MDTextField: - hint_text: "required = True" - required: True - helper_text_mode: "on_error" - helper_text: "Enter text" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-required.gif - :align: center - -Text length control -------------------- - -.. code-block:: kv - - MDTextField: - hint_text: "Max text length = 5" - max_text_length: 5 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-length.gif - :align: center - - -Multi line text ---------------- - -.. code-block:: kv - - MDTextField: - multiline: True - hint_text: "Multi-line text" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-multi-line.gif - :align: center - -Color mode ----------- - -.. code-block:: kv - - MDTextField: - hint_text: "color_mode = 'accent'" - color_mode: 'accent' - -Available options are `'primary'`, `'accent'` or `'custom`'. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-color-mode.gif - :align: center - -.. code-block:: kv - - MDTextField: - hint_text: "color_mode = 'custom'" - color_mode: 'custom' - helper_text_mode: "on_focus" - helper_text: "Color is defined by 'line_color_focus' property" - line_color_focus: 1, 0, 1, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-color-mode-custom.gif - :align: center - -.. code-block:: kv - - MDTextField: - hint_text: "Line color normal" - line_color_normal: app.theme_cls.accent_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.png - :align: center - -Rectangle mode --------------- - -.. code-block:: kv - - MDTextField: - hint_text: "Rectangle mode" - mode: "rectangle" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rectangle-mode.gif - :align: center - -Fill mode ---------- - -.. code-block:: kv - - MDTextField: - hint_text: "Fill mode" - mode: "fill" - fill_color: 0, 0, 0, .4 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode.gif - :align: center - -Maximum height --------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen - - MDTextField: - size_hint_x: .5 - hint_text: "multiline=True" - max_height: "200dp" - mode: "fill" - fill_color: 0, 0, 0, .4 - multiline: True - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode-multiline-max-height.gif - :align: center - -.. MDTextFieldRect: -MDTextFieldRect ---------------- - -.. Note:: :class:`~MDTextFieldRect` inherited from - :class:`~kivy.uix.textinput.TextInput`. You can use all parameters and - attributes of the :class:`~kivy.uix.textinput.TextInput` class in the - :class:`~MDTextFieldRect` class. - -.. code-block:: kv - - MDTextFieldRect: - size_hint: 1, None - height: "30dp" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rect.gif - :align: center - -.. Warning:: While there is no way to change the color of the border. - -.. MDTextFieldRound: -MDTextFieldRound ----------------- - -Without icon ------------- - -.. code-block:: kv - - MDTextFieldRound: - hint_text: 'Empty field' - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round.gif - :align: center - -With left icon --------------- - -.. Warning:: The icons in the :class:`~MDTextFieldRound` are static. You cannot - bind events to them. - -.. code-block:: kv - - MDTextFieldRound: - icon_left: "email" - hint_text: "Field with left icon" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-left-icon.png - :align: center - -With left and right icons -------------------------- - -.. code-block:: kv - - MDTextFieldRound: - icon_left: 'key-variant' - icon_right: 'eye-off' - hint_text: 'Field with left and right icons' - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-left-right-icon.png - :align: center - -Control background color ------------------------- - -.. code-block:: kv - - MDTextFieldRound: - icon_left: 'key-variant' - normal_color: app.theme_cls.accent_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-normal-color.gif - :align: center - -.. code-block:: kv - - MDTextFieldRound: - icon_left: 'key-variant' - normal_color: app.theme_cls.accent_color - color_active: 1, 0, 0, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-active-color.gif - :align: center - -Clickable icon for MDTextFieldRound ------------------------------------ - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.relativelayout import MDRelativeLayout - - KV = ''' - : - size_hint_y: None - height: text_field.height - - MDTextFieldRound: - id: text_field - hint_text: root.hint_text - text: root.text - password: True - color_active: app.theme_cls.primary_light - icon_left: "key-variant" - padding: - self._lbl_icon_left.texture_size[1] + dp(10) if self.icon_left else dp(15), \ - (self.height / 2) - (self.line_height / 2), \ - self._lbl_icon_right.texture_size[1] + dp(20), \ - 0 - - MDIconButton: - icon: "eye-off" - ripple_scale: .5 - pos_hint: {"center_y": .5} - pos: text_field.width - self.width + dp(8), 0 - on_release: - self.icon = "eye" if self.icon == "eye-off" else "eye-off" - text_field.password = False if text_field.password is True else True - - - MDScreen: - - ClickableTextFieldRound: - size_hint_x: None - width: "300dp" - hint_text: "Password" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class ClickableTextFieldRound(MDRelativeLayout): - text = StringProperty() - hint_text = StringProperty() - # Here specify the required parameters for MDTextFieldRound: - # [...] - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -With right icon ---------------- - -.. Note:: The icon on the right is available for use in all text fields. - -.. code-block:: kv - - MDTextField: - hint_text: "Name" - mode: "fill" - fill_color: 0, 0, 0, .4 - icon_right: "arrow-down-drop-circle-outline" - icon_right_color: app.theme_cls.primary_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode-icon.png - :align: center - -.. code-block:: kv - - MDTextField: - hint_text: "Name" - icon_right: "arrow-down-drop-circle-outline" - icon_right_color: app.theme_cls.primary_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-right-icon.png - :align: center - -.. code-block:: kv - - MDTextField: - hint_text: "Name" - mode: "rectangle" - icon_right: "arrow-down-drop-circle-outline" - icon_right_color: app.theme_cls.primary_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rectangle-right-icon.png - :align: center - -.. seealso:: - - See more information in the :class:`~MDTextFieldRect` class. -""" - -__all__ = ("MDTextField", "MDTextFieldRect", "MDTextFieldRound") - -import re -import sys - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp, sp -from kivy.properties import ( - AliasProperty, - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.label import Label -from kivy.uix.textinput import TextInput - -from kivymd.font_definitions import theme_font_styles -from kivymd.theming import ThemableBehavior -from kivymd.uix.label import MDIcon - -Builder.load_string( - """ -#:import images_path kivymd.images_path - - - - - canvas.before: - Clear - - # Disabled line. - Color: - rgba: - (self.line_color_normal \ - if self.line_color_normal else self.theme_cls.divider_color) \ - if root.mode == "line" else (0, 0, 0, 0) - Line: - points: self.x, self.y + dp(16), self.x + self.width, self.y + dp(16) - width: 1 - dash_length: dp(3) - dash_offset: 2 if self.disabled else 0 - - # Active line. - Color: - rgba: self._current_line_color if root.mode in ("line", "fill") and root.active_line else (0, 0, 0, 0) - Rectangle: - size: self._line_width, dp(2) - pos: self.center_x - (self._line_width / 2), self.y + (dp(16) if root.mode != "fill" else 0) - - # Helper text. - Color: - rgba: self._current_error_color if not self.helper_text_color else self.helper_text_color - Rectangle: - texture: self._msg_lbl.texture - size: - self._msg_lbl.texture_size[0] - (dp(3) if root.mode in ("fill", "rectangle") else 0), \ - self._msg_lbl.texture_size[1] - (dp(3) if root.mode in ("fill", "rectangle") else 0) - pos: self.x + (dp(8) if root.mode == "fill" else 0), self.y + (dp(3) if root.mode in ("fill", "rectangle") else 0) - - # Texture of right Icon. - Color: - rgba: self.icon_right_color if self.focus else self._current_hint_text_color - Rectangle: - texture: self._lbl_icon_right.texture - size: self._lbl_icon_right.texture_size if self.icon_right else (0, 0) - pos: - (self.width + self.x) - (self._lbl_icon_right.texture_size[1]) - dp(8), \ - self.center[1] - self._lbl_icon_right.texture_size[1] / 2 + (dp(8) if root.mode != "fill" else 0) \ - if root.mode != "rectangle" else \ - self.center[1] - self._lbl_icon_right.texture_size[1] / 2 - dp(4) - - Color: - rgba: self._current_right_lbl_color - Rectangle: - texture: self._right_msg_lbl.texture - size: self._right_msg_lbl.texture_size - pos: self.x + self.width - self._right_msg_lbl.texture_size[0] - dp(16), self.y - - Color: - rgba: - (self._current_line_color if self.focus and not \ - self._cursor_blink else (0, 0, 0, 0)) - Rectangle: - pos: (int(x) for x in self.cursor_pos) - size: 1, -self.line_height - - # Hint text. - Color: - rgba: self._current_hint_text_color if not self.current_hint_text_color else self.current_hint_text_color - Rectangle: - texture: self._hint_lbl.texture - size: self._hint_lbl.texture_size - pos: self.x + (dp(8) if root.mode == "fill" else 0), self.y + self.height - self._hint_y - - Color: - rgba: - self.disabled_foreground_color if self.disabled else\ - (self.hint_text_color if not self.text and not\ - self.focus else self.foreground_color) - - # "rectangle" mode - Color: - rgba: - (self._current_line_color if not self.text_color else self.text_color) \ - if self.focus else self._current_hint_text_color - Line: - width: dp(1) if root.mode == "rectangle" else dp(0.00001) - points: - ( - self.x + root._line_blank_space_right_point, self.top - self._hint_lbl.texture_size[1] // 2, - self.right + dp(12), self.top - self._hint_lbl.texture_size[1] // 2, - self.right + dp(12), self.y, - self.x - dp(12), self.y, - self.x - dp(12), self.top - self._hint_lbl.texture_size[1] // 2, - self.x + root._line_blank_space_left_point, self.top - self._hint_lbl.texture_size[1] // 2 - ) - - # "fill" mode. - canvas.after: - Color: - rgba: root._fill_color if root.mode == "fill" else (0, 0, 0, 0) - RoundedRectangle: - pos: self.x, self.y - size: self.width, self.height + dp(8) - radius: root.radius - - font_name: "Roboto" if not root.font_name else root.font_name - foreground_color: self.theme_cls.text_color - bold: False - padding: - 0 if root.mode != "fill" else "8dp", \ - "16dp" if root.mode != "fill" else "24dp", \ - 0 if root.mode != "fill" and not root.icon_right else ("14dp" if not root.icon_right else self._lbl_icon_right.texture_size[1] + dp(20)), \ - "16dp" if root.mode == "fill" else "10dp" - multiline: False - size_hint_y: None - height: self.minimum_height + (dp(8) if root.mode != "fill" else 0) - - - - size_hint_x: None - width: self.texture_size[0] - shorten: True - shorten_from: "right" - - - - on_focus: - root.anim_rect((root.x, root.y, root.right, root.y, root.right, \ - root.top, root.x, root.top, root.x, root.y), 1) if root.focus \ - else root.anim_rect((root.x - dp(60), root.y - dp(60), \ - root.right + dp(60), root.y - dp(60), - root.right + dp(60), root.top + dp(60), \ - root.x - dp(60), root.top + dp(60), \ - root.x - dp(60), root.y - dp(60)), 0) - - canvas.after: - Color: - group: "color" - rgba: root._primary_color - Line: - group: "rectangle" - width: dp(1.5) - points: - ( - self.x - dp(60), self.y - dp(60), - self.right + dp(60), self.y - dp(60), - self.right + dp(60), self.top + dp(60), - self.x - dp(60), self.top + dp(60), - self.x - dp(60), self.y - dp(60) - ) - - -: - multiline: False - size_hint: 1, None - height: self.line_height + dp(10) - background_active: f'{images_path}transparent.png' - background_normal: f'{images_path}transparent.png' - hint_text_color: self.theme_cls.disabled_hint_text_color - padding: - self._lbl_icon_left.texture_size[1] + dp(10) if self.icon_left else dp(15), \ - (self.height / 2) - (self.line_height / 2), \ - self._lbl_icon_right.texture_size[1] + dp(20) if self.icon_right else dp(15), \ - 0 - - canvas.before: - Color: - rgba: self.normal_color if not self.focus else self._color_active - Ellipse: - angle_start: 180 - angle_end: 360 - pos: self.pos[0] - self.size[1] / 2, self.pos[1] - size: self.size[1], self.size[1] - Ellipse: - angle_start: 360 - angle_end: 540 - pos: self.size[0] + self.pos[0] - self.size[1]/2.0, self.pos[1] - size: self.size[1], self.size[1] - Rectangle: - pos: self.pos - size: self.size - - # Texture of left Icon. - Color: - rgba: - self.icon_left_color \ - if self.focus else self.theme_cls.disabled_hint_text_color - Rectangle: - texture: self._lbl_icon_left.texture - size: - self._lbl_icon_left.texture_size if self.icon_left \ - else (0, 0) - pos: - self.x, \ - self.center[1] - self._lbl_icon_right.texture_size[1] / 2 - - # Texture of right Icon. - Color: - rgba: - self.icon_right_color \ - if self.focus else self.theme_cls.disabled_hint_text_color - Rectangle: - texture: self._lbl_icon_right.texture - size: - self._lbl_icon_right.texture_size if self.icon_right \ - else (0, 0) - pos: - (self.width + self.x) - (self._lbl_icon_right.texture_size[1]), \ - self.center[1] - self._lbl_icon_right.texture_size[1] / 2 - - Color: - rgba: - self.hint_text_color if not self.text else root.foreground_color - - canvas.after: - Color: - rgba: self.line_color if self.focus else self.theme_cls.disabled_hint_text_color - Line: - points: self.pos[0] , self.pos[1], self.pos[0] + self.size[0], self.pos[1] - Line: - points: self.pos[0], self.pos[1] + self.size[1], self.pos[0] + self.size[0], self.pos[1] + self.size[1] - Line: - ellipse: self.pos[0] - self.size[1] / 2, self.pos[1], self.size[1], self.size[1], 180, 360 - Line: - ellipse: self.size[0] + self.pos[0] - self.size[1] / 2.0, self.pos[1], self.size[1], self.size[1], 360, 540 -""" -) - - -class MDTextFieldRect(ThemableBehavior, TextInput): - line_anim = BooleanProperty(True) - """ - If True, then text field shows animated line when on focus. - - :attr:`line_anim` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - def get_rect_instruction(self): - canvas_instructions = self.canvas.after.get_group("rectangle") - return canvas_instructions[0] - - _rectangle = AliasProperty(get_rect_instruction, cache=True) - """ - It is the :class:`~kivy.graphics.vertex_instructions.Line` - instruction reference of the field rectangle. - - :attr:`_rectangle` is an :class:`~kivy.properties.AliasProperty`. - """ - - def get_color_instruction(self): - canvas_instructions = self.canvas.after.get_group("color") - return canvas_instructions[0] - - _rectangle_color = AliasProperty(get_color_instruction, cache=True) - """ - It is the :class:`~kivy.graphics.context_instructions.Color` - instruction reference of the field rectangle. - - :attr:`_rectangle_color` is an :class:`~kivy.properties.AliasProperty`. - """ - - _primary_color = ColorProperty((0, 0, 0, 0)) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._update_primary_color() - self.theme_cls.bind(primary_color=self._update_primary_color) - - def anim_rect(self, points, alpha): - if alpha == 1: - d_line = 0.3 - d_color = 0.4 - else: - d_line = 0.05 - d_color = 0.05 - - Animation( - points=points, d=(d_line if self.line_anim else 0), t="out_cubic" - ).start(self._rectangle) - Animation(a=alpha, d=(d_color if self.line_anim else 0)).start( - self._rectangle_color - ) - - def _update_primary_color(self, *args): - self._primary_color = self.theme_cls.primary_color - self._primary_color[3] = 0 - - -class TextfieldLabel(ThemableBehavior, Label): - font_style = OptionProperty("Body1", options=theme_font_styles) - # - field = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.font_size = sp(self.theme_cls.font_styles[self.font_style][1]) - - -class MDTextField(ThemableBehavior, TextInput): - helper_text = StringProperty("This field is required") - """ - Text for ``helper_text`` mode. - - :attr:`helper_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `'This field is required'`. - """ - - helper_text_color = ColorProperty(None) - - helper_text_mode = OptionProperty( - "none", options=["none", "on_error", "persistent", "on_focus"] - ) - """ - Helper text mode. Available options are: `'on_error'`, `'persistent'`, - `'on_focus'`. - - :attr:`helper_text_mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'none'`. - """ - - max_text_length = NumericProperty(None) - """ - Maximum allowed value of characters in a text field. - - :attr:`max_text_length` is an :class:`~kivy.properties.NumericProperty` - and defaults to `None`. - """ - - required = BooleanProperty(False) - """ - Required text. If True then the text field requires text. - - :attr:`required` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - color_mode = OptionProperty( - "primary", options=["primary", "accent", "custom"] - ) - """ - Color text mode. Available options are: `'primary'`, `'accent'`, - `'custom'`. - - :attr:`color_mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'primary'`. - """ - - mode = OptionProperty("line", options=["rectangle", "fill"]) - """ - Text field mode. Available options are: `'line'`, `'rectangle'`, `'fill'`. - - :attr:`mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'line'`. - """ - - line_color_normal = ColorProperty(None) - """ - Line color normal in ``rgba`` format. - - :attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - line_color_focus = ColorProperty(None) - """ - Line color focus in ``rgba`` format. - - :attr:`line_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - line_anim = BooleanProperty(True) - """ - If True, then text field shows animated line when on focus. - - :attr:`line_anim` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - error_color = ColorProperty(None) - """ - Error color in ``rgba`` format for ``required = True``. - - :attr:`error_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - fill_color = ColorProperty([0, 0, 0, 0]) - """ - The background color of the fill in rgba format when the ``mode`` parameter - is "fill". - - :attr:`fill_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `(0, 0, 0, 0)`. - """ - - active_line = BooleanProperty(True) - """ - Show active line or not. - - :attr:`active_line` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - error = BooleanProperty(False) - """ - If True, then the text field goes into ``error`` mode. - - :attr:`error` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - current_hint_text_color = ColorProperty(None) - """ - ``hint_text`` text color. - - :attr:`current_hint_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - icon_right = StringProperty() - """ - Right icon. - - :attr:`icon_right` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon_right_color = ColorProperty((0, 0, 0, 1)) - """ - Color of right icon in ``rgba`` format. - - :attr:`icon_right_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `(0, 0, 0, 1)`. - """ - - text_color = ColorProperty(None) - """ - Text color in ``rgba`` format. - - :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - font_size = NumericProperty("16sp") - """ - Font size of the text in pixels. - - :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` and - defaults to `'16sp'`. - """ - - # TODO: Add minimum allowed height. Otherwise, if the value is, - # for example, 20, the text field will simply be lessened. - max_height = NumericProperty(0) - """ - Maximum height of the text box when `multiline = True`. - - :attr:`max_height` is a :class:`~kivy.properties.NumericProperty` and - defaults to `0`. - """ - - radius = ListProperty([10, 10, 0, 0]) - """ - The corner radius for a text field in `fill` mode. - - :attr:`radius` is a :class:`~kivy.properties.ListProperty` and - defaults to `[10, 10, 0, 0]`. - """ - - font_name_helper_text = StringProperty("Roboto") - """ - Font name for helper text. - - :attr:`font_name_helper_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - font_name_hint_text = StringProperty("Roboto") - """ - Font name for hint text. - - :attr:`font_name_hint_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - font_name_max_length = StringProperty("Roboto") - """ - Font name for max text length. - - :attr:`font_name_max_length` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - _text_len_error = BooleanProperty(False) - _hint_lbl_font_size = NumericProperty("16sp") - _line_blank_space_right_point = NumericProperty(0) - _line_blank_space_left_point = NumericProperty(0) - _hint_y = NumericProperty("38dp") - _line_width = NumericProperty(0) - _current_line_color = ColorProperty((0, 0, 0, 0)) - _current_error_color = ColorProperty((0, 0, 0, 0)) - _current_hint_text_color = ColorProperty((0, 0, 0, 0)) - _current_right_lbl_color = ColorProperty((0, 0, 0, 0)) - _fill_color = ColorProperty((0, 0, 0, 0)) - _msg_lbl = None - _right_msg_lbl = None - _hint_lbl = None - _lbl_icon_right = None - - def __init__(self, **kwargs): - self.set_objects_labels() - super().__init__(**kwargs) - # Sets default colors. - self.line_color_normal = self.theme_cls.divider_color - self.line_color_focus = self.theme_cls.primary_color - self.error_color = self.theme_cls.error_color - self._current_hint_text_color = self.theme_cls.disabled_hint_text_color - self._current_line_color = self.theme_cls.primary_color - - self.bind( - helper_text=self._set_msg, - hint_text=self.on_hint_text, - _hint_lbl_font_size=self._hint_lbl.setter("font_size"), - helper_text_mode=self._set_message_mode, - max_text_length=self._set_max_text_length, - text=self.set_text, - ) - self.theme_cls.bind( - primary_color=self._update_primary_color, - theme_style=self._update_theme_style, - accent_color=self._update_accent_color, - ) - self.has_had_text = False - self._better_texture_size = None - Clock.schedule_once(self.check_text) - Clock.schedule_once(self._set_fill_color) - self._set_msg(self, self.helper_text) - - def check_text(self, interval): - self.set_text(self, self.text) - - def _set_fill_color(self, interval): - self._fill_color = self.fill_color - - def set_objects_labels(self): - """Creates labels objects for the parameters - `helper_text`,`hint_text`, etc.""" - - # Label object for `helper_text` parameter. - self._msg_lbl = TextfieldLabel( - font_style="Caption", - halign="left", - valign="middle", - text=self.helper_text, - field=self, - font_name=self.font_name_helper_text, - ) - # Label object for `max_text_length` parameter. - self._right_msg_lbl = TextfieldLabel( - font_style="Caption", - halign="right", - valign="middle", - text="", - field=self, - ) - # Label object for `hint_text` parameter. - self._hint_lbl = TextfieldLabel( - font_style="Subtitle1", halign="left", valign="middle", field=self - ) - # MDIcon object for the icon on the right. - self._lbl_icon_right = MDIcon(theme_text_color="Custom") - - def on_font_name_helper_text(self, instance, value): - self._msg_lbl.font_name = value - - def on_font_name_hint_text(self, instance, value): - self._hint_lbl.font_name = value - - def on_font_name_max_length(self, instance, value): - self._right_msg_lbl.font_name = value - - def on_icon_right(self, instance, value): - self._lbl_icon_right.icon = value - - def on_icon_right_color(self, instance, value): - self._lbl_icon_right.text_color = value - - def on_width(self, instance, width): - """Called when the application window is resized.""" - - if ( - any((self.focus, self.error, self._text_len_error)) - and instance is not None - ): - # Bottom line width when active focus. - self._line_width = width - self._msg_lbl.width = self.width - self._right_msg_lbl.width = self.width - - def on_focus(self, *args): - disabled_hint_text_color = self.theme_cls.disabled_hint_text_color - Animation.cancel_all( - self, "_line_width", "_hint_y", "_hint_lbl_font_size" - ) - self._set_text_len_error() - - if self.focus: - if not self._get_has_error(): - - def on_progress(*args): - self._line_blank_space_right_point = ( - self._hint_lbl.width + dp(5) - ) - - animation = Animation( - _line_blank_space_left_point=self._hint_lbl.x - dp(5), - _current_hint_text_color=self.line_color_focus, - _fill_color=self.fill_color[:-1] - + [self.fill_color[-1] - 0.1], - duration=0.2, - t="out_quad", - ) - animation.bind(on_progress=on_progress) - animation.start(self) - self.has_had_text = True - Animation.cancel_all( - self, "_line_width", "_hint_y", "_hint_lbl_font_size" - ) - if not self.text: - self._anim_lbl_font_size(dp(14), sp(12)) - Animation( - _line_width=self.width, - duration=(0.2 if self.line_anim else 0), - t="out_quad", - ).start(self) - if self._get_has_error(): - self._anim_current_error_color(self.error_color) - if self.helper_text_mode == "on_error" and ( - self.error or self._text_len_error - ): - self._anim_current_error_color(self.error_color) - elif ( - self.helper_text_mode == "on_error" - and not self.error - and not self._text_len_error - ): - self._anim_current_error_color((0, 0, 0, 0)) - elif self.helper_text_mode in ("persistent", "on_focus"): - self._anim_current_error_color(disabled_hint_text_color) - else: - self._anim_current_right_lbl_color(disabled_hint_text_color) - Animation( - duration=0.2, _current_hint_text_color=self.line_color_focus - ).start(self) - if self.helper_text_mode == "on_error": - self._anim_current_error_color((0, 0, 0, 0)) - if self.helper_text_mode in ("persistent", "on_focus"): - self._anim_current_error_color(disabled_hint_text_color) - else: - Animation( - _fill_color=self.fill_color[:-1] + [self.fill_color[-1] + 0.1], - duration=0.2, - t="out_quad", - ).start(self) - if not self.text: - self._anim_lbl_font_size(dp(38), sp(16)) - Animation( - _line_blank_space_right_point=0, - _line_blank_space_left_point=0, - duration=0.2, - t="out_quad", - ).start(self) - if self._get_has_error(): - self._anim_get_has_error_color(self.error_color) - if self.helper_text_mode == "on_error" and ( - self.error or self._text_len_error - ): - self._anim_current_error_color(self.error_color) - elif ( - self.helper_text_mode == "on_error" - and not self.error - and not self._text_len_error - ): - self._anim_current_error_color((0, 0, 0, 0)) - elif self.helper_text_mode == "persistent": - self._anim_current_error_color(disabled_hint_text_color) - elif self.helper_text_mode == "on_focus": - self._anim_current_error_color((0, 0, 0, 0)) - else: - Animation(duration=0.2, color=(1, 1, 1, 1)).start( - self._hint_lbl - ) - self._anim_get_has_error_color() - if self.helper_text_mode == "on_error": - self._anim_current_error_color((0, 0, 0, 0)) - elif self.helper_text_mode == "persistent": - self._anim_current_error_color(disabled_hint_text_color) - elif self.helper_text_mode == "on_focus": - self._anim_current_error_color((0, 0, 0, 0)) - Animation( - _line_width=0, - duration=(0.2 if self.line_anim else 0), - t="out_quad", - ).start(self) - - def on_disabled(self, *args): - if self.disabled: - self._update_colors(self.theme_cls.disabled_hint_text_color) - elif not self.disabled: - if self.color_mode == "primary": - self._update_primary_color() - elif self.color_mode == "accent": - self._update_accent_color() - elif self.color_mode == "custom": - self._update_colors(self.line_color_focus) - - def set_text(self, instance, text): - self.text = re.sub("\n", " ", text) if not self.multiline else text - if len(text) > 0: - self.has_had_text = True - if self.max_text_length is not None: - self._right_msg_lbl.text = f"{len(text)}/{self.max_text_length}" - self._set_text_len_error() - if self.error or self._text_len_error: - self._anim_current_line_color(self.error_color) - if self.helper_text_mode == "on_error" and ( - self.error or self._text_len_error - ): - self._anim_current_error_color(self.error_color) - if self._text_len_error: - self._anim_current_right_lbl_color(self.error_color) - else: - self._anim_current_right_lbl_color( - self.theme_cls.disabled_hint_text_color - ) - self._anim_current_line_color(self.line_color_focus) - if self.helper_text_mode == "on_error": - self._anim_current_error_color((0, 0, 0, 0)) - self.on_focus(self, self.focus) - - if len(self.text) != 0 and not self.focus: - self._hint_y = dp(14) - self._hint_lbl_font_size = sp(12) - - def on_text_validate(self): - self.has_had_text = True - self._set_text_len_error() - - def on_color_mode(self, instance, mode): - if mode == "primary": - self._update_primary_color() - elif mode == "accent": - self._update_accent_color() - elif mode == "custom": - self._update_colors(self.line_color_focus) - - def on_line_color_focus(self, *args): - if self.color_mode == "custom": - self._update_colors(self.line_color_focus) - - def on__hint_text(self, instance, value): - pass - - def on_hint_text(self, instance, value): - self._hint_lbl.text = value - self._hint_lbl.font_size = sp(16) - - def on_height(self, instance, value): - if value >= self.max_height and self.max_height: - self.height = self.max_height - - def _anim_get_has_error_color(self, color=None): - # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_get_has_error.png - if not color: - line_color = self.line_color_focus - hint_text_color = ( - self.theme_cls.disabled_hint_text_color - if not self.current_hint_text_color - else self.current_hint_text_color - ) - right_lbl_color = (0, 0, 0, 0) - else: - line_color = color - hint_text_color = color - right_lbl_color = color - Animation( - duration=0.2, - _current_line_color=line_color, - _current_hint_text_color=hint_text_color, - _current_right_lbl_color=right_lbl_color, - ).start(self) - - def _anim_current_line_color(self, color): - # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_line_color.gif - Animation( - duration=0.2, - _current_hint_text_color=color, - _current_line_color=color, - ).start(self) - - def _anim_lbl_font_size(self, hint_y, font_size): - Animation( - _hint_y=hint_y, - _hint_lbl_font_size=font_size, - duration=0.2, - t="out_quad", - ).start(self) - - def _anim_current_right_lbl_color(self, color, duration=0.2): - # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_right_lbl_color.png - Animation(duration=duration, _current_right_lbl_color=color).start(self) - - def _anim_current_error_color(self, color, duration=0.2): - # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_error_color_to_disabled_color.gif - # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_error_color_to_fade.gif - Animation(duration=duration, _current_error_color=color).start(self) - - def _update_colors(self, color): - self.line_color_focus = color - if not self.error and not self._text_len_error: - self._current_line_color = color - if self.focus: - self._current_line_color = color - - def _update_accent_color(self, *args): - if self.color_mode == "accent": - self._update_colors(self.theme_cls.accent_color) - - def _update_primary_color(self, *args): - if self.color_mode == "primary": - self._update_colors(self.theme_cls.primary_color) - - def _update_theme_style(self, *args): - self.line_color_normal = self.theme_cls.divider_color - if not any([self.error, self._text_len_error]): - if not self.focus: - self._current_hint_text_color = ( - self.theme_cls.disabled_hint_text_color - ) - self._current_right_lbl_color = ( - self.theme_cls.disabled_hint_text_color - ) - if self.helper_text_mode == "persistent": - self._current_error_color = ( - self.theme_cls.disabled_hint_text_color - ) - - def _get_has_error(self): - if self.error or all( - [ - self.max_text_length is not None - and len(self.text) > self.max_text_length - ] - ): - has_error = True - else: - if all((self.required, len(self.text) == 0, self.has_had_text)): - has_error = True - else: - has_error = False - return has_error - - def _get_max_text_length(self): - """Returns the maximum number of characters that can be entered in a - text field.""" - - return ( - sys.maxsize - if self.max_text_length is None - else self.max_text_length - ) - - def _set_text_len_error(self): - if len(self.text) > self._get_max_text_length() or all( - (self.required, len(self.text) == 0, self.has_had_text) - ): - self._text_len_error = True - else: - self._text_len_error = False - - def _set_msg(self, instance, text): - self._msg_lbl.text = text - self.helper_text = text - - def _set_message_mode(self, instance, text): - self.helper_text_mode = text - if self.helper_text_mode == "persistent": - self._anim_current_error_color( - self.theme_cls.disabled_hint_text_color, 0.1 - ) - - def _set_max_text_length(self, instance, length): - self.max_text_length = length - self._right_msg_lbl.text = f"{len(self.text)}/{length}" - - def _refresh_hint_text(self): - pass - - -class MDTextFieldRound(ThemableBehavior, TextInput): - icon_left = StringProperty() - """ - Left icon. - - :attr:`icon_left` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon_left_color = ColorProperty((0, 0, 0, 1)) - """ - Color of left icon in ``rgba`` format. - - :attr:`icon_left_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `(0, 0, 0, 1)`. - """ - - icon_right = StringProperty() - """ - Right icon. - - :attr:`icon_right` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon_right_color = ColorProperty((0, 0, 0, 1)) - """ - Color of right icon. - - :attr:`icon_right_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `(0, 0, 0, 1)`. - """ - - line_color = ColorProperty(None) - """ - Field line color. - - :attr:`line_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - normal_color = ColorProperty(None) - """ - Field color if `focus` is `False`. - - :attr:`normal_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - color_active = ColorProperty(None) - """ - Field color if `focus` is `True`. - - :attr:`color_active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _color_active = ColorProperty(None) - _icon_left_color_copy = ColorProperty(None) - _icon_right_color_copy = ColorProperty(None) - - def __init__(self, **kwargs): - self._lbl_icon_left = MDIcon(theme_text_color="Custom") - self._lbl_icon_right = MDIcon(theme_text_color="Custom") - super().__init__(**kwargs) - self.cursor_color = self.theme_cls.primary_color - self.icon_left_color = self.theme_cls.text_color - self.icon_right_color = self.theme_cls.text_color - - if not self.normal_color: - self.normal_color = self.theme_cls.primary_light - if not self.line_color: - self.line_color = self.theme_cls.primary_dark - if not self.color_active: - self._color_active = (0.5, 0.5, 0.5, 0.5) - - def on_focus(self, instance, value): - if value: - self.icon_left_color = self.theme_cls.primary_color - self.icon_right_color = self.theme_cls.primary_color - else: - self.icon_left_color = ( - self._icon_left_color_copy or self.theme_cls.text_color - ) - self.icon_right_color = ( - self._icon_right_color_copy or self.theme_cls.text_color - ) - - def on_icon_left(self, instance, value): - self._lbl_icon_left.icon = value - - def on_icon_left_color(self, instance, value): - self._lbl_icon_left.text_color = value - if ( - not self._icon_left_color_copy - and value != self.theme_cls.text_color - and value != self.theme_cls.primary_color - ): - self._icon_left_color_copy = value - - def on_icon_right(self, instance, value): - self._lbl_icon_right.icon = value - - def on_icon_right_color(self, instance, value): - self._lbl_icon_right.text_color = value - if ( - not self._icon_right_color_copy - and value != self.theme_cls.text_color - and value != self.theme_cls.primary_color - ): - self._icon_right_color_copy = value - - def on_color_active(self, instance, value): - if value != [0, 0, 0, 0.5]: - self._color_active = value - self._color_active[-1] = 0.5 - else: - self._color_active = value diff --git a/kivymd/uix/toolbar.py b/kivymd/uix/toolbar.py deleted file mode 100644 index 0fb6c3d..0000000 --- a/kivymd/uix/toolbar.py +++ /dev/null @@ -1,889 +0,0 @@ -""" -Components/Toolbar -================== - -.. seealso:: - - `Material Design spec, App bars: top `_ - - `Material Design spec, App bars: bottom `_ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-top.png - :align: center - -`KivyMD` provides the following toolbar positions for use: - -- Top_ -- Bottom_ - -.. Top: -Top ---- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "MDToolbar" - - MDLabel: - text: "Content" - halign: "center" - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-1.png - :align: center - -Add left menu -------------- - -.. code-block:: kv - - MDToolbar: - title: "MDToolbar" - left_action_items: [["menu", lambda x: app.callback()]] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-2.png - :align: center - -.. note:: - - The callback is optional. ``left_action_items: [["menu"]]`` would also work for a button that does nothing. - -Add right menu --------------- - -.. code-block:: kv - - MDToolbar: - title: "MDToolbar" - right_action_items: [["dots-vertical", lambda x: app.callback()]] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-3.png - :align: center - -Add two item to the right menu ------------------------------- - -.. code-block:: kv - - MDToolbar: - title: "MDToolbar" - right_action_items: [["dots-vertical", lambda x: app.callback_1()], ["clock", lambda x: app.callback_2()]] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-4.png - :align: center - -Change toolbar color --------------------- - -.. code-block:: kv - - MDToolbar: - title: "MDToolbar" - md_bg_color: app.theme_cls.accent_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-5.png - :align: center - -Change toolbar text color -------------------------- - -.. code-block:: kv - - MDToolbar: - title: "MDToolbar" - specific_text_color: app.theme_cls.accent_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-6.png - :align: center - -Shadow elevation control ------------------------- - -.. code-block:: kv - - MDToolbar: - title: "Elevation 10" - elevation: 10 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-7.png - :align: center - -.. Bottom: -Bottom ------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-bottom.png - :align: center - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDBoxLayout: - - # Will always be at the bottom of the screen. - MDBottomAppBar: - - MDToolbar: - title: "Title" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-8.png - :align: center - -Event on floating button ------------------------- - -Event ``on_action_button``: - -.. code-block:: kv - - MDBottomAppBar: - - MDToolbar: - title: "Title" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - on_action_button: app.callback(self.icon) - -Floating button position ------------------------- - -Mode: - -- `'free-end'` -- `'free-center'` -- `'end'` -- `'center'` - -.. code-block:: kv - - MDBottomAppBar: - - MDToolbar: - title: "Title" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - mode: "end" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-9.png - :align: center - -.. code-block:: kv - - MDBottomAppBar: - - MDToolbar: - title: "Title" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - mode: "free-end" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-10.png - :align: center - -Custom color ------------- - -.. code-block:: kv - - MDBottomAppBar: - md_bg_color: 0, 1, 0, 1 - - MDToolbar: - title: "Title" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - icon_color: 0, 1, 0, 1 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-11.png - :align: center - -MDToolbar with Menus --------------------- - -A Toolbar without Menus is not particularly useful. However, the -:class:`~MDDropdownMenu` works well with the standard -:class:`~kivymd.uix.toolbar.MDToolbar` to provide this functionality, -as shown in the image below. - -.. seealso:: - - See the - `MDDropdownMenu documentation - `_ - for details of how to implement this. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-menu.gif - :align: center - - -Tooltips --------- - -You can add MDTooltips to the Toolbar icons by ading a text string to the toolbar item, as shown below - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.snackbar import Snackbar - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDToolbar: - title: "MDToolbar" - left_action_items: [["menu", "This is the navigation"]] - right_action_items: [["dots-vertical", lambda x: app.callback(x), "this is the More Actions"]] - - MDLabel: - text: "Content" - halign: "center" - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def callback(self, button): - Snackbar(text="Hello World").open() - - Test().run() - -.. seealso:: - - `Components-Bottom-App-Bar `_ -""" - -from math import cos, radians, sin - -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 ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.floatlayout import FloatLayout - -from kivymd.color_definitions import text_colors -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import ( - FakeRectangularElevationBehavior, - SpecificBackgroundColorBehavior, -) -from kivymd.uix.button import MDFloatingActionButton, MDIconButton -from kivymd.uix.tooltip import MDTooltip - -Builder.load_string( - """ -#:import m_res kivymd.material_resources - - - - md_bg_color: self.theme_cls.primary_color - - canvas.before: - PushMatrix - Scale: - origin: self.center - x: root._scale_x - y: root._scale_y - canvas.after: - PopMatrix - - - - size_hint_y: None - height: root.theme_cls.standard_increment - padding: [root.theme_cls.horizontal_margins - dp(12), 0] - # opposite_colors: False - elevation: root.elevation - - canvas: - Color: - rgba: - ( \ - root.theme_cls.primary_color \ - if root.md_bg_color == [0, 0, 0, 0] \ - else root.md_bg_color \ - ) \ - if root.type == "top" else \ - ( \ - root.theme_cls.primary_color \ - if root.parent.md_bg_color == [0, 0, 0, 0] \ - else root.parent.md_bg_color \ - ) - Mesh: - vertices: root._vertices_left - indices: root._indices_left - mode: "triangle_fan" - Mesh: - vertices: root._vertices_right - indices: root._indices_right - mode: "triangle_fan" - RoundedRectangle: - pos: root._rectangle_left_pos - size: root._rectangle_left_width, root._rounded_rectangle_height - radius: - [0,] if root.mode == "normal" \ - else [0, root.notch_radius * root._rounding_percentage, 0, 0] - RoundedRectangle: - pos: root._rectangle_right_pos - size: root._rectangle_right_width, root._rounded_rectangle_height - radius: - [0,] if root.mode == "normal" \ - else [root.notch_radius * root._rounding_percentage, 0, 0, 0] - - - - - BoxLayout: - id: left_actions - orientation: "horizontal" - size_hint_x: None - padding: [0, (self.height - dp(48)) / 2] - - BoxLayout: - padding: dp(12), 0 - - MDLabel: - id: label_title - font_style: "H6" - opposite_colors: root.opposite_colors - theme_text_color: "Custom" if not root.opposite_colors else "Primary" - text_color: root.specific_text_color - text: root.title - shorten: True - shorten_from: "right" - halign: root.anchor_title - markup: True - - BoxLayout: - id: right_actions - orientation: "horizontal" - size_hint_x: None - padding: [0, (self.height - dp(48)) / 2] -""" -) - - -class MDActionBottomAppBarButton(MDFloatingActionButton): - _scale_x = NumericProperty(1) - _scale_y = NumericProperty(1) - - -class MDActionTopAppBarButton(MDIconButton, MDTooltip): - pass - - -class NotchedBox( - ThemableBehavior, - FakeRectangularElevationBehavior, - SpecificBackgroundColorBehavior, - BoxLayout, -): - elevation = NumericProperty(6) - """ - Elevation value. - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `6`. - """ - - notch_radius = NumericProperty() - notch_center_x = NumericProperty("100dp") - - _indices_right = ListProperty() - _vertices_right = ListProperty() - _indices_left = ListProperty() - _vertices_left = ListProperty() - _rounded_rectangle_height = NumericProperty("6dp") - _total_angle = NumericProperty(180) - _rectangle_left_pos = ListProperty([0, 0]) - _rectangle_left_width = NumericProperty() - _rectangle_right_pos = ListProperty([0, 0]) - _rectangle_right_width = NumericProperty() - _rounding_percentage = NumericProperty(0.15) - - def __init__(self, **kw): - super().__init__(**kw) - self.bind( - size=self._update_canvas, - pos=self._update_canvas, - notch_radius=self._update_canvas, - notch_center_x=self._update_canvas, - ) - Clock.schedule_once(self._update_canvas) - - def _update_canvas(self, *args): - pos = self.pos - size = [ - self.width, - self.size[1] - self._rounded_rectangle_height / 2, - ] - notch_center_x = self.pos[0] + self.notch_center_x - circle_radius = self.notch_radius - degree_diff = int((180 - self._total_angle) / 2) - circle_center = [notch_center_x, pos[1] + size[1]] - left_circle_pos = self._points_on_circle( - circle_center, circle_radius, 180 + degree_diff, 270 - ) - - self._rectangle_left_pos = [ - pos[0], - pos[1] + size[1] - self._rounded_rectangle_height / 2, - ] - self._rectangle_left_width = left_circle_pos[0][0] - self.pos[0] - - right_circle_pos = self._points_on_circle( - circle_center, circle_radius, -degree_diff, -90 - ) - - self._rectangle_right_pos = [ - right_circle_pos[0][0], - pos[1] + size[1] - self._rounded_rectangle_height / 2, - ] - self._rectangle_right_width = pos[0] + size[0] - right_circle_pos[0][0] - - raw_vertices_left = self._make_vertices( - pos, [notch_center_x - pos[0], size[1]], "left", left_circle_pos - ) - raw_vertices_right = self._make_vertices( - [notch_center_x, pos[1]], - [size[0] + pos[0] - notch_center_x, size[1]], - "right", - right_circle_pos, - ) - - left_vertices, left_indices = self._make_vertices_indices( - raw_vertices_left - ) - right_vertices, right_indices = self._make_vertices_indices( - raw_vertices_right - ) - - self._update_mesh(left_vertices, left_indices, "left") - self._update_mesh(right_vertices, right_indices, "right") - - def _update_mesh(self, vertices, indices, mode): - if mode == "left": - self._indices_left = indices - self._vertices_left = vertices - else: - self._indices_right = indices - self._vertices_right = vertices - return True - - @staticmethod - def _make_vertices_indices(points_list): - vertices = [] - indices = [] - for index, point in enumerate(points_list): - indices.append(index) - vertices.extend([point[0], point[1], 0, 0]) - - return [vertices, indices] - - @staticmethod - def _make_vertices(rectangle_pos, rectangle_size, mode, notch_points=[]): - x = rectangle_pos[0] - y = rectangle_pos[1] - w = rectangle_size[0] - h = rectangle_size[1] - - if mode == "left": - rectangle_vertices = [[x, y], [x, y + h]] - elif mode == "right": - rectangle_vertices = [[x + w, y], [x + w, y + h]] - rectangle_vertices.extend(notch_points) - if mode == "left": - rectangle_vertices.extend([[x + w, y]]) - elif mode == "right": - rectangle_vertices.extend([[x, y]]) - - return rectangle_vertices - - @staticmethod - def _points_on_circle(center, radius, start_angle, end_angle): - points = [] - y_diff = False - if end_angle >= 180: - step = 1 - end_angle += 1 - elif end_angle <= 0: - step = -1 - end_angle -= 1 - else: - raise Exception("Invalid value for start angle") - - for degree in range(start_angle, end_angle, step): - - angle = radians(degree) - x = center[0] + (radius * cos(angle)) - y = center[1] + (radius * sin(angle)) - - if y_diff is False: - y_diff = abs(y - center[1]) - - y += y_diff - points.append([x, y]) - - return points - - -class MDToolbar(NotchedBox): - """ - :Events: - `on_action_button` - Method for the button used for the :class:`~MDBottomAppBar` class. - """ - - left_action_items = ListProperty() - """ - The icons on the left of the toolbar. - To add one, append a list like the following: - - .. code-block:: kv - - left_action_items: [`'icon_name'`, callback, tooltip text] - - where `'icon_name'` is a string that corresponds to an icon definition, - ``callback`` is the function called on a touch release event and - ``tooltip text` is the text to be displayed in the tooltip. Both the - ``callback`` and ``tooltip text`` are optional but the order must be - preserved. - - :attr:`left_action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - right_action_items = ListProperty() - """ - The icons on the left of the toolbar. - Works the same way as :attr:`left_action_items`. - - :attr:`right_action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - title = StringProperty() - """ - Text toolbar. - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - anchor_title = OptionProperty("left", options=["left", "center", "right"]) - """ - Position toolbar title. - Available options are: `'left'`, `'center'`, `'right'`. - - :attr:`anchor_title` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'left'`. - """ - - mode = OptionProperty( - "center", options=["free-end", "free-center", "end", "center"] - ) - """ - Floating button position. Only for :class:`~MDBottomAppBar` class. - Available options are: `'free-end'`, `'free-center'`, `'end'`, `'center'`. - - :attr:`mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'center'`. - """ - - round = NumericProperty("10dp") - """ - Rounding the corners at the notch for a button. - Onle for :class:`~MDBottomAppBar` class. - - :attr:`round` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'10dp'`. - """ - - icon = StringProperty("android") - """ - Floating button. Onle for :class:`~MDBottomAppBar` class. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'android'`. - """ - - icon_color = ColorProperty() - """ - Color action button. Onle for :class:`~MDBottomAppBar` class. - - :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. - """ - - type = OptionProperty("top", options=["top", "bottom"]) - """ - When using the :class:`~MDBottomAppBar` class, the parameter ``type`` - must be set to `'bottom'`: - - .. code-block:: kv - - MDBottomAppBar: - - MDToolbar: - type: "bottom" - - Available options are: `'top'`, `'bottom'`. - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'top'`. - """ - - opposite_colors = BooleanProperty(False) - - _shift = NumericProperty("3.5dp") - - def __init__(self, **kwargs): - self.action_button = MDActionBottomAppBarButton() - super().__init__(**kwargs) - self.register_event_type("on_action_button") - if not self.icon_color: - self.icon_color = self.theme_cls.primary_color - Window.bind(on_resize=self._on_resize) - self.bind(specific_text_color=self.update_action_bar_text_colors) - # self.bind(opposite_colors=self.update_opposite_colors) - self.theme_cls.bind(primary_palette=self.update_md_bg_color) - Clock.schedule_once( - lambda x: self.on_left_action_items(0, self.left_action_items) - ) - Clock.schedule_once( - lambda x: self.on_right_action_items(0, self.right_action_items) - ) - Clock.schedule_once(lambda x: self.set_md_bg_color(0, self.md_bg_color)) - - def on_type(self, instance, value): - if value == "bottom": - self.action_button.bind(center_x=self.setter("notch_center_x")) - self.action_button.bind( - on_release=lambda x: self.dispatch("on_action_button") - ) - self.action_button.x = ( - Window.width / 2 - self.action_button.width / 2 - ) - self.action_button.y = ( - (self.center[1] - self.height / 2) - + self.theme_cls.standard_increment / 2 - + self._shift - ) - self.on_mode(None, self.mode) - - def on_action_button(self, *args): - pass - - def on_md_bg_color(self, instance, value): - if self.type == "bottom": - self.md_bg_color = [0, 0, 0, 0] - - def on_left_action_items(self, instance, value): - self.update_action_bar(self.ids["left_actions"], value) - - def on_right_action_items(self, instance, value): - self.update_action_bar(self.ids["right_actions"], value) - - def set_md_bg_color(self, instance, value): - if value == [1.0, 1.0, 1.0, 0.0]: - self.md_bg_color = self.theme_cls.primary_color - - def update_action_bar(self, action_bar, action_bar_items): - action_bar.clear_widgets() - new_width = 0 - for item in action_bar_items: - new_width += dp(48) - if len(item) == 1: - item.append(lambda x: None) - if len(item) > 1 and not item[1]: - item[1] = lambda x: None - if len(item) == 2: - if type(item[1]) is str: - item.insert(1, lambda x: None) - else: - item.append("") - action_bar.add_widget( - MDActionTopAppBarButton( - icon=item[0], - on_release=item[1], - tooltip_text=item[2], - theme_text_color="Custom" - if not self.opposite_colors - else "Primary", - text_color=self.specific_text_color, - opposite_colors=self.opposite_colors, - ) - ) - action_bar.width = new_width - - def update_md_bg_color(self, *args): - self.md_bg_color = self.theme_cls._get_primary_color() - - def update_opposite_colors(self, instance, value): - if value: - self.ids.label_title.theme_text_color = "" - - def update_action_bar_text_colors(self, *args): - for child in self.ids["left_actions"].children: - child.text_color = self.specific_text_color - for child in self.ids["right_actions"].children: - child.text_color = self.specific_text_color - - def on_icon(self, instance, value): - self.action_button.icon = value - - def on_icon_color(self, instance, value): - self.action_button.md_bg_color = value - - def on_mode(self, instance, value): - if self.type == "top": - return - - def set_button_pos(*args): - self.action_button.x = x - self.action_button.y = y - self._rounded_rectangle_height / 2 - self.action_button._hard_shadow_size = (0, 0) - self.action_button._soft_shadow_size = (0, 0) - anim = Animation(_scale_x=1, _scale_y=1, d=0.05) - anim.bind(on_complete=self.set_shadow) - anim.start(self.action_button) - - if value == "center": - self.set_notch() - x = Window.width / 2 - self.action_button.width / 2 - y = ( - (self.center[1] - self.height / 2) - + self.theme_cls.standard_increment / 2 - + self._shift - ) - elif value == "end": - - self.set_notch() - x = Window.width - self.action_button.width * 2 - y = ( - (self.center[1] - self.height / 2) - + self.theme_cls.standard_increment / 2 - + self._shift - ) - self.right_action_items = [] - elif value == "free-end": - self.remove_notch() - x = Window.width - self.action_button.width - dp(10) - y = self.action_button.height + self.action_button.height / 2 - elif value == "free-center": - self.remove_notch() - x = Window.width / 2 - self.action_button.width / 2 - y = self.action_button.height + self.action_button.height / 2 - self.remove_shadow() - anim = Animation(_scale_x=0, _scale_y=0, d=0.1) - anim.bind(on_complete=set_button_pos) - anim.start(self.action_button) - - def remove_notch(self): - anim = Animation(d=0.1) + Animation( - notch_radius=0, - d=0.1, - ) - anim.start(self) - - def set_notch(self): - anim = Animation(d=0.1) + Animation( - notch_radius=self.action_button.width / 2 + dp(8), - d=0.1, - ) - anim.start(self) - - def remove_shadow(self): - self.action_button._elevation = 0 - - def set_shadow(self, *args): - self.action_button._elevation = self.action_button.elevation - - def _on_resize(self, instance, width, height): - if self.mode == "center": - self.action_button.x = width / 2 - self.action_button.width / 2 - else: - self.action_button.x = width - self.action_button.width * 2 - - def _update_specific_text_color(self, instance, value): - if self.specific_text_color in ( - [0.0, 0.0, 0.0, 0.87], - [0.0, 0.0, 0.0, 1.0], - [1.0, 1.0, 1.0, 1.0], - ): - self.specific_text_color = text_colors[ - self.theme_cls.primary_palette - ][self.theme_cls.primary_hue] - - -class MDBottomAppBar(FloatLayout): - md_bg_color = ColorProperty([0, 0, 0, 0]) - """ - Color toolbar. - - :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.size_hint_y = None - - def add_widget(self, widget, index=0, canvas=None): - if isinstance(widget, MDToolbar): - super().add_widget(widget) - return super().add_widget(widget.action_button) diff --git a/kivymd/uix/tooltip.py b/kivymd/uix/tooltip.py deleted file mode 100644 index b0ace81..0000000 --- a/kivymd/uix/tooltip.py +++ /dev/null @@ -1,337 +0,0 @@ -""" -Components/Tooltip -================== - -.. seealso:: - - `Material Design spec, Tooltips `_ - -.. rubric:: Tooltips display informative text when users hover over, focus on, - or tap an element. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip.png - :align: center - -To use the :class:`~MDTooltip` class, you must create a new class inherited -from the :class:`~MDTooltip` class: - -In Kv-language: - -.. code-block:: kv - - - -In Python code: - -.. code-block:: python - - class TooltipMDIconButton(MDIconButton, MDTooltip): - pass - -.. Warning:: :class:`~MDTooltip` only works correctly with button and label classes. - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - - - - Screen: - - TooltipMDIconButton: - icon: "language-python" - tooltip_text: self.icon - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip.gif - :align: center - -.. Note:: The behavior of tooltips on desktop and mobile devices is different. - For more detailed information, - `click here `_. -""" - -__all__ = ("MDTooltip", "MDTooltipViewClass") - -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 ( - BoundedNumericProperty, - ColorProperty, - ListProperty, - NumericProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.boxlayout import BoxLayout - -from kivymd.font_definitions import theme_font_styles -from kivymd.material_resources import DEVICE_TYPE -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import HoverBehavior, TouchBehavior - -Builder.load_string( - """ -#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE - - - - size_hint: None, None - width: self.minimum_width - height: self.minimum_height + root.padding[1] - opacity: 0 - - canvas.before: - PushMatrix - Color: - rgba: - root.theme_cls.opposite_bg_dark if not root.tooltip_bg_color \ - else root.tooltip_bg_color - RoundedRectangle: - pos: self.pos - size: self.size - radius: root.tooltip_radius - Scale: - origin: self.center - x: root._scale_x - y: root._scale_y - canvas.after: - PopMatrix - - MDLabel: - id: label_tooltip - text: root.tooltip_text - size_hint: None, None - -text_size: None, None - size: self.texture_size - bold: True - theme_text_color: "Custom" - font_style: root.tooltip_font_style - markup: True - text_color: - ([0, 0, 0, 1] if not root.tooltip_text_color else root.tooltip_text_color) \ - if root.theme_cls.theme_style == "Dark" else \ - ([1, 1, 1, 1] if not root.tooltip_text_color else root.tooltip_text_color) - pos_hint: {"center_y": .5} -""" -) - - -class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): - tooltip_bg_color = ColorProperty(None) - """ - Tooltip background color in ``rgba`` format. - - :attr:`tooltip_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - tooltip_text_color = ColorProperty(None) - """ - Tooltip text color in ``rgba`` format. - - :attr:`tooltip_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - tooltip_text = StringProperty() - """ - Tooltip text. - - :attr:`tooltip_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - tooltip_font_style = OptionProperty("Caption", options=theme_font_styles) - """ - Tooltip font style. Available options are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, - `'H5'`, `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, - `'Button'`, `'Caption'`, `'Overline'`, `'Icon'`. - - :attr:`tooltip_font_style` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Caption'`. - """ - - tooltip_radius = ListProperty( - [ - dp(7), - ] - ) - """ - Corner radius values. - - :attr:`radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[dp(7),]`. - """ - - tooltip_display_delay = BoundedNumericProperty(0, min=0, max=4) - """ - Tooltip dsiplay delay. - - :attr:`tooltip_display_delay` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `0`, min of `0` & max of `4`. This property only works on desktop. - """ - - shift_y = NumericProperty() - """ - Y-offset of tooltip text. - - :attr:`shift_y` is an :class:`~kivy.properties.StringProperty` - and defaults to `0`. - """ - - _tooltip = None - - def delete_clock(self, widget, touch, *args): - if self.collide_point(touch.x, touch.y) and touch.grab_current: - try: - Clock.unschedule(touch.ud["event"]) - except KeyError: - pass - self.on_leave() - - def adjust_tooltip_position(self, x, y): - """Returns the coordinates of the tooltip - that fit into the borders of the screen.""" - - # If the position of the tooltip is outside the right border - # of the screen. - if x + self._tooltip.width > Window.width: - x = Window.width - (self._tooltip.width + dp(10)) - else: - # If the position of the tooltip is outside the left border - # of the screen. - if x < 0: - x = "10dp" - # If the tooltip position is below bottom the screen border. - if y < 0: - y = dp(10) - # If the tooltip position is below top the screen border. - else: - if Window.height - self._tooltip.height < y: - y = Window.height - (self._tooltip.height + dp(10)) - return x, y - - def display_tooltip(self, interval): - if not self._tooltip: - return - Window.add_widget(self._tooltip) - pos = self.to_window(self.center_x, self.center_y) - x = pos[0] - self._tooltip.width / 2 - - if not self.shift_y: - y = pos[1] - self._tooltip.height / 2 - self.height / 2 - dp(20) - else: - y = pos[1] - self._tooltip.height / 2 - self.height + self.shift_y - - x, y = self.adjust_tooltip_position(x, y) - self._tooltip.pos = (x, y) - - if DEVICE_TYPE == "desktop": - Clock.schedule_once( - self.animation_tooltip_show, self.tooltip_display_delay - ) - else: - Clock.schedule_once(self.animation_tooltip_show, 0) - - def animation_tooltip_show(self, interval): - if not self._tooltip: - return - ( - Animation(_scale_x=1, _scale_y=1, d=0.1) - + Animation(opacity=1, d=0.2) - ).start(self._tooltip) - - def remove_tooltip(self, *args): - Window.remove_widget(self._tooltip) - - def on_long_touch(self, touch, *args): - if DEVICE_TYPE != "desktop": - self.on_enter(True) - - def on_enter(self, *args): - """See - :attr:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior.on_enter` - method in :class:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior` - class. - """ - - if not args and DEVICE_TYPE != "desktop": - return - else: - if not self.tooltip_text: - return - self._tooltip = MDTooltipViewClass( - tooltip_bg_color=self.tooltip_bg_color, - tooltip_text_color=self.tooltip_text_color, - tooltip_text=self.tooltip_text, - tooltip_font_style=self.tooltip_font_style, - tooltip_radius=self.tooltip_radius, - ) - Clock.schedule_once(self.display_tooltip, -1) - - def on_leave(self): - """See - :attr:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior.on_leave` - method in :class:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior` - class. - """ - - if self._tooltip: - Window.remove_widget(self._tooltip) - self._tooltip = None - - -class MDTooltipViewClass(ThemableBehavior, BoxLayout): - tooltip_bg_color = ColorProperty(None) - """ - See :attr:`~MDTooltip.tooltip_bg_color`. - """ - - tooltip_text_color = ColorProperty(None) - """ - See :attr:`~MDTooltip.tooltip_text_color`. - """ - - tooltip_text = StringProperty() - """ - See :attr:`~MDTooltip.tooltip_text`. - """ - - tooltip_font_style = OptionProperty("Caption", options=theme_font_styles) - """ - See :attr:`~MDTooltip.tooltip_font_style`. - """ - - tooltip_radius = ListProperty() - """ - See :attr:`~MDTooltip.tooltip_radius`. - """ - - _scale_x = NumericProperty(0) - _scale_y = NumericProperty(0) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.padding = [ - dp(8) if DEVICE_TYPE == "desktop" else dp(16), - dp(4), - dp(8) if DEVICE_TYPE == "desktop" else dp(16), - dp(4), - ] diff --git a/kivymd/utils/__init__.py b/kivymd/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kivymd/utils/__pycache__/__init__.cpython-38.pyc b/kivymd/utils/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index abfc4fa..0000000 Binary files a/kivymd/utils/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/kivymd/utils/__pycache__/asynckivy.cpython-38.pyc b/kivymd/utils/__pycache__/asynckivy.cpython-38.pyc deleted file mode 100644 index 59260a4..0000000 Binary files a/kivymd/utils/__pycache__/asynckivy.cpython-38.pyc and /dev/null differ diff --git a/kivymd/utils/__pycache__/cropimage.cpython-38.pyc b/kivymd/utils/__pycache__/cropimage.cpython-38.pyc deleted file mode 100644 index ed5d550..0000000 Binary files a/kivymd/utils/__pycache__/cropimage.cpython-38.pyc and /dev/null differ diff --git a/kivymd/utils/__pycache__/fitimage.cpython-38.pyc b/kivymd/utils/__pycache__/fitimage.cpython-38.pyc deleted file mode 100644 index cbce4c2..0000000 Binary files a/kivymd/utils/__pycache__/fitimage.cpython-38.pyc and /dev/null differ diff --git a/kivymd/utils/__pycache__/fpsmonitor.cpython-38.pyc b/kivymd/utils/__pycache__/fpsmonitor.cpython-38.pyc deleted file mode 100644 index ad641ff..0000000 Binary files a/kivymd/utils/__pycache__/fpsmonitor.cpython-38.pyc and /dev/null differ diff --git a/kivymd/utils/__pycache__/hot_reload_viewer.cpython-38.pyc b/kivymd/utils/__pycache__/hot_reload_viewer.cpython-38.pyc deleted file mode 100644 index f76739a..0000000 Binary files a/kivymd/utils/__pycache__/hot_reload_viewer.cpython-38.pyc and /dev/null differ diff --git a/kivymd/utils/asynckivy.py b/kivymd/utils/asynckivy.py deleted file mode 100644 index 373ab0b..0000000 --- a/kivymd/utils/asynckivy.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -asynckivy -========= - -Copyright (c) 2019 Nattōsai Mitō - -GitHub - - https://github.com/gottadiveintopython -GitHub Gist - - https://gist.github.com/gottadiveintopython/5f4a775849f9277081c396de65dc57c1 - -""" - -__all__ = ("start", "sleep", "event") - -import types -from collections import namedtuple -from functools import partial - -from kivy.clock import Clock - -CallbackParameter = namedtuple("CallbackParameter", ("args", "kwargs")) - - -def start(coro): - def step(*args, **kwargs): - try: - coro.send(CallbackParameter(args, kwargs))(step) - except StopIteration: - pass - - try: - coro.send(None)(step) - except StopIteration: - pass - - -@types.coroutine -def sleep(duration): - # The partial() here looks meaningless. But this is needed in order - # to avoid weak reference. - param = yield lambda step_coro: Clock.schedule_once( - partial(step_coro), duration - ) - return param.args[0] - - -class event: - def __init__(self, ed, name): - self.bind_id = None - self.ed = ed - self.name = name - - def bind(self, step_coro): - self.bind_id = bind_id = self.ed.fbind(self.name, self.callback) - assert bind_id > 0 # check if binding succeeded - self.step_coro = step_coro - - def callback(self, *args, **kwargs): - self.parameter = CallbackParameter(args, kwargs) - ed = self.ed - ed.unbind_uid(self.name, self.bind_id) - self.step_coro() - - def __await__(self): - yield self.bind - return self.parameter diff --git a/kivymd/utils/cropimage.py b/kivymd/utils/cropimage.py deleted file mode 100644 index 647be4c..0000000 --- a/kivymd/utils/cropimage.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Crop Image -========== -""" - - -def crop_image( - cutting_size, - path_to_image, - path_to_save_crop_image, - corner=0, - blur=0, - corner_mode="all", -): - """Call functions of cropping/blurring/rounding image. - - cutting_size: size to which the image will be cropped; - path_to_image: path to origin image; - path_to_save_crop_image: path to new image; - corner: value of rounding corners; - blur: blur value; - corner_mode: 'all'/'top'/'bottom' - indicates which corners to round out; - - """ - - im = _crop_image(cutting_size, path_to_image, path_to_save_crop_image) - if corner: - im = add_corners(im, corner, corner_mode) - if blur: - im = add_blur(im, blur) - try: - im.save(path_to_save_crop_image) - except IOError: - im.save(path_to_save_crop_image, "JPEG") - - -def add_blur(im, mode): - from PIL import ImageFilter - - im = im.filter(ImageFilter.GaussianBlur(mode)) - - return im - - -def _crop_image(cutting_size, path_to_image, path_to_save_crop_image): - from PIL import Image, ImageOps - - image = Image.open(path_to_image) - image = ImageOps.fit(image, cutting_size) - image.save(path_to_save_crop_image) - - return image - - -def add_corners(im, corner, corner_mode): - def add_top_corners(): - alpha.paste(circle.crop((0, 0, corner, corner)), (0, 0)) - alpha.paste( - circle.crop((corner, 0, corner * 2, corner)), (w - corner, 0) - ) - - def add_bottom_corners(): - alpha.paste( - circle.crop((0, corner, corner, corner * 2)), (0, h - corner) - ) - alpha.paste( - circle.crop((corner, corner, corner * 2, corner * 2)), - (w - corner, h - corner), - ) - - from PIL import Image, ImageDraw - - circle = Image.new("L", (corner * 2, corner * 2), 0) - draw = ImageDraw.Draw(circle) - draw.ellipse((0, 0, corner * 2, corner * 2), fill=255) - alpha = Image.new("L", im.size, 255) - w, h = im.size - - if corner_mode == "all": - add_top_corners() - add_bottom_corners() - elif corner_mode == "top": - add_top_corners() - if corner_mode == "bottom": - add_bottom_corners() - im.putalpha(alpha) - - return im - - -def prepare_mask(size, antialias=2): - from PIL import Image, ImageDraw - - mask = Image.new("L", (size[0] * antialias, size[1] * antialias), 0) - ImageDraw.Draw(mask).ellipse((0, 0) + mask.size, fill=255) - return mask.resize(size, Image.ANTIALIAS) - - -def _crop_round_image(im, s): - from PIL import Image - - w, h = im.size - k = w // s[0] - h // s[1] - if k > 0: - im = im.crop(((w - h) // 2, 0, (w + h) // 2, h)) - elif k < 0: - im = im.crop((0, (h - w) // 2, w, (h + w) // 2)) - return im.resize(s, Image.ANTIALIAS) - - -def crop_round_image(cutting_size, path_to_image, path_to_new_image): - from PIL import Image - - im = Image.open(path_to_image) - im = _crop_round_image(im, cutting_size) - im.putalpha(prepare_mask(cutting_size, 4)) - im.save(path_to_new_image) diff --git a/kivymd/utils/fitimage.py b/kivymd/utils/fitimage.py deleted file mode 100644 index 269db01..0000000 --- a/kivymd/utils/fitimage.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -Fit Image -========= - -Feature to automatically crop a `Kivy` image to fit your layout -Write by Benedikt Zwölfer - -Referene - https://gist.github.com/benni12er/95a45eb168fc33a4fcd2d545af692dad - - -Example: -======== - -.. code-block:: kv - - BoxLayout: - size_hint_y: None - height: "200dp" - orientation: 'vertical' - - FitImage: - size_hint_y: 3 - source: 'images/img1.jpg' - - FitImage: - size_hint_y: 1 - source: 'images/img2.jpg' - -Example with round corners: -=========================== - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fitimage-round-corners.png - :align: center - -.. code-block:: python - - from kivy.uix.modalview import ModalView - from kivy.lang import Builder - - from kivymd import images_path - from kivymd.app import MDApp - from kivymd.uix.card import MDCard - - Builder.load_string( - ''' - : - elevation: 10 - radius: [36, ] - - FitImage: - id: bg_image - source: "images/bg.png" - size_hint_y: .35 - pos_hint: {"top": 1} - radius: [36, 36, 0, 0, ] - ''') - - - class Card(MDCard): - pass - - - class Example(MDApp): - def build(self): - modal = ModalView( - size_hint=(0.4, 0.8), - background=f"{images_path}/transparent.png", - overlay_color=(0, 0, 0, 0), - ) - modal.add_widget(Card()) - modal.open() - - - Example().run() -""" - -__all__ = ("FitImage",) - -from kivy.clock import Clock -from kivy.graphics.context_instructions import Color -from kivy.graphics.vertex_instructions import Rectangle -from kivy.lang import Builder -from kivy.properties import BooleanProperty, ListProperty, ObjectProperty -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.image import AsyncImage -from kivy.uix.widget import Widget - -Builder.load_string( - """ - - canvas.before: - StencilPush - RoundedRectangle: - size: self.size - pos: self.pos - radius: root.radius - StencilUse - - canvas.after: - StencilUnUse - RoundedRectangle: - size: self.size - pos: self.pos - radius: root.radius - StencilPop -""" -) - - -class FitImage(BoxLayout): - source = ObjectProperty() - container = ObjectProperty() - radius = ListProperty([0, 0, 0, 0]) - mipmap = BooleanProperty(False) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self._late_init) - - def _late_init(self, *args): - self.container = Container(self.source, self.mipmap) - self.bind(source=self.container.setter("source")) - self.add_widget(self.container) - - def reload(self): - self.container.image.reload() - - -class Container(Widget): - source = ObjectProperty() - image = ObjectProperty() - - def __init__(self, source, mipmap, **kwargs): - super().__init__(**kwargs) - self.image = AsyncImage(mipmap=mipmap) - self.image.bind(on_load=self.adjust_size) - self.source = source - self.bind(size=self.adjust_size, pos=self.adjust_size) - - def on_source(self, instance, value): - if isinstance(value, str): - self.image.source = value - else: - self.image.texture = value - self.adjust_size() - - def adjust_size(self, *args): - if not self.parent or not self.image.texture: - return - - (par_x, par_y) = self.parent.size - - if par_x == 0 or par_y == 0: - with self.canvas: - self.canvas.clear() - return - - par_scale = par_x / par_y - (img_x, img_y) = self.image.texture.size - img_scale = img_x / img_y - - if par_scale > img_scale: - (img_x_new, img_y_new) = (img_x, img_x / par_scale) - else: - (img_x_new, img_y_new) = (img_y * par_scale, img_y) - - crop_pos_x = (img_x - img_x_new) / 2 - crop_pos_y = (img_y - img_y_new) / 2 - - subtexture = self.image.texture.get_region( - crop_pos_x, crop_pos_y, img_x_new, img_y_new - ) - - with self.canvas: - self.canvas.clear() - Color(1, 1, 1) - Rectangle(texture=subtexture, pos=self.pos, size=(par_x, par_y)) diff --git a/kivymd/utils/fpsmonitor.py b/kivymd/utils/fpsmonitor.py deleted file mode 100644 index 2284ba8..0000000 --- a/kivymd/utils/fpsmonitor.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Monitor module -============== - -The Monitor module is a toolbar that shows the activity of your current -application : - -* FPS - -""" - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.properties import NumericProperty, StringProperty -from kivy.uix.label import Label - -Builder.load_string( - """ -: - size_hint_y: None - height: self.texture_size[1] - text: root._fsp_value - pos_hint: {"top": 1} - - canvas.before: - Color: - rgba: app.theme_cls.primary_dark - Rectangle: - pos: self.pos - size: self.size -""" -) - - -class FpsMonitor(Label): - updated_interval = NumericProperty(0.5) - """FPS refresh rate.""" - - _fsp_value = StringProperty() - - def start(self): - Clock.schedule_interval(self.update_fps, self.updated_interval) - - def update_fps(self, *args): - self._fsp_value = "FPS: %f" % Clock.get_fps() diff --git a/kivymd/utils/hot_reload_viewer.py b/kivymd/utils/hot_reload_viewer.py deleted file mode 100644 index 4538031..0000000 --- a/kivymd/utils/hot_reload_viewer.py +++ /dev/null @@ -1,222 +0,0 @@ -""" -HotReloadViewer -=============== - -.. Note:: The :class:`~HotReloadViewer` class is based on - the `KvViewerApp `_ class - -:class:`~HotReloadViewer`, for KV-Viewer, is a simple tool allowing you to -dynamically display a KV file, taking its changes into account -(thanks to watchdog). The idea is to facilitate design using the KV language. - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - #:import KivyLexer kivy.extras.highlight.KivyLexer - #:import HotReloadViewer kivymd.utils.hot_reload_viewer.HotReloadViewer - - - BoxLayout: - - CodeInput: - lexer: KivyLexer() - style_name: "native" - on_text: app.update_kv_file(self.text) - size_hint_x: .7 - - HotReloadViewer: - size_hint_x: .3 - path: app.path_to_kv_file - errors: True - errors_text_color: 1, 1, 0, 1 - errors_background_color: app.theme_cls.bg_dark - ''' - - - class Example(MDApp): - path_to_kv_file = "kv_file.kv" - - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - def update_kv_file(self, text): - with open(self.path_to_kv_file, "w") as kv_file: - kv_file.write(text) - - - Example().run() - -This will display the test.kv and automatically update the display when the -file changes. - -.. raw:: html - -
- -
- - -.. rubric:: This scripts uses watchdog to listen for file changes. To install - watchdog. - -.. code-block:: bash - - pip install watchdog -""" - -import os - -from kivy.clock import Clock, mainthread -from kivy.lang import Builder -from kivy.properties import BooleanProperty, ColorProperty, StringProperty -from kivy.uix.scrollview import ScrollView -from watchdog.events import FileSystemEventHandler -from watchdog.observers import Observer - -from kivymd.theming import ThemableBehavior -from kivymd.uix.boxlayout import MDBoxLayout - -Builder.load_string( - """ - - - MDLabel: - size_hint_y: None - height: self.texture_size[1] - theme_text_color: "Custom" - text_color: - root.errors_text_color if root.errors_text_color \ - else root.theme_cls.text_color - text: root.text -""" -) - - -class HotReloadErrorText(ThemableBehavior, ScrollView): - text = StringProperty() - """Text errors. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - errors_text_color = ColorProperty(None) - """ - Error text color. - - :attr:`errors_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - -class HotReloadHandler(FileSystemEventHandler): - def __init__(self, callback, target, **kwargs): - super().__init__(**kwargs) - self.callback = callback - self.target = target - - def on_any_event(self, event): - self.callback() - - -class HotReloadViewer(ThemableBehavior, MDBoxLayout): - """ - :Events: - :attr:`on_error` - Called when an error occurs in the KV-file that the user is editing. - """ - - path = StringProperty() - """Path to KV file. - - :attr:`path` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - errors = BooleanProperty(False) - """ - Show errors while editing KV-file. - - :attr:`errors` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - errors_background_color = ColorProperty(None) - """ - Error background color. - - :attr:`errors_background_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - errors_text_color = ColorProperty(None) - """ - Error text color. - - :attr:`errors_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _temp_widget = None - - def __init__(self, **kwargs): - self.observer = Observer() - self.error_text = HotReloadErrorText() - super().__init__(**kwargs) - self.register_event_type("on_error") - - @mainthread - def update(self, *args): - """Updates and displays the KV-file that the user edits.""" - - Builder.unload_file(self.path) - self.clear_widgets() - try: - self.padding = (0, 0, 0, 0) - self.md_bg_color = (0, 0, 0, 0) - self._temp_widget = Builder.load_file(self.path) - self.add_widget(self._temp_widget) - except Exception as error: - self.show_error(error) - self.dispatch("on_error", error) - - def show_error(self, error): - """Displays text with a current error.""" - - if self._temp_widget and not self.errors: - self.add_widget(self._temp_widget) - return - else: - if self.errors_background_color: - self.md_bg_color = self.errors_background_color - self.padding = ("4dp", "4dp", "4dp", "4dp") - self.error_text.text = ( - error.message - if getattr(error, r"message", None) - else str(error) - ) - self.add_widget(self.error_text) - - def on_error(self, *args): - """ - Called when an error occurs in the KV-file that the user is editing. - """ - - def on_errors_text_color(self, instance, value): - self.error_text.errors_text_color = value - - def on_path(self, instance, value): - value = os.path.abspath(value) - self.observer.schedule( - HotReloadHandler(self.update, value), os.path.dirname(value) - ) - self.observer.start() - Clock.schedule_once(self.update, 1) diff --git a/main.py b/main.py index ffcfba0..d082ac7 100644 --- a/main.py +++ b/main.py @@ -20,8 +20,10 @@ from kivy.logger import Logger from fonts import definitions from kivy.lang import Builder -from kivy.uix.screenmanager import ScreenManager, FadeTransition +from kivy.uix.screenmanager import ScreenManager, FadeTransition, CardTransition, NoTransition from kivy.uix.modalview import ModalView +from kivy.clock import mainthread +from widgets import textfield import json from kivymd.app import MDApp from kivy.core.window import Window @@ -39,9 +41,11 @@ class AccountSafe(MDApp): + __version__ = "1.1" name = "Account Safe" title = "Account Safe" theme = StringProperty("Light") + transition = StringProperty("Fade") text_color = ColorProperty([0, 0, 0, 1]) bg_color = ColorProperty([0.98, 0.98, 0.98, 1]) bg_color_2 = ColorProperty([0.92, 0.92, 0.92, 1]) @@ -56,6 +60,12 @@ class AccountSafe(MDApp): MAX_ACCOUNTS_PER_USER = NumericProperty(20) MAX_PASSWORDS_PER_ACCOUNT = NumericProperty(10) + transitions = { + "Fade": FadeTransition, + "Card": CardTransition, + "None": NoTransition, + } + def open_settings(self, *args): self.switch_screen("settings") @@ -69,6 +79,7 @@ def build(self): from screens.accounts import accounts from screens.passwords import passwords from screens.settings import settings + from screens.about import about from sqloperator import sqloperator from modules import encryption @@ -85,12 +96,14 @@ def build(self): self.accounts = accounts.Accounts() self.passwords = passwords.Passwords() self.settings = settings.Settings() + self.about = about.About() self.screens = { "login": self.login, "dashboard": self.dashboard, "accounts": self.accounts, "passwords": self.passwords, "settings": self.settings, + "about": self.about, } self.screen_history = [] Window.bind(on_key_up=self.back_button) @@ -102,12 +115,24 @@ def build(self): if platform != "android": Window.show() - def start_loading(self): - Window.unbind(on_key_up=self.back_button) + @mainthread + def start_loading(self, *to_unbind): + if to_unbind: + if to_unbind[0]: + for func in to_unbind: + Window.unbind(on_key_up=func) + else: + Window.unbind(on_key_up=self.back_button) self.loader.open() - def stop_loading(self): - Window.bind(on_key_up=self.back_button) + @mainthread + def stop_loading(self, *to_bind): + if to_bind: + if to_bind[0]: + for func in to_bind: + Window.bind(on_key_up=func) + else: + Window.bind(on_key_up=self.back_button) self.loader.dismiss() def on_dropfile(self, window, file_path): @@ -118,6 +143,8 @@ def on_dropfile(self, window, file_path): return def switch_screen(self, screen_name): + self.root.transition.mode = "push" + self.root.transition.direction = "left" self.root.switch_to(self.screens.get(screen_name)) self.screen_history.append(screen_name) @@ -125,6 +152,8 @@ def back_button(self, instance, keyboard, *args): if keyboard in (1001, 27): self.screen_history.pop() if self.screen_history != []: + self.root.transition.mode = "pop" + self.root.transition.direction = "right" self.root.switch_to(self.screens.get(self.screen_history[-1])) else: self.stop() @@ -134,6 +163,9 @@ def on_pause(self): self.set_theme() return True + def on_resume(self): + Window.update_viewport() + def on_stop(self): self.set_theme() @@ -142,8 +174,9 @@ def get_theme(self): try: with open(self.app_settings, 'r') as fp: config = json.load(fp) - self.theme = config.get("theme") - self.theme_cls.primary_palette = config.get("color") + self.theme = config.get("theme", "Light") + self.theme_cls.primary_palette = config.get("color", "Purple") + self.transition = config.get("transition", "Fade") except BaseException as e: self.logger.error(e) self.set_theme() @@ -152,15 +185,23 @@ def get_theme(self): self.set_theme() self.theme_cls.primary_palette = "Purple" self.settings.change_theme(self.theme) + self.change_transition(self.transition) def set_theme(self): try: with open(self.app_settings, 'w+') as fp: json.dump( - {"theme": self.theme, "color": self.theme_cls.primary_palette}, fp) + {"theme": self.theme, + "color": self.theme_cls.primary_palette, + "transition": self.transition}, + fp) except BaseException as e: self.logger.error(e) + def change_transition(self, transition): + self.transition = transition + self.root.transition = self.transitions.get(transition, FadeTransition)() + def has_storage_perms(self): if platform == "android": if not check_permission('android.permission.WRITE_EXTERNAL_STORAGE') or not check_permission('android.permission.READ_EXTERNAL_STORAGE'): @@ -194,7 +235,8 @@ class Loading(ModalView): Builder.load_string(loading) -resource_add_path(AccountSafe.resource_path(os.path.join('screens', 'login'))) +resource_add_path(AccountSafe.resource_path( + os.path.join('screens', 'login'))) resource_add_path(AccountSafe.resource_path( os.path.join('screens', 'dashboard'))) resource_add_path(AccountSafe.resource_path( @@ -203,6 +245,8 @@ class Loading(ModalView): os.path.join('screens', 'passwords'))) resource_add_path(AccountSafe.resource_path( os.path.join('screens', 'settings'))) +resource_add_path(AccountSafe.resource_path( + os.path.join('screens', 'about'))) definitions.add_fonts(AccountSafe.resource_path) diff --git a/requirements.txt b/requirements.txt index 2aa5ac0..2d1336f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ kivy==2.0.0 +https://github.com/kivymd/kivymd/archive/8f83652a5c6eb4e03eb123458a4dd4a9d8730612.zip cryptography==3.4.7 password-strength==0.0.3.post2 Pillow==8.2.0 diff --git a/screens/about/about.kv b/screens/about/about.kv new file mode 100644 index 0000000..bc86a78 --- /dev/null +++ b/screens/about/about.kv @@ -0,0 +1,73 @@ +: + md_bg_color: app.bg_color + canvas.after: + Color: + rgba: 0, 0, 0, 0.3 + Rectangle: + size: dp(3), self.height + pos: self.x-dp(3), self.y + + AnchorLayout: + + MDBoxLayout: + orientation: 'vertical' + adaptive_height: True + + MDBoxLayout: + orientation: 'vertical' + adaptive_height: True + + MDLabel: + text: "Account Safe" + size_hint_y: None + height: self.texture_size[1] + font_name: "Poppins" + font_style: "H5" + halign: "center" + + MDLabel: + text: f"Version {app.__version__}" + size_hint_y: None + height: self.texture_size[1] + font_name: "Poppins" + font_style: "Subtitle1" + theme_text_color: "Secondary" + halign: "center" + + MDBoxLayout: + orientation: 'vertical' + adaptive_height: True + + AnchorLayout: + size_hint_y: None + height: logo.height + + Image: + id: logo + size_hint: None, None + height: dp(120) + source: app.resource_path(os.path.join("res", "icon.png")) + + MDLabel: + text: "GNU General Public License v3.0" + size_hint_y: None + height: self.texture_size[1] + font_name: "Poppins" + font_style: "Subtitle1" + theme_text_color: "Secondary" + halign: "center" + + AnchorLayout: + size_hint_y: None + height: source_code.height + dp(40) + padding: 0, dp(20) + + MDTextButton: + id: source_code + markup: True + size_hint: None, None + text: "[u][color=#4d91ff]SOURCE CODE[/color][/u]" + font_name: "Poppins" + font_style: "Subtitle1" + on_release: + app.settings.source_code() diff --git a/screens/about/about.py b/screens/about/about.py new file mode 100644 index 0000000..7fd527b --- /dev/null +++ b/screens/about/about.py @@ -0,0 +1,12 @@ +from kivymd.uix.screen import MDScreen +from kivy.lang import Builder +from kivymd.app import MDApp +app = MDApp.get_running_app() + + +class About(MDScreen): + def on_enter(self): + app.logger.info('App: SCREEN: About') + + +Builder.load_file('about.kv') diff --git a/screens/accounts/accounts.kv b/screens/accounts/accounts.kv index 77e5c8d..7d56720 100644 --- a/screens/accounts/accounts.kv +++ b/screens/accounts/accounts.kv @@ -2,6 +2,12 @@ : name: 'accounts' + canvas.after: + Color: + rgba: 0, 0, 0, 0.3 + Rectangle: + size: dp(3), self.height + pos: self.x-dp(3), self.y MDBoxLayout: orientation: 'vertical' @@ -121,7 +127,7 @@ size_hint_y: None height: self.texture_size[1] - MDTextField: + TextField: id: name hint_text: "Category" helper_text: "" diff --git a/screens/dashboard/dashboard.kv b/screens/dashboard/dashboard.kv index 31da6bb..3c19b8a 100644 --- a/screens/dashboard/dashboard.kv +++ b/screens/dashboard/dashboard.kv @@ -1,8 +1,11 @@ -#:import Snackbar kivymd.uix.snackbar.Snackbar -#:import Window kivy.core.window.Window - : name: 'dashboard' + canvas.after: + Color: + rgba: 0, 0, 0, 0.3 + Rectangle: + size: dp(3), self.height + pos: self.x-dp(3), self.y MDBoxLayout: orientation: 'vertical' diff --git a/screens/dashboard/dashboard.py b/screens/dashboard/dashboard.py index 6dfc84a..d0ab438 100644 --- a/screens/dashboard/dashboard.py +++ b/screens/dashboard/dashboard.py @@ -32,7 +32,8 @@ class Dashboard(MDScreen): snackbar_y="10dp", size_hint_x=( Window.width - (dp(10) * 2) - ) / Window.width + ) / Window.width, + duration=0.6 ) def on_pre_enter(self): diff --git a/screens/login/login.kv b/screens/login/login.kv index f60782b..54f1043 100644 --- a/screens/login/login.kv +++ b/screens/login/login.kv @@ -1,4 +1,5 @@ -#: import ew kivy.uix.effectwidget +#:import Window kivy.core.window.Window +#:import os os : name: 'login' @@ -84,7 +85,7 @@ orientation: 'vertical' adaptive_height: True - MDTextField: + TextField: id: name hint_text: "Name" helper_text: "" @@ -95,7 +96,7 @@ shorten: True height: self.minimum_height*1.2 - MDTextField: + TextField: id: password hint_text: "Password" helper_text: "" @@ -124,7 +125,7 @@ orientation: 'horizontal' size_hint_y: None height: self.minimum_height - MDTextField: + TextField: id: password hint_text: "Password" helper_text: "" @@ -164,7 +165,7 @@ orientation: 'horizontal' size_hint_y: None height: self.minimum_height - MDTextField: + TextField: id: password hint_text: "Password" helper_text: "" diff --git a/screens/login/login.py b/screens/login/login.py index 7f3214f..e2d1fc1 100644 --- a/screens/login/login.py +++ b/screens/login/login.py @@ -6,8 +6,9 @@ from kivymd.uix.button import MDFlatButton import secrets from kivy.core.window import Window +from threading import Thread from cryptography.fernet import InvalidToken -from kivy.clock import Clock +from kivy.clock import Clock, mainthread from kivymd.app import MDApp app = MDApp.get_running_app() @@ -67,14 +68,22 @@ def display_users(self): }) def add_button_clicked(self): + try: + if self.tap_target_view.state == "open": + self.tap_target_view.stop() + except BaseException: + pass if len(self.user_list) >= app.MAX_USERS: self.dialog = MDDialog( title="User limit reached", - text=f"You have reached max user limit of {app.MAX_USERS}!", + text=f"You have reached max user limit of " + + str(app.MAX_USERS) + "!", md_bg_color=app.bg_color, buttons=[ MDFlatButton( - text="OK", text_color=app.theme_cls.primary_color, on_release=self.close_dialog + text="OK", + text_color=app.theme_cls.primary_color, + on_release=self.close_dialog ), ], on_dismiss=self.dialog_dismissed, @@ -83,11 +92,6 @@ def add_button_clicked(self): Window.unbind(on_key_up=app.back_button) Window.bind(on_key_up=self.events) return - try: - if self.tap_target_view.state == "open": - self.tap_target_view.stop() - except BaseException: - pass content = AddUserDialogContent() self.dialog = MDDialog( title="Add new user", @@ -147,13 +151,22 @@ def add_user_check(self, *args): self.add_user(name.text.strip(), password.text) def add_user(self, name, password): - salt = secrets.token_bytes(32) - encrypting_key = app.encryption.generate_key(salt, password) - key = app.encryption.encrypt(encrypting_key, 'KrYmZiN') - save_query = "INSERT INTO users (name, key, salt, avatar) VALUES (?, ?, ?, ?)" - app.db.execute_query(save_query, (name, key, salt, - "data/logo/kivy-icon-128.png")) - self.display_users() + app.start_loading() + def threaded(self, name, callback): + salt = secrets.token_bytes(32) + encrypting_key = app.encryption.generate_key(salt, password) + key = app.encryption.encrypt(encrypting_key, 'KrYmZiN') + callback(self, name, key, salt) + @mainthread + def save(self, name, key, salt): + save_query = "INSERT INTO users (name, key, salt, avatar) VALUES (?, ?, ?, ?)" + app.db.execute_query(save_query, (name, key, salt, + "data/logo/kivy-icon-128.png")) + app.stop_loading() + self.display_users() + thread = Thread(target=threaded, args=(self, name, save)) + thread.daemon = True + thread.start() def close_dialog(self, *args): self.dialog_dismissable = True @@ -231,30 +244,39 @@ def login_user(self, user_list_item): Window.unbind(on_key_up=app.back_button) Window.bind(on_key_up=self.events) - def password_check(self, user_list_item): + def password_check(self, user_list_item, callback): password = self.dialog.content_cls.ids.password salt = user_list_item.salt - encrypting_key = app.encryption.generate_key(salt, password.text) - try: - decrypted = app.encryption.decrypt( - encrypting_key, user_list_item.key) - if decrypted == 'KrYmZiN': - return True - except InvalidToken: - pass - return False + key = user_list_item.key + app.start_loading(self.events) + def threaded_check(self): + encrypting_key = app.encryption.generate_key(salt, password.text) + try: + decrypted = app.encryption.decrypt( + encrypting_key, key) + if decrypted == 'KrYmZiN': + callback(self, True) + return + except InvalidToken: + pass + callback(self, False) + thread = Thread(target=threaded_check, args=(self,)) + thread.daemon = True + thread.start() def login_user_check(self, user_list_item): - app.start_loading() password = self.dialog.content_cls.ids.password password.helper_text = "" - if self.password_check(user_list_item): - user_list_item.password = password.text - app.stop_loading() - self.user_logged_in(user_list_item) - return - password.helper_text = "Invalid Password." - app.stop_loading() + @mainthread + def callback(self, result): + if result: + app.stop_loading(None) + user_list_item.password = password.text + self.user_logged_in(user_list_item) + return + app.stop_loading(self.events) + password.helper_text = "Invalid Password." + self.password_check(user_list_item, callback) def user_logged_in(self, user_list_item): self.close_dialog() @@ -286,15 +308,22 @@ def confirm_delete_user(self, user_list_item): def delete_user(self, user_list_item): password = self.dialog.content_cls.ids.password + if password.text == "": + password.helper_text = "Cannot be empty" + return password.helper_text = "" - if not self.password_check(user_list_item): + @mainthread + def callback(self, result): + if result: + app.stop_loading(None) + self.close_dialog() + delete_query = "DELETE FROM users WHERE id = ?" + app.db.execute_query(delete_query, (user_list_item.id,)) + self.display_users() + return + app.stop_loading(self.events) password.helper_text = "Invalid Password." - return - self.close_dialog() - delete_query = "DELETE FROM users WHERE id = ?" - app.db.execute_query(delete_query, (user_list_item.id,)) - self.display_users() - + self.password_check(user_list_item, callback) class AddUserDialogContent(BoxLayout): pass diff --git a/screens/passwords/passwords.kv b/screens/passwords/passwords.kv index f8a6035..6969eb6 100644 --- a/screens/passwords/passwords.kv +++ b/screens/passwords/passwords.kv @@ -2,6 +2,12 @@ : name: 'passwords' + canvas.after: + Color: + rgba: 0, 0, 0, 0.3 + Rectangle: + size: dp(3), self.height + pos: self.x-dp(3), self.y MDBoxLayout: orientation: 'vertical' @@ -155,14 +161,14 @@ size_hint_y: None height: dp(150) - MDTextField: + TextField: id: username hint_text: "Username" helper_text: "" helper_text_mode: "persistent" helper_text_color: .9, 0, 0, 1 - MDTextField: + TextField: id: password hint_text: "Password" helper_text: "" diff --git a/screens/passwords/passwords.py b/screens/passwords/passwords.py index ec67c73..d4eba8c 100644 --- a/screens/passwords/passwords.py +++ b/screens/passwords/passwords.py @@ -1,12 +1,14 @@ from kivymd.uix.screen import MDScreen from kivy.lang import Builder -from kivy.clock import Clock +from kivy.clock import Clock, mainthread from kivy.uix.boxlayout import BoxLayout from kivymd.uix.dialog import MDDialog from kivymd.uix.button import MDFlatButton from kivy.core.window import Window from kivy.core.clipboard import Clipboard from password_strength import PasswordStats +from kivymd.uix.taptargetview import MDTapTargetView +from threading import Thread import secrets import string from kivymd.app import MDApp @@ -27,6 +29,11 @@ def on_pre_enter(self): def on_leave(self): self.ids.rv.data = {} self.ids.rv.scroll_y = 1 + try: + if self.tap_target_view.state == "open": + self.tap_target_view.stop() + except BaseException: + pass def on_enter(self): app.logger.info('App: SCREEN: Passwords') @@ -40,6 +47,16 @@ def display_passwords(self): self.password_list = app.db.execute_read_query( select_query, (self.current_account.id,)) if not self.password_list: + self.tap_target_view = MDTapTargetView( + widget=self.ids.add_button_cover, + title_text="No passwords stored", + description_text="Click on this button to add one!", + widget_position="right_bottom", + stop_on_target_touch=False, + target_circle_color=app.bg_color[:3] + ) + if self.tap_target_view.state == "close": + self.tap_target_view.start() return passwords = [] for i in self.password_list: @@ -65,6 +82,11 @@ def display_passwords(self): }) def add_button_clicked(self): + try: + if self.tap_target_view.state == "open": + self.tap_target_view.stop() + except BaseException: + pass if len(self.password_list) >= app.MAX_PASSWORDS_PER_ACCOUNT: self.dialog = MDDialog( title="Account limit reached", @@ -143,16 +165,25 @@ def add_password_check(self, *args): self.add_password(username.text.strip(), password.text) def add_password(self, username, password): + app.start_loading() strength = self.dialog.content_cls.text color = self.dialog.content_cls.color salt = app.dashboard.current_user.salt user_password = app.dashboard.current_user.password - encrypting_key = app.encryption.generate_key(salt, user_password) - password_encrypted = app.encryption.encrypt(encrypting_key, password) - save_query = "INSERT INTO passwords (user, account, username, password, strength, color) VALUES (?, ?, ?, ?, ?, ?)" - app.db.execute_query(save_query, (app.dashboard.current_user.id, - self.current_account.id, username, password_encrypted, strength, color)) - self.display_passwords() + def threaded(self, username, password, strength, color, salt, user_password, callback): + encrypting_key = app.encryption.generate_key(salt, user_password) + password_encrypted = app.encryption.encrypt(encrypting_key, password) + callback(self, username, password_encrypted, strength, color) + @mainthread + def save(self, username, password_encrypted, strength, color): + save_query = "INSERT INTO passwords (user, account, username, password, strength, color) VALUES (?, ?, ?, ?, ?, ?)" + app.db.execute_query(save_query, (app.dashboard.current_user.id, + self.current_account.id, username, password_encrypted, strength, color)) + app.stop_loading() + self.display_passwords() + thread = Thread(target=threaded, args=(self, username, password, strength, color, salt, user_password, save)) + thread.daemon = True + thread.start() def password_touch_down(self, password_item): Clock.unschedule(self.selection_timer) @@ -199,36 +230,43 @@ def open_password(self, password_item): app.start_loading() salt = app.dashboard.current_user.salt user_password = app.dashboard.current_user.password - encrypting_key = app.encryption.generate_key(salt, user_password) - password_decrypted = app.encryption.decrypt( - encrypting_key, password_item.password) content = ShowPasswordDialogContent() content.username = password_item.username - content.password = password_decrypted - show_button = MDFlatButton( - text="SHOW", text_color=app.theme_cls.primary_color) - show_button.bind(on_release=lambda x: content.show_pass(show_button)) - self.dialog = MDDialog( - title="Password", - md_bg_color=app.bg_color, - type="custom", - content_cls=content, - buttons=[ - MDFlatButton( - text="CLOSE", text_color=app.theme_cls.primary_color, on_release=self.close_dialog - ), - MDFlatButton( - text="COPY", text_color=app.theme_cls.primary_color, on_release=lambda x: Clipboard.copy(password_decrypted) - ), - show_button, - ], - on_dismiss=self.dialog_dismissed, - ) - content.start(show_button) - app.stop_loading() - self.dialog.open() - Window.unbind(on_key_up=app.back_button) - Window.bind(on_key_up=self.events) + def threaded(self, callback): + encrypting_key = app.encryption.generate_key(salt, user_password) + password_decrypted = app.encryption.decrypt( + encrypting_key, password_item.password) + callback(self, password_decrypted) + @mainthread + def callback_show(self, password_decrypted): + content.password = password_decrypted + show_button = MDFlatButton( + text="SHOW", text_color=app.theme_cls.primary_color) + show_button.bind(on_release=lambda x: content.show_pass(show_button)) + self.dialog = MDDialog( + title="Password", + md_bg_color=app.bg_color, + type="custom", + content_cls=content, + buttons=[ + MDFlatButton( + text="CLOSE", text_color=app.theme_cls.primary_color, on_release=self.close_dialog + ), + MDFlatButton( + text="COPY", text_color=app.theme_cls.primary_color, on_release=lambda x: Clipboard.copy(password_decrypted) + ), + show_button, + ], + on_dismiss=self.dialog_dismissed, + ) + content.start(show_button) + app.stop_loading() + self.dialog.open() + Window.unbind(on_key_up=app.back_button) + Window.bind(on_key_up=self.events) + thread = Thread(target=threaded, args=(self, callback_show)) + thread.daemon + thread.start() def delete_password(self, password_item, button): if not button.theme_text_color == "Custom": diff --git a/screens/settings/settings.kv b/screens/settings/settings.kv index 671ef03..7dbf88a 100644 --- a/screens/settings/settings.kv +++ b/screens/settings/settings.kv @@ -1,5 +1,11 @@ : name: 'settings' + canvas.after: + Color: + rgba: 0, 0, 0, 0.3 + Rectangle: + size: dp(3), self.height + pos: self.x-dp(3), self.y MDBoxLayout: orientation: 'vertical' @@ -11,6 +17,7 @@ elevation: app.elevation ScrollView: + id: sv effect_cls : 'ScrollEffect' MDBoxLayout: @@ -97,6 +104,38 @@ size_hint_y: None height: self.texture_size[1] + MDCard: + size_hint_y: None + height: self.minimum_height + md_bg_color: app.bg_color + ripple_behavior: True + elevation: 0 + padding: dp(20), dp(10), 0, dp(10) + spacing: dp(15) + on_release: root.transition_picker() + + MDIcon: + id: theme_icon + icon: "arrow-left-right" + size_hint: None, None + size: self.texture_size + pos_hint: {'center_y': .5} + + MDBoxLayout: + orientation: 'vertical' + adaptive_height: True + + MDLabel: + text: "Transition" + size_hint_y: None + height: self.texture_size[1] + + MDLabel: + text: app.transition + font_style: 'Caption' + size_hint_y: None + height: self.texture_size[1] + MDSeparator MDBoxLayout: @@ -142,6 +181,78 @@ size_hint_y: None height: self.texture_size[1] + MDSeparator + + MDBoxLayout: + orientation: 'vertical' + adaptive_height: True + padding: 0, dp(20), 0, 0 + spacing: dp(4) + + MDBoxLayout: + adaptive_height: True + padding: dp(20), 0, 0, 0 + + MDLabel: + text: "Help" + font_style: 'Subtitle2' + size_hint_y: None + height: self.texture_size[1] + + MDCard: + size_hint_y: None + height: self.minimum_height + md_bg_color: app.bg_color + ripple_behavior: True + elevation: 0 + padding: dp(20), dp(10), 0, dp(10) + spacing: dp(15) + on_release: root.about_app() + + MDIcon: + id: backup_icon + icon: "information-outline" + size_hint: None, None + size: self.texture_size + pos_hint: {'center_y': .5} + + MDBoxLayout: + orientation: 'vertical' + adaptive_height: True + pos_hint: {'center_y': .5} + + MDLabel: + text: "About" + size_hint_y: None + height: self.texture_size[1] + + MDCard: + size_hint_y: None + height: self.minimum_height + md_bg_color: app.bg_color + ripple_behavior: True + elevation: 0 + padding: dp(20), dp(10), 0, dp(10) + spacing: dp(15) + on_release: root.rate_app() + + MDIcon: + id: backup_icon + icon: "star" + size_hint: None, None + size: self.texture_size + pos_hint: {'center_y': .5} + + MDBoxLayout: + orientation: 'vertical' + adaptive_height: True + pos_hint: {'center_y': .5} + + MDLabel: + text: "Rate" + size_hint_y: None + height: self.texture_size[1] + : size_hint_y: None @@ -161,7 +272,7 @@ CheckBox: id: check_light allow_no_selection: False - group: "check" + group: "theme" size_hint_x: None width: dp(30) on_active: app.settings.selected_theme = 'Light' @@ -184,7 +295,7 @@ CheckBox: id: check_dark allow_no_selection: False - group: "check" + group: "theme" size_hint_x: None width: dp(30) on_active: app.settings.selected_theme = 'Dark' @@ -244,3 +355,78 @@ text: "CLOSE" pos: root.width - self.width - 10, 10 on_release: app.settings.close_dialog() + + +: + size_hint_y: None + height: self.minimum_height + orientation: 'vertical' + + MDCard: + size_hint_y: None + height: self.minimum_height + md_bg_color: app.bg_color + ripple_behavior: True + elevation: 0 + padding: 0, dp(10) + spacing: dp(10) + on_release: check_fade._do_press() + + CheckBox: + id: check_fade + allow_no_selection: False + group: "transition" + size_hint_x: None + width: dp(30) + on_active: app.settings.selected_transition = 'Fade' + + MDLabel: + text: 'Fade' + size_hint_y: None + height: self.texture_size[1] + + MDCard: + size_hint_y: None + height: self.minimum_height + md_bg_color: app.bg_color + ripple_behavior: True + elevation: 0 + padding: 0, dp(10) + spacing: dp(10) + on_release: check_card._do_press() + + CheckBox: + id: check_card + allow_no_selection: False + group: "transition" + size_hint_x: None + width: dp(30) + on_active: app.settings.selected_transition = 'Card' + + MDLabel: + text: 'Card' + size_hint_y: None + height: self.texture_size[1] + + MDCard: + size_hint_y: None + height: self.minimum_height + md_bg_color: app.bg_color + ripple_behavior: True + elevation: 0 + padding: 0, dp(10) + spacing: dp(10) + on_release: check_none._do_press() + + CheckBox: + id: check_none + allow_no_selection: False + group: "transition" + size_hint_x: None + width: dp(30) + on_active: app.settings.selected_transition = 'None' + + MDLabel: + text: 'None' + size_hint_y: None + height: self.texture_size[1] diff --git a/screens/settings/settings.py b/screens/settings/settings.py index 3d2f51e..ab980c5 100644 --- a/screens/settings/settings.py +++ b/screens/settings/settings.py @@ -14,6 +14,8 @@ from kivymd.uix.button import MDIconButton from modules import backups from kivy.utils import platform +import webbrowser +from threading import Thread import traceback import os from kivy.metrics import dp @@ -31,12 +33,16 @@ class Settings(MDScreen): snackbar_y="10dp", size_hint_x=( Window.width - (dp(10) * 2) - ) / Window.width + ) / Window.width, + duration=0.6 ) def on_enter(self): app.logger.info('App: SCREEN: Settings') + def on_leave(self): + self.ids.sv.scroll_y = 1 + def theme_picker(self): content = ThemeConfirmContent() if app.theme == 'Dark': @@ -66,17 +72,62 @@ def theme_picker(self): Window.bind(on_key_up=self.events) def color_picker(self): + @mainthread + def open_color_dialog(self): + self.color_dialog.open() + Window.unbind(on_key_up=app.back_button) + Window.bind(on_key_up=self.events) if not self.color_dialog: - self.color_dialog = PrimaryColorPicker() - self.color_dialog.bind(on_dismiss=self.dialog_dismissed) - self.color_dialog.open() - Window.unbind(on_key_up=app.back_button) - Window.bind(on_key_up=self.events) + app.start_loading() + def make_color_dialog(self): + self.color_dialog = PrimaryColorPicker() + self.color_dialog.bind(on_dismiss=self.dialog_dismissed) + app.stop_loading() + open_color_dialog(self) + thread = Thread(target=make_color_dialog, args=(self,)) + thread.daemon = True + thread.start() + else: + open_color_dialog(self) def set_theme(self, *args): self.close_dialog() Clock.schedule_once(lambda x: self.change_theme(self.selected_theme)) + def transition_picker(self): + content = TransitionConfirmContent() + if app.transition == 'Fade': + content.ids.check_fade._do_press() + elif app.transition == 'Card': + content.ids.check_card._do_press() + else: + content.ids.check_none._do_press() + self.dialog = MDDialog( + title="Choose transition", + md_bg_color=app.bg_color, + type="custom", + content_cls=content, + buttons=[ + MDFlatButton( + text="CANCEL", text_color=app.theme_cls.primary_color, on_release=self.close_dialog + ), + MDFlatButton( + text="OK", text_color=app.theme_cls.primary_color, on_release=self.set_transition + ), + ], + on_dismiss=self.dialog_dismissed, + ) + for item in self.dialog.items: + if item.text == app.transition: + item.set_icon() + self.dialog.open() + Window.unbind(on_key_up=app.back_button) + Window.bind(on_key_up=self.events) + + def set_transition(self, *args): + self.close_dialog() + Clock.schedule_once(lambda x: app.change_transition(self.selected_transition)) + def change_theme(self, theme): if theme == 'Dark': app.text_color = [1, 1, 1, 1] @@ -197,6 +248,15 @@ def backup_imported(self, result): app.stop_loading() self.snackbar.open() + def about_app(self): + app.switch_screen('about') + + def rate_app(self): + webbrowser.open("https://play.google.com/store/apps/details?id=org.krymzin.locker") + + def source_code(self): + webbrowser.open("https://github.com/Shiv-Patil/Account-Safe") + def dialog_dismissed(self, *args): return True if not self.dialog_dismissable else False @@ -243,4 +303,16 @@ class PrimaryColorPicker(BaseDialog, SpecificBackgroundColorBehavior, FakeRectan pass +class TransitionConfirmContent(BoxLayout): + divider = None + + def set_icon(self): + self.ids.check.active = True + check_list = self.ids.check.get_widgets(self.ids.check.group) + for check in check_list: + if check != self.ids.check: + check.active = False + app.settings.selected_transition = self.text + + Builder.load_file('settings.kv') diff --git a/widgets/recyclegridlayout.py b/widgets/recyclegridlayout.py index 3ea99e0..82a47ab 100644 --- a/widgets/recyclegridlayout.py +++ b/widgets/recyclegridlayout.py @@ -1,5 +1,6 @@ from kivy.uix.recyclegridlayout import RecycleGridLayout + class RecycleGridLayoutFix(RecycleGridLayout): def __init__(self, **kwargs): super(RecycleGridLayoutFix, self).__init__(**kwargs) diff --git a/widgets/textfield.py b/widgets/textfield.py new file mode 100644 index 0000000..3e28200 --- /dev/null +++ b/widgets/textfield.py @@ -0,0 +1,129 @@ +from kivymd.uix.textfield import MDTextField +from kivy.lang import Builder +from kivy.factory import Factory + + +class TextField(MDTextField): + helper_text_color = None + def __init__(self, **kwargs): + super(TextField, self).__init__(**kwargs) + + +kv = """ + + + canvas.before: + Clear + + # Disabled line. + Color: + rgba: + (self.line_color_normal \ + if self.line_color_normal else self.theme_cls.divider_color) \ + if root.mode == "line" else (0, 0, 0, 0) + Line: + points: self.x, self.y + dp(16), self.x + self.width, self.y + dp(16) + width: 1 + dash_length: dp(3) + dash_offset: 2 if self.disabled else 0 + + # Active line. + Color: + rgba: self._current_line_color if root.mode in ("line", "fill") and root.active_line else (0, 0, 0, 0) + Rectangle: + size: self._line_width, dp(2) + pos: self.center_x - (self._line_width / 2), self.y + (dp(16) if root.mode != "fill" else 0) + + # Helper text. + Color: + rgba: self._current_error_color if not self.helper_text_color else self.helper_text_color + Rectangle: + texture: self._msg_lbl.texture + size: + self._msg_lbl.texture_size[0] - (dp(3) if root.mode in ("fill", "rectangle") else 0), \ + self._msg_lbl.texture_size[1] - (dp(3) if root.mode in ("fill", "rectangle") else 0) + pos: self.x + (dp(8) if root.mode == "fill" else 0), self.y + (dp(3) if root.mode in ("fill", "rectangle") else 0) + + # Texture of right Icon. + Color: + rgba: self.icon_right_color if self.focus else self._current_hint_text_color + Rectangle: + texture: self._lbl_icon_right.texture + size: self._lbl_icon_right.texture_size if self.icon_right else (0, 0) + pos: + (self.width + self.x) - (self._lbl_icon_right.texture_size[1]) - dp(8), \ + self.center[1] - self._lbl_icon_right.texture_size[1] / 2 + (dp(8) if root.mode != "fill" else 0) \ + if root.mode != "rectangle" else \ + self.center[1] - self._lbl_icon_right.texture_size[1] / 2 - dp(4) + + Color: + rgba: self._current_right_lbl_color + Rectangle: + texture: self._right_msg_lbl.texture + size: self._right_msg_lbl.texture_size + pos: self.x + self.width - self._right_msg_lbl.texture_size[0] - dp(16), self.y + + Color: + rgba: + (self._current_line_color if self.focus and not \ + self._cursor_blink else (0, 0, 0, 0)) + Rectangle: + pos: (int(x) for x in self.cursor_pos) + size: 1, -self.line_height + + # Hint text. + Color: + rgba: self._current_hint_text_color if not self.current_hint_text_color else self.current_hint_text_color + Rectangle: + texture: self._hint_lbl.texture + size: self._hint_lbl.texture_size + pos: self.x + (dp(8) if root.mode == "fill" else 0), self.y + self.height - self._hint_y + + Color: + rgba: + self.disabled_foreground_color if self.disabled else\ + (self.hint_text_color if not self.text and not\ + self.focus else self.foreground_color) + + # "rectangle" mode + Color: + rgba: + (self._current_line_color if not self.text_color else self.text_color) \ + if self.focus else self._current_hint_text_color + Line: + width: dp(1) if root.mode == "rectangle" else dp(0.00001) + points: + ( + self.x + root._line_blank_space_right_point, self.top - self._hint_lbl.texture_size[1] // 2, + self.right + dp(12), self.top - self._hint_lbl.texture_size[1] // 2, + self.right + dp(12), self.y, + self.x - dp(12), self.y, + self.x - dp(12), self.top - self._hint_lbl.texture_size[1] // 2, + self.x + root._line_blank_space_left_point, self.top - self._hint_lbl.texture_size[1] // 2 + ) + + # "fill" mode. + canvas.after: + Color: + rgba: root._fill_color if root.mode == "fill" else (0, 0, 0, 0) + RoundedRectangle: + pos: self.x, self.y + size: self.width, self.height + dp(8) + radius: root.radius + + font_name: "Roboto" if not root.font_name else root.font_name + foreground_color: self.theme_cls.text_color + bold: False + padding: + 0 if root.mode != "fill" else "8dp", \ + "16dp" if root.mode != "fill" else "24dp", \ + 0 if root.mode != "fill" and not root.icon_right else ("14dp" if not root.icon_right else self._lbl_icon_right.texture_size[1] + dp(20)), \ + "16dp" if root.mode == "fill" else "10dp" + multiline: False + size_hint_y: None + height: self.minimum_height + (dp(8) if root.mode != "fill" else 0) +""" + +Builder.load_string(kv) + +Factory.register("TextField", TextField)