├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── demos └── kitchen_sink │ ├── assets │ ├── african-lion-951778_1280.jpg │ ├── avatar.png │ ├── beautiful-931152_1280.jpg │ ├── camera.png │ ├── cloud-upload.png │ ├── facebook-box.png │ ├── guitar-1139397_1280.jpg │ ├── kitten-1049129_1280.jpg │ ├── kivymd_logo.png │ ├── light-bulb-1042480_1280.jpg │ ├── robin-944887_1280.jpg │ ├── tangerines-1111529_1280.jpg │ ├── twitter.png │ └── youtube-play.png │ ├── buildozer.spec │ └── main.py ├── dockerfiles ├── Dockerfile-linux └── start.sh ├── docs ├── Makefile ├── _templates │ └── sphinx_theme_pd │ │ ├── __init__.py │ │ ├── layout.html │ │ ├── page.html │ │ ├── static │ │ ├── pd.css │ │ └── pd.js │ │ └── theme.conf ├── conf.py ├── index.rst ├── kivymd.bottomsheet.rst ├── kivymd.list.rst ├── kivymd.navigationdrawer.rst ├── kivymd.snackbar.rst └── release.md ├── gitlab-ci ├── README.md ├── android_sdk_downloader.py └── p4a-recipes │ └── kivymd │ └── __init__.py ├── kivymd ├── __init__.py ├── accordion.py ├── backgroundcolorbehavior.py ├── bottomsheet.py ├── button.py ├── card.py ├── color_definitions.py ├── date_picker.py ├── dialog.py ├── elevationbehavior.py ├── fonts │ ├── Roboto-Bold.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 ├── images │ ├── 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 │ └── transparent.png ├── label.py ├── list.py ├── material_resources.py ├── menu.py ├── navigationdrawer.py ├── progressbar.py ├── ripplebehavior.py ├── selectioncontrols.py ├── slider.py ├── slidingpanel.py ├── snackbar.py ├── spinner.py ├── tabs.py ├── textfields.py ├── theme_picker.py ├── theming.py ├── theming_dynamic_text.py ├── time_picker.py ├── toolbar.py ├── vendor │ ├── __init__.py │ ├── circleLayout │ │ ├── LICENSE │ │ ├── README.md │ │ └── __init__.py │ ├── circularTimePicker │ │ ├── LICENSE │ │ ├── README.md │ │ └── __init__.py │ └── navigationdrawer │ │ ├── LICENSE │ │ ├── README.md │ │ └── __init__.py └── version.py ├── requirements.txt ├── requirements └── requirements-test.txt ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | bin 4 | .buildozer 5 | .idea 6 | build 7 | docs/_build 8 | venv/ 9 | .tox/ 10 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | .android_job_template: &android_job_definition 2 | type: build 3 | artifacts: 4 | paths: 5 | - $CI_PROJECT_DIR/demos/kitchen_sink/bin/$CI_BUILD_NAME-$CI_BUILD_REF_NAME-${CI_BUILD_REF:0:8}.apk 6 | before_script: 7 | # Prepare to install Java, skip the accept licence dialog 8 | - apt-get install debconf 9 | - echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections 10 | - echo debconf shared/accepted-oracle-license-v1-1 seen true | debconf-set-selections 11 | - dpkg --add-architecture i386 12 | - apt-get update 13 | # P4A dependencies 14 | - apt-get install -y build-essential ccache git zlib1g-dev python2.7 python2.7-dev libncurses5:i386 libstdc++6:i386 zlib1g:i386 openjdk-7-jdk unzip ant ccache 15 | - apt-get update -qy 16 | - apt-get install -y python-dev python-pip 17 | # Debug things 18 | - python -v 19 | - which python 20 | - pip -V 21 | # P4A dependencies 22 | - pip install https://github.com/kivy/buildozer/archive/master.zip colorama appdirs sh jinja2 six cython --upgrade 23 | - cd $CI_PROJECT_DIR/gitlab-ci 24 | - python ./android_sdk_downloader.py 25 | 26 | 27 | android_kitchen_armeabi: 28 | <<: *android_job_definition 29 | script: 30 | - echo "Building KitchenSink for armeabi" 31 | - cd $CI_PROJECT_DIR/gitlab-ci 32 | # Install API 33 | - python ./android_sdk_downloader.py --api 19 34 | - cd $CI_PROJECT_DIR/demos/kitchen_sink 35 | # Build vars 36 | - export APP_ANDROID_ARCH=armeabi 37 | - export APP_ANDROID_API=19 38 | # Build 39 | - buildozer android debug 40 | # If nothing in bin dir, then the build has failed 41 | - cd $CI_PROJECT_DIR/demos/kitchen_sink 42 | - if [ ! "$(ls -A ./bin)" ]; then exit 1; fi 43 | - cd bin 44 | # Rename apk in format {android_arch}-{git_branch}-{git_commit_hash} 45 | - mv *.apk $CI_BUILD_NAME-$CI_BUILD_REF_NAME-${CI_BUILD_REF:0:8}.apk 46 | 47 | android_kitchen_arm-v7a: 48 | <<: *android_job_definition 49 | script: 50 | - echo "Building KitchenSink for armeabi-v7a" 51 | - cd $CI_PROJECT_DIR/gitlab-ci 52 | # Install API 53 | - python ./android_sdk_downloader.py --api 21 54 | - cd $CI_PROJECT_DIR/demos/kitchen_sink 55 | # Build vars 56 | - export APP_ANDROID_ARCH=armeabi-v7a 57 | - export APP_ANDROID_API=21 58 | # Build 59 | - buildozer android debug 60 | # If nothing in bin dir, then the build has failed 61 | - cd $CI_PROJECT_DIR/demos/kitchen_sink 62 | - if [ ! "$(ls -A ./bin)" ]; then exit 1; fi 63 | - cd bin 64 | # Rename apk in format {android_arch}-{git_branch}-{git_commit_hash} 65 | - mv *.apk $CI_BUILD_NAME-$CI_BUILD_REF_NAME-${CI_BUILD_REF:0:8}.apk 66 | 67 | 68 | android_kitchen_arm64-v8a: 69 | <<: *android_job_definition 70 | script: 71 | - echo "Building KitchenSink for arm64-v8a" 72 | - cd $CI_PROJECT_DIR/gitlab-ci 73 | # Install API 74 | - python ./android_sdk_downloader.py --api 21 75 | - cd $CI_PROJECT_DIR/demos/kitchen_sink 76 | # Build vars 77 | - export APP_ANDROID_ARCH=arm64-v8a 78 | - export APP_ANDROID_API=21 79 | - buildozer android debug 80 | # If nothing in bin dir, then the build has failed 81 | - cd $CI_PROJECT_DIR/demos/kitchen_sink 82 | - if [ ! "$(ls -A ./bin)" ]; then exit 1; fi 83 | - cd bin 84 | # Rename apk in format {android_arch}-{git_branch}-{git_commit_hash} 85 | - mv *.apk $CI_BUILD_NAME-$CI_BUILD_REF_NAME-${CI_BUILD_REF:0:8}.apk 86 | 87 | android_kitchen_x86: 88 | <<: *android_job_definition 89 | script: 90 | - echo "Building KitchenSink for x86" 91 | - cd $CI_PROJECT_DIR/gitlab-ci 92 | # Install API 93 | - python ./android_sdk_downloader.py --api 21 94 | - cd $CI_PROJECT_DIR/demos/kitchen_sink 95 | # Build vars 96 | - export APP_ANDROID_ARCH=x86 97 | - export APP_ANDROID_API=21 98 | # Build 99 | - buildozer android debug 100 | # If nothing in bin dir, then the build has failed 101 | - cd $CI_PROJECT_DIR/demos/kitchen_sink 102 | - if [ ! "$(ls -A ./bin)" ]; then exit 1; fi 103 | - cd bin 104 | # Rename apk in format {android_arch}-{git_branch}-{git_commit_hash} 105 | - mv *.apk $CI_BUILD_NAME-$CI_BUILD_REF_NAME-${CI_BUILD_REF:0:8}.apk 106 | 107 | trigger_build_site: 108 | stage: build 109 | script: 110 | - curl -X POST -F token=$KIVYMD_DOC_BUILD_TOKEN -F ref=master https://gitlab.com/api/v3/projects/2719855/trigger/builds 111 | only: 112 | - master -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: generic 4 | 5 | services: 6 | - docker 7 | 8 | env: 9 | - TAG=kivymd-linux DOCKERFILE=dockerfiles/Dockerfile-linux COMMAND='tox' 10 | 11 | install: 12 | - docker build --tag=$TAG --file=$DOCKERFILE . 13 | 14 | script: 15 | - travis_wait docker run $TAG $COMMAND 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [20190910] 4 | 5 | - Adds release documentation 6 | - Adds `Makefile` 7 | - Applies `isort` linting 8 | - Improves `setup.py` 9 | 10 | ## [20181106] 11 | 12 | - Fixes MDSwitch crash on disable, refs #4 13 | - Fixes MDCheckbox color, refs #3 14 | - Removes the default scrollview from the MDDialog, refs #1 15 | - Fixes `BaseButton.on_disabled` `AttributeError` 16 | - Adds tox/linter testing 17 | - Adds Travis CI testing 18 | - Fixes tox E231, E303, F821, W291, W293 19 | - Fixes crash on `MDSwitch.disabled = True`, refs #4 20 | 21 | 22 | ## [20170825] 23 | 24 | - Initial fork from git@gitlab.com:kivymd/KivyMD.git @19e587e6 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VENV_NAME=venv 2 | PIP=$(VENV_NAME)/bin/pip 3 | TOX=`which tox` 4 | PYTHON=$(VENV_NAME)/bin/python 5 | ISORT=$(VENV_NAME)/bin/isort 6 | FLAKE8=$(VENV_NAME)/bin/flake8 7 | TWINE=`which twine` 8 | SOURCES=kivymd/ demos/ setup.py 9 | # using full path so it can be used outside the root dir 10 | SPHINXBUILD=$(shell realpath venv/bin/sphinx-build) 11 | DOCS_DIR=doc 12 | SYSTEM_DEPENDENCIES= \ 13 | libpython$(PYTHON_VERSION)-dev \ 14 | libsdl2-dev \ 15 | tox \ 16 | virtualenv 17 | OS=$(shell lsb_release -si) 18 | PYTHON_MAJOR_VERSION=3 19 | PYTHON_MINOR_VERSION=6 20 | PYTHON_VERSION=$(PYTHON_MAJOR_VERSION).$(PYTHON_MINOR_VERSION) 21 | PYTHON_WITH_VERSION=python$(PYTHON_VERSION) 22 | 23 | 24 | all: system_dependencies virtualenv 25 | 26 | venv: 27 | test -d venv || virtualenv -p $(PYTHON_WITH_VERSION) venv 28 | 29 | virtualenv: venv 30 | $(PIP) install Cython==0.28.6 31 | $(PIP) install -r requirements.txt 32 | 33 | virtualenv-test: virtualenv 34 | $(PIP) install -r requirements/requirements-test.txt 35 | 36 | system_dependencies: 37 | ifeq ($(OS), Ubuntu) 38 | sudo apt install --yes --no-install-recommends $(SYSTEM_DEPENDENCIES) 39 | endif 40 | 41 | run/linux: virtualenv 42 | PYTHONPATH=. $(PYTHON) demos/kitchen_sink/main.py 43 | 44 | run: run/linux 45 | 46 | test: 47 | $(TOX) 48 | 49 | lint/isort-check: virtualenv-test 50 | $(ISORT) --check-only --recursive --diff $(SOURCES) 51 | 52 | lint/isort-fix: virtualenv-test 53 | $(ISORT) --recursive $(SOURCES) 54 | 55 | lint/flake8: virtualenv-test 56 | $(FLAKE8) $(SOURCES) 57 | 58 | lint: lint/isort-check lint/flake8 59 | 60 | docs/clean: 61 | rm -rf $(DOCS_DIR)/build/ 62 | 63 | docs: 64 | cd $(DOCS_DIR) && SPHINXBUILD=$(SPHINXBUILD) make html 65 | 66 | release/clean: 67 | rm -rf dist/ build/ 68 | 69 | release/build: release/clean 70 | $(PYTHON) setup.py sdist bdist_wheel 71 | $(TWINE) check dist/* 72 | 73 | release/upload: 74 | $(TWINE) upload dist/* 75 | 76 | clean: release/clean docs/clean 77 | py3clean kivymd/ 78 | find kivymd/ -type d -name "__pycache__" -exec rm -r {} + 79 | find . -type d -name "*.egg-info" -exec rm -r {} + 80 | 81 | clean/all: clean 82 | rm -rf $(VENV_NAME) .tox/ 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KivyMD 2 | 3 | [![Build Status](https://secure.travis-ci.org/AndreMiras/KivyMD.png?branch=develop)](http://travis-ci.org/AndreMiras/KivyMD) 4 | [![PyPI version](https://badge.fury.io/py/kivy-garden.kivymd.svg)](https://badge.fury.io/py/kivy-garden.kivymd) 5 | 6 | 7 | 8 | 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. 9 | 10 | 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. 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; giving you freedom to upgrade when you're ready to do the necessary refactoring. 13 | 14 | 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 | Some very early documentation can be found at our project's website, other than that we recommend checking the 21 | [demos/kitchen_sink/main.py](https://github.com/AndreMiras/KivyMD/blob/develop/demos/kitchen_sink/main.py) file for examples. 22 | 23 | ## Install 24 | 25 | #### How to install 26 | 27 | ```sh 28 | pip install kivy-garden.kivymd 29 | ``` 30 | 31 | ## License 32 | 33 | MIT, same as Kivy. 34 | 35 | [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. 36 | 37 | Icons by the materialdesignicons.com community covered by SIL OFL 1.1 38 | -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/african-lion-951778_1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/african-lion-951778_1280.jpg -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/avatar.png -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/beautiful-931152_1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/beautiful-931152_1280.jpg -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/camera.png -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/cloud-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/cloud-upload.png -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/facebook-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/facebook-box.png -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/guitar-1139397_1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/guitar-1139397_1280.jpg -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/kitten-1049129_1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/kitten-1049129_1280.jpg -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/kivymd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/kivymd_logo.png -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/light-bulb-1042480_1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/light-bulb-1042480_1280.jpg -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/robin-944887_1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/robin-944887_1280.jpg -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/tangerines-1111529_1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/tangerines-1111529_1280.jpg -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/twitter.png -------------------------------------------------------------------------------- /demos/kitchen_sink/assets/youtube-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/demos/kitchen_sink/assets/youtube-play.png -------------------------------------------------------------------------------- /demos/kitchen_sink/buildozer.spec: -------------------------------------------------------------------------------- 1 | [app] 2 | 3 | # (str) Title of your application 4 | title = KivyMD Kitchen Sink 5 | 6 | # (str) Package name 7 | package.name = kitchen_sink 8 | 9 | # (str) Package domain (needed for android/ios packaging) 10 | package.domain = org.kivymd 11 | 12 | # (str) Source code where the main.py live 13 | source.dir = . 14 | 15 | # (list) Source files to include (let empty to include all the files) 16 | source.include_exts = py,png,jpg,kv,atlas 17 | 18 | # (list) List of inclusions using pattern matching 19 | #source.include_patterns = assets/*,images/*.png 20 | 21 | # (list) Source files to exclude (let empty to not exclude anything) 22 | #source.exclude_exts = spec 23 | 24 | # (list) List of directory to exclude (let empty to not exclude anything) 25 | source.exclude_dirs = bin 26 | 27 | # (list) List of exclusions using pattern matching 28 | source.exclude_patterns = buildozer.spec 29 | 30 | # (str) Application versioning (method 1) 31 | # version = 0.1 32 | 33 | # (str) Application versioning (method 2) 34 | version.regex = __version__ = ['\"]([^'\"]*)['\"] 35 | version.filename = %(source.dir)s/../../kivymd/__init__.py 36 | 37 | # (list) Application requirements 38 | # comma seperated e.g. requirements = sqlite3,kivy 39 | requirements = kivy==master, kivymd, hostpython2 40 | 41 | # (str) The directory in which python-for-android should look for your own build recipes (if any) 42 | p4a.local_recipes = %(source.dir)s/../../gitlab-ci/p4a-recipes/ 43 | 44 | # (str) Custom source folders for requirements 45 | # Sets custom source for any requirements with recipes 46 | requirements.source.kivymd = ../../ 47 | 48 | # (list) Garden requirements 49 | # garden_requirements = 50 | 51 | # (str) Presplash of the application 52 | presplash.filename = %(source.dir)s/../../kivymd/images/kivymd_logo.png 53 | 54 | # (str) Icon of the application 55 | icon.filename = %(source.dir)s/../../kivymd/images/kivymd_logo.png 56 | 57 | # (str) Supported orientation (one of landscape, portrait or all) 58 | orientation = all 59 | 60 | # (list) List of service to declare 61 | #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY 62 | 63 | # 64 | # OSX Specific 65 | # 66 | 67 | # 68 | # author = © Copyright Info 69 | 70 | # 71 | # Android specific 72 | # 73 | 74 | # (bool) Indicate if the application should be fullscreen or not 75 | fullscreen = 1 76 | 77 | # (list) Permissions 78 | #android.permissions = INTERNET 79 | 80 | # (int) Android API to use 81 | android.api = 19 82 | 83 | # (int) Minimum API required 84 | #android.minapi = 9 85 | 86 | # (int) Android SDK version to use 87 | #android.sdk = 20 88 | 89 | # (str) Android NDK version to use 90 | android.ndk = 10e 91 | 92 | # (bool) Use --private data storage (True) or --dir public storage (False) 93 | #android.private_storage = True 94 | 95 | # (str) Android NDK directory (if empty, it will be automatically downloaded.) 96 | #android.ndk_path = 97 | 98 | # (str) Android SDK directory (if empty, it will be automatically downloaded.) 99 | #android.sdk_path = 100 | 101 | # (str) ANT directory (if empty, it will be automatically downloaded.) 102 | #android.ant_path = 103 | 104 | # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) 105 | #android.p4a_dir = 106 | #android.p4a_dir = /media/zingballyhoo/Media/Code/Repos/python-for-android 107 | 108 | # (str) Filename to the hook for p4a 109 | #p4a.hook = 110 | 111 | 112 | p4a.force-build = True 113 | 114 | 115 | # (list) python-for-android whitelist 116 | #android.p4a_whitelist = 117 | 118 | # (bool) If True, then skip trying to update the Android sdk 119 | # This can be useful to avoid excess Internet downloads or save time 120 | # when an update is due and you just want to test/build your package 121 | android.skip_update = True 122 | 123 | # (str) Bootstrap to use for android builds (android_new only) 124 | # android.bootstrap = sdl2 125 | 126 | # (str) Android entry point, default is ok for Kivy-based app 127 | #android.entrypoint = org.renpy.android.PythonActivity 128 | 129 | # (list) List of Java .jar files to add to the libs so that pyjnius can access 130 | # their classes. Don't add jars that you do not need, since extra jars can slow 131 | # down the build process. Allows wildcards matching, for example: 132 | # OUYA-ODK/libs/*.jar 133 | #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar 134 | 135 | # (list) List of Java files to add to the android project (can be java or a 136 | # directory containing the files) 137 | #android.add_src = 138 | 139 | # (str) python-for-android branch to use, if not master, useful to try 140 | # not yet merged features. 141 | #android.branch = master 142 | 143 | # (str) OUYA Console category. Should be one of GAME or APP 144 | # If you leave this blank, OUYA support will not be enabled 145 | #android.ouya.category = GAME 146 | 147 | # (str) Filename of OUYA Console icon. It must be a 732x412 png image. 148 | #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png 149 | 150 | # (str) XML file to include as an intent filters in tag 151 | #android.manifest.intent_filters = 152 | 153 | # (list) Android additionnal libraries to copy into libs/armeabi 154 | #android.add_libs_armeabi = libs/android/*.so 155 | #android.add_libs_armeabi_v7a = libs/android-v7/*.so 156 | #android.add_libs_x86 = libs/android-x86/*.so 157 | #android.add_libs_mips = libs/android-mips/*.so 158 | 159 | # (bool) Indicate whether the screen should stay on 160 | # Don't forget to add the WAKE_LOCK permission if you set this to True 161 | #android.wakelock = False 162 | 163 | # (list) Android application meta-data to set (key=value format) 164 | #android.meta_data = 165 | 166 | # (list) Android library project to add (will be added in the 167 | # project.properties automatically.) 168 | #android.library_references = 169 | 170 | # (str) Android logcat filters to use 171 | #android.logcat_filters = *:S python:D 172 | 173 | # (bool) Copy library instead of making a libpymodules.so 174 | #android.copy_libs = 1 175 | 176 | # (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86 177 | android.arch = armeabi 178 | 179 | # 180 | # iOS specific 181 | # 182 | 183 | # (str) Path to a custom kivy-ios folder 184 | #ios.kivy_ios_dir = ../kivy-ios 185 | 186 | # (str) Name of the certificate to use for signing the debug version 187 | # Get a list of available identities: buildozer ios list_identities 188 | #ios.codesign.debug = "iPhone Developer: ()" 189 | 190 | # (str) Name of the certificate to use for signing the release version 191 | #ios.codesign.release = %(ios.codesign.debug)s 192 | 193 | 194 | [buildozer] 195 | 196 | # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) 197 | log_level = 2 198 | 199 | # (int) Display warning if buildozer is run as root (0 = False, 1 = True) 200 | warn_on_root = 0 201 | 202 | # (str) Path to build artifact storage, absolute or relative to spec file 203 | # build_dir = ./.buildozer 204 | 205 | # (str) Path to build output (i.e. .apk, .ipa) storage 206 | # bin_dir = ./bin 207 | 208 | # ----------------------------------------------------------------------------- 209 | # List as sections 210 | # 211 | # You can define all the "list" as [section:key]. 212 | # Each line will be considered as a option to the list. 213 | # Let's take [app] / source.exclude_patterns. 214 | # Instead of doing: 215 | # 216 | #[app] 217 | #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* 218 | # 219 | # This can be translated into: 220 | # 221 | #[app:source.exclude_patterns] 222 | #license 223 | #data/audio/*.wav 224 | #data/images/original/* 225 | # 226 | 227 | 228 | # ----------------------------------------------------------------------------- 229 | # Profiles 230 | # 231 | # You can extend section / key with a profile 232 | # For example, you want to deploy a demo version of your application without 233 | # HD content. You could first change the title to add "(demo)" in the name 234 | # and extend the excluded directories to remove the HD content. 235 | # 236 | #[app@demo] 237 | #title = My Application (demo) 238 | # 239 | #[app:source.exclude_patterns@demo] 240 | #images/hd/* 241 | # 242 | # Then, invoke the command line with the "demo" profile: 243 | # 244 | #buildozer --profile demo android debug 245 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile-linux: -------------------------------------------------------------------------------- 1 | # Docker image for installing dependencies on Linux and running tests. 2 | # Build with: 3 | # docker build --tag=kivymd-linux --file=dockerfiles/Dockerfile-linux . 4 | # Run with: 5 | # docker run kivymd-linux /bin/sh -c 'tox' 6 | # Or using the entry point shortcut: 7 | # docker run kivymd-linux 'tox' 8 | # Or for interactive shell: 9 | # docker run -it --rm kivymd-linux 10 | FROM ubuntu:18.04 11 | 12 | # configure locale 13 | RUN apt update -qq > /dev/null && apt install --yes --no-install-recommends \ 14 | locales && \ 15 | locale-gen en_US.UTF-8 16 | ENV LANG="en_US.UTF-8" \ 17 | LANGUAGE="en_US.UTF-8" \ 18 | LC_ALL="en_US.UTF-8" 19 | 20 | # install system dependencies 21 | RUN apt update -qq > /dev/null && apt install --yes --no-install-recommends \ 22 | python3 virtualenv tox 23 | 24 | WORKDIR /app 25 | COPY . /app 26 | ENTRYPOINT ["./dockerfiles/start.sh"] 27 | -------------------------------------------------------------------------------- /dockerfiles/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # if a some command has been passed to container, executes it and exit, 5 | # otherwise runs bash 6 | if [[ $@ ]]; then 7 | eval $@ 8 | else 9 | /bin/bash 10 | fi 11 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build2 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build2 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/KivyMD.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/KivyMD.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/KivyMD" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/KivyMD" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/_templates/sphinx_theme_pd/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def get_html_theme_path(): 5 | theme_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 6 | return theme_dir 7 | -------------------------------------------------------------------------------- /docs/_templates/sphinx_theme_pd/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | 3 | {%- block extrahead %} 4 | 5 | 6 | {% endblock %} 7 | 8 | {%- block header %} 9 | 12 | {%- endblock header %} 13 | 14 | {%- block relbar1 %} 15 | 25 | {% endblock %} 26 | 27 | {%- block relbar2 %}{% endblock %} 28 | 29 | {%- block sidebarrel %}{%- endblock %} 30 | 31 | {%- block sidebarsourcelink %} 32 | {%- if show_source and has_source and sourcename %} 33 |
34 | 38 |
39 | {%- endif %} 40 | {%- endblock %} 41 | 42 | {%- block sidebarsearch %} 43 | 51 | 52 | {%- endblock %} 53 | -------------------------------------------------------------------------------- /docs/_templates/sphinx_theme_pd/page.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block body %} 3 | {{ body }} 4 | {% endblock body %} 5 | -------------------------------------------------------------------------------- /docs/_templates/sphinx_theme_pd/static/pd.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | var admonitions = { 4 | 'note': 'message', 5 | 'warning': 'warning', 6 | 'admonition-todo': 'bookmark' 7 | }; 8 | var iconSize = 'md-30'; 9 | 10 | jQuery.each(admonitions, function (cls, text) { 11 | var container = $("div." + cls + " > p.admonition-title"); 12 | container.prepend('' + text + '') 13 | }); 14 | 15 | $("a.headerlink").html('').prepend('link'); 16 | 17 | var domain_classes = { 18 | 'function': 'function', 19 | 'class': 'class', 20 | 'method': 'method', 21 | 'staticmethod': 'staticmethod', 22 | 'classmethod': 'classmethod' 23 | }; 24 | 25 | jQuery.each(domain_classes, function (cls, text) { 26 | var container_dt = $("dl." + cls + " > dt"); 27 | var container_em = $("dl." + cls + " > dt > em.property"); 28 | 29 | if (container_em[0]) { 30 | // nothing to do; 31 | } else { 32 | container_dt.prepend('' + text + ' ') 33 | } 34 | 35 | container_dt.prepend('code'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /docs/_templates/sphinx_theme_pd/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = pd.css 4 | pygments_style = sphinx 5 | 6 | [option] 7 | nosidebar = false -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # KivyMD documentation build configuration file, created by 4 | # sphinx-quickstart2 on Wed Dec 23 03:40:16 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | import os 15 | import re 16 | import sys 17 | from datetime import datetime 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('../')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.intersphinx', 35 | 'sphinx.ext.todo', 36 | 'sphinx.ext.inheritance_diagram', 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | # source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | # This seems a bit naughty, but I don't care 55 | year = datetime.now().year 56 | project = u'KivyMD' 57 | copyright = u'{}, KivyMD authors'.format(year) 58 | author = u'KivyMD authors' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | 66 | VERSION_FILE = os.path.abspath('../kivymd/__init__.py') 67 | 68 | ver_file_data = open(VERSION_FILE, "rt").read() 69 | ver_regex = r"^__version__ = ['\"]([^'\"]*)['\"]" 70 | ver_reg_search = re.search(ver_regex, ver_file_data, re.M) 71 | if ver_reg_search: 72 | version = ver_reg_search.group(1) 73 | release = version 74 | else: 75 | version = u'0.0' 76 | release = u'0.0' 77 | 78 | # version = u'0.5' 79 | # The full version, including alpha/beta/rc tags. 80 | # release = u'0.5' 81 | 82 | # The language for content autogenerated by Sphinx. Refer to documentation 83 | # for a list of supported languages. 84 | # 85 | # This is also used if you do content translation via gettext catalogs. 86 | # Usually you set "language" from the command line for these cases. 87 | language = None 88 | 89 | # There are two options for replacing |today|: either, you set today to some 90 | # non-false value, then it is used: 91 | # today = '' 92 | # Else, today_fmt is used as the format for a strftime call. 93 | # today_fmt = '%B %d, %Y' 94 | 95 | # List of patterns, relative to source directory, that match files and 96 | # directories to ignore when looking for source files. 97 | exclude_patterns = ['_build'] 98 | 99 | # The reST default role (used for this markup: `text`) to use for all 100 | # documents. 101 | # default_role = None 102 | 103 | # If true, '()' will be appended to :func: etc. cross-reference text. 104 | # add_function_parentheses = True 105 | 106 | # If true, the current module name will be prepended to all description 107 | # unit titles (such as .. function::). 108 | # add_module_names = True 109 | 110 | # If true, sectionauthor and moduleauthor directives will be shown in the 111 | # output. They are ignored by default. 112 | # show_authors = False 113 | 114 | # The name of the Pygments (syntax highlighting) style to use. 115 | pygments_style = 'sphinx' 116 | 117 | # A list of ignored prefixes for module index sorting. 118 | # modindex_common_prefix = [] 119 | 120 | # If true, keep warnings as "system message" paragraphs in the built documents. 121 | # keep_warnings = False 122 | 123 | # If true, `todo` and `todoList` produce output, else they produce nothing. 124 | todo_include_todos = True 125 | 126 | # -- Options for HTML output ---------------------------------------------- 127 | 128 | # The theme to use for HTML and HTML Help pages. See the documentation for 129 | # a list of builtin themes. 130 | html_theme = 'sphinx_theme_pd' 131 | # html_theme = 'sphinx_rtd_theme' 132 | 133 | # Theme options are theme-specific and customize the look and feel of a theme 134 | # further. For a list of options available for each theme, see the 135 | # documentation. 136 | # html_theme_options = {} 137 | 138 | # Add any paths that contain custom themes here, relative to this directory. 139 | html_theme_path = ['./_templates'] 140 | 141 | # The name for this set of Sphinx documents. If None, it defaults to 142 | # " v documentation". 143 | # html_title = None 144 | 145 | # A shorter title for the navigation bar. Default is the same as html_title. 146 | # html_short_title = None 147 | 148 | # The name of an image file (relative to this directory) to place at the top 149 | # of the sidebar. 150 | # html_logo = None 151 | 152 | # The name of an image file (within the static path) to use as favicon of the 153 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 154 | # pixels large. 155 | # html_favicon = None 156 | 157 | # Add any paths that contain custom static files (such as style sheets) here, 158 | # relative to this directory. They are copied after the builtin static files, 159 | # so a file named "default.css" will overwrite the builtin "default.css". 160 | html_static_path = ['_static'] 161 | 162 | # Add any extra paths that contain custom files (such as robots.txt or 163 | # .htaccess) here, relative to this directory. These files are copied 164 | # directly to the root of the documentation. 165 | # html_extra_path = [] 166 | 167 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 168 | # using the given strftime format. 169 | # html_last_updated_fmt = '%b %d, %Y' 170 | 171 | # If true, SmartyPants will be used to convert quotes and dashes to 172 | # typographically correct entities. 173 | # html_use_smartypants = True 174 | 175 | # Custom sidebar templates, maps document names to template names. 176 | # html_sidebars = {} 177 | 178 | # Additional templates that should be rendered to pages, maps page names to 179 | # template names. 180 | # html_additional_pages = {} 181 | 182 | # If false, no module index is generated. 183 | # html_domain_indices = True 184 | 185 | # If false, no index is generated. 186 | # html_use_index = True 187 | 188 | # If true, the index is split into individual pages for each letter. 189 | # html_split_index = False 190 | 191 | # If true, links to the reST sources are added to the pages. 192 | # html_show_sourcelink = True 193 | 194 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 195 | # html_show_sphinx = True 196 | 197 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 198 | # html_show_copyright = True 199 | 200 | # If true, an OpenSearch description file will be output, and all pages will 201 | # contain a tag referring to it. The value of this option must be the 202 | # base URL from which the finished HTML is served. 203 | # html_use_opensearch = '' 204 | 205 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 206 | # html_file_suffix = None 207 | 208 | # Language to be used for generating the HTML full-text search index. 209 | # Sphinx supports the following languages: 210 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 211 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 212 | # html_search_language = 'en' 213 | 214 | # A dictionary with options for the search language support, empty by default. 215 | # Now only 'ja' uses this config value 216 | # html_search_options = {'type': 'default'} 217 | 218 | # The name of a javascript file (relative to the configuration directory) that 219 | # implements a search results scorer. If empty, the default will be used. 220 | # html_search_scorer = 'scorer.js' 221 | 222 | # Output file base name for HTML help builder. 223 | htmlhelp_basename = 'KivyMDdoc' 224 | 225 | # -- Options for LaTeX output --------------------------------------------- 226 | 227 | latex_elements = { 228 | # The paper size ('letterpaper' or 'a4paper'). 229 | # 'papersize': 'letterpaper', 230 | 231 | # The font size ('10pt', '11pt' or '12pt'). 232 | # 'pointsize': '10pt', 233 | 234 | # Additional stuff for the LaTeX preamble. 235 | # 'preamble': '', 236 | 237 | # Latex figure (float) alignment 238 | # 'figure_align': 'htbp', 239 | } 240 | 241 | # Grouping the document tree into LaTeX files. List of tuples 242 | # (source start file, target name, title, 243 | # author, documentclass [howto, manual, or own class]). 244 | latex_documents = [ 245 | (master_doc, 'KivyMD.tex', u'KivyMD Documentation', 246 | u'KivyMD authors', 'manual'), 247 | ] 248 | 249 | # The name of an image file (relative to this directory) to place at the top of 250 | # the title page. 251 | # latex_logo = None 252 | 253 | # For "manual" documents, if this is true, then toplevel headings are parts, 254 | # not chapters. 255 | # latex_use_parts = False 256 | 257 | # If true, show page references after internal links. 258 | # latex_show_pagerefs = False 259 | 260 | # If true, show URL addresses after external links. 261 | # latex_show_urls = False 262 | 263 | # Documents to append as an appendix to all manuals. 264 | # latex_appendices = [] 265 | 266 | # If false, no module index is generated. 267 | # latex_domain_indices = True 268 | 269 | 270 | # -- Options for manual page output --------------------------------------- 271 | 272 | # One entry per manual page. List of tuples 273 | # (source start file, name, description, authors, manual section). 274 | man_pages = [ 275 | (master_doc, 'kivymd', u'KivyMD Documentation', 276 | [author], 1) 277 | ] 278 | 279 | # If true, show URL addresses after external links. 280 | # man_show_urls = False 281 | 282 | 283 | # -- Options for Texinfo output ------------------------------------------- 284 | 285 | # Grouping the document tree into Texinfo files. List of tuples 286 | # (source start file, target name, title, author, 287 | # dir menu entry, description, category) 288 | texinfo_documents = [ 289 | (master_doc, 'KivyMD', u'KivyMD Documentation', 290 | author, 'KivyMD', 'One line description of project.', 291 | 'Miscellaneous'), 292 | ] 293 | 294 | # Documents to append as an appendix to all manuals. 295 | # texinfo_appendices = [] 296 | 297 | # If false, no module index is generated. 298 | # texinfo_domain_indices = True 299 | 300 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 301 | # texinfo_show_urls = 'footnote' 302 | 303 | # If true, do not generate a @detailmenu in the "Top" node's menu. 304 | # texinfo_no_detailmenu = False 305 | 306 | 307 | # Example configuration for intersphinx: refer to the Python standard library. 308 | intersphinx_mapping = {'https://docs.python.org/': None, 'kivy': ('https://kivy.org/docs/', None)} 309 | 310 | # -- Autodoc options ------------------------------------------------------ 311 | 312 | autodoc_member_order = 'bysource' 313 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. KivyMD documentation master file, created by 2 | sphinx-quickstart2 on Wed Dec 23 03:40:16 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to KivyMD's documentation! 7 | ================================== 8 | 9 | .. warning:: 10 | KivyMD is currently 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; giving you freedom to upgrade when you're ready to do the necessary refactoring. 11 | 12 | Contents: 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | 17 | kivymd.list 18 | kivymd.bottomsheet 19 | kivymd.navigationdrawer 20 | kivymd.snackbar 21 | 22 | .. Module contents 23 | .. --------------- 24 | .. 25 | .. .. automodule:: kivymd.snackbar 26 | .. :members: 27 | .. :undoc-members: 28 | .. :show-inheritance: 29 | 30 | Indices and tables 31 | ================== 32 | 33 | * :ref:`genindex` 34 | * :ref:`modindex` 35 | * :ref:`search` 36 | -------------------------------------------------------------------------------- /docs/kivymd.bottomsheet.rst: -------------------------------------------------------------------------------- 1 | kivymd.bottomsheet module 2 | ========================= 3 | 4 | .. automodule:: kivymd.bottomsheet 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/kivymd.list.rst: -------------------------------------------------------------------------------- 1 | kivymd.list module 2 | ================== 3 | 4 | .. automodule:: kivymd.list 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/kivymd.navigationdrawer.rst: -------------------------------------------------------------------------------- 1 | kivymd.navigationdrawer module 2 | ============================== 3 | 4 | .. automodule:: kivymd.navigationdrawer 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/kivymd.snackbar.rst: -------------------------------------------------------------------------------- 1 | kivymd.snackbar module 2 | ====================== 3 | 4 | .. automodule:: kivymd.snackbar 5 | :members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | # How to release 2 | 3 | This is documenting the release process. 4 | 5 | 6 | ## Git flow & CHANGELOG.md 7 | 8 | Make sure the CHANGELOG.md is up to date and follows the http://keepachangelog.com guidelines. 9 | Start the release with git flow: 10 | ```sh 11 | git flow release start YYYYMMDD 12 | ``` 13 | Now update the [CHANGELOG.md](https://github.com/AndreMiras/KivyMD/blob/develop/CHANGELOG.md) 14 | `[Unreleased]` section to match the new release version. 15 | Also update the `version` string from the 16 | [kivymd/version.py](https://github.com/AndreMiras/KivyMD/blob/develop/kivymd/version.py) 17 | file. 18 | Then commit and finish release. 19 | ```sh 20 | git commit -a -m "YYYYMMDD" 21 | git flow release finish 22 | ``` 23 | Push everything, make sure tags are also pushed: 24 | ```sh 25 | git push 26 | git push origin master:master 27 | git push --tags 28 | ``` 29 | 30 | ## Publish to PyPI 31 | 32 | Build it: 33 | ```sh 34 | make release/build 35 | ``` 36 | This will build `kivy_garden.kivymd` and run `twine check`. 37 | You can also check archive content manually via: 38 | ```sh 39 | tar -tvf dist/kivy_garden.kivymd-*.tar.gz 40 | ``` 41 | Last step is to upload both packages: 42 | ```sh 43 | make release/upload 44 | ``` 45 | 46 | ## GitHub 47 | 48 | Got to GitHub [Release/Tags](https://github.com/AndreMiras/KivyMD/tags), click "Add release notes" for the tag just created. 49 | Add the tag name in the "Release title" field and the relevant CHANGELOG.md section in the "Describe this release" textarea field. 50 | Finally, attach the generated APK release file and click "Publish release". 51 | -------------------------------------------------------------------------------- /gitlab-ci/README.md: -------------------------------------------------------------------------------- 1 | These files are here to help with building with Python-for-android Gitlab CI -------------------------------------------------------------------------------- /gitlab-ci/android_sdk_downloader.py: -------------------------------------------------------------------------------- 1 | from buildozer import Buildozer, urlretrieve 2 | from buildozer.targets.android import TargetAndroid 3 | import os 4 | from optparse import OptionParser 5 | 6 | # Designed to be used on Gitlab CI 7 | # This file ensures that Build Tools 19.1 is installed 8 | # This file will also install an android api version if an android api is passed in with "--api" 9 | # 19.1 is used because it is the only build tool version I can get to work properly (minimum required by p4a also) 10 | 11 | parser = OptionParser() 12 | parser.add_option("--api", dest="api", 13 | help="Android API to install", default=None) 14 | 15 | (options, args) = parser.parse_args() 16 | 17 | 18 | class FixedTargetAndroid(TargetAndroid): 19 | @property 20 | def android_ndk_version(self): 21 | return "10e" 22 | 23 | 24 | class NoOutputBuildozer(Buildozer): 25 | def set_target(self, target): 26 | '''Set the target to use (one of buildozer.targets, such as "android") 27 | ''' 28 | self.targetname = target 29 | m = __import__('buildozer.targets.{0}'.format(target), 30 | fromlist=['buildozer']) 31 | self.target = m.get_target(self) 32 | 33 | def download(self, url, filename, cwd=None): 34 | def report_hook(index, blksize, size): 35 | pass 36 | url = url + filename 37 | if cwd: 38 | filename = os.path.join(cwd, filename) 39 | if self.file_exists(filename): 40 | os.unlink(filename) 41 | 42 | self.debug('Downloading {0}'.format(url)) 43 | urlretrieve(url, filename, report_hook) 44 | return filename 45 | 46 | 47 | buildozer = NoOutputBuildozer(target='android') 48 | buildozer.log_level = 0 49 | 50 | # Ensure directories exist 51 | buildozer.mkdir(buildozer.global_buildozer_dir) 52 | buildozer.mkdir(buildozer.global_cache_dir) 53 | buildozer.mkdir(os.path.join(buildozer.global_platform_dir, buildozer.targetname, 'platform')) 54 | 55 | target = FixedTargetAndroid(buildozer) 56 | target._install_android_sdk() 57 | target._install_android_ndk() 58 | target._install_apache_ant() 59 | 60 | 61 | def run_expect(cmd): 62 | from pexpect import EOF 63 | java_tool_options = os.environ.get('JAVA_TOOL_OPTIONS', '') 64 | child = target.buildozer.cmd_expect(cmd, cwd=target.buildozer.global_platform_dir, 65 | timeout=None, 66 | env={ 67 | 'JAVA_TOOL_OPTIONS': java_tool_options + ' -Dfile.encoding=UTF-8' 68 | }) 69 | while True: 70 | index = child.expect([EOF, u'[y/n]: ']) 71 | if index == 0: 72 | break 73 | child.sendline('y') 74 | 75 | plat_dir = buildozer.global_platform_dir 76 | 77 | if not options.api: 78 | for i in range(2): 79 | run_expect(plat_dir + "/android-sdk-20/tools/android update sdk -u -a -t platform-tools,tools") 80 | run_expect(plat_dir + "/android-sdk-20/tools/android update sdk -u -a -t build-tools-19.1.0") 81 | else: 82 | run_expect(plat_dir + "/android-sdk-20/tools/android update sdk -u -a -t android-{}".format(options.api)) 83 | -------------------------------------------------------------------------------- /gitlab-ci/p4a-recipes/kivymd/__init__.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | import sh 4 | from pythonforandroid.logger import shprint, info_main, info 5 | from pythonforandroid.toolchain import PythonRecipe 6 | from pythonforandroid.util import ensure_dir 7 | 8 | 9 | class KivyMDRecipe(PythonRecipe): 10 | # This recipe installs KivyMD into the android dist from source 11 | depends = ['kivy'] 12 | site_packages_name = 'kivymd' 13 | call_hostpython_via_targetpython = False 14 | 15 | def should_build(self, arch): 16 | return True 17 | 18 | def unpack(self, arch): 19 | info_main('Unpacking {} for {}'.format(self.name, arch)) 20 | 21 | build_dir = self.get_build_container_dir(arch) 22 | 23 | user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower())) 24 | if user_dir is not None: 25 | info("Installing KivyMD development versoion (from source)") 26 | self.clean_build() 27 | shprint(sh.rm, '-rf', build_dir) 28 | shprint(sh.mkdir, '-p', build_dir) 29 | shprint(sh.rmdir, build_dir) 30 | ensure_dir(build_dir) 31 | ensure_dir(build_dir + "/kivymd") 32 | shprint(sh.cp, user_dir + '/setup.py', self.get_build_dir(arch) + "/setup.py") 33 | shprint(sh.cp, '-a', user_dir + "/kivymd", self.get_build_dir(arch) + "/kivymd") 34 | return 35 | 36 | 37 | recipe = KivyMDRecipe() 38 | -------------------------------------------------------------------------------- /kivymd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from kivymd.version import __version__ 4 | 5 | path = os.path.dirname(__file__) 6 | fonts_path = os.path.join(path, "fonts/") 7 | images_path = os.path.join(path, 'images/') 8 | -------------------------------------------------------------------------------- /kivymd/accordion.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.lang import Builder 4 | from kivy.properties import (ListProperty, ObjectProperty, OptionProperty, 5 | StringProperty) 6 | from kivy.uix.accordion import Accordion, AccordionItem 7 | from kivy.uix.boxlayout import BoxLayout 8 | 9 | from kivymd.backgroundcolorbehavior import SpecificBackgroundColorBehavior 10 | from kivymd.list import OneLineListItem 11 | from kivymd.theming import ThemableBehavior 12 | 13 | 14 | class MDAccordionItemTitleLayout(ThemableBehavior, BoxLayout): 15 | pass 16 | 17 | 18 | class MDAccordion(ThemableBehavior, SpecificBackgroundColorBehavior, Accordion): 19 | pass 20 | 21 | 22 | class MDAccordionSubItem(OneLineListItem): 23 | parent_item = ObjectProperty() 24 | 25 | 26 | class MDAccordionItem(ThemableBehavior, AccordionItem): 27 | title_theme_color = OptionProperty(None, allownone=True, 28 | options=['Primary', 'Secondary', 'Hint', 29 | 'Error', 'Custom']) 30 | ''' Color theme for title text and icon ''' 31 | 32 | title_color = ListProperty(None, allownone=True) 33 | ''' Color for title text and icon if `title_theme_color` is Custom ''' 34 | 35 | divider_color = ListProperty(None, allownone=True) 36 | ''' Color for dividers between different titles in rgba format 37 | To remove the divider set a color with an alpha of 0. 38 | ''' 39 | 40 | indicator_color = ListProperty(None, allownone=True) 41 | ''' Color for the indicator on the side of the active item in rgba format 42 | To remove the indicator set a color with an alpha of 0. 43 | ''' 44 | 45 | font_style = OptionProperty( 46 | 'Subhead', options=['Body1', 'Body2', 'Caption', 'Subhead', 'Title', 47 | 'Headline', 'Display1', 'Display2', 'Display3', 48 | 'Display4', 'Button', 'Icon']) 49 | ''' Font style to use for the title text ''' 50 | 51 | title_template = StringProperty('MDAccordionItemTitle') 52 | ''' Template to use for the title ''' 53 | 54 | icon = StringProperty('android', allownone=True) 55 | ''' Icon name to use when this item is expanded ''' 56 | 57 | icon_expanded = StringProperty('chevron-up') 58 | ''' Icon name to use when this item is expanded ''' 59 | 60 | icon_collapsed = StringProperty('chevron-down') 61 | ''' Icon name to use when this item is collapsed ''' 62 | 63 | def add_widget(self, widget): 64 | if isinstance(widget, MDAccordionSubItem): 65 | widget.parent_item = self 66 | self.ids.ml.add_widget(widget) 67 | else: 68 | super(MDAccordionItem, self).add_widget(widget) 69 | 70 | 71 | Builder.load_string(''' 72 | #:import MDLabel kivymd.label.MDLabel 73 | #:import md_icons kivymd.icon_definitions.md_icons 74 | 75 | : 76 | md_bg_color: self.theme_cls.primary_color 77 | 78 | : 79 | canvas.before: 80 | # PushMatrix 81 | # Translate: 82 | # xy: (dp(2),0) if self.orientation == 'vertical' else (0,dp(2)) 83 | canvas.after: 84 | # PopMatrix 85 | Color: 86 | rgba: self.divider_color or self.theme_cls.divider_color 87 | Rectangle: 88 | size: (dp(1),self.height) if self.orientation == 'horizontal' else (self.width,dp(1)) 89 | pos:self.pos 90 | Color: 91 | rgba: [0,0,0,0] if self.collapse else (self.indicator_color or self.theme_cls.accent_color) 92 | Rectangle: 93 | size: (dp(2),self.height) if self.orientation == 'vertical' else (self.width,dp(2)) 94 | pos:self.pos 95 | ScrollView: 96 | id: sv 97 | MDList: 98 | id: ml 99 | 100 | : 101 | theme_text_color: 'Custom' 102 | text_color: self.parent_item.parent.specific_text_color 103 | 104 | [MDAccordionItemTitle@MDAccordionItemTitleLayout]: 105 | padding: '12dp' 106 | spacing: '12dp' 107 | orientation: 'horizontal' if ctx.item.orientation=='vertical' else 'vertical' 108 | canvas: 109 | PushMatrix 110 | Translate: 111 | xy: (-dp(2),0) if ctx.item.orientation == 'vertical' else (0,-dp(2)) 112 | 113 | canvas.after: 114 | PopMatrix 115 | MDLabel: 116 | id:_icon 117 | theme_text_color: 'Custom' 118 | text_color: ctx.item.parent.specific_text_color 119 | text: md_icons[ctx.item.icon if ctx.item.icon else 'menu'] 120 | font_style: 'Icon' 121 | size_hint: (None,1) if ctx.item.orientation == 'vertical' else (1,None) 122 | size: ((self.texture_size[0],1) if ctx.item.orientation == 'vertical' else (1,self.texture_size[1])) \ 123 | if ctx.item.icon else (0,0) 124 | text_size: (self.width, None) if ctx.item.orientation=='vertical' else (None,self.width) 125 | canvas.before: 126 | PushMatrix 127 | Rotate: 128 | angle: 90 if ctx.item.orientation == 'horizontal' else 0 129 | origin: self.center 130 | canvas.after: 131 | PopMatrix 132 | MDLabel: 133 | id:_label 134 | theme_text_color: 'Custom' 135 | text_color: ctx.item.parent.specific_text_color 136 | text: ctx.item.title 137 | font_style: ctx.item.font_style 138 | text_size: (self.width, None) if ctx.item.orientation=='vertical' else (None,self.width) 139 | canvas.before: 140 | PushMatrix 141 | Rotate: 142 | angle: 90 if ctx.item.orientation == 'horizontal' else 0 143 | origin: self.center 144 | canvas.after: 145 | PopMatrix 146 | 147 | MDLabel: 148 | id:_expand_icon 149 | theme_text_color: 'Custom' 150 | text_color: ctx.item.parent.specific_text_color 151 | font_style:'Icon' 152 | size_hint: (None,1) if ctx.item.orientation == 'vertical' else (1,None) 153 | size: (self.texture_size[0],1) if ctx.item.orientation == 'vertical' else (1,self.texture_size[1]) 154 | text: md_icons[ctx.item.icon_collapsed if ctx.item.collapse else ctx.item.icon_expanded] 155 | halign: 'right' if ctx.item.orientation=='vertical' else 'center' 156 | #valign: 'middle' if ctx.item.orientation=='vertical' else 'bottom' 157 | canvas.before: 158 | PushMatrix 159 | Rotate: 160 | angle: 90 if ctx.item.orientation == 'horizontal' else 0 161 | origin: self.center 162 | canvas.after: 163 | PopMatrix 164 | ''') 165 | 166 | if __name__ == '__main__': 167 | from kivy.app import App 168 | from kivymd.theming import ThemeManager 169 | 170 | class AccordionApp(App): 171 | theme_cls = ThemeManager() 172 | 173 | def build(self): 174 | # self.theme_cls.primary_palette = 'Indigo' 175 | return Builder.load_string(""" 176 | #:import MDLabel kivymd.label.MDLabel 177 | BoxLayout: 178 | spacing: '64dp' 179 | MDAccordion: 180 | orientation: 'vertical' 181 | MDAccordionItem: 182 | title: 'Item 1' 183 | icon: 'home' 184 | MDAccordionSubItem: 185 | text: "Subitem 1" 186 | MDAccordionSubItem: 187 | text: "Subitem 2" 188 | MDAccordionSubItem: 189 | text: "Subitem 3" 190 | MDAccordionItem: 191 | title: 'Item 2' 192 | icon: 'earth' 193 | MDAccordionSubItem: 194 | text: "Subitem 4" 195 | MDAccordionSubItem: 196 | text: "Subitem 5" 197 | MDAccordionSubItem: 198 | text: "Subitem 6" 199 | MDAccordionItem: 200 | title: 'Item 3' 201 | MDAccordionSubItem: 202 | text: "Subitem 7" 203 | MDAccordionSubItem: 204 | text: "Subitem 8" 205 | MDAccordionSubItem: 206 | text: "Subitem 9" 207 | MDAccordion: 208 | orientation: 'horizontal' 209 | MDAccordionItem: 210 | title:'Item 1' 211 | icon: 'home' 212 | MDLabel: 213 | text:'Content 1' 214 | theme_text_color:'Primary' 215 | MDAccordionItem: 216 | title:'Item 2' 217 | MDLabel: 218 | text:'Content 2' 219 | theme_text_color:'Primary' 220 | MDAccordionItem: 221 | title:'Item 3' 222 | MDLabel: 223 | text:'Content 3' 224 | theme_text_color:'Primary' 225 | """) 226 | 227 | AccordionApp().run() 228 | -------------------------------------------------------------------------------- /kivymd/backgroundcolorbehavior.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from kivy.lang import Builder 3 | from kivy.properties import (BoundedNumericProperty, ListProperty, 4 | OptionProperty, ReferenceListProperty) 5 | from kivy.uix.widget import Widget 6 | from kivy.utils import get_color_from_hex 7 | 8 | from kivymd.color_definitions import text_colors 9 | from kivymd.theming import ThemableBehavior 10 | 11 | 12 | Builder.load_string(''' 13 | 14 | canvas: 15 | Color: 16 | rgba: self.md_bg_color 17 | Rectangle: 18 | size: self.size 19 | pos: self.pos 20 | ''') 21 | 22 | 23 | class BackgroundColorBehavior(Widget): 24 | r = BoundedNumericProperty(1., min=0., max=1.) 25 | g = BoundedNumericProperty(1., min=0., max=1.) 26 | b = BoundedNumericProperty(1., min=0., max=1.) 27 | a = BoundedNumericProperty(0., min=0., max=1.) 28 | 29 | md_bg_color = ReferenceListProperty(r, g, b, a) 30 | 31 | 32 | class SpecificBackgroundColorBehavior(BackgroundColorBehavior): 33 | background_palette = OptionProperty( 34 | 'Primary', 35 | options=['Primary', 'Accent', 36 | 'Red', 'Pink', 'Purple', 'DeepPurple', 'Indigo', 'Blue', 37 | 'LightBlue', 'Cyan', 'Teal', 'Green', 'LightGreen', 38 | 'Lime', 'Yellow', 'Amber', 'Orange', 'DeepOrange', 39 | 'Brown', 'Grey', 'BlueGrey']) 40 | 41 | background_hue = OptionProperty( 42 | '500', 43 | options=['50', '100', '200', '300', '400', '500', '600', '700', 44 | '800', '900', 'A100', 'A200', 'A400', 'A700']) 45 | 46 | specific_text_color = ListProperty([0, 0, 0, 0.87]) 47 | specific_secondary_text_color = ListProperty([0, 0, 0, 0.87]) 48 | 49 | def _update_specific_text_color(self, instance, value): 50 | if hasattr(self, 'theme_cls'): 51 | palette = {'Primary': self.theme_cls.primary_palette, 52 | 'Accent': self.theme_cls.accent_palette 53 | }.get(self.background_palette, self.background_palette) 54 | else: 55 | palette = {'Primary': 'Blue', 56 | 'Accent': 'Amber' 57 | }.get(self.background_palette, self.background_palette) 58 | if text_colors[palette].get(self.background_hue): 59 | color = get_color_from_hex(text_colors[palette] 60 | [self.background_hue]) 61 | else: 62 | # Some palettes do not have 'A100', 'A200', 'A400', 'A700' 63 | # In that situation just default to using 100/200/400/700 64 | hue = self.background_hue[1:] 65 | color = get_color_from_hex(text_colors[palette][hue]) 66 | secondary_color = color[:] 67 | # Check for black text (need to adjust opacity) 68 | if (color[0] + color[1] + color[2]) == 0: 69 | color[3] = 0.87 70 | secondary_color[3] = 0.54 71 | else: 72 | secondary_color[3] = 0.7 73 | self.specific_text_color = color 74 | self.specific_secondary_text_color = secondary_color 75 | 76 | def __init__(self, **kwargs): 77 | super(SpecificBackgroundColorBehavior, self).__init__(**kwargs) 78 | if hasattr(self, 'theme_cls'): 79 | self.theme_cls.bind(primary_palette=self._update_specific_text_color) 80 | self.theme_cls.bind(accent_palette=self._update_specific_text_color) 81 | self.theme_cls.bind(theme_style=self._update_specific_text_color) 82 | self.bind(background_hue=self._update_specific_text_color) 83 | self.bind(background_palette=self._update_specific_text_color) 84 | self._update_specific_text_color(None, None) 85 | -------------------------------------------------------------------------------- /kivymd/bottomsheet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Bottom Sheets 4 | ============= 5 | 6 | `Material Design spec Bottom Sheets page `_ 7 | 8 | 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. 9 | 10 | Examples 11 | -------- 12 | 13 | .. note:: 14 | 15 | These widgets are designed to be called from Python code only. 16 | 17 | For :class:`MDListBottomSheet`: 18 | 19 | .. code-block:: python 20 | 21 | bs = MDListBottomSheet() 22 | bs.add_item("Here's an item with text only", lambda x: x) 23 | bs.add_item("Here's an item with an icon", lambda x: x, icon='md-cast') 24 | bs.add_item("Here's another!", lambda x: x, icon='md-nfc') 25 | bs.open() 26 | 27 | For :class:`MDListBottomSheet`: 28 | 29 | .. code-block:: python 30 | 31 | bs = MDGridBottomSheet() 32 | bs.add_item("Facebook", lambda x: x, icon_src='./assets/facebook-box.png') 33 | bs.add_item("YouTube", lambda x: x, icon_src='./assets/youtube-play.png') 34 | bs.add_item("Twitter", lambda x: x, icon_src='./assets/twitter.png') 35 | bs.add_item("Da Cloud", lambda x: x, icon_src='./assets/cloud-upload.png') 36 | bs.add_item("Camera", lambda x: x, icon_src='./assets/camera.png') 37 | bs.open() 38 | 39 | API 40 | --- 41 | ''' 42 | from kivy.clock import Clock 43 | from kivy.lang import Builder 44 | from kivy.metrics import dp 45 | from kivy.properties import ObjectProperty, StringProperty 46 | from kivy.uix.behaviors import ButtonBehavior 47 | from kivy.uix.boxlayout import BoxLayout 48 | from kivy.uix.floatlayout import FloatLayout 49 | from kivy.uix.gridlayout import GridLayout 50 | from kivy.uix.modalview import ModalView 51 | from kivy.uix.scrollview import ScrollView 52 | 53 | from kivymd import images_path 54 | from kivymd.backgroundcolorbehavior import BackgroundColorBehavior 55 | from kivymd.label import MDLabel 56 | from kivymd.list import ILeftBody, MDList, OneLineIconListItem, OneLineListItem 57 | from kivymd.theming import ThemableBehavior 58 | 59 | 60 | Builder.load_string(''' 61 | 62 | md_bg_color: 0,0,0,.8 63 | sv: sv 64 | upper_padding: upper_padding 65 | gl_content: gl_content 66 | ScrollView: 67 | id: sv 68 | do_scroll_x: False 69 | BoxLayout: 70 | size_hint_y: None 71 | orientation: 'vertical' 72 | padding: 0,1,0,0 73 | height: upper_padding.height + gl_content.height + 1 # +1 to allow overscroll 74 | BsPadding: 75 | id: upper_padding 76 | size_hint_y: None 77 | height: root.height - min(root.width * 9 / 16, gl_content.height) 78 | on_release: root.dismiss() 79 | BottomSheetContent: 80 | id: gl_content 81 | size_hint_y: None 82 | md_bg_color: root.theme_cls.bg_normal 83 | cols: 1 84 | ''') 85 | 86 | 87 | class BsPadding(ButtonBehavior, FloatLayout): 88 | pass 89 | 90 | 91 | class BottomSheetContent(BackgroundColorBehavior, GridLayout): 92 | pass 93 | 94 | 95 | class MDBottomSheet(ThemableBehavior, ModalView): 96 | background = "{}transparent.png".format(images_path) 97 | sv = ObjectProperty() 98 | upper_padding = ObjectProperty() 99 | gl_content = ObjectProperty() 100 | dismiss_zone_scroll = 1000 # Arbitrary high number 101 | 102 | def open(self, *largs): 103 | super(MDBottomSheet, self).open(*largs) 104 | Clock.schedule_once(self.set_dismiss_zone, 0) 105 | 106 | def set_dismiss_zone(self, *largs): 107 | # Scroll to right below overscroll threshold: 108 | self.sv.scroll_y = 1 - self.sv.convert_distance_to_scroll(0, 1)[1] 109 | 110 | # This is a line where m (slope) is 1/6 and b (y-intercept) is 80: 111 | self.dismiss_zone_scroll = self.sv.convert_distance_to_scroll( 112 | 0, (self.height - self.upper_padding.height) * (1 / 6.0) + 80)[ 113 | 1] 114 | # Uncomment next line if the limit should just be half of 115 | # visible content on open (capped by specs to 16 units to width/9: 116 | # self.dismiss_zone_scroll = (self.sv.convert_distance_to_scroll( 117 | # 0, self.height - self.upper_padding.height)[1] * 0.50) 118 | 119 | # Check if user has overscrolled enough to dismiss bottom sheet: 120 | self.sv.bind(on_scroll_stop=self.check_if_scrolled_to_death) 121 | 122 | def check_if_scrolled_to_death(self, *largs): 123 | if self.sv.scroll_y >= 1 + self.dismiss_zone_scroll: 124 | self.dismiss() 125 | 126 | def add_widget(self, widget, index=0): 127 | if type(widget) == ScrollView: 128 | super(MDBottomSheet, self).add_widget(widget, index) 129 | else: 130 | self.gl_content.add_widget(widget, index) 131 | 132 | 133 | Builder.load_string(''' 134 | #:import md_icons kivymd.icon_definitions.md_icons 135 | 136 | font_style: 'Icon' 137 | text: u"{}".format(md_icons[root.icon]) 138 | halign: 'center' 139 | theme_text_color: 'Primary' 140 | valign: 'middle' 141 | ''') 142 | 143 | 144 | class ListBSIconLeft(ILeftBody, MDLabel): 145 | icon = StringProperty() 146 | 147 | 148 | class MDListBottomSheet(MDBottomSheet): 149 | mlist = ObjectProperty() 150 | 151 | def __init__(self, **kwargs): 152 | super(MDListBottomSheet, self).__init__(**kwargs) 153 | self.mlist = MDList() 154 | self.gl_content.add_widget(self.mlist) 155 | Clock.schedule_once(self.resize_content_layout, 0) 156 | 157 | def resize_content_layout(self, *largs): 158 | self.gl_content.height = self.mlist.height 159 | 160 | def add_item(self, text, callback, icon=None): 161 | if icon: 162 | item = OneLineIconListItem(text=text, on_release=callback) 163 | item.add_widget(ListBSIconLeft(icon=icon)) 164 | else: 165 | item = OneLineListItem(text=text, on_release=callback) 166 | 167 | item.bind(on_release=lambda x: self.dismiss()) 168 | self.mlist.add_widget(item) 169 | 170 | 171 | Builder.load_string(''' 172 | 173 | orientation: 'vertical' 174 | padding: 0, dp(24), 0, 0 175 | size_hint_y: None 176 | size: dp(64), dp(96) 177 | BoxLayout: 178 | padding: dp(8), 0, dp(8), dp(8) 179 | size_hint_y: None 180 | height: dp(48) 181 | Image: 182 | source: root.source 183 | MDLabel: 184 | font_style: 'Caption' 185 | theme_text_color: 'Secondary' 186 | text: root.caption 187 | halign: 'center' 188 | ''') 189 | 190 | 191 | class GridBSItem(ButtonBehavior, BoxLayout): 192 | source = StringProperty() 193 | 194 | caption = StringProperty() 195 | 196 | 197 | class MDGridBottomSheet(MDBottomSheet): 198 | def __init__(self, **kwargs): 199 | super(MDGridBottomSheet, self).__init__(**kwargs) 200 | self.gl_content.padding = (dp(16), 0, dp(16), dp(24)) 201 | self.gl_content.height = dp(24) 202 | self.gl_content.cols = 3 203 | 204 | def add_item(self, text, callback, icon_src): 205 | item = GridBSItem( 206 | caption=text, 207 | on_release=callback, 208 | source=icon_src 209 | ) 210 | item.bind(on_release=lambda x: self.dismiss()) 211 | if len(self.gl_content.children) % 3 == 0: 212 | self.gl_content.height += dp(96) 213 | self.gl_content.add_widget(item) 214 | -------------------------------------------------------------------------------- /kivymd/card.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from kivy.lang import Builder 3 | from kivy.metrics import dp 4 | from kivy.properties import (BooleanProperty, BoundedNumericProperty, 5 | ListProperty, ReferenceListProperty) 6 | from kivy.uix.boxlayout import BoxLayout 7 | from kivy.uix.widget import Widget 8 | 9 | from kivymd.elevationbehavior import RectangularElevationBehavior 10 | from kivymd.theming import ThemableBehavior 11 | 12 | 13 | Builder.load_string(''' 14 | 15 | canvas: 16 | Color: 17 | rgba: self.md_bg_color 18 | RoundedRectangle: 19 | size: self.size 20 | pos: self.pos 21 | radius: [self.border_radius] 22 | Color: 23 | rgba: self.theme_cls.divider_color 24 | a: self.border_color_a 25 | Line: 26 | rounded_rectangle: (self.pos[0],self.pos[1],self.size[0],self.size[1],self.border_radius) 27 | md_bg_color: self.theme_cls.bg_light 28 | 29 | 30 | canvas: 31 | Color: 32 | rgba: self.theme_cls.divider_color 33 | Rectangle: 34 | size: self.size 35 | pos: self.pos 36 | ''') 37 | 38 | 39 | class MDSeparator(ThemableBehavior, BoxLayout): 40 | """ A separator line """ 41 | def __init__(self, *args, **kwargs): 42 | super(MDSeparator, self).__init__(*args, **kwargs) 43 | self.on_orientation() 44 | 45 | def on_orientation(self, *args): 46 | self.size_hint = (1, None) if self.orientation == 'horizontal' else (None, 1) 47 | if self.orientation == 'horizontal': 48 | self.height = dp(1) 49 | else: 50 | self.width = dp(1) 51 | 52 | 53 | class MDCard(ThemableBehavior, RectangularElevationBehavior, BoxLayout): 54 | r = BoundedNumericProperty(1., min=0., max=1.) 55 | g = BoundedNumericProperty(1., min=0., max=1.) 56 | b = BoundedNumericProperty(1., min=0., max=1.) 57 | a = BoundedNumericProperty(0., min=0., max=1.) 58 | 59 | border_radius = BoundedNumericProperty(dp(3), min=0) 60 | border_color_a = BoundedNumericProperty(0, min=0., max=1.) 61 | md_bg_color = ReferenceListProperty(r, g, b, a) 62 | -------------------------------------------------------------------------------- /kivymd/dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy import Logger 4 | from kivy.animation import Animation 5 | from kivy.lang import Builder 6 | from kivy.metrics import dp 7 | from kivy.properties import ListProperty, ObjectProperty, StringProperty 8 | from kivy.uix.modalview import ModalView 9 | from kivy.uix.popup import PopupException 10 | 11 | from kivymd.button import MDFlatButton 12 | from kivymd.elevationbehavior import RectangularElevationBehavior 13 | from kivymd.theming import ThemableBehavior 14 | 15 | 16 | Builder.load_string(''' 17 | : 18 | canvas: 19 | Color: 20 | rgba: self.theme_cls.bg_light 21 | Rectangle: 22 | size: self.size 23 | pos: self.pos 24 | 25 | _container: container 26 | _action_area:action_area 27 | elevation: 12 28 | GridLayout: 29 | cols: 1 30 | GridLayout: 31 | cols: 1 32 | padding: dp(24), dp(24), dp(24), dp(24) 33 | spacing: dp(20) 34 | MDLabel: 35 | text: root.title 36 | font_style: 'Title' 37 | theme_text_color: 'Primary' 38 | halign: 'left' 39 | valign: 'middle' 40 | size_hint_y: None 41 | text_size: self.width, None 42 | height: self.texture_size[1] 43 | BoxLayout: 44 | size_hint_y: None 45 | height: self.minimum_height 46 | id: container 47 | AnchorLayout: 48 | anchor_x: 'right' 49 | anchor_y: 'center' 50 | size_hint: 1, None 51 | height: dp(52) if len(root._action_buttons) > 0 else 0 52 | padding: dp(8), dp(8) 53 | GridLayout: 54 | id: action_area 55 | rows: 1 56 | size_hint: None, None if len(root._action_buttons) > 0 else 1 57 | height: dp(36) if len(root._action_buttons) > 0 else 0 58 | width: self.minimum_width 59 | spacing: dp(8) 60 | ''') 61 | 62 | 63 | class MDDialog(ThemableBehavior, RectangularElevationBehavior, ModalView): 64 | title = StringProperty('') 65 | 66 | content = ObjectProperty(None) 67 | 68 | md_bg_color = ListProperty([0, 0, 0, .2]) 69 | 70 | _container = ObjectProperty() 71 | _action_buttons = ListProperty([]) 72 | _action_area = ObjectProperty() 73 | 74 | def __init__(self, **kwargs): 75 | super(MDDialog, self).__init__(**kwargs) 76 | self.bind(_action_buttons=self._update_action_buttons, 77 | auto_dismiss=lambda *x: setattr(self.shadow, 'on_release', 78 | self.shadow.dismiss if self.auto_dismiss else None)) 79 | 80 | def add_action_button(self, text, action=None): 81 | """Add an :class:`FlatButton` to the right of the action area. 82 | 83 | :param icon: Unicode character for the icon 84 | :type icon: str or None 85 | :param action: Function set to trigger when on_release fires 86 | :type action: function or None 87 | """ 88 | button = MDFlatButton(text=text, 89 | size_hint=(None, None), 90 | height=dp(36)) 91 | if action: 92 | button.bind(on_release=action) 93 | button.text_color = self.theme_cls.primary_color 94 | button.md_bg_color = self.theme_cls.bg_light 95 | self._action_buttons.append(button) 96 | 97 | def add_widget(self, widget): 98 | if self._container: 99 | if self.content: 100 | raise PopupException( 101 | 'Popup can have only one widget as content') 102 | self.content = widget 103 | else: 104 | super(MDDialog, self).add_widget(widget) 105 | 106 | def open(self, *largs): 107 | '''Show the view window from the :attr:`attach_to` widget. If set, it 108 | will attach to the nearest window. If the widget is not attached to any 109 | window, the view will attach to the global 110 | :class:`~kivy.core.window.Window`. 111 | ''' 112 | if self._window is not None: 113 | Logger.warning('ModalView: you can only open once.') 114 | return self 115 | # search window 116 | self._window = self._search_window() 117 | if not self._window: 118 | Logger.warning('ModalView: cannot open view, no window found.') 119 | return self 120 | self._window.add_widget(self) 121 | self._window.bind(on_resize=self._align_center, 122 | on_keyboard=self._handle_keyboard) 123 | self.center = self._window.center 124 | self.bind(size=self._align_center) 125 | a = Animation(_anim_alpha=1., d=self._anim_duration) 126 | a.bind(on_complete=lambda *x: self.dispatch('on_open')) 127 | a.start(self) 128 | return self 129 | 130 | def dismiss(self, *largs, **kwargs): 131 | '''Close the view if it is open. If you really want to close the 132 | view, whatever the on_dismiss event returns, you can use the *force* 133 | argument: 134 | :: 135 | 136 | view = ModalView(...) 137 | view.dismiss(force=True) 138 | 139 | When the view is dismissed, it will be faded out before being 140 | removed from the parent. If you don't want animation, use:: 141 | 142 | view.dismiss(animation=False) 143 | 144 | ''' 145 | if self._window is None: 146 | return self 147 | if self.dispatch('on_dismiss') is True: 148 | if kwargs.get('force', False) is not True: 149 | return self 150 | if kwargs.get('animation', True): 151 | Animation(_anim_alpha=0., d=self._anim_duration).start(self) 152 | else: 153 | self._anim_alpha = 0 154 | self._real_remove_widget() 155 | return self 156 | 157 | def on_content(self, instance, value): 158 | if self._container: 159 | self._container.clear_widgets() 160 | self._container.add_widget(value) 161 | 162 | def on__container(self, instance, value): 163 | if value is None or self.content is None: 164 | return 165 | self._container.clear_widgets() 166 | self._container.add_widget(self.content) 167 | 168 | def on_touch_down(self, touch): 169 | if self.disabled and self.collide_point(*touch.pos): 170 | return True 171 | return super(MDDialog, self).on_touch_down(touch) 172 | 173 | def _update_action_buttons(self, *args): 174 | self._action_area.clear_widgets() 175 | for btn in self._action_buttons: 176 | btn.content.texture_update() 177 | btn.width = btn.content.texture_size[0] + dp(16) 178 | self._action_area.add_widget(btn) 179 | -------------------------------------------------------------------------------- /kivymd/elevationbehavior.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.app import App 4 | from kivy.lang import Builder 5 | from kivy.metrics import dp 6 | from kivy.properties import (AliasProperty, ListProperty, NumericProperty, 7 | ObjectProperty) 8 | 9 | 10 | Builder.load_string(''' 11 | 12 | canvas.before: 13 | Color: 14 | a: self._soft_shadow_a 15 | Rectangle: 16 | texture: self._soft_shadow_texture 17 | size: self._soft_shadow_size 18 | pos: self._soft_shadow_pos 19 | Color: 20 | a: self._hard_shadow_a 21 | Rectangle: 22 | texture: self._hard_shadow_texture 23 | size: self._hard_shadow_size 24 | pos: self._hard_shadow_pos 25 | Color: 26 | a: 1 27 | 28 | 29 | canvas.before: 30 | Color: 31 | a: self._soft_shadow_a 32 | Rectangle: 33 | texture: self._soft_shadow_texture 34 | size: self._soft_shadow_size 35 | pos: self._soft_shadow_pos 36 | Color: 37 | a: self._hard_shadow_a 38 | Rectangle: 39 | texture: self._hard_shadow_texture 40 | size: self._hard_shadow_size 41 | pos: self._hard_shadow_pos 42 | Color: 43 | a: 1 44 | ''') 45 | 46 | 47 | class CommonElevationBehavior(object): 48 | _elevation = NumericProperty(1) 49 | 50 | def _get_elevation(self): 51 | return self._elevation 52 | 53 | def _set_elevation(self, elevation): 54 | try: 55 | self._elevation = elevation 56 | except: 57 | self._elevation = 1 58 | 59 | elevation = AliasProperty(_get_elevation, _set_elevation, 60 | bind=('_elevation',)) 61 | 62 | _soft_shadow_texture = ObjectProperty() 63 | _soft_shadow_size = ListProperty([0, 0]) 64 | _soft_shadow_pos = ListProperty([0, 0]) 65 | _soft_shadow_a = NumericProperty(0) 66 | _hard_shadow_texture = ObjectProperty() 67 | _hard_shadow_size = ListProperty([0, 0]) 68 | _hard_shadow_pos = ListProperty([0, 0]) 69 | _hard_shadow_a = NumericProperty(0) 70 | 71 | def __init__(self, **kwargs): 72 | super(CommonElevationBehavior, self).__init__(**kwargs) 73 | self.bind(elevation=self._update_shadow, 74 | pos=self._update_shadow, 75 | size=self._update_shadow) 76 | 77 | def _update_shadow(self, *args): 78 | raise NotImplementedError 79 | 80 | class RectangularElevationBehavior(CommonElevationBehavior): 81 | def _update_shadow(self, *args): 82 | if self.elevation > 0: 83 | ratio = self.width / (self.height if self.height != 0 else 1) 84 | if ratio > -2 and ratio < 2: 85 | self._shadow = App.get_running_app().theme_cls.quad_shadow 86 | width = soft_width = self.width * 1.9 87 | height = soft_height = self.height * 1.9 88 | elif ratio <= -2: 89 | self._shadow = App.get_running_app().theme_cls.rec_st_shadow 90 | ratio = abs(ratio) 91 | if ratio > 5: 92 | ratio = ratio * 22 93 | else: 94 | ratio = ratio * 11.5 95 | 96 | width = soft_width = self.width * 1.9 97 | height = self.height + dp(ratio) 98 | soft_height = self.height + dp(ratio) + dp(self.elevation) * .5 99 | else: 100 | self._shadow = App.get_running_app().theme_cls.quad_shadow 101 | width = soft_width = self.width * 1.8 102 | height = soft_height = self.height * 1.8 103 | # self._shadow = App.get_running_app().theme_cls.rec_shadow 104 | # ratio = abs(ratio) 105 | # if ratio > 5: 106 | # ratio = ratio * 22 107 | # else: 108 | # ratio = ratio * 11.5 109 | # 110 | # width = self.width + dp(ratio) 111 | # soft_width = self.width + dp(ratio) + dp(self.elevation) * .9 112 | # height = soft_height = self.height * 1.9 113 | 114 | x = self.center_x - width / 2 115 | soft_x = self.center_x - soft_width / 2 116 | self._soft_shadow_size = (soft_width, soft_height) 117 | self._hard_shadow_size = (width, height) 118 | 119 | y = self.center_y - soft_height / 2 - dp( 120 | .1 * 1.5 ** self.elevation) 121 | self._soft_shadow_pos = (soft_x, y) 122 | self._soft_shadow_a = 0.1 * 1.1 ** self.elevation 123 | self._soft_shadow_texture = self._shadow.textures[ 124 | str(int(round(self.elevation - 1)))] 125 | 126 | y = self.center_y - height / 2 - dp(.5 * 1.18 ** self.elevation) 127 | self._hard_shadow_pos = (x, y) 128 | self._hard_shadow_a = .4 * .9 ** self.elevation 129 | self._hard_shadow_texture = self._shadow.textures[ 130 | str(int(round(self.elevation)))] 131 | 132 | else: 133 | self._soft_shadow_a = 0 134 | self._hard_shadow_a = 0 135 | 136 | 137 | class CircularElevationBehavior(CommonElevationBehavior): 138 | def __init__(self, **kwargs): 139 | super(CircularElevationBehavior, self).__init__(**kwargs) 140 | self._shadow = App.get_running_app().theme_cls.round_shadow 141 | 142 | def _update_shadow(self, *args): 143 | if self.elevation > 0: 144 | width = self.width * 2 145 | height = self.height * 2 146 | 147 | x = self.center_x - width / 2 148 | self._soft_shadow_size = (width, height) 149 | 150 | self._hard_shadow_size = (width, height) 151 | 152 | y = self.center_y - height / 2 - dp(.1 * 1.5 ** self.elevation) 153 | self._soft_shadow_pos = (x, y) 154 | self._soft_shadow_a = 0.1 * 1.1 ** self.elevation 155 | self._soft_shadow_texture = self._shadow.textures[ 156 | str(int(round(self.elevation)))] 157 | 158 | y = self.center_y - height / 2 - dp(.5 * 1.18 ** self.elevation) 159 | self._hard_shadow_pos = (x, y) 160 | self._hard_shadow_a = .4 * .9 ** self.elevation 161 | self._hard_shadow_texture = self._shadow.textures[ 162 | str(int(round(self.elevation - 1)))] 163 | 164 | else: 165 | self._soft_shadow_a = 0 166 | self._hard_shadow_a = 0 167 | -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /kivymd/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /kivymd/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /kivymd/grid.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from kivy.lang import Builder 3 | from kivy.properties import (BooleanProperty, ListProperty, NumericProperty, 4 | ObjectProperty, OptionProperty, StringProperty) 5 | from kivy.uix.behaviors import ButtonBehavior 6 | from kivy.uix.boxlayout import BoxLayout 7 | from kivy.uix.floatlayout import FloatLayout 8 | 9 | from kivymd.ripplebehavior import RectangularRippleBehavior 10 | from kivymd.theming import ThemableBehavior 11 | 12 | 13 | Builder.load_string(""" 14 | 15 | _img_widget: img 16 | _img_overlay: img_overlay 17 | _box_overlay: box 18 | AsyncImage: 19 | id: img 20 | allow_stretch: root.allow_stretch 21 | anim_delay: root.anim_delay 22 | anim_loop: root.anim_loop 23 | color: root.img_color 24 | keep_ratio: root.keep_ratio 25 | mipmap: root.mipmap 26 | source: root.source 27 | size_hint_y: 1 if root.overlap else None 28 | x: root.x 29 | y: root.y if root.overlap or root.box_position == 'header' else box.top 30 | BoxLayout: 31 | id: img_overlay 32 | size_hint: img.size_hint 33 | size: img.size 34 | pos: img.pos 35 | BoxLayout: 36 | canvas: 37 | Color: 38 | rgba: root.box_color 39 | Rectangle: 40 | pos: self.pos 41 | size: self.size 42 | id: box 43 | size_hint_y: None 44 | height: dp(68) if root.lines == 2 else dp(48) 45 | x: root.x 46 | y: root.y if root.box_position == 'footer' else root.y + root.height - self.height 47 | 48 | 49 | _img_widget: img 50 | _img_overlay: img_overlay 51 | _box_overlay: box 52 | _box_label: boxlabel 53 | AsyncImage: 54 | id: img 55 | allow_stretch: root.allow_stretch 56 | anim_delay: root.anim_delay 57 | anim_loop: root.anim_loop 58 | color: root.img_color 59 | keep_ratio: root.keep_ratio 60 | mipmap: root.mipmap 61 | source: root.source 62 | size_hint_y: 1 if root.overlap else None 63 | x: root.x 64 | y: root.y if root.overlap or root.box_position == 'header' else box.top 65 | BoxLayout: 66 | id: img_overlay 67 | size_hint: img.size_hint 68 | size: img.size 69 | pos: img.pos 70 | BoxLayout: 71 | canvas: 72 | Color: 73 | rgba: root.box_color 74 | Rectangle: 75 | pos: self.pos 76 | size: self.size 77 | id: box 78 | size_hint_y: None 79 | height: dp(68) if root.lines == 2 else dp(48) 80 | x: root.x 81 | y: root.y if root.box_position == 'footer' else root.y + root.height - self.height 82 | MDLabel: 83 | id: boxlabel 84 | font_style: "Caption" 85 | halign: "center" 86 | text: root.text 87 | """) 88 | 89 | 90 | class Tile(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, 91 | BoxLayout): 92 | """A simple tile. It does nothing special, just inherits the right behaviors 93 | to work as a building block. 94 | """ 95 | pass 96 | 97 | 98 | class SmartTile(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, 99 | FloatLayout): 100 | """A tile for more complex needs. 101 | 102 | Includes an image, a container to place overlays and a box that can act 103 | as a header or a footer, as described in the Material Design specs. 104 | """ 105 | 106 | box_color = ListProperty([0, 0, 0, 0.5]) 107 | """Sets the color and opacity for the information box.""" 108 | 109 | box_position = OptionProperty('footer', options=['footer', 'header']) 110 | """Determines wether the information box acts as a header or footer to the 111 | image. 112 | """ 113 | 114 | lines = OptionProperty(1, options=[1, 2]) 115 | """Number of lines in the header/footer. 116 | 117 | As per Material Design specs, only 1 and 2 are valid values. 118 | """ 119 | 120 | overlap = BooleanProperty(True) 121 | """Determines if the header/footer overlaps on top of the image or not""" 122 | 123 | # Img properties 124 | allow_stretch = BooleanProperty(True) 125 | anim_delay = NumericProperty(0.25) 126 | anim_loop = NumericProperty(0) 127 | img_color = ListProperty([1, 1, 1, 1]) 128 | keep_ratio = BooleanProperty(False) 129 | mipmap = BooleanProperty(False) 130 | source = StringProperty() 131 | 132 | _img_widget = ObjectProperty() 133 | _img_overlay = ObjectProperty() 134 | _box_overlay = ObjectProperty() 135 | _box_label = ObjectProperty() 136 | 137 | def reload(self): 138 | self._img_widget.reload() 139 | 140 | def add_widget(self, widget, index=0): 141 | if issubclass(widget.__class__, IOverlay): 142 | self._img_overlay.add_widget(widget, index) 143 | elif issubclass(widget.__class__, IBoxOverlay): 144 | self._box_overlay.add_widget(widget, index) 145 | else: 146 | super(SmartTile, self).add_widget(widget, index) 147 | 148 | 149 | class SmartTileWithLabel(SmartTile): 150 | _box_label = ObjectProperty() 151 | 152 | # MDLabel properties 153 | font_style = StringProperty("Caption") 154 | theme_text_color = StringProperty("") 155 | text = StringProperty("") 156 | """Determines the text for the box footer/header""" 157 | 158 | 159 | class IBoxOverlay(): 160 | """An interface to specify widgets that belong to to the image overlay 161 | in the :class:`SmartTile` widget when added as a child. 162 | """ 163 | pass 164 | 165 | 166 | class IOverlay(): 167 | """An interface to specify widgets that belong to to the image overlay 168 | in the :class:`SmartTile` widget when added as a child. 169 | """ 170 | pass 171 | -------------------------------------------------------------------------------- /kivymd/images/kivymd_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/kivymd_512.png -------------------------------------------------------------------------------- /kivymd/images/kivymd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/kivymd_logo.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/quad_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/quad_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/quad_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/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/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/rec_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/rec_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/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/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/rec_st_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/rec_st_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/rec_st_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/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/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/round_shadow-0.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/round_shadow-1.png -------------------------------------------------------------------------------- /kivymd/images/round_shadow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/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/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreMiras/KivyMD/dfd69f6da73718e2a596d29c33c9a257e58343c8/kivymd/images/transparent.png -------------------------------------------------------------------------------- /kivymd/label.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from kivy.lang import Builder 3 | from kivy.metrics import sp 4 | from kivy.properties import DictProperty, ListProperty, OptionProperty 5 | from kivy.uix.label import Label 6 | 7 | from kivymd.material_resources import DEVICE_TYPE 8 | from kivymd.theming import ThemableBehavior 9 | from kivymd.theming_dynamic_text import get_contrast_text_color 10 | 11 | 12 | Builder.load_string(''' 13 | 14 | disabled_color: self.theme_cls.disabled_hint_text_color 15 | text_size: (self.width, None) 16 | ''') 17 | 18 | 19 | class MDLabel(ThemableBehavior, Label): 20 | font_style = OptionProperty( 21 | 'Body1', options=['Body1', 'Body2', 'Caption', 'Subhead', 'Title', 22 | 'Headline', 'Display1', 'Display2', 'Display3', 23 | 'Display4', 'Button', 'Icon']) 24 | 25 | # Font, Bold, Mobile size, Desktop size (None if same as Mobile) 26 | _font_styles = DictProperty({'Body1': ['Roboto', False, 14, 13], 27 | 'Body2': ['Roboto', True, 14, 13], 28 | 'Caption': ['Roboto', False, 12, None], 29 | 'Subhead': ['Roboto', False, 16, 15], 30 | 'Title': ['Roboto', True, 20, None], 31 | 'Headline': ['Roboto', False, 24, None], 32 | 'Display1': ['Roboto', False, 34, None], 33 | 'Display2': ['Roboto', False, 45, None], 34 | 'Display3': ['Roboto', False, 56, None], 35 | 'Display4': ['RobotoLight', False, 112, None], 36 | 'Button': ['Roboto', True, 14, None], 37 | 'Icon': ['Icons', False, 24, None]}) 38 | 39 | theme_text_color = OptionProperty(None, allownone=True, 40 | options=['Primary', 'Secondary', 'Hint', 'Error', 'Custom', 41 | 'ContrastParentBackground'] 42 | ) 43 | 44 | text_color = ListProperty(None, allownone=True) 45 | 46 | parent_background = ListProperty(None, allownone=True) 47 | 48 | _currently_bound_property = {} 49 | 50 | def __init__(self, **kwargs): 51 | super(MDLabel, self).__init__(**kwargs) 52 | self.on_theme_text_color(None, self.theme_text_color) 53 | self.on_font_style(None, self.font_style) 54 | self.on_opposite_colors(None, self.opposite_colors) 55 | 56 | def on_font_style(self, instance, style): 57 | info = self._font_styles[style] 58 | self.font_name = info[0] 59 | self.bold = info[1] 60 | if DEVICE_TYPE == 'desktop' and info[3] is not None: 61 | self.font_size = sp(info[3]) 62 | else: 63 | self.font_size = sp(info[2]) 64 | 65 | def on_theme_text_color(self, instance, value): 66 | t = self.theme_cls 67 | op = self.opposite_colors 68 | setter = self.setter('color') 69 | t.unbind(**self._currently_bound_property) 70 | attr_name = {'Primary': 'text_color' if not op else 71 | 'opposite_text_color', 72 | 'Secondary': 'secondary_text_color' if not op else 73 | 'opposite_secondary_text_color', 74 | 'Hint': 'disabled_hint_text_color' if not op else 75 | 'opposite_disabled_hint_text_color', 76 | 'Error': 'error_color', 77 | }.get(value, None) 78 | if attr_name: 79 | c = {attr_name: setter} 80 | t.bind(**c) 81 | self._currently_bound_property = c 82 | self.color = getattr(t, attr_name) 83 | else: 84 | # 'Custom' and 'ContrastParentBackground' lead here, as well as the 85 | # generic None value it's not yet been set 86 | if value == 'Custom' and self.text_color: 87 | self.color = self.text_color 88 | elif value == 'ContrastParentBackground' and self.parent_background: 89 | self.color = get_contrast_text_color(self.parent_background) 90 | else: 91 | self.color = [0, 0, 0, 1] 92 | 93 | def on_text_color(self, *args): 94 | if self.theme_text_color == 'Custom': 95 | self.color = self.text_color 96 | 97 | def on_opposite_colors(self, instance, value): 98 | self.on_theme_text_color(self, self.theme_text_color) 99 | -------------------------------------------------------------------------------- /kivymd/material_resources.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from kivy import platform 3 | from kivy.core.window import Window 4 | from kivy.metrics import dp 5 | 6 | from kivymd import fonts_path 7 | 8 | # Feel free to override this const if you're designing for a device such as 9 | # a GNU/Linux tablet. 10 | if platform != "android" and platform != "ios": 11 | DEVICE_TYPE = "desktop" 12 | elif Window.width >= dp(600) and Window.height >= dp(600): 13 | DEVICE_TYPE = "tablet" 14 | else: 15 | DEVICE_TYPE = "mobile" 16 | 17 | if DEVICE_TYPE == "mobile": 18 | MAX_NAV_DRAWER_WIDTH = dp(300) 19 | HORIZ_MARGINS = dp(16) 20 | STANDARD_INCREMENT = dp(56) 21 | PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT 22 | LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT - dp(8) 23 | else: 24 | MAX_NAV_DRAWER_WIDTH = dp(400) 25 | HORIZ_MARGINS = dp(24) 26 | STANDARD_INCREMENT = dp(64) 27 | PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT 28 | LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT 29 | 30 | TOUCH_TARGET_HEIGHT = dp(48) 31 | 32 | FONTS = [ 33 | { 34 | "name": "Roboto", 35 | "fn_regular": fonts_path + 'Roboto-Regular.ttf', 36 | "fn_bold": fonts_path + 'Roboto-Medium.ttf', 37 | "fn_italic": fonts_path + 'Roboto-Italic.ttf', 38 | "fn_bolditalic": fonts_path + 'Roboto-MediumItalic.ttf' 39 | }, 40 | { 41 | "name": "RobotoLight", 42 | "fn_regular": fonts_path + 'Roboto-Thin.ttf', 43 | "fn_bold": fonts_path + 'Roboto-Light.ttf', 44 | "fn_italic": fonts_path + 'Roboto-ThinItalic.ttf', 45 | "fn_bolditalic": fonts_path + 'Roboto-LightItalic.ttf' 46 | }, 47 | { 48 | "name": "Icons", 49 | "fn_regular": fonts_path + 'materialdesignicons-webfont.ttf' 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /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 kivy.metrics import dp 7 | from kivy.properties import (ListProperty, NumericProperty, OptionProperty, 8 | StringProperty) 9 | from kivy.uix.behaviors import ButtonBehavior 10 | from kivy.uix.boxlayout import BoxLayout 11 | from kivy.uix.recycleboxlayout import RecycleBoxLayout 12 | from kivy.uix.recycleview import RecycleView 13 | from kivy.uix.recycleview.views import RecycleDataViewBehavior 14 | 15 | import kivymd.material_resources as m_res 16 | from kivymd.theming import ThemableBehavior 17 | 18 | 19 | Builder.load_string(''' 20 | #:import STD_INC kivymd.material_resources.STANDARD_INCREMENT 21 | 22 | size_hint_y: None 23 | height: dp(48) 24 | padding: dp(16), 0 25 | on_release: root.parent.parent.parent.parent.dismiss() # Horrible, but hey it works 26 | MDLabel: 27 | text: root.text 28 | theme_text_color: 'Primary' 29 | 30 | 31 | size_hint: None, None 32 | width: root.width_mult * STD_INC 33 | key_viewclass: 'viewclass' 34 | #key_size: 'height' 35 | RecycleBoxLayout: 36 | default_size: None, dp(48) 37 | default_size_hint: 1, None 38 | orientation: 'vertical' 39 | 40 | 41 | FloatLayout: 42 | id: fl 43 | MDMenu: 44 | id: md_menu 45 | data: root.items 46 | width_mult: root.width_mult 47 | size_hint: None, None 48 | size: 0,0 49 | canvas.before: 50 | Color: 51 | rgba: root.theme_cls.bg_light 52 | Rectangle: 53 | size: self.size 54 | pos: self.pos 55 | ''') 56 | 57 | 58 | class MDMenuItem(RecycleDataViewBehavior, ButtonBehavior, BoxLayout): 59 | text = StringProperty() 60 | 61 | 62 | class MDMenu(RecycleView): 63 | width_mult = NumericProperty(1) 64 | 65 | 66 | class MDDropdownMenu(ThemableBehavior, BoxLayout): 67 | items = ListProperty() 68 | '''See :attr:`~kivy.uix.recycleview.RecycleView.data` 69 | ''' 70 | 71 | width_mult = NumericProperty(1) 72 | '''This number multiplied by the standard increment (56dp on mobile, 73 | 64dp on desktop, determines the width of the menu items. 74 | 75 | If the resulting number were to be too big for the application Window, 76 | the multiplier will be adjusted for the biggest possible one. 77 | ''' 78 | 79 | max_height = NumericProperty() 80 | '''The menu will grow no bigger than this number. 81 | 82 | Set to 0 for no limit. Defaults to 0. 83 | ''' 84 | 85 | border_margin = NumericProperty(dp(4)) 86 | '''Margin between Window border and menu 87 | ''' 88 | 89 | ver_growth = OptionProperty(None, allownone=True, 90 | options=['up', 'down']) 91 | '''Where the menu will grow vertically to when opening 92 | 93 | Set to None to let the widget pick for you. Defaults to None. 94 | ''' 95 | 96 | hor_growth = OptionProperty(None, allownone=True, 97 | options=['left', 'right']) 98 | '''Where the menu will grow horizontally to when opening 99 | 100 | Set to None to let the widget pick for you. Defaults to None. 101 | ''' 102 | 103 | def open(self, *largs): 104 | Window.add_widget(self) 105 | Clock.schedule_once(lambda x: self.display_menu(largs[0]), -1) 106 | 107 | def display_menu(self, caller): 108 | # We need to pick a starting point, see how big we need to be, 109 | # and where to grow to. 110 | 111 | c = caller.to_window(caller.center_x, 112 | caller.center_y) # Starting coords 113 | 114 | # ---ESTABLISH INITIAL TARGET SIZE ESTIMATE--- 115 | target_width = self.width_mult * m_res.STANDARD_INCREMENT 116 | # If we're wider than the Window... 117 | if target_width > Window.width: 118 | # ...reduce our multiplier to max allowed. 119 | target_width = int( 120 | Window.width / m_res.STANDARD_INCREMENT) * m_res.STANDARD_INCREMENT 121 | 122 | target_height = sum([dp(48) for i in self.items]) 123 | # If we're over max_height... 124 | if 0 < self.max_height < target_height: 125 | target_height = self.max_height 126 | 127 | # ---ESTABLISH VERTICAL GROWTH DIRECTION--- 128 | if self.ver_growth is not None: 129 | ver_growth = self.ver_growth 130 | else: 131 | # If there's enough space below us: 132 | if target_height <= c[1] - self.border_margin: 133 | ver_growth = 'down' 134 | # if there's enough space above us: 135 | elif target_height < Window.height - c[1] - self.border_margin: 136 | ver_growth = 'up' 137 | # otherwise, let's pick the one with more space and adjust ourselves 138 | else: 139 | # if there's more space below us: 140 | if c[1] >= Window.height - c[1]: 141 | ver_growth = 'down' 142 | target_height = c[1] - self.border_margin 143 | # if there's more space above us: 144 | else: 145 | ver_growth = 'up' 146 | target_height = Window.height - c[1] - self.border_margin 147 | 148 | if self.hor_growth is not None: 149 | hor_growth = self.hor_growth 150 | else: 151 | # If there's enough space to the right: 152 | if target_width <= Window.width - c[0] - self.border_margin: 153 | hor_growth = 'right' 154 | # if there's enough space to the left: 155 | elif target_width < c[0] - self.border_margin: 156 | hor_growth = 'left' 157 | # otherwise, let's pick the one with more space and adjust ourselves 158 | else: 159 | # if there's more space to the right: 160 | if Window.width - c[0] >= c[0]: 161 | hor_growth = 'right' 162 | target_width = Window.width - c[0] - self.border_margin 163 | # if there's more space to the left: 164 | else: 165 | hor_growth = 'left' 166 | target_width = c[0] - self.border_margin 167 | 168 | if ver_growth == 'down': 169 | tar_y = c[1] - target_height 170 | else: # should always be 'up' 171 | tar_y = c[1] 172 | 173 | if hor_growth == 'right': 174 | tar_x = c[0] 175 | else: # should always be 'left' 176 | tar_x = c[0] - target_width 177 | anim = Animation(x=tar_x, y=tar_y, 178 | width=target_width, height=target_height, 179 | duration=.3, transition='out_quint') 180 | menu = self.ids['md_menu'] 181 | menu.pos = c 182 | anim.start(menu) 183 | 184 | def on_touch_down(self, touch): 185 | if not self.ids['md_menu'].collide_point(*touch.pos): 186 | self.dismiss() 187 | return True 188 | super(MDDropdownMenu, self).on_touch_down(touch) 189 | return True 190 | 191 | def on_touch_move(self, touch): 192 | super(MDDropdownMenu, self).on_touch_move(touch) 193 | return True 194 | 195 | def on_touch_up(self, touch): 196 | super(MDDropdownMenu, self).on_touch_up(touch) 197 | return True 198 | 199 | def dismiss(self): 200 | Window.remove_widget(self) 201 | -------------------------------------------------------------------------------- /kivymd/navigationdrawer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Navigation Drawer 3 | ================= 4 | 5 | `Material Design spec page `_ 6 | 7 | API 8 | --- 9 | ''' 10 | 11 | from kivy.lang import Builder 12 | from kivy.metrics import dp 13 | from kivy.properties import (BooleanProperty, Clock, ListProperty, 14 | NumericProperty, ObjectProperty, OptionProperty, 15 | StringProperty) 16 | from kivy.uix.boxlayout import BoxLayout 17 | 18 | from kivymd import images_path 19 | from kivymd.elevationbehavior import RectangularElevationBehavior 20 | from kivymd.icon_definitions import md_icons 21 | from kivymd.label import MDLabel 22 | from kivymd.list import (BaseListItem, ILeftBody, IRightBody, 23 | OneLineIconListItem, OneLineListItem) 24 | from kivymd.theming import ThemableBehavior 25 | from kivymd.toolbar import Toolbar 26 | from kivymd.vendor.navigationdrawer import \ 27 | NavigationDrawer as VendorNavigationDrawer 28 | 29 | 30 | Builder.load_string(""" 31 | #:import Toolbar kivymd.toolbar.Toolbar 32 | #:import MDList kivymd.list.MDList 33 | #:import OneLineIconListItem kivymd.list.OneLineIconListItem 34 | #:import colors kivymd.color_definitions.colors 35 | #:import get_color_from_hex kivy.utils.get_color_from_hex 36 | #:import ScrollView kivy.uix.scrollview.ScrollView 37 | #:import Window kivy.core.window.Window 38 | 39 | 40 | elevation: 0 41 | specific_text_color: root.theme_cls.secondary_text_color 42 | opposite_colors: False 43 | title_theme_color: 'Secondary' 44 | md_bg_color: root.theme_cls.bg_light 45 | canvas: 46 | Color: 47 | rgba: root.theme_cls.divider_color 48 | Line: 49 | points: self.x, self.y, self.x+self.width,self.y 50 | 51 | : 52 | _list: list 53 | _header_container: _header_container 54 | canvas: 55 | Color: 56 | rgba: root.theme_cls.bg_light 57 | Rectangle: 58 | size: root.size 59 | pos: root.pos 60 | canvas.before: 61 | Color: 62 | rgba: root.shadow_color 63 | Rectangle: 64 | size: Window.size 65 | pos: 0, 0 66 | BoxLayout: 67 | id: _header_container 68 | size_hint_y: None 69 | height: _header_container.minimum_height 70 | ScrollView: 71 | do_scroll_x: False 72 | MDList: 73 | id: list 74 | 75 | : 76 | theme_text_color: 'Primary' if not root._active else 'Custom' if root.use_active else 'Primary' 77 | text_color: root.theme_cls.secondary_text_color if not root._active else root.active_color if \ 78 | root.active_color_type == "custom" else root._active_color if root.use_active else \ 79 | root.theme_cls.secondary_text_color 80 | NDIconLabel: 81 | id: _icon 82 | font_style: 'Icon' 83 | theme_text_color: 'Secondary' if not root._active else 'Custom' if root.use_active else 'Custom' 84 | text_color: root.theme_cls.secondary_text_color if not root._active else root.active_color if \ 85 | root.active_color_type == "custom" else root._active_color if root.use_active else \ 86 | root.theme_cls.secondary_text_color 87 | BoxLayout: 88 | id: _right_container 89 | size_hint: None, None 90 | x: root.x + root.width - _badge.texture_size[0] - dp(25) # - m_res.HORIZ_MARGINS 91 | y: root.y + root.height/2 - self.height/2 92 | size: dp(70), root.height 93 | NDBadgeLabel: 94 | id: _badge 95 | theme_text_color: 'Secondary' if not root._active else 'Custom' if root.use_active else 'Custom' 96 | text_color: root.theme_cls.secondary_text_color if not root._active else root.active_color if \ 97 | root.active_color_type == "custom" else root._active_color if root.use_active else \ 98 | root.theme_cls.secondary_text_color 99 | text: root.badge_text 100 | halign: 'right' 101 | 102 | 103 | : 104 | canvas: 105 | Color: 106 | rgba: self.theme_cls.divider_color 107 | Line: 108 | points: root.x ,root.y+dp(8), root.x+self.width, root.y+dp(8) 109 | """) 110 | 111 | 112 | class NDIconLabel(ILeftBody, MDLabel): 113 | pass 114 | 115 | 116 | class NDBadgeLabel(IRightBody, MDLabel): 117 | pass 118 | 119 | 120 | class NavigationDrawerHeaderBase: 121 | ''' 122 | Tells the :class:`~MDNavigationDrawer` that this should be in the header area (above the 123 | :class:`~kivy.uix.scrollview.ScrollView`). 124 | ''' 125 | 126 | pass 127 | 128 | 129 | class NavigationDrawerToolbar(Toolbar, NavigationDrawerHeaderBase): 130 | def _update_specific_text_color(self, instance, value): 131 | pass 132 | 133 | 134 | class NavigationDrawerIconButton(OneLineIconListItem): 135 | ''' 136 | An item in the :class:`MDNavigationDrawer`. 137 | ''' 138 | _active = BooleanProperty(False) 139 | _active_color = ListProperty() 140 | _icon = ObjectProperty() 141 | divider = None 142 | 143 | active_color = ListProperty() 144 | '''Custom active color. 145 | This option only takes effect when :attr:`active_color_type` = 'custom'. 146 | 147 | :attr:`active_color` is a :class:`~kivy.properties.ListProperty` and defaults to None. 148 | ''' 149 | 150 | active_color_type = OptionProperty('primary', options=['primary', 'accent', 'custom']) 151 | '''Decides which color should be used for the active color. 152 | This option only takes effect when :attr:`use_active` = True. 153 | 154 | Options: 155 | primary: Active color will be the primary theme color. 156 | 157 | accent: Active color will be the theme's accent color. 158 | 159 | custom: Active color will be taken from the :attr:`active_color` attribute. 160 | 161 | :attr:`active_color_type` is a :class:`~kivy.properties.OptionProperty` and defaults to 'primary'. 162 | ''' 163 | 164 | icon = StringProperty('checkbox-blank-circle') 165 | '''Icon that appears to the left of the widget. 166 | 167 | :attr:`icon` is a :class:`~kivy.properties.StringProperty` and defaults to 'checkbox-blank-circle'. 168 | ''' 169 | badge_text = StringProperty('') 170 | ''' 171 | Text that appears on the right side of the item, usually for displaying a count of sorts. 172 | 173 | 174 | :attr:`badge_text` is a :class:`~kivy.properties.StringProperty` and defaults to ''. 175 | ''' 176 | use_active = BooleanProperty(True) 177 | '''If the button should change to the active color when selected. 178 | 179 | :attr:`use_active` is a :class:`~kivy.properties.BooleanProperty` and defaults to True. 180 | 181 | See also: 182 | :attr:`active_color` 183 | 184 | :attr:`active_color_type` 185 | ''' 186 | 187 | # active_color = get_color_from_hex(colors['Red']['500']) 188 | # active_color_type = 'custom' 189 | 190 | def __init__(self, **kwargs): 191 | super(NavigationDrawerIconButton, self).__init__(**kwargs) 192 | self._set_active_color() 193 | self.theme_cls.bind(primary_color=self._set_active_color_primary, accent_color=self._set_active_color_accent) 194 | Clock.schedule_once(lambda x: self.on_icon(self, self.icon)) 195 | 196 | def _set_active(self, active, list): 197 | if self.use_active: 198 | self._active = active 199 | if list.active_item != self: 200 | if list.active_item is not None: 201 | list.active_item._active = False 202 | list.active_item = self 203 | 204 | def _set_active_color(self, *args): 205 | if self.active_color_type == 'primary': 206 | self._set_active_color_primary() 207 | elif self.active_color_type == 'accent': 208 | self._set_active_color_accent() 209 | 210 | # Note to future developers/myself: These must be separate functions 211 | def _set_active_color_primary(self, *args): 212 | if self.active_color_type == 'primary': 213 | self._active_color = self.theme_cls.primary_color 214 | 215 | def _set_active_color_accent(self, *args): 216 | if self.active_color_type == 'accent': 217 | self._active_color = self.theme_cls.accent_color 218 | 219 | def on_icon(self, instance, value): 220 | self.ids['_icon'].text = u"{}".format(md_icons[value]) 221 | 222 | def on_active_color_type(self, *args): 223 | self._set_active_color(args) 224 | 225 | 226 | class NavigationDrawerSubheader(OneLineListItem): 227 | ''' 228 | A subheader for separating content in :class:`MDNavigationDrawer` 229 | 230 | Works well alongside :class:`NavigationDrawerDivider` 231 | ''' 232 | disabled = True 233 | divider = None 234 | theme_text_color = 'Secondary' 235 | 236 | 237 | class NavigationDrawerDivider(OneLineListItem): 238 | ''' 239 | A small full-width divider that can be placed in the :class:`MDNavigationDrawer` 240 | ''' 241 | disabled = True 242 | divider = None 243 | _txt_top_pad = NumericProperty(dp(8)) 244 | _txt_bot_pad = NumericProperty(dp(8)) 245 | 246 | def __init__(self, **kwargs): 247 | super(OneLineListItem, self).__init__(**kwargs) 248 | self.height = dp(16) 249 | 250 | 251 | class MDNavigationDrawer(BoxLayout, ThemableBehavior, RectangularElevationBehavior): 252 | ''' 253 | ''' 254 | _elevation = NumericProperty(0) 255 | _header_container = ObjectProperty() 256 | _list = ObjectProperty() 257 | active_item = ObjectProperty(None) 258 | orientation = 'vertical' 259 | panel = ObjectProperty() 260 | shadow_color = ListProperty([0, 0, 0, 0]) 261 | 262 | def __init__(self, **kwargs): 263 | super(MDNavigationDrawer, self).__init__(**kwargs) 264 | 265 | def add_widget(self, widget, index=0): 266 | ''' 267 | If the widget is a subclass of :class:`~NavigationDrawerHeaderBase`, then it will be placed above the 268 | :class:`~kivy.uix.scrollview.ScrollView`. Otherwise, it will be placed in the main 269 | :class:`~kivy.uix.scrollview.ScrollView` content area. 270 | ''' 271 | if issubclass(widget.__class__, BaseListItem): 272 | self._list.add_widget(widget, index) 273 | if len(self._list.children) == 1: 274 | widget._active = True 275 | self.active_item = widget 276 | widget.bind(on_release=lambda x: self.panel.toggle_state()) 277 | widget.bind(on_release=lambda x: x._set_active(True, list=self)) 278 | elif issubclass(widget.__class__, NavigationDrawerHeaderBase): 279 | self._header_container.add_widget(widget) 280 | else: 281 | super(MDNavigationDrawer, self).add_widget(widget, index) 282 | 283 | 284 | class NavigationLayout(VendorNavigationDrawer, ThemableBehavior): 285 | ''' 286 | The container layout that manages the :class:`MDNavigationDrawer`. 287 | ''' 288 | opening_transition = StringProperty('out_sine') 289 | closing_transition = StringProperty('out_sine') 290 | min_dist_to_open = NumericProperty(0.2) 291 | min_dist_to_close = NumericProperty(0.8) 292 | anim_time = NumericProperty(0.2) 293 | separator_image = StringProperty('{}'.format(images_path + '/transparent.png')) 294 | side_panel_positioning = 'left' 295 | side_panel_width = NumericProperty(dp(320)) 296 | max_shadow_opacity = NumericProperty(0.5) 297 | anim_type = StringProperty('slide_above_simple') 298 | 299 | def __init__(self, **kwargs): 300 | super(NavigationLayout, self).__init__(**kwargs) 301 | self.on_anim_type() 302 | 303 | def _anim_relax(self): 304 | if self.state == 'open': 305 | if self._anim_progress < self.min_dist_to_close: 306 | self.anim_to_state('closed') 307 | else: 308 | self.anim_to_state('open') 309 | else: 310 | if self._anim_progress > self.min_dist_to_open: 311 | self.anim_to_state('open') 312 | else: 313 | self.anim_to_state('closed') 314 | 315 | def on__anim_progress(self, *args): 316 | self.side_panel.shadow_color = [0, 0, 0, self.max_shadow_opacity*self._anim_progress] 317 | self.side_panel.elevation = 1 * self._anim_progress 318 | if self._anim_progress > 1: 319 | self._anim_progress = 1 320 | elif self._anim_progress < 0: 321 | self._anim_progress = 0 322 | if self._anim_progress >= 1: 323 | self.state = 'open' 324 | elif self._anim_progress <= 0: 325 | self.state = 'closed' 326 | 327 | def add_widget(self, widget, **kwargs): 328 | ''' 329 | First widget added must be the content for the side/sliding panel. 330 | The next widget must be the main content. 331 | 332 | This layout only accepts two widgets, any more than two widgets will raise a ValueError 333 | ''' 334 | # Internal default BoxLayouts 335 | if len(self.children) == 0: 336 | super(NavigationLayout, self).add_widget(widget) 337 | self._side_panel = widget 338 | elif len(self.children) == 1: 339 | super(NavigationLayout, self).add_widget(widget) 340 | self._main_panel = widget 341 | elif len(self.children) == 2: 342 | super(NavigationLayout, self).add_widget(widget) 343 | self._join_image = widget 344 | 345 | # Adding of user widgets 346 | elif self.side_panel is None: 347 | self.set_side_panel(widget) 348 | widget.panel = self 349 | elif self.main_panel is None: 350 | self.set_main_panel(widget) 351 | else: 352 | raise ValueError('Can\'t add more than two widgets directly to NavigationLayout') 353 | 354 | def toggle_nav_drawer(self): 355 | self.toggle_state(True) 356 | -------------------------------------------------------------------------------- /kivymd/progressbar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.lang import Builder 4 | from kivy.properties import BooleanProperty, ListProperty, OptionProperty 5 | from kivy.uix.progressbar import ProgressBar 6 | from kivy.utils import get_color_from_hex 7 | 8 | from kivymd.color_definitions import colors 9 | from kivymd.theming import ThemableBehavior 10 | 11 | 12 | Builder.load_string(''' 13 | : 14 | canvas: 15 | Clear 16 | Color: 17 | rgba: self.theme_cls.divider_color 18 | Rectangle: 19 | size: (self.width , dp(4)) if self.orientation == 'horizontal' else (dp(4),self.height) 20 | pos: (self.x, self.center_y - dp(4)) if self.orientation == 'horizontal' \ 21 | else (self.center_x - dp(4),self.y) 22 | 23 | Color: 24 | rgba: self.theme_cls.primary_color 25 | Rectangle: 26 | size: (self.width*self.value_normalized, sp(4)) if self.orientation == 'horizontal' else (sp(4), \ 27 | self.height*self.value_normalized) 28 | pos: (self.width*(1-self.value_normalized)+self.x if self.reversed else self.x, self.center_y - dp(4)) \ 29 | if self.orientation == 'horizontal' else \ 30 | (self.center_x - dp(4),self.height*(1-self.value_normalized)+self.y if self.reversed else self.y) 31 | ''') 32 | 33 | 34 | class MDProgressBar(ThemableBehavior, ProgressBar): 35 | reversed = BooleanProperty(False) 36 | ''' Reverse the direction the progressbar moves. ''' 37 | 38 | orientation = OptionProperty('horizontal', options=['horizontal', 'vertical']) 39 | ''' Orientation of progressbar''' 40 | 41 | 42 | if __name__ == '__main__': 43 | from kivy.app import App 44 | from kivymd.theming import ThemeManager 45 | 46 | class ProgressBarApp(App): 47 | theme_cls = ThemeManager() 48 | 49 | def build(self): 50 | return Builder.load_string("""#:import MDSlider kivymd.slider.MDSlider 51 | BoxLayout: 52 | orientation:'vertical' 53 | padding: '8dp' 54 | MDSlider: 55 | id:slider 56 | min:0 57 | max:100 58 | value: 40 59 | 60 | MDProgressBar: 61 | value: slider.value 62 | MDProgressBar: 63 | reversed: True 64 | value: slider.value 65 | BoxLayout: 66 | MDProgressBar: 67 | orientation:"vertical" 68 | reversed: True 69 | value: slider.value 70 | 71 | MDProgressBar: 72 | orientation:"vertical" 73 | value: slider.value 74 | """) 75 | 76 | ProgressBarApp().run() 77 | -------------------------------------------------------------------------------- /kivymd/ripplebehavior.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from kivy.animation import Animation 3 | from kivy.graphics import (Color, Ellipse, Rectangle, StencilPop, StencilPush, 4 | StencilUnUse, StencilUse) 5 | from kivy.properties import (BooleanProperty, ListProperty, NumericProperty, 6 | StringProperty) 7 | 8 | 9 | class CommonRipple(object): 10 | ripple_rad = NumericProperty() 11 | ripple_rad_default = NumericProperty(1) 12 | ripple_post = ListProperty() 13 | ripple_color = ListProperty() 14 | ripple_alpha = NumericProperty(.5) 15 | ripple_scale = NumericProperty(None) 16 | ripple_duration_in_fast = NumericProperty(.3) 17 | # FIXME: These speeds should be calculated based on widget size in dp 18 | ripple_duration_in_slow = NumericProperty(2) 19 | ripple_duration_out = NumericProperty(.3) 20 | ripple_func_in = StringProperty('out_quad') 21 | ripple_func_out = StringProperty('out_quad') 22 | 23 | doing_ripple = BooleanProperty(False) 24 | finishing_ripple = BooleanProperty(False) 25 | fading_out = BooleanProperty(False) 26 | 27 | def on_touch_down(self, touch): 28 | if touch.is_mouse_scrolling: 29 | return False 30 | if not self.collide_point(touch.x, touch.y): 31 | return False 32 | 33 | if not self.disabled: 34 | if self.doing_ripple: 35 | Animation.cancel_all(self, 'ripple_rad', 'ripple_color', 36 | 'rect_color') 37 | self.anim_complete() 38 | self.ripple_rad = self.ripple_rad_default 39 | self.ripple_pos = (touch.x, touch.y) 40 | 41 | if self.ripple_color != []: 42 | pass 43 | elif hasattr(self, 'theme_cls'): 44 | self.ripple_color = self.theme_cls.ripple_color 45 | else: 46 | # If no theme, set Grey 300 47 | self.ripple_color = [0.8784313725490196, 0.8784313725490196, 48 | 0.8784313725490196, self.ripple_alpha] 49 | self.ripple_color[3] = self.ripple_alpha 50 | 51 | self.lay_canvas_instructions() 52 | self.finish_rad = max(self.width, self.height) * self.ripple_scale 53 | self.start_ripple() 54 | return super(CommonRipple, self).on_touch_down(touch) 55 | 56 | def lay_canvas_instructions(self): 57 | raise NotImplementedError 58 | 59 | def on_touch_move(self, touch, *args): 60 | if not self.collide_point(touch.x, touch.y): 61 | if not self.finishing_ripple and self.doing_ripple: 62 | self.finish_ripple() 63 | return super(CommonRipple, self).on_touch_move(touch, *args) 64 | 65 | def on_touch_up(self, touch): 66 | if self.collide_point(touch.x, touch.y) and self.doing_ripple: 67 | self.finish_ripple() 68 | return super(CommonRipple, self).on_touch_up(touch) 69 | 70 | def start_ripple(self): 71 | if not self.doing_ripple: 72 | anim = Animation( 73 | ripple_rad=self.finish_rad, 74 | t='linear', 75 | duration=self.ripple_duration_in_slow) 76 | anim.bind(on_complete=self.fade_out) 77 | self.doing_ripple = True 78 | anim.start(self) 79 | 80 | def _set_ellipse(self, instance, value): 81 | self.ellipse.size = (self.ripple_rad, self.ripple_rad) 82 | 83 | # Adjust ellipse pos here 84 | 85 | def _set_color(self, instance, value): 86 | self.col_instruction.a = value[3] 87 | 88 | def finish_ripple(self): 89 | if self.doing_ripple and not self.finishing_ripple: 90 | Animation.cancel_all(self, 'ripple_rad') 91 | anim = Animation(ripple_rad=self.finish_rad, 92 | t=self.ripple_func_in, 93 | duration=self.ripple_duration_in_fast) 94 | anim.bind(on_complete=self.fade_out) 95 | self.finishing_ripple = True 96 | anim.start(self) 97 | 98 | def fade_out(self, *args): 99 | rc = self.ripple_color 100 | if not self.fading_out: 101 | Animation.cancel_all(self, 'ripple_color') 102 | anim = Animation(ripple_color=[rc[0], rc[1], rc[2], 0.], 103 | t=self.ripple_func_out, 104 | duration=self.ripple_duration_out) 105 | anim.bind(on_complete=self.anim_complete) 106 | self.fading_out = True 107 | anim.start(self) 108 | 109 | def anim_complete(self, *args): 110 | self.doing_ripple = False 111 | self.finishing_ripple = False 112 | self.fading_out = False 113 | self.canvas.after.clear() 114 | 115 | 116 | class RectangularRippleBehavior(CommonRipple): 117 | ripple_scale = NumericProperty(2.75) 118 | 119 | def lay_canvas_instructions(self): 120 | with self.canvas.after: 121 | StencilPush() 122 | Rectangle(pos=self.pos, size=self.size) 123 | StencilUse() 124 | self.col_instruction = Color(rgba=self.ripple_color) 125 | self.ellipse = \ 126 | Ellipse(size=(self.ripple_rad, self.ripple_rad), 127 | pos=(self.ripple_pos[0] - self.ripple_rad / 2., 128 | self.ripple_pos[1] - self.ripple_rad / 2.)) 129 | StencilUnUse() 130 | Rectangle(pos=self.pos, size=self.size) 131 | StencilPop() 132 | self.bind(ripple_color=self._set_color, 133 | ripple_rad=self._set_ellipse) 134 | 135 | def _set_ellipse(self, instance, value): 136 | super(RectangularRippleBehavior, self)._set_ellipse(instance, value) 137 | self.ellipse.pos = (self.ripple_pos[0] - self.ripple_rad / 2., 138 | self.ripple_pos[1] - self.ripple_rad / 2.) 139 | 140 | 141 | class CircularRippleBehavior(CommonRipple): 142 | ripple_scale = NumericProperty(1) 143 | 144 | def lay_canvas_instructions(self): 145 | with self.canvas.after: 146 | StencilPush() 147 | self.stencil = Ellipse(size=(self.width * self.ripple_scale, 148 | self.height * self.ripple_scale), 149 | pos=(self.center_x - ( 150 | self.width * self.ripple_scale) / 2, 151 | self.center_y - ( 152 | self.height * self.ripple_scale) / 2)) 153 | StencilUse() 154 | self.col_instruction = Color(rgba=self.ripple_color) 155 | self.ellipse = Ellipse(size=(self.ripple_rad, self.ripple_rad), 156 | pos=(self.center_x - self.ripple_rad / 2., 157 | self.center_y - self.ripple_rad / 2.)) 158 | StencilUnUse() 159 | Ellipse(pos=self.pos, size=self.size) 160 | StencilPop() 161 | self.bind(ripple_color=self._set_color, 162 | ripple_rad=self._set_ellipse) 163 | 164 | def _set_ellipse(self, instance, value): 165 | super(CircularRippleBehavior, self)._set_ellipse(instance, value) 166 | if self.ellipse.size[0] > self.width * .6 and not self.fading_out: 167 | self.fade_out() 168 | self.ellipse.pos = (self.center_x - self.ripple_rad / 2., 169 | self.center_y - self.ripple_rad / 2.) 170 | -------------------------------------------------------------------------------- /kivymd/selectioncontrols.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.animation import Animation 4 | from kivy.lang import Builder 5 | from kivy.metrics import dp, sp 6 | from kivy.properties import (AliasProperty, BooleanProperty, ListProperty, 7 | NumericProperty, StringProperty) 8 | from kivy.uix.behaviors import ButtonBehavior, ToggleButtonBehavior 9 | from kivy.uix.floatlayout import FloatLayout 10 | from kivy.uix.label import Label 11 | from kivy.uix.widget import Widget 12 | from kivy.utils import get_color_from_hex 13 | 14 | from kivymd.color_definitions import colors 15 | from kivymd.elevationbehavior import CircularElevationBehavior 16 | from kivymd.icon_definitions import md_icons 17 | from kivymd.ripplebehavior import CircularRippleBehavior 18 | from kivymd.theming import ThemableBehavior 19 | 20 | 21 | Builder.load_string(''' 22 | : 23 | canvas: 24 | Clear 25 | Color: 26 | rgba: 1, 1, 1, 1 27 | Rectangle: 28 | texture: self.texture 29 | size: self.texture_size 30 | pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.) 31 | 32 | text: self._radio_icon if self.group else self._checkbox_icon 33 | font_name: 'Icons' 34 | font_size: sp(24) 35 | color: self.theme_cls.primary_color if self.active else self.theme_cls.secondary_text_color 36 | halign: 'center' 37 | valign: 'middle' 38 | 39 | : 40 | color: 1, 1, 1, 1 41 | canvas: 42 | Color: 43 | rgba: self.color 44 | Ellipse: 45 | size: self.size 46 | pos: self.pos 47 | 48 | : 49 | canvas.before: 50 | Color: 51 | rgba: self._track_color_disabled if self.disabled else \ 52 | (self._track_color_active if self.active else self._track_color_normal) 53 | Ellipse: 54 | size: dp(16), dp(16) 55 | pos: self.x, self.center_y - dp(8) 56 | angle_start: 180 57 | angle_end: 360 58 | Rectangle: 59 | size: self.width - dp(16), dp(16) 60 | pos: self.x + dp(8), self.center_y - dp(8) 61 | Ellipse: 62 | size: dp(16), dp(16) 63 | pos: self.right - dp(16), self.center_y - dp(8) 64 | angle_start: 0 65 | angle_end: 180 66 | on_release: thumb.trigger_action() 67 | 68 | Thumb: 69 | id: thumb 70 | size_hint: None, None 71 | size: dp(24), dp(24) 72 | pos: root._thumb_pos 73 | color: root.thumb_color_disabled if root.disabled else \ 74 | (root.thumb_color_down if root.active else root.thumb_color) 75 | elevation: 4 if root.active else 2 76 | on_release: setattr(root, 'active', not root.active) 77 | ''') 78 | 79 | 80 | class MDCheckbox(ThemableBehavior, CircularRippleBehavior, 81 | ToggleButtonBehavior, Label): 82 | active = BooleanProperty(False) 83 | 84 | _checkbox_icon = StringProperty( 85 | u"{}".format(md_icons['checkbox-blank-outline'])) 86 | _radio_icon = StringProperty(u"{}".format( 87 | md_icons['checkbox-blank-circle-outline'])) 88 | _icon_active = StringProperty(u"{}".format(md_icons['checkbox-marked'])) 89 | 90 | def __init__(self, **kwargs): 91 | self.check_anim_out = Animation(font_size=0, duration=.1, t='out_quad') 92 | self.check_anim_in = Animation(font_size=sp(24), duration=.1, 93 | t='out_quad') 94 | super(MDCheckbox, self).__init__(**kwargs) 95 | self.register_event_type('on_active') 96 | self.check_anim_out.bind( 97 | on_complete=lambda *x: self.check_anim_in.start(self)) 98 | 99 | def on_state(self, *args): 100 | if self.state == 'down': 101 | self.check_anim_in.cancel(self) 102 | self.check_anim_out.start(self) 103 | self._radio_icon = u"{}".format( 104 | md_icons['checkbox-marked-circle-outline']) 105 | self._checkbox_icon = u"{}".format( 106 | md_icons['checkbox-marked-outline']) 107 | self.active = True 108 | else: 109 | self.check_anim_in.cancel(self) 110 | self.check_anim_out.start(self) 111 | self._radio_icon = u"{}".format( 112 | md_icons['checkbox-blank-circle-outline']) 113 | self._checkbox_icon = u"{}".format( 114 | md_icons['checkbox-blank-outline']) 115 | self.active = False 116 | 117 | def on_active(self, instance, value): 118 | self.state = 'down' if value else 'normal' 119 | 120 | 121 | class Thumb(CircularElevationBehavior, CircularRippleBehavior, ButtonBehavior, 122 | Widget): 123 | ripple_scale = NumericProperty(2) 124 | 125 | def _set_ellipse(self, instance, value): 126 | self.ellipse.size = (self.ripple_rad, self.ripple_rad) 127 | if self.ellipse.size[0] > self.width * 1.5 and not self.fading_out: 128 | self.fade_out() 129 | self.ellipse.pos = (self.center_x - self.ripple_rad / 2., 130 | self.center_y - self.ripple_rad / 2.) 131 | self.stencil.pos = ( 132 | self.center_x - (self.width * self.ripple_scale) / 2, 133 | self.center_y - (self.height * self.ripple_scale) / 2) 134 | 135 | 136 | class MDSwitch(ThemableBehavior, ButtonBehavior, FloatLayout): 137 | active = BooleanProperty(False) 138 | 139 | _thumb_color = ListProperty(get_color_from_hex(colors['Grey']['50'])) 140 | 141 | def _get_thumb_color(self): 142 | return self._thumb_color 143 | 144 | def _set_thumb_color(self, color, alpha=None): 145 | if len(color) == 2: 146 | self._thumb_color = get_color_from_hex(colors[color[0]][color[1]]) 147 | if alpha: 148 | self._thumb_color[3] = alpha 149 | elif len(color) == 4: 150 | self._thumb_color = color 151 | 152 | thumb_color = AliasProperty(_get_thumb_color, _set_thumb_color, 153 | bind=['_thumb_color']) 154 | 155 | _thumb_color_down = ListProperty([1, 1, 1, 1]) 156 | 157 | def _get_thumb_color_down(self): 158 | return self._thumb_color_down 159 | 160 | def _set_thumb_color_down(self, color, alpha=None): 161 | if len(color) == 2: 162 | self._thumb_color_down = get_color_from_hex( 163 | colors[color[0]][color[1]]) 164 | if alpha: 165 | self._thumb_color_down[3] = alpha 166 | else: 167 | self._thumb_color_down[3] = 1 168 | elif len(color) == 4: 169 | self._thumb_color_down = color 170 | 171 | thumb_color_down = AliasProperty(_get_thumb_color_down, 172 | _set_thumb_color_down, 173 | bind=['_thumb_color_down']) 174 | 175 | _thumb_color_disabled = ListProperty( 176 | get_color_from_hex(colors['Grey']['400'])) 177 | 178 | def _get_thumb_color_disabled(self): 179 | return self._thumb_color_disabled 180 | 181 | def _set_thumb_color_disabled(self, color, alpha=None): 182 | if len(color) == 2: 183 | self._thumb_color_disabled = get_color_from_hex( 184 | colors[color[0]][color[1]]) 185 | if alpha: 186 | self._thumb_color_disabled[3] = alpha 187 | elif len(color) == 4: 188 | self._thumb_color_disabled = color 189 | 190 | thumb_color_down = AliasProperty(_get_thumb_color_disabled, 191 | _set_thumb_color_disabled, 192 | bind=['_thumb_color_disabled']) 193 | 194 | _track_color_active = ListProperty() 195 | _track_color_normal = ListProperty() 196 | _track_color_disabled = ListProperty() 197 | _thumb_pos = ListProperty([0, 0]) 198 | 199 | def __init__(self, **kwargs): 200 | super(MDSwitch, self).__init__(**kwargs) 201 | self.theme_cls.bind(theme_style=self._set_colors, 202 | primary_color=self._set_colors, 203 | primary_palette=self._set_colors) 204 | self._set_colors() 205 | 206 | def _set_colors(self, *args): 207 | self._track_color_normal = self.theme_cls.disabled_hint_text_color 208 | if self.theme_cls.theme_style == 'Dark': 209 | self._track_color_active = self.theme_cls.primary_color 210 | self._track_color_active[3] = .5 211 | self._track_color_disabled = get_color_from_hex('FFFFFF') 212 | self._track_color_disabled[3] = .1 213 | self.thumb_color = get_color_from_hex(colors['Grey']['400']) 214 | self.thumb_color_down = get_color_from_hex( 215 | colors[self.theme_cls.primary_palette]['200']) 216 | self.thumb_color_disabled = get_color_from_hex( 217 | colors['Grey']['800']) 218 | else: 219 | self._track_color_active = get_color_from_hex( 220 | colors[self.theme_cls.primary_palette]['200']) 221 | self._track_color_active[3] = .5 222 | self._track_color_disabled = self.theme_cls.disabled_hint_text_color 223 | self.thumb_color_down = self.theme_cls.primary_color 224 | self.thumb_color_disabled = get_color_from_hex( 225 | colors['Grey']['400']) 226 | 227 | def on_pos(self, *args): 228 | if self.active: 229 | self._thumb_pos = (self.right - dp(12), self.center_y - dp(12)) 230 | else: 231 | self._thumb_pos = (self.x - dp(12), self.center_y - dp(12)) 232 | self.bind(active=self._update_thumb) 233 | 234 | def _update_thumb(self, *args): 235 | if self.active: 236 | Animation.cancel_all(self, '_thumb_pos') 237 | anim = Animation( 238 | _thumb_pos=(self.right - dp(12), self.center_y - dp(12)), 239 | duration=.2, 240 | t='out_quad') 241 | else: 242 | Animation.cancel_all(self, '_thumb_pos') 243 | anim = Animation( 244 | _thumb_pos=(self.x - dp(12), self.center_y - dp(12)), 245 | duration=.2, 246 | t='out_quad') 247 | anim.start(self) 248 | -------------------------------------------------------------------------------- /kivymd/slider.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.lang import Builder 4 | from kivy.metrics import dp, sp 5 | from kivy.properties import (AliasProperty, BooleanProperty, ListProperty, 6 | NumericProperty, StringProperty) 7 | from kivy.uix.slider import Slider 8 | from kivy.utils import get_color_from_hex 9 | 10 | from kivymd.color_definitions import colors 11 | from kivymd.theming import ThemableBehavior 12 | 13 | 14 | Builder.load_string(''' 15 | #:import Thumb kivymd.selectioncontrols.Thumb 16 | 17 | : 18 | id: slider 19 | canvas: 20 | Clear 21 | Color: 22 | rgba: self._track_color_disabled if self.disabled else (self._track_color_active if self.active \ 23 | else self._track_color_normal) 24 | Rectangle: 25 | size: (self.width - self.padding*2 - self._offset[0], dp(4)) if self.orientation == 'horizontal' \ 26 | else (dp(4),self.height - self.padding*2 - self._offset[1]) 27 | pos: (self.x + self.padding + self._offset[0], self.center_y - dp(4)) \ 28 | if self.orientation == 'horizontal' else (self.center_x - dp(4),self.y + self.padding + self._offset[1]) 29 | 30 | # If 0 draw circle 31 | Color: 32 | rgba: [0,0,0,0] if not self._is_off else (self._track_color_disabled if self.disabled \ 33 | else (self._track_color_active if self.active else self._track_color_normal)) 34 | Line: 35 | width: 2 36 | circle: (self.x+self.padding+dp(3),self.center_y-dp(2),8 if self.active else 6 ) \ 37 | if self.orientation == 'horizontal' else (self.center_x-dp(2),self.y+self.padding+dp(3),8 \ 38 | if self.active else 6) 39 | 40 | Color: 41 | rgba: [0,0,0,0] if self._is_off \ 42 | else (self.thumb_color_down if not self.disabled else self._track_color_disabled) 43 | Rectangle: 44 | size: ((self.width-self.padding*2)*self.value_normalized, sp(4)) \ 45 | if slider.orientation == 'horizontal' else (sp(4), (self.height-self.padding*2)*self.value_normalized) 46 | pos: (self.x + self.padding, self.center_y - dp(4)) if self.orientation == 'horizontal' \ 47 | else (self.center_x - dp(4),self.y + self.padding) 48 | Thumb: 49 | id: thumb 50 | size_hint: None, None 51 | size: (dp(12), dp(12)) if root.disabled else ((dp(24), dp(24)) if root.active else (dp(16),dp(16))) 52 | pos: (slider.value_pos[0] - dp(8), slider.center_y - thumb.height/2 - dp(2)) \ 53 | if slider.orientation == 'horizontal' \ 54 | else (slider.center_x - thumb.width/2 - dp(2), slider.value_pos[1]-dp(8)) 55 | color: [0,0,0,0] if slider._is_off else (root._track_color_disabled if root.disabled \ 56 | else root.thumb_color_down) 57 | elevation: 0 if slider._is_off else (4 if root.active else 2) 58 | ''') 59 | 60 | 61 | class MDSlider(ThemableBehavior, Slider): 62 | # If the slider is clicked 63 | active = BooleanProperty(False) 64 | 65 | # Show the "off" ring when set to minimum value 66 | show_off = BooleanProperty(True) 67 | 68 | # Internal state of ring 69 | _is_off = BooleanProperty(False) 70 | 71 | # Internal adjustment to reposition sliders for ring 72 | _offset = ListProperty((0, 0)) 73 | 74 | _thumb_color = ListProperty(get_color_from_hex(colors['Grey']['50'])) 75 | 76 | def _get_thumb_color(self): 77 | return self._thumb_color 78 | 79 | def _set_thumb_color(self, color, alpha=None): 80 | if len(color) == 2: 81 | self._thumb_color = get_color_from_hex(colors[color[0]][color[1]]) 82 | if alpha: 83 | self._thumb_color[3] = alpha 84 | elif len(color) == 4: 85 | self._thumb_color = color 86 | 87 | thumb_color = AliasProperty(_get_thumb_color, _set_thumb_color, 88 | bind=['_thumb_color']) 89 | 90 | _thumb_color_down = ListProperty([1, 1, 1, 1]) 91 | 92 | def _get_thumb_color_down(self): 93 | return self._thumb_color_down 94 | 95 | def _set_thumb_color_down(self, color, alpha=None): 96 | if len(color) == 2: 97 | self._thumb_color_down = get_color_from_hex( 98 | colors[color[0]][color[1]]) 99 | if alpha: 100 | self._thumb_color_down[3] = alpha 101 | else: 102 | self._thumb_color_down[3] = 1 103 | elif len(color) == 4: 104 | self._thumb_color_down = color 105 | 106 | thumb_color_down = AliasProperty(_get_thumb_color_down, 107 | _set_thumb_color_down, 108 | bind=['_thumb_color_down']) 109 | 110 | _thumb_color_disabled = ListProperty( 111 | get_color_from_hex(colors['Grey']['400'])) 112 | 113 | def _get_thumb_color_disabled(self): 114 | return self._thumb_color_disabled 115 | 116 | def _set_thumb_color_disabled(self, color, alpha=None): 117 | if len(color) == 2: 118 | self._thumb_color_disabled = get_color_from_hex( 119 | colors[color[0]][color[1]]) 120 | if alpha: 121 | self._thumb_color_disabled[3] = alpha 122 | elif len(color) == 4: 123 | self._thumb_color_disabled = color 124 | 125 | thumb_color_down = AliasProperty(_get_thumb_color_disabled, 126 | _set_thumb_color_disabled, 127 | bind=['_thumb_color_disabled']) 128 | 129 | _track_color_active = ListProperty() 130 | _track_color_normal = ListProperty() 131 | _track_color_disabled = ListProperty() 132 | _thumb_pos = ListProperty([0, 0]) 133 | 134 | def __init__(self, **kwargs): 135 | super(MDSlider, self).__init__(**kwargs) 136 | self.theme_cls.bind(theme_style=self._set_colors, 137 | primary_color=self._set_colors, 138 | primary_palette=self._set_colors) 139 | self._set_colors() 140 | 141 | def _set_colors(self, *args): 142 | if self.theme_cls.theme_style == 'Dark': 143 | self._track_color_normal = get_color_from_hex('FFFFFF') 144 | self._track_color_normal[3] = .3 145 | self._track_color_active = self._track_color_normal 146 | self._track_color_disabled = self._track_color_normal 147 | self.thumb_color = get_color_from_hex(colors['Grey']['400']) 148 | self.thumb_color_down = get_color_from_hex(colors[self.theme_cls.primary_palette]['200']) 149 | self.thumb_color_disabled = get_color_from_hex(colors['Grey']['800']) 150 | else: 151 | self._track_color_normal = get_color_from_hex('000000') 152 | self._track_color_normal[3] = 0.26 153 | self._track_color_active = get_color_from_hex('000000') 154 | self._track_color_active[3] = 0.38 155 | self._track_color_disabled = get_color_from_hex('000000') 156 | self._track_color_disabled[3] = 0.26 157 | self.thumb_color_down = self.theme_cls.primary_color 158 | 159 | def on_value_normalized(self, *args): 160 | """ When the value == min set it to "off" state and make slider a ring """ 161 | self._update_is_off() 162 | 163 | def on_show_off(self, *args): 164 | self._update_is_off() 165 | 166 | def _update_is_off(self): 167 | self._is_off = self.show_off and (self.value_normalized == 0) 168 | 169 | def on__is_off(self, *args): 170 | self._update_offset() 171 | 172 | def on_active(self, *args): 173 | self._update_offset() 174 | 175 | def _update_offset(self): 176 | """ Offset is used to shift the sliders so the background color 177 | shows through the off circle. 178 | """ 179 | d = 2 if self.active else 0 180 | self._offset = (dp(11+d), dp(11+d)) if self._is_off else (0, 0) 181 | 182 | def on_touch_down(self, touch): 183 | if super(MDSlider, self).on_touch_down(touch): 184 | self.active = True 185 | 186 | def on_touch_up(self, touch): 187 | if super(MDSlider, self).on_touch_up(touch): 188 | self.active = False 189 | # thumb = self.ids['thumb'] 190 | # if thumb.collide_point(*touch.pos): 191 | # thumb.on_touch_down(touch) 192 | # thumb.on_touch_up(touch) 193 | 194 | if __name__ == '__main__': 195 | from kivy.app import App 196 | from kivymd.theming import ThemeManager 197 | 198 | class SliderApp(App): 199 | theme_cls = ThemeManager() 200 | 201 | def build(self): 202 | return Builder.load_string(""" 203 | BoxLayout: 204 | orientation:'vertical' 205 | BoxLayout: 206 | size_hint_y:None 207 | height: '48dp' 208 | Label: 209 | text:"Toggle disabled" 210 | color: [0,0,0,1] 211 | CheckBox: 212 | on_press: slider.disabled = not slider.disabled 213 | BoxLayout: 214 | size_hint_y:None 215 | height: '48dp' 216 | Label: 217 | text:"Toggle active" 218 | color: [0,0,0,1] 219 | CheckBox: 220 | on_press: slider.active = not slider.active 221 | BoxLayout: 222 | size_hint_y:None 223 | height: '48dp' 224 | Label: 225 | text:"Toggle show off" 226 | color: [0,0,0,1] 227 | CheckBox: 228 | on_press: slider.show_off = not slider.show_off 229 | 230 | MDSlider: 231 | id:slider 232 | min:0 233 | max:100 234 | value: 40 235 | 236 | MDSlider: 237 | id:slider2 238 | orientation:"vertical" 239 | min:0 240 | max:100 241 | value: 40 242 | """) 243 | 244 | SliderApp().run() 245 | -------------------------------------------------------------------------------- /kivymd/slidingpanel.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 kivy.metrics import dp 7 | from kivy.properties import (BooleanProperty, ListProperty, NumericProperty, 8 | OptionProperty, StringProperty) 9 | from kivy.uix.boxlayout import BoxLayout 10 | from kivy.uix.relativelayout import RelativeLayout 11 | 12 | 13 | Builder.load_string(""" 14 | #: import Window kivy.core.window.Window 15 | 16 | orientation: 'vertical' 17 | size_hint_x: None 18 | width: dp(320) 19 | x: -1 * self.width if self.side == 'left' else Window.width 20 | 21 | 22 | canvas: 23 | Color: 24 | rgba: root.color 25 | Rectangle: 26 | size: root.size 27 | """) 28 | 29 | 30 | class PanelShadow(BoxLayout): 31 | color = ListProperty([0, 0, 0, 0]) 32 | 33 | 34 | class SlidingPanel(BoxLayout): 35 | anim_length_close = NumericProperty(0.3) 36 | anim_length_open = NumericProperty(0.3) 37 | animation_t_open = StringProperty('out_sine') 38 | animation_t_close = StringProperty('out_sine') 39 | side = OptionProperty('left', options=['left', 'right']) 40 | 41 | _open = False 42 | 43 | def __init__(self, **kwargs): 44 | super(SlidingPanel, self).__init__(**kwargs) 45 | self.shadow = PanelShadow() 46 | Clock.schedule_once(lambda x: Window.add_widget(self.shadow, 89), 0) 47 | Clock.schedule_once(lambda x: Window.add_widget(self, 90), 0) 48 | 49 | def toggle(self): 50 | Animation.stop_all(self, 'x') 51 | Animation.stop_all(self.shadow, 'color') 52 | if self._open: 53 | if self.side == 'left': 54 | target_x = -1 * self.width 55 | else: 56 | target_x = Window.width 57 | 58 | sh_anim = Animation(duration=self.anim_length_open, 59 | t=self.animation_t_open, 60 | color=[0, 0, 0, 0]) 61 | sh_anim.start(self.shadow) 62 | self._get_main_animation(duration=self.anim_length_close, 63 | t=self.animation_t_close, 64 | x=target_x, 65 | is_closing=True).start(self) 66 | self._open = False 67 | else: 68 | if self.side == 'left': 69 | target_x = 0 70 | else: 71 | target_x = Window.width - self.width 72 | Animation(duration=self.anim_length_open, t=self.animation_t_open, 73 | color=[0, 0, 0, 0.5]).start(self.shadow) 74 | self._get_main_animation(duration=self.anim_length_open, 75 | t=self.animation_t_open, 76 | x=target_x, 77 | is_closing=False).start(self) 78 | self._open = True 79 | 80 | def _get_main_animation(self, duration, t, x, is_closing): 81 | return Animation(duration=duration, t=t, x=x) 82 | 83 | def on_touch_down(self, touch): 84 | # Prevents touch events from propagating to anything below the widget. 85 | super(SlidingPanel, self).on_touch_down(touch) 86 | if self.collide_point(*touch.pos) or self._open: 87 | return True 88 | 89 | def on_touch_up(self, touch): 90 | super(SlidingPanel, self).on_touch_up(touch) 91 | if not self.collide_point(touch.x, touch.y) and self._open: 92 | self.toggle() 93 | return True 94 | -------------------------------------------------------------------------------- /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 | 14 | from kivy.animation import Animation 15 | from kivy.clock import Clock 16 | from kivy.core.window import Window 17 | from kivy.event import EventDispatcher 18 | from kivy.lang import Builder 19 | from kivy.metrics import dp 20 | from kivy.properties import NumericProperty, ObjectProperty, StringProperty 21 | from kivy.uix.relativelayout import RelativeLayout 22 | 23 | from kivymd.material_resources import DEVICE_TYPE 24 | 25 | 26 | Builder.load_string(''' 27 | #:import Window kivy.core.window.Window 28 | #:import get_color_from_hex kivy.utils.get_color_from_hex 29 | #:import MDFlatButton kivymd.button.MDFlatButton 30 | #:import MDLabel kivymd.label.MDLabel 31 | #:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE 32 | <_SnackbarWidget> 33 | canvas: 34 | Color: 35 | rgb: get_color_from_hex('323232') 36 | Rectangle: 37 | size: self.size 38 | size_hint_y: None 39 | size_hint_x: 1 if DEVICE_TYPE == 'mobile' else None 40 | height: dp(48) if _label.texture_size[1] < dp(30) else dp(80) 41 | width: dp(24) + _label.width + _spacer.width + root.padding_right if root.button_text == '' else dp(24) + \ 42 | _label.width + _spacer.width + _button.width + root.padding_right 43 | top: 0 44 | x: 0 if DEVICE_TYPE == 'mobile' else Window.width/2 - self.width/2 45 | BoxLayout: 46 | width: Window.width - root.padding_right - _spacer.width - dp(24) if DEVICE_TYPE == 'mobile' and \ 47 | root.button_text == '' else Window.width - root.padding_right - _button.width - _spacer.width - dp(24) \ 48 | if DEVICE_TYPE == 'mobile' else _label.texture_size[0] if (dp(568) - root.padding_right - _button.width - \ 49 | _spacer.width - _label.texture_size[0] - dp(24)) >= 0 else (dp(568) - root.padding_right - _button.width - \ 50 | _spacer.width - dp(24)) 51 | size_hint_x: None 52 | x: dp(24) 53 | MDLabel: 54 | id: _label 55 | text: root.text 56 | theme_text_color: 'Custom' 57 | text_color: get_color_from_hex('ffffff') 58 | size: self.texture_size 59 | BoxLayout: 60 | id: _spacer 61 | size_hint_x: None 62 | x: _label.right 63 | width: 0 64 | MDFlatButton: 65 | id: _button 66 | text: root.button_text 67 | theme_text_color: 'Custom' 68 | text_color: get_color_from_hex('ffffff') 69 | size_hint_x: None 70 | x: _spacer.right if root.button_text != '' else root.right 71 | center_y: root.height/2 72 | on_release: root.button_callback() 73 | ''') 74 | 75 | 76 | class SnackbarManager: 77 | playing = False 78 | queue = deque() 79 | 80 | def _play_next(self, dying_widget=None): 81 | if (dying_widget or not self.playing) and len(self.queue) > 0: 82 | self.playing = True 83 | self.queue.popleft().begin() 84 | elif len(self.queue) == 0: 85 | self.playing = False 86 | 87 | def make(self, text, button_text=None, button_callback=None, duration=3): 88 | if button_text is not None and button_callback is not None: 89 | self.queue.append(_SnackbarWidget(text=text, button_text=button_text, button_callback=button_callback, 90 | duration=duration)) 91 | else: 92 | self.queue.append(_SnackbarWidget(text=text, duration=duration)) 93 | self._play_next() 94 | 95 | 96 | class Snackbar(EventDispatcher): 97 | ''' 98 | A Material Design Snackbar 99 | ''' 100 | text = StringProperty("") 101 | '''The text that will appear in the Snackbar. 102 | 103 | :attr:`text` is a :class:`~kivy.properties.StringProperty` and defaults to ''. 104 | ''' 105 | button_text = StringProperty(None, allownone=True) 106 | '''The text that will appear in the Snackbar's button. 107 | 108 | .. note:: 109 | If this variable is None, the Snackbar will have no button. 110 | 111 | :attr:`button_text` is a :class:`~kivy.properties.StringProperty` and defaults to None. 112 | ''' 113 | button_callback = ObjectProperty(None, allownone=True) 114 | '''The callback that will be triggered when the Snackbar's button is pressed. 115 | 116 | .. note:: 117 | If this variable is None, the Snackbar will have no button. 118 | 119 | :attr:`button_callback` is a :class:`~kivy.properties.ObjectProperty` and defaults to None. 120 | ''' 121 | duration = NumericProperty(3) 122 | '''The amount of time that the Snackbar will stay on screen for. 123 | 124 | :attr:`duration` is a :class:`~kivy.properties.NumericProperty` and defaults to 3. 125 | ''' 126 | 127 | def __init__(self, text, button_text=None, button_callback=None, duration=None): 128 | self.text = text 129 | self.button_text = button_text 130 | self.button_callback = button_callback 131 | self.duration = duration or self.duration 132 | 133 | def show(self): 134 | '''Show the Snackbar''' 135 | manager.make(text=self.text, button_text=self.button_text, button_callback=self.button_callback, 136 | duration=self.duration) 137 | 138 | 139 | class _SnackbarWidget(RelativeLayout): 140 | text = StringProperty() 141 | button_text = StringProperty() 142 | button_callback = ObjectProperty() 143 | duration = NumericProperty() 144 | padding_right = NumericProperty(dp(24)) 145 | 146 | def __init__(self, text, duration, button_text='', button_callback=None, 147 | **kwargs): 148 | super(_SnackbarWidget, self).__init__(**kwargs) 149 | self.text = text 150 | self.button_text = button_text 151 | self.button_callback = button_callback 152 | self.duration = duration 153 | self.ids['_label'].text_size = (None, None) 154 | 155 | def begin(self): 156 | if self.button_text == '': 157 | self.remove_widget(self.ids['_button']) 158 | else: 159 | self.ids['_spacer'].width = dp(16) if DEVICE_TYPE == "mobile" else dp(40) 160 | self.padding_right = dp(16) 161 | Window.add_widget(self) 162 | anim = Animation(y=0, duration=.3, t='out_quad') 163 | anim.start(self) 164 | Clock.schedule_once(lambda dt: self.die(), self.duration) 165 | 166 | def die(self): 167 | anim = Animation(top=0, duration=.3, t='out_quad') 168 | anim.bind(on_complete=lambda *args: manager._play_next(self)) 169 | anim.bind(on_complete=lambda *args: Window.remove_widget(self)) 170 | anim.start(self) 171 | 172 | manager = SnackbarManager() 173 | -------------------------------------------------------------------------------- /kivymd/spinner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.animation import Animation 4 | from kivy.lang import Builder 5 | from kivy.properties import BooleanProperty, ListProperty, NumericProperty 6 | from kivy.uix.widget import Widget 7 | 8 | from kivymd.theming import ThemableBehavior 9 | 10 | 11 | Builder.load_string(''' 12 | : 13 | canvas.before: 14 | PushMatrix 15 | Rotate: 16 | angle: self._rotation_angle 17 | origin: self.center 18 | canvas: 19 | Color: 20 | rgba: self.color 21 | a: self._alpha 22 | SmoothLine: 23 | circle: self.center_x, self.center_y, self.width / 2, \ 24 | self._angle_start, self._angle_end 25 | cap: 'square' 26 | width: dp(2.25) 27 | canvas.after: 28 | PopMatrix 29 | 30 | ''') 31 | 32 | 33 | class MDSpinner(ThemableBehavior, Widget): 34 | """:class:`MDSpinner` is an implementation of the circular progress 35 | indicator in Google's Material Design. 36 | 37 | It can be used either as an indeterminate indicator that loops while 38 | the user waits for something to happen, or as a determinate indicator. 39 | 40 | Set :attr:`determinate` to **True** to activate determinate mode, and 41 | :attr:`determinate_time` to set the duration of the animation. 42 | """ 43 | 44 | determinate = BooleanProperty(False) 45 | """:attr:`determinate` is a :class:`~kivy.properties.BooleanProperty` and 46 | defaults to False 47 | """ 48 | 49 | determinate_time = NumericProperty(2) 50 | """:attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty` 51 | and defaults to 2 52 | """ 53 | 54 | active = BooleanProperty(True) 55 | """Use :attr:`active` to start or stop the spinner. 56 | 57 | :attr:`active` is a :class:`~kivy.properties.BooleanProperty` and 58 | defaults to True 59 | """ 60 | 61 | color = ListProperty([]) 62 | """:attr:`color` is a :class:`~kivy.properties.ListProperty` and 63 | defaults to 'self.theme_cls.primary_color' 64 | """ 65 | 66 | _alpha = NumericProperty(0) 67 | _rotation_angle = NumericProperty(360) 68 | _angle_start = NumericProperty(0) 69 | _angle_end = NumericProperty(8) 70 | 71 | def __init__(self, **kwargs): 72 | super(MDSpinner, self).__init__(**kwargs) 73 | self.color = self.theme_cls.primary_color 74 | self._alpha_anim_in = Animation(_alpha=1, duration=.8, t='out_quad') 75 | self._alpha_anim_out = Animation(_alpha=0, duration=.3, t='out_quad') 76 | self._alpha_anim_out.bind(on_complete=self._reset) 77 | self.theme_cls.bind(primary_color=self._update_color) 78 | 79 | if self.determinate: 80 | self._start_determinate() 81 | else: 82 | self._start_loop() 83 | 84 | def _update_color(self, *args): 85 | self.color = self.theme_cls.primary_color 86 | 87 | def _start_determinate(self, *args): 88 | self._alpha_anim_in.start(self) 89 | 90 | _rot_anim = Animation(_rotation_angle=0, 91 | duration=self.determinate_time * .7, 92 | t='out_quad') 93 | _rot_anim.start(self) 94 | 95 | _angle_start_anim = Animation(_angle_end=360, 96 | duration=self.determinate_time, 97 | t='in_out_quad') 98 | _angle_start_anim.bind(on_complete=lambda *x: \ 99 | self._alpha_anim_out.start(self)) 100 | 101 | _angle_start_anim.start(self) 102 | 103 | def _start_loop(self, *args): 104 | if self._alpha == 0: 105 | _rot_anim = Animation(_rotation_angle=0, 106 | duration=2, 107 | t='linear') 108 | _rot_anim.start(self) 109 | 110 | self._alpha = 1 111 | self._alpha_anim_in.start(self) 112 | _angle_start_anim = Animation(_angle_end=self._angle_end + 270, 113 | duration=.6, 114 | t='in_out_cubic') 115 | _angle_start_anim.bind(on_complete=self._anim_back) 116 | _angle_start_anim.start(self) 117 | 118 | def _anim_back(self, *args): 119 | _angle_back_anim = Animation(_angle_start=self._angle_end - 8, 120 | duration=.6, 121 | t='in_out_cubic') 122 | _angle_back_anim.bind(on_complete=self._start_loop) 123 | 124 | _angle_back_anim.start(self) 125 | 126 | def on__rotation_angle(self, *args): 127 | if self._rotation_angle == 0: 128 | self._rotation_angle = 360 129 | if not self.determinate: 130 | _rot_anim = Animation(_rotation_angle=0, 131 | duration=2) 132 | _rot_anim.start(self) 133 | 134 | def _reset(self, *args): 135 | Animation.cancel_all(self, '_angle_start', '_rotation_angle', 136 | '_angle_end', '_alpha') 137 | self._angle_start = 0 138 | self._angle_end = 8 139 | self._rotation_angle = 360 140 | self._alpha = 0 141 | self.active = False 142 | 143 | def on_active(self, *args): 144 | if not self.active: 145 | self._reset() 146 | else: 147 | if self.determinate: 148 | self._start_determinate() 149 | else: 150 | self._start_loop() 151 | -------------------------------------------------------------------------------- /kivymd/theming_dynamic_text.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Two implementations. The first is based on color brightness obtained from:- 3 | # https://www.w3.org/TR/AERT#color-contrast 4 | # The second is based on relative luminance calculation for sRGB obtained from:- 5 | # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef 6 | # and contrast ratio calculation obtained from:- 7 | # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef 8 | # 9 | # Preliminary testing suggests color brightness more closely matches the 10 | # Material Design spec suggested text colors, but the alternative implementation 11 | # is both newer and the current 'correct' recommendation, so is included here 12 | # as an option. 13 | 14 | 15 | def _color_brightness(color): 16 | # Implementation of color brightness method 17 | brightness = color[0] * 299 + color[1] * 587 + color[2] * 114 18 | brightness = brightness 19 | return brightness 20 | 21 | 22 | def _black_or_white_by_color_brightness(color): 23 | if _color_brightness(color) >= 500: 24 | return 'black' 25 | else: 26 | return 'white' 27 | 28 | 29 | def _normalized_channel(color): 30 | # Implementation of contrast ratio and relative luminance method 31 | if color <= 0.03928: 32 | return color / 12.92 33 | else: 34 | return ((color + 0.055) / 1.055) ** 2.4 35 | 36 | 37 | def _luminance(color): 38 | rg = _normalized_channel(color[0]) 39 | gg = _normalized_channel(color[1]) 40 | bg = _normalized_channel(color[2]) 41 | return 0.2126*rg + 0.7152*gg + 0.0722*bg 42 | 43 | 44 | def _black_or_white_by_contrast_ratio(color): 45 | l_color = _luminance(color) 46 | l_black = 0.0 47 | l_white = 1.0 48 | b_contrast = (l_color + 0.05) / (l_black + 0.05) 49 | w_contrast = (l_white + 0.05) / (l_color + 0.05) 50 | return 'white' if w_contrast >= b_contrast else 'black' 51 | 52 | 53 | def get_contrast_text_color(color, use_color_brightness=True): 54 | if use_color_brightness: 55 | contrast_color = _black_or_white_by_color_brightness(color) 56 | else: 57 | contrast_color = _black_or_white_by_contrast_ratio(color) 58 | if contrast_color == 'white': 59 | return 1, 1, 1, 1 60 | else: 61 | return 0, 0, 0, 1 62 | 63 | if __name__ == '__main__': 64 | from kivy.utils import get_color_from_hex 65 | from kivymd.color_definitions import colors, text_colors 66 | for c in colors.items(): 67 | if c[0] in ['Light', 'Dark']: 68 | continue 69 | print("For the {} color palette:".format(c[0])) 70 | for name, hex_color in c[1].items(): 71 | if hex_color: 72 | col = get_color_from_hex(hex_color) 73 | col_bri = get_contrast_text_color(col) 74 | con_rat = get_contrast_text_color(col, use_color_brightness=False) 75 | text_color = text_colors[c[0]][name] 76 | print(" The {} hue gives {} using color brightness, {} using contrast ratio, and {} from the MD spec" 77 | .format(name, col_bri, con_rat, text_color)) 78 | -------------------------------------------------------------------------------- /kivymd/time_picker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kivy.lang import Builder 4 | from kivy.properties import ListProperty, ObjectProperty 5 | from kivy.uix.floatlayout import FloatLayout 6 | from kivy.uix.modalview import ModalView 7 | 8 | from kivymd.elevationbehavior import RectangularElevationBehavior 9 | from kivymd.theming import ThemableBehavior 10 | 11 | 12 | Builder.load_string(""" 13 | #:import MDFlatButton kivymd.button.MDFlatButton 14 | #:import CircularTimePicker kivymd.vendor.circularTimePicker.CircularTimePicker 15 | #:import dp kivy.metrics.dp 16 | : 17 | size_hint: (None, None) 18 | size: [dp(270), dp(335)+dp(95)] 19 | #if root.theme_cls.device_orientation == 'portrait' else [dp(520), dp(325)] 20 | pos_hint: {'center_x': .5, 'center_y': .5} 21 | canvas: 22 | Color: 23 | rgba: self.theme_cls.bg_light 24 | Rectangle: 25 | size: [dp(270), dp(335)] 26 | #if root.theme_cls.device_orientation == 'portrait' else [dp(250), root.height] 27 | pos: [root.pos[0], root.pos[1] + root.height - dp(335) - dp(95)] 28 | #if root.theme_cls.device_orientation == 'portrait' else [root.pos[0]+dp(270), root.pos[1]] 29 | Color: 30 | rgba: self.theme_cls.primary_color 31 | Rectangle: 32 | size: [dp(270), dp(95)] 33 | #if root.theme_cls.device_orientation == 'portrait' else [dp(270), root.height] 34 | pos: [root.pos[0], root.pos[1] + root.height - dp(95)] 35 | #if root.theme_cls.device_orientation == 'portrait' else [root.pos[0], root.pos[1]] 36 | Color: 37 | rgba: self.theme_cls.bg_dark 38 | Ellipse: 39 | size: [dp(220), dp(220)] 40 | #if root.theme_cls.device_orientation == 'portrait' else [dp(195), dp(195)] 41 | 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) 42 | #Color: 43 | #rgba: (1, 0, 0, 1) 44 | #Line: 45 | #width: 4 46 | #points: dp(270)/2, root.height, dp(270)/2, 0 47 | CircularTimePicker: 48 | id: time_picker 49 | pos: (dp(270)/2)-(self.width/2), root.height-self.height 50 | size_hint: [.8, .8] 51 | #if root.theme_cls.device_orientation == 'portrait' else [0.35, 0.9] 52 | pos_hint: {'center_x': 0.5, 'center_y': 0.585} 53 | #if root.theme_cls.device_orientation == 'portrait' else {'center_x': 0.75, 'center_y': 0.7} 54 | MDFlatButton: 55 | width: dp(32) 56 | id: ok_button 57 | pos: root.pos[0]+root.size[0]-self.width-dp(10), root.pos[1] + dp(10) 58 | text: "OK" 59 | on_release: root.close_ok() 60 | MDFlatButton: 61 | id: cancel_button 62 | pos: root.pos[0]+root.size[0]-self.width-ok_button.width-dp(10), root.pos[1] + dp(10) 63 | text: "Cancel" 64 | on_release: root.close_cancel() 65 | """) 66 | 67 | 68 | class MDTimePicker(ThemableBehavior, FloatLayout, ModalView, 69 | RectangularElevationBehavior): 70 | # md_bg_color = ListProperty((0, 0, 0, 0)) 71 | time = ObjectProperty() 72 | 73 | def __init__(self, **kwargs): 74 | super(MDTimePicker, self).__init__(**kwargs) 75 | self.current_time = self.ids.time_picker.time 76 | 77 | def set_time(self, time): 78 | try: 79 | self.ids.time_picker.set_time(time) 80 | except AttributeError: 81 | raise TypeError( 82 | 'MDTimePicker.set_time must receive a datetime object, ' 83 | 'not a "{}"'.format(type(time).__name__)) 84 | 85 | def close_cancel(self): 86 | self.dismiss() 87 | 88 | def close_ok(self): 89 | self.current_time = self.ids.time_picker.time 90 | self.time = self.current_time 91 | self.dismiss() 92 | -------------------------------------------------------------------------------- /kivymd/toolbar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from kivy.clock import Clock 3 | from kivy.factory import Factory 4 | from kivy.lang import Builder 5 | from kivy.metrics import dp 6 | from kivy.properties import ListProperty, OptionProperty, StringProperty 7 | from kivy.uix.boxlayout import BoxLayout 8 | 9 | from kivymd.backgroundcolorbehavior import SpecificBackgroundColorBehavior 10 | from kivymd.button import MDIconButton 11 | from kivymd.elevationbehavior import RectangularElevationBehavior 12 | from kivymd.theming import ThemableBehavior 13 | 14 | 15 | Builder.load_string(''' 16 | #:import m_res kivymd.material_resources 17 | 18 | size_hint_y: None 19 | height: root.theme_cls.standard_increment 20 | padding: [root.theme_cls.horizontal_margins - dp(12), 0] 21 | opposite_colors: True 22 | elevation: 6 23 | BoxLayout: 24 | id: left_actions 25 | orientation: 'horizontal' 26 | size_hint_x: None 27 | padding: [0, (self.height - dp(48))/2] 28 | BoxLayout: 29 | padding: dp(12), 0 30 | MDLabel: 31 | font_style: 'Title' 32 | opposite_colors: root.opposite_colors 33 | theme_text_color: 'Custom' 34 | text_color: root.specific_text_color 35 | text: root.title 36 | shorten: True 37 | shorten_from: 'right' 38 | BoxLayout: 39 | id: right_actions 40 | orientation: 'horizontal' 41 | size_hint_x: None 42 | padding: [0, (self.height - dp(48))/2] 43 | ''') 44 | 45 | 46 | class Toolbar(ThemableBehavior, RectangularElevationBehavior, 47 | SpecificBackgroundColorBehavior, BoxLayout): 48 | left_action_items = ListProperty() 49 | """The icons on the left of the Toolbar. 50 | 51 | To add one, append a list like the following: 52 | 53 | ['icon_name', callback] 54 | 55 | where 'icon_name' is a string that corresponds to an icon definition and 56 | callback is the function called on a touch release event. 57 | """ 58 | 59 | right_action_items = ListProperty() 60 | """The icons on the left of the Toolbar. 61 | 62 | Works the same way as :attr:`left_action_items` 63 | """ 64 | 65 | title = StringProperty() 66 | """The text displayed on the Toolbar.""" 67 | 68 | md_bg_color = ListProperty([0, 0, 0, 1]) 69 | 70 | def __init__(self, **kwargs): 71 | super(Toolbar, self).__init__(**kwargs) 72 | self.bind(specific_text_color=self.update_action_bar_text_colors) 73 | Clock.schedule_once( 74 | lambda x: self.on_left_action_items(0, self.left_action_items)) 75 | Clock.schedule_once( 76 | lambda x: self.on_right_action_items(0, 77 | self.right_action_items)) 78 | 79 | def on_left_action_items(self, instance, value): 80 | self.update_action_bar(self.ids['left_actions'], value) 81 | 82 | def on_right_action_items(self, instance, value): 83 | self.update_action_bar(self.ids['right_actions'], value) 84 | 85 | def update_action_bar(self, action_bar, action_bar_items): 86 | action_bar.clear_widgets() 87 | new_width = 0 88 | for item in action_bar_items: 89 | new_width += dp(48) 90 | action_bar.add_widget(MDIconButton(icon=item[0], 91 | on_release=item[1], 92 | opposite_colors=True, 93 | text_color=self.specific_text_color, 94 | theme_text_color='Custom')) 95 | action_bar.width = new_width 96 | 97 | def update_action_bar_text_colors(self, instance, value): 98 | for child in self.ids['left_actions'].children: 99 | child.text_color = self.specific_text_color 100 | for child in self.ids['right_actions'].children: 101 | child.text_color = self.specific_text_color 102 | -------------------------------------------------------------------------------- /kivymd/vendor/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /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 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | CircularLayout 6 | ============== 7 | 8 | CircularLayout is a special layout that places widgets around a circle. 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 vice versa), 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. 22 | """ 23 | 24 | from kivy.uix.layout import Layout 25 | from kivy.properties import NumericProperty, ReferenceListProperty, OptionProperty, \ 26 | BoundedNumericProperty, VariableListProperty, AliasProperty 27 | from math import sin, cos, pi, radians 28 | 29 | __all__ = ('CircularLayout') 30 | 31 | try: 32 | xrange(1, 2) 33 | except NameError: 34 | def xrange(first, second, third=None): 35 | if third: 36 | return range(first, second, third) 37 | else: 38 | return range(first, second) 39 | 40 | 41 | class CircularLayout(Layout): 42 | ''' 43 | Circular layout class. See module documentation for more information. 44 | ''' 45 | 46 | padding = VariableListProperty([0, 0, 0, 0]) 47 | '''Padding between the layout box and it's children: [padding_left, 48 | padding_top, padding_right, padding_bottom]. 49 | 50 | padding also accepts a two argument form [padding_horizontal, 51 | padding_vertical] and a one argument form [padding]. 52 | 53 | .. version changed:: 1.7.0 54 | Replaced NumericProperty with VariableListProperty. 55 | 56 | :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and 57 | defaults to [0, 0, 0, 0]. 58 | ''' 59 | 60 | start_angle = NumericProperty(0) 61 | '''Angle (in degrees) at which the first widget will be placed. 62 | Start counting angles from the X axis, going counterclockwise. 63 | 64 | :attr:`start_angle` is a :class:`~kivy.properties.NumericProperty` and 65 | defaults to 0 (start from the right). 66 | ''' 67 | 68 | circle_quota = BoundedNumericProperty(360, min=0, max=360) 69 | '''Size (in degrees) of the part of the circumference that will actually 70 | be used to place widgets. 71 | 72 | :attr:`circle_quota` is a :class:`~kivy.properties.BoundedNumericProperty` 73 | and defaults to 360 (all the circumference). 74 | ''' 75 | 76 | direction = OptionProperty("ccw", options=("cw", "ccw")) 77 | '''Direction of widgets in the circle. 78 | 79 | :attr:`direction` is an :class:`~kivy.properties.OptionProperty` and 80 | defaults to 'ccw'. Can be 'ccw' (counterclockwise) or 'cw' (clockwise). 81 | ''' 82 | 83 | outer_radius_hint = NumericProperty(1) 84 | '''Sets the size of the outer circle. A number greater than 1 will make the 85 | widgets larger than the actual widget, a number smaller than 1 will leave 86 | a gap. 87 | 88 | :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` and 89 | defaults to 1. 90 | ''' 91 | 92 | inner_radius_hint = NumericProperty(.6) 93 | '''Sets the size of the inner circle. A number greater than 94 | :attr:`outer_radius_hint` will cause glitches. The closest it is to 95 | :attr:`outer_radius_hint`, the smallest will be the widget in the layout. 96 | 97 | :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` and 98 | defaults to 1. 99 | ''' 100 | 101 | radius_hint = ReferenceListProperty(inner_radius_hint, outer_radius_hint) 102 | '''Combined :attr:`outer_radius_hint` and :attr:`inner_radius_hint` in a list 103 | for convenience. See their documentation for more details. 104 | 105 | :attr:`radius_hint` is a :class:`~kivy.properties.ReferenceListProperty`. 106 | ''' 107 | 108 | def _get_delta_radii(self): 109 | radius = min(self.width-self.padding[0]-self.padding[2], self.height-self.padding[1]-self.padding[3]) / 2. 110 | outer_r = radius * self.outer_radius_hint 111 | inner_r = radius * self.inner_radius_hint 112 | return outer_r - inner_r 113 | delta_radii = AliasProperty(_get_delta_radii, None, bind=("radius_hint", "padding", "size")) 114 | 115 | def __init__(self, **kwargs): 116 | super(CircularLayout, self).__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("size_hint_x cannot be None in a CircularLayout") 155 | else: 156 | stretch_weight_angle += sha 157 | 158 | sign = +1. 159 | angle_offset = start_angle_r 160 | if direction == 'cw': 161 | angle_offset = 2 * pi - start_angle_r 162 | sign = -1. 163 | 164 | for c in reversed(self.children): 165 | sha = c.size_hint_x 166 | shs = c.size_hint_y 167 | 168 | angle_quota = cquota / stretch_weight_angle * sha 169 | angle = angle_offset + (sign * angle_quota / 2) 170 | angle_offset += sign * angle_quota 171 | 172 | # kived: looking it up, yes. x = cos(angle) * radius + centerx; y = sin(angle) * radius + centery 173 | ccx = cos(angle) * middle_r + selfcx + padding_left - padding_right 174 | ccy = sin(angle) * middle_r + selfcy + padding_bottom - padding_top 175 | 176 | c.center_x = ccx 177 | c.center_y = ccy 178 | if shs: 179 | s = delta_r * shs 180 | c.width = s 181 | c.height = s 182 | 183 | if __name__ == "__main__": 184 | from kivy.app import App 185 | from kivy.uix.button import Button 186 | 187 | class CircLayoutApp(App): 188 | def build(self): 189 | cly = CircularLayout(direction="cw", start_angle=-75, inner_radius_hint=.7, padding="20dp") 190 | 191 | for i in xrange(1, 13): 192 | cly.add_widget(Button(text=str(i), font_size="30dp")) 193 | 194 | return cly 195 | 196 | CircLayoutApp().run() 197 | -------------------------------------------------------------------------------- /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 | Copyright (c) 2013 Alexander Taylor 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /kivymd/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '2019.0910' 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Kivy==1.11.1 2 | -------------------------------------------------------------------------------- /requirements/requirements-test.txt: -------------------------------------------------------------------------------- 1 | isort==4.2.5 2 | flake8==3.3.0 3 | mock==2.0.0 4 | pytest==4.3.0 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from setuptools import setup 4 | 5 | from kivymd.version import __version__ 6 | 7 | 8 | def read(fname): 9 | with open(os.path.join(os.path.dirname(__file__), fname)) as f: 10 | return f.read() 11 | 12 | 13 | setup_params = { 14 | 'name': 'kivy_garden.kivymd', 15 | 'version': __version__, 16 | 'description': "Set of widgets for Kivy inspired by Google's Material Design", 17 | 'long_description': read('README.md'), 18 | 'long_description_content_type': 'text/markdown', 19 | 'author': 'Andrés Rodríguez', 20 | 'author_email': 'andres.rodriguez@lithersoft.com', 21 | 'url': 'https://github.com/AndreMiras/KivyMD', 22 | 'packages': ['kivymd'], 23 | 'package_data': { 24 | 'kivymd': [ 25 | 'images/*.png', 26 | 'images/*.jpg', 27 | 'images/*.atlas', 28 | 'vendor/*.py', 29 | 'fonts/*.ttf', 'vendor/circleLayout/*.py', 30 | 'vendor/circularTimePicker/*.py', 31 | 'vendor/navigationdrawer/*.py', 32 | ] 33 | }, 34 | 'install_requires': ['kivy'], 35 | } 36 | 37 | 38 | def run_setup(): 39 | setup(**setup_params) 40 | 41 | 42 | # makes sure the setup doesn't run at import time 43 | if __name__ == '__main__': 44 | run_setup() 45 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = pep8,isort-check 3 | # no setup.py to be ran 4 | skipsdist = True 5 | 6 | [testenv:pep8] 7 | deps = -r{toxinidir}/requirements/requirements-test.txt 8 | commands = flake8 kivymd/ demos/ 9 | 10 | [testenv:isort-check] 11 | deps = -r{toxinidir}/requirements/requirements-test.txt 12 | -r{toxinidir}/requirements.txt 13 | commands = isort --check-only --recursive --diff kivymd/ demos/ 14 | 15 | [flake8] 16 | ignore = 17 | E111, E114, E115, E116, E202, E121, E123, E124, E225, E126, E127, E128, 18 | E129, E201, E221, E226, E241, E251, E265, E266, E271, E302, E305, 19 | E401, E402, E501, E502, E703, E722, E741, F401, F403, 20 | F812, F841, F811, W292, W503 21 | --------------------------------------------------------------------------------