This commit is contained in:
yumoqing 2020-03-07 22:11:07 +08:00
parent 1cf4c0a421
commit 8657c9764f
11 changed files with 2130 additions and 2 deletions

View File

@ -0,0 +1,9 @@
import os
from kivy import logger
__version_info__ = (0.5.0)
__version__ = '0.5.0'
path = os.path.dirname(__file__)
fonts_path = os.path.join(path,"ttf/")
images_path = os.path.join(path,'imgs/')
logger.info("kivyblocks:kivblocks version:{}".format(__version__)

View File

@ -55,6 +55,7 @@ from .widgetExt.inputext import FloatInput,IntegerInput, \
StrInput,SelectInput, BoolInput, AmountInput
from .widgetExt.messager import Messager
from .charts.bar import Bar
from .tree import Tree, TextTree
if platform == 'android':
from .widgetExt.phonebutton import PhoneButton
@ -62,7 +63,11 @@ if platform == 'android':
class PressableImage(ButtonBehavior,AsyncImage):
def on_press(self):
print
pass
class PressableLabel(ButtonBehavior, Label):
def on_press(self):
pass
class Text(Label):
bgColor = ListProperty([0.5,0.5,0.5,1])

View File

@ -28,6 +28,7 @@ from .utils import *
from .pagescontainer import PageContainer
from .widgetExt.messager import Messager
from .blocks import Blocks
from .theming import ThemeManager
from appPublic.rsa import RSA
class ServerInfo:
@ -68,6 +69,7 @@ class BlocksApp(App):
def build(self):
config = getConfig()
self.config = config
self.theme_cls = ThemeManager()
self.serverinfo = ServerInfo()
self.title = 'Test Title'
self.blocks = Blocks()

View File

@ -0,0 +1,676 @@
colors = {
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'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'
},
'Brown': {
'200': 'bcaaa4',
'900': '3e2723',
'600': '6d4c41',
'300': 'a1887f',
'700': '5d4037',
'50': 'efebe9',
'400': '8d6e63',
'100': 'd7ccc8',
'800': '4e342e',
'500': '795548'
},
'Grey': {
'200': 'eeeeee',
'900': '212121',
'600': '757575',
'300': 'e0e0e0',
'700': '616161',
'50': 'fafafa',
'400': 'bdbdbd',
'100': 'f5f5f5',
'800': '424242',
'500': '9e9e9e'
},
'BlueGrey': {
'200': 'b0bec5',
'900': '263238',
'600': '546e7a',
'300': '90a4ae',
'700': '455a64',
'50': 'eceff1',
'400': '78909c',
'100': 'cfd8dc',
'800': '37474f',
'500': '607d8b'
},
'Light': {
'StatusBar': 'E0E0E0',
'AppBar': 'F5F5F5',
'Background': 'FAFAFA',
'CardsDialogs': 'FFFFFF',
'FlatButtonDown': 'cccccc'
},
'Dark': {
'StatusBar': '000000',
'AppBar': '212121',
'Background': '303030',
'CardsDialogs': '424242',
'FlatButtonDown': '999999'
}
}
light_colors = {
'Red': ['50', '100', '200', '300', 'A100'],
'Pink': ['50', '100', '200', 'A100'],
'Purple': ['50', '100', '200', 'A100'],
'DeepPurple': ['50', '100', '200', 'A100'],
'Indigo': ['50', '100', '200', 'A100'],
'Blue': ['50','100', '200', '300', '400', 'A100'],
'LightBlue': ['50', '100', '200', '300', '400', '500', 'A100', 'A200',
'A400'],
'Cyan': ['50', '100', '200', '300', '400', '500', '600', 'A100', 'A200',
'A400', 'A700'],
'Teal': ['50', '100', '200', '300', '400', 'A100', 'A200', 'A400', 'A700'],
'Green': ['50', '100', '200', '300', '400', '500', 'A100', 'A200', 'A400',
'A700'],
'LightGreen': ['50', '100', '200', '300', '400', '500', '600', 'A100',
'A200', 'A400', 'A700'],
'Lime': ['50', '100', '200', '300', '400', '500', '600', '700', '800',
'A100', 'A200', 'A400', 'A700'],
'Yellow': ['50', '100', '200', '300', '400', '500', '600', '700', '800',
'900', 'A100', 'A200', 'A400', 'A700'],
'Amber': ['50', '100', '200', '300', '400', '500', '600', '700', '800',
'900', 'A100', 'A200', 'A400', 'A700'],
'Orange': ['50', '100', '200', '300', '400', '500', '600', '700', 'A100',
'A200', 'A400', 'A700'],
'DeepOrange': ['50', '100', '200', '300', '400', 'A100', 'A200'],
'Brown': ['50', '100', '200'],
'Grey': ['51', '100', '200', '300', '400', '500'],
'BlueGrey': ['50', '100', '200', '300'],
'Dark': [],
'Light': ['White', 'MainBackground', 'DialogBackground']
}
level_bg_colors = [
'50',
'100',
'200',
'300',
]
level_selected_bg_colors = [
'400',
'500',
'600',
'700'
]
def getColors(style,level):
i = level % len(level_bg_colors)
text_color = text_colors[style][ level_bg_colors[i]]
bg_color = colors[style][ level_bg_colors[i]]
return color
def getSelectedColors(style,level):
i = level % len(level_selected_bg_colors)
text_color = text_colors[style][ level_bg_colors[i]]
bg_color = colors[style][ level_bg_colors[i]]
return text_color, bg_color
error_color_id = '900',
info_color_id = '800'
def getErrorColor(style):
text_color = text_colors[style][ error_color_id ]
bg_color = colors[style][ error_color_id ]
return text_color, bg_color
def getInfoColor(style):
text_color = text_colors[style][ info_color_id ]
bg_color = colors[style][ info_color_id ]
return text_color, bg_color
text_colors = {
'Red': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': 'ffffff',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': 'ffffff',
'A400': 'ffffff',
'A700': 'ffffff',
},
'Pink': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': 'ffffff',
'400': 'ffffff',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': 'ffffff',
'A400': 'ffffff',
'A700': 'ffffff',
},
'Purple': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': 'ffffff',
'400': 'ffffff',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': 'ffffff',
'A400': 'ffffff',
'A700': 'ffffff',
},
'DeepPurple': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': 'ffffff',
'400': 'ffffff',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': 'ffffff',
'A400': 'ffffff',
'A700': 'ffffff',
},
'Indigo': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': 'ffffff',
'400': 'ffffff',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': 'ffffff',
'A400': 'ffffff',
'A700': 'ffffff',
},
'Blue': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': 'ffffff',
'A400': 'ffffff',
'A700': 'ffffff',
},
'LightBlue': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': 'ffffff',
},
'Cyan': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': '000000',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': '000000',
},
'Teal': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': '000000',
},
'Green': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': '000000',
},
'LightGreen': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': '000000',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': '000000',
},
'Lime': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': '000000',
'700': '000000',
'800': '000000',
'900': 'ffffff',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': '000000',
},
'Yellow': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': '000000',
'700': '000000',
'800': '000000',
'900': '000000',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': '000000',
},
'Amber': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': '000000',
'700': '000000',
'800': '000000',
'900': '000000',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': '000000',
},
'Orange': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': '000000',
'700': '000000',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': '000000',
'A400': '000000',
'A700': '000000',
},
'DeepOrange': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
'A100': '000000',
'A200': '000000',
'A400': 'ffffff',
'A700': 'ffffff',
},
'Brown': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': 'ffffff',
'400': 'ffffff',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
},
'Grey': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': '000000',
'500': '000000',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
},
'BlueGrey': {
'50': '000000',
'100': '000000',
'200': '000000',
'300': '000000',
'400': 'ffffff',
'500': 'ffffff',
'600': 'ffffff',
'700': 'ffffff',
'800': 'ffffff',
'900': 'ffffff',
},
}

