├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── kivyEmu.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── assets ├── android_lolipop1.png ├── kivy-icon-128.png ├── kivyemu.PNG ├── kivyemu.gif ├── kivyemu2.PNG └── pyjokes.png ├── emulator.py ├── history.json ├── kaki ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ └── app.cpython-36.pyc └── app.py ├── kivyEmu.spec ├── kivyemu.kv ├── kivymd ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __init__.py ├── accordion.py ├── accordionlistitem.py ├── backgroundcolorbehavior.py ├── bottomnavigation.py ├── bottomsheet.py ├── button.py ├── card.py ├── cards.py ├── chips.py ├── color_definitions.py ├── date_picker.py ├── dialog.py ├── elevation.py ├── elevationbehavior.py ├── fanscreenmanager.py ├── filemanager.py ├── font_definitions.py ├── fonts │ ├── Roboto-Black.ttf │ ├── Roboto-BlackItalic.ttf │ ├── Roboto-Bold.ttf │ ├── Roboto-BoldItalic.ttf │ ├── Roboto-Italic.ttf │ ├── Roboto-Light.ttf │ ├── Roboto-LightItalic.ttf │ ├── Roboto-Medium.ttf │ ├── Roboto-MediumItalic.ttf │ ├── Roboto-Regular.ttf │ ├── Roboto-Thin.ttf │ ├── Roboto-ThinItalic.ttf │ └── materialdesignicons-webfont.ttf ├── grid.py ├── icon_definitions.py ├── imagelists.py ├── images │ ├── dialog_in_fade.png │ ├── folder.png │ ├── ios_bg_mod.png │ ├── ios_bg_mod_for_toast.png │ ├── ios_entr_ti.png │ ├── kivy-logo-white-512.png │ ├── kivymd_512.png │ ├── kivymd_logo.png │ ├── quad_shadow-0.png │ ├── quad_shadow-1.png │ ├── quad_shadow-2.png │ ├── quad_shadow.atlas │ ├── rec_shadow-0.png │ ├── rec_shadow-1.png │ ├── rec_shadow.atlas │ ├── rec_st_shadow-0.png │ ├── rec_st_shadow-1.png │ ├── rec_st_shadow-2.png │ ├── rec_st_shadow.atlas │ ├── round_shadow-0.png │ ├── round_shadow-1.png │ ├── round_shadow-2.png │ ├── round_shadow.atlas │ ├── swipe_shadow.png │ └── transparent.png ├── label.py ├── list.py ├── managerswiper.py ├── material_resources.py ├── menu.py ├── menus.py ├── navigationdrawer.py ├── pickers.py ├── popupscreen.py ├── progressbar.py ├── progressloader.py ├── refreshlayout.py ├── ripplebehavior.py ├── selectioncontrols.py ├── setup.py ├── slider.py ├── slidingpanel.py ├── snackbar.py ├── snackbars.py ├── spinner.py ├── stackfloatingbuttons.py ├── stiffscroll │ ├── LICENSE │ ├── README.md │ └── __init__.py ├── tabs.py ├── textfields.py ├── theme_picker.py ├── theming.py ├── theming_dynamic_text.py ├── time_picker.py ├── toast │ ├── LICENSE │ ├── README.md │ ├── __init__.py │ ├── androidtoast │ │ ├── __init__.py │ │ └── androidtoast.py │ └── kivytoast │ │ ├── __init__.py │ │ └── kivytoast.py ├── toolbar.py ├── useranimationcard.py ├── utils │ ├── __init__.py │ ├── asynckivy.py │ └── cropimage.py └── vendor │ ├── __init__.py │ ├── circleLayout │ ├── LICENSE │ ├── README.md │ └── __init__.py │ ├── circularTimePicker │ ├── LICENSE │ ├── README.md │ └── __init__.py │ └── navigationdrawer │ ├── LICENSE │ ├── README.md │ └── __init__.py ├── main.py ├── test.kv └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/kivyEmu.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |

16 | 17 | Logo 18 | Logo 19 | Logo 20 | 21 |

22 | 23 |

KivyLiteEmulator

24 | 25 |

26 | An Lite Emulator for Kivy Hot Reloading 27 |
28 | Demo Video » 29 |
30 |
31 | 32 | 33 |

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ## About The Project 43 | 44 | Kivy is an awesome framework for creating GUI apps. However it lacks one of the most sought after features of a GUI framework which is an 45 | autoreloader. We are currently working on a kivystudio. Before releasing a stable version, i just need something to help me display my UI in realtime. 46 | 47 | This currently works with some of the apps i have created, it seems it is scared of bigger projects,:smile: lol, 48 | 49 | You may also suggest changes by forking this repo and creating a pull request or opening an issue. 50 | 51 | A list of commonly used resources that I find helpful are listed in the acknowledgements. 52 | 53 | 54 | 55 | 56 | 57 | ## Getting Started 58 | 59 | This is an example of how you may give instructions on setting up your project locally. 60 | To get a local copy up and running follow these simple example steps. 61 | 62 | ### Prerequisites 63 | 64 | This is an example of how to list things you need to use the software and how to install them. I Hope you already have `kivy` installed 65 | 66 | ```sh 67 | pip install monotonic watchdog plyer 68 | ``` 69 | 70 | ### Installation 71 | 72 | 73 | 1. Clone the repo 74 | ```sh 75 | git clone https:://github.com/mcroni/KivyLiteEmulator.git 76 | ``` 77 | 3. Change Dir into the Cloned Repo 78 | ```sh 79 | cd KivyLiteEmulator 80 | ``` 81 | 3. Set `DEBUG=True` in the `OS` Environ 82 | ```sh 83 | set DEBUG = True 84 | ``` 85 | 4. Run the `main.py` file and select a python file containing your kivy codes to run 86 | ```sh 87 | python main.py 88 | ``` 89 | 90 | 91 | 92 | 93 | 94 | 95 | ## Contributing 96 | 97 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 98 | 99 | 1. Fork the Project 100 | 2. Create your Feature Branch 101 | 3. Commit your Changes 102 | 4. Push to the Branch 103 | 5. Open a Pull Request 104 | 105 | 106 | 107 | 108 | ## License 109 | 110 | Distributed under the MIT License. See `LICENSE` for more information. 111 | 112 | 113 | 114 | 115 | ## Contact 116 | [@kojo_mcroni](https://twitter.com/kojo_mcroni) - joeydanieldarko@gmail.com 117 | 118 | 119 | 120 | 121 | ## Acknowledgements 122 | * [Kaki By Tito](https://github.com/tito/kaki) 123 | * [KivyStudio](https://github.com/avour/kivystudio) 124 | * [KivyMD](https://github.com/HeaTTheatR/KivyMD) 125 | * [Kivy](https://kivy.org) 126 | * [Cruor99](https://github.com/cruor99) 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /assets/android_lolipop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/assets/android_lolipop1.png -------------------------------------------------------------------------------- /assets/kivy-icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/assets/kivy-icon-128.png -------------------------------------------------------------------------------- /assets/kivyemu.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/assets/kivyemu.PNG -------------------------------------------------------------------------------- /assets/kivyemu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/assets/kivyemu.gif -------------------------------------------------------------------------------- /assets/kivyemu2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/assets/kivyemu2.PNG -------------------------------------------------------------------------------- /assets/pyjokes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/assets/pyjokes.png -------------------------------------------------------------------------------- /emulator.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import traceback 3 | from threading import Thread 4 | from functools import partial 5 | try: 6 | from importlib import reload 7 | except: 8 | pass 9 | from kivy.lang import Builder 10 | from kivy.clock import mainthread 11 | from kivy.resources import resource_add_path, resource_remove_path 12 | # from kivystudio.components.emulator_area import emulator_area 13 | from kivy.uix.floatlayout import FloatLayout 14 | from kivy.uix.boxlayout import BoxLayout 15 | from kivy.uix.label import Label 16 | from kivy.uix.behaviors import ToggleButtonBehavior 17 | from kivy.uix.screenmanager import ScreenManager, Screen 18 | from kivy.lang import Builder 19 | from kivy.properties import ObjectProperty, StringProperty 20 | from kivy.clock import Clock 21 | import os 22 | # __all__ = ('get_emulator',) 23 | # filepath = os.path.dirname(__file__) 24 | # Builder.load_file(os.path.join(filepath, 'emulator.kv')) 25 | 26 | 27 | # class EmulatorArea(BoxLayout): 28 | # screen_display = ObjectProperty(None) 29 | # emulation_file = StringProperty('') 30 | # 31 | # def __init__(self, **kwargs): 32 | # super(EmulatorArea, self).__init__(**kwargs) 33 | # self.screen_manager = EmulatorScreens() 34 | # self.add_widget(self.screen_manager) 35 | # # self.screen_display = ScreenDisplay() 36 | # self.screen_manager.add_widget(self.screen_display) 37 | # 38 | # def add_widget(self, widget): 39 | # super(EmulatorArea, self).add_widget(widget) 40 | 41 | # def toggle_orientation(self): 42 | # if self.screen_display.screen.orientation == 'portrait': 43 | # self.screen_display.screen.orientation = 'landscape' 44 | # else: 45 | # self.screen_display.screen.orientation = 'portrait' 46 | 47 | # def open_screen_drop(self, widget): 48 | # ScreenDrop().open(widget, self.screen_display) 49 | 50 | 51 | 52 | 53 | 54 | # 55 | # instance = [] 56 | # 57 | # 58 | # def emulator_area(): 59 | # if instance: 60 | # return instance[0] 61 | # else: 62 | # emulator_area = EmulatorArea(size_hint_x=.45) 63 | # instance.append(emulator_area) 64 | # return emulator_area 65 | 66 | 67 | def emulate_file(filename, threaded=False): 68 | root = None 69 | if not os.path.exists(filename): 70 | return 71 | 72 | dirname = os.path.dirname(filename) 73 | sys.path.append(dirname) 74 | os.chdir(dirname) 75 | resource_add_path(dirname) 76 | 77 | emulator_area().screen_display.screen.clear_widgets() 78 | 79 | if threaded: 80 | Thread(target=partial(start_emulation, filename, threaded=threaded)).start() 81 | else: 82 | start_emulation(filename, threaded=threaded) 83 | 84 | 85 | def start_emulation(filename, threaded=False): 86 | root = None 87 | if os.path.splitext(filename)[1] == '.kv': # load the kivy file directly 88 | try: # cahching error with kivy files 89 | Builder.unload_file(filename) 90 | root = Builder.load_file(filename) 91 | except: 92 | traceback.print_exc() 93 | print("You kivy file has a problem") 94 | 95 | elif os.path.splitext(filename)[1] == '.py': 96 | load_defualt_kv(filename) 97 | 98 | try: # cahching error with python files 99 | root = load_py_file(filename) 100 | except: 101 | traceback.print_exc() 102 | print("You python file has a problem") 103 | 104 | if root: 105 | if threaded: 106 | emulation_done(root, filename) 107 | else: 108 | emulator_area().screen_display.screen.add_widget(root) 109 | 110 | dirname = os.path.dirname(filename) 111 | sys.path.pop() 112 | resource_remove_path(dirname) 113 | 114 | 115 | @mainthread 116 | def emulation_done(root, filename): 117 | if root: 118 | emulator_area().screen_display.screen.add_widget(root) 119 | 120 | 121 | def load_defualt_kv(filename): 122 | app_cls_name = get_app_cls_name(filename) 123 | if app_cls_name is None: 124 | return 125 | 126 | kv_name = app_cls_name.lower() 127 | if app_cls_name.endswith('App'): 128 | kv_name = app_cls_name[:len(app_cls_name) - 3].lower() 129 | 130 | if app_cls_name: 131 | file_dir = os.path.dirname(filename) 132 | kv_filename = os.path.join(file_dir, kv_name + '.kv') 133 | 134 | if os.path.exists(kv_filename): 135 | try: # cahching error with kivy files 136 | Builder.unload_file(kv_filename) 137 | root = Builder.load_file(kv_filename) 138 | except: 139 | traceback.print_exc() 140 | print("You kivy file has a problem") 141 | 142 | 143 | def get_app_cls_name(filename): 144 | with open(filename) as fn: 145 | text = fn.read() 146 | 147 | lines = text.splitlines() 148 | app_cls = get_import_as('from kivy.app import App', lines) 149 | 150 | def check_app_cls(line): 151 | line = line.strip() 152 | return line.startswith('class') and line.endswith('(%s):' % app_cls) 153 | 154 | found = list(filter(check_app_cls, lines)) 155 | 156 | if found: 157 | line = found[0] 158 | cls_name = line.split('(')[0].split(' ')[1] 159 | return cls_name 160 | 161 | 162 | def get_root_from_runTouch(): 163 | with open(filename) as fn: 164 | text = fn.read() 165 | 166 | lines = text.splitlines() 167 | run_touch = get_import_as('from kivy.base import runTouchApp', lines) 168 | 169 | def check_run_touch(line): 170 | line = line.strip() 171 | return line.startswith('%s(' % run_touch) 172 | 173 | found = list(filter(check_run_touch, lines)) 174 | 175 | if found: 176 | line = found[0] 177 | root_name = line.strip().split('(')[1].split(')')[0] 178 | 179 | root_file = import_from_dir(filename) 180 | root = getattr(reload(root_file), root_name) 181 | 182 | return root 183 | 184 | 185 | def load_py_file(filename): 186 | app_cls_name = get_app_cls_name(filename) 187 | if app_cls_name: 188 | root_file = import_from_dir(filename) 189 | app_cls = getattr(reload(root_file), app_cls_name) 190 | root = app_cls().build() 191 | 192 | return root 193 | 194 | run_root = get_root_from_runTouch(filename) 195 | if run_root: 196 | return run_root 197 | 198 | 199 | def import_from_dir(filename): 200 | ''' force python to import this file 201 | from the project_ dir''' 202 | 203 | dirname, file = os.path.split(filename) 204 | sys.path = [dirname] + sys.path 205 | 206 | import_word = os.path.splitext(file)[0] 207 | imported = __import__(import_word) 208 | return imported 209 | 210 | 211 | def get_import_as(start, lines): 212 | line = list(filter(lambda line: line.strip().startswith(start), lines)) 213 | if line: 214 | words = line[0].split(' ') 215 | import_word = words[len(words) - 1] 216 | return import_word 217 | else: 218 | return 219 | 220 | -------------------------------------------------------------------------------- /history.json: -------------------------------------------------------------------------------- 1 | {"C:\\Users\\MCRONI\\PycharmProjects\\kivyemu_test\\main.py": {"file_name": "C:\\Users\\MCRONI\\PycharmProjects\\kivyemu_test\\main.py"}} -------------------------------------------------------------------------------- /kaki/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __version__ = "0.1.4.dev0" 4 | -------------------------------------------------------------------------------- /kaki/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kaki/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /kaki/__pycache__/app.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kaki/__pycache__/app.cpython-36.pyc -------------------------------------------------------------------------------- /kivyEmu.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | from kivy_deps import sdl2, glew 3 | 4 | block_cipher = None 5 | 6 | 7 | a = Analysis(['main.py'], 8 | pathex=['C:\\Users\\MCRONI\\PycharmProjects\\kivyEmu'], 9 | binaries=[], 10 | datas=[], 11 | hiddenimports=['kivymd.theming', 'kivymd', 'kivymd.selectioncontrols', 'kivymd.card', 'kivymd.tabs', 'kivymd.menu','plyer','plyer.platforms','plyer.platforms.win','plyer.platforms.win.filechooser'], 12 | hookspath=[], 13 | runtime_hooks=[], 14 | excludes=[], 15 | win_no_prefer_redirects=False, 16 | win_private_assemblies=False, 17 | cipher=block_cipher) 18 | pyz = PYZ(a.pure, a.zipped_data, 19 | cipher=block_cipher) 20 | exe = EXE(pyz, 21 | a.scripts, 22 | exclude_binaries=True, 23 | name='kivyEmi', 24 | debug=False, 25 | strip=False, 26 | upx=True, 27 | console=True ) 28 | 29 | coll = COLLECT(exe, Tree('.'), 30 | a.binaries, 31 | a.zipfiles, 32 | a.datas, 33 | *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)], 34 | strip=False, 35 | upx=True, 36 | name='kivyemu') 37 | -------------------------------------------------------------------------------- /kivyemu.kv: -------------------------------------------------------------------------------- 1 | #:import OneLineIconListItem kivymd.list.OneLineIconListItem 2 | #:import images_path kivymd.images_path 3 | #:import MDTextFieldRect kivymd.textfields.MDTextField 4 | #:import MDIconButton kivymd.button.MDIconButton 5 | #:import MDDropdownMenu kivymd.menus.MDDropdownMenu 6 | #:import MDList kivymd.list.MDList 7 | #:import OneLineListItem kivymd.list.OneLineListItem 8 | #:import TwoLineListItem kivymd.list.TwoLineListItem 9 | #:import ThreeLineListItem kivymd.list.ThreeLineListItem 10 | #:import OneLineAvatarListItem kivymd.list.OneLineAvatarListItem 11 | #:import OneLineIconListItem kivymd.list.OneLineIconListItem 12 | #:import OneLineAvatarIconListItem kivymd.list.OneLineAvatarIconListItem 13 | #:import MDTabsBase kivymd.tabs.MDTabsBase 14 | #:import MDTabs kivymd.tabs.MDTabs 15 | #:import MDRaisedButton kivymd.button.MDRaisedButton 16 | #:import MDTextFieldRect kivymd.textfields.MDTextFieldRect 17 | #:import MDTextFieldClear kivymd.textfields.MDTextFieldClear 18 | #:import MDTextField kivymd.textfields.MDTextField 19 | #:import MDTextFieldRound kivymd.textfields.MDTextFieldRound 20 | #:import MDRaisedButton kivymd.button.MDRaisedButton 21 | #:import MDLabel kivymd.label.MDLabel 22 | #:import MDToolbar kivymd.toolbar.MDToolbar 23 | #:import NavigationDrawerToolbar kivymd.navigationdrawer.NavigationDrawerToolbar 24 | #:import NavigationLayout kivymd.navigationdrawer.NavigationLayout 25 | #:import MDSpinner kivymd.spinner.MDSpinner 26 | #: import get_color_from_hex kivy.utils.get_color_from_hex 27 | 28 | : 29 | screen_manager:screen_manager 30 | emulator_screen:emulator_screen 31 | Image: 32 | source: './assets/android_lolipop1.png' 33 | pos_hint: {'center_x': 0.5, 'center_y': 0.46} 34 | 35 | BoxLayout: 36 | orientation: "vertical" 37 | MDToolbar: 38 | id: toolbar 39 | title: 'KivyEmu' 40 | md_bg_color: app.theme_cls.primary_color 41 | background_palette: 'Primary' 42 | background_hue: '500' 43 | elevation: 10 44 | right_action_items: 45 | [['menu', lambda x:app.show_example_bottom_sheet()]] 46 | BoxLayout: 47 | id: box_man 48 | FloatLayout 49 | BoxLayout: 50 | size_hint: .926,.8325 51 | pos_hint: {'center_x': 0.5, 'center_y': 0.525} 52 | ScreenManager: 53 | id:screen_manager 54 | EmulatorScreen: 55 | id: emulator_screen 56 | name: "emulator_screen" 57 | HistoryScreen: 58 | id: history_screen 59 | name: "history_screen" 60 | # MDFloatingActionButton: 61 | # id:chevron_up 62 | # icon: 'file' 63 | # opacity: 0.9 64 | # pos_hint: {'center_x': 0.8, 'center_y': 0.1} 65 | # size_hint: None, None 66 | # size: dp(55), dp(55) 67 | # on_press:app.choose() 68 | 69 | 70 | 71 | : 72 | on_pre_enter: root.build_screen() 73 | -------------------------------------------------------------------------------- /kivymd/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | .buildozer 4 | .idea 5 | build 6 | docs/_build 7 | demos/kitchen_sink/bin -------------------------------------------------------------------------------- /kivymd/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 4 | ## [0.99.92] 5 | 6 | - Removed automatic change of text field length in the `MDTextFieldRound` class 7 | 8 | ## [0.99.93] 9 | 10 | - Updated `materialdesignicons-webfont.ttf` and `icon_definitions.py` files 11 | 12 | ## [0.99.94] 13 | 14 | - Added property `_no_ripple_effect` to BaseListItem class 15 | - [Banned](https://www.youtube.com/watch?v=P_9oSx0Pz_U) use `ripple effect` in MDAccordionListItem class 16 | - Added check to use `ripple effect` in RectangularRippleBehavior class 17 | 18 | ## [0.99.95] 19 | 20 | - Added function to create a round image in `kivymd/utils/cropimage.py` module 21 | - Added new `MDCustomRoundIconButton` class in `kivymd/button.py` module 22 | - Added demo application [Account Page](https://www.youtube.com/watch?v=dfUOwqtYoYg) 23 | 24 | ## [0.99.96] 25 | - Added asynchronous call to list update method in RefreshLayout example in main.py file 26 | - Added asynckivy.py module for using asynchronous function calls in Kivy 27 | 28 | ## [0.99.97] 29 | - Fixed: spinner closes after updating the screen. 30 | 31 | ## [0.99.98] 32 | - Added new MDFillRoundFlatIconButton class 33 | 34 | ## [0.100.0] 35 | - Fix "In MDNavigationDrawer I used use_logo='all' it's showing image only not the drawer_title text!" -------------------------------------------------------------------------------- /kivymd/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors - KivyMD library up to version 0.1.2 4 | Copyright (c) 2019 Ivanov Yuri and KivyMD contributors - KivyMD library version 0.1.3 and higher 5 | 6 | Other libraries used in the project: 7 | 8 | Copyright (c) 2010-2019 Kivy Team and other contributors 9 | Copyright (c) 2013 Brian Knapp - Androidoast library 10 | Copyright (c) 2013 Alexander Taylor - navigationdrawer library 11 | Copyright (c) 2014 LogicalDash - stiffscroll library 12 | Copyright (c) 2015 Davide Depau - circularTimePicker, circleLayout libraries 13 | Copyright (c) 2015 Kivy Garden - tabs module 14 | Copyright (c) 2019 Nattōsai Mitō - asynckivy module 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in 24 | all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | THE SOFTWARE. 33 | -------------------------------------------------------------------------------- /kivymd/README.md: -------------------------------------------------------------------------------- 1 | KivyMD 2 | ====== 3 | 4 | 5 | 6 | KivyMD is a collection of Material Design compliant widgets for use with [Kivy](http://kivy.org), a framework for cross-platform, touch-enabled graphical applications. 7 | 8 | The project's goal is to approximate Google's [Material Design spec](https://www.google.com/design/spec/material-design/introduction.html) as close as possible without sacrificing ease of use or application performance. 9 | 10 | This library is a fork of the [KivyMD project](https://gitlab.com/kivymd/KivyMD) the author of which stopped supporting this project three years ago. We found the strength and brought this project to a new level. 11 | 12 | Currently we're in **alpha** status, so things are changing all the time and we cannot promise any kind of API stability. However it is safe to vendor now and make use of what's currently available. 13 | 14 | Join the project! Just fork the project, branch out and submit a pull request when your patch is ready. If any changes are necessary, we'll guide you through the steps that need to be done via PR comments or access to your for may be requested to outright submit them. 15 | 16 | If you wish to become a project developer (permission to create branches on the project without forking for easier collaboration), have at least one PR approved and ask for it. If you contribute regularly to the project the role may be offered to you without asking too. 17 | 18 | Documentation 19 | ============= 20 | 21 | Full documentation yet. Some examples of using KivyMD widgets can be found in our [Wiki](https://github.com/HeaTTheatR/KivyMD/wiki) and in the code of the [demo applications](https://github.com/HeaTTheatR/KivyMD/tree/master/demos/kitchen_sink/demo_apps) 22 | 23 | Support 24 | ======= 25 | If you need assistance, you can ask for help on our mailing list: 26 | 27 | * User Groups: [vk group](https://vk.com/kivy_development), [google group](https://groups.google.com/forum/#!categories/kivymd-users-support), [Discord Channel](https://discord.gg/TegSJDD) 28 | * Email: kivydevelopment@gmail.com 29 | 30 | 31 | Installation and use with Buildozer 32 | =================================== 33 | 34 | #### Dependencies: 35 | * Kivy version is not less than 1.10.1 36 | * PIL 37 | * Python 3 (Python 2 not supported) 38 | 39 | #### How to install 40 | 41 | To install KivyMD, clone the project and run the setup.py script. The following line works on Linux and Mac OS, other OSes not tested: 42 | 43 | sudo python ./setup.py install 44 | 45 | Replace "python" with the Python interpreter you want to install KivyMD on (Python 3 is supported) 46 | 47 | 48 | #### How to use with Buildozer 49 | 50 | If you want to use KivyMD with buildozer, in your buildozer.spec's requirements line you should add the full git HTTPS address, like this example: 51 | 52 | requirements = kivy==master,git+https://github.com/HeaTTheatR/KivyMD.git 53 | 54 | Running on Android 55 | ================== 56 | Android 6.0 and higher [kitchen_sink-0.98.4-x86.apk](https://github.com/HeaTTheatR/KivyMD-data/tree/master/bin/x86) or [kitchen_sink-0.98.4-armeabi-v7a.apk](https://github.com/HeaTTheatR/KivyMD-data/tree/master/bin/armeabi-v7a) 57 | 58 | Packages for Android are built according to the following instructions: 59 | ======================================================================= 60 | 61 | Download [XUbuntu 18.04](https://xubuntu.org/release/18-04/): 62 | 63 |

