experiment with markers
BIN
assets/images/marker.png
Normal file
After Width: | Height: | Size: 3 KiB |
|
@ -36,7 +36,7 @@ version = 0.1
|
|||
|
||||
# (list) Application requirements
|
||||
# comma separated e.g. requirements = sqlite3,kivy
|
||||
requirements = python3,requests,certifi,urllib3,chardet,idna,sqlite3,kivy,kivymd,mapview
|
||||
requirements = python3,requests,certifi,urllib3,chardet,idna,sqlite3,kivy,mapview
|
||||
|
||||
# (str) Custom source folders for requirements
|
||||
# Sets custom source for any requirements with recipes
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
lon: 24.747571
|
||||
zoom: 15
|
||||
on_zoom:
|
||||
self.zoom = 15 if self.zoom < 15 else self.zoom
|
||||
self.zoom = 5 if self.zoom < 5 else self.zoom
|
||||
on_lat:
|
||||
self.start_get_fov_trees()
|
||||
on_lon:
|
||||
|
|
|
@ -2,8 +2,11 @@ from kivy_garden.mapview import MapView
|
|||
from kivy.clock import Clock
|
||||
from kivy.app import App
|
||||
|
||||
from treemarker import TreeMarker
|
||||
|
||||
class ForestMapView(MapView):
|
||||
get_trees_timer = None
|
||||
tree_names = []
|
||||
|
||||
def start_get_fov_trees(self):
|
||||
# After one second get the trees in field of view
|
||||
|
@ -17,15 +20,31 @@ class ForestMapView(MapView):
|
|||
def get_fov_trees(self, *args):
|
||||
# Get reference to main app and the db cursor
|
||||
app = App.get_running_app()
|
||||
print(self.get_bbox()) # debug gps position
|
||||
# Gebug gps position
|
||||
#print(self.get_bbox())
|
||||
min_lat, min_lon, max_lat, max_lon = self.get_bbox()
|
||||
sql_statement = "SELECT * FROM locations WHERE x > %s AND x < %s AND y > %s AND y < %s" % (min_lat, max_lat, min_lon, max_lon) #sql_statement = "SELECT * FROM locations"
|
||||
app.cursor.execute(sql_statement)
|
||||
trees = app.cursor.fetchall()
|
||||
print(trees)
|
||||
for tree in trees:
|
||||
self.add_tree(tree)
|
||||
name = tree[0]
|
||||
print(tree)
|
||||
print("Tree detected")
|
||||
if name in self.tree_names:
|
||||
continue
|
||||
else:
|
||||
self.add_tree(tree)
|
||||
|
||||
def add_tree(self, tree):
|
||||
pass
|
||||
# Create TreeMarker
|
||||
name = tree[0]
|
||||
lat, lon = tree[1], tree[2]
|
||||
treemarker = TreeMarker(lat=lat, lon=lon, source='assets/images/marker.png')
|
||||
treemarker.tree_data = treemarker
|
||||
|
||||
# Add TreeMarker to the map
|
||||
self.add_widget(treemarker)
|
||||
|
||||
# Keep track of the TreeMarker's name
|
||||
|
||||
self.tree_names.append(name)
|
66
kivymd/__init__.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
"""
|
||||
KivyMD
|
||||
======
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/previous.png
|
||||
|
||||
Is a collection of Material Design compliant widgets for use with,
|
||||
`Kivy cross-platform graphical framework <http://kivy.org/#home>`_
|
||||
a framework for cross-platform, touch-enabled graphical applications.
|
||||
The project's goal is to approximate Google's `Material Design spec
|
||||
<https://material.io/design/introduction>`_ as close as possible without
|
||||
sacrificing ease of use or application performance.
|
||||
|
||||
This library is a fork of the `KivyMD project
|
||||
<https://gitlab.com/kivymd/KivyMD>`_ the author of which stopped supporting
|
||||
this project three years ago. We found the strength and brought this project
|
||||
to a new level. Currently we're in **beta** 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.
|
||||
|
||||
Join the project! Just fork the project, branch out and submit a pull request
|
||||
when your patch is ready. If any changes are necessary, we'll guide you
|
||||
through the steps that need to be done via PR comments or access to your for
|
||||
may be requested to outright submit them. If you wish to become a project
|
||||
developer (permission to create branches on the project without forking for
|
||||
easier collaboration), have at least one PR approved and ask for it.
|
||||
If you contribute regularly to the project the role may be offered to you
|
||||
without asking too.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from kivy.logger import Logger
|
||||
|
||||
__version__ = "0.104.2.dev0"
|
||||
"""KivyMD version."""
|
||||
|
||||
release = False
|
||||
|
||||
try:
|
||||
from kivymd._version import __hash__, __short_hash__, __date__
|
||||
except ImportError:
|
||||
__hash__ = __short_hash__ = __date__ = ""
|
||||
|
||||
path = os.path.dirname(__file__)
|
||||
"""Path to KivyMD package directory."""
|
||||
|
||||
fonts_path = os.path.join(path, f"fonts{os.sep}")
|
||||
"""Path to fonts directory."""
|
||||
|
||||
images_path = os.path.join(path, f"images{os.sep}")
|
||||
"""Path to images directory."""
|
||||
|
||||
_log_message = (
|
||||
"KivyMD:"
|
||||
+ (" Release" if release else "")
|
||||
+ f" {__version__}"
|
||||
+ (f", git-{__short_hash__}" if __short_hash__ else "")
|
||||
+ (f", {__date__}" if __date__ else "")
|
||||
+ f' (installed at "{__file__}")'
|
||||
)
|
||||
Logger.info(_log_message)
|
||||
|
||||
import kivymd.factory_registers # NOQA
|
||||
import kivymd.font_definitions # NOQA
|
||||
from kivymd.tools.packaging.pyinstaller import hooks_path # NOQA
|
90
kivymd/app.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
"""
|
||||
Themes/Material App
|
||||
===================
|
||||
|
||||
This module contains :class:`MDApp` class that is inherited from
|
||||
:class:`~kivy.app.App`. :class:`MDApp` has some properties needed for ``KivyMD``
|
||||
library (like :attr:`~MDApp.theme_cls`).
|
||||
|
||||
You can turn on the monitor displaying the current ``FPS`` value in your application:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDLabel:
|
||||
text: "Hello, World!"
|
||||
halign: "center"
|
||||
'''
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def on_start(self):
|
||||
self.fps_monitor_start()
|
||||
|
||||
|
||||
MainApp().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fps-monitor.png
|
||||
:width: 350 px
|
||||
:align: center
|
||||
|
||||
"""
|
||||
|
||||
__all__ = ("MDApp",)
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.properties import ObjectProperty
|
||||
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
|
||||
class FpsMonitoring:
|
||||
"""Adds a monitor to display the current FPS in the toolbar."""
|
||||
|
||||
def fps_monitor_start(self):
|
||||
from kivymd.utils.fpsmonitor import FpsMonitor
|
||||
from kivy.core.window import Window
|
||||
|
||||
monitor = FpsMonitor()
|
||||
monitor.start()
|
||||
Window.add_widget(monitor)
|
||||
|
||||
|
||||
class MDApp(App, FpsMonitoring):
|
||||
theme_cls = ObjectProperty()
|
||||
"""
|
||||
Instance of :class:`~ThemeManager` class.
|
||||
|
||||
.. Warning:: The :attr:`~theme_cls` attribute is already available
|
||||
in a class that is inherited from the :class:`~MDApp` class.
|
||||
The following code will result in an error!
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MainApp(MDApp):
|
||||
theme_cls = ThemeManager()
|
||||
theme_cls.primary_palette = "Teal"
|
||||
|
||||
.. Note:: Correctly do as shown below!
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.primary_palette = "Teal"
|
||||
|
||||
:attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.theme_cls = ThemeManager()
|
944
kivymd/color_definitions.py
Executable file
|
@ -0,0 +1,944 @@
|
|||
"""
|
||||
Themes/Color Definitions
|
||||
========================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, The color system <https://material.io/design/color/the-color-system.html>`_
|
||||
|
||||
Material colors palette to use in :class:`kivymd.theming.ThemeManager`.
|
||||
:data:`~colors` is a dict-in-dict where the first key is a value from
|
||||
:data:`~palette` and the second key is a value from :data:`~hue`. Color is a hex
|
||||
value, a string of 6 characters (0-9, A-F) written in uppercase.
|
||||
|
||||
For example, ``colors["Red"]["900"]`` is ``"B71C1C"``.
|
||||
"""
|
||||
|
||||
colors = {
|
||||
"Red": {
|
||||
"50": "FFEBEE",
|
||||
"100": "FFCDD2",
|
||||
"200": "EF9A9A",
|
||||
"300": "E57373",
|
||||
"400": "EF5350",
|
||||
"500": "F44336",
|
||||
"600": "E53935",
|
||||
"700": "D32F2F",
|
||||
"800": "C62828",
|
||||
"900": "B71C1C",
|
||||
"A100": "FF8A80",
|
||||
"A200": "FF5252",
|
||||
"A400": "FF1744",
|
||||
"A700": "D50000",
|
||||
},
|
||||
"Pink": {
|
||||
"50": "FCE4EC",
|
||||
"100": "F8BBD0",
|
||||
"200": "F48FB1",
|
||||
"300": "F06292",
|
||||
"400": "EC407A",
|
||||
"500": "E91E63",
|
||||
"600": "D81B60",
|
||||
"700": "C2185B",
|
||||
"800": "AD1457",
|
||||
"900": "880E4F",
|
||||
"A100": "FF80AB",
|
||||
"A200": "FF4081",
|
||||
"A400": "F50057",
|
||||
"A700": "C51162",
|
||||
},
|
||||
"Purple": {
|
||||
"50": "F3E5F5",
|
||||
"100": "E1BEE7",
|
||||
"200": "CE93D8",
|
||||
"300": "BA68C8",
|
||||
"400": "AB47BC",
|
||||
"500": "9C27B0",
|
||||
"600": "8E24AA",
|
||||
"700": "7B1FA2",
|
||||
"800": "6A1B9A",
|
||||
"900": "4A148C",
|
||||
"A100": "EA80FC",
|
||||
"A200": "E040FB",
|
||||
"A400": "D500F9FF",
|
||||
},
|
||||
"DeepPurple": {
|
||||
"50": "EDE7F6",
|
||||
"100": "D1C4E9",
|
||||
"200": "B39DDB",
|
||||
"300": "9575CD",
|
||||
"400": "7E57C2",
|
||||
"500": "673AB7",
|
||||
"600": "5E35B1",
|
||||
"700": "512DA8",
|
||||
"800": "4527A0",
|
||||
"900": "311B92",
|
||||
"A100": "B388FF",
|
||||
"A200": "7C4DFF",
|
||||
"A400": "651FFF",
|
||||
"A700": "6200EA",
|
||||
},
|
||||
"Indigo": {
|
||||
"50": "E8EAF6",
|
||||
"100": "C5CAE9",
|
||||
"200": "9FA8DA",
|
||||
"300": "7986CB",
|
||||
"400": "5C6BC0",
|
||||
"500": "3F51B5",
|
||||
"600": "3949AB",
|
||||
"700": "303F9F",
|
||||
"800": "283593",
|
||||
"900": "1A237E",
|
||||
"A100": "8C9EFF",
|
||||
"A200": "536DFE",
|
||||
"A400": "3D5AFE",
|
||||
"A700": "304FFE",
|
||||
},
|
||||
"Blue": {
|
||||
"50": "E3F2FD",
|
||||
"100": "BBDEFB",
|
||||
"200": "90CAF9",
|
||||
"300": "64B5F6",
|
||||
"400": "42A5F5",
|
||||
"500": "2196F3",
|
||||
"600": "1E88E5",
|
||||
"700": "1976D2",
|
||||
"800": "1565C0",
|
||||
"900": "0D47A1",
|
||||
"A100": "82B1FF",
|
||||
"A200": "448AFF",
|
||||
"A400": "2979FF",
|
||||
"A700": "2962FF",
|
||||
},
|
||||
"LightBlue": {
|
||||
"50": "E1F5FE",
|
||||
"100": "B3E5FC",
|
||||
"200": "81D4FA",
|
||||
"300": "4FC3F7",
|
||||
"400": "29B6F6",
|
||||
"500": "03A9F4",
|
||||
"600": "039BE5",
|
||||
"700": "0288D1",
|
||||
"800": "0277BD",
|
||||
"900": "01579B",
|
||||
"A100": "80D8FF",
|
||||
"A200": "40C4FF",
|
||||
"A400": "00B0FF",
|
||||
"A700": "0091EA",
|
||||
},
|
||||
"Cyan": {
|
||||
"50": "E0F7FA",
|
||||
"100": "B2EBF2",
|
||||
"200": "80DEEA",
|
||||
"300": "4DD0E1",
|
||||
"400": "26C6DA",
|
||||
"500": "00BCD4",
|
||||
"600": "00ACC1",
|
||||
"700": "0097A7",
|
||||
"800": "00838F",
|
||||
"900": "006064",
|
||||
"A100": "84FFFF",
|
||||
"A200": "18FFFF",
|
||||
"A400": "00E5FF",
|
||||
"A700": "00B8D4",
|
||||
},
|
||||
"Teal": {
|
||||
"50": "E0F2F1",
|
||||
"100": "B2DFDB",
|
||||
"200": "80CBC4",
|
||||
"300": "4DB6AC",
|
||||
"400": "26A69A",
|
||||
"500": "009688",
|
||||
"600": "00897B",
|
||||
"700": "00796B",
|
||||
"800": "00695C",
|
||||
"900": "004D40",
|
||||
"A100": "A7FFEB",
|
||||
"A200": "64FFDA",
|
||||
"A400": "1DE9B6",
|
||||
"A700": "00BFA5",
|
||||
},
|
||||
"Green": {
|
||||
"50": "E8F5E9",
|
||||
"100": "C8E6C9",
|
||||
"200": "A5D6A7",
|
||||
"300": "81C784",
|
||||
"400": "66BB6A",
|
||||
"500": "4CAF50",
|
||||
"600": "43A047",
|
||||
"700": "388E3C",
|
||||
"800": "2E7D32",
|
||||
"900": "1B5E20",
|
||||
"A100": "B9F6CA",
|
||||
"A200": "69F0AE",
|
||||
"A400": "00E676",
|
||||
"A700": "00C853",
|
||||
},
|
||||
"LightGreen": {
|
||||
"50": "F1F8E9",
|
||||
"100": "DCEDC8",
|
||||
"200": "C5E1A5",
|
||||
"300": "AED581",
|
||||
"400": "9CCC65",
|
||||
"500": "8BC34A",
|
||||
"600": "7CB342",
|
||||
"700": "689F38",
|
||||
"800": "558B2F",
|
||||
"900": "33691E",
|
||||
"A100": "CCFF90",
|
||||
"A200": "B2FF59",
|
||||
"A400": "76FF03",
|
||||
"A700": "64DD17",
|
||||
},
|
||||
"Lime": {
|
||||
"50": "F9FBE7",
|
||||
"100": "F0F4C3",
|
||||
"200": "E6EE9C",
|
||||
"300": "DCE775",
|
||||
"400": "D4E157",
|
||||
"500": "CDDC39",
|
||||
"600": "C0CA33",
|
||||
"700": "AFB42B",
|
||||
"800": "9E9D24",
|
||||
"900": "827717",
|
||||
"A100": "F4FF81",
|
||||
"A200": "EEFF41",
|
||||
"A400": "C6FF00",
|
||||
"A700": "AEEA00",
|
||||
},
|
||||
"Yellow": {
|
||||
"50": "FFFDE7",
|
||||
"100": "FFF9C4",
|
||||
"200": "FFF59D",
|
||||
"300": "FFF176",
|
||||
"400": "FFEE58",
|
||||
"500": "FFEB3B",
|
||||
"600": "FDD835",
|
||||
"700": "FBC02D",
|
||||
"800": "F9A825",
|
||||
"900": "F57F17",
|
||||
"A100": "FFFF8D",
|
||||
"A200": "FFFF00",
|
||||
"A400": "FFEA00",
|
||||
"A700": "FFD600",
|
||||
},
|
||||
"Amber": {
|
||||
"50": "FFF8E1",
|
||||
"100": "FFECB3",
|
||||
"200": "FFE082",
|
||||
"300": "FFD54F",
|
||||
"400": "FFCA28",
|
||||
"500": "FFC107",
|
||||
"600": "FFB300",
|
||||
"700": "FFA000",
|
||||
"800": "FF8F00",
|
||||
"900": "FF6F00",
|
||||
"A100": "FFE57F",
|
||||
"A200": "FFD740",
|
||||
"A400": "FFC400",
|
||||
"A700": "FFAB00",
|
||||
},
|
||||
"Orange": {
|
||||
"50": "FFF3E0",
|
||||
"100": "FFE0B2",
|
||||
"200": "FFCC80",
|
||||
"300": "FFB74D",
|
||||
"400": "FFA726",
|
||||
"500": "FF9800",
|
||||
"600": "FB8C00",
|
||||
"700": "F57C00",
|
||||
"800": "EF6C00",
|
||||
"900": "E65100",
|
||||
"A100": "FFD180",
|
||||
"A200": "FFAB40",
|
||||
"A400": "FF9100",
|
||||
"A700": "FF6D00",
|
||||
},
|
||||
"DeepOrange": {
|
||||
"50": "FBE9E7",
|
||||
"100": "FFCCBC",
|
||||
"200": "FFAB91",
|
||||
"300": "FF8A65",
|
||||
"400": "FF7043",
|
||||
"500": "FF5722",
|
||||
"600": "F4511E",
|
||||
"700": "E64A19",
|
||||
"800": "D84315",
|
||||
"900": "BF360C",
|
||||
"A100": "FF9E80",
|
||||
"A200": "FF6E40",
|
||||
"A400": "FF3D00",
|
||||
"A700": "DD2C00",
|
||||
},
|
||||
"Brown": {
|
||||
"50": "EFEBE9",
|
||||
"100": "D7CCC8",
|
||||
"200": "BCAAA4",
|
||||
"300": "A1887F",
|
||||
"400": "8D6E63",
|
||||
"500": "795548",
|
||||
"600": "6D4C41",
|
||||
"700": "5D4037",
|
||||
"800": "4E342E",
|
||||
"900": "3E2723",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"Gray": {
|
||||
"50": "FAFAFA",
|
||||
"100": "F5F5F5",
|
||||
"200": "EEEEEE",
|
||||
"300": "E0E0E0",
|
||||
"400": "BDBDBD",
|
||||
"500": "9E9E9E",
|
||||
"600": "757575",
|
||||
"700": "616161",
|
||||
"800": "424242",
|
||||
"900": "212121",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"BlueGray": {
|
||||
"50": "ECEFF1",
|
||||
"100": "CFD8DC",
|
||||
"200": "B0BEC5",
|
||||
"300": "90A4AE",
|
||||
"400": "78909C",
|
||||
"500": "607D8B",
|
||||
"600": "546E7A",
|
||||
"700": "455A64",
|
||||
"800": "37474F",
|
||||
"900": "263238",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"Light": {
|
||||
"StatusBar": "E0E0E0",
|
||||
"AppBar": "F5F5F5",
|
||||
"Background": "FAFAFA",
|
||||
"CardsDialogs": "FFFFFF",
|
||||
"FlatButtonDown": "cccccc",
|
||||
},
|
||||
"Dark": {
|
||||
"StatusBar": "000000",
|
||||
"AppBar": "1f1f1f",
|
||||
"Background": "121212",
|
||||
"CardsDialogs": "212121",
|
||||
"FlatButtonDown": "999999",
|
||||
},
|
||||
}
|
||||
"""Color palette. Taken from `2014 Material Design color palettes
|
||||
<https://material.io/design/color/the-color-system.html>`_.
|
||||
|
||||
To demonstrate the shades of the palette, you can run the following code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivy.properties import ListProperty, StringProperty
|
||||
|
||||
from kivymd.color_definitions import colors
|
||||
from kivymd.uix.tab import MDTabsBase
|
||||
|
||||
demo = '''
|
||||
<Root@BoxLayout>
|
||||
orientation: 'vertical'
|
||||
|
||||
MDToolbar:
|
||||
title: app.title
|
||||
|
||||
MDTabs:
|
||||
id: android_tabs
|
||||
on_tab_switch: app.on_tab_switch(*args)
|
||||
size_hint_y: None
|
||||
height: "48dp"
|
||||
tab_indicator_anim: False
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDList:
|
||||
id: box
|
||||
|
||||
|
||||
<ItemColor>:
|
||||
size_hint_y: None
|
||||
height: "42dp"
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
MDLabel:
|
||||
text: root.text
|
||||
halign: "center"
|
||||
|
||||
|
||||
<Tab>:
|
||||
'''
|
||||
|
||||
from kivy.factory import Factory
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
class Tab(BoxLayout, MDTabsBase):
|
||||
pass
|
||||
|
||||
|
||||
class ItemColor(BoxLayout):
|
||||
text = StringProperty()
|
||||
color = ListProperty()
|
||||
|
||||
|
||||
class Palette(MDApp):
|
||||
title = "Colors definitions"
|
||||
|
||||
def build(self):
|
||||
Builder.load_string(demo)
|
||||
self.screen = Factory.Root()
|
||||
|
||||
for name_tab in colors.keys():
|
||||
tab = Tab(text=name_tab)
|
||||
self.screen.ids.android_tabs.add_widget(tab)
|
||||
return self.screen
|
||||
|
||||
def on_tab_switch(self, instance_tabs, instance_tab, instance_tabs_label, tab_text):
|
||||
self.screen.ids.box.clear_widgets()
|
||||
for value_color in colors[tab_text]:
|
||||
self.screen.ids.box.add_widget(
|
||||
ItemColor(
|
||||
color=get_color_from_hex(colors[tab_text][value_color]),
|
||||
text=value_color,
|
||||
)
|
||||
)
|
||||
|
||||
def on_start(self):
|
||||
self.on_tab_switch(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
self.screen.ids.android_tabs.ids.layout.children[-1].text,
|
||||
)
|
||||
|
||||
|
||||
Palette().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/palette.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
palette = [
|
||||
"Red",
|
||||
"Pink",
|
||||
"Purple",
|
||||
"DeepPurple",
|
||||
"Indigo",
|
||||
"Blue",
|
||||
"LightBlue",
|
||||
"Cyan",
|
||||
"Teal",
|
||||
"Green",
|
||||
"LightGreen",
|
||||
"Lime",
|
||||
"Yellow",
|
||||
"Amber",
|
||||
"Orange",
|
||||
"DeepOrange",
|
||||
"Brown",
|
||||
"Gray",
|
||||
"BlueGray",
|
||||
]
|
||||
"""Valid values for color palette selecting."""
|
||||
|
||||
hue = [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"600",
|
||||
"700",
|
||||
"800",
|
||||
"900",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
"A700",
|
||||
]
|
||||
"""Valid values for color hue selecting."""
|
||||
|
||||
|
||||
light_colors = {
|
||||
"Red": ["50", "100", "200", "300", "A100"],
|
||||
"Pink": ["50", "100", "200", "A100"],
|
||||
"Purple": ["50", "100", "200", "A100"],
|
||||
"DeepPurple": ["50", "100", "200", "A100"],
|
||||
"Indigo": ["50", "100", "200", "A100"],
|
||||
"Blue": ["50", "100", "200", "300", "400", "A100"],
|
||||
"LightBlue": [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
],
|
||||
"Cyan": [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"600",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
"A700",
|
||||
],
|
||||
"Teal": ["50", "100", "200", "300", "400", "A100", "A200", "A400", "A700"],
|
||||
"Green": [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
"A700",
|
||||
],
|
||||
"LightGreen": [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"600",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
"A700",
|
||||
],
|
||||
"Lime": [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"600",
|
||||
"700",
|
||||
"800",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
"A700",
|
||||
],
|
||||
"Yellow": [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"600",
|
||||
"700",
|
||||
"800",
|
||||
"900",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
"A700",
|
||||
],
|
||||
"Amber": [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"600",
|
||||
"700",
|
||||
"800",
|
||||
"900",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
"A700",
|
||||
],
|
||||
"Orange": [
|
||||
"50",
|
||||
"100",
|
||||
"200",
|
||||
"300",
|
||||
"400",
|
||||
"500",
|
||||
"600",
|
||||
"700",
|
||||
"A100",
|
||||
"A200",
|
||||
"A400",
|
||||
"A700",
|
||||
],
|
||||
"DeepOrange": ["50", "100", "200", "300", "400", "A100", "A200"],
|
||||
"Brown": ["50", "100", "200"],
|
||||
"Gray": ["51", "100", "200", "300", "400", "500"],
|
||||
"BlueGray": ["50", "100", "200", "300"],
|
||||
"Dark": [],
|
||||
"Light": ["White", "MainBackground", "DialogBackground"],
|
||||
}
|
||||
"""Which colors are light. Other are dark."""
|
||||
|
||||
text_colors = {
|
||||
"Red": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "FFFFFF",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"Pink": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "FFFFFF",
|
||||
"400": "FFFFFF",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"Purple": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "FFFFFF",
|
||||
"400": "FFFFFF",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"DeepPurple": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "FFFFFF",
|
||||
"400": "FFFFFF",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"Indigo": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "FFFFFF",
|
||||
"400": "FFFFFF",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"Blue": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"LightBlue": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"Cyan": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "000000",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"Teal": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"Green": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"LightGreen": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "000000",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"Lime": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "000000",
|
||||
"700": "000000",
|
||||
"800": "000000",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"Yellow": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "000000",
|
||||
"700": "000000",
|
||||
"800": "000000",
|
||||
"900": "000000",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"Amber": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "000000",
|
||||
"700": "000000",
|
||||
"800": "000000",
|
||||
"900": "000000",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"Orange": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "000000",
|
||||
"700": "000000",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "000000",
|
||||
"A700": "000000",
|
||||
},
|
||||
"DeepOrange": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "000000",
|
||||
"A200": "000000",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"Brown": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "FFFFFF",
|
||||
"400": "FFFFFF",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "FFFFFF",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"Gray": {
|
||||
"50": "FFFFFF",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "000000",
|
||||
"500": "000000",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "FFFFFF",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
"BlueGray": {
|
||||
"50": "000000",
|
||||
"100": "000000",
|
||||
"200": "000000",
|
||||
"300": "000000",
|
||||
"400": "FFFFFF",
|
||||
"500": "FFFFFF",
|
||||
"600": "FFFFFF",
|
||||
"700": "FFFFFF",
|
||||
"800": "FFFFFF",
|
||||
"900": "FFFFFF",
|
||||
"A100": "FFFFFF",
|
||||
"A200": "FFFFFF",
|
||||
"A400": "FFFFFF",
|
||||
"A700": "FFFFFF",
|
||||
},
|
||||
}
|
||||
"""Text colors generated from :data:`~light_colors`. "000000" for light and
|
||||
"FFFFFF" for dark.
|
||||
|
||||
How to generate text_colors dict
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
text_colors = {}
|
||||
for p in palette:
|
||||
text_colors[p] = {}
|
||||
for h in hue:
|
||||
if h in light_colors[p]:
|
||||
text_colors[p][h] = "000000"
|
||||
else:
|
||||
text_colors[p][h] = "FFFFFF"
|
||||
"""
|
||||
|
||||
theme_colors = [
|
||||
"Primary",
|
||||
"Secondary",
|
||||
"Background",
|
||||
"Surface",
|
||||
"Error",
|
||||
"On_Primary",
|
||||
"On_Secondary",
|
||||
"On_Background",
|
||||
"On_Surface",
|
||||
"On_Error",
|
||||
]
|
||||
"""Valid theme colors."""
|
113
kivymd/factory_registers.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
"""
|
||||
Register KivyMD widgets to use without import
|
||||
"""
|
||||
|
||||
from kivy.factory import Factory
|
||||
|
||||
r = Factory.register
|
||||
r("MDCarousel", module="kivymd.uix.carousel")
|
||||
r("MDFloatLayout", module="kivymd.uix.floatlayout")
|
||||
r("MDScreen", module="kivymd.uix.screen")
|
||||
r("MDBoxLayout", module="kivymd.uix.boxlayout")
|
||||
r("MDRelativeLayout", module="kivymd.uix.relativelayout")
|
||||
r("MDGridLayout", module="kivymd.uix.gridlayout")
|
||||
r("MDStackLayout", module="kivymd.uix.stacklayout")
|
||||
r("MDExpansionPanel", module="kivymd.uix.expansionpanel")
|
||||
r("MDExpansionPanelOneLine", module="kivymd.uix.expansionpanel")
|
||||
r("MDExpansionPanelTwoLine", module="kivymd.uix.expansionpanel")
|
||||
r("MDExpansionPanelThreeLine", module="kivymd.uix.expansionpanel")
|
||||
r("FitImage", module="kivymd.utils.fitimage")
|
||||
r("MDBackdrop", module="kivymd.uix.backdrop")
|
||||
r("MDBanner", module="kivymd.uix.banner")
|
||||
r("MDTooltip", module="kivymd.uix.tooltip")
|
||||
r("MDBottomNavigation", module="kivymd.uix.bottomnavigation")
|
||||
r("MDBottomNavigationItem", module="kivymd.uix.bottomnavigation")
|
||||
r("MDBottomNavigationHeader", module="kivymd.uix.bottomnavigation")
|
||||
r("MDBottomNavigationBar", module="kivymd.uix.bottomnavigation")
|
||||
r("MDTab", module="kivymd.uix.bottomnavigation")
|
||||
r("MDBottomSheet", module="kivymd.uix.bottomsheet")
|
||||
r("MDListBottomSheet", module="kivymd.uix.bottomsheet")
|
||||
r("MDGridBottomSheet", module="kivymd.uix.bottomsheet")
|
||||
r("MDFloatingActionButtonSpeedDial", module="kivymd.uix.button")
|
||||
r("MDIconButton", module="kivymd.uix.button")
|
||||
r("MDRoundImageButton", module="kivymd.uix.button")
|
||||
r("MDFlatButton", module="kivymd.uix.button")
|
||||
r("MDRaisedButton", module="kivymd.uix.button")
|
||||
r("MDFloatingActionButton", module="kivymd.uix.button")
|
||||
r("MDRectangleFlatButton", module="kivymd.uix.button")
|
||||
r("MDTextButton", module="kivymd.uix.button")
|
||||
r("MDCustomRoundIconButton", module="kivymd.uix.button")
|
||||
r("MDRoundFlatButton", module="kivymd.uix.button")
|
||||
r("MDFillRoundFlatButton", module="kivymd.uix.button")
|
||||
r("MDRectangleFlatIconButton", module="kivymd.uix.button")
|
||||
r("MDRoundFlatIconButton", module="kivymd.uix.button")
|
||||
r("MDFillRoundFlatIconButton", module="kivymd.uix.button")
|
||||
r("MDCard", module="kivymd.uix.card")
|
||||
r("MDSeparator", module="kivymd.uix.card")
|
||||
r("MDChip", module="kivymd.uix.chip")
|
||||
r("MDChooseChip", module="kivymd.uix.chip")
|
||||
r("MDDialog", module="kivymd.uix.dialog")
|
||||
r("MDInputDialog", module="kivymd.uix.dialog")
|
||||
r("MDFileManager", module="kivymd.uix.filemanager")
|
||||
r("Tile", module="kivymd.uix.imagelist")
|
||||
r("SmartTile", module="kivymd.uix.imagelist")
|
||||
r("SmartTileWithLabel", module="kivymd.uix.imagelist")
|
||||
r("SmartTileWithStar", module="kivymd.uix.imagelist")
|
||||
r("MDLabel", module="kivymd.uix.label")
|
||||
r("MDIcon", module="kivymd.uix.label")
|
||||
r("MDList", module="kivymd.uix.list")
|
||||
r("ILeftBody", module="kivymd.uix.list")
|
||||
r("ILeftBodyTouch", module="kivymd.uix.list")
|
||||
r("IRightBody", module="kivymd.uix.list")
|
||||
r("IRightBodyTouch", module="kivymd.uix.list")
|
||||
r("ContainerSupport", module="kivymd.uix.list")
|
||||
r("OneLineListItem", module="kivymd.uix.list")
|
||||
r("TwoLineListItem", module="kivymd.uix.list")
|
||||
r("ThreeLineListItem", module="kivymd.uix.list")
|
||||
r("OneLineAvatarListItem", module="kivymd.uix.list")
|
||||
r("TwoLineAvatarListItem", module="kivymd.uix.list")
|
||||
r("ThreeLineAvatarListItem", module="kivymd.uix.list")
|
||||
r("OneLineIconListItem", module="kivymd.uix.list")
|
||||
r("TwoLineIconListItem", module="kivymd.uix.list")
|
||||
r("ThreeLineIconListItem", module="kivymd.uix.list")
|
||||
r("OneLineRightIconListItem", module="kivymd.uix.list")
|
||||
r("TwoLineRightIconListItem", module="kivymd.uix.list")
|
||||
r("ThreeLineRightIconListItem", module="kivymd.uix.list")
|
||||
r("OneLineAvatarIconListItem", module="kivymd.uix.list")
|
||||
r("TwoLineAvatarIconListItem", module="kivymd.uix.list")
|
||||
r("ThreeLineAvatarIconListItem", module="kivymd.uix.list")
|
||||
r("MDMenu", module="kivymd.uix.menu")
|
||||
r("MDDropdownMenu", module="kivymd.uix.menu")
|
||||
r("MDContextMenu", module="kivymd.uix.context_menu")
|
||||
r("MDMenuItem", module="kivymd.uix.menu")
|
||||
r("HoverBehavior", module="kivymd.uix.behaviors.hover_behavior")
|
||||
r("FocusBehavior", module="kivymd.uix.behaviors.hover_behavior")
|
||||
r("MDNavigationDrawer", module="kivymd.uix.navigationdrawer")
|
||||
r("NavigationLayout", module="kivymd.uix.navigationdrawer")
|
||||
r("MDDatePicker", module="kivymd.uix.picker")
|
||||
r("MDTimePicker", module="kivymd.uix.picker")
|
||||
r("MDThemePicker", module="kivymd.uix.picker")
|
||||
r("MDProgressBar", module="kivymd.uix.progressbar")
|
||||
r("MDProgressLoader", module="kivymd.uix.progressloader")
|
||||
r("MDScrollViewRefreshLayout", module="kivymd.uix.refreshlayout")
|
||||
r("MDCheckbox", module="kivymd.uix.selectioncontrol")
|
||||
r("Thumb", module="kivymd.uix.selectioncontrol")
|
||||
r("MDSwitch", module="kivymd.uix.selectioncontrol")
|
||||
r("MDSlider", module="kivymd.uix.slider")
|
||||
r("Snackbar", module="kivymd.uix.snackbar")
|
||||
r("MDSpinner", module="kivymd.uix.spinner")
|
||||
r("MDFloatingLabel", module="kivymd.uix.tab")
|
||||
r("MDTabsLabel", module="kivymd.uix.tab")
|
||||
r("MDTabsBase", module="kivymd.uix.tab")
|
||||
r("MDTabsMain", module="kivymd.uix.tab")
|
||||
r("MDTabsCarousel", module="kivymd.uix.tab")
|
||||
r("MDTabsScrollView", module="kivymd.uix.tab")
|
||||
r("MDTabsBar", module="kivymd.uix.tab")
|
||||
r("MDTabs", module="kivymd.uix.tab")
|
||||
r("MDTextField", module="kivymd.uix.textfield")
|
||||
r("MDTextField", module="kivymd.uix.textfield")
|
||||
r("MDTextFieldRound", module="kivymd.uix.textfield")
|
||||
r("MDTextFieldRect", module="kivymd.uix.textfield")
|
||||
r("MDToolbar", module="kivymd.uix.toolbar")
|
||||
r("MDBottomAppBar", module="kivymd.uix.toolbar")
|
||||
r("MDDropDownItem", module="kivymd.uix.dropdownitem")
|
69
kivymd/font_definitions.py
Executable file
|
@ -0,0 +1,69 @@
|
|||
"""
|
||||
Themes/Font Definitions
|
||||
=======================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, The type system <https://material.io/design/typography/the-type-system.html>`_
|
||||
"""
|
||||
|
||||
from kivy.core.text import LabelBase
|
||||
|
||||
from kivymd import fonts_path
|
||||
|
||||
fonts = [
|
||||
{
|
||||
"name": "Roboto",
|
||||
"fn_regular": fonts_path + "Roboto-Regular.ttf",
|
||||
"fn_bold": fonts_path + "Roboto-Bold.ttf",
|
||||
"fn_italic": fonts_path + "Roboto-Italic.ttf",
|
||||
"fn_bolditalic": fonts_path + "Roboto-BoldItalic.ttf",
|
||||
},
|
||||
{
|
||||
"name": "RobotoThin",
|
||||
"fn_regular": fonts_path + "Roboto-Thin.ttf",
|
||||
"fn_italic": fonts_path + "Roboto-ThinItalic.ttf",
|
||||
},
|
||||
{
|
||||
"name": "RobotoLight",
|
||||
"fn_regular": fonts_path + "Roboto-Light.ttf",
|
||||
"fn_italic": fonts_path + "Roboto-LightItalic.ttf",
|
||||
},
|
||||
{
|
||||
"name": "RobotoMedium",
|
||||
"fn_regular": fonts_path + "Roboto-Medium.ttf",
|
||||
"fn_italic": fonts_path + "Roboto-MediumItalic.ttf",
|
||||
},
|
||||
{
|
||||
"name": "RobotoBlack",
|
||||
"fn_regular": fonts_path + "Roboto-Black.ttf",
|
||||
"fn_italic": fonts_path + "Roboto-BlackItalic.ttf",
|
||||
},
|
||||
{
|
||||
"name": "Icons",
|
||||
"fn_regular": fonts_path + "materialdesignicons-webfont.ttf",
|
||||
},
|
||||
]
|
||||
|
||||
for font in fonts:
|
||||
LabelBase.register(**font)
|
||||
|
||||
theme_font_styles = [
|
||||
"H1",
|
||||
"H2",
|
||||
"H3",
|
||||
"H4",
|
||||
"H5",
|
||||
"H6",
|
||||
"Subtitle1",
|
||||
"Subtitle2",
|
||||
"Body1",
|
||||
"Body2",
|
||||
"Button",
|
||||
"Caption",
|
||||
"Overline",
|
||||
"Icon",
|
||||
]
|
||||
"""
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles-2.png
|
||||
"""
|
BIN
kivymd/fonts/Roboto-Black.ttf
Normal file
BIN
kivymd/fonts/Roboto-BlackItalic.ttf
Normal file
BIN
kivymd/fonts/Roboto-Bold.ttf
Normal file
BIN
kivymd/fonts/Roboto-BoldItalic.ttf
Normal file
BIN
kivymd/fonts/Roboto-Italic.ttf
Normal file
BIN
kivymd/fonts/Roboto-Light.ttf
Normal file
BIN
kivymd/fonts/Roboto-LightItalic.ttf
Normal file
BIN
kivymd/fonts/Roboto-Medium.ttf
Normal file
BIN
kivymd/fonts/Roboto-MediumItalic.ttf
Normal file
BIN
kivymd/fonts/Roboto-Regular.ttf
Normal file
BIN
kivymd/fonts/Roboto-Thin.ttf
Normal file
BIN
kivymd/fonts/Roboto-ThinItalic.ttf
Normal file
BIN
kivymd/fonts/materialdesignicons-webfont.ttf
Normal file
5782
kivymd/icon_definitions.py
Executable file
BIN
kivymd/images/folder.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
kivymd/images/quad_shadow-0.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
kivymd/images/quad_shadow-1.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
kivymd/images/quad_shadow-2.png
Normal file
After Width: | Height: | Size: 19 KiB |
1
kivymd/images/quad_shadow.atlas
Normal file
|
@ -0,0 +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]}}
|
BIN
kivymd/images/rec_shadow-0.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
kivymd/images/rec_shadow-1.png
Normal file
After Width: | Height: | Size: 43 KiB |
1
kivymd/images/rec_shadow.atlas
Normal file
|
@ -0,0 +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]}}
|
BIN
kivymd/images/rec_st_shadow-0.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
kivymd/images/rec_st_shadow-1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
kivymd/images/rec_st_shadow-2.png
Normal file
After Width: | Height: | Size: 28 KiB |
1
kivymd/images/rec_st_shadow.atlas
Normal file
|
@ -0,0 +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]}}
|
BIN
kivymd/images/round_shadow-0.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
kivymd/images/round_shadow-1.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
kivymd/images/round_shadow-2.png
Normal file
After Width: | Height: | Size: 26 KiB |
1
kivymd/images/round_shadow.atlas
Normal file
|
@ -0,0 +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]}}
|
BIN
kivymd/images/transparent.png
Normal file
After Width: | Height: | Size: 156 B |
38
kivymd/material_resources.py
Executable file
|
@ -0,0 +1,38 @@
|
|||
"""
|
||||
Material Resources
|
||||
==================
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.metrics import dp
|
||||
from kivy.utils import platform
|
||||
|
||||
if "KIVY_DOC_INCLUDE" in os.environ:
|
||||
dp = lambda x: x # NOQA: F811
|
||||
|
||||
# Feel free to override this const if you're designing for a device such as
|
||||
# a GNU/Linux tablet.
|
||||
DEVICE_IOS = platform == "ios" or platform == "macosx"
|
||||
if platform != "android" and platform != "ios":
|
||||
DEVICE_TYPE = "desktop"
|
||||
elif Window.width >= dp(600) and Window.height >= dp(600):
|
||||
DEVICE_TYPE = "tablet"
|
||||
else:
|
||||
DEVICE_TYPE = "mobile"
|
||||
|
||||
if DEVICE_TYPE == "mobile":
|
||||
MAX_NAV_DRAWER_WIDTH = dp(300)
|
||||
HORIZ_MARGINS = dp(16)
|
||||
STANDARD_INCREMENT = dp(56)
|
||||
PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT
|
||||
LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT - dp(8)
|
||||
else:
|
||||
MAX_NAV_DRAWER_WIDTH = dp(400)
|
||||
HORIZ_MARGINS = dp(24)
|
||||
STANDARD_INCREMENT = dp(64)
|
||||
PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT
|
||||
LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT
|
||||
|
||||
TOUCH_TARGET_HEIGHT = dp(48)
|
20
kivymd/stiffscroll/LICENSE
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 LogicalDash
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
kivymd/stiffscroll/README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
stiffscroll
|
||||
===========
|
||||
|
||||
A ScrollEffect for use with a Kivy ScrollView. It makes scrolling more
|
||||
laborious as you reach the edge of the scrollable area.
|
||||
|
||||
A ScrollView constructed with StiffScrollEffect,
|
||||
eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to
|
||||
scroll as you get nearer to its edges. You can scroll all the way to
|
||||
the edge if you want to, but it will take more finger-movement than
|
||||
usual.
|
||||
|
||||
Unlike DampedScrollEffect, it is impossible to overscroll with
|
||||
StiffScrollEffect. That means you cannot push the contents of the
|
||||
ScrollView far enough to see what's beneath them. This is appropriate
|
||||
if the ScrollView contains, eg., a background image, like a desktop
|
||||
wallpaper. Overscrolling may give the impression that there is some
|
||||
reason to overscroll, even if just to take a peek beneath, and that
|
||||
impression may be misleading.
|
||||
|
||||
StiffScrollEffect was written by Zachary Spector. His other stuff is at:
|
||||
https://github.com/LogicalDash/
|
||||
He can be reached, and possibly hired, at:
|
||||
zacharyspector@gmail.com
|
215
kivymd/stiffscroll/__init__.py
Normal file
|
@ -0,0 +1,215 @@
|
|||
"""
|
||||
Stiff Scroll Effect
|
||||
===================
|
||||
|
||||
An Effect to be used with ScrollView to prevent scrolling beyond
|
||||
the bounds, but politely.
|
||||
|
||||
A ScrollView constructed with StiffScrollEffect,
|
||||
eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to
|
||||
scroll as you get nearer to its edges. You can scroll all the way to
|
||||
the edge if you want to, but it will take more finger-movement than
|
||||
usual.
|
||||
|
||||
Unlike DampedScrollEffect, it is impossible to overscroll with
|
||||
StiffScrollEffect. That means you cannot push the contents of the
|
||||
ScrollView far enough to see what's beneath them. This is appropriate
|
||||
if the ScrollView contains, eg., a background image, like a desktop
|
||||
wallpaper. Overscrolling may give the impression that there is some
|
||||
reason to overscroll, even if just to take a peek beneath, and that
|
||||
impression may be misleading.
|
||||
|
||||
StiffScrollEffect was written by Zachary Spector. His other stuff is at:
|
||||
https://github.com/LogicalDash/
|
||||
He can be reached, and possibly hired, at:
|
||||
zacharyspector@gmail.com
|
||||
|
||||
"""
|
||||
|
||||
from time import time
|
||||
|
||||
from kivy.animation import AnimationTransition
|
||||
from kivy.effects.kinetic import KineticEffect
|
||||
from kivy.properties import NumericProperty, ObjectProperty
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
|
||||
class StiffScrollEffect(KineticEffect):
|
||||
drag_threshold = NumericProperty("20sp")
|
||||
"""Minimum distance to travel before the movement is considered as a
|
||||
drag.
|
||||
|
||||
:attr:`drag_threshold` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `'20sp'`.
|
||||
"""
|
||||
|
||||
min = NumericProperty(0)
|
||||
"""Minimum boundary to stop the scrolling at.
|
||||
|
||||
:attr:`min` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
max = NumericProperty(0)
|
||||
"""Maximum boundary to stop the scrolling at.
|
||||
|
||||
:attr:`max` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
max_friction = NumericProperty(1)
|
||||
"""How hard should it be to scroll, at the worst?
|
||||
|
||||
:attr:`max_friction` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
body = NumericProperty(0.7)
|
||||
"""Proportion of the range in which you can scroll unimpeded.
|
||||
|
||||
:attr:`body` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.7`.
|
||||
"""
|
||||
|
||||
scroll = NumericProperty(0.0)
|
||||
"""Computed value for scrolling
|
||||
|
||||
:attr:`scroll` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.0`.
|
||||
"""
|
||||
|
||||
transition_min = ObjectProperty(AnimationTransition.in_cubic)
|
||||
"""The AnimationTransition function to use when adjusting the friction
|
||||
near the minimum end of the effect.
|
||||
|
||||
:attr:`transition_min` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to :class:`kivy.animation.AnimationTransition`.
|
||||
"""
|
||||
|
||||
transition_max = ObjectProperty(AnimationTransition.in_cubic)
|
||||
"""The AnimationTransition function to use when adjusting the friction
|
||||
near the maximum end of the effect.
|
||||
|
||||
:attr:`transition_max` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to :class:`kivy.animation.AnimationTransition`.
|
||||
"""
|
||||
|
||||
target_widget = ObjectProperty(None, allownone=True, baseclass=Widget)
|
||||
"""The widget to apply the effect to.
|
||||
|
||||
:attr:`target_widget` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to ``None``.
|
||||
"""
|
||||
|
||||
displacement = NumericProperty(0)
|
||||
"""The absolute distance moved in either direction.
|
||||
|
||||
:attr:`displacement` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Set ``self.base_friction`` to the value of ``self.friction`` just
|
||||
after instantiation, so that I can reset to that value later.
|
||||
"""
|
||||
|
||||
super().__init__(**kwargs)
|
||||
self.base_friction = self.friction
|
||||
|
||||
def update_velocity(self, dt):
|
||||
"""Before actually updating my velocity, meddle with ``self.friction``
|
||||
to make it appropriate to where I'm at, currently.
|
||||
"""
|
||||
|
||||
hard_min = self.min
|
||||
hard_max = self.max
|
||||
if hard_min > hard_max:
|
||||
hard_min, hard_max = hard_max, hard_min
|
||||
|
||||
margin = (1.0 - self.body) * (hard_max - hard_min)
|
||||
soft_min = hard_min + margin
|
||||
soft_max = hard_max - margin
|
||||
|
||||
if self.value < soft_min:
|
||||
try:
|
||||
prop = (soft_min - self.value) / (soft_min - hard_min)
|
||||
self.friction = self.base_friction + abs(
|
||||
self.max_friction - self.base_friction
|
||||
) * self.transition_min(prop)
|
||||
except ZeroDivisionError:
|
||||
pass
|
||||
elif self.value > soft_max:
|
||||
try:
|
||||
# normalize how far past soft_max I've gone as a
|
||||
# proportion of the distance between soft_max and hard_max
|
||||
prop = (self.value - soft_max) / (hard_max - soft_max)
|
||||
self.friction = self.base_friction + abs(
|
||||
self.max_friction - self.base_friction
|
||||
) * self.transition_min(prop)
|
||||
except ZeroDivisionError:
|
||||
pass
|
||||
else:
|
||||
self.friction = self.base_friction
|
||||
|
||||
return super().update_velocity(dt)
|
||||
|
||||
def on_value(self, *args):
|
||||
"""Prevent moving beyond my bounds, and update ``self.scroll``"""
|
||||
|
||||
if self.value > self.min:
|
||||
self.velocity = 0
|
||||
self.scroll = self.min
|
||||
elif self.value < self.max:
|
||||
self.velocity = 0
|
||||
self.scroll = self.max
|
||||
else:
|
||||
self.scroll = self.value
|
||||
|
||||
def start(self, val, t=None):
|
||||
"""Start movement with ``self.friction`` = ``self.base_friction``"""
|
||||
|
||||
self.is_manual = True
|
||||
t = t or time()
|
||||
self.velocity = self.displacement = 0
|
||||
self.friction = self.base_friction
|
||||
self.history = [(t, val)]
|
||||
|
||||
def update(self, val, t=None):
|
||||
"""Reduce the impact of whatever change has been made to me, in
|
||||
proportion with my current friction.
|
||||
"""
|
||||
|
||||
t = t or time()
|
||||
hard_min = self.min
|
||||
hard_max = self.max
|
||||
if hard_min > hard_max:
|
||||
hard_min, hard_max = hard_max, hard_min
|
||||
|
||||
gamut = hard_max - hard_min
|
||||
margin = (1.0 - self.body) * gamut
|
||||
soft_min = hard_min + margin
|
||||
soft_max = hard_max - margin
|
||||
distance = val - self.history[-1][1]
|
||||
reach = distance + self.value
|
||||
|
||||
if (distance < 0 and reach < soft_min) or (
|
||||
distance > 0 and soft_max < reach
|
||||
):
|
||||
distance -= distance * self.friction
|
||||
self.apply_distance(distance)
|
||||
self.history.append((t, val))
|
||||
|
||||
if len(self.history) > self.max_history:
|
||||
self.history.pop(0)
|
||||
self.displacement += abs(distance)
|
||||
self.trigger_velocity_update()
|
||||
|
||||
def stop(self, val, t=None):
|
||||
"""Work out whether I've been flung."""
|
||||
|
||||
self.is_manual = False
|
||||
self.displacement += abs(val - self.history[-1][1])
|
||||
if self.displacement <= self.drag_threshold:
|
||||
self.velocity = 0
|
||||
|
||||
return super().stop(val, t)
|
21
kivymd/tests/test_app.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from kivy import lang
|
||||
from kivy.clock import Clock
|
||||
from kivy.tests.common import GraphicUnitTest
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
|
||||
class AppTest(GraphicUnitTest):
|
||||
def test_start_raw_app(self):
|
||||
lang._delayed_start = None
|
||||
a = MDApp()
|
||||
Clock.schedule_once(a.stop, 0.1)
|
||||
a.run()
|
||||
|
||||
def test_theme_manager_existance(self):
|
||||
lang._delayed_start = None
|
||||
a = MDApp()
|
||||
Clock.schedule_once(a.stop, 0.1)
|
||||
a.run()
|
||||
assert isinstance(a.theme_cls, ThemeManager)
|
15
kivymd/tests/test_font_definitions.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
def test_fonts_registration():
|
||||
# This should register fonts:
|
||||
import kivymd # NOQA
|
||||
from kivy.core.text import LabelBase
|
||||
|
||||
fonts = [
|
||||
"Roboto",
|
||||
"RobotoThin",
|
||||
"RobotoLight",
|
||||
"RobotoMedium",
|
||||
"RobotoBlack",
|
||||
"Icons",
|
||||
]
|
||||
for font in fonts:
|
||||
assert font in LabelBase._fonts.keys()
|
9
kivymd/tests/test_icon_definitions.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
def test_icons_have_size():
|
||||
from kivymd.icon_definitions import md_icons
|
||||
from kivy.core.text import Label
|
||||
|
||||
lbl = Label(font_name="Icons")
|
||||
for icon_name, icon_value in md_icons.items():
|
||||
assert len(icon_value) == 1
|
||||
lbl.refresh()
|
||||
assert lbl.get_extents(icon_value) is not None
|
908
kivymd/theming.py
Executable file
|
@ -0,0 +1,908 @@
|
|||
"""
|
||||
Themes/Theming
|
||||
==============
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Material theming <https://material.io/design/material-theming>`_
|
||||
|
||||
Material App
|
||||
------------
|
||||
|
||||
The main class of your application, which in `Kivy` inherits from the App class,
|
||||
in `KivyMD` must inherit from the `MDApp` class. The `MDApp` class has
|
||||
properties that allow you to control application properties
|
||||
such as :attr:`color/style/font` of interface elements and much more.
|
||||
|
||||
Control material properties
|
||||
---------------------------
|
||||
|
||||
The main application class inherited from the `MDApp` class has the :attr:`theme_cls`
|
||||
attribute, with which you control the material properties of your application.
|
||||
"""
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.atlas import Atlas
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.event import EventDispatcher
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import (
|
||||
AliasProperty,
|
||||
BooleanProperty,
|
||||
DictProperty,
|
||||
ListProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.utils import get_color_from_hex
|
||||
|
||||
from kivymd import images_path
|
||||
from kivymd.color_definitions import colors, hue, palette
|
||||
from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE
|
||||
|
||||
from kivymd.font_definitions import theme_font_styles # NOQA: F401
|
||||
|
||||
|
||||
class ThemeManager(EventDispatcher):
|
||||
primary_palette = OptionProperty("Blue", options=palette)
|
||||
"""
|
||||
The name of the color scheme that the application will use.
|
||||
All major `material` components will have the color
|
||||
of the specified color theme.
|
||||
|
||||
Available options are: `'Red'`, `'Pink'`, `'Purple'`, `'DeepPurple'`,
|
||||
`'Indigo'`, `'Blue'`, `'LightBlue'`, `'Cyan'`, `'Teal'`, `'Green'`,
|
||||
`'LightGreen'`, `'Lime'`, `'Yellow'`, `'Amber'`, `'Orange'`, `'DeepOrange'`,
|
||||
`'Brown'`, `'Gray'`, `'BlueGray'`.
|
||||
|
||||
To change the color scheme of an application:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.uix.screenmanager import Screen
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDRectangleFlatButton
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.primary_palette = "Green" # "Purple", "Red"
|
||||
|
||||
screen = Screen()
|
||||
screen.add_widget(
|
||||
MDRectangleFlatButton(
|
||||
text="Hello, World",
|
||||
pos_hint={"center_x": 0.5, "center_y": 0.5},
|
||||
)
|
||||
)
|
||||
return screen
|
||||
|
||||
|
||||
MainApp().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png
|
||||
|
||||
:attr:`primary_palette` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'Blue'`.
|
||||
"""
|
||||
|
||||
primary_hue = OptionProperty("500", options=hue)
|
||||
"""
|
||||
The color hue of the application.
|
||||
|
||||
Available options are: `'50'`, `'100'`, `'200'`, `'300'`, `'400'`, `'500'`,
|
||||
`'600'`, `'700'`, `'800'`, `'900'`, `'A100'`, `'A200'`, `'A400'`, `'A700'`.
|
||||
|
||||
To change the hue color scheme of an application:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.uix.screenmanager import Screen
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDRectangleFlatButton
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.primary_palette = "Green" # "Purple", "Red"
|
||||
self.theme_cls.primary_hue = "200" # "500"
|
||||
|
||||
screen = Screen()
|
||||
screen.add_widget(
|
||||
MDRectangleFlatButton(
|
||||
text="Hello, World",
|
||||
pos_hint={"center_x": 0.5, "center_y": 0.5},
|
||||
)
|
||||
)
|
||||
return screen
|
||||
|
||||
|
||||
MainApp().run()
|
||||
|
||||
With a value of ``self.theme_cls.primary_hue = "500"``:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png
|
||||
|
||||
With a value of ``self.theme_cls.primary_hue = "200"``:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-hue.png
|
||||
|
||||
:attr:`primary_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'500'`.
|
||||
"""
|
||||
|
||||
primary_light_hue = OptionProperty("200", options=hue)
|
||||
"""
|
||||
Hue value for :attr:`primary_light`.
|
||||
|
||||
:attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'200'`.
|
||||
"""
|
||||
|
||||
primary_dark_hue = OptionProperty("700", options=hue)
|
||||
"""
|
||||
Hue value for :attr:`primary_dark`.
|
||||
|
||||
:attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'700'`.
|
||||
"""
|
||||
|
||||
def _get_primary_color(self):
|
||||
return get_color_from_hex(
|
||||
colors[self.primary_palette][self.primary_hue]
|
||||
)
|
||||
|
||||
primary_color = AliasProperty(
|
||||
_get_primary_color, bind=("primary_palette", "primary_hue")
|
||||
)
|
||||
"""
|
||||
The color of the current application theme in ``rgba`` format.
|
||||
|
||||
:attr:`primary_color` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value of the current application theme, property is readonly.
|
||||
"""
|
||||
|
||||
def _get_primary_light(self):
|
||||
return get_color_from_hex(
|
||||
colors[self.primary_palette][self.primary_light_hue]
|
||||
)
|
||||
|
||||
primary_light = AliasProperty(
|
||||
_get_primary_light, bind=("primary_palette", "primary_light_hue")
|
||||
)
|
||||
"""
|
||||
Colors of the current application color theme in ``rgba`` format
|
||||
(in lighter color).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDRaisedButton:
|
||||
text: "primary_light"
|
||||
pos_hint: {"center_x": 0.5, "center_y": 0.7}
|
||||
md_bg_color: app.theme_cls.primary_light
|
||||
|
||||
MDRaisedButton:
|
||||
text: "primary_color"
|
||||
pos_hint: {"center_x": 0.5, "center_y": 0.5}
|
||||
|
||||
MDRaisedButton:
|
||||
text: "primary_dark"
|
||||
pos_hint: {"center_x": 0.5, "center_y": 0.3}
|
||||
md_bg_color: app.theme_cls.primary_dark
|
||||
'''
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.primary_palette = "Green"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
MainApp().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-colors-light-dark.png
|
||||
:align: center
|
||||
|
||||
:attr:`primary_light` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value of the current application theme (in lighter color),
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_primary_dark(self):
|
||||
return get_color_from_hex(
|
||||
colors[self.primary_palette][self.primary_dark_hue]
|
||||
)
|
||||
|
||||
primary_dark = AliasProperty(
|
||||
_get_primary_dark, bind=("primary_palette", "primary_dark_hue")
|
||||
)
|
||||
"""
|
||||
Colors of the current application color theme
|
||||
in ``rgba`` format (in darker color).
|
||||
|
||||
:attr:`primary_dark` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value of the current application theme (in darker color),
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
accent_palette = OptionProperty("Amber", options=palette)
|
||||
"""
|
||||
The application color palette used for items such as the tab indicator
|
||||
in the :attr:`MDTabsBar` class and so on...
|
||||
|
||||
The image below shows the color schemes with the values
|
||||
``self.theme_cls.accent_palette = 'Blue'``, ``Red'`` and ``Yellow'``:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-palette.png
|
||||
|
||||
:attr:`primary_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'Amber'`.
|
||||
"""
|
||||
|
||||
accent_hue = OptionProperty("500", options=hue)
|
||||
"""Similar to :attr:`primary_hue`,
|
||||
but returns a value for :attr:`accent_palette`.
|
||||
|
||||
:attr:`accent_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'500'`.
|
||||
"""
|
||||
|
||||
accent_light_hue = OptionProperty("200", options=hue)
|
||||
"""
|
||||
Hue value for :attr:`accent_light`.
|
||||
|
||||
:attr:`accent_light_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'200'`.
|
||||
"""
|
||||
|
||||
accent_dark_hue = OptionProperty("700", options=hue)
|
||||
"""
|
||||
Hue value for :attr:`accent_dark`.
|
||||
|
||||
:attr:`accent_dark_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'700'`.
|
||||
"""
|
||||
|
||||
def _get_accent_color(self):
|
||||
return get_color_from_hex(colors[self.accent_palette][self.accent_hue])
|
||||
|
||||
accent_color = AliasProperty(
|
||||
_get_accent_color, bind=["accent_palette", "accent_hue"]
|
||||
)
|
||||
"""Similar to :attr:`primary_color`,
|
||||
but returns a value for :attr:`accent_color`.
|
||||
|
||||
:attr:`accent_color` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`accent_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_accent_light(self):
|
||||
return get_color_from_hex(
|
||||
colors[self.accent_palette][self.accent_light_hue]
|
||||
)
|
||||
|
||||
accent_light = AliasProperty(
|
||||
_get_accent_light, bind=["accent_palette", "accent_light_hue"]
|
||||
)
|
||||
"""Similar to :attr:`primary_light`,
|
||||
but returns a value for :attr:`accent_light`.
|
||||
|
||||
:attr:`accent_light` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`accent_light`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_accent_dark(self):
|
||||
return get_color_from_hex(
|
||||
colors[self.accent_palette][self.accent_dark_hue]
|
||||
)
|
||||
|
||||
accent_dark = AliasProperty(
|
||||
_get_accent_dark, bind=["accent_palette", "accent_dark_hue"]
|
||||
)
|
||||
"""Similar to :attr:`primary_dark`,
|
||||
but returns a value for :attr:`accent_dark`.
|
||||
|
||||
:attr:`accent_dark` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`accent_dark`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
theme_style = OptionProperty("Light", options=["Light", "Dark"])
|
||||
"""App theme style.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.uix.screenmanager import Screen
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDRectangleFlatButton
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark" # "Light"
|
||||
|
||||
screen = Screen()
|
||||
screen.add_widget(
|
||||
MDRectangleFlatButton(
|
||||
text="Hello, World",
|
||||
pos_hint={"center_x": 0.5, "center_y": 0.5},
|
||||
)
|
||||
)
|
||||
return screen
|
||||
|
||||
|
||||
MainApp().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style.png
|
||||
|
||||
:attr:`theme_style` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'Light'`.
|
||||
"""
|
||||
|
||||
def _get_theme_style(self, opposite):
|
||||
if opposite:
|
||||
return "Light" if self.theme_style == "Dark" else "Dark"
|
||||
else:
|
||||
return self.theme_style
|
||||
|
||||
def _get_bg_darkest(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
return get_color_from_hex(colors["Light"]["StatusBar"])
|
||||
elif theme_style == "Dark":
|
||||
return get_color_from_hex(colors["Dark"]["StatusBar"])
|
||||
|
||||
bg_darkest = AliasProperty(_get_bg_darkest, bind=["theme_style"])
|
||||
"""
|
||||
Similar to :attr:`bg_dark`,
|
||||
but the color values are a tone lower (darker) than :attr:`bg_dark`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
KV = '''
|
||||
<Box@BoxLayout>:
|
||||
bg: 0, 0, 0, 0
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.bg
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
BoxLayout:
|
||||
|
||||
Box:
|
||||
bg: app.theme_cls.bg_light
|
||||
Box:
|
||||
bg: app.theme_cls.bg_normal
|
||||
Box:
|
||||
bg: app.theme_cls.bg_dark
|
||||
Box:
|
||||
bg: app.theme_cls.bg_darkest
|
||||
'''
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark" # "Light"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
MainApp().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bg-normal-dark-darkest.png
|
||||
|
||||
:attr:`bg_darkest` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`bg_darkest`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_bg_darkest(self):
|
||||
return self._get_bg_darkest(True)
|
||||
|
||||
opposite_bg_darkest = AliasProperty(
|
||||
_get_op_bg_darkest, bind=["theme_style"]
|
||||
)
|
||||
"""
|
||||
The opposite value of color in the :attr:`bg_darkest`.
|
||||
|
||||
:attr:`opposite_bg_darkest` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`opposite_bg_darkest`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_bg_dark(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
return get_color_from_hex(colors["Light"]["AppBar"])
|
||||
elif theme_style == "Dark":
|
||||
return get_color_from_hex(colors["Dark"]["AppBar"])
|
||||
|
||||
bg_dark = AliasProperty(_get_bg_dark, bind=["theme_style"])
|
||||
"""
|
||||
Similar to :attr:`bg_normal`,
|
||||
but the color values are one tone lower (darker) than :attr:`bg_normal`.
|
||||
|
||||
:attr:`bg_dark` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`bg_dark`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_bg_dark(self):
|
||||
return self._get_bg_dark(True)
|
||||
|
||||
opposite_bg_dark = AliasProperty(_get_op_bg_dark, bind=["theme_style"])
|
||||
"""
|
||||
The opposite value of color in the :attr:`bg_dark`.
|
||||
|
||||
:attr:`opposite_bg_dark` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`opposite_bg_dark`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_bg_normal(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
return get_color_from_hex(colors["Light"]["Background"])
|
||||
elif theme_style == "Dark":
|
||||
return get_color_from_hex(colors["Dark"]["Background"])
|
||||
|
||||
bg_normal = AliasProperty(_get_bg_normal, bind=["theme_style"])
|
||||
"""
|
||||
Similar to :attr:`bg_light`,
|
||||
but the color values are one tone lower (darker) than :attr:`bg_light`.
|
||||
|
||||
:attr:`bg_normal` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`bg_normal`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_bg_normal(self):
|
||||
return self._get_bg_normal(True)
|
||||
|
||||
opposite_bg_normal = AliasProperty(_get_op_bg_normal, bind=["theme_style"])
|
||||
"""
|
||||
The opposite value of color in the :attr:`bg_normal`.
|
||||
|
||||
:attr:`opposite_bg_normal` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`opposite_bg_normal`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_bg_light(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
return get_color_from_hex(colors["Light"]["CardsDialogs"])
|
||||
elif theme_style == "Dark":
|
||||
return get_color_from_hex(colors["Dark"]["CardsDialogs"])
|
||||
|
||||
bg_light = AliasProperty(_get_bg_light, bind=["theme_style"])
|
||||
""""
|
||||
Depending on the style of the theme (`'Dark'` or `'Light`')
|
||||
that the application uses, :attr:`bg_light` contains the color value
|
||||
in ``rgba`` format for the widgets background.
|
||||
|
||||
:attr:`bg_light` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`bg_light`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_bg_light(self):
|
||||
return self._get_bg_light(True)
|
||||
|
||||
opposite_bg_light = AliasProperty(_get_op_bg_light, bind=["theme_style"])
|
||||
"""
|
||||
The opposite value of color in the :attr:`bg_light`.
|
||||
|
||||
:attr:`opposite_bg_light` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`opposite_bg_light`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_divider_color(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
color = get_color_from_hex("000000")
|
||||
elif theme_style == "Dark":
|
||||
color = get_color_from_hex("FFFFFF")
|
||||
color[3] = 0.12
|
||||
return color
|
||||
|
||||
divider_color = AliasProperty(_get_divider_color, bind=["theme_style"])
|
||||
"""
|
||||
Color for dividing lines such as :class:`~kivymd.uix.card.MDSeparator`.
|
||||
|
||||
:attr:`divider_color` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`divider_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_divider_color(self):
|
||||
return self._get_divider_color(True)
|
||||
|
||||
opposite_divider_color = AliasProperty(
|
||||
_get_op_divider_color, bind=["theme_style"]
|
||||
)
|
||||
"""
|
||||
The opposite value of color in the :attr:`divider_color`.
|
||||
|
||||
:attr:`opposite_divider_color` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`opposite_divider_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_text_color(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
color = get_color_from_hex("000000")
|
||||
color[3] = 0.87
|
||||
elif theme_style == "Dark":
|
||||
color = get_color_from_hex("FFFFFF")
|
||||
return color
|
||||
|
||||
text_color = AliasProperty(_get_text_color, bind=["theme_style"])
|
||||
"""
|
||||
Color of the text used in the :class:`~kivymd.uix.label.MDLabel`.
|
||||
|
||||
:attr:`text_color` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`text_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_text_color(self):
|
||||
return self._get_text_color(True)
|
||||
|
||||
opposite_text_color = AliasProperty(
|
||||
_get_op_text_color, bind=["theme_style"]
|
||||
)
|
||||
"""
|
||||
The opposite value of color in the :attr:`text_color`.
|
||||
|
||||
:attr:`opposite_text_color` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`opposite_text_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_secondary_text_color(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
color = get_color_from_hex("000000")
|
||||
color[3] = 0.54
|
||||
elif theme_style == "Dark":
|
||||
color = get_color_from_hex("FFFFFF")
|
||||
color[3] = 0.70
|
||||
return color
|
||||
|
||||
secondary_text_color = AliasProperty(
|
||||
_get_secondary_text_color, bind=["theme_style"]
|
||||
)
|
||||
"""
|
||||
The color for the secondary text that is used in classes
|
||||
from the module :class:`~kivymd/uix/list.TwoLineListItem`.
|
||||
|
||||
:attr:`secondary_text_color` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`secondary_text_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_secondary_text_color(self):
|
||||
return self._get_secondary_text_color(True)
|
||||
|
||||
opposite_secondary_text_color = AliasProperty(
|
||||
_get_op_secondary_text_color, bind=["theme_style"]
|
||||
)
|
||||
"""
|
||||
The opposite value of color in the :attr:`secondary_text_color`.
|
||||
|
||||
:attr:`opposite_secondary_text_color`
|
||||
is an :class:`~kivy.properties.AliasProperty` that returns the value
|
||||
in ``rgba`` format for :attr:`opposite_secondary_text_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_icon_color(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
color = get_color_from_hex("000000")
|
||||
color[3] = 0.54
|
||||
elif theme_style == "Dark":
|
||||
color = get_color_from_hex("FFFFFF")
|
||||
return color
|
||||
|
||||
icon_color = AliasProperty(_get_icon_color, bind=["theme_style"])
|
||||
"""
|
||||
Color of the icon used in the :class:`~kivymd.uix.button.MDIconButton`.
|
||||
|
||||
:attr:`icon_color` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`icon_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_icon_color(self):
|
||||
return self._get_icon_color(True)
|
||||
|
||||
opposite_icon_color = AliasProperty(
|
||||
_get_op_icon_color, bind=["theme_style"]
|
||||
)
|
||||
"""
|
||||
The opposite value of color in the :attr:`icon_color`.
|
||||
|
||||
:attr:`opposite_icon_color` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`opposite_icon_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_disabled_hint_text_color(self, opposite=False):
|
||||
theme_style = self._get_theme_style(opposite)
|
||||
if theme_style == "Light":
|
||||
color = get_color_from_hex("000000")
|
||||
color[3] = 0.38
|
||||
elif theme_style == "Dark":
|
||||
color = get_color_from_hex("FFFFFF")
|
||||
color[3] = 0.50
|
||||
return color
|
||||
|
||||
disabled_hint_text_color = AliasProperty(
|
||||
_get_disabled_hint_text_color, bind=["theme_style"]
|
||||
)
|
||||
"""
|
||||
Color of the disabled text used in the :class:`~kivymd.uix.textfield.MDTextField`.
|
||||
|
||||
:attr:`disabled_hint_text_color`
|
||||
is an :class:`~kivy.properties.AliasProperty` that returns the value
|
||||
in ``rgba`` format for :attr:`disabled_hint_text_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_op_disabled_hint_text_color(self):
|
||||
return self._get_disabled_hint_text_color(True)
|
||||
|
||||
opposite_disabled_hint_text_color = AliasProperty(
|
||||
_get_op_disabled_hint_text_color, bind=["theme_style"]
|
||||
)
|
||||
"""
|
||||
The opposite value of color in the :attr:`disabled_hint_text_color`.
|
||||
|
||||
:attr:`opposite_disabled_hint_text_color`
|
||||
is an :class:`~kivy.properties.AliasProperty` that returns the value
|
||||
in ``rgba`` format for :attr:`opposite_disabled_hint_text_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
# Hardcoded because muh standard
|
||||
def _get_error_color(self):
|
||||
return get_color_from_hex(colors["Red"]["A700"])
|
||||
|
||||
error_color = AliasProperty(_get_error_color)
|
||||
"""
|
||||
Color of the error text used
|
||||
in the :class:`~kivymd.uix.textfield.MDTextField`.
|
||||
|
||||
:attr:`error_color` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`error_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_ripple_color(self):
|
||||
return self._ripple_color
|
||||
|
||||
def _set_ripple_color(self, value):
|
||||
self._ripple_color = value
|
||||
|
||||
_ripple_color = ListProperty(get_color_from_hex(colors["Gray"]["400"]))
|
||||
"""Private value."""
|
||||
|
||||
ripple_color = AliasProperty(
|
||||
_get_ripple_color, _set_ripple_color, bind=["_ripple_color"]
|
||||
)
|
||||
"""
|
||||
Color of ripple effects.
|
||||
|
||||
:attr:`ripple_color` is an :class:`~kivy.properties.AliasProperty` that
|
||||
returns the value in ``rgba`` format for :attr:`ripple_color`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _determine_device_orientation(self, _, window_size):
|
||||
if window_size[0] > window_size[1]:
|
||||
self.device_orientation = "landscape"
|
||||
elif window_size[1] >= window_size[0]:
|
||||
self.device_orientation = "portrait"
|
||||
|
||||
device_orientation = StringProperty("")
|
||||
"""
|
||||
Device orientation.
|
||||
|
||||
:attr:`device_orientation` is an :class:`~kivy.properties.StringProperty`.
|
||||
"""
|
||||
|
||||
def _get_standard_increment(self):
|
||||
if DEVICE_TYPE == "mobile":
|
||||
if self.device_orientation == "landscape":
|
||||
return dp(48)
|
||||
else:
|
||||
return dp(56)
|
||||
else:
|
||||
return dp(64)
|
||||
|
||||
standard_increment = AliasProperty(
|
||||
_get_standard_increment, bind=["device_orientation"]
|
||||
)
|
||||
"""
|
||||
Value of standard increment.
|
||||
|
||||
:attr:`standard_increment` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`standard_increment`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def _get_horizontal_margins(self):
|
||||
if DEVICE_TYPE == "mobile":
|
||||
return dp(16)
|
||||
else:
|
||||
return dp(24)
|
||||
|
||||
horizontal_margins = AliasProperty(_get_horizontal_margins)
|
||||
"""
|
||||
Value of horizontal margins.
|
||||
|
||||
:attr:`horizontal_margins` is an :class:`~kivy.properties.AliasProperty`
|
||||
that returns the value in ``rgba`` format for :attr:`horizontal_margins`,
|
||||
property is readonly.
|
||||
"""
|
||||
|
||||
def on_theme_style(self, instance, value):
|
||||
if (
|
||||
hasattr(App.get_running_app(), "theme_cls")
|
||||
and App.get_running_app().theme_cls == self
|
||||
):
|
||||
self.set_clearcolor_by_theme_style(value)
|
||||
|
||||
set_clearcolor = BooleanProperty(True)
|
||||
|
||||
def set_clearcolor_by_theme_style(self, theme_style):
|
||||
if not self.set_clearcolor:
|
||||
return
|
||||
if theme_style == "Light":
|
||||
Window.clearcolor = get_color_from_hex(
|
||||
colors["Light"]["Background"]
|
||||
)
|
||||
elif theme_style == "Dark":
|
||||
Window.clearcolor = get_color_from_hex(colors["Dark"]["Background"])
|
||||
|
||||
# font name, size (sp), always caps, letter spacing (sp)
|
||||
font_styles = DictProperty(
|
||||
{
|
||||
"H1": ["RobotoLight", 96, False, -1.5],
|
||||
"H2": ["RobotoLight", 60, False, -0.5],
|
||||
"H3": ["Roboto", 48, False, 0],
|
||||
"H4": ["Roboto", 34, False, 0.25],
|
||||
"H5": ["Roboto", 24, False, 0],
|
||||
"H6": ["RobotoMedium", 20, False, 0.15],
|
||||
"Subtitle1": ["Roboto", 16, False, 0.15],
|
||||
"Subtitle2": ["RobotoMedium", 14, False, 0.1],
|
||||
"Body1": ["Roboto", 16, False, 0.5],
|
||||
"Body2": ["Roboto", 14, False, 0.25],
|
||||
"Button": ["RobotoMedium", 14, True, 1.25],
|
||||
"Caption": ["Roboto", 12, False, 0.4],
|
||||
"Overline": ["Roboto", 10, True, 1.5],
|
||||
"Icon": ["Icons", 24, False, 0],
|
||||
}
|
||||
)
|
||||
"""
|
||||
Data of default font styles.
|
||||
|
||||
Add custom font:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDLabel:
|
||||
text: "JetBrainsMono"
|
||||
halign: "center"
|
||||
font_style: "JetBrainsMono"
|
||||
'''
|
||||
|
||||
from kivy.core.text import LabelBase
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.font_definitions import theme_font_styles
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
LabelBase.register(
|
||||
name="JetBrainsMono",
|
||||
fn_regular="JetBrainsMono-Regular.ttf")
|
||||
|
||||
theme_font_styles.append('JetBrainsMono')
|
||||
self.theme_cls.font_styles["JetBrainsMono"] = [
|
||||
"JetBrainsMono",
|
||||
16,
|
||||
False,
|
||||
0.15,
|
||||
]
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
MainApp().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles.png
|
||||
|
||||
:attr:`font_styles` is an :class:`~kivy.properties.DictProperty`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.rec_shadow = Atlas(f"{images_path}rec_shadow.atlas")
|
||||
self.rec_st_shadow = Atlas(f"{images_path}rec_st_shadow.atlas")
|
||||
self.quad_shadow = Atlas(f"{images_path}quad_shadow.atlas")
|
||||
self.round_shadow = Atlas(f"{images_path}round_shadow.atlas")
|
||||
Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style))
|
||||
self._determine_device_orientation(None, Window.size)
|
||||
Window.bind(size=self._determine_device_orientation)
|
||||
|
||||
|
||||
class ThemableBehavior(EventDispatcher):
|
||||
theme_cls = ObjectProperty()
|
||||
"""
|
||||
Instance of :class:`~ThemeManager` class.
|
||||
|
||||
:attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`.
|
||||
"""
|
||||
|
||||
device_ios = BooleanProperty(DEVICE_IOS)
|
||||
"""
|
||||
``True`` if device is ``iOS``.
|
||||
|
||||
:attr:`device_ios` is an :class:`~kivy.properties.BooleanProperty`.
|
||||
"""
|
||||
|
||||
opposite_colors = BooleanProperty(False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if self.theme_cls is not None:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
if not isinstance(
|
||||
App.get_running_app().property("theme_cls", True),
|
||||
ObjectProperty,
|
||||
):
|
||||
raise ValueError(
|
||||
"KivyMD: App object must be inherited from "
|
||||
"`kivymd.app.MDApp`. See "
|
||||
"https://github.com/kivymd/KivyMD/blob/master/README.md#api-breaking-changes"
|
||||
)
|
||||
except AttributeError:
|
||||
raise ValueError(
|
||||
"KivyMD: App object must be initialized before loading "
|
||||
"root widget. See "
|
||||
"https://github.com/kivymd/KivyMD/wiki/Modules-Material-App#exceptions"
|
||||
)
|
||||
self.theme_cls = App.get_running_app().theme_cls
|
||||
super().__init__(**kwargs)
|
89
kivymd/theming_dynamic_text.py
Executable file
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
Theming Dynamic Text
|
||||
====================
|
||||
|
||||
Two implementations. The first is based on color brightness obtained from-
|
||||
https://www.w3.org/TR/AERT#color-contrast
|
||||
The second is based on relative luminance calculation for sRGB obtained from-
|
||||
https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
||||
and contrast ratio calculation obtained from-
|
||||
https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
|
||||
|
||||
Preliminary testing suggests color brightness more closely matches the
|
||||
`Material Design spec` suggested text colors, but the alternative implementation
|
||||
is both newer and the current 'correct' recommendation, so is included here
|
||||
as an option.
|
||||
"""
|
||||
|
||||
|
||||
def _color_brightness(color):
|
||||
# Implementation of color brightness method
|
||||
brightness = color[0] * 299 + color[1] * 587 + color[2] * 114
|
||||
brightness = brightness
|
||||
return brightness
|
||||
|
||||
|
||||
def _black_or_white_by_color_brightness(color):
|
||||
if _color_brightness(color) >= 500:
|
||||
return "black"
|
||||
else:
|
||||
return "white"
|
||||
|
||||
|
||||
def _normalized_channel(color):
|
||||
# Implementation of contrast ratio and relative luminance method
|
||||
if color <= 0.03928:
|
||||
return color / 12.92
|
||||
else:
|
||||
return ((color + 0.055) / 1.055) ** 2.4
|
||||
|
||||
|
||||
def _luminance(color):
|
||||
rg = _normalized_channel(color[0])
|
||||
gg = _normalized_channel(color[1])
|
||||
bg = _normalized_channel(color[2])
|
||||
return 0.2126 * rg + 0.7152 * gg + 0.0722 * bg
|
||||
|
||||
|
||||
def _black_or_white_by_contrast_ratio(color):
|
||||
l_color = _luminance(color)
|
||||
l_black = 0.0
|
||||
l_white = 1.0
|
||||
b_contrast = (l_color + 0.05) / (l_black + 0.05)
|
||||
w_contrast = (l_white + 0.05) / (l_color + 0.05)
|
||||
return "white" if w_contrast >= b_contrast else "black"
|
||||
|
||||
|
||||
def get_contrast_text_color(color, use_color_brightness=True):
|
||||
if use_color_brightness:
|
||||
contrast_color = _black_or_white_by_color_brightness(color)
|
||||
else:
|
||||
contrast_color = _black_or_white_by_contrast_ratio(color)
|
||||
if contrast_color == "white":
|
||||
return 1, 1, 1, 1
|
||||
else:
|
||||
return 0, 0, 0, 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivymd.color_definitions import colors, text_colors
|
||||
|
||||
for c in colors.items():
|
||||
if c[0] in ["Light", "Dark"]:
|
||||
continue
|
||||
color = c[0]
|
||||
print(f"For the {color} color palette:")
|
||||
for name, hex_color in c[1].items():
|
||||
if hex_color:
|
||||
col = get_color_from_hex(hex_color)
|
||||
col_bri = get_contrast_text_color(col)
|
||||
con_rat = get_contrast_text_color(
|
||||
col, use_color_brightness=False
|
||||
)
|
||||
text_color = text_colors[c[0]][name]
|
||||
print(
|
||||
f" The {name} hue gives {col_bri} using color "
|
||||
f"brightness, {con_rat} using contrast ratio, and "
|
||||
f"{text_color} from the MD spec"
|
||||
)
|
21
kivymd/toast/LICENSE
Executable file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Brian - androidtoast library
|
||||
Copyright (c) 2019 Ivanov Yuri - kivytoast library
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
41
kivymd/toast/README.md
Executable file
|
@ -0,0 +1,41 @@
|
|||
KivyToast
|
||||
========
|
||||
|
||||
A package for working with messages like Toast on Android. It is intended for use in applications written using the Kivy framework.
|
||||
|
||||
This package is an improved version of the package https://github.com/knappador/kivy-toaster in which human toasts are written, written on Kivy.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/HeaTTheatR/KivyToast/master/Screenshot.png" align="center"/>
|
||||
|
||||
The package modules are written using the framework for cross-platform development of <Kivy>.
|
||||
Information about the <Kivy> framework is available at http://kivy.org.
|
||||
|
||||
An example of usage (note that with this import the native implementation of toasts will be used for the Android platform and implementation on Kivy for others:
|
||||
|
||||
```python
|
||||
from toast import toast
|
||||
|
||||
...
|
||||
|
||||
# And then in the code, toasts are available
|
||||
# by calling the toast function:
|
||||
toast ('Your message')
|
||||
```
|
||||
|
||||
To force the Kivy implementation on the Android platform, use the import of the form:
|
||||
|
||||
```python
|
||||
from toast.kivytoast import toast
|
||||
```
|
||||
|
||||
PROGRAMMING LANGUAGE
|
||||
--------------------
|
||||
Python 2.7 +
|
||||
|
||||
DEPENDENCE
|
||||
----------
|
||||
The [Kivy] framework (http://kivy.org/docs/installation/installation.html)
|
||||
|
||||
LICENSE
|
||||
-------
|
||||
MIT
|
11
kivymd/toast/__init__.py
Executable file
|
@ -0,0 +1,11 @@
|
|||
__all__ = ("toast",)
|
||||
|
||||
from kivy.utils import platform
|
||||
|
||||
if platform == "android":
|
||||
try:
|
||||
from .androidtoast import toast
|
||||
except BaseException:
|
||||
from .kivytoast import toast
|
||||
else:
|
||||
from .kivytoast import toast
|
12
kivymd/toast/androidtoast/__init__.py
Executable file
|
@ -0,0 +1,12 @@
|
|||
"""
|
||||
Toast for Android device
|
||||
========================
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toast.png
|
||||
:align: center
|
||||
|
||||
"""
|
||||
|
||||
__all__ = ("toast",)
|
||||
|
||||
from .androidtoast import toast
|
68
kivymd/toast/androidtoast/androidtoast.py
Executable file
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
AndroidToast
|
||||
============
|
||||
|
||||
.. rubric:: Native implementation of toast for Android devices.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
# Will be automatically used native implementation of the toast
|
||||
# if your application is running on an Android device.
|
||||
# Otherwise, will be used toast implementation
|
||||
# from the kivymd/toast/kivytoast package.
|
||||
from kivymd.toast import toast
|
||||
|
||||
KV = '''
|
||||
BoxLayout:
|
||||
orientation:'vertical'
|
||||
|
||||
MDToolbar:
|
||||
id: toolbar
|
||||
title: 'Test Toast'
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
left_action_items: [['menu', lambda x: '']]
|
||||
|
||||
FloatLayout:
|
||||
|
||||
MDRaisedButton:
|
||||
text: 'TEST KIVY TOAST'
|
||||
on_release: app.show_toast()
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def show_toast(self):
|
||||
'''Displays a toast on the screen.'''
|
||||
|
||||
toast('Test Kivy Toast')
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
Test().run()
|
||||
"""
|
||||
|
||||
__all__ = ("toast",)
|
||||
|
||||
from android.runnable import run_on_ui_thread
|
||||
from jnius import autoclass, cast
|
||||
|
||||
Toast = autoclass("android.widget.Toast")
|
||||
context = autoclass("org.kivy.android.PythonActivity").mActivity
|
||||
|
||||
|
||||
@run_on_ui_thread
|
||||
def toast(text, length_long=False):
|
||||
"""Displays a toast.
|
||||
|
||||
:length_long: The amount of time (in seconds) that the toast is visible on the screen.
|
||||
"""
|
||||
|
||||
duration = Toast.LENGTH_LONG if length_long else Toast.LENGTH_SHORT
|
||||
String = autoclass("java.lang.String")
|
||||
c = cast("java.lang.CharSequence", String(text))
|
||||
t = Toast.makeText(context, c, duration)
|
||||
t.show()
|
3
kivymd/toast/kivytoast/__init__.py
Executable file
|
@ -0,0 +1,3 @@
|
|||
__all__ = ("toast",)
|
||||
|
||||
from .kivytoast import toast
|
133
kivymd/toast/kivytoast/kivytoast.py
Executable file
|
@ -0,0 +1,133 @@
|
|||
"""
|
||||
KivyToast
|
||||
=========
|
||||
|
||||
.. rubric:: Implementation of toasts for desktop.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.toast import toast
|
||||
|
||||
KV = '''
|
||||
BoxLayout:
|
||||
orientation:'vertical'
|
||||
|
||||
MDToolbar:
|
||||
id: toolbar
|
||||
title: 'Test Toast'
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
left_action_items: [['menu', lambda x: '']]
|
||||
|
||||
FloatLayout:
|
||||
|
||||
MDRaisedButton:
|
||||
text: 'TEST KIVY TOAST'
|
||||
on_release: app.show_toast()
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def show_toast(self):
|
||||
'''Displays a toast on the screen.'''
|
||||
|
||||
toast('Test Kivy Toast')
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
Test().run()
|
||||
"""
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import NumericProperty
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.modalview import ModalView
|
||||
|
||||
from kivymd import images_path
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<Toast>:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: .2, .2, .2, 1
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
radius: [15,]
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class Toast(ModalView):
|
||||
duration = NumericProperty(2.5)
|
||||
"""
|
||||
The amount of time (in seconds) that the toast is visible on the screen.
|
||||
|
||||
:attr:`duration` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2.5`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.size_hint = (None, None)
|
||||
self.pos_hint = {"center_x": 0.5, "center_y": 0.1}
|
||||
self.background_color = [0, 0, 0, 0]
|
||||
self.background = f"{images_path}transparent.png"
|
||||
self.opacity = 0
|
||||
self.auto_dismiss = True
|
||||
self.label_toast = Label(size_hint=(None, None), opacity=0)
|
||||
self.label_toast.bind(texture_size=self.label_check_texture_size)
|
||||
self.add_widget(self.label_toast)
|
||||
|
||||
def label_check_texture_size(self, instance, texture_size):
|
||||
texture_width, texture_height = texture_size
|
||||
if texture_width > Window.width:
|
||||
instance.text_size = (Window.width - dp(10), None)
|
||||
instance.texture_update()
|
||||
texture_width, texture_height = instance.texture_size
|
||||
self.size = (texture_width + 25, texture_height + 25)
|
||||
|
||||
def toast(self, text_toast):
|
||||
self.label_toast.text = text_toast
|
||||
self.open()
|
||||
|
||||
def on_open(self):
|
||||
self.fade_in()
|
||||
Clock.schedule_once(self.fade_out, self.duration)
|
||||
|
||||
def fade_in(self):
|
||||
Animation(opacity=1, duration=0.4).start(self.label_toast)
|
||||
Animation(opacity=1, duration=0.4).start(self)
|
||||
|
||||
def fade_out(self, interval):
|
||||
Animation(opacity=0, duration=0.4).start(self.label_toast)
|
||||
anim_body = Animation(opacity=0, duration=0.4)
|
||||
anim_body.bind(on_complete=lambda *x: self.dismiss())
|
||||
anim_body.start(self)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if not self.collide_point(*touch.pos):
|
||||
if self.auto_dismiss:
|
||||
self.dismiss()
|
||||
return False
|
||||
super(ModalView, self).on_touch_down(touch)
|
||||
return True
|
||||
|
||||
|
||||
def toast(text: str, duration=2.5):
|
||||
"""Displays a toast.
|
||||
|
||||
:duration: The amount of time (in seconds) that the toast is visible on the screen.
|
||||
"""
|
||||
|
||||
Toast(duration=duration).toast(text)
|
0
kivymd/tools/__init__.py
Normal file
0
kivymd/tools/packaging/__init__.py
Normal file
63
kivymd/tools/packaging/pyinstaller/__init__.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
"""
|
||||
PyInstaller hooks
|
||||
=================
|
||||
|
||||
Add ``hookspath=[kivymd.hooks_path]`` to your .spec file.
|
||||
|
||||
Example of .spec file
|
||||
=====================
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
from kivy_deps import sdl2, glew
|
||||
|
||||
from kivymd import hooks_path as kivymd_hooks_path
|
||||
|
||||
path = os.path.abspath(".")
|
||||
|
||||
a = Analysis(
|
||||
["main.py"],
|
||||
pathex=[path],
|
||||
hookspath=[kivymd_hooks_path],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=None,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=None)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
*[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
name="app_name",
|
||||
console=True,
|
||||
)
|
||||
"""
|
||||
|
||||
__all__ = ("hooks_path", "datas", "hiddenimports")
|
||||
|
||||
from os.path import abspath, basename, dirname, join
|
||||
|
||||
import kivymd
|
||||
|
||||
hooks_path = dirname(abspath(__file__))
|
||||
"""Path to hook directory to use with PyInstaller.
|
||||
See :mod:`kivymd.tools.packaging.pyinstaller` for more information."""
|
||||
|
||||
datas = [
|
||||
(kivymd.fonts_path, join("kivymd", basename(dirname(kivymd.fonts_path)))),
|
||||
(kivymd.images_path, join("kivymd", basename(dirname(kivymd.images_path)))),
|
||||
]
|
||||
hiddenimports = ["PIL"]
|
1
kivymd/tools/packaging/pyinstaller/hook-kivymd.py
Normal file
|
@ -0,0 +1 @@
|
|||
from kivymd.tools.packaging.pyinstaller import * # NOQA
|
0
kivymd/tools/release/__init__.py
Normal file
92
kivymd/tools/release/argument_parser.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# Copyright (c) 2019-2020 Artem Bulgakov
|
||||
#
|
||||
# This file is distributed under the terms of the same license,
|
||||
# as the Kivy framework.
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
class ArgumentParserWithHelp(argparse.ArgumentParser):
|
||||
def parse_args(self, args=None, namespace=None):
|
||||
# Add help when no arguments specified
|
||||
if not args and not len(sys.argv) > 1:
|
||||
self.print_help()
|
||||
self.exit(1)
|
||||
return super().parse_args(args, namespace)
|
||||
|
||||
def error(self, message):
|
||||
# Add full help on error
|
||||
self.print_help()
|
||||
self.exit(2, f"\nError: {message}\n")
|
||||
|
||||
def format_help(self):
|
||||
# Add subparsers usage and help to full help text
|
||||
formatter = self._get_formatter()
|
||||
|
||||
# Get subparsers
|
||||
subparsers_actions = [
|
||||
action
|
||||
for action in self._actions
|
||||
if isinstance(action, argparse._SubParsersAction)
|
||||
]
|
||||
|
||||
# Description
|
||||
formatter.add_text(self.description)
|
||||
|
||||
# Usage
|
||||
formatter.add_usage(
|
||||
self.usage,
|
||||
self._actions,
|
||||
self._mutually_exclusive_groups,
|
||||
prefix="Usage:\n",
|
||||
)
|
||||
|
||||
# Subparsers usage
|
||||
for subparsers_action in subparsers_actions:
|
||||
for choice, subparser in subparsers_action.choices.items():
|
||||
formatter.add_usage(
|
||||
subparser.usage,
|
||||
subparser._actions,
|
||||
subparser._mutually_exclusive_groups,
|
||||
prefix="",
|
||||
)
|
||||
|
||||
# Positionals, optionals and user-defined groups
|
||||
for action_group in self._action_groups:
|
||||
if not any(
|
||||
[
|
||||
action in subparsers_actions
|
||||
for action in action_group._group_actions
|
||||
]
|
||||
):
|
||||
formatter.start_section(action_group.title)
|
||||
formatter.add_text(action_group.description)
|
||||
formatter.add_arguments(action_group._group_actions)
|
||||
formatter.end_section()
|
||||
else:
|
||||
# Process subparsers differently
|
||||
# Just show list of choices
|
||||
formatter.start_section(action_group.title)
|
||||
# formatter.add_text(action_group.description)
|
||||
for action in action_group._group_actions:
|
||||
for choice in action.choices:
|
||||
formatter.add_text(choice)
|
||||
formatter.end_section()
|
||||
|
||||
# Subparsers help
|
||||
for subparsers_action in subparsers_actions:
|
||||
for choice, subparser in subparsers_action.choices.items():
|
||||
formatter.start_section(choice)
|
||||
for action_group in subparser._action_groups:
|
||||
formatter.start_section(action_group.title)
|
||||
formatter.add_text(action_group.description)
|
||||
formatter.add_arguments(action_group._group_actions)
|
||||
formatter.end_section()
|
||||
formatter.end_section()
|
||||
|
||||
# Epilog
|
||||
formatter.add_text(self.epilog)
|
||||
|
||||
# Determine help from format above
|
||||
return formatter.format_help()
|
88
kivymd/tools/release/git_commands.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
# Copyright (c) 2019-2020 Artem Bulgakov
|
||||
#
|
||||
# This file is distributed under the terms of the same license,
|
||||
# as the Kivy framework.
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def command(cmd: list, capture_output: bool = False) -> str:
|
||||
"""Run system command."""
|
||||
print(f"Command: {subprocess.list2cmdline(cmd)}")
|
||||
if capture_output:
|
||||
out = subprocess.check_output(cmd)
|
||||
out = out.decode("utf-8")
|
||||
print(out.strip())
|
||||
return out
|
||||
else:
|
||||
subprocess.check_call(cmd)
|
||||
return ""
|
||||
|
||||
|
||||
def get_previous_version() -> str:
|
||||
"""Returns latest tag in git."""
|
||||
command(["git", "checkout", "master"])
|
||||
old_version = command(
|
||||
["git", "describe", "--abbrev=0", "--tags"], capture_output=True
|
||||
)
|
||||
old_version = old_version[:-1] # Remove \n
|
||||
return old_version
|
||||
|
||||
|
||||
def git_clean(ask: bool = True):
|
||||
"""Clean git repository from untracked and changed files."""
|
||||
# Check what files will be removed
|
||||
files_to_clean = command(
|
||||
["git", "clean", "-dx", "--force", "--dry-run"], capture_output=True
|
||||
).strip()
|
||||
# Ask before removing
|
||||
if ask and files_to_clean:
|
||||
while True:
|
||||
ans = input("Do you want to remove these files? (yes/no)").lower()
|
||||
if ans == "y" or ans == "yes":
|
||||
break
|
||||
elif ans == "n" or ans == "no":
|
||||
print("git clean is required. Exit")
|
||||
exit(0)
|
||||
|
||||
# Remove all untracked files
|
||||
command(["git", "clean", "-dx", "--force"])
|
||||
command(["git", "reset", "--hard"])
|
||||
|
||||
|
||||
def git_commit(message: str, allow_error: bool = False, add_files: list = None):
|
||||
"""Make commit."""
|
||||
add_files = add_files if add_files else ["-A"]
|
||||
command(["git", "add", *add_files])
|
||||
try:
|
||||
command(["git", "commit", "--all", "-m", message])
|
||||
except subprocess.CalledProcessError as e:
|
||||
if not allow_error:
|
||||
raise e
|
||||
|
||||
|
||||
def git_tag(name: str):
|
||||
"""Create tag."""
|
||||
command(["git", "tag", name])
|
||||
|
||||
|
||||
def git_push(branches_to_push: list, ask: bool = True, push: bool = False):
|
||||
"""Push all changes."""
|
||||
if ask:
|
||||
push = input("Do you want to push changes? (y)") in (
|
||||
"",
|
||||
"y",
|
||||
"yes",
|
||||
)
|
||||
|
||||
cmd = ["git", "push", "--tags", "origin", "master", *branches_to_push]
|
||||
if push:
|
||||
command(cmd)
|
||||
else:
|
||||
print(
|
||||
f"Changes are not pushed. Command for manual pushing: {subprocess.list2cmdline(cmd)}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
git_clean(ask=True)
|
337
kivymd/tools/release/make_release.py
Normal file
|
@ -0,0 +1,337 @@
|
|||
# Copyright (c) 2019-2020 Artem Bulgakov
|
||||
#
|
||||
# This file is distributed under the terms of the same license,
|
||||
# as the Kivy framework.
|
||||
|
||||
"""
|
||||
Script to make release
|
||||
======================
|
||||
|
||||
Run this script before release (before deploying).
|
||||
|
||||
What this script does:
|
||||
|
||||
* Undo all local changes in repository
|
||||
* Update version in __init__.py, README
|
||||
* Format files
|
||||
* Rename file "unreleased.rst" to version, add to index.rst
|
||||
* Commit "Version ..."
|
||||
* Create tag
|
||||
* Add "unreleased.rst" to Change Log, add to index.rst
|
||||
* Commit
|
||||
* Git push
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from kivymd.tools.release.argument_parser import ArgumentParserWithHelp
|
||||
from kivymd.tools.release.git_commands import (
|
||||
command,
|
||||
get_previous_version,
|
||||
git_clean,
|
||||
git_commit,
|
||||
git_push,
|
||||
git_tag,
|
||||
)
|
||||
from kivymd.tools.release.update_icons import update_icons
|
||||
|
||||
|
||||
def run_pre_commit():
|
||||
"""Run pre-commit."""
|
||||
try:
|
||||
command(["pre-commit", "run", "--all-files"])
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
git_commit("Run pre-commit", allow_error=True)
|
||||
|
||||
|
||||
def replace_in_file(pattern, repl, file):
|
||||
"""Replace one `pattern` match to `repl` in file `file`."""
|
||||
file_content = open(file, "rt", encoding="utf-8").read()
|
||||
new_file_content = re.sub(pattern, repl, file_content, 1, re.M)
|
||||
open(file, "wt", encoding="utf-8").write(new_file_content)
|
||||
return not file_content == new_file_content
|
||||
|
||||
|
||||
def update_init_py(version, is_release, test: bool = False):
|
||||
"""Change version in `kivymd/__init__.py`."""
|
||||
init_file = os.path.abspath("kivymd/__init__.py")
|
||||
init_version_regex = r"(?<=^__version__ = ['\"])[^'\"]+(?=['\"]$)"
|
||||
success = replace_in_file(init_version_regex, version, init_file)
|
||||
if test and not success:
|
||||
print("Couldn't update __init__.py file.", file=sys.stderr)
|
||||
init_version_regex = r"(?<=^release = )(True|False)(?=$)"
|
||||
success = replace_in_file(init_version_regex, str(is_release), init_file)
|
||||
if test and not success:
|
||||
print("Couldn't update __init__.py file.", file=sys.stderr)
|
||||
|
||||
|
||||
def update_readme(previous_version, version, test: bool = False):
|
||||
"""Change version in README."""
|
||||
readme_file = os.path.abspath("README.md")
|
||||
readme_version_regex = rf"(?<=\[){previous_version}[ \-*\w^\]\n]*(?=\])"
|
||||
success = replace_in_file(readme_version_regex, version, readme_file)
|
||||
if test and not success:
|
||||
print("Couldn't update README.md file.", file=sys.stderr)
|
||||
readme_install_version_regex = (
|
||||
rf"(?<=pip install kivymd==){previous_version}(?=\n```)"
|
||||
)
|
||||
success = replace_in_file(
|
||||
readme_install_version_regex, version, readme_file
|
||||
)
|
||||
if test and not success:
|
||||
print("Couldn't update README.md file.", file=sys.stderr)
|
||||
readme_buildozer_version_regex = (
|
||||
rf"(?<=, kivymd==){previous_version}(?=\n```)"
|
||||
)
|
||||
success = replace_in_file(
|
||||
readme_buildozer_version_regex, version, readme_file
|
||||
)
|
||||
if test and not success:
|
||||
print("Couldn't update README.md file.", file=sys.stderr)
|
||||
|
||||
|
||||
def move_changelog(
|
||||
index_file,
|
||||
unreleased_file,
|
||||
previous_version,
|
||||
version_file,
|
||||
version,
|
||||
test: bool = False,
|
||||
):
|
||||
"""Edit unreleased.rst and rename to <version>.rst."""
|
||||
# Read unreleased changelog
|
||||
changelog = open(unreleased_file, "rt", encoding="utf-8").read()
|
||||
|
||||
# Edit changelog
|
||||
changelog = re.sub(
|
||||
r"Unreleased\n----------",
|
||||
f"{version}\n{'-' * (1 + len(version))}",
|
||||
changelog,
|
||||
1,
|
||||
re.M,
|
||||
)
|
||||
changelog = re.sub(
|
||||
r"(?<=See on GitHub: `)branch master",
|
||||
f"tag {version}",
|
||||
changelog,
|
||||
1,
|
||||
re.M,
|
||||
)
|
||||
changelog = re.sub(r"(?<=/tree/)master", f"{version}", changelog, 1, re.M)
|
||||
changelog = re.sub(
|
||||
rf"(?<=compare {previous_version}/)master",
|
||||
f"{version}",
|
||||
changelog,
|
||||
1,
|
||||
re.M,
|
||||
)
|
||||
changelog = re.sub(
|
||||
rf"(?<=compare/{previous_version}...)master",
|
||||
f"{version}",
|
||||
changelog,
|
||||
1,
|
||||
re.M,
|
||||
)
|
||||
changelog = re.sub(
|
||||
r"(?<=pip install )https[\S]*/master.zip(?=\n)",
|
||||
f"kivymd=={version}",
|
||||
changelog,
|
||||
1,
|
||||
re.M,
|
||||
)
|
||||
|
||||
# Write changelog
|
||||
open(version_file, "wt", encoding="utf-8").write(changelog)
|
||||
# Remove unreleased changelog
|
||||
os.remove(unreleased_file)
|
||||
# Update index file
|
||||
success = replace_in_file(
|
||||
"/changelog/unreleased.rst", f"/changelog/{version}.rst", index_file
|
||||
)
|
||||
if test and not success:
|
||||
print("Couldn't update changelog file.", file=sys.stderr)
|
||||
|
||||
|
||||
def create_unreleased_changelog(
|
||||
index_file,
|
||||
unreleased_file,
|
||||
version,
|
||||
ask: bool = True,
|
||||
test: bool = False,
|
||||
):
|
||||
"""Create unreleased.rst by template."""
|
||||
# Check if unreleased file exists
|
||||
if os.path.exists(unreleased_file):
|
||||
if ask and input(
|
||||
f'Do you want to rewrite "{unreleased_file}"? (y)'
|
||||
) not in (
|
||||
"",
|
||||
"y",
|
||||
"yes",
|
||||
):
|
||||
exit(0)
|
||||
# Generate unreleased changelog
|
||||
changelog = f"""Unreleased
|
||||
----------
|
||||
|
||||
See on GitHub: `branch master <https://github.com/kivymd/KivyMD/tree/master>`_ | `compare {version}/master <https://github.com/kivymd/KivyMD/compare/{version}...master>`_
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install https://github.com/kivymd/KivyMD/archive/master.zip
|
||||
|
||||
* Bug fixes and other minor improvements.
|
||||
"""
|
||||
# Create unreleased file
|
||||
open(unreleased_file, "wt", encoding="utf-8").write(changelog)
|
||||
# Update index file
|
||||
success = replace_in_file(
|
||||
r"(?<=Change Log\n==========\n\n)",
|
||||
".. include:: /changelog/unreleased.rst\n",
|
||||
index_file,
|
||||
)
|
||||
if test and not success:
|
||||
print("Couldn't update changelog index file.", file=sys.stderr)
|
||||
|
||||
|
||||
def main():
|
||||
parser = create_argument_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
release = args.command == "release"
|
||||
version = args.version or "0.0.0"
|
||||
next_version = args.next_version or (
|
||||
(version[:-1] + str(int(version[-1]) + 1) + ".dev0")
|
||||
if "rc" not in version
|
||||
else version
|
||||
)
|
||||
prepare = args.command == "prepare"
|
||||
test = args.command == "test"
|
||||
ask = args.yes is not True
|
||||
push = args.push is True
|
||||
|
||||
if release and version == "0.0.0":
|
||||
parser.error("Please specify new version.")
|
||||
version_re = r"[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}(rc[\d]{1,3})?"
|
||||
if not re.match(version_re, version):
|
||||
parser.error(f'Version "{version}" doesn\'t match template.')
|
||||
next_version_re = (
|
||||
r"[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}(\.dev[\d]{1,3}|rc[\d]{1,3})?"
|
||||
)
|
||||
if not re.match(next_version_re, next_version):
|
||||
parser.error(f'Next version "{next_version}" doesn\'t match template.')
|
||||
if test and push:
|
||||
parser.error("Don't use --push with test.")
|
||||
|
||||
repository_root = os.path.normpath(
|
||||
os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
)
|
||||
|
||||
# Change directory to repository root
|
||||
os.chdir(repository_root)
|
||||
|
||||
previous_version = get_previous_version()
|
||||
|
||||
# Print info
|
||||
print(f"Previous version: {previous_version}")
|
||||
print(f"New version: {version}")
|
||||
print(f"Next version: {next_version}")
|
||||
|
||||
update_icons(make_commit=True)
|
||||
git_clean(ask=ask)
|
||||
run_pre_commit()
|
||||
|
||||
if prepare:
|
||||
git_push([], ask=ask, push=push)
|
||||
return
|
||||
|
||||
update_init_py(version, is_release=True, test=test)
|
||||
update_readme(previous_version, version, test=test)
|
||||
|
||||
changelog_index_file = os.path.join(
|
||||
repository_root, "docs", "sources", "changelog", "index.rst"
|
||||
)
|
||||
changelog_unreleased_file = os.path.join(
|
||||
repository_root, "docs", "sources", "changelog", "unreleased.rst"
|
||||
)
|
||||
changelog_version_file = os.path.join(
|
||||
repository_root, "docs", "sources", "changelog", f"{version}.rst"
|
||||
)
|
||||
move_changelog(
|
||||
changelog_index_file,
|
||||
changelog_unreleased_file,
|
||||
previous_version,
|
||||
changelog_version_file,
|
||||
version,
|
||||
test=test,
|
||||
)
|
||||
|
||||
git_commit(f"KivyMD {version}")
|
||||
git_tag(version)
|
||||
|
||||
branches_to_push = []
|
||||
# Move branch stable to stable-x.x.x
|
||||
# command(["git", "branch", "-m", "stable", f"stable-{old_version}"])
|
||||
# branches_to_push.append(f"stable-{old_version}")
|
||||
# Create branch stable
|
||||
# command(["git", "branch", "stable"])
|
||||
# command(["git", "push", "--force", "origin", "master:stable"])
|
||||
# branches_to_push.append("stable")
|
||||
|
||||
create_unreleased_changelog(
|
||||
changelog_index_file,
|
||||
changelog_unreleased_file,
|
||||
version,
|
||||
test=test,
|
||||
)
|
||||
update_init_py(next_version, is_release=False, test=test)
|
||||
git_commit(f"KivyMD {next_version}")
|
||||
git_push(branches_to_push, ask=ask, push=push)
|
||||
|
||||
|
||||
def create_argument_parser():
|
||||
parser = ArgumentParserWithHelp(
|
||||
prog="make_release.py",
|
||||
allow_abbrev=False,
|
||||
# usage="%(prog)s command [options] extensions [--exclude extensions]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--yes",
|
||||
action="store_true",
|
||||
help="remove and modify files without asking.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--push",
|
||||
action="store_true",
|
||||
help="push changes to remote repository. Use only with release and prepare.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"command",
|
||||
choices=["release", "prepare", "test"],
|
||||
help="release will update icons, modify files and make tag.\n"
|
||||
"prepare will update icons and format files.\n"
|
||||
"test will check if script can modify each file correctly.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"version",
|
||||
type=str,
|
||||
nargs="?",
|
||||
help="new version in format n.n.n (1.111.11).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"next_version",
|
||||
type=str,
|
||||
nargs="?",
|
||||
help="development version in format n.n.n.devn (1.111.11.dev0).",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
172
kivymd/tools/release/update_icons.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
# Copyright (c) 2019-2020 Artem Bulgakov
|
||||
#
|
||||
# This file is distributed under the terms of the same license,
|
||||
# as the Kivy framework.
|
||||
|
||||
"""
|
||||
Tool for updating Iconic font
|
||||
=============================
|
||||
|
||||
Downloads archive from https://github.com/Templarian/MaterialDesign-Webfont and
|
||||
updates font file with icon_definitions.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
import requests
|
||||
|
||||
from kivymd.tools.release.git_commands import git_commit
|
||||
|
||||
# Paths to files in kivymd repository
|
||||
kivymd_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
font_path = os.path.join(
|
||||
kivymd_path, "fonts", "materialdesignicons-webfont.ttf"
|
||||
)
|
||||
icon_definitions_path = os.path.join(kivymd_path, "icon_definitions.py")
|
||||
|
||||
font_version = "master"
|
||||
# URL to download new archive (set None if already downloaded)
|
||||
url = (
|
||||
f"https://github.com/Templarian/MaterialDesign-Webfont"
|
||||
f"/archive/{font_version}.zip"
|
||||
)
|
||||
# url = None
|
||||
|
||||
# Paths to files in loaded archive
|
||||
temp_path = os.path.join(os.path.dirname(__file__), "temp")
|
||||
temp_repo_path = os.path.join(
|
||||
temp_path, f"MaterialDesign-Webfont-{font_version}"
|
||||
)
|
||||
temp_font_path = os.path.join(
|
||||
temp_repo_path, "fonts", "materialdesignicons-webfont.ttf"
|
||||
)
|
||||
temp_preview_path = os.path.join(temp_repo_path, "preview.html")
|
||||
|
||||
# Regex
|
||||
re_icons_json = re.compile(r"(?<=var icons = )[\S ]+(?=;)")
|
||||
re_additional_icons = re.compile(r"(?<=icons\.push\()[\S ]+(?=\);)")
|
||||
re_version = re.compile(r"(?<=<span class=\"version\">)[\d.]+(?=</span>)")
|
||||
re_quote_keys = re.compile(r"([{\s,])(\w+)(:)")
|
||||
re_icon_definitions = re.compile(r"md_icons = {\n([ ]{4}[\s\S]*,\n)*}")
|
||||
re_version_in_file = re.compile(r"(?<=LAST UPDATED: Version )[\d.]+(?=\n)")
|
||||
|
||||
|
||||
def download_file(url, path):
|
||||
response = requests.get(url, stream=True)
|
||||
if response.status_code != 200:
|
||||
return False
|
||||
with open(path, "wb") as f:
|
||||
shutil.copyfileobj(response.raw, f)
|
||||
return True
|
||||
|
||||
|
||||
def unzip_archive(archive_path, dir_path):
|
||||
with zipfile.ZipFile(archive_path, "r") as zip_ref:
|
||||
zip_ref.extractall(dir_path)
|
||||
|
||||
|
||||
def get_icons_list():
|
||||
# There is js array with icons in file preview.html
|
||||
with open(temp_preview_path, "r") as f:
|
||||
preview_file = f.read()
|
||||
# Find version
|
||||
version = re_version.findall(preview_file)[0]
|
||||
# Load icons
|
||||
jsons_icons = re_icons_json.findall(preview_file)[0]
|
||||
json_icons = re_quote_keys.sub(r'\1"\2"\3', jsons_icons)
|
||||
icons = json.loads(json_icons)
|
||||
# Find additional icons (like a blank icon)
|
||||
# jsons_additional_icons = re_additional_icons.findall(preview_file)
|
||||
# for j in jsons_additional_icons:
|
||||
# json_additional_icons = re_quote_keys.sub(r'\1"\2"\3', j)
|
||||
# icons.append(json.loads(json_additional_icons))
|
||||
return icons, version
|
||||
|
||||
|
||||
def make_icon_definitions(icons):
|
||||
# Make python dict ("name": hex)
|
||||
icon_definitions = "md_icons = {\n"
|
||||
for i in icons:
|
||||
icon_definitions += " " * 4
|
||||
if len(i["hex"]) != 4:
|
||||
# Some icons has 5-digit unicode
|
||||
i["hex"] = "0" * (8 - len(i["hex"])) + i["hex"]
|
||||
icon_definitions += f'"{i["name"]}": "\\U{i["hex"].upper()}",\n'
|
||||
else:
|
||||
icon_definitions += f'"{i["name"]}": "\\u{i["hex"].upper()}",\n'
|
||||
icon_definitions += " " * 4 + '"blank": " ",\n' # Add blank icon (space)
|
||||
icon_definitions += "}"
|
||||
return icon_definitions
|
||||
|
||||
|
||||
def export_icon_definitions(icon_definitions, version):
|
||||
with open(icon_definitions_path, "r") as f:
|
||||
icon_definitions_file = f.read()
|
||||
# Change md_icons list
|
||||
new_icon_definitions = re_icon_definitions.sub(
|
||||
icon_definitions.replace("\\", "\\\\"), icon_definitions_file, 1
|
||||
)
|
||||
# Change version
|
||||
new_icon_definitions = re_version_in_file.sub(
|
||||
version, new_icon_definitions, 1
|
||||
)
|
||||
with open(icon_definitions_path, "w") as f:
|
||||
f.write(new_icon_definitions)
|
||||
|
||||
|
||||
def update_icons(make_commit: bool = False):
|
||||
if url is not None:
|
||||
print(f"Downloading Material Design Icons from {url}")
|
||||
if download_file(url, "iconic-font.zip"):
|
||||
print("Archive downloaded")
|
||||
else:
|
||||
print("Error: Could not download archive", file=sys.stderr)
|
||||
else:
|
||||
print("URL is None. Do not download archive")
|
||||
if os.path.exists("iconic-font.zip"):
|
||||
unzip_archive("iconic-font.zip", temp_path)
|
||||
print("Unzip successful")
|
||||
os.remove("iconic-font.zip")
|
||||
if os.path.exists(temp_repo_path):
|
||||
shutil.copy2(temp_font_path, font_path)
|
||||
print("Font copied")
|
||||
icons, version = get_icons_list()
|
||||
print(f"Version {version}. {len(icons)} icons loaded")
|
||||
icon_definitions = make_icon_definitions(icons)
|
||||
export_icon_definitions(icon_definitions, version)
|
||||
print("File icon_definitions.py updated")
|
||||
shutil.rmtree(temp_path, ignore_errors=True)
|
||||
|
||||
if make_commit:
|
||||
git_commit(
|
||||
f"Update Iconic font (v{version})",
|
||||
allow_error=True,
|
||||
add_files=[
|
||||
"kivymd/icon_definitions.py",
|
||||
"kivymd/fonts/materialdesignicons-webfont.ttf",
|
||||
],
|
||||
)
|
||||
print("\nSuccessful. You can now push changes")
|
||||
else:
|
||||
print(
|
||||
f'\nSuccessful. Commit message: "Update Iconic font (v{version})"'
|
||||
)
|
||||
else:
|
||||
print(f"Error: {temp_repo_path} not exists", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
make_commit = "--commit" in sys.argv
|
||||
if "--commit" in sys.argv:
|
||||
sys.argv.remove("--commit")
|
||||
update_icons(make_commit=make_commit)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
61
kivymd/uix/__init__.py
Executable file
|
@ -0,0 +1,61 @@
|
|||
from kivy.properties import BooleanProperty
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.screenmanager import Screen
|
||||
|
||||
from kivymd.uix.behaviors import SpecificBackgroundColorBehavior
|
||||
|
||||
|
||||
class MDAdaptiveWidget(SpecificBackgroundColorBehavior):
|
||||
adaptive_height = BooleanProperty(False)
|
||||
"""
|
||||
If `True`, the following properties will be applied to the widget:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
:attr:`adaptive_height` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
adaptive_width = BooleanProperty(False)
|
||||
"""
|
||||
If `True`, the following properties will be applied to the widget:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_x: None
|
||||
width: self.minimum_width
|
||||
|
||||
:attr:`adaptive_width` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
adaptive_size = BooleanProperty(False)
|
||||
"""
|
||||
If `True`, the following properties will be applied to the widget:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
|
||||
:attr:`adaptive_size` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
def on_adaptive_height(self, instance, value):
|
||||
if not isinstance(self, (FloatLayout, Screen)):
|
||||
self.size_hint_y = None
|
||||
self.bind(minimum_height=self.setter("height"))
|
||||
|
||||
def on_adaptive_width(self, instance, value):
|
||||
if not isinstance(self, (FloatLayout, Screen)):
|
||||
self.size_hint_x = None
|
||||
self.bind(minimum_width=self.setter("width"))
|
||||
|
||||
def on_adaptive_size(self, instance, value):
|
||||
if not isinstance(self, (FloatLayout, Screen)):
|
||||
self.size_hint = (None, None)
|
||||
self.bind(minimum_size=self.setter("size"))
|
395
kivymd/uix/backdrop.py
Normal file
|
@ -0,0 +1,395 @@
|
|||
"""
|
||||
Components/Backdrop
|
||||
===================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Backdrop <https://material.io/components/backdrop>`_
|
||||
|
||||
.. rubric:: Skeleton layout for using :class:`~MDBackdrop`:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<Root>:
|
||||
|
||||
MDBackdrop:
|
||||
|
||||
MDBackdropBackLayer:
|
||||
|
||||
ContentForBackdropBackLayer:
|
||||
|
||||
MDBackdropFrontLayer:
|
||||
|
||||
ContentForBackdropFrontLayer:
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.screenmanager import Screen
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
# Your layouts.
|
||||
Builder.load_string(
|
||||
'''
|
||||
#:import Window kivy.core.window.Window
|
||||
#:import IconLeftWidget kivymd.uix.list.IconLeftWidget
|
||||
|
||||
|
||||
<ItemBackdropFrontLayer@TwoLineAvatarListItem>
|
||||
icon: "android"
|
||||
|
||||
IconLeftWidget:
|
||||
icon: root.icon
|
||||
|
||||
|
||||
<MyBackdropFrontLayer@ItemBackdropFrontLayer>
|
||||
backdrop: None
|
||||
text: "Lower the front layer"
|
||||
secondary_text: " by 50 %"
|
||||
icon: "transfer-down"
|
||||
on_press: root.backdrop.open(-Window.height / 2)
|
||||
pos_hint: {"top": 1}
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
<MyBackdropBackLayer@Image>
|
||||
size_hint: .8, .8
|
||||
source: "data/logo/kivy-icon-512.png"
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
'''
|
||||
)
|
||||
|
||||
# Usage example of MDBackdrop.
|
||||
Builder.load_string(
|
||||
'''
|
||||
<ExampleBackdrop>
|
||||
|
||||
MDBackdrop:
|
||||
id: backdrop
|
||||
left_action_items: [['menu', lambda x: self.open()]]
|
||||
title: "Example Backdrop"
|
||||
radius_left: "25dp"
|
||||
radius_right: "0dp"
|
||||
header_text: "Menu:"
|
||||
|
||||
MDBackdropBackLayer:
|
||||
MyBackdropBackLayer:
|
||||
id: backlayer
|
||||
|
||||
MDBackdropFrontLayer:
|
||||
MyBackdropFrontLayer:
|
||||
backdrop: backdrop
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
class ExampleBackdrop(Screen):
|
||||
pass
|
||||
|
||||
|
||||
class TestBackdrop(MDApp):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def build(self):
|
||||
return ExampleBackdrop()
|
||||
|
||||
|
||||
TestBackdrop().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.gif
|
||||
:width: 280 px
|
||||
:align: center
|
||||
|
||||
.. Note:: `See full example <https://github.com/kivymd/KivyMD/wiki/Components-Backdrop>`_
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"MDBackdropToolbar",
|
||||
"MDBackdropFrontLayer",
|
||||
"MDBackdropBackLayer",
|
||||
"MDBackdrop",
|
||||
)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.card import MDCard
|
||||
from kivymd.uix.toolbar import MDToolbar
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<MDBackdrop>
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba:
|
||||
root.theme_cls.primary_color if not root.background_color \
|
||||
else root.background_color
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
MDBackdropToolbar:
|
||||
id: toolbar
|
||||
title: root.title
|
||||
elevation: 0
|
||||
md_bg_color:
|
||||
root.theme_cls.primary_color if not root.background_color \
|
||||
else root.background_color
|
||||
left_action_items: root.left_action_items
|
||||
right_action_items: root.right_action_items
|
||||
pos_hint: {'top': 1}
|
||||
|
||||
_BackLayer:
|
||||
id: back_layer
|
||||
y: -toolbar.height
|
||||
padding: 0, 0, 0, toolbar.height + dp(10)
|
||||
|
||||
_FrontLayer:
|
||||
id: _front_layer
|
||||
md_bg_color: 0, 0, 0, 0
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: root.height - toolbar.height
|
||||
padding: root.padding
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.theme_cls.bg_normal
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
radius:
|
||||
[
|
||||
(root.radius_left, root.radius_left),
|
||||
(root.radius_right, root.radius_right),
|
||||
(0, 0),
|
||||
(0, 0)
|
||||
]
|
||||
|
||||
OneLineListItem:
|
||||
id: header_button
|
||||
text: root.header_text
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
on_press: root.open()
|
||||
|
||||
BoxLayout:
|
||||
id: front_layer
|
||||
padding: 0, 0, 0, "10dp"
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MDBackdrop(ThemableBehavior, FloatLayout):
|
||||
"""
|
||||
:Events:
|
||||
:attr:`on_open`
|
||||
When the front layer drops.
|
||||
:attr:`on_close`
|
||||
When the front layer rises.
|
||||
"""
|
||||
|
||||
padding = ListProperty([0, 0, 0, 0])
|
||||
"""Padding for contents of the front layer.
|
||||
|
||||
:attr:`padding` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
left_action_items = ListProperty()
|
||||
"""The icons and methods left of the :class:`kivymd.uix.toolbar.MDToolbar`
|
||||
in back layer. For more information, see the :class:`kivymd.uix.toolbar.MDToolbar` module
|
||||
and :attr:`left_action_items` parameter.
|
||||
|
||||
:attr:`left_action_items` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
right_action_items = ListProperty()
|
||||
"""Works the same way as :attr:`left_action_items`.
|
||||
|
||||
:attr:`right_action_items` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
title = StringProperty()
|
||||
"""See the :class:`kivymd.uix.toolbar.MDToolbar.title` parameter.
|
||||
|
||||
:attr:`title` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
background_color = ListProperty()
|
||||
"""Background color of back layer.
|
||||
|
||||
:attr:`background_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
radius_left = NumericProperty("16dp")
|
||||
"""The value of the rounding radius of the upper left corner
|
||||
of the front layer.
|
||||
|
||||
:attr:`radius_left` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `16dp`.
|
||||
"""
|
||||
|
||||
radius_right = NumericProperty("16dp")
|
||||
"""The value of the rounding radius of the upper right corner
|
||||
of the front layer.
|
||||
|
||||
:attr:`radius_right` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `16dp`.
|
||||
"""
|
||||
|
||||
header = BooleanProperty(True)
|
||||
"""Whether to use a header above the contents of the front layer.
|
||||
|
||||
:attr:`header` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
header_text = StringProperty("Header")
|
||||
"""Text of header.
|
||||
|
||||
:attr:`header_text` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'Header'`.
|
||||
"""
|
||||
|
||||
close_icon = StringProperty("close")
|
||||
"""The name of the icon that will be installed on the toolbar
|
||||
on the left when opening the front layer.
|
||||
|
||||
:attr:`close_icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'close'`.
|
||||
"""
|
||||
|
||||
_open_icon = ""
|
||||
_front_layer_open = False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.register_event_type("on_open")
|
||||
self.register_event_type("on_close")
|
||||
Clock.schedule_once(
|
||||
lambda x: self.on_left_action_items(self, self.left_action_items)
|
||||
)
|
||||
|
||||
def on_open(self):
|
||||
"""When the front layer drops."""
|
||||
|
||||
def on_close(self):
|
||||
"""When the front layer rises."""
|
||||
|
||||
def on_left_action_items(self, instance, value):
|
||||
if value:
|
||||
self.left_action_items = [value[0]]
|
||||
else:
|
||||
self.left_action_items = [["menu", lambda x: self.open()]]
|
||||
self._open_icon = self.left_action_items[0][0]
|
||||
|
||||
def on_header(self, instance, value):
|
||||
if not value:
|
||||
self.ids._front_layer.remove_widget(self.ids.header_button)
|
||||
|
||||
def open(self, open_up_to=0):
|
||||
"""
|
||||
Opens the front layer.
|
||||
|
||||
:open_up_to:
|
||||
the height to which the front screen will be lowered;
|
||||
if equal to zero - falls to the bottom of the screen;
|
||||
"""
|
||||
|
||||
self.animtion_icon_menu()
|
||||
if self._front_layer_open:
|
||||
self.close()
|
||||
return
|
||||
|
||||
if open_up_to:
|
||||
if open_up_to < (
|
||||
self.ids.header_button.height - self.ids._front_layer.height
|
||||
):
|
||||
y = self.ids.header_button.height - self.ids._front_layer.height
|
||||
elif open_up_to > 0:
|
||||
y = 0
|
||||
else:
|
||||
y = open_up_to
|
||||
else:
|
||||
y = self.ids.header_button.height - self.ids._front_layer.height
|
||||
|
||||
Animation(y=y, d=0.2, t="out_quad").start(self.ids._front_layer)
|
||||
self._front_layer_open = True
|
||||
self.dispatch("on_open")
|
||||
|
||||
def close(self):
|
||||
"""Opens the front layer."""
|
||||
|
||||
Animation(y=0, d=0.2, t="out_quad").start(self.ids._front_layer)
|
||||
self._front_layer_open = False
|
||||
self.dispatch("on_close")
|
||||
|
||||
def animtion_icon_menu(self):
|
||||
icon_menu = self.ids.toolbar.ids.left_actions.children[0]
|
||||
anim = Animation(opacity=0, d=0.2, t="out_quad")
|
||||
anim.bind(on_complete=self.animtion_icon_close)
|
||||
anim.start(icon_menu)
|
||||
|
||||
def animtion_icon_close(self, instance_animation, instance_icon_menu):
|
||||
instance_icon_menu.icon = (
|
||||
self.close_icon
|
||||
if instance_icon_menu.icon == self._open_icon
|
||||
else self._open_icon
|
||||
)
|
||||
Animation(opacity=1, d=0.2).start(instance_icon_menu)
|
||||
|
||||
def add_widget(self, widget, index=0, canvas=None):
|
||||
if widget.__class__ in (MDBackdropToolbar, _BackLayer, _FrontLayer):
|
||||
return super().add_widget(widget)
|
||||
else:
|
||||
if widget.__class__ is MDBackdropBackLayer:
|
||||
self.ids.back_layer.add_widget(widget)
|
||||
elif widget.__class__ is MDBackdropFrontLayer:
|
||||
self.ids.front_layer.add_widget(widget)
|
||||
|
||||
|
||||
class MDBackdropToolbar(MDToolbar):
|
||||
pass
|
||||
|
||||
|
||||
class MDBackdropFrontLayer(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class MDBackdropBackLayer(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class _BackLayer(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class _FrontLayer(MDCard):
|
||||
pass
|
455
kivymd/uix/banner.py
Normal file
|
@ -0,0 +1,455 @@
|
|||
"""
|
||||
Components/Banner
|
||||
=================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Banner <https://material.io/components/banners>`_
|
||||
|
||||
.. rubric:: A banner displays a prominent message and related optional actions.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.factory import Factory
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
Builder.load_string('''
|
||||
<ExampleBanner@Screen>
|
||||
|
||||
MDBanner:
|
||||
id: banner
|
||||
text: ["One line string text example without actions."]
|
||||
# The widget that is under the banner.
|
||||
# It will be shifted down to the height of the banner.
|
||||
over_widget: screen
|
||||
vertical_pad: toolbar.height
|
||||
|
||||
MDToolbar:
|
||||
id: toolbar
|
||||
title: "Example Banners"
|
||||
elevation: 10
|
||||
pos_hint: {'top': 1}
|
||||
|
||||
BoxLayout:
|
||||
id: screen
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: Window.height - toolbar.height
|
||||
|
||||
OneLineListItem:
|
||||
text: "Banner without actions"
|
||||
on_release: banner.show()
|
||||
|
||||
Widget:
|
||||
''')
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Factory.ExampleBanner()
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-example-1.gif
|
||||
:align: center
|
||||
|
||||
.. rubric:: Banner type.
|
||||
|
||||
By default, the banner is of the type ``'one-line'``:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
text: ["One line string text example without actions."]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-one-line.png
|
||||
:align: center
|
||||
|
||||
To use a two-line banner, specify the ``'two-line'`` :attr:`MDBanner.type` for the banner
|
||||
and pass the list of two lines to the :attr:`MDBanner.text` parameter:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
type: "two-line"
|
||||
text:
|
||||
["One line string text example without actions.", "This is the second line of the banner message."]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-two-line.png
|
||||
:align: center
|
||||
|
||||
Similarly, create a three-line banner:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
type: "three-line"
|
||||
text:
|
||||
["One line string text example without actions.", "This is the second line of the banner message." "and this is the third line of the banner message."]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-three-line.png
|
||||
:align: center
|
||||
|
||||
To add buttons to any type of banner,
|
||||
use the :attr:`MDBanner.left_action` and :attr:`MDBanner.right_action` parameters,
|
||||
which should take a list ['Button name', function]:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
text: ["One line string text example without actions."]
|
||||
left_action: ["CANCEL", lambda x: None]
|
||||
|
||||
Or two buttons:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
text: ["One line string text example without actions."]
|
||||
left_action: ["CANCEL", lambda x: None]
|
||||
right_action: ["CLOSE", lambda x: None]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-actions.png
|
||||
:align: center
|
||||
|
||||
If you want to use the icon on the left in the banner,
|
||||
add the prefix `'-icon'` to the banner type:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
type: "one-line-icon"
|
||||
icon: f"{images_path}/kivymd.png"
|
||||
text: ["One line string text example without actions."]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-icon.png
|
||||
:align: center
|
||||
|
||||
.. Note:: `See full example <https://github.com/kivymd/KivyMD/wiki/Components-Banner>`_
|
||||
"""
|
||||
|
||||
__all__ = ("MDBanner",)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import (
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.card import MDCard
|
||||
from kivymd.uix.list import (
|
||||
OneLineAvatarListItem,
|
||||
OneLineListItem,
|
||||
ThreeLineAvatarListItem,
|
||||
ThreeLineListItem,
|
||||
TwoLineAvatarListItem,
|
||||
TwoLineListItem,
|
||||
)
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import Window kivy.core.window.Window
|
||||
#:import Clock kivy.clock.Clock
|
||||
|
||||
|
||||
<ThreeLineIconBanner>
|
||||
text: root.text_message[0]
|
||||
secondary_text: root.text_message[1]
|
||||
tertiary_text: root.text_message[2]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
ImageLeftWidget:
|
||||
source: root.icon
|
||||
|
||||
|
||||
<TwoLineIconBanner>
|
||||
text: root.text_message[0]
|
||||
secondary_text: root.text_message[1]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
ImageLeftWidget:
|
||||
source: root.icon
|
||||
|
||||
|
||||
<OneLineIconBanner>
|
||||
text: root.text_message[0]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
ImageLeftWidget:
|
||||
source: root.icon
|
||||
|
||||
|
||||
<ThreeLineBanner>
|
||||
text: root.text_message[0]
|
||||
secondary_text: root.text_message[1]
|
||||
tertiary_text: root.text_message[2]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
<TwoLineBanner>
|
||||
text: root.text_message[0]
|
||||
secondary_text: root.text_message[1]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
<OneLineBanner>
|
||||
text: root.text_message[0]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
<MDBanner>
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
banner_y: 0
|
||||
orientation: "vertical"
|
||||
y: Window.height - self.banner_y
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 0, 0, 0, 0
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
BoxLayout:
|
||||
id: container_message
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
BoxLayout:
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
pos_hint: {"right": 1}
|
||||
padding: 0, 0, "8dp", "8dp"
|
||||
spacing: "8dp"
|
||||
|
||||
BoxLayout:
|
||||
id: left_action_box
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
|
||||
BoxLayout:
|
||||
id: right_action_box
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MDBanner(MDCard):
|
||||
vertical_pad = NumericProperty(dp(68))
|
||||
"""
|
||||
Indent the banner at the top of the screen.
|
||||
|
||||
:attr:`vertical_pad` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `dp(68)`.
|
||||
"""
|
||||
|
||||
opening_transition = StringProperty("in_quad")
|
||||
"""
|
||||
The name of the animation transition.
|
||||
|
||||
:attr:`opening_transition` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'in_quad'`.
|
||||
"""
|
||||
|
||||
icon = StringProperty("data/logo/kivy-icon-128.png")
|
||||
"""Icon banner.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'data/logo/kivy-icon-128.png'`.
|
||||
"""
|
||||
|
||||
over_widget = ObjectProperty()
|
||||
"""
|
||||
The widget that is under the banner.
|
||||
It will be shifted down to the height of the banner.
|
||||
|
||||
:attr:`over_widget` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
text = ListProperty()
|
||||
"""List of lines for banner text.
|
||||
Must contain no more than three lines for a
|
||||
`'one-line'`, `'two-line'` and `'three-line'` banner, respectively.
|
||||
|
||||
:attr:`text` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
left_action = ListProperty()
|
||||
"""The action of banner.
|
||||
|
||||
To add one action, make a list [`'name_action'`, callback]
|
||||
where `'name_action'` is a string that corresponds to an action name and
|
||||
``callback`` is the function called on a touch release event.
|
||||
|
||||
:attr:`left_action` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
right_action = ListProperty()
|
||||
"""Works the same way as :attr:`left_action`.
|
||||
|
||||
:attr:`right_action` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
type = OptionProperty(
|
||||
"one-line",
|
||||
options=[
|
||||
"one-line",
|
||||
"two-line",
|
||||
"three-line",
|
||||
"one-line-icon",
|
||||
"two-line-icon",
|
||||
"three-line-icon",
|
||||
],
|
||||
allownone=True,
|
||||
)
|
||||
"""Banner type. . Available options are: (`"one-line"`, `"two-line"`,
|
||||
`"three-line"`, `"one-line-icon"`, `"two-line-icon"`, `"three-line-icon"`).
|
||||
|
||||
:attr:`type` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'one-line'`.
|
||||
"""
|
||||
|
||||
_type_message = None
|
||||
_progress = False
|
||||
|
||||
def add_actions_buttons(self, box, data):
|
||||
if data:
|
||||
name_action_button, function_action_button = data
|
||||
action_button = MDFlatButton(
|
||||
text=f"[b]{name_action_button}[/b]",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
on_release=function_action_button,
|
||||
)
|
||||
action_button.markup = True
|
||||
box.add_widget(action_button)
|
||||
|
||||
def set_left_action(self):
|
||||
self.add_actions_buttons(self.ids.left_action_box, self.left_action)
|
||||
|
||||
def set_right_action(self):
|
||||
self.add_actions_buttons(self.ids.right_action_box, self.right_action)
|
||||
|
||||
def set_type_banner(self):
|
||||
self._type_message = {
|
||||
"three-line-icon": ThreeLineIconBanner,
|
||||
"two-line-icon": TwoLineIconBanner,
|
||||
"one-line-icon": OneLineIconBanner,
|
||||
"three-line": ThreeLineBanner,
|
||||
"two-line": TwoLineBanner,
|
||||
"one-line": OneLineBanner,
|
||||
}[self.type]
|
||||
|
||||
def add_banner_to_container(self):
|
||||
self.ids.container_message.add_widget(
|
||||
self._type_message(text_message=self.text, icon=self.icon)
|
||||
)
|
||||
|
||||
def show(self):
|
||||
def show(interval):
|
||||
self.set_type_banner()
|
||||
self.set_left_action()
|
||||
self.set_right_action()
|
||||
self.add_banner_to_container()
|
||||
Clock.schedule_once(self.animation_display_banner, 0.1)
|
||||
|
||||
if self._progress:
|
||||
return
|
||||
self._progress = True
|
||||
if self.ids.container_message.children:
|
||||
self.hide()
|
||||
Clock.schedule_once(show, 0.7)
|
||||
|
||||
def animation_display_banner(self, i):
|
||||
Animation(
|
||||
banner_y=self.height + self.vertical_pad,
|
||||
d=0.15,
|
||||
t=self.opening_transition,
|
||||
).start(self)
|
||||
anim = Animation(
|
||||
y=self.over_widget.y - self.height,
|
||||
d=0.15,
|
||||
t=self.opening_transition,
|
||||
)
|
||||
anim.bind(on_complete=self._reset_progress)
|
||||
anim.start(self.over_widget)
|
||||
|
||||
def hide(self):
|
||||
def hide(interval):
|
||||
anim = Animation(banner_y=0, d=0.15)
|
||||
anim.bind(on_complete=self._remove_banner)
|
||||
anim.start(self)
|
||||
Animation(y=self.over_widget.y + self.height, d=0.15).start(
|
||||
self.over_widget
|
||||
)
|
||||
|
||||
Clock.schedule_once(hide, 0.5)
|
||||
|
||||
def _remove_banner(self, *args):
|
||||
self.ids.container_message.clear_widgets()
|
||||
self.ids.left_action_box.clear_widgets()
|
||||
self.ids.right_action_box.clear_widgets()
|
||||
|
||||
def _reset_progress(self, *args):
|
||||
self._progress = False
|
||||
|
||||
|
||||
class BaseBanner(Widget):
|
||||
text_message = ListProperty(["", "", ""])
|
||||
icon = StringProperty()
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
self.parent.parent.hide()
|
||||
|
||||
|
||||
class ThreeLineIconBanner(ThreeLineAvatarListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class TwoLineIconBanner(TwoLineAvatarListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class OneLineIconBanner(OneLineAvatarListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class ThreeLineBanner(ThreeLineListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class TwoLineBanner(TwoLineListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class OneLineBanner(OneLineListItem, BaseBanner):
|
||||
pass
|
22
kivymd/uix/behaviors/__init__.py
Executable file
|
@ -0,0 +1,22 @@
|
|||
"""
|
||||
Behaviors
|
||||
=========
|
||||
|
||||
Modules and classes implementing various behaviors for buttons etc.
|
||||
"""
|
||||
|
||||
# flake8: NOQA
|
||||
from .hover_behavior import HoverBehavior # isort:skip
|
||||
from .backgroundcolorbehavior import (
|
||||
BackgroundColorBehavior,
|
||||
SpecificBackgroundColorBehavior,
|
||||
)
|
||||
from .elevation import (
|
||||
CircularElevationBehavior,
|
||||
CommonElevationBehavior,
|
||||
RectangularElevationBehavior,
|
||||
)
|
||||
from .focus_behavior import FocusBehavior
|
||||
from .magic_behavior import MagicBehavior
|
||||
from .ripplebehavior import CircularRippleBehavior, RectangularRippleBehavior
|
||||
from .touch_behavior import TouchBehavior
|
168
kivymd/uix/behaviors/backgroundcolorbehavior.py
Executable file
|
@ -0,0 +1,168 @@
|
|||
"""
|
||||
Behaviors/Background Color
|
||||
==========================
|
||||
|
||||
.. note:: The following classes are intended for in-house use of the library.
|
||||
"""
|
||||
|
||||
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
BoundedNumericProperty,
|
||||
ListProperty,
|
||||
OptionProperty,
|
||||
ReferenceListProperty,
|
||||
)
|
||||
from kivy.uix.widget import Widget
|
||||
from kivy.utils import get_color_from_hex
|
||||
|
||||
from kivymd.color_definitions import hue, palette, text_colors
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import RelativeLayout kivy.uix.relativelayout.RelativeLayout
|
||||
|
||||
|
||||
<BackgroundColorBehavior>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.md_bg_color
|
||||
RoundedRectangle:
|
||||
size: self.size
|
||||
pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0)
|
||||
radius: root.radius
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class BackgroundColorBehavior(Widget):
|
||||
r = BoundedNumericProperty(1.0, min=0.0, max=1.0)
|
||||
"""The value of ``red`` in the ``rgba`` palette.
|
||||
|
||||
:attr:`r` is an :class:`~kivy.properties.BoundedNumericProperty`
|
||||
and defaults to `1.0`.
|
||||
"""
|
||||
|
||||
g = BoundedNumericProperty(1.0, min=0.0, max=1.0)
|
||||
"""The value of ``green`` in the ``rgba`` palette.
|
||||
|
||||
:attr:`g` is an :class:`~kivy.properties.BoundedNumericProperty`
|
||||
and defaults to `1.0`.
|
||||
"""
|
||||
|
||||
b = BoundedNumericProperty(1.0, min=0.0, max=1.0)
|
||||
"""The value of ``blue`` in the ``rgba`` palette.
|
||||
|
||||
:attr:`b` is an :class:`~kivy.properties.BoundedNumericProperty`
|
||||
and defaults to `1.0`.
|
||||
"""
|
||||
|
||||
a = BoundedNumericProperty(0.0, min=0.0, max=1.0)
|
||||
"""The value of ``alpha channel`` in the ``rgba`` palette.
|
||||
|
||||
:attr:`a` is an :class:`~kivy.properties.BoundedNumericProperty`
|
||||
and defaults to `0.0`.
|
||||
"""
|
||||
|
||||
radius = ListProperty([0, 0, 0, 0])
|
||||
"""Canvas radius.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Top left corner slice.
|
||||
MDBoxLayout:
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
radius: [25, 0, 0, 0]
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
md_bg_color = ReferenceListProperty(r, g, b, a)
|
||||
"""The background color of the widget (:class:`~kivy.uix.widget.Widget`)
|
||||
that will be inherited from the :attr:`BackgroundColorBehavior` class.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
Widget:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 0, 1, 1, 1
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
similar to code:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<MyWidget@BackgroundColorBehavior>
|
||||
md_bg_color: 0, 1, 1, 1
|
||||
|
||||
:attr:`md_bg_color` is an :class:`~kivy.properties.ReferenceListProperty`
|
||||
and defaults to :attr:`r`, :attr:`g`, :attr:`b`, :attr:`a`.
|
||||
"""
|
||||
|
||||
|
||||
class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
|
||||
background_palette = OptionProperty(
|
||||
"Primary", options=["Primary", "Accent", *palette]
|
||||
)
|
||||
"""See :attr:`kivymd.color_definitions.palette`.
|
||||
|
||||
:attr:`background_palette` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'Primary'`.
|
||||
"""
|
||||
|
||||
background_hue = OptionProperty("500", options=hue)
|
||||
"""See :attr:`kivymd.color_definitions.hue`.
|
||||
|
||||
:attr:`background_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'500'`.
|
||||
"""
|
||||
|
||||
specific_text_color = ListProperty([0, 0, 0, 0.87])
|
||||
""":attr:`specific_text_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[0, 0, 0, 0.87]`.
|
||||
"""
|
||||
|
||||
specific_secondary_text_color = ListProperty([0, 0, 0, 0.87])
|
||||
""":attr:`specific_secondary_text_color`is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[0, 0, 0, 0.87]`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if hasattr(self, "theme_cls"):
|
||||
self.theme_cls.bind(
|
||||
primary_palette=self._update_specific_text_color
|
||||
)
|
||||
self.theme_cls.bind(accent_palette=self._update_specific_text_color)
|
||||
self.theme_cls.bind(theme_style=self._update_specific_text_color)
|
||||
self.bind(background_hue=self._update_specific_text_color)
|
||||
self.bind(background_palette=self._update_specific_text_color)
|
||||
self._update_specific_text_color(None, None)
|
||||
|
||||
def _update_specific_text_color(self, instance, value):
|
||||
if hasattr(self, "theme_cls"):
|
||||
palette = {
|
||||
"Primary": self.theme_cls.primary_palette,
|
||||
"Accent": self.theme_cls.accent_palette,
|
||||
}.get(self.background_palette, self.background_palette)
|
||||
else:
|
||||
palette = {"Primary": "Blue", "Accent": "Amber"}.get(
|
||||
self.background_palette, self.background_palette
|
||||
)
|
||||
color = get_color_from_hex(text_colors[palette][self.background_hue])
|
||||
secondary_color = color[:]
|
||||
# Check for black text (need to adjust opacity).
|
||||
if (color[0] + color[1] + color[2]) == 0:
|
||||
color[3] = 0.87
|
||||
secondary_color[3] = 0.54
|
||||
else:
|
||||
secondary_color[3] = 0.7
|
||||
self.specific_text_color = color
|
||||
self.specific_secondary_text_color = secondary_color
|
303
kivymd/uix/behaviors/elevation.py
Executable file
|
@ -0,0 +1,303 @@
|
|||
"""
|
||||
Behaviors/Elevation
|
||||
===================
|
||||
|
||||
.. rubric:: Classes implements a circular and rectangular elevation effects.
|
||||
|
||||
To create a widget with rectangular or circular elevation effect,
|
||||
you must create a new class that inherits from the
|
||||
:class:`~RectangularElevationBehavior` or :class:`~CircularElevationBehavior`
|
||||
class.
|
||||
|
||||
For example, let's create an button with a rectangular elevation effect:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import (
|
||||
RectangularRippleBehavior,
|
||||
BackgroundColorBehavior,
|
||||
RectangularElevationBehavior,
|
||||
)
|
||||
|
||||
KV = '''
|
||||
<RectangularElevationButton>:
|
||||
size_hint: None, None
|
||||
size: "250dp", "50dp"
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
# With elevation effect
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
elevation: 11
|
||||
|
||||
# Without elevation effect
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
'''
|
||||
|
||||
|
||||
class RectangularElevationButton(
|
||||
RectangularRippleBehavior,
|
||||
RectangularElevationBehavior,
|
||||
ButtonBehavior,
|
||||
BackgroundColorBehavior,
|
||||
):
|
||||
md_bg_color = [0, 0, 1, 1]
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.gif
|
||||
:align: center
|
||||
|
||||
Similarly, create a button with a circular elevation effect:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.image import Image
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import (
|
||||
CircularRippleBehavior,
|
||||
CircularElevationBehavior,
|
||||
)
|
||||
|
||||
KV = '''
|
||||
#:import images_path kivymd.images_path
|
||||
|
||||
|
||||
<CircularElevationButton>:
|
||||
size_hint: None, None
|
||||
size: "100dp", "100dp"
|
||||
source: f"{images_path}/kivymd.png"
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
# With elevation effect
|
||||
CircularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
elevation: 5
|
||||
|
||||
# Without elevation effect
|
||||
CircularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
elevation: 0
|
||||
'''
|
||||
|
||||
|
||||
class CircularElevationButton(
|
||||
CircularRippleBehavior,
|
||||
CircularElevationBehavior,
|
||||
ButtonBehavior,
|
||||
Image,
|
||||
):
|
||||
md_bg_color = [0, 0, 1, 1]
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-elevation-effect.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"CommonElevationBehavior",
|
||||
"RectangularElevationBehavior",
|
||||
"CircularElevationBehavior",
|
||||
)
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import ListProperty, NumericProperty, ObjectProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<RectangularElevationBehavior>
|
||||
canvas.before:
|
||||
Color:
|
||||
a: self._soft_shadow_a
|
||||
Rectangle:
|
||||
texture: self._soft_shadow_texture
|
||||
size: self._soft_shadow_size
|
||||
pos: self._soft_shadow_pos
|
||||
Color:
|
||||
a: self._hard_shadow_a
|
||||
Rectangle:
|
||||
texture: self._hard_shadow_texture
|
||||
size: self._hard_shadow_size
|
||||
pos: self._hard_shadow_pos
|
||||
Color:
|
||||
a: 1
|
||||
|
||||
|
||||
<CircularElevationBehavior>
|
||||
canvas.before:
|
||||
Color:
|
||||
a: self._soft_shadow_a
|
||||
Rectangle:
|
||||
texture: self._soft_shadow_texture
|
||||
size: self._soft_shadow_size
|
||||
pos: self._soft_shadow_pos
|
||||
Color:
|
||||
a: self._hard_shadow_a
|
||||
Rectangle:
|
||||
texture: self._hard_shadow_texture
|
||||
size: self._hard_shadow_size
|
||||
pos: self._hard_shadow_pos
|
||||
Color:
|
||||
a: 1
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class CommonElevationBehavior(object):
|
||||
"""Common base class for rectangular and circular elevation behavior."""
|
||||
|
||||
elevation = NumericProperty(1)
|
||||
"""
|
||||
Elevation value.
|
||||
|
||||
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to 1.
|
||||
"""
|
||||
|
||||
_elevation = NumericProperty(0)
|
||||
_soft_shadow_texture = ObjectProperty()
|
||||
_soft_shadow_size = ListProperty((0, 0))
|
||||
_soft_shadow_pos = ListProperty((0, 0))
|
||||
_soft_shadow_a = NumericProperty(0)
|
||||
_hard_shadow_texture = ObjectProperty()
|
||||
_hard_shadow_size = ListProperty((0, 0))
|
||||
_hard_shadow_pos = ListProperty((0, 0))
|
||||
_hard_shadow_a = NumericProperty(0)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.bind(
|
||||
elevation=self._update_elevation,
|
||||
pos=self._update_shadow,
|
||||
size=self._update_shadow,
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def _update_shadow(self, *args):
|
||||
raise NotImplementedError
|
||||
|
||||
def _update_elevation(self, instance, value):
|
||||
if not self._elevation:
|
||||
self._elevation = value
|
||||
self._update_shadow(instance, value)
|
||||
|
||||
|
||||
class RectangularElevationBehavior(CommonElevationBehavior):
|
||||
"""Base class for rectangular elevation behavior.
|
||||
Controls the size and position of the shadow."""
|
||||
|
||||
def _update_shadow(self, *args):
|
||||
if self._elevation > 0:
|
||||
# Set shadow size.
|
||||
ratio = self.width / (self.height if self.height != 0 else 1)
|
||||
if -2 < ratio < 2:
|
||||
self._shadow = MDApp.get_running_app().theme_cls.quad_shadow
|
||||
width = soft_width = self.width * 1.9
|
||||
height = soft_height = self.height * 1.9
|
||||
elif ratio <= -2:
|
||||
self._shadow = MDApp.get_running_app().theme_cls.rec_st_shadow
|
||||
ratio = abs(ratio)
|
||||
if ratio > 5:
|
||||
ratio = ratio * 22
|
||||
else:
|
||||
ratio = ratio * 11.5
|
||||
width = soft_width = self.width * 1.9
|
||||
height = self.height + dp(ratio)
|
||||
soft_height = (
|
||||
self.height + dp(ratio) + dp(self._elevation) * 0.5
|
||||
)
|
||||
else:
|
||||
self._shadow = MDApp.get_running_app().theme_cls.quad_shadow
|
||||
width = soft_width = self.width * 1.8
|
||||
height = soft_height = self.height * 1.8
|
||||
|
||||
self._soft_shadow_size = (soft_width, soft_height)
|
||||
self._hard_shadow_size = (width, height)
|
||||
# Set ``soft_shadow`` parameters.
|
||||
self._soft_shadow_pos = (
|
||||
self.center_x - soft_width / 2,
|
||||
self.center_y
|
||||
- soft_height / 2
|
||||
- dp(0.1 * 1.5 ** self._elevation),
|
||||
)
|
||||
self._soft_shadow_a = 0.1 * 1.1 ** self._elevation
|
||||
self._soft_shadow_texture = self._shadow.textures[
|
||||
str(int(round(self._elevation)))
|
||||
]
|
||||
# Set ``hard_shadow`` parameters.
|
||||
self._hard_shadow_pos = (
|
||||
self.center_x - width / 2,
|
||||
self.center_y - height / 2 - dp(0.5 * 1.18 ** self._elevation),
|
||||
)
|
||||
self._hard_shadow_a = 0.4 * 0.9 ** self._elevation
|
||||
self._hard_shadow_texture = self._shadow.textures[
|
||||
str(int(round(self._elevation)))
|
||||
]
|
||||
else:
|
||||
self._soft_shadow_a = 0
|
||||
self._hard_shadow_a = 0
|
||||
|
||||
|
||||
class CircularElevationBehavior(CommonElevationBehavior):
|
||||
"""Base class for circular elevation behavior.
|
||||
Controls the size and position of the shadow."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._shadow = MDApp.get_running_app().theme_cls.round_shadow
|
||||
|
||||
def _update_shadow(self, *args):
|
||||
if self.elevation > 0:
|
||||
# Set shadow size.
|
||||
width = self.width * 2
|
||||
height = self.height * 2
|
||||
|
||||
x = self.center_x - width / 2
|
||||
self._soft_shadow_size = (width, height)
|
||||
self._hard_shadow_size = (width, height)
|
||||
# Set ``soft_shadow`` parameters.
|
||||
y = self.center_y - height / 2 - dp(0.1 * 1.5 ** self._elevation)
|
||||
self._soft_shadow_pos = (x, y)
|
||||
self._soft_shadow_a = 0.1 * 1.1 ** self._elevation
|
||||
if hasattr(self, "_shadow"):
|
||||
self._soft_shadow_texture = self._shadow.textures[
|
||||
str(int(round(self._elevation)))
|
||||
]
|
||||
# Set ``hard_shadow`` parameters.
|
||||
y = self.center_y - height / 2 - dp(0.5 * 1.18 ** self._elevation)
|
||||
self._hard_shadow_pos = (x, y)
|
||||
self._hard_shadow_a = 0.4 * 0.9 ** self._elevation
|
||||
if hasattr(self, "_shadow"):
|
||||
self._hard_shadow_texture = self._shadow.textures[
|
||||
str(int(round(self._elevation)))
|
||||
]
|
||||
else:
|
||||
self._soft_shadow_a = 0
|
||||
self._hard_shadow_a = 0
|
122
kivymd/uix/behaviors/focus_behavior.py
Normal file
|
@ -0,0 +1,122 @@
|
|||
"""
|
||||
Behaviors/Focus
|
||||
===============
|
||||
|
||||
.. rubric:: Changing the background color when the mouse is on the widget.
|
||||
|
||||
To apply focus behavior, you must create a new class that is inherited from the
|
||||
widget to which you apply the behavior and from the :class:`FocusBehavior` class.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import RectangularElevationBehavior, FocusBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
md_bg_color: 1, 1, 1, 1
|
||||
|
||||
FocusWidget:
|
||||
size_hint: .5, .3
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
md_bg_color: app.theme_cls.bg_light
|
||||
|
||||
MDLabel:
|
||||
text: "Label"
|
||||
theme_text_color: "Primary"
|
||||
pos_hint: {"center_y": .5}
|
||||
halign: "center"
|
||||
'''
|
||||
|
||||
|
||||
class FocusWidget(MDBoxLayout, RectangularElevationBehavior, FocusBehavior):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-widget.gif
|
||||
:align: center
|
||||
|
||||
Color change at focus/defocus
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
FocusWidget:
|
||||
focus_color: 1, 0, 1, 1
|
||||
unfocus_color: 0, 0, 1, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-defocus-color.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("FocusBehavior",)
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.properties import BooleanProperty, ListProperty
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.uix.behaviors import HoverBehavior
|
||||
|
||||
|
||||
class FocusBehavior(HoverBehavior, ButtonBehavior):
|
||||
|
||||
focus_behavior = BooleanProperty(True)
|
||||
"""
|
||||
Using focus when hovering over a widget.
|
||||
|
||||
:attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
focus_color = ListProperty()
|
||||
"""
|
||||
The color of the widget when the mouse enters the bbox of the widget.
|
||||
|
||||
:attr:`focus_color` is a :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
unfocus_color = ListProperty()
|
||||
"""
|
||||
The color of the widget when the mouse exits the bbox widget.
|
||||
|
||||
:attr:`unfocus_color` is a :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
def on_enter(self):
|
||||
"""Called when mouse enter the bbox of the widget."""
|
||||
|
||||
if hasattr(self, "md_bg_color") and self.focus_behavior:
|
||||
if hasattr(self, "theme_cls") and not self.focus_color:
|
||||
self.md_bg_color = self.theme_cls.bg_normal
|
||||
else:
|
||||
if not self.focus_color:
|
||||
self.md_bg_color = App.get_running_app().theme_cls.bg_normal
|
||||
else:
|
||||
self.md_bg_color = self.focus_color
|
||||
|
||||
def on_leave(self):
|
||||
"""Called when the mouse exit the widget."""
|
||||
|
||||
if hasattr(self, "md_bg_color") and self.focus_behavior:
|
||||
if hasattr(self, "theme_cls") and not self.unfocus_color:
|
||||
self.md_bg_color = self.theme_cls.bg_light
|
||||
else:
|
||||
if not self.unfocus_color:
|
||||
self.md_bg_color = App.get_running_app().theme_cls.bg_light
|
||||
else:
|
||||
self.md_bg_color = self.unfocus_color
|
141
kivymd/uix/behaviors/hover_behavior.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
"""
|
||||
Behaviors/Hover
|
||||
===============
|
||||
|
||||
.. rubric:: Changing when the mouse is on the widget.
|
||||
|
||||
To apply hover behavior, you must create a new class that is inherited from the
|
||||
widget to which you apply the behavior and from the :attr:`HoverBehavior` class.
|
||||
|
||||
In `KV file`:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<HoverItem@MDBoxLayout+ThemableBehavior+HoverBehavior>
|
||||
|
||||
In `python file`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class HoverItem(MDBoxLayout, ThemableBehavior, HoverBehavior):
|
||||
'''Custom item implementing hover behavior.'''
|
||||
|
||||
After creating a class, you must define two methods for it:
|
||||
:attr:`HoverBehavior.on_enter` and :attr:`HoverBehavior.on_leave`, which will be automatically called
|
||||
when the mouse cursor is over the widget and when the mouse cursor goes beyond
|
||||
the widget.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import HoverBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
KV = '''
|
||||
Screen
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
size_hint: .8, .8
|
||||
md_bg_color: app.theme_cls.bg_darkest
|
||||
'''
|
||||
|
||||
|
||||
class HoverItem(MDBoxLayout, ThemableBehavior, HoverBehavior):
|
||||
'''Custom item implementing hover behavior.'''
|
||||
|
||||
def on_enter(self, *args):
|
||||
'''The method will be called when the mouse cursor
|
||||
is within the borders of the current widget.'''
|
||||
|
||||
self.md_bg_color = (1, 1, 1, 1)
|
||||
|
||||
def on_leave(self, *args):
|
||||
'''The method will be called when the mouse cursor goes beyond
|
||||
the borders of the current widget.'''
|
||||
|
||||
self.md_bg_color = self.theme_cls.bg_darkest
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.screen = Builder.load_string(KV)
|
||||
for i in range(5):
|
||||
self.screen.ids.box.add_widget(HoverItem())
|
||||
return self.screen
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hover-behavior.gif
|
||||
:width: 250 px
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("HoverBehavior",)
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.factory import Factory
|
||||
from kivy.properties import BooleanProperty, ObjectProperty
|
||||
|
||||
|
||||
class HoverBehavior(object):
|
||||
"""
|
||||
:Events:
|
||||
:attr:`on_enter`
|
||||
Call when mouse enter the bbox of the widget.
|
||||
:attr:`on_leave`
|
||||
Call when the mouse exit the widget.
|
||||
"""
|
||||
|
||||
hovered = BooleanProperty(False)
|
||||
"""
|
||||
`True`, if the mouse cursor is within the borders of the widget.
|
||||
|
||||
:attr:`hovered` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
border_point = ObjectProperty(None)
|
||||
"""Contains the last relevant point received by the Hoverable.
|
||||
This can be used in :attr:`on_enter` or :attr:`on_leave` in order
|
||||
to know where was dispatched the event.
|
||||
|
||||
:attr:`border_point` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.register_event_type("on_enter")
|
||||
self.register_event_type("on_leave")
|
||||
Window.bind(mouse_pos=self.on_mouse_pos)
|
||||
super(HoverBehavior, self).__init__(**kwargs)
|
||||
|
||||
def on_mouse_pos(self, *args):
|
||||
if not self.get_root_window():
|
||||
return # do proceed if I'm not displayed <=> If have no parent
|
||||
pos = args[1]
|
||||
# Next line to_widget allow to compensate for relative layout
|
||||
inside = self.collide_point(*self.to_widget(*pos))
|
||||
if self.hovered == inside:
|
||||
# We have already done what was needed
|
||||
return
|
||||
self.border_point = pos
|
||||
self.hovered = inside
|
||||
if inside:
|
||||
self.dispatch("on_enter")
|
||||
else:
|
||||
self.dispatch("on_leave")
|
||||
|
||||
def on_enter(self):
|
||||
"""Call when mouse enter the bbox of the widget."""
|
||||
|
||||
def on_leave(self):
|
||||
"""Call when the mouse exit the widget."""
|
||||
|
||||
|
||||
Factory.register("HoverBehavior", HoverBehavior)
|
172
kivymd/uix/behaviors/magic_behavior.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
"""
|
||||
Behaviors/Magic
|
||||
===============
|
||||
|
||||
.. rubric:: Magical effects for buttons.
|
||||
|
||||
.. warning:: Magic effects do not work correctly with `KivyMD` buttons!
|
||||
|
||||
To apply magic effects, you must create a new class that is inherited from the
|
||||
widget to which you apply the effect and from the :attr:`MagicBehavior` class.
|
||||
|
||||
In `KV file`:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<MagicButton@MagicBehavior+MDRectangleFlatButton>
|
||||
|
||||
In `python file`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MagicButton(MagicBehavior, MDRectangleFlatButton):
|
||||
pass
|
||||
|
||||
.. rubric:: The :attr:`MagicBehavior` class provides five effects:
|
||||
|
||||
- :attr:`MagicBehavior.wobble`
|
||||
- :attr:`MagicBehavior.grow`
|
||||
- :attr:`MagicBehavior.shake`
|
||||
- :attr:`MagicBehavior.twist`
|
||||
- :attr:`MagicBehavior.shrink`
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivy.lang import Builder
|
||||
|
||||
KV = '''
|
||||
#:import MagicBehavior kivymd.uix.behaviors.MagicBehavior
|
||||
|
||||
|
||||
<MagicButton@MagicBehavior+MDRectangleFlatButton>
|
||||
|
||||
|
||||
FloatLayout:
|
||||
|
||||
MagicButton:
|
||||
text: "WOBBLE EFFECT"
|
||||
on_release: self.wobble()
|
||||
pos_hint: {"center_x": .5, "center_y": .3}
|
||||
|
||||
MagicButton:
|
||||
text: "GROW EFFECT"
|
||||
on_release: self.grow()
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
|
||||
MagicButton:
|
||||
text: "SHAKE EFFECT"
|
||||
on_release: self.shake()
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
MagicButton:
|
||||
text: "TWIST EFFECT"
|
||||
on_release: self.twist()
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
|
||||
MagicButton:
|
||||
text: "SHRINK EFFECT"
|
||||
on_release: self.shrink()
|
||||
pos_hint: {"center_x": .5, "center_y": .7}
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/magic-button.gif
|
||||
:width: 250 px
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("MagicBehavior",)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.factory import Factory
|
||||
from kivy.lang import Builder
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<MagicBehavior>
|
||||
translate_x: 0
|
||||
translate_y: 0
|
||||
scale_x: 1
|
||||
scale_y: 1
|
||||
rotate: 0
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Translate:
|
||||
x: self.translate_x or 0
|
||||
y: self.translate_y or 0
|
||||
Rotate:
|
||||
origin: self.center
|
||||
angle: self.rotate or 0
|
||||
Scale:
|
||||
origin: self.center
|
||||
x: self.scale_x or 1
|
||||
y: self.scale_y or 1
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MagicBehavior:
|
||||
def grow(self):
|
||||
"""Grow effect animation."""
|
||||
|
||||
Animation.stop_all(self)
|
||||
(
|
||||
Animation(scale_x=1.2, scale_y=1.2, t="out_quad", d=0.03)
|
||||
+ Animation(scale_x=1, scale_y=1, t="out_elastic", d=0.4)
|
||||
).start(self)
|
||||
|
||||
def shake(self):
|
||||
"""Shake effect animation."""
|
||||
|
||||
Animation.stop_all(self)
|
||||
(
|
||||
Animation(translate_x=50, t="out_quad", d=0.02)
|
||||
+ Animation(translate_x=0, t="out_elastic", d=0.5)
|
||||
).start(self)
|
||||
|
||||
def wobble(self):
|
||||
"""Wobble effect animation."""
|
||||
|
||||
Animation.stop_all(self)
|
||||
(
|
||||
(
|
||||
Animation(scale_y=0.7, t="out_quad", d=0.03)
|
||||
& Animation(scale_x=1.4, t="out_quad", d=0.03)
|
||||
)
|
||||
+ (
|
||||
Animation(scale_y=1, t="out_elastic", d=0.5)
|
||||
& Animation(scale_x=1, t="out_elastic", d=0.4)
|
||||
)
|
||||
).start(self)
|
||||
|
||||
def twist(self):
|
||||
"""Twist effect animation."""
|
||||
|
||||
Animation.stop_all(self)
|
||||
(
|
||||
Animation(rotate=25, t="out_quad", d=0.05)
|
||||
+ Animation(rotate=0, t="out_elastic", d=0.5)
|
||||
).start(self)
|
||||
|
||||
def shrink(self):
|
||||
"""Shrink effect animation."""
|
||||
|
||||
Animation.stop_all(self)
|
||||
Animation(scale_x=0.95, scale_y=0.95, t="out_quad", d=0.1).start(self)
|
||||
|
||||
|
||||
Factory.register("MagicBehavior", cls=MagicBehavior)
|
391
kivymd/uix/behaviors/ripplebehavior.py
Executable file
|
@ -0,0 +1,391 @@
|
|||
"""
|
||||
Behaviors/Ripple
|
||||
================
|
||||
|
||||
.. rubric:: Classes implements a circular and rectangular ripple effects.
|
||||
|
||||
To create a widget with сircular ripple effect, you must create a new class
|
||||
that inherits from the :class:`~CircularRippleBehavior` class.
|
||||
|
||||
For example, let's create an image button with a circular ripple effect:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.image import Image
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior
|
||||
|
||||
KV = '''
|
||||
#:import images_path kivymd.images_path
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
CircularRippleButton:
|
||||
source: f"{images_path}/kivymd.png"
|
||||
size_hint: None, None
|
||||
size: "250dp", "250dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class CircularRippleButton(CircularRippleBehavior, ButtonBehavior, Image):
|
||||
def __init__(self, **kwargs):
|
||||
self.ripple_scale = 0.85
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-ripple-effect.gif
|
||||
:align: center
|
||||
|
||||
To create a widget with rectangular ripple effect, you must create a new class
|
||||
that inherits from the :class:`~RectangularRippleBehavior` class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import RectangularRippleBehavior, BackgroundColorBehavior
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
RectangularRippleButton:
|
||||
size_hint: None, None
|
||||
size: "250dp", "50dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class RectangularRippleButton(
|
||||
RectangularRippleBehavior, ButtonBehavior, BackgroundColorBehavior
|
||||
):
|
||||
md_bg_color = [0, 0, 1, 1]
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-ripple-effect.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"CommonRipple",
|
||||
"RectangularRippleBehavior",
|
||||
"CircularRippleBehavior",
|
||||
)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.graphics import (
|
||||
Color,
|
||||
Ellipse,
|
||||
Rectangle,
|
||||
StencilPop,
|
||||
StencilPush,
|
||||
StencilUnUse,
|
||||
StencilUse,
|
||||
)
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
StringProperty,
|
||||
)
|
||||
|
||||
|
||||
class CommonRipple(object):
|
||||
"""Base class for ripple effect."""
|
||||
|
||||
ripple_rad_default = NumericProperty(1)
|
||||
"""
|
||||
Default value of the ripple effect radius.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-rad-default.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_rad_default` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
ripple_color = ListProperty()
|
||||
"""
|
||||
Ripple color in ``rgba`` format.
|
||||
|
||||
:attr:`ripple_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
ripple_alpha = NumericProperty(0.5)
|
||||
"""
|
||||
Alpha channel values for ripple effect.
|
||||
|
||||
:attr:`ripple_alpha` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.5`.
|
||||
"""
|
||||
|
||||
ripple_scale = NumericProperty(None)
|
||||
"""
|
||||
Ripple effect scale.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-scale-1.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
ripple_duration_in_fast = NumericProperty(0.3)
|
||||
"""
|
||||
Ripple duration when touching to widget.
|
||||
|
||||
:attr:`ripple_duration_in_fast` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.3`.
|
||||
"""
|
||||
|
||||
ripple_duration_in_slow = NumericProperty(2)
|
||||
"""
|
||||
Ripple duration when long touching to widget.
|
||||
|
||||
:attr:`ripple_duration_in_slow` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2`.
|
||||
"""
|
||||
|
||||
ripple_duration_out = NumericProperty(0.3)
|
||||
"""
|
||||
The duration of the disappearance of the wave effect.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-duration-out.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_duration_out` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.3`.
|
||||
"""
|
||||
|
||||
ripple_func_in = StringProperty("out_quad")
|
||||
"""
|
||||
Type of animation for ripple in effect.
|
||||
|
||||
:attr:`ripple_func_in` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_quad'`.
|
||||
"""
|
||||
|
||||
ripple_func_out = StringProperty("out_quad")
|
||||
"""
|
||||
Type of animation for ripple out effect.
|
||||
|
||||
:attr:`ripple_func_in` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'ripple_func_out'`.
|
||||
"""
|
||||
|
||||
_ripple_rad = NumericProperty()
|
||||
_doing_ripple = BooleanProperty(False)
|
||||
_finishing_ripple = BooleanProperty(False)
|
||||
_fading_out = BooleanProperty(False)
|
||||
_no_ripple_effect = BooleanProperty(False)
|
||||
|
||||
def lay_canvas_instructions(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def start_ripple(self):
|
||||
if not self._doing_ripple:
|
||||
self._doing_ripple = True
|
||||
anim = Animation(
|
||||
_ripple_rad=self.finish_rad,
|
||||
t="linear",
|
||||
duration=self.ripple_duration_in_slow,
|
||||
)
|
||||
anim.bind(on_complete=self.fade_out)
|
||||
|
||||
anim.start(self)
|
||||
|
||||
def finish_ripple(self):
|
||||
if self._doing_ripple and not self._finishing_ripple:
|
||||
self._finishing_ripple = True
|
||||
self._doing_ripple = False
|
||||
Animation.cancel_all(self, "_ripple_rad")
|
||||
anim = Animation(
|
||||
_ripple_rad=self.finish_rad,
|
||||
t=self.ripple_func_in,
|
||||
duration=self.ripple_duration_in_fast,
|
||||
)
|
||||
anim.bind(on_complete=self.fade_out)
|
||||
anim.start(self)
|
||||
|
||||
def fade_out(self, *args):
|
||||
rc = self.ripple_color
|
||||
if not self._fading_out:
|
||||
self._fading_out = True
|
||||
Animation.cancel_all(self, "ripple_color")
|
||||
anim = Animation(
|
||||
ripple_color=[rc[0], rc[1], rc[2], 0.0],
|
||||
t=self.ripple_func_out,
|
||||
duration=self.ripple_duration_out,
|
||||
)
|
||||
anim.bind(on_complete=self.anim_complete)
|
||||
anim.start(self)
|
||||
|
||||
def anim_complete(self, *args):
|
||||
self._doing_ripple = False
|
||||
self._finishing_ripple = False
|
||||
self._fading_out = False
|
||||
self.canvas.after.clear()
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if touch.is_mouse_scrolling:
|
||||
return False
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
return False
|
||||
|
||||
if not self.disabled:
|
||||
if self._doing_ripple:
|
||||
Animation.cancel_all(
|
||||
self, "_ripple_rad", "ripple_color", "rect_color"
|
||||
)
|
||||
self.anim_complete()
|
||||
self._ripple_rad = self.ripple_rad_default
|
||||
self.ripple_pos = (touch.x, touch.y)
|
||||
|
||||
if self.ripple_color:
|
||||
pass
|
||||
elif hasattr(self, "theme_cls"):
|
||||
self.ripple_color = self.theme_cls.ripple_color
|
||||
else:
|
||||
# If no theme, set Gray 300.
|
||||
self.ripple_color = [
|
||||
0.8784313725490196,
|
||||
0.8784313725490196,
|
||||
0.8784313725490196,
|
||||
self.ripple_alpha,
|
||||
]
|
||||
self.ripple_color[3] = self.ripple_alpha
|
||||
self.lay_canvas_instructions()
|
||||
self.finish_rad = max(self.width, self.height) * self.ripple_scale
|
||||
self.start_ripple()
|
||||
return super().on_touch_down(touch)
|
||||
|
||||
def on_touch_move(self, touch, *args):
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
if not self._finishing_ripple and self._doing_ripple:
|
||||
self.finish_ripple()
|
||||
return super().on_touch_move(touch, *args)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if self.collide_point(touch.x, touch.y) and self._doing_ripple:
|
||||
self.finish_ripple()
|
||||
return super().on_touch_up(touch)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
self.ellipse.size = (self._ripple_rad, self._ripple_rad)
|
||||
|
||||
# Adjust ellipse pos here
|
||||
|
||||
def _set_color(self, instance, value):
|
||||
self.col_instruction.a = value[3]
|
||||
|
||||
|
||||
class RectangularRippleBehavior(CommonRipple):
|
||||
"""Class implements a rectangular ripple effect."""
|
||||
|
||||
ripple_scale = NumericProperty(2.75)
|
||||
"""
|
||||
See :class:`~CommonRipple.ripple_scale`.
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2.75`.
|
||||
"""
|
||||
|
||||
def lay_canvas_instructions(self):
|
||||
if self._no_ripple_effect:
|
||||
return
|
||||
with self.canvas.after:
|
||||
StencilPush()
|
||||
Rectangle(pos=self.pos, size=self.size)
|
||||
StencilUse()
|
||||
self.col_instruction = Color(rgba=self.ripple_color)
|
||||
self.ellipse = Ellipse(
|
||||
size=(self._ripple_rad, self._ripple_rad),
|
||||
pos=(
|
||||
self.ripple_pos[0] - self._ripple_rad / 2.0,
|
||||
self.ripple_pos[1] - self._ripple_rad / 2.0,
|
||||
),
|
||||
)
|
||||
StencilUnUse()
|
||||
Rectangle(pos=self.pos, size=self.size)
|
||||
StencilPop()
|
||||
self.bind(ripple_color=self._set_color, _ripple_rad=self._set_ellipse)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
super()._set_ellipse(instance, value)
|
||||
self.ellipse.pos = (
|
||||
self.ripple_pos[0] - self._ripple_rad / 2.0,
|
||||
self.ripple_pos[1] - self._ripple_rad / 2.0,
|
||||
)
|
||||
|
||||
|
||||
class CircularRippleBehavior(CommonRipple):
|
||||
"""Class implements a circular ripple effect."""
|
||||
|
||||
ripple_scale = NumericProperty(1)
|
||||
"""
|
||||
See :class:`~CommonRipple.ripple_scale`.
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
def lay_canvas_instructions(self):
|
||||
with self.canvas.after:
|
||||
StencilPush()
|
||||
self.stencil = Ellipse(
|
||||
size=(
|
||||
self.width * self.ripple_scale,
|
||||
self.height * self.ripple_scale,
|
||||
),
|
||||
pos=(
|
||||
self.center_x - (self.width * self.ripple_scale) / 2,
|
||||
self.center_y - (self.height * self.ripple_scale) / 2,
|
||||
),
|
||||
)
|
||||
StencilUse()
|
||||
self.col_instruction = Color(rgba=self.ripple_color)
|
||||
self.ellipse = Ellipse(
|
||||
size=(self._ripple_rad, self._ripple_rad),
|
||||
pos=(
|
||||
self.center_x - self._ripple_rad / 2.0,
|
||||
self.center_y - self._ripple_rad / 2.0,
|
||||
),
|
||||
)
|
||||
StencilUnUse()
|
||||
Ellipse(pos=self.pos, size=self.size)
|
||||
StencilPop()
|
||||
self.bind(
|
||||
ripple_color=self._set_color, _ripple_rad=self._set_ellipse
|
||||
)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
super()._set_ellipse(instance, value)
|
||||
if self.ellipse.size[0] > self.width * 0.6 and not self._fading_out:
|
||||
self.fade_out()
|
||||
self.ellipse.pos = (
|
||||
self.center_x - self._ripple_rad / 2.0,
|
||||
self.center_y - self._ripple_rad / 2.0,
|
||||
)
|
202
kivymd/uix/behaviors/toggle_behavior.py
Normal file
|
@ -0,0 +1,202 @@
|
|||
"""
|
||||
Behaviors/ToggleButton
|
||||
======================
|
||||
|
||||
This behavior must always be inherited after the button's Widget class since it
|
||||
works with the inherited properties of the button class.
|
||||
|
||||
example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyToggleButtonWidget(MDFlatButton, MDToggleButton):
|
||||
# [...]
|
||||
pass
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
|
||||
from kivymd.uix.button import MDRectangleFlatButton
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
MyToggleButton:
|
||||
text: "Show ads"
|
||||
group: "x"
|
||||
|
||||
MyToggleButton:
|
||||
text: "Do not show ads"
|
||||
group: "x"
|
||||
|
||||
MyToggleButton:
|
||||
text: "Does not matter"
|
||||
group: "x"
|
||||
'''
|
||||
|
||||
|
||||
class MyToggleButton(MDRectangleFlatButton, MDToggleButton):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.background_down = self.theme_cls.primary_light
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif
|
||||
:align: center
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyToggleButton(MDFillRoundFlatButton, MDToggleButton):
|
||||
def __init__(self, **kwargs):
|
||||
self.background_down = MDApp.get_running_app().theme_cls.primary_dark
|
||||
super().__init__(**kwargs)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-2.gif
|
||||
:align: center
|
||||
|
||||
You can inherit the ``MyToggleButton`` class only from the following classes
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
- :class:`~kivymd.uix.button.MDRaisedButton`
|
||||
- :class:`~kivymd.uix.button.MDFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRectangleFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRectangleFlatIconButton`
|
||||
- :class:`~kivymd.uix.button.MDRoundFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRoundFlatIconButton`
|
||||
- :class:`~kivymd.uix.button.MDFillRoundFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDFillRoundFlatIconButton`
|
||||
"""
|
||||
|
||||
__all__ = ("MDToggleButton",)
|
||||
|
||||
from kivy.properties import BooleanProperty, ListProperty
|
||||
from kivy.uix.behaviors import ToggleButtonBehavior
|
||||
|
||||
from kivymd.uix.button import (
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
MDFlatButton,
|
||||
MDRaisedButton,
|
||||
MDRectangleFlatButton,
|
||||
MDRectangleFlatIconButton,
|
||||
MDRoundFlatButton,
|
||||
MDRoundFlatIconButton,
|
||||
)
|
||||
|
||||
|
||||
class MDToggleButton(ToggleButtonBehavior):
|
||||
background_normal = ListProperty()
|
||||
"""
|
||||
Color of the button in ``rgba`` format for the 'normal' state.
|
||||
|
||||
:attr:`background_normal` is a :class:`~kivy.properties.ListProperty`
|
||||
and is defaults to `[]`.
|
||||
"""
|
||||
|
||||
background_down = ListProperty()
|
||||
"""
|
||||
Color of the button in ``rgba`` format for the 'down' state.
|
||||
|
||||
:attr:`background_down` is a :class:`~kivy.properties.ListProperty`
|
||||
and is defaults to `[]`.
|
||||
"""
|
||||
|
||||
font_color_normal = ListProperty()
|
||||
"""
|
||||
Color of the font's button in ``rgba`` format for the 'normal' state.
|
||||
|
||||
:attr:`font_color_normal` is a :class:`~kivy.properties.ListProperty`
|
||||
and is defaults to `[]`.
|
||||
"""
|
||||
|
||||
font_color_down = ListProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Color of the font's button in ``rgba`` format for the 'down' state.
|
||||
|
||||
:attr:`font_color_down` is a :class:`~kivy.properties.ListProperty`
|
||||
and is defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
__is_filled = BooleanProperty(False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
classinfo = (
|
||||
MDRaisedButton,
|
||||
MDFlatButton,
|
||||
MDRectangleFlatButton,
|
||||
MDRectangleFlatIconButton,
|
||||
MDRoundFlatButton,
|
||||
MDRoundFlatIconButton,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
)
|
||||
# Do the object inherited from the "supported" buttons?
|
||||
if not issubclass(self.__class__, classinfo):
|
||||
raise ValueError(
|
||||
f"Class {self.__class__} must be inherited from one of the classes in the list {classinfo}"
|
||||
)
|
||||
if (
|
||||
not self.background_normal
|
||||
): # This means that if the value == [] or None will return True.
|
||||
# If the object inherits from buttons with background:
|
||||
if isinstance(
|
||||
self,
|
||||
(
|
||||
MDRaisedButton,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
),
|
||||
):
|
||||
self.__is_filled = True
|
||||
self.background_normal = self.theme_cls.primary_color
|
||||
# If not the background_normal must be the same as the inherited one:
|
||||
else:
|
||||
self.background_normal = self.md_bg_color[:]
|
||||
# If no background_down is setted:
|
||||
if (
|
||||
not self.background_down
|
||||
): # This means that if the value == [] or None will return True.
|
||||
self.background_down = (
|
||||
self.theme_cls.primary_dark
|
||||
) # get the primary_color dark from theme_cls
|
||||
if not self.font_color_normal:
|
||||
self.font_color_normal = self.theme_cls.primary_color
|
||||
# Alternative to bind the function to the property.
|
||||
# self.bind(state=self._update_bg)
|
||||
self.fbind("state", self._update_bg)
|
||||
|
||||
def _update_bg(self, ins, val):
|
||||
"""Updates the color of the background."""
|
||||
|
||||
if val == "down":
|
||||
self.md_bg_color = self.background_down
|
||||
if (
|
||||
self.__is_filled is False
|
||||
): # If the background is transparent, and the button it toggled,
|
||||
# the font color must be withe [1, 1, 1, 1].
|
||||
self.text_color = self.font_color_down
|
||||
if self.group:
|
||||
self._release_group(self)
|
||||
else:
|
||||
self.md_bg_color = self.background_normal
|
||||
if (
|
||||
self.__is_filled is False
|
||||
): # If the background is transparent, the font color must be the
|
||||
# primary color.
|
||||
self.text_color = self.font_color_normal
|
100
kivymd/uix/behaviors/touch_behavior.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
"""
|
||||
Behaviors/Touch
|
||||
===============
|
||||
|
||||
.. rubric:: Provides easy access to events.
|
||||
|
||||
The following events are available:
|
||||
|
||||
- on_long_touch
|
||||
- on_double_tap
|
||||
- on_triple_tap
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import TouchBehavior
|
||||
from kivymd.uix.button import MDRaisedButton
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MyButton:
|
||||
text: "PRESS ME"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class MyButton(MDRaisedButton, TouchBehavior):
|
||||
def on_long_touch(self, *args):
|
||||
print("<on_long_touch> event")
|
||||
|
||||
def on_double_tap(self, *args):
|
||||
print("<on_double_tap> event")
|
||||
|
||||
def on_triple_tap(self, *args):
|
||||
print("<on_triple_tap> event")
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
MainApp().run()
|
||||
"""
|
||||
|
||||
__all__ = ("TouchBehavior",)
|
||||
|
||||
from functools import partial
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.properties import NumericProperty
|
||||
|
||||
|
||||
class TouchBehavior:
|
||||
duration_long_touch = NumericProperty(0.4)
|
||||
"""
|
||||
Time for a long touch.
|
||||
|
||||
:attr:`duration_long_touch` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.4`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bind(
|
||||
on_touch_down=self.create_clock, on_touch_up=self.delete_clock
|
||||
)
|
||||
|
||||
def create_clock(self, widget, touch, *args):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
callback = partial(self.on_long_touch, touch)
|
||||
Clock.schedule_once(callback, self.duration_long_touch)
|
||||
touch.ud["event"] = callback
|
||||
|
||||
def delete_clock(self, widget, touch, *args):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
try:
|
||||
Clock.unschedule(touch.ud["event"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if touch.is_double_tap:
|
||||
self.on_double_tap(touch, *args)
|
||||
if touch.is_triple_tap:
|
||||
self.on_triple_tap(touch, *args)
|
||||
|
||||
def on_long_touch(self, touch, *args):
|
||||
"""Called when the widget is pressed for a long time."""
|
||||
|
||||
def on_double_tap(self, touch, *args):
|
||||
"""Called by double clicking on the widget."""
|
||||
|
||||
def on_triple_tap(self, touch, *args):
|
||||
"""Called by triple clicking on the widget."""
|
646
kivymd/uix/bottomnavigation.py
Executable file
|
@ -0,0 +1,646 @@
|
|||
"""
|
||||
Components/Bottom Navigation
|
||||
============================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Bottom navigation <https://material.io/components/bottom-navigation>`_
|
||||
|
||||
.. rubric:: Bottom navigation bars allow movement between primary destinations in an app:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<Root>>:
|
||||
|
||||
MDBottomNavigation:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 1"
|
||||
|
||||
YourContent:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 2"
|
||||
|
||||
YourContent:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 3"
|
||||
|
||||
YourContent:
|
||||
|
||||
For ease of understanding, this code works like this:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<Root>>:
|
||||
|
||||
ScreenManager:
|
||||
|
||||
Screen:
|
||||
name: "screen 1"
|
||||
|
||||
YourContent:
|
||||
|
||||
Screen:
|
||||
name: "screen 2"
|
||||
|
||||
YourContent:
|
||||
|
||||
Screen:
|
||||
name: "screen 3"
|
||||
|
||||
YourContent:
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivy.lang import Builder
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
|
||||
def build(self):
|
||||
self.theme_cls.primary_palette = "Gray"
|
||||
return Builder.load_string(
|
||||
'''
|
||||
BoxLayout:
|
||||
orientation:'vertical'
|
||||
|
||||
MDToolbar:
|
||||
title: 'Bottom navigation'
|
||||
md_bg_color: .2, .2, .2, 1
|
||||
specific_text_color: 1, 1, 1, 1
|
||||
|
||||
MDBottomNavigation:
|
||||
panel_color: .2, .2, .2, 1
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: 'screen 1'
|
||||
text: 'Python'
|
||||
icon: 'language-python'
|
||||
|
||||
MDLabel:
|
||||
text: 'Python'
|
||||
halign: 'center'
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: 'screen 2'
|
||||
text: 'C++'
|
||||
icon: 'language-cpp'
|
||||
|
||||
MDLabel:
|
||||
text: 'I programming of C++'
|
||||
halign: 'center'
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: 'screen 3'
|
||||
text: 'JS'
|
||||
icon: 'language-javascript'
|
||||
|
||||
MDLabel:
|
||||
text: 'JS'
|
||||
halign: 'center'
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif
|
||||
:align: center
|
||||
|
||||
.. rubric:: :class:`~MDBottomNavigationItem` provides the following events for use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
__events__ = (
|
||||
"on_tab_touch_down",
|
||||
"on_tab_touch_move",
|
||||
"on_tab_touch_up",
|
||||
"on_tab_press",
|
||||
"on_tab_release",
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
See :class:`~MDTab.__events__`
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
Root:
|
||||
|
||||
MDBottomNavigation:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
on_tab_touch_down: print("on_tab_touch_down")
|
||||
on_tab_touch_move: print("on_tab_touch_move")
|
||||
on_tab_touch_up: print("on_tab_touch_up")
|
||||
on_tab_press: print("on_tab_press")
|
||||
on_tab_release: print("on_tab_release")
|
||||
|
||||
YourContent:
|
||||
|
||||
How to automatically switch a tab?
|
||||
----------------------------------
|
||||
|
||||
Use method :attr:`~MDBottomNavigation.switch_tab` which takes as argument
|
||||
the name of the tab you want to switch to.
|
||||
|
||||
How to change icon color?
|
||||
-------------------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBottomNavigation:
|
||||
text_color_active: 1, 0, 1, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_active.png
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBottomNavigation:
|
||||
text_color_normal: 1, 0, 1, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_normal.png
|
||||
|
||||
.. seealso::
|
||||
|
||||
`See Tab auto switch example <https://github.com/kivymd/KivyMD/wiki/Components-Tabs-Auto-Switch>`_
|
||||
|
||||
`See full example <https://github.com/kivymd/KivyMD/wiki/Components-Bottom-Navigation>`_
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"TabbedPanelBase",
|
||||
"MDBottomNavigationItem",
|
||||
"MDBottomNavigation",
|
||||
"MDTab",
|
||||
)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import sp
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.screenmanager import Screen, ScreenManagerException
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import RectangularElevationBehavior
|
||||
from kivymd.uix.behaviors.backgroundcolorbehavior import (
|
||||
BackgroundColorBehavior,
|
||||
SpecificBackgroundColorBehavior,
|
||||
)
|
||||
from kivymd.uix.button import BaseFlatButton, BasePressedButton
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import sm kivy.uix.screenmanager
|
||||
#:import Window kivy.core.window.Window
|
||||
|
||||
|
||||
<MDBottomNavigation>
|
||||
id: panel
|
||||
orientation: 'vertical'
|
||||
height: dp(56) # Spec
|
||||
|
||||
ScreenManager:
|
||||
id: tab_manager
|
||||
transition: sm.FadeTransition(duration=.2)
|
||||
current: root.current
|
||||
screens: root.tabs
|
||||
|
||||
MDBottomNavigationBar:
|
||||
id: bottom_panel
|
||||
size_hint_y: None
|
||||
height: dp(56)
|
||||
md_bg_color: root.theme_cls.bg_dark if not root.panel_color else root.panel_color
|
||||
|
||||
BoxLayout:
|
||||
id: tab_bar
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
height: dp(56)
|
||||
size_hint: None, None
|
||||
|
||||
|
||||
<MDBottomNavigationHeader>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.panel_color
|
||||
#rgba: self.panel.theme_cls.bg_dark if not root.panel_color else root.panel_color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
width:
|
||||
root.panel.width / len(root.panel.ids.tab_manager.screens)\
|
||||
if len(root.panel.ids.tab_manager.screens) != 0 else root.panel.width
|
||||
padding: (dp(12), dp(12))
|
||||
on_press:
|
||||
self.tab.dispatch('on_tab_press')
|
||||
on_release: self.tab.dispatch('on_tab_release')
|
||||
on_touch_down: self.tab.dispatch('on_tab_touch_down',*args)
|
||||
on_touch_move: self.tab.dispatch('on_tab_touch_move',*args)
|
||||
on_touch_up: self.tab.dispatch('on_tab_touch_up',*args)
|
||||
|
||||
FloatLayout:
|
||||
id: item_container
|
||||
|
||||
MDIcon:
|
||||
id: _label_icon
|
||||
icon: root.tab.icon
|
||||
size_hint_x: None
|
||||
text_size: (None, root.height)
|
||||
height: self.texture_size[1]
|
||||
theme_text_color: 'Custom'
|
||||
text_color: root._text_color_normal
|
||||
valign: 'middle'
|
||||
halign: 'center'
|
||||
opposite_colors: root.opposite_colors
|
||||
pos: [self.pos[0], self.pos[1]]
|
||||
font_size: dp(24)
|
||||
pos_hint: {'center_x': .5, 'center_y': .7}
|
||||
|
||||
MDLabel:
|
||||
id: _label
|
||||
text: root.tab.text
|
||||
font_style: 'Button'
|
||||
size_hint_x: None
|
||||
text_size: (None, root.height)
|
||||
height: self.texture_size[1]
|
||||
theme_text_color: 'Custom'
|
||||
text_color: root._text_color_normal
|
||||
valign: 'bottom'
|
||||
halign: 'center'
|
||||
opposite_colors: root.opposite_colors
|
||||
font_size: root._label_font_size
|
||||
pos_hint: {'center_x': .5, 'center_y': .6}
|
||||
|
||||
|
||||
<MDTab>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.theme_cls.bg_normal
|
||||
Rectangle:
|
||||
size: root.size
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MDBottomNavigationHeader(BaseFlatButton, BasePressedButton):
|
||||
panel_color = ListProperty([1, 1, 1, 0])
|
||||
"""Panel color of bottom navigation.
|
||||
|
||||
:attr:`panel_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[1, 1, 1, 0]`.
|
||||
"""
|
||||
|
||||
tab = ObjectProperty()
|
||||
"""
|
||||
:attr:`tab` is an :class:`~MDBottomNavigationItem`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
panel = ObjectProperty()
|
||||
"""
|
||||
:attr:`panel` is an :class:`~MDBottomNavigation`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
active = BooleanProperty(False)
|
||||
|
||||
text = StringProperty()
|
||||
"""
|
||||
:attr:`text` is an :class:`~MDTab.text`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
text_color_normal = ListProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Text color of the label when it is not selected.
|
||||
|
||||
:attr:`text_color_normal` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
text_color_active = ListProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Text color of the label when it is selected.
|
||||
|
||||
:attr:`text_color_active` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
_label = ObjectProperty()
|
||||
_label_font_size = NumericProperty("12sp")
|
||||
_text_color_normal = ListProperty([1, 1, 1, 1])
|
||||
_text_color_active = ListProperty([1, 1, 1, 1])
|
||||
|
||||
def __init__(self, panel, height, tab):
|
||||
self.panel = panel
|
||||
self.height = height
|
||||
self.tab = tab
|
||||
super().__init__()
|
||||
self._text_color_normal = (
|
||||
self.theme_cls.disabled_hint_text_color
|
||||
if self.text_color_normal == [1, 1, 1, 1]
|
||||
else self.text_color_normal
|
||||
)
|
||||
self._label = self.ids._label
|
||||
self._label_font_size = sp(12)
|
||||
self.theme_cls.bind(disabled_hint_text_color=self._update_theme_style)
|
||||
self.active = False
|
||||
|
||||
def on_press(self):
|
||||
Animation(_label_font_size=sp(14), d=0.1).start(self)
|
||||
Animation(
|
||||
_text_color_normal=self.theme_cls.primary_color
|
||||
if self.text_color_active == [1, 1, 1, 1]
|
||||
else self.text_color_active,
|
||||
d=0.1,
|
||||
).start(self)
|
||||
|
||||
def _update_theme_style(self, instance, color):
|
||||
"""Called when the application theme style changes (White/Black)."""
|
||||
|
||||
if not self.active:
|
||||
self._text_color_normal = (
|
||||
color
|
||||
if self.text_color_normal == [1, 1, 1, 1]
|
||||
else self.text_color_normal
|
||||
)
|
||||
|
||||
|
||||
class MDTab(Screen, ThemableBehavior):
|
||||
"""A tab is simply a screen with meta information
|
||||
that defines the content that goes in the tab header.
|
||||
"""
|
||||
|
||||
__events__ = (
|
||||
"on_tab_touch_down",
|
||||
"on_tab_touch_move",
|
||||
"on_tab_touch_up",
|
||||
"on_tab_press",
|
||||
"on_tab_release",
|
||||
)
|
||||
"""Events provided."""
|
||||
|
||||
text = StringProperty()
|
||||
"""Tab header text.
|
||||
|
||||
:attr:`text` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
icon = StringProperty("checkbox-blank-circle")
|
||||
"""Tab header icon.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'checkbox-blank-circle'`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.index = 0
|
||||
self.parent_widget = None
|
||||
self.register_event_type("on_tab_touch_down")
|
||||
self.register_event_type("on_tab_touch_move")
|
||||
self.register_event_type("on_tab_touch_up")
|
||||
self.register_event_type("on_tab_press")
|
||||
self.register_event_type("on_tab_release")
|
||||
|
||||
def on_tab_touch_down(self, *args):
|
||||
pass
|
||||
|
||||
def on_tab_touch_move(self, *args):
|
||||
pass
|
||||
|
||||
def on_tab_touch_up(self, *args):
|
||||
pass
|
||||
|
||||
def on_tab_press(self, *args):
|
||||
par = self.parent_widget
|
||||
if par.previous_tab is not self:
|
||||
if par.previous_tab.index > self.index:
|
||||
par.ids.tab_manager.transition.direction = "right"
|
||||
elif par.previous_tab.index < self.index:
|
||||
par.ids.tab_manager.transition.direction = "left"
|
||||
par.ids.tab_manager.current = self.name
|
||||
par.previous_tab = self
|
||||
|
||||
def on_tab_release(self, *args):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return f"<MDTab name='{self.name}', text='{self.text}'>"
|
||||
|
||||
|
||||
class MDBottomNavigationItem(MDTab):
|
||||
header = ObjectProperty()
|
||||
"""
|
||||
:attr:`header` is an :class:`~MDBottomNavigationHeader`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def on_tab_press(self, *args):
|
||||
par = self.parent_widget
|
||||
par.ids.tab_manager.current = self.name
|
||||
if par.previous_tab is not self:
|
||||
Animation(_label_font_size=sp(12), d=0.1).start(
|
||||
par.previous_tab.header
|
||||
)
|
||||
Animation(
|
||||
_text_color_normal=par.previous_tab.header.text_color_normal
|
||||
if par.previous_tab.header.text_color_normal != [1, 1, 1, 1]
|
||||
else self.theme_cls.disabled_hint_text_color,
|
||||
d=0.1,
|
||||
).start(par.previous_tab.header)
|
||||
par.previous_tab.header.active = False
|
||||
self.header.active = True
|
||||
par.previous_tab = self
|
||||
|
||||
def on_leave(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
class TabbedPanelBase(
|
||||
ThemableBehavior, SpecificBackgroundColorBehavior, BoxLayout
|
||||
):
|
||||
"""A class that contains all variables a :class:`~kivy.properties.TabPannel`
|
||||
must have. It is here so I (zingballyhoo) don't get mad about
|
||||
the :class:`~kivy.properties.TabbedPannels` not being DRY.
|
||||
|
||||
"""
|
||||
|
||||
current = StringProperty(None)
|
||||
"""Current tab name.
|
||||
|
||||
:attr:`current` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
previous_tab = ObjectProperty()
|
||||
"""
|
||||
:attr:`previous_tab` is an :class:`~MDTab` and defaults to `None`.
|
||||
"""
|
||||
|
||||
panel_color = ListProperty()
|
||||
"""Panel color of bottom navigation.
|
||||
|
||||
:attr:`panel_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
tabs = ListProperty()
|
||||
|
||||
|
||||
class MDBottomNavigation(TabbedPanelBase):
|
||||
"""A bottom navigation that is implemented by delegating
|
||||
all items to a ScreenManager."""
|
||||
|
||||
first_widget = ObjectProperty()
|
||||
"""
|
||||
:attr:`first_widget` is an :class:`~MDBottomNavigationItem`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
tab_header = ObjectProperty()
|
||||
"""
|
||||
:attr:`tab_header` is an :class:`~MDBottomNavigationHeader`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
text_color_normal = ListProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Text color of the label when it is not selected.
|
||||
|
||||
:attr:`text_color_normal` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
text_color_active = ListProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Text color of the label when it is selected.
|
||||
|
||||
:attr:`text_color_active` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.previous_tab = None
|
||||
self.widget_index = 0
|
||||
Window.bind(on_resize=self.on_resize)
|
||||
Clock.schedule_once(lambda x: self.on_resize(), 0)
|
||||
|
||||
def on_panel_color(self, instance, value):
|
||||
self.tab_header.panel_color = value
|
||||
|
||||
def on_text_color_normal(self, instance, value):
|
||||
for tab in self.ids.tab_bar.children:
|
||||
if not tab.active:
|
||||
tab._text_color_normal = value
|
||||
|
||||
def on_text_color_active(self, instance, value):
|
||||
for tab in self.ids.tab_bar.children:
|
||||
tab.text_color_active = value
|
||||
if tab.active:
|
||||
tab._text_color_normal = value
|
||||
|
||||
def switch_tab(self, name_tab):
|
||||
"""Switching the tab by name."""
|
||||
|
||||
if not self.ids.tab_manager.has_screen(name_tab):
|
||||
raise ScreenManagerException(f"No Screen with name '{name_tab}'.")
|
||||
self.ids.tab_manager.get_screen(name_tab).dispatch("on_tab_press")
|
||||
count_index_screen = [
|
||||
self.ids.tab_manager.screens.index(screen)
|
||||
for screen in self.ids.tab_manager.screens
|
||||
if screen.name == name_tab
|
||||
][0]
|
||||
numbers_screens = list(range(len(self.ids.tab_manager.screens)))
|
||||
numbers_screens.reverse()
|
||||
self.ids.tab_bar.children[
|
||||
numbers_screens.index(count_index_screen)
|
||||
].dispatch("on_press")
|
||||
|
||||
def refresh_tabs(self):
|
||||
"""Refresh all tabs."""
|
||||
|
||||
if not self.ids:
|
||||
return
|
||||
tab_bar = self.ids.tab_bar
|
||||
tab_bar.clear_widgets()
|
||||
tab_manager = self.ids.tab_manager
|
||||
for tab in tab_manager.screens:
|
||||
self.tab_header = MDBottomNavigationHeader(
|
||||
tab=tab, panel=self, height=tab_bar.height
|
||||
)
|
||||
tab.header = self.tab_header
|
||||
tab_bar.add_widget(self.tab_header)
|
||||
if tab is self.first_widget:
|
||||
self.tab_header._text_color_normal = (
|
||||
self.theme_cls.primary_color
|
||||
)
|
||||
self.tab_header._label_font_size = sp(14)
|
||||
self.tab_header.active = True
|
||||
else:
|
||||
self.tab_header._label_font_size = sp(12)
|
||||
|
||||
def on_resize(self, instance=None, width=None, do_again=True):
|
||||
"""Called when the application window is resized."""
|
||||
|
||||
full_width = 0
|
||||
for tab in self.ids.tab_manager.screens:
|
||||
full_width += tab.header.width
|
||||
tab.header.text_color_normal = self.text_color_normal
|
||||
self.ids.tab_bar.width = full_width
|
||||
if do_again:
|
||||
Clock.schedule_once(lambda x: self.on_resize(do_again=False), 0.1)
|
||||
|
||||
def add_widget(self, widget, **kwargs):
|
||||
if isinstance(widget, MDBottomNavigationItem):
|
||||
self.widget_index += 1
|
||||
widget.index = self.widget_index
|
||||
widget.parent_widget = self
|
||||
self.ids.tab_manager.add_widget(widget)
|
||||
if self.widget_index == 1:
|
||||
self.previous_tab = widget
|
||||
self.first_widget = widget
|
||||
self.refresh_tabs()
|
||||
else:
|
||||
super().add_widget(widget)
|
||||
|
||||
def remove_widget(self, widget):
|
||||
if isinstance(widget, MDBottomNavigationItem):
|
||||
self.ids.tab_manager.remove_widget(widget)
|
||||
self.refresh_tabs()
|
||||
else:
|
||||
super().remove_widget(widget)
|
||||
|
||||
|
||||
class MDBottomNavigationBar(
|
||||
ThemableBehavior,
|
||||
BackgroundColorBehavior,
|
||||
FloatLayout,
|
||||
RectangularElevationBehavior,
|
||||
):
|
||||
pass
|
564
kivymd/uix/bottomsheet.py
Executable file
|
@ -0,0 +1,564 @@
|
|||
"""
|
||||
Components/Bottom Sheet
|
||||
=======================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Sheets: bottom <https://material.io/components/sheets-bottom>`_
|
||||
|
||||
.. rubric:: Bottom sheets are surfaces containing supplementary content that are anchored to the bottom of the screen.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet.png
|
||||
:align: center
|
||||
|
||||
Two classes are available to you :class:`~MDListBottomSheet` and :class:`~MDGridBottomSheet`
|
||||
for standard bottom sheets dialogs:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/grid-list-bottomsheets.png
|
||||
:align: center
|
||||
|
||||
Usage :class:`~MDListBottomSheet`
|
||||
=================================
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.toast import toast
|
||||
from kivymd.uix.bottomsheet import MDListBottomSheet
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDToolbar:
|
||||
title: "Example BottomSheet"
|
||||
pos_hint: {"top": 1}
|
||||
elevation: 10
|
||||
|
||||
MDRaisedButton:
|
||||
text: "Open list bottom sheet"
|
||||
on_release: app.show_example_list_bottom_sheet()
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def callback_for_menu_items(self, *args):
|
||||
toast(args[0])
|
||||
|
||||
def show_example_list_bottom_sheet(self):
|
||||
bottom_sheet_menu = MDListBottomSheet()
|
||||
for i in range(1, 11):
|
||||
bottom_sheet_menu.add_item(
|
||||
f"Standart Item {i}",
|
||||
lambda x, y=i: self.callback_for_menu_items(
|
||||
f"Standart Item {y}"
|
||||
),
|
||||
)
|
||||
bottom_sheet_menu.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
The :attr:`~MDListBottomSheet.add_item` method of the :class:`~MDListBottomSheet`
|
||||
class takes the following arguments:
|
||||
|
||||
``text`` - element text;
|
||||
|
||||
``callback`` - function that will be called when clicking on an item;
|
||||
|
||||
There is also an optional argument ``icon``,
|
||||
which will be used as an icon to the left of the item:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-list-bottomsheets.png
|
||||
:align: center
|
||||
|
||||
.. rubric:: Using the :class:`~MDGridBottomSheet` class is similar
|
||||
to using the :class:`~MDListBottomSheet` class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.toast import toast
|
||||
from kivymd.uix.bottomsheet import MDGridBottomSheet
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDToolbar:
|
||||
title: 'Example BottomSheet'
|
||||
pos_hint: {"top": 1}
|
||||
elevation: 10
|
||||
|
||||
MDRaisedButton:
|
||||
text: "Open grid bottom sheet"
|
||||
on_release: app.show_example_grid_bottom_sheet()
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def callback_for_menu_items(self, *args):
|
||||
toast(args[0])
|
||||
|
||||
def show_example_grid_bottom_sheet(self):
|
||||
bottom_sheet_menu = MDGridBottomSheet()
|
||||
data = {
|
||||
"Facebook": "facebook-box",
|
||||
"YouTube": "youtube",
|
||||
"Twitter": "twitter-box",
|
||||
"Da Cloud": "cloud-upload",
|
||||
"Camera": "camera",
|
||||
}
|
||||
for item in data.items():
|
||||
bottom_sheet_menu.add_item(
|
||||
item[0],
|
||||
lambda x, y=item[0]: self.callback_for_menu_items(y),
|
||||
icon_src=item[1],
|
||||
)
|
||||
bottom_sheet_menu.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/grid-bottomsheet.png
|
||||
:align: center
|
||||
|
||||
.. rubric:: You can use custom content for bottom sheet dialogs:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.factory import Factory
|
||||
|
||||
from kivymd.uix.bottomsheet import MDCustomBottomSheet
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
<ItemForCustomBottomSheet@OneLineIconListItem>
|
||||
on_press: app.custom_sheet.dismiss()
|
||||
icon: ""
|
||||
|
||||
IconLeftWidget:
|
||||
icon: root.icon
|
||||
|
||||
|
||||
<ContentCustomSheet@BoxLayout>:
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: "400dp"
|
||||
|
||||
MDToolbar:
|
||||
title: 'Custom bottom sheet:'
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDGridLayout:
|
||||
cols: 1
|
||||
adaptive_height: True
|
||||
|
||||
ItemForCustomBottomSheet:
|
||||
icon: "page-previous"
|
||||
text: "Preview"
|
||||
|
||||
ItemForCustomBottomSheet:
|
||||
icon: "exit-to-app"
|
||||
text: "Exit"
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
MDToolbar:
|
||||
title: 'Example BottomSheet'
|
||||
pos_hint: {"top": 1}
|
||||
elevation: 10
|
||||
|
||||
MDRaisedButton:
|
||||
text: "Open custom bottom sheet"
|
||||
on_release: app.show_example_custom_bottom_sheet()
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
custom_sheet = None
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_example_custom_bottom_sheet(self):
|
||||
self.custom_sheet = MDCustomBottomSheet(screen=Factory.ContentCustomSheet())
|
||||
self.custom_sheet.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-bottomsheet.png
|
||||
:align: center
|
||||
|
||||
.. note:: When you use the :attr:`~MDCustomBottomSheet` class, you must specify
|
||||
the height of the user-defined content exactly, otherwise ``dp(100)``
|
||||
heights will be used for your ``ContentCustomSheet`` class:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<ContentCustomSheet@BoxLayout>:
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: "400dp"
|
||||
|
||||
.. note:: The height of the bottom sheet dialog will never exceed half
|
||||
the height of the screen!
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"MDGridBottomSheet",
|
||||
"GridBottomSheetItem",
|
||||
"MDListBottomSheet",
|
||||
"MDCustomBottomSheet",
|
||||
"MDBottomSheet",
|
||||
)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.modalview import ModalView
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
|
||||
from kivymd import images_path
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import BackgroundColorBehavior
|
||||
from kivymd.uix.label import MDIcon
|
||||
from kivymd.uix.list import ILeftBody, OneLineIconListItem, OneLineListItem
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import Window kivy.core.window.Window
|
||||
|
||||
|
||||
<SheetList>:
|
||||
|
||||
MDGridLayout:
|
||||
id: box_sheet_list
|
||||
cols: 1
|
||||
adaptive_height: True
|
||||
padding: 0, 0, 0, "96dp"
|
||||
|
||||
|
||||
<MDBottomSheet>
|
||||
md_bg_color: root.value_transparent
|
||||
_upper_padding: _upper_padding
|
||||
_gl_content: _gl_content
|
||||
_position_content: Window.height
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "vertical"
|
||||
padding: 0, 1, 0, 0
|
||||
|
||||
BsPadding:
|
||||
id: _upper_padding
|
||||
size_hint_y: None
|
||||
height: root.height - min(root.width * 9 / 16, root._gl_content.height)
|
||||
on_release: root.dismiss()
|
||||
|
||||
BottomSheetContent:
|
||||
id: _gl_content
|
||||
size_hint_y: None
|
||||
cols: 1
|
||||
md_bg_color: 0, 0, 0, 0
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.theme_cls.bg_normal if not root.bg_color else root.bg_color
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
radius:
|
||||
[
|
||||
(root.radius, root.radius) if root.radius_from == "top_left" or root.radius_from == "top" else (0, 0),
|
||||
(root.radius, root.radius) if root.radius_from == "top_right" or root.radius_from == "top" else (0, 0),
|
||||
(root.radius, root.radius) if root.radius_from == "bottom_right" or root.radius_from == "bottom" else (0, 0),
|
||||
(root.radius, root.radius) if root.radius_from == "bottom_left" or root.radius_from == "bottom" else (0, 0)
|
||||
]
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class SheetList(ScrollView):
|
||||
pass
|
||||
|
||||
|
||||
class BsPadding(ButtonBehavior, FloatLayout):
|
||||
pass
|
||||
|
||||
|
||||
class BottomSheetContent(BackgroundColorBehavior, GridLayout):
|
||||
pass
|
||||
|
||||
|
||||
class MDBottomSheet(ThemableBehavior, ModalView):
|
||||
background = f"{images_path}transparent.png"
|
||||
"""Private attribute."""
|
||||
|
||||
duration_opening = NumericProperty(0.15)
|
||||
"""The duration of the bottom sheet dialog opening animation.
|
||||
|
||||
:attr:`duration_opening` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.15`.
|
||||
"""
|
||||
|
||||
radius = NumericProperty(25)
|
||||
"""The value of the rounding of the corners of the dialog.
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `25`.
|
||||
"""
|
||||
|
||||
radius_from = OptionProperty(
|
||||
None,
|
||||
options=[
|
||||
"top_left",
|
||||
"top_right",
|
||||
"top",
|
||||
"bottom_right",
|
||||
"bottom_left",
|
||||
"bottom",
|
||||
],
|
||||
allownone=True,
|
||||
)
|
||||
"""Sets which corners to cut from the dialog. Available options are:
|
||||
(`"top_left"`, `"top_right"`, `"top"`, `"bottom_right"`, `"bottom_left"`, `"bottom"`).
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-radius-from.png
|
||||
:align: center
|
||||
|
||||
:attr:`radius_from` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
animation = BooleanProperty(False)
|
||||
"""To use animation of opening of dialogue of the bottom sheet or not.
|
||||
|
||||
:attr:`animation` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
bg_color = ListProperty()
|
||||
"""Dialog background color in ``rgba`` format.
|
||||
|
||||
:attr:`bg_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
value_transparent = ListProperty([0, 0, 0, 0.8])
|
||||
"""Background transparency value when opening a dialog.
|
||||
|
||||
:attr:`value_transparent` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[0, 0, 0, 0.8]`.
|
||||
"""
|
||||
|
||||
_upper_padding = ObjectProperty()
|
||||
_gl_content = ObjectProperty()
|
||||
_position_content = NumericProperty()
|
||||
|
||||
def open(self, *largs):
|
||||
super().open(*largs)
|
||||
|
||||
def add_widget(self, widget, index=0, canvas=None):
|
||||
super().add_widget(widget, index, canvas)
|
||||
|
||||
def on_dismiss(self):
|
||||
self._gl_content.clear_widgets()
|
||||
|
||||
def resize_content_layout(self, content, layout, interval=0):
|
||||
if not layout.ids.get("box_sheet_list"):
|
||||
_layout = layout
|
||||
else:
|
||||
_layout = layout.ids.box_sheet_list
|
||||
|
||||
if _layout.height > Window.height / 2:
|
||||
height = Window.height / 2
|
||||
else:
|
||||
height = _layout.height
|
||||
|
||||
if self.animation:
|
||||
Animation(height=height, d=self.duration_opening).start(_layout)
|
||||
Animation(height=height, d=self.duration_opening).start(content)
|
||||
else:
|
||||
layout.height = height
|
||||
content.height = height
|
||||
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<ListBottomSheetIconLeft>
|
||||
halign: "center"
|
||||
theme_text_color: "Primary"
|
||||
valign: "middle"
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class ListBottomSheetIconLeft(ILeftBody, MDIcon):
|
||||
pass
|
||||
|
||||
|
||||
class MDCustomBottomSheet(MDBottomSheet):
|
||||
screen = ObjectProperty()
|
||||
"""
|
||||
Custom content.
|
||||
|
||||
:attr:`screen` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._gl_content.add_widget(self.screen)
|
||||
Clock.schedule_once(
|
||||
lambda x: self.resize_content_layout(self._gl_content, self.screen),
|
||||
0,
|
||||
)
|
||||
|
||||
|
||||
class MDListBottomSheet(MDBottomSheet):
|
||||
sheet_list = ObjectProperty()
|
||||
"""
|
||||
:attr:`sheet_list` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.sheet_list = SheetList(size_hint_y=None)
|
||||
self._gl_content.add_widget(self.sheet_list)
|
||||
Clock.schedule_once(
|
||||
lambda x: self.resize_content_layout(
|
||||
self._gl_content, self.sheet_list
|
||||
),
|
||||
0,
|
||||
)
|
||||
|
||||
def add_item(self, text, callback, icon=None):
|
||||
"""
|
||||
:arg text: element text;
|
||||
:arg callback: function that will be called when clicking on an item;
|
||||
:arg icon: which will be used as an icon to the left of the item;
|
||||
"""
|
||||
|
||||
if icon:
|
||||
item = OneLineIconListItem(text=text, on_release=callback)
|
||||
item.add_widget(ListBottomSheetIconLeft(icon=icon))
|
||||
else:
|
||||
item = OneLineListItem(text=text, on_release=callback)
|
||||
item.bind(on_release=lambda x: self.dismiss())
|
||||
self.sheet_list.ids.box_sheet_list.add_widget(item)
|
||||
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<GridBottomSheetItem>
|
||||
orientation: "vertical"
|
||||
padding: 0, dp(24), 0, 0
|
||||
size_hint_y: None
|
||||
size: dp(64), dp(96)
|
||||
|
||||
AnchorLayout:
|
||||
anchoor_x: "center"
|
||||
|
||||
MDIconButton:
|
||||
icon: root.source
|
||||
user_font_size: root.icon_size
|
||||
on_release: root.dispatch("on_release")
|
||||
|
||||
MDLabel:
|
||||
font_style: "Caption"
|
||||
theme_text_color: "Secondary"
|
||||
text: root.caption
|
||||
halign: "center"
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class GridBottomSheetItem(ButtonBehavior, BoxLayout):
|
||||
source = StringProperty()
|
||||
"""
|
||||
Icon path if you use a local image or icon name
|
||||
if you use icon names from a file ``kivymd/icon_definitions.py``.
|
||||
|
||||
:attr:`source` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
caption = StringProperty()
|
||||
"""
|
||||
Item text.
|
||||
|
||||
:attr:`caption` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
icon_size = StringProperty("32sp")
|
||||
"""
|
||||
Icon size.
|
||||
|
||||
:attr:`caption` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'32sp'`.
|
||||
"""
|
||||
|
||||
|
||||
class MDGridBottomSheet(MDBottomSheet):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.sheet_list = SheetList(size_hint_y=None)
|
||||
self.sheet_list.ids.box_sheet_list.cols = 3
|
||||
self.sheet_list.ids.box_sheet_list.padding = (dp(16), 0, dp(16), dp(96))
|
||||
self._gl_content.add_widget(self.sheet_list)
|
||||
Clock.schedule_once(
|
||||
lambda x: self.resize_content_layout(
|
||||
self._gl_content, self.sheet_list
|
||||
),
|
||||
0,
|
||||
)
|
||||
|
||||
def add_item(self, text, callback, icon_src):
|
||||
"""
|
||||
:arg text: element text;
|
||||
:arg callback: function that will be called when clicking on an item;
|
||||
:arg icon_src: icon item;
|
||||
"""
|
||||
|
||||
def tap_on_item(instance):
|
||||
callback(instance)
|
||||
self.dismiss()
|
||||
|
||||
item = GridBottomSheetItem(
|
||||
caption=text, on_release=tap_on_item, source=icon_src
|
||||
)
|
||||
if len(self._gl_content.children) % 3 == 0:
|
||||
self._gl_content.height += dp(96)
|
||||
self.sheet_list.ids.box_sheet_list.add_widget(item)
|
92
kivymd/uix/boxlayout.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
Components/BoxLayout
|
||||
====================
|
||||
|
||||
:class:`~kivy.uix.boxlayout.BoxLayout` class equivalent. Simplifies working
|
||||
with some widget properties. For example:
|
||||
|
||||
BoxLayout
|
||||
---------
|
||||
|
||||
.. code-block::
|
||||
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: app.theme_cls.primary_color
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
MDBoxLayout
|
||||
-----------
|
||||
|
||||
.. code-block::
|
||||
|
||||
MDBoxLayout:
|
||||
adaptive_height: True
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
|
||||
Available options are:
|
||||
---------------------
|
||||
|
||||
- adaptive_height_
|
||||
- adaptive_width_
|
||||
- adaptive_size_
|
||||
|
||||
.. adaptive_height:
|
||||
adaptive_height
|
||||
---------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_height: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
.. adaptive_width:
|
||||
adaptive_width
|
||||
--------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_width: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_x: None
|
||||
height: self.minimum_width
|
||||
|
||||
.. adaptive_size:
|
||||
adaptive_size
|
||||
-------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_size: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
"""
|
||||
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
from kivymd.uix import MDAdaptiveWidget
|
||||
|
||||
|
||||
class MDBoxLayout(BoxLayout, MDAdaptiveWidget):
|
||||
pass
|
1842
kivymd/uix/button.py
Executable file
896
kivymd/uix/card.py
Executable file
|
@ -0,0 +1,896 @@
|
|||
"""
|
||||
Components/Card
|
||||
===============
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Cards <https://material.io/components/cards>`_
|
||||
|
||||
.. rubric:: Cards contain content and actions about a single subject.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/cards.gif
|
||||
:align: center
|
||||
|
||||
`KivyMD` provides the following card classes for use:
|
||||
|
||||
- MDCard_
|
||||
- MDCardSwipe_
|
||||
|
||||
.. Note:: :class:`~MDCard` inherited from
|
||||
:class:`~kivy.uix.boxlayout.BoxLayout`. You can use all parameters and
|
||||
attributes of the :class:`~kivy.uix.boxlayout.BoxLayout` class in the
|
||||
:class:`~MDCard` class.
|
||||
|
||||
.. MDCard:
|
||||
MDCard
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDCard:
|
||||
size_hint: None, None
|
||||
size: "280dp", "180dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class TestCard(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
TestCard().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card.png
|
||||
:align: center
|
||||
|
||||
Add content to card:
|
||||
--------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDCard:
|
||||
orientation: "vertical"
|
||||
padding: "8dp"
|
||||
size_hint: None, None
|
||||
size: "280dp", "180dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
MDLabel:
|
||||
text: "Title"
|
||||
theme_text_color: "Secondary"
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
|
||||
MDSeparator:
|
||||
height: "1dp"
|
||||
|
||||
MDLabel:
|
||||
text: "Body"
|
||||
'''
|
||||
|
||||
|
||||
class TestCard(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
TestCard().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-content.png
|
||||
:align: center
|
||||
|
||||
.. MDCardSwipe:
|
||||
MDCardSwipe
|
||||
-----------
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDCardSwipe.gif
|
||||
:align: center
|
||||
|
||||
To create a card with `swipe-to-delete` behavior, you must create a new class
|
||||
that inherits from the :class:`~MDCardSwipe` class:
|
||||
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<SwipeToDeleteItem>:
|
||||
size_hint_y: None
|
||||
height: content.height
|
||||
|
||||
MDCardSwipeLayerBox:
|
||||
|
||||
MDCardSwipeFrontBox:
|
||||
|
||||
OneLineListItem:
|
||||
id: content
|
||||
text: root.text
|
||||
_no_ripple_effect: True
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class SwipeToDeleteItem(MDCardSwipe):
|
||||
text = StringProperty()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/map-mdcard-swipr.png
|
||||
:align: center
|
||||
|
||||
End full code
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.card import MDCardSwipe
|
||||
|
||||
KV = '''
|
||||
<SwipeToDeleteItem>:
|
||||
size_hint_y: None
|
||||
height: content.height
|
||||
|
||||
MDCardSwipeLayerBox:
|
||||
# Content under the card.
|
||||
|
||||
MDCardSwipeFrontBox:
|
||||
|
||||
# Content of card.
|
||||
OneLineListItem:
|
||||
id: content
|
||||
text: root.text
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
spacing: "10dp"
|
||||
|
||||
MDToolbar:
|
||||
elevation: 10
|
||||
title: "MDCardSwipe"
|
||||
|
||||
ScrollView:
|
||||
scroll_timeout : 100
|
||||
|
||||
MDList:
|
||||
id: md_list
|
||||
padding: 0
|
||||
'''
|
||||
|
||||
|
||||
class SwipeToDeleteItem(MDCardSwipe):
|
||||
'''Card with `swipe-to-delete` behavior.'''
|
||||
|
||||
text = StringProperty()
|
||||
|
||||
|
||||
class TestCard(MDApp):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.screen = Builder.load_string(KV)
|
||||
|
||||
def build(self):
|
||||
return self.screen
|
||||
|
||||
def on_start(self):
|
||||
'''Creates a list of cards.'''
|
||||
|
||||
for i in range(20):
|
||||
self.screen.ids.md_list.add_widget(
|
||||
SwipeToDeleteItem(text=f"One-line item {i}")
|
||||
)
|
||||
|
||||
|
||||
TestCard().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-mdcard-swipe.gif
|
||||
:align: center
|
||||
|
||||
Binding a swipe to one of the sides of the screen
|
||||
-------------------------------------------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<SwipeToDeleteItem>:
|
||||
# By default, the parameter is "left"
|
||||
anchor: "right"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdcard-swipe-anchor-right.gif
|
||||
:align: center
|
||||
|
||||
|
||||
.. None:: You cannot use the left and right swipe at the same time.
|
||||
|
||||
Swipe behavior
|
||||
--------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<SwipeToDeleteItem>:
|
||||
# By default, the parameter is "hand"
|
||||
type_swipe: "hand"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hand-mdcard-swipe.gif
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<SwipeToDeleteItem>:
|
||||
type_swipe: "auto"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/auto-mdcard-swipe.gif
|
||||
:align: center
|
||||
|
||||
Removing an item using the ``type_swipe = "auto"`` parameter
|
||||
------------------------------------------------------------
|
||||
|
||||
The map provides the :attr:`MDCardSwipe.on_swipe_complete` event.
|
||||
You can use this event to remove items from a list:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<SwipeToDeleteItem>:
|
||||
on_swipe_complete: app.on_swipe_complete(root)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_swipe_complete(self, instance):
|
||||
self.screen.ids.md_list.remove_widget(instance)
|
||||
|
||||
End full code
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.card import MDCardSwipe
|
||||
|
||||
KV = '''
|
||||
<SwipeToDeleteItem>:
|
||||
size_hint_y: None
|
||||
height: content.height
|
||||
type_swipe: "auto"
|
||||
on_swipe_complete: app.on_swipe_complete(root)
|
||||
|
||||
MDCardSwipeLayerBox:
|
||||
|
||||
MDCardSwipeFrontBox:
|
||||
|
||||
OneLineListItem:
|
||||
id: content
|
||||
text: root.text
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
spacing: "10dp"
|
||||
|
||||
MDToolbar:
|
||||
elevation: 10
|
||||
title: "MDCardSwipe"
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDList:
|
||||
id: md_list
|
||||
padding: 0
|
||||
'''
|
||||
|
||||
|
||||
class SwipeToDeleteItem(MDCardSwipe):
|
||||
text = StringProperty()
|
||||
|
||||
|
||||
class TestCard(MDApp):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.screen = Builder.load_string(KV)
|
||||
|
||||
def build(self):
|
||||
return self.screen
|
||||
|
||||
def on_swipe_complete(self, instance):
|
||||
self.screen.ids.md_list.remove_widget(instance)
|
||||
|
||||
def on_start(self):
|
||||
for i in range(20):
|
||||
self.screen.ids.md_list.add_widget(
|
||||
SwipeToDeleteItem(text=f"One-line item {i}")
|
||||
)
|
||||
|
||||
|
||||
TestCard().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/autodelete-mdcard-swipe.gif
|
||||
:align: center
|
||||
|
||||
Add content to the bottom layer of the card
|
||||
-------------------------------------------
|
||||
|
||||
To add content to the bottom layer of the card,
|
||||
use the :class:`~MDCardSwipeLayerBox` class.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<SwipeToDeleteItem>:
|
||||
|
||||
MDCardSwipeLayerBox:
|
||||
padding: "8dp"
|
||||
|
||||
MDIconButton:
|
||||
icon: "trash-can"
|
||||
pos_hint: {"center_y": .5}
|
||||
on_release: app.remove_item(root)
|
||||
|
||||
End full code
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.card import MDCardSwipe
|
||||
|
||||
KV = '''
|
||||
<SwipeToDeleteItem>:
|
||||
size_hint_y: None
|
||||
height: content.height
|
||||
|
||||
MDCardSwipeLayerBox:
|
||||
padding: "8dp"
|
||||
|
||||
MDIconButton:
|
||||
icon: "trash-can"
|
||||
pos_hint: {"center_y": .5}
|
||||
on_release: app.remove_item(root)
|
||||
|
||||
MDCardSwipeFrontBox:
|
||||
|
||||
OneLineListItem:
|
||||
id: content
|
||||
text: root.text
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
spacing: "10dp"
|
||||
|
||||
MDToolbar:
|
||||
elevation: 10
|
||||
title: "MDCardSwipe"
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDList:
|
||||
id: md_list
|
||||
padding: 0
|
||||
'''
|
||||
|
||||
|
||||
class SwipeToDeleteItem(MDCardSwipe):
|
||||
text = StringProperty()
|
||||
|
||||
|
||||
class TestCard(MDApp):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.screen = Builder.load_string(KV)
|
||||
|
||||
def build(self):
|
||||
return self.screen
|
||||
|
||||
def remove_item(self, instance):
|
||||
self.screen.ids.md_list.remove_widget(instance)
|
||||
|
||||
def on_start(self):
|
||||
for i in range(20):
|
||||
self.screen.ids.md_list.add_widget(
|
||||
SwipeToDeleteItem(text=f"One-line item {i}")
|
||||
)
|
||||
|
||||
|
||||
TestCard().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/handdelete-mdcard-swipe.gif
|
||||
:align: center
|
||||
|
||||
Focus behavior
|
||||
-------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDCard:
|
||||
focus_behavior: True
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif
|
||||
:align: center
|
||||
|
||||
Ripple behavior
|
||||
---------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDCard:
|
||||
ripple_behavior: True
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-behavior.gif
|
||||
:align: center
|
||||
|
||||
End full code
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
<StarButton@MDIconButton>
|
||||
icon: "star"
|
||||
on_release: self.icon = "star-outline" if self.icon == "star" else "star"
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
MDCard:
|
||||
orientation: "vertical"
|
||||
size_hint: .5, None
|
||||
height: box_top.height + box_bottom.height
|
||||
focus_behavior: True
|
||||
ripple_behavior: True
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
MDBoxLayout:
|
||||
id: box_top
|
||||
spacing: "20dp"
|
||||
adaptive_height: True
|
||||
|
||||
FitImage:
|
||||
source: "/Users/macbookair/album.jpeg"
|
||||
size_hint: .3, None
|
||||
height: text_box.height
|
||||
|
||||
MDBoxLayout:
|
||||
id: text_box
|
||||
orientation: "vertical"
|
||||
adaptive_height: True
|
||||
spacing: "10dp"
|
||||
padding: 0, "10dp", "10dp", "10dp"
|
||||
|
||||
MDLabel:
|
||||
text: "Ride the Lightning"
|
||||
theme_text_color: "Primary"
|
||||
font_style: "H5"
|
||||
bold: True
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
|
||||
MDLabel:
|
||||
text: "July 27, 1984"
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
theme_text_color: "Primary"
|
||||
|
||||
MDSeparator:
|
||||
|
||||
MDBoxLayout:
|
||||
id: box_bottom
|
||||
adaptive_height: True
|
||||
padding: "10dp", 0, 0, 0
|
||||
|
||||
MDLabel:
|
||||
text: "Rate this album"
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
pos_hint: {"center_y": .5}
|
||||
theme_text_color: "Primary"
|
||||
|
||||
StarButton:
|
||||
StarButton:
|
||||
StarButton:
|
||||
StarButton:
|
||||
StarButton:
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"MDCard",
|
||||
"MDCardSwipe",
|
||||
"MDCardSwipeFrontBox",
|
||||
"MDCardSwipeLayerBox",
|
||||
"MDSeparator",
|
||||
)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.relativelayout import RelativeLayout
|
||||
from kivy.utils import get_color_from_hex
|
||||
|
||||
from kivymd import images_path
|
||||
from kivymd.color_definitions import colors
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import (
|
||||
BackgroundColorBehavior,
|
||||
FocusBehavior,
|
||||
RectangularElevationBehavior,
|
||||
RectangularRippleBehavior,
|
||||
)
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<MDCardSwipeLayerBox>:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: app.theme_cls.divider_color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
|
||||
<MDCard>
|
||||
canvas:
|
||||
RoundedRectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
source: root.background
|
||||
|
||||
|
||||
<MDSeparator>
|
||||
canvas:
|
||||
Color:
|
||||
rgba:
|
||||
self.theme_cls.divider_color if not root.color else root.color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MDSeparator(ThemableBehavior, BoxLayout):
|
||||
"""A separator line."""
|
||||
|
||||
color = ListProperty()
|
||||
"""Separator color in ``rgba`` format.
|
||||
|
||||
:attr:`color` is a :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.on_orientation()
|
||||
|
||||
def on_orientation(self, *args):
|
||||
self.size_hint = (
|
||||
(1, None) if self.orientation == "horizontal" else (None, 1)
|
||||
)
|
||||
if self.orientation == "horizontal":
|
||||
self.height = dp(1)
|
||||
else:
|
||||
self.width = dp(1)
|
||||
|
||||
|
||||
class MDCard(
|
||||
ThemableBehavior,
|
||||
BackgroundColorBehavior,
|
||||
RectangularElevationBehavior,
|
||||
FocusBehavior,
|
||||
BoxLayout,
|
||||
RectangularRippleBehavior,
|
||||
):
|
||||
background = StringProperty()
|
||||
"""
|
||||
Background image path.
|
||||
|
||||
:attr:`background` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
focus_behavior = BooleanProperty(False)
|
||||
"""
|
||||
Using focus when hovering over a card.
|
||||
|
||||
:attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
ripple_behavior = BooleanProperty(False)
|
||||
"""
|
||||
Use ripple effect for card.
|
||||
|
||||
:attr:`ripple_behavior` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
elevation = NumericProperty(None, allownone=True)
|
||||
"""
|
||||
Elevation value.
|
||||
|
||||
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to 1.
|
||||
"""
|
||||
|
||||
_bg_color_map = (
|
||||
get_color_from_hex(colors["Light"]["CardsDialogs"]),
|
||||
get_color_from_hex(colors["Dark"]["CardsDialogs"]),
|
||||
[1.0, 1.0, 1.0, 0.0],
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.theme_cls.bind(theme_style=self.update_md_bg_color)
|
||||
Clock.schedule_once(lambda x: self._on_elevation(self.elevation))
|
||||
Clock.schedule_once(
|
||||
lambda x: self._on_ripple_behavior(self.ripple_behavior)
|
||||
)
|
||||
self.update_md_bg_color(self, self.theme_cls.theme_style)
|
||||
|
||||
def update_md_bg_color(self, instance, value):
|
||||
if self.md_bg_color in self._bg_color_map:
|
||||
self.md_bg_color = get_color_from_hex(colors[value]["CardsDialogs"])
|
||||
|
||||
def on_radius(self, instance, value):
|
||||
if self.radius != [0, 0, 0, 0]:
|
||||
self.background = f"{images_path}/transparent.png"
|
||||
|
||||
def _on_elevation(self, value):
|
||||
if value is None:
|
||||
self.elevation = 6
|
||||
else:
|
||||
self.elevation = value
|
||||
|
||||
def _on_ripple_behavior(self, value):
|
||||
self._no_ripple_effect = False if value else True
|
||||
|
||||
|
||||
class MDCardSwipe(RelativeLayout):
|
||||
"""
|
||||
:Events:
|
||||
:attr:`on_swipe_complete`
|
||||
Called when a swipe of card is completed.
|
||||
"""
|
||||
|
||||
open_progress = NumericProperty(0.0)
|
||||
"""
|
||||
Percent of visible part of side panel. The percent is specified as a
|
||||
floating point number in the range 0-1. 0.0 if panel is closed and 1.0 if
|
||||
panel is opened.
|
||||
|
||||
:attr:`open_progress` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.0`.
|
||||
"""
|
||||
|
||||
opening_transition = StringProperty("out_cubic")
|
||||
"""
|
||||
The name of the animation transition type to use when animating to
|
||||
the :attr:`state` `'opened'`.
|
||||
|
||||
:attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_cubic'`.
|
||||
"""
|
||||
|
||||
closing_transition = StringProperty("out_sine")
|
||||
"""
|
||||
The name of the animation transition type to use when animating to
|
||||
the :attr:`state` 'closed'.
|
||||
|
||||
:attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_sine'`.
|
||||
"""
|
||||
|
||||
anchor = OptionProperty("left", options=("left", "right"))
|
||||
"""
|
||||
Anchoring screen edge for card. Available options are: `'left'`, `'right'`.
|
||||
|
||||
:attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `left`.
|
||||
"""
|
||||
|
||||
swipe_distance = NumericProperty(50)
|
||||
"""
|
||||
The distance of the swipe with which the movement of navigation drawer
|
||||
begins.
|
||||
|
||||
:attr:`swipe_distance` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `50`.
|
||||
"""
|
||||
|
||||
opening_time = NumericProperty(0.2)
|
||||
"""
|
||||
The time taken for the card to slide to the :attr:`state` `'open'`.
|
||||
|
||||
:attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
state = OptionProperty("closed", options=("closed", "opened"))
|
||||
"""
|
||||
Detailed state. Sets before :attr:`state`. Bind to :attr:`state` instead
|
||||
of :attr:`status`. Available options are: `'closed'`, `'opened'`.
|
||||
|
||||
:attr:`status` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'closed'`.
|
||||
"""
|
||||
|
||||
max_swipe_x = NumericProperty(0.3)
|
||||
"""
|
||||
If, after the events of :attr:`~on_touch_up` card position exceeds this
|
||||
value - will automatically execute the method :attr:`~open_card`,
|
||||
and if not - will automatically be :attr:`~close_card` method.
|
||||
|
||||
:attr:`max_swipe_x` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.3`.
|
||||
"""
|
||||
|
||||
max_opened_x = NumericProperty("100dp")
|
||||
"""
|
||||
The value of the position the card shifts to when :attr:`~type_swipe`
|
||||
s set to `'hand'`.
|
||||
|
||||
:attr:`max_opened_x` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `100dp`.
|
||||
"""
|
||||
|
||||
type_swipe = OptionProperty("hand", options=("auto", "hand"))
|
||||
"""
|
||||
Type of card opening when swipe. Shift the card to the edge or to
|
||||
a set position :attr:`~max_opened_x`. Available options are:
|
||||
`'auto'`, `'hand'`.
|
||||
|
||||
:attr:`type_swipe` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `auto`.
|
||||
"""
|
||||
|
||||
_opens_process = False
|
||||
_to_closed = True
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.register_event_type("on_swipe_complete")
|
||||
super().__init__(**kw)
|
||||
|
||||
def _on_swipe_complete(self, *args):
|
||||
self.dispatch("on_swipe_complete")
|
||||
|
||||
def add_widget(self, widget, index=0, canvas=None):
|
||||
if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)):
|
||||
return super().add_widget(widget)
|
||||
|
||||
def on_swipe_complete(self, *args):
|
||||
"""Called when a swipe of card is completed."""
|
||||
|
||||
def on_anchor(self, instance, value):
|
||||
if value == "right":
|
||||
self.open_progress = 1.0
|
||||
else:
|
||||
self.open_progress = 0.0
|
||||
|
||||
def on_open_progress(self, instance, value):
|
||||
if self.anchor == "left":
|
||||
self.children[0].x = self.width * value
|
||||
else:
|
||||
self.children[0].x = self.width * value - self.width
|
||||
|
||||
def on_touch_move(self, touch):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
expr = (
|
||||
touch.x < self.swipe_distance
|
||||
if self.anchor == "left"
|
||||
else touch.x > self.width - self.swipe_distance
|
||||
)
|
||||
if expr and not self._opens_process:
|
||||
self._opens_process = True
|
||||
self._to_closed = False
|
||||
if self._opens_process:
|
||||
self.open_progress = max(
|
||||
min(self.open_progress + touch.dx / self.width, 2.5), 0
|
||||
)
|
||||
return super().on_touch_move(touch)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
if not self._to_closed:
|
||||
self._opens_process = False
|
||||
self.complete_swipe()
|
||||
return super().on_touch_up(touch)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
if self.state == "opened":
|
||||
self._to_closed = True
|
||||
self.close_card()
|
||||
return super().on_touch_down(touch)
|
||||
|
||||
def complete_swipe(self):
|
||||
expr = (
|
||||
self.open_progress <= self.max_swipe_x
|
||||
if self.anchor == "left"
|
||||
else self.open_progress >= self.max_swipe_x
|
||||
)
|
||||
if expr:
|
||||
self.close_card()
|
||||
else:
|
||||
self.open_card()
|
||||
|
||||
def open_card(self):
|
||||
if self.type_swipe == "hand":
|
||||
swipe_x = (
|
||||
self.max_opened_x
|
||||
if self.anchor == "left"
|
||||
else -self.max_opened_x
|
||||
)
|
||||
else:
|
||||
swipe_x = self.width if self.anchor == "left" else 0
|
||||
anim = Animation(
|
||||
x=swipe_x, t=self.opening_transition, d=self.opening_time
|
||||
)
|
||||
anim.bind(on_complete=self._on_swipe_complete)
|
||||
anim.start(self.children[0])
|
||||
self.state = "opened"
|
||||
|
||||
def close_card(self):
|
||||
anim = Animation(x=0, t=self.closing_transition, d=self.opening_time)
|
||||
anim.bind(on_complete=self._reset_open_progress)
|
||||
anim.start(self.children[0])
|
||||
self.state = "closed"
|
||||
|
||||
def _reset_open_progress(self, *args):
|
||||
self.open_progress = 0.0 if self.anchor == "left" else 1.0
|
||||
self._to_closed = False
|
||||
self.dispatch("on_swipe_complete")
|
||||
|
||||
|
||||
class MDCardSwipeFrontBox(MDCard):
|
||||
pass
|
||||
|
||||
|
||||
class MDCardSwipeLayerBox(BoxLayout):
|
||||
pass
|
130
kivymd/uix/carousel.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
# TODO: Add documentation.
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.carousel import Carousel
|
||||
|
||||
|
||||
class MDCarousel(Carousel):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.register_event_type("on_slide_progress")
|
||||
self.register_event_type("on_slide_complete")
|
||||
|
||||
def on_slide_progress(self, *args):
|
||||
pass
|
||||
|
||||
def on_slide_complete(self, *args):
|
||||
pass
|
||||
|
||||
def _position_visible_slides(self, *args):
|
||||
slides, index = self.slides, self.index
|
||||
no_of_slides = len(slides) - 1
|
||||
if not slides:
|
||||
return
|
||||
x, y, width, height = self.x, self.y, self.width, self.height
|
||||
_offset, direction = self._offset, self.direction
|
||||
_prev, _next, _current = self._prev, self._next, self._current
|
||||
get_slide_container = self.get_slide_container
|
||||
last_slide = get_slide_container(slides[-1])
|
||||
first_slide = get_slide_container(slides[0])
|
||||
skip_next = False
|
||||
_loop = self.loop
|
||||
|
||||
if direction[0] in ["r", "l"]:
|
||||
xoff = x + _offset
|
||||
x_prev = {"l": xoff + width, "r": xoff - width}
|
||||
x_next = {"l": xoff - width, "r": xoff + width}
|
||||
if _prev:
|
||||
_prev.pos = (x_prev[direction[0]], y)
|
||||
elif _loop and _next and index == 0:
|
||||
if (_offset > 0 and direction[0] == "r") or (
|
||||
_offset < 0 and direction[0] == "l"
|
||||
):
|
||||
last_slide.pos = (x_prev[direction[0]], y)
|
||||
skip_next = True
|
||||
if _current:
|
||||
_current.pos = (xoff, y)
|
||||
self.dispatch("on_slide_progress", (xoff, y))
|
||||
if skip_next:
|
||||
return
|
||||
if _next:
|
||||
_next.pos = (x_next[direction[0]], y)
|
||||
elif _loop and _prev and index == no_of_slides:
|
||||
if (_offset < 0 and direction[0] == "r") or (
|
||||
_offset > 0 and direction[0] == "l"
|
||||
):
|
||||
first_slide.pos = (x_next[direction[0]], y)
|
||||
if direction[0] in ["t", "b"]:
|
||||
yoff = y + _offset
|
||||
y_prev = {"t": yoff - height, "b": yoff + height}
|
||||
y_next = {"t": yoff + height, "b": yoff - height}
|
||||
if _prev:
|
||||
_prev.pos = (x, y_prev[direction[0]])
|
||||
elif _loop and _next and index == 0:
|
||||
if (_offset > 0 and direction[0] == "t") or (
|
||||
_offset < 0 and direction[0] == "b"
|
||||
):
|
||||
last_slide.pos = (x, y_prev[direction[0]])
|
||||
skip_next = True
|
||||
if _current:
|
||||
_current.pos = (x, yoff)
|
||||
if skip_next:
|
||||
return
|
||||
if _next:
|
||||
_next.pos = (x, y_next[direction[0]])
|
||||
elif _loop and _prev and index == no_of_slides:
|
||||
if (_offset < 0 and direction[0] == "t") or (
|
||||
_offset > 0 and direction[0] == "b"
|
||||
):
|
||||
first_slide.pos = (x, y_next[direction[0]])
|
||||
|
||||
def _start_animation(self, *args, **kwargs):
|
||||
# compute target offset for ease back, next or prev
|
||||
new_offset = 0
|
||||
direction = kwargs.get("direction", self.direction)[0]
|
||||
is_horizontal = direction in "rl"
|
||||
extent = self.width if is_horizontal else self.height
|
||||
min_move = kwargs.get("min_move", self.min_move)
|
||||
_offset = kwargs.get("offset", self._offset)
|
||||
|
||||
if _offset < min_move * -extent:
|
||||
new_offset = -extent
|
||||
elif _offset > min_move * extent:
|
||||
new_offset = extent
|
||||
|
||||
# if new_offset is 0, it wasnt enough to go next/prev
|
||||
dur = self.anim_move_duration
|
||||
if new_offset == 0:
|
||||
dur = self.anim_cancel_duration
|
||||
|
||||
# detect edge cases if not looping
|
||||
len_slides = len(self.slides)
|
||||
index = self.index
|
||||
if not self.loop or len_slides == 1:
|
||||
is_first = index == 0
|
||||
is_last = index == len_slides - 1
|
||||
if direction in "rt":
|
||||
towards_prev = new_offset > 0
|
||||
towards_next = new_offset < 0
|
||||
else:
|
||||
towards_prev = new_offset < 0
|
||||
towards_next = new_offset > 0
|
||||
if (is_first and towards_prev) or (is_last and towards_next):
|
||||
new_offset = 0
|
||||
|
||||
anim = Animation(_offset=new_offset, d=dur, t=self.anim_type)
|
||||
anim.cancel_all(self)
|
||||
|
||||
def _cmp(*args):
|
||||
self.dispatch(
|
||||
"on_slide_complete",
|
||||
self.previous_slide,
|
||||
self.current_slide,
|
||||
self.next_slide,
|
||||
)
|
||||
if self._skip_slide is not None:
|
||||
self.index = self._skip_slide
|
||||
self._skip_slide = None
|
||||
|
||||
anim.bind(on_complete=_cmp)
|
||||
anim.start(self)
|
275
kivymd/uix/chip.py
Executable file
|
@ -0,0 +1,275 @@
|
|||
"""
|
||||
Components/Chip
|
||||
===============
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Chips <https://material.io/components/chips>`_
|
||||
|
||||
.. rubric:: Chips are compact elements that represent an input, attribute, or action.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chips.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDChip:
|
||||
label: 'Coffee'
|
||||
color: .4470588235118, .1960787254902, 0, 1
|
||||
icon: 'coffee'
|
||||
callback: app.callback_for_menu_items
|
||||
|
||||
The user function takes two arguments - the object and the text of the chip:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def callback_for_menu_items(self, instance, value):
|
||||
print(instance, value)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ordinary-chip.png
|
||||
:align: center
|
||||
|
||||
Use custom icon
|
||||
---------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDChip:
|
||||
label: 'Kivy'
|
||||
icon: 'data/logo/kivy-icon-256.png'
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-custom-icon.png
|
||||
:align: center
|
||||
|
||||
Use without icon
|
||||
----------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDChip:
|
||||
label: 'Without icon'
|
||||
icon: ''
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-without-icon.png
|
||||
:align: center
|
||||
|
||||
Chips with check
|
||||
----------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDChip:
|
||||
label: 'Check with icon'
|
||||
icon: 'city'
|
||||
check: True
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-check-icon.gif
|
||||
:align: center
|
||||
|
||||
Choose chip
|
||||
-----------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDChooseChip:
|
||||
|
||||
MDChip:
|
||||
label: 'Earth'
|
||||
icon: 'earth'
|
||||
selected_chip_color: .21176470535294, .098039627451, 1, 1
|
||||
|
||||
MDChip:
|
||||
label: 'Face'
|
||||
icon: 'face'
|
||||
selected_chip_color: .21176470535294, .098039627451, 1, 1
|
||||
|
||||
MDChip:
|
||||
label: 'Facebook'
|
||||
icon: 'facebook'
|
||||
selected_chip_color: .21176470535294, .098039627451, 1, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-shoose-icon.gif
|
||||
:align: center
|
||||
|
||||
.. Note:: `See full example <https://github.com/kivymd/KivyMD/wiki/Components-Chip>`_
|
||||
"""
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.button import MDIconButton
|
||||
from kivymd.uix.stacklayout import MDStackLayout
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE
|
||||
|
||||
|
||||
<MDChooseChip>
|
||||
adaptive_height: True
|
||||
spacing: "5dp"
|
||||
|
||||
|
||||
<MDChip>
|
||||
size_hint: None, None
|
||||
height: "26dp"
|
||||
padding: 0, 0, "5dp", 0
|
||||
width:
|
||||
self.minimum_width - (dp(10) if DEVICE_TYPE == "desktop" else dp(20)) \
|
||||
if root.icon != 'checkbox-blank-circle' else self.minimum_width
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.color
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
radius: [root.radius]
|
||||
|
||||
MDBoxLayout:
|
||||
id: box_check
|
||||
adaptive_size: True
|
||||
pos_hint: {'center_y': .5}
|
||||
|
||||
MDBoxLayout:
|
||||
adaptive_width: True
|
||||
padding: dp(10)
|
||||
|
||||
Label:
|
||||
id: label
|
||||
text: root.label
|
||||
size_hint_x: None
|
||||
width: self.texture_size[0]
|
||||
color: root.text_color if root.text_color else (root.theme_cls.text_color)
|
||||
|
||||
MDIconButton:
|
||||
id: icon
|
||||
icon: root.icon
|
||||
size_hint_y: None
|
||||
height: "20dp"
|
||||
pos_hint: {"center_y": .5}
|
||||
user_font_size: "20dp"
|
||||
disabled: True
|
||||
md_bg_color_disabled: 0, 0, 0, 0
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MDChip(BoxLayout, ThemableBehavior):
|
||||
label = StringProperty()
|
||||
"""Chip text.
|
||||
|
||||
:attr:`label` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
icon = StringProperty("checkbox-blank-circle")
|
||||
"""Chip icon.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'checkbox-blank-circle'`.
|
||||
"""
|
||||
|
||||
color = ListProperty()
|
||||
"""Chip color in ``rgba`` format.
|
||||
|
||||
:attr:`color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
text_color = ListProperty()
|
||||
"""Chip's text color in ``rgba`` format.
|
||||
|
||||
:attr:`text_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
check = BooleanProperty(False)
|
||||
"""
|
||||
If True, a checkmark is added to the left when touch to the chip.
|
||||
|
||||
:attr:`check` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
callback = ObjectProperty()
|
||||
"""Custom method.
|
||||
|
||||
:attr:`callback` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
radius = NumericProperty("12dp")
|
||||
"""Corner radius values.
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `'12dp'`.
|
||||
"""
|
||||
|
||||
selected_chip_color = ListProperty()
|
||||
"""The color of the chip that is currently selected in ``rgba`` format.
|
||||
|
||||
:attr:`selected_chip_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if not self.color:
|
||||
self.color = self.theme_cls.primary_color
|
||||
|
||||
def on_icon(self, instance, value):
|
||||
if value == "":
|
||||
self.icon = "checkbox-blank-circle"
|
||||
self.remove_widget(self.ids.icon)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if self.collide_point(*touch.pos):
|
||||
md_choose_chip = self.parent
|
||||
if self.selected_chip_color:
|
||||
Animation(
|
||||
color=self.theme_cls.primary_dark
|
||||
if not self.selected_chip_color
|
||||
else self.selected_chip_color,
|
||||
d=0.3,
|
||||
).start(self)
|
||||
if issubclass(md_choose_chip.__class__, MDChooseChip):
|
||||
for chip in md_choose_chip.children:
|
||||
if chip is not self:
|
||||
chip.color = self.theme_cls.primary_color
|
||||
if self.check:
|
||||
if not len(self.ids.box_check.children):
|
||||
self.ids.box_check.add_widget(
|
||||
MDIconButton(
|
||||
icon="check",
|
||||
size_hint_y=None,
|
||||
height=dp(20),
|
||||
disabled=True,
|
||||
user_font_size=dp(20),
|
||||
pos_hint={"center_y": 0.5},
|
||||
)
|
||||
)
|
||||
else:
|
||||
check = self.ids.box_check.children[0]
|
||||
self.ids.box_check.remove_widget(check)
|
||||
if self.callback:
|
||||
self.callback(self, self.label)
|
||||
|
||||
|
||||
class MDChooseChip(MDStackLayout):
|
||||
def add_widget(self, widget, index=0, canvas=None):
|
||||
if isinstance(widget, MDChip):
|
||||
return super().add_widget(widget)
|
971
kivymd/uix/datatables.py
Normal file
|
@ -0,0 +1,971 @@
|
|||
"""
|
||||
Components/DataTables
|
||||
=====================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, DataTables <https://material.io/components/data-tables>`_
|
||||
|
||||
.. rubric:: Data tables display sets of data across rows and columns.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-previous.png
|
||||
:align: center
|
||||
|
||||
.. warning::
|
||||
|
||||
Data tables are still far from perfect. Errors are possible and we hope
|
||||
you inform us about them.
|
||||
"""
|
||||
|
||||
# Special thanks for the info -
|
||||
# https://stackoverflow.com/questions/50219281/python-how-to-add-vertical-scroll-in-recycleview
|
||||
|
||||
__all__ = ("MDDataTable",)
|
||||
|
||||
from kivy import Logger
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
DictProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ButtonBehavior, FocusBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.recyclegridlayout import RecycleGridLayout
|
||||
from kivy.uix.recycleview import RecycleView
|
||||
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
|
||||
from kivy.uix.recycleview.views import RecycleDataViewBehavior
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import HoverBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.dialog import BaseDialog
|
||||
from kivymd.uix.menu import MDDropdownMenu
|
||||
from kivymd.uix.tooltip import MDTooltip
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE
|
||||
#:import StiffScrollEffect kivymd.stiffscroll.StiffScrollEffect
|
||||
|
||||
|
||||
<CellRow>
|
||||
orientation: "vertical"
|
||||
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba:
|
||||
(root.theme_cls.bg_darkest if root.theme_cls.theme_style == "Light" else root.theme_cls.bg_light) \
|
||||
if self.selected else root.theme_cls.bg_normal
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
on_press: if DEVICE_TYPE != "desktop": root.table.on_mouse_select(self)
|
||||
on_enter: if DEVICE_TYPE == "desktop": root.table.on_mouse_select(self)
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
padding: "8dp", "8dp", 0, "8dp"
|
||||
spacing: "16dp"
|
||||
|
||||
MDCheckbox:
|
||||
id: check
|
||||
size_hint: None, None
|
||||
size: 0, 0
|
||||
opacity: 0
|
||||
on_active: root.select_check(self.active)
|
||||
|
||||
MDLabel:
|
||||
id: label
|
||||
text: " " + root.text
|
||||
color: (1, 1, 1, 1) if root.theme_cls.theme_style == "Dark" else (0, 0, 0, 1)
|
||||
|
||||
MDSeparator:
|
||||
|
||||
|
||||
<CellHeader>
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
spacing: "4dp"
|
||||
tooltip_text: root.text
|
||||
|
||||
MDLabel:
|
||||
text: " " + root.text
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
bold: True
|
||||
color: (1, 1, 1, 1) if root.theme_cls.theme_style == "Dark" else (0, 0, 0, 1)
|
||||
|
||||
MDSeparator:
|
||||
id: separator
|
||||
|
||||
|
||||
<TableHeader>
|
||||
bar_width: 0
|
||||
do_scroll: False
|
||||
size_hint: 1, None
|
||||
height: header.height
|
||||
|
||||
MDGridLayout:
|
||||
id: header
|
||||
rows: 1
|
||||
cols_minimum: root.cols_minimum
|
||||
adaptive_size: True
|
||||
padding: 0, "8dp", 0, 0
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
padding: "8dp", "8dp", "4dp", 0
|
||||
spacing: "16dp"
|
||||
|
||||
MDCheckbox:
|
||||
id: check
|
||||
size_hint: None, None
|
||||
size: 0, 0
|
||||
opacity: 0
|
||||
on_active: root.table_data.select_all(self.state)
|
||||
disabled: True
|
||||
|
||||
#MDIconButton:
|
||||
# id: sort_button
|
||||
# icon: "menu-up"
|
||||
# pos_hint: {"center_y": 1}
|
||||
# ripple_scale: .65
|
||||
# on_release: root.table_data.sort_by_name()
|
||||
|
||||
CellHeader:
|
||||
id: first_cell
|
||||
|
||||
MDSeparator:
|
||||
|
||||
|
||||
<TableData>
|
||||
data: root.recycle_data
|
||||
data_first_cells: root.data_first_cells
|
||||
key_viewclass: "viewclass"
|
||||
effect_cls: StiffScrollEffect
|
||||
|
||||
TableRecycleGridLayout:
|
||||
id: row_controller
|
||||
key_selection: "selectable"
|
||||
cols: root.total_col_headings
|
||||
cols_minimum: root.cols_minimum
|
||||
default_size: None, dp(52)
|
||||
default_size_hint: 1, None
|
||||
size_hint: None, None
|
||||
height: self.minimum_height
|
||||
width: self.minimum_width
|
||||
orientation: "vertical"
|
||||
multiselect: True
|
||||
touch_multiselect: True
|
||||
|
||||
|
||||
<TablePagination>
|
||||
adaptive_height: True
|
||||
spacing: "8dp"
|
||||
|
||||
Widget:
|
||||
|
||||
MDLabel:
|
||||
text: "Rows per page"
|
||||
size_hint: None, 1
|
||||
width: self.texture_size[0]
|
||||
text_size: None, None
|
||||
font_style: "Caption"
|
||||
color: (1, 1, 1, 1) if root.theme_cls.theme_style == "Dark" else (0, 0, 0, 1)
|
||||
|
||||
MDDropDownItem:
|
||||
id: drop_item
|
||||
pos_hint: {'center_y': .5}
|
||||
text: str(root.table_data.rows_num)
|
||||
font_size: "14sp"
|
||||
on_release: root.table_data.open_pagination_menu()
|
||||
|
||||
Widget:
|
||||
size_hint_x: None
|
||||
width: "32dp"
|
||||
|
||||
MDLabel:
|
||||
id: label_rows_per_page
|
||||
text: f"1-{root.table_data.rows_num} of {len(root.table_data.row_data)}"
|
||||
size_hint: None, 1
|
||||
#width: self.texture_size[0]
|
||||
text_size: None, None
|
||||
font_style: "Caption"
|
||||
color: (1, 1, 1, 1) if root.theme_cls.theme_style == "Dark" else (0, 0, 0, 1)
|
||||
|
||||
MDIconButton:
|
||||
id: button_back
|
||||
icon: "chevron-left"
|
||||
user_font_size: "20sp"
|
||||
pos_hint: {'center_y': .5}
|
||||
disabled: True
|
||||
on_release: root.table_data.set_next_row_data_parts("back")
|
||||
|
||||
MDIconButton:
|
||||
id: button_forward
|
||||
icon: "chevron-right"
|
||||
user_font_size: "20sp"
|
||||
pos_hint: {'center_y': .5}
|
||||
on_release: root.table_data.set_next_row_data_parts("forward")
|
||||
|
||||
|
||||
<MDDataTable>
|
||||
|
||||
MDCard:
|
||||
id: container
|
||||
orientation: "vertical"
|
||||
elevation: 14
|
||||
md_bg_color: 0, 0, 0, 0
|
||||
padding: "24dp", "24dp", "8dp", "8dp"
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.theme_cls.bg_normal
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class TableRecycleGridLayout(
|
||||
FocusBehavior, LayoutSelectionBehavior, RecycleGridLayout
|
||||
):
|
||||
selected_row = NumericProperty(0)
|
||||
table_data = ObjectProperty(None)
|
||||
|
||||
def get_nodes(self):
|
||||
nodes = self.get_selectable_nodes()
|
||||
if self.nodes_order_reversed:
|
||||
nodes = nodes[::-1]
|
||||
if not nodes:
|
||||
return None, None
|
||||
|
||||
selected = self.selected_nodes
|
||||
if not selected: # nothing selected, select the first
|
||||
self.selected_row = 0
|
||||
self.select_row(nodes)
|
||||
return None, None
|
||||
|
||||
if len(nodes) == 1: # the only selectable node is selected already
|
||||
return None, None
|
||||
|
||||
index = selected[-1]
|
||||
if index > len(nodes):
|
||||
last = len(nodes)
|
||||
else:
|
||||
last = nodes.index(index)
|
||||
self.clear_selection()
|
||||
return last, nodes
|
||||
|
||||
def select_next(self, instance):
|
||||
"""Select next row."""
|
||||
|
||||
self.table_data = instance
|
||||
last, nodes = self.get_nodes()
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
if last == len(nodes) - 1:
|
||||
self.selected_row = nodes[0]
|
||||
else:
|
||||
self.selected_row = nodes[last + 1]
|
||||
|
||||
self.selected_row += self.table_data.total_col_headings
|
||||
self.select_row(nodes)
|
||||
|
||||
def select_current(self, instance):
|
||||
"""Select current row."""
|
||||
|
||||
self.table_data = instance
|
||||
last, nodes = self.get_nodes()
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
self.select_row(nodes)
|
||||
|
||||
def select_row(self, nodes):
|
||||
col = self.table_data.recycle_data[self.selected_row]["range"]
|
||||
for x in range(col[0], col[1] + 1):
|
||||
self.select_node(nodes[x])
|
||||
|
||||
|
||||
class CellRow(
|
||||
ThemableBehavior,
|
||||
RecycleDataViewBehavior,
|
||||
HoverBehavior,
|
||||
ButtonBehavior,
|
||||
BoxLayout,
|
||||
):
|
||||
text = StringProperty() # row text
|
||||
table = ObjectProperty() # <TableData object>
|
||||
index = None
|
||||
selected = BooleanProperty(False)
|
||||
selectable = BooleanProperty(True)
|
||||
|
||||
def on_table(self, instance, table):
|
||||
"""Sets padding/spacing to zero if no checkboxes are used for rows."""
|
||||
|
||||
if not table.check:
|
||||
self.ids.box.padding = 0
|
||||
self.ids.box.spacing = 0
|
||||
|
||||
def refresh_view_attrs(self, table_data, index, data):
|
||||
"""
|
||||
Called by the :class:`RecycleAdapter` when the view is initially
|
||||
populated with the values from the `data` dictionary for this item.
|
||||
|
||||
Any pos or size info should be removed because they are set
|
||||
subsequently with :attr:`refresh_view_layout`.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`table_data`: :class:`TableData` instance
|
||||
The :class:`TableData` that caused the update.
|
||||
`data`: dict
|
||||
The data dict used to populate this view.
|
||||
"""
|
||||
|
||||
self.index = index
|
||||
return super().refresh_view_attrs(table_data, index, data)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if super().on_touch_down(touch):
|
||||
if self.table._parent:
|
||||
self.table._parent.dispatch("on_row_press", self)
|
||||
return True
|
||||
|
||||
def apply_selection(self, table_data, index, is_selected):
|
||||
"""Called when list items of table appear on the screen."""
|
||||
|
||||
self.selected = is_selected
|
||||
|
||||
# Set checkboxes.
|
||||
if table_data.check:
|
||||
if self.text in table_data.data_first_cells:
|
||||
self.ids.check.size = (dp(32), dp(32))
|
||||
self.ids.check.opacity = 1
|
||||
self.ids.box.spacing = dp(16)
|
||||
self.ids.box.padding[0] = dp(8)
|
||||
else:
|
||||
self.ids.check.size = (0, 0)
|
||||
self.ids.check.opacity = 0
|
||||
self.ids.box.spacing = 0
|
||||
self.ids.box.padding[0] = 0
|
||||
# Set checkboxes state.
|
||||
if table_data._rows_number in table_data.current_selection_check:
|
||||
for index in table_data.current_selection_check[
|
||||
table_data._rows_number
|
||||
]:
|
||||
if (
|
||||
self.index
|
||||
in table_data.current_selection_check[
|
||||
table_data._rows_number
|
||||
]
|
||||
):
|
||||
self.ids.check.state = "down"
|
||||
else:
|
||||
self.ids.check.state = "normal"
|
||||
else:
|
||||
self.ids.check.state = "normal"
|
||||
|
||||
def select_check(self, active):
|
||||
"""Called upon activation/deactivation of the checkbox."""
|
||||
|
||||
if active and self.index not in self.table.current_selection_check:
|
||||
if (
|
||||
self.table._rows_number
|
||||
not in self.table.current_selection_check
|
||||
):
|
||||
self.table.current_selection_check[self.table._rows_number] = []
|
||||
if (
|
||||
self.index
|
||||
not in self.table.current_selection_check[
|
||||
self.table._rows_number
|
||||
]
|
||||
):
|
||||
self.table.current_selection_check[
|
||||
self.table._rows_number
|
||||
].append(self.index)
|
||||
else:
|
||||
if self.table._rows_number in self.table.current_selection_check:
|
||||
if (
|
||||
self.index
|
||||
in self.table.current_selection_check[
|
||||
self.table._rows_number
|
||||
]
|
||||
and not active
|
||||
):
|
||||
self.table.current_selection_check[
|
||||
self.table._rows_number
|
||||
].remove(self.index)
|
||||
self.table.get_select_row(self.index)
|
||||
|
||||
|
||||
class CellHeader(MDTooltip, BoxLayout):
|
||||
text = StringProperty() # column text
|
||||
|
||||
|
||||
class TableHeader(ScrollView):
|
||||
table_data = ObjectProperty() # <TableData object>
|
||||
column_data = ListProperty() # MDDataTable.column_data
|
||||
col_headings = ListProperty() # column names list
|
||||
sort = BooleanProperty(False) # MDDataTable.sort
|
||||
# kivy.uix.gridlayout.GridLayout.cols_minimum
|
||||
cols_minimum = DictProperty()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# Create cells.
|
||||
for i, col_heading in enumerate(self.column_data):
|
||||
self.cols_minimum[i] = col_heading[1] * 5
|
||||
self.col_headings.append(col_heading[0])
|
||||
if i:
|
||||
self.ids.header.add_widget(
|
||||
CellHeader(text=col_heading[0], width=self.cols_minimum[i])
|
||||
)
|
||||
else:
|
||||
# Sets the text in the first cell.
|
||||
self.ids.first_cell.text = col_heading[0]
|
||||
self.ids.first_cell.ids.separator.height = 0
|
||||
self.ids.first_cell.width = self.cols_minimum[i]
|
||||
|
||||
def on_table_data(self, instance, value):
|
||||
"""Sets the checkbox in the first cell."""
|
||||
|
||||
if self.table_data.check:
|
||||
self.ids.check.size = (dp(32), dp(32))
|
||||
self.ids.check.opacity = 1
|
||||
else:
|
||||
self.ids.box.padding[0] = 0
|
||||
self.ids.box.spacing = 0
|
||||
|
||||
def on_sort(self, instance, value):
|
||||
"""Rows sorting method."""
|
||||
|
||||
Logger.info("TableData: Sorting table items is not implemented")
|
||||
# if not self.sort:
|
||||
# self.ids.sort_button.size = (0, 0)
|
||||
# self.ids.sort_button.opacity = 0
|
||||
|
||||
|
||||
class TableData(RecycleView):
|
||||
recycle_data = ListProperty() # kivy.uix.recycleview.RecycleView.data
|
||||
data_first_cells = ListProperty() # list of first row cells
|
||||
row_data = ListProperty() # MDDataTable.row_data
|
||||
total_col_headings = NumericProperty(0) # TableHeader.col_headings
|
||||
cols_minimum = DictProperty() # TableHeader.cols_minimum
|
||||
table_header = ObjectProperty() # <TableHeader object>
|
||||
pagination_menu = ObjectProperty() # <MDDropdownMenu object>
|
||||
pagination = ObjectProperty() # <TablePagination object>
|
||||
check = ObjectProperty() # MDDataTable.check
|
||||
rows_num = NumericProperty() # number of rows displayed on the table page
|
||||
# Open or close the menu for selecting the number of rows displayed
|
||||
# on the table page.
|
||||
pagination_menu_open = BooleanProperty(False)
|
||||
# List of indexes of marked checkboxes.
|
||||
current_selection_check = DictProperty()
|
||||
sort = BooleanProperty()
|
||||
|
||||
_parent = ObjectProperty()
|
||||
_rows_number = NumericProperty(0)
|
||||
_rows_num = NumericProperty()
|
||||
_current_value = NumericProperty(1)
|
||||
_to_value = NumericProperty()
|
||||
_row_data_parts = ListProperty()
|
||||
|
||||
def __init__(self, table_header, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.table_header = table_header
|
||||
self.total_col_headings = len(table_header.col_headings)
|
||||
self.cols_minimum = table_header.cols_minimum
|
||||
self.set_row_data()
|
||||
Clock.schedule_once(self.set_default_first_row, 0)
|
||||
|
||||
def get_select_row(self, index):
|
||||
"""Returns the current row with all elements."""
|
||||
|
||||
row = []
|
||||
for data in self.recycle_data:
|
||||
if index in data["range"]:
|
||||
row.append(data["text"])
|
||||
self._parent.dispatch("on_check_press", row)
|
||||
|
||||
def set_default_first_row(self, dt):
|
||||
"""Set default first row as selected."""
|
||||
|
||||
self.ids.row_controller.select_next(self)
|
||||
|
||||
def sort_by_name(self):
|
||||
"""Sorts table data."""
|
||||
|
||||
# TODO: implement a rows sorting method.
|
||||
|
||||
def set_row_data(self):
|
||||
data = []
|
||||
low = 0
|
||||
high = self.total_col_headings - 1
|
||||
self.recycle_data = []
|
||||
self.data_first_cells = []
|
||||
|
||||
if self._row_data_parts:
|
||||
# for row in self.row_data:
|
||||
for row in self._row_data_parts[self._rows_number]:
|
||||
for i in range(len(row)):
|
||||
data.append([row[i], row[0], [low, high]])
|
||||
low += self.total_col_headings
|
||||
high += self.total_col_headings
|
||||
|
||||
for j, x in enumerate(data):
|
||||
if x[0] == x[1]:
|
||||
self.data_first_cells.append(x[0])
|
||||
self.recycle_data.append(
|
||||
{
|
||||
"text": str(x[0]),
|
||||
"Index": str(x[1]),
|
||||
"range": x[2],
|
||||
"selectable": True,
|
||||
"viewclass": "CellRow",
|
||||
"table": self,
|
||||
}
|
||||
)
|
||||
if not self.table_header.column_data:
|
||||
raise ValueError("Set value for column_data in class TableData")
|
||||
self.data_first_cells.append(self.table_header.column_data[0][0])
|
||||
|
||||
def set_text_from_of(self, direction):
|
||||
"""Sets the text of the numbers of displayed pages in table."""
|
||||
|
||||
if self.pagination:
|
||||
if direction == "forward":
|
||||
if (
|
||||
len(self._row_data_parts[self._rows_number])
|
||||
< self._to_value
|
||||
):
|
||||
self._current_value = self._current_value + self.rows_num
|
||||
else:
|
||||
self._current_value = self._current_value + len(
|
||||
self._row_data_parts[self._rows_number]
|
||||
)
|
||||
self._to_value = self._to_value + len(
|
||||
self._row_data_parts[self._rows_number]
|
||||
)
|
||||
if direction == "back":
|
||||
self._current_value = self._current_value - len(
|
||||
self._row_data_parts[self._rows_number]
|
||||
)
|
||||
self._to_value = self._to_value - len(
|
||||
self._row_data_parts[self._rows_number + 1]
|
||||
)
|
||||
if direction == "increment":
|
||||
self._current_value = 1
|
||||
self._to_value = self.rows_num + self._current_value - 1
|
||||
|
||||
self.pagination.ids.label_rows_per_page.text = f"{self._current_value}-{self._to_value} of {len(self.row_data)}"
|
||||
|
||||
def select_all(self, state):
|
||||
"""Sets the checkboxes of all rows to the active/inactive position."""
|
||||
|
||||
# FIXME: fix the work of selecting all cells..
|
||||
for i, data in enumerate(self.recycle_data):
|
||||
opts = self.layout_manager.view_opts
|
||||
cell_row_obj = self.view_adapter.get_view(
|
||||
i, self.data[i], opts[i]["viewclass"]
|
||||
)
|
||||
cell_row_obj.ids.check.state = state
|
||||
self.on_mouse_select(cell_row_obj)
|
||||
cell_row_obj.select_check(True if state == "down" else False)
|
||||
|
||||
def close_pagination_menu(self, *args):
|
||||
"""Called when the pagination menu window is closed."""
|
||||
|
||||
self.pagination_menu_open = False
|
||||
|
||||
def open_pagination_menu(self):
|
||||
"""Open pagination menu window."""
|
||||
|
||||
if self.pagination_menu.items:
|
||||
self.pagination_menu_open = True
|
||||
self.pagination_menu.open()
|
||||
|
||||
def set_number_displayed_lines(self, instance_menu, instance_menu_item):
|
||||
"""
|
||||
Called when the user sets the number of pages displayed
|
||||
in the table.
|
||||
"""
|
||||
|
||||
self.rows_num = int(instance_menu_item.text)
|
||||
self.set_row_data()
|
||||
self.set_text_from_of("increment")
|
||||
|
||||
def set_next_row_data_parts(self, direction):
|
||||
"""Called when switching the pages of the table."""
|
||||
|
||||
if direction == "forward":
|
||||
self._rows_number += 1
|
||||
self.pagination.ids.button_back.disabled = False
|
||||
elif direction == "back":
|
||||
self._rows_number -= 1
|
||||
self.pagination.ids.button_forward.disabled = False
|
||||
|
||||
self.set_row_data()
|
||||
self.set_text_from_of(direction)
|
||||
|
||||
if self._to_value == len(self.row_data):
|
||||
self.pagination.ids.button_forward.disabled = True
|
||||
if self._current_value == 1:
|
||||
self.pagination.ids.button_back.disabled = True
|
||||
|
||||
def _split_list_into_equal_parts(self, lst, parts):
|
||||
for i in range(0, len(lst), parts):
|
||||
yield lst[i : i + parts]
|
||||
|
||||
def on_mouse_select(self, instance):
|
||||
"""Called on the ``on_enter`` event of the :class:`~CellRow` class"""
|
||||
|
||||
if not self.pagination_menu_open:
|
||||
if self.ids.row_controller.selected_row != instance.index:
|
||||
self.ids.row_controller.selected_row = instance.index
|
||||
self.ids.row_controller.select_current(self)
|
||||
|
||||
def on_rows_num(self, instance, value):
|
||||
if not self._to_value:
|
||||
self._to_value = value
|
||||
|
||||
self._rows_number = 0
|
||||
self._row_data_parts = list(
|
||||
self._split_list_into_equal_parts(self.row_data, value)
|
||||
)
|
||||
|
||||
# def on_pagination(self, instance_table, instance_pagination):
|
||||
# if len(self._row_data_parts) <= self._to_value:
|
||||
# instance_pagination.ids.button_forward.disabled = True
|
||||
|
||||
|
||||
class TablePagination(ThemableBehavior, MDBoxLayout):
|
||||
"""Pagination Container."""
|
||||
|
||||
table_data = ObjectProperty() # <TableData object>
|
||||
|
||||
|
||||
class MDDataTable(BaseDialog):
|
||||
"""
|
||||
:Events:
|
||||
:attr:`on_row_press`
|
||||
Called when a table row is clicked.
|
||||
:attr:`on_check_press`
|
||||
Called when the check box in the table row is checked.
|
||||
|
||||
.. rubric:: Use events as follows
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.metrics import dp
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.datatables import MDDataTable
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
self.data_tables = MDDataTable(
|
||||
size_hint=(0.9, 0.6),
|
||||
use_pagination=True,
|
||||
check=True,
|
||||
column_data=[
|
||||
("No.", dp(30)),
|
||||
("Column 1", dp(30)),
|
||||
("Column 2", dp(30)),
|
||||
("Column 3", dp(30)),
|
||||
("Column 4", dp(30)),
|
||||
("Column 5", dp(30)),
|
||||
],
|
||||
row_data=[
|
||||
(f"{i + 1}", "2.23", "3.65", "44.1", "0.45", "62.5")
|
||||
for i in range(50)
|
||||
],
|
||||
)
|
||||
self.data_tables.bind(on_row_press=self.on_row_press)
|
||||
self.data_tables.bind(on_check_press=self.on_check_press)
|
||||
|
||||
def on_start(self):
|
||||
self.data_tables.open()
|
||||
|
||||
def on_row_press(self, instance_table, instance_row):
|
||||
'''Called when a table row is clicked.'''
|
||||
|
||||
print(instance_table, instance_row)
|
||||
|
||||
def on_check_press(self, instance_table, current_row):
|
||||
'''Called when the check box in the table row is checked.'''
|
||||
|
||||
print(instance_table, current_row)
|
||||
|
||||
|
||||
Example().run()
|
||||
"""
|
||||
|
||||
column_data = ListProperty()
|
||||
"""
|
||||
Data for header columns.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.metrics import dp
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.datatables import MDDataTable
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
self.data_tables = MDDataTable(
|
||||
size_hint=(0.9, 0.6),
|
||||
# name column, width column
|
||||
column_data=[
|
||||
("Column 1", dp(30)),
|
||||
("Column 2", dp(30)),
|
||||
("Column 3", dp(30)),
|
||||
("Column 4", dp(30)),
|
||||
("Column 5", dp(30)),
|
||||
("Column 6", dp(30)),
|
||||
],
|
||||
)
|
||||
|
||||
def on_start(self):
|
||||
self.data_tables.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-column-data.png
|
||||
:align: center
|
||||
|
||||
:attr:`column_data` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
row_data = ListProperty()
|
||||
"""
|
||||
Data for rows.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.metrics import dp
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.datatables import MDDataTable
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
self.data_tables = MDDataTable(
|
||||
size_hint=(0.9, 0.6),
|
||||
column_data=[
|
||||
("Column 1", dp(30)),
|
||||
("Column 2", dp(30)),
|
||||
("Column 3", dp(30)),
|
||||
("Column 4", dp(30)),
|
||||
("Column 5", dp(30)),
|
||||
("Column 6", dp(30)),
|
||||
],
|
||||
row_data=[
|
||||
# The number of elements must match the length
|
||||
# of the `column_data` list.
|
||||
("1", "2", "3", "4", "5", "6"),
|
||||
("1", "2", "3", "4", "5", "6"),
|
||||
],
|
||||
)
|
||||
|
||||
def on_start(self):
|
||||
self.data_tables.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-row-data.png
|
||||
:align: center
|
||||
|
||||
:attr:`row_data` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
sort = BooleanProperty(False)
|
||||
"""
|
||||
Whether to display buttons for sorting table items.
|
||||
|
||||
:attr:`sort` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
check = BooleanProperty(False)
|
||||
"""
|
||||
Use or not use checkboxes for rows.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.gif
|
||||
:align: center
|
||||
|
||||
:attr:`check` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
use_pagination = BooleanProperty(False)
|
||||
"""
|
||||
Use page pagination for table or not.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.datatables import MDDataTable
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
self.data_tables = MDDataTable(
|
||||
size_hint=(0.9, 0.6),
|
||||
use_pagination=True,
|
||||
column_data=[
|
||||
("No.", dp(30)),
|
||||
("Column 1", dp(30)),
|
||||
("Column 2", dp(30)),
|
||||
("Column 3", dp(30)),
|
||||
("Column 4", dp(30)),
|
||||
("Column 5", dp(30)),
|
||||
],
|
||||
row_data=[
|
||||
(f"{i + 1}", "1", "2", "3", "4", "5") for i in range(50)
|
||||
],
|
||||
)
|
||||
|
||||
def on_start(self):
|
||||
self.data_tables.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination.png
|
||||
:align: center
|
||||
|
||||
:attr:`use_pagination` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
rows_num = NumericProperty(5)
|
||||
"""
|
||||
The number of rows displayed on one page of the table.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination.gif
|
||||
:align: center
|
||||
|
||||
:attr:`rows_num` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `10`.
|
||||
"""
|
||||
|
||||
pagination_menu_pos = OptionProperty("center", options=["center", "auto"])
|
||||
"""
|
||||
Menu position for selecting the number of displayed rows.
|
||||
Available options are `'center'`, `'auto'`.
|
||||
|
||||
.. rubric:: Center
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-center.png
|
||||
:align: center
|
||||
|
||||
.. rubric:: Auto
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-auto.png
|
||||
:align: center
|
||||
|
||||
:attr:`pagination_menu_pos` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'center'`.
|
||||
"""
|
||||
|
||||
pagination_menu_height = NumericProperty("140dp")
|
||||
"""
|
||||
Menu height for selecting the number of displayed rows.
|
||||
|
||||
.. rubric:: 140dp
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-140.png
|
||||
:align: center
|
||||
|
||||
.. rubric:: 240dp
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-240.png
|
||||
:align: center
|
||||
|
||||
:attr:`pagination_menu_height` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `'140dp'`.
|
||||
"""
|
||||
|
||||
background_color = ListProperty([0, 0, 0, 0])
|
||||
"""
|
||||
Background color in the format (r, g, b, a).
|
||||
See :attr:`~kivy.uix.modalview.ModalView.background_color`.
|
||||
|
||||
:attr:`background_color` is a :class:`~kivy.properties.ListProperty` and
|
||||
defaults to [0, 0, 0, .7].
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.register_event_type("on_row_press")
|
||||
self.register_event_type("on_check_press")
|
||||
self.header = TableHeader(column_data=self.column_data, sort=self.sort)
|
||||
self.table_data = TableData(
|
||||
self.header,
|
||||
row_data=self.row_data,
|
||||
check=self.check,
|
||||
rows_num=self.rows_num,
|
||||
_parent=self,
|
||||
)
|
||||
self.pagination = TablePagination(table_data=self.table_data)
|
||||
self.table_data.pagination = self.pagination
|
||||
self.header.table_data = self.table_data
|
||||
self.table_data.fbind("scroll_x", self._scroll_with_header)
|
||||
self.ids.container.add_widget(self.header)
|
||||
self.ids.container.add_widget(self.table_data)
|
||||
if self.use_pagination:
|
||||
self.ids.container.add_widget(self.pagination)
|
||||
Clock.schedule_once(self.create_pagination_menu, 0.5)
|
||||
|
||||
def on_row_press(self, *args):
|
||||
"""Called when a table row is clicked."""
|
||||
|
||||
def on_check_press(self, *args):
|
||||
"""Called when the check box in the table row is checked."""
|
||||
|
||||
def _scroll_with_header(self, instance, value):
|
||||
self.header.scroll_x = value
|
||||
|
||||
def create_pagination_menu(self, interval):
|
||||
menu_items = [
|
||||
{"text": f"{i}"}
|
||||
for i in range(
|
||||
self.rows_num, len(self.row_data) // 2, self.rows_num
|
||||
)
|
||||
]
|
||||
pagination_menu = MDDropdownMenu(
|
||||
caller=self.pagination.ids.drop_item,
|
||||
items=menu_items,
|
||||
position=self.pagination_menu_pos,
|
||||
max_height=self.pagination_menu_height,
|
||||
width_mult=2,
|
||||
)
|
||||
pagination_menu.bind(
|
||||
on_release=self.table_data.set_number_displayed_lines,
|
||||
on_dismiss=self.table_data.close_pagination_menu,
|
||||
)
|
||||
self.table_data.pagination_menu = pagination_menu
|
603
kivymd/uix/dialog.py
Executable file
|
@ -0,0 +1,603 @@
|
|||
"""
|
||||
Components/Dialog
|
||||
=================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Dialogs <https://material.io/components/dialogs>`_
|
||||
|
||||
|
||||
.. rubric:: Dialogs inform users about a task and can contain critical
|
||||
information, require decisions, or involve multiple tasks.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialogs.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
|
||||
KV = '''
|
||||
FloatLayout:
|
||||
|
||||
MDFlatButton:
|
||||
text: "ALERT DIALOG"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
on_release: app.show_alert_dialog()
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_alert_dialog(self):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
text="Discard draft?",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
MDFlatButton(
|
||||
text="DISCARD", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/alert-dialog.png
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("MDDialog",)
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import (
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.modalview import ModalView
|
||||
|
||||
from kivymd.material_resources import DEVICE_TYPE
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.button import BaseButton
|
||||
from kivymd.uix.card import MDSeparator
|
||||
from kivymd.uix.list import BaseListItem
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import images_path kivymd.images_path
|
||||
|
||||
|
||||
<BaseDialog>
|
||||
background: '{}/transparent.png'.format(images_path)
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
radius: [5]
|
||||
Scale:
|
||||
origin: self.center
|
||||
x: root._scale_x
|
||||
y: root._scale_y
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
|
||||
<MDDialog>
|
||||
|
||||
MDCard:
|
||||
id: container
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
elevation: 4
|
||||
md_bg_color: 0, 0, 0, 0
|
||||
padding: "24dp", "24dp", "8dp", "8dp"
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.md_bg_color
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
radius: root.radius
|
||||
|
||||
MDLabel:
|
||||
id: title
|
||||
text: root.title
|
||||
font_style: "H6"
|
||||
bold: True
|
||||
markup: True
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
valign: "top"
|
||||
|
||||
BoxLayout:
|
||||
id: spacer_top_box
|
||||
size_hint_y: None
|
||||
height: root._spacer_top
|
||||
|
||||
MDLabel:
|
||||
id: text
|
||||
text: root.text
|
||||
font_style: "Body1"
|
||||
theme_text_color: "Custom"
|
||||
text_color: root.theme_cls.disabled_hint_text_color
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
markup: True
|
||||
|
||||
ScrollView:
|
||||
id: scroll
|
||||
size_hint_y: None
|
||||
height: root._scroll_height
|
||||
|
||||
MDGridLayout:
|
||||
id: box_items
|
||||
adaptive_height: True
|
||||
cols: 1
|
||||
|
||||
BoxLayout:
|
||||
id: spacer_bottom_box
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
AnchorLayout:
|
||||
id: root_button_box
|
||||
size_hint_y: None
|
||||
height: "52dp"
|
||||
anchor_x: "right"
|
||||
|
||||
MDBoxLayout:
|
||||
id: button_box
|
||||
adaptive_size: True
|
||||
spacing: "8dp"
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class BaseDialog(ThemableBehavior, ModalView):
|
||||
_scale_x = NumericProperty(1)
|
||||
_scale_y = NumericProperty(1)
|
||||
|
||||
|
||||
class MDDialog(BaseDialog):
|
||||
title = StringProperty()
|
||||
"""
|
||||
Title dialog.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.dialog = MDDialog(
|
||||
title="Reset settings?",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
MDFlatButton(
|
||||
text="ACCEPT", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-title.png
|
||||
:align: center
|
||||
|
||||
:attr:`title` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
text = StringProperty()
|
||||
"""
|
||||
Text dialog.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.dialog = MDDialog(
|
||||
title="Reset settings?",
|
||||
text="This will reset your device to its default factory settings.",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
MDFlatButton(
|
||||
text="ACCEPT", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-text.png
|
||||
:align: center
|
||||
|
||||
:attr:`text` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
radius = ListProperty([7, 7, 7, 7])
|
||||
"""
|
||||
Dialog corners rounding value.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.dialog = MDDialog(
|
||||
text="Oops! Something seems to have gone wrong!",
|
||||
radius=[20, 7, 20, 7],
|
||||
)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-radius.png
|
||||
:align: center
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[7, 7, 7, 7]`.
|
||||
"""
|
||||
|
||||
buttons = ListProperty()
|
||||
"""
|
||||
List of button objects for dialog.
|
||||
Objects must be inherited from :class:`~kivymd.uix.button.BaseButton` class.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.dialog = MDDialog(
|
||||
text="Discard draft?",
|
||||
buttons=[
|
||||
MDFlatButton(text="CANCEL"), MDRaisedButton(text="DISCARD"),
|
||||
],
|
||||
)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-buttons.png
|
||||
:align: center
|
||||
|
||||
:attr:`buttons` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
items = ListProperty()
|
||||
"""
|
||||
List of items objects for dialog.
|
||||
Objects must be inherited from :class:`~kivymd.uix.list.BaseListItem` class.
|
||||
|
||||
With type 'simple'
|
||||
-----------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from kivymd.uix.list import OneLineAvatarListItem
|
||||
|
||||
KV = '''
|
||||
<Item>
|
||||
|
||||
ImageLeftWidget:
|
||||
source: root.source
|
||||
|
||||
|
||||
FloatLayout:
|
||||
|
||||
MDFlatButton:
|
||||
text: "ALERT DIALOG"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
on_release: app.show_simple_dialog()
|
||||
'''
|
||||
|
||||
|
||||
class Item(OneLineAvatarListItem):
|
||||
divider = None
|
||||
source = StringProperty()
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_simple_dialog(self):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
title="Set backup account",
|
||||
type="simple",
|
||||
items=[
|
||||
Item(text="user01@gmail.com", source="user-1.png"),
|
||||
Item(text="user02@gmail.com", source="user-2.png"),
|
||||
Item(text="Add account", source="add-icon.png"),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-items.png
|
||||
:align: center
|
||||
|
||||
With type 'confirmation'
|
||||
-----------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from kivymd.uix.list import OneLineAvatarIconListItem
|
||||
|
||||
KV = '''
|
||||
<ItemConfirm>
|
||||
on_release: root.set_icon(check)
|
||||
|
||||
CheckboxLeftWidget:
|
||||
id: check
|
||||
group: "check"
|
||||
|
||||
|
||||
FloatLayout:
|
||||
|
||||
MDFlatButton:
|
||||
text: "ALERT DIALOG"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
on_release: app.show_confirmation_dialog()
|
||||
'''
|
||||
|
||||
|
||||
class ItemConfirm(OneLineAvatarIconListItem):
|
||||
divider = None
|
||||
|
||||
def set_icon(self, instance_check):
|
||||
instance_check.active = True
|
||||
check_list = instance_check.get_widgets(instance_check.group)
|
||||
for check in check_list:
|
||||
if check != instance_check:
|
||||
check.active = False
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_confirmation_dialog(self):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
title="Phone ringtone",
|
||||
type="confirmation",
|
||||
items=[
|
||||
ItemConfirm(text="Callisto"),
|
||||
ItemConfirm(text="Luna"),
|
||||
ItemConfirm(text="Night"),
|
||||
ItemConfirm(text="Solo"),
|
||||
ItemConfirm(text="Phobos"),
|
||||
ItemConfirm(text="Diamond"),
|
||||
ItemConfirm(text="Sirena"),
|
||||
ItemConfirm(text="Red music"),
|
||||
ItemConfirm(text="Allergio"),
|
||||
ItemConfirm(text="Magic"),
|
||||
ItemConfirm(text="Tic-tac"),
|
||||
],
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
MDFlatButton(
|
||||
text="OK", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-confirmation.png
|
||||
:align: center
|
||||
|
||||
:attr:`items` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
type = OptionProperty(
|
||||
"alert", options=["alert", "simple", "confirmation", "custom"]
|
||||
)
|
||||
"""
|
||||
Dialog type.
|
||||
Available option are `'alert'`, `'simple'`, `'confirmation'`, `'custom'`.
|
||||
|
||||
:attr:`type` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'alert'`.
|
||||
"""
|
||||
|
||||
content_cls = ObjectProperty()
|
||||
"""
|
||||
Custom content class.
|
||||
|
||||
.. code-block::
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
|
||||
KV = '''
|
||||
<Content>
|
||||
orientation: "vertical"
|
||||
spacing: "12dp"
|
||||
size_hint_y: None
|
||||
height: "120dp"
|
||||
|
||||
MDTextField:
|
||||
hint_text: "City"
|
||||
|
||||
MDTextField:
|
||||
hint_text: "Street"
|
||||
|
||||
|
||||
FloatLayout:
|
||||
|
||||
MDFlatButton:
|
||||
text: "ALERT DIALOG"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
on_release: app.show_confirmation_dialog()
|
||||
'''
|
||||
|
||||
|
||||
class Content(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_confirmation_dialog(self):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
title="Address:",
|
||||
type="custom",
|
||||
content_cls=Content(),
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
MDFlatButton(
|
||||
text="OK", text_color=self.theme_cls.primary_color
|
||||
),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png
|
||||
:align: center
|
||||
|
||||
:attr:`content_cls` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `'None'`.
|
||||
"""
|
||||
|
||||
md_bg_color = ListProperty()
|
||||
"""
|
||||
Background color in the format (r, g, b, a).
|
||||
|
||||
:attr:`md_bg_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
_scroll_height = NumericProperty("28dp")
|
||||
_spacer_top = NumericProperty("24dp")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.md_bg_color = (
|
||||
self.theme_cls.bg_dark if not self.md_bg_color else self.md_bg_color
|
||||
)
|
||||
|
||||
if self.size_hint == [1, 1] and DEVICE_TYPE == "mobile":
|
||||
self.size_hint = (None, None)
|
||||
self.width = dp(280)
|
||||
elif self.size_hint == [1, 1] and DEVICE_TYPE == "desktop":
|
||||
self.size_hint = (None, None)
|
||||
self.width = dp(560)
|
||||
|
||||
if not self.title:
|
||||
self._spacer_top = 0
|
||||
|
||||
if not self.buttons:
|
||||
self.ids.root_button_box.height = 0
|
||||
else:
|
||||
self.create_buttons()
|
||||
|
||||
update_height = False
|
||||
if self.type in ("simple", "confirmation"):
|
||||
if self.type == "confirmation":
|
||||
self.ids.spacer_top_box.add_widget(MDSeparator())
|
||||
self.ids.spacer_bottom_box.add_widget(MDSeparator())
|
||||
self.create_items()
|
||||
if self.type == "custom":
|
||||
if self.content_cls:
|
||||
self.ids.container.remove_widget(self.ids.scroll)
|
||||
self.ids.container.remove_widget(self.ids.text)
|
||||
self.ids.spacer_top_box.add_widget(self.content_cls)
|
||||
self.ids.spacer_top_box.padding = (0, "24dp", "16dp", 0)
|
||||
update_height = True
|
||||
if self.type == "alert":
|
||||
self.ids.scroll.bar_width = 0
|
||||
|
||||
if update_height:
|
||||
Clock.schedule_once(self.update_height)
|
||||
|
||||
def update_height(self, *_):
|
||||
self._spacer_top = self.content_cls.height + dp(24)
|
||||
|
||||
def on_open(self):
|
||||
# TODO: Add scrolling text.
|
||||
self.height = self.ids.container.height
|
||||
|
||||
def set_normal_height(self):
|
||||
self.size_hint_y = 0.8
|
||||
|
||||
def get_normal_height(self):
|
||||
return (
|
||||
(Window.height * 80 / 100)
|
||||
- self._spacer_top
|
||||
- dp(52)
|
||||
- self.ids.container.padding[1]
|
||||
- self.ids.container.padding[-1]
|
||||
- 100
|
||||
)
|
||||
|
||||
def edit_padding_for_item(self, instance_item):
|
||||
instance_item.ids._left_container.x = 0
|
||||
instance_item._txt_left_pad = "56dp"
|
||||
|
||||
def create_items(self):
|
||||
self.ids.container.remove_widget(self.ids.text)
|
||||
height = 0
|
||||
|
||||
for item in self.items:
|
||||
if issubclass(item.__class__, BaseListItem):
|
||||
height += item.height # calculate height contents
|
||||
self.edit_padding_for_item(item)
|
||||
self.ids.box_items.add_widget(item)
|
||||
|
||||
if height > Window.height:
|
||||
self.set_normal_height()
|
||||
self.ids.scroll.height = self.get_normal_height()
|
||||
else:
|
||||
self.ids.scroll.height = height
|
||||
|
||||
def create_buttons(self):
|
||||
for button in self.buttons:
|
||||
if issubclass(button.__class__, BaseButton):
|
||||
self.ids.button_box.add_widget(button)
|
713
kivymd/uix/dialog2.py
Normal file
|
@ -0,0 +1,713 @@
|
|||
"""
|
||||
Dialog
|
||||
======
|
||||
|
||||
Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors -
|
||||
KivyMD library up to version 0.1.2
|
||||
Copyright (c) 2019 Ivanov Yuri and KivyMD contributors -
|
||||
KivyMD library version 0.1.3 and higher
|
||||
|
||||
For suggestions and questions:
|
||||
<kivydevelopment@gmail.com>
|
||||
|
||||
This file is distributed under the terms of the same license,
|
||||
as the Kivy framework.
|
||||
|
||||
`Material Design spec, Dialogs <https://material.io/design/components/dialogs.html>`_
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivy.lang import Builder
|
||||
from kivy.factory import Factory
|
||||
from kivy.utils import get_hex_from_color
|
||||
|
||||
from kivymd.uix.dialog import MDInputDialog, MDDialog
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
|
||||
Builder.load_string('''
|
||||
<ExampleDialogs@BoxLayout>
|
||||
orientation: 'vertical'
|
||||
spacing: dp(5)
|
||||
|
||||
MDToolbar:
|
||||
id: toolbar
|
||||
title: app.title
|
||||
left_action_items: [['menu', lambda x: None]]
|
||||
elevation: 10
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
|
||||
FloatLayout:
|
||||
MDRectangleFlatButton:
|
||||
text: "Open input dialog"
|
||||
pos_hint: {'center_x': .5, 'center_y': .7}
|
||||
opposite_colors: True
|
||||
on_release: app.show_example_input_dialog()
|
||||
|
||||
MDRectangleFlatButton:
|
||||
text: "Open Ok Cancel dialog"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
opposite_colors: True
|
||||
on_release: app.show_example_okcancel_dialog()
|
||||
''')
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
title = "Dialogs"
|
||||
|
||||
def build(self):
|
||||
return Factory.ExampleDialogs()
|
||||
|
||||
def callback_for_menu_items(self, *args):
|
||||
from kivymd.toast.kivytoast import toast
|
||||
toast(args[0])
|
||||
|
||||
def show_example_input_dialog(self):
|
||||
dialog = MDInputDialog(
|
||||
title='Title', hint_text='Hint text', size_hint=(.8, .4),
|
||||
text_button_ok='Yes',
|
||||
events_callback=self.callback_for_menu_items)
|
||||
dialog.open()
|
||||
|
||||
def show_example_okcancel_dialog(self):
|
||||
dialog = MDDialog(
|
||||
title='Title', size_hint=(.8, .3), text_button_ok='Yes',
|
||||
text="Your [color=%s][b]text[/b][/color] dialog" % get_hex_from_color(
|
||||
self.theme_cls.primary_color),
|
||||
text_button_cancel='Cancel',
|
||||
events_callback=self.callback_for_menu_items)
|
||||
dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
"""
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty, ObjectProperty, BooleanProperty
|
||||
from kivy.metrics import dp
|
||||
from kivy.uix.anchorlayout import AnchorLayout
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.modalview import ModalView
|
||||
|
||||
from kivymd.uix.card import MDCard
|
||||
from kivymd.uix.button import MDFlatButton, MDRaisedButton, MDTextButton
|
||||
from kivymd.uix.textfield import MDTextField, MDTextFieldRect
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd import images_path
|
||||
from kivymd.material_resources import DEVICE_IOS
|
||||
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import images_path kivymd.images_path
|
||||
|
||||
|
||||
<ContentInputDialog>
|
||||
orientation: 'vertical'
|
||||
padding: dp(15)
|
||||
spacing: dp(10)
|
||||
|
||||
MDLabel:
|
||||
font_style: 'H6'
|
||||
text: root.title
|
||||
halign: 'left' if not root.device_ios else 'center'
|
||||
|
||||
BoxLayout:
|
||||
id: box_input
|
||||
size_hint: 1, None
|
||||
|
||||
Widget:
|
||||
Widget:
|
||||
|
||||
MDSeparator:
|
||||
id: sep
|
||||
|
||||
BoxLayout:
|
||||
id: box_buttons
|
||||
size_hint_y: None
|
||||
height: dp(20)
|
||||
padding: dp(20), 0, dp(20), 0
|
||||
|
||||
#:import webbrowser webbrowser
|
||||
#:import parse urllib.parse
|
||||
<ThinLabel@MDLabel>:
|
||||
size_hint: 1, None
|
||||
valign: 'middle'
|
||||
height: self.texture_size[1]
|
||||
|
||||
<ThinLabelButton@ThinLabel+MDTextButton>:
|
||||
size_hint_y: None
|
||||
valign: 'middle'
|
||||
height: self.texture_size[1]
|
||||
|
||||
<ThinBox@BoxLayout>:
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
padding: dp(0), dp(0), dp(10), dp(0)
|
||||
|
||||
|
||||
<ListMDDialog>
|
||||
title: ""
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
padding: dp(15)
|
||||
spacing: dp(10)
|
||||
|
||||
MDLabel:
|
||||
id: title
|
||||
text: root.title
|
||||
font_style: 'H6'
|
||||
halign: 'left' if not root.device_ios else 'center'
|
||||
valign: 'top'
|
||||
size_hint_y: None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
|
||||
ScrollView:
|
||||
id: scroll
|
||||
size_hint_y: None
|
||||
height:
|
||||
root.height - (title.height + dp(48)\
|
||||
+ sep.height)
|
||||
|
||||
canvas:
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
#source: '{}dialog_in_fade.png'.format(images_path)
|
||||
source: '{}transparent.png'.format(images_path)
|
||||
|
||||
MDList:
|
||||
id: list_layout
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
spacing: dp(15)
|
||||
canvas.before:
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
Color:
|
||||
rgba: [1,0,0,.5]
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Address: "
|
||||
ThinLabelButton:
|
||||
text: root.address
|
||||
on_release:
|
||||
webbrowser.open("http://maps.apple.com/?address="+parse.quote(self.text))
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Website: "
|
||||
ThinLabelButton:
|
||||
text: root.Website
|
||||
on_release:
|
||||
webbrowser.open(self.text)
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Facebook: "
|
||||
ThinLabelButton:
|
||||
text: root.Facebook
|
||||
on_release:
|
||||
webbrowser.open(self.text)
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Twitter: "
|
||||
ThinLabelButton:
|
||||
text: root.Twitter
|
||||
on_release:
|
||||
webbrowser.open(self.text)
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Season1 Date: "
|
||||
ThinLabel:
|
||||
text: root.Season1_date
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Season1 Hours: "
|
||||
ThinLabel:
|
||||
text: root.Season1_hours
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Season2 Date: "
|
||||
ThinLabel:
|
||||
text: root.Season2_date
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Season2 Hours: "
|
||||
ThinLabel:
|
||||
text: root.Season2_hours
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Season3 Date: "
|
||||
ThinLabel:
|
||||
text: root.Season3_date
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Season3 Hours: "
|
||||
ThinLabel:
|
||||
text: root.Season3_hours
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Season4 Date: "
|
||||
ThinLabel:
|
||||
text: root.Season4_date
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Season4 Hours: "
|
||||
ThinLabel:
|
||||
text: root.Season4_hours
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Credit: "
|
||||
ThinLabel:
|
||||
text: root.Credit
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "WIC: "
|
||||
ThinLabel:
|
||||
text: root.WIC
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "WICcash: "
|
||||
ThinLabel:
|
||||
text: root.WICcash
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "SFMNP: "
|
||||
ThinLabel:
|
||||
text: root.SFMNP
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "SNAP: "
|
||||
ThinLabel:
|
||||
text: root.SNAP
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Organic: "
|
||||
ThinLabel:
|
||||
text: root.Organic
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Baked Goods: "
|
||||
ThinLabel:
|
||||
text: root.Bakedgoods
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Cheese: "
|
||||
ThinLabel:
|
||||
text: root.Cheese
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Crafts: "
|
||||
ThinLabel:
|
||||
text: root.Crafts
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Flowers: "
|
||||
ThinLabel:
|
||||
text: root.Flowers
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Eggs: "
|
||||
ThinLabel:
|
||||
text: root.Eggs
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Seafood: "
|
||||
ThinLabel:
|
||||
text: root.Seafood
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Herbs: "
|
||||
ThinLabel:
|
||||
text: root.Herbs
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Vegetables: "
|
||||
ThinLabel:
|
||||
text: root.Vegetables
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Honey: "
|
||||
ThinLabel:
|
||||
text: root.Honey
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Jams: "
|
||||
ThinLabel:
|
||||
text: root.Jams
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Maple: "
|
||||
ThinLabel:
|
||||
text: root.Maple
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Meat: "
|
||||
ThinLabel:
|
||||
text: root.Meat
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Nursery: "
|
||||
ThinLabel:
|
||||
text: root.Nursery
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Nuts: "
|
||||
ThinLabel:
|
||||
text: root.Nuts
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Plants: "
|
||||
ThinLabel:
|
||||
text: root.Plants
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Poultry: "
|
||||
ThinLabel:
|
||||
text: root.Poultry
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Prepared: "
|
||||
ThinLabel:
|
||||
text: root.Prepared
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Soap: "
|
||||
ThinLabel:
|
||||
text: root.Soap
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Trees: "
|
||||
ThinLabel:
|
||||
text: root.Trees
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Wine: "
|
||||
ThinLabel:
|
||||
text: root.Wine
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Coffee: "
|
||||
ThinLabel:
|
||||
text: root.Coffee
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Beans: "
|
||||
ThinLabel:
|
||||
text: root.Beans
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Fruits: "
|
||||
ThinLabel:
|
||||
text: root.Fruits
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Grains: "
|
||||
ThinLabel:
|
||||
text: root.Grains
|
||||
ThinBox:
|
||||
spacing: dp(10)
|
||||
ThinLabel:
|
||||
id: juices
|
||||
text: "Juices: "
|
||||
ThinLabel:
|
||||
text: root.Juices
|
||||
ThinBox:
|
||||
spacing: dp(10)
|
||||
ThinLabel:
|
||||
text: "Mushrooms: "
|
||||
ThinLabel:
|
||||
text: root.Mushrooms
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Pet Food: "
|
||||
ThinLabel:
|
||||
text: root.PetFood
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Tofu: "
|
||||
ThinLabel:
|
||||
text: root.Tofu
|
||||
ThinBox:
|
||||
ThinLabel:
|
||||
text: "Wild Harvested: "
|
||||
ThinLabel:
|
||||
text: root.WildHarvested
|
||||
MDSeparator:
|
||||
id: sep
|
||||
|
||||
|
||||
<ContentMDDialog>
|
||||
orientation: 'vertical'
|
||||
padding: dp(15)
|
||||
spacing: dp(10)
|
||||
|
||||
text_button_ok: ''
|
||||
text_button_cancel: ''
|
||||
|
||||
MDLabel:
|
||||
id: title
|
||||
text: root.title
|
||||
font_style: 'H6'
|
||||
halign: 'left' if not root.device_ios else 'center'
|
||||
valign: 'top'
|
||||
size_hint_y: None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
|
||||
ScrollView:
|
||||
id: scroll
|
||||
size_hint_y: None
|
||||
height:
|
||||
root.height - (box_buttons.height + title.height + dp(48)\
|
||||
+ sep.height)
|
||||
|
||||
canvas:
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
#source: f'{images_path}dialog_in_fade.png'
|
||||
source: f'{images_path}transparent.png'
|
||||
|
||||
MDLabel:
|
||||
text: '\\n' + root.text + '\\n'
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
valign: 'top'
|
||||
halign: 'left' if not root.device_ios else 'center'
|
||||
markup: True
|
||||
|
||||
MDSeparator:
|
||||
id: sep
|
||||
|
||||
BoxLayout:
|
||||
id: box_buttons
|
||||
size_hint_y: None
|
||||
height: dp(20)
|
||||
padding: dp(20), 0, dp(20), 0
|
||||
"""
|
||||
)
|
||||
|
||||
if DEVICE_IOS:
|
||||
Heir = BoxLayout
|
||||
else:
|
||||
Heir = MDCard
|
||||
|
||||
|
||||
# FIXME: Not work themes for iOS.
|
||||
class BaseDialog(ThemableBehavior, ModalView):
|
||||
def set_content(self, instance_content_dialog):
|
||||
def _events_callback(result_press):
|
||||
self.dismiss()
|
||||
if result_press and self.events_callback:
|
||||
self.events_callback(result_press, self)
|
||||
|
||||
if self.device_ios: # create buttons for iOS
|
||||
self.background = self._background
|
||||
|
||||
if isinstance(instance_content_dialog, ContentInputDialog):
|
||||
self.text_field = MDTextFieldRect(
|
||||
pos_hint={"center_x": 0.5},
|
||||
size_hint=(1, None),
|
||||
multiline=False,
|
||||
height=dp(33),
|
||||
cursor_color=self.theme_cls.primary_color,
|
||||
hint_text=instance_content_dialog.hint_text,
|
||||
)
|
||||
instance_content_dialog.ids.box_input.height = dp(33)
|
||||
instance_content_dialog.ids.box_input.add_widget(
|
||||
self.text_field
|
||||
)
|
||||
|
||||
if self.text_button_cancel != "":
|
||||
anchor = "left"
|
||||
else:
|
||||
anchor = "center"
|
||||
box_button_ok = AnchorLayout(anchor_x=anchor)
|
||||
box_button_ok.add_widget(
|
||||
MDTextButton(
|
||||
text=self.text_button_ok,
|
||||
font_size="18sp",
|
||||
on_release=lambda x: _events_callback(self.text_button_ok),
|
||||
)
|
||||
)
|
||||
instance_content_dialog.ids.box_buttons.add_widget(box_button_ok)
|
||||
|
||||
if self.text_button_cancel != "":
|
||||
box_button_ok.anchor_x = "left"
|
||||
box_button_cancel = AnchorLayout(anchor_x="right")
|
||||
box_button_cancel.add_widget(
|
||||
MDTextButton(
|
||||
text=self.text_button_cancel,
|
||||
font_size="18sp",
|
||||
on_release=lambda x: _events_callback(
|
||||
self.text_button_cancel
|
||||
),
|
||||
)
|
||||
)
|
||||
instance_content_dialog.ids.box_buttons.add_widget(
|
||||
box_button_cancel
|
||||
)
|
||||
|
||||
else: # create buttons for Android
|
||||
if isinstance(instance_content_dialog, ContentInputDialog):
|
||||
self.text_field = MDTextField(
|
||||
size_hint=(1, None),
|
||||
height=dp(48),
|
||||
hint_text=instance_content_dialog.hint_text,
|
||||
)
|
||||
instance_content_dialog.ids.box_input.height = dp(48)
|
||||
instance_content_dialog.ids.box_input.add_widget(
|
||||
self.text_field
|
||||
)
|
||||
instance_content_dialog.ids.box_buttons.remove_widget(
|
||||
instance_content_dialog.ids.sep
|
||||
)
|
||||
|
||||
box_buttons = AnchorLayout(
|
||||
anchor_x="right", size_hint_y=None, height=dp(30)
|
||||
)
|
||||
box = BoxLayout(size_hint_x=None, spacing=dp(5))
|
||||
box.bind(minimum_width=box.setter("width"))
|
||||
button_ok = MDRaisedButton(
|
||||
text=self.text_button_ok,
|
||||
on_release=lambda x: _events_callback(self.text_button_ok),
|
||||
)
|
||||
box.add_widget(button_ok)
|
||||
|
||||
if self.text_button_cancel != "":
|
||||
button_cancel = MDFlatButton(
|
||||
text=self.text_button_cancel,
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
on_release=lambda x: _events_callback(
|
||||
self.text_button_cancel
|
||||
),
|
||||
)
|
||||
box.add_widget(button_cancel)
|
||||
|
||||
box_buttons.add_widget(box)
|
||||
instance_content_dialog.ids.box_buttons.add_widget(box_buttons)
|
||||
instance_content_dialog.ids.box_buttons.height = button_ok.height
|
||||
instance_content_dialog.remove_widget(
|
||||
instance_content_dialog.ids.sep
|
||||
)
|
||||
|
||||
class ListMDDialog(BaseDialog):
|
||||
name = StringProperty("Missing data")
|
||||
address = StringProperty("Missing data")
|
||||
Website = StringProperty("Missing data")
|
||||
Facebook = StringProperty("Missing data")
|
||||
Twitter = StringProperty("Missing data")
|
||||
Season1_date = StringProperty("Missing data")
|
||||
Season1_hours = StringProperty("Missing data")
|
||||
Season2_date = StringProperty("Missing data")
|
||||
Season2_hours = StringProperty("Missing data")
|
||||
Season3_date = StringProperty("Missing data")
|
||||
Season3_hours = StringProperty("Missing data")
|
||||
Season4_date = StringProperty("Missing data")
|
||||
Season4_hours = StringProperty("Missing data")
|
||||
Credit = StringProperty("Missing data")
|
||||
WIC = StringProperty("Missing data")
|
||||
WICcash = StringProperty("Missing data")
|
||||
SFMNP = StringProperty("Missing data")
|
||||
SNAP = StringProperty("Missing data")
|
||||
Organic = StringProperty("Missing data")
|
||||
Bakedgoods = StringProperty("Missing data")
|
||||
Cheese = StringProperty("Missing data")
|
||||
Crafts = StringProperty("Missing data")
|
||||
Flowers = StringProperty("Missing data")
|
||||
Eggs = StringProperty("Missing data")
|
||||
Seafood = StringProperty("Missing data")
|
||||
Herbs = StringProperty("Missing data")
|
||||
Vegetables = StringProperty("Missing data")
|
||||
Honey = StringProperty("Missing data")
|
||||
Jams = StringProperty("Missing data")
|
||||
Maple = StringProperty("Missing data")
|
||||
Meat = StringProperty("Missing data")
|
||||
Nursery = StringProperty("Missing data")
|
||||
Nuts = StringProperty("Missing data")
|
||||
Plants = StringProperty("Missing data")
|
||||
Poultry = StringProperty("Missing data")
|
||||
Prepared = StringProperty("Missing data")
|
||||
Soap = StringProperty("Missing data")
|
||||
Trees = StringProperty("Missing data")
|
||||
Wine = StringProperty("Missing data")
|
||||
Coffee = StringProperty("Missing data")
|
||||
Beans = StringProperty("Missing data")
|
||||
Fruits = StringProperty("Missing data")
|
||||
Grains = StringProperty("Missing data")
|
||||
Juices = StringProperty("Missing data")
|
||||
Mushrooms = StringProperty("Missing data")
|
||||
PetFood = StringProperty("Missing data")
|
||||
Tofu = StringProperty("Missing data")
|
||||
WildHarvested = StringProperty("Missing data")
|
||||
background = StringProperty('{}ios_bg_mod.png'.format(images_path))
|
||||
|
||||
|
||||
|
||||
class MDInputDialog(BaseDialog):
|
||||
title = StringProperty("Title")
|
||||
hint_text = StringProperty()
|
||||
text_button_ok = StringProperty("Ok")
|
||||
text_button_cancel = StringProperty()
|
||||
events_callback = ObjectProperty()
|
||||
_background = StringProperty(f"{images_path}ios_bg_mod.png")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.content_dialog = ContentInputDialog(
|
||||
title=self.title,
|
||||
hint_text=self.hint_text,
|
||||
text_button_ok=self.text_button_ok,
|
||||
text_button_cancel=self.text_button_cancel,
|
||||
device_ios=self.device_ios,
|
||||
)
|
||||
self.add_widget(self.content_dialog)
|
||||
self.set_content(self.content_dialog)
|
||||
Clock.schedule_once(self.set_field_focus, 0.5)
|
||||
|
||||
def set_field_focus(self, interval):
|
||||
self.text_field.focus = True
|
||||
|
||||
|
||||
class MDDialog(BaseDialog):
|
||||
title = StringProperty("Title")
|
||||
text = StringProperty("Text dialog")
|
||||
text_button_cancel = StringProperty()
|
||||
text_button_ok = StringProperty("Ok")
|
||||
events_callback = ObjectProperty()
|
||||
_background = StringProperty(f"{images_path}ios_bg_mod.png")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
content_dialog = ContentMDDialog(
|
||||
title=self.title,
|
||||
text=self.text,
|
||||
text_button_ok=self.text_button_ok,
|
||||
text_button_cancel=self.text_button_cancel,
|
||||
device_ios=self.device_ios,
|
||||
)
|
||||
self.add_widget(content_dialog)
|
||||
self.set_content(content_dialog)
|
||||
|
||||
|
||||
class ContentInputDialog(Heir):
|
||||
title = StringProperty()
|
||||
hint_text = StringProperty()
|
||||
text_button_ok = StringProperty()
|
||||
text_button_cancel = StringProperty()
|
||||
device_ios = BooleanProperty()
|
||||
|
||||
|
||||
class ContentMDDialog(Heir):
|
||||
title = StringProperty()
|
||||
text = StringProperty()
|
||||
text_button_cancel = StringProperty()
|
||||
text_button_ok = StringProperty()
|
||||
device_ios = BooleanProperty()
|
135
kivymd/uix/dropdownitem.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
"""
|
||||
Components/Dropdown Item
|
||||
========================
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dropdown-item.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
Screen
|
||||
|
||||
MDDropDownItem:
|
||||
id: drop_item
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
text: 'Item'
|
||||
on_release: self.set_item("New Item")
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.screen = Builder.load_string(KV)
|
||||
|
||||
def build(self):
|
||||
return self.screen
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Work with the class MDDropdownMenu see here <https://kivymd.readthedocs.io/en/latest/components/menu/index.html#center-position>`_
|
||||
"""
|
||||
|
||||
__all__ = ("MDDropDownItem",)
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import NumericProperty, StringProperty
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import RectangularRippleBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<_Triangle>:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.theme_cls.text_color
|
||||
Triangle:
|
||||
points:
|
||||
[ \
|
||||
self.right-14, self.y+7, \
|
||||
self.right-7, self.y+7, \
|
||||
self.right-7, self.y+14 \
|
||||
]
|
||||
|
||||
|
||||
<MDDropDownItem>
|
||||
orientation: "vertical"
|
||||
adaptive_size: True
|
||||
spacing: "5dp"
|
||||
padding: "5dp", "5dp", "5dp", 0
|
||||
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
spacing: "10dp"
|
||||
|
||||
Label:
|
||||
id: label_item
|
||||
size_hint: None, None
|
||||
size: self.texture_size
|
||||
color: root.theme_cls.text_color
|
||||
font_size: root.font_size
|
||||
|
||||
|
||||
_Triangle:
|
||||
size_hint: None, None
|
||||
size: "20dp", "20dp"
|
||||
|
||||
MDSeparator:
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class _Triangle(ThemableBehavior, Widget):
|
||||
pass
|
||||
|
||||
|
||||
class MDDropDownItem(
|
||||
ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, MDBoxLayout
|
||||
):
|
||||
text = StringProperty()
|
||||
"""
|
||||
Text item.
|
||||
|
||||
:attr:`text` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
current_item = StringProperty()
|
||||
"""
|
||||
Current name item.
|
||||
|
||||
:attr:`current_item` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
font_size = NumericProperty("16sp")
|
||||
"""
|
||||
Item font size.
|
||||
|
||||
:attr:`font_size` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `'16sp'`.
|
||||
"""
|
||||
|
||||
def on_text(self, instance, value):
|
||||
self.ids.label_item.text = value
|
||||
|
||||
def set_item(self, name_item):
|
||||
"""Sets new text for an item."""
|
||||
|
||||
self.ids.label_item.text = name_item
|
||||
self.current_item = name_item
|
395
kivymd/uix/expansionpanel.py
Executable file
|
@ -0,0 +1,395 @@
|
|||
"""
|
||||
Components/Expansion Panel
|
||||
==========================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Expansion panel <https://material.io/archive/guidelines/components/expansion-panels.html#>`_
|
||||
|
||||
.. rubric:: Expansion panels contain creation flows and allow lightweight editing of an element.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.add_widget(
|
||||
MDExpansionPanel(
|
||||
icon="logo.png", # panel icon
|
||||
content=Content(), # panel content
|
||||
panel_cls=MDExpansionPanelOneLine(text="Secondary text"), # panel class
|
||||
)
|
||||
)
|
||||
|
||||
To use :class:`~MDExpansionPanel` you must pass one of the following classes
|
||||
to the :attr:`~MDExpansionPanel.panel_cls` parameter:
|
||||
|
||||
- :class:`~MDExpansionPanelOneLine`
|
||||
- :class:`~MDExpansionPanelTwoLine`
|
||||
- :class:`~MDExpansionPanelThreeLine`
|
||||
|
||||
These classes are inherited from the following classes:
|
||||
|
||||
- :class:`~kivymd.uix.list.OneLineAvatarIconListItem`
|
||||
- :class:`~kivymd.uix.list.TwoLineAvatarIconListItem`
|
||||
- :class:`~kivymd.uix.list.ThreeLineAvatarIconListItem`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.root.ids.box.add_widget(
|
||||
MDExpansionPanel(
|
||||
icon="logo.png",
|
||||
content=Content(),
|
||||
panel_cls=MDExpansionPanelThreeLine(
|
||||
text="Text",
|
||||
secondary_text="Secondary text",
|
||||
tertiary_text="Tertiary text",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelThreeLine
|
||||
from kivymd import images_path
|
||||
|
||||
KV = '''
|
||||
<Content>
|
||||
adaptive_height: True
|
||||
|
||||
TwoLineIconListItem:
|
||||
text: "(050)-123-45-67"
|
||||
secondary_text: "Mobile"
|
||||
|
||||
IconLeftWidget:
|
||||
icon: 'phone'
|
||||
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDGridLayout:
|
||||
id: box
|
||||
cols: 1
|
||||
adaptive_height: True
|
||||
'''
|
||||
|
||||
|
||||
class Content(MDBoxLayout):
|
||||
'''Custom content.'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def on_start(self):
|
||||
for i in range(10):
|
||||
self.root.ids.box.add_widget(
|
||||
MDExpansionPanel(
|
||||
icon=f"{images_path}kivymd.png",
|
||||
content=Content(),
|
||||
panel_cls=MDExpansionPanelThreeLine(
|
||||
text="Text",
|
||||
secondary_text="Secondary text",
|
||||
tertiary_text="Tertiary text",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.gif
|
||||
:align: center
|
||||
|
||||
Two events are available for :class:`~MDExpansionPanel`
|
||||
------------------------------------------------------
|
||||
|
||||
- :attr:`~MDExpansionPanel.on_open`
|
||||
- :attr:`~MDExpansionPanel.on_close`
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDExpansionPanel:
|
||||
on_open: app.on_panel_open(args)
|
||||
on_close: app.on_panel_close(args)
|
||||
|
||||
The user function takes one argument - the object of the panel:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_panel_open(self, instance_panel):
|
||||
print(instance_panel)
|
||||
|
||||
.. seealso:: `See Expansion panel example <https://github.com/kivymd/KivyMD/wiki/Components-Expansion-Panel>`_
|
||||
|
||||
`Expansion panel and MDCard <https://github.com/kivymd/KivyMD/wiki/Components-Expansion-Panel-and-MDCard>`_
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"MDExpansionPanel",
|
||||
"MDExpansionPanelOneLine",
|
||||
"MDExpansionPanelTwoLine",
|
||||
"MDExpansionPanelThreeLine",
|
||||
)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import NumericProperty, ObjectProperty, StringProperty
|
||||
from kivy.uix.relativelayout import RelativeLayout
|
||||
from kivy.uix.widget import WidgetException
|
||||
|
||||
import kivymd.material_resources as m_res
|
||||
from kivymd.icon_definitions import md_icons
|
||||
from kivymd.uix.button import MDIconButton
|
||||
from kivymd.uix.list import (
|
||||
IconLeftWidget,
|
||||
ImageLeftWidget,
|
||||
IRightBodyTouch,
|
||||
OneLineAvatarIconListItem,
|
||||
ThreeLineAvatarIconListItem,
|
||||
TwoLineAvatarIconListItem,
|
||||
)
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<MDExpansionChevronRight>:
|
||||
icon: 'chevron-right'
|
||||
disabled: True
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: self._angle
|
||||
axis: (0, 0, 1)
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
|
||||
|
||||
<MDExpansionPanel>
|
||||
size_hint_y: None
|
||||
#height: dp(68)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MDExpansionChevronRight(IRightBodyTouch, MDIconButton):
|
||||
"""Chevron icon on the right panel."""
|
||||
|
||||
_angle = NumericProperty(0)
|
||||
|
||||
|
||||
class MDExpansionPanelOneLine(OneLineAvatarIconListItem):
|
||||
"""Single line panel."""
|
||||
|
||||
|
||||
class MDExpansionPanelTwoLine(TwoLineAvatarIconListItem):
|
||||
"""Two-line panel."""
|
||||
|
||||
|
||||
class MDExpansionPanelThreeLine(ThreeLineAvatarIconListItem):
|
||||
"""Three-line panel."""
|
||||
|
||||
|
||||
class MDExpansionPanel(RelativeLayout):
|
||||
"""
|
||||
:Events:
|
||||
:attr:`on_open`
|
||||
Called when a panel is opened.
|
||||
:attr:`on_close`
|
||||
Called when a panel is closed.
|
||||
"""
|
||||
|
||||
content = ObjectProperty()
|
||||
"""Content of panel. Must be `Kivy` widget.
|
||||
|
||||
:attr:`content` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
icon = StringProperty()
|
||||
"""Icon of panel.
|
||||
|
||||
Icon Should be either be a path to an image or
|
||||
a logo name in :class:`~kivymd.icon_definitions.md_icons`
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
opening_transition = StringProperty("out_cubic")
|
||||
"""
|
||||
The name of the animation transition type to use when animating to
|
||||
the :attr:`state` `'open'`.
|
||||
|
||||
:attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_cubic'`.
|
||||
"""
|
||||
|
||||
opening_time = NumericProperty(0.2)
|
||||
"""
|
||||
The time taken for the panel to slide to the :attr:`state` `'open'`.
|
||||
|
||||
:attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
closing_transition = StringProperty("out_sine")
|
||||
"""The name of the animation transition type to use when animating to
|
||||
the :attr:`state` 'close'.
|
||||
|
||||
:attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_sine'`.
|
||||
"""
|
||||
|
||||
closing_time = NumericProperty(0.2)
|
||||
"""
|
||||
The time taken for the panel to slide to the :attr:`state` `'close'`.
|
||||
|
||||
:attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
panel_cls = ObjectProperty()
|
||||
"""
|
||||
Panel object. The object must be one of the classes
|
||||
:class:`~MDExpansionPanelOneLine`, :class:`~MDExpansionPanelTwoLine` or
|
||||
:class:`~MDExpansionPanelThreeLine`.
|
||||
|
||||
:attr:`panel_cls` is a :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.register_event_type("on_open")
|
||||
self.register_event_type("on_close")
|
||||
|
||||
if self.panel_cls and isinstance(
|
||||
self.panel_cls,
|
||||
(
|
||||
MDExpansionPanelOneLine,
|
||||
MDExpansionPanelTwoLine,
|
||||
MDExpansionPanelThreeLine,
|
||||
),
|
||||
):
|
||||
self.panel_cls.pos_hint = {"top": 1}
|
||||
self.panel_cls._no_ripple_effect = True
|
||||
self.panel_cls.bind(
|
||||
on_release=lambda x: self.check_open_panel(self.panel_cls)
|
||||
)
|
||||
self.chevron = MDExpansionChevronRight()
|
||||
self.panel_cls.add_widget(self.chevron)
|
||||
if self.icon:
|
||||
if self.icon in md_icons.keys():
|
||||
self.panel_cls.add_widget(
|
||||
IconLeftWidget(
|
||||
icon=self.icon, pos_hint={"center_y": 0.5}
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.panel_cls.add_widget(
|
||||
ImageLeftWidget(
|
||||
source=self.icon, pos_hint={"center_y": 0.5}
|
||||
)
|
||||
)
|
||||
else:
|
||||
# if no icon
|
||||
self.panel_cls._txt_left_pad = m_res.HORIZ_MARGINS
|
||||
self.add_widget(self.panel_cls)
|
||||
else:
|
||||
raise ValueError(
|
||||
"KivyMD: `panel_cls` object must be must be one of the "
|
||||
"objects from the list\n"
|
||||
"[MDExpansionPanelOneLine, MDExpansionPanelTwoLine, "
|
||||
"MDExpansionPanelThreeLine]"
|
||||
)
|
||||
|
||||
def on_open(self, *args):
|
||||
"""Called when a panel is opened."""
|
||||
|
||||
def on_close(self, *args):
|
||||
"""Called when a panel is closed."""
|
||||
|
||||
def check_open_panel(self, instance):
|
||||
"""
|
||||
Called when you click on the panel. Called methods to open or close
|
||||
a panel.
|
||||
"""
|
||||
|
||||
press_current_panel = False
|
||||
for panel in self.parent.children:
|
||||
if isinstance(panel, MDExpansionPanel):
|
||||
if len(panel.children) == 2:
|
||||
if instance is panel.children[1]:
|
||||
press_current_panel = True
|
||||
panel.remove_widget(panel.children[0])
|
||||
chevron = panel.children[0].children[0].children[0]
|
||||
self.set_chevron_up(chevron)
|
||||
self.close_panel(panel)
|
||||
self.dispatch("on_close")
|
||||
break
|
||||
if not press_current_panel:
|
||||
self.set_chevron_down()
|
||||
|
||||
def set_chevron_down(self):
|
||||
"""Sets the chevron down."""
|
||||
|
||||
Animation(_angle=-90, d=self.opening_time).start(self.chevron)
|
||||
self.open_panel()
|
||||
self.dispatch("on_open")
|
||||
|
||||
def set_chevron_up(self, instance_chevron):
|
||||
"""Sets the chevron up."""
|
||||
|
||||
Animation(_angle=0, d=self.closing_time).start(instance_chevron)
|
||||
|
||||
def close_panel(self, instance_panel):
|
||||
"""Method closes the panel."""
|
||||
|
||||
Animation(
|
||||
height=self.panel_cls.height,
|
||||
d=self.closing_time,
|
||||
t=self.closing_transition,
|
||||
).start(instance_panel)
|
||||
|
||||
def open_panel(self, *args):
|
||||
"""Method opens a panel."""
|
||||
|
||||
anim = Animation(
|
||||
height=self.content.height + self.height,
|
||||
d=self.opening_time,
|
||||
t=self.opening_transition,
|
||||
)
|
||||
anim.bind(on_complete=self._add_content)
|
||||
anim.start(self)
|
||||
|
||||
def add_widget(self, widget, index=0, canvas=None):
|
||||
if isinstance(
|
||||
widget,
|
||||
(
|
||||
MDExpansionPanelOneLine,
|
||||
MDExpansionPanelTwoLine,
|
||||
MDExpansionPanelThreeLine,
|
||||
),
|
||||
):
|
||||
self.height = widget.height
|
||||
return super().add_widget(widget)
|
||||
|
||||
def _add_content(self, *args):
|
||||
if self.content:
|
||||
try:
|
||||
self.add_widget(self.content)
|
||||
except WidgetException:
|
||||
pass
|
658
kivymd/uix/filemanager.py
Executable file
|
@ -0,0 +1,658 @@
|
|||
"""
|
||||
Components/File Manager
|
||||
=======================
|
||||
|
||||
A simple manager for selecting directories and files.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
path = '/' # path to the directory that will be opened in the file manager
|
||||
file_manager = MDFileManager(
|
||||
exit_manager=self.exit_manager, # function called when the user reaches directory tree root
|
||||
select_path=self.select_path, # function called when selecting a file/directory
|
||||
)
|
||||
file_manager.show(path)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager.png
|
||||
:align: center
|
||||
|
||||
Or with ``preview`` mode:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
file_manager = MDFileManager(
|
||||
exit_manager=self.exit_manager,
|
||||
select_path=self.select_path,
|
||||
preview=True,
|
||||
)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-previous.png
|
||||
:align: center
|
||||
|
||||
.. warning:: The `preview` mode is intended only for viewing images and will
|
||||
not display other types of files.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.filemanager import MDFileManager
|
||||
from kivymd.toast import toast
|
||||
|
||||
|
||||
KV = '''
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
|
||||
MDToolbar:
|
||||
title: "MDFileManager"
|
||||
left_action_items: [['menu', lambda x: None]]
|
||||
elevation: 10
|
||||
|
||||
FloatLayout:
|
||||
|
||||
MDRoundFlatIconButton:
|
||||
text: "Open manager"
|
||||
icon: "folder"
|
||||
pos_hint: {'center_x': .5, 'center_y': .6}
|
||||
on_release: app.file_manager_open()
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Window.bind(on_keyboard=self.events)
|
||||
self.manager_open = False
|
||||
self.file_manager = MDFileManager(
|
||||
exit_manager=self.exit_manager,
|
||||
select_path=self.select_path,
|
||||
preview=True,
|
||||
)
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def file_manager_open(self):
|
||||
self.file_manager.show('/') # output manager to the screen
|
||||
self.manager_open = True
|
||||
|
||||
def select_path(self, path):
|
||||
'''It will be called when you click on the file name
|
||||
or the catalog selection button.
|
||||
|
||||
:type path: str;
|
||||
:param path: path to the selected directory or file;
|
||||
'''
|
||||
|
||||
self.exit_manager()
|
||||
toast(path)
|
||||
|
||||
def exit_manager(self, *args):
|
||||
'''Called when the user reaches the root of the directory tree.'''
|
||||
|
||||
self.manager_open = False
|
||||
self.file_manager.close()
|
||||
|
||||
def events(self, instance, keyboard, keycode, text, modifiers):
|
||||
'''Called when buttons are pressed on the mobile device.'''
|
||||
|
||||
if keyboard in (1001, 27):
|
||||
if self.manager_open:
|
||||
self.file_manager.back()
|
||||
return True
|
||||
|
||||
|
||||
Example().run()
|
||||
"""
|
||||
|
||||
__all__ = ("MDFileManager",)
|
||||
|
||||
import locale
|
||||
import os
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.anchorlayout import AnchorLayout
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.modalview import ModalView
|
||||
|
||||
from kivymd import images_path
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
from kivymd.uix.list import BaseListItem, ContainerSupport
|
||||
from kivymd.utils.fitimage import FitImage
|
||||
|
||||
ACTIVITY_MANAGER = """
|
||||
#:import os os
|
||||
|
||||
|
||||
<BodyManager@BoxLayout>
|
||||
icon: "folder"
|
||||
path: ""
|
||||
background_normal: ""
|
||||
background_down: ""
|
||||
dir_or_file_name: ""
|
||||
_selected: False
|
||||
events_callback: lambda x: None
|
||||
orientation: "vertical"
|
||||
|
||||
ModifiedOneLineIconListItem:
|
||||
text: root.dir_or_file_name
|
||||
bg_color: self.theme_cls.bg_darkest if root._selected else self.theme_cls.bg_normal
|
||||
on_release: root.events_callback(root.path, root)
|
||||
|
||||
IconLeftWidget:
|
||||
icon: root.icon
|
||||
theme_text_color: "Custom"
|
||||
text_color: self.theme_cls.primary_color
|
||||
|
||||
MDSeparator:
|
||||
|
||||
|
||||
<LabelContent@MDLabel>
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
shorten: True
|
||||
shorten_from: "center"
|
||||
halign: "center"
|
||||
text_size: self.width, None
|
||||
|
||||
|
||||
<BodyManagerWithPreview>
|
||||
name: ""
|
||||
path: ""
|
||||
realpath: ""
|
||||
type: "folder"
|
||||
events_callback: lambda x: None
|
||||
_selected: False
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
hright: root.height
|
||||
padding: dp(20)
|
||||
|
||||
IconButton:
|
||||
mipmap: True
|
||||
source: root.path
|
||||
bg_color: app.theme_cls.bg_darkest if root._selected else app.theme_cls.bg_normal
|
||||
on_release:
|
||||
root.events_callback(\
|
||||
os.path.join(root.path if root.type != "folder" else root.realpath, \
|
||||
root.name), root)
|
||||
|
||||
LabelContent:
|
||||
text: root.name
|
||||
|
||||
|
||||
<FloatButton>
|
||||
anchor_x: "right"
|
||||
anchor_y: "bottom"
|
||||
size_hint_y: None
|
||||
height: dp(56)
|
||||
padding: dp(10)
|
||||
|
||||
MDFloatingActionButton:
|
||||
size_hint: None, None
|
||||
size:dp(56), dp(56)
|
||||
icon: root.icon
|
||||
opposite_colors: True
|
||||
elevation: 8
|
||||
on_release: root.callback()
|
||||
md_bg_color: root.md_bg_color
|
||||
|
||||
|
||||
<MDFileManager>
|
||||
md_bg_color: root.theme_cls.bg_normal
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
spacing: dp(5)
|
||||
|
||||
MDToolbar:
|
||||
id: toolbar
|
||||
title: root.current_path
|
||||
right_action_items: [["close-box", lambda x: root.exit_manager(1)]]
|
||||
left_action_items: [["chevron-left", lambda x: root.back()]]
|
||||
elevation: 10
|
||||
|
||||
RecycleView:
|
||||
id: rv
|
||||
key_viewclass: "viewclass"
|
||||
key_size: "height"
|
||||
bar_width: dp(4)
|
||||
bar_color: root.theme_cls.primary_color
|
||||
#on_scroll_stop: root._update_list_images()
|
||||
|
||||
RecycleGridLayout:
|
||||
padding: dp(10)
|
||||
cols: 3 if root.preview else 1
|
||||
default_size: None, dp(48)
|
||||
default_size_hint: 1, None
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
orientation: "vertical"
|
||||
|
||||
|
||||
<ModifiedOneLineIconListItem>
|
||||
|
||||
BoxLayout:
|
||||
id: _left_container
|
||||
size_hint: None, None
|
||||
x: root.x + dp(16)
|
||||
y: root.y + root.height / 2 - self.height / 2
|
||||
size: dp(48), dp(48)
|
||||
"""
|
||||
|
||||
|
||||
class BodyManagerWithPreview(MDBoxLayout):
|
||||
"""Base class for folder icons and thumbnails images in ``preview`` mode."""
|
||||
|
||||
|
||||
class IconButton(CircularRippleBehavior, ButtonBehavior, FitImage):
|
||||
"""Folder icons/thumbnails images in ``preview`` mode."""
|
||||
|
||||
|
||||
class FloatButton(AnchorLayout):
|
||||
callback = ObjectProperty()
|
||||
md_bg_color = ListProperty([1, 1, 1, 1])
|
||||
icon = StringProperty()
|
||||
|
||||
|
||||
class ModifiedOneLineIconListItem(ContainerSupport, BaseListItem):
|
||||
_txt_left_pad = NumericProperty("72dp")
|
||||
_txt_top_pad = NumericProperty("16dp")
|
||||
_txt_bot_pad = NumericProperty("15dp")
|
||||
_num_lines = 1
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.height = dp(48)
|
||||
|
||||
|
||||
class MDFileManager(ThemableBehavior, MDFloatLayout):
|
||||
icon = StringProperty("check")
|
||||
"""
|
||||
The icon that will be used on the directory selection button.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `check`.
|
||||
"""
|
||||
|
||||
icon_folder = StringProperty(f"{images_path}folder.png")
|
||||
"""
|
||||
The icon that will be used for folder icons when using ``preview = True``.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `check`.
|
||||
"""
|
||||
|
||||
exit_manager = ObjectProperty(lambda x: None)
|
||||
"""
|
||||
Function called when the user reaches directory tree root.
|
||||
|
||||
:attr:`exit_manager` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `lambda x: None`.
|
||||
"""
|
||||
|
||||
select_path = ObjectProperty(lambda x: None)
|
||||
"""
|
||||
Function, called when selecting a file/directory.
|
||||
|
||||
:attr:`select_path` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `lambda x: None`.
|
||||
"""
|
||||
|
||||
ext = ListProperty()
|
||||
"""
|
||||
List of file extensions to be displayed in the manager.
|
||||
For example, `['.py', '.kv']` - will filter out all files,
|
||||
except python scripts and Kv Language.
|
||||
|
||||
:attr:`ext` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
search = OptionProperty("all", options=["all", "dirs", "files"])
|
||||
"""
|
||||
It can take the values 'all' 'dirs' 'files' - display only directories
|
||||
or only files or both them. By default, it displays folders, and files.
|
||||
Available options are: `'all'`, `'dirs'`, `'files'`.
|
||||
|
||||
:attr:`search` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `all`.
|
||||
"""
|
||||
|
||||
current_path = StringProperty(os.getcwd())
|
||||
"""
|
||||
Current directory.
|
||||
|
||||
:attr:`current_path` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `/`.
|
||||
"""
|
||||
|
||||
use_access = BooleanProperty(True)
|
||||
"""
|
||||
Show access to files and directories.
|
||||
|
||||
:attr:`use_access` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
preview = BooleanProperty(False)
|
||||
"""
|
||||
Shows only image previews.
|
||||
|
||||
:attr:`preview` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
show_hidden_files = BooleanProperty(False)
|
||||
"""
|
||||
Shows hidden files.
|
||||
|
||||
:attr:`show_hidden_files` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
sort_by = OptionProperty(
|
||||
"name", options=["nothing", "name", "date", "size", "type"]
|
||||
)
|
||||
"""
|
||||
It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files by option
|
||||
By default, sort by name.
|
||||
Available options are: `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`.
|
||||
|
||||
:attr:`sort_by` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `name`.
|
||||
"""
|
||||
|
||||
sort_by_desc = BooleanProperty(False)
|
||||
"""
|
||||
Sort by descending.
|
||||
|
||||
:attr:`sort_by_desc` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
multiselect = BooleanProperty(False)
|
||||
"""
|
||||
Determines whether the user is able to select multiple files or not.
|
||||
|
||||
:attr:`multiselect` is a :class:`~kivy.properties.BooleanProperty` and defaults to
|
||||
False.
|
||||
"""
|
||||
|
||||
selection = ListProperty([])
|
||||
"""
|
||||
Contains the list of files that are currently selected.
|
||||
|
||||
:attr:`selection` is a read-only :class:`~kivy.properties.ListProperty` and
|
||||
defaults to [].
|
||||
"""
|
||||
|
||||
_window_manager = None
|
||||
_window_manager_open = False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
toolbar_label = self.ids.toolbar.children[1].children[0]
|
||||
toolbar_label.font_style = "Subtitle1"
|
||||
|
||||
self.add_widget(
|
||||
FloatButton(
|
||||
callback=self.select_directory_on_press_button,
|
||||
md_bg_color=self.theme_cls.primary_color,
|
||||
icon=self.icon,
|
||||
)
|
||||
)
|
||||
|
||||
if self.preview:
|
||||
self.ext = [".png", ".jpg", ".jpeg"]
|
||||
|
||||
def __sort_files(self, files):
|
||||
def sort_by_name(files):
|
||||
files.sort(key=locale.strxfrm)
|
||||
files.sort(key=str.casefold)
|
||||
|
||||
return files
|
||||
|
||||
if self.sort_by == "name":
|
||||
sorted_files = sort_by_name(files)
|
||||
|
||||
elif self.sort_by == "date":
|
||||
_files = sort_by_name(files)
|
||||
_sorted_files = [os.path.join(self.current_path, f) for f in _files]
|
||||
_sorted_files.sort(key=os.path.getmtime, reverse=True)
|
||||
|
||||
sorted_files = [os.path.basename(f) for f in _sorted_files]
|
||||
|
||||
elif self.sort_by == "size":
|
||||
_files = sort_by_name(files)
|
||||
_sorted_files = [os.path.join(self.current_path, f) for f in _files]
|
||||
_sorted_files.sort(key=os.path.getsize, reverse=True)
|
||||
|
||||
sorted_files = [os.path.basename(f) for f in _sorted_files]
|
||||
|
||||
elif self.sort_by == "type":
|
||||
_files = sort_by_name(files)
|
||||
|
||||
sorted_files = sorted(
|
||||
_files,
|
||||
key=lambda f: (os.path.splitext(f)[1], os.path.splitext(f)[0]),
|
||||
)
|
||||
|
||||
else:
|
||||
sorted_files = files
|
||||
|
||||
if self.sort_by_desc:
|
||||
sorted_files.reverse()
|
||||
|
||||
return sorted_files
|
||||
|
||||
def show(self, path):
|
||||
"""Forms the body of a directory tree.
|
||||
|
||||
:param path:
|
||||
The path to the directory that will be opened in the file manager.
|
||||
"""
|
||||
|
||||
self.current_path = path
|
||||
self.selection = []
|
||||
dirs, files = self.get_content()
|
||||
manager_list = []
|
||||
|
||||
if dirs == [] and files == []: # selected directory
|
||||
pass
|
||||
elif not dirs and not files: # directory is unavailable
|
||||
return
|
||||
|
||||
if self.preview:
|
||||
for name_dir in self.__sort_files(dirs):
|
||||
manager_list.append(
|
||||
{
|
||||
"viewclass": "BodyManagerWithPreview",
|
||||
"path": self.icon_folder,
|
||||
"realpath": os.path.join(path),
|
||||
"type": "folder",
|
||||
"name": name_dir,
|
||||
"events_callback": self.select_dir_or_file,
|
||||
"height": dp(150),
|
||||
"_selected": False,
|
||||
}
|
||||
)
|
||||
for name_file in self.__sort_files(files):
|
||||
if (
|
||||
os.path.splitext(os.path.join(path, name_file))[1]
|
||||
in self.ext
|
||||
):
|
||||
manager_list.append(
|
||||
{
|
||||
"viewclass": "BodyManagerWithPreview",
|
||||
"path": os.path.join(path, name_file),
|
||||
"name": name_file,
|
||||
"type": "files",
|
||||
"events_callback": self.select_dir_or_file,
|
||||
"height": dp(150),
|
||||
"_selected": False,
|
||||
}
|
||||
)
|
||||
else:
|
||||
for name in self.__sort_files(dirs):
|
||||
_path = os.path.join(path, name)
|
||||
access_string = self.get_access_string(_path)
|
||||
if "r" not in access_string:
|
||||
icon = "folder-lock"
|
||||
else:
|
||||
icon = "folder"
|
||||
|
||||
manager_list.append(
|
||||
{
|
||||
"viewclass": "BodyManager",
|
||||
"path": _path,
|
||||
"icon": icon,
|
||||
"dir_or_file_name": name,
|
||||
"events_callback": self.select_dir_or_file,
|
||||
"_selected": False,
|
||||
}
|
||||
)
|
||||
for name in self.__sort_files(files):
|
||||
if self.ext and os.path.splitext(name)[1] not in self.ext:
|
||||
continue
|
||||
|
||||
manager_list.append(
|
||||
{
|
||||
"viewclass": "BodyManager",
|
||||
"path": name,
|
||||
"icon": "file-outline",
|
||||
"dir_or_file_name": os.path.split(name)[1],
|
||||
"events_callback": self.select_dir_or_file,
|
||||
"_selected": False,
|
||||
}
|
||||
)
|
||||
self.ids.rv.data = manager_list
|
||||
|
||||
if not self._window_manager:
|
||||
self._window_manager = ModalView(
|
||||
size_hint=(1, 1), auto_dismiss=False
|
||||
)
|
||||
self._window_manager.add_widget(self)
|
||||
if not self._window_manager_open:
|
||||
self._window_manager.open()
|
||||
self._window_manager_open = True
|
||||
|
||||
def get_access_string(self, path):
|
||||
access_string = ""
|
||||
if self.use_access:
|
||||
access_data = {"r": os.R_OK, "w": os.W_OK, "x": os.X_OK}
|
||||
for access in access_data.keys():
|
||||
access_string += (
|
||||
access if os.access(path, access_data[access]) else "-"
|
||||
)
|
||||
return access_string
|
||||
|
||||
def get_content(self):
|
||||
"""Returns a list of the type [[Folder List], [file list]]."""
|
||||
|
||||
try:
|
||||
files = []
|
||||
dirs = []
|
||||
|
||||
for content in os.listdir(self.current_path):
|
||||
if os.path.isdir(os.path.join(self.current_path, content)):
|
||||
if self.search == "all" or self.search == "dirs":
|
||||
if (not self.show_hidden_files) and (
|
||||
content.startswith(".")
|
||||
):
|
||||
continue
|
||||
else:
|
||||
dirs.append(content)
|
||||
|
||||
else:
|
||||
if self.search == "all" or self.search == "files":
|
||||
if len(self.ext) != 0:
|
||||
try:
|
||||
files.append(
|
||||
os.path.join(self.current_path, content)
|
||||
)
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if (
|
||||
not self.show_hidden_files
|
||||
and content.startswith(".")
|
||||
):
|
||||
continue
|
||||
else:
|
||||
files.append(content)
|
||||
|
||||
return dirs, files
|
||||
|
||||
except OSError:
|
||||
return None, None
|
||||
|
||||
def close(self):
|
||||
"""Closes the file manager window."""
|
||||
|
||||
self._window_manager.dismiss()
|
||||
self._window_manager_open = False
|
||||
|
||||
def select_dir_or_file(self, path, widget):
|
||||
"""Called by tap on the name of the directory or file."""
|
||||
|
||||
if os.path.isfile(os.path.join(self.current_path, path)):
|
||||
if self.multiselect:
|
||||
file_path = os.path.join(self.current_path, path)
|
||||
if file_path in self.selection:
|
||||
widget._selected = False
|
||||
self.selection.remove(file_path)
|
||||
else:
|
||||
widget._selected = True
|
||||
self.selection.append(file_path)
|
||||
else:
|
||||
self.select_path(os.path.join(self.current_path, path))
|
||||
|
||||
else:
|
||||
self.current_path = path
|
||||
self.show(path)
|
||||
|
||||
def back(self):
|
||||
"""Returning to the branch down in the directory tree."""
|
||||
|
||||
path, end = os.path.split(self.current_path)
|
||||
|
||||
if not end:
|
||||
self.close()
|
||||
self.exit_manager(1)
|
||||
|
||||
else:
|
||||
self.show(path)
|
||||
|
||||
def select_directory_on_press_button(self, *args):
|
||||
"""Called when a click on a floating button."""
|
||||
|
||||
if len(self.selection) > 0:
|
||||
self.select_path(self.selection)
|
||||
else:
|
||||
self.select_path(self.current_path)
|
||||
|
||||
|
||||
Builder.load_string(ACTIVITY_MANAGER)
|
42
kivymd/uix/floatlayout.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
"""
|
||||
Components/FloatLayout
|
||||
======================
|
||||
|
||||
:class:`~kivy.uix.floatlayout.FloatLayout` class equivalent. Simplifies working
|
||||
with some widget properties. For example:
|
||||
|
||||
FloatLayout
|
||||
-----------
|
||||
|
||||
.. code-block::
|
||||
|
||||
FloatLayout:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: app.theme_cls.primary_color
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
radius: [25, 0, 0, 0]
|
||||
|
||||
MDFloatLayout
|
||||
-------------
|
||||
|
||||
.. code-block::
|
||||
|
||||
MDFloatLayout:
|
||||
radius: [25, 0, 0, 0]
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
|
||||
.. Warning:: For a :class:`~kivy.uix.floatlayout.FloatLayout`, the
|
||||
``minimum_size`` attributes are always 0, so you cannot use
|
||||
``adaptive_size`` and related options.
|
||||
"""
|
||||
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
|
||||
from kivymd.uix import MDAdaptiveWidget
|
||||
|
||||
|
||||
class MDFloatLayout(FloatLayout, MDAdaptiveWidget):
|
||||
pass
|
92
kivymd/uix/gridlayout.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
Components/GridLayout
|
||||
====================
|
||||
|
||||
:class:`~kivy.uix.gridlayout.GridLayout` class equivalent. Simplifies working
|
||||
with some widget properties. For example:
|
||||
|
||||
GridLayout
|
||||
---------
|
||||
|
||||
.. code-block::
|
||||
|
||||
GridLayout:
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: app.theme_cls.primary_color
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
MDGridLayout
|
||||
-----------
|
||||
|
||||
.. code-block::
|
||||
|
||||
MDGridLayout:
|
||||
adaptive_height: True
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
|
||||
Available options are:
|
||||
---------------------
|
||||
|
||||
- adaptive_height_
|
||||
- adaptive_width_
|
||||
- adaptive_size_
|
||||
|
||||
.. adaptive_height:
|
||||
adaptive_height
|
||||
---------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_height: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
.. adaptive_width:
|
||||
adaptive_width
|
||||
--------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_width: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_x: None
|
||||
height: self.minimum_width
|
||||
|
||||
.. adaptive_size:
|
||||
adaptive_size
|
||||
-------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_size: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
"""
|
||||
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
|
||||
from kivymd.uix import MDAdaptiveWidget
|
||||
|
||||
|
||||
class MDGridLayout(GridLayout, MDAdaptiveWidget):
|
||||
pass
|
311
kivymd/uix/imagelist.py
Executable file
|
@ -0,0 +1,311 @@
|
|||
"""
|
||||
Components/Image List
|
||||
=====================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Image lists <https://material.io/components/image-lists>`_
|
||||
|
||||
.. rubric:: Image lists display a collection of images in an organized grid.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/image-list.png
|
||||
:align: center
|
||||
|
||||
`KivyMD` provides the following tile classes for use:
|
||||
|
||||
- SmartTileWithStar_
|
||||
- SmartTileWithLabel_
|
||||
|
||||
.. SmartTileWithStar:
|
||||
SmartTileWithStar
|
||||
-----------------
|
||||
|
||||
.. code-block::
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivy.lang import Builder
|
||||
|
||||
KV = '''
|
||||
<MyTile@SmartTileWithStar>
|
||||
size_hint_y: None
|
||||
height: "240dp"
|
||||
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDGridLayout:
|
||||
cols: 3
|
||||
adaptive_height: True
|
||||
padding: dp(4), dp(4)
|
||||
spacing: dp(4)
|
||||
|
||||
MyTile:
|
||||
stars: 5
|
||||
source: "cat-1.jpg"
|
||||
|
||||
MyTile:
|
||||
stars: 5
|
||||
source: "cat-2.jpg"
|
||||
|
||||
MyTile:
|
||||
stars: 5
|
||||
source: "cat-3.jpg"
|
||||
'''
|
||||
|
||||
|
||||
class MyApp(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
MyApp().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/SmartTileWithStar.gif
|
||||
:align: center
|
||||
|
||||
.. SmartTileWithLabel:
|
||||
SmartTileWithLabel
|
||||
------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivy.lang import Builder
|
||||
|
||||
KV = '''
|
||||
<MyTile@SmartTileWithLabel>
|
||||
size_hint_y: None
|
||||
height: "240dp"
|
||||
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDGridLayout:
|
||||
cols: 3
|
||||
adaptive_height: True
|
||||
padding: dp(4), dp(4)
|
||||
spacing: dp(4)
|
||||
|
||||
MyTile:
|
||||
source: "cat-1.jpg"
|
||||
text: "[size=26]Cat 1[/size]\\n[size=14]cat-1.jpg[/size]"
|
||||
|
||||
MyTile:
|
||||
source: "cat-2.jpg"
|
||||
text: "[size=26]Cat 2[/size]\\n[size=14]cat-2.jpg[/size]"
|
||||
tile_text_color: app.theme_cls.accent_color
|
||||
|
||||
MyTile:
|
||||
source: "cat-3.jpg"
|
||||
text: "[size=26][color=#ffffff]Cat 3[/color][/size]\\n[size=14]cat-3.jpg[/size]"
|
||||
tile_text_color: app.theme_cls.accent_color
|
||||
'''
|
||||
|
||||
|
||||
class MyApp(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
MyApp().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/SmartTileWithLabel.png
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("SmartTile", "SmartTileWithLabel", "SmartTileWithStar")
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import RectangularRippleBehavior
|
||||
from kivymd.uix.button import MDIconButton
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<SmartTile>
|
||||
_img_widget: img
|
||||
_img_overlay: img_overlay
|
||||
_box_overlay: box
|
||||
|
||||
FitImage:
|
||||
id: img
|
||||
source: root.source
|
||||
x: root.x
|
||||
y: root.y if root.overlap or root.box_position == 'header' else box.top
|
||||
|
||||
BoxLayout:
|
||||
id: img_overlay
|
||||
size_hint: img.size_hint
|
||||
size: img.size
|
||||
pos: img.pos
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
md_bg_color: root.box_color
|
||||
size_hint_y: None
|
||||
height: "68dp" if root.lines == 2 else "48dp"
|
||||
x: root.x
|
||||
y: root.y if root.box_position == 'footer' else root.y + root.height - self.height
|
||||
|
||||
|
||||
<SmartTileWithLabel>
|
||||
_img_widget: img
|
||||
_img_overlay: img_overlay
|
||||
_box_overlay: box
|
||||
_box_label: boxlabel
|
||||
|
||||
FitImage:
|
||||
id: img
|
||||
source: root.source
|
||||
x: root.x
|
||||
y: root.y if root.overlap or root.box_position == 'header' else box.top
|
||||
|
||||
BoxLayout:
|
||||
id: img_overlay
|
||||
size_hint: img.size_hint
|
||||
size: img.size
|
||||
pos: img.pos
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
padding: "5dp", 0, 0, 0
|
||||
md_bg_color: root.box_color
|
||||
adaptive_height: True
|
||||
x: root.x
|
||||
y: root.y if root.box_position == 'footer' else root.y + root.height - self.height
|
||||
|
||||
MDLabel:
|
||||
id: boxlabel
|
||||
font_style: root.font_style
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
text: root.text
|
||||
color: root.tile_text_color
|
||||
markup: True
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class SmartTile(
|
||||
ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, MDFloatLayout
|
||||
):
|
||||
"""
|
||||
A tile for more complex needs.
|
||||
|
||||
Includes an image, a container to place overlays and a box that can act
|
||||
as a header or a footer, as described in the Material Design specs.
|
||||
"""
|
||||
|
||||
box_color = ListProperty((0, 0, 0, 0.5))
|
||||
"""
|
||||
Sets the color and opacity for the information box.
|
||||
|
||||
:attr:`box_color` is a :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `(0, 0, 0, 0.5)`.
|
||||
"""
|
||||
|
||||
box_position = OptionProperty("footer", options=["footer", "header"])
|
||||
"""
|
||||
Determines wether the information box acts as a header or footer to the
|
||||
image. Available are options: `'footer'`, `'header'`.
|
||||
|
||||
:attr:`box_position` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'footer'`.
|
||||
"""
|
||||
|
||||
lines = OptionProperty(1, options=[1, 2])
|
||||
"""
|
||||
Number of lines in the `header/footer`. As per `Material Design specs`,
|
||||
only 1 and 2 are valid values. Available are options: ``1``, ``2``.
|
||||
|
||||
:attr:`lines` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
overlap = BooleanProperty(True)
|
||||
"""
|
||||
Determines if the `header/footer` overlaps on top of the image or not.
|
||||
|
||||
:attr:`overlap` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
source = StringProperty()
|
||||
"""
|
||||
Path to tile image. See :attr:`~kivy.uix.image.Image.source`.
|
||||
|
||||
:attr:`source` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
_img_widget = ObjectProperty()
|
||||
_img_overlay = ObjectProperty()
|
||||
_box_overlay = ObjectProperty()
|
||||
_box_label = ObjectProperty()
|
||||
|
||||
def reload(self):
|
||||
self._img_widget.reload()
|
||||
|
||||
|
||||
class SmartTileWithLabel(SmartTile):
|
||||
font_style = StringProperty("Caption")
|
||||
"""
|
||||
Tile font style.
|
||||
|
||||
:attr:`font_style` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'Caption'`.
|
||||
"""
|
||||
|
||||
tile_text_color = ListProperty((1, 1, 1, 1))
|
||||
"""
|
||||
Tile text color in ``rgba`` format.
|
||||
|
||||
:attr:`tile_text_color` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `(1, 1, 1, 1)`.
|
||||
"""
|
||||
|
||||
text = StringProperty()
|
||||
"""
|
||||
Determines the text for the box `footer/header`.
|
||||
|
||||
:attr:`text` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
_box_label = ObjectProperty()
|
||||
|
||||
|
||||
class SmartTileWithStar(SmartTileWithLabel):
|
||||
stars = NumericProperty(1)
|
||||
"""
|
||||
Tile stars.
|
||||
|
||||
:attr:`stars` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
def on_stars(self, *args):
|
||||
for star in range(self.stars):
|
||||
self.ids.box.add_widget(
|
||||
_Star(
|
||||
icon="star-outline",
|
||||
theme_text_color="Custom",
|
||||
text_color=(1, 1, 1, 1),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class _Star(MDIconButton):
|
||||
def on_touch_down(self, touch):
|
||||
return True
|
400
kivymd/uix/label.py
Executable file
|
@ -0,0 +1,400 @@
|
|||
"""
|
||||
Components/Label
|
||||
================
|
||||
|
||||
.. rubric:: The :class:`MDLabel` widget is for rendering text.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label.png
|
||||
:align: center
|
||||
|
||||
- MDLabel_
|
||||
- MDIcon_
|
||||
|
||||
.. MDLabel:
|
||||
MDLabel
|
||||
-------
|
||||
|
||||
Class :class:`MDLabel` inherited from the :class:`~kivy.uix.label.Label` class
|
||||
but for :class:`MDLabel` the ``text_size`` parameter is ``(self.width, None)``
|
||||
and default is positioned on the left:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
MDToolbar:
|
||||
title: "MDLabel"
|
||||
|
||||
MDLabel:
|
||||
text: "MDLabel"
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-left.png
|
||||
:align: center
|
||||
|
||||
.. Note:: See :attr:`~kivy.uix.label.Label.halign`
|
||||
and :attr:`~kivy.uix.label.Label.valign` attributes
|
||||
of the :class:`~kivy.uix.label.Label` class
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDLabel:
|
||||
text: "MDLabel"
|
||||
halign: "center"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-center.png
|
||||
:align: center
|
||||
|
||||
:class:`~MDLabel` color:
|
||||
------------------------
|
||||
|
||||
:class:`~MDLabel` provides standard color themes for label color management:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.label import MDLabel
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
BoxLayout:
|
||||
id: box
|
||||
orientation: "vertical"
|
||||
|
||||
MDToolbar:
|
||||
title: "MDLabel"
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
screen = Builder.load_string(KV)
|
||||
# Names of standard color themes.
|
||||
for name_theme in [
|
||||
"Primary",
|
||||
"Secondary",
|
||||
"Hint",
|
||||
"Error",
|
||||
"ContrastParentBackground",
|
||||
]:
|
||||
screen.ids.box.add_widget(
|
||||
MDLabel(
|
||||
text=name_theme,
|
||||
halign="center",
|
||||
theme_text_color=name_theme,
|
||||
)
|
||||
)
|
||||
return screen
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-theme-text-color.png
|
||||
:align: center
|
||||
|
||||
To use a custom color for :class:`~MDLabel`, use a theme `'Custom'`.
|
||||
After that, you can specify the desired color in the ``rgba`` format
|
||||
in the ``text_color`` parameter:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDLabel:
|
||||
text: "Custom color"
|
||||
halign: "center"
|
||||
theme_text_color: "Custom"
|
||||
text_color: 0, 0, 1, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-custom-color.png
|
||||
:align: center
|
||||
|
||||
:class:`~MDLabel` provides standard font styles for labels. To do this,
|
||||
specify the name of the desired style in the :attr:`~MDLabel.font_style`
|
||||
parameter:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.label import MDLabel
|
||||
from kivymd.font_definitions import theme_font_styles
|
||||
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
MDToolbar:
|
||||
title: "MDLabel"
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDList:
|
||||
id: box
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
screen = Builder.load_string(KV)
|
||||
# Names of standard font styles.
|
||||
for name_style in theme_font_styles[:-1]:
|
||||
screen.ids.box.add_widget(
|
||||
MDLabel(
|
||||
text=f"{name_style} style",
|
||||
halign="center",
|
||||
font_style=name_style,
|
||||
)
|
||||
)
|
||||
return screen
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style.gif
|
||||
:align: center
|
||||
|
||||
.. MDIcon:
|
||||
MDIcon
|
||||
-------
|
||||
|
||||
You can use labels to display material design icons using the
|
||||
:class:`~MDIcon` class.
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design Icons <https://materialdesignicons.com/>`_
|
||||
|
||||
`Material Design Icon Names <https://github.com/kivymd/KivyMD/blob/master/kivymd/icon_definitions.py>`_
|
||||
|
||||
The :class:`~MDIcon` class is inherited from
|
||||
:class:`~MDLabel` and has the same parameters.
|
||||
|
||||
.. Warning:: For the :class:`~MDIcon` class, you cannot use ``text``
|
||||
and ``font_style`` options!
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDIcon:
|
||||
halign: "center"
|
||||
icon: "language-python"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon.png
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("MDLabel", "MDIcon")
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import sp
|
||||
from kivy.properties import (
|
||||
AliasProperty,
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.label import Label
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.theming_dynamic_text import get_contrast_text_color
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import md_icons kivymd.icon_definitions.md_icons
|
||||
|
||||
|
||||
<MDLabel>
|
||||
disabled_color: self.theme_cls.disabled_hint_text_color
|
||||
text_size: self.width, None
|
||||
|
||||
|
||||
<MDIcon>:
|
||||
font_style: "Icon"
|
||||
text: u"{}".format(md_icons[self.icon]) if self.icon in md_icons else ""
|
||||
source: None if self.icon in md_icons else self.icon
|
||||
canvas:
|
||||
Color:
|
||||
rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0)
|
||||
Rectangle:
|
||||
source: self.source if self.source else None
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MDLabel(ThemableBehavior, Label):
|
||||
font_style = StringProperty("Body1")
|
||||
"""
|
||||
Label font style.
|
||||
|
||||
Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`, `'H6'`,
|
||||
`'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`,
|
||||
`'Caption'`, `'Overline'`, `'Icon'`.
|
||||
|
||||
:attr:`font_style` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'Body1'`.
|
||||
"""
|
||||
|
||||
_capitalizing = BooleanProperty(False)
|
||||
|
||||
def _get_text(self):
|
||||
if self._capitalizing:
|
||||
return self._text.upper()
|
||||
return self._text
|
||||
|
||||
def _set_text(self, value):
|
||||
self._text = value
|
||||
|
||||
_text = StringProperty()
|
||||
|
||||
text = AliasProperty(_get_text, _set_text, bind=["_text", "_capitalizing"])
|
||||
"""Text of the label."""
|
||||
|
||||
theme_text_color = OptionProperty(
|
||||
"Primary",
|
||||
allownone=True,
|
||||
options=[
|
||||
"Primary",
|
||||
"Secondary",
|
||||
"Hint",
|
||||
"Error",
|
||||
"Custom",
|
||||
"ContrastParentBackground",
|
||||
],
|
||||
)
|
||||
"""
|
||||
Label color scheme name.
|
||||
|
||||
Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
|
||||
`'Custom'`, `'ContrastParentBackground'`.
|
||||
|
||||
:attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
text_color = ListProperty(None, allownone=True)
|
||||
"""Label text color in ``rgba`` format.
|
||||
|
||||
:attr:`text_color` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
parent_background = ListProperty(None, allownone=True)
|
||||
|
||||
_currently_bound_property = {}
|
||||
|
||||
can_capitalize = BooleanProperty(True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.bind(
|
||||
font_style=self.update_font_style,
|
||||
can_capitalize=self.update_font_style,
|
||||
)
|
||||
self.on_theme_text_color(None, self.theme_text_color)
|
||||
self.update_font_style()
|
||||
self.on_opposite_colors(None, self.opposite_colors)
|
||||
|
||||
Clock.schedule_once(self.check_font_styles)
|
||||
|
||||
def check_font_styles(self, *dt):
|
||||
if self.font_style not in list(self.theme_cls.font_styles.keys()):
|
||||
raise ValueError(
|
||||
f"MDLabel.font_style is set to an invalid option '{self.font_style}'."
|
||||
f"Must be one of: {list(self.theme_cls.font_styles)}"
|
||||
)
|
||||
else:
|
||||
return True
|
||||
|
||||
def update_font_style(self, *args):
|
||||
if self.check_font_styles() is True:
|
||||
font_info = self.theme_cls.font_styles[self.font_style]
|
||||
self.font_name = font_info[0]
|
||||
self.font_size = sp(font_info[1])
|
||||
if font_info[2] and self.can_capitalize:
|
||||
self._capitalizing = True
|
||||
else:
|
||||
self._capitalizing = False
|
||||
|
||||
# TODO: Add letter spacing change
|
||||
# self.letter_spacing = font_info[3]
|
||||
|
||||
def on_theme_text_color(self, instance, value):
|
||||
t = self.theme_cls
|
||||
op = self.opposite_colors
|
||||
setter = self.setter("color")
|
||||
t.unbind(**self._currently_bound_property)
|
||||
attr_name = {
|
||||
"Primary": "text_color" if not op else "opposite_text_color",
|
||||
"Secondary": "secondary_text_color"
|
||||
if not op
|
||||
else "opposite_secondary_text_color",
|
||||
"Hint": "disabled_hint_text_color"
|
||||
if not op
|
||||
else "opposite_disabled_hint_text_color",
|
||||
"Error": "error_color",
|
||||
}.get(value, None)
|
||||
if attr_name:
|
||||
c = {attr_name: setter}
|
||||
t.bind(**c)
|
||||
self._currently_bound_property = c
|
||||
self.color = getattr(t, attr_name)
|
||||
else:
|
||||
# 'Custom' and 'ContrastParentBackground' lead here, as well as the
|
||||
# generic None value it's not yet been set
|
||||
if value == "Custom" and self.text_color:
|
||||
self.color = self.text_color
|
||||
elif value == "ContrastParentBackground" and self.parent_background:
|
||||
self.color = get_contrast_text_color(self.parent_background)
|
||||
else:
|
||||
self.color = [0, 0, 0, 1]
|
||||
|
||||
def on_text_color(self, *args):
|
||||
if self.theme_text_color == "Custom":
|
||||
self.color = self.text_color
|
||||
|
||||
def on_opposite_colors(self, instance, value):
|
||||
self.on_theme_text_color(self, self.theme_text_color)
|
||||
|
||||
|
||||
class MDIcon(MDLabel):
|
||||
icon = StringProperty("android")
|
||||
"""
|
||||
Label icon name.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'android'`.
|
||||
"""
|
||||
|
||||
source = StringProperty(None, allownone=True)
|
||||
"""
|
||||
Path to icon.
|
||||
|
||||
:attr:`source` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
1026
kivymd/uix/list.py
Executable file
1198
kivymd/uix/menu.py
Executable file
754
kivymd/uix/navigationdrawer.py
Executable file
|
@ -0,0 +1,754 @@
|
|||
"""
|
||||
Components/Navigation Drawer
|
||||
============================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Navigation drawer <https://material.io/components/navigation-drawer>`_
|
||||
|
||||
.. rubric:: Navigation drawers provide access to destinations in your app.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.png
|
||||
:align: center
|
||||
|
||||
When using the class :class:`~MDNavigationDrawer` skeleton of your `KV` markup
|
||||
should look like this:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
Root:
|
||||
|
||||
NavigationLayout:
|
||||
|
||||
ScreenManager:
|
||||
|
||||
Screen_1:
|
||||
|
||||
Screen_2:
|
||||
|
||||
MDNavigationDrawer:
|
||||
# This custom rule should implement what will be appear in your MDNavigationDrawer
|
||||
ContentNavigationDrawer
|
||||
|
||||
A simple example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
NavigationLayout:
|
||||
|
||||
ScreenManager:
|
||||
|
||||
Screen:
|
||||
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
|
||||
MDToolbar:
|
||||
title: "Navigation Drawer"
|
||||
elevation: 10
|
||||
left_action_items: [['menu', lambda x: nav_drawer.toggle_nav_drawer()]]
|
||||
|
||||
Widget:
|
||||
|
||||
|
||||
MDNavigationDrawer:
|
||||
id: nav_drawer
|
||||
|
||||
ContentNavigationDrawer:
|
||||
'''
|
||||
|
||||
|
||||
class ContentNavigationDrawer(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class TestNavigationDrawer(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
TestNavigationDrawer().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.gif
|
||||
:align: center
|
||||
|
||||
.. Note:: :class:`~MDNavigationDrawer` is an empty
|
||||
:class:`~kivymd.uix.card.MDCard` panel.
|
||||
|
||||
Let's extend the ``ContentNavigationDrawer`` class from the above example and
|
||||
create content for our :class:`~MDNavigationDrawer` panel:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
# Menu item in the DrawerList list.
|
||||
<ItemDrawer>:
|
||||
theme_text_color: "Custom"
|
||||
on_release: self.parent.set_color_item(self)
|
||||
|
||||
IconLeftWidget:
|
||||
id: icon
|
||||
icon: root.icon
|
||||
theme_text_color: "Custom"
|
||||
text_color: root.text_color
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ItemDrawer(OneLineIconListItem):
|
||||
icon = StringProperty()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-item.png
|
||||
:align: center
|
||||
|
||||
Top of ``ContentNavigationDrawer`` and ``DrawerList`` for menu items:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<ContentNavigationDrawer>:
|
||||
orientation: "vertical"
|
||||
padding: "8dp"
|
||||
spacing: "8dp"
|
||||
|
||||
AnchorLayout:
|
||||
anchor_x: "left"
|
||||
size_hint_y: None
|
||||
height: avatar.height
|
||||
|
||||
Image:
|
||||
id: avatar
|
||||
size_hint: None, None
|
||||
size: "56dp", "56dp"
|
||||
source: "kivymd.png"
|
||||
|
||||
MDLabel:
|
||||
text: "KivyMD library"
|
||||
font_style: "Button"
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
|
||||
MDLabel:
|
||||
text: "kivydevelopment@gmail.com"
|
||||
font_style: "Caption"
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
|
||||
ScrollView:
|
||||
|
||||
DrawerList:
|
||||
id: md_list
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ContentNavigationDrawer(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class DrawerList(ThemableBehavior, MDList):
|
||||
def set_color_item(self, instance_item):
|
||||
'''Called when tap on a menu item.'''
|
||||
|
||||
# Set the color of the icon and text for the menu item.
|
||||
for item in self.children:
|
||||
if item.text_color == self.theme_cls.primary_color:
|
||||
item.text_color = self.theme_cls.text_color
|
||||
break
|
||||
instance_item.text_color = self.theme_cls.primary_color
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-top.png
|
||||
:align: center
|
||||
|
||||
Create a menu list for ``ContentNavigationDrawer``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_start(self):
|
||||
icons_item = {
|
||||
"folder": "My files",
|
||||
"account-multiple": "Shared with me",
|
||||
"star": "Starred",
|
||||
"history": "Recent",
|
||||
"checkbox-marked": "Shared with me",
|
||||
"upload": "Upload",
|
||||
}
|
||||
for icon_name in icons_item.keys():
|
||||
self.root.ids.content_drawer.ids.md_list.add_widget(
|
||||
ItemDrawer(icon=icon_name, text=icons_item[icon_name])
|
||||
)
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-work.gif
|
||||
:align: center
|
||||
|
||||
Switching screens in the ``ScreenManager`` and using the common ``MDToolbar``
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.properties import ObjectProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
<ContentNavigationDrawer>:
|
||||
|
||||
ScrollView:
|
||||
|
||||
MDList:
|
||||
|
||||
OneLineListItem:
|
||||
text: "Screen 1"
|
||||
on_press:
|
||||
root.nav_drawer.set_state("close")
|
||||
root.screen_manager.current = "scr 1"
|
||||
|
||||
OneLineListItem:
|
||||
text: "Screen 2"
|
||||
on_press:
|
||||
root.nav_drawer.set_state("close")
|
||||
root.screen_manager.current = "scr 2"
|
||||
|
||||
|
||||
Screen:
|
||||
|
||||
MDToolbar:
|
||||
id: toolbar
|
||||
pos_hint: {"top": 1}
|
||||
elevation: 10
|
||||
title: "MDNavigationDrawer"
|
||||
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
|
||||
|
||||
NavigationLayout:
|
||||
x: toolbar.height
|
||||
|
||||
ScreenManager:
|
||||
id: screen_manager
|
||||
|
||||
Screen:
|
||||
name: "scr 1"
|
||||
|
||||
MDLabel:
|
||||
text: "Screen 1"
|
||||
halign: "center"
|
||||
|
||||
Screen:
|
||||
name: "scr 2"
|
||||
|
||||
MDLabel:
|
||||
text: "Screen 2"
|
||||
halign: "center"
|
||||
|
||||
MDNavigationDrawer:
|
||||
id: nav_drawer
|
||||
|
||||
ContentNavigationDrawer:
|
||||
screen_manager: screen_manager
|
||||
nav_drawer: nav_drawer
|
||||
'''
|
||||
|
||||
|
||||
class ContentNavigationDrawer(BoxLayout):
|
||||
screen_manager = ObjectProperty()
|
||||
nav_drawer = ObjectProperty()
|
||||
|
||||
|
||||
class TestNavigationDrawer(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
TestNavigationDrawer().run()
|
||||
|
||||
NavigationDrawer with type ``standard``
|
||||
---------------------------------------
|
||||
|
||||
You can use the ``standard`` behavior type for the NavigationDrawer:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDNavigationDrawer:
|
||||
type: "standard"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standard.gif
|
||||
:align: center
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Full example of Components-Navigation-Drawer <https://github.com/kivymd/KivyMD/wiki/Components-Navigation-Drawer>`_
|
||||
"""
|
||||
|
||||
__all__ = ("NavigationLayout", "MDNavigationDrawer")
|
||||
|
||||
from kivy.animation import Animation, AnimationTransition
|
||||
from kivy.core.window import Window
|
||||
from kivy.graphics.context_instructions import Color
|
||||
from kivy.graphics.vertex_instructions import Rectangle
|
||||
from kivy.lang import Builder
|
||||
from kivy.logger import Logger
|
||||
from kivy.properties import (
|
||||
AliasProperty,
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.screenmanager import ScreenManager
|
||||
|
||||
from kivymd.uix.card import MDCard
|
||||
from kivymd.uix.toolbar import MDToolbar
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import Window kivy.core.window.Window
|
||||
|
||||
|
||||
<MDNavigationDrawer>:
|
||||
size_hint_x: None
|
||||
width: Window.width - dp(56) if Window.width <= dp(376) else dp(320)
|
||||
x:
|
||||
(self.width * (self.open_progress - 1)) \
|
||||
if self.anchor == "left" \
|
||||
else (Window.width - self.width * self.open_progress)
|
||||
elevation: 10
|
||||
|
||||
canvas:
|
||||
Clear
|
||||
Color:
|
||||
rgba: self.md_bg_color
|
||||
RoundedRectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
source: root.background
|
||||
radius: root._radius
|
||||
md_bg_color: self.theme_cls.bg_light
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class NavigationDrawerContentError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NavigationLayout(FloatLayout):
|
||||
_scrim_color = ObjectProperty(None)
|
||||
_scrim_rectangle = ObjectProperty(None)
|
||||
|
||||
_screen_manager = ObjectProperty(None)
|
||||
_navigation_drawer = ObjectProperty(None)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bind(width=self.update_pos)
|
||||
|
||||
def update_pos(self, *args):
|
||||
drawer = self._navigation_drawer
|
||||
manager = self._screen_manager
|
||||
if not drawer or not manager:
|
||||
return
|
||||
if drawer.type == "standard":
|
||||
manager.size_hint_x = None
|
||||
if drawer.anchor == "left":
|
||||
manager.x = drawer.width + drawer.x
|
||||
manager.width = self.width - manager.x
|
||||
else:
|
||||
manager.x = 0
|
||||
manager.width = drawer.x
|
||||
elif drawer.type == "modal":
|
||||
manager.size_hint_x = None
|
||||
manager.x = 0
|
||||
if drawer.anchor == "left":
|
||||
manager.width = self.width - manager.x
|
||||
else:
|
||||
manager.width = self.width
|
||||
|
||||
def add_scrim(self, widget):
|
||||
with widget.canvas.after:
|
||||
self._scrim_color = Color(rgba=[0, 0, 0, 0])
|
||||
self._scrim_rectangle = Rectangle(pos=widget.pos, size=widget.size)
|
||||
widget.bind(
|
||||
pos=self.update_scrim_rectangle,
|
||||
size=self.update_scrim_rectangle,
|
||||
)
|
||||
|
||||
def update_scrim_rectangle(self, *args):
|
||||
self._scrim_rectangle.pos = self.pos
|
||||
self._scrim_rectangle.size = self.size
|
||||
|
||||
def add_widget(self, widget, index=0, canvas=None):
|
||||
"""
|
||||
Only two layouts are allowed:
|
||||
:class:`~kivy.uix.screenmanager.ScreenManager` and
|
||||
:class:`~MDNavigationDrawer`.
|
||||
"""
|
||||
|
||||
if not isinstance(
|
||||
widget, (MDNavigationDrawer, ScreenManager, MDToolbar)
|
||||
):
|
||||
raise NavigationDrawerContentError(
|
||||
"The NavigationLayout must contain "
|
||||
"only `MDNavigationDrawer` and `ScreenManager`"
|
||||
)
|
||||
if isinstance(widget, ScreenManager):
|
||||
self._screen_manager = widget
|
||||
self.add_scrim(widget)
|
||||
if isinstance(widget, MDNavigationDrawer):
|
||||
self._navigation_drawer = widget
|
||||
widget.bind(
|
||||
x=self.update_pos, width=self.update_pos, anchor=self.update_pos
|
||||
)
|
||||
if len(self.children) > 3:
|
||||
raise NavigationDrawerContentError(
|
||||
"The NavigationLayout must contain "
|
||||
"only `MDNavigationDrawer` and `ScreenManager`"
|
||||
)
|
||||
return super().add_widget(widget)
|
||||
|
||||
|
||||
class MDNavigationDrawer(MDCard):
|
||||
type = OptionProperty("modal", options=("standard", "modal"))
|
||||
"""
|
||||
Type of drawer. Modal type will be on top of screen. Standard type will be
|
||||
at left or right of screen. Also it automatically disables
|
||||
:attr:`close_on_click` and :attr:`enable_swiping` to prevent closing
|
||||
drawer for standard type.
|
||||
|
||||
:attr:`type` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `modal`.
|
||||
"""
|
||||
|
||||
anchor = OptionProperty("left", options=("left", "right"))
|
||||
"""
|
||||
Anchoring screen edge for drawer. Set it to `'right'` for right-to-left
|
||||
languages. Available options are: `'left'`, `'right'`.
|
||||
|
||||
:attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `left`.
|
||||
"""
|
||||
|
||||
close_on_click = BooleanProperty(True)
|
||||
"""
|
||||
Close when click on scrim or keyboard escape. It automatically sets to
|
||||
False for "standard" type.
|
||||
|
||||
:attr:`close_on_click` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
state = OptionProperty("close", options=("close", "open"))
|
||||
"""
|
||||
Indicates if panel closed or opened. Sets after :attr:`status` change.
|
||||
Available options are: `'close'`, `'open'`.
|
||||
|
||||
:attr:`state` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'close'`.
|
||||
"""
|
||||
|
||||
status = OptionProperty(
|
||||
"closed",
|
||||
options=(
|
||||
"closed",
|
||||
"opening_with_swipe",
|
||||
"opening_with_animation",
|
||||
"opened",
|
||||
"closing_with_swipe",
|
||||
"closing_with_animation",
|
||||
),
|
||||
)
|
||||
"""
|
||||
Detailed state. Sets before :attr:`state`. Bind to :attr:`state` instead
|
||||
of :attr:`status`. Available options are: `'closed'`,
|
||||
`'opening_with_swipe'`, `'opening_with_animation'`, `'opened'`,
|
||||
`'closing_with_swipe'`, `'closing_with_animation'`.
|
||||
|
||||
:attr:`status` is a :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'closed'`.
|
||||
"""
|
||||
|
||||
open_progress = NumericProperty(0.0)
|
||||
"""
|
||||
Percent of visible part of side panel. The percent is specified as a
|
||||
floating point number in the range 0-1. 0.0 if panel is closed and 1.0 if
|
||||
panel is opened.
|
||||
|
||||
:attr:`open_progress` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.0`.
|
||||
"""
|
||||
|
||||
enable_swiping = BooleanProperty(True)
|
||||
"""
|
||||
Allow to open or close navigation drawer with swipe. It automatically
|
||||
sets to False for "standard" type.
|
||||
|
||||
:attr:`enable_swiping` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
swipe_distance = NumericProperty(10)
|
||||
"""
|
||||
The distance of the swipe with which the movement of navigation drawer
|
||||
begins.
|
||||
|
||||
:attr:`swipe_distance` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `10`.
|
||||
"""
|
||||
|
||||
swipe_edge_width = NumericProperty(20)
|
||||
"""
|
||||
The size of the area in px inside which should start swipe to drag
|
||||
navigation drawer.
|
||||
|
||||
:attr:`swipe_edge_width` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `20`.
|
||||
"""
|
||||
|
||||
scrim_color = ListProperty([0, 0, 0, 0.5])
|
||||
"""
|
||||
Color for scrim. Alpha channel will be multiplied with
|
||||
:attr:`_scrim_alpha`. Set fourth channel to 0 if you want to disable
|
||||
scrim.
|
||||
|
||||
:attr:`scrim_color` is a :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[0, 0, 0, 0.5]`.
|
||||
"""
|
||||
|
||||
_radius = ListProperty([0, 0, 0, 0])
|
||||
|
||||
def _get_scrim_alpha(self):
|
||||
_scrim_alpha = 0
|
||||
if self.type == "modal":
|
||||
_scrim_alpha = self._scrim_alpha_transition(self.open_progress)
|
||||
if (
|
||||
isinstance(self.parent, NavigationLayout)
|
||||
and self.parent._scrim_color
|
||||
):
|
||||
self.parent._scrim_color.rgba = self.scrim_color[:3] + [
|
||||
self.scrim_color[3] * _scrim_alpha
|
||||
]
|
||||
return _scrim_alpha
|
||||
|
||||
_scrim_alpha = AliasProperty(
|
||||
_get_scrim_alpha,
|
||||
None,
|
||||
bind=("_scrim_alpha_transition", "open_progress", "scrim_color"),
|
||||
)
|
||||
"""
|
||||
Multiplier for alpha channel of :attr:`scrim_color`. For internal
|
||||
usage only.
|
||||
"""
|
||||
|
||||
scrim_alpha_transition = StringProperty("linear")
|
||||
"""
|
||||
The name of the animation transition type to use for changing
|
||||
:attr:`scrim_alpha`.
|
||||
|
||||
:attr:`scrim_alpha_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'linear'`.
|
||||
"""
|
||||
|
||||
def _get_scrim_alpha_transition(self):
|
||||
return getattr(AnimationTransition, self.scrim_alpha_transition)
|
||||
|
||||
_scrim_alpha_transition = AliasProperty(
|
||||
_get_scrim_alpha_transition,
|
||||
None,
|
||||
bind=("scrim_alpha_transition",),
|
||||
cache=True,
|
||||
)
|
||||
|
||||
opening_transition = StringProperty("out_cubic")
|
||||
"""
|
||||
The name of the animation transition type to use when animating to
|
||||
the :attr:`state` `'open'`.
|
||||
|
||||
:attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_cubic'`.
|
||||
"""
|
||||
|
||||
opening_time = NumericProperty(0.2)
|
||||
"""
|
||||
The time taken for the panel to slide to the :attr:`state` `'open'`.
|
||||
|
||||
:attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
closing_transition = StringProperty("out_sine")
|
||||
"""The name of the animation transition type to use when animating to
|
||||
the :attr:`state` 'close'.
|
||||
|
||||
:attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_sine'`.
|
||||
"""
|
||||
|
||||
closing_time = NumericProperty(0.2)
|
||||
"""
|
||||
The time taken for the panel to slide to the :attr:`state` `'close'`.
|
||||
|
||||
:attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bind(
|
||||
open_progress=self.update_status,
|
||||
status=self.update_status,
|
||||
state=self.update_status,
|
||||
)
|
||||
Window.bind(on_keyboard=self._handle_keyboard)
|
||||
|
||||
def set_state(self, new_state="toggle", animation=True):
|
||||
"""Change state of the side panel.
|
||||
New_state can be one of `"toggle"`, `"open"` or `"close"`.
|
||||
"""
|
||||
|
||||
if new_state == "toggle":
|
||||
new_state = "close" if self.state == "open" else "open"
|
||||
|
||||
if new_state == "open":
|
||||
Animation.cancel_all(self, "open_progress")
|
||||
self.status = "opening_with_animation"
|
||||
if animation:
|
||||
Animation(
|
||||
open_progress=1.0,
|
||||
d=self.opening_time * (1 - self.open_progress),
|
||||
t=self.opening_transition,
|
||||
).start(self)
|
||||
else:
|
||||
self.open_progress = 1
|
||||
else: # "close"
|
||||
Animation.cancel_all(self, "open_progress")
|
||||
self.status = "closing_with_animation"
|
||||
if animation:
|
||||
Animation(
|
||||
open_progress=0.0,
|
||||
d=self.closing_time * self.open_progress,
|
||||
t=self.closing_transition,
|
||||
).start(self)
|
||||
else:
|
||||
self.open_progress = 0
|
||||
|
||||
def toggle_nav_drawer(self):
|
||||
Logger.warning(
|
||||
"KivyMD: The 'toggle_nav_drawer' method is deprecated, "
|
||||
"use 'set_state' instead."
|
||||
)
|
||||
self.set_state("toggle", animation=True)
|
||||
|
||||
def update_status(self, *_):
|
||||
status = self.status
|
||||
if status == "closed":
|
||||
self.state = "close"
|
||||
elif status == "opened":
|
||||
self.state = "open"
|
||||
elif self.open_progress == 1 and status == "opening_with_animation":
|
||||
self.status = "opened"
|
||||
self.state = "open"
|
||||
elif self.open_progress == 0 and status == "closing_with_animation":
|
||||
self.status = "closed"
|
||||
self.state = "close"
|
||||
elif status in (
|
||||
"opening_with_swipe",
|
||||
"opening_with_animation",
|
||||
"closing_with_swipe",
|
||||
"closing_with_animation",
|
||||
):
|
||||
pass
|
||||
if self.status == "closed":
|
||||
self._elevation = 0
|
||||
self._update_shadow(self, self._elevation)
|
||||
else:
|
||||
self._elevation = self.elevation
|
||||
self._update_shadow(self, self._elevation)
|
||||
|
||||
def get_dist_from_side(self, x):
|
||||
if self.anchor == "left":
|
||||
return 0 if x < 0 else x
|
||||
return 0 if x > Window.width else Window.width - x
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if self.status == "closed":
|
||||
return False
|
||||
elif self.status == "opened":
|
||||
for child in self.children[:]:
|
||||
if child.dispatch("on_touch_down", touch):
|
||||
return True
|
||||
if self.type == "standard" and not self.collide_point(
|
||||
touch.ox, touch.oy
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def on_touch_move(self, touch):
|
||||
if self.enable_swiping:
|
||||
if self.status == "closed":
|
||||
if (
|
||||
self.get_dist_from_side(touch.ox) <= self.swipe_edge_width
|
||||
and abs(touch.x - touch.ox) > self.swipe_distance
|
||||
):
|
||||
self.status = "opening_with_swipe"
|
||||
elif self.status == "opened":
|
||||
self.status = "closing_with_swipe"
|
||||
|
||||
if self.status in ("opening_with_swipe", "closing_with_swipe"):
|
||||
self.open_progress = max(
|
||||
min(
|
||||
self.open_progress
|
||||
+ (touch.dx if self.anchor == "left" else -touch.dx)
|
||||
/ self.width,
|
||||
1,
|
||||
),
|
||||
0,
|
||||
)
|
||||
return True
|
||||
return super().on_touch_move(touch)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if self.status == "opening_with_swipe":
|
||||
if self.open_progress > 0.5:
|
||||
self.set_state("open", animation=True)
|
||||
else:
|
||||
self.set_state("close", animation=True)
|
||||
elif self.status == "closing_with_swipe":
|
||||
if self.open_progress < 0.5:
|
||||
self.set_state("close", animation=True)
|
||||
else:
|
||||
self.set_state("open", animation=True)
|
||||
elif self.status == "opened":
|
||||
if self.close_on_click and not self.collide_point(
|
||||
touch.ox, touch.oy
|
||||
):
|
||||
self.set_state("close", animation=True)
|
||||
elif self.type == "standard" and not self.collide_point(
|
||||
touch.ox, touch.oy
|
||||
):
|
||||
return False
|
||||
elif self.status == "closed":
|
||||
return False
|
||||
return True
|
||||
|
||||
def on_radius(self, instance, value):
|
||||
self._radius = value
|
||||
|
||||
def on_type(self, *args):
|
||||
if self.type == "standard":
|
||||
self.enable_swiping = False
|
||||
self.close_on_click = False
|
||||
else:
|
||||
self.enable_swiping = True
|
||||
self.close_on_click = True
|
||||
|
||||
def _handle_keyboard(self, window, key, *largs):
|
||||
if key == 27 and self.status == "opened" and self.close_on_click:
|
||||
self.set_state("close")
|
||||
return True
|
1114
kivymd/uix/picker.py
Executable file
313
kivymd/uix/progressbar.py
Executable file
|
@ -0,0 +1,313 @@
|
|||
"""
|
||||
Components/Progress Bar
|
||||
=======================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Progress indicators https://material.io/components/progress-indicators`_
|
||||
|
||||
.. rubric:: Progress indicators express an unspecified wait time or display
|
||||
the length of a process.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-preview.png
|
||||
:align: center
|
||||
|
||||
`KivyMD` provides the following bars classes for use:
|
||||
|
||||
- MDProgressBar_
|
||||
- Determinate_
|
||||
- Indeterminate_
|
||||
|
||||
.. MDProgressBar:
|
||||
MDProgressBar
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
BoxLayout:
|
||||
padding: "10dp"
|
||||
|
||||
MDProgressBar:
|
||||
value: 50
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar.png
|
||||
:align: center
|
||||
|
||||
Vertical orientation
|
||||
--------------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDProgressBar:
|
||||
orientation: "vertical"
|
||||
value: 50
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-vertical.png
|
||||
:align: center
|
||||
|
||||
With custom color
|
||||
-----------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDProgressBar:
|
||||
value: 50
|
||||
color: app.theme_cls.accent_color
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-custom-color.png
|
||||
:align: center
|
||||
|
||||
.. Indeterminate:
|
||||
Indeterminate
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
MDProgressBar:
|
||||
id: progress
|
||||
pos_hint: {"center_y": .6}
|
||||
type: "indeterminate"
|
||||
|
||||
MDRaisedButton:
|
||||
text: "START"
|
||||
pos_hint: {"center_x": .5, "center_y": .45}
|
||||
on_press: app.state = "stop" if app.state == "start" else "start"
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
state = StringProperty("stop")
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def on_state(self, instance, value):
|
||||
{
|
||||
"start": self.root.ids.progress.start,
|
||||
"stop": self.root.ids.progress.stop,
|
||||
}.get(value)()
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/indeterminate-progress-bar.gif
|
||||
:align: center
|
||||
|
||||
.. Determinate:
|
||||
Determinate
|
||||
-----------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDProgressBar:
|
||||
type: "determinate"
|
||||
running_duration: 1
|
||||
catching_duration: 1.5
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/determinate-progress-bar.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("MDProgressBar",)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.progressbar import ProgressBar
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<MDProgressBar>
|
||||
canvas:
|
||||
Clear
|
||||
Color:
|
||||
rgba: self.theme_cls.divider_color
|
||||
Rectangle:
|
||||
size:
|
||||
(self.width, dp(4)) \
|
||||
if self.orientation == "horizontal" \
|
||||
else (dp(4), self.height)
|
||||
pos:
|
||||
(self.x, self.center_y - dp(4)) \
|
||||
if self.orientation == "horizontal" \
|
||||
else (self.center_x - dp(4),self.y)
|
||||
Color:
|
||||
rgba:
|
||||
self.theme_cls.primary_color if not self.color else self.color
|
||||
Rectangle:
|
||||
size:
|
||||
(self.width * self.value_normalized, sp(4)) \
|
||||
if self.orientation == "horizontal" \
|
||||
else (sp(4), self.height * self.value_normalized)
|
||||
pos:
|
||||
(self.width * (1 - self.value_normalized) + self.x \
|
||||
if self.reversed else self.x + self._x, self.center_y - dp(4)) \
|
||||
if self.orientation == "horizontal" \
|
||||
else (self.center_x - dp(4),self.height \
|
||||
* (1 - self.value_normalized) + self.y if self.reversed \
|
||||
else self.y)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MDProgressBar(ThemableBehavior, ProgressBar):
|
||||
reversed = BooleanProperty(False)
|
||||
"""Reverse the direction the progressbar moves.
|
||||
|
||||
:attr:`reversed` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
orientation = OptionProperty(
|
||||
"horizontal", options=["horizontal", "vertical"]
|
||||
)
|
||||
"""Orientation of progressbar. Available options are: `'horizontal '`,
|
||||
`'vertical'`.
|
||||
|
||||
:attr:`orientation` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'horizontal'`.
|
||||
"""
|
||||
|
||||
color = ListProperty()
|
||||
"""
|
||||
Progress bar color in ``rgba`` format.
|
||||
|
||||
:attr:`color` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
running_transition = StringProperty("in_cubic")
|
||||
"""Running transition.
|
||||
|
||||
:attr:`running_transition` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'in_cubic'`.
|
||||
"""
|
||||
|
||||
catching_transition = StringProperty("out_quart")
|
||||
"""Catching transition.
|
||||
|
||||
:attr:`catching_transition` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_quart'`.
|
||||
"""
|
||||
|
||||
running_duration = NumericProperty(0.5)
|
||||
"""Running duration.
|
||||
|
||||
:attr:`running_duration` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.5`.
|
||||
"""
|
||||
|
||||
catching_duration = NumericProperty(0.8)
|
||||
"""Catching duration.
|
||||
|
||||
:attr:`running_duration` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.8`.
|
||||
"""
|
||||
|
||||
type = OptionProperty(
|
||||
None, options=["indeterminate", "determinate"], allownone=True
|
||||
)
|
||||
"""Type of progressbar. Available options are: `'indeterminate '`,
|
||||
`'determinate'`.
|
||||
|
||||
:attr:`type` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
_x = NumericProperty(0)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.catching_anim = None
|
||||
self.running_anim = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def start(self):
|
||||
"""Start animation."""
|
||||
|
||||
if self.type in ("indeterminate", "determinate"):
|
||||
Clock.schedule_once(self._set_default_value)
|
||||
if not self.catching_anim and not self.running_anim:
|
||||
if self.type == "indeterminate":
|
||||
self._create_indeterminate_animations()
|
||||
else:
|
||||
self._create_determinate_animations()
|
||||
self.running_away()
|
||||
|
||||
def stop(self):
|
||||
"""Stop animation."""
|
||||
|
||||
Animation.cancel_all(self)
|
||||
self._set_default_value(0)
|
||||
|
||||
def running_away(self, *args):
|
||||
self._set_default_value(0)
|
||||
self.running_anim.start(self)
|
||||
|
||||
def catching_up(self, *args):
|
||||
if self.type == "indeterminate":
|
||||
self.reversed = True
|
||||
self.catching_anim.start(self)
|
||||
|
||||
def _create_determinate_animations(self):
|
||||
self.running_anim = Animation(
|
||||
value=100,
|
||||
opacity=1,
|
||||
t=self.running_transition,
|
||||
d=self.running_duration,
|
||||
)
|
||||
self.running_anim.bind(on_complete=self.catching_up)
|
||||
self.catching_anim = Animation(
|
||||
opacity=0,
|
||||
t=self.catching_transition,
|
||||
d=self.catching_duration,
|
||||
)
|
||||
self.catching_anim.bind(on_complete=self.running_away)
|
||||
|
||||
def _create_indeterminate_animations(self):
|
||||
self.running_anim = Animation(
|
||||
_x=self.width / 2,
|
||||
value=50,
|
||||
t=self.running_transition,
|
||||
d=self.running_duration,
|
||||
)
|
||||
self.running_anim.bind(on_complete=self.catching_up)
|
||||
self.catching_anim = Animation(
|
||||
value=0, t=self.catching_transition, d=self.catching_duration
|
||||
)
|
||||
self.catching_anim.bind(on_complete=self.running_away)
|
||||
|
||||
def _set_default_value(self, interval):
|
||||
self._x = 0
|
||||
self.value = 0
|
||||
self.reversed = False
|
223
kivymd/uix/refreshlayout.py
Executable file
|
@ -0,0 +1,223 @@
|
|||
"""
|
||||
Components/Refresh Layout
|
||||
=========================
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.factory import Factory
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.uix.button import MDIconButton
|
||||
from kivymd.icon_definitions import md_icons
|
||||
from kivymd.uix.list import ILeftBodyTouch, OneLineIconListItem
|
||||
from kivymd.theming import ThemeManager
|
||||
from kivymd.utils import asynckivy
|
||||
|
||||
Builder.load_string('''
|
||||
<ItemForList>
|
||||
text: root.text
|
||||
|
||||
IconLeftSampleWidget:
|
||||
icon: root.icon
|
||||
|
||||
|
||||
<Example@FloatLayout>
|
||||
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
|
||||
MDToolbar:
|
||||
title: app.title
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
background_palette: 'Primary'
|
||||
elevation: 10
|
||||
left_action_items: [['menu', lambda x: x]]
|
||||
|
||||
MDScrollViewRefreshLayout:
|
||||
id: refresh_layout
|
||||
refresh_callback: app.refresh_callback
|
||||
root_layout: root
|
||||
|
||||
MDGridLayout:
|
||||
id: box
|
||||
adaptive_height: True
|
||||
cols: 1
|
||||
''')
|
||||
|
||||
|
||||
class IconLeftSampleWidget(ILeftBodyTouch, MDIconButton):
|
||||
pass
|
||||
|
||||
|
||||
class ItemForList(OneLineIconListItem):
|
||||
icon = StringProperty()
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
title = 'Example Refresh Layout'
|
||||
screen = None
|
||||
x = 0
|
||||
y = 15
|
||||
|
||||
def build(self):
|
||||
self.screen = Factory.Example()
|
||||
self.set_list()
|
||||
|
||||
return self.screen
|
||||
|
||||
def set_list(self):
|
||||
async def set_list():
|
||||
names_icons_list = list(md_icons.keys())[self.x:self.y]
|
||||
for name_icon in names_icons_list:
|
||||
await asynckivy.sleep(0)
|
||||
self.screen.ids.box.add_widget(
|
||||
ItemForList(icon=name_icon, text=name_icon))
|
||||
asynckivy.start(set_list())
|
||||
|
||||
def refresh_callback(self, *args):
|
||||
'''A method that updates the state of your application
|
||||
while the spinner remains on the screen.'''
|
||||
|
||||
def refresh_callback(interval):
|
||||
self.screen.ids.box.clear_widgets()
|
||||
if self.x == 0:
|
||||
self.x, self.y = 15, 30
|
||||
else:
|
||||
self.x, self.y = 0, 15
|
||||
self.set_list()
|
||||
self.screen.ids.refresh_layout.refresh_done()
|
||||
self.tick = 0
|
||||
|
||||
Clock.schedule_once(refresh_callback, 1)
|
||||
|
||||
|
||||
Example().run()
|
||||
"""
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.core.window import Window
|
||||
from kivy.effects.dampedscroll import DampedScrollEffect
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import ListProperty, NumericProperty, ObjectProperty
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import Window kivy.core.window.Window
|
||||
|
||||
|
||||
<RefreshSpinner>
|
||||
|
||||
AnchorLayout:
|
||||
id: body_spinner
|
||||
size_hint: None, None
|
||||
size: dp(46), dp(46)
|
||||
y: Window.height
|
||||
pos_hint: {'center_x': .5}
|
||||
anchor_x: 'center'
|
||||
anchor_y: 'center'
|
||||
|
||||
canvas:
|
||||
Clear
|
||||
Color:
|
||||
rgba: root.theme_cls.primary_dark
|
||||
Ellipse:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
MDSpinner:
|
||||
id: spinner
|
||||
size_hint: None, None
|
||||
size: dp(30), dp(30)
|
||||
color: 1, 1, 1, 1
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class _RefreshScrollEffect(DampedScrollEffect):
|
||||
"""This class is simply based on DampedScrollEffect.
|
||||
If you need any documentation please look at kivy.effects.dampedscrolleffect.
|
||||
"""
|
||||
|
||||
min_scroll_to_reload = NumericProperty("-100dp")
|
||||
"""Minimum overscroll value to reload."""
|
||||
|
||||
def on_overscroll(self, scrollview, overscroll):
|
||||
if overscroll < self.min_scroll_to_reload:
|
||||
scroll_view = self.target_widget.parent
|
||||
scroll_view._did_overscroll = True
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class MDScrollViewRefreshLayout(ScrollView):
|
||||
root_layout = ObjectProperty()
|
||||
"""The spinner will be attached to this layout."""
|
||||
|
||||
def __init__(self, **kargs):
|
||||
super().__init__(**kargs)
|
||||
self.effect_cls = _RefreshScrollEffect
|
||||
self._work_spinnrer = False
|
||||
self._did_overscroll = False
|
||||
self.refresh_spinner = None
|
||||
|
||||
def on_touch_up(self, *args):
|
||||
if self._did_overscroll and not self._work_spinnrer:
|
||||
if self.refresh_callback:
|
||||
self.refresh_callback()
|
||||
if not self.refresh_spinner:
|
||||
self.refresh_spinner = RefreshSpinner(_refresh_layout=self)
|
||||
self.root_layout.add_widget(self.refresh_spinner)
|
||||
self.refresh_spinner.start_anim_spinner()
|
||||
self._work_spinnrer = True
|
||||
self._did_overscroll = False
|
||||
return True
|
||||
|
||||
return super().on_touch_up(*args)
|
||||
|
||||
def refresh_done(self):
|
||||
if self.refresh_spinner:
|
||||
self.refresh_spinner.hide_anim_spinner()
|
||||
|
||||
|
||||
class RefreshSpinner(ThemableBehavior, FloatLayout):
|
||||
spinner_color = ListProperty([1, 1, 1, 1])
|
||||
|
||||
_refresh_layout = ObjectProperty()
|
||||
"""kivymd.refreshlayout.MDScrollViewRefreshLayout object."""
|
||||
|
||||
def start_anim_spinner(self):
|
||||
spinner = self.ids.body_spinner
|
||||
Animation(
|
||||
y=spinner.y - self.theme_cls.standard_increment * 2 + dp(10),
|
||||
d=0.8,
|
||||
t="out_elastic",
|
||||
).start(spinner)
|
||||
|
||||
def hide_anim_spinner(self):
|
||||
spinner = self.ids.body_spinner
|
||||
anim = Animation(y=Window.height, d=0.8, t="out_elastic")
|
||||
anim.bind(on_complete=self.set_spinner)
|
||||
anim.start(spinner)
|
||||
|
||||
def set_spinner(self, *args):
|
||||
body_spinner = self.ids.body_spinner
|
||||
body_spinner.size = (dp(46), dp(46))
|
||||
body_spinner.y = Window.height
|
||||
body_spinner.opacity = 1
|
||||
spinner = self.ids.spinner
|
||||
spinner.size = (dp(30), dp(30))
|
||||
spinner.opacity = 1
|
||||
self._refresh_layout._work_spinnrer = False
|
||||
self._refresh_layout._did_overscroll = False
|