├── 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 | 
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 | 
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 |
--------------------------------------------------------------------------------