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 |
44 |
50 |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------