├── kivymd ├── uix │ ├── __init__.py │ ├── behaviors │ │ ├── __init__.py │ │ ├── hover_behavior.py │ │ ├── magic_behavior.py │ │ ├── backgroundcolorbehavior.py │ │ ├── elevation.py │ │ └── ripplebehavior.py │ ├── progressbar.py │ ├── slidingpanel.py │ ├── label.py │ ├── spinner.py │ ├── dropdownitem.py │ ├── stackfloatingbutton.py │ ├── refreshlayout.py │ ├── progressloader.py │ ├── snackbar.py │ ├── expansionpanel.py │ ├── tooltip.py │ ├── chip.py │ ├── managerswiper.py │ ├── navigationdrawer.py │ └── selectioncontrol.py ├── tools │ ├── __init__.py │ ├── release │ │ ├── __init__.py │ │ └── make_release.py │ ├── packaging │ │ ├── __init__.py │ │ └── pyinstaller │ │ │ ├── hook-kivymd.py │ │ │ └── __init__.py │ └── update_icons.py ├── vendor │ ├── __init__.py │ ├── circleLayout │ │ ├── README.md │ │ ├── LICENSE │ │ └── __init__.py │ └── circularTimePicker │ │ ├── README.md │ │ └── LICENSE ├── images │ ├── folder.png │ ├── ios_bg_mod.png │ ├── kivymd_512.png │ ├── ios_entr_ti.png │ ├── kivymd_alpha.png │ ├── kivymd_logo.png │ ├── quad_shadow-0.png │ ├── quad_shadow-1.png │ ├── quad_shadow-2.png │ ├── rec_shadow-0.png │ ├── rec_shadow-1.png │ ├── swipe_shadow.png │ ├── transparent.png │ ├── dialog_in_fade.png │ ├── rec_st_shadow-0.png │ ├── rec_st_shadow-1.png │ ├── rec_st_shadow-2.png │ ├── round_shadow-0.png │ ├── round_shadow-1.png │ ├── round_shadow-2.png │ ├── ios_bg_mod_for_toast.png │ ├── kivy-logo-white-512.png │ ├── rec_shadow.atlas │ ├── quad_shadow.atlas │ ├── round_shadow.atlas │ └── rec_st_shadow.atlas ├── fonts │ ├── Roboto-Bold.ttf │ ├── Roboto-Thin.ttf │ ├── Roboto-Black.ttf │ ├── Roboto-Italic.ttf │ ├── Roboto-Light.ttf │ ├── Roboto-Medium.ttf │ ├── Roboto-Regular.ttf │ ├── Roboto-BlackItalic.ttf │ ├── Roboto-BoldItalic.ttf │ ├── Roboto-LightItalic.ttf │ ├── Roboto-MediumItalic.ttf │ ├── Roboto-ThinItalic.ttf │ └── materialdesignicons-webfont.ttf ├── utils │ ├── __init__.py │ ├── fpsmonitor.py │ ├── asynckivy.py │ ├── fitimage.py │ └── cropimage.py ├── toast │ ├── kivytoast │ │ ├── __init__.py │ │ └── kivytoast.py │ ├── androidtoast │ │ ├── __init__.py │ │ └── androidtoast.py │ ├── __init__.py │ ├── LICENSE │ └── README.md ├── app.py ├── stiffscroll │ ├── README.md │ ├── LICENSE │ └── __init__.py ├── __init__.py ├── material_resources.py ├── font_definitions.py ├── theming_dynamic_text.py └── factory_registers.py ├── README.md ├── requirements.txt ├── cloudfunctions └── detect_handwriting │ ├── requirements.txt │ ├── test.py │ └── main.py ├── .idea └── vcs.xml ├── main.kv └── main.py /kivymd/uix/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kivymd/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kivymd/vendor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kivymd/tools/release/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdf-and-handwriting-reader -------------------------------------------------------------------------------- /kivymd/tools/packaging/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kivymd/tools/packaging/pyinstaller/hook-kivymd.py: -------------------------------------------------------------------------------- 1 | from kivymd.tools.packaging.pyinstaller import * 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Function dependencies, for example: 2 | # package>=version 3 | google-cloud-storage==1.20.0 -------------------------------------------------------------------------------- /kivymd/images/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/folder.png -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /kivymd/images/ios_bg_mod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/ios_bg_mod.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/kivymd_512.png -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /kivymd/images/ios_entr_ti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/ios_entr_ti.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/kivymd_alpha.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/kivymd_logo.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/quad_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/quad_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/quad_shadow-2.png -------------------------------------------------------------------------------- /kivymd/images/rec_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/rec_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/rec_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/rec_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/swipe_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/swipe_shadow.png -------------------------------------------------------------------------------- /kivymd/images/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/transparent.png -------------------------------------------------------------------------------- /kivymd/images/dialog_in_fade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/dialog_in_fade.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/rec_st_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/rec_st_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/rec_st_shadow-2.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/round_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/round_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/round_shadow-2.png -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /kivymd/images/ios_bg_mod_for_toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/ios_bg_mod_for_toast.png -------------------------------------------------------------------------------- /kivymd/images/kivy-logo-white-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/images/kivy-logo-white-512.png -------------------------------------------------------------------------------- /kivymd/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk-Sandberg/pdf-and-handwriting-reader/HEAD/kivymd/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /cloudfunctions/detect_handwriting/requirements.txt: -------------------------------------------------------------------------------- 1 | # Function dependencies, for example: 2 | # package>=version 3 | google-cloud-vision==1.0.0 4 | google-cloud-storage==1.20.0 -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /kivymd/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019 Ivanov Yuri 3 | 4 | For suggestions and questions: 5 | 6 | 7 | This file is distributed under the terms of the same license, 8 | as the Kivy framework. 9 | 10 | """ 11 | -------------------------------------------------------------------------------- /kivymd/toast/kivytoast/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | KivyToast 3 | ========= 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | """ 13 | 14 | from .kivytoast import toast 15 | -------------------------------------------------------------------------------- /kivymd/toast/androidtoast/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AndroidToast 3 | ============ 4 | 5 | Copyright (c) 2013 Brian Knapp 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | """ 13 | 14 | from .androidtoast import toast 15 | -------------------------------------------------------------------------------- /cloudfunctions/detect_handwriting/test.py: -------------------------------------------------------------------------------- 1 | from main import get_pdf_image, perform_cloud_vision 2 | 3 | def test_local(): 4 | local_image = get_pdf_image('test.png', True, '/Users/eriksandberg/Downloads/online-server-test-5be6288e792b.json') 5 | output_text = perform_cloud_vision(local_image, True, '/Users/eriksandberg/Downloads/online-server-test-5be6288e792b.json') 6 | print(output_text) 7 | 8 | if __name__ == "__main__": 9 | test_local() -------------------------------------------------------------------------------- /kivymd/toast/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Toast 3 | ===== 4 | 5 | Copyright (c) 2013 Brian Knapp - androidtoast module 6 | Copyright (c) 2019 Ivanov Yuri - kivytoast module 7 | 8 | For suggestions and questions: 9 | 10 | 11 | This file is distributed under the terms of the same license, 12 | as the Kivy framework. 13 | """ 14 | 15 | from kivy.utils import platform 16 | 17 | 18 | if platform == "android": 19 | from .androidtoast import toast 20 | else: 21 | from .kivytoast import toast 22 | -------------------------------------------------------------------------------- /kivymd/images/rec_shadow.atlas: -------------------------------------------------------------------------------- 1 | {"rec_shadow-1.png": {"20": [2, 266, 256, 128], "21": [260, 266, 256, 128], "22": [518, 266, 256, 128], "23": [776, 266, 256, 128], "3": [260, 136, 256, 128], "2": [2, 136, 256, 128], "5": [776, 136, 256, 128], "4": [518, 136, 256, 128], "7": [260, 6, 256, 128], "6": [2, 6, 256, 128], "9": [776, 6, 256, 128], "8": [518, 6, 256, 128]}, "rec_shadow-0.png": {"11": [518, 266, 256, 128], "10": [260, 266, 256, 128], "13": [2, 136, 256, 128], "12": [776, 266, 256, 128], "15": [518, 136, 256, 128], "14": [260, 136, 256, 128], "17": [2, 6, 256, 128], "16": [776, 136, 256, 128], "19": [518, 6, 256, 128], "18": [260, 6, 256, 128], "1": [776, 6, 256, 128], "0": [2, 266, 256, 128]}} -------------------------------------------------------------------------------- /kivymd/images/quad_shadow.atlas: -------------------------------------------------------------------------------- 1 | {"quad_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "quad_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "quad_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}} -------------------------------------------------------------------------------- /kivymd/images/round_shadow.atlas: -------------------------------------------------------------------------------- 1 | {"round_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "round_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "round_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}} -------------------------------------------------------------------------------- /main.kv: -------------------------------------------------------------------------------- 1 | FloatLayout: 2 | BoxLayout: 3 | orientation: 'vertical' 4 | MDLabel: 5 | id: message_label 6 | halign: 'center' 7 | MDRaisedButton: 8 | text: "Take Image" 9 | pos_hint: {'center_x': .5, 'center_y': .5} 10 | on_release: 11 | app.detect_handwriting() 12 | Widget: 13 | size_hint_y: .1 14 | MDSpinner: 15 | id: spinner 16 | size_hint: .25, .25 17 | pos_hint: {'center_x': .5, 'center_y': .5} 18 | opacity: 0 19 | MDLabel: 20 | id: info_label 21 | size_hint: 1, .1 22 | pos_hint: {'center_x': .5, 'center_y': .25} 23 | halign: 'center' 24 | -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow.atlas: -------------------------------------------------------------------------------- 1 | {"rec_st_shadow-0.png": {"11": [262, 138, 128, 256], "10": [132, 138, 128, 256], "13": [522, 138, 128, 256], "12": [392, 138, 128, 256], "15": [782, 138, 128, 256], "14": [652, 138, 128, 256], "16": [912, 138, 128, 256], "0": [2, 138, 128, 256]}, "rec_st_shadow-1.png": {"20": [522, 138, 128, 256], "21": [652, 138, 128, 256], "17": [2, 138, 128, 256], "23": [912, 138, 128, 256], "19": [262, 138, 128, 256], "18": [132, 138, 128, 256], "22": [782, 138, 128, 256], "1": [392, 138, 128, 256]}, "rec_st_shadow-2.png": {"3": [132, 138, 128, 256], "2": [2, 138, 128, 256], "5": [392, 138, 128, 256], "4": [262, 138, 128, 256], "7": [652, 138, 128, 256], "6": [522, 138, 128, 256], "9": [912, 138, 128, 256], "8": [782, 138, 128, 256]}} -------------------------------------------------------------------------------- /kivymd/vendor/circleLayout/README.md: -------------------------------------------------------------------------------- 1 | CircularLayout 2 | ============== 3 | 4 | CircularLayout is a special layout that places widgets around a circle. 5 | 6 | See the widget's documentation and the example for more information. 7 | 8 | ![Screenshot](screenshot.png) 9 | 10 | size_hint 11 | --------- 12 | 13 | size_hint_x is used as an angle-quota hint (widget with higher 14 | size_hint_x will be farther from each other, and viceversa), while 15 | size_hint_y is used as a widget size hint (widgets with a higher size 16 | hint will be bigger).size_hint_x cannot be None. 17 | 18 | Widgets are all squares, unless you set size_hint_y to None (in that 19 | case you'll be able to specify your own size), and their size is the 20 | difference between the outer and the inner circle's radii. To make the 21 | widgets bigger you can just decrease inner_radius_hint. -------------------------------------------------------------------------------- /kivymd/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | App 3 | === 4 | 5 | This module contains :class:`MDApp` class that is inherited from 6 | :class:`~kivy.app.App`. :class:`MDApp` has some properties needed for KivyMD 7 | library (like :attr:`~MDApp.theme_cls`). 8 | """ 9 | 10 | __all__ = ("MDApp",) 11 | 12 | from kivy.app import App 13 | from kivy.properties import ObjectProperty 14 | from kivymd.theming import ThemeManager 15 | 16 | 17 | class FpsMonitoring: 18 | """Adds a monitor to display the current FPS in the toolbar.""" 19 | 20 | def fps_monitor_start(self): 21 | from kivymd.utils.fpsmonitor import FpsMonitor 22 | from kivy.core.window import Window 23 | 24 | monitor = FpsMonitor() 25 | monitor.start() 26 | Window.add_widget(monitor) 27 | 28 | 29 | class MDApp(App, FpsMonitoring): 30 | theme_cls = ObjectProperty(ThemeManager()) 31 | -------------------------------------------------------------------------------- /kivymd/toast/androidtoast/androidtoast.py: -------------------------------------------------------------------------------- 1 | """ 2 | AndroidToast 3 | ============ 4 | 5 | Copyright (c) 2013 Brian Knapp 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | """ 13 | 14 | from kivy.logger import Logger 15 | from jnius import autoclass, PythonJavaClass, java_method, cast 16 | from android import activity 17 | from android.runnable import run_on_ui_thread 18 | 19 | Toast = autoclass("android.widget.Toast") 20 | context = autoclass("org.kivy.android.PythonActivity").mActivity 21 | 22 | 23 | @run_on_ui_thread 24 | def toast(text, length_long=False): 25 | duration = Toast.LENGTH_LONG if length_long else Toast.LENGTH_SHORT 26 | String = autoclass("java.lang.String") 27 | c = cast("java.lang.CharSequence", String(text)) 28 | t = Toast.makeText(context, c, duration) 29 | t.show() 30 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behaviors 3 | ========= 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from .ripplebehavior import CircularRippleBehavior, RectangularRippleBehavior 18 | from .hover_behavior import HoverBehavior 19 | from .elevation import ( 20 | CommonElevationBehavior, 21 | RectangularElevationBehavior, 22 | CircularElevationBehavior, 23 | RectangularElevationBehavior, 24 | ) 25 | from .backgroundcolorbehavior import ( 26 | BackgroundColorBehavior, 27 | SpecificBackgroundColorBehavior, 28 | ) 29 | from .magic_behavior import MagicBehavior 30 | -------------------------------------------------------------------------------- /kivymd/vendor/circularTimePicker/README.md: -------------------------------------------------------------------------------- 1 | Circular Date & Time Picker for Kivy 2 | ==================================== 3 | 4 | (currently only time, date coming soon) 5 | 6 | Based on [CircularLayout](https://github.com/kivy-garden/garden.circularlayout). 7 | The main aim is to provide a date and time selector similar to the 8 | one found in Android KitKat+. 9 | 10 | ![Screenshot](screenshot.png) 11 | 12 | Simple usage 13 | ------------ 14 | 15 | Import the widget with 16 | 17 | ```python 18 | from kivy.garden.circulardatetimepicker import CircularTimePicker 19 | ``` 20 | 21 | then use it! That's it! 22 | 23 | ```python 24 | c = CircularTimePicker() 25 | c.bind(time=self.set_time) 26 | root.add_widget(c) 27 | ``` 28 | 29 | in Kv language: 30 | 31 | ``` 32 | : 33 | BoxLayout: 34 | orientation: "vertical" 35 | 36 | CircularTimePicker 37 | 38 | Button: 39 | text: "Dismiss" 40 | size_hint_y: None 41 | height: "40dp" 42 | on_release: root.dismiss() 43 | ``` -------------------------------------------------------------------------------- /kivymd/stiffscroll/README.md: -------------------------------------------------------------------------------- 1 | stiffscroll 2 | =========== 3 | 4 | A ScrollEffect for use with a Kivy ScrollView. It makes scrolling more 5 | laborious as you reach the edge of the scrollable area. 6 | 7 | A ScrollView constructed with StiffScrollEffect, 8 | eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to 9 | scroll as you get nearer to its edges. You can scroll all the way to 10 | the edge if you want to, but it will take more finger-movement than 11 | usual. 12 | 13 | Unlike DampedScrollEffect, it is impossible to overscroll with 14 | StiffScrollEffect. That means you cannot push the contents of the 15 | ScrollView far enough to see what's beneath them. This is appropriate 16 | if the ScrollView contains, eg., a background image, like a desktop 17 | wallpaper. Overscrolling may give the impression that there is some 18 | reason to overscroll, even if just to take a peek beneath, and that 19 | impression may be misleading. 20 | 21 | StiffScrollEffect was written by Zachary Spector. His other stuff is at: 22 | https://github.com/LogicalDash/ 23 | He can be reached, and possibly hired, at: 24 | zacharyspector@gmail.com -------------------------------------------------------------------------------- /kivymd/utils/fpsmonitor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Monitor module 3 | ============== 4 | 5 | The Monitor module is a toolbar that shows the activity of your current 6 | application : 7 | 8 | * FPS 9 | 10 | """ 11 | 12 | from kivy.clock import Clock 13 | from kivy.lang import Builder 14 | from kivy.properties import StringProperty, NumericProperty 15 | from kivy.uix.label import Label 16 | 17 | Builder.load_string( 18 | """ 19 | : 20 | size_hint_y: None 21 | height: self.texture_size[1] 22 | text: root._fsp_value 23 | pos_hint: {"top": 1} 24 | 25 | canvas.before: 26 | Color: 27 | rgba: app.theme_cls.primary_dark 28 | Rectangle: 29 | pos: self.pos 30 | size: self.size 31 | """ 32 | ) 33 | 34 | 35 | class FpsMonitor(Label): 36 | updated_interval = NumericProperty(0.5) 37 | """FPS refresh rate.""" 38 | 39 | _fsp_value = StringProperty() 40 | 41 | def start(self): 42 | Clock.schedule_interval(self.update_fps, self.updated_interval) 43 | 44 | def update_fps(self, *args): 45 | self._fsp_value = "FPS: %f" % Clock.get_fps() 46 | -------------------------------------------------------------------------------- /kivymd/stiffscroll/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 LogicalDash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /kivymd/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | KivyMD 3 | ====== 4 | 5 | KivyMD is a collection of Material Design compliant widgets for use with Kivy, 6 | a framework for cross-platform, touch-enabled graphical applications. 7 | The project's goal is to approximate Google's Material Design spec as close 8 | as possible without sacrificing ease of use or application performance. 9 | 10 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 11 | KivyMD library up to version 0.1.2 12 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 13 | KivyMD library version 0.1.3 and higher 14 | 15 | For suggestions and questions: 16 | 17 | 18 | This file is distributed under the terms of the same license, 19 | as the Kivy framework. 20 | """ 21 | 22 | import os 23 | from kivy.logger import Logger 24 | 25 | __version_info__ = (0, 103, 0) 26 | __version__ = "0.103.0" 27 | 28 | path = os.path.dirname(__file__) 29 | fonts_path = os.path.join(path, f"fonts{os.sep}") 30 | images_path = os.path.join(path, f"images{os.sep}") 31 | 32 | Logger.info(f"KivyMD: v{__version__}") 33 | 34 | import kivymd.factory_registers 35 | from kivymd.tools.packaging.pyinstaller import hooks_path 36 | -------------------------------------------------------------------------------- /kivymd/vendor/circleLayout/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Davide Depau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /kivymd/vendor/circularTimePicker/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Davide Depau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /kivymd/toast/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Brian - androidtoast library 4 | Copyright (c) 2019 Ivanov Yuri - kivytoast library 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /kivymd/toast/README.md: -------------------------------------------------------------------------------- 1 | KivyToast 2 | ======== 3 | 4 | A package for working with messages like Toast on Android. It is intended for use in applications written using the Kivy framework. 5 | 6 | This package is an improved version of the package https://github.com/knappador/kivy-toaster in which human toasts are written, written on Kivy. 7 | 8 | 9 | 10 | The package modules are written using the framework for cross-platform development of . 11 | Information about the framework is available at http://kivy.org. 12 | 13 | An example of usage (note that with this import the native implementation of toasts will be used for the Android platform and implementation on Kivy for others: 14 | 15 | ```python 16 | from toast import toast 17 | 18 | ... 19 | 20 | # And then in the code, toasts are available 21 | # by calling the toast function: 22 | toast ('Your message') 23 | ``` 24 | 25 | To force the Kivy implementation on the Android platform, use the import of the form: 26 | 27 | ```python 28 | from toast.kivytoast import toast 29 | ``` 30 | 31 | PROGRAMMING LANGUAGE 32 | ---------------------- 33 | Python 2.7 + 34 | 35 | DEPENDENCE 36 | ----------- 37 | The [Kivy] framework (http://kivy.org/docs/installation/installation.html) 38 | 39 | LICENSE 40 | -------- 41 | MIT -------------------------------------------------------------------------------- /kivymd/tools/packaging/pyinstaller/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyInstaller hooks 3 | ================= 4 | 5 | Add `hookspath=[kivymd.hooks_path]` to your .spec file. 6 | 7 | Example of .spec file 8 | ===================== 9 | 10 | # -*- mode: python ; coding: utf-8 -*- 11 | 12 | import sys 13 | import os 14 | from kivy_deps import sdl2, glew 15 | from kivymd import hooks_path as kivymd_hooks_path 16 | 17 | path = os.path.abspath(".") 18 | 19 | a = Analysis( 20 | ["main.py"], 21 | pathex=[path], 22 | hookspath=[kivymd_hooks_path], 23 | win_no_prefer_redirects=False, 24 | win_private_assemblies=False, 25 | cipher=None, 26 | noarchive=False, 27 | ) 28 | pyz = PYZ(a.pure, a.zipped_data, cipher=None) 29 | 30 | exe = EXE( 31 | pyz, 32 | a.scripts, 33 | a.binaries, 34 | a.zipfiles, 35 | a.datas, 36 | *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)], 37 | debug=False, 38 | strip=False, 39 | upx=True, 40 | name="app_name", 41 | console=True, 42 | ) 43 | """ 44 | 45 | __all__ = ("hooks_path", "datas") 46 | 47 | from os.path import dirname, abspath, join, basename 48 | import kivymd 49 | 50 | hooks_path = dirname(abspath(__file__)) 51 | datas = [ 52 | (kivymd.fonts_path, join("kivymd", basename(dirname(kivymd.fonts_path)))), 53 | (kivymd.images_path, join("kivymd", basename(dirname(kivymd.images_path)))), 54 | ] 55 | hiddenimports = ["PIL"] 56 | -------------------------------------------------------------------------------- /kivymd/material_resources.py: -------------------------------------------------------------------------------- 1 | """ 2 | Material Resources 3 | ================== 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from kivy.utils import platform 18 | from kivy.core.window import Window 19 | from kivy.metrics import dp 20 | 21 | # Feel free to override this const if you're designing for a device such as 22 | # a GNU/Linux tablet. 23 | DEVICE_IOS = platform == "ios" or platform == "macosx" or platform == "android" 24 | if platform != "android" and platform != "ios": 25 | DEVICE_TYPE = "desktop" 26 | elif Window.width >= dp(600) and Window.height >= dp(600): 27 | DEVICE_TYPE = "tablet" 28 | else: 29 | DEVICE_TYPE = "mobile" 30 | 31 | if DEVICE_TYPE == "mobile": 32 | MAX_NAV_DRAWER_WIDTH = dp(300) 33 | HORIZ_MARGINS = dp(16) 34 | STANDARD_INCREMENT = dp(56) 35 | PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT 36 | LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT - dp(8) 37 | else: 38 | MAX_NAV_DRAWER_WIDTH = dp(400) 39 | HORIZ_MARGINS = dp(24) 40 | STANDARD_INCREMENT = dp(64) 41 | PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT 42 | LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT 43 | 44 | TOUCH_TARGET_HEIGHT = dp(48) 45 | -------------------------------------------------------------------------------- /kivymd/utils/asynckivy.py: -------------------------------------------------------------------------------- 1 | """ 2 | asynckivy 3 | ========= 4 | 5 | Copyright (c) 2019 Nattōsai Mitō 6 | 7 | GitHub - 8 | https://github.com/gottadiveintopython 9 | GitHub Gist - 10 | https://gist.github.com/gottadiveintopython/5f4a775849f9277081c396de65dc57c1 11 | 12 | """ 13 | 14 | __all__ = ("start", "sleep", "event") 15 | 16 | import types 17 | from functools import partial 18 | from collections import namedtuple 19 | from kivy.clock import Clock 20 | 21 | CallbackParameter = namedtuple("CallbackParameter", ("args", "kwargs")) 22 | 23 | 24 | def start(coro): 25 | def step(*args, **kwargs): 26 | try: 27 | coro.send(CallbackParameter(args, kwargs))(step) 28 | except StopIteration: 29 | pass 30 | 31 | try: 32 | coro.send(None)(step) 33 | except StopIteration: 34 | pass 35 | 36 | 37 | @types.coroutine 38 | def sleep(duration): 39 | # The partial() here looks meaningless. But this is needed in order 40 | # to avoid weak reference. 41 | param = yield lambda step_coro: Clock.schedule_once( 42 | partial(step_coro), duration 43 | ) 44 | return param.args[0] 45 | 46 | 47 | class event: 48 | def __init__(self, ed, name): 49 | self.bind_id = None 50 | self.ed = ed 51 | self.name = name 52 | 53 | def bind(self, step_coro): 54 | self.bind_id = bind_id = self.ed.fbind(self.name, self.callback) 55 | assert bind_id > 0 # check if binding succeeded 56 | self.step_coro = step_coro 57 | 58 | def callback(self, *args, **kwargs): 59 | self.parameter = CallbackParameter(args, kwargs) 60 | ed = self.ed 61 | ed.unbind_uid(self.name, self.bind_id) 62 | self.step_coro() 63 | 64 | def __await__(self): 65 | yield self.bind 66 | return self.parameter 67 | -------------------------------------------------------------------------------- /kivymd/font_definitions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Font Definitions 3 | ================ 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | 16 | `Material Design spec, The type system `_ 17 | """ 18 | 19 | from kivy.core.text import LabelBase 20 | from kivymd import fonts_path 21 | 22 | fonts = [ 23 | { 24 | "name": "Roboto", 25 | "fn_regular": fonts_path + "Roboto-Regular.ttf", 26 | "fn_bold": fonts_path + "Roboto-Bold.ttf", 27 | "fn_italic": fonts_path + "Roboto-Italic.ttf", 28 | "fn_bolditalic": fonts_path + "Roboto-BoldItalic.ttf", 29 | }, 30 | { 31 | "name": "RobotoThin", 32 | "fn_regular": fonts_path + "Roboto-Thin.ttf", 33 | "fn_italic": fonts_path + "Roboto-ThinItalic.ttf", 34 | }, 35 | { 36 | "name": "RobotoLight", 37 | "fn_regular": fonts_path + "Roboto-Light.ttf", 38 | "fn_italic": fonts_path + "Roboto-LightItalic.ttf", 39 | }, 40 | { 41 | "name": "RobotoMedium", 42 | "fn_regular": fonts_path + "Roboto-Medium.ttf", 43 | "fn_italic": fonts_path + "Roboto-MediumItalic.ttf", 44 | }, 45 | { 46 | "name": "RobotoBlack", 47 | "fn_regular": fonts_path + "Roboto-Black.ttf", 48 | "fn_italic": fonts_path + "Roboto-BlackItalic.ttf", 49 | }, 50 | { 51 | "name": "Icons", 52 | "fn_regular": fonts_path + "materialdesignicons-webfont.ttf", 53 | }, 54 | ] 55 | 56 | for font in fonts: 57 | LabelBase.register(**font) 58 | 59 | theme_font_styles = [ 60 | "H1", 61 | "H2", 62 | "H3", 63 | "H4", 64 | "H5", 65 | "H6", 66 | "Subtitle1", 67 | "Subtitle2", 68 | "Body1", 69 | "Body2", 70 | "Button", 71 | "Caption", 72 | "Overline", 73 | "Icon", 74 | ] 75 | -------------------------------------------------------------------------------- /cloudfunctions/detect_handwriting/main.py: -------------------------------------------------------------------------------- 1 | def get_pdf_image(pdf_image_url, local=False, cred_file=''): 2 | from google.cloud import storage 3 | if local: 4 | output_filename = './temp.png' 5 | else: 6 | # Cloud Function environment only allows write to /tmp folder 7 | output_filename = '/tmp/temp.png' 8 | # If you don't specify credentials when constructing the client, the 9 | # client library will look for credentials in the environment. 10 | if local: 11 | storage_client = storage.Client.from_service_account_json(cred_file) 12 | else: 13 | storage_client = storage.Client() 14 | 15 | # Make an authenticated API request 16 | buckets = list(storage_client.list_buckets()) 17 | bucket = storage_client.get_bucket('oneline-server-test') 18 | blob = bucket.blob(pdf_image_url) 19 | blob.download_to_filename(output_filename) 20 | return output_filename 21 | 22 | def perform_cloud_vision(image_filename, local=False, cred_file=''): 23 | from google.cloud import vision 24 | from google.cloud.vision import types 25 | if local: 26 | client = vision.ImageAnnotatorClient.from_service_account_file(cred_file) 27 | else: 28 | # If you don't specify credentials when constructing the client, the 29 | # client library will look for credentials in the environment. 30 | client = vision.ImageAnnotatorClient() 31 | with open(image_filename, 'rb') as f: 32 | content = f.read() 33 | image = vision.types.Image(content=content) 34 | response = client.document_text_detection(image=image) 35 | doc_text = response.full_text_annotation.text 36 | return doc_text 37 | 38 | 39 | def detect_handwriting(request): 40 | """Responds to any HTTP request. 41 | Args: 42 | request (flask.Request): HTTP request object. 43 | Returns: 44 | The response text or any set of values that can be turned into a 45 | Response object using 46 | `make_response `. 47 | """ 48 | # Data from UrlRequest req_body is sent in the request.form dict 49 | image_url = request.form.get('message', '') 50 | local_image = get_pdf_image(image_url) 51 | output_text = perform_cloud_vision(local_image) 52 | 53 | 54 | return output_text 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /kivymd/utils/fitimage.py: -------------------------------------------------------------------------------- 1 | """ 2 | fitimage.py 3 | 4 | Feature to automatically crop a `Kivy` image to fit your layout 5 | Write by Benedikt Zwölfer 6 | 7 | Referene - https://gist.github.com/benni12er/95a45eb168fc33a4fcd2d545af692dad 8 | 9 | 10 | Example: 11 | ======= 12 | 13 | BoxLayout: 14 | size_hint_y: None 15 | height: dp(200) 16 | orientation: 'vertical' 17 | 18 | FitImage: 19 | size_hint_y: 3 20 | source: 'images/img1.jpg' 21 | 22 | FitImage: 23 | size_hint_y: 1 24 | source: 'images/img2.jpg' 25 | """ 26 | 27 | from kivy.graphics.context_instructions import Color 28 | from kivy.graphics.vertex_instructions import Rectangle 29 | from kivy.properties import StringProperty, Clock 30 | from kivy.uix.boxlayout import BoxLayout 31 | from kivy.uix.image import Image 32 | from kivy.uix.widget import Widget 33 | 34 | 35 | class FitImage(BoxLayout): 36 | source = StringProperty() 37 | 38 | def __init__(self, **kwargs): 39 | super().__init__(**kwargs) 40 | Clock.schedule_once(self._late_init) 41 | 42 | def _late_init(self, *args): 43 | self.container = Container(self.source) 44 | self.add_widget(self.container) 45 | 46 | 47 | class Container(Widget): 48 | def __init__(self, source, **kwargs): 49 | super().__init__(**kwargs) 50 | self.bind(size=self.adjust_size, pos=self.adjust_size) 51 | self.image = Image(source=source) 52 | 53 | def adjust_size(self, *args): 54 | (par_x, par_y) = self.parent.size 55 | 56 | if par_x == 0 or par_y == 0: 57 | with self.canvas: 58 | self.canvas.clear() 59 | return 60 | 61 | par_scale = par_x / par_y 62 | 63 | (img_x, img_y) = self.image.texture.size 64 | img_scale = img_x / img_y 65 | 66 | if par_scale > img_scale: 67 | (img_x_new, img_y_new) = (img_x, img_x / par_scale) 68 | else: 69 | (img_x_new, img_y_new) = (img_y * par_scale, img_y) 70 | 71 | crop_pos_x = (img_x - img_x_new) / 2 72 | crop_pos_y = (img_y - img_y_new) / 2 73 | 74 | subtexture = self.image.texture.get_region( 75 | crop_pos_x, crop_pos_y, img_x_new, img_y_new 76 | ) 77 | 78 | with self.canvas: 79 | self.canvas.clear() 80 | Color(1, 1, 1) 81 | Rectangle(texture=subtexture, pos=self.pos, size=(par_x, par_y)) 82 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/hover_behavior.py: -------------------------------------------------------------------------------- 1 | """Hoverable Behaviour (changing when the mouse is on the widget by O. Poyen. 2 | License: LGPL 3 | """ 4 | __author__ = "Olivier POYEN" 5 | __site__ = "https://gist.github.com/opqopq/15c707dc4cffc2b6455f" 6 | 7 | 8 | from kivy.properties import BooleanProperty, ObjectProperty 9 | from kivy.core.window import Window 10 | 11 | 12 | class HoverBehavior(object): 13 | """Hover behavior. 14 | :Events: 15 | `on_enter` 16 | Fired when mouse enter the bbox of the widget. 17 | `on_leave` 18 | Fired when the mouse exit the widget 19 | """ 20 | 21 | hovered = BooleanProperty(False) 22 | border_point = ObjectProperty(None) 23 | """Contains the last relevant point received by the Hoverable. This can 24 | be used in `on_enter` or `on_leave` in order to know where was dispatched the event. 25 | """ 26 | 27 | def __init__(self, **kwargs): 28 | self.register_event_type("on_enter") 29 | self.register_event_type("on_leave") 30 | Window.bind(mouse_pos=self.on_mouse_pos) 31 | super(HoverBehavior, self).__init__(**kwargs) 32 | 33 | def on_mouse_pos(self, *args): 34 | if not self.get_root_window(): 35 | return # do proceed if I'm not displayed <=> If have no parent 36 | pos = args[1] 37 | # Next line to_widget allow to compensate for relative layout 38 | inside = self.collide_point(*self.to_widget(*pos)) 39 | if self.hovered == inside: 40 | # We have already done what was needed 41 | return 42 | self.border_point = pos 43 | self.hovered = inside 44 | if inside: 45 | self.dispatch("on_enter") 46 | else: 47 | self.dispatch("on_leave") 48 | 49 | def on_enter(self): 50 | pass 51 | 52 | def on_leave(self): 53 | pass 54 | 55 | 56 | from kivy.factory import Factory 57 | 58 | Factory.register("HoverBehavior", HoverBehavior) 59 | 60 | 61 | if __name__ == "__main__": 62 | from kivy.uix.floatlayout import FloatLayout 63 | from kivy.lang import Builder 64 | from kivy.uix.label import Label 65 | from kivy.base import runTouchApp 66 | 67 | class HoverLabel(Label, HoverBehavior): 68 | def on_enter(self, *args): 69 | print("You are in, through this point", self) 70 | 71 | def on_leave(self, *args): 72 | print("You left through this point", self) 73 | 74 | Builder.load_string( 75 | """ 76 | : 77 | text: "inside" if self.hovered else "outside" 78 | pos: 200,200 79 | size_hint: None, None 80 | size: 100, 30 81 | canvas.before: 82 | Color: 83 | rgb: 1,0,0 84 | Rectangle: 85 | size: self.size 86 | pos: self.pos 87 | """ 88 | ) 89 | fl = FloatLayout() 90 | fl.add_widget(HoverLabel()) 91 | runTouchApp(fl) 92 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/magic_behavior.py: -------------------------------------------------------------------------------- 1 | """ 2 | https://gist.github.com/tshirtman/a1065cf74a788434e1162e342b130df8 3 | Write by tshirtman - https://github.com/tshirtman 4 | 5 | Example: 6 | ======= 7 | 8 | from kivymd.app import MDApp 9 | from kivy.lang import Builder 10 | 11 | from kivymd.theming import ThemeManager 12 | 13 | KV = ''' 14 | #:import MagicBehavior kivymd.uix.behaviors.MagicBehavior 15 | 16 | 17 | 18 | 19 | 20 | FloatLayout: 21 | 22 | MDIconMagicButton: 23 | text: "GROW EFFECT" 24 | on_release: self.grow() 25 | pos_hint: {"center_x": .5, "center_y": .5} 26 | ''' 27 | 28 | 29 | class Example(MDApp): 30 | 31 | def build(self): 32 | return Builder.load_string(KV) 33 | 34 | 35 | Example().run() 36 | """ 37 | 38 | from kivy.animation import Animation 39 | from kivy.factory import Factory 40 | from kivy.lang import Builder 41 | 42 | Builder.load_string( 43 | """ 44 | 45 | translate_x: 0 46 | translate_y: 0 47 | scale_x: 1 48 | scale_y: 1 49 | rotate: 0 50 | 51 | canvas.before: 52 | PushMatrix 53 | Translate: 54 | x: self.translate_x or 0 55 | y: self.translate_y or 0 56 | Rotate: 57 | origin: self.center 58 | angle: self.rotate or 0 59 | Scale: 60 | origin: self.center 61 | x: self.scale_x or 1 62 | y: self.scale_y or 1 63 | canvas.after: 64 | PopMatrix 65 | """ 66 | ) 67 | 68 | 69 | class MagicBehavior: 70 | def grow(self): 71 | Animation.stop_all(self) 72 | ( 73 | Animation(scale_x=1.2, scale_y=1.2, t="out_quad", d=0.03) 74 | + Animation(scale_x=1, scale_y=1, t="out_elastic", d=0.4) 75 | ).start(self) 76 | 77 | def shake(self): 78 | Animation.stop_all(self) 79 | ( 80 | Animation(translate_x=50, t="out_quad", d=0.02) 81 | + Animation(translate_x=0, t="out_elastic", d=0.5) 82 | ).start(self) 83 | 84 | def wobble(self): 85 | Animation.stop_all(self) 86 | ( 87 | ( 88 | Animation(scale_y=0.7, t="out_quad", d=0.03) 89 | & Animation(scale_x=1.4, t="out_quad", d=0.03) 90 | ) 91 | + ( 92 | Animation(scale_y=1, t="out_elastic", d=0.5) 93 | & Animation(scale_x=1, t="out_elastic", d=0.4) 94 | ) 95 | ).start(self) 96 | 97 | def twist(self): 98 | Animation.stop_all(self) 99 | ( 100 | Animation(rotate=25, t="out_quad", d=0.05) 101 | + Animation(rotate=0, t="out_elastic", d=0.5) 102 | ).start(self) 103 | 104 | def shrink(self): 105 | Animation.stop_all(self) 106 | Animation(scale_x=0.95, scale_y=0.95, t="out_quad", d=0.1).start(self) 107 | 108 | 109 | Factory.register("MagicBehavior", cls=MagicBehavior) 110 | -------------------------------------------------------------------------------- /kivymd/uix/progressbar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Progress Bar 3 | ============ 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from kivy.lang import Builder 18 | from kivy.properties import OptionProperty, BooleanProperty 19 | 20 | from kivymd.theming import ThemableBehavior 21 | from kivy.uix.progressbar import ProgressBar 22 | 23 | Builder.load_string( 24 | """ 25 | 26 | canvas: 27 | Clear 28 | Color: 29 | rgba: self.theme_cls.divider_color 30 | Rectangle: 31 | size: 32 | (self.width , dp(4)) if self.orientation == 'horizontal'\ 33 | else (dp(4),self.height) 34 | pos: 35 | (self.x, self.center_y - dp(4))\ 36 | if self.orientation == 'horizontal'\ 37 | else (self.center_x - dp(4),self.y) 38 | Color: 39 | rgba: self.theme_cls.primary_color 40 | Rectangle: 41 | size: 42 | (self.width * self.value_normalized, sp(4))\ 43 | if self.orientation == 'horizontal' else (sp(4),\ 44 | self.height*self.value_normalized) 45 | pos: 46 | (self.width*(1 - self.value_normalized) + self.x\ 47 | if self.reversed else self.x, self.center_y - dp(4))\ 48 | if self.orientation == 'horizontal'\ 49 | else (self.center_x - dp(4),self.height\ 50 | * (1 - self.value_normalized) + self.y if self.reversed\ 51 | else self.y) 52 | """ 53 | ) 54 | 55 | 56 | class MDProgressBar(ThemableBehavior, ProgressBar): 57 | reversed = BooleanProperty(False) 58 | """ Reverse the direction the progressbar moves. """ 59 | 60 | orientation = OptionProperty( 61 | "horizontal", options=["horizontal", "vertical"] 62 | ) 63 | """ Orientation of progressbar""" 64 | 65 | 66 | if __name__ == "__main__": 67 | from kivymd.app import MDApp 68 | from kivymd.theming import ThemeManager 69 | 70 | class ProgressBarApp(MDApp): 71 | def build(self): 72 | return Builder.load_string( 73 | """ 74 | BoxLayout: 75 | orientation:'vertical' 76 | padding: '8dp' 77 | MDSlider: 78 | id:slider 79 | min:0 80 | max:100 81 | value: 40 82 | 83 | MDProgressBar: 84 | value: slider.value 85 | MDProgressBar: 86 | reversed: True 87 | value: slider.value 88 | BoxLayout: 89 | MDProgressBar: 90 | orientation:"vertical" 91 | reversed: True 92 | value: slider.value 93 | 94 | MDProgressBar: 95 | orientation:"vertical" 96 | value: slider.value 97 | """ 98 | ) 99 | 100 | ProgressBarApp().run() 101 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/backgroundcolorbehavior.py: -------------------------------------------------------------------------------- 1 | """ 2 | Background Color Behavior 3 | ========================= 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from kivy.lang import Builder 18 | from kivy.properties import BoundedNumericProperty, ReferenceListProperty 19 | from kivy.properties import OptionProperty, ListProperty 20 | from kivy.uix.widget import Widget 21 | from kivy.utils import get_color_from_hex 22 | from kivymd.color_definitions import palette, hue, text_colors 23 | 24 | Builder.load_string( 25 | """ 26 | 27 | canvas: 28 | Color: 29 | rgba: self.md_bg_color 30 | Rectangle: 31 | size: self.size 32 | pos: self.pos 33 | """ 34 | ) 35 | 36 | 37 | class BackgroundColorBehavior(Widget): 38 | r = BoundedNumericProperty(1.0, min=0.0, max=1.0) 39 | g = BoundedNumericProperty(1.0, min=0.0, max=1.0) 40 | b = BoundedNumericProperty(1.0, min=0.0, max=1.0) 41 | a = BoundedNumericProperty(0.0, min=0.0, max=1.0) 42 | 43 | md_bg_color = ReferenceListProperty(r, g, b, a) 44 | 45 | 46 | class SpecificBackgroundColorBehavior(BackgroundColorBehavior): 47 | background_palette = OptionProperty( 48 | "Primary", options=["Primary", "Accent", *palette] 49 | ) 50 | background_hue = OptionProperty("500", options=hue) 51 | 52 | specific_text_color = ListProperty([0, 0, 0, 0.87]) 53 | specific_secondary_text_color = ListProperty([0, 0, 0, 0.87]) 54 | 55 | def _update_specific_text_color(self, instance, value): 56 | if hasattr(self, "theme_cls"): 57 | palette = { 58 | "Primary": self.theme_cls.primary_palette, 59 | "Accent": self.theme_cls.accent_palette, 60 | }.get(self.background_palette, self.background_palette) 61 | else: 62 | palette = {"Primary": "Blue", "Accent": "Amber"}.get( 63 | self.background_palette, self.background_palette 64 | ) 65 | color = get_color_from_hex(text_colors[palette][self.background_hue]) 66 | secondary_color = color[:] 67 | # Check for black text (need to adjust opacity) 68 | if (color[0] + color[1] + color[2]) == 0: 69 | color[3] = 0.87 70 | secondary_color[3] = 0.54 71 | else: 72 | secondary_color[3] = 0.7 73 | self.specific_text_color = color 74 | self.specific_secondary_text_color = secondary_color 75 | 76 | def __init__(self, **kwargs): 77 | super().__init__(**kwargs) 78 | if hasattr(self, "theme_cls"): 79 | self.theme_cls.bind( 80 | primary_palette=self._update_specific_text_color 81 | ) 82 | self.theme_cls.bind(accent_palette=self._update_specific_text_color) 83 | self.theme_cls.bind(theme_style=self._update_specific_text_color) 84 | self.bind(background_hue=self._update_specific_text_color) 85 | self.bind(background_palette=self._update_specific_text_color) 86 | self._update_specific_text_color(None, None) 87 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from kivymd.app import MDApp 2 | from kivy.network.urlrequest import UrlRequest 3 | import certifi 4 | from kivy.clock import Clock, mainthread 5 | import threading 6 | 7 | class MainApp(MDApp): 8 | source_file_name = 'temp.jpg' 9 | 10 | def on_start(self): 11 | self.image_upload_thread = threading.Thread(target=self.upload_image) 12 | 13 | def take_image(self): 14 | from plyer import camera 15 | filepath = self.user_data_dir + '/' + 'temp.png' 16 | try: 17 | camera.take_picture(filename=filepath, 18 | on_complete=self.camera_callback) 19 | except NotImplementedError: 20 | print("Can't take a picture on this platform") 21 | 22 | def camera_callback(self, filepath): 23 | from os.path import exists 24 | if(exists(filepath)): 25 | print("Picture saved!", filepath) 26 | self.source_file_name = filepath 27 | self.root.ids.info_label.text = 'Uploading image...' 28 | self.root.ids.spinner.opacity = 1 29 | self.root.ids.spinner.color = self.theme_cls.primary_color 30 | self.root.do_layout() 31 | self.image_upload_thread.start() 32 | 33 | else: 34 | print("Couldnt save picture") 35 | 36 | 37 | def detect_handwriting(self): 38 | print("Detecting handwriting...") 39 | blob_name = self.select_image() 40 | 41 | def select_image(self): 42 | # Select a file from your device 43 | self.take_image() 44 | # Upload the file 45 | 46 | def upload_image(self, *args): 47 | 48 | from google.cloud import storage 49 | 50 | """Uploads a file to the bucket.""" 51 | bucket_name = 'oneline-server-test' 52 | destination_blob_name = "test-storage-blob.png" 53 | debug = True 54 | if not debug: 55 | storage_client = storage.Client() 56 | else: 57 | cred_file = 'online-server-test-5be6288e792b.json' 58 | storage_client = storage.Client.from_service_account_json(cred_file) 59 | 60 | bucket = storage_client.bucket(bucket_name) 61 | blob = bucket.blob(destination_blob_name) 62 | 63 | blob.upload_from_filename(self.source_file_name) 64 | print( 65 | "File {} uploaded to {}.".format( 66 | self.source_file_name, destination_blob_name 67 | ) 68 | ) 69 | 70 | self.hit_cloud_function(destination_blob_name) 71 | 72 | @mainthread 73 | def hit_cloud_function(self, blob_name): 74 | self.root.ids.spinner.color = self.theme_cls.accent_color 75 | self.root.ids.info_label.text = 'Identifying text...' 76 | from urllib.parse import urlencode 77 | msg_data = urlencode({'message': blob_name}) 78 | headers = {'Content-type': 'application/x-www-form-urlencoded', 79 | 'Accept': 'text/plain'} 80 | 81 | print("Sending trigger request") 82 | trigger_url = "https://us-central1-online-server-test.cloudfunctions.net/detect-handwriting" 83 | UrlRequest(trigger_url, req_body=msg_data, req_headers=headers, ca_file=certifi.where(), 84 | on_failure=self.error, on_error=self.error, on_success=self.success) 85 | 86 | def error(self, *args): 87 | self.root.ids.spinner.opacity = 0 88 | print("Error", args) 89 | 90 | def success(self, request, response): 91 | self.root.ids.info_label.text = '' 92 | self.root.ids.spinner.opacity = 0 93 | print("Success!") 94 | self.root.ids.message_label.text = response 95 | 96 | 97 | MainApp().run() 98 | -------------------------------------------------------------------------------- /kivymd/theming_dynamic_text.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bottom Sheets 3 | ============= 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | 16 | Two implementations. The first is based on color brightness obtained from:- 17 | https://www.w3.org/TR/AERT#color-contrast 18 | The second is based on relative luminance calculation for sRGB obtained from:- 19 | https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef 20 | and contrast ratio calculation obtained from:- 21 | https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef 22 | 23 | Preliminary testing suggests color brightness more closely matches the 24 | Material Design spec suggested text colors, but the alternative implementation 25 | is both newer and the current 'correct' recommendation, so is included here 26 | as an option. 27 | """ 28 | 29 | 30 | def _color_brightness(color): 31 | # Implementation of color brightness method 32 | brightness = color[0] * 299 + color[1] * 587 + color[2] * 114 33 | brightness = brightness 34 | return brightness 35 | 36 | 37 | def _black_or_white_by_color_brightness(color): 38 | if _color_brightness(color) >= 500: 39 | return "black" 40 | else: 41 | return "white" 42 | 43 | 44 | def _normalized_channel(color): 45 | # Implementation of contrast ratio and relative luminance method 46 | if color <= 0.03928: 47 | return color / 12.92 48 | else: 49 | return ((color + 0.055) / 1.055) ** 2.4 50 | 51 | 52 | def _luminance(color): 53 | rg = _normalized_channel(color[0]) 54 | gg = _normalized_channel(color[1]) 55 | bg = _normalized_channel(color[2]) 56 | return 0.2126 * rg + 0.7152 * gg + 0.0722 * bg 57 | 58 | 59 | def _black_or_white_by_contrast_ratio(color): 60 | l_color = _luminance(color) 61 | l_black = 0.0 62 | l_white = 1.0 63 | b_contrast = (l_color + 0.05) / (l_black + 0.05) 64 | w_contrast = (l_white + 0.05) / (l_color + 0.05) 65 | return "white" if w_contrast >= b_contrast else "black" 66 | 67 | 68 | def get_contrast_text_color(color, use_color_brightness=True): 69 | if use_color_brightness: 70 | contrast_color = _black_or_white_by_color_brightness(color) 71 | else: 72 | contrast_color = _black_or_white_by_contrast_ratio(color) 73 | if contrast_color == "white": 74 | return 1, 1, 1, 1 75 | else: 76 | return 0, 0, 0, 1 77 | 78 | 79 | if __name__ == "__main__": 80 | from kivy.utils import get_color_from_hex 81 | from kivymd.color_definitions import colors, text_colors 82 | 83 | for c in colors.items(): 84 | if c[0] in ["Light", "Dark"]: 85 | continue 86 | color = c[0] 87 | print(f"For the {color} color palette:") 88 | for name, hex_color in c[1].items(): 89 | if hex_color: 90 | col = get_color_from_hex(hex_color) 91 | col_bri = get_contrast_text_color(col) 92 | con_rat = get_contrast_text_color( 93 | col, use_color_brightness=False 94 | ) 95 | text_color = text_colors[c[0]][name] 96 | print( 97 | f" The {name} hue gives {col_bri} using color " 98 | f"brightness, {con_rat} using contrast ratio, and " 99 | f"{text_color} from the MD spec" 100 | ) 101 | -------------------------------------------------------------------------------- /kivymd/utils/cropimage.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019 Ivanov Yuri 3 | 4 | For suggestions and questions: 5 | 6 | 7 | This file is distributed under the terms of the same license, 8 | as the Kivy framework. 9 | 10 | """ 11 | 12 | 13 | def crop_image( 14 | cutting_size, 15 | path_to_image, 16 | path_to_save_crop_image, 17 | corner=0, 18 | blur=0, 19 | corner_mode="all", 20 | ): 21 | """Call functions of cropping/blurring/rounding image. 22 | 23 | cutting_size: size to which the image will be cropped; 24 | path_to_image: path to origin image; 25 | path_to_save_crop_image: path to new image; 26 | corner: value of rounding corners; 27 | blur: blur value; 28 | corner_mode: 'all'/'top'/'bottom' - indicates which corners to round out; 29 | 30 | """ 31 | 32 | im = _crop_image(cutting_size, path_to_image, path_to_save_crop_image) 33 | if corner: 34 | im = add_corners(im, corner, corner_mode) 35 | if blur: 36 | im = add_blur(im, blur) 37 | try: 38 | im.save(path_to_save_crop_image) 39 | except IOError: 40 | im.save(path_to_save_crop_image, "JPEG") 41 | 42 | 43 | def add_blur(im, mode): 44 | from PIL import ImageFilter 45 | 46 | im = im.filter(ImageFilter.GaussianBlur(mode)) 47 | 48 | return im 49 | 50 | 51 | def _crop_image(cutting_size, path_to_image, path_to_save_crop_image): 52 | from PIL import Image, ImageOps 53 | 54 | image = Image.open(path_to_image) 55 | image = ImageOps.fit(image, cutting_size) 56 | image.save(path_to_save_crop_image) 57 | 58 | return image 59 | 60 | 61 | def add_corners(im, corner, corner_mode): 62 | def add_top_corners(): 63 | alpha.paste(circle.crop((0, 0, corner, corner)), (0, 0)) 64 | alpha.paste( 65 | circle.crop((corner, 0, corner * 2, corner)), (w - corner, 0) 66 | ) 67 | 68 | def add_bottom_corners(): 69 | alpha.paste( 70 | circle.crop((0, corner, corner, corner * 2)), (0, h - corner) 71 | ) 72 | alpha.paste( 73 | circle.crop((corner, corner, corner * 2, corner * 2)), 74 | (w - corner, h - corner), 75 | ) 76 | 77 | from PIL import Image, ImageDraw 78 | 79 | circle = Image.new("L", (corner * 2, corner * 2), 0) 80 | draw = ImageDraw.Draw(circle) 81 | draw.ellipse((0, 0, corner * 2, corner * 2), fill=255) 82 | alpha = Image.new("L", im.size, 255) 83 | w, h = im.size 84 | 85 | if corner_mode == "all": 86 | add_top_corners() 87 | add_bottom_corners() 88 | elif corner_mode == "top": 89 | add_top_corners() 90 | if corner_mode == "bottom": 91 | add_bottom_corners() 92 | im.putalpha(alpha) 93 | 94 | return im 95 | 96 | 97 | def prepare_mask(size, antialias=2): 98 | from PIL import Image, ImageDraw 99 | 100 | mask = Image.new("L", (size[0] * antialias, size[1] * antialias), 0) 101 | ImageDraw.Draw(mask).ellipse((0, 0) + mask.size, fill=255) 102 | return mask.resize(size, Image.ANTIALIAS) 103 | 104 | 105 | def _crop_round_image(im, s): 106 | from PIL import Image 107 | 108 | w, h = im.size 109 | k = w // s[0] - h // s[1] 110 | if k > 0: 111 | im = im.crop(((w - h) // 2, 0, (w + h) // 2, h)) 112 | elif k < 0: 113 | im = im.crop((0, (h - w) // 2, w, (h + w) // 2)) 114 | return im.resize(s, Image.ANTIALIAS) 115 | 116 | 117 | def crop_round_image(cutting_size, path_to_image, path_to_new_image): 118 | from PIL import Image 119 | 120 | im = Image.open(path_to_image) 121 | im = _crop_round_image(im, cutting_size) 122 | im.putalpha(prepare_mask(cutting_size, 4)) 123 | im.save(path_to_new_image) 124 | -------------------------------------------------------------------------------- /kivymd/toast/kivytoast/kivytoast.py: -------------------------------------------------------------------------------- 1 | """ 2 | KivyToast 3 | ========= 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | 13 | Example: 14 | 15 | from kivymd.app import MDApp 16 | from kivymd.theming import ThemeManager 17 | from kivymd.toast.kivytoast.kivytoast import toast 18 | 19 | 20 | class Test(MDApp): 21 | 22 | def show_toast(self): 23 | toast('Test Kivy Toast') 24 | 25 | def build(self): 26 | return Builder.load_string( 27 | ''' 28 | BoxLayout: 29 | orientation:'vertical' 30 | 31 | MDToolbar: 32 | id: toolbar 33 | title: 'Test Toast' 34 | md_bg_color: app.theme_cls.primary_color 35 | left_action_items: [['menu', lambda x: '']] 36 | 37 | FloatLayout: 38 | 39 | MDRaisedButton: 40 | text: 'TEST KIVY TOAST' 41 | on_release: app.show_toast() 42 | pos_hint: {'center_x': .5, 'center_y': .5} 43 | 44 | ''' 45 | ) 46 | 47 | Test().run() 48 | """ 49 | 50 | from kivy.core.window import Window 51 | from kivy.properties import NumericProperty 52 | from kivy.uix.label import Label 53 | from kivy.animation import Animation 54 | from kivy.uix.modalview import ModalView 55 | from kivy.clock import Clock 56 | from kivy.metrics import dp 57 | from kivy.lang import Builder 58 | 59 | from kivymd import images_path 60 | 61 | Builder.load_string( 62 | """ 63 | : 64 | canvas: 65 | Color: 66 | rgba: .2, .2, .2, 1 67 | RoundedRectangle: 68 | pos: self.pos 69 | size: self.size 70 | radius: [15,] 71 | """ 72 | ) 73 | 74 | 75 | class Toast(ModalView): 76 | duration = NumericProperty(2.5) 77 | 78 | def __init__(self, **kwargs): 79 | super().__init__(**kwargs) 80 | self.size_hint = (None, None) 81 | self.pos_hint = {"center_x": 0.5, "center_y": 0.1} 82 | self.background_color = [0, 0, 0, 0] 83 | self.background = f"{images_path}transparent.png" 84 | self.opacity = 0 85 | self.auto_dismiss = True 86 | self.label_toast = Label(size_hint=(None, None), opacity=0) 87 | self.label_toast.bind(texture_size=self.label_check_texture_size) 88 | self.add_widget(self.label_toast) 89 | 90 | def label_check_texture_size(self, instance, texture_size): 91 | texture_width, texture_height = texture_size 92 | if texture_width > Window.width: 93 | instance.text_size = (Window.width - dp(10), None) 94 | instance.texture_update() 95 | texture_width, texture_height = instance.texture_size 96 | self.size = (texture_width + 25, texture_height + 25) 97 | 98 | def toast(self, text_toast): 99 | self.label_toast.text = text_toast 100 | self.open() 101 | 102 | def on_open(self): 103 | self.fade_in() 104 | Clock.schedule_once(self.fade_out, self.duration) 105 | 106 | def fade_in(self): 107 | Animation(opacity=1, duration=0.4).start(self.label_toast) 108 | Animation(opacity=1, duration=0.4).start(self) 109 | 110 | def fade_out(self, interval): 111 | Animation(opacity=0, duration=0.4).start(self.label_toast) 112 | anim_body = Animation(opacity=0, duration=0.4) 113 | anim_body.bind(on_complete=lambda *x: self.dismiss()) 114 | anim_body.start(self) 115 | 116 | def on_touch_down(self, touch): 117 | if not self.collide_point(*touch.pos): 118 | if self.auto_dismiss: 119 | self.dismiss() 120 | return False 121 | super(ModalView, self).on_touch_down(touch) 122 | return True 123 | 124 | 125 | def toast(text, duration=2.5): 126 | Toast(duration=duration).toast(text) 127 | -------------------------------------------------------------------------------- /kivymd/uix/slidingpanel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sliding Panel 3 | ============= 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from kivy.animation import Animation 18 | from kivy.clock import Clock 19 | from kivy.core.window import Window 20 | from kivy.lang import Builder 21 | from kivy.properties import ( 22 | OptionProperty, 23 | NumericProperty, 24 | StringProperty, 25 | ListProperty, 26 | ) 27 | from kivy.uix.boxlayout import BoxLayout 28 | 29 | Builder.load_string( 30 | """ 31 | #:import Window kivy.core.window.Window 32 | 33 | 34 | 35 | orientation: 'vertical' 36 | size_hint_x: None 37 | width: dp(320) 38 | x: -1 * self.width if self.side == 'left' else Window.width 39 | 40 | 41 | 42 | canvas: 43 | Color: 44 | rgba: root.color 45 | Rectangle: 46 | size: root.size 47 | """ 48 | ) 49 | 50 | 51 | class PanelShadow(BoxLayout): 52 | color = ListProperty([0, 0, 0, 0]) 53 | 54 | 55 | class SlidingPanel(BoxLayout): 56 | anim_length_close = NumericProperty(0.3) 57 | anim_length_open = NumericProperty(0.3) 58 | animation_t_open = StringProperty("out_sine") 59 | animation_t_close = StringProperty("out_sine") 60 | side = OptionProperty("left", options=["left", "right"]) 61 | 62 | _open = False 63 | 64 | def __init__(self, **kwargs): 65 | super().__init__(**kwargs) 66 | self.shadow = PanelShadow() 67 | Clock.schedule_once(lambda x: Window.add_widget(self.shadow, 89), 0) 68 | Clock.schedule_once(lambda x: Window.add_widget(self, 90), 0) 69 | 70 | def toggle(self): 71 | Animation.stop_all(self, "x") 72 | Animation.stop_all(self.shadow, "color") 73 | if self._open: 74 | if self.side == "left": 75 | target_x = -1 * self.width 76 | else: 77 | target_x = Window.width 78 | 79 | sh_anim = Animation( 80 | duration=self.anim_length_open, 81 | t=self.animation_t_open, 82 | color=[0, 0, 0, 0], 83 | ) 84 | sh_anim.start(self.shadow) 85 | self._get_main_animation( 86 | duration=self.anim_length_close, 87 | t=self.animation_t_close, 88 | x=target_x, 89 | is_closing=True, 90 | ).start(self) 91 | self._open = False 92 | else: 93 | if self.side == "left": 94 | target_x = 0 95 | else: 96 | target_x = Window.width - self.width 97 | Animation( 98 | duration=self.anim_length_open, 99 | t=self.animation_t_open, 100 | color=[0, 0, 0, 0.5], 101 | ).start(self.shadow) 102 | self._get_main_animation( 103 | duration=self.anim_length_open, 104 | t=self.animation_t_open, 105 | x=target_x, 106 | is_closing=False, 107 | ).start(self) 108 | self._open = True 109 | 110 | def _get_main_animation(self, duration, t, x, is_closing): 111 | return Animation(duration=duration, t=t, x=x) 112 | 113 | def on_touch_down(self, touch): 114 | # Prevents touch events from propagating to anything below the widget. 115 | super().on_touch_down(touch) 116 | if self.collide_point(*touch.pos) or self._open: 117 | return True 118 | 119 | def on_touch_up(self, touch): 120 | super().on_touch_up(touch) 121 | if not self.collide_point(touch.x, touch.y) and self._open: 122 | self.toggle() 123 | return True 124 | -------------------------------------------------------------------------------- /kivymd/uix/label.py: -------------------------------------------------------------------------------- 1 | """ 2 | Label 3 | ===== 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from kivy.lang import Builder 18 | from kivy.metrics import sp 19 | from kivy.properties import ( 20 | OptionProperty, 21 | ListProperty, 22 | BooleanProperty, 23 | StringProperty, 24 | AliasProperty, 25 | ) 26 | from kivy.uix.label import Label 27 | 28 | from kivymd.font_definitions import theme_font_styles 29 | from kivymd.theming import ThemableBehavior 30 | from kivymd.theming_dynamic_text import get_contrast_text_color 31 | 32 | Builder.load_string( 33 | """ 34 | #:import md_icons kivymd.icon_definitions.md_icons 35 | 36 | 37 | 38 | disabled_color: self.theme_cls.disabled_hint_text_color 39 | text_size: (self.width, None) 40 | 41 | 42 | : 43 | font_style: "Icon" 44 | text: u"{}".format(md_icons[self.icon]) if self.icon in md_icons else "" 45 | source: None if self.icon in md_icons else self.icon 46 | canvas: 47 | Color: 48 | rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0) 49 | Rectangle: 50 | source: self.source if self.source else None 51 | pos: self.pos 52 | size: self.size 53 | """ 54 | ) 55 | 56 | 57 | class MDLabel(ThemableBehavior, Label): 58 | font_style = OptionProperty("Body1", options=theme_font_styles) 59 | 60 | can_capitalize = BooleanProperty(True) 61 | _capitalizing = BooleanProperty(False) 62 | 63 | def _get_text(self): 64 | if self._capitalizing: 65 | return self._text.upper() 66 | return self._text 67 | 68 | def _set_text(self, value): 69 | self._text = value 70 | 71 | _text = StringProperty() 72 | text = AliasProperty(_get_text, _set_text, bind=["_text", "_capitalizing"]) 73 | 74 | theme_text_color = OptionProperty( 75 | None, 76 | allownone=True, 77 | options=[ 78 | "Primary", 79 | "Secondary", 80 | "Hint", 81 | "Error", 82 | "Custom", 83 | "ContrastParentBackground", 84 | ], 85 | ) 86 | 87 | text_color = ListProperty(None, allownone=True) 88 | 89 | parent_background = ListProperty(None, allownone=True) 90 | 91 | _currently_bound_property = {} 92 | 93 | def __init__(self, **kwargs): 94 | super().__init__(**kwargs) 95 | self.bind( 96 | font_style=self.update_font_style, 97 | can_capitalize=self.update_font_style, 98 | ) 99 | self.on_theme_text_color(None, self.theme_text_color) 100 | self.update_font_style() 101 | self.on_opposite_colors(None, self.opposite_colors) 102 | 103 | def update_font_style(self, *args): 104 | font_info = self.theme_cls.font_styles[self.font_style] 105 | self.font_name = font_info[0] 106 | self.font_size = sp(font_info[1]) 107 | if font_info[2] and self.can_capitalize: 108 | self._capitalizing = True 109 | else: 110 | self._capitalizing = False 111 | # TODO: Add letter spacing change 112 | # self.letter_spacing = font_info[3] 113 | 114 | def on_theme_text_color(self, instance, value): 115 | t = self.theme_cls 116 | op = self.opposite_colors 117 | setter = self.setter("color") 118 | t.unbind(**self._currently_bound_property) 119 | attr_name = { 120 | "Primary": "text_color" if not op else "opposite_text_color", 121 | "Secondary": "secondary_text_color" 122 | if not op 123 | else "opposite_secondary_text_color", 124 | "Hint": "disabled_hint_text_color" 125 | if not op 126 | else "opposite_disabled_hint_text_color", 127 | "Error": "error_color", 128 | }.get(value, None) 129 | if attr_name: 130 | c = {attr_name: setter} 131 | t.bind(**c) 132 | self._currently_bound_property = c 133 | self.color = getattr(t, attr_name) 134 | else: 135 | # 'Custom' and 'ContrastParentBackground' lead here, as well as the 136 | # generic None value it's not yet been set 137 | if value == "Custom" and self.text_color: 138 | self.color = self.text_color 139 | elif value == "ContrastParentBackground" and self.parent_background: 140 | self.color = get_contrast_text_color(self.parent_background) 141 | else: 142 | self.color = [0, 0, 0, 1] 143 | 144 | def on_text_color(self, *args): 145 | if self.theme_text_color == "Custom": 146 | self.color = self.text_color 147 | 148 | def on_opposite_colors(self, instance, value): 149 | self.on_theme_text_color(self, self.theme_text_color) 150 | 151 | 152 | class MDIcon(MDLabel): 153 | icon = StringProperty("android") 154 | source = StringProperty(None, allownone=True) 155 | -------------------------------------------------------------------------------- /kivymd/tools/update_icons.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Artem S. Bulgakov 2 | # 3 | # This file is distributed under the terms of the same license, 4 | # as the Kivy framework. 5 | 6 | """ 7 | Tool for updating Iconic font 8 | ============================= 9 | 10 | Downloads archive from https://github.com/Templarian/MaterialDesign-Webfont and 11 | updates font file with icon_definitions. 12 | """ 13 | 14 | import os 15 | import requests 16 | import zipfile 17 | import shutil 18 | import re 19 | import json 20 | 21 | os.chdir(os.path.dirname(__file__)) 22 | # Paths to files in kivymd repository 23 | font_path = "../fonts/materialdesignicons-webfont.ttf" 24 | icon_definitions_path = "../icon_definitions.py" 25 | 26 | font_version = "master" 27 | # URL to download new archive (set None if already downloaded) 28 | url = ( 29 | f"https://github.com/Templarian/MaterialDesign-Webfont" 30 | f"/archive/{font_version}.zip" 31 | ) 32 | # url = None 33 | 34 | # Paths to files in loaded archive 35 | temp_path = os.path.abspath("temp") 36 | temp_repo_path = f"{temp_path}/MaterialDesign-Webfont-{font_version}" 37 | temp_font_path = f"{temp_repo_path}/fonts/materialdesignicons-webfont.ttf" 38 | temp_preview_path = f"{temp_repo_path}/preview.html" 39 | 40 | # Regex 41 | re_icons_json = re.compile(r"(?<=var icons = )[\S ]+(?=;)") 42 | re_additional_icons = re.compile(r"(?<=icons\.push\()[\S ]+(?=\);)") 43 | re_version = re.compile(r"(?<=)[\d.]+(?=)") 44 | re_quote_keys = re.compile(r"([{\s,])(\w+)(:)") 45 | re_icon_definitions = re.compile(r"md_icons = {\n([ ]{4}[\s\S]*,\n)*}") 46 | re_version_in_file = re.compile(r"(?<=LAST UPDATED: Version )[\d.]+(?=\n)") 47 | 48 | 49 | def download_file(url, path): 50 | response = requests.get(url, stream=True) 51 | if response.status_code != 200: 52 | return False 53 | with open(path, "wb") as f: 54 | shutil.copyfileobj(response.raw, f) 55 | return True 56 | 57 | 58 | def unzip_archive(archive_path, dir_path): 59 | with zipfile.ZipFile(archive_path, "r") as zip_ref: 60 | zip_ref.extractall(dir_path) 61 | 62 | 63 | def get_icons_list(): 64 | # There is js array with icons in file preview.html 65 | with open(temp_preview_path, "r") as f: 66 | preview_file = f.read() 67 | # Find version 68 | version = re_version.findall(preview_file)[0] 69 | # Load icons 70 | jsons_icons = re_icons_json.findall(preview_file)[0] 71 | json_icons = re_quote_keys.sub(r'\1"\2"\3', jsons_icons) 72 | icons = json.loads(json_icons) 73 | # Find additional icons (like a blank icon) 74 | # jsons_additional_icons = re_additional_icons.findall(preview_file) 75 | # for j in jsons_additional_icons: 76 | # json_additional_icons = re_quote_keys.sub(r'\1"\2"\3', j) 77 | # icons.append(json.loads(json_additional_icons)) 78 | return icons, version 79 | 80 | 81 | def make_icon_definitions(icons): 82 | # Make python dict ("name": hex) 83 | icon_definitions = "md_icons = {\n" 84 | for i in icons: 85 | icon_definitions += " " * 4 86 | if len(i["hex"]) != 4: 87 | # Some icons has 5-digit unicode 88 | i["hex"] = "0" * (8 - len(i["hex"])) + i["hex"] 89 | icon_definitions += f'"{i["name"]}": "\\U{i["hex"].upper()}",\n' 90 | else: 91 | icon_definitions += f'"{i["name"]}": "\\u{i["hex"].upper()}",\n' 92 | icon_definitions += f'"blank": " ",\n' # Add blank icon (space) 93 | icon_definitions += "}" 94 | return icon_definitions 95 | 96 | 97 | def export_icon_definitions(icon_definitions, version): 98 | with open(icon_definitions_path, "r") as f: 99 | icon_definitions_file = f.read() 100 | # Change md_icons list 101 | new_icon_definitions = re_icon_definitions.sub( 102 | icon_definitions.replace("\\", "\\\\"), icon_definitions_file, 1 103 | ) 104 | # Change version 105 | new_icon_definitions = re_version_in_file.sub( 106 | version, new_icon_definitions, 1 107 | ) 108 | with open(icon_definitions_path, "w") as f: 109 | f.write(new_icon_definitions) 110 | 111 | 112 | def main(): 113 | if url is not None: 114 | print(f"Downloading Material Design Icons from {url}") 115 | if download_file(url, "iconic-font.zip"): 116 | print("Archive downloaded") 117 | else: 118 | print("Could not download archive") 119 | else: 120 | print("URL is None. Do not download archive") 121 | if os.path.exists("iconic-font.zip"): 122 | unzip_archive("iconic-font.zip", temp_path) 123 | print("Unzip successful") 124 | os.remove("iconic-font.zip") 125 | if os.path.exists(temp_repo_path): 126 | shutil.copy2(temp_font_path, font_path) 127 | print("Font copied") 128 | icons, version = get_icons_list() 129 | print(f"Version {version}. {len(icons)} icons loaded") 130 | icon_definitions = make_icon_definitions(icons) 131 | export_icon_definitions(icon_definitions, version) 132 | print("File icon_definitions.py updated") 133 | shutil.rmtree(temp_path, ignore_errors=True) 134 | print( 135 | f'\nSuccessful. Commit message: "Update Iconic font (v{version})"' 136 | ) 137 | else: 138 | print(f"{temp_repo_path} not exists") 139 | 140 | 141 | if __name__ == "__main__": 142 | main() 143 | -------------------------------------------------------------------------------- /kivymd/uix/spinner.py: -------------------------------------------------------------------------------- 1 | """ 2 | Spinner 3 | ======= 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from kivy.lang import Builder 18 | from kivy.uix.widget import Widget 19 | from kivy.properties import NumericProperty, ListProperty, BooleanProperty 20 | from kivy.animation import Animation 21 | 22 | from kivymd.theming import ThemableBehavior 23 | 24 | Builder.load_string( 25 | """ 26 | 27 | canvas.before: 28 | PushMatrix 29 | Rotate: 30 | angle: self._rotation_angle 31 | origin: self.center 32 | canvas: 33 | Color: 34 | rgba: self.color 35 | a: self._alpha 36 | SmoothLine: 37 | circle: self.center_x, self.center_y, self.width / 2,\ 38 | self._angle_start, self._angle_end 39 | cap: 'square' 40 | width: dp(2.25) 41 | canvas.after: 42 | PopMatrix 43 | 44 | """ 45 | ) 46 | 47 | 48 | class MDSpinner(ThemableBehavior, Widget): 49 | """:class:`MDSpinner` is an implementation of the circular progress 50 | indicator in Google's Material Design. 51 | 52 | It can be used either as an indeterminate indicator that loops while 53 | the user waits for something to happen, or as a determinate indicator. 54 | 55 | Set :attr:`determinate` to **True** to activate determinate mode, and 56 | :attr:`determinate_time` to set the duration of the animation. 57 | """ 58 | 59 | determinate = BooleanProperty(False) 60 | """:attr:`determinate` is a :class:`~kivy.properties.BooleanProperty` and 61 | defaults to False 62 | """ 63 | 64 | determinate_time = NumericProperty(2) 65 | """:attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty` 66 | and defaults to 2 67 | """ 68 | 69 | active = BooleanProperty(True) 70 | """Use :attr:`active` to start or stop the spinner. 71 | 72 | :attr:`active` is a :class:`~kivy.properties.BooleanProperty` and 73 | defaults to True 74 | """ 75 | 76 | color = ListProperty([0, 0, 0, 0]) 77 | """:attr:`color` is a :class:`~kivy.properties.ListProperty` and 78 | defaults to 'self.theme_cls.primary_color' 79 | """ 80 | 81 | _alpha = NumericProperty(0) 82 | _rotation_angle = NumericProperty(360) 83 | _angle_start = NumericProperty(0) 84 | _angle_end = NumericProperty(8) 85 | 86 | def __init__(self, **kwargs): 87 | super().__init__(**kwargs) 88 | self.color = self.theme_cls.primary_color 89 | self._alpha_anim_in = Animation(_alpha=1, duration=0.8, t="out_quad") 90 | self._alpha_anim_out = Animation(_alpha=0, duration=0.3, t="out_quad") 91 | self._alpha_anim_out.bind(on_complete=self._reset) 92 | self.theme_cls.bind(primary_color=self._update_color) 93 | 94 | if self.determinate: 95 | self._start_determinate() 96 | else: 97 | self._start_loop() 98 | 99 | def _update_color(self, *args): 100 | self.color = self.theme_cls.primary_color 101 | 102 | def _start_determinate(self, *args): 103 | self._alpha_anim_in.start(self) 104 | 105 | _rot_anim = Animation( 106 | _rotation_angle=0, 107 | duration=self.determinate_time * 0.7, 108 | t="out_quad", 109 | ) 110 | _rot_anim.start(self) 111 | 112 | _angle_start_anim = Animation( 113 | _angle_end=360, duration=self.determinate_time, t="in_out_quad" 114 | ) 115 | _angle_start_anim.bind( 116 | on_complete=lambda *x: self._alpha_anim_out.start(self) 117 | ) 118 | 119 | _angle_start_anim.start(self) 120 | 121 | def _start_loop(self, *args): 122 | if self._alpha == 0: 123 | _rot_anim = Animation(_rotation_angle=0, duration=2, t="linear") 124 | _rot_anim.start(self) 125 | 126 | self._alpha = 1 127 | self._alpha_anim_in.start(self) 128 | _angle_start_anim = Animation( 129 | _angle_end=self._angle_end + 270, duration=0.6, t="in_out_cubic" 130 | ) 131 | _angle_start_anim.bind(on_complete=self._anim_back) 132 | _angle_start_anim.start(self) 133 | 134 | def _anim_back(self, *args): 135 | _angle_back_anim = Animation( 136 | _angle_start=self._angle_end - 8, duration=0.6, t="in_out_cubic" 137 | ) 138 | _angle_back_anim.bind(on_complete=self._start_loop) 139 | 140 | _angle_back_anim.start(self) 141 | 142 | def on__rotation_angle(self, *args): 143 | if self._rotation_angle == 0: 144 | self._rotation_angle = 360 145 | if not self.determinate: 146 | _rot_anim = Animation(_rotation_angle=0, duration=2) 147 | _rot_anim.start(self) 148 | 149 | def _reset(self, *args): 150 | Animation.cancel_all( 151 | self, "_angle_start", "_rotation_angle", "_angle_end", "_alpha" 152 | ) 153 | self._angle_start = 0 154 | self._angle_end = 8 155 | self._rotation_angle = 360 156 | self._alpha = 0 157 | self.active = False 158 | 159 | def on_active(self, *args): 160 | if not self.active: 161 | self._reset() 162 | else: 163 | if self.determinate: 164 | self._start_determinate() 165 | else: 166 | self._start_loop() 167 | -------------------------------------------------------------------------------- /kivymd/factory_registers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Register KivyMD widgets to use without import 3 | """ 4 | 5 | from kivy.factory import Factory 6 | 7 | r = Factory.register 8 | r("MDExpansionPanel", module="kivymd.uix.expansionpanel") 9 | r("FitImage", module="kivymd.utils.fitimage") 10 | r("MDBackdrop", module="kivymd.uix.backdrop") 11 | r("MDTab", module="kivymd.uix.bottomnavigation") 12 | r("MDBanner", module="kivymd.uix.banner") 13 | r("MDTooltip", module="kivymd.uix.tooltip") 14 | r("MDBottomNavigation", module="kivymd.uix.bottomnavigation") 15 | r("MDBottomNavigationItem", module="kivymd.uix.bottomnavigation") 16 | r("MDBottomNavigationHeader", module="kivymd.uix.bottomnavigation") 17 | r("MDBottomNavigationBar", module="kivymd.uix.bottomnavigation") 18 | r("MDBottomSheet", module="kivymd.uix.bottomsheet") 19 | r("MDListBottomSheet", module="kivymd.uix.bottomsheet") 20 | r("MDGridBottomSheet", module="kivymd.uix.bottomsheet") 21 | r("MDIconButton", module="kivymd.uix.button") 22 | r("MDRoundImageButton", module="kivymd.uix.button") 23 | r("MDFlatButton", module="kivymd.uix.button") 24 | r("MDRaisedButton", module="kivymd.uix.button") 25 | r("MDFloatingActionButton", module="kivymd.uix.button") 26 | r("MDRectangleFlatButton", module="kivymd.uix.button") 27 | r("MDTextButton", module="kivymd.uix.button") 28 | r("MDCustomRoundIconButton", module="kivymd.uix.button") 29 | r("MDRoundFlatButton", module="kivymd.uix.button") 30 | r("MDFillRoundFlatButton", module="kivymd.uix.button") 31 | r("MDRectangleFlatIconButton", module="kivymd.uix.button") 32 | r("MDRoundFlatIconButton", module="kivymd.uix.button") 33 | r("MDFillRoundFlatIconButton", module="kivymd.uix.button") 34 | r("MDCard", module="kivymd.uix.card") 35 | r("MDSeparator", module="kivymd.uix.card") 36 | r("MDCardPost", module="kivymd.uix.card") 37 | r("MDChip", module="kivymd.uix.chip") 38 | r("MDChooseChip", module="kivymd.uix.chip") 39 | r("MDDialog", module="kivymd.uix.dialog") 40 | r("MDInputDialog", module="kivymd.uix.dialog") 41 | r("MDFileManager", module="kivymd.uix.filemanager") 42 | r("Tile", module="kivymd.uix.imagelist") 43 | r("SmartTile", module="kivymd.uix.imagelist") 44 | r("SmartTileWithLabel", module="kivymd.uix.imagelist") 45 | r("SmartTileWithStar", module="kivymd.uix.imagelist") 46 | r("MDLabel", module="kivymd.uix.label") 47 | r("MDIcon", module="kivymd.uix.label") 48 | r("MDList", module="kivymd.uix.list") 49 | r("ILeftBody", module="kivymd.uix.list") 50 | r("ILeftBodyTouch", module="kivymd.uix.list") 51 | r("IRightBody", module="kivymd.uix.list") 52 | r("IRightBodyTouch", module="kivymd.uix.list") 53 | r("ContainerSupport", module="kivymd.uix.list") 54 | r("OneLineListItem", module="kivymd.uix.list") 55 | r("TwoLineListItem", module="kivymd.uix.list") 56 | r("ThreeLineListItem", module="kivymd.uix.list") 57 | r("OneLineAvatarListItem", module="kivymd.uix.list") 58 | r("TwoLineAvatarListItem", module="kivymd.uix.list") 59 | r("ThreeLineAvatarListItem", module="kivymd.uix.list") 60 | r("OneLineIconListItem", module="kivymd.uix.list") 61 | r("TwoLineIconListItem", module="kivymd.uix.list") 62 | r("ThreeLineIconListItem", module="kivymd.uix.list") 63 | r("OneLineRightIconListItem", module="kivymd.uix.list") 64 | r("TwoLineRightIconListItem", module="kivymd.uix.list") 65 | r("ThreeLineRightIconListItem", module="kivymd.uix.list") 66 | r("OneLineAvatarIconListItem", module="kivymd.uix.list") 67 | r("TwoLineAvatarIconListItem", module="kivymd.uix.list") 68 | r("ThreeLineAvatarIconListItem", module="kivymd.uix.list") 69 | r("ThreeLineAvatarIconListItem", module="kivymd.uix.managerswiper") 70 | r("MDSwiperManager", module="kivymd.uix.managerswiper") 71 | r("MDSwiperPagination", module="kivymd.uix.managerswiper") 72 | r("ItemPagination", module="kivymd.uix.managerswiper") 73 | r("MDMenu", module="kivymd.uix.menu") 74 | r("MDDropdownMenu", module="kivymd.uix.menu") 75 | r("MDContextMenu", module="kivymd.uix.context_menu") 76 | r("HoverBehavior", module="kivymd.uix.behaviors.hover_behavior") 77 | r("MDMenuItem", module="kivymd.uix.menu") 78 | r("MDNavigationDrawer", module="kivymd.uix.navigationdrawer") 79 | r("NavigationLayout", module="kivymd.uix.navigationdrawer") 80 | r("MDDatePicker", module="kivymd.uix.picker") 81 | r("MDTimePicker", module="kivymd.uix.picker") 82 | r("MDThemePicker", module="kivymd.uix.picker") 83 | r("MDProgressBar", module="kivymd.uix.progressbar") 84 | r("MDProgressLoader", module="kivymd.uix.progressloader") 85 | r("MDScrollViewRefreshLayout", module="kivymd.uix.refreshlayout") 86 | r("MDCheckbox", module="kivymd.uix.selectioncontrol") 87 | r("Thumb", module="kivymd.uix.selectioncontrol") 88 | r("MDSwitch", module="kivymd.uix.selectioncontrol") 89 | r("MDSlider", module="kivymd.uix.slider") 90 | r("SlidingPanel", module="kivymd.uix.slidingpanel") 91 | r("Snackbar", module="kivymd.uix.snackbar") 92 | r("MDSpinner", module="kivymd.uix.spinner") 93 | r("MDStackFloatingButtons", module="kivymd.uix.stackfloatingbutton") 94 | r("MDFloatingLabel", module="kivymd.uix.stackfloatingbutton") 95 | r("MDFloatingLabel", module="kivymd.uix.tab") 96 | r("MDTabsLabel", module="kivymd.uix.tab") 97 | r("MDTabsBase", module="kivymd.uix.tab") 98 | r("MDTabsMain", module="kivymd.uix.tab") 99 | r("MDTabsCarousel", module="kivymd.uix.tab") 100 | r("MDTabsScrollView", module="kivymd.uix.tab") 101 | r("MDTabsBar", module="kivymd.uix.tab") 102 | r("MDTabs", module="kivymd.uix.tab") 103 | r("MDTextField", module="kivymd.uix.textfield") 104 | r("MDTextField", module="kivymd.uix.textfield") 105 | r("MDTextFieldRound", module="kivymd.uix.textfield") 106 | r("MDTextFieldRect", module="kivymd.uix.textfield") 107 | r("MDToolbar", module="kivymd.uix.toolbar") 108 | r("MDBottomAppBar", module="kivymd.uix.toolbar") 109 | r("MDUserAnimationCard", module="kivymd.uix.useranimationcard") 110 | r("MDDropDownItem", module="kivymd.uix.dropdownitem") 111 | -------------------------------------------------------------------------------- /kivymd/uix/dropdownitem.py: -------------------------------------------------------------------------------- 1 | """ 2 | MDDropDownItem 3 | ============== 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | 16 | Example 17 | ------- 18 | 19 | from kivymd.app import MDApp 20 | from kivy.factory import Factory 21 | from kivy.lang import Builder 22 | 23 | from kivymd.theming import ThemeManager 24 | 25 | Builder.load_string( 26 | ''' 27 | #:import toast kivymd.toast.toast 28 | 29 | 30 | 31 | orientation: 'vertical' 32 | 33 | MDToolbar: 34 | title: "Test MDDropDownItem" 35 | md_bg_color: app.theme_cls.primary_color 36 | elevation: 10 37 | left_action_items: [['menu', lambda x: x]] 38 | 39 | FloatLayout: 40 | 41 | MDDropDownItem: 42 | id: dropdown_item 43 | pos_hint: {'center_x': 0.5, 'center_y': 0.6} 44 | items: app.items 45 | dropdown_bg: [1, 1, 1, 1] 46 | 47 | MDRaisedButton: 48 | pos_hint: {'center_x': 0.5, 'center_y': 0.3} 49 | text: 'Chek Item' 50 | on_release: toast(dropdown_item.current_item) 51 | ''') 52 | 53 | 54 | class Test(MDApp): 55 | 56 | def build(self): 57 | self.items = [f"Item {i}" for i in range(50)] 58 | return Factory.MyRoot() 59 | 60 | 61 | Test().run() 62 | """ 63 | 64 | from kivy.lang import Builder 65 | from kivy.properties import ( 66 | ListProperty, 67 | StringProperty, 68 | NumericProperty, 69 | BooleanProperty, 70 | ) 71 | from kivy.uix.boxlayout import BoxLayout 72 | from kivy.uix.widget import Widget 73 | 74 | from kivymd.uix.menu import MDDropdownMenu 75 | from kivymd.theming import ThemableBehavior 76 | 77 | Builder.load_string( 78 | """ 79 | <_Triangle>: 80 | canvas: 81 | Color: 82 | rgba: root.theme_cls.text_color 83 | Triangle: 84 | points: 85 | [\ 86 | self.right-14, self.y+7, \ 87 | self.right-7, self.y+7, \ 88 | self.right-7, self.y+14 \ 89 | ] 90 | 91 | 92 | 93 | orientation: 'vertical' 94 | size_hint: None, None 95 | size: self.minimum_size 96 | spacing: '5dp' 97 | 98 | BoxLayout: 99 | size_hint: None, None 100 | size: self.minimum_size 101 | spacing: '10dp' 102 | 103 | Label: 104 | id: label_item 105 | size_hint: None, None 106 | size: self.texture_size 107 | color: root.theme_cls.text_color 108 | 109 | 110 | _Triangle: 111 | size_hint: None, None 112 | size: '20dp', '20dp' 113 | 114 | MDSeparator: 115 | """ 116 | ) 117 | 118 | 119 | class _Triangle(ThemableBehavior, Widget): 120 | pass 121 | 122 | 123 | class MDDropDownItem(ThemableBehavior, BoxLayout): 124 | """ 125 | :Events: 126 | `on_select` 127 | Called when an item is selected 128 | """ 129 | 130 | items = ListProperty() 131 | """String list of items for a drop-down list.""" 132 | 133 | dropdown_bg = ListProperty() 134 | """Color of the background of the menu.""" 135 | 136 | dropdown_max_height = NumericProperty() 137 | """The menu will grow no bigger than this number.""" 138 | 139 | dropdown_width_mult = NumericProperty(2) 140 | """This number multiplied by the standard increment (56dp on mobile, 141 | 64dp on desktop, determines the width of the menu items. 142 | 143 | If the resulting number were to be too big for the application Window, 144 | the multiplier will be adjusted for the biggest possible one. 145 | """ 146 | 147 | current_item = StringProperty() 148 | """Current label of item MDDropDownItem class.""" 149 | 150 | _center = BooleanProperty(True) 151 | 152 | _drop_list = None 153 | 154 | _list_menu = ListProperty() 155 | 156 | def __init__(self, **kwargs): 157 | super().__init__(**kwargs) 158 | self.register_event_type("on_select") 159 | if not self.dropdown_bg: 160 | self.dropdown_bg = self.theme_cls.primary_color 161 | 162 | def on_items(self, instance, value): 163 | _list_menu = [] 164 | for name_item in value: 165 | _list_menu.append( 166 | { 167 | "viewclass": "OneLineListItem", 168 | "text": name_item, 169 | "text_color": self.theme_cls.text_color, 170 | "theme_text_color": "Custom", 171 | "on_release": lambda x=name_item: self.set_item(x), 172 | } 173 | ) 174 | self._list_menu = _list_menu 175 | self.ids.label_item.text = ( 176 | self.current_item if self.current_item else value[0] 177 | ) 178 | self.current_item = self.ids.label_item.text 179 | 180 | def set_item(self, name_item): 181 | self.ids.label_item.text = name_item 182 | self.current_item = name_item 183 | self._drop_list.dismiss() 184 | self.dispatch("on_select", name_item) 185 | 186 | def on_select(self, value): 187 | pass 188 | 189 | def on_touch_down(self, touch): 190 | if self.collide_point(touch.x, touch.y) and self._list_menu: 191 | self._drop_list = MDDropdownMenu( 192 | _center=self._center, 193 | items=self._list_menu, 194 | background_color=self.dropdown_bg, 195 | max_height=self.dropdown_max_height, 196 | width_mult=self.dropdown_width_mult, 197 | width_rectangle=1, 198 | ) 199 | self._drop_list.open(self) 200 | -------------------------------------------------------------------------------- /kivymd/uix/stackfloatingbutton.py: -------------------------------------------------------------------------------- 1 | """ 2 | Stack Floating Buttons 3 | ====================== 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | 13 | Example 14 | ------- 15 | 16 | from kivymd.app import MDApp 17 | from kivy.lang import Builder 18 | from kivy.factory import Factory 19 | 20 | from kivymd.toast import toast 21 | from kivymd.theming import ThemeManager 22 | from kivymd.uix.stackfloatingbuttons import MDStackFloatingButtons 23 | 24 | 25 | Builder.load_string(''' 26 | : 27 | orientation: 'vertical' 28 | 29 | MDToolbar: 30 | title: 'Stack Floating Buttons' 31 | md_bg_color: app.theme_cls.primary_color 32 | elevation: 10 33 | left_action_items: [['menu', lambda x: None]] 34 | 35 | ''') 36 | 37 | 38 | class Example(MDApp): 39 | title = "Example Stack Floating Buttons" 40 | create_stack_floating_buttons = False 41 | floating_data = { 42 | 'Python': 'language-python', 43 | 'Php': 'language-php', 44 | 'C++': 'language-cpp'} 45 | 46 | def set_my_language(self, instance_button): 47 | toast(instance_button.icon) 48 | 49 | def build(self): 50 | screen = Factory.ExampleFloatingButtons() 51 | # Use this condition otherwise the stack will be created each time. 52 | if not self.create_stack_floating_buttons: 53 | screen.add_widget(MDStackFloatingButtons( 54 | icon='lead-pencil', 55 | floating_data=self.floating_data, 56 | callback=self.set_my_language)) 57 | self.create_stack_floating_buttons = True 58 | return screen 59 | 60 | 61 | Example().run() 62 | """ 63 | 64 | from kivy.animation import Animation 65 | from kivy.core.window import Window 66 | from kivy.uix.floatlayout import FloatLayout 67 | from kivy.lang import Builder 68 | from kivy.properties import ( 69 | StringProperty, 70 | DictProperty, 71 | ObjectProperty, 72 | ListProperty, 73 | ) 74 | from kivy.metrics import dp 75 | 76 | from kivymd.theming import ThemableBehavior 77 | from kivymd.uix.card import MDCard 78 | 79 | 80 | Builder.load_string( 81 | """ 82 | #:import Window kivy.core.window.Window 83 | 84 | 85 | 86 | x: Window.width - (self.width + dp(21)) 87 | y: dp(25) 88 | size_hint: None, None 89 | size: dp(46), dp(46) 90 | elevation: 5 91 | md_bg_color: app.theme_cls.primary_color 92 | text_color: root.parent.text_color 93 | on_release: self.parent.callback(self) 94 | 95 | 96 | 97 | size_hint: None, None 98 | height: dp(20) 99 | width: label.texture_size[0] 100 | border_color_a: .5 101 | md_bg_color: app.theme_cls.primary_color 102 | x: -self.width 103 | 104 | Label: 105 | id: label 106 | color: root.parent.text_color 107 | bold: True 108 | markup: True 109 | text: ' %s ' % root.text 110 | 111 | 112 | 113 | FloatingButton: 114 | id: f_btn_1 115 | icon: list(root.floating_data.values())[0] 116 | FloatingButton: 117 | id: f_btn_2 118 | icon: list(root.floating_data.values())[1] 119 | FloatingButton: 120 | id: f_btn_3 121 | icon: list(root.floating_data.values())[2] 122 | 123 | MDFloatingLabel: 124 | id: f_lbl_1 125 | text: list(root.floating_data.keys())[0] 126 | y: dp(117) 127 | MDFloatingLabel: 128 | id: f_lbl_2 129 | text: list(root.floating_data.keys())[1] 130 | y: dp(170) 131 | MDFloatingLabel: 132 | id: f_lbl_3 133 | text: list(root.floating_data.keys())[2] 134 | y: dp(226) 135 | 136 | MDFloatingActionButton: 137 | icon: root.icon 138 | size: dp(56), dp(56) 139 | x: Window.width - (self.width + dp(15)) 140 | md_bg_color: app.theme_cls.primary_color 141 | text_color: root.text_color 142 | y: dp(15) 143 | on_release: root.show_floating_buttons() 144 | """ 145 | ) 146 | 147 | 148 | class MDFloatingLabel(MDCard, ThemableBehavior): 149 | text = StringProperty() 150 | text_color = ListProperty([0, 0, 0, 1]) 151 | 152 | def on_md_bg_color(self, instance, value): 153 | self.md_bg_color = self.theme_cls.primary_color 154 | 155 | 156 | class MDStackFloatingButtons(FloatLayout): 157 | icon = StringProperty("checkbox-blank-circle") 158 | callback = ObjectProperty(lambda x: None) 159 | text_color = ListProperty([0, 0, 0, 1]) 160 | floating_data = DictProperty() 161 | show = False 162 | in_progress = False 163 | 164 | def __init__(self, **kwargs): 165 | super().__init__(**kwargs) 166 | 167 | self.lbl_list = [self.ids.f_lbl_1, self.ids.f_lbl_2, self.ids.f_lbl_3] 168 | self.btn_list = [self.ids.f_btn_1, self.ids.f_btn_2, self.ids.f_btn_3] 169 | 170 | def set_in_progress(self, instance_anim, instance): 171 | if instance is self.ids.f_btn_3: 172 | self.in_progress = False 173 | 174 | def show_floating_buttons(self): 175 | step = dp(46) 176 | if self.in_progress: 177 | return 178 | self.in_progress = True 179 | for i, btn in enumerate(self.btn_list): 180 | step += dp(56) 181 | anim = Animation(y=step, d=0.5, t="out_elastic") 182 | anim.bind(on_complete=self.set_in_progress) 183 | anim.start(btn) 184 | 185 | self.show = True if not self.show else False 186 | self.show_floating_labels() if self.show else self.hide_floating_labels() 187 | 188 | def show_floating_labels(self): 189 | i = 0 190 | for lbl in self.lbl_list: 191 | i += 0.3 192 | pos_x = Window.width - (lbl.width + dp(46 + 21 * 1.5)) 193 | Animation(x=pos_x, d=i, t="out_elastic").start(lbl) 194 | 195 | def hide_floating_buttons(self): 196 | for btn in self.btn_list: 197 | Animation(y=dp(25), d=0.5, t="in_elastic").start(btn) 198 | 199 | def hide_floating_labels(self): 200 | i = 1 201 | for lbl in self.lbl_list: 202 | i -= 0.3 203 | Animation(x=-lbl.width, d=i, t="out_elastic").start(lbl) 204 | self.hide_floating_buttons() 205 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/elevation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Elevation Behavior 3 | ================== 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from kivy.app import App 18 | from kivy.lang import Builder 19 | from kivy.properties import ListProperty, ObjectProperty, NumericProperty 20 | from kivy.properties import AliasProperty 21 | from kivy.metrics import dp 22 | 23 | Builder.load_string( 24 | """ 25 | 26 | canvas.before: 27 | Color: 28 | a: self._soft_shadow_a 29 | Rectangle: 30 | texture: self._soft_shadow_texture 31 | size: self._soft_shadow_size 32 | pos: self._soft_shadow_pos 33 | Color: 34 | a: self._hard_shadow_a 35 | Rectangle: 36 | texture: self._hard_shadow_texture 37 | size: self._hard_shadow_size 38 | pos: self._hard_shadow_pos 39 | Color: 40 | a: 1 41 | 42 | 43 | 44 | canvas.before: 45 | Color: 46 | a: self._soft_shadow_a 47 | Rectangle: 48 | texture: self._soft_shadow_texture 49 | size: self._soft_shadow_size 50 | pos: self._soft_shadow_pos 51 | Color: 52 | a: self._hard_shadow_a 53 | Rectangle: 54 | texture: self._hard_shadow_texture 55 | size: self._hard_shadow_size 56 | pos: self._hard_shadow_pos 57 | Color: 58 | a: 1 59 | """ 60 | ) 61 | 62 | 63 | class CommonElevationBehavior(object): 64 | _elevation = NumericProperty(1) 65 | 66 | def _get_elevation(self): 67 | return self._elevation 68 | 69 | def _set_elevation(self, elevation): 70 | try: 71 | self._elevation = elevation 72 | except KeyError: 73 | self._elevation = 1 74 | 75 | elevation = AliasProperty( 76 | _get_elevation, _set_elevation, bind=("_elevation",) 77 | ) 78 | 79 | _soft_shadow_texture = ObjectProperty() 80 | _soft_shadow_size = ListProperty([0, 0]) 81 | _soft_shadow_pos = ListProperty([0, 0]) 82 | _soft_shadow_a = NumericProperty(0) 83 | _hard_shadow_texture = ObjectProperty() 84 | _hard_shadow_size = ListProperty([0, 0]) 85 | _hard_shadow_pos = ListProperty([0, 0]) 86 | _hard_shadow_a = NumericProperty(0) 87 | 88 | def __init__(self, **kwargs): 89 | super().__init__(**kwargs) 90 | self.bind( 91 | elevation=self._update_shadow, 92 | pos=self._update_shadow, 93 | size=self._update_shadow, 94 | ) 95 | 96 | def _update_shadow(self, *args): 97 | raise NotImplemented 98 | 99 | 100 | class RectangularElevationBehavior(CommonElevationBehavior): 101 | def _update_shadow(self, *args): 102 | if self.elevation > 0: 103 | ratio = self.width / (self.height if self.height != 0 else 1) 104 | if -2 < ratio < 2: 105 | self._shadow = App.get_running_app().theme_cls.quad_shadow 106 | width = soft_width = self.width * 1.9 107 | height = soft_height = self.height * 1.9 108 | elif ratio <= -2: 109 | self._shadow = App.get_running_app().theme_cls.rec_st_shadow 110 | ratio = abs(ratio) 111 | if ratio > 5: 112 | ratio = ratio * 22 113 | else: 114 | ratio = ratio * 11.5 115 | 116 | width = soft_width = self.width * 1.9 117 | height = self.height + dp(ratio) 118 | soft_height = self.height + dp(ratio) + dp(self.elevation) * 0.5 119 | else: 120 | self._shadow = App.get_running_app().theme_cls.quad_shadow 121 | width = soft_width = self.width * 1.8 122 | height = soft_height = self.height * 1.8 123 | 124 | x = self.center_x - width / 2 125 | soft_x = self.center_x - soft_width / 2 126 | self._soft_shadow_size = (soft_width, soft_height) 127 | self._hard_shadow_size = (width, height) 128 | 129 | y = ( 130 | self.center_y 131 | - soft_height / 2 132 | - dp(0.1 * 1.5 ** self.elevation) 133 | ) 134 | self._soft_shadow_pos = (soft_x, y) 135 | self._soft_shadow_a = 0.1 * 1.1 ** self.elevation 136 | self._soft_shadow_texture = self._shadow.textures[ 137 | str(int(round(self.elevation - 1))) 138 | ] 139 | 140 | y = self.center_y - height / 2 - dp(0.5 * 1.18 ** self.elevation) 141 | self._hard_shadow_pos = (x, y) 142 | self._hard_shadow_a = 0.4 * 0.9 ** self.elevation 143 | self._hard_shadow_texture = self._shadow.textures[ 144 | str(int(round(self.elevation))) 145 | ] 146 | 147 | else: 148 | self._soft_shadow_a = 0 149 | self._hard_shadow_a = 0 150 | 151 | 152 | class CircularElevationBehavior(CommonElevationBehavior): 153 | def __init__(self, **kwargs): 154 | super().__init__(**kwargs) 155 | self._shadow = App.get_running_app().theme_cls.round_shadow 156 | 157 | def _update_shadow(self, *args): 158 | if self.elevation > 0: 159 | width = self.width * 2 160 | height = self.height * 2 161 | 162 | x = self.center_x - width / 2 163 | self._soft_shadow_size = (width, height) 164 | 165 | self._hard_shadow_size = (width, height) 166 | 167 | y = self.center_y - height / 2 - dp(0.1 * 1.5 ** self.elevation) 168 | self._soft_shadow_pos = (x, y) 169 | self._soft_shadow_a = 0.1 * 1.1 ** self.elevation 170 | self._soft_shadow_texture = self._shadow.textures[ 171 | str(int(round(self.elevation))) 172 | ] 173 | 174 | y = self.center_y - height / 2 - dp(0.5 * 1.18 ** self.elevation) 175 | self._hard_shadow_pos = (x, y) 176 | self._hard_shadow_a = 0.4 * 0.9 ** self.elevation 177 | self._hard_shadow_texture = self._shadow.textures[ 178 | str(int(round(self.elevation - 1))) 179 | ] 180 | 181 | else: 182 | self._soft_shadow_a = 0 183 | self._hard_shadow_a = 0 184 | -------------------------------------------------------------------------------- /kivymd/stiffscroll/__init__.py: -------------------------------------------------------------------------------- 1 | """An Effect to be used with ScrollView to prevent scrolling beyond 2 | the bounds, but politely. 3 | 4 | A ScrollView constructed with StiffScrollEffect, 5 | eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to 6 | scroll as you get nearer to its edges. You can scroll all the way to 7 | the edge if you want to, but it will take more finger-movement than 8 | usual. 9 | 10 | Unlike DampedScrollEffect, it is impossible to overscroll with 11 | StiffScrollEffect. That means you cannot push the contents of the 12 | ScrollView far enough to see what's beneath them. This is appropriate 13 | if the ScrollView contains, eg., a background image, like a desktop 14 | wallpaper. Overscrolling may give the impression that there is some 15 | reason to overscroll, even if just to take a peek beneath, and that 16 | impression may be misleading. 17 | 18 | StiffScrollEffect was written by Zachary Spector. His other stuff is at: 19 | https://github.com/LogicalDash/ 20 | He can be reached, and possibly hired, at: 21 | zacharyspector@gmail.com 22 | 23 | """ 24 | 25 | from time import time 26 | 27 | from kivy.animation import AnimationTransition 28 | from kivy.effects.kinetic import KineticEffect 29 | from kivy.properties import ObjectProperty, NumericProperty 30 | from kivy.uix.widget import Widget 31 | 32 | 33 | class StiffScrollEffect(KineticEffect): 34 | drag_threshold = NumericProperty("20sp") 35 | """Minimum distance to travel before the movement is considered as a 36 | drag.""" 37 | 38 | min = NumericProperty(0) 39 | """Minimum boundary to stop the scrolling at.""" 40 | 41 | max = NumericProperty(0) 42 | """Maximum boundary to stop the scrolling at.""" 43 | 44 | max_friction = NumericProperty(1) 45 | """How hard should it be to scroll, at the worst?""" 46 | 47 | body = NumericProperty(0.7) 48 | """Proportion of the range in which you can scroll unimpeded.""" 49 | 50 | scroll = NumericProperty(0.0) 51 | """Computed value for scrolling""" 52 | 53 | transition_min = ObjectProperty(AnimationTransition.in_cubic) 54 | """The AnimationTransition function to use when adjusting the friction 55 | near the minimum end of the effect. 56 | 57 | """ 58 | 59 | transition_max = ObjectProperty(AnimationTransition.in_cubic) 60 | """The AnimationTransition function to use when adjusting the friction 61 | near the maximum end of the effect. 62 | 63 | """ 64 | 65 | target_widget = ObjectProperty(None, allownone=True, baseclass=Widget) 66 | """The widget to apply the effect to.""" 67 | 68 | displacement = NumericProperty(0) 69 | """The absolute distance moved in either direction.""" 70 | 71 | scroll = NumericProperty(0.0) 72 | """The distance to be used for scrolling.""" 73 | 74 | def __init__(self, **kwargs): 75 | """Set ``self.base_friction`` to the value of ``self.friction`` just 76 | after instantiation, so that I can reset to that value later. 77 | 78 | """ 79 | 80 | super().__init__(**kwargs) 81 | self.base_friction = self.friction 82 | 83 | def update_velocity(self, dt): 84 | """Before actually updating my velocity, meddle with ``self.friction`` 85 | to make it appropriate to where I'm at, currently. 86 | 87 | """ 88 | 89 | hard_min = self.min 90 | hard_max = self.max 91 | if hard_min > hard_max: 92 | hard_min, hard_max = hard_max, hard_min 93 | 94 | margin = (1.0 - self.body) * (hard_max - hard_min) 95 | soft_min = hard_min + margin 96 | soft_max = hard_max - margin 97 | 98 | if self.value < soft_min: 99 | try: 100 | prop = (soft_min - self.value) / (soft_min - hard_min) 101 | self.friction = self.base_friction + abs( 102 | self.max_friction - self.base_friction 103 | ) * self.transition_min(prop) 104 | except ZeroDivisionError: 105 | pass 106 | elif self.value > soft_max: 107 | try: 108 | # normalize how far past soft_max I've gone as a 109 | # proportion of the distance between soft_max and hard_max 110 | prop = (self.value - soft_max) / (hard_max - soft_max) 111 | self.friction = self.base_friction + abs( 112 | self.max_friction - self.base_friction 113 | ) * self.transition_min(prop) 114 | except ZeroDivisionError: 115 | pass 116 | else: 117 | self.friction = self.base_friction 118 | 119 | return super().update_velocity(dt) 120 | 121 | def on_value(self, *args): 122 | """Prevent moving beyond my bounds, and update ``self.scroll``""" 123 | 124 | if self.value < self.min: 125 | self.velocity = 0 126 | self.scroll = self.min 127 | elif self.value > self.max: 128 | self.velocity = 0 129 | self.scroll = self.max 130 | else: 131 | self.scroll = self.value 132 | 133 | def start(self, val, t=None): 134 | """Start movement with ``self.friction`` = ``self.base_friction``""" 135 | 136 | self.is_manual = True 137 | t = t or time() 138 | self.velocity = self.displacement = 0 139 | self.friction = self.base_friction 140 | self.history = [(t, val)] 141 | 142 | def update(self, val, t=None): 143 | """Reduce the impact of whatever change has been made to me, in 144 | proportion with my current friction. 145 | 146 | """ 147 | 148 | t = t or time() 149 | hard_min = self.min 150 | hard_max = self.max 151 | if hard_min > hard_max: 152 | hard_min, hard_max = hard_max, hard_min 153 | 154 | gamut = hard_max - hard_min 155 | margin = (1.0 - self.body) * gamut 156 | soft_min = hard_min + margin 157 | soft_max = hard_max - margin 158 | distance = val - self.history[-1][1] 159 | reach = distance + self.value 160 | 161 | if (distance < 0 and reach < soft_min) or ( 162 | distance > 0 and soft_max < reach 163 | ): 164 | distance -= distance * self.friction 165 | self.apply_distance(distance) 166 | self.history.append((t, val)) 167 | 168 | if len(self.history) > self.max_history: 169 | self.history.pop(0) 170 | self.displacement += abs(distance) 171 | self.trigger_velocity_update() 172 | 173 | def stop(self, val, t=None): 174 | """Work out whether I've been flung.""" 175 | 176 | self.is_manual = False 177 | self.displacement += abs(val - self.history[-1][1]) 178 | if self.displacement <= self.drag_threshold: 179 | self.velocity = 0 180 | 181 | return super().stop(val, t) 182 | -------------------------------------------------------------------------------- /kivymd/uix/refreshlayout.py: -------------------------------------------------------------------------------- 1 | """ 2 | ScrollView Refresh Layout 3 | ========================= 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | 13 | Example 14 | ------- 15 | 16 | from kivymd.app import MDApp 17 | from kivy.clock import Clock 18 | from kivy.lang import Builder 19 | from kivy.factory import Factory 20 | from kivy.properties import StringProperty 21 | 22 | from kivymd.uix.button import MDIconButton 23 | from kivymd.icon_definitions import md_icons 24 | from kivymd.uix.list import ILeftBodyTouch, OneLineIconListItem 25 | from kivymd.theming import ThemeManager 26 | from kivymd.utils import asynckivy 27 | 28 | Builder.load_string(''' 29 | 30 | text: root.text 31 | 32 | IconLeftSampleWidget: 33 | icon: root.icon 34 | 35 | 36 | 37 | 38 | BoxLayout: 39 | orientation: 'vertical' 40 | 41 | MDToolbar: 42 | title: app.title 43 | md_bg_color: app.theme_cls.primary_color 44 | background_palette: 'Primary' 45 | elevation: 10 46 | left_action_items: [['menu', lambda x: x]] 47 | 48 | MDScrollViewRefreshLayout: 49 | id: refresh_layout 50 | refresh_callback: app.refresh_callback 51 | root_layout: root 52 | 53 | GridLayout: 54 | id: box 55 | size_hint_y: None 56 | height: self.minimum_height 57 | cols: 1 58 | ''') 59 | 60 | 61 | class IconLeftSampleWidget(ILeftBodyTouch, MDIconButton): 62 | pass 63 | 64 | 65 | class ItemForList(OneLineIconListItem): 66 | icon = StringProperty() 67 | 68 | 69 | class Example(MDApp): 70 | title = 'Example Refresh Layout' 71 | screen = None 72 | x = 0 73 | y = 15 74 | 75 | def build(self): 76 | self.screen = Factory.Example() 77 | self.set_list() 78 | 79 | return self.screen 80 | 81 | def set_list(self): 82 | async def set_list(): 83 | names_icons_list = list(md_icons.keys())[self.x:self.y] 84 | for name_icon in names_icons_list: 85 | await asynckivy.sleep(0) 86 | self.screen.ids.box.add_widget( 87 | ItemForList(icon=name_icon, text=name_icon)) 88 | asynckivy.start(set_list()) 89 | 90 | def refresh_callback(self, *args): 91 | '''A method that updates the state of your application 92 | while the spinner remains on the screen.''' 93 | 94 | def refresh_callback(interval): 95 | self.screen.ids.box.clear_widgets() 96 | if self.x == 0: 97 | self.x, self.y = 15, 30 98 | else: 99 | self.x, self.y = 0, 15 100 | self.set_list() 101 | self.screen.ids.refresh_layout.refresh_done() 102 | self.tick = 0 103 | 104 | Clock.schedule_once(refresh_callback, 1) 105 | 106 | 107 | Example().run() 108 | 109 | """ 110 | 111 | from kivy.animation import Animation 112 | from kivy.effects.dampedscroll import DampedScrollEffect 113 | from kivy.lang import Builder 114 | from kivy.metrics import dp 115 | from kivy.properties import NumericProperty, ObjectProperty, ListProperty 116 | from kivy.uix.floatlayout import FloatLayout 117 | from kivy.uix.scrollview import ScrollView 118 | from kivy.core.window import Window 119 | 120 | from kivymd.theming import ThemableBehavior 121 | 122 | Builder.load_string( 123 | """ 124 | #:import Window kivy.core.window.Window 125 | 126 | 127 | 128 | 129 | AnchorLayout: 130 | id: body_spinner 131 | size_hint: None, None 132 | size: dp(46), dp(46) 133 | y: Window.height 134 | pos_hint: {'center_x': .5} 135 | anchor_x: 'center' 136 | anchor_y: 'center' 137 | 138 | canvas: 139 | Clear 140 | Color: 141 | rgba: root.theme_cls.primary_dark 142 | Ellipse: 143 | pos: self.pos 144 | size: self.size 145 | 146 | MDSpinner: 147 | id: spinner 148 | size_hint: None, None 149 | size: dp(30), dp(30) 150 | color: 1, 1, 1, 1 151 | """ 152 | ) 153 | 154 | 155 | class _RefreshScrollEffect(DampedScrollEffect): 156 | """This class is simply based on DampedScrollEffect. 157 | If you need any documentation please look at kivy.effects.dampedscrolleffect. 158 | """ 159 | 160 | min_scroll_to_reload = NumericProperty(-dp(100)) 161 | """Minimum overscroll value to reload.""" 162 | 163 | def on_overscroll(self, scrollview, overscroll): 164 | if overscroll < self.min_scroll_to_reload: 165 | scroll_view = self.target_widget.parent 166 | scroll_view._did_overscroll = True 167 | return True 168 | else: 169 | return False 170 | 171 | 172 | class MDScrollViewRefreshLayout(ScrollView): 173 | root_layout = ObjectProperty() 174 | """The spinner will be attached to this layout.""" 175 | 176 | def __init__(self, **kargs): 177 | super().__init__(**kargs) 178 | self.effect_cls = _RefreshScrollEffect 179 | self._work_spinnrer = False 180 | self._did_overscroll = False 181 | self.refresh_spinner = None 182 | 183 | def on_touch_up(self, *args): 184 | if self._did_overscroll and not self._work_spinnrer: 185 | if self.refresh_callback: 186 | self.refresh_callback() 187 | if not self.refresh_spinner: 188 | self.refresh_spinner = RefreshSpinner(_refresh_layout=self) 189 | self.root_layout.add_widget(self.refresh_spinner) 190 | self.refresh_spinner.start_anim_spinner() 191 | self._work_spinnrer = True 192 | self._did_overscroll = False 193 | return True 194 | 195 | return super().on_touch_up(*args) 196 | 197 | def refresh_done(self): 198 | if self.refresh_spinner: 199 | self.refresh_spinner.hide_anim_spinner() 200 | 201 | 202 | class RefreshSpinner(ThemableBehavior, FloatLayout): 203 | spinner_color = ListProperty([1, 1, 1, 1]) 204 | 205 | _refresh_layout = ObjectProperty() 206 | """kivymd.refreshlayout.MDScrollViewRefreshLayout object.""" 207 | 208 | def start_anim_spinner(self): 209 | spinner = self.ids.body_spinner 210 | Animation( 211 | y=spinner.y - self.theme_cls.standard_increment * 2 + dp(10), 212 | d=0.8, 213 | t="out_elastic", 214 | ).start(spinner) 215 | 216 | def hide_anim_spinner(self): 217 | spinner = self.ids.body_spinner 218 | anim = Animation(y=Window.height, d=0.8, t="out_elastic") 219 | anim.bind(on_complete=self.set_spinner) 220 | anim.start(spinner) 221 | 222 | def set_spinner(self, *args): 223 | body_spinner = self.ids.body_spinner 224 | body_spinner.size = (dp(46), dp(46)) 225 | body_spinner.y = Window.height 226 | body_spinner.opacity = 1 227 | spinner = self.ids.spinner 228 | spinner.size = (dp(30), dp(30)) 229 | spinner.opacity = 1 230 | self._refresh_layout._work_spinnrer = False 231 | self._refresh_layout._did_overscroll = False 232 | -------------------------------------------------------------------------------- /kivymd/tools/release/make_release.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Artem S. Bulgakov 2 | # 3 | # This file is distributed under the terms of the same license, 4 | # as the Kivy framework. 5 | 6 | """ 7 | Script Before release 8 | ===================== 9 | 10 | Run this script before release (before deploying). 11 | 12 | * Undo all local changes in repository 13 | * Update version in __init__.py, README 14 | * Rename "Unreleased" to version in CHANGELOG.md 15 | * Commit "Version ..." 16 | * Tag 17 | * Add "Unreleased" to CHANGELOG.md 18 | * Force push repository 19 | """ 20 | 21 | import sys 22 | import os 23 | import subprocess 24 | import re 25 | 26 | if not len(sys.argv) in (3, 5): 27 | print( 28 | "Usage:\n" 29 | "python before_release.py new_version new_version_status" 30 | " next_version next_version_status\n" 31 | "Example: python3 before_release.py 1.9.3 alpha 1.9.4 alpha\n" 32 | "Example: python3 before_release.py 1.9.3 1.9.4" 33 | ) 34 | exit(0) 35 | 36 | 37 | def command(cmd): 38 | print("Command:", " ".join(cmd)) 39 | return subprocess.check_output(cmd) 40 | 41 | 42 | os.chdir(os.path.join(os.path.dirname(__file__), "../../..")) 43 | 44 | # Get new version 45 | new_version = sys.argv[1] 46 | new_version_status = sys.argv[2] if len(sys.argv) == 5 else None 47 | postfix_new_version = ( 48 | f" - *{new_version_status.capitalize()}*" 49 | if new_version_status is not None 50 | else "" 51 | ) 52 | full_new_version = f"v{new_version}{postfix_new_version}" 53 | 54 | # Get next version 55 | next_version = sys.argv[3] 56 | next_version_status = sys.argv[4] if len(sys.argv) == 5 else None 57 | postfix_next_version = ( 58 | f" - *{next_version_status.capitalize()}*" 59 | if next_version_status is not None 60 | else "" 61 | ) 62 | full_next_version = f"v{next_version}{postfix_next_version}" 63 | 64 | # Get old version 65 | command(["git", "checkout", "master"]) 66 | old_version = command(["git", "describe", "--abbrev=0", "--tags"]) 67 | old_version = str(old_version, encoding="utf-8")[:-1] # Remove \n 68 | 69 | # Print info 70 | print(f"Old version: {old_version}") 71 | print(f"New version: {new_version} ({full_new_version})") 72 | print(f"Next version: {next_version} ({full_next_version})\n") 73 | 74 | # Check what files will be removed 75 | clean = str( 76 | command(["git", "clean", "-dx", "--force", "--dry-run"]), encoding="utf-8" 77 | ) 78 | if not clean == "\n": 79 | print(clean) 80 | while True: 81 | ans = input("Do you want to remove these files? (yes/no)").lower() 82 | if ans == "y" or ans == "yes": 83 | break 84 | elif ans == "n" or ans == "no": 85 | print("git clean is required. Exit") 86 | exit(0) 87 | 88 | # Remove all untracked files 89 | command(["git", "clean", "-dx", "--force"]) 90 | command(["git", "reset", "--hard"]) 91 | 92 | # Black all files 93 | # command(["black", "."]) 94 | # command(["git", "commit", "--all", "-m", f"Black formatting"]) 95 | 96 | 97 | def replace_in_file(pattern, repl, file): 98 | # Replace one `pattern` match to `repl` in file `file` 99 | file_content = open(file, "rt", encoding="utf-8").read() 100 | file_content = re.sub(pattern, repl, file_content, 1, re.M) 101 | open(file, "wt", encoding="utf-8").write(file_content) 102 | 103 | 104 | # Change version in kivymd/__init__.py 105 | init = os.path.abspath("kivymd/__init__.py") 106 | init_version_regex = r"(?<=^__version__ = ['\"])[^'\"]+(?=['\"]$)" 107 | init_version_info_regex = r"(?<=^__version_info__ = \()[^\)]+(?=\)$)" 108 | replace_in_file(init_version_regex, new_version, init) 109 | replace_in_file(init_version_info_regex, new_version.replace(".", ", "), init) 110 | 111 | # Change version in README.md 112 | readme = os.path.abspath("README.md") 113 | readme_version_regex = rf"(?<=\[)v{old_version}[ \-*\w^\]\n]*(?=\])" 114 | replace_in_file(readme_version_regex, full_new_version, readme) 115 | 116 | # Change version in CHANGELOG.md 117 | changelog = os.path.abspath("CHANGELOG.md") 118 | changelog_new_version_regex = r"(?<=\> )[^\n]*(?=\n\n)" 119 | changelog_new_version_line_regex = r"\> [^\n]*\n\n" 120 | changelog_unreleased_regex = r"(?<=## )[^\n]*(?=\n\n)" 121 | changelog_see_changes_regex = rf"{old_version}\.\.\.master" 122 | changelog_see_changes_string = f"{old_version}...{new_version}" 123 | changelog_install_regex = r"git\+https[\S]*@master(?=\n)" 124 | changelog_install_string = f"kivymd=={new_version}" 125 | 126 | try: 127 | # Rename Unreleased section with new version 128 | changelog_file_content = open(changelog, "rt", encoding="utf-8").read() 129 | new_version_in_changelog = re.findall( 130 | changelog_new_version_regex, changelog_file_content, re.M 131 | )[0] 132 | changelog_file_content = re.sub( 133 | changelog_new_version_line_regex, "", changelog_file_content, 1, re.M 134 | ) 135 | changelog_file_content = re.sub( 136 | changelog_see_changes_regex, 137 | changelog_see_changes_string, 138 | changelog_file_content, 139 | 1, 140 | re.M, 141 | ) 142 | changelog_file_content = re.sub( 143 | changelog_install_regex, 144 | changelog_install_string, 145 | changelog_file_content, 146 | 1, 147 | re.M, 148 | ) 149 | changelog_file_content = re.sub( 150 | changelog_unreleased_regex, 151 | new_version_in_changelog, 152 | changelog_file_content, 153 | 1, 154 | re.M, 155 | ) 156 | open(changelog, "wt", encoding="utf-8").write(changelog_file_content) 157 | except IndexError: 158 | pass 159 | 160 | # Black all files 161 | command(["black", "."]) 162 | 163 | # Make commit and tag 164 | command(["git", "commit", "--all", "-m", f"Version {new_version}"]) 165 | command(["git", "tag", new_version]) 166 | 167 | # Create branch `stable` and `stable-{old_version}` (not tested) 168 | branches_to_push = [] 169 | # command(["git", "branch", "-m", "stable", f"stable-{old_version}"]) 170 | # branches_to_push.append(f"stable-{old_version}") 171 | # command(["git", "branch", "stable"]) 172 | # command(["git", "push", "--force", "origin", "master:stable"]) 173 | # branches_to_push.append("stable") 174 | 175 | # Regex where to place Unreleased section 176 | changelog_unreleased_place_regex = f"(?<=Change Log\n==========\n\n)" 177 | # Unreleased section to place in top of CHANGELOG.md 178 | changelog_unreleased_string = f"""\ 179 | ## [Unreleased](https://github.com/HeaTTheatR/KivyMD/tree/master) 180 | 181 | > [v{next_version}](https://github.com/HeaTTheatR/KivyMD/tree/{next_version})\ 182 | {postfix_next_version} 183 | 184 | * 185 | * 186 | * 187 | 188 | [See changes](https://github.com/HeaTTheatR/KivyMD/compare/{new_version}...master) 189 | ```bash 190 | pip install git+https://github.com/HeaTTheatR/KivyMD.git@master 191 | ``` 192 | 193 | 194 | """ 195 | replace_in_file( 196 | changelog_unreleased_place_regex, changelog_unreleased_string, changelog 197 | ) 198 | command( 199 | ["git", "commit", "--all", "-m", f"Add section Unreleased to Change Log"] 200 | ) 201 | 202 | # Push all changes 203 | if input("Do you want to push changes? (y)") in ("", "y", "yes"): 204 | command(["git", "push", "--tags", "origin", "master", *branches_to_push]) 205 | else: 206 | print( 207 | "Changes did not pushed. Command for manual pushing:" 208 | " git push --tags origin master " + " ".join(branches_to_push) 209 | ) 210 | -------------------------------------------------------------------------------- /kivymd/uix/progressloader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Progress Loader 3 | =============== 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | 13 | Progressbar downloads files from the server. 14 | 15 | Example 16 | ------- 17 | 18 | import os 19 | 20 | from kivymd.app import MDApp 21 | from kivy.lang import Builder 22 | from kivy.factory import Factory 23 | 24 | from kivymd.uix.progressloader import MDProgressLoader 25 | from kivymd.theming import ThemeManager 26 | from kivymd.toast import toast 27 | 28 | 29 | Builder.load_string(''' 30 | 31 | orientation: 'vertical' 32 | spacing: dp(5) 33 | 34 | MDToolbar: 35 | id: toolbar 36 | title: 'MD Progress Loader' 37 | left_action_items: [['menu', lambda x: None]] 38 | elevation: 10 39 | md_bg_color: app.theme_cls.primary_color 40 | 41 | FloatLayout: 42 | id: box 43 | 44 | MDRoundFlatIconButton: 45 | text: "Download file" 46 | icon: "download" 47 | pos_hint: {'center_x': .5, 'center_y': .6} 48 | on_release: app.show_example_download_file() 49 | ''') 50 | 51 | 52 | class Test(MDApp): 53 | 54 | def build(self): 55 | self.main_widget = Factory.Root() 56 | return self.main_widget 57 | 58 | def set_chevron_back_screen(self): 59 | '''Sets the return chevron to the previous screen in ToolBar.''' 60 | 61 | self.main_widget.ids.toolbar.right_action_items = [] 62 | 63 | def download_progress_hide(self, instance_progress, value): 64 | '''Hides progress progress.''' 65 | 66 | self.main_widget.ids.toolbar.right_action_items =\ 67 | [['download', 68 | lambda x: self.download_progress_show(instance_progress)]] 69 | 70 | def download_progress_show(self, instance_progress): 71 | self.set_chevron_back_screen() 72 | instance_progress.open() 73 | instance_progress.animation_progress_from_fade() 74 | 75 | def show_example_download_file(self): 76 | link = 'https://www.python.org/ftp/python/3.5.1/python-3.5.1-embed-win32.zip' 77 | progress = MDProgressLoader( 78 | url_on_image=link, 79 | path_to_file=os.path.join(self.directory, 'python-3.5.1.zip'), 80 | download_complete=self.download_complete, 81 | download_hide=self.download_progress_hide 82 | ) 83 | progress.start(self.main_widget.ids.box) 84 | 85 | def download_complete(self): 86 | self.set_chevron_back_screen() 87 | toast('Done') 88 | 89 | 90 | Test().run() 91 | """ 92 | 93 | from kivy.clock import Clock 94 | from kivy.core.window import Window 95 | from kivy.animation import Animation 96 | from kivy.network.urlrequest import UrlRequest 97 | from kivy.lang import Builder 98 | from kivy.properties import StringProperty, ObjectProperty, BooleanProperty 99 | 100 | from kivymd.uix.card import MDCard 101 | 102 | Builder.load_string( 103 | """ 104 | #:import Window kivy.core.window.Window 105 | 106 | 107 | 108 | pos: (Window.width // 2) - (self.width // 2), (Window.height // 2) - (self.height // 2) 109 | size_hint_y: None 110 | size_hint_x: .8 111 | height: spinner.height + dp(20) 112 | spacing: dp(10) 113 | padding: dp(10) 114 | 115 | canvas: 116 | Color: 117 | rgba: app.theme_cls.primary_color 118 | Rectangle: 119 | size: self.size 120 | pos: self.pos 121 | 122 | MDSpinner 123 | id: spinner 124 | size_hint: None, None 125 | size: dp(46), dp(46) 126 | color: 1, 1, 1, 1 127 | 128 | MDLabel: 129 | id: label_download 130 | shorten: True 131 | max_lines: 1 132 | halign: 'left' 133 | valign: 'top' 134 | text_size: self.width, None 135 | size_hint_y: None 136 | height: spinner.height 137 | size_hint_x: .8 138 | text: root.label_downloading_text 139 | 140 | Widget: 141 | size_hint_x: .1 142 | """ 143 | ) 144 | 145 | 146 | class MDProgressLoader(MDCard): 147 | path_to_file = StringProperty() 148 | """The path to which the uploaded file will be saved.""" 149 | 150 | url_on_image = StringProperty() 151 | """Link to uploaded file.""" 152 | 153 | label_downloading_text = StringProperty("Downloading...") 154 | """Default text before downloading.""" 155 | 156 | downloading_text = StringProperty("Downloading: {}%") 157 | """Signature of the downloaded file.""" 158 | 159 | download_complete = ObjectProperty() 160 | """Function, called after a successful file upload.""" 161 | 162 | download_hide = ObjectProperty(lambda x: None) 163 | """Function that is called when the download window is closed.""" 164 | 165 | download_flag = BooleanProperty(False) 166 | """If True - the download process is in progress.""" 167 | 168 | request = ObjectProperty() 169 | """UrlRequest object.""" 170 | 171 | def __init__(self, **kwargs): 172 | super().__init__(**kwargs) 173 | self.root_instance = None 174 | 175 | def start(self, root_instance): 176 | self.root_instance = root_instance 177 | self.download_flag = True 178 | self.root_instance.add_widget(self) 179 | self.retrieve_progress_load(self.url_on_image, self.path_to_file) 180 | Clock.schedule_once(self.animation_progress_to_fade, 2.5) 181 | 182 | def open(self): 183 | self.animation_progress_from_fade() 184 | 185 | def draw_progress(self, percent): 186 | """ 187 | :type percent: int; 188 | :param percent: loading percentage; 189 | 190 | """ 191 | 192 | self.label_downloading_text = self.downloading_text.format(percent) 193 | 194 | def animation_progress_to_fade(self, interval): 195 | if not self.download_flag: 196 | return 197 | 198 | animation = Animation( 199 | center_y=Window.height, 200 | center_x=Window.width, 201 | opacity=0, 202 | d=0.2, 203 | t="out_quad", 204 | ) 205 | animation.bind(on_complete=lambda x, y: self.download_hide(self, None)) 206 | animation.start(self) 207 | 208 | def animation_progress_from_fade(self): 209 | animation = Animation( 210 | center_y=Window.height // 2, 211 | center_x=Window.width // 2, 212 | opacity=1, 213 | d=0.2, 214 | t="out_quad", 215 | ) 216 | animation.start(self) 217 | Clock.schedule_once(self.animation_progress_to_fade, 2.5) 218 | 219 | def retrieve_progress_load(self, url, path): 220 | """ 221 | :type url: str; 222 | :param url: link to content; 223 | 224 | :type path: str; 225 | :param path: path to save content; 226 | """ 227 | 228 | self.request = UrlRequest( 229 | url, 230 | file_path=path, 231 | chunk_size=1024, 232 | on_progress=self.update_progress, 233 | on_success=self.on_success, 234 | ) 235 | 236 | def update_progress(self, request, current_size, total_size): 237 | if total_size == 0: 238 | self.draw_progress(0) 239 | return 240 | percent = current_size * 100 // total_size 241 | self.draw_progress(percent) 242 | 243 | def on_success(self, request, result): 244 | self.root_instance.remove_widget(self) 245 | self.download_complete() 246 | self.download_flag = False 247 | -------------------------------------------------------------------------------- /kivymd/vendor/circleLayout/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CircularLayout 3 | ============== 4 | 5 | CircularLayout is a special layout that places widgets around a circle. 6 | 7 | size_hint 8 | --------- 9 | 10 | size_hint_x is used as an angle-quota hint (widget with higher 11 | size_hint_x will be farther from each other, and vice versa), while 12 | size_hint_y is used as a widget size hint (widgets with a higher size 13 | hint will be bigger).size_hint_x cannot be None. 14 | 15 | Widgets are all squares, unless you set size_hint_y to None (in that 16 | case you'll be able to specify your own size), and their size is the 17 | difference between the outer and the inner circle's radii. To make the 18 | widgets bigger you can just decrease inner_radius_hint. 19 | """ 20 | 21 | from math import sin, cos, pi, radians 22 | 23 | from kivy.uix.layout import Layout 24 | from kivy.properties import ( 25 | NumericProperty, 26 | ReferenceListProperty, 27 | OptionProperty, 28 | BoundedNumericProperty, 29 | VariableListProperty, 30 | AliasProperty, 31 | ) 32 | 33 | __all__ = "CircularLayout" 34 | 35 | try: 36 | xrange(1, 2) 37 | except NameError: 38 | 39 | def xrange(first, second, third=None): 40 | if third: 41 | return range(first, second, third) 42 | else: 43 | return range(first, second) 44 | 45 | 46 | class CircularLayout(Layout): 47 | """ 48 | Circular layout class. See module documentation for more information. 49 | """ 50 | 51 | padding = VariableListProperty([0, 0, 0, 0]) 52 | """Padding between the layout box and it's children: [padding_left, 53 | padding_top, padding_right, padding_bottom]. 54 | 55 | padding also accepts a two argument form [padding_horizontal, 56 | padding_vertical] and a one argument form [padding]. 57 | 58 | .. version changed:: 1.7.0 59 | Replaced NumericProperty with VariableListProperty. 60 | 61 | :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and 62 | defaults to [0, 0, 0, 0]. 63 | """ 64 | 65 | start_angle = NumericProperty(0) 66 | """Angle (in degrees) at which the first widget will be placed. 67 | Start counting angles from the X axis, going counterclockwise. 68 | 69 | :attr:`start_angle` is a :class:`~kivy.properties.NumericProperty` and 70 | defaults to 0 (start from the right). 71 | """ 72 | 73 | circle_quota = BoundedNumericProperty(360, min=0, max=360) 74 | """Size (in degrees) of the part of the circumference that will actually 75 | be used to place widgets. 76 | 77 | :attr:`circle_quota` is a :class:`~kivy.properties.BoundedNumericProperty` 78 | and defaults to 360 (all the circumference). 79 | """ 80 | 81 | direction = OptionProperty("ccw", options=("cw", "ccw")) 82 | """Direction of widgets in the circle. 83 | 84 | :attr:`direction` is an :class:`~kivy.properties.OptionProperty` and 85 | defaults to 'ccw'. Can be 'ccw' (counterclockwise) or 'cw' (clockwise). 86 | """ 87 | 88 | outer_radius_hint = NumericProperty(1) 89 | """Sets the size of the outer circle. A number greater than 1 will make the 90 | widgets larger than the actual widget, a number smaller than 1 will leave 91 | a gap. 92 | 93 | :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` 94 | and defaults to 1. 95 | """ 96 | 97 | inner_radius_hint = NumericProperty(0.6) 98 | """Sets the size of the inner circle. A number greater than 99 | :attr:`outer_radius_hint` will cause glitches. The closest it is to 100 | :attr:`outer_radius_hint`, the smallest will be the widget in the layout. 101 | 102 | :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` 103 | and defaults to 1. 104 | """ 105 | 106 | radius_hint = ReferenceListProperty(inner_radius_hint, outer_radius_hint) 107 | """Combined :attr:`outer_radius_hint` and :attr:`inner_radius_hint` 108 | in a list for convenience. See their documentation for more details. 109 | 110 | :attr:`radius_hint` is a :class:`~kivy.properties.ReferenceListProperty`. 111 | """ 112 | 113 | def _get_delta_radii(self): 114 | radius = ( 115 | min( 116 | self.width - self.padding[0] - self.padding[2], 117 | self.height - self.padding[1] - self.padding[3], 118 | ) 119 | / 2.0 120 | ) 121 | outer_r = radius * self.outer_radius_hint 122 | inner_r = radius * self.inner_radius_hint 123 | return outer_r - inner_r 124 | 125 | delta_radii = AliasProperty( 126 | _get_delta_radii, None, bind=("radius_hint", "padding", "size") 127 | ) 128 | 129 | def __init__(self, **kwargs): 130 | super().__init__(**kwargs) 131 | 132 | self.bind( 133 | start_angle=self._trigger_layout, 134 | parent=self._trigger_layout, 135 | # padding=self._trigger_layout, 136 | children=self._trigger_layout, 137 | size=self._trigger_layout, 138 | radius_hint=self._trigger_layout, 139 | pos=self._trigger_layout, 140 | ) 141 | 142 | def do_layout(self, *largs): 143 | # optimize layout by preventing looking at the same attribute in a loop 144 | len_children = len(self.children) 145 | if len_children == 0: 146 | return 147 | selfcx = self.center_x 148 | selfcy = self.center_y 149 | direction = self.direction 150 | cquota = radians(self.circle_quota) 151 | start_angle_r = radians(self.start_angle) 152 | padding_left = self.padding[0] 153 | padding_top = self.padding[1] 154 | padding_right = self.padding[2] 155 | padding_bottom = self.padding[3] 156 | padding_x = padding_left + padding_right 157 | padding_y = padding_top + padding_bottom 158 | 159 | radius = min(self.width - padding_x, self.height - padding_y) / 2.0 160 | outer_r = radius * self.outer_radius_hint 161 | inner_r = radius * self.inner_radius_hint 162 | middle_r = radius * sum(self.radius_hint) / 2.0 163 | delta_r = outer_r - inner_r 164 | 165 | stretch_weight_angle = 0.0 166 | for w in self.children: 167 | sha = w.size_hint_x 168 | if sha is None: 169 | raise ValueError( 170 | "size_hint_x cannot be None in a CircularLayout" 171 | ) 172 | else: 173 | stretch_weight_angle += sha 174 | 175 | sign = +1.0 176 | angle_offset = start_angle_r 177 | if direction == "cw": 178 | angle_offset = 2 * pi - start_angle_r 179 | sign = -1.0 180 | 181 | for c in reversed(self.children): 182 | sha = c.size_hint_x 183 | shs = c.size_hint_y 184 | 185 | angle_quota = cquota / stretch_weight_angle * sha 186 | angle = angle_offset + (sign * angle_quota / 2) 187 | angle_offset += sign * angle_quota 188 | 189 | # kived: looking it up, yes. x = cos(angle) * radius + centerx; 190 | # y = sin(angle) * radius + centery 191 | ccx = cos(angle) * middle_r + selfcx + padding_left - padding_right 192 | ccy = sin(angle) * middle_r + selfcy + padding_bottom - padding_top 193 | 194 | c.center_x = ccx 195 | c.center_y = ccy 196 | if shs: 197 | s = delta_r * shs 198 | c.width = s 199 | c.height = s 200 | 201 | 202 | if __name__ == "__main__": 203 | from kivymd.app import MDApp 204 | from kivy.uix.button import Button 205 | 206 | class CircLayoutApp(MDApp): 207 | def build(self): 208 | cly = CircularLayout( 209 | direction="cw", 210 | start_angle=-75, 211 | inner_radius_hint=0.7, 212 | padding="20dp", 213 | ) 214 | 215 | for i in xrange(1, 13): 216 | cly.add_widget(Button(text=str(i), font_size="30dp")) 217 | 218 | return cly 219 | 220 | CircLayoutApp().run() 221 | -------------------------------------------------------------------------------- /kivymd/uix/snackbar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Snackbars 3 | ========= 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | 13 | `Material Design spec, Snackbars `_ 14 | 15 | Example 16 | ======= 17 | 18 | from kivymd.app import MDApp 19 | from kivy.animation import Animation 20 | from kivy.clock import Clock 21 | from kivy.metrics import dp 22 | from kivy.lang import Builder 23 | 24 | from kivymd.uix.snackbar import Snackbar 25 | from kivymd.theming import ThemeManager 26 | from kivymd.toast import toast 27 | 28 | KV = ''' 29 | #:import Window kivy.core.window.Window 30 | 31 | 32 | Screen: 33 | name: 'snackbar' 34 | 35 | BoxLayout: 36 | orientation: 'vertical' 37 | spacing: dp(10) 38 | 39 | MDToolbar: 40 | title: 'Example Snackbar' 41 | md_bg_color: app.theme_cls.primary_color 42 | left_action_items: [['menu', lambda x: x]] 43 | background_palette: 'Primary' 44 | 45 | BoxLayout: 46 | orientation: 'vertical' 47 | spacing: dp(10) 48 | padding: dp(10) 49 | 50 | Widget: 51 | 52 | MDRaisedButton: 53 | text: "Create simple snackbar" 54 | pos_hint: {'center_x': .5} 55 | on_release: app.show_example_snackbar('simple') 56 | 57 | MDRaisedButton: 58 | text: "Create snackbar with button" 59 | pos_hint: {'center_x': .5} 60 | on_release: app.show_example_snackbar('button') 61 | 62 | MDRaisedButton: 63 | text: "Create snackbar with a lot of text" 64 | pos_hint: {'center_x': .5} 65 | on_release: app.show_example_snackbar('verylong') 66 | 67 | MDSeparator: 68 | 69 | MDLabel: 70 | text: 'Click the MDFloatingActionButton to show the following example...' 71 | halign: 'center' 72 | 73 | Widget: 74 | 75 | MDFloatingActionButton: 76 | id: button 77 | md_bg_color: app.theme_cls.primary_color 78 | x: Window.width - self.width - dp(10) 79 | y: dp(10) 80 | on_release: app.show_example_snackbar('float') 81 | ''' 82 | 83 | 84 | class ExampleSnackBar(MDApp): 85 | _interval = 0 86 | my_snackbar = None 87 | screen = None 88 | 89 | def build(self): 90 | self.screen = Builder.load_string(KV) 91 | return self.screen 92 | 93 | def show_example_snackbar(self, snack_type): 94 | def callback(instance): 95 | toast(instance.text) 96 | 97 | def wait_interval(interval): 98 | self._interval += interval 99 | if self._interval > self.my_snackbar.duration: 100 | anim = Animation(y=dp(10), d=.2) 101 | anim.start(self.screen.ids.button) 102 | Clock.unschedule(wait_interval) 103 | self._interval = 0 104 | self.my_snackbar = None 105 | 106 | if snack_type == 'simple': 107 | Snackbar(text="This is a snackbar!").show() 108 | elif snack_type == 'button': 109 | Snackbar(text="This is a snackbar", button_text="with a button!", 110 | button_callback=callback).show() 111 | elif snack_type == 'verylong': 112 | Snackbar(text="This is a very very very very very very very " 113 | "long snackbar!").show() 114 | elif snack_type == 'float': 115 | if not self.my_snackbar: 116 | self.my_snackbar = Snackbar( 117 | text="This is a snackbar!", button_text='Button', 118 | duration=3, button_callback=callback) 119 | self.my_snackbar.show() 120 | anim = Animation(y=dp(72), d=.2) 121 | anim.bind(on_complete=lambda *args: Clock.schedule_interval( 122 | wait_interval, 0)) 123 | anim.start(self.screen.ids.button) 124 | 125 | 126 | ExampleSnackBar().run() 127 | """ 128 | 129 | from kivy.animation import Animation 130 | from kivy.clock import Clock 131 | from kivy.core.window import Window 132 | from kivy.lang import Builder 133 | from kivy.properties import ObjectProperty, StringProperty, NumericProperty 134 | from kivy.uix.floatlayout import FloatLayout 135 | 136 | from kivymd.uix.button import MDFlatButton 137 | 138 | Builder.load_string( 139 | """ 140 | #:import get_color_from_hex kivy.utils.get_color_from_hex 141 | 142 | 143 | : 144 | 145 | BoxLayout: 146 | id: box 147 | size_hint_y: None 148 | height: dp(58) 149 | spacing: dp(5) 150 | padding: dp(10) 151 | y: -self.height 152 | 153 | canvas: 154 | Color: 155 | rgba: get_color_from_hex('323232') 156 | Rectangle: 157 | pos: self.pos 158 | size: self.size 159 | 160 | MDLabel: 161 | id: text_bar 162 | size_hint_y: None 163 | height: self.texture_size[1] 164 | text: root.text 165 | font_size: root.font_size 166 | theme_text_color: 'Custom' 167 | text_color: get_color_from_hex('ffffff') 168 | shorten: True 169 | shorten_from: 'right' 170 | pos_hint: {'center_y': .5} 171 | """ 172 | ) 173 | 174 | 175 | class Snackbar(FloatLayout): 176 | """A Material Design Snackbar""" 177 | 178 | text = StringProperty() 179 | """The text that will appear in the Snackbar. 180 | 181 | :attr:`text` is a :class:`~kivy.properties.StringProperty` 182 | and defaults to ''. 183 | """ 184 | 185 | font_size = NumericProperty("15sp") 186 | """The font size of the text that will appear in the Snackbar. 187 | 188 | :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` and 189 | defaults to 15sp. 190 | """ 191 | 192 | button_text = StringProperty() 193 | """The text that will appear in the Snackbar's button. 194 | 195 | .. note:: 196 | If this variable is None, the Snackbar will have no button. 197 | 198 | :attr:`button_text` is a :class:`~kivy.properties.StringProperty` 199 | and defaults to ''. 200 | """ 201 | 202 | button_callback = ObjectProperty() 203 | """The callback that will be triggered when the Snackbar's 204 | button is pressed. 205 | 206 | .. note:: 207 | If this variable is None, the Snackbar will have no button. 208 | 209 | :attr:`button_callback` is a :class:`~kivy.properties.ObjectProperty` 210 | and defaults to None. 211 | """ 212 | 213 | duration = NumericProperty(3) 214 | """The amount of time that the Snackbar will stay on screen for. 215 | 216 | :attr:`duration` is a :class:`~kivy.properties.NumericProperty` 217 | and defaults to 3. 218 | """ 219 | 220 | _interval = 0 221 | 222 | def __init__(self, **kwargs): 223 | super().__init__(**kwargs) 224 | if self.button_text != "": 225 | button = MDFlatButton(text=self.button_text) 226 | self.ids.box.add_widget(button) 227 | if self.button_callback: 228 | button.bind(on_release=self.button_callback) 229 | 230 | def show(self): 231 | """Show the Snackbar.""" 232 | 233 | def wait_interval(interval): 234 | self._interval += interval 235 | if self._interval > self.duration: 236 | anim = Animation(y=-self.ids.box.height, d=0.2) 237 | anim.bind( 238 | on_complete=lambda *args: Window.parent.remove_widget(self) 239 | ) 240 | anim.start(self.ids.box) 241 | Clock.unschedule(wait_interval) 242 | self._interval = 0 243 | 244 | Window.parent.add_widget(self) 245 | anim = Animation(y=0, d=0.2) 246 | anim.bind( 247 | on_complete=lambda *args: Clock.schedule_interval(wait_interval, 0) 248 | ) 249 | anim.start(self.ids.box) 250 | -------------------------------------------------------------------------------- /kivymd/uix/expansionpanel.py: -------------------------------------------------------------------------------- 1 | """ 2 | MDExpansionPanel 3 | ================ 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | 16 | Example 17 | ------- 18 | 19 | from kivymd.app import MDApp 20 | from kivy.lang import Builder 21 | from kivy.factory import Factory 22 | from kivy.properties import ObjectProperty 23 | from kivy.uix.boxlayout import BoxLayout 24 | 25 | from kivymd.uix.button import MDIconButton 26 | from kivymd.uix.list import ILeftBodyTouch 27 | from kivymd.uix.expansionpanel import MDExpansionPanel 28 | from kivymd.theming import ThemeManager 29 | from kivymd.toast import toast 30 | 31 | Builder.load_string(''' 32 | #:import get_hex_from_color kivy.utils.get_hex_from_color 33 | 34 | 35 | 36 | orientation: 'vertical' 37 | padding: dp(10) 38 | spacing: dp(10) 39 | size_hint_y: None 40 | height: self.minimum_height 41 | 42 | BoxLayout: 43 | size_hint_y: None 44 | height: self.minimum_height 45 | 46 | Widget: 47 | MDRoundFlatButton: 48 | text: "Free call" 49 | on_press: root.callback(self.text) 50 | Widget: 51 | MDRoundFlatButton: 52 | text: "Free message" 53 | on_press: root.callback(self.text) 54 | Widget: 55 | 56 | OneLineIconListItem: 57 | text: "Video call" 58 | on_press: root.callback(self.text) 59 | IconLeftSampleWidget: 60 | icon: 'camera-front-variant' 61 | 62 | TwoLineIconListItem: 63 | text: "Call Viber Out" 64 | on_press: root.callback(self.text) 65 | secondary_text: 66 | "[color=%s]Advantageous rates for calls[/color]"\ 67 | % get_hex_from_color(app.theme_cls.primary_color) 68 | IconLeftSampleWidget: 69 | icon: 'phone' 70 | 71 | TwoLineIconListItem: 72 | text: "Call over mobile network" 73 | on_press: root.callback(self.text) 74 | secondary_text: 75 | "[color=%s]Operator's tariffs apply[/color]"\ 76 | % get_hex_from_color(app.theme_cls.primary_color) 77 | IconLeftSampleWidget: 78 | icon: 'remote' 79 | 80 | 81 | 82 | orientation: 'vertical' 83 | 84 | MDToolbar: 85 | id: toolbar 86 | title: app.title 87 | md_bg_color: app.theme_cls.primary_color 88 | background_palette: 'Primary' 89 | elevation: 10 90 | left_action_items: [['dots-vertical', lambda x: None]] 91 | 92 | Screen: 93 | 94 | ScrollView: 95 | 96 | GridLayout: 97 | id: anim_list 98 | cols: 1 99 | size_hint_y: None 100 | height: self.minimum_height 101 | ''') 102 | 103 | 104 | class ContentForAnimCard(BoxLayout): 105 | callback = ObjectProperty(lambda x: None) 106 | 107 | 108 | class IconLeftSampleWidget(ILeftBodyTouch, MDIconButton): 109 | pass 110 | 111 | 112 | class Example(MDApp): 113 | title = "Example Expansion Panel" 114 | main_widget = None 115 | 116 | def build(self): 117 | self.main_widget = Factory.ExampleExpansionPanel() 118 | return self.main_widget 119 | 120 | def on_start(self): 121 | def callback(text): 122 | toast(f'{text} to {content.name_item}') 123 | 124 | content = ContentForAnimCard(callback=callback) 125 | names_contacts = ( 126 | 'Alexandr Taylor', 'Yuri Ivanov', 'Robert Patric', 'Bob Marley', 127 | 'Magnus Carlsen', 'Jon Romero', 'Anna Bell', 'Maxim Kramerer', 128 | 'Sasha Gray', 'Vladimir Ivanenko') 129 | 130 | for name_contact in names_contacts: 131 | self.main_widget.ids.anim_list.add_widget( 132 | MDExpansionPanel(content=content, 133 | icon='data/logo/kivy-icon-128.png', 134 | title=name_contact)) 135 | 136 | 137 | Example().run() 138 | """ 139 | 140 | from kivy.lang import Builder 141 | from kivy.animation import Animation 142 | from kivy.metrics import dp 143 | from kivy.properties import ObjectProperty, NumericProperty, StringProperty 144 | from kivy.uix.boxlayout import BoxLayout 145 | from kivy.uix.image import Image 146 | 147 | from kivymd.uix.button import MDIconButton 148 | from kivymd.uix.list import ( 149 | IRightBodyTouch, 150 | OneLineAvatarIconListItem, 151 | ILeftBody, 152 | ) 153 | 154 | Builder.load_string( 155 | """ 156 | 157 | text: root.title 158 | _no_ripple_effect: True 159 | 160 | AvatarLeft: 161 | source: root.icon 162 | 163 | ChevronRight: 164 | id: chevron 165 | icon: 'chevron-right' 166 | disabled: True 167 | 168 | canvas.before: 169 | PushMatrix 170 | Rotate: 171 | angle: self.angle 172 | axis: (0, 0, 1) 173 | origin: self.center 174 | canvas.after: 175 | PopMatrix 176 | 177 | 178 | 179 | size_hint_y: None 180 | height: dp(68) 181 | 182 | BoxLayout: 183 | id: box_item 184 | size_hint_y: None 185 | height: root.height 186 | orientation: 'vertical' 187 | 188 | ExpansionPanel: 189 | id: item_anim 190 | title: root.title 191 | icon: root.icon 192 | on_press: root.check_open_box(self) 193 | """ 194 | ) 195 | 196 | 197 | class AvatarLeft(ILeftBody, Image): 198 | pass 199 | 200 | 201 | class ChevronRight(IRightBodyTouch, MDIconButton): 202 | angle = NumericProperty(0) 203 | 204 | 205 | class ExpansionPanel(OneLineAvatarIconListItem): 206 | title = StringProperty() 207 | icon = StringProperty() 208 | 209 | 210 | class MDExpansionPanel(BoxLayout): 211 | content = ObjectProperty() 212 | icon = StringProperty() 213 | title = StringProperty() 214 | 215 | def __init__(self, **kwargs): 216 | super().__init__(**kwargs) 217 | self.register_event_type("on_open") 218 | self.register_event_type("on_close") 219 | 220 | def on_open(self, *args): 221 | pass 222 | 223 | def on_close(self, *args): 224 | pass 225 | 226 | def check_open_box(self, instance): 227 | press_current_item = False 228 | for box in self.parent.children: 229 | if box.__class__ is MDExpansionPanel: 230 | if len(box.ids.box_item.children) == 2: 231 | if instance is box.ids.item_anim: 232 | press_current_item = True 233 | box.ids.box_item.remove_widget(box.ids.box_item.children[0]) 234 | chevron = box.ids.box_item.children[0].ids.chevron 235 | self.anim_chevron_up(chevron) 236 | self.anim_resize_close(box) 237 | self.dispatch("on_close") 238 | break 239 | 240 | if not press_current_item: 241 | self.anim_chevron_down() 242 | 243 | def anim_chevron_down(self): 244 | chevron = self.ids.item_anim.ids.chevron 245 | angle = -90 246 | Animation(angle=angle, d=0.2).start(chevron) 247 | self.anim_resize_open_item() 248 | self.dispatch("on_open") 249 | 250 | def anim_chevron_up(self, instance): 251 | angle = 0 252 | Animation(angle=angle, d=0.2).start(instance) 253 | 254 | def anim_resize_close(self, box): 255 | Animation(height=dp(68), d=0.1, t="in_cubic").start(box) 256 | 257 | def anim_resize_open_item(self, *args): 258 | self.content.name_item = self.title 259 | anim = Animation( 260 | height=self.content.height + dp(70), d=0.2, t="in_cubic" 261 | ) 262 | anim.bind(on_complete=self.add_content) 263 | anim.start(self) 264 | 265 | def add_content(self, *args): 266 | if self.content: 267 | self.ids.box_item.add_widget(self.content) 268 | -------------------------------------------------------------------------------- /kivymd/uix/behaviors/ripplebehavior.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ripple Behavior 3 | =============== 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | """ 16 | 17 | from kivy.properties import ( 18 | ListProperty, 19 | NumericProperty, 20 | StringProperty, 21 | BooleanProperty, 22 | ) 23 | from kivy.animation import Animation 24 | from kivy.graphics import ( 25 | Color, 26 | Ellipse, 27 | StencilPush, 28 | StencilPop, 29 | StencilUse, 30 | StencilUnUse, 31 | Rectangle, 32 | ) 33 | 34 | 35 | class CommonRipple(object): 36 | ripple_rad = NumericProperty() 37 | ripple_rad_default = NumericProperty(1) 38 | ripple_post = ListProperty() 39 | ripple_color = ListProperty() 40 | ripple_alpha = NumericProperty(0.5) 41 | ripple_scale = NumericProperty(None) 42 | ripple_duration_in_fast = NumericProperty(0.3) 43 | # FIXME: These speeds should be calculated based on widget size in dp 44 | ripple_duration_in_slow = NumericProperty(2) 45 | ripple_duration_out = NumericProperty(0.3) 46 | ripple_func_in = StringProperty("out_quad") 47 | ripple_func_out = StringProperty("out_quad") 48 | 49 | doing_ripple = BooleanProperty(False) 50 | finishing_ripple = BooleanProperty(False) 51 | fading_out = BooleanProperty(False) 52 | _no_ripple_effect = BooleanProperty(False) 53 | 54 | def on_touch_down(self, touch): 55 | if touch.is_mouse_scrolling: 56 | return False 57 | if not self.collide_point(touch.x, touch.y): 58 | return False 59 | 60 | if not self.disabled: 61 | if self.doing_ripple: 62 | Animation.cancel_all( 63 | self, "ripple_rad", "ripple_color", "rect_color" 64 | ) 65 | self.anim_complete() 66 | self.ripple_rad = self.ripple_rad_default 67 | self.ripple_pos = (touch.x, touch.y) 68 | 69 | if self.ripple_color: 70 | pass 71 | elif hasattr(self, "theme_cls"): 72 | self.ripple_color = self.theme_cls.ripple_color 73 | else: 74 | # If no theme, set Gray 300 75 | self.ripple_color = [ 76 | 0.8784313725490196, 77 | 0.8784313725490196, 78 | 0.8784313725490196, 79 | self.ripple_alpha, 80 | ] 81 | self.ripple_color[3] = self.ripple_alpha 82 | 83 | self.lay_canvas_instructions() 84 | self.finish_rad = max(self.width, self.height) * self.ripple_scale 85 | self.start_ripple() 86 | return super().on_touch_down(touch) 87 | 88 | def lay_canvas_instructions(self): 89 | raise NotImplementedError 90 | 91 | def on_touch_move(self, touch, *args): 92 | if not self.collide_point(touch.x, touch.y): 93 | if not self.finishing_ripple and self.doing_ripple: 94 | self.finish_ripple() 95 | return super().on_touch_move(touch, *args) 96 | 97 | def on_touch_up(self, touch): 98 | if self.collide_point(touch.x, touch.y) and self.doing_ripple: 99 | self.finish_ripple() 100 | return super().on_touch_up(touch) 101 | 102 | def start_ripple(self): 103 | if not self.doing_ripple: 104 | anim = Animation( 105 | ripple_rad=self.finish_rad, 106 | t="linear", 107 | duration=self.ripple_duration_in_slow, 108 | ) 109 | anim.bind(on_complete=self.fade_out) 110 | self.doing_ripple = True 111 | anim.start(self) 112 | 113 | def _set_ellipse(self, instance, value): 114 | self.ellipse.size = (self.ripple_rad, self.ripple_rad) 115 | 116 | # Adjust ellipse pos here 117 | 118 | def _set_color(self, instance, value): 119 | self.col_instruction.a = value[3] 120 | 121 | def finish_ripple(self): 122 | if self.doing_ripple and not self.finishing_ripple: 123 | Animation.cancel_all(self, "ripple_rad") 124 | anim = Animation( 125 | ripple_rad=self.finish_rad, 126 | t=self.ripple_func_in, 127 | duration=self.ripple_duration_in_fast, 128 | ) 129 | anim.bind(on_complete=self.fade_out) 130 | self.finishing_ripple = True 131 | anim.start(self) 132 | 133 | def fade_out(self, *args): 134 | rc = self.ripple_color 135 | if not self.fading_out: 136 | Animation.cancel_all(self, "ripple_color") 137 | anim = Animation( 138 | ripple_color=[rc[0], rc[1], rc[2], 0.0], 139 | t=self.ripple_func_out, 140 | duration=self.ripple_duration_out, 141 | ) 142 | anim.bind(on_complete=self.anim_complete) 143 | self.fading_out = True 144 | anim.start(self) 145 | 146 | def anim_complete(self, *args): 147 | self.doing_ripple = False 148 | self.finishing_ripple = False 149 | self.fading_out = False 150 | self.canvas.after.clear() 151 | 152 | 153 | class RectangularRippleBehavior(CommonRipple): 154 | ripple_scale = NumericProperty(2.75) 155 | 156 | def lay_canvas_instructions(self): 157 | if self._no_ripple_effect: 158 | return 159 | with self.canvas.after: 160 | StencilPush() 161 | Rectangle(pos=self.pos, size=self.size) 162 | StencilUse() 163 | self.col_instruction = Color(rgba=self.ripple_color) 164 | self.ellipse = Ellipse( 165 | size=(self.ripple_rad, self.ripple_rad), 166 | pos=( 167 | self.ripple_pos[0] - self.ripple_rad / 2.0, 168 | self.ripple_pos[1] - self.ripple_rad / 2.0, 169 | ), 170 | ) 171 | StencilUnUse() 172 | Rectangle(pos=self.pos, size=self.size) 173 | StencilPop() 174 | self.bind(ripple_color=self._set_color, ripple_rad=self._set_ellipse) 175 | 176 | def _set_ellipse(self, instance, value): 177 | super()._set_ellipse(instance, value) 178 | self.ellipse.pos = ( 179 | self.ripple_pos[0] - self.ripple_rad / 2.0, 180 | self.ripple_pos[1] - self.ripple_rad / 2.0, 181 | ) 182 | 183 | 184 | class CircularRippleBehavior(CommonRipple): 185 | ripple_scale = NumericProperty(1) 186 | 187 | def lay_canvas_instructions(self): 188 | with self.canvas.after: 189 | StencilPush() 190 | self.stencil = Ellipse( 191 | size=( 192 | self.width * self.ripple_scale, 193 | self.height * self.ripple_scale, 194 | ), 195 | pos=( 196 | self.center_x - (self.width * self.ripple_scale) / 2, 197 | self.center_y - (self.height * self.ripple_scale) / 2, 198 | ), 199 | ) 200 | StencilUse() 201 | self.col_instruction = Color(rgba=self.ripple_color) 202 | self.ellipse = Ellipse( 203 | size=(self.ripple_rad, self.ripple_rad), 204 | pos=( 205 | self.center_x - self.ripple_rad / 2.0, 206 | self.center_y - self.ripple_rad / 2.0, 207 | ), 208 | ) 209 | StencilUnUse() 210 | Ellipse(pos=self.pos, size=self.size) 211 | StencilPop() 212 | self.bind( 213 | ripple_color=self._set_color, ripple_rad=self._set_ellipse 214 | ) 215 | 216 | def _set_ellipse(self, instance, value): 217 | super()._set_ellipse(instance, value) 218 | if self.ellipse.size[0] > self.width * 0.6 and not self.fading_out: 219 | self.fade_out() 220 | self.ellipse.pos = ( 221 | self.center_x - self.ripple_rad / 2.0, 222 | self.center_y - self.ripple_rad / 2.0, 223 | ) 224 | -------------------------------------------------------------------------------- /kivymd/uix/tooltip.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tooltip 3 | ======= 4 | 5 | Tooltips display informative text when users hover over, focus on, 6 | or tap an element. 7 | 8 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 9 | KivyMD library up to version 0.1.2 10 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 11 | KivyMD library version 0.1.3 and higher 12 | 13 | For suggestions and questions: 14 | 15 | 16 | This file is distributed under the terms of the same license, 17 | as the Kivy framework. 18 | 19 | `Material Design spec, Menus `_ 20 | 21 | Example 22 | ------- 23 | 24 | from kivy.lang import Builder 25 | from kivy.factory import Factory 26 | 27 | from kivymd.app import MDApp 28 | 29 | Builder.load_string(''' 30 | #:import random random 31 | #:import hex_colormap kivy.utils.hex_colormap 32 | #:import get_color_from_hex kivy.utils.get_color_from_hex 33 | #:import md_icons kivymd.icon_definitions.md_icons 34 | 35 | #:set ICONS list(md_icons.keys()) 36 | 37 | 38 | 39 | 40 | 41 | 42 | orientation: 'vertical' 43 | 44 | MDToolbar: 45 | title: "Example Tooltips" 46 | md_bg_color: get_color_from_hex(hex_colormap["crimson"]) 47 | elevation: 10 48 | left_action_items: [['dots-vertical', lambda x: None]] 49 | tooltip_text: "MDToolbar" 50 | 51 | Screen: 52 | 53 | BoxLayout: 54 | size_hint: None, None 55 | size: self.minimum_size 56 | padding: "10dp" 57 | spacing: "10dp" 58 | pos_hint: {'center_x': .5, "center_y": .9} 59 | 60 | IconButtonTooltips: 61 | icon: random.choice(ICONS) 62 | tooltip_text: "MDIconButton" 63 | IconButtonTooltips: 64 | icon: random.choice(ICONS) 65 | tooltip_text: "MDIconButton" 66 | IconButtonTooltips: 67 | icon: random.choice(ICONS) 68 | tooltip_text: "MDIconButton" 69 | IconButtonTooltips: 70 | icon: random.choice(ICONS) 71 | tooltip_text: "MDIconButton" 72 | IconButtonTooltips: 73 | icon: random.choice(ICONS) 74 | tooltip_text: "MDIconButton" 75 | IconButtonTooltips: 76 | icon: random.choice(ICONS) 77 | tooltip_text: "MDIconButton" 78 | ''') 79 | 80 | 81 | class Test(MDApp): 82 | def build(self): 83 | return Factory.ExampleTooltips() 84 | 85 | 86 | Test().run() 87 | """ 88 | 89 | __all__ = ( 90 | "MDTooltip", 91 | "MDTooltipViewClass", 92 | ) 93 | 94 | from functools import partial 95 | 96 | from kivy.clock import Clock 97 | from kivy.animation import Animation 98 | from kivy.core.window import Window 99 | from kivy.metrics import dp 100 | from kivy.lang import Builder 101 | from kivy.uix.boxlayout import BoxLayout 102 | from kivy.properties import ListProperty, StringProperty, NumericProperty 103 | 104 | from kivymd.theming import ThemableBehavior 105 | from kivymd.uix.behaviors import HoverBehavior 106 | from kivymd.material_resources import DEVICE_TYPE 107 | 108 | Builder.load_string( 109 | """ 110 | #:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE 111 | 112 | 113 | 114 | size_hint: None, None 115 | width: self.minimum_width 116 | height: self.minimum_height + root.padding[1] 117 | opacity: 0 118 | 119 | padding: 120 | dp(8) if DEVICE_TYPE == "desktop" else dp(16), \ 121 | dp(4), \ 122 | dp(8) if DEVICE_TYPE == "desktop" else dp(16), \ 123 | dp(4) 124 | 125 | canvas.before: 126 | PushMatrix 127 | Color: 128 | rgba: 129 | root.theme_cls.opposite_bg_dark if not root.tooltip_bg_color \ 130 | else root.tooltip_bg_color 131 | RoundedRectangle: 132 | pos: self.pos 133 | size: self.size 134 | radius: [5] 135 | Scale: 136 | origin: self.center 137 | x: root._scale_x 138 | y: root._scale_y 139 | canvas.after: 140 | PopMatrix 141 | 142 | 143 | Label: 144 | id: label_tooltip 145 | text: root.tooltip_text 146 | size_hint: None, None 147 | size: self.texture_size 148 | bold: True 149 | color: 150 | ([0, 0, 0, 1] if not root.tooltip_text_color else root.tooltip_text_color) \ 151 | if root.theme_cls.theme_style == "Dark" else \ 152 | ([1, 1, 1, 1] if not root.tooltip_text_color else root.tooltip_text_color) 153 | pos_hint: {"center_y": .5} 154 | """ 155 | ) 156 | 157 | 158 | class MDTooltipViewClass(ThemableBehavior, BoxLayout): 159 | tooltip_bg_color = ListProperty() 160 | tooltip_text_color = ListProperty() 161 | tooltip_text = StringProperty() 162 | 163 | _scale_x = NumericProperty(0) 164 | _scale_y = NumericProperty(0) 165 | 166 | 167 | class MDTooltip(ThemableBehavior, HoverBehavior, BoxLayout): 168 | tooltip_bg_color = ListProperty() 169 | """Tooltip background color.""" 170 | 171 | tooltip_text_color = ListProperty() 172 | """Tooltip text color.""" 173 | 174 | tooltip_text = StringProperty() 175 | """Tooltip text.""" 176 | 177 | duration_long_touch = NumericProperty(0.4) 178 | """Time for a long touch until a tooltip appears. 179 | Used only on mobile devices.""" 180 | 181 | _tooltip = None 182 | 183 | def __init__(self, **kwargs): 184 | super().__init__(**kwargs) 185 | self.bind( 186 | on_touch_down=self.create_clock, on_touch_up=self.delete_clock, 187 | ) 188 | 189 | # Methods `create_clock` and `delete_clock` taken from this source - 190 | # https://github.com/kivy/kivy/wiki/Menu-on-long-touch 191 | 192 | def create_clock(self, widget, touch, *args): 193 | if self.collide_point(touch.x, touch.y): 194 | callback = partial(self.on_long_touch, touch) 195 | Clock.schedule_once(callback, self.duration_long_touch) 196 | touch.ud["event"] = callback 197 | 198 | def delete_clock(self, widget, touch, *args): 199 | if self.collide_point(touch.x, touch.y): 200 | try: 201 | Clock.unschedule(touch.ud["event"]) 202 | except KeyError: 203 | pass 204 | self.on_leave() 205 | 206 | def adjust_tooltip_position(self, x, y): 207 | """Returns the coordinates of the tooltip 208 | that fit into the borders of the screen.""" 209 | 210 | # If the position of the tooltip is outside the right border of the screen. 211 | if x + self._tooltip.width > Window.width: 212 | x = Window.width - (self._tooltip.width + dp(10)) 213 | else: 214 | # If the position of the tooltip is outside the left border of the screen. 215 | if x < 0: 216 | x = "10dp" 217 | # If the tooltip position is below bottom the screen border. 218 | if y < 0: 219 | y = dp(10) 220 | # If the tooltip position is below top the screen border. 221 | else: 222 | if Window.height - self._tooltip.height < y: 223 | y = Window.height - (self._tooltip.height + dp(10)) 224 | return x, y 225 | 226 | def display_tooltip(self, interval): 227 | if not self._tooltip: 228 | return 229 | Window.add_widget(self._tooltip) 230 | pos = self.to_window(self.center_x, self.center_y) 231 | x = pos[0] - self._tooltip.width / 2 232 | y = pos[1] - self._tooltip.height / 2 - self.height / 2 - dp(20) 233 | x, y = self.adjust_tooltip_position(x, y) 234 | self._tooltip.pos = (x, y) 235 | Clock.schedule_once(self.animation_tooltip_show, 0) 236 | 237 | def animation_tooltip_show(self, interval): 238 | if not self._tooltip: 239 | return 240 | ( 241 | Animation(_scale_x=1, _scale_y=1, d=0.1) 242 | + Animation(opacity=1, d=0.2) 243 | ).start(self._tooltip) 244 | 245 | def remove_tooltip(self, *args): 246 | Window.remove_widget(self._tooltip) 247 | 248 | def on_long_touch(self, touch, *args): 249 | if DEVICE_TYPE != "desktop": 250 | self.on_enter(True) 251 | 252 | def on_enter(self, *args): 253 | """See method `on_enter` 254 | in `kivymd.uix.behaviors.hover_behavior.HoverBehavior` class.""" 255 | 256 | if not args and DEVICE_TYPE != "desktop": 257 | return 258 | else: 259 | if not self.tooltip_text: 260 | return 261 | self._tooltip = MDTooltipViewClass( 262 | tooltip_bg_color=self.tooltip_bg_color, 263 | tooltip_text_color=self.tooltip_text_color, 264 | tooltip_text=self.tooltip_text, 265 | ) 266 | Clock.schedule_once(self.display_tooltip, -1) 267 | 268 | def on_leave(self): 269 | """See method `on_leave` 270 | in `kivymd.uix.behaviors.hover_behavior.HoverBehavior` class.""" 271 | 272 | if self._tooltip: 273 | Window.remove_widget(self._tooltip) 274 | self._tooltip = None 275 | -------------------------------------------------------------------------------- /kivymd/uix/chip.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chips 3 | ===== 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | 13 | `Material Design spec, Chips `_ 14 | 15 | Example 16 | ------- 17 | 18 | from kivymd.app import MDApp 19 | from kivy.lang import Builder 20 | 21 | from kivymd.theming import ThemeManager 22 | 23 | kv = ''' 24 | BoxLayout: 25 | orientation: 'vertical' 26 | spacing: dp(10) 27 | 28 | MDToolbar: 29 | title: 'Example Chips' 30 | md_bg_color: app.theme_cls.primary_color 31 | left_action_items: [['menu', lambda x: x]] 32 | background_palette: 'Primary' 33 | 34 | ScrollView: 35 | 36 | GridLayout: 37 | padding: dp(10) 38 | spacing: dp(10) 39 | cols: 1 40 | size_hint_y: None 41 | height: self.minimum_height 42 | 43 | MDLabel: 44 | text: 'Chips with color:' 45 | 46 | MDSeparator: 47 | 48 | StackLayout: 49 | size_hint_y: None 50 | height: self.minimum_height 51 | spacing: dp(5) 52 | 53 | MDChip: 54 | label: 'Coffee' 55 | color: .4470588235294118, .19607843137254902, 0, 1 56 | icon: 'coffee' 57 | callback: app.callback 58 | 59 | MDChip: 60 | label: 'Duck' 61 | color: .9215686274509803, 0, 0, 1 62 | icon: 'duck' 63 | callback: app.callback 64 | 65 | MDChip: 66 | label: 'Earth' 67 | color: .21176470588235294, .09803921568627451, 1, 1 68 | icon: 'earth' 69 | callback: app.callback 70 | 71 | MDChip: 72 | label: 'Face' 73 | color: .20392156865098, .48235294117606, .43529411764705883, 1 74 | icon: 'face' 75 | callback: app.callback 76 | 77 | MDChip: 78 | label: 'Facebook' 79 | color: .5607843137254902, .48235294164706, .435294117705883, 1 80 | icon: 'facebook' 81 | callback: app.callback 82 | 83 | Widget: 84 | size_hint_y: None 85 | height: dp(5) 86 | 87 | MDLabel: 88 | text: 'Chip without icon:' 89 | 90 | MDSeparator: 91 | 92 | StackLayout: 93 | size_hint_y: None 94 | height: self.minimum_height 95 | spacing: dp(5) 96 | 97 | MDChip: 98 | label: 'Without icon' 99 | icon: '' 100 | callback: app.callback 101 | 102 | Widget: 103 | size_hint_y: None 104 | height: dp(5) 105 | 106 | MDLabel: 107 | text: 'Chips with check:' 108 | 109 | MDSeparator: 110 | 111 | StackLayout: 112 | size_hint_y: None 113 | height: self.minimum_height 114 | spacing: dp(5) 115 | 116 | MDChip: 117 | label: 'Check' 118 | icon: '' 119 | check: True 120 | callback: app.callback 121 | 122 | MDChip: 123 | label: 'Check with icon' 124 | icon: 'city' 125 | check: True 126 | callback: app.callback 127 | Widget: 128 | size_hint_y: None 129 | height: dp(5) 130 | 131 | MDLabel: 132 | text: 'Choose chip:' 133 | 134 | MDSeparator: 135 | 136 | MDChooseChip: 137 | 138 | MDChip: 139 | label: 'Earth' 140 | icon: 'earth' 141 | callback: app.callback 142 | 143 | MDChip: 144 | label: 'Face' 145 | icon: 'face' 146 | callback: app.callback 147 | 148 | MDChip: 149 | label: 'Facebook' 150 | icon: 'facebook' 151 | callback: app.callback 152 | ''' 153 | 154 | 155 | class MyApp(MDApp): 156 | 157 | def callback(self, name_chip): 158 | pass 159 | 160 | def build(self): 161 | return Builder.load_string(kv) 162 | 163 | 164 | MyApp().run() 165 | """ 166 | from kivy.animation import Animation 167 | from kivy.metrics import dp 168 | from kivy.properties import ( 169 | StringProperty, 170 | ListProperty, 171 | ObjectProperty, 172 | BooleanProperty, 173 | NumericProperty, 174 | ) 175 | from kivy.uix.boxlayout import BoxLayout 176 | from kivy.lang import Builder 177 | from kivy.uix.stacklayout import StackLayout 178 | 179 | from kivymd.uix.button import MDIconButton 180 | from kivymd.theming import ThemableBehavior 181 | 182 | Builder.load_string( 183 | """ 184 | #:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE 185 | 186 | 187 | 188 | size_hint_y: None 189 | height: self.minimum_height 190 | spacing: "5dp" 191 | 192 | 193 | 194 | size_hint: None, None 195 | height: "26dp" 196 | padding: 0, 0, "5dp", 0 197 | width: 198 | self.minimum_width - (dp(10) if DEVICE_TYPE == "desktop" else dp(20)) \ 199 | if root.icon != 'checkbox-blank-circle' else self.minimum_width 200 | 201 | canvas: 202 | Color: 203 | rgba: root.color 204 | RoundedRectangle: 205 | pos: self.pos 206 | size: self.size 207 | radius: [root.radius] 208 | 209 | BoxLayout: 210 | id: box_check 211 | size_hint: None, None 212 | size: self.minimum_size 213 | pos_hint: {'center_y': .5} 214 | 215 | BoxLayout: 216 | size_hint_x: None 217 | width: self.minimum_width 218 | padding: dp(10) 219 | 220 | Label: 221 | id: label 222 | text: root.label 223 | size_hint_x: None 224 | width: self.texture_size[0] 225 | 226 | MDIconButton: 227 | id: icon 228 | icon: root.icon 229 | size_hint_y: None 230 | height: "20dp" 231 | pos_hint: {"center_y": .5} 232 | user_font_size: "20dp" 233 | disabled: True 234 | """ 235 | ) 236 | 237 | 238 | class MDChip(BoxLayout, ThemableBehavior): 239 | label = StringProperty() 240 | """`MDChip` text.""" 241 | 242 | icon = StringProperty("checkbox-blank-circle") 243 | """`MDChip` icon.""" 244 | 245 | color = ListProperty() 246 | """`MDChip` color.""" 247 | 248 | check = BooleanProperty(False) 249 | """If True, a checkmark is added to the left when touch to the chip.""" 250 | 251 | callback = ObjectProperty() 252 | """Custom method.""" 253 | 254 | radius = NumericProperty(dp(12)) 255 | """Corner radius values.""" 256 | 257 | selected_chip_color = ListProperty() 258 | """The color of the chip that is currently selected.""" 259 | 260 | def __init__(self, **kwargs): 261 | super().__init__(**kwargs) 262 | if not self.color: 263 | self.color = self.theme_cls.primary_color 264 | 265 | def on_icon(self, instance, value): 266 | if value == "": 267 | self.icon = "checkbox-blank-circle" 268 | self.remove_widget(self.ids.icon) 269 | 270 | def on_touch_down(self, touch): 271 | if self.collide_point(*touch.pos): 272 | md_choose_chip = self.parent 273 | if self.selected_chip_color: 274 | Animation( 275 | color=self.theme_cls.primary_dark 276 | if not self.selected_chip_color 277 | else self.selected_chip_color, 278 | d=0.3, 279 | ).start(self) 280 | if issubclass(md_choose_chip.__class__, MDChooseChip): 281 | for chip in md_choose_chip.children: 282 | if chip is not self: 283 | chip.color = self.theme_cls.primary_color 284 | if self.check: 285 | if not len(self.ids.box_check.children): 286 | self.ids.box_check.add_widget( 287 | MDIconButton( 288 | icon="check", 289 | size_hint_y=None, 290 | height=dp(20), 291 | disabled=True, 292 | user_font_size=dp(20), 293 | pos_hint={"center_y": 0.5}, 294 | ) 295 | ) 296 | else: 297 | check = self.ids.box_check.children[0] 298 | self.ids.box_check.remove_widget(check) 299 | if self.callback: 300 | self.callback(self, self.label) 301 | 302 | 303 | class MDChooseChip(StackLayout): 304 | def add_widget(self, widget, index=0, canvas=None): 305 | if isinstance(widget, MDChip): 306 | return super().add_widget(widget) 307 | -------------------------------------------------------------------------------- /kivymd/uix/managerswiper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manager Swiper 3 | ============== 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | 13 | Example 14 | ------- 15 | 16 | import os 17 | 18 | from kivymd.app import MDApp 19 | from kivy.core.window import Window 20 | from kivy.lang import Builder 21 | from kivy.metrics import dp 22 | from kivy.properties import StringProperty 23 | from kivy.uix.boxlayout import BoxLayout 24 | 25 | from kivymd.uix.card import MDCard 26 | from kivymd.uix.managerswiper import MDSwiperPagination 27 | from kivymd.theming import ThemeManager 28 | 29 | activity = ''' 30 | #:import images_path kivymd.images_path 31 | 32 | 33 | 34 | orientation: 'vertical' 35 | size_hint_y: None 36 | height: dp(300) 37 | pos_hint: {'top': 1} 38 | 39 | FitImage: 40 | source: 41 | f'{app.directory}/demos/kitchen_sink/assets/'\ 42 | f'guitar-1139397_1280.png' 43 | size_hint: None, None 44 | size: root.width, dp(250) 45 | pos_hint: {'top': 1} 46 | 47 | MDLabel: 48 | theme_text_color: 'Custom' 49 | bold: True 50 | text_color: app.theme_cls.primary_color 51 | text: root.text 52 | size_hint_y: None 53 | height: dp(60) 54 | halign: 'center' 55 | 56 | 57 | 58 | name: 'screen one' 59 | MyCard: 60 | text: 'Swipe to switch to screen one'.upper() 61 | 62 | 63 | 64 | name: 'screen two' 65 | MyCard: 66 | text: 'Swipe to switch to screen two'.upper() 67 | 68 | 69 | 70 | name: 'screen three' 71 | MyCard: 72 | text: 'Swipe to switch to screen three'.upper() 73 | 74 | 75 | 76 | name: 'screen four' 77 | MyCard: 78 | text: 'Swipe to switch to screen four'.upper() 79 | 80 | 81 | 82 | name: 'screen five' 83 | MyCard: 84 | text: 'Swipe to switch to screen five'.upper() 85 | 86 | 87 | 88 | orientation: 'vertical' 89 | 90 | canvas: 91 | Color: 92 | rgba: 0, 0, 0, .2 93 | Rectangle: 94 | pos: self.pos 95 | size: self.size 96 | 97 | MDToolbar: 98 | id: toolbar 99 | title: 'Swiper Manager' 100 | md_bg_color: app.theme_cls.primary_color 101 | background_palette: 'Primary' 102 | elevation: 10 103 | left_action_items: [['menu', lambda x: x]] 104 | 105 | BoxLayout: 106 | padding: dp(10) 107 | orientation: 'vertical' 108 | 109 | MDSwiperManager: 110 | id: swiper_manager 111 | 112 | ScreenOne: 113 | 114 | ScreenTwo: 115 | 116 | ScreenThree: 117 | 118 | ScreenFour: 119 | 120 | ScreenFive: 121 | ''' 122 | 123 | 124 | class MySwiperManager(BoxLayout): 125 | pass 126 | 127 | 128 | class MyCard(MDCard): 129 | text = StringProperty('') 130 | 131 | 132 | class Test(MDApp): 133 | swiper_manager = None 134 | 135 | def build(self): 136 | Builder.load_string(activity) 137 | start_screen = MySwiperManager() 138 | self.swiper_manager = start_screen.ids.swiper_manager 139 | paginator = MDSwiperPagination() 140 | paginator.screens = self.swiper_manager.screen_names 141 | paginator.manager = self.swiper_manager 142 | self.swiper_manager.paginator = paginator 143 | start_screen.add_widget(paginator) 144 | 145 | return start_screen 146 | 147 | 148 | if __name__ == '__main__': 149 | Test().run() 150 | """ 151 | 152 | from kivy.properties import NumericProperty, ObjectProperty, ListProperty 153 | from kivy.uix.boxlayout import BoxLayout 154 | from kivy.uix.screenmanager import ScreenManager, SlideTransition 155 | from kivy.core.window import Window 156 | from kivy.lang import Builder 157 | from kivy.uix.widget import Widget 158 | from kivy.animation import Animation 159 | 160 | from kivymd.uix.navigationdrawer import NavigationLayout 161 | from kivymd.theming import ThemableBehavior 162 | 163 | Builder.load_string( 164 | """ 165 | 166 | size_hint: None, None 167 | size: dp(15), dp(15) 168 | pos_hint: {'center_y': .5} 169 | 170 | canvas: 171 | Color: 172 | rgba: 173 | self.theme_cls.primary_color\ 174 | if root.current_index == 0 else root.color_round_not_active 175 | RoundedRectangle: 176 | pos: self.pos 177 | size: self.size 178 | 179 | 180 | 181 | padding: dp(5) 182 | size_hint: None, None 183 | width: self.minimum_width 184 | pos_hint: {'center_x': .5} 185 | height: dp(56) 186 | 187 | MDIconButton: 188 | icon: 'chevron-left' 189 | theme_text_color: 'Custom' 190 | text_color: app.theme_cls.primary_color 191 | on_release: root.manager.swith_screen('right') 192 | 193 | BoxLayout: 194 | id: box 195 | spacing: dp(5) 196 | size_hint_x: None 197 | width: self.minimum_width 198 | 199 | MDIconButton: 200 | theme_text_color: 'Custom' 201 | text_color: app.theme_cls.primary_color 202 | icon: 'chevron-right' 203 | on_release: root.manager.swith_screen('left') 204 | """ 205 | ) 206 | 207 | 208 | class ItemPagination(ThemableBehavior, Widget): 209 | current_index = NumericProperty(0) 210 | color_round_not_active = ListProperty() 211 | 212 | def __init__(self, **kwargs): 213 | super().__init__(**kwargs) 214 | if not self.color_round_not_active: 215 | self.color_round_not_active = self.theme_cls.primary_light 216 | 217 | 218 | class MDSwiperPagination(ThemableBehavior, BoxLayout): 219 | screens = ListProperty() 220 | items_round_paginator = ListProperty() 221 | manager = ObjectProperty() 222 | 223 | def on_screens(self, instance, screen_names): 224 | self.items_round_paginator = [] 225 | self.ids.box.clear_widgets() 226 | for i, screen_name in enumerate(screen_names): 227 | item_paginator = ItemPagination(current_index=i) 228 | self.ids.box.add_widget(item_paginator) 229 | self.items_round_paginator.append(item_paginator) 230 | 231 | def set_current_screen_round(self, index_screen): 232 | old_color = self.items_round_paginator[ 233 | index_screen 234 | ].color_round_not_active 235 | for i, screen in enumerate(self.items_round_paginator): 236 | if i == index_screen: 237 | self.animation_set_not_active_round( 238 | screen.canvas.children[0], self.theme_cls.primary_color 239 | ) 240 | else: 241 | self.animation_set_not_active_round( 242 | screen.canvas.children[0], old_color 243 | ) 244 | 245 | def animation_set_not_active_round(self, instance, color): 246 | Animation(rgba=color, d=0.3).start(instance) 247 | 248 | 249 | class MDSwiperManager(ScreenManager): 250 | swipe = False 251 | index_screen = NumericProperty(0) 252 | paginator = ObjectProperty() 253 | _x = 0 254 | 255 | def __init__(self, **kwargs): 256 | super().__init__(**kwargs) 257 | self.transition = SlideTransition() 258 | self.transition.on_complete = self.on_complete 259 | 260 | def on_complete(self): 261 | self.transition.screen_in.pos = self.pos 262 | self.transition.screen_out.pos = self.pos 263 | super(SlideTransition, self.transition).on_complete() 264 | self.swipe = False 265 | 266 | def swith_screen(self, direction): 267 | if direction == "right": 268 | if self.index_screen == 0: 269 | self.index_screen = len(self.screen_names) - 1 270 | else: 271 | self.index_screen -= 1 272 | else: 273 | self.index_screen += 1 274 | if self.index_screen >= len(self.screen_names) and direction != "right": 275 | self.index_screen = 0 276 | self.transition.direction = direction 277 | self.current = self.screen_names[self.index_screen] 278 | if self.paginator: 279 | self.paginator.set_current_screen_round(self.index_screen) 280 | 281 | def on_touch_move(self, touch): 282 | if self.collide_point(*touch.pos) and not self.swipe: 283 | # When the Navigation panel is open and 284 | # the list of its menu is scrolled, 285 | # the event is also processed on the cards 286 | for widget in Window.children: 287 | if widget.__class__ is NavigationLayout: 288 | if widget.state == "open": 289 | return 290 | if touch.x < Window.width - 10: 291 | if self._x > touch.x: 292 | direction = "left" 293 | else: 294 | direction = "right" 295 | self.swipe = True 296 | self.swith_screen(direction) 297 | self._x = 0 298 | return super().on_touch_move(touch) 299 | 300 | def on_touch_down(self, touch): 301 | if self.collide_point(*touch.pos): 302 | self._x = touch.x 303 | return super().on_touch_down(touch) 304 | -------------------------------------------------------------------------------- /kivymd/uix/navigationdrawer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Navigation Drawer 3 | ================= 4 | 5 | Copyright (c) 2019 Ivanov Yuri 6 | 7 | For suggestions and questions: 8 | 9 | 10 | This file is distributed under the terms of the same license, 11 | as the Kivy framework. 12 | 13 | `Material Design spec, Navigation drawer ` 14 | 15 | Example 16 | ------- 17 | 18 | from kivy.uix.boxlayout import BoxLayout 19 | 20 | from kivymd.app import MDApp 21 | from kivy.lang import Builder 22 | from kivy.properties import StringProperty 23 | 24 | from kivymd.uix.list import OneLineAvatarListItem 25 | 26 | KV = ''' 27 | #:import IconLeftWidget kivymd.uix.list.IconLeftWidget 28 | #:import images_path kivymd.images_path 29 | 30 | 31 | 32 | theme_text_color: 'Custom' 33 | divider: None 34 | 35 | IconLeftWidget: 36 | icon: root.icon 37 | 38 | 39 | 40 | 41 | BoxLayout: 42 | orientation: 'vertical' 43 | 44 | FloatLayout: 45 | size_hint_y: None 46 | height: "200dp" 47 | 48 | canvas: 49 | Color: 50 | rgba: app.theme_cls.primary_color 51 | Rectangle: 52 | pos: self.pos 53 | size: self.size 54 | 55 | BoxLayout: 56 | id: top_box 57 | size_hint_y: None 58 | height: "200dp" 59 | #padding: "10dp" 60 | x: root.parent.x 61 | pos_hint: {"top": 1} 62 | 63 | FitImage: 64 | source: f"{images_path}kivymd_alpha.png" 65 | 66 | MDIconButton: 67 | icon: "close" 68 | x: root.parent.x + dp(10) 69 | pos_hint: {"top": 1} 70 | on_release: root.parent.toggle_nav_drawer() 71 | 72 | MDLabel: 73 | markup: True 74 | text: "[b]KivyMD[/b]\\nVersion: 0.102.1" 75 | #pos_hint: {'center_y': .5} 76 | x: root.parent.x + dp(10) 77 | y: root.height - top_box.height + dp(10) 78 | size_hint_y: None 79 | height: self.texture_size[1] 80 | 81 | ScrollView: 82 | pos_hint: {"top": 1} 83 | 84 | GridLayout: 85 | id: box_item 86 | cols: 1 87 | size_hint_y: None 88 | height: self.minimum_height 89 | 90 | 91 | Screen: 92 | 93 | NavigationLayout: 94 | 95 | ScreenManager: 96 | 97 | Screen: 98 | 99 | BoxLayout: 100 | orientation: 'vertical' 101 | 102 | MDToolbar: 103 | title: "Navigation Drawer" 104 | md_bg_color: app.theme_cls.primary_color 105 | elevation: 10 106 | left_action_items: [['menu', lambda x: nav_drawer.toggle_nav_drawer()]] 107 | 108 | Widget: 109 | 110 | 111 | MDNavigationDrawer: 112 | id: nav_drawer 113 | 114 | ContentNavigationDrawer: 115 | id: content_drawer 116 | 117 | ''' 118 | 119 | 120 | class ContentNavigationDrawer(BoxLayout): 121 | pass 122 | 123 | 124 | class NavigationItem(OneLineAvatarListItem): 125 | icon = StringProperty() 126 | 127 | 128 | class TestNavigationDrawer(MDApp): 129 | def build(self): 130 | return Builder.load_string(KV) 131 | 132 | def on_start(self): 133 | for items in { 134 | "home-circle-outline": "Home", 135 | "update": "Check for Update", 136 | "settings-outline": "Settings", 137 | "exit-to-app": "Exit", 138 | }.items(): 139 | self.root.ids.content_drawer.ids.box_item.add_widget( 140 | NavigationItem( 141 | text=items[1], 142 | icon=items[0], 143 | ) 144 | ) 145 | 146 | 147 | TestNavigationDrawer().run() 148 | 149 | """ 150 | 151 | from kivy.animation import Animation 152 | from kivy.core.window import Window 153 | from kivy.graphics.context_instructions import Color 154 | from kivy.graphics.vertex_instructions import Rectangle 155 | from kivy.lang import Builder 156 | from kivy.metrics import dp 157 | from kivy.properties import NumericProperty, StringProperty 158 | from kivy.uix.floatlayout import FloatLayout 159 | from kivy.uix.screenmanager import ScreenManager 160 | 161 | from kivymd.uix.card import MDCard 162 | from kivymd.uix.toolbar import MDToolbar 163 | 164 | Builder.load_string( 165 | """ 166 | #:import Window kivy.core.window.Window 167 | 168 | 169 | 170 | size_hint: None, None 171 | width: root.side_panel_width 172 | height: Window.height 173 | drawer_x: 0 174 | elevation: 10 175 | x: self.drawer_x - self.width 176 | """ 177 | ) 178 | 179 | 180 | class NavigationDrawerContentError(Exception): 181 | pass 182 | 183 | 184 | class NavigationLayout(FloatLayout): 185 | _cache = [] 186 | _color = None 187 | _rectangle = None 188 | 189 | def add_canvas(self, widget): 190 | with widget.canvas.after: 191 | self._color = Color(rgba=[0, 0, 0, 0]) 192 | self._rectangle = Rectangle(pos=widget.pos, size=widget.size) 193 | widget.bind(pos=self.update_rect, size=self.update_rect) 194 | 195 | def update_rect(self, *args): 196 | self._rectangle.pos = self.pos 197 | self._rectangle.size = self.size 198 | 199 | def add_widget(self, widget, index=0, canvas=None): 200 | """Only two layouts are allowed: 201 | ScreenManager and MDNavigationDrawer. 202 | 203 | """ 204 | 205 | if ( 206 | widget.__class__ is MDNavigationDrawer 207 | or widget.__class__ is ScreenManager 208 | or widget.__class__ is MDToolbar 209 | ): 210 | if widget.__class__ is ScreenManager: 211 | self.add_canvas(widget) 212 | self._cache.append(widget) 213 | if len(self._cache) > 3: 214 | raise NavigationDrawerContentError( 215 | "The NavigationLayoutNew should contain " 216 | "only MDNavigationDrawer class and only ScreenManager class" 217 | ) 218 | return super().add_widget(widget) 219 | 220 | 221 | class MDNavigationDrawer(MDCard): 222 | side_panel_width = ( 223 | (dp(320) * 80) // 100 if dp(320) >= Window.width else dp(320) 224 | ) 225 | """The width of the hidden side panel. Defaults to the minimum of 226 | 320dp or half the NavigationDrawer width.""" 227 | 228 | anim_time = NumericProperty(0.2) 229 | """The time taken for the panel to slide to the open/closed state when 230 | released or manually animated with anim_to_state.""" 231 | 232 | opening_transition = StringProperty("out_cubic") 233 | """The name of the animation transition type to use when animating to 234 | an open state. Defaults to 'out_cubic'.""" 235 | 236 | closing_transition = StringProperty("out_sine") 237 | """The name of the animation transition type to use when animating to 238 | a closed state. Defaults to 'out_sine'.""" 239 | 240 | swipe_distance = NumericProperty(10) 241 | """The size of the area with which the movement of navigation drawer begins.""" 242 | 243 | state = StringProperty("close") 244 | """Closed or open panel.""" 245 | 246 | _count_distance = False 247 | _direction = "unknown" 248 | __state = "close" 249 | 250 | def _on_touch_move(self, touch): 251 | if touch.dx > 0: 252 | if self.drawer_x < self.width: 253 | self._direction = "right" 254 | if self.drawer_x < self.width: 255 | self.drawer_x += abs(touch.dx) 256 | else: 257 | if self.drawer_x > 0: 258 | self._direction = "left" 259 | self.drawer_x -= abs(touch.dx) 260 | 261 | def on_touch_move(self, touch): 262 | if self.__state == "close": 263 | if self.swipe_distance > touch.x or self._count_distance is True: 264 | self._count_distance = True 265 | self._on_touch_move(touch) 266 | else: 267 | self._on_touch_move(touch) 268 | return super().on_touch_move(touch) 269 | 270 | def on_touch_up(self, touch): 271 | if self._direction == "right": 272 | self.animation_open() 273 | elif self._direction == "left": 274 | self.animation_close() 275 | self._direction = "unknown" 276 | self._count_distance = False 277 | return super().on_touch_up(touch) 278 | 279 | def animation_open(self): 280 | anim = Animation( 281 | drawer_x=self.side_panel_width, 282 | d=self.anim_time, 283 | t=self.opening_transition, 284 | ) 285 | anim.bind(on_progress=self._on_progress_open) 286 | anim.start(self) 287 | self.__state = "open" 288 | self.state = self.__state 289 | 290 | def animation_close(self): 291 | anim = Animation( 292 | drawer_x=0, d=self.anim_time, t=self.closing_transition 293 | ) 294 | anim.bind(on_progress=self._on_progress_close) 295 | anim.start(self) 296 | self.__state = "close" 297 | self.state = self.__state 298 | 299 | def toggle_nav_drawer(self): 300 | if self.__state == "open": 301 | self.animation_close() 302 | elif self.__state == "close": 303 | self.animation_open() 304 | 305 | def _on_progress_open(self, animation, widget, progress): 306 | self.parent._color.rgba = [0, 0, 0, progress / 2] 307 | 308 | def _on_progress_close(self, animation, widget, progress): 309 | self.parent._color.rgba = [0, 0, 0, 0.5 - progress] 310 | -------------------------------------------------------------------------------- /kivymd/uix/selectioncontrol.py: -------------------------------------------------------------------------------- 1 | """ 2 | Selection Controls 3 | ================== 4 | 5 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - 6 | KivyMD library up to version 0.1.2 7 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - 8 | KivyMD library version 0.1.3 and higher 9 | 10 | For suggestions and questions: 11 | 12 | 13 | This file is distributed under the terms of the same license, 14 | as the Kivy framework. 15 | 16 | `Material Design spec, Selection controls `_ 17 | """ 18 | 19 | from kivy.lang import Builder 20 | from kivy.properties import StringProperty, ListProperty, NumericProperty 21 | from kivy.uix.behaviors import ToggleButtonBehavior 22 | from kivy.uix.floatlayout import FloatLayout 23 | from kivy.properties import AliasProperty, BooleanProperty 24 | from kivy.metrics import dp, sp 25 | from kivy.animation import Animation 26 | from kivy.utils import get_color_from_hex 27 | from kivy.uix.behaviors import ButtonBehavior 28 | from kivy.uix.widget import Widget 29 | 30 | from kivymd.color_definitions import colors 31 | from kivymd.theming import ThemableBehavior 32 | from kivymd.uix.behaviors import ( 33 | CircularElevationBehavior, 34 | CircularRippleBehavior, 35 | ) 36 | from kivymd.uix.label import MDIcon 37 | 38 | Builder.load_string( 39 | """ 40 | 41 | canvas: 42 | Clear 43 | Color: 44 | rgba: self.color 45 | Rectangle: 46 | texture: self.texture 47 | size: self.texture_size 48 | pos: 49 | int(self.center_x - self.texture_size[0] / 2.),\ 50 | int(self.center_y - self.texture_size[1] / 2.) 51 | 52 | color: self._current_color 53 | halign: 'center' 54 | valign: 'middle' 55 | 56 | 57 | 58 | color: 1, 1, 1, 1 59 | canvas: 60 | Color: 61 | rgba: self.color 62 | Ellipse: 63 | size: self.size 64 | pos: self.pos 65 | 66 | 67 | 68 | canvas.before: 69 | Color: 70 | rgba: 71 | self._track_color_disabled if self.disabled else\ 72 | (self._track_color_active if self.active\ 73 | else self._track_color_normal) 74 | #Ellipse: 75 | # size: dp(8), dp(16) 76 | # pos: self.x, self.center_y - dp(8) 77 | # angle_start: 180 78 | # angle_end: 360 79 | RoundedRectangle: 80 | size: self.width - dp(8), dp(16) 81 | pos: self.x + dp(8), self.center_y - dp(8) 82 | radius: [dp(7)] 83 | #Ellipse: 84 | # size: dp(8), dp(16) 85 | # pos: self.right - dp(4), self.center_y - dp(8) 86 | # angle_start: 0 87 | # angle_end: 180 88 | 89 | on_release: thumb.trigger_action() 90 | 91 | Thumb: 92 | id: thumb 93 | size_hint: None, None 94 | size: dp(24), dp(24) 95 | pos: root.pos[0] + root._thumb_pos[0], root.pos[1] + root._thumb_pos[1] 96 | color: 97 | root.thumb_color_disabled if root.disabled else\ 98 | (root.thumb_color_down if root.active else root.thumb_color) 99 | elevation: 4 if root.active else 2 100 | on_release: setattr(root, 'active', not root.active) 101 | """ 102 | ) 103 | 104 | 105 | class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon): 106 | active = BooleanProperty(False) 107 | 108 | checkbox_icon_normal = StringProperty("checkbox-blank-outline") 109 | checkbox_icon_down = StringProperty("checkbox-marked-outline") 110 | radio_icon_normal = StringProperty("checkbox-blank-circle-outline") 111 | radio_icon_down = StringProperty("checkbox-marked-circle-outline") 112 | 113 | selected_color = ListProperty() 114 | unselected_color = ListProperty() 115 | disabled_color = ListProperty() 116 | _current_color = ListProperty([0.0, 0.0, 0.0, 0.0]) 117 | 118 | def __init__(self, **kwargs): 119 | self.check_anim_out = Animation(font_size=0, duration=0.1, t="out_quad") 120 | self.check_anim_in = Animation( 121 | font_size=sp(24), duration=0.1, t="out_quad" 122 | ) 123 | super().__init__(**kwargs) 124 | self.selected_color = self.theme_cls.primary_color 125 | self.unselected_color = self.theme_cls.secondary_text_color 126 | self.disabled_color = self.theme_cls.divider_color 127 | self._current_color = self.unselected_color 128 | self.check_anim_out.bind( 129 | on_complete=lambda *x: self.check_anim_in.start(self) 130 | ) 131 | self.bind( 132 | checkbox_icon_normal=self.update_icon, 133 | checkbox_icon_down=self.update_icon, 134 | radio_icon_normal=self.update_icon, 135 | radio_icon_down=self.update_icon, 136 | group=self.update_icon, 137 | selected_color=self.update_color, 138 | unselected_color=self.update_color, 139 | disabled_color=self.update_color, 140 | disabled=self.update_color, 141 | state=self.update_color, 142 | ) 143 | self.update_icon() 144 | self.update_color() 145 | 146 | def update_icon(self, *args): 147 | if self.state == "down": 148 | self.icon = ( 149 | self.radio_icon_down if self.group else self.checkbox_icon_down 150 | ) 151 | else: 152 | self.icon = ( 153 | self.radio_icon_normal 154 | if self.group 155 | else self.checkbox_icon_normal 156 | ) 157 | 158 | def update_color(self, *args): 159 | if self.disabled: 160 | self._current_color = self.disabled_color 161 | elif self.state == "down": 162 | self._current_color = self.selected_color 163 | else: 164 | self._current_color = self.unselected_color 165 | 166 | def on_state(self, *args): 167 | if self.state == "down": 168 | self.check_anim_in.cancel(self) 169 | self.check_anim_out.start(self) 170 | self.update_icon() 171 | self.active = True 172 | else: 173 | self.check_anim_in.cancel(self) 174 | self.check_anim_out.start(self) 175 | self.update_icon() 176 | self.active = False 177 | 178 | def on_active(self, *args): 179 | self.state = "down" if self.active else "normal" 180 | 181 | 182 | class Thumb( 183 | CircularElevationBehavior, CircularRippleBehavior, ButtonBehavior, Widget 184 | ): 185 | ripple_scale = NumericProperty(2) 186 | 187 | def _set_ellipse(self, instance, value): 188 | self.ellipse.size = (self.ripple_rad, self.ripple_rad) 189 | if self.ellipse.size[0] > self.width * 1.5 and not self.fading_out: 190 | self.fade_out() 191 | self.ellipse.pos = ( 192 | self.center_x - self.ripple_rad / 2.0, 193 | self.center_y - self.ripple_rad / 2.0, 194 | ) 195 | self.stencil.pos = ( 196 | self.center_x - (self.width * self.ripple_scale) / 2, 197 | self.center_y - (self.height * self.ripple_scale) / 2, 198 | ) 199 | 200 | 201 | class MDSwitch(ThemableBehavior, ButtonBehavior, FloatLayout): 202 | active = BooleanProperty(False) 203 | 204 | _thumb_color = ListProperty(get_color_from_hex(colors["Gray"]["50"])) 205 | 206 | def _get_thumb_color(self): 207 | return self._thumb_color 208 | 209 | def _set_thumb_color(self, color, alpha=None): 210 | if len(color) == 2: 211 | self._thumb_color = get_color_from_hex(colors[color[0]][color[1]]) 212 | if alpha: 213 | self._thumb_color[3] = alpha 214 | elif len(color) == 4: 215 | self._thumb_color = color 216 | 217 | thumb_color = AliasProperty( 218 | _get_thumb_color, _set_thumb_color, bind=["_thumb_color"] 219 | ) 220 | 221 | _thumb_color_down = ListProperty([1, 1, 1, 1]) 222 | 223 | def _get_thumb_color_down(self): 224 | return self._thumb_color_down 225 | 226 | def _set_thumb_color_down(self, color, alpha=None): 227 | if len(color) == 2: 228 | self._thumb_color_down = get_color_from_hex( 229 | colors[color[0]][color[1]] 230 | ) 231 | if alpha: 232 | self._thumb_color_down[3] = alpha 233 | else: 234 | self._thumb_color_down[3] = 1 235 | elif len(color) == 4: 236 | self._thumb_color_down = color 237 | 238 | thumb_color_down = AliasProperty( 239 | _get_thumb_color_down, _set_thumb_color_down, bind=["_thumb_color_down"] 240 | ) 241 | 242 | _thumb_color_disabled = ListProperty( 243 | get_color_from_hex(colors["Gray"]["400"]) 244 | ) 245 | 246 | thumb_color_disabled = get_color_from_hex(colors["Gray"]["800"]) 247 | 248 | def _get_thumb_color_disabled(self): 249 | return self._thumb_color_disabled 250 | 251 | def _set_thumb_color_disabled(self, color, alpha=None): 252 | if len(color) == 2: 253 | self._thumb_color_disabled = get_color_from_hex( 254 | colors[color[0]][color[1]] 255 | ) 256 | if alpha: 257 | self._thumb_color_disabled[3] = alpha 258 | elif len(color) == 4: 259 | self._thumb_color_disabled = color 260 | 261 | thumb_color_down = AliasProperty( 262 | _get_thumb_color_disabled, 263 | _set_thumb_color_disabled, 264 | bind=["_thumb_color_disabled"], 265 | ) 266 | 267 | _track_color_active = ListProperty() 268 | _track_color_normal = ListProperty() 269 | _track_color_disabled = ListProperty() 270 | _thumb_pos = ListProperty([0, 0]) 271 | 272 | def __init__(self, **kwargs): 273 | super().__init__(**kwargs) 274 | self.theme_cls.bind( 275 | theme_style=self._set_colors, 276 | primary_color=self._set_colors, 277 | primary_palette=self._set_colors, 278 | ) 279 | self.bind(active=self._update_thumb_pos) 280 | self._set_colors() 281 | 282 | def _set_colors(self, *args): 283 | self._track_color_normal = self.theme_cls.disabled_hint_text_color 284 | if self.theme_cls.theme_style == "Dark": 285 | self._track_color_active = self.theme_cls.primary_color 286 | self._track_color_active[3] = 0.5 287 | self._track_color_disabled = get_color_from_hex("FFFFFF") 288 | self._track_color_disabled[3] = 0.1 289 | self.thumb_color = get_color_from_hex(colors["Gray"]["400"]) 290 | self.thumb_color_down = get_color_from_hex( 291 | colors[self.theme_cls.primary_palette]["200"] 292 | ) 293 | else: 294 | self._track_color_active = get_color_from_hex( 295 | colors[self.theme_cls.primary_palette]["200"] 296 | ) 297 | self._track_color_active[3] = 0.5 298 | self._track_color_disabled = self.theme_cls.disabled_hint_text_color 299 | self.thumb_color_down = self.theme_cls.primary_color 300 | 301 | def _update_thumb_pos(self, *args, animation=True): 302 | if self.active: 303 | _thumb_pos = (self.width - dp(12), self.height / 2 - dp(12)) 304 | else: 305 | _thumb_pos = (0, self.height / 2 - dp(12)) 306 | Animation.cancel_all(self, "_thumb_pos") 307 | if animation: 308 | Animation(_thumb_pos=_thumb_pos, duration=0.2, t="out_quad").start( 309 | self 310 | ) 311 | else: 312 | self._thumb_pos = _thumb_pos 313 | 314 | def on_size(self, *args): 315 | self._update_thumb_pos(animation=False) 316 | --------------------------------------------------------------------------------