331
kivyblocks/date_picker.py Normal file
View File

@ -0,0 +1,331 @@
# -*- 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 kivymd.backgroundcolorbehavior import SpecificBackgroundColorBehavior
from kivy.uix.floatlayout import FloatLayout
from kivymd.elevationbehavior import RectangularElevationBehavior
import calendar
from datetime import date
import datetime
from kivy.properties import StringProperty, NumericProperty, ObjectProperty, \
BooleanProperty, ListProperty
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
#:import platform platform
<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: root.specific_text_color
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: root.specific_text_color
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.08, '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:
width: dp(32)
id: ok_button
pos: root.pos[0]+root.size[0]-self.width-dp(10), root.pos[1] + dp(10)
text: "OK"
on_release: root.ok_click()
MDFlatButton:
id: cancel_button
pos: root.pos[0]+root.size[0]-self.width-ok_button.width-dp(10), root.pos[1] + dp(10)
text: "Cancel"
on_release: root.dismiss()
<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], 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, RectangularElevationBehavior,
SpecificBackgroundColorBehavior, 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()
background_color = ListProperty([0, 0, 0, 0.7])
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)

View File

@ -119,7 +119,6 @@ class Row(GridLayout):
self.part.datagrid.select_rowid = self.row_id
self.part.datagrid.dispatch('on_selected',self)
class Header(WidgetReady, ScrollWidget):
def __init__(self,part,**kw):
self.part = part

