Disable kivy and removed kivy components and modules from master #1717
|
@ -1,45 +0,0 @@
|
|||
from helper_sql import *
|
||||
|
||||
|
||||
def search_sql(xAddress="toaddress", account=None, folder="inbox", where=None, what=None, unreadOnly=False):
|
||||
if what is not None and what != "":
|
||||
what = "%" + what + "%"
|
||||
else:
|
||||
what = None
|
||||
|
||||
if folder == "sent":
|
||||
sqlStatementBase = '''
|
||||
SELECT toaddress, fromaddress, subject, status, ackdata, lastactiontime
|
||||
FROM sent '''
|
||||
else:
|
||||
sqlStatementBase = '''SELECT folder, msgid, toaddress, fromaddress, subject, received, read
|
||||
FROM inbox '''
|
||||
sqlStatementParts = []
|
||||
sqlArguments = []
|
||||
if account is not None:
|
||||
if xAddress == 'both':
|
||||
sqlStatementParts.append("(fromaddress = ? OR toaddress = ?)")
|
||||
sqlArguments.append(account)
|
||||
sqlArguments.append(account)
|
||||
else:
|
||||
sqlStatementParts.append(xAddress + " = ? ")
|
||||
sqlArguments.append(account)
|
||||
if folder is not None:
|
||||
if folder == "new":
|
||||
folder = "inbox"
|
||||
unreadOnly = True
|
||||
sqlStatementParts.append("folder = ? ")
|
||||
sqlArguments.append(folder)
|
||||
else:
|
||||
sqlStatementParts.append("folder != ?")
|
||||
sqlArguments.append("trash")
|
||||
if what is not None:
|
||||
sqlStatementParts.append("%s LIKE ?" % (where))
|
||||
sqlArguments.append(what)
|
||||
if unreadOnly:
|
||||
sqlStatementParts.append("read = 0")
|
||||
if len(sqlStatementParts) > 0:
|
||||
sqlStatementBase += "WHERE " + " AND ".join(sqlStatementParts)
|
||||
if folder == "sent":
|
||||
sqlStatementBase += " ORDER BY lastactiontime"
|
||||
return sqlQuery(sqlStatementBase, sqlArguments)
|
|
@ -1,354 +0,0 @@
|
|||
#:import la kivy.adapters.listadapter
|
||||
#:import factory kivy.factory
|
||||
#:import mpybit bitmessagekivy.mpybit
|
||||
#:import C kivy.utils.get_color_from_hex
|
||||
|
||||
<Navigator>:
|
||||
id: nav_drawer
|
||||
NavigationDrawerIconButton:
|
||||
Spinner:
|
||||
pos_hint:{"x":0,"y":.3}
|
||||
id: btn
|
||||
background_color: app.theme_cls.primary_dark
|
||||
text: app.showmeaddresses(name='text')
|
||||
values: app.showmeaddresses(name='values')
|
||||
on_text:app.getCurrentAccountData(self.text)
|
||||
|
||||
NavigationDrawerIconButton:
|
||||
icon: 'email-open'
|
||||
text: "inbox"
|
||||
on_release: app.root.ids.scr_mngr.current = 'inbox'
|
||||
NavigationDrawerIconButton:
|
||||
icon: 'mail-send'
|
||||
text: "sent"
|
||||
on_release: app.root.ids.scr_mngr.current = 'sent'
|
||||
NavigationDrawerIconButton:
|
||||
icon: 'dropbox'
|
||||
text: "trash"
|
||||
on_release: app.root.ids.scr_mngr.current = 'trash'
|
||||
NavigationDrawerIconButton:
|
||||
icon: 'email'
|
||||
text: "drafts"
|
||||
on_release: app.root.ids.scr_mngr.current = 'dialog'
|
||||
NavigationDrawerIconButton:
|
||||
icon: 'markunread-mailbox'
|
||||
text: "test"
|
||||
on_release: app.root.ids.scr_mngr.current = 'test'
|
||||
NavigationDrawerIconButton:
|
||||
text: "new identity"
|
||||
icon:'accounts-add'
|
||||
on_release: app.root.ids.scr_mngr.current = 'newidentity'
|
||||
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
Toolbar:
|
||||
id: toolbar
|
||||
title: app.getCurrentAccount()
|
||||
background_color: app.theme_cls.primary_dark
|
||||
left_action_items: [['menu', lambda x: app.nav_drawer.toggle()]]
|
||||
Button:
|
||||
text:"EXIT"
|
||||
color: 0,0,0,1
|
||||
background_color: (0,0,0,0)
|
||||
size_hint_y: 0.4
|
||||
size_hint_x: 0.1
|
||||
pos_hint: {'x': 0.8, 'y':0.4}
|
||||
on_press: app.say_exit()
|
||||
|
||||
|
||||
ScreenManager:
|
||||
id: scr_mngr
|
||||
Inbox:
|
||||
id:sc1
|
||||
Sent:
|
||||
id:sc2
|
||||
Trash:
|
||||
id:sc3
|
||||
Dialog:
|
||||
id:sc4
|
||||
Test:
|
||||
id:sc5
|
||||
Create:
|
||||
id:sc6
|
||||
NewIdentity:
|
||||
id:sc7
|
||||
Page:
|
||||
id:sc8
|
||||
AddressSuccessful:
|
||||
id:sc9
|
||||
|
||||
Button:
|
||||
id:create
|
||||
height:100
|
||||
size_hint_y: 0.13
|
||||
size_hint_x: 0.1
|
||||
pos_hint: {'x': 0.85, 'y': 0.5}
|
||||
background_color: (0,0,0,0)
|
||||
on_press: scr_mngr.current = 'create'
|
||||
Image:
|
||||
source: 'images/plus.png'
|
||||
y: self.parent.y - 7.5
|
||||
x: self.parent.x + self.parent.width - 50
|
||||
size: 70, 70
|
||||
|
||||
<SwipeButton@Carousel>:
|
||||
text: ''
|
||||
size_hint_y: None
|
||||
height: 48
|
||||
ignore_perpendicular_swipes: True
|
||||
data_index: 0
|
||||
min_move: 20 / self.width
|
||||
|
||||
on__offset: app.update_index(root.data_index, self.index)
|
||||
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: C('FFFFFF33')
|
||||
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
Line:
|
||||
rectangle: self.pos + self.size
|
||||
|
||||
Button:
|
||||
text: 'delete ({}:{})'.format(root.text, root.data_index)
|
||||
on_press: app.delete(root.data_index)
|
||||
|
||||
Button:
|
||||
text: root.text
|
||||
on_press: app.getInboxMessageDetail(self.text)
|
||||
|
||||
Button:
|
||||
text: 'archive'
|
||||
on_press: app.archive(root.data_index)
|
||||
|
||||
<Inbox>:
|
||||
name: 'inbox'
|
||||
RecycleView:
|
||||
data: root.data
|
||||
viewclass: 'SwipeButton'
|
||||
do_scroll_x: False
|
||||
scroll_timeout: 100
|
||||
|
||||
RecycleBoxLayout:
|
||||
id:rc
|
||||
orientation: 'vertical'
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
default_size_hint: 1, None
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: 0,0,0, 1
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
<Sent>:
|
||||
name: 'sent'
|
||||
RecycleView:
|
||||
data: root.data
|
||||
viewclass: 'SwipeButton'
|
||||
do_scroll_x: False
|
||||
scroll_timeout: 100
|
||||
|
||||
RecycleBoxLayout:
|
||||
id:rc
|
||||
orientation: 'vertical'
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
default_size_hint: 1, None
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: 0,0,0, 1
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
<Trash>:
|
||||
name: 'trash'
|
||||
RecycleView:
|
||||
data: root.data
|
||||
viewclass: 'SwipeButton'
|
||||
do_scroll_x: False
|
||||
scroll_timeout: 100
|
||||
|
||||
RecycleBoxLayout:
|
||||
id:rc
|
||||
orientation: 'vertical'
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
default_size_hint: 1, None
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: 0,0,0, 1
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
<Dialog>:
|
||||
name: 'dialog'
|
||||
Label:
|
||||
text:"I have a good dialox box"
|
||||
color: 0,0,0,1
|
||||
<Test>:
|
||||
name: 'test'
|
||||
Label:
|
||||
text:"I am in test"
|
||||
color: 0,0,0,1
|
||||
|
||||
<Create>:
|
||||
name: 'create'
|
||||
GridLayout:
|
||||
rows: 5
|
||||
cols: 1
|
||||
padding: 60,60,60,60
|
||||
spacing: 50
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: '32dp'
|
||||
Label:
|
||||
text: 'FROM'
|
||||
color: 0,0,0,1
|
||||
Spinner:
|
||||
size_hint: 1,1
|
||||
pos_hint: {"x":0,"top":1.}
|
||||
pos: 10,10
|
||||
id: spinner_id
|
||||
text: app.showmeaddresses(name='text')
|
||||
values: app.showmeaddresses(name='values')
|
||||
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: '32dp'
|
||||
Label:
|
||||
text: 'TO'
|
||||
color: 0,0,0,1
|
||||
TextInput:
|
||||
id: recipent
|
||||
hint_text: 'To'
|
||||
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: '32dp'
|
||||
Label:
|
||||
text: 'SUBJECT'
|
||||
color: 0,0,0,1
|
||||
TextInput:
|
||||
id: subject
|
||||
hint_text: 'SUBJECT'
|
||||
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: '32dp'
|
||||
Label:
|
||||
text: 'BODY'
|
||||
color: 0,0,0,1
|
||||
TextInput:
|
||||
id: message
|
||||
multiline:True
|
||||
size_hint: 1,2
|
||||
|
||||
Button:
|
||||
text: 'send'
|
||||
size_hint_y: 0.1
|
||||
size_hint_x: 0.2
|
||||
height: '32dp'
|
||||
pos_hint: {'x': .5, 'y': 0.1}
|
||||
on_press: root.send()
|
||||
Button:
|
||||
text: 'cancel'
|
||||
size_hint_y: 0.1
|
||||
size_hint_x: 0.2
|
||||
height: '32dp'
|
||||
pos_hint: {'x': .72, 'y': 0.1}
|
||||
on_press: root.cancel()
|
||||
|
||||
<NewIdentity>:
|
||||
name: 'newidentity'
|
||||
GridLayout:
|
||||
padding: '120dp'
|
||||
cols: 1
|
||||
Label:
|
||||
text:"""Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged."""
|
||||
line_height:1.5
|
||||
text_size:(700,None)
|
||||
color: 0,0,0,1
|
||||
BoxLayout:
|
||||
CheckBox:
|
||||
canvas.before:
|
||||
Color:
|
||||
rgb: 1,0,0
|
||||
Ellipse:
|
||||
pos:self.center_x-8, self.center_y-8
|
||||
size:[16,16]
|
||||
group: "money"
|
||||
id:chk
|
||||
text:"use a random number generator to make an address"
|
||||
on_active:
|
||||
root.checked = self.text
|
||||
active:root.is_active
|
||||
|
||||
Label:
|
||||
text: "use a random number generator to make an address"
|
||||
color: 0,0,0,1
|
||||
BoxLayout:
|
||||
CheckBox:
|
||||
canvas.before:
|
||||
Color:
|
||||
rgb: 1,0,0
|
||||
Ellipse:
|
||||
pos:self.center_x-8, self.center_y-8
|
||||
size:[16,16]
|
||||
group: "money"
|
||||
id:chk
|
||||
text:"use a pseudo number generator to make an address"
|
||||
on_active:
|
||||
root.checked = self.text
|
||||
active:not root.is_active
|
||||
Label:
|
||||
text: "use a pseudo number generator to make an address"
|
||||
color: 0,0,0,1
|
||||
Label:
|
||||
color: 0,0,0,1
|
||||
size_hint_x: .35
|
||||
markup: True
|
||||
text: "[b]{}[/b]".format("Randomly generated addresses")
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: '32dp'
|
||||
Label:
|
||||
text: "Label (not shown to anyone except you)"
|
||||
color: 0,0,0,1
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: '32dp'
|
||||
TextInput:
|
||||
id: label
|
||||
|
||||
Button:
|
||||
text: 'Cancel'
|
||||
size_hint_y: 0.1
|
||||
size_hint_x: 0.3
|
||||
height: '32dp'
|
||||
pos_hint: {'x': .1, 'y': 0.1}
|
||||
Button:
|
||||
text: 'Ok'
|
||||
size_hint_y: 0.1
|
||||
size_hint_x: 0.3
|
||||
height: '32dp'
|
||||
pos_hint: {'x': .5, 'y': 0.1}
|
||||
on_press: root.generateaddress()
|
||||
|
||||
<Page>:
|
||||
name: 'page'
|
||||
Label:
|
||||
text: 'I am on description of my email yooooo'
|
||||
color: 0,0,0,1
|
||||
|
||||
<AddressSuccessful>:
|
||||
name: 'add_sucess'
|
||||
Label:
|
||||
text: 'Successfully created a new bit address'
|
||||
color: 0,0,0,1
|
|
@ -1,393 +0,0 @@
|
|||
import kivy_helper_search
|
||||
import os
|
||||
import queues
|
||||
import shutdown
|
||||
import state
|
||||
import time
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import BooleanProperty
|
||||
from kivy.clock import Clock
|
||||
from navigationdrawer import NavigationDrawer
|
||||
from kivy.properties import ObjectProperty, StringProperty, ListProperty
|
||||
from kivy.uix.screenmanager import Screen
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivymd.theming import ThemeManager
|
||||
from kivymd.toolbar import Toolbar
|
||||
from bmconfigparser import BMConfigParser
|
||||
from helper_ackPayload import genAckPayload
|
||||
from addresses import decodeAddress, addBMIfNotPresent
|
||||
from helper_sql import sqlExecute
|
||||
|
||||
statusIconColor = 'red'
|
||||
|
||||
|
||||
class NavigateApp(App, TextInput):
|
||||
"""Application uses kivy in which base Class of Navigate App inherits from the App class."""
|
||||
|
||||
theme_cls = ThemeManager()
|
||||
nav_drawer = ObjectProperty()
|
||||
|
||||
def build(self):
|
||||
"""Return a main_widget as a root widget.
|
||||
|
||||
An application can be built if you return a widget on build(), or if you set
|
||||
self.root.
|
||||
"""
|
||||
main_widget = Builder.load_file(
|
||||
os.path.join(os.path.dirname(__file__), 'main.kv'))
|
||||
self.nav_drawer = Navigator()
|
||||
return main_widget
|
||||
|
||||
def getCurrentAccountData(self, text):
|
||||
"""Get Current Address Account Data."""
|
||||
state.association = text
|
||||
self.root.ids.sc1.clear_widgets()
|
||||
self.root.ids.sc2.clear_widgets()
|
||||
self.root.ids.sc3.clear_widgets()
|
||||
self.root.ids.sc1.add_widget(Inbox())
|
||||
self.root.ids.sc2.add_widget(Sent())
|
||||
self.root.ids.sc3.add_widget(Trash())
|
||||
self.root.ids.toolbar.title = BMConfigParser().get(
|
||||
state.association, 'label') + '({})'.format(state.association)
|
||||
Inbox()
|
||||
Sent()
|
||||
Trash()
|
||||
|
||||
def say_exit(self):
|
||||
"""Exit the application as uses shutdown PyBitmessage."""
|
||||
print("**************************EXITING FROM APPLICATION*****************************")
|
||||
App.get_running_app().stop()
|
||||
shutdown.doCleanShutdown()
|
||||
|
||||
@staticmethod
|
||||
def showmeaddresses(name="text"):
|
||||
"""Show the addresses in spinner to make as dropdown."""
|
||||
if name == "text":
|
||||
return BMConfigParser().addresses()[0]
|
||||
elif name == "values":
|
||||
return BMConfigParser().addresses()
|
||||
|
||||
def update_index(self, data_index, index):
|
||||
"""Update index after archieve message to trash."""
|
||||
if self.root.ids.scr_mngr.current == 'inbox':
|
||||
self.root.ids.sc1.data[data_index]['index'] = index
|
||||
elif self.root.ids.scr_mngr.current == 'sent':
|
||||
self.root.ids.sc2.data[data_index]['index'] = index
|
||||
elif self.root.ids.scr_mngr.current == 'trash':
|
||||
self.root.ids.sc3.data[data_index]['index'] = index
|
||||
|
||||
def delete(self, data_index):
|
||||
"""It will make delete using remove function."""
|
||||
print("delete {}".format(data_index))
|
||||
self._remove(data_index)
|
||||
|
||||
def archive(self, data_index):
|
||||
"""It will make archieve using remove function."""
|
||||
print("archive {}".format(data_index))
|
||||
self._remove(data_index)
|
||||
|
||||
def _remove(self, data_index):
|
||||
"""It will remove message by resetting the values in recycleview data."""
|
||||
if self.root.ids.scr_mngr.current == 'inbox':
|
||||
self.root.ids.sc1.data.pop(data_index)
|
||||
self.root.ids.sc1.data = [{
|
||||
'data_index': i,
|
||||
'index': d['index'],
|
||||
'height': d['height'],
|
||||
'text': d['text']}
|
||||
for i, d in enumerate(self.root.ids.sc1.data)
|
||||
]
|
||||
elif self.root.ids.scr_mngr.current == 'sent':
|
||||
self.root.ids.sc2.data.pop(data_index)
|
||||
self.root.ids.sc2.data = [{
|
||||
'data_index': i,
|
||||
'index': d['index'],
|
||||
'height': d['height'],
|
||||
'text': d['text']}
|
||||
for i, d in enumerate(self.root.ids.sc2.data)
|
||||
]
|
||||
elif self.root.ids.scr_mngr.current == 'trash':
|
||||
self.root.ids.sc3.data.pop(data_index)
|
||||
self.root.ids.sc3.data = [{
|
||||
'data_index': i,
|
||||
'index': d['index'],
|
||||
'height': d['height'],
|
||||
'text': d['text']}
|
||||
for i, d in enumerate(self.root.ids.sc3.data)
|
||||
]
|
||||
|
||||
def getInboxMessageDetail(self, instance):
|
||||
"""It will get message detail after make selected message description."""
|
||||
try:
|
||||
self.root.ids.scr_mngr.current = 'page'
|
||||
except AttributeError:
|
||||
self.parent.manager.current = 'page'
|
||||
print('Message Clicked {}'.format(instance))
|
||||
|
||||
@staticmethod
|
||||
def getCurrentAccount():
|
||||
"""It uses to get current account label."""
|
||||
return BMConfigParser().get(state.association, 'label') + '({})'.format(state.association)
|
||||
|
||||
|
||||
class Navigator(NavigationDrawer):
|
||||
"""Navigator class uses NavigationDrawer.
|
||||
|
||||
It is an UI panel that shows our app's main navigation menu
|
||||
It is hidden when not in use, but appears when the user swipes
|
||||
a finger from the left edge of the screen or, when at the top
|
||||
level of the app, the user touches the drawer icon in the app bar
|
||||
"""
|
||||
|
||||
image_source = StringProperty('images/qidenticon_two.png')
|
||||
title = StringProperty('Navigation')
|
||||
|
||||
|
||||
class Inbox(Screen):
|
||||
"""Inbox Screen uses screen to show widgets of screens."""
|
||||
|
||||
data = ListProperty()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Inbox, self).__init__(*args, **kwargs)
|
||||
if state.association == '':
|
||||
state.association = Navigator().ids.btn.text
|
||||
Clock.schedule_once(self.init_ui, 0)
|
||||
|
||||
def init_ui(self, dt=0):
|
||||
"""Clock Schdule for method inbox accounts."""
|
||||
self.inboxaccounts()
|
||||
print(dt)
|
||||
|
||||
def inboxaccounts(self):
|
||||
"""Load inbox accounts."""
|
||||
account = state.association
|
||||
self.loadMessagelist(account, 'All', '')
|
||||
|
||||
def loadMessagelist(self, account, where="", what=""):
|
||||
"""Load Inbox list for inbox messages."""
|
||||
xAddress = "toaddress"
|
||||
queryreturn = kivy_helper_search.search_sql(
|
||||
xAddress, account, 'inbox', where, what, False)
|
||||
if queryreturn:
|
||||
self.data = [{
|
||||
'data_index': i,
|
||||
'index': 1,
|
||||
'height': 48,
|
||||
'text': row[4]}
|
||||
for i, row in enumerate(queryreturn)
|
||||
]
|
||||
else:
|
||||
self.data = [{
|
||||
'data_index': 1,
|
||||
'index': 1,
|
||||
'height': 48,
|
||||
'text': "yet no message for this account!!!!!!!!!!!!!"}
|
||||
]
|
||||
|
||||
|
||||
class Page(Screen):
|
||||
pass
|
||||
|
||||
|
||||
class AddressSuccessful(Screen):
|
||||
pass
|
||||
|
||||
|
||||
class Sent(Screen):
|
||||
"""Sent Screen uses screen to show widgets of screens."""
|
||||
|
||||
data = ListProperty()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Sent, self).__init__(*args, **kwargs)
|
||||
if state.association == '':
|
||||
state.association = Navigator().ids.btn.text
|
||||
Clock.schedule_once(self.init_ui, 0)
|
||||
|
||||
def init_ui(self, dt=0):
|
||||
"""Clock Schdule for method sent accounts."""
|
||||
self.sentaccounts()
|
||||
print(dt)
|
||||
|
||||
def sentaccounts(self):
|
||||
"""Load sent accounts."""
|
||||
account = state.association
|
||||
self.loadSent(account, 'All', '')
|
||||
|
||||
def loadSent(self, account, where="", what=""):
|
||||
"""Load Sent list for Sent messages."""
|
||||
xAddress = 'fromaddress'
|
||||
queryreturn = kivy_helper_search.search_sql(
|
||||
xAddress, account, "sent", where, what, False)
|
||||
if queryreturn:
|
||||
self.data = [{
|
||||
'data_index': i,
|
||||
'index': 1,
|
||||
'height': 48,
|
||||
'text': row[2]}
|
||||
for i, row in enumerate(queryreturn)
|
||||
]
|
||||
else:
|
||||
self.data = [{
|
||||
'data_index': 1,
|
||||
'index': 1,
|
||||
'height': 48,
|
||||
'text': "yet no message for this account!!!!!!!!!!!!!"}
|
||||
]
|
||||
|
||||
|
||||
class Trash(Screen):
|
||||
"""Trash Screen uses screen to show widgets of screens."""
|
||||
|
||||
data = ListProperty()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Trash, self).__init__(*args, **kwargs)
|
||||
if state.association == '':
|
||||
state.association = Navigator().ids.btn.text
|
||||
Clock.schedule_once(self.init_ui, 0)
|
||||
|
||||
def init_ui(self, dt=0):
|
||||
"""Clock Schdule for method inbox accounts."""
|
||||
self.inboxaccounts()
|
||||
print(dt)
|
||||
|
||||
def inboxaccounts(self):
|
||||
"""Load inbox accounts."""
|
||||
account = state.association
|
||||
self.loadTrashlist(account, 'All', '')
|
||||
|
||||
def loadTrashlist(self, account, where="", what=""):
|
||||
"""Load Trash list for trashed messages."""
|
||||
xAddress = "toaddress"
|
||||
queryreturn = kivy_helper_search.search_sql(
|
||||
xAddress, account, 'trash', where, what, False)
|
||||
if queryreturn:
|
||||
self.data = [{
|
||||
'data_index': i,
|
||||
'index': 1,
|
||||
'height': 48,
|
||||
'text': row[4]}
|
||||
for i, row in enumerate(queryreturn)
|
||||
]
|
||||
else:
|
||||
self.data = [{
|
||||
'data_index': 1,
|
||||
'index': 1,
|
||||
'height': 48,
|
||||
'text': "yet no message for this account!!!!!!!!!!!!!"}
|
||||
]
|
||||
|
||||
|
||||
class Dialog(Screen):
|
||||
"""Dialog Screen uses screen to show widgets of screens."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Test(Screen):
|
||||
"""Test Screen uses screen to show widgets of screens."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Create(Screen):
|
||||
"""Create Screen uses screen to show widgets of screens."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Create, self).__init__(*args, **kwargs)
|
||||
|
||||
def send(self):
|
||||
"""Send message from one address to another."""
|
||||
fromAddress = self.ids.spinner_id.text
|
||||
# For now we are using static address i.e we are not using recipent field value.
|
||||
toAddress = "BM-2cWyUfBdY2FbgyuCb7abFZ49JYxSzUhNFe"
|
||||
message = self.ids.message.text
|
||||
subject = self.ids.subject.text
|
||||
encoding = 3
|
||||
print("message: ", self.ids.message.text)
|
||||
sendMessageToPeople = True
|
||||
if sendMessageToPeople:
|
||||
if toAddress != '':
|
||||
status, addressVersionNumber, streamNumber, ripe = decodeAddress(
|
||||
toAddress)
|
||||
if status == 'success':
|
||||
toAddress = addBMIfNotPresent(toAddress)
|
||||
|
||||
if addressVersionNumber > 4 or addressVersionNumber <= 1:
|
||||
print("addressVersionNumber > 4 or addressVersionNumber <= 1")
|
||||
if streamNumber > 1 or streamNumber == 0:
|
||||
print("streamNumber > 1 or streamNumber == 0")
|
||||
if statusIconColor == 'red':
|
||||
print("shared.statusIconColor == 'red'")
|
||||
stealthLevel = BMConfigParser().safeGetInt(
|
||||
'bitmessagesettings', 'ackstealthlevel')
|
||||
ackdata = genAckPayload(streamNumber, stealthLevel)
|
||||
t = ()
|
||||
sqlExecute(
|
||||
'''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
|
||||
'',
|
||||
toAddress,
|
||||
ripe,
|
||||
fromAddress,
|
||||
subject,
|
||||
message,
|
||||
ackdata,
|
||||
int(time.time()),
|
||||
int(time.time()),
|
||||
0,
|
||||
'msgqueued',
|
||||
0,
|
||||
'sent',
|
||||
encoding,
|
||||
BMConfigParser().getint('bitmessagesettings', 'ttl'))
|
||||
toLabel = ''
|
||||
queues.workerQueue.put(('sendmessage', toAddress))
|
||||
print("sqlExecute successfully ##### ##################")
|
||||
self.ids.message.text = ''
|
||||
self.ids.spinner_id.text = '<select>'
|
||||
self.ids.subject.text = ''
|
||||
self.ids.recipent.text = ''
|
||||
return None
|
||||
|
||||
def cancel(self):
|
||||
"""Reset values for send message."""
|
||||
self.ids.message.text = ''
|
||||
self.ids.spinner_id.text = '<select>'
|
||||
self.ids.subject.text = ''
|
||||
self.ids.recipent.text = ''
|
||||
return None
|
||||
|
||||
|
||||
class NewIdentity(Screen):
|
||||
"""Create new address for PyBitmessage."""
|
||||
|
||||
is_active = BooleanProperty(False)
|
||||
checked = StringProperty("")
|
||||
# self.manager.parent.ids.create.children[0].source = 'images/plus-4-xxl.png'
|
||||
|
||||
def generateaddress(self):
|
||||
"""Generate new address."""
|
||||
if self.checked == 'use a random number generator to make an address':
|
||||
queues.apiAddressGeneratorReturnQueue.queue.clear()
|
||||
streamNumberForAddress = 1
|
||||
label = self.ids.label.text
|
||||
eighteenByteRipe = False
|
||||
nonceTrialsPerByte = 1000
|
||||
payloadLengthExtraBytes = 1000
|
||||
|
||||
queues.addressGeneratorQueue.put((
|
||||
'createRandomAddress',
|
||||
4, streamNumberForAddress,
|
||||
label, 1, "", eighteenByteRipe,
|
||||
nonceTrialsPerByte,
|
||||
payloadLengthExtraBytes)
|
||||
)
|
||||
self.manager.current = 'add_sucess'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NavigateApp().run()
|
|
@ -353,8 +353,8 @@ class Main(object):
|
|||
bitmessagecurses.runwrapper()
|
||||
elif state.kivy:
|
||||
config.remove_option('bitmessagesettings', 'dontconnect')
|
||||
from bitmessagekivy.mpybit import NavigateApp
|
||||
NavigateApp().run()
|
||||
# from bitmessagekivy.mpybit import NavigateApp
|
||||
# NavigateApp().run()
|
||||
else:
|
||||
import bitmessageqt
|
||||
bitmessageqt.run()
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Andrés Rodríguez and KivyMD contributors
|
||||
|
||||
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.
|
|
@ -1,6 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
path = os.path.dirname(__file__)
|
||||
fonts_path = os.path.join(path, "fonts/")
|
||||
images_path = os.path.join(path, 'images/')
|
|
@ -1,254 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty, ListProperty, OptionProperty
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivymd.color_definitions import colors
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivy.uix.accordion import Accordion, AccordionItem
|
||||
from kivymd.backgroundcolorbehavior import BackgroundColorBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
|
||||
class MDAccordionItemTitleLayout(ThemableBehavior, BackgroundColorBehavior, BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class MDAccordion(ThemableBehavior, BackgroundColorBehavior, Accordion):
|
||||
pass
|
||||
|
||||
|
||||
class MDAccordionItem(ThemableBehavior, AccordionItem):
|
||||
title_theme_color = OptionProperty(None, allownone=True,
|
||||
options=['Primary', 'Secondary', 'Hint',
|
||||
'Error', 'Custom'])
|
||||
''' Color theme for title text and icon '''
|
||||
|
||||
title_color = ListProperty(None, allownone=True)
|
||||
''' Color for title text and icon if `title_theme_color` is Custom '''
|
||||
|
||||
background_color = ListProperty(None, allownone=True)
|
||||
''' Color for the background of the accordian item title in rgba format.
|
||||
'''
|
||||
|
||||
divider_color = ListProperty(None, allownone=True)
|
||||
''' Color for dividers between different titles in rgba format
|
||||
To remove the divider set a color with an alpha of 0.
|
||||
'''
|
||||
|
||||
indicator_color = ListProperty(None, allownone=True)
|
||||
''' Color for the indicator on the side of the active item in rgba format
|
||||
To remove the indicator set a color with an alpha of 0.
|
||||
'''
|
||||
|
||||
font_style = OptionProperty(
|
||||
'Subhead', options=['Body1', 'Body2', 'Caption', 'Subhead', 'Title',
|
||||
'Headline', 'Display1', 'Display2', 'Display3',
|
||||
'Display4', 'Button', 'Icon'])
|
||||
''' Font style to use for the title text '''
|
||||
|
||||
title_template = StringProperty('MDAccordionItemTitle')
|
||||
''' Template to use for the title '''
|
||||
|
||||
icon = StringProperty(None,allownone=True)
|
||||
''' Icon name to use when this item is expanded '''
|
||||
|
||||
icon_expanded = StringProperty('chevron-up')
|
||||
''' Icon name to use when this item is expanded '''
|
||||
|
||||
icon_collapsed = StringProperty('chevron-down')
|
||||
''' Icon name to use when this item is collapsed '''
|
||||
|
||||
|
||||
Builder.load_string('''
|
||||
#:import MDLabel kivymd.label.MDLabel
|
||||
#:import md_icons kivymd.icon_definitions.md_icons
|
||||
|
||||
|
||||
<MDAccordionItem>:
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: self.background_color or self.theme_cls.primary_color
|
||||
Rectangle:
|
||||
size:self.size
|
||||
pos:self.pos
|
||||
|
||||
PushMatrix
|
||||
Translate:
|
||||
xy: (dp(2),0) if self.orientation == 'vertical' else (0,dp(2))
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
Color:
|
||||
rgba: self.divider_color or self.theme_cls.divider_color
|
||||
Rectangle:
|
||||
size:(dp(1),self.height) if self.orientation == 'horizontal' else (self.width,dp(1))
|
||||
pos:self.pos
|
||||
Color:
|
||||
rgba: [0,0,0,0] if self.collapse else (self.indicator_color or self.theme_cls.accent_color)
|
||||
Rectangle:
|
||||
size:(dp(2),self.height) if self.orientation == 'vertical' else (self.width,dp(2))
|
||||
pos:self.pos
|
||||
|
||||
[MDAccordionItemTitle@MDAccordionItemTitleLayout]:
|
||||
padding: '12dp'
|
||||
spacing: '12dp'
|
||||
orientation: 'horizontal' if ctx.item.orientation=='vertical' else 'vertical'
|
||||
canvas:
|
||||
PushMatrix
|
||||
Translate:
|
||||
xy: (-dp(2),0) if ctx.item.orientation == 'vertical' else (0,-dp(2))
|
||||
|
||||
Color:
|
||||
rgba: self.background_color or self.theme_cls.primary_color
|
||||
Rectangle:
|
||||
size:self.size
|
||||
pos:self.pos
|
||||
|
||||
canvas.after:
|
||||
Color:
|
||||
rgba: [0,0,0,0] if ctx.item.collapse else (ctx.item.indicator_color or self.theme_cls.accent_color)
|
||||
Rectangle:
|
||||
size:(dp(2),self.height) if ctx.item.orientation == 'vertical' else (self.width,dp(2))
|
||||
pos:self.pos
|
||||
PopMatrix
|
||||
MDLabel:
|
||||
id:_icon
|
||||
theme_text_color:ctx.item.title_theme_color if ctx.item.icon else 'Custom'
|
||||
text_color:ctx.item.title_color if ctx.item.icon else [0,0,0,0]
|
||||
text: md_icons[ctx.item.icon if ctx.item.icon else 'menu']
|
||||
font_style:'Icon'
|
||||
size_hint: (None,1) if ctx.item.orientation == 'vertical' else (1,None)
|
||||
size: ((self.texture_size[0],1) if ctx.item.orientation == 'vertical' else (1,self.texture_size[1])) \
|
||||
if ctx.item.icon else (0,0)
|
||||
text_size: (self.width, None) if ctx.item.orientation=='vertical' else (None,self.width)
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: 90 if ctx.item.orientation == 'horizontal' else 0
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
MDLabel:
|
||||
id:_label
|
||||
theme_text_color:ctx.item.title_theme_color
|
||||
text_color:ctx.item.title_color
|
||||
text: ctx.item.title
|
||||
font_style:ctx.item.font_style
|
||||
text_size: (self.width, None) if ctx.item.orientation=='vertical' else (None,self.width)
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: 90 if ctx.item.orientation == 'horizontal' else 0
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
|
||||
MDLabel:
|
||||
id:_expand_icon
|
||||
theme_text_color:ctx.item.title_theme_color
|
||||
text_color:ctx.item.title_color
|
||||
font_style:'Icon'
|
||||
size_hint: (None,1) if ctx.item.orientation == 'vertical' else (1,None)
|
||||
size: (self.texture_size[0],1) if ctx.item.orientation == 'vertical' else (1,self.texture_size[1])
|
||||
text:md_icons[ctx.item.icon_collapsed if ctx.item.collapse else ctx.item.icon_expanded]
|
||||
halign: 'right' if ctx.item.orientation=='vertical' else 'center'
|
||||
#valign: 'middle' if ctx.item.orientation=='vertical' else 'bottom'
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: 90 if ctx.item.orientation == 'horizontal' else 0
|
||||
origin:self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
|
||||
''')
|
||||
|
||||
if __name__ == '__main__':
|
||||
from kivy.app import App
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
class AccordionApp(App):
|
||||
theme_cls = ThemeManager()
|
||||
|
||||
def build(self):
|
||||
# self.theme_cls.primary_palette = 'Indigo'
|
||||
return Builder.load_string("""
|
||||
#:import MDLabel kivymd.label.MDLabel
|
||||
#:import MDList kivymd.list.MDList
|
||||
#:import OneLineListItem kivymd.list.OneLineListItem
|
||||
BoxLayout:
|
||||
spacing: '64dp'
|
||||
MDAccordion:
|
||||
orientation:'vertical'
|
||||
MDAccordionItem:
|
||||
title:'Item 1'
|
||||
icon: 'home'
|
||||
ScrollView:
|
||||
MDList:
|
||||
OneLineListItem:
|
||||
text: "Subitem 1"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
OneLineListItem:
|
||||
text: "Subitem 2"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
OneLineListItem:
|
||||
text: "Subitem 3"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
MDAccordionItem:
|
||||
title:'Item 2'
|
||||
icon: 'globe'
|
||||
ScrollView:
|
||||
MDList:
|
||||
OneLineListItem:
|
||||
text: "Subitem 4"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
OneLineListItem:
|
||||
text: "Subitem 5"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
OneLineListItem:
|
||||
text: "Subitem 6"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
MDAccordionItem:
|
||||
title:'Item 3'
|
||||
ScrollView:
|
||||
MDList:
|
||||
OneLineListItem:
|
||||
text: "Subitem 7"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
OneLineListItem:
|
||||
text: "Subitem 8"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
OneLineListItem:
|
||||
text: "Subitem 9"
|
||||
theme_text_color: 'Custom'
|
||||
text_color: [1,1,1,1]
|
||||
MDAccordion:
|
||||
orientation:'horizontal'
|
||||
MDAccordionItem:
|
||||
title:'Item 1'
|
||||
icon: 'home'
|
||||
MDLabel:
|
||||
text:'Content 1'
|
||||
theme_text_color:'Primary'
|
||||
MDAccordionItem:
|
||||
title:'Item 2'
|
||||
MDLabel:
|
||||
text:'Content 2'
|
||||
theme_text_color:'Primary'
|
||||
MDAccordionItem:
|
||||
title:'Item 3'
|
||||
MDLabel:
|
||||
text:'Content 3'
|
||||
theme_text_color:'Primary'
|
||||
""")
|
||||
|
||||
|
||||
AccordionApp().run()
|
|
@ -1,23 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import BoundedNumericProperty, ReferenceListProperty
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
Builder.load_string('''
|
||||
<BackgroundColorBehavior>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.background_color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
''')
|
||||
|
||||
|
||||
class BackgroundColorBehavior(Widget):
|
||||
r = BoundedNumericProperty(1., min=0., max=1.)
|
||||
g = BoundedNumericProperty(1., min=0., max=1.)
|
||||
b = BoundedNumericProperty(1., min=0., max=1.)
|
||||
a = BoundedNumericProperty(0., min=0., max=1.)
|
||||
|
||||
background_color = ReferenceListProperty(r, g, b, a)
|
|
@ -1,211 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Bottom Sheets
|
||||
=============
|
||||
|
||||
`Material Design spec Bottom Sheets page <http://www.google.com/design/spec/components/bottom-sheets.html>`_
|
||||
|
||||
In this module there's the :class:`MDBottomSheet` class which will let you implement your own Material Design Bottom Sheets, and there are two classes called :class:`MDListBottomSheet` and :class:`MDGridBottomSheet` implementing the ones mentioned in the spec.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. note::
|
||||
|
||||
These widgets are designed to be called from Python code only.
|
||||
|
||||
For :class:`MDListBottomSheet`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bs = MDListBottomSheet()
|
||||
bs.add_item("Here's an item with text only", lambda x: x)
|
||||
bs.add_item("Here's an item with an icon", lambda x: x, icon='md-cast')
|
||||
bs.add_item("Here's another!", lambda x: x, icon='md-nfc')
|
||||
bs.open()
|
||||
|
||||
For :class:`MDListBottomSheet`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bs = MDGridBottomSheet()
|
||||
bs.add_item("Facebook", lambda x: x, icon_src='./assets/facebook-box.png')
|
||||
bs.add_item("YouTube", lambda x: x, icon_src='./assets/youtube-play.png')
|
||||
bs.add_item("Twitter", lambda x: x, icon_src='./assets/twitter.png')
|
||||
bs.add_item("Da Cloud", lambda x: x, icon_src='./assets/cloud-upload.png')
|
||||
bs.add_item("Camera", lambda x: x, icon_src='./assets/camera.png')
|
||||
bs.open()
|
||||
|
||||
API
|
||||
---
|
||||
'''
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import ObjectProperty, 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.backgroundcolorbehavior import BackgroundColorBehavior
|
||||
from kivymd.label import MDLabel
|
||||
from kivymd.list import MDList, OneLineListItem, ILeftBody, \
|
||||
OneLineIconListItem
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string('''
|
||||
<MDBottomSheet>
|
||||
background: 'atlas://data/images/defaulttheme/action_group_disabled'
|
||||
background_color: 0,0,0,.8
|
||||
sv: sv
|
||||
upper_padding: upper_padding
|
||||
gl_content: gl_content
|
||||
ScrollView:
|
||||
id: sv
|
||||
do_scroll_x: False
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
orientation: 'vertical'
|
||||
padding: 0,1,0,0
|
||||
height: upper_padding.height + gl_content.height + 1 # +1 to allow overscroll
|
||||
BsPadding:
|
||||
id: upper_padding
|
||||
size_hint_y: None
|
||||
height: root.height - min(root.width * 9 / 16, gl_content.height)
|
||||
on_release: root.dismiss()
|
||||
BottomSheetContent:
|
||||
id: gl_content
|
||||
size_hint_y: None
|
||||
background_color: root.theme_cls.bg_normal
|
||||
cols: 1
|
||||
''')
|
||||
|
||||
|
||||
class BsPadding(ButtonBehavior, FloatLayout):
|
||||
pass
|
||||
|
||||
|
||||
class BottomSheetContent(BackgroundColorBehavior, GridLayout):
|
||||
pass
|
||||
|
||||
|
||||
class MDBottomSheet(ThemableBehavior, ModalView):
|
||||
sv = ObjectProperty()
|
||||
upper_padding = ObjectProperty()
|
||||
gl_content = ObjectProperty()
|
||||
dismiss_zone_scroll = 1000 # Arbitrary high number
|
||||
|
||||
def open(self, *largs):
|
||||
super(MDBottomSheet, self).open(*largs)
|
||||
Clock.schedule_once(self.set_dismiss_zone, 0)
|
||||
|
||||
def set_dismiss_zone(self, *largs):
|
||||
# Scroll to right below overscroll threshold:
|
||||
self.sv.scroll_y = 1 - self.sv.convert_distance_to_scroll(0, 1)[1]
|
||||
|
||||
# This is a line where m (slope) is 1/6 and b (y-intercept) is 80:
|
||||
self.dismiss_zone_scroll = self.sv.convert_distance_to_scroll(
|
||||
0, (self.height - self.upper_padding.height) * (1 / 6.0) + 80)[
|
||||
1]
|
||||
# Uncomment next line if the limit should just be half of
|
||||
# visible content on open (capped by specs to 16 units to width/9:
|
||||
# self.dismiss_zone_scroll = (self.sv.convert_distance_to_scroll(
|
||||
# 0, self.height - self.upper_padding.height)[1] * 0.50)
|
||||
|
||||
# Check if user has overscrolled enough to dismiss bottom sheet:
|
||||
self.sv.bind(on_scroll_stop=self.check_if_scrolled_to_death)
|
||||
|
||||
def check_if_scrolled_to_death(self, *largs):
|
||||
if self.sv.scroll_y >= 1 + self.dismiss_zone_scroll:
|
||||
self.dismiss()
|
||||
|
||||
def add_widget(self, widget, index=0):
|
||||
if type(widget) == ScrollView:
|
||||
super(MDBottomSheet, self).add_widget(widget, index)
|
||||
else:
|
||||
self.gl_content.add_widget(widget,index)
|
||||
|
||||
|
||||
Builder.load_string('''
|
||||
#:import md_icons kivymd.icon_definitions.md_icons
|
||||
<ListBSIconLeft>
|
||||
font_style: 'Icon'
|
||||
text: u"{}".format(md_icons[root.icon])
|
||||
halign: 'center'
|
||||
theme_text_color: 'Primary'
|
||||
valign: 'middle'
|
||||
''')
|
||||
|
||||
|
||||
class ListBSIconLeft(ILeftBody, MDLabel):
|
||||
icon = StringProperty()
|
||||
|
||||
|
||||
class MDListBottomSheet(MDBottomSheet):
|
||||
mlist = ObjectProperty()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDListBottomSheet, self).__init__(**kwargs)
|
||||
self.mlist = MDList()
|
||||
self.gl_content.add_widget(self.mlist)
|
||||
Clock.schedule_once(self.resize_content_layout, 0)
|
||||
|
||||
def resize_content_layout(self, *largs):
|
||||
self.gl_content.height = self.mlist.height
|
||||
|
||||
def add_item(self, text, callback, icon=None):
|
||||
if icon:
|
||||
item = OneLineIconListItem(text=text, on_release=callback)
|
||||
item.add_widget(ListBSIconLeft(icon=icon))
|
||||
else:
|
||||
item = OneLineListItem(text=text, on_release=callback)
|
||||
|
||||
item.bind(on_release=lambda x: self.dismiss())
|
||||
self.mlist.add_widget(item)
|
||||
|
||||
|
||||
Builder.load_string('''
|
||||
<GridBSItem>
|
||||
orientation: 'vertical'
|
||||
padding: 0, dp(24), 0, 0
|
||||
size_hint_y: None
|
||||
size: dp(64), dp(96)
|
||||
BoxLayout:
|
||||
padding: dp(8), 0, dp(8), dp(8)
|
||||
size_hint_y: None
|
||||
height: dp(48)
|
||||
Image:
|
||||
source: root.source
|
||||
MDLabel:
|
||||
font_style: 'Caption'
|
||||
theme_text_color: 'Secondary'
|
||||
text: root.caption
|
||||
halign: 'center'
|
||||
''')
|
||||
|
||||
|
||||
class GridBSItem(ButtonBehavior, BoxLayout):
|
||||
source = StringProperty()
|
||||
|
||||
caption = StringProperty()
|
||||
|
||||
|
||||
class MDGridBottomSheet(MDBottomSheet):
|
||||
def __init__(self, **kwargs):
|
||||
super(MDGridBottomSheet, self).__init__(**kwargs)
|
||||
self.gl_content.padding = (dp(16), 0, dp(16), dp(24))
|
||||
self.gl_content.height = dp(24)
|
||||
self.gl_content.cols = 3
|
||||
|
||||
def add_item(self, text, callback, icon_src):
|
||||
item = GridBSItem(
|
||||
caption=text,
|
||||
on_release=callback,
|
||||
source=icon_src
|
||||
)
|
||||
item.bind(on_release=lambda x: self.dismiss())
|
||||
if len(self.gl_content.children) % 3 == 0:
|
||||
self.gl_content.height += dp(96)
|
||||
self.gl_content.add_widget(item)
|
|
@ -1,453 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Buttons
|
||||
=======
|
||||
|
||||
`Material Design spec, Buttons page <https://www.google.com/design/spec/components/buttons.html>`_
|
||||
|
||||
`Material Design spec, Buttons: Floating Action Button page <https://www.google.com/design/spec/components/buttons-floating-action-button.html>`_
|
||||
|
||||
TO-DO: DOCUMENT MODULE
|
||||
'''
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivy.properties import StringProperty, BoundedNumericProperty, \
|
||||
ListProperty, AliasProperty, BooleanProperty, NumericProperty, \
|
||||
OptionProperty
|
||||
from kivy.uix.anchorlayout import AnchorLayout
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.animation import Animation
|
||||
from kivymd.backgroundcolorbehavior import BackgroundColorBehavior
|
||||
from kivymd.ripplebehavior import CircularRippleBehavior, \
|
||||
RectangularRippleBehavior
|
||||
from kivymd.elevationbehavior import ElevationBehavior, \
|
||||
RoundElevationBehavior
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.color_definitions import colors
|
||||
|
||||
Builder.load_string('''
|
||||
#:import md_icons kivymd.icon_definitions.md_icons
|
||||
#:import colors kivymd.color_definitions.colors
|
||||
#:import MDLabel kivymd.label.MDLabel
|
||||
<MDIconButton>
|
||||
size_hint: (None, None)
|
||||
size: (dp(48), dp(48))
|
||||
padding: dp(12)
|
||||
theme_text_color: 'Primary'
|
||||
MDLabel:
|
||||
id: _label
|
||||
font_style: 'Icon'
|
||||
text: u"{}".format(md_icons[root.icon])
|
||||
halign: 'center'
|
||||
theme_text_color: root.theme_text_color
|
||||
text_color: root.text_color
|
||||
opposite_colors: root.opposite_colors
|
||||
valign: 'middle'
|
||||
|
||||
<MDFlatButton>
|
||||
canvas:
|
||||
Color:
|
||||
#rgba: self.background_color if self.state == 'normal' else self._bg_color_down
|
||||
rgba: self._current_button_color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
height: dp(36)
|
||||
width: _label.texture_size[0] + dp(16)
|
||||
padding: (dp(8), 0)
|
||||
theme_text_color: 'Custom'
|
||||
text_color: root.theme_cls.primary_color
|
||||
MDLabel:
|
||||
id: _label
|
||||
text: root._text
|
||||
font_style: 'Button'
|
||||
size_hint_x: None
|
||||
text_size: (None, root.height)
|
||||
height: self.texture_size[1]
|
||||
theme_text_color: root.theme_text_color
|
||||
text_color: root.text_color
|
||||
valign: 'middle'
|
||||
halign: 'center'
|
||||
opposite_colors: root.opposite_colors
|
||||
|
||||
<MDRaisedButton>:
|
||||
canvas:
|
||||
Clear
|
||||
Color:
|
||||
rgba: self.background_color_disabled if self.disabled else \
|
||||
(self.background_color if self.state == 'normal' else self.background_color_down)
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
anchor_x: 'center'
|
||||
anchor_y: 'center'
|
||||
background_color: root.theme_cls.primary_color
|
||||
background_color_down: root.theme_cls.primary_dark
|
||||
background_color_disabled: root.theme_cls.divider_color
|
||||
theme_text_color: 'Primary'
|
||||
MDLabel:
|
||||
id: label
|
||||
font_style: 'Button'
|
||||
text: root._text
|
||||
size_hint: None, None
|
||||
width: root.width
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
theme_text_color: root.theme_text_color
|
||||
text_color: root.text_color
|
||||
opposite_colors: root.opposite_colors
|
||||
disabled: root.disabled
|
||||
halign: 'center'
|
||||
valign: 'middle'
|
||||
|
||||
<MDFloatingActionButton>:
|
||||
canvas:
|
||||
Clear
|
||||
Color:
|
||||
rgba: self.background_color_disabled if self.disabled else \
|
||||
(self.background_color if self.state == 'normal' else self.background_color_down)
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
anchor_x: 'center'
|
||||
anchor_y: 'center'
|
||||
background_color: root.theme_cls.accent_color
|
||||
background_color_down: root.theme_cls.accent_dark
|
||||
background_color_disabled: root.theme_cls.divider_color
|
||||
theme_text_color: 'Primary'
|
||||
MDLabel:
|
||||
id: label
|
||||
font_style: 'Icon'
|
||||
text: u"{}".format(md_icons[root.icon])
|
||||
size_hint: None, None
|
||||
size: dp(24), dp(24)
|
||||
text_size: self.size
|
||||
theme_text_color: root.theme_text_color
|
||||
text_color: root.text_color
|
||||
opposite_colors: root.opposite_colors
|
||||
disabled: root.disabled
|
||||
halign: 'center'
|
||||
valign: 'middle'
|
||||
''')
|
||||
|
||||
|
||||
class MDIconButton(CircularRippleBehavior, ButtonBehavior, BoxLayout):
|
||||
icon = StringProperty('circle')
|
||||
theme_text_color = OptionProperty(None, allownone=True,
|
||||
options=['Primary', 'Secondary', 'Hint',
|
||||
'Error', 'Custom'])
|
||||
text_color = ListProperty(None, allownone=True)
|
||||
opposite_colors = BooleanProperty(False)
|
||||
|
||||
|
||||
class MDFlatButton(ThemableBehavior, RectangularRippleBehavior,
|
||||
ButtonBehavior, BackgroundColorBehavior, AnchorLayout):
|
||||
width = BoundedNumericProperty(dp(64), min=dp(64), max=None,
|
||||
errorhandler=lambda x: dp(64))
|
||||
|
||||
text_color = ListProperty()
|
||||
|
||||
text = StringProperty('')
|
||||
theme_text_color = OptionProperty(None, allownone=True,
|
||||
options=['Primary', 'Secondary', 'Hint',
|
||||
'Error', 'Custom'])
|
||||
text_color = ListProperty(None, allownone=True)
|
||||
|
||||
_text = StringProperty('')
|
||||
_bg_color_down = ListProperty([0, 0, 0, 0])
|
||||
_current_button_color = ListProperty([0, 0, 0, 0])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDFlatButton, self).__init__(**kwargs)
|
||||
self._current_button_color = self.background_color
|
||||
self._bg_color_down = get_color_from_hex(
|
||||
colors[self.theme_cls.theme_style]['FlatButtonDown'])
|
||||
|
||||
Clock.schedule_once(lambda x: self.ids._label.bind(
|
||||
texture_size=self.update_width_on_label_texture))
|
||||
|
||||
def update_width_on_label_texture(self, instance, value):
|
||||
self.ids._label.width = value[0]
|
||||
|
||||
def on_text(self, instance, value):
|
||||
self._text = value.upper()
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if touch.is_mouse_scrolling:
|
||||
return False
|
||||
elif not self.collide_point(touch.x, touch.y):
|
||||
return False
|
||||
elif self in touch.ud:
|
||||
return False
|
||||
elif self.disabled:
|
||||
return False
|
||||
else:
|
||||
self.fade_bg = Animation(duration=.2, _current_button_color=get_color_from_hex(
|
||||
colors[self.theme_cls.theme_style]['FlatButtonDown']))
|
||||
self.fade_bg.start(self)
|
||||
return super(MDFlatButton, self).on_touch_down(touch)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if touch.grab_current is self:
|
||||
self.fade_bg.stop_property(self, '_current_button_color')
|
||||
Animation(duration=.05, _current_button_color=self.background_color).start(self)
|
||||
return super(MDFlatButton, self).on_touch_up(touch)
|
||||
|
||||
|
||||
class MDRaisedButton(ThemableBehavior, RectangularRippleBehavior,
|
||||
ElevationBehavior, ButtonBehavior,
|
||||
AnchorLayout):
|
||||
_bg_color_down = ListProperty([])
|
||||
background_color = ListProperty()
|
||||
background_color_down = ListProperty()
|
||||
background_color_disabled = ListProperty()
|
||||
theme_text_color = OptionProperty(None, allownone=True,
|
||||
options=['Primary', 'Secondary', 'Hint',
|
||||
'Error', 'Custom'])
|
||||
text_color = ListProperty(None, allownone=True)
|
||||
|
||||
def _get_bg_color_down(self):
|
||||
return self._bg_color_down
|
||||
|
||||
def _set_bg_color_down(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._bg_color_down = get_color_from_hex(
|
||||
colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._bg_color_down[3] = alpha
|
||||
elif len(color) == 4:
|
||||
self._bg_color_down = color
|
||||
|
||||
background_color_down = AliasProperty(_get_bg_color_down,
|
||||
_set_bg_color_down,
|
||||
bind=('_bg_color_down',))
|
||||
|
||||
_bg_color_disabled = ListProperty([])
|
||||
|
||||
def _get_bg_color_disabled(self):
|
||||
return self._bg_color_disabled
|
||||
|
||||
def _set_bg_color_disabled(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._bg_color_disabled = get_color_from_hex(
|
||||
colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._bg_color_disabled[3] = alpha
|
||||
elif len(color) == 4:
|
||||
self._bg_color_disabled = color
|
||||
|
||||
background_color_disabled = AliasProperty(_get_bg_color_disabled,
|
||||
_set_bg_color_disabled,
|
||||
bind=('_bg_color_disabled',))
|
||||
|
||||
_elev_norm = NumericProperty(2)
|
||||
|
||||
def _get_elev_norm(self):
|
||||
return self._elev_norm
|
||||
|
||||
def _set_elev_norm(self, value):
|
||||
self._elev_norm = value if value <= 12 else 12
|
||||
self._elev_raised = (value + 6) if value + 6 <= 12 else 12
|
||||
self.elevation = self._elev_norm
|
||||
|
||||
elevation_normal = AliasProperty(_get_elev_norm, _set_elev_norm,
|
||||
bind=('_elev_norm',))
|
||||
|
||||
_elev_raised = NumericProperty(8)
|
||||
|
||||
def _get_elev_raised(self):
|
||||
return self._elev_raised
|
||||
|
||||
def _set_elev_raised(self, value):
|
||||
self._elev_raised = value if value + self._elev_norm <= 12 else 12
|
||||
|
||||
elevation_raised = AliasProperty(_get_elev_raised, _set_elev_raised,
|
||||
bind=('_elev_raised',))
|
||||
|
||||
text = StringProperty()
|
||||
|
||||
_text = StringProperty()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDRaisedButton, self).__init__(**kwargs)
|
||||
self.elevation_press_anim = Animation(elevation=self.elevation_raised,
|
||||
duration=.2, t='out_quad')
|
||||
self.elevation_release_anim = Animation(
|
||||
elevation=self.elevation_normal, duration=.2, t='out_quad')
|
||||
|
||||
def on_disabled(self, instance, value):
|
||||
if value:
|
||||
self.elevation = 0
|
||||
else:
|
||||
self.elevation = self.elevation_normal
|
||||
super(MDRaisedButton, self).on_disabled(instance, value)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if not self.disabled:
|
||||
if touch.is_mouse_scrolling:
|
||||
return False
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
return False
|
||||
if self in touch.ud:
|
||||
return False
|
||||
Animation.cancel_all(self, 'elevation')
|
||||
self.elevation_press_anim.start(self)
|
||||
return super(MDRaisedButton, self).on_touch_down(touch)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if not self.disabled:
|
||||
if touch.grab_current is not self:
|
||||
return super(ButtonBehavior, self).on_touch_up(touch)
|
||||
Animation.cancel_all(self, 'elevation')
|
||||
self.elevation_release_anim.start(self)
|
||||
else:
|
||||
Animation.cancel_all(self, 'elevation')
|
||||
self.elevation = 0
|
||||
return super(MDRaisedButton, self).on_touch_up(touch)
|
||||
|
||||
def on_text(self, instance, text):
|
||||
self._text = text.upper()
|
||||
|
||||
def on__elev_norm(self, instance, value):
|
||||
self.elevation_release_anim = Animation(elevation=value,
|
||||
duration=.2, t='out_quad')
|
||||
|
||||
def on__elev_raised(self, instance, value):
|
||||
self.elevation_press_anim = Animation(elevation=value,
|
||||
duration=.2, t='out_quad')
|
||||
|
||||
|
||||
class MDFloatingActionButton(ThemableBehavior, CircularRippleBehavior,
|
||||
RoundElevationBehavior, ButtonBehavior,
|
||||
AnchorLayout):
|
||||
_bg_color_down = ListProperty([])
|
||||
background_color = ListProperty()
|
||||
background_color_down = ListProperty()
|
||||
background_color_disabled = ListProperty()
|
||||
theme_text_color = OptionProperty(None, allownone=True,
|
||||
options=['Primary', 'Secondary', 'Hint',
|
||||
'Error', 'Custom'])
|
||||
text_color = ListProperty(None, allownone=True)
|
||||
|
||||
def _get_bg_color_down(self):
|
||||
return self._bg_color_down
|
||||
|
||||
def _set_bg_color_down(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._bg_color_down = get_color_from_hex(
|
||||
colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._bg_color_down[3] = alpha
|
||||
elif len(color) == 4:
|
||||
self._bg_color_down = color
|
||||
|
||||
background_color_down = AliasProperty(_get_bg_color_down,
|
||||
_set_bg_color_down,
|
||||
bind=('_bg_color_down',))
|
||||
|
||||
_bg_color_disabled = ListProperty([])
|
||||
|
||||
def _get_bg_color_disabled(self):
|
||||
return self._bg_color_disabled
|
||||
|
||||
def _set_bg_color_disabled(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._bg_color_disabled = get_color_from_hex(
|
||||
colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._bg_color_disabled[3] = alpha
|
||||
elif len(color) == 4:
|
||||
self._bg_color_disabled = color
|
||||
|
||||
background_color_disabled = AliasProperty(_get_bg_color_disabled,
|
||||
_set_bg_color_disabled,
|
||||
bind=('_bg_color_disabled',))
|
||||
icon = StringProperty('android')
|
||||
|
||||
_elev_norm = NumericProperty(6)
|
||||
|
||||
def _get_elev_norm(self):
|
||||
return self._elev_norm
|
||||
|
||||
def _set_elev_norm(self, value):
|
||||
self._elev_norm = value if value <= 12 else 12
|
||||
self._elev_raised = (value + 6) if value + 6 <= 12 else 12
|
||||
self.elevation = self._elev_norm
|
||||
|
||||
elevation_normal = AliasProperty(_get_elev_norm, _set_elev_norm,
|
||||
bind=('_elev_norm',))
|
||||
|
||||
# _elev_raised = NumericProperty(12)
|
||||
_elev_raised = NumericProperty(6)
|
||||
|
||||
def _get_elev_raised(self):
|
||||
return self._elev_raised
|
||||
|
||||
def _set_elev_raised(self, value):
|
||||
self._elev_raised = value if value + self._elev_norm <= 12 else 12
|
||||
|
||||
elevation_raised = AliasProperty(_get_elev_raised, _set_elev_raised,
|
||||
bind=('_elev_raised',))
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if self.elevation_raised == 0 and self.elevation_normal + 6 <= 12:
|
||||
self.elevation_raised = self.elevation_normal + 6
|
||||
elif self.elevation_raised == 0:
|
||||
self.elevation_raised = 12
|
||||
|
||||
super(MDFloatingActionButton, self).__init__(**kwargs)
|
||||
|
||||
self.elevation_press_anim = Animation(elevation=self.elevation_raised,
|
||||
duration=.2, t='out_quad')
|
||||
self.elevation_release_anim = Animation(
|
||||
elevation=self.elevation_normal, duration=.2, t='out_quad')
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
ellipse = self.ellipse
|
||||
ripple_rad = self.ripple_rad
|
||||
|
||||
ellipse.size = (ripple_rad, ripple_rad)
|
||||
ellipse.pos = (self.center_x - ripple_rad / 2.,
|
||||
self.center_y - ripple_rad / 2.)
|
||||
|
||||
def on_disabled(self, instance, value):
|
||||
super(MDFloatingActionButton, self).on_disabled(instance, value)
|
||||
if self.disabled:
|
||||
self.elevation = 0
|
||||
else:
|
||||
self.elevation = self.elevation_normal
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if not self.disabled:
|
||||
if touch.is_mouse_scrolling:
|
||||
return False
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
return False
|
||||
if self in touch.ud:
|
||||
return False
|
||||
self.elevation_press_anim.stop(self)
|
||||
self.elevation_press_anim.start(self)
|
||||
return super(MDFloatingActionButton, self).on_touch_down(touch)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if not self.disabled:
|
||||
if touch.grab_current is not self:
|
||||
return super(ButtonBehavior, self).on_touch_up(touch)
|
||||
self.elevation_release_anim.stop(self)
|
||||
self.elevation_release_anim.start(self)
|
||||
return super(MDFloatingActionButton, self).on_touch_up(touch)
|
||||
|
||||
def on_elevation_normal(self, instance, value):
|
||||
self.elevation = value
|
||||
|
||||
def on_elevation_raised(self, instance, value):
|
||||
if self.elevation_raised == 0 and self.elevation_normal + 6 <= 12:
|
||||
self.elevation_raised = self.elevation_normal + 6
|
||||
elif self.elevation_raised == 0:
|
||||
self.elevation_raised = 12
|
|
@ -1,58 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import BoundedNumericProperty, ReferenceListProperty, ListProperty,BooleanProperty
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivymd.elevationbehavior import ElevationBehavior
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivy.metrics import dp
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
Builder.load_string('''
|
||||
<MDCard>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.background_color
|
||||
RoundedRectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
radius: [self.border_radius]
|
||||
Color:
|
||||
rgba: self.theme_cls.divider_color
|
||||
a: self.border_color_a
|
||||
Line:
|
||||
rounded_rectangle: (self.pos[0],self.pos[1],self.size[0],self.size[1],self.border_radius)
|
||||
background_color: self.theme_cls.bg_light
|
||||
|
||||
<MDSeparator>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.theme_cls.divider_color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
''')
|
||||
|
||||
|
||||
class MDSeparator(ThemableBehavior, BoxLayout):
|
||||
""" A separator line """
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MDSeparator, self).__init__(*args, **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, ElevationBehavior, BoxLayout):
|
||||
r = BoundedNumericProperty(1., min=0., max=1.)
|
||||
g = BoundedNumericProperty(1., min=0., max=1.)
|
||||
b = BoundedNumericProperty(1., min=0., max=1.)
|
||||
a = BoundedNumericProperty(0., min=0., max=1.)
|
||||
|
||||
border_radius = BoundedNumericProperty(dp(3),min=0)
|
||||
border_color_a = BoundedNumericProperty(0, min=0., max=1.)
|
||||
background_color = ReferenceListProperty(r, g, b, a)
|
|
@ -1,360 +0,0 @@
|
|||
colors = {
|
||||
'Pink': {
|
||||
'50': 'fce4ec',
|
||||
'100': 'f8bbd0',
|
||||
'200': 'f48fb1',
|
||||
'300': 'f06292',
|
||||
'400': 'ec407a',
|
||||
'500': 'e91e63',
|
||||
'600': 'd81b60',
|
||||
'700': 'C2185B',
|
||||
'800': 'ad1457',
|
||||
'900': '88e4ff',
|
||||
'A100': 'ff80ab',
|
||||
'A400': 'F50057',
|
||||
'A700': 'c51162',
|
||||
'A200': 'ff4081'
|
||||
},
|
||||
|
||||
'Blue': {
|
||||
'200': '90caf9',
|
||||
'900': '0D47A1',
|
||||
'600': '1e88e5',
|
||||
'A100': '82b1ff',
|
||||
'300': '64b5f6',
|
||||
'A400': '2979ff',
|
||||
'700': '1976d2',
|
||||
'50': 'e3f2fd',
|
||||
'A700': '2962ff',
|
||||
'400': '42a5f5',
|
||||
'100': 'bbdefb',
|
||||
'800': '1565c0',
|
||||
'A200': '448aff',
|
||||
'500': '2196f3'
|
||||
},
|
||||
|
||||
'Indigo': {
|
||||
'200': '9fa8da',
|
||||
'900': '1a237e',
|
||||
'600': '3949ab',
|
||||
'A100': '8c9eff',
|
||||
'300': '7986cb',
|
||||
'A400': '3d5afe',
|
||||
'700': '303f9f',
|
||||
'50': 'e8eaf6',
|
||||
'A700': '304ffe',
|
||||
'400': '5c6bc0',
|
||||
'100': 'c5cae9',
|
||||
'800': '283593',
|
||||
'A200': '536dfe',
|
||||
'500': '3f51b5'
|
||||
},
|
||||
|
||||
'BlueGrey': {
|
||||
'200': 'b0bec5',
|
||||
'900': '263238',
|
||||
'600': '546e7a',
|
||||
'300': '90a4ae',
|
||||
'700': '455a64',
|
||||
'50': 'eceff1',
|
||||
'400': '78909c',
|
||||
'100': 'cfd8dc',
|
||||
'800': '37474f',
|
||||
'500': '607d8b'
|
||||
},
|
||||
|
||||
'Brown': {
|
||||
'200': 'bcaaa4',
|
||||
'900': '3e2723',
|
||||
'600': '6d4c41',
|
||||
'300': 'a1887f',
|
||||
'700': '5d4037',
|
||||
'50': 'efebe9',
|
||||
'400': '8d6e63',
|
||||
'100': 'd7ccc8',
|
||||
'800': '4e342e',
|
||||
'500': '795548'
|
||||
},
|
||||
|
||||
'LightBlue': {
|
||||
'200': '81d4fa',
|
||||
'900': '01579B',
|
||||
'600': '039BE5',
|
||||
'A100': '80d8ff',
|
||||
'300': '4fc3f7',
|
||||
'A400': '00B0FF',
|
||||
'700': '0288D1',
|
||||
'50': 'e1f5fe',
|
||||
'A700': '0091EA',
|
||||
'400': '29b6f6',
|
||||
'100': 'b3e5fc',
|
||||
'800': '0277BD',
|
||||
'A200': '40c4ff',
|
||||
'500': '03A9F4'
|
||||
},
|
||||
|
||||
'Purple': {
|
||||
'200': 'ce93d8',
|
||||
'900': '4a148c',
|
||||
'600': '8e24aa',
|
||||
'A100': 'ea80fc',
|
||||
'300': 'ba68c8',
|
||||
'A400': 'D500F9',
|
||||
'700': '7b1fa2',
|
||||
'50': 'f3e5f5',
|
||||
'A700': 'AA00FF',
|
||||
'400': 'ab47bc',
|
||||
'100': 'e1bee7',
|
||||
'800': '6a1b9a',
|
||||
'A200': 'e040fb',
|
||||
'500': '9c27b0'
|
||||
},
|
||||
|
||||
'Grey': {
|
||||
'200': 'eeeeee',
|
||||
'900': '212121',
|
||||
'600': '757575',
|
||||
'300': 'e0e0e0',
|
||||
'700': '616161',
|
||||
'50': 'fafafa',
|
||||
'400': 'bdbdbd',
|
||||
'100': 'f5f5f5',
|
||||
'800': '424242',
|
||||
'500': '9e9e9e'
|
||||
},
|
||||
|
||||
'Yellow': {
|
||||
'200': 'fff59d',
|
||||
'900': 'f57f17',
|
||||
'600': 'fdd835',
|
||||
'A100': 'ffff8d',
|
||||
'300': 'fff176',
|
||||
'A400': 'FFEA00',
|
||||
'700': 'fbc02d',
|
||||
'50': 'fffde7',
|
||||
'A700': 'FFD600',
|
||||
'400': 'ffee58',
|
||||
'100': 'fff9c4',
|
||||
'800': 'f9a825',
|
||||
'A200': 'FFFF00',
|
||||
'500': 'ffeb3b'
|
||||
},
|
||||
|
||||
'LightGreen': {
|
||||
'200': 'c5e1a5',
|
||||
'900': '33691e',
|
||||
'600': '7cb342',
|
||||
'A100': 'ccff90',
|
||||
'300': 'aed581',
|
||||
'A400': '76FF03',
|
||||
'700': '689f38',
|
||||
'50': 'f1f8e9',
|
||||
'A700': '64dd17',
|
||||
'400': '9ccc65',
|
||||
'100': 'dcedc8',
|
||||
'800': '558b2f',
|
||||
'A200': 'b2ff59',
|
||||
'500': '8bc34a'
|
||||
},
|
||||
|
||||
'DeepOrange': {
|
||||
'200': 'ffab91',
|
||||
'900': 'bf36c',
|
||||
'600': 'f4511e',
|
||||
'A100': 'ff9e80',
|
||||
'300': 'ff8a65',
|
||||
'A400': 'FF3D00',
|
||||
'700': 'e64a19',
|
||||
'50': 'fbe9e7',
|
||||
'A700': 'DD2C00',
|
||||
'400': 'ff7043',
|
||||
'100': 'ffccbc',
|
||||
'800': 'd84315',
|
||||
'A200': 'ff6e40',
|
||||
'500': 'ff5722'
|
||||
},
|
||||
|
||||
'Green': {
|
||||
'200': 'a5d6a7',
|
||||
'900': '1b5e20',
|
||||
'600': '43a047',
|
||||
'A100': 'b9f6ca',
|
||||
'300': '81c784',
|
||||
'A400': '00E676',
|
||||
'700': '388e3c',
|
||||
'50': 'e8f5e9',
|
||||
'A700': '00C853',
|
||||
'400': '66bb6a',
|
||||
'100': 'c8e6c9',
|
||||
'800': '2e7d32',
|
||||
'A200': '69f0ae',
|
||||
'500': '4caf50'
|
||||
},
|
||||
|
||||
'Red': {
|
||||
'200': 'ef9a9a',
|
||||
'900': 'b71c1c',
|
||||
'600': 'e53935',
|
||||
'A100': 'ff8a80',
|
||||
'300': 'e57373',
|
||||
'A400': 'ff1744',
|
||||
'700': 'd32f2f',
|
||||
'50': 'ffebee',
|
||||
'A700': 'd50000',
|
||||
'400': 'ef5350',
|
||||
'100': 'ffcdd2',
|
||||
'800': 'c62828',
|
||||
'A200': 'ff5252',
|
||||
'500': 'f44336'
|
||||
},
|
||||
|
||||
'Teal': {
|
||||
'200': '80cbc4',
|
||||
'900': '004D40',
|
||||
'600': '00897B',
|
||||
'A100': 'a7ffeb',
|
||||
'300': '4db6ac',
|
||||
'A400': '1de9b6',
|
||||
'700': '00796B',
|
||||
'50': 'e0f2f1',
|
||||
'A700': '00BFA5',
|
||||
'400': '26a69a',
|
||||
'100': 'b2dfdb',
|
||||
'800': '00695C',
|
||||
'A200': '64ffda',
|
||||
'500': '009688'
|
||||
},
|
||||
|
||||
'Orange': {
|
||||
'200': 'ffcc80',
|
||||
'900': 'E65100',
|
||||
'600': 'FB8C00',
|
||||
'A100': 'ffd180',
|
||||
'300': 'ffb74d',
|
||||
'A400': 'FF9100',
|
||||
'700': 'F57C00',
|
||||
'50': 'fff3e0',
|
||||
'A700': 'FF6D00',
|
||||
'400': 'ffa726',
|
||||
'100': 'ffe0b2',
|
||||
'800': 'EF6C00',
|
||||
'A200': 'ffab40',
|
||||
'500': 'FF9800'
|
||||
},
|
||||
|
||||
'Cyan': {
|
||||
'200': '80deea',
|
||||
'900': '006064',
|
||||
'600': '00ACC1',
|
||||
'A100': '84ffff',
|
||||
'300': '4dd0e1',
|
||||
'A400': '00E5FF',
|
||||
'700': '0097A7',
|
||||
'50': 'e0f7fa',
|
||||
'A700': '00B8D4',
|
||||
'400': '26c6da',
|
||||
'100': 'b2ebf2',
|
||||
'800': '00838F',
|
||||
'A200': '18ffff',
|
||||
'500': '00BCD4'
|
||||
},
|
||||
|
||||
'Amber': {
|
||||
'200': 'ffe082',
|
||||
'900': 'FF6F00',
|
||||
'600': 'FFB300',
|
||||
'A100': 'ffe57f',
|
||||
'300': 'ffd54f',
|
||||
'A400': 'FFC400',
|
||||
'700': 'FFA000',
|
||||
'50': 'fff8e1',
|
||||
'A700': 'FFAB00',
|
||||
'400': 'ffca28',
|
||||
'100': 'ffecb3',
|
||||
'800': 'FF8F00',
|
||||
'A200': 'ffd740',
|
||||
'500': 'FFC107'
|
||||
},
|
||||
|
||||
'DeepPurple': {
|
||||
'200': 'b39ddb',
|
||||
'900': '311b92',
|
||||
'600': '5e35b1',
|
||||
'A100': 'b388ff',
|
||||
'300': '9575cd',
|
||||
'A400': '651fff',
|
||||
'700': '512da8',
|
||||
'50': 'ede7f6',
|
||||
'A700': '6200EA',
|
||||
'400': '7e57c2',
|
||||
'100': 'd1c4e9',
|
||||
'800': '4527a0',
|
||||
'A200': '7c4dff',
|
||||
'500': '673ab7'
|
||||
},
|
||||
|
||||
'Lime': {
|
||||
'200': 'e6ee9c',
|
||||
'900': '827717',
|
||||
'600': 'c0ca33',
|
||||
'A100': 'f4ff81',
|
||||
'300': 'dce775',
|
||||
'A400': 'C6FF00',
|
||||
'700': 'afb42b',
|
||||
'50': 'f9fbe7',
|
||||
'A700': 'AEEA00',
|
||||
'400': 'd4e157',
|
||||
'100': 'f0f4c3',
|
||||
'800': '9e9d24',
|
||||
'A200': 'eeff41',
|
||||
'500': 'cddc39'
|
||||
},
|
||||
|
||||
'Light': {
|
||||
'StatusBar': 'E0E0E0',
|
||||
'AppBar': 'F5F5F5',
|
||||
'Background': 'FAFAFA',
|
||||
'CardsDialogs': 'FFFFFF',
|
||||
'FlatButtonDown': 'cccccc'
|
||||
},
|
||||
|
||||
'Dark': {
|
||||
'StatusBar': '000000',
|
||||
'AppBar': '212121',
|
||||
'Background': '303030',
|
||||
'CardsDialogs': '424242',
|
||||
'FlatButtonDown': '999999'
|
||||
}
|
||||
}
|
||||
|
||||
light_colors = {
|
||||
'Pink': ['50' '100', '200', 'A100'],
|
||||
'Blue': ['50' '100', '200', '300', '400', 'A100'],
|
||||
'Indigo': ['50' '100', '200', 'A100'],
|
||||
'BlueGrey': ['50' '100', '200', '300'],
|
||||
'Brown': ['50' '100', '200'],
|
||||
'LightBlue': ['50' '100', '200', '300', '400', '500', 'A100', 'A200',
|
||||
'A400'],
|
||||
'Purple': ['50' '100', '200', 'A100'],
|
||||
'Grey': ['50' '100', '200', '300', '400', '500'],
|
||||
'Yellow': ['50' '100', '200', '300', '400', '500', '600', '700', '800',
|
||||
'900', 'A100', 'A200', 'A400', 'A700'],
|
||||
'LightGreen': ['50' '100', '200', '300', '400', '500', '600', 'A100',
|
||||
'A200', 'A400', 'A700'],
|
||||
'DeepOrange': ['50' '100', '200', '300', '400', 'A100', 'A200'],
|
||||
'Green': ['50' '100', '200', '300', '400', '500', 'A100', 'A200', 'A400',
|
||||
'A700'],
|
||||
'Red': ['50' '100', '200', '300', 'A100'],
|
||||
'Teal': ['50' '100', '200', '300', '400', 'A100', 'A200', 'A400', 'A700'],
|
||||
'Orange': ['50' '100', '200', '300', '400', '500', '600', '700', 'A100',
|
||||
'A200', 'A400', 'A700'],
|
||||
'Cyan': ['50' '100', '200', '300', '400', '500', '600', 'A100', 'A200',
|
||||
'A400', 'A700'],
|
||||
'Amber': ['50' '100', '200', '300', '400', '500', '600', '700', '800',
|
||||
'900', 'A100', 'A200', 'A400', 'A700'],
|
||||
'DeepPurple': ['50' '100', '200', 'A100'],
|
||||
'Lime': ['50' '100', '200', '300', '400', '500', '600', '700', '800',
|
||||
'A100', 'A200', 'A400', 'A700'],
|
||||
'Dark': [],
|
||||
'Light': ['White', 'MainBackground', 'DialogBackground']
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.modalview import ModalView
|
||||
from kivymd.label import MDLabel
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivymd.elevationbehavior import ElevationBehavior
|
||||
import calendar
|
||||
from datetime import date
|
||||
import datetime
|
||||
from kivy.properties import StringProperty, NumericProperty, ObjectProperty, \
|
||||
BooleanProperty
|
||||
from kivy.uix.anchorlayout import AnchorLayout
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivymd.ripplebehavior import CircularRippleBehavior
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
|
||||
Builder.load_string("""
|
||||
#:import calendar calendar
|
||||
<MDDatePicker>
|
||||
cal_layout: cal_layout
|
||||
|
||||
size_hint: (None, None)
|
||||
size: [dp(328), dp(484)] if self.theme_cls.device_orientation == 'portrait'\
|
||||
else [dp(512), dp(304)]
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
canvas:
|
||||
Color:
|
||||
rgb: app.theme_cls.primary_color
|
||||
Rectangle:
|
||||
size: [dp(328), dp(96)] if self.theme_cls.device_orientation == 'portrait'\
|
||||
else [dp(168), dp(304)]
|
||||
pos: [root.pos[0], root.pos[1] + root.height-dp(96)] if self.theme_cls.device_orientation == 'portrait'\
|
||||
else [root.pos[0], root.pos[1] + root.height-dp(304)]
|
||||
Color:
|
||||
rgb: app.theme_cls.bg_normal
|
||||
Rectangle:
|
||||
size: [dp(328), dp(484)-dp(96)] if self.theme_cls.device_orientation == 'portrait'\
|
||||
else [dp(344), dp(304)]
|
||||
pos: [root.pos[0], root.pos[1] + root.height-dp(96)-(dp(484)-dp(96))]\
|
||||
if self.theme_cls.device_orientation == 'portrait' else [root.pos[0]+dp(168), root.pos[1]] #+dp(334)
|
||||
MDLabel:
|
||||
id: label_full_date
|
||||
font_style: 'Display1'
|
||||
text_color: 1, 1, 1, 1
|
||||
theme_text_color: 'Custom'
|
||||
size_hint: (None, None)
|
||||
size: [root.width, dp(30)] if root.theme_cls.device_orientation == 'portrait'\
|
||||
else [dp(168), dp(30)]
|
||||
pos: [root.pos[0]+dp(23), root.pos[1] + root.height - dp(74)] \
|
||||
if root.theme_cls.device_orientation == 'portrait' \
|
||||
else [root.pos[0]+dp(3), root.pos[1] + dp(214)]
|
||||
line_height: 0.84
|
||||
valign: 'middle'
|
||||
text_size: [root.width, None] if root.theme_cls.device_orientation == 'portrait'\
|
||||
else [dp(149), None]
|
||||
bold: True
|
||||
text: root.fmt_lbl_date(root.sel_year, root.sel_month, root.sel_day, root.theme_cls.device_orientation)
|
||||
MDLabel:
|
||||
id: label_year
|
||||
font_style: 'Subhead'
|
||||
text_color: 1, 1, 1, 1
|
||||
theme_text_color: 'Custom'
|
||||
size_hint: (None, None)
|
||||
size: root.width, dp(30)
|
||||
pos: (root.pos[0]+dp(23), root.pos[1]+root.height-dp(40)) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (root.pos[0]+dp(16), root.pos[1]+root.height-dp(41))
|
||||
valign: 'middle'
|
||||
text: str(root.sel_year)
|
||||
GridLayout:
|
||||
id: cal_layout
|
||||
cols: 7
|
||||
size: (dp(44*7), dp(40*7)) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (dp(46*7), dp(32*7))
|
||||
col_default_width: dp(42) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else dp(39)
|
||||
size_hint: (None, None)
|
||||
padding: (dp(2), 0) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (dp(7), 0)
|
||||
spacing: (dp(2), 0) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (dp(7), 0)
|
||||
pos: (root.pos[0]+dp(10), root.pos[1]+dp(60)) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (root.pos[0]+dp(168)+dp(8), root.pos[1]+dp(48))
|
||||
MDLabel:
|
||||
id: label_month_selector
|
||||
font_style: 'Body2'
|
||||
text: calendar.month_name[root.month].capitalize() + ' ' + str(root.year)
|
||||
size_hint: (None, None)
|
||||
size: root.width, dp(30)
|
||||
pos: root.pos
|
||||
theme_text_color: 'Primary'
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.75} if self.theme_cls.device_orientation == 'portrait'\
|
||||
else {'center_x': 0.67, 'center_y': 0.915}
|
||||
valign: "middle"
|
||||
halign: "center"
|
||||
MDIconButton:
|
||||
icon: 'chevron-left'
|
||||
theme_text_color: 'Secondary'
|
||||
pos_hint: {'center_x': 0.09, 'center_y': 0.745} if root.theme_cls.device_orientation == 'portrait'\
|
||||
else {'center_x': 0.39, 'center_y': 0.925}
|
||||
on_release: root.change_month('prev')
|
||||
MDIconButton:
|
||||
icon: 'chevron-right'
|
||||
theme_text_color: 'Secondary'
|
||||
pos_hint: {'center_x': 0.92, 'center_y': 0.745} if root.theme_cls.device_orientation == 'portrait'\
|
||||
else {'center_x': 0.94, 'center_y': 0.925}
|
||||
on_release: root.change_month('next')
|
||||
MDFlatButton:
|
||||
pos: root.pos[0]+root.size[0]-dp(72)*2, root.pos[1] + dp(7)
|
||||
text: "Cancel"
|
||||
on_release: root.dismiss()
|
||||
MDFlatButton:
|
||||
pos: root.pos[0]+root.size[0]-dp(72), root.pos[1] + dp(7)
|
||||
text: "OK"
|
||||
on_release: root.ok_click()
|
||||
|
||||
<DayButton>
|
||||
size_hint: None, None
|
||||
size: (dp(40), dp(40)) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (dp(32), dp(32))
|
||||
MDLabel:
|
||||
font_style: 'Caption'
|
||||
theme_text_color: 'Custom' if root.is_today and not root.is_selected else 'Primary'
|
||||
text_color: root.theme_cls.primary_color
|
||||
opposite_colors: root.is_selected if root.owner.sel_month == root.owner.month \
|
||||
and root.owner.sel_year == root.owner.year and str(self.text) == str(root.owner.sel_day) else False
|
||||
size_hint_x: None
|
||||
valign: 'middle'
|
||||
halign: 'center'
|
||||
text: root.text
|
||||
|
||||
<WeekdayLabel>
|
||||
font_style: 'Caption'
|
||||
theme_text_color: 'Secondary'
|
||||
size: (dp(40), dp(40)) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (dp(32), dp(32))
|
||||
size_hint: None, None
|
||||
text_size: self.size
|
||||
valign: 'middle' if root.theme_cls.device_orientation == 'portrait' else 'bottom'
|
||||
halign: 'center'
|
||||
|
||||
<DaySelector>
|
||||
size: (dp(40), dp(40)) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (dp(32), dp(32))
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.theme_cls.primary_color if self.shown else [0, 0, 0, 0]
|
||||
Ellipse:
|
||||
size: (dp(40), dp(40)) if root.theme_cls.device_orientation == 'portrait'\
|
||||
else (dp(32), dp(32))
|
||||
pos: self.pos if root.theme_cls.device_orientation == 'portrait'\
|
||||
else [self.pos[0] + dp(3), self.pos[1]]
|
||||
""")
|
||||
|
||||
|
||||
class DaySelector(ThemableBehavior, AnchorLayout):
|
||||
shown = BooleanProperty(False)
|
||||
|
||||
def __init__(self, parent):
|
||||
super(DaySelector, self).__init__()
|
||||
self.parent_class = parent
|
||||
self.parent_class.add_widget(self, index=7)
|
||||
self.selected_widget = None
|
||||
Window.bind(on_resize=self.move_resize)
|
||||
|
||||
def update(self):
|
||||
parent = self.parent_class
|
||||
if parent.sel_month == parent.month and parent.sel_year == parent.year:
|
||||
self.shown = True
|
||||
else:
|
||||
self.shown = False
|
||||
|
||||
def set_widget(self, widget):
|
||||
self.selected_widget = widget
|
||||
self.pos = widget.pos
|
||||
self.move_resize(do_again=True)
|
||||
self.update()
|
||||
|
||||
def move_resize(self, window=None, width=None, height=None, do_again=True):
|
||||
self.pos = self.selected_widget.pos
|
||||
if do_again:
|
||||
Clock.schedule_once(lambda x: self.move_resize(do_again=False), 0.01)
|
||||
|
||||
|
||||
class DayButton(ThemableBehavior, CircularRippleBehavior, ButtonBehavior,
|
||||
AnchorLayout):
|
||||
text = StringProperty()
|
||||
owner = ObjectProperty()
|
||||
is_today = BooleanProperty(False)
|
||||
is_selected = BooleanProperty(False)
|
||||
|
||||
def on_release(self):
|
||||
self.owner.set_selected_widget(self)
|
||||
|
||||
|
||||
class WeekdayLabel(MDLabel):
|
||||
pass
|
||||
|
||||
|
||||
class MDDatePicker(FloatLayout, ThemableBehavior, ElevationBehavior,
|
||||
ModalView):
|
||||
_sel_day_widget = ObjectProperty()
|
||||
cal_list = None
|
||||
cal_layout = ObjectProperty()
|
||||
sel_year = NumericProperty()
|
||||
sel_month = NumericProperty()
|
||||
sel_day = NumericProperty()
|
||||
day = NumericProperty()
|
||||
month = NumericProperty()
|
||||
year = NumericProperty()
|
||||
today = date.today()
|
||||
callback = ObjectProperty()
|
||||
|
||||
class SetDateError(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, callback, year=None, month=None, day=None,
|
||||
firstweekday=0,
|
||||
**kwargs):
|
||||
self.callback = callback
|
||||
self.cal = calendar.Calendar(firstweekday)
|
||||
self.sel_year = year if year else self.today.year
|
||||
self.sel_month = month if month else self.today.month
|
||||
self.sel_day = day if day else self.today.day
|
||||
self.month = self.sel_month
|
||||
self.year = self.sel_year
|
||||
self.day = self.sel_day
|
||||
super(MDDatePicker, self).__init__(**kwargs)
|
||||
self.selector = DaySelector(parent=self)
|
||||
self.generate_cal_widgets()
|
||||
self.update_cal_matrix(self.sel_year, self.sel_month)
|
||||
self.set_month_day(self.sel_day)
|
||||
self.selector.update()
|
||||
|
||||
def ok_click(self):
|
||||
self.callback(date(self.sel_year, self.sel_month, self.sel_day))
|
||||
self.dismiss()
|
||||
|
||||
def fmt_lbl_date(self, year, month, day, orientation):
|
||||
d = datetime.date(int(year), int(month), int(day))
|
||||
separator = '\n' if orientation == 'landscape' else ' '
|
||||
return d.strftime('%a,').capitalize() + separator + d.strftime(
|
||||
'%b').capitalize() + ' ' + str(day).lstrip('0')
|
||||
|
||||
def set_date(self, year, month, day):
|
||||
try:
|
||||
date(year, month, day)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if str(e) == "day is out of range for month":
|
||||
raise self.SetDateError(" Day %s day is out of range for month %s" % (day, month))
|
||||
elif str(e) == "month must be in 1..12":
|
||||
raise self.SetDateError("Month must be between 1 and 12, got %s" % month)
|
||||
elif str(e) == "year is out of range":
|
||||
raise self.SetDateError("Year must be between %s and %s, got %s" %
|
||||
(datetime.MINYEAR, datetime.MAXYEAR, year))
|
||||
else:
|
||||
self.sel_year = year
|
||||
self.sel_month = month
|
||||
self.sel_day = day
|
||||
self.month = self.sel_month
|
||||
self.year = self.sel_year
|
||||
self.day = self.sel_day
|
||||
self.update_cal_matrix(self.sel_year, self.sel_month)
|
||||
self.set_month_day(self.sel_day)
|
||||
self.selector.update()
|
||||
|
||||
def set_selected_widget(self, widget):
|
||||
if self._sel_day_widget:
|
||||
self._sel_day_widget.is_selected = False
|
||||
widget.is_selected = True
|
||||
self.sel_month = int(self.month)
|
||||
self.sel_year = int(self.year)
|
||||
self.sel_day = int(widget.text)
|
||||
self._sel_day_widget = widget
|
||||
self.selector.set_widget(widget)
|
||||
|
||||
def set_month_day(self, day):
|
||||
for idx in range(len(self.cal_list)):
|
||||
if str(day) == str(self.cal_list[idx].text):
|
||||
self._sel_day_widget = self.cal_list[idx]
|
||||
self.sel_day = int(self.cal_list[idx].text)
|
||||
if self._sel_day_widget:
|
||||
self._sel_day_widget.is_selected = False
|
||||
self._sel_day_widget = self.cal_list[idx]
|
||||
self.cal_list[idx].is_selected = True
|
||||
self.selector.set_widget(self.cal_list[idx])
|
||||
|
||||
def update_cal_matrix(self, year, month):
|
||||
try:
|
||||
dates = [x for x in self.cal.itermonthdates(year, month)]
|
||||
except ValueError as e:
|
||||
if str(e) == "year is out of range":
|
||||
pass
|
||||
else:
|
||||
self.year = year
|
||||
self.month = month
|
||||
for idx in range(len(self.cal_list)):
|
||||
if idx >= len(dates) or dates[idx].month != month:
|
||||
self.cal_list[idx].disabled = True
|
||||
self.cal_list[idx].text = ''
|
||||
else:
|
||||
self.cal_list[idx].disabled = False
|
||||
self.cal_list[idx].text = str(dates[idx].day)
|
||||
self.cal_list[idx].is_today = dates[idx] == self.today
|
||||
self.selector.update()
|
||||
|
||||
def generate_cal_widgets(self):
|
||||
cal_list = []
|
||||
for i in calendar.day_abbr:
|
||||
self.cal_layout.add_widget(WeekdayLabel(text=i[0].upper()))
|
||||
for i in range(6 * 7): # 6 weeks, 7 days a week
|
||||
db = DayButton(owner=self)
|
||||
cal_list.append(db)
|
||||
self.cal_layout.add_widget(db)
|
||||
self.cal_list = cal_list
|
||||
|
||||
def change_month(self, operation):
|
||||
op = 1 if operation is 'next' else -1
|
||||
sl, sy = self.month, self.year
|
||||
m = 12 if sl + op == 0 else 1 if sl + op == 13 else sl + op
|
||||
y = sy - 1 if sl + op == 0 else sy + 1 if sl + op == 13 else sy
|
||||
self.update_cal_matrix(y, m)
|
|
@ -1,176 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty, ObjectProperty, ListProperty
|
||||
from kivy.metrics import dp
|
||||
from kivy.uix.modalview import ModalView
|
||||
from kivy.animation import Animation
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.elevationbehavior import ElevationBehavior
|
||||
from kivymd.button import MDFlatButton
|
||||
|
||||
Builder.load_string('''
|
||||
<MDDialog>:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.theme_cls.bg_light
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
_container: container
|
||||
_action_area: action_area
|
||||
elevation: 12
|
||||
GridLayout:
|
||||
cols: 1
|
||||
|
||||
GridLayout:
|
||||
cols: 1
|
||||
padding: dp(24), dp(24), dp(24), 0
|
||||
spacing: dp(20)
|
||||
MDLabel:
|
||||
text: root.title
|
||||
font_style: 'Title'
|
||||
theme_text_color: 'Primary'
|
||||
halign: 'left'
|
||||
valign: 'middle'
|
||||
size_hint_y: None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
|
||||
BoxLayout:
|
||||
id: container
|
||||
|
||||
AnchorLayout:
|
||||
anchor_x: 'right'
|
||||
anchor_y: 'center'
|
||||
size_hint: 1, None
|
||||
height: dp(48)
|
||||
padding: dp(8), dp(8)
|
||||
spacing: dp(4)
|
||||
|
||||
GridLayout:
|
||||
id: action_area
|
||||
rows: 1
|
||||
size_hint: None, None if len(root._action_buttons) > 0 else 1
|
||||
height: dp(36) if len(root._action_buttons) > 0 else 0
|
||||
width: self.minimum_width
|
||||
''')
|
||||
|
||||
|
||||
class MDDialog(ThemableBehavior, ElevationBehavior, ModalView):
|
||||
title = StringProperty('')
|
||||
|
||||
content = ObjectProperty(None)
|
||||
|
||||
background_color = ListProperty([0, 0, 0, .2])
|
||||
|
||||
_container = ObjectProperty()
|
||||
_action_buttons = ListProperty([])
|
||||
_action_area = ObjectProperty()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDDialog, self).__init__(**kwargs)
|
||||
self.bind(_action_buttons=self._update_action_buttons,
|
||||
auto_dismiss=lambda *x: setattr(self.shadow, 'on_release',
|
||||
self.shadow.dismiss if self.auto_dismiss else None))
|
||||
|
||||
def add_action_button(self, text, action=None):
|
||||
"""Add an :class:`FlatButton` to the right of the action area.
|
||||
|
||||
:param icon: Unicode character for the icon
|
||||
:type icon: str or None
|
||||
:param action: Function set to trigger when on_release fires
|
||||
:type action: function or None
|
||||
"""
|
||||
button = MDFlatButton(text=text,
|
||||
size_hint=(None, None),
|
||||
height=dp(36))
|
||||
if action:
|
||||
button.bind(on_release=action)
|
||||
button.text_color = self.theme_cls.primary_color
|
||||
button.background_color = self.theme_cls.bg_light
|
||||
self._action_buttons.append(button)
|
||||
|
||||
def add_widget(self, widget):
|
||||
if self._container:
|
||||
if self.content:
|
||||
raise PopupException(
|
||||
'Popup can have only one widget as content')
|
||||
self.content = widget
|
||||
else:
|
||||
super(MDDialog, self).add_widget(widget)
|
||||
|
||||
def open(self, *largs):
|
||||
'''Show the view window from the :attr:`attach_to` widget. If set, it
|
||||
will attach to the nearest window. If the widget is not attached to any
|
||||
window, the view will attach to the global
|
||||
:class:`~kivy.core.window.Window`.
|
||||
'''
|
||||
if self._window is not None:
|
||||
Logger.warning('ModalView: you can only open once.')
|
||||
return self
|
||||
# search window
|
||||
self._window = self._search_window()
|
||||
if not self._window:
|
||||
Logger.warning('ModalView: cannot open view, no window found.')
|
||||
return self
|
||||
self._window.add_widget(self)
|
||||
self._window.bind(on_resize=self._align_center,
|
||||
on_keyboard=self._handle_keyboard)
|
||||
self.center = self._window.center
|
||||
self.bind(size=self._align_center)
|
||||
a = Animation(_anim_alpha=1., d=self._anim_duration)
|
||||
a.bind(on_complete=lambda *x: self.dispatch('on_open'))
|
||||
a.start(self)
|
||||
return self
|
||||
|
||||
def dismiss(self, *largs, **kwargs):
|
||||
'''Close the view if it is open. If you really want to close the
|
||||
view, whatever the on_dismiss event returns, you can use the *force*
|
||||
argument:
|
||||
::
|
||||
|
||||
view = ModalView(...)
|
||||
view.dismiss(force=True)
|
||||
|
||||
When the view is dismissed, it will be faded out before being
|
||||
removed from the parent. If you don't want animation, use::
|
||||
|
||||
view.dismiss(animation=False)
|
||||
|
||||
'''
|
||||
if self._window is None:
|
||||
return self
|
||||
if self.dispatch('on_dismiss') is True:
|
||||
if kwargs.get('force', False) is not True:
|
||||
return self
|
||||
if kwargs.get('animation', True):
|
||||
Animation(_anim_alpha=0., d=self._anim_duration).start(self)
|
||||
else:
|
||||
self._anim_alpha = 0
|
||||
self._real_remove_widget()
|
||||
return self
|
||||
|
||||
def on_content(self, instance, value):
|
||||
if self._container:
|
||||
self._container.clear_widgets()
|
||||
self._container.add_widget(value)
|
||||
|
||||
def on__container(self, instance, value):
|
||||
if value is None or self.content is None:
|
||||
return
|
||||
self._container.clear_widgets()
|
||||
self._container.add_widget(self.content)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if self.disabled and self.collide_point(*touch.pos):
|
||||
return True
|
||||
return super(MDDialog, self).on_touch_down(touch)
|
||||
|
||||
def _update_action_buttons(self, *args):
|
||||
self._action_area.clear_widgets()
|
||||
for btn in self._action_buttons:
|
||||
btn.ids._label.texture_update()
|
||||
btn.width = btn.ids._label.texture_size[0] + dp(16)
|
||||
self._action_area.add_widget(btn)
|
|
@ -1,187 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (ListProperty, ObjectProperty, NumericProperty)
|
||||
from kivy.properties import AliasProperty
|
||||
from kivy.metrics import dp
|
||||
|
||||
Builder.load_string('''
|
||||
<ElevationBehavior>
|
||||
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
|
||||
|
||||
<RoundElevationBehavior>
|
||||
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 ElevationBehavior(object):
|
||||
_elevation = NumericProperty(1)
|
||||
|
||||
def _get_elevation(self):
|
||||
return self._elevation
|
||||
|
||||
def _set_elevation(self, elevation):
|
||||
try:
|
||||
self._elevation = elevation
|
||||
except:
|
||||
self._elevation = 1
|
||||
|
||||
elevation = AliasProperty(_get_elevation, _set_elevation,
|
||||
bind=('_elevation',))
|
||||
|
||||
_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):
|
||||
super(ElevationBehavior, self).__init__(**kwargs)
|
||||
self.bind(elevation=self._update_shadow,
|
||||
pos=self._update_shadow,
|
||||
size=self._update_shadow)
|
||||
|
||||
def _update_shadow(self, *args):
|
||||
if self.elevation > 0:
|
||||
ratio = self.width / (self.height if self.height != 0 else 1)
|
||||
if ratio > -2 and ratio < 2:
|
||||
self._shadow = App.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 = App.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) * .5
|
||||
else:
|
||||
self._shadow = App.get_running_app().theme_cls.quad_shadow
|
||||
width = soft_width = self.width * 1.8
|
||||
height = soft_height = self.height * 1.8
|
||||
# self._shadow = App.get_running_app().theme_cls.rec_shadow
|
||||
# ratio = abs(ratio)
|
||||
# if ratio > 5:
|
||||
# ratio = ratio * 22
|
||||
# else:
|
||||
# ratio = ratio * 11.5
|
||||
#
|
||||
# width = self.width + dp(ratio)
|
||||
# soft_width = self.width + dp(ratio) + dp(self.elevation) * .9
|
||||
# height = soft_height = self.height * 1.9
|
||||
|
||||
x = self.center_x - width / 2
|
||||
soft_x = self.center_x - soft_width / 2
|
||||
self._soft_shadow_size = (soft_width, soft_height)
|
||||
self._hard_shadow_size = (width, height)
|
||||
|
||||
y = self.center_y - soft_height / 2 - dp(
|
||||
.1 * 1.5 ** self.elevation)
|
||||
self._soft_shadow_pos = (soft_x, y)
|
||||
self._soft_shadow_a = 0.1 * 1.1 ** self.elevation
|
||||
self._soft_shadow_texture = self._shadow.textures[
|
||||
str(int(round(self.elevation - 1)))]
|
||||
|
||||
y = self.center_y - height / 2 - dp(.5 * 1.18 ** self.elevation)
|
||||
self._hard_shadow_pos = (x, y)
|
||||
self._hard_shadow_a = .4 * .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 RoundElevationBehavior(object):
|
||||
_elevation = NumericProperty(1)
|
||||
|
||||
def _get_elevation(self):
|
||||
return self._elevation
|
||||
|
||||
def _set_elevation(self, elevation):
|
||||
try:
|
||||
self._elevation = elevation
|
||||
except:
|
||||
self._elevation = 1
|
||||
|
||||
elevation = AliasProperty(_get_elevation, _set_elevation,
|
||||
bind=('_elevation',))
|
||||
|
||||
_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):
|
||||
super(RoundElevationBehavior, self).__init__(**kwargs)
|
||||
self._shadow = App.get_running_app().theme_cls.round_shadow
|
||||
self.bind(elevation=self._update_shadow,
|
||||
pos=self._update_shadow,
|
||||
size=self._update_shadow)
|
||||
|
||||
def _update_shadow(self, *args):
|
||||
if self.elevation > 0:
|
||||
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)
|
||||
|
||||
y = self.center_y - height / 2 - dp(.1 * 1.5 ** self.elevation)
|
||||
self._soft_shadow_pos = (x, y)
|
||||
self._soft_shadow_a = 0.1 * 1.1 ** self.elevation
|
||||
self._soft_shadow_texture = self._shadow.textures[
|
||||
str(int(round(self.elevation)))]
|
||||
|
||||
y = self.center_y - height / 2 - dp(.5 * 1.18 ** self.elevation)
|
||||
self._hard_shadow_pos = (x, y)
|
||||
self._hard_shadow_a = .4 * .9 ** self.elevation
|
||||
self._hard_shadow_texture = self._shadow.textures[
|
||||
str(int(round(self.elevation - 1)))]
|
||||
|
||||
else:
|
||||
self._soft_shadow_a = 0
|
||||
self._hard_shadow_a = 0
|
|
@ -1,168 +0,0 @@
|
|||
# coding=utf-8
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty, BooleanProperty, ObjectProperty, \
|
||||
NumericProperty, ListProperty, OptionProperty
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivymd.ripplebehavior import RectangularRippleBehavior
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string("""
|
||||
<SmartTile>
|
||||
_img_widget: img
|
||||
_img_overlay: img_overlay
|
||||
_box_overlay: box
|
||||
AsyncImage:
|
||||
id: img
|
||||
allow_stretch: root.allow_stretch
|
||||
anim_delay: root.anim_delay
|
||||
anim_loop: root.anim_loop
|
||||
color: root.img_color
|
||||
keep_ratio: root.keep_ratio
|
||||
mipmap: root.mipmap
|
||||
source: root.source
|
||||
size_hint_y: 1 if root.overlap else None
|
||||
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
|
||||
BoxLayout:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.box_color
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
id: box
|
||||
size_hint_y: None
|
||||
height: dp(68) if root.lines == 2 else dp(48)
|
||||
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
|
||||
AsyncImage:
|
||||
id: img
|
||||
allow_stretch: root.allow_stretch
|
||||
anim_delay: root.anim_delay
|
||||
anim_loop: root.anim_loop
|
||||
color: root.img_color
|
||||
keep_ratio: root.keep_ratio
|
||||
mipmap: root.mipmap
|
||||
source: root.source
|
||||
size_hint_y: 1 if root.overlap else None
|
||||
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
|
||||
BoxLayout:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.box_color
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
id: box
|
||||
size_hint_y: None
|
||||
height: dp(68) if root.lines == 2 else dp(48)
|
||||
x: root.x
|
||||
y: root.y if root.box_position == 'footer' else root.y + root.height - self.height
|
||||
MDLabel:
|
||||
id: boxlabel
|
||||
font_style: "Caption"
|
||||
halign: "center"
|
||||
text: root.text
|
||||
""")
|
||||
|
||||
|
||||
class Tile(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior,
|
||||
BoxLayout):
|
||||
"""A simple tile. It does nothing special, just inherits the right behaviors
|
||||
to work as a building block.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class SmartTile(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior,
|
||||
FloatLayout):
|
||||
"""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."""
|
||||
|
||||
box_position = OptionProperty('footer', options=['footer', 'header'])
|
||||
"""Determines wether the information box acts as a header or footer to the
|
||||
image.
|
||||
"""
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
overlap = BooleanProperty(True)
|
||||
"""Determines if the header/footer overlaps on top of the image or not"""
|
||||
|
||||
# Img properties
|
||||
allow_stretch = BooleanProperty(True)
|
||||
anim_delay = NumericProperty(0.25)
|
||||
anim_loop = NumericProperty(0)
|
||||
img_color = ListProperty([1, 1, 1, 1])
|
||||
keep_ratio = BooleanProperty(False)
|
||||
mipmap = BooleanProperty(False)
|
||||
source = StringProperty()
|
||||
|
||||
_img_widget = ObjectProperty()
|
||||
_img_overlay = ObjectProperty()
|
||||
_box_overlay = ObjectProperty()
|
||||
_box_label = ObjectProperty()
|
||||
|
||||
def reload(self):
|
||||
self._img_widget.reload()
|
||||
|
||||
def add_widget(self, widget, index=0):
|
||||
if issubclass(widget.__class__, IOverlay):
|
||||
self._img_overlay.add_widget(widget, index)
|
||||
elif issubclass(widget.__class__, IBoxOverlay):
|
||||
self._box_overlay.add_widget(widget, index)
|
||||
else:
|
||||
super(SmartTile, self).add_widget(widget, index)
|
||||
|
||||
|
||||
class SmartTileWithLabel(SmartTile):
|
||||
_box_label = ObjectProperty()
|
||||
|
||||
# MDLabel properties
|
||||
font_style = StringProperty("Caption")
|
||||
theme_text_color = StringProperty("")
|
||||
text = StringProperty("")
|
||||
"""Determines the text for the box footer/header"""
|
||||
|
||||
|
||||
class IBoxOverlay():
|
||||
"""An interface to specify widgets that belong to to the image overlay
|
||||
in the :class:`SmartTile` widget when added as a child.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class IOverlay():
|
||||
"""An interface to specify widgets that belong to to the image overlay
|
||||
in the :class:`SmartTile` widget when added as a child.
|
||||
"""
|
||||
pass
|
|
@ -1,1569 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Thanks to Sergey Kupletsky (github.com/zavoloklom) for its Material Design
|
||||
# Iconic Font, which provides KivyMD's icons.
|
||||
|
||||
# GALLERY HERE:
|
||||
# https://zavoloklom.github.io/material-design-iconic-font/icons.html
|
||||
|
||||
# LAST UPDATED: version 2.2.0 of Material Design Iconic Font
|
||||
|
||||
md_icons = {
|
||||
'3d-rotation': u'',
|
||||
|
||||
'airplane-off': u'',
|
||||
|
||||
'address': u'',
|
||||
|
||||
'airplane': u'',
|
||||
|
||||
'album': u'',
|
||||
|
||||
'archive': u'',
|
||||
|
||||
'assignment-account': u'',
|
||||
|
||||
'assignment-alert': u'',
|
||||
|
||||
'assignment-check': u'',
|
||||
|
||||
'assignment-o': u'',
|
||||
|
||||
'assignment-return': u'',
|
||||
|
||||
'assignment-returned': u'',
|
||||
|
||||
'assignment': u'',
|
||||
|
||||
'attachment-alt': u'',
|
||||
|
||||
'attachment': u'',
|
||||
|
||||
'audio': u'',
|
||||
|
||||
'badge-check': u'',
|
||||
|
||||
'balance-wallet': u'',
|
||||
|
||||
'balance': u'',
|
||||
|
||||
'battery-alert': u'',
|
||||
|
||||
'battery-flash': u'',
|
||||
|
||||
'battery-unknown': u'',
|
||||
|
||||
'battery': u'',
|
||||
|
||||
'bike': u'',
|
||||
|
||||
'block-alt': u'',
|
||||
|
||||
'block': u'',
|
||||
|
||||
'boat': u'',
|
||||
|
||||
'book-image': u'',
|
||||
|
||||
'book': u'',
|
||||
|
||||
'bookmark-outline': u'',
|
||||
|
||||
'bookmark': u'',
|
||||
|
||||
'brush': u'',
|
||||
|
||||
'bug': u'',
|
||||
|
||||
'bus': u'',
|
||||
|
||||
'cake': u'',
|
||||
|
||||
'car-taxi': u'',
|
||||
|
||||
'car-wash': u'',
|
||||
|
||||
'car': u'',
|
||||
|
||||
'card-giftcard': u'',
|
||||
|
||||
'card-membership': u'',
|
||||
|
||||
'card-travel': u'',
|
||||
|
||||
'card': u'',
|
||||
|
||||
'case-check': u'',
|
||||
|
||||
'case-download': u'',
|
||||
|
||||
'case-play': u'',
|
||||
|
||||
'case': u'',
|
||||
|
||||
'cast-connected': u'',
|
||||
|
||||
'cast': u'',
|
||||
|
||||
'chart-donut': u'',
|
||||
|
||||
'chart': u'',
|
||||
|
||||
'city-alt': u'',
|
||||
|
||||
'city': u'',
|
||||
|
||||
'close-circle-o': u'',
|
||||
|
||||
'close-circle': u'',
|
||||
|
||||
'close': u'',
|
||||
|
||||
'cocktail': u'',
|
||||
|
||||
'code-setting': u'',
|
||||
|
||||
'code-smartphone': u'',
|
||||
|
||||
'code': u'',
|
||||
|
||||
'coffee': u'',
|
||||
|
||||
'collection-bookmark': u'',
|
||||
|
||||
'collection-case-play': u'',
|
||||
|
||||
'collection-folder-image': u'',
|
||||
|
||||
'collection-image-o': u'',
|
||||
|
||||
'collection-image': u'',
|
||||
|
||||
'collection-item-1': u'',
|
||||
|
||||
'collection-item-2': u'',
|
||||
|
||||
'collection-item-3': u'',
|
||||
|
||||
'collection-item-4': u'',
|
||||
|
||||
'collection-item-5': u'',
|
||||
|
||||
'collection-item-6': u'',
|
||||
|
||||
'collection-item-7': u'',
|
||||
|
||||
'collection-item-8': u'',
|
||||
|
||||
'collection-item-9-plus': u'',
|
||||
|
||||
'collection-item-9': u'',
|
||||
|
||||
'collection-item': u'',
|
||||
|
||||
'collection-music': u'',
|
||||
|
||||
'collection-pdf': u'',
|
||||
|
||||
'collection-plus': u'',
|
||||
|
||||
'collection-speaker': u'',
|
||||
|
||||
'collection-text': u'',
|
||||
|
||||
'collection-video': u'',
|
||||
|
||||
'compass': u'',
|
||||
|
||||
'cutlery': u'',
|
||||
|
||||
'delete': u'',
|
||||
|
||||
'dialpad': u'',
|
||||
|
||||
'dns': u'',
|
||||
|
||||
'drink': u'',
|
||||
|
||||
'edit': u'',
|
||||
|
||||
'email-open': u'',
|
||||
|
||||
'email': u'',
|
||||
|
||||
'eye-off': u'',
|
||||
|
||||
'eye': u'',
|
||||
|
||||
'eyedropper': u'',
|
||||
|
||||
'favorite-outline': u'',
|
||||
|
||||
'favorite': u'',
|
||||
|
||||
'filter-list': u'',
|
||||
|
||||
'fire': u'',
|
||||
|
||||
'flag': u'',
|
||||
|
||||
'flare': u'',
|
||||
|
||||
'flash-auto': u'',
|
||||
|
||||
'flash-off': u'',
|
||||
|
||||
'flash': u'',
|
||||
|
||||
'flip': u'',
|
||||
|
||||
'flower-alt': u'',
|
||||
|
||||
'flower': u'',
|
||||
|
||||
'font': u'',
|
||||
|
||||
'fullscreen-alt': u'',
|
||||
|
||||
'fullscreen-exit': u'',
|
||||
|
||||
'fullscreen': u'',
|
||||
|
||||
'functions': u'',
|
||||
|
||||
'gas-station': u'',
|
||||
|
||||
'gesture': u'',
|
||||
|
||||
'globe-alt': u'',
|
||||
|
||||
'globe-lock': u'',
|
||||
|
||||
'globe': u'',
|
||||
|
||||
'graduation-cap': u'',
|
||||
|
||||
'group': u'',
|
||||
|
||||
'home': u'',
|
||||
|
||||
'hospital-alt': u'',
|
||||
|
||||
'hospital': u'',
|
||||
|
||||
'hotel': u'',
|
||||
|
||||
'hourglass-alt': u'',
|
||||
|
||||
'hourglass-outline': u'',
|
||||
|
||||
'hourglass': u'',
|
||||
|
||||
'http': u'',
|
||||
|
||||
'image-alt': u'',
|
||||
|
||||
'image-o': u'',
|
||||
|
||||
'image': u'',
|
||||
|
||||
'inbox': u'',
|
||||
|
||||
'invert-colors-off': u'',
|
||||
|
||||
'invert-colors': u'',
|
||||
|
||||
'key': u'',
|
||||
|
||||
'label-alt-outline': u'',
|
||||
|
||||
'label-alt': u'',
|
||||
|
||||
'label-heart': u'',
|
||||
|
||||
'label': u'',
|
||||
|
||||
'labels': u'',
|
||||
|
||||
'lamp': u'',
|
||||
|
||||
'landscape': u'',
|
||||
|
||||
'layers-off': u'',
|
||||
|
||||
'layers': u'',
|
||||
|
||||
'library': u'',
|
||||
|
||||
'link': u'',
|
||||
|
||||
'lock-open': u'',
|
||||
|
||||
'lock-outline': u'',
|
||||
|
||||
'lock': u'',
|
||||
|
||||
'mail-reply-all': u'',
|
||||
|
||||
'mail-reply': u'',
|
||||
|
||||
'mail-send': u'',
|
||||
|
||||
'mall': u'',
|
||||
|
||||
'map': u'',
|
||||
|
||||
'menu': u'',
|
||||
|
||||
'money-box': u'',
|
||||
|
||||
'money-off': u'',
|
||||
|
||||
'money': u'',
|
||||
|
||||
'more-vert': u'',
|
||||
|
||||
'more': u'',
|
||||
|
||||
'movie-alt': u'',
|
||||
|
||||
'movie': u'',
|
||||
|
||||
'nature-people': u'',
|
||||
|
||||
'nature': u'',
|
||||
|
||||
'navigation': u'',
|
||||
|
||||
'open-in-browser': u'',
|
||||
|
||||
'open-in-new': u'',
|
||||
|
||||
'palette': u'',
|
||||
|
||||
'parking': u'',
|
||||
|
||||
'pin-account': u'',
|
||||
|
||||
'pin-assistant': u'',
|
||||
|
||||
'pin-drop': u'',
|
||||
|
||||
'pin-help': u'',
|
||||
|
||||
'pin-off': u'',
|
||||
|
||||
'pin': u'',
|
||||
|
||||
'pizza': u'',
|
||||
|
||||
'plaster': u'',
|
||||
|
||||
'power-setting': u'',
|
||||
|
||||
'power': u'',
|
||||
|
||||
'print': u'',
|
||||
|
||||
'puzzle-piece': u'',
|
||||
|
||||
'quote': u'',
|
||||
|
||||
'railway': u'',
|
||||
|
||||
'receipt': u'',
|
||||
|
||||
'refresh-alt': u'',
|
||||
|
||||
'refresh-sync-alert': u'',
|
||||
|
||||
'refresh-sync-off': u'',
|
||||
|
||||
'refresh-sync': u'',
|
||||
|
||||
'refresh': u'',
|
||||
|
||||
'roller': u'',
|
||||
|
||||
'ruler': u'',
|
||||
|
||||
'scissors': u'',
|
||||
|
||||
'screen-rotation-lock': u'',
|
||||
|
||||
'screen-rotation': u'',
|
||||
|
||||
'search-for': u'',
|
||||
|
||||
'search-in-file': u'',
|
||||
|
||||
'search-in-page': u'',
|
||||
|
||||
'search-replace': u'',
|
||||
|
||||
'search': u'',
|
||||
|
||||
'seat': u'',
|
||||
|
||||
'settings-square': u'',
|
||||
|
||||
'settings': u'',
|
||||
|
||||
'shape': u'',
|
||||
|
||||
'shield-check': u'',
|
||||
|
||||
'shield-security': u'',
|
||||
|
||||
'shopping-basket': u'',
|
||||
|
||||
'shopping-cart-plus': u'',
|
||||
|
||||
'shopping-cart': u'',
|
||||
|
||||
'sign-in': u'',
|
||||
|
||||
'sort-amount-asc': u'',
|
||||
|
||||
'sort-amount-desc': u'',
|
||||
|
||||
'sort-asc': u'',
|
||||
|
||||
'sort-desc': u'',
|
||||
|
||||
'spellcheck': u'',
|
||||
|
||||
'spinner': u'',
|
||||
|
||||
'storage': u'',
|
||||
|
||||
'store-24': u'',
|
||||
|
||||
'store': u'',
|
||||
|
||||
'subway': u'',
|
||||
|
||||
'sun': u'',
|
||||
|
||||
'tab-unselected': u'',
|
||||
|
||||
'tab': u'',
|
||||
|
||||
'tag-close': u'',
|
||||
|
||||
'tag-more': u'',
|
||||
|
||||
'tag': u'',
|
||||
|
||||
'thumb-down': u'',
|
||||
|
||||
'thumb-up-down': u'',
|
||||
|
||||
'thumb-up': u'',
|
||||
|
||||
'ticket-star': u'',
|
||||
|
||||
'toll': u'',
|
||||
|
||||
'toys': u'',
|
||||
|
||||
'traffic': u'',
|
||||
|
||||
'translate': u'',
|
||||
|
||||
'triangle-down': u'',
|
||||
|
||||
'triangle-up': u'',
|
||||
|
||||
'truck': u'',
|
||||
|
||||
'turning-sign': u'',
|
||||
|
||||
' ungroup': u'',
|
||||
|
||||
'wallpaper': u'',
|
||||
|
||||
'washing-machine': u'',
|
||||
|
||||
'window-maximize': u'',
|
||||
|
||||
'window-minimize': u'',
|
||||
|
||||
'window-restore': u'',
|
||||
|
||||
'wrench': u'',
|
||||
|
||||
'zoom-in': u'',
|
||||
|
||||
'zoom-out': u'',
|
||||
|
||||
'alert-circle-o': u'',
|
||||
|
||||
'alert-circle': u'',
|
||||
|
||||
'alert-octagon': u'',
|
||||
|
||||
'alert-polygon': u'',
|
||||
|
||||
'alert-triangle': u'',
|
||||
|
||||
'help-outline': u'',
|
||||
|
||||
'help': u'',
|
||||
|
||||
'info-outline': u'',
|
||||
|
||||
'info': u'',
|
||||
|
||||
'notifications-active': u'',
|
||||
|
||||
'notifications-add': u'',
|
||||
|
||||
'notifications-none': u'',
|
||||
|
||||
'notifications-off': u'',
|
||||
|
||||
'notifications-paused': u'',
|
||||
|
||||
'notifications': u'',
|
||||
|
||||
'account-add': u'',
|
||||
|
||||
'account-box-mail': u'',
|
||||
|
||||
'account-box-o': u'',
|
||||
|
||||
'account-box-phone': u'',
|
||||
|
||||
'account-box': u'',
|
||||
|
||||
'account-calendar': u'',
|
||||
|
||||
'account-circle': u'',
|
||||
|
||||
'account-o': u'',
|
||||
|
||||
'account': u'',
|
||||
|
||||
'accounts-add': u'',
|
||||
|
||||
'accounts-alt': u'',
|
||||
|
||||
'accounts-list-alt': u'',
|
||||
|
||||
'accounts-list': u'',
|
||||
|
||||
'accounts-outline': u'',
|
||||
|
||||
'accounts': u'',
|
||||
|
||||
'face': u'',
|
||||
|
||||
'female': u'',
|
||||
|
||||
'male-alt': u'',
|
||||
|
||||
'male-female': u'',
|
||||
|
||||
'male': u'',
|
||||
|
||||
'mood-bad': u'',
|
||||
|
||||
'mood': u'',
|
||||
|
||||
'run': u'',
|
||||
|
||||
'walk': u'',
|
||||
|
||||
'cloud-box': u'',
|
||||
|
||||
'cloud-circle': u'',
|
||||
|
||||
'cloud-done': u'',
|
||||
|
||||
'cloud-download': u'',
|
||||
|
||||
'cloud-off': u'',
|
||||
|
||||
'cloud-outline-alt': u'',
|
||||
|
||||
'cloud-outline': u'',
|
||||
|
||||
'cloud-upload': u'',
|
||||
|
||||
'cloud': u'',
|
||||
|
||||
'download': u'',
|
||||
|
||||
'file-plus': u'',
|
||||
|
||||
'file-text': u'',
|
||||
|
||||
'file': u'',
|
||||
|
||||
'folder-outline': u'',
|
||||
|
||||
'folder-person': u'',
|
||||
|
||||
'folder-star-alt': u'',
|
||||
|
||||
'folder-star': u'',
|
||||
|
||||
'folder': u'',
|
||||
|
||||
'gif': u'',
|
||||
|
||||
'upload': u'',
|
||||
|
||||
'border-all': u'',
|
||||
|
||||
'border-bottom': u'',
|
||||
|
||||
'border-clear': u'',
|
||||
|
||||
'border-color': u'',
|
||||
|
||||
'border-horizontal': u'',
|
||||
|
||||
'border-inner': u'',
|
||||
|
||||
'border-left': u'',
|
||||
|
||||
'border-outer': u'',
|
||||
|
||||
'border-right': u'',
|
||||
|
||||
'border-style': u'',
|
||||
|
||||
'border-top': u'',
|
||||
|
||||
'border-vertical': u'',
|
||||
|
||||
'copy': u'',
|
||||
|
||||
'crop': u'',
|
||||
|
||||
'format-align-center': u'',
|
||||
|
||||
'format-align-justify': u'',
|
||||
|
||||
'format-align-left': u'',
|
||||
|
||||
'format-align-right': u'',
|
||||
|
||||
'format-bold': u'',
|
||||
|
||||
'format-clear-all': u'',
|
||||
|
||||
'format-clear': u'',
|
||||
|
||||
'format-color-fill': u'',
|
||||
|
||||
'format-color-reset': u'',
|
||||
|
||||
'format-color-text': u'',
|
||||
|
||||
'format-indent-decrease': u'',
|
||||
|
||||
'format-indent-increase': u'',
|
||||
|
||||
'format-italic': u'',
|
||||
|
||||
'format-line-spacing': u'',
|
||||
|
||||
'format-list-bulleted': u'',
|
||||
|
||||
'format-list-numbered': u'',
|
||||
|
||||
'format-ltr': u'',
|
||||
|
||||
'format-rtl': u'',
|
||||
|
||||
'format-size': u'',
|
||||
|
||||
'format-strikethrough-s': u'',
|
||||
|
||||
'format-strikethrough': u'',
|
||||
|
||||
'format-subject': u'',
|
||||
|
||||
'format-underlined': u'',
|
||||
|
||||
'format-valign-bottom': u'',
|
||||
|
||||
'format-valign-center': u'',
|
||||
|
||||
'format-valign-top': u'',
|
||||
|
||||
'redo': u'',
|
||||
|
||||
'select-all': u'',
|
||||
|
||||
'space-bar': u'',
|
||||
|
||||
'text-format': u'',
|
||||
|
||||
'transform': u'',
|
||||
|
||||
'undo': u'',
|
||||
|
||||
'wrap-text': u'',
|
||||
|
||||
'comment-alert': u'',
|
||||
|
||||
'comment-alt-text': u'',
|
||||
|
||||
'comment-alt': u'',
|
||||
|
||||
'comment-edit': u'',
|
||||
|
||||
'comment-image': u'',
|
||||
|
||||
'comment-list': u'',
|
||||
|
||||
'comment-more': u'',
|
||||
|
||||
'comment-outline': u'',
|
||||
|
||||
'comment-text-alt': u'',
|
||||
|
||||
'comment-text': u'',
|
||||
|
||||
'comment-video': u'',
|
||||
|
||||
'comment': u'',
|
||||
|
||||
'comments': u'',
|
||||
|
||||
'rm': u'F',
|
||||
|
||||
'check-all': u'',
|
||||
|
||||
'check-circle-u': u'',
|
||||
|
||||
'check-circle': u'',
|
||||
|
||||
'check-square': u'',
|
||||
|
||||
'check': u'',
|
||||
|
||||
'circle-o': u'',
|
||||
|
||||
'circle': u'',
|
||||
|
||||
'dot-circle-alt': u'',
|
||||
|
||||
'dot-circle': u'',
|
||||
|
||||
'minus-circle-outline': u'',
|
||||
|
||||
'minus-circle': u'',
|
||||
|
||||
'minus-square': u'',
|
||||
|
||||
'minus': u'',
|
||||
|
||||
'plus-circle-o-duplicate': u'',
|
||||
|
||||
'plus-circle-o': u'',
|
||||
|
||||
'plus-circle': u'',
|
||||
|
||||
'plus-square': u'',
|
||||
|
||||
'plus': u'',
|
||||
|
||||
'square-o': u'',
|
||||
|
||||
'star-circle': u'',
|
||||
|
||||
'star-half': u'',
|
||||
|
||||
'star-outline': u'',
|
||||
|
||||
'star': u'',
|
||||
|
||||
'bluetooth-connected': u'',
|
||||
|
||||
'bluetooth-off': u'',
|
||||
|
||||
'bluetooth-search': u'',
|
||||
|
||||
'bluetooth-setting': u'',
|
||||
|
||||
'bluetooth': u'',
|
||||
|
||||
'camera-add': u'',
|
||||
|
||||
'camera-alt': u'',
|
||||
|
||||
'camera-bw': u'',
|
||||
|
||||
'camera-front': u'',
|
||||
|
||||
'camera-mic': u'',
|
||||
|
||||
'camera-party-mode': u'',
|
||||
|
||||
'camera-rear': u'',
|
||||
|
||||
'camera-roll': u'',
|
||||
|
||||
'camera-switch': u'',
|
||||
|
||||
'camera': u'',
|
||||
|
||||
'card-alert': u'',
|
||||
|
||||
'card-off': u'',
|
||||
|
||||
'card-sd': u'',
|
||||
|
||||
'card-sim': u'',
|
||||
|
||||
'desktop-mac': u'',
|
||||
|
||||
'desktop-windows': u'',
|
||||
|
||||
'device-hub': u'',
|
||||
|
||||
'devices-off': u'',
|
||||
|
||||
'devices': u'',
|
||||
|
||||
'dock': u'',
|
||||
|
||||
'floppy': u'',
|
||||
|
||||
'gamepad': u'',
|
||||
|
||||
'gps-dot': u'',
|
||||
|
||||
'gps-off': u'',
|
||||
|
||||
'gps': u'',
|
||||
|
||||
'headset-mic': u'',
|
||||
|
||||
'headset': u'',
|
||||
|
||||
'input-antenna': u'',
|
||||
|
||||
'input-composite': u'',
|
||||
|
||||
'input-hdmi': u'',
|
||||
|
||||
'input-power': u'',
|
||||
|
||||
'input-svideo': u'',
|
||||
|
||||
'keyboard-hide': u'',
|
||||
|
||||
'keyboard': u'',
|
||||
|
||||
'laptop-chromebook': u'',
|
||||
|
||||
'laptop-mac': u'',
|
||||
|
||||
'laptop': u'',
|
||||
|
||||
'mic-off': u'',
|
||||
|
||||
'mic-outline': u'',
|
||||
|
||||
'mic-setting': u'',
|
||||
|
||||
'mic': u'',
|
||||
|
||||
'mouse': u'',
|
||||
|
||||
'network-alert': u'',
|
||||
|
||||
'network-locked': u'',
|
||||
|
||||
'network-off': u'',
|
||||
|
||||
'network-outline': u'',
|
||||
|
||||
'network-setting': u'',
|
||||
|
||||
'network': u'',
|
||||
|
||||
'phone-bluetooth': u'',
|
||||
|
||||
'phone-end': u'',
|
||||
|
||||
'phone-forwarded': u'',
|
||||
|
||||
'phone-in-talk': u'',
|
||||
|
||||
'phone-locked': u'',
|
||||
|
||||
'phone-missed': u'',
|
||||
|
||||
'phone-msg': u'',
|
||||
|
||||
'phone-paused': u'',
|
||||
|
||||
'phone-ring': u'',
|
||||
|
||||
'phone-setting': u'',
|
||||
|
||||
'phone-sip': u'',
|
||||
|
||||
'phone': u'',
|
||||
|
||||
'portable-wifi-changes': u'',
|
||||
|
||||
'portable-wifi-off': u'',
|
||||
|
||||
'portable-wifi': u'',
|
||||
|
||||
'radio': u'',
|
||||
|
||||
'reader': u'',
|
||||
|
||||
'remote-control-alt': u'',
|
||||
|
||||
'remote-control': u'',
|
||||
|
||||
'router': u'',
|
||||
|
||||
'scanner': u'',
|
||||
|
||||
'smartphone-android': u'',
|
||||
|
||||
'smartphone-download': u'',
|
||||
|
||||
'smartphone-erase': u'',
|
||||
|
||||
'smartphone-info': u'',
|
||||
|
||||
'smartphone-iphone': u'',
|
||||
|
||||
'smartphone-landscape-lock': u'',
|
||||
|
||||
'smartphone-landscape': u'',
|
||||
|
||||
'smartphone-lock': u'',
|
||||
|
||||
'smartphone-portrait-lock': u'',
|
||||
|
||||
'smartphone-ring': u'',
|
||||
|
||||
'smartphone-setting': u'',
|
||||
|
||||
'smartphone-setup': u'',
|
||||
|
||||
'smartphone': u'',
|
||||
|
||||
'speaker': u'',
|
||||
|
||||
'tablet-android': u'',
|
||||
|
||||
'tablet-mac': u'',
|
||||
|
||||
'tablet': u'',
|
||||
|
||||
'tv-alt-play': u'',
|
||||
|
||||
'tv-list': u'',
|
||||
|
||||
'tv-play': u'',
|
||||
|
||||
'tv': u'',
|
||||
|
||||
'usb': u'',
|
||||
|
||||
'videocam-off': u'',
|
||||
|
||||
'videocam-switch': u'',
|
||||
|
||||
'videocam': u'',
|
||||
|
||||
'watch': u'',
|
||||
|
||||
'wifi-alt-2': u'',
|
||||
|
||||
'wifi-alt': u'',
|
||||
|
||||
'wifi-info': u'',
|
||||
|
||||
'wifi-lock': u'',
|
||||
|
||||
'wifi-off': u'',
|
||||
|
||||
'wifi-outline': u'',
|
||||
|
||||
'wifi': u'',
|
||||
|
||||
'arrow-left-bottom': u'',
|
||||
|
||||
'arrow-left': u'',
|
||||
|
||||
'arrow-merge': u'',
|
||||
|
||||
'arrow-missed': u'',
|
||||
|
||||
'arrow-right-top': u'',
|
||||
|
||||
'arrow-right': u'',
|
||||
|
||||
'arrow-split': u'',
|
||||
|
||||
'arrows': u'',
|
||||
|
||||
'caret-down-circle': u'',
|
||||
|
||||
'caret-down': u'',
|
||||
|
||||
'caret-left-circle': u'',
|
||||
|
||||
'caret-left': u'',
|
||||
|
||||
'caret-right-circle': u'',
|
||||
|
||||
'caret-right': u'',
|
||||
|
||||
'caret-up-circle': u'',
|
||||
|
||||
'caret-up': u'',
|
||||
|
||||
'chevron-down': u'',
|
||||
|
||||
'chevron-left': u'',
|
||||
|
||||
'chevron-right': u'',
|
||||
|
||||
'chevron-up': u'',
|
||||
|
||||
'forward': u'',
|
||||
|
||||
'long-arrow-down': u'',
|
||||
|
||||
'long-arrow-left': u'',
|
||||
|
||||
'long-arrow-return': u'',
|
||||
|
||||
'long-arrow-right': u'',
|
||||
|
||||
'long-arrow-tab': u'',
|
||||
|
||||
'long-arrow-up': u'',
|
||||
|
||||
'rotate-ccw': u'',
|
||||
|
||||
'rotate-cw': u'',
|
||||
|
||||
'rotate-left': u'',
|
||||
|
||||
'rotate-right': u'',
|
||||
|
||||
'square-down': u'',
|
||||
|
||||
'square-right': u'',
|
||||
|
||||
'swap-alt': u'',
|
||||
|
||||
'swap-vertical-circle': u'',
|
||||
|
||||
'swap-vertical': u'',
|
||||
|
||||
'swap': u'',
|
||||
|
||||
'trending-down': u'',
|
||||
|
||||
'trending-flat': u'',
|
||||
|
||||
'trending-up': u'',
|
||||
|
||||
'unfold-less': u'',
|
||||
|
||||
'unfold-more': u'',
|
||||
|
||||
'apps': u'',
|
||||
|
||||
'grid-off': u'',
|
||||
|
||||
'grid': u'',
|
||||
|
||||
'view-agenda': u'',
|
||||
|
||||
'view-array': u'',
|
||||
|
||||
'view-carousel': u'',
|
||||
|
||||
'view-column': u'',
|
||||
|
||||
'view-comfy': u'',
|
||||
|
||||
'view-compact': u'',
|
||||
|
||||
'view-dashboard': u'',
|
||||
|
||||
'view-day': u'',
|
||||
|
||||
'view-headline': u'',
|
||||
|
||||
'view-list-alt': u'',
|
||||
|
||||
'view-list': u'',
|
||||
|
||||
'view-module': u'',
|
||||
|
||||
'view-quilt': u'',
|
||||
|
||||
'view-stream': u'',
|
||||
|
||||
'view-subtitles': u'',
|
||||
|
||||
'view-toc': u'',
|
||||
|
||||
'view-web': u'',
|
||||
|
||||
'view-week': u'',
|
||||
|
||||
'widgets': u'',
|
||||
|
||||
'alarm-check': u'',
|
||||
|
||||
'alarm-off': u'',
|
||||
|
||||
'alarm-plus': u'',
|
||||
|
||||
'alarm-snooze': u'',
|
||||
|
||||
'alarm': u'',
|
||||
|
||||
'calendar-alt': u'',
|
||||
|
||||
'calendar-check': u'',
|
||||
|
||||
'calendar-close': u'',
|
||||
|
||||
'calendar-note': u'',
|
||||
|
||||
'calendar': u'',
|
||||
|
||||
'time-countdown': u'',
|
||||
|
||||
'time-interval': u'',
|
||||
|
||||
'time-restore-setting': u'',
|
||||
|
||||
'time-restore': u'',
|
||||
|
||||
'time': u'',
|
||||
|
||||
'timer-off': u'',
|
||||
|
||||
'timer': u'',
|
||||
|
||||
'android-alt': u'',
|
||||
|
||||
'android': u'',
|
||||
|
||||
'apple': u'',
|
||||
|
||||
'behance': u'',
|
||||
|
||||
'codepen': u'',
|
||||
|
||||
'dribbble': u'',
|
||||
|
||||
'dropbox': u'',
|
||||
|
||||
'evernote': u'',
|
||||
|
||||
'facebook-box': u'',
|
||||
|
||||
'facebook': u'',
|
||||
|
||||
'github-box': u'',
|
||||
|
||||
'github': u'',
|
||||
|
||||
'google-drive': u'',
|
||||
|
||||
'google-earth': u'',
|
||||
|
||||
'google-glass': u'',
|
||||
|
||||
'google-maps': u'',
|
||||
|
||||
'google-pages': u'',
|
||||
|
||||
'google-play': u'',
|
||||
|
||||
'google-plus-box': u'',
|
||||
|
||||
'google-plus': u'',
|
||||
|
||||
'google': u'',
|
||||
|
||||
'instagram': u'',
|
||||
|
||||
'language-css3': u'',
|
||||
|
||||
'language-html5': u'',
|
||||
|
||||
'language-javascript': u'',
|
||||
|
||||
'language-python-alt': u'',
|
||||
|
||||
'language-python': u'',
|
||||
|
||||
'lastfm': u'',
|
||||
|
||||
'linkedin-box': u'',
|
||||
|
||||
'paypal': u'',
|
||||
|
||||
'pinterest-box': u'',
|
||||
|
||||
'pocket': u'',
|
||||
|
||||
'polymer': u'',
|
||||
|
||||
'rss': u'',
|
||||
|
||||
'share': u'',
|
||||
|
||||
'stackoverflow': u'',
|
||||
|
||||
'steam-square': u'',
|
||||
|
||||
'steam': u'',
|
||||
|
||||
'twitter-box': u'',
|
||||
|
||||
'twitter': u'',
|
||||
|
||||
'vk': u'',
|
||||
|
||||
'wikipedia': u'',
|
||||
|
||||
'windows': u'',
|
||||
|
||||
'500px': u'',
|
||||
|
||||
'8tracks': u'',
|
||||
|
||||
'amazon': u'',
|
||||
|
||||
'blogger': u'',
|
||||
|
||||
'delicious': u'',
|
||||
|
||||
'disqus': u'',
|
||||
|
||||
'flattr': u'',
|
||||
|
||||
'flickr': u'',
|
||||
|
||||
'github-alt': u'',
|
||||
|
||||
'google-old': u'',
|
||||
|
||||
'linkedin': u'',
|
||||
|
||||
'odnoklassniki': u'',
|
||||
|
||||
'outlook': u'',
|
||||
|
||||
'paypal-alt': u'',
|
||||
|
||||
'pinterest': u'',
|
||||
|
||||
'playstation': u'',
|
||||
|
||||
'reddit': u'',
|
||||
|
||||
'skype': u'',
|
||||
|
||||
'slideshare': u'',
|
||||
|
||||
'soundcloud': u'',
|
||||
|
||||
'tumblr': u'',
|
||||
|
||||
'twitch': u'',
|
||||
|
||||
'vimeo': u'',
|
||||
|
||||
'whatsapp': u'',
|
||||
|
||||
'xbox': u'',
|
||||
|
||||
'yahoo': u'',
|
||||
|
||||
'youtube-play': u'',
|
||||
|
||||
'youtube': u'',
|
||||
|
||||
'aspect-ratio-alt': u'',
|
||||
|
||||
'aspect-ratio': u'',
|
||||
|
||||
'blur-circular': u'',
|
||||
|
||||
'blur-linear': u'',
|
||||
|
||||
'blur-off': u'',
|
||||
|
||||
'blur': u'',
|
||||
|
||||
'brightness-2': u'',
|
||||
|
||||
'brightness-3': u'',
|
||||
|
||||
'brightness-4': u'',
|
||||
|
||||
'brightness-5': u'',
|
||||
|
||||
'brightness-6': u'',
|
||||
|
||||
'brightness-7': u'',
|
||||
|
||||
'brightness-auto': u'',
|
||||
|
||||
'brightness-setting': u'',
|
||||
|
||||
'broken-image': u'',
|
||||
|
||||
'center-focus-strong': u'',
|
||||
|
||||
'center-focus-weak': u'',
|
||||
|
||||
'compare': u'',
|
||||
|
||||
'crop-16-9': u'',
|
||||
|
||||
'crop-3-2': u'',
|
||||
|
||||
'crop-5-4': u'',
|
||||
|
||||
'crop-7-5': u'',
|
||||
|
||||
'crop-din': u'',
|
||||
|
||||
'crop-free': u'',
|
||||
|
||||
'crop-landscape': u'',
|
||||
|
||||
'crop-portrait': u'',
|
||||
|
||||
'crop-square': u'',
|
||||
|
||||
'exposure-alt': u'',
|
||||
|
||||
'exposure': u'',
|
||||
|
||||
'filter-b-and-w': u'',
|
||||
|
||||
'filter-center-focus': u'',
|
||||
|
||||
'filter-frames': u'',
|
||||
|
||||
'filter-tilt-shift': u'',
|
||||
|
||||
'gradient': u'',
|
||||
|
||||
'grain': u'',
|
||||
|
||||
'graphic-eq': u'',
|
||||
|
||||
'hdr-off': u'',
|
||||
|
||||
'hdr-strong': u'',
|
||||
|
||||
'hdr-weak': u'',
|
||||
|
||||
'hdr': u'',
|
||||
|
||||
'iridescent': u'',
|
||||
|
||||
'leak-off': u'',
|
||||
|
||||
'leak': u'',
|
||||
|
||||
'looks': u'',
|
||||
|
||||
'loupe': u'',
|
||||
|
||||
'panorama-horizontal': u'',
|
||||
|
||||
'panorama-vertical': u'',
|
||||
|
||||
'panorama-wide-angle': u'',
|
||||
|
||||
'photo-size-select-large': u'',
|
||||
|
||||
'photo-size-select-small': u'',
|
||||
|
||||
'picture-in-picture': u'',
|
||||
|
||||
'slideshow': u'',
|
||||
|
||||
'texture': u'',
|
||||
|
||||
'tonality': u'',
|
||||
|
||||
'vignette': u'',
|
||||
|
||||
'wb-auto': u'',
|
||||
|
||||
'eject-alt': u'',
|
||||
|
||||
'eject': u'',
|
||||
|
||||
'equalizer': u'',
|
||||
|
||||
'fast-forward': u'',
|
||||
|
||||
'fast-rewind': u'',
|
||||
|
||||
'forward-10': u'',
|
||||
|
||||
'forward-30': u'',
|
||||
|
||||
'forward-5': u'',
|
||||
|
||||
'hearing': u'',
|
||||
|
||||
'pause-circle-outline': u'',
|
||||
|
||||
'pause-circle': u'',
|
||||
|
||||
'pause': u'',
|
||||
|
||||
'play-circle-outline': u'',
|
||||
|
||||
'play-circle': u'',
|
||||
|
||||
'play': u'',
|
||||
|
||||
'playlist-audio': u'',
|
||||
|
||||
'playlist-plus': u'',
|
||||
|
||||
'repeat-one': u'',
|
||||
|
||||
'repeat': u'',
|
||||
|
||||
'replay-10': u'',
|
||||
|
||||
'replay-30': u'',
|
||||
|
||||
'replay-5': u'',
|
||||
|
||||
'replay': u'',
|
||||
|
||||
'shuffle': u'',
|
||||
|
||||
'skip-next': u'',
|
||||
|
||||
'skip-previous': u'',
|
||||
|
||||
'stop': u'',
|
||||
|
||||
'surround-sound': u'',
|
||||
|
||||
'tune': u'',
|
||||
|
||||
'volume-down': u'',
|
||||
|
||||
'volume-mute': u'',
|
||||
|
||||
'volume-off': u'',
|
||||
|
||||
'volume-up': u'',
|
||||
|
||||
'n-1-square': u'',
|
||||
|
||||
'n-2-square': u'',
|
||||
|
||||
'n-3-square': u'',
|
||||
|
||||
'n-4-square': u'',
|
||||
|
||||
'n-5-square': u'',
|
||||
|
||||
'n-6-square': u'',
|
||||
|
||||
'neg-1': u'',
|
||||
|
||||
'neg-2': u'',
|
||||
|
||||
'plus-1': u'',
|
||||
|
||||
'plus-2': u'',
|
||||
|
||||
'sec-10': u'',
|
||||
|
||||
'sec-3': u'',
|
||||
|
||||
'zero': u'',
|
||||
|
||||
'airline-seat-flat-angled': u'',
|
||||
|
||||
'airline-seat-flat': u'',
|
||||
|
||||
'airline-seat-individual-suite': u'',
|
||||
|
||||
'airline-seat-legroom-extra': u'',
|
||||
|
||||
'airline-seat-legroom-normal': u'',
|
||||
|
||||
'airline-seat-legroom-reduced': u'',
|
||||
|
||||
'airline-seat-recline-extra': u'',
|
||||
|
||||
'airline-seat-recline-normal': u'',
|
||||
|
||||
'airplay': u'',
|
||||
|
||||
'closed-caption': u'',
|
||||
|
||||
'confirmation-number': u'',
|
||||
|
||||
'developer-board': u'',
|
||||
|
||||
'disc-full': u'',
|
||||
|
||||
'explicit': u'',
|
||||
|
||||
'flight-land': u'',
|
||||
|
||||
'flight-takeoff': u'',
|
||||
|
||||
'flip-to-back': u'',
|
||||
|
||||
'flip-to-front': u'',
|
||||
|
||||
'group-work': u'',
|
||||
|
||||
'hd': u'',
|
||||
|
||||
'hq': u'',
|
||||
|
||||
'markunread-mailbox': u'',
|
||||
|
||||
'memory': u'',
|
||||
|
||||
'nfc': u'',
|
||||
|
||||
'play-for-work': u'',
|
||||
|
||||
'power-input': u'',
|
||||
|
||||
'present-to-all': u'',
|
||||
|
||||
'satellite': u'',
|
||||
|
||||
'tap-and-play': u'',
|
||||
|
||||
'vibration': u'',
|
||||
|
||||
'voicemail': u'',
|
||||
}
|
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 19 KiB |
|
@ -1 +0,0 @@
|
|||
{"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]}}
|
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 43 KiB |
|
@ -1 +0,0 @@
|
|||
{"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]}}
|
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 28 KiB |
|
@ -1 +0,0 @@
|
|||
{"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]}}
|
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 26 KiB |
|
@ -1 +0,0 @@
|
|||
{"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]}}
|
|
@ -1,94 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import sp
|
||||
from kivy.properties import OptionProperty, DictProperty, ListProperty
|
||||
from kivy.uix.label import Label
|
||||
from kivymd.material_resources import DEVICE_TYPE
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string('''
|
||||
<MDLabel>
|
||||
disabled_color: self.theme_cls.disabled_hint_text_color
|
||||
text_size: (self.width, None)
|
||||
''')
|
||||
|
||||
|
||||
class MDLabel(ThemableBehavior, Label):
|
||||
font_style = OptionProperty(
|
||||
'Body1', options=['Body1', 'Body2', 'Caption', 'Subhead', 'Title',
|
||||
'Headline', 'Display1', 'Display2', 'Display3',
|
||||
'Display4', 'Button', 'Icon'])
|
||||
|
||||
# Font, Bold, Mobile size, Desktop size (None if same as Mobile)
|
||||
_font_styles = DictProperty({'Body1': ['Roboto', False, 14, 13],
|
||||
'Body2': ['Roboto', True, 14, 13],
|
||||
'Caption': ['Roboto', False, 12, None],
|
||||
'Subhead': ['Roboto', False, 16, 15],
|
||||
'Title': ['Roboto', True, 20, None],
|
||||
'Headline': ['Roboto', False, 24, None],
|
||||
'Display1': ['Roboto', False, 34, None],
|
||||
'Display2': ['Roboto', False, 45, None],
|
||||
'Display3': ['Roboto', False, 56, None],
|
||||
'Display4': ['RobotoLight', False, 112, None],
|
||||
'Button': ['Roboto', True, 14, None],
|
||||
'Icon': ['Icons', False, 24, None]})
|
||||
|
||||
theme_text_color = OptionProperty(None, allownone=True,
|
||||
options=['Primary', 'Secondary', 'Hint',
|
||||
'Error', 'Custom'])
|
||||
|
||||
text_color = ListProperty(None, allownone=True)
|
||||
|
||||
_currently_bound_property = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDLabel, self).__init__(**kwargs)
|
||||
self.on_theme_text_color(None, self.theme_text_color)
|
||||
self.on_font_style(None, self.font_style)
|
||||
self.on_opposite_colors(None, self.opposite_colors)
|
||||
|
||||
def on_font_style(self, instance, style):
|
||||
info = self._font_styles[style]
|
||||
self.font_name = info[0]
|
||||
self.bold = info[1]
|
||||
if DEVICE_TYPE == 'desktop' and info[3] is not None:
|
||||
self.font_size = sp(info[3])
|
||||
else:
|
||||
self.font_size = sp(info[2])
|
||||
|
||||
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)
|
||||
c = {}
|
||||
if value == 'Primary':
|
||||
c = {'text_color' if not op else 'opposite_text_color': setter}
|
||||
t.bind(**c)
|
||||
self.color = t.text_color if not op else t.opposite_text_color
|
||||
elif value == 'Secondary':
|
||||
c = {'secondary_text_color' if not op else
|
||||
'opposite_secondary_text_color': setter}
|
||||
t.bind(**c)
|
||||
self.color = t.secondary_text_color if not op else \
|
||||
t.opposite_secondary_text_color
|
||||
elif value == 'Hint':
|
||||
c = {'disabled_hint_text_color' if not op else
|
||||
'opposite_disabled_hint_text_color': setter}
|
||||
t.bind(**c)
|
||||
self.color = t.disabled_hint_text_color if not op else \
|
||||
t.opposite_disabled_hint_text_color
|
||||
elif value == 'Error':
|
||||
c = {'error_color': setter}
|
||||
t.bind(**c)
|
||||
self.color = t.error_color
|
||||
elif value == 'Custom':
|
||||
self.color = self.text_color if self.text_color else (0, 0, 0, 1)
|
||||
self._currently_bound_property = c
|
||||
|
||||
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)
|
|
@ -1,531 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Lists
|
||||
=====
|
||||
|
||||
`Material Design spec, Lists page <https://www.google.com/design/spec/components/lists.html>`_
|
||||
|
||||
`Material Design spec, Lists: Controls page <https://www.google.com/design/spec/components/lists-controls.html>`_
|
||||
|
||||
The class :class:`MDList` in combination with a ListItem like
|
||||
:class:`OneLineListItem` will create a list that expands as items are added to
|
||||
it, working nicely with Kivy's :class:`~kivy.uix.scrollview.ScrollView`.
|
||||
|
||||
|
||||
Simple examples
|
||||
---------------
|
||||
|
||||
Kv Lang:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ScrollView:
|
||||
do_scroll_x: False # Important for MD compliance
|
||||
MDList:
|
||||
OneLineListItem:
|
||||
text: "Single-line item"
|
||||
TwoLineListItem:
|
||||
text: "Two-line item"
|
||||
secondary_text: "Secondary text here"
|
||||
ThreeLineListItem:
|
||||
text: "Three-line item"
|
||||
secondary_text: "This is a multi-line label where you can fit more text than usual"
|
||||
|
||||
|
||||
Python:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Sets up ScrollView with MDList, as normally used in Android:
|
||||
sv = ScrollView()
|
||||
ml = MDList()
|
||||
sv.add_widget(ml)
|
||||
|
||||
contacts = ["Paula", "John", "Kate", "Vlad"]
|
||||
for c in contacts:
|
||||
ml.add_widget(
|
||||
OneLineListItem(
|
||||
text=c
|
||||
)
|
||||
)
|
||||
|
||||
Advanced usage
|
||||
--------------
|
||||
|
||||
Due to the variety in sizes and controls in the MD spec, this module suffers
|
||||
from a certain level of complexity to keep the widgets compliant, flexible
|
||||
and performant.
|
||||
|
||||
For this KivyMD provides ListItems that try to cover the most common usecases,
|
||||
when those are insufficient, there's a base class called :class:`ListItem`
|
||||
which you can use to create your own ListItems. This documentation will only
|
||||
cover the provided ones, for custom implementations please refer to this
|
||||
module's source code.
|
||||
|
||||
Text only ListItems
|
||||
-------------------
|
||||
|
||||
- :class:`~OneLineListItem`
|
||||
- :class:`~TwoLineListItem`
|
||||
- :class:`~ThreeLineListItem`
|
||||
|
||||
These are the simplest ones. The :attr:`~ListItem.text` attribute changes the
|
||||
text in the most prominent line, while :attr:`~ListItem.secondary_text`
|
||||
changes the second and third line.
|
||||
|
||||
If there are only two lines, :attr:`~ListItem.secondary_text` will shorten
|
||||
the text to fit in case it is too long; if a third line is available, it will
|
||||
instead wrap the text to make use of it.
|
||||
|
||||
ListItems with widget containers
|
||||
--------------------------------
|
||||
|
||||
- :class:`~OneLineAvatarListItem`
|
||||
- :class:`~TwoLineAvatarListItem`
|
||||
- :class:`~ThreeLineAvatarListItem`
|
||||
- :class:`~OneLineIconListItem`
|
||||
- :class:`~TwoLineIconListItem`
|
||||
- :class:`~ThreeLineIconListItem`
|
||||
- :class:`~OneLineAvatarIconListItem`
|
||||
- :class:`~TwoLineAvatarIconListItem`
|
||||
- :class:`~ThreeLineAvatarIconListItem`
|
||||
|
||||
These widgets will take other widgets that inherit from :class:`~ILeftBody`,
|
||||
:class:`ILeftBodyTouch`, :class:`~IRightBody` or :class:`~IRightBodyTouch` and
|
||||
put them in their corresponding container.
|
||||
|
||||
As the name implies, :class:`~ILeftBody` and :class:`~IRightBody` will signal
|
||||
that the widget goes into the left or right container, respectively.
|
||||
|
||||
:class:`~ILeftBodyTouch` and :class:`~IRightBodyTouch` do the same thing,
|
||||
except these widgets will also receive touch events that occur within their
|
||||
surfaces.
|
||||
|
||||
Python example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ContactPhoto(ILeftBody, AsyncImage):
|
||||
pass
|
||||
|
||||
class MessageButton(IRightBodyTouch, MDIconButton):
|
||||
phone_number = StringProperty()
|
||||
|
||||
def on_release(self):
|
||||
# sample code:
|
||||
Dialer.send_sms(phone_number, "Hey! What's up?")
|
||||
pass
|
||||
|
||||
# Sets up ScrollView with MDList, as normally used in Android:
|
||||
sv = ScrollView()
|
||||
ml = MDList()
|
||||
sv.add_widget(ml)
|
||||
|
||||
contacts = [
|
||||
["Annie", "555-24235", "http://myphotos.com/annie.png"],
|
||||
["Bob", "555-15423", "http://myphotos.com/bob.png"],
|
||||
["Claire", "555-66098", "http://myphotos.com/claire.png"]
|
||||
]
|
||||
|
||||
for c in contacts:
|
||||
item = TwoLineAvatarIconListItem(
|
||||
text=c[0],
|
||||
secondary_text=c[1]
|
||||
)
|
||||
item.add_widget(ContactPhoto(source=c[2]))
|
||||
item.add_widget(MessageButton(phone_number=c[1])
|
||||
ml.add_widget(item)
|
||||
|
||||
API
|
||||
---
|
||||
'''
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import ObjectProperty, StringProperty, NumericProperty, \
|
||||
ListProperty, OptionProperty
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
import kivymd.material_resources as m_res
|
||||
from kivymd.ripplebehavior import RectangularRippleBehavior
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string('''
|
||||
#:import m_res kivymd.material_resources
|
||||
<MDList>
|
||||
cols: 1
|
||||
size_hint_y: None
|
||||
height: self._min_list_height
|
||||
padding: 0, self._list_vertical_padding
|
||||
|
||||
<BaseListItem>
|
||||
size_hint_y: None
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.theme_cls.divider_color
|
||||
Line:
|
||||
points: root.x,root.y, root.x+self.width,root.y
|
||||
BoxLayout:
|
||||
id: _text_container
|
||||
orientation: 'vertical'
|
||||
pos: root.pos
|
||||
padding: root._txt_left_pad, root._txt_top_pad, root._txt_right_pad, root._txt_bot_pad
|
||||
MDLabel:
|
||||
id: _lbl_primary
|
||||
text: root.text
|
||||
font_style: root.font_style
|
||||
theme_text_color: root.theme_text_color
|
||||
text_color: root.text_color
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
MDLabel:
|
||||
id: _lbl_secondary
|
||||
text: '' if root._num_lines == 1 else root.secondary_text
|
||||
font_style: root.secondary_font_style
|
||||
theme_text_color: root.secondary_theme_text_color
|
||||
text_color: root.secondary_text_color
|
||||
size_hint_y: None
|
||||
height: 0 if root._num_lines == 1 else self.texture_size[1]
|
||||
shorten: True if root._num_lines == 2 else False
|
||||
|
||||
<OneLineAvatarListItem>
|
||||
BoxLayout:
|
||||
id: _left_container
|
||||
size_hint: None, None
|
||||
x: root.x + dp(16)
|
||||
y: root.y + root.height/2 - self.height/2
|
||||
size: dp(40), dp(40)
|
||||
|
||||
<ThreeLineAvatarListItem>
|
||||
BoxLayout:
|
||||
id: _left_container
|
||||
size_hint: None, None
|
||||
x: root.x + dp(16)
|
||||
y: root.y + root.height - root._txt_top_pad - self.height - dp(5)
|
||||
size: dp(40), dp(40)
|
||||
|
||||
<OneLineIconListItem>
|
||||
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)
|
||||
|
||||
<ThreeLineIconListItem>
|
||||
BoxLayout:
|
||||
id: _left_container
|
||||
size_hint: None, None
|
||||
x: root.x + dp(16)
|
||||
y: root.y + root.height - root._txt_top_pad - self.height - dp(5)
|
||||
size: dp(48), dp(48)
|
||||
|
||||
<OneLineRightIconListItem>
|
||||
BoxLayout:
|
||||
id: _right_container
|
||||
size_hint: None, None
|
||||
x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
|
||||
y: root.y + root.height/2 - self.height/2
|
||||
size: dp(48), dp(48)
|
||||
|
||||
<ThreeLineRightIconListItem>
|
||||
BoxLayout:
|
||||
id: _right_container
|
||||
size_hint: None, None
|
||||
x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
|
||||
y: root.y + root.height/2 - self.height/2
|
||||
size: dp(48), dp(48)
|
||||
|
||||
<OneLineAvatarIconListItem>
|
||||
BoxLayout:
|
||||
id: _right_container
|
||||
size_hint: None, None
|
||||
x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
|
||||
y: root.y + root.height/2 - self.height/2
|
||||
size: dp(48), dp(48)
|
||||
|
||||
<TwoLineAvatarIconListItem>
|
||||
BoxLayout:
|
||||
id: _right_container
|
||||
size_hint: None, None
|
||||
x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
|
||||
y: root.y + root.height/2 - self.height/2
|
||||
size: dp(48), dp(48)
|
||||
|
||||
<ThreeLineAvatarIconListItem>
|
||||
BoxLayout:
|
||||
id: _right_container
|
||||
size_hint: None, None
|
||||
x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
|
||||
y: root.y + root.height - root._txt_top_pad - self.height - dp(5)
|
||||
size: dp(48), dp(48)
|
||||
''')
|
||||
|
||||
|
||||
class MDList(GridLayout):
|
||||
'''ListItem container. Best used in conjunction with a
|
||||
:class:`kivy.uix.ScrollView`.
|
||||
|
||||
When adding (or removing) a widget, it will resize itself to fit its
|
||||
children, plus top and bottom paddings as described by the MD spec.
|
||||
'''
|
||||
selected = ObjectProperty()
|
||||
_min_list_height = dp(16)
|
||||
_list_vertical_padding = dp(8)
|
||||
|
||||
icon = StringProperty()
|
||||
|
||||
def add_widget(self, widget, index=0):
|
||||
super(MDList, self).add_widget(widget, index)
|
||||
self.height += widget.height
|
||||
|
||||
def remove_widget(self, widget):
|
||||
super(MDList, self).remove_widget(widget)
|
||||
self.height -= widget.height
|
||||
|
||||
|
||||
class BaseListItem(ThemableBehavior, RectangularRippleBehavior,
|
||||
ButtonBehavior, FloatLayout):
|
||||
'''Base class to all ListItems. Not supposed to be instantiated on its own.
|
||||
'''
|
||||
|
||||
text = StringProperty()
|
||||
'''Text shown in the first line.
|
||||
|
||||
:attr:`text` is a :class:`~kivy.properties.StringProperty` and defaults
|
||||
to "".
|
||||
'''
|
||||
|
||||
text_color = ListProperty(None)
|
||||
''' Text color used if theme_text_color is set to 'Custom' '''
|
||||
|
||||
font_style = OptionProperty(
|
||||
'Subhead', options=['Body1', 'Body2', 'Caption', 'Subhead', 'Title',
|
||||
'Headline', 'Display1', 'Display2', 'Display3',
|
||||
'Display4', 'Button', 'Icon'])
|
||||
|
||||
theme_text_color = StringProperty('Primary',allownone=True)
|
||||
''' Theme text color for primary text '''
|
||||
|
||||
secondary_text = StringProperty()
|
||||
'''Text shown in the second and potentially third line.
|
||||
|
||||
The text will wrap into the third line if the ListItem's type is set to
|
||||
\'one-line\'. It can be forced into the third line by adding a \\n
|
||||
escape sequence.
|
||||
|
||||
:attr:`secondary_text` is a :class:`~kivy.properties.StringProperty` and
|
||||
defaults to "".
|
||||
'''
|
||||
|
||||
secondary_text_color = ListProperty(None)
|
||||
''' Text color used for secondary text if secondary_theme_text_color
|
||||
is set to 'Custom' '''
|
||||
|
||||
secondary_theme_text_color = StringProperty('Secondary',allownone=True)
|
||||
''' Theme text color for secondary primary text '''
|
||||
|
||||
secondary_font_style = OptionProperty(
|
||||
'Body1', options=['Body1', 'Body2', 'Caption', 'Subhead', 'Title',
|
||||
'Headline', 'Display1', 'Display2', 'Display3',
|
||||
'Display4', 'Button', 'Icon'])
|
||||
|
||||
_txt_left_pad = NumericProperty(dp(16))
|
||||
_txt_top_pad = NumericProperty()
|
||||
_txt_bot_pad = NumericProperty()
|
||||
_txt_right_pad = NumericProperty(m_res.HORIZ_MARGINS)
|
||||
_num_lines = 2
|
||||
|
||||
|
||||
class ILeftBody:
|
||||
'''Pseudo-interface for widgets that go in the left container for
|
||||
ListItems that support it.
|
||||
|
||||
Implements nothing and requires no implementation, for annotation only.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class ILeftBodyTouch:
|
||||
'''Same as :class:`~ILeftBody`, but allows the widget to receive touch
|
||||
events instead of triggering the ListItem's ripple effect
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class IRightBody:
|
||||
'''Pseudo-interface for widgets that go in the right container for
|
||||
ListItems that support it.
|
||||
|
||||
Implements nothing and requires no implementation, for annotation only.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class IRightBodyTouch:
|
||||
'''Same as :class:`~IRightBody`, but allows the widget to receive touch
|
||||
events instead of triggering the ListItem's ripple effect
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class ContainerSupport:
|
||||
'''Overrides add_widget in a ListItem to include support for I*Body
|
||||
widgets when the appropiate containers are present.
|
||||
'''
|
||||
_touchable_widgets = ListProperty()
|
||||
|
||||
def add_widget(self, widget, index=0):
|
||||
if issubclass(widget.__class__, ILeftBody):
|
||||
self.ids['_left_container'].add_widget(widget)
|
||||
elif issubclass(widget.__class__, ILeftBodyTouch):
|
||||
self.ids['_left_container'].add_widget(widget)
|
||||
self._touchable_widgets.append(widget)
|
||||
elif issubclass(widget.__class__, IRightBody):
|
||||
self.ids['_right_container'].add_widget(widget)
|
||||
elif issubclass(widget.__class__, IRightBodyTouch):
|
||||
self.ids['_right_container'].add_widget(widget)
|
||||
self._touchable_widgets.append(widget)
|
||||
else:
|
||||
return super(BaseListItem, self).add_widget(widget,index)
|
||||
|
||||
def remove_widget(self, widget):
|
||||
super(BaseListItem, self).remove_widget(widget)
|
||||
if widget in self._touchable_widgets:
|
||||
self._touchable_widgets.remove(widget)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if self.propagate_touch_to_touchable_widgets(touch, 'down'):
|
||||
return
|
||||
super(BaseListItem, self).on_touch_down(touch)
|
||||
|
||||
def on_touch_move(self, touch, *args):
|
||||
if self.propagate_touch_to_touchable_widgets(touch, 'move', *args):
|
||||
return
|
||||
super(BaseListItem, self).on_touch_move(touch, *args)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if self.propagate_touch_to_touchable_widgets(touch, 'up'):
|
||||
return
|
||||
super(BaseListItem, self).on_touch_up(touch)
|
||||
|
||||
def propagate_touch_to_touchable_widgets(self, touch, touch_event, *args):
|
||||
triggered = False
|
||||
for i in self._touchable_widgets:
|
||||
if i.collide_point(touch.x, touch.y):
|
||||
triggered = True
|
||||
if touch_event == 'down':
|
||||
i.on_touch_down(touch)
|
||||
elif touch_event == 'move':
|
||||
i.on_touch_move(touch, *args)
|
||||
elif touch_event == 'up':
|
||||
i.on_touch_up(touch)
|
||||
return triggered
|
||||
|
||||
|
||||
class OneLineListItem(BaseListItem):
|
||||
_txt_top_pad = NumericProperty(dp(16))
|
||||
_txt_bot_pad = NumericProperty(dp(15)) # dp(20) - dp(5)
|
||||
_num_lines = 1
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(OneLineListItem, self).__init__(**kwargs)
|
||||
self.height = dp(48)
|
||||
|
||||
|
||||
class TwoLineListItem(BaseListItem):
|
||||
_txt_top_pad = NumericProperty(dp(20))
|
||||
_txt_bot_pad = NumericProperty(dp(15)) # dp(20) - dp(5)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(TwoLineListItem, self).__init__(**kwargs)
|
||||
self.height = dp(72)
|
||||
|
||||
|
||||
class ThreeLineListItem(BaseListItem):
|
||||
_txt_top_pad = NumericProperty(dp(16))
|
||||
_txt_bot_pad = NumericProperty(dp(15)) # dp(20) - dp(5)
|
||||
_num_lines = 3
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(ThreeLineListItem, self).__init__(**kwargs)
|
||||
self.height = dp(88)
|
||||
|
||||
|
||||
class OneLineAvatarListItem(ContainerSupport, BaseListItem):
|
||||
_txt_left_pad = NumericProperty(dp(72))
|
||||
_txt_top_pad = NumericProperty(dp(20))
|
||||
_txt_bot_pad = NumericProperty(dp(19)) # dp(24) - dp(5)
|
||||
_num_lines = 1
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(OneLineAvatarListItem, self).__init__(**kwargs)
|
||||
self.height = dp(56)
|
||||
|
||||
|
||||
class TwoLineAvatarListItem(OneLineAvatarListItem):
|
||||
_txt_top_pad = NumericProperty(dp(20))
|
||||
_txt_bot_pad = NumericProperty(dp(15)) # dp(20) - dp(5)
|
||||
_num_lines = 2
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(BaseListItem, self).__init__(**kwargs)
|
||||
self.height = dp(72)
|
||||
|
||||
|
||||
class ThreeLineAvatarListItem(ContainerSupport, ThreeLineListItem):
|
||||
_txt_left_pad = NumericProperty(dp(72))
|
||||
|
||||
|
||||
class OneLineIconListItem(ContainerSupport, OneLineListItem):
|
||||
_txt_left_pad = NumericProperty(dp(72))
|
||||
|
||||
|
||||
class TwoLineIconListItem(OneLineIconListItem):
|
||||
_txt_top_pad = NumericProperty(dp(20))
|
||||
_txt_bot_pad = NumericProperty(dp(15)) # dp(20) - dp(5)
|
||||
_num_lines = 2
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(BaseListItem, self).__init__(**kwargs)
|
||||
self.height = dp(72)
|
||||
|
||||
|
||||
class ThreeLineIconListItem(ContainerSupport, ThreeLineListItem):
|
||||
_txt_left_pad = NumericProperty(dp(72))
|
||||
|
||||
|
||||
class OneLineRightIconListItem(ContainerSupport, OneLineListItem):
|
||||
# dp(40) = dp(16) + dp(24):
|
||||
_txt_right_pad = NumericProperty(dp(40) + m_res.HORIZ_MARGINS)
|
||||
|
||||
|
||||
class TwoLineRightIconListItem(OneLineRightIconListItem):
|
||||
_txt_top_pad = NumericProperty(dp(20))
|
||||
_txt_bot_pad = NumericProperty(dp(15)) # dp(20) - dp(5)
|
||||
_num_lines = 2
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(BaseListItem, self).__init__(**kwargs)
|
||||
self.height = dp(72)
|
||||
|
||||
|
||||
class ThreeLineRightIconListitem(ContainerSupport, ThreeLineListItem):
|
||||
# dp(40) = dp(16) + dp(24):
|
||||
_txt_right_pad = NumericProperty(dp(40) + m_res.HORIZ_MARGINS)
|
||||
|
||||
|
||||
class OneLineAvatarIconListItem(OneLineAvatarListItem):
|
||||
# dp(40) = dp(16) + dp(24):
|
||||
_txt_right_pad = NumericProperty(dp(40) + m_res.HORIZ_MARGINS)
|
||||
|
||||
|
||||
class TwoLineAvatarIconListItem(TwoLineAvatarListItem):
|
||||
# dp(40) = dp(16) + dp(24):
|
||||
_txt_right_pad = NumericProperty(dp(40) + m_res.HORIZ_MARGINS)
|
||||
|
||||
|
||||
class ThreeLineAvatarIconListItem(ThreeLineAvatarListItem):
|
||||
# dp(40) = dp(16) + dp(24):
|
||||
_txt_right_pad = NumericProperty(dp(40) + m_res.HORIZ_MARGINS)
|
|
@ -1,50 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy import platform
|
||||
from kivy.core.window import Window
|
||||
from kivy.metrics import dp
|
||||
from kivymd import fonts_path
|
||||
|
||||
# Feel free to override this const if you're designing for a device such as
|
||||
# a GNU/Linux tablet.
|
||||
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)
|
||||
|
||||
FONTS = [
|
||||
{
|
||||
"name": "Roboto",
|
||||
"fn_regular": fonts_path + 'Roboto-Regular.ttf',
|
||||
"fn_bold": fonts_path + 'Roboto-Medium.ttf',
|
||||
"fn_italic": fonts_path + 'Roboto-Italic.ttf',
|
||||
"fn_bolditalic": fonts_path + 'Roboto-MediumItalic.ttf'
|
||||
},
|
||||
{
|
||||
"name": "RobotoLight",
|
||||
"fn_regular": fonts_path + 'Roboto-Thin.ttf',
|
||||
"fn_bold": fonts_path + 'Roboto-Light.ttf',
|
||||
"fn_italic": fonts_path + 'Roboto-ThinItalic.ttf',
|
||||
"fn_bolditalic": fonts_path + 'Roboto-LightItalic.ttf'
|
||||
},
|
||||
{
|
||||
"name": "Icons",
|
||||
"fn_regular": fonts_path + 'Material-Design-Iconic-Font.ttf'
|
||||
}
|
||||
]
|
|
@ -1,192 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.lang import Builder
|
||||
from kivy.garden.recycleview import RecycleView
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import NumericProperty, ListProperty, OptionProperty, \
|
||||
StringProperty
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
import kivymd.material_resources as m_res
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string('''
|
||||
#:import STD_INC kivymd.material_resources.STANDARD_INCREMENT
|
||||
<MDMenuItem>
|
||||
size_hint_y: None
|
||||
height: dp(48)
|
||||
padding: dp(16), 0
|
||||
on_release: root.parent.parent.parent.parent.dismiss() # Horrible, but hey it works
|
||||
MDLabel:
|
||||
text: root.text
|
||||
theme_text_color: 'Primary'
|
||||
|
||||
<MDMenu>
|
||||
size_hint: None, None
|
||||
width: root.width_mult * STD_INC
|
||||
key_viewclass: 'viewclass'
|
||||
key_size: 'height'
|
||||
|
||||
<MDDropdownMenu>
|
||||
FloatLayout:
|
||||
id: fl
|
||||
MDMenu:
|
||||
id: md_menu
|
||||
data: root.items
|
||||
width_mult: root.width_mult
|
||||
size_hint: None, None
|
||||
size: 0,0
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: root.theme_cls.bg_light
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
''')
|
||||
|
||||
|
||||
class MDMenuItem(ButtonBehavior, BoxLayout):
|
||||
text = StringProperty()
|
||||
|
||||
|
||||
class MDMenu(RecycleView):
|
||||
width_mult = NumericProperty(1)
|
||||
|
||||
|
||||
class MDDropdownMenu(ThemableBehavior, BoxLayout):
|
||||
items = ListProperty()
|
||||
'''See :attr:`~kivy.garden.recycleview.RecycleView.data`
|
||||
'''
|
||||
|
||||
width_mult = NumericProperty(1)
|
||||
'''This number multiplied by the standard increment (56dp on mobile,
|
||||
64dp on desktop, determines the width of the menu items.
|
||||
|
||||
If the resulting number were to be too big for the application Window,
|
||||
the multiplier will be adjusted for the biggest possible one.
|
||||
'''
|
||||
|
||||
max_height = NumericProperty()
|
||||
'''The menu will grow no bigger than this number.
|
||||
|
||||
Set to 0 for no limit. Defaults to 0.
|
||||
'''
|
||||
|
||||
border_margin = NumericProperty(dp(4))
|
||||
'''Margin between Window border and menu
|
||||
'''
|
||||
|
||||
ver_growth = OptionProperty(None, allownone=True,
|
||||
options=['up', 'down'])
|
||||
'''Where the menu will grow vertically to when opening
|
||||
|
||||
Set to None to let the widget pick for you. Defaults to None.
|
||||
'''
|
||||
|
||||
hor_growth = OptionProperty(None, allownone=True,
|
||||
options=['left', 'right'])
|
||||
'''Where the menu will grow horizontally to when opening
|
||||
|
||||
Set to None to let the widget pick for you. Defaults to None.
|
||||
'''
|
||||
|
||||
def open(self, *largs):
|
||||
Window.add_widget(self)
|
||||
Clock.schedule_once(lambda x: self.display_menu(largs[0]), -1)
|
||||
|
||||
def display_menu(self, caller):
|
||||
# We need to pick a starting point, see how big we need to be,
|
||||
# and where to grow to.
|
||||
|
||||
c = caller.to_window(caller.center_x,
|
||||
caller.center_y) # Starting coords
|
||||
|
||||
# ---ESTABLISH INITIAL TARGET SIZE ESTIMATE---
|
||||
target_width = self.width_mult * m_res.STANDARD_INCREMENT
|
||||
# If we're wider than the Window...
|
||||
if target_width > Window.width:
|
||||
# ...reduce our multiplier to max allowed.
|
||||
target_width = int(
|
||||
Window.width / m_res.STANDARD_INCREMENT) * m_res.STANDARD_INCREMENT
|
||||
|
||||
target_height = sum([dp(48) for i in self.items])
|
||||
# If we're over max_height...
|
||||
if 0 < self.max_height < target_height:
|
||||
target_height = self.max_height
|
||||
|
||||
# ---ESTABLISH VERTICAL GROWTH DIRECTION---
|
||||
if self.ver_growth is not None:
|
||||
ver_growth = self.ver_growth
|
||||
else:
|
||||
# If there's enough space below us:
|
||||
if target_height <= c[1] - self.border_margin:
|
||||
ver_growth = 'down'
|
||||
# if there's enough space above us:
|
||||
elif target_height < Window.height - c[1] - self.border_margin:
|
||||
ver_growth = 'up'
|
||||
# otherwise, let's pick the one with more space and adjust ourselves
|
||||
else:
|
||||
# if there's more space below us:
|
||||
if c[1] >= Window.height - c[1]:
|
||||
ver_growth = 'down'
|
||||
target_height = c[1] - self.border_margin
|
||||
# if there's more space above us:
|
||||
else:
|
||||
ver_growth = 'up'
|
||||
target_height = Window.height - c[1] - self.border_margin
|
||||
|
||||
if self.hor_growth is not None:
|
||||
hor_growth = self.hor_growth
|
||||
else:
|
||||
# If there's enough space to the right:
|
||||
if target_width <= Window.width - c[0] - self.border_margin:
|
||||
hor_growth = 'right'
|
||||
# if there's enough space to the left:
|
||||
elif target_width < c[0] - self.border_margin:
|
||||
hor_growth = 'left'
|
||||
# otherwise, let's pick the one with more space and adjust ourselves
|
||||
else:
|
||||
# if there's more space to the right:
|
||||
if Window.width - c[0] >= c[0]:
|
||||
hor_growth = 'right'
|
||||
target_width = Window.width - c[0] - self.border_margin
|
||||
# if there's more space to the left:
|
||||
else:
|
||||
hor_growth = 'left'
|
||||
target_width = c[0] - self.border_margin
|
||||
|
||||
if ver_growth == 'down':
|
||||
tar_y = c[1] - target_height
|
||||
else: # should always be 'up'
|
||||
tar_y = c[1]
|
||||
|
||||
if hor_growth == 'right':
|
||||
tar_x = c[0]
|
||||
else: # should always be 'left'
|
||||
tar_x = c[0] - target_width
|
||||
anim = Animation(x=tar_x, y=tar_y,
|
||||
width=target_width, height=target_height,
|
||||
duration=.3, transition='out_quint')
|
||||
menu = self.ids['md_menu']
|
||||
menu.pos = c
|
||||
anim.start(menu)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if not self.ids['md_menu'].collide_point(*touch.pos):
|
||||
self.dismiss()
|
||||
return True
|
||||
super(MDDropdownMenu, self).on_touch_down(touch)
|
||||
return True
|
||||
|
||||
def on_touch_move(self, touch):
|
||||
super(MDDropdownMenu, self).on_touch_move(touch)
|
||||
return True
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
super(MDDropdownMenu, self).on_touch_up(touch)
|
||||
return True
|
||||
|
||||
def dismiss(self):
|
||||
Window.remove_widget(self)
|
|
@ -1,76 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty, ObjectProperty
|
||||
from kivymd.elevationbehavior import ElevationBehavior
|
||||
from kivymd.icon_definitions import md_icons
|
||||
from kivymd.label import MDLabel
|
||||
from kivymd.list import OneLineIconListItem, ILeftBody, BaseListItem
|
||||
from kivymd.slidingpanel import SlidingPanel
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string('''
|
||||
<NavDrawerToolbar@Toolbar>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.theme_cls.divider_color
|
||||
Line:
|
||||
points: self.x, self.y, self.x+self.width,self.y
|
||||
|
||||
<NavigationDrawer>
|
||||
_list: list
|
||||
elevation: 0
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.theme_cls.bg_light
|
||||
Rectangle:
|
||||
size: root.size
|
||||
pos: root.pos
|
||||
NavDrawerToolbar:
|
||||
title: root.title
|
||||
opposite_colors: False
|
||||
title_theme_color: 'Secondary'
|
||||
background_color: root.theme_cls.bg_light
|
||||
elevation: 0
|
||||
ScrollView:
|
||||
do_scroll_x: False
|
||||
MDList:
|
||||
id: ml
|
||||
id: list
|
||||
|
||||
<NavigationDrawerIconButton>
|
||||
NDIconLabel:
|
||||
id: _icon
|
||||
font_style: 'Icon'
|
||||
theme_text_color: 'Secondary'
|
||||
''')
|
||||
|
||||
|
||||
class NavigationDrawer(SlidingPanel, ThemableBehavior, ElevationBehavior):
|
||||
title = StringProperty()
|
||||
|
||||
_list = ObjectProperty()
|
||||
|
||||
def add_widget(self, widget, index=0):
|
||||
if issubclass(widget.__class__, BaseListItem):
|
||||
self._list.add_widget(widget, index)
|
||||
widget.bind(on_release=lambda x: self.toggle())
|
||||
else:
|
||||
super(NavigationDrawer, self).add_widget(widget, index)
|
||||
|
||||
def _get_main_animation(self, duration, t, x, is_closing):
|
||||
a = super(NavigationDrawer, self)._get_main_animation(duration, t, x,
|
||||
is_closing)
|
||||
a &= Animation(elevation=0 if is_closing else 5, t=t, duration=duration)
|
||||
return a
|
||||
|
||||
|
||||
class NDIconLabel(ILeftBody, MDLabel):
|
||||
pass
|
||||
|
||||
|
||||
class NavigationDrawerIconButton(OneLineIconListItem):
|
||||
icon = StringProperty()
|
||||
|
||||
def on_icon(self, instance, value):
|
||||
self.ids['_icon'].text = u"{}".format(md_icons[value])
|
|
@ -1,79 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import ListProperty, OptionProperty, BooleanProperty
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivymd.color_definitions import colors
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivy.uix.progressbar import ProgressBar
|
||||
|
||||
|
||||
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
|
||||
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.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. '''
|
||||
|
||||
orientation = OptionProperty('horizontal', options=['horizontal', 'vertical'])
|
||||
''' Orientation of progressbar'''
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from kivy.app import App
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
class ProgressBarApp(App):
|
||||
theme_cls = ThemeManager()
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string("""#:import MDSlider kivymd.slider.MDSlider
|
||||
BoxLayout:
|
||||
orientation:'vertical'
|
||||
padding: '8dp'
|
||||
MDSlider:
|
||||
id:slider
|
||||
min:0
|
||||
max:100
|
||||
value: 40
|
||||
|
||||
MDProgressBar:
|
||||
value: slider.value
|
||||
MDProgressBar:
|
||||
reversed: True
|
||||
value: slider.value
|
||||
BoxLayout:
|
||||
MDProgressBar:
|
||||
orientation:"vertical"
|
||||
reversed: True
|
||||
value: slider.value
|
||||
|
||||
MDProgressBar:
|
||||
orientation:"vertical"
|
||||
value: slider.value
|
||||
|
||||
""")
|
||||
|
||||
|
||||
ProgressBarApp().run()
|
|
@ -1,169 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.properties import ListProperty, NumericProperty, StringProperty, \
|
||||
BooleanProperty
|
||||
from kivy.animation import Animation
|
||||
from kivy.graphics import Color, Ellipse, StencilPush, StencilPop, \
|
||||
StencilUse, StencilUnUse, Rectangle
|
||||
|
||||
|
||||
class CommonRipple(object):
|
||||
ripple_rad = NumericProperty()
|
||||
ripple_rad_default = NumericProperty(1)
|
||||
ripple_post = ListProperty()
|
||||
ripple_color = ListProperty()
|
||||
ripple_alpha = NumericProperty(.5)
|
||||
ripple_scale = NumericProperty(None)
|
||||
ripple_duration_in_fast = NumericProperty(.3)
|
||||
# FIXME: These speeds should be calculated based on widget size in dp
|
||||
ripple_duration_in_slow = NumericProperty(2)
|
||||
ripple_duration_out = NumericProperty(.3)
|
||||
ripple_func_in = StringProperty('out_quad')
|
||||
ripple_func_out = StringProperty('out_quad')
|
||||
|
||||
doing_ripple = BooleanProperty(False)
|
||||
finishing_ripple = BooleanProperty(False)
|
||||
fading_out = BooleanProperty(False)
|
||||
|
||||
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 Grey 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(CommonRipple, self).on_touch_down(touch)
|
||||
|
||||
def lay_canvas_instructions(self):
|
||||
raise NotImplementedError
|
||||
|
||||
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(CommonRipple, self).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(CommonRipple, self).on_touch_up(touch)
|
||||
|
||||
def start_ripple(self):
|
||||
if not self.doing_ripple:
|
||||
anim = Animation(
|
||||
ripple_rad=self.finish_rad,
|
||||
t='linear',
|
||||
duration=self.ripple_duration_in_slow)
|
||||
anim.bind(on_complete=self.fade_out)
|
||||
self.doing_ripple = True
|
||||
anim.start(self)
|
||||
|
||||
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]
|
||||
|
||||
def finish_ripple(self):
|
||||
if self.doing_ripple and not self.finishing_ripple:
|
||||
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)
|
||||
self.finishing_ripple = True
|
||||
anim.start(self)
|
||||
|
||||
def fade_out(self, *args):
|
||||
rc = self.ripple_color
|
||||
if not self.fading_out:
|
||||
Animation.cancel_all(self, 'ripple_color')
|
||||
anim = Animation(ripple_color=[rc[0], rc[1], rc[2], 0.],
|
||||
t=self.ripple_func_out,
|
||||
duration=self.ripple_duration_out)
|
||||
anim.bind(on_complete=self.anim_complete)
|
||||
self.fading_out = True
|
||||
anim.start(self)
|
||||
|
||||
def anim_complete(self, *args):
|
||||
self.doing_ripple = False
|
||||
self.finishing_ripple = False
|
||||
self.fading_out = False
|
||||
self.canvas.after.clear()
|
||||
|
||||
|
||||
class RectangularRippleBehavior(CommonRipple):
|
||||
ripple_scale = NumericProperty(2.75)
|
||||
|
||||
def lay_canvas_instructions(self):
|
||||
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.,
|
||||
self.ripple_pos[1] - self.ripple_rad / 2.))
|
||||
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(RectangularRippleBehavior, self)._set_ellipse(instance, value)
|
||||
self.ellipse.pos = (self.ripple_pos[0] - self.ripple_rad / 2.,
|
||||
self.ripple_pos[1] - self.ripple_rad / 2.)
|
||||
|
||||
|
||||
class CircularRippleBehavior(CommonRipple):
|
||||
ripple_scale = NumericProperty(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.,
|
||||
self.center_y - self.ripple_rad / 2.))
|
||||
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(CircularRippleBehavior, self)._set_ellipse(instance, value)
|
||||
if self.ellipse.size[0] > self.width * .6 and not self.fading_out:
|
||||
self.fade_out()
|
||||
self.ellipse.pos = (self.center_x - self.ripple_rad / 2.,
|
||||
self.center_y - self.ripple_rad / 2.)
|
|
@ -1,240 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty, ListProperty, NumericProperty
|
||||
from kivy.uix.behaviors import ToggleButtonBehavior
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.properties import AliasProperty, BooleanProperty
|
||||
from kivy.metrics import dp, sp
|
||||
from kivy.animation import Animation
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivymd.color_definitions import colors
|
||||
from kivymd.icon_definitions import md_icons
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.elevationbehavior import RoundElevationBehavior
|
||||
from kivymd.ripplebehavior import CircularRippleBehavior
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
Builder.load_string('''
|
||||
<MDCheckbox>:
|
||||
canvas:
|
||||
Clear
|
||||
Color:
|
||||
rgba: self.color
|
||||
Rectangle:
|
||||
texture: self.texture
|
||||
size: self.texture_size
|
||||
pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)
|
||||
|
||||
text: self._radio_icon if self.group else self._checkbox_icon
|
||||
font_name: 'Icons'
|
||||
font_size: sp(24)
|
||||
color: self.theme_cls.primary_color if self.active else self.theme_cls.secondary_text_color
|
||||
halign: 'center'
|
||||
valign: 'middle'
|
||||
|
||||
<Thumb>:
|
||||
color: 1, 1, 1, 1
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.color
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
<MDSwitch>:
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: self._track_color_disabled if self.disabled else \
|
||||
(self._track_color_active if self.active else self._track_color_normal)
|
||||
Ellipse:
|
||||
size: dp(16), dp(16)
|
||||
pos: self.x, self.center_y - dp(8)
|
||||
angle_start: 180
|
||||
angle_end: 360
|
||||
Rectangle:
|
||||
size: self.width - dp(16), dp(16)
|
||||
pos: self.x + dp(8), self.center_y - dp(8)
|
||||
Ellipse:
|
||||
size: dp(16), dp(16)
|
||||
pos: self.right - dp(16), self.center_y - dp(8)
|
||||
angle_start: 0
|
||||
angle_end: 180
|
||||
on_release: thumb.trigger_action()
|
||||
|
||||
Thumb:
|
||||
id: thumb
|
||||
size_hint: None, None
|
||||
size: dp(24), dp(24)
|
||||
pos: root._thumb_pos
|
||||
color: root.thumb_color_disabled if root.disabled else \
|
||||
(root.thumb_color_down if root.active else root.thumb_color)
|
||||
elevation: 4 if root.active else 2
|
||||
on_release: setattr(root, 'active', not root.active)
|
||||
''')
|
||||
|
||||
|
||||
class MDCheckbox(ThemableBehavior, CircularRippleBehavior,
|
||||
ToggleButtonBehavior, Label):
|
||||
active = BooleanProperty(False)
|
||||
|
||||
_checkbox_icon = StringProperty(
|
||||
u"{}".format(md_icons['square-o']))
|
||||
_radio_icon = StringProperty(u"{}".format(md_icons['circle-o']))
|
||||
_icon_active = StringProperty(u"{}".format(md_icons['check-square']))
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDCheckbox, self).__init__(**kwargs)
|
||||
self.register_event_type('on_active')
|
||||
self.check_anim_out = Animation(font_size=0, duration=.1, t='out_quad')
|
||||
self.check_anim_in = Animation(font_size=sp(24), duration=.1,
|
||||
t='out_quad')
|
||||
self.check_anim_out.bind(
|
||||
on_complete=lambda *x: self.check_anim_in.start(self))
|
||||
|
||||
def on_state(self, *args):
|
||||
if self.state == 'down':
|
||||
self.check_anim_in.cancel(self)
|
||||
self.check_anim_out.start(self)
|
||||
self._radio_icon = u"{}".format(md_icons['dot-circle'])
|
||||
self._checkbox_icon = u"{}".format(md_icons['check-square'])
|
||||
self.active = True
|
||||
else:
|
||||
self.check_anim_in.cancel(self)
|
||||
self.check_anim_out.start(self)
|
||||
self._radio_icon = u"{}".format(md_icons['circle-o'])
|
||||
self._checkbox_icon = u"{}".format(
|
||||
md_icons['square-o'])
|
||||
self.active = False
|
||||
|
||||
def on_active(self, instance, value):
|
||||
self.state = 'down' if value else 'normal'
|
||||
|
||||
|
||||
class Thumb(RoundElevationBehavior, CircularRippleBehavior, ButtonBehavior,
|
||||
Widget):
|
||||
ripple_scale = NumericProperty(2)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
self.ellipse.size = (self.ripple_rad, self.ripple_rad)
|
||||
if self.ellipse.size[0] > self.width * 1.5 and not self.fading_out:
|
||||
self.fade_out()
|
||||
self.ellipse.pos = (self.center_x - self.ripple_rad / 2.,
|
||||
self.center_y - self.ripple_rad / 2.)
|
||||
self.stencil.pos = (
|
||||
self.center_x - (self.width * self.ripple_scale) / 2,
|
||||
self.center_y - (self.height * self.ripple_scale) / 2)
|
||||
|
||||
|
||||
class MDSwitch(ThemableBehavior, ButtonBehavior, FloatLayout):
|
||||
active = BooleanProperty(False)
|
||||
|
||||
_thumb_color = ListProperty(get_color_from_hex(colors['Grey']['50']))
|
||||
|
||||
def _get_thumb_color(self):
|
||||
return self._thumb_color
|
||||
|
||||
def _set_thumb_color(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._thumb_color = get_color_from_hex(colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._thumb_color[3] = alpha
|
||||
elif len(color) == 4:
|
||||
self._thumb_color = color
|
||||
|
||||
thumb_color = AliasProperty(_get_thumb_color, _set_thumb_color,
|
||||
bind=['_thumb_color'])
|
||||
|
||||
_thumb_color_down = ListProperty([1, 1, 1, 1])
|
||||
|
||||
def _get_thumb_color_down(self):
|
||||
return self._thumb_color_down
|
||||
|
||||
def _set_thumb_color_down(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._thumb_color_down = get_color_from_hex(
|
||||
colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._thumb_color_down[3] = alpha
|
||||
else:
|
||||
self._thumb_color_down[3] = 1
|
||||
elif len(color) == 4:
|
||||
self._thumb_color_down = color
|
||||
|
||||
thumb_color_down = AliasProperty(_get_thumb_color_down,
|
||||
_set_thumb_color_down,
|
||||
bind=['_thumb_color_down'])
|
||||
|
||||
_thumb_color_disabled = ListProperty(
|
||||
get_color_from_hex(colors['Grey']['400']))
|
||||
|
||||
def _get_thumb_color_disabled(self):
|
||||
return self._thumb_color_disabled
|
||||
|
||||
def _set_thumb_color_disabled(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._thumb_color_disabled = get_color_from_hex(
|
||||
colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._thumb_color_disabled[3] = alpha
|
||||
elif len(color) == 4:
|
||||
self._thumb_color_disabled = color
|
||||
|
||||
thumb_color_down = AliasProperty(_get_thumb_color_disabled,
|
||||
_set_thumb_color_disabled,
|
||||
bind=['_thumb_color_disabled'])
|
||||
|
||||
_track_color_active = ListProperty()
|
||||
_track_color_normal = ListProperty()
|
||||
_track_color_disabled = ListProperty()
|
||||
_thumb_pos = ListProperty([0, 0])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDSwitch, self).__init__(**kwargs)
|
||||
self.theme_cls.bind(theme_style=self._set_colors,
|
||||
primary_color=self._set_colors,
|
||||
primary_palette=self._set_colors)
|
||||
self._set_colors()
|
||||
|
||||
def _set_colors(self, *args):
|
||||
self._track_color_normal = self.theme_cls.disabled_hint_text_color
|
||||
if self.theme_cls.theme_style == 'Dark':
|
||||
self._track_color_active = self.theme_cls.primary_color
|
||||
self._track_color_active[3] = .5
|
||||
self._track_color_disabled = get_color_from_hex('FFFFFF')
|
||||
self._track_color_disabled[3] = .1
|
||||
self.thumb_color = get_color_from_hex(colors['Grey']['400'])
|
||||
self.thumb_color_down = get_color_from_hex(
|
||||
colors[self.theme_cls.primary_palette]['200'])
|
||||
self.thumb_color_disabled = get_color_from_hex(
|
||||
colors['Grey']['800'])
|
||||
else:
|
||||
self._track_color_active = get_color_from_hex(
|
||||
colors[self.theme_cls.primary_palette]['200'])
|
||||
self._track_color_active[3] = .5
|
||||
self._track_color_disabled = self.theme_cls.disabled_hint_text_color
|
||||
self.thumb_color_down = self.theme_cls.primary_color
|
||||
|
||||
def on_pos(self, *args):
|
||||
if self.active:
|
||||
self._thumb_pos = (self.right - dp(12), self.center_y - dp(12))
|
||||
else:
|
||||
self._thumb_pos = (self.x - dp(12), self.center_y - dp(12))
|
||||
self.bind(active=self._update_thumb)
|
||||
|
||||
def _update_thumb(self, *args):
|
||||
if self.active:
|
||||
Animation.cancel_all(self, '_thumb_pos')
|
||||
anim = Animation(
|
||||
_thumb_pos=(self.right - dp(12), self.center_y - dp(12)),
|
||||
duration=.2,
|
||||
t='out_quad')
|
||||
else:
|
||||
Animation.cancel_all(self, '_thumb_pos')
|
||||
anim = Animation(
|
||||
_thumb_pos=(self.x - dp(12), self.center_y - dp(12)),
|
||||
duration=.2,
|
||||
t='out_quad')
|
||||
anim.start(self)
|
|
@ -1,247 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty, ListProperty, NumericProperty,AliasProperty, BooleanProperty
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivy.metrics import dp, sp
|
||||
from kivymd.color_definitions import colors
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivy.uix.slider import Slider
|
||||
|
||||
|
||||
Builder.load_string('''
|
||||
#:import Thumb kivymd.selectioncontrols.Thumb
|
||||
|
||||
<MDSlider>:
|
||||
id: slider
|
||||
canvas:
|
||||
Clear
|
||||
Color:
|
||||
rgba: self._track_color_disabled if self.disabled else (self._track_color_active if self.active \
|
||||
else self._track_color_normal)
|
||||
Rectangle:
|
||||
size: (self.width - self.padding*2 - self._offset[0], dp(4)) if self.orientation == 'horizontal' \
|
||||
else (dp(4),self.height - self.padding*2 - self._offset[1])
|
||||
pos: (self.x + self.padding + self._offset[0], self.center_y - dp(4)) \
|
||||
if self.orientation == 'horizontal' else (self.center_x - dp(4),self.y + self.padding + self._offset[1])
|
||||
|
||||
# If 0 draw circle
|
||||
Color:
|
||||
rgba: [0,0,0,0] if not self._is_off else (self._track_color_disabled if self.disabled \
|
||||
else (self._track_color_active if self.active else self._track_color_normal))
|
||||
Line:
|
||||
width: 2
|
||||
circle: (self.x+self.padding+dp(3),self.center_y-dp(2),8 if self.active else 6 ) \
|
||||
if self.orientation == 'horizontal' else (self.center_x-dp(2),self.y+self.padding+dp(3),8 \
|
||||
if self.active else 6)
|
||||
|
||||
Color:
|
||||
rgba: [0,0,0,0] if self._is_off \
|
||||
else (self.thumb_color_down if not self.disabled else self._track_color_disabled)
|
||||
Rectangle:
|
||||
size: ((self.width-self.padding*2)*self.value_normalized, sp(4)) \
|
||||
if slider.orientation == 'horizontal' else (sp(4), (self.height-self.padding*2)*self.value_normalized)
|
||||
pos: (self.x + self.padding, self.center_y - dp(4)) if self.orientation == 'horizontal' \
|
||||
else (self.center_x - dp(4),self.y + self.padding)
|
||||
Thumb:
|
||||
id: thumb
|
||||
size_hint: None, None
|
||||
size: (dp(12), dp(12)) if root.disabled else ((dp(24), dp(24)) if root.active else (dp(16),dp(16)))
|
||||
pos: (slider.value_pos[0] - dp(8), slider.center_y - thumb.height/2 - dp(2)) \
|
||||
if slider.orientation == 'horizontal' \
|
||||
else (slider.center_x - thumb.width/2 - dp(2), slider.value_pos[1]-dp(8))
|
||||
color: [0,0,0,0] if slider._is_off else (root._track_color_disabled if root.disabled \
|
||||
else root.thumb_color_down)
|
||||
elevation: 0 if slider._is_off else (4 if root.active else 2)
|
||||
|
||||
''')
|
||||
|
||||
|
||||
class MDSlider(ThemableBehavior, Slider):
|
||||
# If the slider is clicked
|
||||
active = BooleanProperty(False)
|
||||
|
||||
# Show the "off" ring when set to minimum value
|
||||
show_off = BooleanProperty(True)
|
||||
|
||||
# Internal state of ring
|
||||
_is_off = BooleanProperty(False)
|
||||
|
||||
# Internal adjustment to reposition sliders for ring
|
||||
_offset = ListProperty((0, 0))
|
||||
|
||||
_thumb_color = ListProperty(get_color_from_hex(colors['Grey']['50']))
|
||||
|
||||
def _get_thumb_color(self):
|
||||
return self._thumb_color
|
||||
|
||||
def _set_thumb_color(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._thumb_color = get_color_from_hex(colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._thumb_color[3] = alpha
|
||||
elif len(color) == 4:
|
||||
self._thumb_color = color
|
||||
|
||||
thumb_color = AliasProperty(_get_thumb_color, _set_thumb_color,
|
||||
bind=['_thumb_color'])
|
||||
|
||||
_thumb_color_down = ListProperty([1, 1, 1, 1])
|
||||
|
||||
def _get_thumb_color_down(self):
|
||||
return self._thumb_color_down
|
||||
|
||||
def _set_thumb_color_down(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._thumb_color_down = get_color_from_hex(
|
||||
colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._thumb_color_down[3] = alpha
|
||||
else:
|
||||
self._thumb_color_down[3] = 1
|
||||
elif len(color) == 4:
|
||||
self._thumb_color_down = color
|
||||
|
||||
thumb_color_down = AliasProperty(_get_thumb_color_down,
|
||||
_set_thumb_color_down,
|
||||
bind=['_thumb_color_down'])
|
||||
|
||||
_thumb_color_disabled = ListProperty(
|
||||
get_color_from_hex(colors['Grey']['400']))
|
||||
|
||||
def _get_thumb_color_disabled(self):
|
||||
return self._thumb_color_disabled
|
||||
|
||||
def _set_thumb_color_disabled(self, color, alpha=None):
|
||||
if len(color) == 2:
|
||||
self._thumb_color_disabled = get_color_from_hex(
|
||||
colors[color[0]][color[1]])
|
||||
if alpha:
|
||||
self._thumb_color_disabled[3] = alpha
|
||||
elif len(color) == 4:
|
||||
self._thumb_color_disabled = color
|
||||
|
||||
thumb_color_down = AliasProperty(_get_thumb_color_disabled,
|
||||
_set_thumb_color_disabled,
|
||||
bind=['_thumb_color_disabled'])
|
||||
|
||||
_track_color_active = ListProperty()
|
||||
_track_color_normal = ListProperty()
|
||||
_track_color_disabled = ListProperty()
|
||||
_thumb_pos = ListProperty([0, 0])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDSlider, self).__init__(**kwargs)
|
||||
self.theme_cls.bind(theme_style=self._set_colors,
|
||||
primary_color=self._set_colors,
|
||||
primary_palette=self._set_colors)
|
||||
self._set_colors()
|
||||
|
||||
def _set_colors(self, *args):
|
||||
if self.theme_cls.theme_style == 'Dark':
|
||||
self._track_color_normal = get_color_from_hex('FFFFFF')
|
||||
self._track_color_normal[3] = .3
|
||||
self._track_color_active = self._track_color_normal
|
||||
self._track_color_disabled = self._track_color_normal
|
||||
self.thumb_color = get_color_from_hex(colors['Grey']['400'])
|
||||
self.thumb_color_down = get_color_from_hex(
|
||||
colors[self.theme_cls.primary_palette]['200'])
|
||||
self.thumb_color_disabled = get_color_from_hex(
|
||||
colors['Grey']['800'])
|
||||
else:
|
||||
self._track_color_normal = get_color_from_hex('000000')
|
||||
self._track_color_normal[3] = 0.26
|
||||
self._track_color_active = get_color_from_hex('000000')
|
||||
self._track_color_active[3] = 0.38
|
||||
self._track_color_disabled = get_color_from_hex('000000')
|
||||
self._track_color_disabled[3] = 0.26
|
||||
self.thumb_color_down = self.theme_cls.primary_color
|
||||
|
||||
def on_value_normalized(self, *args):
|
||||
""" When the value == min set it to "off" state and make slider a ring """
|
||||
self._update_is_off()
|
||||
|
||||
def on_show_off(self, *args):
|
||||
self._update_is_off()
|
||||
|
||||
def _update_is_off(self):
|
||||
self._is_off = self.show_off and (self.value_normalized == 0)
|
||||
|
||||
def on__is_off(self, *args):
|
||||
self._update_offset()
|
||||
|
||||
def on_active(self, *args):
|
||||
self._update_offset()
|
||||
|
||||
def _update_offset(self):
|
||||
""" Offset is used to shift the sliders so the background color
|
||||
shows through the off circle.
|
||||
"""
|
||||
d = 2 if self.active else 0
|
||||
self._offset = (dp(11+d), dp(11+d)) if self._is_off else (0, 0)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if super(MDSlider, self).on_touch_down(touch):
|
||||
self.active = True
|
||||
|
||||
def on_touch_up(self,touch):
|
||||
if super(MDSlider, self).on_touch_up(touch):
|
||||
self.active = False
|
||||
# thumb = self.ids['thumb']
|
||||
# if thumb.collide_point(*touch.pos):
|
||||
# thumb.on_touch_down(touch)
|
||||
# thumb.on_touch_up(touch)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from kivy.app import App
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
class SliderApp(App):
|
||||
theme_cls = ThemeManager()
|
||||
|
||||
def build(self):
|
||||
return Builder.load_string("""
|
||||
BoxLayout:
|
||||
orientation:'vertical'
|
||||
BoxLayout:
|
||||
size_hint_y:None
|
||||
height: '48dp'
|
||||
Label:
|
||||
text:"Toggle disabled"
|
||||
color: [0,0,0,1]
|
||||
CheckBox:
|
||||
on_press: slider.disabled = not slider.disabled
|
||||
BoxLayout:
|
||||
size_hint_y:None
|
||||
height: '48dp'
|
||||
Label:
|
||||
text:"Toggle active"
|
||||
color: [0,0,0,1]
|
||||
CheckBox:
|
||||
on_press: slider.active = not slider.active
|
||||
BoxLayout:
|
||||
size_hint_y:None
|
||||
height: '48dp'
|
||||
Label:
|
||||
text:"Toggle show off"
|
||||
color: [0,0,0,1]
|
||||
CheckBox:
|
||||
on_press: slider.show_off = not slider.show_off
|
||||
|
||||
MDSlider:
|
||||
id:slider
|
||||
min:0
|
||||
max:100
|
||||
value: 40
|
||||
|
||||
MDSlider:
|
||||
id:slider2
|
||||
orientation:"vertical"
|
||||
min:0
|
||||
max:100
|
||||
value: 40
|
||||
|
||||
""")
|
||||
|
||||
|
||||
SliderApp().run()
|
|
@ -1,92 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
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 OptionProperty, NumericProperty, StringProperty, \
|
||||
BooleanProperty, ListProperty
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.relativelayout import RelativeLayout
|
||||
|
||||
Builder.load_string("""
|
||||
#: import Window kivy.core.window.Window
|
||||
<SlidingPanel>
|
||||
orientation: 'vertical'
|
||||
size_hint_x: None
|
||||
width: dp(320)
|
||||
x: -1 * self.width if self.side == 'left' else Window.width
|
||||
|
||||
<PanelShadow>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.color
|
||||
Rectangle:
|
||||
size: root.size
|
||||
""")
|
||||
|
||||
|
||||
class PanelShadow(BoxLayout):
|
||||
color = ListProperty([0, 0, 0, 0])
|
||||
|
||||
|
||||
class SlidingPanel(BoxLayout):
|
||||
anim_length_close = NumericProperty(0.3)
|
||||
anim_length_open = NumericProperty(0.3)
|
||||
animation_t_open = StringProperty('out_sine')
|
||||
animation_t_close = StringProperty('out_sine')
|
||||
side = OptionProperty('left', options=['left', 'right'])
|
||||
|
||||
_open = False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(SlidingPanel, self).__init__(**kwargs)
|
||||
self.shadow = PanelShadow()
|
||||
Clock.schedule_once(lambda x: Window.add_widget(self.shadow,89), 0)
|
||||
Clock.schedule_once(lambda x: Window.add_widget(self,90), 0)
|
||||
|
||||
def toggle(self):
|
||||
Animation.stop_all(self, 'x')
|
||||
Animation.stop_all(self.shadow, 'color')
|
||||
if self._open:
|
||||
if self.side == 'left':
|
||||
target_x = -1 * self.width
|
||||
else:
|
||||
target_x = Window.width
|
||||
|
||||
sh_anim = Animation(duration=self.anim_length_open,
|
||||
t=self.animation_t_open,
|
||||
color=[0, 0, 0, 0])
|
||||
sh_anim.start(self.shadow)
|
||||
self._get_main_animation(duration=self.anim_length_close,
|
||||
t=self.animation_t_close,
|
||||
x=target_x,
|
||||
is_closing=True).start(self)
|
||||
self._open = False
|
||||
else:
|
||||
if self.side == 'left':
|
||||
target_x = 0
|
||||
else:
|
||||
target_x = Window.width - self.width
|
||||
Animation(duration=self.anim_length_open, t=self.animation_t_open,
|
||||
color=[0, 0, 0, 0.5]).start(self.shadow)
|
||||
self._get_main_animation(duration=self.anim_length_open,
|
||||
t=self.animation_t_open,
|
||||
x=target_x,
|
||||
is_closing=False).start(self)
|
||||
self._open = True
|
||||
|
||||
def _get_main_animation(self, duration, t, x, is_closing):
|
||||
return Animation(duration=duration, t=t, x=x)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
# Prevents touch events from propagating to anything below the widget.
|
||||
super(SlidingPanel, self).on_touch_down(touch)
|
||||
if self.collide_point(*touch.pos) or self._open:
|
||||
return True
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if not self.collide_point(touch.x, touch.y) and self._open:
|
||||
self.toggle()
|
||||
return True
|
||||
super(SlidingPanel, self).on_touch_up(touch)
|
|
@ -1,115 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from collections import deque
|
||||
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 ObjectProperty, StringProperty, NumericProperty
|
||||
from kivy.uix.relativelayout import RelativeLayout
|
||||
from kivymd.material_resources import DEVICE_TYPE
|
||||
|
||||
Builder.load_string('''
|
||||
#:import Window kivy.core.window.Window
|
||||
#:import get_color_from_hex kivy.utils.get_color_from_hex
|
||||
#:import MDFlatButton kivymd.button.MDFlatButton
|
||||
#:import MDLabel kivymd.label.MDLabel
|
||||
#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE
|
||||
<_SnackbarWidget>
|
||||
canvas:
|
||||
Color:
|
||||
rgb: get_color_from_hex('323232')
|
||||
Rectangle:
|
||||
size: self.size
|
||||
size_hint_y: None
|
||||
size_hint_x: 1 if DEVICE_TYPE == 'mobile' else None
|
||||
height: dp(48) if _label.texture_size[1] < dp(30) else dp(80)
|
||||
width: dp(24) + _label.width + _spacer.width + root.padding_right if root.button_text == '' else dp(24) + \
|
||||
_label.width + _spacer.width + _button.width + root.padding_right
|
||||
top: 0
|
||||
x: 0 if DEVICE_TYPE == 'mobile' else Window.width/2 - self.width/2
|
||||
BoxLayout:
|
||||
width: Window.width - root.padding_right - _spacer.width - dp(24) if DEVICE_TYPE == 'mobile' and \
|
||||
root.button_text == '' else Window.width - root.padding_right - _button.width - _spacer.width - dp(24) \
|
||||
if DEVICE_TYPE == 'mobile' else _label.texture_size[0] if (dp(568) - root.padding_right - _button.width - \
|
||||
_spacer.width - _label.texture_size[0] - dp(24)) >= 0 else (dp(568) - root.padding_right - _button.width - \
|
||||
_spacer.width - dp(24))
|
||||
size_hint_x: None
|
||||
x: dp(24)
|
||||
MDLabel:
|
||||
id: _label
|
||||
text: root.text
|
||||
size: self.texture_size
|
||||
BoxLayout:
|
||||
id: _spacer
|
||||
size_hint_x: None
|
||||
x: _label.right
|
||||
width: 0
|
||||
MDFlatButton:
|
||||
id: _button
|
||||
text: root.button_text
|
||||
size_hint_x: None
|
||||
x: _spacer.right if root.button_text != '' else root.right
|
||||
center_y: root.height/2
|
||||
on_release: root.button_callback()
|
||||
''')
|
||||
|
||||
|
||||
class _SnackbarWidget(RelativeLayout):
|
||||
text = StringProperty()
|
||||
button_text = StringProperty()
|
||||
button_callback = ObjectProperty()
|
||||
duration = NumericProperty()
|
||||
padding_right = NumericProperty(dp(24))
|
||||
|
||||
def __init__(self, text, duration, button_text='', button_callback=None,
|
||||
**kwargs):
|
||||
super(_SnackbarWidget, self).__init__(**kwargs)
|
||||
self.text = text
|
||||
self.button_text = button_text
|
||||
self.button_callback = button_callback
|
||||
self.duration = duration
|
||||
self.ids['_label'].text_size = (None, None)
|
||||
|
||||
def begin(self):
|
||||
if self.button_text == '':
|
||||
self.remove_widget(self.ids['_button'])
|
||||
else:
|
||||
self.ids['_spacer'].width = dp(16) if \
|
||||
DEVICE_TYPE == "mobile" else dp(40)
|
||||
self.padding_right = dp(16)
|
||||
Window.add_widget(self)
|
||||
anim = Animation(y=0, duration=.3, t='out_quad')
|
||||
anim.start(self)
|
||||
Clock.schedule_once(lambda dt: self.die(), self.duration)
|
||||
|
||||
def die(self):
|
||||
anim = Animation(top=0, duration=.3, t='out_quad')
|
||||
anim.bind(on_complete=lambda *args: _play_next(self))
|
||||
anim.bind(on_complete=lambda *args: Window.remove_widget(self))
|
||||
anim.start(self)
|
||||
|
||||
|
||||
queue = deque()
|
||||
playing = False
|
||||
|
||||
|
||||
def make(text, button_text=None, button_callback=None, duration=3):
|
||||
if button_text is not None and button_callback is not None:
|
||||
queue.append(_SnackbarWidget(text=text,
|
||||
button_text=button_text,
|
||||
button_callback=button_callback,
|
||||
duration=duration))
|
||||
else:
|
||||
queue.append(_SnackbarWidget(text=text,
|
||||
duration=duration))
|
||||
_play_next()
|
||||
|
||||
|
||||
def _play_next(dying_widget=None):
|
||||
global playing
|
||||
if (dying_widget or not playing) and len(queue) > 0:
|
||||
playing = True
|
||||
queue.popleft().begin()
|
||||
elif len(queue) == 0:
|
||||
playing = False
|
|
@ -1,149 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.widget import Widget
|
||||
from kivy.properties import NumericProperty, ListProperty, BooleanProperty
|
||||
from kivy.animation import Animation
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivy.clock import Clock
|
||||
|
||||
Builder.load_string('''
|
||||
<MDSpinner>:
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: self._rotation_angle
|
||||
origin: self.center
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.color
|
||||
a: self._alpha
|
||||
Line:
|
||||
circle: self.center_x, self.center_y, self.width / 2, \
|
||||
self._angle_start, self._angle_end
|
||||
cap: 'square'
|
||||
width: dp(2)
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
|
||||
''')
|
||||
|
||||
|
||||
class MDSpinner(ThemableBehavior, Widget):
|
||||
""":class:`MDSpinner` is an implementation of the circular progress
|
||||
indicator in Google's Material Design.
|
||||
|
||||
It can be used either as an indeterminate indicator that loops while
|
||||
the user waits for something to happen, or as a determinate indicator.
|
||||
|
||||
Set :attr:`determinate` to **True** to activate determinate mode, and
|
||||
:attr:`determinate_time` to set the duration of the animation.
|
||||
"""
|
||||
|
||||
determinate = BooleanProperty(False)
|
||||
""":attr:`determinate` is a :class:`~kivy.properties.BooleanProperty` and
|
||||
defaults to False
|
||||
"""
|
||||
|
||||
determinate_time = NumericProperty(2)
|
||||
""":attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to 2
|
||||
"""
|
||||
|
||||
active = BooleanProperty(True)
|
||||
"""Use :attr:`active` to start or stop the spinner.
|
||||
|
||||
:attr:`active` is a :class:`~kivy.properties.BooleanProperty` and
|
||||
defaults to True
|
||||
"""
|
||||
|
||||
color = ListProperty([])
|
||||
""":attr:`color` is a :class:`~kivy.properties.ListProperty` and
|
||||
defaults to 'self.theme_cls.primary_color'
|
||||
"""
|
||||
|
||||
_alpha = NumericProperty(0)
|
||||
_rotation_angle = NumericProperty(360)
|
||||
_angle_start = NumericProperty(0)
|
||||
_angle_end = NumericProperty(8)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDSpinner, self).__init__(**kwargs)
|
||||
Clock.schedule_interval(self._update_color, 5)
|
||||
self.color = self.theme_cls.primary_color
|
||||
self._alpha_anim_in = Animation(_alpha=1, duration=.8, t='out_quad')
|
||||
self._alpha_anim_out = Animation(_alpha=0, duration=.3, t='out_quad')
|
||||
self._alpha_anim_out.bind(on_complete=self._reset)
|
||||
|
||||
if self.determinate:
|
||||
self._start_determinate()
|
||||
else:
|
||||
self._start_loop()
|
||||
|
||||
def _update_color(self, *args):
|
||||
self.color = self.theme_cls.primary_color
|
||||
|
||||
def _start_determinate(self, *args):
|
||||
self._alpha_anim_in.start(self)
|
||||
|
||||
_rot_anim = Animation(_rotation_angle=0,
|
||||
duration=self.determinate_time * .7,
|
||||
t='out_quad')
|
||||
_rot_anim.start(self)
|
||||
|
||||
_angle_start_anim = Animation(_angle_end=360,
|
||||
duration=self.determinate_time,
|
||||
t='in_out_quad')
|
||||
_angle_start_anim.bind(on_complete=lambda *x: \
|
||||
self._alpha_anim_out.start(self))
|
||||
|
||||
_angle_start_anim.start(self)
|
||||
|
||||
def _start_loop(self, *args):
|
||||
if self._alpha == 0:
|
||||
_rot_anim = Animation(_rotation_angle=0,
|
||||
duration=2,
|
||||
t='linear')
|
||||
_rot_anim.start(self)
|
||||
|
||||
self._alpha = 1
|
||||
self._alpha_anim_in.start(self)
|
||||
_angle_start_anim = Animation(_angle_end=self._angle_end + 270,
|
||||
duration=.6,
|
||||
t='in_out_cubic')
|
||||
_angle_start_anim.bind(on_complete=self._anim_back)
|
||||
_angle_start_anim.start(self)
|
||||
|
||||
def _anim_back(self, *args):
|
||||
_angle_back_anim = Animation(_angle_start=self._angle_end - 8,
|
||||
duration=.6,
|
||||
t='in_out_cubic')
|
||||
_angle_back_anim.bind(on_complete=self._start_loop)
|
||||
|
||||
_angle_back_anim.start(self)
|
||||
|
||||
def on__rotation_angle(self, *args):
|
||||
if self._rotation_angle == 0:
|
||||
self._rotation_angle = 360
|
||||
if not self.determinate:
|
||||
_rot_anim = Animation(_rotation_angle=0,
|
||||
duration=2)
|
||||
_rot_anim.start(self)
|
||||
|
||||
def _reset(self, *args):
|
||||
Animation.cancel_all(self, '_angle_start', '_rotation_angle',
|
||||
'_angle_end', '_alpha')
|
||||
self._angle_start = 0
|
||||
self._angle_end = 8
|
||||
self._rotation_angle = 360
|
||||
self._alpha = 0
|
||||
self.active = False
|
||||
|
||||
def on_active(self, *args):
|
||||
if not self.active:
|
||||
self._reset()
|
||||
else:
|
||||
if self.determinate:
|
||||
self._start_determinate()
|
||||
else:
|
||||
self._start_loop()
|
|
@ -1,303 +0,0 @@
|
|||
# Created on Jul 8, 2016
|
||||
#
|
||||
# The default kivy tab implementation seems like a stupid design to me. The
|
||||
# ScreenManager is much better.
|
||||
#
|
||||
# @author: jrm
|
||||
|
||||
from kivy.properties import StringProperty, DictProperty, ListProperty, \
|
||||
ObjectProperty, OptionProperty, BoundedNumericProperty
|
||||
from kivy.uix.screenmanager import Screen
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.backgroundcolorbehavior import BackgroundColorBehavior
|
||||
from kivymd.button import MDFlatButton
|
||||
|
||||
Builder.load_string("""
|
||||
<MDTabbedPanel>:
|
||||
id: panel
|
||||
orientation: 'vertical' if panel.tab_orientation in ['top','bottom'] else 'horizontal'
|
||||
ScrollView:
|
||||
id: scroll_view
|
||||
size_hint_y: None
|
||||
height: panel._tab_display_height[panel.tab_display_mode]
|
||||
MDTabBar:
|
||||
id: tab_bar
|
||||
size_hint_y: None
|
||||
height: panel._tab_display_height[panel.tab_display_mode]
|
||||
background_color: panel.tab_color or panel.theme_cls.primary_color
|
||||
canvas:
|
||||
# Draw bottom border
|
||||
Color:
|
||||
rgba: (panel.tab_border_color or panel.tab_color or panel.theme_cls.primary_dark)
|
||||
Rectangle:
|
||||
size: (self.width,dp(2))
|
||||
ScreenManager:
|
||||
id: tab_manager
|
||||
current: root.current
|
||||
screens: root.tabs
|
||||
|
||||
|
||||
<MDTabHeader>:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.panel.tab_color or self.panel.theme_cls.primary_color
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
# Draw indicator
|
||||
Color:
|
||||
rgba: (self.panel.tab_indicator_color or self.panel.theme_cls.accent_color) if self.tab \
|
||||
and self.tab.manager and self.tab.manager.current==self.tab.name else (self.panel.tab_border_color \
|
||||
or self.panel.tab_color or self.panel.theme_cls.primary_dark)
|
||||
Rectangle:
|
||||
size: (self.width,dp(2))
|
||||
pos: self.pos
|
||||
|
||||
size_hint: (None,None) #(1, None) if self.panel.tab_width_mode=='fixed' else (None,None)
|
||||
width: (_label.texture_size[0] + dp(16))
|
||||
padding: (dp(12), 0)
|
||||
theme_text_color: 'Custom'
|
||||
text_color: (self.panel.tab_text_color_active or app.theme_cls.bg_light if app.theme_cls.theme_style == "Light" \
|
||||
else app.theme_cls.opposite_bg_light) if self.tab and self.tab.manager \
|
||||
and self.tab.manager.current==self.tab.name else (self.panel.tab_text_color \
|
||||
or self.panel.theme_cls.primary_light)
|
||||
on_press:
|
||||
self.tab.dispatch('on_tab_press')
|
||||
# self.tab.manager.current = self.tab.name
|
||||
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)
|
||||
|
||||
|
||||
MDLabel:
|
||||
id: _label
|
||||
text: root.tab.text if root.panel.tab_display_mode == 'text' else u"{}".format(md_icons[root.tab.icon])
|
||||
font_style: 'Button' if root.panel.tab_display_mode == 'text' else 'Icon'
|
||||
size_hint_x: None# if root.panel.tab_width_mode=='fixed' else 1
|
||||
text_size: (None, root.height)
|
||||
height: self.texture_size[1]
|
||||
theme_text_color: root.theme_text_color
|
||||
text_color: root.text_color
|
||||
valign: 'middle'
|
||||
halign: 'center'
|
||||
opposite_colors: root.opposite_colors
|
||||
""")
|
||||
|
||||
|
||||
class MDTabBar(ThemableBehavior, BackgroundColorBehavior, BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class MDTabHeader(MDFlatButton):
|
||||
""" Internal widget for headers based on MDFlatButton"""
|
||||
|
||||
width = BoundedNumericProperty(dp(None), min=dp(72), max=dp(264), errorhandler=lambda x: dp(72))
|
||||
tab = ObjectProperty(None)
|
||||
panel = ObjectProperty(None)
|
||||
|
||||
|
||||
class MDTab(Screen):
|
||||
""" 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')
|
||||
|
||||
# Tab header text
|
||||
text = StringProperty("")
|
||||
|
||||
# Tab header icon
|
||||
icon = StringProperty("circle")
|
||||
|
||||
# Tab dropdown menu items
|
||||
menu_items = ListProperty()
|
||||
|
||||
# Tab dropdown menu (if you want to customize it)
|
||||
menu = ObjectProperty(None)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDTab, self).__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_leave(self, *args):
|
||||
self.parent_widget.ids.tab_manager.transition.direction = self.parent_widget.prev_dir
|
||||
|
||||
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:
|
||||
par.prev_dir = str(par.ids.tab_manager.transition.direction)
|
||||
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 "<MDTab name='{}', text='{}'>".format(self.name, self.text)
|
||||
|
||||
|
||||
class MDTabbedPanel(ThemableBehavior, BackgroundColorBehavior, BoxLayout):
|
||||
""" A tab panel that is implemented by delegating all tabs
|
||||
to a ScreenManager.
|
||||
"""
|
||||
# If tabs should fill space
|
||||
tab_width_mode = OptionProperty('stacked', options=['stacked', 'fixed'])
|
||||
|
||||
# Where the tabs go
|
||||
tab_orientation = OptionProperty('top', options=['top']) # ,'left','bottom','right'])
|
||||
|
||||
# How tabs are displayed
|
||||
tab_display_mode = OptionProperty('text', options=['text', 'icons']) # ,'both'])
|
||||
_tab_display_height = DictProperty({'text': dp(46), 'icons': dp(46), 'both': dp(72)})
|
||||
|
||||
# Tab background color (leave empty for theme color)
|
||||
tab_color = ListProperty([])
|
||||
|
||||
# Tab text color in normal state (leave empty for theme color)
|
||||
tab_text_color = ListProperty([])
|
||||
|
||||
# Tab text color in active state (leave empty for theme color)
|
||||
tab_text_color_active = ListProperty([])
|
||||
|
||||
# Tab indicator color (leave empty for theme color)
|
||||
tab_indicator_color = ListProperty([])
|
||||
|
||||
# Tab bar bottom border color (leave empty for theme color)
|
||||
tab_border_color = ListProperty([])
|
||||
|
||||
# List of all the tabs so you can dynamically change them
|
||||
tabs = ListProperty([])
|
||||
|
||||
# Current tab name
|
||||
current = StringProperty(None)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDTabbedPanel, self).__init__(**kwargs)
|
||||
self.previous_tab = None
|
||||
self.prev_dir = None
|
||||
self.index = 0
|
||||
self._refresh_tabs()
|
||||
|
||||
def on_tab_width_mode(self, *args):
|
||||
self._refresh_tabs()
|
||||
|
||||
def on_tab_display_mode(self, *args):
|
||||
self._refresh_tabs()
|
||||
|
||||
def _refresh_tabs(self):
|
||||
""" Refresh all tabs """
|
||||
# if fixed width, use a box layout
|
||||
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:
|
||||
tab_header = MDTabHeader(tab=tab,
|
||||
panel=self,
|
||||
height=tab_bar.height,
|
||||
)
|
||||
tab_bar.add_widget(tab_header)
|
||||
|
||||
def add_widget(self, widget, **kwargs):
|
||||
""" Add tabs to the screen or the layout.
|
||||
:param widget: The widget to add.
|
||||
"""
|
||||
d = {}
|
||||
if isinstance(widget, MDTab):
|
||||
self.index += 1
|
||||
if self.index == 1:
|
||||
self.previous_tab = widget
|
||||
widget.index = self.index
|
||||
widget.parent_widget = self
|
||||
self.ids.tab_manager.add_widget(widget)
|
||||
self._refresh_tabs()
|
||||
else:
|
||||
super(MDTabbedPanel, self).add_widget(widget)
|
||||
|
||||
def remove_widget(self, widget):
|
||||
""" Remove tabs from the screen or the layout.
|
||||
:param widget: The widget to remove.
|
||||
"""
|
||||
self.index -= 1
|
||||
if isinstance(widget, MDTab):
|
||||
self.ids.tab_manager.remove_widget(widget)
|
||||
self._refresh_tabs()
|
||||
else:
|
||||
super(MDTabbedPanel, self).remove_widget(widget)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from kivy.app import App
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
class TabsApp(App):
|
||||
theme_cls = ThemeManager()
|
||||
|
||||
def build(self):
|
||||
from kivy.core.window import Window
|
||||
Window.size = (540, 720)
|
||||
# self.theme_cls.theme_style = 'Dark'
|
||||
|
||||
return Builder.load_string("""
|
||||
#:import Toolbar kivymd.toolbar.Toolbar
|
||||
BoxLayout:
|
||||
orientation:'vertical'
|
||||
Toolbar:
|
||||
id: toolbar
|
||||
title: 'Page title'
|
||||
background_color: app.theme_cls.primary_color
|
||||
left_action_items: [['menu', lambda x: '']]
|
||||
right_action_items: [['search', lambda x: ''],['more-vert',lambda x:'']]
|
||||
MDTabbedPanel:
|
||||
id: tab_mgr
|
||||
tab_display_mode:'icons'
|
||||
|
||||
MDTab:
|
||||
name: 'music'
|
||||
text: "Music" # Why are these not set!!!
|
||||
icon: "playlist-audio"
|
||||
MDLabel:
|
||||
font_style: 'Body1'
|
||||
theme_text_color: 'Primary'
|
||||
text: "Here is my music list :)"
|
||||
halign: 'center'
|
||||
MDTab:
|
||||
name: 'movies'
|
||||
text: 'Movies'
|
||||
icon: "movie"
|
||||
|
||||
MDLabel:
|
||||
font_style: 'Body1'
|
||||
theme_text_color: 'Primary'
|
||||
text: "Show movies here :)"
|
||||
halign: 'center'
|
||||
|
||||
|
||||
""")
|
||||
|
||||
|
||||
TabsApp().run()
|
|
@ -1,215 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.properties import ObjectProperty, NumericProperty, StringProperty, \
|
||||
ListProperty, BooleanProperty
|
||||
from kivy.metrics import sp, dp
|
||||
from kivy.animation import Animation
|
||||
from kivymd.label import MDLabel
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivy.clock import Clock
|
||||
|
||||
Builder.load_string('''
|
||||
<SingleLineTextField>:
|
||||
canvas.before:
|
||||
Clear
|
||||
Color:
|
||||
rgba: self.line_color_normal
|
||||
Line:
|
||||
id: "the_line"
|
||||
points: self.x, self.y + dp(8), self.x + self.width, self.y + dp(8)
|
||||
width: 1
|
||||
dash_length: dp(3)
|
||||
dash_offset: 2 if self.disabled else 0
|
||||
Color:
|
||||
rgba: self._current_line_color
|
||||
Rectangle:
|
||||
size: self._line_width, dp(2)
|
||||
pos: self.center_x - (self._line_width / 2), self.y + dp(8)
|
||||
Color:
|
||||
rgba: self._current_error_color
|
||||
Rectangle:
|
||||
texture: self._msg_lbl.texture
|
||||
size: self._msg_lbl.texture_size
|
||||
pos: self.x, self.y - dp(8)
|
||||
Color:
|
||||
rgba: (self._current_line_color if self.focus and not self.cursor_blink \
|
||||
else (0, 0, 0, 0))
|
||||
Rectangle:
|
||||
pos: [int(x) for x in self.cursor_pos]
|
||||
size: 1, -self.line_height
|
||||
Color:
|
||||
#rgba: self._hint_txt_color if not self.text and not self.focus\
|
||||
#else (self.line_color_focus if not self.text or self.focus\
|
||||
#else self.line_color_normal)
|
||||
rgba: self._current_hint_text_color
|
||||
Rectangle:
|
||||
texture: self._hint_lbl.texture
|
||||
size: self._hint_lbl.texture_size
|
||||
pos: self.x, self.y + self._hint_y
|
||||
Color:
|
||||
rgba: self.disabled_foreground_color if self.disabled else \
|
||||
(self.hint_text_color if not self.text and not self.focus else \
|
||||
self.foreground_color)
|
||||
|
||||
font_name: 'Roboto'
|
||||
foreground_color: app.theme_cls.text_color
|
||||
font_size: sp(16)
|
||||
bold: False
|
||||
padding: 0, dp(16), 0, dp(10)
|
||||
multiline: False
|
||||
size_hint_y: None
|
||||
height: dp(48)
|
||||
''')
|
||||
|
||||
|
||||
class SingleLineTextField(ThemableBehavior, TextInput):
|
||||
line_color_normal = ListProperty()
|
||||
line_color_focus = ListProperty()
|
||||
error_color = ListProperty()
|
||||
error = BooleanProperty(False)
|
||||
message = StringProperty("")
|
||||
message_mode = StringProperty("none")
|
||||
mode = message_mode
|
||||
|
||||
_hint_txt_color = ListProperty()
|
||||
_hint_lbl = ObjectProperty()
|
||||
_hint_lbl_font_size = NumericProperty(sp(16))
|
||||
_hint_y = NumericProperty(dp(10))
|
||||
_error_label = ObjectProperty()
|
||||
_line_width = NumericProperty(0)
|
||||
_hint_txt = StringProperty('')
|
||||
_current_line_color = line_color_focus
|
||||
_current_error_color = ListProperty([0.0, 0.0, 0.0, 0.0])
|
||||
_current_hint_text_color = _hint_txt_color
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
Clock.schedule_interval(self._update_color, 5)
|
||||
self._msg_lbl = MDLabel(font_style='Caption',
|
||||
theme_text_color='Error',
|
||||
halign='left',
|
||||
valign='middle',
|
||||
text=self.message)
|
||||
|
||||
self._hint_lbl = MDLabel(font_style='Subhead',
|
||||
halign='left',
|
||||
valign='middle')
|
||||
super(SingleLineTextField, self).__init__(**kwargs)
|
||||
self.line_color_normal = self.theme_cls.divider_color
|
||||
self.line_color_focus = list(self.theme_cls.primary_color)
|
||||
self.base_line_color_focus = list(self.theme_cls.primary_color)
|
||||
self.error_color = self.theme_cls.error_color
|
||||
|
||||
self._hint_txt_color = self.theme_cls.disabled_hint_text_color
|
||||
self.hint_text_color = (1, 1, 1, 0)
|
||||
self.cursor_color = self.theme_cls.primary_color
|
||||
self.bind(message=self._set_msg,
|
||||
hint_text=self._set_hint,
|
||||
_hint_lbl_font_size=self._hint_lbl.setter('font_size'),
|
||||
message_mode=self._set_mode)
|
||||
self.hint_anim_in = Animation(_hint_y=dp(34),
|
||||
_hint_lbl_font_size=sp(12), duration=.2,
|
||||
t='out_quad')
|
||||
|
||||
self.hint_anim_out = Animation(_hint_y=dp(10),
|
||||
_hint_lbl_font_size=sp(16), duration=.2,
|
||||
t='out_quad')
|
||||
|
||||
def _update_color(self, *args):
|
||||
self.line_color_normal = self.theme_cls.divider_color
|
||||
self.base_line_color_focus = list(self.theme_cls.primary_color)
|
||||
if not self.focus and not self.error:
|
||||
self.line_color_focus = self.theme_cls.primary_color
|
||||
Animation(duration=.2, _current_hint_text_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
if self.mode == "persistent":
|
||||
Animation(duration=.1, _current_error_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
if self.focus and not self.error:
|
||||
self.cursor_color = self.theme_cls.primary_color
|
||||
|
||||
def on_hint_text_color(self, instance, color):
|
||||
self._hint_txt_color = self.theme_cls.disabled_hint_text_color
|
||||
self.hint_text_color = (1, 1, 1, 0)
|
||||
|
||||
def on_width(self, instance, width):
|
||||
if self.focus and instance is not None or self.error and instance is not None:
|
||||
self._line_width = width
|
||||
self.anim = Animation(_line_width=width, duration=.2, t='out_quad')
|
||||
self._msg_lbl.width = self.width
|
||||
self._hint_lbl.width = self.width
|
||||
|
||||
def on_pos(self, *args):
|
||||
self.hint_anim_in = Animation(_hint_y=dp(34),
|
||||
_hint_lbl_font_size=sp(12), duration=.2,
|
||||
t='out_quad')
|
||||
self.hint_anim_out = Animation(_hint_y=dp(10),
|
||||
_hint_lbl_font_size=sp(16), duration=.2,
|
||||
t='out_quad')
|
||||
|
||||
def on_focus(self, *args):
|
||||
if self.focus:
|
||||
Animation.cancel_all(self, '_line_width', '_hint_y',
|
||||
'_hint_lbl_font_size')
|
||||
if len(self.text) == 0:
|
||||
self.hint_anim_in.start(self)
|
||||
if self.error:
|
||||
Animation(duration=.2, _current_hint_text_color=self.error_color).start(self)
|
||||
if self.mode == "on_error":
|
||||
Animation(duration=.2, _current_error_color=self.error_color).start(self)
|
||||
elif self.mode == "persistent":
|
||||
Animation(duration=.2, _current_error_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
elif self.mode == "on_focus":
|
||||
Animation(duration=.2, _current_error_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
else:
|
||||
pass
|
||||
elif not self.error:
|
||||
self.on_width(None, self.width)
|
||||
self.anim.start(self)
|
||||
Animation(duration=.2, _current_hint_text_color=self.line_color_focus).start(self)
|
||||
if self.mode == "on_error":
|
||||
Animation(duration=.2, _current_error_color=(0, 0, 0, 0)).start(self)
|
||||
if self.mode == "persistent":
|
||||
Animation(duration=.2, _current_error_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
elif self.mode == "on_focus":
|
||||
Animation(duration=.2, _current_error_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
Animation.cancel_all(self, '_line_width', '_hint_y',
|
||||
'_hint_lbl_font_size')
|
||||
if len(self.text) == 0:
|
||||
self.hint_anim_out.start(self)
|
||||
if not self.error:
|
||||
self.line_color_focus = self.base_line_color_focus
|
||||
Animation(duration=.2, _current_line_color=self.line_color_focus,
|
||||
_current_hint_text_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
if self.mode == "on_error":
|
||||
Animation(duration=.2, _current_error_color=(0, 0, 0, 0)).start(self)
|
||||
elif self.mode == "persistent":
|
||||
Animation(duration=.2, _current_error_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
elif self.mode == "on_focus":
|
||||
Animation(duration=.2, _current_error_color=(0, 0, 0, 0)).start(self)
|
||||
|
||||
self.on_width(None, 0)
|
||||
self.anim.start(self)
|
||||
elif self.error:
|
||||
Animation(duration=.2, _current_line_color=self.error_color,
|
||||
_current_hint_text_color=self.error_color).start(self)
|
||||
if self.mode == "on_error":
|
||||
Animation(duration=.2, _current_error_color=self.error_color).start(self)
|
||||
elif self.mode == "persistent":
|
||||
Animation(duration=.2, _current_error_color=self.theme_cls.disabled_hint_text_color).start(self)
|
||||
elif self.mode == "on_focus":
|
||||
Animation(duration=.2, _current_error_color=(0, 0, 0, 0)).start(self)
|
||||
|
||||
def _set_hint(self, instance, text):
|
||||
self._hint_lbl.text = text
|
||||
|
||||
def _set_msg(self, instance, text):
|
||||
self._msg_lbl.text = text
|
||||
self.message = text
|
||||
|
||||
def _set_mode(self, instance, text):
|
||||
self.mode = text
|
||||
if self.mode == "persistent":
|
||||
Animation(duration=.1, _current_error_color=self.theme_cls.disabled_hint_text_color).start(self)
|
|
@ -1,422 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.modalview import ModalView
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivymd.button import MDFlatButton, MDIconButton
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.elevationbehavior import ElevationBehavior
|
||||
from kivy.properties import ObjectProperty, ListProperty
|
||||
from kivymd.label import MDLabel
|
||||
from kivy.metrics import dp
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivymd.color_definitions import colors
|
||||
|
||||
Builder.load_string("""
|
||||
#:import SingleLineTextField kivymd.textfields.SingleLineTextField
|
||||
#:import MDTabbedPanel kivymd.tabs.MDTabbedPanel
|
||||
#:import MDTab kivymd.tabs.MDTab
|
||||
<MDThemePicker>:
|
||||
size_hint: (None, None)
|
||||
size: dp(260), dp(120)+dp(290)
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
canvas:
|
||||
Color:
|
||||
rgb: app.theme_cls.primary_color
|
||||
Rectangle:
|
||||
size: dp(260), dp(120)
|
||||
pos: root.pos[0], root.pos[1] + root.height-dp(120)
|
||||
Color:
|
||||
rgb: app.theme_cls.bg_normal
|
||||
Rectangle:
|
||||
size: dp(260), dp(290)
|
||||
pos: root.pos[0], root.pos[1] + root.height-(dp(120)+dp(290))
|
||||
|
||||
MDFlatButton:
|
||||
pos: root.pos[0]+root.size[0]-dp(72), root.pos[1] + dp(10)
|
||||
text: "Close"
|
||||
on_release: root.dismiss()
|
||||
MDLabel:
|
||||
font_style: "Headline"
|
||||
text: "Change theme"
|
||||
size_hint: (None, None)
|
||||
size: dp(160), dp(50)
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.9}
|
||||
MDTabbedPanel:
|
||||
size_hint: (None, None)
|
||||
size: dp(260), root.height-dp(135)
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.475}
|
||||
id: tab_panel
|
||||
tab_display_mode:'text'
|
||||
|
||||
MDTab:
|
||||
name: 'color'
|
||||
text: "Theme Color"
|
||||
BoxLayout:
|
||||
spacing: dp(4)
|
||||
size_hint: (None, None)
|
||||
size: dp(270), root.height # -dp(120)
|
||||
pos_hint: {'center_x': 0.532, 'center_y': 0.89}
|
||||
orientation: 'vertical'
|
||||
BoxLayout:
|
||||
size_hint: (None, None)
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
|
||||
size: dp(230), dp(40)
|
||||
pos: self.pos
|
||||
halign: 'center'
|
||||
orientation: 'horizontal'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Red')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Red'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Pink')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Pink'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Purple')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Purple'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('DeepPurple')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'DeepPurple'
|
||||
BoxLayout:
|
||||
size_hint: (None, None)
|
||||
pos_hint: {'center_x': .5, 'center_y': 0.5}
|
||||
size: dp(230), dp(40)
|
||||
pos: self.pos
|
||||
halign: 'center'
|
||||
orientation: 'horizontal'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Indigo')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Indigo'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Blue')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Blue'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('LightBlue')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'LightBlue'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Cyan')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Cyan'
|
||||
BoxLayout:
|
||||
size_hint: (None, None)
|
||||
pos_hint: {'center_x': .5, 'center_y': 0.5}
|
||||
size: dp(230), dp(40)
|
||||
pos: self.pos
|
||||
halign: 'center'
|
||||
orientation: 'horizontal'
|
||||
padding: 0, 0, 0, dp(1)
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Teal')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Teal'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Green')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Green'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('LightGreen')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'LightGreen'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Lime')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Lime'
|
||||
BoxLayout:
|
||||
size_hint: (None, None)
|
||||
pos_hint: {'center_x': .5, 'center_y': 0.5}
|
||||
size: dp(230), dp(40)
|
||||
pos: self.pos
|
||||
orientation: 'horizontal'
|
||||
halign: 'center'
|
||||
padding: 0, 0, 0, dp(1)
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Yellow')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Yellow'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Amber')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Amber'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Orange')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Orange'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('DeepOrange')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'DeepOrange'
|
||||
BoxLayout:
|
||||
size_hint: (None, None)
|
||||
pos_hint: {'center_x': .5, 'center_y': 0.5}
|
||||
size: dp(230), dp(40)
|
||||
#pos: self.pos
|
||||
orientation: 'horizontal'
|
||||
padding: 0, 0, 0, dp(1)
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Brown')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Brown'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('Grey')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'Grey'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
#pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.rgb_hex('BlueGrey')
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.primary_palette = 'BlueGrey'
|
||||
BoxLayout:
|
||||
MDIconButton:
|
||||
size: dp(40), dp(40)
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: app.theme_cls.bg_normal
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
disabled: True
|
||||
|
||||
MDTab:
|
||||
name: 'style'
|
||||
text: "Theme Style"
|
||||
BoxLayout:
|
||||
size_hint: (None, None)
|
||||
pos_hint: {'center_x': .3, 'center_y': 0.5}
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
halign: 'center'
|
||||
spacing: dp(10)
|
||||
BoxLayout:
|
||||
halign: 'center'
|
||||
size_hint: (None, None)
|
||||
size: dp(100), dp(100)
|
||||
pos: self.pos
|
||||
pos_hint: {'center_x': .3, 'center_y': 0.5}
|
||||
MDIconButton:
|
||||
size: dp(100), dp(100)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 1, 1, 1, 1
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
Color:
|
||||
rgba: 0, 0, 0, 1
|
||||
Line:
|
||||
width: 1.
|
||||
circle: (self.center_x, self.center_y, 50)
|
||||
on_release: app.theme_cls.theme_style = 'Light'
|
||||
BoxLayout:
|
||||
halign: 'center'
|
||||
size_hint: (None, None)
|
||||
size: dp(100), dp(100)
|
||||
MDIconButton:
|
||||
size: dp(100), dp(100)
|
||||
pos: self.pos
|
||||
size_hint: (None, None)
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 0, 0, 0, 1
|
||||
Ellipse:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
on_release: app.theme_cls.theme_style = 'Dark'
|
||||
""")
|
||||
|
||||
|
||||
class MDThemePicker(ThemableBehavior, FloatLayout, ModalView, ElevationBehavior):
|
||||
# background_color = ListProperty([0, 0, 0, 0])
|
||||
time = ObjectProperty()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDThemePicker, self).__init__(**kwargs)
|
||||
|
||||
def rgb_hex(self, col):
|
||||
return get_color_from_hex(colors[col][self.theme_cls.accent_hue])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from kivy.app import App
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
class ThemePickerApp(App):
|
||||
theme_cls = ThemeManager()
|
||||
|
||||
def build(self):
|
||||
main_widget = Builder.load_string("""
|
||||
#:import MDRaisedButton kivymd.button.MDRaisedButton
|
||||
#:import MDThemePicker kivymd.theme_picker.MDThemePicker
|
||||
FloatLayout:
|
||||
MDRaisedButton:
|
||||
size_hint: None, None
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
size: 3 * dp(48), dp(48)
|
||||
center_x: self.parent.center_x
|
||||
text: 'Open theme picker'
|
||||
on_release: MDThemePicker().open()
|
||||
opposite_colors: True
|
||||
""")
|
||||
return main_widget
|
||||
|
||||
ThemePickerApp().run()
|
|
@ -1,350 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.app import App
|
||||
from kivy.core.text import LabelBase
|
||||
from kivy.core.window import Window
|
||||
from kivy.clock import Clock
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import OptionProperty, AliasProperty, ObjectProperty, \
|
||||
StringProperty, ListProperty, BooleanProperty
|
||||
from kivy.uix.widget import Widget
|
||||
from kivy.utils import get_color_from_hex
|
||||
from kivy.atlas import Atlas
|
||||
from kivymd.color_definitions import colors
|
||||
from kivymd.material_resources import FONTS, DEVICE_TYPE
|
||||
from kivymd import images_path
|
||||
|
||||
for font in FONTS:
|
||||
LabelBase.register(**font)
|
||||
|
||||
|
||||
class ThemeManager(Widget):
|
||||
primary_palette = OptionProperty(
|
||||
'Blue',
|
||||
options=['Pink', 'Blue', 'Indigo', 'BlueGrey', 'Brown',
|
||||
'LightBlue',
|
||||
'Purple', 'Grey', 'Yellow', 'LightGreen', 'DeepOrange',
|
||||
'Green', 'Red', 'Teal', 'Orange', 'Cyan', 'Amber',
|
||||
'DeepPurple', 'Lime'])
|
||||
|
||||
primary_hue = OptionProperty(
|
||||
'500',
|
||||
options=['50', '100', '200', '300', '400', '500', '600', '700',
|
||||
'800',
|
||||
'900', 'A100', 'A200', 'A400', 'A700'])
|
||||
|
||||
primary_light_hue = OptionProperty(
|
||||
'200',
|
||||
options=['50', '100', '200', '300', '400', '500', '600', '700',
|
||||
'800',
|
||||
'900', 'A100', 'A200', 'A400', 'A700'])
|
||||
|
||||
primary_dark_hue = OptionProperty(
|
||||
'700',
|
||||
options=['50', '100', '200', '300', '400', '500', '600', '700',
|
||||
'800',
|
||||
'900', 'A100', 'A200', 'A400', 'A700'])
|
||||
|
||||
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'))
|
||||
|
||||
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'))
|
||||
|
||||
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'))
|
||||
|
||||
accent_palette = OptionProperty(
|
||||
'Amber',
|
||||
options=['Pink', 'Blue', 'Indigo', 'BlueGrey', 'Brown',
|
||||
'LightBlue',
|
||||
'Purple', 'Grey', 'Yellow', 'LightGreen', 'DeepOrange',
|
||||
'Green', 'Red', 'Teal', 'Orange', 'Cyan', 'Amber',
|
||||
'DeepPurple', 'Lime'])
|
||||
|
||||
accent_hue = OptionProperty(
|
||||
'500',
|
||||
options=['50', '100', '200', '300', '400', '500', '600', '700',
|
||||
'800',
|
||||
'900', 'A100', 'A200', 'A400', 'A700'])
|
||||
|
||||
accent_light_hue = OptionProperty(
|
||||
'200',
|
||||
options=['50', '100', '200', '300', '400', '500', '600', '700',
|
||||
'800',
|
||||
'900', 'A100', 'A200', 'A400', 'A700'])
|
||||
|
||||
accent_dark_hue = OptionProperty(
|
||||
'700',
|
||||
options=['50', '100', '200', '300', '400', '500', '600', '700',
|
||||
'800',
|
||||
'900', 'A100', 'A200', 'A400', 'A700'])
|
||||
|
||||
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'])
|
||||
|
||||
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'])
|
||||
|
||||
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'])
|
||||
|
||||
theme_style = OptionProperty('Light', options=['Light', 'Dark'])
|
||||
|
||||
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'])
|
||||
|
||||
def _get_op_bg_darkest(self):
|
||||
return self._get_bg_darkest(True)
|
||||
|
||||
opposite_bg_darkest = AliasProperty(_get_op_bg_darkest,
|
||||
bind=['theme_style'])
|
||||
|
||||
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'])
|
||||
|
||||
def _get_op_bg_dark(self):
|
||||
return self._get_bg_dark(True)
|
||||
|
||||
opposite_bg_dark = AliasProperty(_get_op_bg_dark, bind=['theme_style'])
|
||||
|
||||
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'])
|
||||
|
||||
def _get_op_bg_normal(self):
|
||||
return self._get_bg_normal(True)
|
||||
|
||||
opposite_bg_normal = AliasProperty(_get_op_bg_normal, bind=['theme_style'])
|
||||
|
||||
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'])
|
||||
|
||||
def _get_op_bg_light(self):
|
||||
return self._get_bg_light(True)
|
||||
|
||||
opposite_bg_light = AliasProperty(_get_op_bg_light, bind=['theme_style'])
|
||||
|
||||
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] = .12
|
||||
return color
|
||||
|
||||
divider_color = AliasProperty(_get_divider_color, bind=['theme_style'])
|
||||
|
||||
def _get_op_divider_color(self):
|
||||
return self._get_divider_color(True)
|
||||
|
||||
opposite_divider_color = AliasProperty(_get_op_divider_color,
|
||||
bind=['theme_style'])
|
||||
|
||||
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] = .87
|
||||
elif theme_style == 'Dark':
|
||||
color = get_color_from_hex('FFFFFF')
|
||||
return color
|
||||
|
||||
text_color = AliasProperty(_get_text_color, bind=['theme_style'])
|
||||
|
||||
def _get_op_text_color(self):
|
||||
return self._get_text_color(True)
|
||||
|
||||
opposite_text_color = AliasProperty(_get_op_text_color,
|
||||
bind=['theme_style'])
|
||||
|
||||
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] = .54
|
||||
elif theme_style == 'Dark':
|
||||
color = get_color_from_hex('FFFFFF')
|
||||
color[3] = .70
|
||||
return color
|
||||
|
||||
secondary_text_color = AliasProperty(_get_secondary_text_color,
|
||||
bind=['theme_style'])
|
||||
|
||||
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'])
|
||||
|
||||
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] = .54
|
||||
elif theme_style == 'Dark':
|
||||
color = get_color_from_hex('FFFFFF')
|
||||
return color
|
||||
|
||||
icon_color = AliasProperty(_get_icon_color,
|
||||
bind=['theme_style'])
|
||||
|
||||
def _get_op_icon_color(self):
|
||||
return self._get_icon_color(True)
|
||||
|
||||
opposite_icon_color = AliasProperty(_get_op_icon_color,
|
||||
bind=['theme_style'])
|
||||
|
||||
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] = .26
|
||||
elif theme_style == 'Dark':
|
||||
color = get_color_from_hex('FFFFFF')
|
||||
color[3] = .30
|
||||
return color
|
||||
|
||||
disabled_hint_text_color = AliasProperty(_get_disabled_hint_text_color,
|
||||
bind=['theme_style'])
|
||||
|
||||
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'])
|
||||
|
||||
# Hardcoded because muh standard
|
||||
def _get_error_color(self):
|
||||
return get_color_from_hex(colors['Red']['A700'])
|
||||
|
||||
error_color = AliasProperty(_get_error_color)
|
||||
|
||||
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['Grey']['400']))
|
||||
ripple_color = AliasProperty(_get_ripple_color,
|
||||
_set_ripple_color,
|
||||
bind=['_ripple_color'])
|
||||
|
||||
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('')
|
||||
|
||||
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'])
|
||||
|
||||
def _get_horizontal_margins(self):
|
||||
if DEVICE_TYPE == 'mobile':
|
||||
return dp(16)
|
||||
else:
|
||||
return dp(24)
|
||||
|
||||
horizontal_margins = AliasProperty(_get_horizontal_margins)
|
||||
|
||||
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)
|
||||
|
||||
def set_clearcolor_by_theme_style(self, theme_style):
|
||||
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'])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(ThemeManager, self).__init__(**kwargs)
|
||||
self.rec_shadow = Atlas('{}rec_shadow.atlas'.format(images_path))
|
||||
self.rec_st_shadow = Atlas('{}rec_st_shadow.atlas'.format(images_path))
|
||||
self.quad_shadow = Atlas('{}quad_shadow.atlas'.format(images_path))
|
||||
self.round_shadow = Atlas('{}round_shadow.atlas'.format(images_path))
|
||||
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(object):
|
||||
theme_cls = ObjectProperty(None)
|
||||
opposite_colors = BooleanProperty(False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if self.theme_cls is not None:
|
||||
pass
|
||||
elif hasattr(App.get_running_app(), 'theme_cls'):
|
||||
self.theme_cls = App.get_running_app().theme_cls
|
||||
else:
|
||||
self.theme_cls = ThemeManager()
|
||||
super(ThemableBehavior, self).__init__(**kwargs)
|
|
@ -1,84 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.modalview import ModalView
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.elevationbehavior import ElevationBehavior
|
||||
from kivy.properties import ObjectProperty, ListProperty
|
||||
|
||||
Builder.load_string("""
|
||||
#:import MDFlatButton kivymd.button.MDFlatButton
|
||||
#:import CircularTimePicker kivymd.vendor.circularTimePicker.CircularTimePicker
|
||||
#:import dp kivy.metrics.dp
|
||||
<MDTimePicker>:
|
||||
size_hint: (None, None)
|
||||
size: [dp(270), dp(335)+dp(95)]
|
||||
#if root.theme_cls.device_orientation == 'portrait' else [dp(520), dp(325)]
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
canvas:
|
||||
Color:
|
||||
rgba: self.theme_cls.bg_light
|
||||
Rectangle:
|
||||
size: [dp(270), dp(335)]
|
||||
#if root.theme_cls.device_orientation == 'portrait' else [dp(250), root.height]
|
||||
pos: [root.pos[0], root.pos[1] + root.height - dp(335) - dp(95)]
|
||||
#if root.theme_cls.device_orientation == 'portrait' else [root.pos[0]+dp(270), root.pos[1]]
|
||||
Color:
|
||||
rgba: self.theme_cls.primary_color
|
||||
Rectangle:
|
||||
size: [dp(270), dp(95)]
|
||||
#if root.theme_cls.device_orientation == 'portrait' else [dp(270), root.height]
|
||||
pos: [root.pos[0], root.pos[1] + root.height - dp(95)]
|
||||
#if root.theme_cls.device_orientation == 'portrait' else [root.pos[0], root.pos[1]]
|
||||
Color:
|
||||
rgba: self.theme_cls.bg_dark
|
||||
Ellipse:
|
||||
size: [dp(220), dp(220)]
|
||||
#if root.theme_cls.device_orientation == 'portrait' else [dp(195), dp(195)]
|
||||
pos: root.pos[0]+dp(270)/2-dp(220)/2, root.pos[1] + root.height - (dp(335)/2+dp(95)) - dp(220)/2 + dp(35)
|
||||
#Color:
|
||||
#rgba: (1, 0, 0, 1)
|
||||
#Line:
|
||||
#width: 4
|
||||
#points: dp(270)/2, root.height, dp(270)/2, 0
|
||||
CircularTimePicker:
|
||||
id: time_picker
|
||||
pos: (dp(270)/2)-(self.width/2), root.height-self.height
|
||||
size_hint: [.8, .8]
|
||||
#if root.theme_cls.device_orientation == 'portrait' else [0.35, 0.9]
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.585}
|
||||
#if root.theme_cls.device_orientation == 'portrait' else {'center_x': 0.75, 'center_y': 0.7}
|
||||
MDFlatButton:
|
||||
pos: root.pos[0]+root.size[0]-dp(72)*2, root.pos[1] + dp(10)
|
||||
text: "Cancel"
|
||||
on_release: root.close_cancel()
|
||||
MDFlatButton:
|
||||
pos: root.pos[0]+root.size[0]-dp(72), root.pos[1] + dp(10)
|
||||
text: "OK"
|
||||
on_release: root.close_ok()
|
||||
""")
|
||||
|
||||
|
||||
class MDTimePicker(ThemableBehavior, FloatLayout, ModalView, ElevationBehavior):
|
||||
# background_color = ListProperty((0, 0, 0, 0))
|
||||
time = ObjectProperty()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MDTimePicker, self).__init__(**kwargs)
|
||||
self.current_time = self.ids.time_picker.time
|
||||
|
||||
def set_time(self, time):
|
||||
try:
|
||||
self.ids.time_picker.set_time(time)
|
||||
except AttributeError:
|
||||
raise TypeError("MDTimePicker._set_time must receive a datetime object, not a \"" +
|
||||
type(time).__name__ + "\"")
|
||||
|
||||
def close_cancel(self):
|
||||
self.dismiss()
|
||||
|
||||
def close_ok(self):
|
||||
self.current_time = self.ids.time_picker.time
|
||||
self.time = self.current_time
|
||||
self.dismiss()
|
|
@ -1,98 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import ListProperty, StringProperty, OptionProperty
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivymd.backgroundcolorbehavior import BackgroundColorBehavior
|
||||
from kivymd.button import MDIconButton
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.elevationbehavior import ElevationBehavior
|
||||
|
||||
Builder.load_string('''
|
||||
#:import m_res kivymd.material_resources
|
||||
<Toolbar>
|
||||
size_hint_y: None
|
||||
height: root.theme_cls.standard_increment
|
||||
background_color: root.background_color
|
||||
padding: [root.theme_cls.horizontal_margins - dp(12), 0]
|
||||
opposite_colors: True
|
||||
elevation: 6
|
||||
BoxLayout:
|
||||
id: left_actions
|
||||
orientation: 'horizontal'
|
||||
size_hint_x: None
|
||||
padding: [0, (self.height - dp(48))/2]
|
||||
BoxLayout:
|
||||
padding: dp(12), 0
|
||||
MDLabel:
|
||||
font_style: 'Title'
|
||||
opposite_colors: root.opposite_colors
|
||||
theme_text_color: root.title_theme_color
|
||||
text_color: root.title_color
|
||||
text: root.title
|
||||
shorten: True
|
||||
shorten_from: 'right'
|
||||
BoxLayout:
|
||||
id: right_actions
|
||||
orientation: 'horizontal'
|
||||
size_hint_x: None
|
||||
padding: [0, (self.height - dp(48))/2]
|
||||
''')
|
||||
|
||||
|
||||
class Toolbar(ThemableBehavior, ElevationBehavior, BackgroundColorBehavior,
|
||||
BoxLayout):
|
||||
left_action_items = ListProperty()
|
||||
"""The icons on the left of the Toolbar.
|
||||
|
||||
To add one, append a list like the following:
|
||||
|
||||
['icon_name', callback]
|
||||
|
||||
where 'icon_name' is a string that corresponds to an icon definition and
|
||||
callback is the function called on a touch release event.
|
||||
"""
|
||||
|
||||
right_action_items = ListProperty()
|
||||
"""The icons on the left of the Toolbar.
|
||||
|
||||
Works the same way as :attr:`left_action_items`
|
||||
"""
|
||||
|
||||
title = StringProperty()
|
||||
"""The text displayed on the Toolbar."""
|
||||
|
||||
title_theme_color = OptionProperty(None, allownone=True,
|
||||
options=['Primary', 'Secondary', 'Hint',
|
||||
'Error', 'Custom'])
|
||||
|
||||
title_color = ListProperty(None, allownone=True)
|
||||
|
||||
background_color = ListProperty([0, 0, 0, 1])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Toolbar, self).__init__(**kwargs)
|
||||
Clock.schedule_once(
|
||||
lambda x: self.on_left_action_items(0, self.left_action_items))
|
||||
Clock.schedule_once(
|
||||
lambda x: self.on_right_action_items(0,
|
||||
self.right_action_items))
|
||||
|
||||
def on_left_action_items(self, instance, value):
|
||||
self.update_action_bar(self.ids['left_actions'], value)
|
||||
|
||||
def on_right_action_items(self, instance, value):
|
||||
self.update_action_bar(self.ids['right_actions'], value)
|
||||
|
||||
def update_action_bar(self, action_bar, action_bar_items):
|
||||
action_bar.clear_widgets()
|
||||
new_width = 0
|
||||
for item in action_bar_items:
|
||||
new_width += dp(48)
|
||||
action_bar.add_widget(MDIconButton(icon=item[0],
|
||||
on_release=item[1],
|
||||
opposite_colors=True,
|
||||
text_color=self.title_color,
|
||||
theme_text_color=self.title_theme_color))
|
||||
action_bar.width = new_width
|
1
src/kivymd/vendor/__init__.py
vendored
|
@ -1 +0,0 @@
|
|||
# coding=utf-8
|
22
src/kivymd/vendor/circleLayout/LICENSE
vendored
|
@ -1,22 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Davide Depau
|
||||
|
||||
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.
|
||||
|
21
src/kivymd/vendor/circleLayout/README.md
vendored
|
@ -1,21 +0,0 @@
|
|||
CircularLayout
|
||||
==============
|
||||
|
||||
CircularLayout is a special layout that places widgets around a circle.
|
||||
|
||||
See the widget's documentation and the example for more information.
|
||||
|
||||
![Screenshot](screenshot.png)
|
||||
|
||||
size_hint
|
||||
---------
|
||||
|
||||
size_hint_x is used as an angle-quota hint (widget with higher
|
||||
size_hint_x will be farther from each other, and viceversa), while
|
||||
size_hint_y is used as a widget size hint (widgets with a higher size
|
||||
hint will be bigger).size_hint_x cannot be None.
|
||||
|
||||
Widgets are all squares, unless you set size_hint_y to None (in that
|
||||
case you'll be able to specify your own size), and their size is the
|
||||
difference between the outer and the inner circle's radii. To make the
|
||||
widgets bigger you can just decrease inner_radius_hint.
|
196
src/kivymd/vendor/circleLayout/__init__.py
vendored
|
@ -1,196 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
CircularLayout
|
||||
==============
|
||||
|
||||
CircularLayout is a special layout that places widgets around a circle.
|
||||
|
||||
size_hint
|
||||
---------
|
||||
|
||||
size_hint_x is used as an angle-quota hint (widget with higher
|
||||
size_hint_x will be farther from each other, and vice versa), while
|
||||
size_hint_y is used as a widget size hint (widgets with a higher size
|
||||
hint will be bigger).size_hint_x cannot be None.
|
||||
|
||||
Widgets are all squares, unless you set size_hint_y to None (in that
|
||||
case you'll be able to specify your own size), and their size is the
|
||||
difference between the outer and the inner circle's radii. To make the
|
||||
widgets bigger you can just decrease inner_radius_hint.
|
||||
"""
|
||||
|
||||
from kivy.uix.layout import Layout
|
||||
from kivy.properties import NumericProperty, ReferenceListProperty, OptionProperty, \
|
||||
BoundedNumericProperty, VariableListProperty, AliasProperty
|
||||
from math import sin, cos, pi, radians
|
||||
|
||||
__all__ = ('CircularLayout')
|
||||
|
||||
try:
|
||||
xrange(1, 2)
|
||||
except NameError:
|
||||
def xrange(first, second, third=None):
|
||||
if third:
|
||||
return range(first, second, third)
|
||||
else:
|
||||
return range(first, second)
|
||||
|
||||
|
||||
class CircularLayout(Layout):
|
||||
'''
|
||||
Circular layout class. See module documentation for more information.
|
||||
'''
|
||||
|
||||
padding = VariableListProperty([0, 0, 0, 0])
|
||||
'''Padding between the layout box and it's children: [padding_left,
|
||||
padding_top, padding_right, padding_bottom].
|
||||
|
||||
padding also accepts a two argument form [padding_horizontal,
|
||||
padding_vertical] and a one argument form [padding].
|
||||
|
||||
.. version changed:: 1.7.0
|
||||
Replaced NumericProperty with VariableListProperty.
|
||||
|
||||
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and
|
||||
defaults to [0, 0, 0, 0].
|
||||
'''
|
||||
|
||||
start_angle = NumericProperty(0)
|
||||
'''Angle (in degrees) at which the first widget will be placed.
|
||||
Start counting angles from the X axis, going counterclockwise.
|
||||
|
||||
:attr:`start_angle` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 0 (start from the right).
|
||||
'''
|
||||
|
||||
circle_quota = BoundedNumericProperty(360, min=0, max=360)
|
||||
'''Size (in degrees) of the part of the circumference that will actually
|
||||
be used to place widgets.
|
||||
|
||||
:attr:`circle_quota` is a :class:`~kivy.properties.BoundedNumericProperty`
|
||||
and defaults to 360 (all the circumference).
|
||||
'''
|
||||
|
||||
direction = OptionProperty("ccw", options=("cw", "ccw"))
|
||||
'''Direction of widgets in the circle.
|
||||
|
||||
:attr:`direction` is an :class:`~kivy.properties.OptionProperty` and
|
||||
defaults to 'ccw'. Can be 'ccw' (counterclockwise) or 'cw' (clockwise).
|
||||
'''
|
||||
|
||||
outer_radius_hint = NumericProperty(1)
|
||||
'''Sets the size of the outer circle. A number greater than 1 will make the
|
||||
widgets larger than the actual widget, a number smaller than 1 will leave
|
||||
a gap.
|
||||
|
||||
:attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 1.
|
||||
'''
|
||||
|
||||
inner_radius_hint = NumericProperty(.6)
|
||||
'''Sets the size of the inner circle. A number greater than
|
||||
:attr:`outer_radius_hint` will cause glitches. The closest it is to
|
||||
:attr:`outer_radius_hint`, the smallest will be the widget in the layout.
|
||||
|
||||
:attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 1.
|
||||
'''
|
||||
|
||||
radius_hint = ReferenceListProperty(inner_radius_hint, outer_radius_hint)
|
||||
'''Combined :attr:`outer_radius_hint` and :attr:`inner_radius_hint` in a list
|
||||
for convenience. See their documentation for more details.
|
||||
|
||||
:attr:`radius_hint` is a :class:`~kivy.properties.ReferenceListProperty`.
|
||||
'''
|
||||
|
||||
def _get_delta_radii(self):
|
||||
radius = min(self.width-self.padding[0]-self.padding[2], self.height-self.padding[1]-self.padding[3]) / 2.
|
||||
outer_r = radius * self.outer_radius_hint
|
||||
inner_r = radius * self.inner_radius_hint
|
||||
return outer_r - inner_r
|
||||
delta_radii = AliasProperty(_get_delta_radii, None, bind=("radius_hint", "padding", "size"))
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(CircularLayout, self).__init__(**kwargs)
|
||||
|
||||
self.bind(
|
||||
start_angle=self._trigger_layout,
|
||||
parent=self._trigger_layout,
|
||||
# padding=self._trigger_layout,
|
||||
children=self._trigger_layout,
|
||||
size=self._trigger_layout,
|
||||
radius_hint=self._trigger_layout,
|
||||
pos=self._trigger_layout)
|
||||
|
||||
def do_layout(self, *largs):
|
||||
# optimize layout by preventing looking at the same attribute in a loop
|
||||
len_children = len(self.children)
|
||||
if len_children == 0:
|
||||
return
|
||||
selfcx = self.center_x
|
||||
selfcy = self.center_y
|
||||
direction = self.direction
|
||||
cquota = radians(self.circle_quota)
|
||||
start_angle_r = radians(self.start_angle)
|
||||
padding_left = self.padding[0]
|
||||
padding_top = self.padding[1]
|
||||
padding_right = self.padding[2]
|
||||
padding_bottom = self.padding[3]
|
||||
padding_x = padding_left + padding_right
|
||||
padding_y = padding_top + padding_bottom
|
||||
|
||||
radius = min(self.width-padding_x, self.height-padding_y) / 2.
|
||||
outer_r = radius * self.outer_radius_hint
|
||||
inner_r = radius * self.inner_radius_hint
|
||||
middle_r = radius * sum(self.radius_hint) / 2.
|
||||
delta_r = outer_r - inner_r
|
||||
|
||||
stretch_weight_angle = 0.
|
||||
for w in self.children:
|
||||
sha = w.size_hint_x
|
||||
if sha is None:
|
||||
raise ValueError("size_hint_x cannot be None in a CircularLayout")
|
||||
else:
|
||||
stretch_weight_angle += sha
|
||||
|
||||
sign = +1.
|
||||
angle_offset = start_angle_r
|
||||
if direction == 'cw':
|
||||
angle_offset = 2 * pi - start_angle_r
|
||||
sign = -1.
|
||||
|
||||
for c in reversed(self.children):
|
||||
sha = c.size_hint_x
|
||||
shs = c.size_hint_y
|
||||
|
||||
angle_quota = cquota / stretch_weight_angle * sha
|
||||
angle = angle_offset + (sign * angle_quota / 2)
|
||||
angle_offset += sign * angle_quota
|
||||
|
||||
# kived: looking it up, yes. x = cos(angle) * radius + centerx; y = sin(angle) * radius + centery
|
||||
ccx = cos(angle) * middle_r + selfcx + padding_left - padding_right
|
||||
ccy = sin(angle) * middle_r + selfcy + padding_bottom - padding_top
|
||||
|
||||
c.center_x = ccx
|
||||
c.center_y = ccy
|
||||
if shs:
|
||||
s = delta_r * shs
|
||||
c.width = s
|
||||
c.height = s
|
||||
|
||||
if __name__ == "__main__":
|
||||
from kivy.app import App
|
||||
from kivy.uix.button import Button
|
||||
|
||||
class CircLayoutApp(App):
|
||||
def build(self):
|
||||
cly = CircularLayout(direction="cw", start_angle=-75, inner_radius_hint=.7, padding="20dp")
|
||||
|
||||
for i in xrange(1, 13):
|
||||
cly.add_widget(Button(text=str(i), font_size="30dp"))
|
||||
|
||||
return cly
|
||||
|
||||
CircLayoutApp().run()
|
22
src/kivymd/vendor/circularTimePicker/LICENSE
vendored
|
@ -1,22 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Davide Depau
|
||||
|
||||
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.
|
||||
|
43
src/kivymd/vendor/circularTimePicker/README.md
vendored
|
@ -1,43 +0,0 @@
|
|||
Circular Date & Time Picker for Kivy
|
||||
====================================
|
||||
|
||||
(currently only time, date coming soon)
|
||||
|
||||
Based on [CircularLayout](https://github.com/kivy-garden/garden.circularlayout).
|
||||
The main aim is to provide a date and time selector similar to the
|
||||
one found in Android KitKat+.
|
||||
|
||||
![Screenshot](screenshot.png)
|
||||
|
||||
Simple usage
|
||||
------------
|
||||
|
||||
Import the widget with
|
||||
|
||||
```python
|
||||
from kivy.garden.circulardatetimepicker import CircularTimePicker
|
||||
```
|
||||
|
||||
then use it! That's it!
|
||||
|
||||
```python
|
||||
c = CircularTimePicker()
|
||||
c.bind(time=self.set_time)
|
||||
root.add_widget(c)
|
||||
```
|
||||
|
||||
in Kv language:
|
||||
|
||||
```
|
||||
<TimeChooserPopup@Popup>:
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
CircularTimePicker
|
||||
|
||||
Button:
|
||||
text: "Dismiss"
|
||||
size_hint_y: None
|
||||
height: "40dp"
|
||||
on_release: root.dismiss()
|
||||
```
|
770
src/kivymd/vendor/circularTimePicker/__init__.py
vendored
|
@ -1,770 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Circular Date & Time Picker for Kivy
|
||||
====================================
|
||||
|
||||
(currently only time, date coming soon)
|
||||
|
||||
Based on [CircularLayout](https://github.com/kivy-garden/garden.circularlayout).
|
||||
The main aim is to provide a date and time selector similar to the
|
||||
one found in Android KitKat+.
|
||||
|
||||
Simple usage
|
||||
------------
|
||||
|
||||
Import the widget with
|
||||
|
||||
```python
|
||||
from kivy.garden.circulardatetimepicker import CircularTimePicker
|
||||
```
|
||||
|
||||
then use it! That's it!
|
||||
|
||||
```python
|
||||
c = CircularTimePicker()
|
||||
c.bind(time=self.set_time)
|
||||
root.add_widget(c)
|
||||
```
|
||||
|
||||
in Kv language:
|
||||
|
||||
```
|
||||
<TimeChooserPopup@Popup>:
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
CircularTimePicker
|
||||
|
||||
Button:
|
||||
text: "Dismiss"
|
||||
size_hint_y: None
|
||||
height: "40dp"
|
||||
on_release: root.dismiss()
|
||||
```
|
||||
"""
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivymd.vendor.circleLayout import CircularLayout
|
||||
from kivy.graphics import Line, Color, Ellipse
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import NumericProperty, BoundedNumericProperty, \
|
||||
ObjectProperty, StringProperty, DictProperty, \
|
||||
ListProperty, OptionProperty, BooleanProperty, \
|
||||
ReferenceListProperty, AliasProperty
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.metrics import dp
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from math import atan, pi, radians, sin, cos
|
||||
import sys
|
||||
import datetime
|
||||
if sys.version_info[0] > 2:
|
||||
def xrange(first=None, second=None, third=None):
|
||||
if third:
|
||||
return range(first, second, third)
|
||||
else:
|
||||
return range(first, second)
|
||||
|
||||
|
||||
def map_number(x, in_min, in_max, out_min, out_max):
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||
|
||||
|
||||
def rgb_to_hex(*color):
|
||||
tor = "#"
|
||||
for col in color:
|
||||
tor += "{:>02}".format(hex(int(col * 255))[2:])
|
||||
return tor
|
||||
|
||||
|
||||
Builder.load_string("""
|
||||
|
||||
<Number>:
|
||||
text_size: self.size
|
||||
valign: "middle"
|
||||
halign: "center"
|
||||
font_size: self.height * self.size_factor
|
||||
|
||||
<CircularNumberPicker>:
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Scale:
|
||||
origin: self.center_x + self.padding[0] - self.padding[2], self.center_y + self.padding[3] - self.padding[1]
|
||||
x: self.scale
|
||||
y: self.scale
|
||||
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
|
||||
<CircularTimePicker>:
|
||||
orientation: "vertical"
|
||||
spacing: "20dp"
|
||||
|
||||
FloatLayout:
|
||||
anchor_x: "center"
|
||||
anchor_y: "center"
|
||||
size_hint_y: 1./3
|
||||
size_hint_x: 1
|
||||
size: root.size
|
||||
pos: root.pos
|
||||
|
||||
GridLayout:
|
||||
cols: 2
|
||||
spacing: "10dp"
|
||||
size_hint_x: None
|
||||
width: self.minimum_width
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
|
||||
Label:
|
||||
id: timelabel
|
||||
text: root.time_text
|
||||
markup: True
|
||||
halign: "right"
|
||||
valign: "middle"
|
||||
# text_size: self.size
|
||||
size_hint_x: None #.6
|
||||
width: self.texture_size[0]
|
||||
font_size: self.height * .75
|
||||
|
||||
Label:
|
||||
id: ampmlabel
|
||||
text: root.ampm_text
|
||||
markup: True
|
||||
halign: "left"
|
||||
valign: "middle"
|
||||
# text_size: self.size
|
||||
size_hint_x: None #.4
|
||||
width: self.texture_size[0]
|
||||
font_size: self.height * .3
|
||||
|
||||
FloatLayout:
|
||||
id: picker_container
|
||||
#size_hint_y: 2./3
|
||||
_bound: {}
|
||||
""")
|
||||
|
||||
|
||||
class Number(Label):
|
||||
"""The class used to show the numbers in the selector.
|
||||
"""
|
||||
|
||||
size_factor = NumericProperty(.5)
|
||||
"""Font size scale.
|
||||
|
||||
:attr:`size_factor` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 0.5.
|
||||
"""
|
||||
|
||||
|
||||
class CircularNumberPicker(CircularLayout):
|
||||
"""A circular number picker based on CircularLayout. A selector will
|
||||
help you pick a number. You can also set :attr:`multiples_of` to make
|
||||
it show only some numbers and use the space in between for the other
|
||||
numbers.
|
||||
"""
|
||||
|
||||
min = NumericProperty(0)
|
||||
"""The first value of the range.
|
||||
|
||||
:attr:`min` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 0.
|
||||
"""
|
||||
|
||||
max = NumericProperty(0)
|
||||
"""The last value of the range. Note that it behaves like xrange, so
|
||||
the actual last displayed value will be :attr:`max` - 1.
|
||||
|
||||
:attr:`max` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 0.
|
||||
"""
|
||||
|
||||
range = ReferenceListProperty(min, max)
|
||||
"""Packs :attr:`min` and :attr:`max` into a list for convenience. See
|
||||
their documentation for further information.
|
||||
|
||||
:attr:`range` is a :class:`~kivy.properties.ReferenceListProperty`.
|
||||
"""
|
||||
|
||||
multiples_of = NumericProperty(1)
|
||||
"""Only show numbers that are multiples of this number. The other numbers
|
||||
will be selectable, but won't have their own label.
|
||||
|
||||
:attr:`multiples_of` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 1.
|
||||
"""
|
||||
|
||||
# selector_color = ListProperty([.337, .439, .490])
|
||||
selector_color = ListProperty([1, 1, 1])
|
||||
"""Color of the number selector. RGB.
|
||||
|
||||
:attr:`selector_color` is a :class:`~kivy.properties.ListProperty` and
|
||||
defaults to [.337, .439, .490] (material green).
|
||||
"""
|
||||
|
||||
color = ListProperty([0, 0, 0])
|
||||
"""Color of the number labels and of the center dot. RGB.
|
||||
|
||||
:attr:`color` is a :class:`~kivy.properties.ListProperty` and
|
||||
defaults to [1, 1, 1] (white).
|
||||
"""
|
||||
|
||||
selector_alpha = BoundedNumericProperty(.3, min=0, max=1)
|
||||
"""Alpha value for the transparent parts of the selector.
|
||||
|
||||
:attr:`selector_alpha` is a :class:`~kivy.properties.BoundedNumericProperty` and
|
||||
defaults to 0.3 (min=0, max=1).
|
||||
"""
|
||||
|
||||
selected = NumericProperty(None)
|
||||
"""Currently selected number.
|
||||
|
||||
:attr:`selected` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to :attr:`min`.
|
||||
"""
|
||||
|
||||
number_size_factor = NumericProperty(.5)
|
||||
"""Font size scale factor fot the :class:`Number`s.
|
||||
|
||||
:attr:`number_size_factor` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 0.5.
|
||||
"""
|
||||
|
||||
number_format_string = StringProperty("{}")
|
||||
"""String that will be formatted with the selected number as the first argument.
|
||||
Can be anything supported by :meth:`str.format` (es. "{:02d}").
|
||||
|
||||
:attr:`number_format_string` is a :class:`~kivy.properties.StringProperty` and
|
||||
defaults to "{}".
|
||||
"""
|
||||
|
||||
scale = NumericProperty(1)
|
||||
"""Canvas scale factor. Used in :class:`CircularTimePicker` transitions.
|
||||
|
||||
:attr:`scale` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 1.
|
||||
"""
|
||||
|
||||
_selection_circle = ObjectProperty(None)
|
||||
_selection_line = ObjectProperty(None)
|
||||
_selection_dot = ObjectProperty(None)
|
||||
_selection_dot_color = ObjectProperty(None)
|
||||
_selection_color = ObjectProperty(None)
|
||||
_center_dot = ObjectProperty(None)
|
||||
_center_color = ObjectProperty(None)
|
||||
|
||||
def _get_items(self):
|
||||
return self.max - self.min
|
||||
|
||||
items = AliasProperty(_get_items, None)
|
||||
|
||||
def _get_shown_items(self):
|
||||
sh = 0
|
||||
for i in xrange(*self.range):
|
||||
if i % self.multiples_of == 0:
|
||||
sh += 1
|
||||
return sh
|
||||
|
||||
shown_items = AliasProperty(_get_shown_items, None)
|
||||
|
||||
def __init__(self, **kw):
|
||||
self._trigger_genitems = Clock.create_trigger(self._genitems, -1)
|
||||
self.bind(min=self._trigger_genitems,
|
||||
max=self._trigger_genitems,
|
||||
multiples_of=self._trigger_genitems)
|
||||
super(CircularNumberPicker, self).__init__(**kw)
|
||||
self.selected = self.min
|
||||
self.bind(selected=self.on_selected,
|
||||
pos=self.on_selected,
|
||||
size=self.on_selected)
|
||||
|
||||
cx = self.center_x + self.padding[0] - self.padding[2]
|
||||
cy = self.center_y + self.padding[3] - self.padding[1]
|
||||
sx, sy = self.pos_for_number(self.selected)
|
||||
epos = [i - (self.delta_radii * self.number_size_factor) for i in (sx, sy)]
|
||||
esize = [self.delta_radii * self.number_size_factor * 2] * 2
|
||||
dsize = [i * .3 for i in esize]
|
||||
dpos = [i + esize[0] / 2. - dsize[0] / 2. for i in epos]
|
||||
csize = [i * .05 for i in esize]
|
||||
cpos = [i - csize[0] / 2. for i in (cx, cy)]
|
||||
dot_alpha = 0 if self.selected % self.multiples_of == 0 else 1
|
||||
color = list(self.selector_color)
|
||||
|
||||
with self.canvas:
|
||||
self._selection_color = Color(*(color + [self.selector_alpha]))
|
||||
self._selection_circle = Ellipse(pos=epos, size=esize)
|
||||
self._selection_line = Line(points=[cx, cy, sx, sy], width=dp(1.25))
|
||||
self._selection_dot_color = Color(*(color + [dot_alpha]))
|
||||
self._selection_dot = Ellipse(pos=dpos, size=dsize)
|
||||
self._center_color = Color(*self.color)
|
||||
self._center_dot = Ellipse(pos=cpos, size=csize)
|
||||
|
||||
self.bind(selector_color=lambda ign, u: setattr(self._selection_color, "rgba", u + [self.selector_alpha]))
|
||||
self.bind(selector_color=lambda ign, u: setattr(self._selection_dot_color, "rgb", u))
|
||||
self.bind(selector_color=lambda ign, u: self.dot_is_none())
|
||||
self.bind(color=lambda ign, u: setattr(self._center_color, "rgb", u))
|
||||
Clock.schedule_once(self._genitems)
|
||||
Clock.schedule_once(self.on_selected) # Just to make sure pos/size are set
|
||||
|
||||
def dot_is_none(self, *args):
|
||||
dot_alpha = 0 if self.selected % self.multiples_of == 0 else 1
|
||||
if self._selection_dot_color:
|
||||
self._selection_dot_color.a = dot_alpha
|
||||
|
||||
def _genitems(self, *a):
|
||||
self.clear_widgets()
|
||||
for i in xrange(*self.range):
|
||||
if i % self.multiples_of != 0:
|
||||
continue
|
||||
n = Number(text=self.number_format_string.format(i), size_factor=self.number_size_factor, color=self.color)
|
||||
self.bind(color=n.setter("color"))
|
||||
self.add_widget(n)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if not self.collide_point(*touch.pos):
|
||||
return
|
||||
touch.grab(self)
|
||||
self.selected = self.number_at_pos(*touch.pos)
|
||||
if self.selected == 60:
|
||||
self.selected = 0
|
||||
|
||||
def on_touch_move(self, touch):
|
||||
if touch.grab_current is not self:
|
||||
return super(CircularNumberPicker, self).on_touch_move(touch)
|
||||
self.selected = self.number_at_pos(*touch.pos)
|
||||
if self.selected == 60:
|
||||
self.selected = 0
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if touch.grab_current is not self:
|
||||
return super(CircularNumberPicker, self).on_touch_up(touch)
|
||||
touch.ungrab(self)
|
||||
|
||||
def on_selected(self, *a):
|
||||
cx = self.center_x + self.padding[0] - self.padding[2]
|
||||
cy = self.center_y + self.padding[3] - self.padding[1]
|
||||
sx, sy = self.pos_for_number(self.selected)
|
||||
epos = [i - (self.delta_radii * self.number_size_factor) for i in (sx, sy)]
|
||||
esize = [self.delta_radii * self.number_size_factor * 2] * 2
|
||||
dsize = [i * .3 for i in esize]
|
||||
dpos = [i + esize[0] / 2. - dsize[0] / 2. for i in epos]
|
||||
csize = [i * .05 for i in esize]
|
||||
cpos = [i - csize[0] / 2. for i in (cx, cy)]
|
||||
dot_alpha = 0 if self.selected % self.multiples_of == 0 else 1
|
||||
|
||||
if self._selection_circle:
|
||||
self._selection_circle.pos = epos
|
||||
self._selection_circle.size = esize
|
||||
if self._selection_line:
|
||||
self._selection_line.points = [cx, cy, sx, sy]
|
||||
if self._selection_dot:
|
||||
self._selection_dot.pos = dpos
|
||||
self._selection_dot.size = dsize
|
||||
if self._selection_dot_color:
|
||||
self._selection_dot_color.a = dot_alpha
|
||||
if self._center_dot:
|
||||
self._center_dot.pos = cpos
|
||||
self._center_dot.size = csize
|
||||
|
||||
def pos_for_number(self, n):
|
||||
"""Returns the center x, y coordinates for a given number.
|
||||
"""
|
||||
|
||||
if self.items == 0:
|
||||
return 0, 0
|
||||
radius = min(self.width - self.padding[0] - self.padding[2],
|
||||
self.height - self.padding[1] - self.padding[3]) / 2.
|
||||
middle_r = radius * sum(self.radius_hint) / 2.
|
||||
cx = self.center_x + self.padding[0] - self.padding[2]
|
||||
cy = self.center_y + self.padding[3] - self.padding[1]
|
||||
sign = +1.
|
||||
angle_offset = radians(self.start_angle)
|
||||
if self.direction == 'cw':
|
||||
angle_offset = 2 * pi - angle_offset
|
||||
sign = -1.
|
||||
quota = 2 * pi / self.items
|
||||
mult_quota = 2 * pi / self.shown_items
|
||||
angle = angle_offset + n * sign * quota
|
||||
|
||||
if self.items == self.shown_items:
|
||||
angle += quota / 2
|
||||
else:
|
||||
angle -= mult_quota / 2
|
||||
|
||||
# kived: looking it up, yes. x = cos(angle) * radius + centerx; y = sin(angle) * radius + centery
|
||||
x = cos(angle) * middle_r + cx
|
||||
y = sin(angle) * middle_r + cy
|
||||
|
||||
return x, y
|
||||
|
||||
def number_at_pos(self, x, y):
|
||||
"""Returns the number at a given x, y position. The number is found
|
||||
using the widget's center as a starting point for angle calculations.
|
||||
|
||||
Not thoroughly tested, may yield wrong results.
|
||||
"""
|
||||
if self.items == 0:
|
||||
return self.min
|
||||
cx = self.center_x + self.padding[0] - self.padding[2]
|
||||
cy = self.center_y + self.padding[3] - self.padding[1]
|
||||
lx = x - cx
|
||||
ly = y - cy
|
||||
quota = 2 * pi / self.items
|
||||
mult_quota = 2 * pi / self.shown_items
|
||||
if lx == 0 and ly > 0:
|
||||
angle = pi / 2
|
||||
elif lx == 0 and ly < 0:
|
||||
angle = 3 * pi / 2
|
||||
else:
|
||||
angle = atan(ly / lx)
|
||||
if lx < 0 < ly:
|
||||
angle += pi
|
||||
if lx > 0 > ly:
|
||||
angle += 2 * pi
|
||||
if lx < 0 and ly < 0:
|
||||
angle += pi
|
||||
angle += radians(self.start_angle)
|
||||
if self.direction == "cw":
|
||||
angle = 2 * pi - angle
|
||||
if mult_quota != quota:
|
||||
angle -= mult_quota / 2
|
||||
if angle < 0:
|
||||
angle += 2 * pi
|
||||
elif angle > 2 * pi:
|
||||
angle -= 2 * pi
|
||||
|
||||
return int(angle / quota) + self.min
|
||||
|
||||
|
||||
class CircularMinutePicker(CircularNumberPicker):
|
||||
""":class:`CircularNumberPicker` implementation for minutes.
|
||||
"""
|
||||
|
||||
def __init__(self, **kw):
|
||||
super(CircularMinutePicker, self).__init__(**kw)
|
||||
self.min = 0
|
||||
self.max = 60
|
||||
self.multiples_of = 5
|
||||
self.number_format_string = "{:02d}"
|
||||
self.direction = "cw"
|
||||
self.bind(shown_items=self._update_start_angle)
|
||||
Clock.schedule_once(self._update_start_angle)
|
||||
Clock.schedule_once(self.on_selected)
|
||||
|
||||
def _update_start_angle(self, *a):
|
||||
self.start_angle = -(360. / self.shown_items / 2) - 90
|
||||
|
||||
|
||||
class CircularHourPicker(CircularNumberPicker):
|
||||
""":class:`CircularNumberPicker` implementation for hours.
|
||||
"""
|
||||
|
||||
# military = BooleanProperty(False)
|
||||
|
||||
def __init__(self, **kw):
|
||||
super(CircularHourPicker, self).__init__(**kw)
|
||||
self.min = 1
|
||||
self.max = 13
|
||||
# 25 if self.military else 13
|
||||
# self.inner_radius_hint = .8 if self.military else .6
|
||||
self.multiples_of = 1
|
||||
self.number_format_string = "{}"
|
||||
self.direction = "cw"
|
||||
self.bind(shown_items=self._update_start_angle)
|
||||
# self.bind(military=lambda v: setattr(self, "max", 25 if v else 13))
|
||||
# self.bind(military=lambda v: setattr(self, "inner_radius_hint", .8 if self.military else .6))
|
||||
# Clock.schedule_once(self._genitems)
|
||||
Clock.schedule_once(self._update_start_angle)
|
||||
Clock.schedule_once(self.on_selected)
|
||||
|
||||
def _update_start_angle(self, *a):
|
||||
self.start_angle = (360. / self.shown_items / 2) - 90
|
||||
|
||||
|
||||
class CircularTimePicker(BoxLayout, ThemableBehavior):
|
||||
"""Widget that makes use of :class:`CircularHourPicker` and
|
||||
:class:`CircularMinutePicker` to create a user-friendly, animated
|
||||
time picker like the one seen on Android.
|
||||
|
||||
See module documentation for more details.
|
||||
"""
|
||||
|
||||
primary_dark = ListProperty([1, 1, 1])
|
||||
|
||||
hours = NumericProperty(0)
|
||||
"""The hours, in military format (0-23).
|
||||
|
||||
:attr:`hours` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 0 (12am).
|
||||
"""
|
||||
|
||||
minutes = NumericProperty(0)
|
||||
"""The minutes.
|
||||
|
||||
:attr:`minutes` is a :class:`~kivy.properties.NumericProperty` and
|
||||
defaults to 0.
|
||||
"""
|
||||
|
||||
time_list = ReferenceListProperty(hours, minutes)
|
||||
"""Packs :attr:`hours` and :attr:`minutes` in a list for convenience.
|
||||
|
||||
:attr:`time_list` is a :class:`~kivy.properties.ReferenceListProperty`.
|
||||
"""
|
||||
|
||||
# military = BooleanProperty(False)
|
||||
time_format = StringProperty(
|
||||
"[color={hours_color}][ref=hours]{hours}[/ref][/color][color={primary_dark}][ref=colon]:[/ref][/color]\
|
||||
[color={minutes_color}][ref=minutes]{minutes:02d}[/ref][/color]")
|
||||
"""String that will be formatted with the time and shown in the time label.
|
||||
Can be anything supported by :meth:`str.format`. Make sure you don't
|
||||
remove the refs. See the default for the arguments passed to format.
|
||||
:attr:`time_format` is a :class:`~kivy.properties.StringProperty` and
|
||||
defaults to "[color={hours_color}][ref=hours]{hours}[/ref][/color]:[color={minutes_color}][ref=minutes]\
|
||||
{minutes:02d}[/ref][/color]".
|
||||
"""
|
||||
|
||||
ampm_format = StringProperty(
|
||||
"[color={am_color}][ref=am]AM[/ref][/color]\n[color={pm_color}][ref=pm]PM[/ref][/color]")
|
||||
"""String that will be formatted and shown in the AM/PM label.
|
||||
Can be anything supported by :meth:`str.format`. Make sure you don't
|
||||
remove the refs. See the default for the arguments passed to format.
|
||||
|
||||
:attr:`ampm_format` is a :class:`~kivy.properties.StringProperty` and
|
||||
defaults to "[color={am_color}][ref=am]AM[/ref][/color]\n[color={pm_color}][ref=pm]PM[/ref][/color]".
|
||||
"""
|
||||
|
||||
picker = OptionProperty("hours", options=("minutes", "hours"))
|
||||
"""Currently shown time picker. Can be one of "minutes", "hours".
|
||||
|
||||
:attr:`picker` is a :class:`~kivy.properties.OptionProperty` and
|
||||
defaults to "hours".
|
||||
"""
|
||||
|
||||
# selector_color = ListProperty([.337, .439, .490])
|
||||
selector_color = ListProperty([0, 0, 0])
|
||||
"""Color of the number selector and of the highlighted text. RGB.
|
||||
|
||||
:attr:`selector_color` is a :class:`~kivy.properties.ListProperty` and
|
||||
defaults to [.337, .439, .490] (material green).
|
||||
"""
|
||||
|
||||
color = ListProperty([1, 1, 1])
|
||||
"""Color of the number labels and of the center dot. RGB.
|
||||
|
||||
:attr:`color` is a :class:`~kivy.properties.ListProperty` and
|
||||
defaults to [1, 1, 1] (white).
|
||||
"""
|
||||
|
||||
selector_alpha = BoundedNumericProperty(.3, min=0, max=1)
|
||||
"""Alpha value for the transparent parts of the selector.
|
||||
|
||||
:attr:`selector_alpha` is a :class:`~kivy.properties.BoundedNumericProperty` and
|
||||
defaults to 0.3 (min=0, max=1).
|
||||
"""
|
||||
|
||||
_am = BooleanProperty(True)
|
||||
_h_picker = ObjectProperty(None)
|
||||
_m_picker = ObjectProperty(None)
|
||||
_bound = DictProperty({})
|
||||
|
||||
def _get_time(self):
|
||||
try:
|
||||
return datetime.time(*self.time_list)
|
||||
except ValueError:
|
||||
self.time_list = [self.hours, 0]
|
||||
return datetime.time(*self.time_list)
|
||||
|
||||
def set_time(self, dt):
|
||||
if dt.hour >= 12:
|
||||
dt.strftime("%I:%M")
|
||||
self._am = False
|
||||
self.time_list = [dt.hour, dt.minute]
|
||||
|
||||
time = AliasProperty(_get_time, set_time, bind=("time_list",))
|
||||
"""Selected time as a datetime.time object.
|
||||
|
||||
:attr:`time` is an :class:`~kivy.properties.AliasProperty`.
|
||||
"""
|
||||
|
||||
def _get_picker(self):
|
||||
if self.picker == "hours":
|
||||
return self._h_picker
|
||||
return self._m_picker
|
||||
|
||||
_picker = AliasProperty(_get_picker, None)
|
||||
|
||||
def _get_time_text(self):
|
||||
hc = rgb_to_hex(0, 0, 0) if self.picker == "hours" else rgb_to_hex(*self.primary_dark)
|
||||
mc = rgb_to_hex(0, 0, 0) if self.picker == "minutes" else rgb_to_hex(*self.primary_dark)
|
||||
h = self.hours == 0 and 12 or self.hours <= 12 and self.hours or self.hours - 12
|
||||
m = self.minutes
|
||||
primary_dark = rgb_to_hex(*self.primary_dark)
|
||||
return self.time_format.format(hours_color=hc,
|
||||
minutes_color=mc,
|
||||
hours=h,
|
||||
minutes=m,
|
||||
primary_dark=primary_dark)
|
||||
time_text = AliasProperty(_get_time_text, None, bind=("hours", "minutes", "time_format", "picker"))
|
||||
|
||||
def _get_ampm_text(self, *args):
|
||||
amc = rgb_to_hex(0, 0, 0) if self._am else rgb_to_hex(*self.primary_dark)
|
||||
pmc = rgb_to_hex(0, 0, 0) if not self._am else rgb_to_hex(*self.primary_dark)
|
||||
return self.ampm_format.format(am_color=amc,
|
||||
pm_color=pmc)
|
||||
|
||||
ampm_text = AliasProperty(_get_ampm_text, None, bind=("hours", "ampm_format", "_am"))
|
||||
|
||||
def __init__(self, **kw):
|
||||
super(CircularTimePicker, self).__init__(**kw)
|
||||
self.selector_color = self.theme_cls.primary_color[0], self.theme_cls.primary_color[1], \
|
||||
self.theme_cls.primary_color[2]
|
||||
self.color = self.theme_cls.text_color
|
||||
self.primary_dark = self.theme_cls.primary_dark[0] / 2, self.theme_cls.primary_dark[1] / 2, \
|
||||
self.theme_cls.primary_dark[2] / 2
|
||||
self.on_ampm()
|
||||
if self.hours >= 12:
|
||||
self._am = False
|
||||
self.bind(time_list=self.on_time_list,
|
||||
picker=self._switch_picker,
|
||||
_am=self.on_ampm,
|
||||
primary_dark=self._get_ampm_text)
|
||||
self._h_picker = CircularHourPicker()
|
||||
self.h_picker_touch = False
|
||||
self._m_picker = CircularMinutePicker()
|
||||
self.animating = False
|
||||
Clock.schedule_once(self.on_selected)
|
||||
Clock.schedule_once(self.on_time_list)
|
||||
Clock.schedule_once(self._init_later)
|
||||
Clock.schedule_once(lambda *a: self._switch_picker(noanim=True))
|
||||
|
||||
def _init_later(self, *args):
|
||||
self.ids.timelabel.bind(on_ref_press=self.on_ref_press)
|
||||
self.ids.ampmlabel.bind(on_ref_press=self.on_ref_press)
|
||||
|
||||
def on_ref_press(self, ign, ref):
|
||||
if not self.animating:
|
||||
if ref == "hours":
|
||||
self.picker = "hours"
|
||||
elif ref == "minutes":
|
||||
self.picker = "minutes"
|
||||
if ref == "am":
|
||||
self._am = True
|
||||
elif ref == "pm":
|
||||
self._am = False
|
||||
|
||||
def on_selected(self, *a):
|
||||
if not self._picker:
|
||||
return
|
||||
if self.picker == "hours":
|
||||
hours = self._picker.selected if self._am else self._picker.selected + 12
|
||||
if hours == 24 and not self._am:
|
||||
hours = 12
|
||||
elif hours == 12 and self._am:
|
||||
hours = 0
|
||||
self.hours = hours
|
||||
elif self.picker == "minutes":
|
||||
self.minutes = self._picker.selected
|
||||
|
||||
def on_time_list(self, *a):
|
||||
if not self._picker:
|
||||
return
|
||||
self._h_picker.selected = self.hours == 0 and 12 or self._am and self.hours or self.hours - 12
|
||||
self._m_picker.selected = self.minutes
|
||||
self.on_selected()
|
||||
|
||||
def on_ampm(self, *a):
|
||||
if self._am:
|
||||
self.hours = self.hours if self.hours < 12 else self.hours - 12
|
||||
else:
|
||||
self.hours = self.hours if self.hours >= 12 else self.hours + 12
|
||||
|
||||
def is_animating(self, *args):
|
||||
self.animating = True
|
||||
|
||||
def is_not_animating(self, *args):
|
||||
self.animating = False
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if not self._h_picker.collide_point(*touch.pos):
|
||||
self.h_picker_touch = False
|
||||
else:
|
||||
self.h_picker_touch = True
|
||||
super(CircularTimePicker, self).on_touch_down(touch)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
try:
|
||||
if not self.h_picker_touch:
|
||||
return
|
||||
if not self.animating:
|
||||
if touch.grab_current is not self:
|
||||
if self.picker == "hours":
|
||||
self.picker = "minutes"
|
||||
except AttributeError:
|
||||
pass
|
||||
super(CircularTimePicker, self).on_touch_up(touch)
|
||||
|
||||
def _switch_picker(self, *a, **kw):
|
||||
noanim = "noanim" in kw
|
||||
if noanim:
|
||||
noanim = kw["noanim"]
|
||||
|
||||
try:
|
||||
container = self.ids.picker_container
|
||||
except (AttributeError, NameError):
|
||||
Clock.schedule_once(lambda *a: self._switch_picker(noanim=noanim))
|
||||
|
||||
if self.picker == "hours":
|
||||
picker = self._h_picker
|
||||
prevpicker = self._m_picker
|
||||
elif self.picker == "minutes":
|
||||
picker = self._m_picker
|
||||
prevpicker = self._h_picker
|
||||
|
||||
if len(self._bound) > 0:
|
||||
prevpicker.unbind(selected=self.on_selected)
|
||||
self.unbind(**self._bound)
|
||||
picker.bind(selected=self.on_selected)
|
||||
self._bound = {"selector_color": picker.setter("selector_color"),
|
||||
"color": picker.setter("color"),
|
||||
"selector_alpha": picker.setter("selector_alpha")}
|
||||
self.bind(**self._bound)
|
||||
|
||||
if len(container._bound) > 0:
|
||||
container.unbind(**container._bound)
|
||||
container._bound = {"size": picker.setter("size"),
|
||||
"pos": picker.setter("pos")}
|
||||
container.bind(**container._bound)
|
||||
|
||||
picker.pos = container.pos
|
||||
picker.size = container.size
|
||||
picker.selector_color = self.selector_color
|
||||
picker.color = self.color
|
||||
picker.selector_alpha = self.selector_alpha
|
||||
if noanim:
|
||||
if prevpicker in container.children:
|
||||
container.remove_widget(prevpicker)
|
||||
if picker.parent:
|
||||
picker.parent.remove_widget(picker)
|
||||
container.add_widget(picker)
|
||||
else:
|
||||
self.is_animating()
|
||||
if prevpicker in container.children:
|
||||
anim = Animation(scale=1.5, d=.5, t="in_back") & Animation(opacity=0, d=.5, t="in_cubic")
|
||||
anim.start(prevpicker)
|
||||
Clock.schedule_once(lambda *y: container.remove_widget(prevpicker), .5) # .31)
|
||||
picker.scale = 1.5
|
||||
picker.opacity = 0
|
||||
if picker.parent:
|
||||
picker.parent.remove_widget(picker)
|
||||
container.add_widget(picker)
|
||||
anim = Animation(scale=1, d=.5, t="out_back") & Animation(opacity=1, d=.5, t="out_cubic")
|
||||
anim.bind(on_complete=self.is_not_animating)
|
||||
Clock.schedule_once(lambda *y: anim.start(picker), .3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from kivy.base import runTouchApp
|
||||
|
||||
c = CircularTimePicker()
|
||||
runTouchApp(c)
|
|
@ -1,6 +1,11 @@
|
|||
"""This module is for thread start."""
|
||||
import state
|
||||
import sys
|
||||
from bitmessagemain import main
|
||||
from termcolor import colored
|
||||
print(colored('kivy is not supported at the moment for this version..', 'red'))
|
||||
sys.exit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
state.kivy = True
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty, ObjectProperty
|
||||
from kivymd.elevationbehavior import ElevationBehavior
|
||||
from kivymd.icon_definitions import md_icons
|
||||
from kivymd.label import MDLabel
|
||||
from kivymd.list import OneLineIconListItem, ILeftBody, BaseListItem
|
||||
from kivymd.slidingpanel import SlidingPanel
|
||||
from kivymd.theming import ThemableBehavior
|
||||
|
||||
Builder.load_string('''
|
||||
<NavDrawerToolbar@Label>
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.parent.parent.theme_cls.divider_color
|
||||
Line:
|
||||
points: self.x, self.y, self.x+self.width,self.y
|
||||
|
||||
<NavigationDrawer>
|
||||
widget_list: widget_list
|
||||
elevation: 0
|
||||
canvas:
|
||||
Color:
|
||||
rgba: root.theme_cls.bg_light
|
||||
Rectangle:
|
||||
size: root.size
|
||||
pos: root.pos
|
||||
BoxLayout:
|
||||
size_hint: (1, .4)
|
||||
NavDrawerToolbar:
|
||||
padding: 10, 10
|
||||
canvas.after:
|
||||
Color:
|
||||
rgba: (1, 1, 1, 1)
|
||||
RoundedRectangle:
|
||||
size: (self.size[1]-dp(14), self.size[1]-dp(14))
|
||||
pos: (self.pos[0]+(self.size[0]-self.size[1])/2, self.pos[1]+dp(7))
|
||||
source: root.image_source
|
||||
radius: [self.size[1]-(self.size[1]/2)]
|
||||
|
||||
ScrollView:
|
||||
do_scroll_x: False
|
||||
MDList:
|
||||
id: ml
|
||||
id: widget_list
|
||||
|
||||
<NavigationDrawerIconButton>
|
||||
NDIconLabel:
|
||||
id: _icon
|
||||
font_style: 'Icon'
|
||||
theme_text_color: 'Secondary'
|
||||
''')
|
||||
|
||||
|
||||
class NavigationDrawer(SlidingPanel, ThemableBehavior, ElevationBehavior):
|
||||
image_source = StringProperty()
|
||||
widget_list = ObjectProperty()
|
||||
|
||||
def add_widget(self, widget, index=0):
|
||||
if issubclass(widget.__class__, BaseListItem):
|
||||
self.widget_list.add_widget(widget, index)
|
||||
widget.bind(on_release=lambda x: self.toggle())
|
||||
else:
|
||||
super(NavigationDrawer, self).add_widget(widget, index)
|
||||
|
||||
def _get_main_animation(self, duration, t, x, is_closing):
|
||||
a = super(NavigationDrawer, self)._get_main_animation(duration, t, x,
|
||||
is_closing)
|
||||
a &= Animation(elevation=0 if is_closing else 5, t=t, duration=duration)
|
||||
return a
|
||||
|
||||
|
||||
class NDIconLabel(ILeftBody, MDLabel):
|
||||
pass
|
||||
|
||||
|
||||
class NavigationDrawerIconButton(OneLineIconListItem):
|
||||
icon = StringProperty()
|
||||
|
||||
def on_icon(self, instance, value):
|
||||
self.ids['_icon'].text = u"{}".format(md_icons[value])
|