64 | 65 |

66 | 67 | * Create a new virtual machine based on the downloaded image of XUbuntu 68 | * Start the XUbuntu virtual machine, open the browser and download [this bash script](https://github.com/HeaTTheatR/KivyMD-data/blob/master/install-kivy-buildozer-dependencies.sh): 69 | 70 | Add execution permissions: 71 | ```bash 72 | chmod +x install-kivy-buildozer-dependencies.sh 73 | ``` 74 | ...and run script: 75 | 76 | ```bash 77 | ./install-kivy-buildozer-dependencies.sh 78 | ``` 79 | 80 | * Run the script - it will install all the necessary libraries and tools for creating packages for Android 81 | * Done! Now you have a virtual machine for building Kivy application packages! 82 | * Or see the instructions [here](https://github.com/zaemiel/kivy-buildozer-installer) 83 | 84 | What's new in version 0.100.0: 85 | ============================ 86 | [CHANGELOG.md](https://github.com/HeaTTheatR/KivyMD/blob/master/CHANGELOG.md) 87 | 88 | API Breaking changes: 89 | ===================== 90 | * Moving classes to new modules: 91 | 92 | | Old | New | 93 | |------------------------------------------------------|----------------------------------------------------| 94 | | `kivymd.tabs.MDBottomNavigation` | `kivymd.bottomnavigation.MDBottomNavigation` | 95 | | `kivymd.updatespinner` | `kivymd.refreshlayout` | 96 | 97 | * Renamed files, classes and variables: 98 | 99 | | Old | New | 100 | |------------------------------------------------------|-------------------------------------| 101 | | `kivymd.elevationbehavior` | `kivymd.elevation` | 102 | | `kivymd.grid` | `kivymd.imagelists` | 103 | | `kivymd.date_picker` | `kivymd.pickers` | 104 | | `kivymd.time_picker` | `kivymd.pickers` | 105 | | `kivymd.time_picker` | `kivymd.pickers` | 106 | | `kivymd.card` | `kivymd.cards` | 107 | | `kivymd.menu` | `kivymd.menus` | 108 | | `kivymd.snackbar` | `kivymd.snackbars` | 109 | | `Toolbar` (from `kivymd.toolbar`) | `MDToolbar` (from `kivymd.toolbar`) | 110 | 111 | * Changed font styles: 112 | 113 | | Old | New | 114 | |----------|-----------| 115 | | Icon | Icon | 116 | | - | Overline | 117 | | Caption | Caption | 118 | | Button | Button | 119 | | Body2 | Body2 | 120 | | Body1 | Body1 | 121 | | - | Subtitle2 | 122 | | Subhead | Subtitle1 | 123 | | Title | H6 | 124 | | Headline | H5 | 125 | | Display1 | H4 | 126 | | Display2 | H3 | 127 | | Display3 | H2 | 128 | | Display4 | H1 | 129 | 130 | * Colors `BlueGrey` and `Grey` renamed to `BlueGray` and `Gray` (for better fit MD spec) 131 | 132 | 133 | Video previous 134 | ============== 135 |

136 | 137 |

138 | 139 | Image previous 140 | ============== 141 |

142 | 143 |

144 | 145 | Sister project: 146 | ============== 147 | 148 | [Creator Kivy Project](https://github.com/HeaTTheatR/CreatorKivyProject) - Wizard for creating a new project for applications written using the Kivy framework 149 | 150 | License 151 | ======= 152 | 153 | MIT, same as Kivy. 154 | 155 | Roboto font is licensed and distributed under the terms of the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). 156 | 157 | [Material Design Iconic Font](https://github.com/zavoloklom/material-design-iconic-font) by [Sergey Kupletsky](https://twitter.com/zavoloklom) covered by the licenses described at https://zavoloklom.github.io/material-design-iconic-font/license.html. 158 | 159 | Icons by the materialdesignicons.com community covered by SIL OFL 1.1 160 | -------------------------------------------------------------------------------- /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 | 24 | from kivy import Logger 25 | 26 | __version_info__ = (0, 100, 0) 27 | __version__ = '0.100.0' 28 | 29 | path = os.path.dirname(__file__) 30 | fonts_path = os.path.join(path, f'fonts{os.sep}') 31 | images_path = os.path.join(path, f'images{os.sep}') 32 | 33 | Logger.info(f'KivyMD: KivyMD version: {__version__}') 34 | -------------------------------------------------------------------------------- /kivymd/accordionlistitem.py: -------------------------------------------------------------------------------- 1 | """ 2 | Accordion List Item 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 kivy.app import App 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.button import MDIconButton 26 | from kivymd.list import ILeftBodyTouch 27 | from kivymd.theming import ThemeManager 28 | from kivymd.accordionlistitem import MDAccordionListItem 29 | from kivymd.toast import toast 30 | 31 | Builder.load_string(''' 32 | #:import MDToolbar kivymd.toolbar.MDToolbar 33 | #:import get_hex_from_color kivy.utils.get_hex_from_color 34 | #:import TwoLineIconListItem kivymd.list.TwoLineIconListItem 35 | #:import OneLineIconListItem kivymd.list.OneLineIconListItem 36 | #:import MDRoundFlatButton kivymd.button.MDRoundFlatButton 37 | 38 | 39 | 40 | orientation: 'vertical' 41 | padding: dp(10) 42 | spacing: dp(10) 43 | size_hint_y: None 44 | height: self.minimum_height 45 | 46 | BoxLayout: 47 | size_hint_y: None 48 | height: self.minimum_height 49 | 50 | Widget: 51 | MDRoundFlatButton: 52 | text: "Free call" 53 | on_press: root.callback(self.text) 54 | Widget: 55 | MDRoundFlatButton: 56 | text: "Free message" 57 | on_press: root.callback(self.text) 58 | Widget: 59 | 60 | OneLineIconListItem: 61 | text: "Video call" 62 | on_press: root.callback(self.text) 63 | IconLeftSampleWidget: 64 | icon: 'camera-front-variant' 65 | 66 | TwoLineIconListItem: 67 | text: "Call Viber Out" 68 | on_press: root.callback(self.text) 69 | secondary_text: 70 | "[color=%s]Advantageous rates for calls[/color]"\ 71 | % get_hex_from_color(app.theme_cls.primary_color) 72 | IconLeftSampleWidget: 73 | icon: 'phone' 74 | 75 | TwoLineIconListItem: 76 | text: "Call over mobile network" 77 | on_press: root.callback(self.text) 78 | secondary_text: 79 | "[color=%s]Operator's tariffs apply[/color]"\ 80 | % get_hex_from_color(app.theme_cls.primary_color) 81 | IconLeftSampleWidget: 82 | icon: 'remote' 83 | 84 | 85 | 86 | orientation: 'vertical' 87 | 88 | MDToolbar: 89 | id: toolbar 90 | title: app.title 91 | md_bg_color: app.theme_cls.primary_color 92 | background_palette: 'Primary' 93 | elevation: 10 94 | left_action_items: [['dots-vertical', lambda x: None]] 95 | 96 | Screen: 97 | 98 | ScrollView: 99 | 100 | GridLayout: 101 | id: anim_list 102 | cols: 1 103 | size_hint_y: None 104 | height: self.minimum_height 105 | ''') 106 | 107 | 108 | class ContentForAnimCard(BoxLayout): 109 | callback = ObjectProperty(lambda x: None) 110 | 111 | 112 | class IconLeftSampleWidget(ILeftBodyTouch, MDIconButton): 113 | pass 114 | 115 | 116 | class Example(App): 117 | theme_cls = ThemeManager() 118 | theme_cls.primary_palette = 'Blue' 119 | title = "Example Accordion List" 120 | main_widget = None 121 | 122 | def build(self): 123 | self.main_widget = Factory.ExampleAccordionList() 124 | return self.main_widget 125 | 126 | def on_start(self): 127 | def callback(text): 128 | toast(f'{text} to {content.name_item}') 129 | 130 | content = ContentForAnimCard(callback=callback) 131 | names_contacts = ( 132 | 'Alexandr Taylor', 'Yuri Ivanov', 'Robert Patric', 'Bob Marley', 133 | 'Magnus Carlsen', 'Jon Romero', 'Anna Bell', 'Maxim Kramerer', 134 | 'Sasha Gray', 'Vladimir Ivanenko') 135 | 136 | for name_contact in names_contacts: 137 | self.main_widget.ids.anim_list.add_widget( 138 | MDAccordionListItem(content=content, 139 | icon='data/logo/kivy-icon-128.png', 140 | title=name_contact)) 141 | 142 | 143 | Example().run() 144 | """ 145 | 146 | from kivy.lang import Builder 147 | from kivy.animation import Animation 148 | from kivy.metrics import dp 149 | from kivy.properties import ObjectProperty, NumericProperty, StringProperty 150 | from kivy.uix.boxlayout import BoxLayout 151 | from kivy.uix.image import Image 152 | 153 | from kivymd.button import MDIconButton 154 | from kivymd.list import IRightBodyTouch, OneLineAvatarIconListItem, ILeftBody 155 | 156 | Builder.load_string(''' 157 | 158 | text: root.title 159 | 160 | AvatarLeft: 161 | source: root.icon 162 | 163 | ChevronRight: 164 | icon: 'chevron-right' 165 | disabled: True 166 | 167 | canvas.before: 168 | PushMatrix 169 | Rotate: 170 | angle: self.angle 171 | axis: (0, 0, 1) 172 | origin: self.center 173 | canvas.after: 174 | PopMatrix 175 | 176 | 177 | 178 | size_hint_y: None 179 | height: dp(68) 180 | 181 | BoxLayout: 182 | id: box_item 183 | size_hint_y: None 184 | height: root.height 185 | orientation: 'vertical' 186 | 187 | AccordionListItem: 188 | id: item_anim 189 | title: root.title 190 | icon: root.icon 191 | _no_ripple_effect: True 192 | on_press: root.check_open_box(self) 193 | ''') 194 | 195 | 196 | class AvatarLeft(ILeftBody, Image): 197 | pass 198 | 199 | 200 | class ChevronRight(IRightBodyTouch, MDIconButton): 201 | angle = NumericProperty(0) 202 | 203 | 204 | class AccordionListItem(OneLineAvatarIconListItem): 205 | title = StringProperty() 206 | icon = StringProperty() 207 | 208 | 209 | class MDAccordionListItem(BoxLayout): 210 | content = ObjectProperty() 211 | icon = StringProperty() 212 | title = StringProperty() 213 | 214 | def check_open_box(self, instance): 215 | press_current_item = False 216 | 217 | for box in self.parent.children: 218 | if len(box.ids.box_item.children) == 2: 219 | if instance is box.ids.item_anim: 220 | press_current_item = True 221 | box.ids.box_item.remove_widget(box.ids.box_item.children[0]) 222 | chevron = box.ids.box_item.children[0].children[0].children[0] 223 | self.anim_chevron_up(chevron) 224 | self.anim_resize_close(box) 225 | break 226 | 227 | if not press_current_item: 228 | self.anim_chevron_down() 229 | 230 | def anim_chevron_down(self): 231 | chevron = self.ids.item_anim.children[0].children[0] 232 | angle = -90 233 | Animation(angle=angle, d=.2).start(chevron) 234 | self.anim_resize_open_item() 235 | 236 | def anim_chevron_up(self, inctance): 237 | angle = 0 238 | Animation(angle=angle, d=.2).start(inctance) 239 | 240 | def anim_resize_close(self, box): 241 | Animation(height=dp(68), d=.1, t='in_cubic').start(box) 242 | 243 | def anim_resize_open_item(self, *args): 244 | self.content.name_item = self.title 245 | anim = Animation(height=self.content.height + dp(70), 246 | d=.2, t='in_cubic') 247 | anim.bind(on_complete=self.add_content) 248 | anim.start(self) 249 | 250 | def add_content(self, *args): 251 | if self.content: 252 | self.ids.box_item.add_widget(self.content) 253 | -------------------------------------------------------------------------------- /kivymd/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 | canvas: 27 | Color: 28 | rgba: self.md_bg_color 29 | Rectangle: 30 | size: self.size 31 | pos: self.pos 32 | ''') 33 | 34 | 35 | class BackgroundColorBehavior(Widget): 36 | r = BoundedNumericProperty(1., min=.0, max=1.) 37 | g = BoundedNumericProperty(1., min=.0, max=1.) 38 | b = BoundedNumericProperty(1., min=.0, max=1.) 39 | a = BoundedNumericProperty(.0, min=.0, max=1.) 40 | 41 | md_bg_color = ReferenceListProperty(r, g, b, a) 42 | 43 | 44 | class SpecificBackgroundColorBehavior(BackgroundColorBehavior): 45 | background_palette = OptionProperty( 46 | 'Primary', options=['Primary', 'Accent', *palette]) 47 | background_hue = OptionProperty('500', options=hue) 48 | 49 | specific_text_color = ListProperty([0, 0, 0, .87]) 50 | specific_secondary_text_color = ListProperty([0, 0, 0, .87]) 51 | 52 | def _update_specific_text_color(self, instance, value): 53 | if hasattr(self, 'theme_cls'): 54 | palette = {'Primary': self.theme_cls.primary_palette, 55 | 'Accent': self.theme_cls.accent_palette 56 | }.get(self.background_palette, self.background_palette) 57 | else: 58 | palette = {'Primary': 'Blue', 59 | 'Accent': 'Amber' 60 | }.get(self.background_palette, self.background_palette) 61 | color = get_color_from_hex(text_colors[palette][self.background_hue]) 62 | secondary_color = color[:] 63 | # Check for black text (need to adjust opacity) 64 | if (color[0] + color[1] + color[2]) == 0: 65 | color[3] = .87 66 | secondary_color[3] = .54 67 | else: 68 | secondary_color[3] = .7 69 | self.specific_text_color = color 70 | self.specific_secondary_text_color = secondary_color 71 | 72 | def __init__(self, **kwargs): 73 | super().__init__(**kwargs) 74 | if hasattr(self, 'theme_cls'): 75 | self.theme_cls.bind(primary_palette=self._update_specific_text_color) 76 | self.theme_cls.bind(accent_palette=self._update_specific_text_color) 77 | self.theme_cls.bind(theme_style=self._update_specific_text_color) 78 | self.bind(background_hue=self._update_specific_text_color) 79 | self.bind(background_palette=self._update_specific_text_color) 80 | self._update_specific_text_color(None, None) 81 | -------------------------------------------------------------------------------- /kivymd/bottomsheet.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 | `Material Design spec, Sheets: bottom `_ 17 | 18 | In this module there's the :class:`MDBottomSheet` class which will let you implement your own Material Design Bottom Sheets, and there are two classes called :class:`MDListBottomSheet` and :class:`MDGridBottomSheet` implementing the ones mentioned in the spec. 19 | 20 | Example 21 | ------- 22 | 23 | .. note:: 24 | 25 | These widgets are designed to be called from Python code only. 26 | 27 | For :class:`MDListBottomSheet`: 28 | 29 | .. code-block:: python 30 | 31 | bs = MDListBottomSheet() 32 | bs.add_item("Here's an item with text only", lambda x: x) 33 | bs.add_item("Here's an item with an icon", lambda x: x, icon='md-cast') 34 | bs.add_item("Here's another!", lambda x: x, icon='md-nfc') 35 | bs.open() 36 | 37 | For :class:`MDListBottomSheet`: 38 | 39 | .. code-block:: python 40 | 41 | bs = MDGridBottomSheet() 42 | bs.add_item("Facebook", lambda x: x, icon_src='./assets/facebook-box.png') 43 | bs.add_item("YouTube", lambda x: x, icon_src='./assets/youtube-play.png') 44 | bs.add_item("Twitter", lambda x: x, icon_src='./assets/twitter.png') 45 | bs.add_item("Da Cloud", lambda x: x, icon_src='./assets/cloud-upload.png') 46 | bs.add_item("Camera", lambda x: x, icon_src='./assets/camera.png') 47 | bs.open() 48 | """ 49 | 50 | from kivy.clock import Clock 51 | from kivy.lang import Builder 52 | from kivy.metrics import dp 53 | from kivy.properties import ObjectProperty, StringProperty 54 | from kivy.uix.behaviors import ButtonBehavior 55 | from kivy.uix.boxlayout import BoxLayout 56 | from kivy.uix.floatlayout import FloatLayout 57 | from kivy.uix.gridlayout import GridLayout 58 | from kivy.uix.modalview import ModalView 59 | 60 | from kivymd import images_path 61 | from kivymd.backgroundcolorbehavior import BackgroundColorBehavior 62 | from kivymd.label import MDIcon 63 | from kivymd.list import MDList, OneLineListItem, ILeftBody,\ 64 | OneLineIconListItem 65 | from kivymd.theming import ThemableBehavior 66 | 67 | Builder.load_string(''' 68 | 69 | md_bg_color: 0, 0, 0, .8 70 | upper_padding: upper_padding 71 | gl_content: gl_content 72 | 73 | BoxLayout: 74 | size_hint_y: None 75 | orientation: 'vertical' 76 | padding: 0, 1, 0, 0 77 | height: upper_padding.height + gl_content.height + 1 78 | 79 | BsPadding: 80 | id: upper_padding 81 | size_hint_y: None 82 | height: root.height - min(root.width * 9 / 16, gl_content.height) 83 | on_release: root.dismiss() 84 | 85 | BottomSheetContent: 86 | id: gl_content 87 | size_hint_y: None 88 | md_bg_color: root.theme_cls.bg_normal 89 | cols: 1 90 | ''') 91 | 92 | 93 | class BsPadding(ButtonBehavior, FloatLayout): 94 | pass 95 | 96 | 97 | class BottomSheetContent(BackgroundColorBehavior, GridLayout): 98 | pass 99 | 100 | 101 | class MDBottomSheet(ThemableBehavior, ModalView): 102 | background = f'{images_path}transparent.png' 103 | upper_padding = ObjectProperty() 104 | gl_content = ObjectProperty() 105 | 106 | def open(self, *largs): 107 | super().open(*largs) 108 | 109 | def add_widget(self, widget, index=0, canvas=None): 110 | super().add_widget(widget, index, canvas) 111 | 112 | 113 | Builder.load_string(''' 114 | #:import md_icons kivymd.icon_definitions.md_icons 115 | 116 | 117 | 118 | halign: 'center' 119 | theme_text_color: 'Primary' 120 | valign: 'middle' 121 | ''') 122 | 123 | 124 | class ListBSIconLeft(ILeftBody, MDIcon): 125 | pass 126 | 127 | 128 | class MDListBottomSheet(MDBottomSheet): 129 | mlist = ObjectProperty() 130 | 131 | def __init__(self, **kwargs): 132 | super().__init__(**kwargs) 133 | self.mlist = MDList() 134 | self.gl_content.add_widget(self.mlist) 135 | Clock.schedule_once(self.resize_content_layout, 0) 136 | 137 | def resize_content_layout(self, *largs): 138 | self.gl_content.height = self.mlist.height 139 | 140 | def add_item(self, text, callback, icon=None): 141 | if icon: 142 | item = OneLineIconListItem(text=text, on_release=callback) 143 | item.add_widget(ListBSIconLeft(icon=icon)) 144 | else: 145 | item = OneLineListItem(text=text, on_release=callback) 146 | item.bind(on_release=lambda x: self.dismiss()) 147 | self.mlist.add_widget(item) 148 | 149 | 150 | Builder.load_string(''' 151 | #:import MDLabel kivymd.label.MDLabel 152 | 153 | 154 | 155 | orientation: 'vertical' 156 | padding: 0, dp(24), 0, 0 157 | size_hint_y: None 158 | size: dp(64), dp(96) 159 | 160 | BoxLayout: 161 | padding: dp(8), 0, dp(8), dp(8) 162 | size_hint_y: None 163 | height: dp(48) 164 | 165 | Image: 166 | source: root.source 167 | 168 | MDLabel: 169 | font_style: 'Caption' 170 | theme_text_color: 'Secondary' 171 | text: root.caption 172 | halign: 'center' 173 | ''') 174 | 175 | 176 | class GridBSItem(ButtonBehavior, BoxLayout): 177 | source = StringProperty() 178 | caption = StringProperty() 179 | 180 | 181 | class MDGridBottomSheet(MDBottomSheet): 182 | def __init__(self, **kwargs): 183 | super().__init__(**kwargs) 184 | self.gl_content.padding = (dp(16), 0, dp(16), dp(24)) 185 | self.gl_content.height = dp(24) 186 | self.gl_content.cols = 3 187 | 188 | def add_item(self, text, callback, icon_src): 189 | item = GridBSItem(caption=text, on_release=callback, source=icon_src) 190 | item.bind(on_release=lambda x: self.dismiss()) 191 | if len(self.gl_content.children) % 3 == 0: 192 | self.gl_content.height += dp(96) 193 | self.gl_content.add_widget(item) 194 | -------------------------------------------------------------------------------- /kivymd/card.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from kivy.lang import Builder 3 | from kivy.properties import BoundedNumericProperty, ReferenceListProperty, ListProperty,BooleanProperty 4 | from kivy.uix.boxlayout import BoxLayout 5 | from kivymd.elevationbehavior import RectangularElevationBehavior 6 | from kivymd.theming import ThemableBehavior 7 | from kivy.metrics import dp 8 | from kivy.uix.widget import Widget 9 | 10 | Builder.load_string(''' 11 | 12 | canvas: 13 | Color: 14 | rgba: self.md_bg_color 15 | RoundedRectangle: 16 | size: self.size 17 | pos: self.pos 18 | radius: [self.border_radius] 19 | Color: 20 | rgba: self.theme_cls.divider_color 21 | a: self.border_color_a 22 | Line: 23 | rounded_rectangle: (self.pos[0],self.pos[1],self.size[0],self.size[1],self.border_radius) 24 | md_bg_color: self.theme_cls.bg_light 25 | 26 | 27 | canvas: 28 | Color: 29 | rgba: self.theme_cls.divider_color 30 | Rectangle: 31 | size: self.size 32 | pos: self.pos 33 | ''') 34 | 35 | 36 | class MDSeparator(ThemableBehavior, BoxLayout): 37 | """ A separator line """ 38 | def __init__(self, *args, **kwargs): 39 | super(MDSeparator, self).__init__(*args, **kwargs) 40 | self.on_orientation() 41 | 42 | def on_orientation(self,*args): 43 | self.size_hint = (1, None) if self.orientation == 'horizontal' else (None, 1) 44 | if self.orientation == 'horizontal': 45 | self.height = dp(1) 46 | else: 47 | self.width = dp(1) 48 | 49 | 50 | class MDCard(ThemableBehavior, RectangularElevationBehavior, BoxLayout): 51 | r = BoundedNumericProperty(1., min=0., max=1.) 52 | g = BoundedNumericProperty(1., min=0., max=1.) 53 | b = BoundedNumericProperty(1., min=0., max=1.) 54 | a = BoundedNumericProperty(0., min=0., max=1.) 55 | 56 | border_radius = BoundedNumericProperty(dp(3),min=0) 57 | border_color_a = BoundedNumericProperty(0, min=0., max=1.) 58 | md_bg_color = ReferenceListProperty(r, g, b, a) 59 | -------------------------------------------------------------------------------- /kivymd/chips.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 kivy.app import App 19 | from kivy.lang import Builder 20 | 21 | from kivymd.theming import ThemeManager 22 | 23 | kv = ''' 24 | #:import MDToolbar kivymd.toolbar.MDToolbar 25 | #:import MDChip kivymd.chips.MDChip 26 | #:import MDChooseChip kivymd.chips.MDChooseChip 27 | #:import MDSeparator kivymd.cards.MDSeparator 28 | #:import MDLabel kivymd.label.MDLabel 29 | 30 | 31 | BoxLayout: 32 | orientation: 'vertical' 33 | spacing: dp(10) 34 | 35 | MDToolbar: 36 | title: 'Example Chips' 37 | md_bg_color: app.theme_cls.primary_color 38 | left_action_items: [['menu', lambda x: x]] 39 | background_palette: 'Primary' 40 | 41 | ScrollView: 42 | 43 | GridLayout: 44 | padding: dp(10) 45 | spacing: dp(10) 46 | cols: 1 47 | size_hint_y: None 48 | height: self.minimum_height 49 | 50 | MDLabel: 51 | text: 'Chips with color:' 52 | 53 | MDSeparator: 54 | 55 | StackLayout: 56 | size_hint_y: None 57 | height: self.minimum_height 58 | spacing: dp(5) 59 | 60 | MDChip: 61 | label: 'Coffee' 62 | color: .4470588235294118, .19607843137254902, 0, 1 63 | icon: 'coffee' 64 | callback: app.callback 65 | 66 | MDChip: 67 | label: 'Duck' 68 | color: .9215686274509803, 0, 0, 1 69 | icon: 'duck' 70 | callback: app.callback 71 | 72 | MDChip: 73 | label: 'Earth' 74 | color: .21176470588235294, .09803921568627451, 1, 1 75 | icon: 'earth' 76 | callback: app.callback 77 | 78 | MDChip: 79 | label: 'Face' 80 | color: .20392156865098, .48235294117606, .43529411764705883, 1 81 | icon: 'face' 82 | callback: app.callback 83 | 84 | MDChip: 85 | label: 'Facebook' 86 | color: .5607843137254902, .48235294164706, .435294117705883, 1 87 | icon: 'facebook' 88 | callback: app.callback 89 | 90 | Widget: 91 | size_hint_y: None 92 | height: dp(5) 93 | 94 | MDLabel: 95 | text: 'Chip without icon:' 96 | 97 | MDSeparator: 98 | 99 | StackLayout: 100 | size_hint_y: None 101 | height: self.minimum_height 102 | spacing: dp(5) 103 | 104 | MDChip: 105 | label: 'Without icon' 106 | icon: '' 107 | callback: app.callback 108 | 109 | Widget: 110 | size_hint_y: None 111 | height: dp(5) 112 | 113 | MDLabel: 114 | text: 'Chips with check:' 115 | 116 | MDSeparator: 117 | 118 | StackLayout: 119 | size_hint_y: None 120 | height: self.minimum_height 121 | spacing: dp(5) 122 | 123 | MDChip: 124 | label: 'Check' 125 | icon: '' 126 | check: True 127 | callback: app.callback 128 | 129 | MDChip: 130 | label: 'Check with icon' 131 | icon: 'city' 132 | check: True 133 | callback: app.callback 134 | Widget: 135 | size_hint_y: None 136 | height: dp(5) 137 | 138 | MDLabel: 139 | text: 'Choose chip:' 140 | 141 | MDSeparator: 142 | 143 | MDChooseChip: 144 | 145 | MDChip: 146 | label: 'Earth' 147 | icon: 'earth' 148 | callback: app.callback 149 | 150 | MDChip: 151 | label: 'Face' 152 | icon: 'face' 153 | callback: app.callback 154 | 155 | MDChip: 156 | label: 'Facebook' 157 | icon: 'facebook' 158 | callback: app.callback 159 | ''' 160 | 161 | 162 | class MyApp(App): 163 | theme_cls = ThemeManager() 164 | theme_cls.primary_palette = 'Red' 165 | 166 | def callback(self, name_chip): 167 | pass 168 | 169 | def build(self): 170 | return Builder.load_string(kv) 171 | 172 | 173 | MyApp().run() 174 | """ 175 | 176 | from kivy.metrics import dp 177 | from kivy.properties import StringProperty, ListProperty, ObjectProperty,\ 178 | BooleanProperty 179 | from kivy.uix.boxlayout import BoxLayout 180 | from kivy.lang import Builder 181 | from kivy.uix.stacklayout import StackLayout 182 | 183 | from kivymd.button import MDIconButton 184 | from kivymd.theming import ThemableBehavior 185 | 186 | Builder.load_string(''' 187 | #:import MDIconButton kivymd.button.MDIconButton 188 | 189 | 190 | 191 | size_hint_y: None 192 | height: self.minimum_height 193 | spacing: dp(5) 194 | 195 | 196 | 197 | size_hint: None, None 198 | height: dp(26) 199 | width: 200 | self.minimum_width - dp(10) if root.icon != 'checkbox-blank-circle'\ 201 | else self.minimum_width 202 | 203 | canvas: 204 | Color: 205 | rgba: root.color 206 | RoundedRectangle: 207 | pos: self.pos 208 | size: self.size 209 | radius: [15, ] 210 | 211 | BoxLayout: 212 | id: box_check 213 | size_hint: None, None 214 | size: self.minimum_size 215 | pos_hint: {'center_y': .5} 216 | 217 | BoxLayout: 218 | size_hint_x: None 219 | width: self.minimum_width 220 | padding: dp(10) 221 | 222 | Label: 223 | id: label 224 | text: root.label 225 | size_hint_x: None 226 | width: self.texture_size[0] 227 | 228 | MDIconButton: 229 | id: icon 230 | icon: root.icon 231 | size_hint_y: None 232 | height: dp(26) 233 | disabled: True 234 | ''') 235 | 236 | 237 | class MDChip(BoxLayout, ThemableBehavior): 238 | label = StringProperty() 239 | icon = StringProperty('checkbox-blank-circle') 240 | color = ListProperty([.4, .4, .4, 1]) 241 | check = BooleanProperty(False) 242 | callback = ObjectProperty(lambda x: None) 243 | 244 | def on_icon(self, instance, value): 245 | if value == '': 246 | self.icon = 'checkbox-blank-circle' 247 | self.remove_widget(self.ids.icon) 248 | 249 | def on_touch_down(self, touch): 250 | if self.collide_point(*touch.pos): 251 | self.callback(self.label) 252 | md_choose_chip = self.parent 253 | if md_choose_chip.__class__ is MDChooseChip: 254 | if md_choose_chip.selected_chip: 255 | md_choose_chip.selected_chip.color =\ 256 | md_choose_chip.selected_chip_color 257 | md_choose_chip.selected_chip = self 258 | md_choose_chip.selected_chip_color = self.color 259 | self.color = self.theme_cls.primary_color 260 | if self.check: 261 | if not len(self.ids.box_check.children): 262 | self.ids.box_check.add_widget( 263 | MDIconButton(icon='check', size_hint_y=None, 264 | height=dp(26), 265 | disabled=True)) 266 | else: 267 | check = self.ids.box_check.children[0] 268 | self.ids.box_check.remove_widget(check) 269 | 270 | 271 | class MDChooseChip(StackLayout): 272 | selected_chip = None 273 | selected_chip_color = None 274 | -------------------------------------------------------------------------------- /kivymd/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 | canvas.before: 26 | Color: 27 | a: self._soft_shadow_a 28 | Rectangle: 29 | texture: self._soft_shadow_texture 30 | size: self._soft_shadow_size 31 | pos: self._soft_shadow_pos 32 | Color: 33 | a: self._hard_shadow_a 34 | Rectangle: 35 | texture: self._hard_shadow_texture 36 | size: self._hard_shadow_size 37 | pos: self._hard_shadow_pos 38 | Color: 39 | a: 1 40 | 41 | 42 | 43 | canvas.before: 44 | Color: 45 | a: self._soft_shadow_a 46 | Rectangle: 47 | texture: self._soft_shadow_texture 48 | size: self._soft_shadow_size 49 | pos: self._soft_shadow_pos 50 | Color: 51 | a: self._hard_shadow_a 52 | Rectangle: 53 | texture: self._hard_shadow_texture 54 | size: self._hard_shadow_size 55 | pos: self._hard_shadow_pos 56 | Color: 57 | a: 1 58 | ''') 59 | 60 | 61 | class CommonElevationBehavior(object): 62 | _elevation = NumericProperty(1) 63 | 64 | def _get_elevation(self): 65 | return self._elevation 66 | 67 | def _set_elevation(self, elevation): 68 | try: 69 | self._elevation = elevation 70 | except KeyError: 71 | self._elevation = 1 72 | 73 | elevation = AliasProperty(_get_elevation, _set_elevation, 74 | bind=('_elevation', )) 75 | 76 | _soft_shadow_texture = ObjectProperty() 77 | _soft_shadow_size = ListProperty([0, 0]) 78 | _soft_shadow_pos = ListProperty([0, 0]) 79 | _soft_shadow_a = NumericProperty(0) 80 | _hard_shadow_texture = ObjectProperty() 81 | _hard_shadow_size = ListProperty([0, 0]) 82 | _hard_shadow_pos = ListProperty([0, 0]) 83 | _hard_shadow_a = NumericProperty(0) 84 | 85 | def __init__(self, **kwargs): 86 | super().__init__(**kwargs) 87 | self.bind(elevation=self._update_shadow, 88 | pos=self._update_shadow, 89 | size=self._update_shadow) 90 | 91 | def _update_shadow(self, *args): 92 | raise NotImplemented 93 | 94 | 95 | class RectangularElevationBehavior(CommonElevationBehavior): 96 | def _update_shadow(self, *args): 97 | if self.elevation > 0: 98 | ratio = self.width / (self.height if self.height != 0 else 1) 99 | if -2 < ratio < 2: 100 | self._shadow = App.get_running_app().theme_cls.quad_shadow 101 | width = soft_width = self.width * 1.9 102 | height = soft_height = self.height * 1.9 103 | elif ratio <= -2: 104 | self._shadow = App.get_running_app().theme_cls.rec_st_shadow 105 | ratio = abs(ratio) 106 | if ratio > 5: 107 | ratio = ratio * 22 108 | else: 109 | ratio = ratio * 11.5 110 | 111 | width = soft_width = self.width * 1.9 112 | height = self.height + dp(ratio) 113 | soft_height = self.height + dp(ratio) + dp(self.elevation) * .5 114 | else: 115 | self._shadow = App.get_running_app().theme_cls.quad_shadow 116 | width = soft_width = self.width * 1.8 117 | height = soft_height = self.height * 1.8 118 | 119 | x = self.center_x - width / 2 120 | soft_x = self.center_x - soft_width / 2 121 | self._soft_shadow_size = (soft_width, soft_height) 122 | self._hard_shadow_size = (width, height) 123 | 124 | y = self.center_y - soft_height / 2 - dp( 125 | .1 * 1.5 ** self.elevation) 126 | self._soft_shadow_pos = (soft_x, y) 127 | self._soft_shadow_a = .1 * 1.1 ** self.elevation 128 | self._soft_shadow_texture = self._shadow.textures[ 129 | str(int(round(self.elevation - 1)))] 130 | 131 | y = self.center_y - height / 2 - dp(.5 * 1.18 ** self.elevation) 132 | self._hard_shadow_pos = (x, y) 133 | self._hard_shadow_a = .4 * .9 ** self.elevation 134 | self._hard_shadow_texture = self._shadow.textures[ 135 | str(int(round(self.elevation)))] 136 | 137 | else: 138 | self._soft_shadow_a = 0 139 | self._hard_shadow_a = 0 140 | 141 | 142 | class CircularElevationBehavior(CommonElevationBehavior): 143 | def __init__(self, **kwargs): 144 | super().__init__(**kwargs) 145 | self._shadow = App.get_running_app().theme_cls.round_shadow 146 | 147 | def _update_shadow(self, *args): 148 | if self.elevation > 0: 149 | width = self.width * 2 150 | height = self.height * 2 151 | 152 | x = self.center_x - width / 2 153 | self._soft_shadow_size = (width, height) 154 | 155 | self._hard_shadow_size = (width, height) 156 | 157 | y = self.center_y - height / 2 - dp(.1 * 1.5 ** self.elevation) 158 | self._soft_shadow_pos = (x, y) 159 | self._soft_shadow_a = .1 * 1.1 ** self.elevation 160 | self._soft_shadow_texture = self._shadow.textures[ 161 | str(int(round(self.elevation)))] 162 | 163 | y = self.center_y - height / 2 - dp(.5 * 1.18 ** self.elevation) 164 | self._hard_shadow_pos = (x, y) 165 | self._hard_shadow_a = .4 * .9 ** self.elevation 166 | self._hard_shadow_texture = self._shadow.textures[ 167 | str(int(round(self.elevation - 1)))] 168 | 169 | else: 170 | self._soft_shadow_a = 0 171 | self._hard_shadow_a = 0 172 | -------------------------------------------------------------------------------- /kivymd/elevationbehavior.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.app import App 4 | from kivy.lang import Builder 5 | from kivy.properties import (ListProperty, ObjectProperty, NumericProperty) 6 | from kivy.properties import AliasProperty 7 | from kivy.metrics import dp 8 | 9 | Builder.load_string(''' 10 | 11 | canvas.before: 12 | Color: 13 | a: self._soft_shadow_a 14 | Rectangle: 15 | texture: self._soft_shadow_texture 16 | size: self._soft_shadow_size 17 | pos: self._soft_shadow_pos 18 | Color: 19 | a: self._hard_shadow_a 20 | Rectangle: 21 | texture: self._hard_shadow_texture 22 | size: self._hard_shadow_size 23 | pos: self._hard_shadow_pos 24 | Color: 25 | a: 1 26 | 27 | 28 | canvas.before: 29 | Color: 30 | a: self._soft_shadow_a 31 | Rectangle: 32 | texture: self._soft_shadow_texture 33 | size: self._soft_shadow_size 34 | pos: self._soft_shadow_pos 35 | Color: 36 | a: self._hard_shadow_a 37 | Rectangle: 38 | texture: self._hard_shadow_texture 39 | size: self._hard_shadow_size 40 | pos: self._hard_shadow_pos 41 | Color: 42 | a: 1 43 | ''') 44 | 45 | 46 | class CommonElevationBehavior(object): 47 | _elevation = NumericProperty(1) 48 | 49 | def _get_elevation(self): 50 | return self._elevation 51 | 52 | def _set_elevation(self, elevation): 53 | try: 54 | self._elevation = elevation 55 | except: 56 | self._elevation = 1 57 | 58 | elevation = AliasProperty(_get_elevation, _set_elevation, 59 | bind=('_elevation',)) 60 | 61 | _soft_shadow_texture = ObjectProperty() 62 | _soft_shadow_size = ListProperty([0, 0]) 63 | _soft_shadow_pos = ListProperty([0, 0]) 64 | _soft_shadow_a = NumericProperty(0) 65 | _hard_shadow_texture = ObjectProperty() 66 | _hard_shadow_size = ListProperty([0, 0]) 67 | _hard_shadow_pos = ListProperty([0, 0]) 68 | _hard_shadow_a = NumericProperty(0) 69 | 70 | def __init__(self, **kwargs): 71 | super(CommonElevationBehavior, self).__init__(**kwargs) 72 | self.bind(elevation=self._update_shadow, 73 | pos=self._update_shadow, 74 | size=self._update_shadow) 75 | 76 | def _update_shadow(self, *args): 77 | raise NotImplemented 78 | 79 | class RectangularElevationBehavior(CommonElevationBehavior): 80 | def _update_shadow(self, *args): 81 | if self.elevation > 0: 82 | ratio = self.width / (self.height if self.height != 0 else 1) 83 | if ratio > -2 and ratio < 2: 84 | self._shadow = App.get_running_app().theme_cls.quad_shadow 85 | width = soft_width = self.width * 1.9 86 | height = soft_height = self.height * 1.9 87 | elif ratio <= -2: 88 | self._shadow = App.get_running_app().theme_cls.rec_st_shadow 89 | ratio = abs(ratio) 90 | if ratio > 5: 91 | ratio = ratio * 22 92 | else: 93 | ratio = ratio * 11.5 94 | 95 | width = soft_width = self.width * 1.9 96 | height = self.height + dp(ratio) 97 | soft_height = self.height + dp(ratio) + dp(self.elevation) * .5 98 | else: 99 | self._shadow = App.get_running_app().theme_cls.quad_shadow 100 | width = soft_width = self.width * 1.8 101 | height = soft_height = self.height * 1.8 102 | # self._shadow = App.get_running_app().theme_cls.rec_shadow 103 | # ratio = abs(ratio) 104 | # if ratio > 5: 105 | # ratio = ratio * 22 106 | # else: 107 | # ratio = ratio * 11.5 108 | # 109 | # width = self.width + dp(ratio) 110 | # soft_width = self.width + dp(ratio) + dp(self.elevation) * .9 111 | # height = soft_height = self.height * 1.9 112 | 113 | x = self.center_x - width / 2 114 | soft_x = self.center_x - soft_width / 2 115 | self._soft_shadow_size = (soft_width, soft_height) 116 | self._hard_shadow_size = (width, height) 117 | 118 | y = self.center_y - soft_height / 2 - dp( 119 | .1 * 1.5 ** self.elevation) 120 | self._soft_shadow_pos = (soft_x, y) 121 | self._soft_shadow_a = 0.1 * 1.1 ** self.elevation 122 | self._soft_shadow_texture = self._shadow.textures[ 123 | str(int(round(self.elevation - 1)))] 124 | 125 | y = self.center_y - height / 2 - dp(.5 * 1.18 ** self.elevation) 126 | self._hard_shadow_pos = (x, y) 127 | self._hard_shadow_a = .4 * .9 ** self.elevation 128 | self._hard_shadow_texture = self._shadow.textures[ 129 | str(int(round(self.elevation)))] 130 | 131 | else: 132 | self._soft_shadow_a = 0 133 | self._hard_shadow_a = 0 134 | 135 | 136 | class CircularElevationBehavior(CommonElevationBehavior): 137 | def __init__(self, **kwargs): 138 | super(CircularElevationBehavior, self).__init__(**kwargs) 139 | self._shadow = App.get_running_app().theme_cls.round_shadow 140 | 141 | def _update_shadow(self, *args): 142 | if self.elevation > 0: 143 | width = self.width * 2 144 | height = self.height * 2 145 | 146 | x = self.center_x - width / 2 147 | self._soft_shadow_size = (width, height) 148 | 149 | self._hard_shadow_size = (width, height) 150 | 151 | y = self.center_y - height / 2 - dp(.1 * 1.5 ** self.elevation) 152 | self._soft_shadow_pos = (x, y) 153 | self._soft_shadow_a = 0.1 * 1.1 ** self.elevation 154 | self._soft_shadow_texture = self._shadow.textures[ 155 | str(int(round(self.elevation)))] 156 | 157 | y = self.center_y - height / 2 - dp(.5 * 1.18 ** self.elevation) 158 | self._hard_shadow_pos = (x, y) 159 | self._hard_shadow_a = .4 * .9 ** self.elevation 160 | self._hard_shadow_texture = self._shadow.textures[ 161 | str(int(round(self.elevation - 1)))] 162 | 163 | else: 164 | self._soft_shadow_a = 0 165 | self._hard_shadow_a = 0 166 | -------------------------------------------------------------------------------- /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 = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Subtitle1', 60 | 'Subtitle2', 'Body1', 'Body2', 'Button', 'Caption', 61 | 'Overline', 'Icon'] 62 | -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /kivymd/grid.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from kivy.lang import Builder 3 | from kivy.properties import StringProperty, BooleanProperty, ObjectProperty, \ 4 | NumericProperty, ListProperty, OptionProperty 5 | from kivy.uix.behaviors import ButtonBehavior 6 | from kivy.uix.boxlayout import BoxLayout 7 | from kivy.uix.floatlayout import FloatLayout 8 | from kivymd.ripplebehavior import RectangularRippleBehavior 9 | from kivymd.theming import ThemableBehavior 10 | 11 | Builder.load_string(""" 12 | 13 | _img_widget: img 14 | _img_overlay: img_overlay 15 | _box_overlay: box 16 | AsyncImage: 17 | id: img 18 | allow_stretch: root.allow_stretch 19 | anim_delay: root.anim_delay 20 | anim_loop: root.anim_loop 21 | color: root.img_color 22 | keep_ratio: root.keep_ratio 23 | mipmap: root.mipmap 24 | source: root.source 25 | size_hint_y: 1 if root.overlap else None 26 | x: root.x 27 | y: root.y if root.overlap or root.box_position == 'header' else box.top 28 | BoxLayout: 29 | id: img_overlay 30 | size_hint: img.size_hint 31 | size: img.size 32 | pos: img.pos 33 | BoxLayout: 34 | canvas: 35 | Color: 36 | rgba: root.box_color 37 | Rectangle: 38 | pos: self.pos 39 | size: self.size 40 | id: box 41 | size_hint_y: None 42 | height: dp(68) if root.lines == 2 else dp(48) 43 | x: root.x 44 | y: root.y if root.box_position == 'footer' else root.y + root.height - self.height 45 | 46 | 47 | _img_widget: img 48 | _img_overlay: img_overlay 49 | _box_overlay: box 50 | _box_label: boxlabel 51 | AsyncImage: 52 | id: img 53 | allow_stretch: root.allow_stretch 54 | anim_delay: root.anim_delay 55 | anim_loop: root.anim_loop 56 | color: root.img_color 57 | keep_ratio: root.keep_ratio 58 | mipmap: root.mipmap 59 | source: root.source 60 | size_hint_y: 1 if root.overlap else None 61 | x: root.x 62 | y: root.y if root.overlap or root.box_position == 'header' else box.top 63 | BoxLayout: 64 | id: img_overlay 65 | size_hint: img.size_hint 66 | size: img.size 67 | pos: img.pos 68 | BoxLayout: 69 | canvas: 70 | Color: 71 | rgba: root.box_color 72 | Rectangle: 73 | pos: self.pos 74 | size: self.size 75 | id: box 76 | size_hint_y: None 77 | height: dp(68) if root.lines == 2 else dp(48) 78 | x: root.x 79 | y: root.y if root.box_position == 'footer' else root.y + root.height - self.height 80 | MDLabel: 81 | id: boxlabel 82 | font_style: "Caption" 83 | halign: "center" 84 | text: root.text 85 | """) 86 | 87 | 88 | class Tile(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, 89 | BoxLayout): 90 | """A simple tile. It does nothing special, just inherits the right behaviors 91 | to work as a building block. 92 | """ 93 | pass 94 | 95 | 96 | class SmartTile(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, 97 | FloatLayout): 98 | """A tile for more complex needs. 99 | 100 | Includes an image, a container to place overlays and a box that can act 101 | as a header or a footer, as described in the Material Design specs. 102 | """ 103 | 104 | box_color = ListProperty([0, 0, 0, 0.5]) 105 | """Sets the color and opacity for the information box.""" 106 | 107 | box_position = OptionProperty('footer', options=['footer', 'header']) 108 | """Determines wether the information box acts as a header or footer to the 109 | image. 110 | """ 111 | 112 | lines = OptionProperty(1, options=[1, 2]) 113 | """Number of lines in the header/footer. 114 | 115 | As per Material Design specs, only 1 and 2 are valid values. 116 | """ 117 | 118 | overlap = BooleanProperty(True) 119 | """Determines if the header/footer overlaps on top of the image or not""" 120 | 121 | # Img properties 122 | allow_stretch = BooleanProperty(True) 123 | anim_delay = NumericProperty(0.25) 124 | anim_loop = NumericProperty(0) 125 | img_color = ListProperty([1, 1, 1, 1]) 126 | keep_ratio = BooleanProperty(False) 127 | mipmap = BooleanProperty(False) 128 | source = StringProperty() 129 | 130 | _img_widget = ObjectProperty() 131 | _img_overlay = ObjectProperty() 132 | _box_overlay = ObjectProperty() 133 | _box_label = ObjectProperty() 134 | 135 | def reload(self): 136 | self._img_widget.reload() 137 | 138 | def add_widget(self, widget, index=0): 139 | if issubclass(widget.__class__, IOverlay): 140 | self._img_overlay.add_widget(widget, index) 141 | elif issubclass(widget.__class__, IBoxOverlay): 142 | self._box_overlay.add_widget(widget, index) 143 | else: 144 | super(SmartTile, self).add_widget(widget, index) 145 | 146 | 147 | class SmartTileWithLabel(SmartTile): 148 | _box_label = ObjectProperty() 149 | 150 | # MDLabel properties 151 | font_style = StringProperty("Caption") 152 | theme_text_color = StringProperty("") 153 | text = StringProperty("") 154 | """Determines the text for the box footer/header""" 155 | 156 | 157 | class IBoxOverlay(): 158 | """An interface to specify widgets that belong to to the image overlay 159 | in the :class:`SmartTile` widget when added as a child. 160 | """ 161 | pass 162 | 163 | 164 | class IOverlay(): 165 | """An interface to specify widgets that belong to to the image overlay 166 | in the :class:`SmartTile` widget when added as a child. 167 | """ 168 | pass 169 | -------------------------------------------------------------------------------- /kivymd/images/dialog_in_fade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/dialog_in_fade.png -------------------------------------------------------------------------------- /kivymd/images/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/folder.png -------------------------------------------------------------------------------- /kivymd/images/ios_bg_mod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/ios_bg_mod.png -------------------------------------------------------------------------------- /kivymd/images/ios_bg_mod_for_toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/ios_bg_mod_for_toast.png -------------------------------------------------------------------------------- /kivymd/images/ios_entr_ti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/ios_entr_ti.png -------------------------------------------------------------------------------- /kivymd/images/kivy-logo-white-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/kivy-logo-white-512.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/kivymd_512.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/kivymd_logo.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/quad_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/quad_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/quad_shadow-2.png -------------------------------------------------------------------------------- /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/rec_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/rec_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/rec_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/rec_shadow-1.png -------------------------------------------------------------------------------- /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/rec_st_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/rec_st_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/rec_st_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/rec_st_shadow-2.png -------------------------------------------------------------------------------- /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/images/round_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/round_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/round_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/round_shadow-2.png -------------------------------------------------------------------------------- /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]}} -------------------------------------------------------------------------------- /kivymd/images/swipe_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/swipe_shadow.png -------------------------------------------------------------------------------- /kivymd/images/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/images/transparent.png -------------------------------------------------------------------------------- /kivymd/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 OptionProperty, ListProperty, BooleanProperty,\ 20 | StringProperty, AliasProperty 21 | from kivy.uix.label import Label 22 | 23 | from kivymd.font_definitions import theme_font_styles 24 | from kivymd.material_resources import DEVICE_TYPE 25 | from kivymd.theming import ThemableBehavior 26 | from kivymd.theming_dynamic_text import get_contrast_text_color 27 | 28 | Builder.load_string(''' 29 | #:import md_icons kivymd.icon_definitions.md_icons 30 | 31 | 32 | 33 | disabled_color: self.theme_cls.disabled_hint_text_color 34 | text_size: (self.width, None) 35 | 36 | 37 | 38 | font_style: 'Icon' 39 | text: u'{}'.format(md_icons[self.icon]) 40 | ''') 41 | 42 | 43 | class MDLabel(ThemableBehavior, Label): 44 | font_style = OptionProperty('Body1', options=theme_font_styles) 45 | 46 | can_capitalize = BooleanProperty(True) 47 | _capitalizing = BooleanProperty(False) 48 | 49 | def _get_text(self): 50 | if self._capitalizing: 51 | return self._text.upper() 52 | return self._text 53 | 54 | def _set_text(self, value): 55 | self._text = value 56 | 57 | _text = StringProperty() 58 | text = AliasProperty(_get_text, _set_text, bind=['_text', '_capitalizing']) 59 | 60 | theme_text_color = OptionProperty(None, allownone=True, 61 | options=['Primary', 'Secondary', 'Hint', 62 | 'Error', 'Custom', 63 | 'ContrastParentBackground'] 64 | ) 65 | 66 | text_color = ListProperty(None, allownone=True) 67 | 68 | parent_background = ListProperty(None, allownone=True) 69 | 70 | _currently_bound_property = {} 71 | 72 | def __init__(self, **kwargs): 73 | super().__init__(**kwargs) 74 | self.bind(font_style=self.update_font_style, 75 | can_capitalize=self.update_font_style) 76 | self.on_theme_text_color(None, self.theme_text_color) 77 | self.update_font_style() 78 | self.on_opposite_colors(None, self.opposite_colors) 79 | 80 | def update_font_style(self, *args): 81 | font_info = self.theme_cls.font_styles[self.font_style] 82 | self.font_name = font_info[0] 83 | self.font_size = sp(font_info[1]) 84 | if font_info[2] and self.can_capitalize: 85 | self._capitalizing = True 86 | else: 87 | self._capitalizing = False 88 | # TODO: Add letter spacing change 89 | # self.letter_spacing = font_info[3] 90 | 91 | def on_theme_text_color(self, instance, value): 92 | t = self.theme_cls 93 | op = self.opposite_colors 94 | setter = self.setter('color') 95 | t.unbind(**self._currently_bound_property) 96 | attr_name = { 97 | 'Primary': 'text_color' if not op else 'opposite_text_color', 98 | 'Secondary': 'secondary_text_color' if not op else 99 | 'opposite_secondary_text_color', 100 | 'Hint': 'disabled_hint_text_color' if not op else 101 | 'opposite_disabled_hint_text_color', 102 | 'Error': 'error_color', 103 | }.get(value, None) 104 | if attr_name: 105 | c = {attr_name: setter} 106 | t.bind(**c) 107 | self._currently_bound_property = c 108 | self.color = getattr(t, attr_name) 109 | else: 110 | # 'Custom' and 'ContrastParentBackground' lead here, as well as the 111 | # generic None value it's not yet been set 112 | if value == 'Custom' and self.text_color: 113 | self.color = self.text_color 114 | elif value == 'ContrastParentBackground' and self.parent_background: 115 | self.color = get_contrast_text_color(self.parent_background) 116 | else: 117 | self.color = [0, 0, 0, 1] 118 | 119 | def on_text_color(self, *args): 120 | if self.theme_text_color == 'Custom': 121 | self.color = self.text_color 122 | 123 | def on_opposite_colors(self, instance, value): 124 | self.on_theme_text_color(self, self.theme_text_color) 125 | 126 | 127 | class MDIcon(MDLabel): 128 | icon = StringProperty('android') 129 | -------------------------------------------------------------------------------- /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 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" 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/menu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from kivy.animation import Animation 3 | from kivy.clock import Clock 4 | from kivy.core.window import Window 5 | from kivy.lang import Builder 6 | from recycleview import RecycleView 7 | from kivy.metrics import dp 8 | from kivy.properties import NumericProperty, ListProperty, OptionProperty, \ 9 | StringProperty 10 | from kivy.uix.behaviors import ButtonBehavior 11 | from kivy.uix.boxlayout import BoxLayout 12 | import kivymd.material_resources as m_res 13 | from kivymd.theming import ThemableBehavior 14 | 15 | Builder.load_string(''' 16 | #:import STD_INC kivymd.material_resources.STANDARD_INCREMENT 17 | 18 | size_hint_y: None 19 | height: dp(48) 20 | padding: dp(16), 0 21 | on_release: root.parent.parent.parent.parent.dismiss() # Horrible, but hey it works 22 | MDLabel: 23 | text: root.text 24 | theme_text_color: 'Primary' 25 | 26 | 27 | size_hint: None, None 28 | width: root.width_mult * STD_INC 29 | key_viewclass: 'viewclass' 30 | key_size: 'height' 31 | 32 | 33 | FloatLayout: 34 | id: fl 35 | MDMenu: 36 | id: md_menu 37 | data: root.items 38 | width_mult: root.width_mult 39 | size_hint: None, None 40 | size: 0,0 41 | canvas.before: 42 | Color: 43 | rgba: root.theme_cls.bg_light 44 | Rectangle: 45 | size: self.size 46 | pos: self.pos 47 | ''') 48 | 49 | 50 | class MDMenuItem(ButtonBehavior, BoxLayout): 51 | text = StringProperty() 52 | 53 | 54 | class MDMenu(RecycleView): 55 | width_mult = NumericProperty(1) 56 | 57 | 58 | class MDDropdownMenu(ThemableBehavior, BoxLayout): 59 | items = ListProperty() 60 | '''See :attr:`~kivy.garden.recycleview.RecycleView.data` 61 | ''' 62 | 63 | width_mult = NumericProperty(1) 64 | '''This number multiplied by the standard increment (56dp on mobile, 65 | 64dp on desktop, determines the width of the menu items. 66 | 67 | If the resulting number were to be too big for the application Window, 68 | the multiplier will be adjusted for the biggest possible one. 69 | ''' 70 | 71 | max_height = NumericProperty() 72 | '''The menu will grow no bigger than this number. 73 | 74 | Set to 0 for no limit. Defaults to 0. 75 | ''' 76 | 77 | border_margin = NumericProperty(dp(4)) 78 | '''Margin between Window border and menu 79 | ''' 80 | 81 | ver_growth = OptionProperty(None, allownone=True, 82 | options=['up', 'down']) 83 | '''Where the menu will grow vertically to when opening 84 | 85 | Set to None to let the widget pick for you. Defaults to None. 86 | ''' 87 | 88 | hor_growth = OptionProperty(None, allownone=True, 89 | options=['left', 'right']) 90 | '''Where the menu will grow horizontally to when opening 91 | 92 | Set to None to let the widget pick for you. Defaults to None. 93 | ''' 94 | 95 | def open(self, *largs): 96 | Window.add_widget(self) 97 | Clock.schedule_once(lambda x: self.display_menu(largs[0]), -1) 98 | 99 | def display_menu(self, caller): 100 | # We need to pick a starting point, see how big we need to be, 101 | # and where to grow to. 102 | 103 | c = caller.to_window(caller.center_x, 104 | caller.center_y) # Starting coords 105 | 106 | # ---ESTABLISH INITIAL TARGET SIZE ESTIMATE--- 107 | target_width = self.width_mult * m_res.STANDARD_INCREMENT 108 | # If we're wider than the Window... 109 | if target_width > Window.width: 110 | # ...reduce our multiplier to max allowed. 111 | target_width = int( 112 | Window.width / m_res.STANDARD_INCREMENT) * m_res.STANDARD_INCREMENT 113 | 114 | target_height = sum([dp(48) for i in self.items]) 115 | # If we're over max_height... 116 | if self.max_height > 0 and target_height > self.max_height: 117 | target_height = self.max_height 118 | 119 | # ---ESTABLISH VERTICAL GROWTH DIRECTION--- 120 | if self.ver_growth is not None: 121 | ver_growth = self.ver_growth 122 | else: 123 | # If there's enough space below us: 124 | if target_height <= c[1] - self.border_margin: 125 | ver_growth = 'down' 126 | # if there's enough space above us: 127 | elif target_height < Window.height - c[1] - self.border_margin: 128 | ver_growth = 'up' 129 | # otherwise, let's pick the one with more space and adjust ourselves 130 | else: 131 | # if there's more space below us: 132 | if c[1] >= Window.height - c[1]: 133 | ver_growth = 'down' 134 | target_height = c[1] - self.border_margin 135 | # if there's more space above us: 136 | else: 137 | ver_growth = 'up' 138 | target_height = Window.height - c[1] - self.border_margin 139 | 140 | if self.hor_growth is not None: 141 | hor_growth = self.hor_growth 142 | else: 143 | # If there's enough space to the right: 144 | if target_width <= Window.width - c[0] - self.border_margin: 145 | hor_growth = 'right' 146 | # if there's enough space to the left: 147 | elif target_width < c[0] - self.border_margin: 148 | hor_growth = 'left' 149 | # otherwise, let's pick the one with more space and adjust ourselves 150 | else: 151 | # if there's more space to the right: 152 | if Window.width - c[0] >= c[0]: 153 | hor_growth = 'right' 154 | target_width = Window.width - c[0] - self.border_margin 155 | # if there's more space to the left: 156 | else: 157 | hor_growth = 'left' 158 | target_width = c[0] - self.border_margin 159 | 160 | if ver_growth == 'down': 161 | tar_y = c[1] - target_height 162 | else: # should always be 'up' 163 | tar_y = c[1] 164 | 165 | if hor_growth == 'right': 166 | tar_x = c[0] 167 | else: # should always be 'left' 168 | tar_x = c[0] - target_width 169 | anim = Animation(x=tar_x, y=tar_y, 170 | width=target_width, height=target_height, 171 | duration=.3, transition='out_quint') 172 | menu = self.ids['md_menu'] 173 | menu.pos = c 174 | anim.start(menu) 175 | 176 | def on_touch_down(self, touch): 177 | if not self.ids['md_menu'].collide_point(*touch.pos): 178 | self.dismiss() 179 | return True 180 | super(MDDropdownMenu, self).on_touch_down(touch) 181 | return True 182 | 183 | def on_touch_move(self, touch): 184 | super(MDDropdownMenu, self).on_touch_move(touch) 185 | return True 186 | 187 | def on_touch_up(self, touch): 188 | super(MDDropdownMenu, self).on_touch_up(touch) 189 | return True 190 | 191 | def dismiss(self): 192 | Window.remove_widget(self) 193 | -------------------------------------------------------------------------------- /kivymd/popupscreen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Popup Screen 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 kivy.app import App 17 | from kivy.lang import Builder 18 | from kivy.metrics import dp 19 | from kivy.uix.boxlayout import BoxLayout 20 | 21 | from kivymd.button import MDIconButton 22 | from kivymd.list import ILeftBodyTouch 23 | from kivymd.popupscreen import MDPopupScreen 24 | from kivymd.theming import ThemeManager 25 | 26 | Builder.load_string(''' 27 | #:import get_hex_from_color kivy.utils.get_hex_from_color 28 | #:import MDToolbar kivymd.toolbar.MDToolbar 29 | #:import OneLineIconListItem kivymd.list.OneLineIconListItem 30 | #:import MDRoundFlatButton kivymd.button.MDRoundFlatButton 31 | #:import Window kivy.core.window.Window 32 | 33 | 34 | ############################################################################### 35 | # 36 | # EXAMPLE TO USE 37 | # 38 | ############################################################################### 39 | 40 | 41 | 42 | BoxLayout: 43 | orientation: 'vertical' 44 | 45 | MDToolbar: 46 | id: toolbar 47 | title: 'Example Popup Screen' 48 | md_bg_color: app.theme_cls.primary_color 49 | left_action_items: [['menu', lambda x: x]] 50 | background_palette: 'Primary' 51 | 52 | StartScreen: 53 | id: start_screen 54 | 55 | canvas.before: 56 | Color: 57 | rgba: 1, 1, 1, 1 58 | Rectangle: 59 | pos: self.pos 60 | size: self.size 61 | 62 | ############################################################################### 63 | # 64 | # YOUR ROOT SCREEN 65 | # 66 | ############################################################################### 67 | 68 | 69 | orientation: 'vertical' 70 | padding: dp(1) 71 | spacing: dp(30) 72 | 73 | Image: 74 | id: image 75 | source: 'demos/kitchen_sink/assets/tangerines-1111529_1280.jpg' 76 | size_hint: 1, None 77 | height: dp(Window.height * 35 // 100) 78 | allow_stretch: True 79 | keep_ratio: False 80 | 81 | MDRoundFlatButton: 82 | text: 'Open Menu' 83 | pos_hint: {'center_x': .5} 84 | on_release: root.parent.parent.show() 85 | 86 | Widget: 87 | 88 | ############################################################################### 89 | # 90 | # YOUR POPUP SCREEN 91 | # 92 | ############################################################################### 93 | 94 | 95 | orientation: 'vertical' 96 | 97 | BoxLayout: 98 | size_hint_y: None 99 | height: self.minimum_height 100 | 101 | Widget: 102 | MDRoundFlatButton: 103 | text: "Free call" 104 | Widget: 105 | MDRoundFlatButton: 106 | text: "Free message" 107 | Widget: 108 | 109 | OneLineIconListItem: 110 | text: "Video call" 111 | IconLeftSampleWidget: 112 | icon: 'camera-front-variant' 113 | 114 | TwoLineIconListItem: 115 | text: "Call Viber Out" 116 | secondary_text: 117 | "[color=%s]Advantageous rates for calls[/color]"\ 118 | % get_hex_from_color(app.theme_cls.primary_color) 119 | IconLeftSampleWidget: 120 | icon: 'phone' 121 | 122 | TwoLineIconListItem: 123 | text: "Call over mobile network" 124 | secondary_text: 125 | "[color=%s]Operator's tariffs apply[/color]"\ 126 | % get_hex_from_color(app.theme_cls.primary_color) 127 | IconLeftSampleWidget: 128 | icon: 'remote' 129 | 130 | Widget: 131 | ''') 132 | 133 | 134 | class PopupScreen(MDPopupScreen): 135 | pass 136 | 137 | 138 | class MyPopupScreen(BoxLayout): 139 | pass 140 | 141 | 142 | class StartScreen(BoxLayout): 143 | pass 144 | 145 | 146 | class IconLeftSampleWidget(ILeftBodyTouch, MDIconButton): 147 | pass 148 | 149 | 150 | class MyApp(App): 151 | theme_cls = ThemeManager() 152 | theme_cls.primary_palette = 'Red' 153 | 154 | def build(self): 155 | popup_screen = MyPopupScreen() 156 | root = PopupScreen(screen=popup_screen, 157 | background_color=[.3, .3, .3, 1]) 158 | root.max_height = root.ids.start_screen.ids.image.height\ 159 | + root.ids.toolbar.height + dp(5) 160 | return root 161 | 162 | 163 | MyApp().run() 164 | """ 165 | 166 | from kivy.clock import Clock 167 | from kivy.metrics import dp 168 | from kivy.uix.boxlayout import BoxLayout 169 | from kivy.uix.floatlayout import FloatLayout 170 | from kivy.animation import Animation 171 | from kivy.lang import Builder 172 | from kivy.core.window import Window 173 | from kivy.properties import ObjectProperty, ListProperty 174 | 175 | from kivymd.theming import ThemableBehavior 176 | 177 | Builder.load_string(''' 178 | 179 | padding: dp(15) 180 | 181 | canvas: 182 | Color: 183 | rgba: 184 | self.theme_cls.bg_light if not len(root.background_color)\ 185 | else root.background_color 186 | RoundedRectangle: 187 | pos: self.pos 188 | size: self.size 189 | radius: [15, ] 190 | ''') 191 | 192 | 193 | class RootScreen(BoxLayout, ThemableBehavior): 194 | background_color = ListProperty() 195 | 196 | 197 | class MDPopupScreen(FloatLayout): 198 | screen = ObjectProperty() 199 | background_color = ListProperty() 200 | added_screen = False 201 | max_height = dp(100) 202 | open_menu = False 203 | 204 | def __init__(self, **kwargs): 205 | super().__init__(**kwargs) 206 | self.root_screen = RootScreen() 207 | self.root_screen.y = -Window.height 208 | 209 | def show(self): 210 | if not self.added_screen: 211 | self.root_screen.add_widget(self.screen) 212 | self.add_widget(self.root_screen) 213 | self.added_screen = True 214 | self.root_screen.background_color = self.background_color 215 | self.open_menu = True 216 | Animation(y=-self.max_height, d=.2, t='in_out_bounce').start( 217 | self.root_screen) 218 | 219 | def hide(self, interval): 220 | Animation(y=-Window.height, d=.2, t='in_out_bounce').start( 221 | self.root_screen) 222 | self.open_menu = False 223 | 224 | def on_touch_down(self, touch): 225 | if touch.button == 'scrollup' or touch.button == 'scrolldown': 226 | return 227 | if self.open_menu: 228 | Clock.schedule_once(self.hide, .3) 229 | return super().on_touch_down(touch) 230 | -------------------------------------------------------------------------------- /kivymd/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 | canvas: 26 | Clear 27 | Color: 28 | rgba: self.theme_cls.divider_color 29 | Rectangle: 30 | size: 31 | (self.width , dp(4)) if self.orientation == 'horizontal'\ 32 | else (dp(4),self.height) 33 | pos: 34 | (self.x, self.center_y - dp(4))\ 35 | if self.orientation == 'horizontal'\ 36 | else (self.center_x - dp(4),self.y) 37 | Color: 38 | rgba: self.theme_cls.primary_color 39 | Rectangle: 40 | size: 41 | (self.width * self.value_normalized, sp(4))\ 42 | if self.orientation == 'horizontal' else (sp(4),\ 43 | self.height*self.value_normalized) 44 | pos: 45 | (self.width*(1 - self.value_normalized) + self.x\ 46 | if self.reversed else self.x, self.center_y - dp(4))\ 47 | if self.orientation == 'horizontal'\ 48 | else (self.center_x - dp(4),self.height\ 49 | * (1 - self.value_normalized) + self.y if self.reversed\ 50 | else self.y) 51 | ''') 52 | 53 | 54 | class MDProgressBar(ThemableBehavior, ProgressBar): 55 | reversed = BooleanProperty(False) 56 | ''' Reverse the direction the progressbar moves. ''' 57 | 58 | orientation = OptionProperty('horizontal', 59 | options=['horizontal', 'vertical']) 60 | ''' Orientation of progressbar''' 61 | 62 | 63 | if __name__ == '__main__': 64 | from kivy.app import App 65 | from kivymd.theming import ThemeManager 66 | 67 | class ProgressBarApp(App): 68 | theme_cls = ThemeManager() 69 | 70 | def build(self): 71 | return Builder.load_string(''' 72 | #:import MDSlider kivymd.slider.MDSlider 73 | 74 | 75 | BoxLayout: 76 | orientation:'vertical' 77 | padding: '8dp' 78 | MDSlider: 79 | id:slider 80 | min:0 81 | max:100 82 | value: 40 83 | 84 | MDProgressBar: 85 | value: slider.value 86 | MDProgressBar: 87 | reversed: True 88 | value: slider.value 89 | BoxLayout: 90 | MDProgressBar: 91 | orientation:"vertical" 92 | reversed: True 93 | value: slider.value 94 | 95 | MDProgressBar: 96 | orientation:"vertical" 97 | value: slider.value 98 | ''') 99 | 100 | 101 | ProgressBarApp().run() 102 | -------------------------------------------------------------------------------- /kivymd/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 kivy.app import App 21 | from kivy.lang import Builder 22 | from kivy.factory import Factory 23 | 24 | from kivymd.progressloader import MDProgressLoader 25 | from kivymd.theming import ThemeManager 26 | from kivymd.toast import toast 27 | 28 | 29 | Builder.load_string(''' 30 | #:import MDToolbar kivymd.toolbar.MDToolbar 31 | #:import MDRoundFlatIconButton kivymd.button.MDRoundFlatIconButton 32 | 33 | 34 | 35 | orientation: 'vertical' 36 | spacing: dp(5) 37 | 38 | MDToolbar: 39 | id: toolbar 40 | title: 'MD Progress Loader' 41 | left_action_items: [['menu', lambda x: None]] 42 | elevation: 10 43 | md_bg_color: app.theme_cls.primary_color 44 | 45 | FloatLayout: 46 | id: box 47 | 48 | MDRoundFlatIconButton: 49 | text: "Download file" 50 | icon: "download" 51 | pos_hint: {'center_x': .5, 'center_y': .6} 52 | on_release: app.show_example_download_file() 53 | ''') 54 | 55 | 56 | class Test(App): 57 | theme_cls = ThemeManager() 58 | 59 | def __init__(self, **kwargs): 60 | super().__init__(**kwargs) 61 | 62 | def build(self): 63 | self.main_widget = Factory.Root() 64 | return self.main_widget 65 | 66 | def set_chevron_back_screen(self): 67 | '''Sets the return chevron to the previous screen in ToolBar.''' 68 | 69 | self.main_widget.ids.toolbar.right_action_items = [] 70 | 71 | def download_progress_hide(self, instance_progress, value): 72 | '''Hides progress progress.''' 73 | 74 | self.main_widget.ids.toolbar.right_action_items =\ 75 | [['download', 76 | lambda x: self.download_progress_show(instance_progress)]] 77 | 78 | def download_progress_show(self, instance_progress): 79 | self.set_chevron_back_screen() 80 | instance_progress.open() 81 | instance_progress.animation_progress_from_fade() 82 | 83 | def show_example_download_file(self): 84 | link = 'https://www.python.org/ftp/python/3.5.1/python-3.5.1-embed-win32.zip' 85 | progress = MDProgressLoader( 86 | url_on_image=link, 87 | path_to_file=os.path.join(self.directory, 'python-3.5.1.zip'), 88 | download_complete=self.download_complete, 89 | download_hide=self.download_progress_hide 90 | ) 91 | progress.start(self.main_widget.ids.box) 92 | 93 | def download_complete(self): 94 | self.set_chevron_back_screen() 95 | toast('Done') 96 | 97 | 98 | Test().run() 99 | """ 100 | 101 | from kivy.clock import Clock 102 | from kivy.core.window import Window 103 | from kivy.animation import Animation 104 | from kivy.network.urlrequest import UrlRequest 105 | from kivy.lang import Builder 106 | from kivy.properties import StringProperty, ObjectProperty, BooleanProperty 107 | 108 | from kivymd.cards import MDCard 109 | 110 | Builder.load_string(''' 111 | #:import Window kivy.core.window.Window 112 | #:import MDSpinner kivymd.spinner.MDSpinner 113 | #:import MDLabel kivymd.label.MDLabel 114 | #:import MDCard kivymd.cards.MDCard 115 | 116 | 117 | 118 | pos: (Window.width // 2) - (self.width // 2), (Window.height // 2) - (self.height // 2) 119 | size_hint_y: None 120 | size_hint_x: .8 121 | height: spinner.height + dp(20) 122 | spacing: dp(10) 123 | padding: dp(10) 124 | 125 | canvas: 126 | Color: 127 | rgba: app.theme_cls.primary_color 128 | Rectangle: 129 | size: self.size 130 | pos: self.pos 131 | 132 | MDSpinner 133 | id: spinner 134 | size_hint: None, None 135 | size: dp(46), dp(46) 136 | color: 1, 1, 1, 1 137 | 138 | MDLabel: 139 | id: label_download 140 | shorten: True 141 | max_lines: 1 142 | halign: 'left' 143 | valign: 'top' 144 | text_size: self.width, None 145 | size_hint_y: None 146 | height: spinner.height 147 | size_hint_x: .8 148 | text: 'Download...' 149 | 150 | Widget: 151 | size_hint_x: .1 152 | ''') 153 | 154 | 155 | class MDProgressLoader(MDCard): 156 | path_to_file = StringProperty() 157 | '''The path to which the uploaded file will be saved.''' 158 | 159 | url_on_image = StringProperty() 160 | '''Link to uploaded file.''' 161 | 162 | label_download = StringProperty('Download') 163 | '''Signature of the downloaded file.''' 164 | 165 | download_complete = ObjectProperty() 166 | '''Function, called after a successful file upload.''' 167 | 168 | download_hide = ObjectProperty(lambda x: None) 169 | '''Function that is called when the download window is closed.''' 170 | 171 | download_flag = BooleanProperty(False) 172 | '''If True - the download process is in progress.''' 173 | 174 | def __init__(self, **kwargs): 175 | super().__init__(**kwargs) 176 | self.root_instance = None 177 | 178 | def start(self, root_instance): 179 | self.root_instance = root_instance 180 | self.download_flag = True 181 | self.root_instance.add_widget(self) 182 | self.retrieve_progress_load(self.url_on_image, self.path_to_file) 183 | Clock.schedule_once(self.animation_progress_to_fade, 2.5) 184 | 185 | def open(self): 186 | self.animation_progress_from_fade() 187 | 188 | def draw_progress(self, percent): 189 | """ 190 | :type percent: int; 191 | :param percent: loading percentage; 192 | 193 | """ 194 | 195 | self.ids.label_download.text = '%s: %d %%'\ 196 | % (self.label_download, percent) 197 | 198 | def animation_progress_to_fade(self, interval): 199 | if not self.download_flag: 200 | return 201 | 202 | animation = Animation( 203 | center_y=Window.height, center_x=Window.width, 204 | opacity=0, d=.2, t='out_quad' 205 | ) 206 | animation.bind(on_complete=lambda x, y: self.download_hide(self, None)) 207 | animation.start(self) 208 | 209 | def animation_progress_from_fade(self): 210 | animation = Animation( 211 | center_y=Window.height // 2, center_x=Window.width // 2, 212 | opacity=1, d=.2, t='out_quad' 213 | ) 214 | animation.start(self) 215 | Clock.schedule_once(self.animation_progress_to_fade, 2.5) 216 | 217 | def retrieve_progress_load(self, url, path): 218 | """ 219 | :type url: str; 220 | :param url: link to content; 221 | 222 | :type path: str; 223 | :param path: path to save content; 224 | """ 225 | 226 | req = UrlRequest( 227 | url, on_progress=self.update_progress, chunk_size=1024, 228 | on_success=self.on_success, file_path=path) 229 | 230 | def update_progress(self, request, current_size, total_size): 231 | percent = current_size * 100 // total_size 232 | self.draw_progress(percent) 233 | 234 | def on_success(self, req, result): 235 | self.root_instance.remove_widget(self) 236 | self.download_complete() 237 | self.download_flag = False 238 | -------------------------------------------------------------------------------- /kivymd/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 kivy.app import App 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.button import MDIconButton 23 | from kivymd.icon_definitions import md_icons 24 | from kivymd.list import ILeftBodyTouch, OneLineIconListItem 25 | from kivymd.theming import ThemeManager 26 | from kivymd.utils import asynckivy 27 | 28 | Builder.load_string(''' 29 | #:import MDToolbar kivymd.toolbar.MDToolbar 30 | #:import MDScrollViewRefreshLayout kivymd.refreshlayout.MDScrollViewRefreshLayout 31 | 32 | 33 | 34 | text: root.text 35 | 36 | IconLeftSampleWidget: 37 | icon: root.icon 38 | 39 | 40 | 41 | 42 | BoxLayout: 43 | orientation: 'vertical' 44 | 45 | MDToolbar: 46 | title: app.title 47 | md_bg_color: app.theme_cls.primary_color 48 | background_palette: 'Primary' 49 | elevation: 10 50 | left_action_items: [['menu', lambda x: x]] 51 | 52 | MDScrollViewRefreshLayout: 53 | id: refresh_layout 54 | refresh_callback: app.refresh_callback 55 | root_layout: root 56 | 57 | GridLayout: 58 | id: box 59 | size_hint_y: None 60 | height: self.minimum_height 61 | cols: 1 62 | ''') 63 | 64 | 65 | class IconLeftSampleWidget(ILeftBodyTouch, MDIconButton): 66 | pass 67 | 68 | 69 | class ItemForList(OneLineIconListItem): 70 | icon = StringProperty() 71 | 72 | 73 | class Example(App): 74 | title = 'Example Refresh Layout' 75 | theme_cls = ThemeManager() 76 | screen = None 77 | x = 0 78 | y = 15 79 | 80 | def build(self): 81 | self.screen = Factory.Example() 82 | self.set_list() 83 | 84 | return self.screen 85 | 86 | def set_list(self): 87 | async def set_list(): 88 | names_icons_list = list(md_icons.keys())[self.x:self.y] 89 | for name_icon in names_icons_list: 90 | await asynckivy.sleep(0) 91 | self.screen.ids.box.add_widget( 92 | ItemForList(icon=name_icon, text=name_icon)) 93 | asynckivy.start(set_list()) 94 | 95 | def refresh_callback(self, *args): 96 | '''A method that updates the state of your application 97 | while the spinner remains on the screen.''' 98 | 99 | def refresh_callback(interval): 100 | self.screen.ids.box.clear_widgets() 101 | if self.x == 0: 102 | self.x, self.y = 15, 30 103 | else: 104 | self.x, self.y = 0, 15 105 | self.set_list() 106 | self.screen.ids.refresh_layout.refresh_done() 107 | self.tick = 0 108 | 109 | Clock.schedule_once(refresh_callback, 1) 110 | 111 | 112 | Example().run() 113 | 114 | """ 115 | 116 | from kivy.animation import Animation 117 | from kivy.effects.dampedscroll import DampedScrollEffect 118 | from kivy.lang import Builder 119 | from kivy.metrics import dp 120 | from kivy.properties import NumericProperty, ObjectProperty, ListProperty 121 | from kivy.uix.floatlayout import FloatLayout 122 | from kivy.uix.scrollview import ScrollView 123 | from kivy.core.window import Window 124 | 125 | from kivymd.theming import ThemableBehavior 126 | 127 | Builder.load_string(""" 128 | #:import Window kivy.core.window.Window 129 | #:import MDSpinner kivymd.spinner.MDSpinner 130 | 131 | 132 | 133 | 134 | AnchorLayout: 135 | id: body_spinner 136 | size_hint: None, None 137 | size: dp(46), dp(46) 138 | y: Window.height 139 | pos_hint: {'center_x': .5} 140 | anchor_x: 'center' 141 | anchor_y: 'center' 142 | 143 | canvas: 144 | Clear 145 | Color: 146 | rgba: root.theme_cls.primary_dark 147 | Ellipse: 148 | pos: self.pos 149 | size: self.size 150 | 151 | MDSpinner: 152 | id: spinner 153 | size_hint: None, None 154 | size: dp(30), dp(30) 155 | color: 1, 1, 1, 1 156 | """) 157 | 158 | 159 | class _RefreshScrollEffect(DampedScrollEffect): 160 | """This class is simply based on DampedScrollEffect. 161 | If you need any documentation please look at kivy.effects.dampedscrolleffect. 162 | """ 163 | 164 | min_scroll_to_reload = NumericProperty(-dp(100)) 165 | '''Minimum overscroll value to reload.''' 166 | 167 | def on_overscroll(self, scrollview, overscroll): 168 | if overscroll < self.min_scroll_to_reload: 169 | scroll_view = self.target_widget.parent 170 | scroll_view._did_overscroll = True 171 | return True 172 | else: 173 | return False 174 | 175 | 176 | class MDScrollViewRefreshLayout(ScrollView): 177 | root_layout = ObjectProperty() 178 | '''The spinner will be attached to this layout.''' 179 | 180 | def __init__(self, **kargs): 181 | super().__init__(**kargs) 182 | self.effect_cls = _RefreshScrollEffect 183 | self._work_spinnrer = False 184 | self._did_overscroll = False 185 | self.refresh_spinner = None 186 | 187 | def on_touch_up(self, *args): 188 | if self._did_overscroll and not self._work_spinnrer: 189 | if self.refresh_callback: 190 | self.refresh_callback() 191 | if not self.refresh_spinner: 192 | self.refresh_spinner = RefreshSpinner(_refresh_layout=self) 193 | self.root_layout.add_widget(self.refresh_spinner) 194 | self.refresh_spinner.start_anim_spinner() 195 | self._work_spinnrer = True 196 | self._did_overscroll = False 197 | return True 198 | 199 | return super().on_touch_up(*args) 200 | 201 | def refresh_done(self): 202 | if self.refresh_spinner: 203 | self.refresh_spinner.hide_anim_spinner() 204 | 205 | 206 | class RefreshSpinner(ThemableBehavior, FloatLayout): 207 | spinner_color = ListProperty([1, 1, 1, 1]) 208 | 209 | _refresh_layout = ObjectProperty() 210 | '''kivymd.refreshlayout.MDScrollViewRefreshLayout object.''' 211 | 212 | def start_anim_spinner(self): 213 | spinner = self.ids.body_spinner 214 | Animation(y=spinner.y - dp(76), d=.8, t='out_elastic').start(spinner) 215 | 216 | def hide_anim_spinner(self): 217 | spinner = self.ids.body_spinner 218 | anim = Animation(y=Window.height, d=.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/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 ListProperty, NumericProperty, StringProperty,\ 18 | BooleanProperty 19 | from kivy.animation import Animation 20 | from kivy.graphics import Color, Ellipse, StencilPush, StencilPop,\ 21 | StencilUse, StencilUnUse, Rectangle 22 | 23 | 24 | class CommonRipple(object): 25 | ripple_rad = NumericProperty() 26 | ripple_rad_default = NumericProperty(1) 27 | ripple_post = ListProperty() 28 | ripple_color = ListProperty() 29 | ripple_alpha = NumericProperty(.5) 30 | ripple_scale = NumericProperty(None) 31 | ripple_duration_in_fast = NumericProperty(.3) 32 | # FIXME: These speeds should be calculated based on widget size in dp 33 | ripple_duration_in_slow = NumericProperty(2) 34 | ripple_duration_out = NumericProperty(.3) 35 | ripple_func_in = StringProperty('out_quad') 36 | ripple_func_out = StringProperty('out_quad') 37 | 38 | doing_ripple = BooleanProperty(False) 39 | finishing_ripple = BooleanProperty(False) 40 | fading_out = BooleanProperty(False) 41 | _no_ripple_effect = BooleanProperty(False) 42 | 43 | def on_touch_down(self, touch): 44 | if touch.is_mouse_scrolling: 45 | return False 46 | if not self.collide_point(touch.x, touch.y): 47 | return False 48 | 49 | if not self.disabled: 50 | if self.doing_ripple: 51 | Animation.cancel_all(self, 'ripple_rad', 'ripple_color', 52 | 'rect_color') 53 | self.anim_complete() 54 | self.ripple_rad = self.ripple_rad_default 55 | self.ripple_pos = (touch.x, touch.y) 56 | 57 | if self.ripple_color: 58 | pass 59 | elif hasattr(self, 'theme_cls'): 60 | self.ripple_color = self.theme_cls.ripple_color 61 | else: 62 | # If no theme, set Gray 300 63 | self.ripple_color = [.8784313725490196, .8784313725490196, 64 | .8784313725490196, self.ripple_alpha] 65 | self.ripple_color[3] = self.ripple_alpha 66 | 67 | self.lay_canvas_instructions() 68 | self.finish_rad = max(self.width, self.height) * self.ripple_scale 69 | self.start_ripple() 70 | return super().on_touch_down(touch) 71 | 72 | def lay_canvas_instructions(self): 73 | raise NotImplementedError 74 | 75 | def on_touch_move(self, touch, *args): 76 | if not self.collide_point(touch.x, touch.y): 77 | if not self.finishing_ripple and self.doing_ripple: 78 | self.finish_ripple() 79 | return super().on_touch_move(touch, *args) 80 | 81 | def on_touch_up(self, touch): 82 | if self.collide_point(touch.x, touch.y) and self.doing_ripple: 83 | self.finish_ripple() 84 | return super().on_touch_up(touch) 85 | 86 | def start_ripple(self): 87 | if not self.doing_ripple: 88 | anim = Animation( 89 | ripple_rad=self.finish_rad, 90 | t='linear', 91 | duration=self.ripple_duration_in_slow) 92 | anim.bind(on_complete=self.fade_out) 93 | self.doing_ripple = True 94 | anim.start(self) 95 | 96 | def _set_ellipse(self, instance, value): 97 | self.ellipse.size = (self.ripple_rad, self.ripple_rad) 98 | 99 | # Adjust ellipse pos here 100 | 101 | def _set_color(self, instance, value): 102 | self.col_instruction.a = value[3] 103 | 104 | def finish_ripple(self): 105 | if self.doing_ripple and not self.finishing_ripple: 106 | Animation.cancel_all(self, 'ripple_rad') 107 | anim = Animation(ripple_rad=self.finish_rad, 108 | t=self.ripple_func_in, 109 | duration=self.ripple_duration_in_fast) 110 | anim.bind(on_complete=self.fade_out) 111 | self.finishing_ripple = True 112 | anim.start(self) 113 | 114 | def fade_out(self, *args): 115 | rc = self.ripple_color 116 | if not self.fading_out: 117 | Animation.cancel_all(self, 'ripple_color') 118 | anim = Animation(ripple_color=[rc[0], rc[1], rc[2], .0], 119 | t=self.ripple_func_out, 120 | duration=self.ripple_duration_out) 121 | anim.bind(on_complete=self.anim_complete) 122 | self.fading_out = True 123 | anim.start(self) 124 | 125 | def anim_complete(self, *args): 126 | self.doing_ripple = False 127 | self.finishing_ripple = False 128 | self.fading_out = False 129 | self.canvas.after.clear() 130 | 131 | 132 | class RectangularRippleBehavior(CommonRipple): 133 | ripple_scale = NumericProperty(2.75) 134 | 135 | def lay_canvas_instructions(self): 136 | if self._no_ripple_effect: 137 | return 138 | with self.canvas.after: 139 | StencilPush() 140 | Rectangle(pos=self.pos, size=self.size) 141 | StencilUse() 142 | self.col_instruction = Color(rgba=self.ripple_color) 143 | self.ellipse =\ 144 | Ellipse(size=(self.ripple_rad, self.ripple_rad), 145 | pos=(self.ripple_pos[0] - self.ripple_rad / 2., 146 | self.ripple_pos[1] - self.ripple_rad / 2.)) 147 | StencilUnUse() 148 | Rectangle(pos=self.pos, size=self.size) 149 | StencilPop() 150 | self.bind(ripple_color=self._set_color, 151 | ripple_rad=self._set_ellipse) 152 | 153 | def _set_ellipse(self, instance, value): 154 | super()._set_ellipse(instance, value) 155 | self.ellipse.pos = (self.ripple_pos[0] - self.ripple_rad / 2., 156 | self.ripple_pos[1] - self.ripple_rad / 2.) 157 | 158 | 159 | class CircularRippleBehavior(CommonRipple): 160 | ripple_scale = NumericProperty(1) 161 | 162 | def lay_canvas_instructions(self): 163 | with self.canvas.after: 164 | StencilPush() 165 | self.stencil = Ellipse(size=(self.width * self.ripple_scale, 166 | self.height * self.ripple_scale), 167 | pos=(self.center_x - ( 168 | self.width * self.ripple_scale) / 2, 169 | self.center_y - ( 170 | self.height * self.ripple_scale) / 2)) 171 | StencilUse() 172 | self.col_instruction = Color(rgba=self.ripple_color) 173 | self.ellipse = Ellipse(size=(self.ripple_rad, self.ripple_rad), 174 | pos=(self.center_x - self.ripple_rad / 2., 175 | self.center_y - self.ripple_rad / 2.)) 176 | StencilUnUse() 177 | Ellipse(pos=self.pos, size=self.size) 178 | StencilPop() 179 | self.bind(ripple_color=self._set_color, 180 | ripple_rad=self._set_ellipse) 181 | 182 | def _set_ellipse(self, instance, value): 183 | super()._set_ellipse(instance, value) 184 | if self.ellipse.size[0] > self.width * .6 and not self.fading_out: 185 | self.fade_out() 186 | self.ellipse.pos = (self.center_x - self.ripple_rad / 2., 187 | self.center_y - self.ripple_rad / 2.) 188 | -------------------------------------------------------------------------------- /kivymd/setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from distutils.core import setup 3 | 4 | VERSION_FILE = "kivymd/__init__.py" 5 | ver_file_data = open(VERSION_FILE, "rt").read() 6 | ver_regex = r"^__version__ = ['\"]([^'\"]*)['\"]" 7 | ver_reg_search = re.search(ver_regex, ver_file_data, re.M) 8 | 9 | if ver_reg_search: 10 | version = ver_reg_search.group(1) 11 | else: 12 | raise ValueError( 13 | "Unable to find version string in {}.".format(VERSION_FILE)) 14 | 15 | setup(name='kivymd', 16 | version=version, 17 | description='Set of widgets for Kivy inspired by Google\'s Material ' 18 | 'Design', 19 | author='Andrés Rodríguez, author fork - HeaTTheatR', 20 | author_email='andres.rodriguez@lithersoft.com, email author fork ' 21 | '- kivydevelopment@gmail.com', 22 | url='https://github.com/HeaTTheatR/KivyMD', 23 | packages=['kivymd'], 24 | package_data={ 25 | 'kivymd': ['images/*.png', 'images/*.jpg', 'images/*.atlas', 26 | 'fonts/*.ttf', 27 | 'vendor/*.py', 'vendor/circleLayout/*.py', 28 | 'vendor/circularTimePicker/*.py', 29 | 'vendor/navigationdrawer/*.py', 30 | 'toast/*.py', 'toast/kivytoast/*.py', 31 | 'toast/androidtoast/*.py', 'stiffscroll/*.py', 32 | 'utils/*.py']}, 33 | requires=['kivy', 'pillow']) 34 | -------------------------------------------------------------------------------- /kivymd/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 OptionProperty, NumericProperty, StringProperty,\ 22 | ListProperty 23 | from kivy.uix.boxlayout import BoxLayout 24 | 25 | Builder.load_string(''' 26 | #:import Window kivy.core.window.Window 27 | 28 | 29 | 30 | orientation: 'vertical' 31 | size_hint_x: None 32 | width: dp(320) 33 | x: -1 * self.width if self.side == 'left' else Window.width 34 | 35 | 36 | 37 | canvas: 38 | Color: 39 | rgba: root.color 40 | Rectangle: 41 | size: root.size 42 | ''') 43 | 44 | 45 | class PanelShadow(BoxLayout): 46 | color = ListProperty([0, 0, 0, 0]) 47 | 48 | 49 | class SlidingPanel(BoxLayout): 50 | anim_length_close = NumericProperty(.3) 51 | anim_length_open = NumericProperty(.3) 52 | animation_t_open = StringProperty('out_sine') 53 | animation_t_close = StringProperty('out_sine') 54 | side = OptionProperty('left', options=['left', 'right']) 55 | 56 | _open = False 57 | 58 | def __init__(self, **kwargs): 59 | super().__init__(**kwargs) 60 | self.shadow = PanelShadow() 61 | Clock.schedule_once(lambda x: Window.add_widget(self.shadow, 89), 0) 62 | Clock.schedule_once(lambda x: Window.add_widget(self, 90), 0) 63 | 64 | def toggle(self): 65 | Animation.stop_all(self, 'x') 66 | Animation.stop_all(self.shadow, 'color') 67 | if self._open: 68 | if self.side == 'left': 69 | target_x = -1 * self.width 70 | else: 71 | target_x = Window.width 72 | 73 | sh_anim = Animation(duration=self.anim_length_open, 74 | t=self.animation_t_open, 75 | color=[0, 0, 0, 0]) 76 | sh_anim.start(self.shadow) 77 | self._get_main_animation(duration=self.anim_length_close, 78 | t=self.animation_t_close, 79 | x=target_x, 80 | is_closing=True).start(self) 81 | self._open = False 82 | else: 83 | if self.side == 'left': 84 | target_x = 0 85 | else: 86 | target_x = Window.width - self.width 87 | Animation(duration=self.anim_length_open, t=self.animation_t_open, 88 | color=[0, 0, 0, .5]).start(self.shadow) 89 | self._get_main_animation(duration=self.anim_length_open, 90 | t=self.animation_t_open, 91 | x=target_x, 92 | is_closing=False).start(self) 93 | self._open = True 94 | 95 | def _get_main_animation(self, duration, t, x, is_closing): 96 | return Animation(duration=duration, t=t, x=x) 97 | 98 | def on_touch_down(self, touch): 99 | # Prevents touch events from propagating to anything below the widget. 100 | super().on_touch_down(touch) 101 | if self.collide_point(*touch.pos) or self._open: 102 | return True 103 | 104 | def on_touch_up(self, touch): 105 | super().on_touch_up(touch) 106 | if not self.collide_point(touch.x, touch.y) and self._open: 107 | self.toggle() 108 | return True 109 | -------------------------------------------------------------------------------- /kivymd/snackbar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Snackbar 4 | ======== 5 | 6 | `Material Design spec page `_ 7 | 8 | API 9 | --- 10 | ''' 11 | 12 | from collections import deque 13 | from kivy.animation import Animation 14 | from kivy.clock import Clock 15 | from kivy.core.window import Window 16 | from kivy.event import EventDispatcher 17 | from kivy.lang import Builder 18 | from kivy.metrics import dp 19 | from kivy.properties import ObjectProperty, StringProperty, NumericProperty 20 | from kivy.uix.relativelayout import RelativeLayout 21 | from kivymd.material_resources import DEVICE_TYPE 22 | 23 | Builder.load_string(''' 24 | #:import Window kivy.core.window.Window 25 | #:import get_color_from_hex kivy.utils.get_color_from_hex 26 | #:import MDFlatButton kivymd.button.MDFlatButton 27 | #:import MDLabel kivymd.label.MDLabel 28 | #:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE 29 | <_SnackbarWidget> 30 | canvas: 31 | Color: 32 | rgb: get_color_from_hex('323232') 33 | Rectangle: 34 | size: self.size 35 | size_hint_y: None 36 | size_hint_x: 1 if DEVICE_TYPE == 'mobile' else None 37 | height: dp(48) if _label.texture_size[1] < dp(30) else dp(80) 38 | width: dp(24) + _label.width + _spacer.width + root.padding_right if root.button_text == '' else dp(24) + \ 39 | _label.width + _spacer.width + _button.width + root.padding_right 40 | top: 0 41 | x: 0 if DEVICE_TYPE == 'mobile' else Window.width/2 - self.width/2 42 | BoxLayout: 43 | width: Window.width - root.padding_right - _spacer.width - dp(24) if DEVICE_TYPE == 'mobile' and \ 44 | root.button_text == '' else Window.width - root.padding_right - _button.width - _spacer.width - dp(24) \ 45 | if DEVICE_TYPE == 'mobile' else _label.texture_size[0] if (dp(568) - root.padding_right - _button.width - \ 46 | _spacer.width - _label.texture_size[0] - dp(24)) >= 0 else (dp(568) - root.padding_right - _button.width - \ 47 | _spacer.width - dp(24)) 48 | size_hint_x: None 49 | x: dp(24) 50 | MDLabel: 51 | id: _label 52 | text: root.text 53 | theme_text_color: 'Custom' 54 | text_color: get_color_from_hex('ffffff') 55 | size: self.texture_size 56 | BoxLayout: 57 | id: _spacer 58 | size_hint_x: None 59 | x: _label.right 60 | width: 0 61 | MDFlatButton: 62 | id: _button 63 | text: root.button_text 64 | theme_text_color: 'Custom' 65 | text_color: get_color_from_hex('ffffff') 66 | size_hint_x: None 67 | x: _spacer.right if root.button_text != '' else root.right 68 | center_y: root.height/2 69 | on_release: root.button_callback() 70 | ''') 71 | 72 | 73 | class SnackbarManager: 74 | playing = False 75 | queue = deque() 76 | 77 | def _play_next(self, dying_widget=None): 78 | if (dying_widget or not self.playing) and len(self.queue) > 0: 79 | self.playing = True 80 | self.queue.popleft().begin() 81 | elif len(self.queue) == 0: 82 | self.playing = False 83 | 84 | def make(self, text, button_text=None, button_callback=None, duration=3): 85 | if button_text is not None and button_callback is not None: 86 | self.queue.append(_SnackbarWidget(text=text, button_text=button_text, button_callback=button_callback, 87 | duration=duration)) 88 | else: 89 | self.queue.append(_SnackbarWidget(text=text, duration=duration)) 90 | self._play_next() 91 | 92 | 93 | class Snackbar(EventDispatcher): 94 | ''' 95 | A Material Design Snackbar 96 | ''' 97 | text = StringProperty("") 98 | '''The text that will appear in the Snackbar. 99 | 100 | :attr:`text` is a :class:`~kivy.properties.StringProperty` and defaults to ''. 101 | ''' 102 | button_text = StringProperty(None, allownone=True) 103 | '''The text that will appear in the Snackbar's button. 104 | 105 | .. note:: 106 | If this variable is None, the Snackbar will have no button. 107 | 108 | :attr:`button_text` is a :class:`~kivy.properties.StringProperty` and defaults to None. 109 | ''' 110 | button_callback = ObjectProperty(None, allownone=True) 111 | '''The callback that will be triggered when the Snackbar's button is pressed. 112 | 113 | .. note:: 114 | If this variable is None, the Snackbar will have no button. 115 | 116 | :attr:`button_callback` is a :class:`~kivy.properties.ObjectProperty` and defaults to None. 117 | ''' 118 | duration = NumericProperty(3) 119 | '''The amount of time that the Snackbar will stay on screen for. 120 | 121 | :attr:`duration` is a :class:`~kivy.properties.NumericProperty` and defaults to 3. 122 | ''' 123 | 124 | def __init__(self, text, button_text=None, button_callback=None, duration=None): 125 | self.text = text 126 | self.button_text = button_text 127 | self.button_callback = button_callback 128 | self.duration = duration or self.duration 129 | 130 | def show(self): 131 | '''Show the Snackbar''' 132 | manager.make(text=self.text, button_text=self.button_text, button_callback=self.button_callback, 133 | duration=self.duration) 134 | 135 | 136 | class _SnackbarWidget(RelativeLayout): 137 | text = StringProperty() 138 | button_text = StringProperty() 139 | button_callback = ObjectProperty() 140 | duration = NumericProperty() 141 | padding_right = NumericProperty(dp(24)) 142 | 143 | def __init__(self, text, duration, button_text='', button_callback=None, 144 | **kwargs): 145 | super(_SnackbarWidget, self).__init__(**kwargs) 146 | self.text = text 147 | self.button_text = button_text 148 | self.button_callback = button_callback 149 | self.duration = duration 150 | self.ids['_label'].text_size = (None, None) 151 | 152 | def begin(self): 153 | if self.button_text == '': 154 | self.remove_widget(self.ids['_button']) 155 | else: 156 | self.ids['_spacer'].width = dp(16) if DEVICE_TYPE == "mobile" else dp(40) 157 | self.padding_right = dp(16) 158 | Window.add_widget(self) 159 | anim = Animation(y=0, duration=.3, t='out_quad') 160 | anim.start(self) 161 | Clock.schedule_once(lambda dt: self.die(), self.duration) 162 | 163 | def die(self): 164 | anim = Animation(top=0, duration=.3, t='out_quad') 165 | anim.bind(on_complete=lambda *args: manager._play_next(self)) 166 | anim.bind(on_complete=lambda *args: Window.remove_widget(self)) 167 | anim.start(self) 168 | 169 | manager = SnackbarManager() 170 | -------------------------------------------------------------------------------- /kivymd/snackbars.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 kivy.app import App 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.snackbars 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 | #:import MDToolbar kivymd.toolbar.MDToolbar 31 | #:import MDRaisedButton kivymd.button.MDRaisedButton 32 | #:import MDSeparator kivymd.cards.MDSeparator 33 | #:import MDLabel kivymd.label.MDLabel 34 | 35 | 36 | Screen: 37 | name: 'snackbar' 38 | 39 | BoxLayout: 40 | orientation: 'vertical' 41 | spacing: dp(10) 42 | 43 | MDToolbar: 44 | title: 'Example Snackbar' 45 | md_bg_color: app.theme_cls.primary_color 46 | left_action_items: [['menu', lambda x: x]] 47 | background_palette: 'Primary' 48 | 49 | BoxLayout: 50 | orientation: 'vertical' 51 | spacing: dp(10) 52 | padding: dp(10) 53 | 54 | Widget: 55 | 56 | MDRaisedButton: 57 | text: "Create simple snackbar" 58 | pos_hint: {'center_x': .5} 59 | on_release: app.show_example_snackbar('simple') 60 | 61 | MDRaisedButton: 62 | text: "Create snackbar with button" 63 | pos_hint: {'center_x': .5} 64 | on_release: app.show_example_snackbar('button') 65 | 66 | MDRaisedButton: 67 | text: "Create snackbar with a lot of text" 68 | pos_hint: {'center_x': .5} 69 | on_release: app.show_example_snackbar('verylong') 70 | 71 | MDSeparator: 72 | 73 | MDLabel: 74 | text: 'Click the MDFloatingActionButton to show the following example...' 75 | halign: 'center' 76 | 77 | Widget: 78 | 79 | MDFloatingActionButton: 80 | id: button 81 | md_bg_color: app.theme_cls.primary_color 82 | x: Window.width - self.width - dp(10) 83 | y: dp(10) 84 | on_release: app.show_example_snackbar('float') 85 | ''' 86 | 87 | 88 | class ExampleSnackBar(App): 89 | theme_cls = ThemeManager() 90 | _interval = 0 91 | my_snackbar = None 92 | screen = None 93 | 94 | def build(self): 95 | self.screen = Builder.load_string(KV) 96 | return self.screen 97 | 98 | def show_example_snackbar(self, snack_type): 99 | def callback(instance): 100 | toast(instance.text) 101 | 102 | def wait_interval(interval): 103 | self._interval += interval 104 | if self._interval > self.my_snackbar.duration: 105 | anim = Animation(y=dp(10), d=.2) 106 | anim.start(self.screen.ids.button) 107 | Clock.unschedule(wait_interval) 108 | self._interval = 0 109 | self.my_snackbar = None 110 | 111 | if snack_type == 'simple': 112 | Snackbar(text="This is a snackbar!").show() 113 | elif snack_type == 'button': 114 | Snackbar(text="This is a snackbar", button_text="with a button!", 115 | button_callback=callback).show() 116 | elif snack_type == 'verylong': 117 | Snackbar(text="This is a very very very very very very very " 118 | "long snackbar!").show() 119 | elif snack_type == 'float': 120 | if not self.my_snackbar: 121 | self.my_snackbar = Snackbar( 122 | text="This is a snackbar!", button_text='Button', 123 | duration=3, button_callback=callback) 124 | self.my_snackbar.show() 125 | anim = Animation(y=dp(72), d=.2) 126 | anim.bind(on_complete=lambda *args: Clock.schedule_interval( 127 | wait_interval, 0)) 128 | anim.start(self.screen.ids.button) 129 | 130 | 131 | ExampleSnackBar().run() 132 | """ 133 | 134 | from kivy.animation import Animation 135 | from kivy.clock import Clock 136 | from kivy.core.window import Window 137 | from kivy.lang import Builder 138 | from kivy.properties import ObjectProperty, StringProperty, NumericProperty 139 | from kivy.uix.floatlayout import FloatLayout 140 | 141 | from kivymd.button import MDFlatButton 142 | 143 | Builder.load_string(''' 144 | #:import get_color_from_hex kivy.utils.get_color_from_hex 145 | #:import MDLabel kivymd.label.MDLabel 146 | 147 | 148 | : 149 | 150 | BoxLayout: 151 | id: box 152 | size_hint_y: None 153 | height: dp(58) 154 | spacing: dp(5) 155 | padding: dp(10) 156 | y: -self.height 157 | 158 | canvas: 159 | Color: 160 | rgba: get_color_from_hex('323232') 161 | Rectangle: 162 | pos: self.pos 163 | size: self.size 164 | 165 | MDLabel: 166 | id: text_bar 167 | size_hint_y: None 168 | height: self.texture_size[1] 169 | text: root.text 170 | font_size: root.font_size 171 | theme_text_color: 'Custom' 172 | text_color: get_color_from_hex('ffffff') 173 | shorten: True 174 | shorten_from: 'right' 175 | pos_hint: {'center_y': .5} 176 | ''') 177 | 178 | 179 | class Snackbar(FloatLayout): 180 | """A Material Design Snackbar""" 181 | 182 | text = StringProperty() 183 | """The text that will appear in the Snackbar. 184 | 185 | :attr:`text` is a :class:`~kivy.properties.StringProperty` 186 | and defaults to ''. 187 | """ 188 | 189 | font_size = NumericProperty('15sp') 190 | """The font size of the text that will appear in the Snackbar. 191 | 192 | :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` and 193 | defaults to 15sp. 194 | """ 195 | 196 | button_text = StringProperty() 197 | """The text that will appear in the Snackbar's button. 198 | 199 | .. note:: 200 | If this variable is None, the Snackbar will have no button. 201 | 202 | :attr:`button_text` is a :class:`~kivy.properties.StringProperty` 203 | and defaults to ''. 204 | """ 205 | 206 | button_callback = ObjectProperty() 207 | """The callback that will be triggered when the Snackbar's 208 | button is pressed. 209 | 210 | .. note:: 211 | If this variable is None, the Snackbar will have no button. 212 | 213 | :attr:`button_callback` is a :class:`~kivy.properties.ObjectProperty` 214 | and defaults to None. 215 | """ 216 | 217 | duration = NumericProperty(3) 218 | """The amount of time that the Snackbar will stay on screen for. 219 | 220 | :attr:`duration` is a :class:`~kivy.properties.NumericProperty` 221 | and defaults to 3. 222 | """ 223 | 224 | _interval = 0 225 | 226 | def __init__(self, **kwargs): 227 | super().__init__(**kwargs) 228 | if self.button_text != '': 229 | button = MDFlatButton(text=self.button_text) 230 | self.ids.box.add_widget(button) 231 | if self.button_callback: 232 | button.bind(on_release=self.button_callback) 233 | 234 | def show(self): 235 | """Show the Snackbar.""" 236 | 237 | def wait_interval(interval): 238 | self._interval += interval 239 | if self._interval > self.duration: 240 | anim = Animation(y=-self.ids.box.height, d=.2) 241 | anim.bind(on_complete=lambda *args: Window.parent.remove_widget(self)) 242 | anim.start(self.ids.box) 243 | Clock.unschedule(wait_interval) 244 | self._interval = 0 245 | 246 | Window.parent.add_widget(self) 247 | anim = Animation(y=0, d=.2) 248 | anim.bind(on_complete=lambda *args: Clock.schedule_interval(wait_interval, 0)) 249 | anim.start(self.ids.box) 250 | -------------------------------------------------------------------------------- /kivymd/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 | from kivymd.theming import ThemableBehavior 22 | 23 | Builder.load_string(''' 24 | 25 | canvas.before: 26 | PushMatrix 27 | Rotate: 28 | angle: self._rotation_angle 29 | origin: self.center 30 | canvas: 31 | Color: 32 | rgba: self.color 33 | a: self._alpha 34 | SmoothLine: 35 | circle: self.center_x, self.center_y, self.width / 2,\ 36 | self._angle_start, self._angle_end 37 | cap: 'square' 38 | width: dp(2.25) 39 | canvas.after: 40 | PopMatrix 41 | 42 | ''') 43 | 44 | 45 | class MDSpinner(ThemableBehavior, Widget): 46 | """:class:`MDSpinner` is an implementation of the circular progress 47 | indicator in Google's Material Design. 48 | 49 | It can be used either as an indeterminate indicator that loops while 50 | the user waits for something to happen, or as a determinate indicator. 51 | 52 | Set :attr:`determinate` to **True** to activate determinate mode, and 53 | :attr:`determinate_time` to set the duration of the animation. 54 | """ 55 | 56 | determinate = BooleanProperty(False) 57 | """:attr:`determinate` is a :class:`~kivy.properties.BooleanProperty` and 58 | defaults to False 59 | """ 60 | 61 | determinate_time = NumericProperty(2) 62 | """:attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty` 63 | and defaults to 2 64 | """ 65 | 66 | active = BooleanProperty(True) 67 | """Use :attr:`active` to start or stop the spinner. 68 | 69 | :attr:`active` is a :class:`~kivy.properties.BooleanProperty` and 70 | defaults to True 71 | """ 72 | 73 | color = ListProperty([]) 74 | """:attr:`color` is a :class:`~kivy.properties.ListProperty` and 75 | defaults to 'self.theme_cls.primary_color' 76 | """ 77 | 78 | _alpha = NumericProperty(0) 79 | _rotation_angle = NumericProperty(360) 80 | _angle_start = NumericProperty(0) 81 | _angle_end = NumericProperty(8) 82 | 83 | def __init__(self, **kwargs): 84 | super().__init__(**kwargs) 85 | self.color = self.theme_cls.primary_color 86 | self._alpha_anim_in = Animation(_alpha=1, duration=.8, t='out_quad') 87 | self._alpha_anim_out = Animation(_alpha=0, duration=.3, t='out_quad') 88 | self._alpha_anim_out.bind(on_complete=self._reset) 89 | self.theme_cls.bind(primary_color=self._update_color) 90 | 91 | if self.determinate: 92 | self._start_determinate() 93 | else: 94 | self._start_loop() 95 | 96 | def _update_color(self, *args): 97 | self.color = self.theme_cls.primary_color 98 | 99 | def _start_determinate(self, *args): 100 | self._alpha_anim_in.start(self) 101 | 102 | _rot_anim = Animation(_rotation_angle=0, 103 | duration=self.determinate_time * .7, 104 | t='out_quad') 105 | _rot_anim.start(self) 106 | 107 | _angle_start_anim = Animation(_angle_end=360, 108 | duration=self.determinate_time, 109 | t='in_out_quad') 110 | _angle_start_anim.bind( 111 | on_complete=lambda *x: self._alpha_anim_out.start(self)) 112 | 113 | _angle_start_anim.start(self) 114 | 115 | def _start_loop(self, *args): 116 | if self._alpha == 0: 117 | _rot_anim = Animation(_rotation_angle=0, 118 | duration=2, 119 | t='linear') 120 | _rot_anim.start(self) 121 | 122 | self._alpha = 1 123 | self._alpha_anim_in.start(self) 124 | _angle_start_anim = Animation(_angle_end=self._angle_end + 270, 125 | duration=.6, 126 | t='in_out_cubic') 127 | _angle_start_anim.bind(on_complete=self._anim_back) 128 | _angle_start_anim.start(self) 129 | 130 | def _anim_back(self, *args): 131 | _angle_back_anim = Animation(_angle_start=self._angle_end - 8, 132 | duration=.6, 133 | t='in_out_cubic') 134 | _angle_back_anim.bind(on_complete=self._start_loop) 135 | 136 | _angle_back_anim.start(self) 137 | 138 | def on__rotation_angle(self, *args): 139 | if self._rotation_angle == 0: 140 | self._rotation_angle = 360 141 | if not self.determinate: 142 | _rot_anim = Animation(_rotation_angle=0, 143 | duration=2) 144 | _rot_anim.start(self) 145 | 146 | def _reset(self, *args): 147 | Animation.cancel_all(self, '_angle_start', '_rotation_angle', 148 | '_angle_end', '_alpha') 149 | self._angle_start = 0 150 | self._angle_end = 8 151 | self._rotation_angle = 360 152 | self._alpha = 0 153 | self.active = False 154 | 155 | def on_active(self, *args): 156 | if not self.active: 157 | self._reset() 158 | else: 159 | if self.determinate: 160 | self._start_determinate() 161 | else: 162 | self._start_loop() 163 | -------------------------------------------------------------------------------- /kivymd/stackfloatingbuttons.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 kivy.app import App 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.stackfloatingbuttons import MDStackFloatingButtons 23 | 24 | 25 | Builder.load_string(''' 26 | #:import MDToolbar kivymd.toolbar.MDToolbar 27 | 28 | 29 | : 30 | orientation: 'vertical' 31 | 32 | MDToolbar: 33 | title: 'Stack Floating Buttons' 34 | md_bg_color: app.theme_cls.primary_color 35 | elevation: 10 36 | left_action_items: [['menu', lambda x: None]] 37 | 38 | ''') 39 | 40 | 41 | class Example(App): 42 | theme_cls = ThemeManager() 43 | theme_cls.primary_palette = 'Teal' 44 | title = "Example Stack Floating Buttons" 45 | create_stack_floating_buttons = False 46 | floating_data = { 47 | 'Python': 'language-python', 48 | 'Php': 'language-php', 49 | 'C++': 'language-cpp'} 50 | 51 | def set_my_language(self, instance_button): 52 | toast(instance_button.icon) 53 | 54 | def build(self): 55 | screen = Factory.ExampleFloatingButtons() 56 | # Use this condition otherwise the stack will be created each time. 57 | if not self.create_stack_floating_buttons: 58 | screen.add_widget(MDStackFloatingButtons( 59 | icon='lead-pencil', 60 | floating_data=self.floating_data, 61 | callback=self.set_my_language)) 62 | self.create_stack_floating_buttons = True 63 | return screen 64 | 65 | 66 | Example().run() 67 | """ 68 | 69 | from kivy.animation import Animation 70 | from kivy.core.window import Window 71 | from kivy.uix.floatlayout import FloatLayout 72 | from kivy.lang import Builder 73 | from kivy.properties import StringProperty, DictProperty, ObjectProperty, ListProperty 74 | from kivy.metrics import dp 75 | 76 | from kivymd.cards import MDCard 77 | 78 | 79 | Builder.load_string(''' 80 | #:import Window kivy.core.window.Window 81 | #:import MDFloatingActionButton kivymd.button.MDFloatingActionButton 82 | 83 | 84 | 85 | x: Window.width - (self.width + dp(21)) 86 | y: dp(25) 87 | size_hint: None, None 88 | size: dp(46), dp(46) 89 | elevation: 5 90 | md_bg_color: app.theme_cls.primary_color 91 | text_color: root.parent.text_color 92 | on_release: self.parent.callback(self) 93 | 94 | 95 | 96 | size_hint: None, None 97 | height: dp(20) 98 | width: label.texture_size[0] 99 | border_color_a: .5 100 | md_bg_color: app.theme_cls.primary_color 101 | x: -self.width 102 | 103 | Label: 104 | id: label 105 | color: root.parent.text_color 106 | bold: True 107 | markup: True 108 | text: ' %s ' % root.text 109 | 110 | 111 | 112 | FloatingButton: 113 | id: f_btn_1 114 | icon: list(root.floating_data.values())[0] 115 | FloatingButton: 116 | id: f_btn_2 117 | icon: list(root.floating_data.values())[1] 118 | FloatingButton: 119 | id: f_btn_3 120 | icon: list(root.floating_data.values())[2] 121 | 122 | MDFloatingLabel: 123 | id: f_lbl_1 124 | text: list(root.floating_data.keys())[0] 125 | y: dp(117) 126 | MDFloatingLabel: 127 | id: f_lbl_2 128 | text: list(root.floating_data.keys())[1] 129 | y: dp(170) 130 | MDFloatingLabel: 131 | id: f_lbl_3 132 | text: list(root.floating_data.keys())[2] 133 | y: dp(226) 134 | 135 | MDFloatingActionButton: 136 | icon: root.icon 137 | size: dp(56), dp(56) 138 | x: Window.width - (self.width + dp(15)) 139 | md_bg_color: app.theme_cls.primary_color 140 | text_color: root.text_color 141 | y: dp(15) 142 | on_release: root.show_floating_buttons() 143 | ''') 144 | 145 | 146 | class MDFloatingLabel(MDCard): 147 | text = StringProperty() 148 | text_color = ListProperty([0, 0, 0, 1]) 149 | 150 | class MDStackFloatingButtons(FloatLayout): 151 | icon = StringProperty('checkbox-blank-circle') 152 | callback = ObjectProperty(lambda x: None) 153 | text_color = ListProperty([0, 0, 0, 1]) 154 | floating_data = DictProperty() 155 | show = False 156 | in_progress = False 157 | 158 | def __init__(self, **kwargs): 159 | super().__init__(**kwargs) 160 | 161 | self.lbl_list = [self.ids.f_lbl_1, self.ids.f_lbl_2, self.ids.f_lbl_3] 162 | self.btn_list = [self.ids.f_btn_1, self.ids.f_btn_2, self.ids.f_btn_3] 163 | 164 | def set_in_progress(self, instance_anim, instance): 165 | if instance is self.ids.f_btn_3: 166 | self.in_progress = False 167 | 168 | def show_floating_buttons(self): 169 | step = dp(46) 170 | if self.in_progress: 171 | return 172 | self.in_progress = True 173 | for i, btn in enumerate(self.btn_list): 174 | step += dp(56) 175 | anim = Animation(y=step, d=.5, t='out_elastic') 176 | anim.bind(on_complete=self.set_in_progress) 177 | anim.start(btn) 178 | 179 | self.show = True if not self.show else False 180 | self.show_floating_labels() if self.show \ 181 | else self.hide_floating_labels() 182 | 183 | def show_floating_labels(self): 184 | i = 0 185 | for lbl in self.lbl_list: 186 | i += .3 187 | pos_x = Window.width - (lbl.width + dp(46 + 21 * 1.5)) 188 | Animation(x=pos_x, d=i, t='out_elastic').start(lbl) 189 | 190 | def hide_floating_buttons(self): 191 | for btn in self.btn_list: 192 | Animation(y=25, d=.5, t='in_elastic').start(btn) 193 | 194 | def hide_floating_labels(self): 195 | i = 1 196 | for lbl in self.lbl_list: 197 | i -= .3 198 | Animation(x=-lbl.width, d=i, t='out_elastic').start(lbl) 199 | self.hide_floating_buttons() 200 | -------------------------------------------------------------------------------- /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/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/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.) 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.) 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. - 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. - 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 ( 162 | distance < 0 and reach < soft_min) or ( 163 | distance > 0 and soft_max < reach): 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/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 <= .03928: 47 | return color / 12.92 48 | else: 49 | return ((color + .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 .2126*rg + .7152*gg + .0722*bg 57 | 58 | 59 | def _black_or_white_by_contrast_ratio(color): 60 | l_color = _luminance(color) 61 | l_black = .0 62 | l_white = 1.0 63 | b_contrast = (l_color + .05) / (l_black + .05) 64 | w_contrast = (l_white + .05) / (l_color + .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 | text_color = text_colors[c[0]][name] 95 | print( 96 | f" The {name} hue gives {col_bri} using color " 97 | f"brightness, {con_rat} using contrast ratio, and " 98 | f"{text_color} from the MD spec") 99 | -------------------------------------------------------------------------------- /kivymd/time_picker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.lang import Builder 4 | from kivy.uix.modalview import ModalView 5 | from kivy.uix.floatlayout import FloatLayout 6 | from kivymd.theming import ThemableBehavior 7 | from kivymd.elevationbehavior import RectangularElevationBehavior 8 | from kivy.properties import ObjectProperty, ListProperty 9 | 10 | Builder.load_string(""" 11 | #:import MDFlatButton kivymd.button.MDFlatButton 12 | #:import CircularTimePicker kivymd.vendor.circularTimePicker.CircularTimePicker 13 | #:import dp kivy.metrics.dp 14 | : 15 | size_hint: (None, None) 16 | size: [dp(270), dp(335)+dp(95)] 17 | #if root.theme_cls.device_orientation == 'portrait' else [dp(520), dp(325)] 18 | pos_hint: {'center_x': .5, 'center_y': .5} 19 | canvas: 20 | Color: 21 | rgba: self.theme_cls.bg_light 22 | Rectangle: 23 | size: [dp(270), dp(335)] 24 | #if root.theme_cls.device_orientation == 'portrait' else [dp(250), root.height] 25 | pos: [root.pos[0], root.pos[1] + root.height - dp(335) - dp(95)] 26 | #if root.theme_cls.device_orientation == 'portrait' else [root.pos[0]+dp(270), root.pos[1]] 27 | Color: 28 | rgba: self.theme_cls.primary_color 29 | Rectangle: 30 | size: [dp(270), dp(95)] 31 | #if root.theme_cls.device_orientation == 'portrait' else [dp(270), root.height] 32 | pos: [root.pos[0], root.pos[1] + root.height - dp(95)] 33 | #if root.theme_cls.device_orientation == 'portrait' else [root.pos[0], root.pos[1]] 34 | Color: 35 | rgba: self.theme_cls.bg_dark 36 | Ellipse: 37 | size: [dp(220), dp(220)] 38 | #if root.theme_cls.device_orientation == 'portrait' else [dp(195), dp(195)] 39 | pos: root.pos[0]+dp(270)/2-dp(220)/2, root.pos[1] + root.height - (dp(335)/2+dp(95)) - dp(220)/2 + dp(35) 40 | #Color: 41 | #rgba: (1, 0, 0, 1) 42 | #Line: 43 | #width: 4 44 | #points: dp(270)/2, root.height, dp(270)/2, 0 45 | CircularTimePicker: 46 | id: time_picker 47 | pos: (dp(270)/2)-(self.width/2), root.height-self.height 48 | size_hint: [.8, .8] 49 | #if root.theme_cls.device_orientation == 'portrait' else [0.35, 0.9] 50 | pos_hint: {'center_x': 0.5, 'center_y': 0.585} 51 | #if root.theme_cls.device_orientation == 'portrait' else {'center_x': 0.75, 'center_y': 0.7} 52 | MDFlatButton: 53 | width: dp(32) 54 | id: ok_button 55 | pos: root.pos[0]+root.size[0]-self.width-dp(10), root.pos[1] + dp(10) 56 | text: "OK" 57 | on_release: root.close_ok() 58 | MDFlatButton: 59 | id: cancel_button 60 | pos: root.pos[0]+root.size[0]-self.width-ok_button.width-dp(10), root.pos[1] + dp(10) 61 | text: "Cancel" 62 | on_release: root.close_cancel() 63 | """) 64 | 65 | 66 | class MDTimePicker(ThemableBehavior, FloatLayout, ModalView, 67 | RectangularElevationBehavior): 68 | # md_bg_color = ListProperty((0, 0, 0, 0)) 69 | time = ObjectProperty() 70 | 71 | def __init__(self, **kwargs): 72 | super(MDTimePicker, self).__init__(**kwargs) 73 | self.current_time = self.ids.time_picker.time 74 | 75 | def set_time(self, time): 76 | try: 77 | self.ids.time_picker.set_time(time) 78 | except AttributeError: 79 | raise TypeError("MDTimePicker._set_time must receive a datetime object, not a \"" + 80 | type(time).__name__ + "\"") 81 | 82 | def close_cancel(self): 83 | self.dismiss() 84 | 85 | def close_ok(self): 86 | self.current_time = self.ids.time_picker.time 87 | self.time = self.current_time 88 | self.dismiss() 89 | -------------------------------------------------------------------------------- /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/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 import platform 16 | 17 | 18 | if platform == 'android': 19 | from . androidtoast import toast 20 | else: 21 | from . kivytoast import toast 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | @run_on_ui_thread 23 | def toast(text, length_long=False): 24 | duration = Toast.LENGTH_LONG if length_long else Toast.LENGTH_SHORT 25 | String = autoclass('java.lang.String') 26 | c = cast('java.lang.CharSequence', String(text)) 27 | t = Toast.makeText(context, c, duration) 28 | t.show() 29 | -------------------------------------------------------------------------------- /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/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 | 14 | from kivy.core.window import Window 15 | from kivy.uix.label import Label 16 | from kivy.animation import Animation 17 | from kivy.uix.modalview import ModalView 18 | from kivy.clock import Clock 19 | from kivy.metrics import dp 20 | from kivy.lang import Builder 21 | 22 | from kivymd import images_path 23 | 24 | Builder.load_string(""" 25 | : 26 | canvas: 27 | Color: 28 | rgba: .2, .2, .2, 1 29 | RoundedRectangle: 30 | pos: self.pos 31 | size: self.size 32 | radius: [15,] 33 | """) 34 | 35 | 36 | class Toast(ModalView): 37 | def __init__(self, **kwargs): 38 | super().__init__(**kwargs) 39 | self.size_hint = (None, None) 40 | self.pos_hint = {'center_x': .5, 'center_y': .1} 41 | self.background_color = [0, 0, 0, 0] 42 | self.background = f'{images_path}transparent.png' 43 | self.opacity = 0 44 | self.auto_dismiss = True 45 | self.label_toast = Label(size_hint=(None, None), opacity=0) 46 | self.label_toast.bind(texture_size=self.label_check_texture_size) 47 | self.add_widget(self.label_toast) 48 | 49 | def label_check_texture_size(self, instance, texture_size): 50 | texture_width, texture_height = texture_size 51 | if texture_width > Window.width: 52 | instance.text_size = (Window.width - dp(10), None) 53 | instance.texture_update() 54 | texture_width, texture_height = instance.texture_size 55 | self.size = (texture_width + 25, texture_height + 25) 56 | 57 | def toast(self, text_toast): 58 | self.label_toast.text = text_toast 59 | self.open() 60 | 61 | def on_open(self): 62 | self.fade_in() 63 | Clock.schedule_once(self.fade_out, 2.5) 64 | 65 | def fade_in(self): 66 | Animation(opacity=1, duration=.4).start(self.label_toast) 67 | Animation(opacity=1, duration=.4).start(self) 68 | 69 | def fade_out(self, interval): 70 | Animation(opacity=0, duration=.4).start(self.label_toast) 71 | anim_body = Animation(opacity=0, duration=.4) 72 | anim_body.bind(on_complete=lambda *x: self.dismiss()) 73 | anim_body.start(self) 74 | 75 | def on_touch_down(self, touch): 76 | if not self.collide_point(*touch.pos): 77 | if self.auto_dismiss: 78 | self.dismiss() 79 | return False 80 | super(ModalView, self).on_touch_down(touch) 81 | return True 82 | 83 | 84 | def toast(text, length_long=False): 85 | Toast().toast(text) 86 | -------------------------------------------------------------------------------- /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/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 | return param.args[0] 44 | 45 | 46 | class event: 47 | def __init__(self, ed, name): 48 | self.bind_id = None 49 | self.ed = ed 50 | self.name = name 51 | 52 | def bind(self, step_coro): 53 | self.bind_id = bind_id = self.ed.fbind(self.name, self.callback) 54 | assert bind_id > 0 # check if binding succeeded 55 | self.step_coro = step_coro 56 | 57 | def callback(self, *args, **kwargs): 58 | self.parameter = CallbackParameter(args, kwargs) 59 | ed = self.ed 60 | ed.unbind_uid(self.name, self.bind_id) 61 | self.step_coro() 62 | 63 | def __await__(self): 64 | yield self.bind 65 | return self.parameter 66 | -------------------------------------------------------------------------------- /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(cutting_size, path_to_image, path_to_save_crop_image, 14 | corner=0, blur=0, corner_mode='all'): 15 | """Call functions of cropping/blurring/rounding image. 16 | 17 | cutting_size: size to which the image will be cropped; 18 | path_to_image: path to origin image; 19 | path_to_save_crop_image: path to new image; 20 | corner: value of rounding corners; 21 | blur: blur value; 22 | corner_mode: 'all'/'top'/'bottom' - indicates which corners to round out; 23 | 24 | """ 25 | 26 | im = _crop_image(cutting_size, path_to_image, path_to_save_crop_image) 27 | if corner: 28 | im = add_corners(im, corner, corner_mode) 29 | if blur: 30 | im = add_blur(im, blur) 31 | try: 32 | im.save(path_to_save_crop_image) 33 | except IOError: 34 | im.save(path_to_save_crop_image, 'JPEG') 35 | 36 | 37 | def add_blur(im, mode): 38 | from PIL import ImageFilter 39 | 40 | im = im.filter(ImageFilter.GaussianBlur(mode)) 41 | 42 | return im 43 | 44 | 45 | def _crop_image(cutting_size, path_to_image, path_to_save_crop_image): 46 | from PIL import Image, ImageOps 47 | 48 | image = Image.open(path_to_image) 49 | image = ImageOps.fit(image, cutting_size) 50 | image.save(path_to_save_crop_image) 51 | 52 | return image 53 | 54 | 55 | def add_corners(im, corner, corner_mode): 56 | def add_top_corners(): 57 | alpha.paste(circle.crop((0, 0, corner, corner)), (0, 0)) 58 | alpha.paste(circle.crop( 59 | (corner, 0, corner * 2, corner)), (w - corner, 0)) 60 | print(corner) 61 | 62 | def add_bottom_corners(): 63 | alpha.paste(circle.crop( 64 | (0, corner, corner, corner * 2)), (0, h - corner)) 65 | alpha.paste( 66 | circle.crop((corner, corner, corner * 2, corner * 2)), (w - corner, h - corner)) 67 | print(corner) 68 | 69 | from PIL import Image, ImageDraw 70 | 71 | circle = Image.new('L', (corner * 2, corner * 2), 0) 72 | draw = ImageDraw.Draw(circle) 73 | draw.ellipse((0, 0, corner * 2, corner * 2), fill=255) 74 | alpha = Image.new('L', im.size, 255) 75 | w, h = im.size 76 | 77 | if corner_mode == 'all': 78 | add_top_corners() 79 | add_bottom_corners() 80 | elif corner_mode == 'top': 81 | add_top_corners() 82 | if corner_mode == 'bottom': 83 | add_bottom_corners() 84 | im.putalpha(alpha) 85 | 86 | return im 87 | 88 | 89 | def prepare_mask(size, antialias=2): 90 | from PIL import Image, ImageDraw 91 | 92 | mask = Image.new('L', (size[0] * antialias, size[1] * antialias), 0) 93 | ImageDraw.Draw(mask).ellipse((0, 0) + mask.size, fill=255) 94 | return mask.resize(size, Image.ANTIALIAS) 95 | 96 | 97 | def _crop_round_image(im, s): 98 | from PIL import Image 99 | 100 | w, h = im.size 101 | k = w // s[0] - h // s[1] 102 | if k > 0: 103 | im = im.crop(((w - h) // 2, 0, (w + h) // 2, h)) 104 | elif k < 0: 105 | im = im.crop((0, (h - w) // 2, w, (h + w) // 2)) 106 | return im.resize(s, Image.ANTIALIAS) 107 | 108 | 109 | def crop_round_image(cutting_size, path_to_image, path_to_new_image): 110 | from PIL import Image 111 | 112 | im = Image.open(path_to_image) 113 | im = _crop_round_image(im, cutting_size) 114 | im.putalpha(prepare_mask(cutting_size, 4)) 115 | im.save(path_to_new_image) -------------------------------------------------------------------------------- /kivymd/vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcroni/KivyLiteEmulator/380c81f5cb3f2a7a18049a186a3c4b853869e7ab/kivymd/vendor/__init__.py -------------------------------------------------------------------------------- /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/circleLayout/README.md: -------------------------------------------------------------------------------- 1 | CircularLayout 2 | ============== 3 | 4 | CircularLayout is a special layout that places widgets around a circle. 5 | 6 | See the widget's documentation and the example for more information. 7 | 8 | ![Screenshot](screenshot.png) 9 | 10 | size_hint 11 | --------- 12 | 13 | size_hint_x is used as an angle-quota hint (widget with higher 14 | size_hint_x will be farther from each other, and viceversa), while 15 | size_hint_y is used as a widget size hint (widgets with a higher size 16 | hint will be bigger).size_hint_x cannot be None. 17 | 18 | Widgets are all squares, unless you set size_hint_y to None (in that 19 | case you'll be able to specify your own size), and their size is the 20 | difference between the outer and the inner circle's radii. To make the 21 | widgets bigger you can just decrease inner_radius_hint. -------------------------------------------------------------------------------- /kivymd/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 NumericProperty, ReferenceListProperty, \ 25 | OptionProperty, BoundedNumericProperty, VariableListProperty, AliasProperty 26 | 27 | __all__ = ('CircularLayout') 28 | 29 | try: 30 | xrange(1, 2) 31 | except NameError: 32 | def xrange(first, second, third=None): 33 | if third: 34 | return range(first, second, third) 35 | else: 36 | return range(first, second) 37 | 38 | 39 | class CircularLayout(Layout): 40 | """ 41 | Circular layout class. See module documentation for more information. 42 | """ 43 | 44 | padding = VariableListProperty([0, 0, 0, 0]) 45 | """Padding between the layout box and it's children: [padding_left, 46 | padding_top, padding_right, padding_bottom]. 47 | 48 | padding also accepts a two argument form [padding_horizontal, 49 | padding_vertical] and a one argument form [padding]. 50 | 51 | .. version changed:: 1.7.0 52 | Replaced NumericProperty with VariableListProperty. 53 | 54 | :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and 55 | defaults to [0, 0, 0, 0]. 56 | """ 57 | 58 | start_angle = NumericProperty(0) 59 | """Angle (in degrees) at which the first widget will be placed. 60 | Start counting angles from the X axis, going counterclockwise. 61 | 62 | :attr:`start_angle` is a :class:`~kivy.properties.NumericProperty` and 63 | defaults to 0 (start from the right). 64 | """ 65 | 66 | circle_quota = BoundedNumericProperty(360, min=0, max=360) 67 | """Size (in degrees) of the part of the circumference that will actually 68 | be used to place widgets. 69 | 70 | :attr:`circle_quota` is a :class:`~kivy.properties.BoundedNumericProperty` 71 | and defaults to 360 (all the circumference). 72 | """ 73 | 74 | direction = OptionProperty("ccw", options=("cw", "ccw")) 75 | """Direction of widgets in the circle. 76 | 77 | :attr:`direction` is an :class:`~kivy.properties.OptionProperty` and 78 | defaults to 'ccw'. Can be 'ccw' (counterclockwise) or 'cw' (clockwise). 79 | """ 80 | 81 | outer_radius_hint = NumericProperty(1) 82 | """Sets the size of the outer circle. A number greater than 1 will make the 83 | widgets larger than the actual widget, a number smaller than 1 will leave 84 | a gap. 85 | 86 | :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` 87 | and defaults to 1. 88 | """ 89 | 90 | inner_radius_hint = NumericProperty(.6) 91 | """Sets the size of the inner circle. A number greater than 92 | :attr:`outer_radius_hint` will cause glitches. The closest it is to 93 | :attr:`outer_radius_hint`, the smallest will be the widget in the layout. 94 | 95 | :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` 96 | and defaults to 1. 97 | """ 98 | 99 | radius_hint = ReferenceListProperty(inner_radius_hint, outer_radius_hint) 100 | """Combined :attr:`outer_radius_hint` and :attr:`inner_radius_hint` 101 | in a list for convenience. See their documentation for more details. 102 | 103 | :attr:`radius_hint` is a :class:`~kivy.properties.ReferenceListProperty`. 104 | """ 105 | 106 | def _get_delta_radii(self): 107 | radius = min(self.width - self.padding[0] - self.padding[2], 108 | self.height - self.padding[1] - self.padding[3]) / 2. 109 | outer_r = radius * self.outer_radius_hint 110 | inner_r = radius * self.inner_radius_hint 111 | return outer_r - inner_r 112 | delta_radii = AliasProperty(_get_delta_radii, None, 113 | bind=("radius_hint", "padding", "size")) 114 | 115 | def __init__(self, **kwargs): 116 | super().__init__(**kwargs) 117 | 118 | self.bind( 119 | start_angle=self._trigger_layout, 120 | parent=self._trigger_layout, 121 | # padding=self._trigger_layout, 122 | children=self._trigger_layout, 123 | size=self._trigger_layout, 124 | radius_hint=self._trigger_layout, 125 | pos=self._trigger_layout) 126 | 127 | def do_layout(self, *largs): 128 | # optimize layout by preventing looking at the same attribute in a loop 129 | len_children = len(self.children) 130 | if len_children == 0: 131 | return 132 | selfcx = self.center_x 133 | selfcy = self.center_y 134 | direction = self.direction 135 | cquota = radians(self.circle_quota) 136 | start_angle_r = radians(self.start_angle) 137 | padding_left = self.padding[0] 138 | padding_top = self.padding[1] 139 | padding_right = self.padding[2] 140 | padding_bottom = self.padding[3] 141 | padding_x = padding_left + padding_right 142 | padding_y = padding_top + padding_bottom 143 | 144 | radius = min(self.width-padding_x, self.height-padding_y) / 2. 145 | outer_r = radius * self.outer_radius_hint 146 | inner_r = radius * self.inner_radius_hint 147 | middle_r = radius * sum(self.radius_hint) / 2. 148 | delta_r = outer_r - inner_r 149 | 150 | stretch_weight_angle = 0. 151 | for w in self.children: 152 | sha = w.size_hint_x 153 | if sha is None: 154 | raise ValueError( 155 | "size_hint_x cannot be None in a CircularLayout") 156 | else: 157 | stretch_weight_angle += sha 158 | 159 | sign = +1. 160 | angle_offset = start_angle_r 161 | if direction == 'cw': 162 | angle_offset = 2 * pi - start_angle_r 163 | sign = -1. 164 | 165 | for c in reversed(self.children): 166 | sha = c.size_hint_x 167 | shs = c.size_hint_y 168 | 169 | angle_quota = cquota / stretch_weight_angle * sha 170 | angle = angle_offset + (sign * angle_quota / 2) 171 | angle_offset += sign * angle_quota 172 | 173 | # kived: looking it up, yes. x = cos(angle) * radius + centerx; 174 | # y = sin(angle) * radius + centery 175 | ccx = cos(angle) * middle_r + selfcx + padding_left - padding_right 176 | ccy = sin(angle) * middle_r + selfcy + padding_bottom - padding_top 177 | 178 | c.center_x = ccx 179 | c.center_y = ccy 180 | if shs: 181 | s = delta_r * shs 182 | c.width = s 183 | c.height = s 184 | 185 | 186 | if __name__ == "__main__": 187 | from kivy.app import App 188 | from kivy.uix.button import Button 189 | 190 | class CircLayoutApp(App): 191 | def build(self): 192 | cly = CircularLayout(direction="cw", start_angle=-75, 193 | inner_radius_hint=.7, padding="20dp") 194 | 195 | for i in xrange(1, 13): 196 | cly.add_widget(Button(text=str(i), font_size="30dp")) 197 | 198 | return cly 199 | 200 | CircLayoutApp().run() 201 | -------------------------------------------------------------------------------- /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/vendor/circularTimePicker/README.md: -------------------------------------------------------------------------------- 1 | Circular Date & Time Picker for Kivy 2 | ==================================== 3 | 4 | (currently only time, date coming soon) 5 | 6 | Based on [CircularLayout](https://github.com/kivy-garden/garden.circularlayout). 7 | The main aim is to provide a date and time selector similar to the 8 | one found in Android KitKat+. 9 | 10 | ![Screenshot](screenshot.png) 11 | 12 | Simple usage 13 | ------------ 14 | 15 | Import the widget with 16 | 17 | ```python 18 | from kivy.garden.circulardatetimepicker import CircularTimePicker 19 | ``` 20 | 21 | then use it! That's it! 22 | 23 | ```python 24 | c = CircularTimePicker() 25 | c.bind(time=self.set_time) 26 | root.add_widget(c) 27 | ``` 28 | 29 | in Kv language: 30 | 31 | ``` 32 | : 33 | BoxLayout: 34 | orientation: "vertical" 35 | 36 | CircularTimePicker 37 | 38 | Button: 39 | text: "Dismiss" 40 | size_hint_y: None 41 | height: "40dp" 42 | on_release: root.dismiss() 43 | ``` -------------------------------------------------------------------------------- /kivymd/vendor/navigationdrawer/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Alexander Taylor 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /kivymd/vendor/navigationdrawer/README.md: -------------------------------------------------------------------------------- 1 | # NavigationDrawer 2 | 3 | The NavigationDrawer widget provides a hidden panel view designed to 4 | duplicate the popular Android layout. The user views one main widget 5 | but can slide from the left of the screen to view a second, previously 6 | hidden widget. The transition between open/closed is smoothly 7 | animated, with the parameters (anim time, panel width, touch 8 | detection) all user configurable. If the panel is released without 9 | being fully open or closed, it animates to an appropriate 10 | configuration. 11 | 12 | NavigationDrawer supports many different animation properties, 13 | including moving one or both of the side/main panels, darkening 14 | either/both widgets, changing side panel opacity, and changing which 15 | widget is on top. The user can edit these individually to taste (this 16 | is enough rope to hang oneself, it's easy to make a useless or silly 17 | configuration!), or use one of a few preset animations. 18 | 19 | The hidden panel might normally a set of navigation buttons (e.g. in a 20 | GridLayout), but the implementation lets the user use any widget(s). 21 | 22 | The first widget added to the NavigationDrawer is automatically used 23 | as the side panel, and the second widget as the main panel. No further 24 | widgets can be added, further changes are left to the user via editing 25 | the panel widgets. 26 | 27 | # Usage summary 28 | 29 | - The first widget added to a NavigationDrawer is used as the hidden 30 | side panel. 31 | - The second widget added is used as the main panel. 32 | - Both widgets can be removed with remove_widget, or alternatively 33 | set/removed with set_main_panel and set_side_panel. 34 | - The hidden side panel can be revealed by dragging from the left of 35 | the NavigationDrawer. The touch detection width is the 36 | touch_accept_width property. 37 | - Every animation property is user-editable, or default animations 38 | can be chosen by setting anim_type. 39 | 40 | See the example and docstrings for information on individual properties. 41 | 42 | 43 | # Example:: 44 | 45 | from kivy.app import App 46 | from kivy.base import runTouchApp 47 | from kivy.uix.boxlayout import BoxLayout 48 | from kivy.uix.label import Label 49 | from kivy.uix.button import Button 50 | from kivy.uix.image import Image 51 | from kivy.uix.widget import Widget 52 | from kivy.core.window import Window 53 | from kivy.metrics import dp 54 | 55 | from kivy.garden.navigationdrawer import NavigationDrawer 56 | 57 | class ExampleApp(App): 58 | 59 | def build(self): 60 | navigationdrawer = NavigationDrawer() 61 | 62 | side_panel = BoxLayout(orientation='vertical') 63 | side_panel.add_widget(Label(text='Panel label')) 64 | side_panel.add_widget(Button(text='A button')) 65 | side_panel.add_widget(Button(text='Another button')) 66 | navigationdrawer.add_widget(side_panel) 67 | 68 | label_head = ( 69 | '[b]Example label filling main panel[/b]\n\n[color=ff0000](p' 70 | 'ull from left to right!)[/color]\n\nIn this example, the le' 71 | 'ft panel is a simple boxlayout menu, and this main panel is' 72 | ' a BoxLayout with a label and example image.\n\nSeveral pre' 73 | 'set layouts are available (see buttons below), but users ma' 74 | 'y edit every parameter for much more customisation.') 75 | main_panel = BoxLayout(orientation='vertical') 76 | label_bl = BoxLayout(orientation='horizontal') 77 | label = Label(text=label_head, font_size='15sp', 78 | markup=True, valign='top') 79 | label_bl.add_widget(Widget(size_hint_x=None, width=dp(10))) 80 | label_bl.add_widget(label) 81 | label_bl.add_widget(Widget(size_hint_x=None, width=dp(10))) 82 | main_panel.add_widget(Widget(size_hint_y=None, height=dp(10))) 83 | main_panel.add_widget(label_bl) 84 | main_panel.add_widget(Widget(size_hint_y=None, height=dp(10))) 85 | main_panel.add_widget(Image(source='red_pixel.png', allow_stretch=True, 86 | keep_ratio=False, size_hint_y=0.2)) 87 | navigationdrawer.add_widget(main_panel) 88 | label.bind(size=label.setter('text_size')) 89 | 90 | def set_anim_type(name): 91 | navigationdrawer.anim_type = name 92 | modes_layout = BoxLayout(orientation='horizontal') 93 | modes_layout.add_widget(Label(text='preset\nanims:')) 94 | slide_an = Button(text='slide_\nabove_\nanim') 95 | slide_an.bind(on_press=lambda j: set_anim_type('slide_above_anim')) 96 | slide_sim = Button(text='slide_\nabove_\nsimple') 97 | slide_sim.bind(on_press=lambda j: set_anim_type('slide_above_simple')) 98 | fade_in_button = Button(text='fade_in') 99 | fade_in_button.bind(on_press=lambda j: set_anim_type('fade_in')) 100 | reveal_button = Button(text='reveal_\nbelow_\nanim') 101 | reveal_button.bind(on_press= 102 | lambda j: set_anim_type('reveal_below_anim')) 103 | slide_button = Button(text='reveal_\nbelow_\nsimple') 104 | slide_button.bind(on_press= 105 | lambda j: set_anim_type('reveal_below_simple')) 106 | modes_layout.add_widget(slide_an) 107 | modes_layout.add_widget(slide_sim) 108 | modes_layout.add_widget(fade_in_button) 109 | modes_layout.add_widget(reveal_button) 110 | modes_layout.add_widget(slide_button) 111 | main_panel.add_widget(modes_layout) 112 | 113 | button = Button(text='toggle NavigationDrawer state (animate)', 114 | size_hint_y=0.2) 115 | button.bind(on_press=lambda j: navigationdrawer.toggle_state()) 116 | button2 = Button(text='toggle NavigationDrawer state (jump)', 117 | size_hint_y=0.2) 118 | button2.bind(on_press=lambda j: navigationdrawer.toggle_state(False)) 119 | button3 = Button(text='toggle _main_above', size_hint_y=0.2) 120 | button3.bind(on_press=navigationdrawer.toggle_main_above) 121 | main_panel.add_widget(button) 122 | main_panel.add_widget(button2) 123 | main_panel.add_widget(button3) 124 | 125 | return navigationdrawer 126 | 127 | ExampleApp().run() 128 | 129 | -------------------------------------------------------------------------------- /test.kv: -------------------------------------------------------------------------------- 1 | : 2 | MDIconButton: 3 | icon: 'emoticon-excited' 4 | id:get_a_joke 5 | pos_hint: {'center_x': 0.4, 'center_y': 0.75} 6 | size_hint: None, None 7 | size: dp(45), dp(45) 8 | 9 | MDFloatingActionButton: 10 | id:chevron_up 11 | icon: 'share-variant' 12 | opacity: 0.9 13 | pos_hint: {'center_x': 0.8, 'center_y': 0.2} 14 | size_hint: None, None 15 | size: dp(45), dp(45) 16 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from kivy.app import App 2 | from kivy.uix.boxlayout import BoxLayout 3 | from kivy.uix.button import Button 4 | from kivy.uix.floatlayout import FloatLayout 5 | 6 | from kivymd.theming import ThemeManager 7 | 8 | 9 | class Interface(FloatLayout): 10 | pass 11 | 12 | 13 | class Test(App): 14 | theme_cls = ThemeManager() 15 | theme_cls.primary_palette = 'Indigo' 16 | theme_cls.accent_palette = 'Indigo' 17 | def build(self): 18 | return Interface() 19 | 20 | 21 | if __name__ == '__main__': 22 | Test().run() --------------------------------------------------------------------------------