View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from kivy import platform
from kivy.core.window import Window
from kivy.metrics import dp
from kivyblocks 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 + 'materialdesignicons-webfont.ttf'
}
]

383
kivyblocks/theme_picker.py Normal file
View File

@ -0,0 +1,383 @@
# -*- coding: utf-8 -*-
from kivy.lang import Builder
from kivy.properties import OptionProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.modalview import ModalView
from kivy.utils import get_color_from_hex
from kivymd.backgroundcolorbehavior import SpecificBackgroundColorBehavior
from kivymd.button import MDIconButton
from kivymd.color_definitions import colors
from kivymd.elevationbehavior import RectangularElevationBehavior
from kivymd.theming import ThemableBehavior
Builder.load_string("""
#:import MDTabbedPanel kivymd.tabs.MDTabbedPanel
#:import MDTab kivymd.tabs.MDTab
<ColorSelector>:
size: dp(40), dp(40)
pos: self.pos
size_hint: (None, None)
canvas:
Color:
rgba: root.rgb_hex(root.color_name)
Ellipse:
size: self.size
pos: self.pos
<AccentColorSelector@ColorSelector>:
on_release: app.theme_cls.accent_palette = root.color_name
<PrimaryColorSelector@ColorSelector>:
on_release: app.theme_cls.primary_palette = root.color_name
<MDThemePicker>:
size_hint: (None, None)
size: dp(284), dp(120)+dp(290)
pos_hint: {'center_x': .5, 'center_y': .5}
canvas:
Color:
rgb: app.theme_cls.primary_color
Rectangle:
size: self.width, dp(120)
pos: root.pos[0], root.pos[1] + root.height-dp(120)
Color:
rgb: app.theme_cls.bg_normal
Rectangle:
size: self.width, dp(290)
pos: root.pos[0], root.pos[1] + root.height-(dp(120)+dp(290))
MDFlatButton:
pos: root.pos[0]+root.size[0]-self.width-dp(10), 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}
theme_text_color: 'Custom'
text_color: root.specific_text_color
MDTabbedPanel:
size_hint: (None, None)
size: root.width, 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:
PrimaryColorSelector:
color_name: 'Red'
BoxLayout:
PrimaryColorSelector:
color_name: 'Pink'
BoxLayout:
PrimaryColorSelector:
color_name: 'Purple'
BoxLayout:
PrimaryColorSelector:
color_name: '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:
PrimaryColorSelector:
color_name: 'Indigo'
BoxLayout:
PrimaryColorSelector:
color_name: 'Blue'
BoxLayout:
PrimaryColorSelector:
color_name: 'LightBlue'
BoxLayout:
PrimaryColorSelector:
color_name: '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:
PrimaryColorSelector:
color_name: 'Teal'
BoxLayout:
PrimaryColorSelector:
color_name: 'Green'
BoxLayout:
PrimaryColorSelector:
color_name: 'LightGreen'
BoxLayout:
PrimaryColorSelector:
color_name: '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:
PrimaryColorSelector:
color_name: 'Yellow'
BoxLayout:
PrimaryColorSelector:
color_name: 'Amber'
BoxLayout:
PrimaryColorSelector:
color_name: 'Orange'
BoxLayout:
PrimaryColorSelector:
color_name: '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:
PrimaryColorSelector:
color_name: 'Brown'
BoxLayout:
PrimaryColorSelector:
color_name: 'Grey'
BoxLayout:
PrimaryColorSelector:
color_name: '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: 'accent_color'
text: "Accent 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:
AccentColorSelector:
color_name: 'Red'
BoxLayout:
AccentColorSelector:
color_name: 'Pink'
BoxLayout:
AccentColorSelector:
color_name: 'Purple'
BoxLayout:
AccentColorSelector:
color_name: '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:
AccentColorSelector:
color_name: 'Indigo'
BoxLayout:
AccentColorSelector:
color_name: 'Blue'
BoxLayout:
AccentColorSelector:
color_name: 'LightBlue'
BoxLayout:
AccentColorSelector:
color_name: '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:
AccentColorSelector:
color_name: 'Teal'
BoxLayout:
AccentColorSelector:
color_name: 'Green'
BoxLayout:
AccentColorSelector:
color_name: 'LightGreen'
BoxLayout:
AccentColorSelector:
color_name: '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:
AccentColorSelector:
color_name: 'Yellow'
BoxLayout:
AccentColorSelector:
color_name: 'Amber'
BoxLayout:
AccentColorSelector:
color_name: 'Orange'
BoxLayout:
AccentColorSelector:
color_name: '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:
AccentColorSelector:
color_name: 'Brown'
BoxLayout:
AccentColorSelector:
color_name: 'Grey'
BoxLayout:
AccentColorSelector:
color_name: '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"
FloatLayout:
size: self.size
pos: self.pos
BoxLayout:
size_hint: (None, None)
pos_hint: {'center_x': .5, 'center_y': .6}
halign: 'center'
valign: 'center'
spacing: dp(10)
width: dp(210)
height: dp(100)
MDIconButton:
size: dp(100), dp(100)
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, dp(50))
on_release: app.theme_cls.theme_style = 'Light'
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 ColorSelector(MDIconButton):
color_name = OptionProperty(
'Indigo',
options=['Red', 'Pink', 'Purple', 'DeepPurple', 'Indigo', 'Blue',
'LightBlue', 'Cyan', 'Teal', 'Green', 'LightGreen',
'Lime', 'Yellow', 'Amber', 'Orange', 'DeepOrange',
'Brown', 'Grey', 'BlueGrey'])
def rgb_hex(self, col):
return get_color_from_hex(colors[col][self.theme_cls.accent_hue])
class MDThemePicker(ThemableBehavior, FloatLayout, ModalView,
SpecificBackgroundColorBehavior,
RectangularElevationBehavior):
pass
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()

351
kivyblocks/theming.py Normal file
View File

@ -0,0 +1,351 @@
# -*- 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 kivyblocks.color_definitions import colors, text_colors
from kivyblocks.material_resources import FONTS, DEVICE_TYPE
from kivyblocks 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] = .38
elif theme_style == 'Dark':
color = get_color_from_hex('FFFFFF')
color[3] = .50
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)

View File

@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# Two implementations. The first is based on color brightness obtained from:-
# https://www.w3.org/TR/AERT#color-contrast
# The second is based on relative luminance calculation for sRGB obtained from:-
# https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
# and contrast ratio calculation obtained from:-
# https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
#
# Preliminary testing suggests color brightness more closely matches the
# Material Design spec suggested text colors, but the alternative implementation
# is both newer and the current 'correct' recommendation, so is included here
# as an option.
def _color_brightness(color):
# Implementation of color brightness method
brightness = color[0] * 299 + color[1] * 587 + color[2] * 114
brightness = brightness
return brightness
def _black_or_white_by_color_brightness(color):
if _color_brightness(color) >= 500:
return 'black'
else:
return 'white'
def _normalized_channel(color):
# Implementation of contrast ratio and relative luminance method
if color <= 0.03928:
return color / 12.92
else:
return ((color + 0.055) / 1.055) ** 2.4
def _luminance(color):
rg = _normalized_channel(color[0])
gg = _normalized_channel(color[1])
bg = _normalized_channel(color[2])
return 0.2126*rg + 0.7152*gg + 0.0722*bg
def _black_or_white_by_contrast_ratio(color):
l_color = _luminance(color)
l_black = 0.0
l_white = 1.0
b_contrast = (l_color + 0.05) / (l_black + 0.05)
w_contrast = (l_white + 0.05) / (l_color + 0.05)
return 'white' if w_contrast >= b_contrast else 'black'
def get_contrast_text_color(color, use_color_brightness=True):
if use_color_brightness:
contrast_color = _black_or_white_by_color_brightness(color)
else:
contrast_color = _black_or_white_by_contrast_ratio(color)
if contrast_color == 'white':
return 1, 1, 1, 1
else:
return 0, 0, 0, 1
if __name__ == '__main__':
from kivy.utils import get_color_from_hex
from kivymd.color_definitions import colors, text_colors
for c in colors.items():
if c[0] in ['Light', 'Dark']:
continue
print("For the {} color palette:".format(c[0]))
for name, hex_color in c[1].items():
if hex_color:
col = get_color_from_hex(hex_color)
col_bri = get_contrast_text_color(col)
con_rat = get_contrast_text_color(col, use_color_brightness=False)
text_color = text_colors[c[0]][name]
print(" The {} hue gives {} using color brightness, {} using contrast ratio, and {} from the MD spec"
.format(name, col_bri, con_rat, text_color))

245
kivyblocks/tree.py Normal file
View File

@ -0,0 +1,245 @@
from kivy.app import App
from kivy.graphics import Color, Triangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.button import ButtonBehavior
from kivyblocks.widgetExt import ScrollWidget
from kivyblocks.blocksapp import BlocksApp
from kivyblocks.utils import CSize
from baseWidget import PressableLabel
from appPublic.dictObject import DictObject
class NodeTrigger(ButtonBehavior, Widget):
def __init__(self, open_status=False,color=[1,0,0,1]):
siz = CSize(1)
super().__init__(size_hint=(None,None),size=[siz,siz]) #CSize(1))
self.open_status = open_status
self.line_color = color
self.register_event_type('on_change')
self.open_points = [0,self.height, self.width,self.height,self.width/2,0]
self.close_points = [0,self.height, 0,0, self.width,self.height/2]
self.showOpenStatus()
self.bind(size=self.on_size)
def on_change(self,status):
print('new_status=',status)
def on_size(self,o,v):
self.open_points = [0,self.height, self.width,self.height,self.width/2,0]
self.close_points = [0,self.height, 0,0, self.width,self.height/2]
def on_press(self):
self.open_status = False if self.open_status else True
self.showOpenStatus()
self.dispatch('on_change',self.open_status)
def showOpenStatus(self):
points = self.close_points
if self.open_status:
points = self.open_points
self.canvas.clear()
with self.canvas:
Color(*self.line_color)
Triangle(points=points)
class EmptyBox(Widget):
def __init__(self):
siz=CSize(1)
super().__init__(size_hint=[None,None],size=[siz,siz])
class TreeNode(BoxLayout):
def __init__(self,tree:Tree=None,
parentNode:TreeNode=None,
data:dict):
"""
base TreeNode
data:{
if children miss, it is a leaf node,if children is a empty array, is mean need load at it is first expanded.
}
"""
super().__init__(orientation='vertical',size_hint=(None,None))
self.treeObj = tree
self.data = data
self.parentNonde = parentNode
self.content = None
self.children_open = False
self.subNodes = []
self.node_box = BoxLayout(orientation='horizontal',size_hint_y=None)
self.node_box1 = BoxLayout(orientation='horizontal')
n = data.get('children')
self.hasChildren_nodes = True if not n is None else False
self.children_nodes = n
self.children_loaded = False
self.hasChildren = data.get('hasChildren')
if hasChildren_nodes:
self.trigger = NodeTrigger()
self.trigger.bind(on_change=self.toggleChildren)
self.buildChildrenContainer()
else:
self.trigger = EmptyBox()
self.add_widget(self.node_box)
self.node_box.add_widget(self.trigger)
self.buildContent()
def buildChildrenContainer(self):
self.node_box1.add_widget(EmptyBox())
self.children_box = BoxLayout(orientation='vertical')
self.node_box1.add_widget(self.children_box)
def setMinWidth(self,width):
if self.node_box.width < width:
self.node_box.width = width
if self.node_box1.width < width:
self.node_box1.width = width
for n in self.subNodes:
n.setMinWidth(width)
def buildContent(self):
pass
def addContent(self):
self.buildContent()
self.node_box.add_widget(self.content)
self.node_box.height = self.content.height + CSize(1)
self.node_box.width = self.trigger.width + self.content.width
def buildChildren(self):
if self.data.children is None:
return
if self.data.children == []:
self.treeObj.getUrlData(self.addChildren,self.data)
return
if len(self.subNodes) == 0:
self.addChildren(self.data.children)
def addChildren(self,children):
self.data.children = children
for c in children:
options['tree'] = self.treeObj
options['parentNode'] = self
options['data'] = c
tn = self.teeeObj.NodeKlass(**options)
self.subNodes.append(tn)
self.children_box.add_widget(tn)
def toggleChildren(self,o,v):
if v:
self.buildChildren()
self.add_widget(self.node_box1)
self.children_open = True
self.height = self.node_box.height + self.node_box1.height
else:
self.remove_widget(self.node_box1)
self.height = self.node_box.height
self.children_open = False
def on_size(self,o,v):
print('************treenode on_size', o, v)
self.node_box1.height = self.children_box.height
"""
tree options
{
"url":
"params",
"bg_color",
"color",
"checkbox",
"multplecheck",
"idField",
"textFiled",
"data" # array of {children:{},...}
}
"""
class Tree(ScrollWidget):
def __init__(self,**options):
super().__init__() #orientation="vertical")
self.options = DictObject(**options)
self.nodes = []
self.initflag = False
self.bind(size=self.onSize,pos=self.onSize)
def onSize(self,o,v=None):
if not self.initflag:
self.buildTree(self)
for n in self.nodes:
n.setMinWidth(self.width)
def setNodeKlass(self,klass):
self.NodeKlass = klass
def getUrlData(self,callback,kv=None):
hc = App.get_running_app().hc
params = self.options.get(params,{}).copy()
if not data is None:
for k,v in kv:
if k in params.keys():
params[k] = v
params['id'] = kv[self.options.idField]
hc.get(self.url,params=params,callback=callback)
def buildTree(self,kv=None):
if not hasattr(self,'NodeKlass'):
self.NodeKlass = TreeNode
if self.options.url:
return self.getUrlData(self.dataLoaded,kv)
data = self.options.data
self.dataLoaded(data)
def dataLoaded(self,d):
self.data = d
self.addNodes()
def addNodes(self):
for c in self.data:
options = {}
options['tree'] = self
options['parentNode'] = None
options['data'] = DictObject(c)
self.nodes.append(self.NodeKlass(**options))
for w in self.nodes:
self.add_widget(w)
class TextTreeNode(TreeNode):
def buildContent(self):
txt = self.data.get(self.treeObj.options.textField,
self.data.get(self.treeObj.options.idField,'defaulttext'))
self.content = PressableLabel(text=txt,
size_hint=(None,None),
font_size=CSize(1),
text_size=CSize(len(txt),1),
halign='left',
height=CSize(2),width=CSize(len(txt)))
self.content.bind(on_press=self.onPress)
return
def onPress(self,o,v=None):
self.treeObj.select_row(self)
def selected(self):
pass
def unselected(self):
pass
class TextTree(Tree):
def __init__(self,**options):
self.NodeKlass = TextTReeNode
super().__init__(**options)
self.register_event_type('on_press')
def onPress(self,o,v=None):
if self.selectNode:
self.selectNode.unselected()
self.selectNode = o
o.selected()
self.dispatch(on_press,o,o.data)
def on_press(self,o,v):
print('TextTree():on_press(),o=',o,'v=',v)