Created
January 22, 2023 11:17
-
-
Save akshayaurora/3d1a48a3c7ae972cce7931e58077fcb1 to your computer and use it in GitHub Desktop.
Date picker: Sample: Not directly usable. You need some additions with this like images for focus, glow, a theme folder in, a app.theme property etc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Custom Behaviors are defined in this module. | |
''' | |
from kivy.app import App | |
from kivy.animation import Animation | |
from kivy.lang import Builder | |
from kivy.clock import Clock | |
from kivy.config import Config | |
from kivy.core.window import Window | |
is_desktop = Config.get('kivy', 'desktop') == '1' | |
from kivy.event import EventDispatcher | |
from kivy.factory import Factory | |
from kivy.graphics import Rectangle, BorderImage, Color | |
from kivy.metrics import dp | |
from kivy.properties import (NumericProperty, StringProperty, | |
ListProperty, BooleanProperty) | |
from kivy.graphics import (Rectangle, Color, Ellipse, StencilPop, | |
StencilPush, StencilUnUse, StencilUse) | |
from kivy.uix.behaviors import CoverBehavior | |
from kivy.uix.image import Image | |
from kivy.uix.behaviors import FocusBehavior | |
class CoverImage(CoverBehavior, Image): | |
def __init__(self, **kwargs): | |
super(CoverImage, self).__init__(**kwargs) | |
def on_texture(self, *args, **kwargs): | |
texture = self._coreimage.texture | |
self.reference_size = texture.size | |
self.texture = texture | |
class TouchRippleBehavior(EventDispatcher): | |
__events__ = ('on_released',) | |
ripple_rad = NumericProperty(10) | |
ripple_pos = ListProperty([0, 0]) | |
#141 ,188, 234 | |
ripple_color = ListProperty((141./256., 188./256., 234./256., 1)) | |
ripple_duration_in = NumericProperty(.3) | |
ripple_duration_out = NumericProperty(.3) | |
fade_to_alpha = NumericProperty(.3) | |
ripple_scale = NumericProperty(2.0) | |
ripple_func_in = StringProperty('out_quad') | |
ripple_func_out = StringProperty('in_quad') | |
def on_touch_down(self, touch): | |
if self.collide_point(touch.x, touch.y): | |
# self.anim_complete(self, self) | |
self.ripple_pos = ripple_pos = (touch.x, touch.y) | |
Animation.cancel_all(self, 'ripple_rad', 'ripple_color') | |
rc = self.ripple_color | |
ripple_rad = self.ripple_rad | |
self.ripple_color = [rc[0], rc[1], rc[2], 1.] | |
anim = Animation( | |
ripple_rad=max(self.width, self.height) * self.ripple_scale, | |
t=self.ripple_func_in, | |
ripple_color=[rc[0], rc[1], rc[2], self.fade_to_alpha], | |
duration=self.ripple_duration_in) | |
anim.bind(on_complete=self.anim_complete) | |
anim.start(self) | |
with self.canvas: | |
StencilPush() | |
Rectangle(size=self.size, pos=self.pos) | |
StencilUse() | |
self.col_instruction = Color( | |
rgba=self.ripple_color, group='one') | |
self.ellipse = Ellipse( | |
size=(ripple_rad, ripple_rad), | |
pos=(ripple_pos[0] - ripple_rad/2., | |
ripple_pos[1] - ripple_rad/2.), | |
group='one') | |
StencilUnUse() | |
Rectangle(size=self.size, pos=self.pos) | |
StencilPop() | |
self.bind( | |
ripple_color=self.set_color, ripple_pos=self.set_ellipse, | |
ripple_rad=self.set_ellipse) | |
return super(TouchRippleBehavior, self).on_touch_down(touch) | |
def set_ellipse(self, instance, value): | |
ellipse = self.ellipse | |
ripple_pos = self.ripple_pos | |
ripple_rad = self.ripple_rad | |
ellipse.size = (ripple_rad, ripple_rad) | |
ellipse.pos = ( | |
ripple_pos[0] - ripple_rad/2., | |
ripple_pos[1] - ripple_rad/2.) | |
def set_color(self, instance, value): | |
self.col_instruction.rgba = value | |
def on_release(self): | |
rc = 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_completed) | |
anim.start(self) | |
def anim_complete(self, anim, instance): | |
self.ripple_rad = 10 | |
self.canvas.remove_group('one') | |
def on_released(self): | |
pass | |
def anim_completed(self, anim, instance): | |
self.anim_complete(anim, instance) | |
self.dispatch('on_released') | |
class HoverBehavior(object): | |
hover = BooleanProperty(False) | |
def __init__(self, *args, **kwargs): | |
super(HoverBehavior, self).__init__(*args, **kwargs) | |
Window.bind(mouse_pos=self.on_mouse_pos) | |
def on_mouse_pos(self, discard, pos): | |
if not self.get_root_window(): | |
return | |
self.hover = self.collide_point(*self.to_widget(*pos)) | |
def on_touch_up(self, touch): | |
super(HoverBehavior, self).on_touch_up(touch) | |
class HoverLookBehavior(HoverBehavior): | |
_animating = False | |
Builder.load_string(''' | |
#:import Clock kivy.clock.Clock | |
<HoverLookBehavior> | |
hovering_opacity: 0 | |
dx: -1000 | |
on_hover: | |
if args[1]:\ | |
self.hovering_opacity = .5;\ | |
dx=0;\ | |
Clock.schedule_once(self._anim_flash, .5) | |
if not args[1]:\ | |
self.hovering_opacity = 0;\ | |
self.dx = -self.height;\ | |
self._animating = False;\ | |
Animation.cancel_all(self);\ | |
Clock.unschedule(self._anim_flash) | |
canvas.after: | |
Color: | |
rgba: 1, 1, 1, self.hovering_opacity | |
# Rectangle: | |
# source: 'data/theme/' + app.theme + '/images/glow.png' | |
# size: self.width - dp(18), self.height | |
# pos: self.x + dp(9), self.y | |
Rectangle | |
source: 'data/theme/' + app.theme + '/images/flash_white.png' | |
size: root.height, root.height | |
pos: root.dx, root.y | |
''') | |
def _anim_flash(self, dt): | |
if self._animating: | |
return | |
self._animating = True | |
Animation.cancel_all(self) | |
anim = Animation(dx=self.width+self.height, d=.45) | |
def _on_complete(self, widget): | |
self._animating = False | |
anim.bind(on_complete=_on_complete) | |
anim.start(self) | |
class FocusTrigger(FocusBehavior): | |
'''This defines a class that triggers the focusable item when a space/enter | |
key is pressed. This class can only be used with classes like | |
Button/ButtonBehavior that have a `trigger_action`. | |
''' | |
touched = BooleanProperty(False) | |
def on_touch_down(self, touch): | |
if self.collide_point(*touch.pos): | |
self.touched = True | |
super(FocusTrigger, self).on_touch_down(touch) | |
def on_touch_up(self, touch): | |
super(FocusTrigger, self).on_touch_up(touch) | |
Clock.schedule_once(self._untouch, .25) | |
def keyboard_on_key_down(self, window, keycode, text, modifiers): | |
'''Trigger action when `space` or `enter` is pressed. | |
''' | |
if keycode[1] in ('enter', 'spacebar') and not modifiers: | |
self.touched = True | |
self.trigger_action() | |
Clock.schedule_once(self._untouch, .25) | |
return True | |
return super(FocusTrigger, self).keyboard_on_key_down( | |
window, keycode, text, modifiers) | |
def _untouch(self, dt): | |
self.touched = False | |
class FocusLook(object): | |
Builder.load_string(''' | |
<FocusLook> | |
focus_look_width: 0 | |
focus_opacity: 0 | |
on_focused: | |
from kivy.animation import Animation | |
anim = Animation(\ | |
focus_look_width=self.width if args[1] else 0,\ | |
focus_opacity=.4 if args[1] else 0, d=.25) | |
anim.start(self) | |
canvas.after: | |
Color: | |
rgba: 1, 1, 1, 1 if self.focus_look_width > 10 else 0 | |
BorderImage | |
border: 0, 0, 36, 0 | |
source: 'data/theme/' + app.theme + '/images/focused_overlay.png' | |
pos: self.pos | |
size: root.focus_look_width, dp(1) | |
Color: | |
rgba: 1, 1, 1, root.focus_opacity | |
Rectangle | |
source: 'data/theme/' + app.theme + '/images/glow.png' | |
size: self.size | |
pos: self.pos | |
''') | |
class FocusLookBehavior(FocusLook, FocusTrigger): | |
pass | |
class FocusArrowKeys(FocusBehavior): | |
'''This defines a class that allows the focusable item to react when a arrow up/down | |
key is pressed. This class can be used with widgets that have a | |
`value` property. | |
''' | |
touched = BooleanProperty(False) | |
def on_touch_down(self, touch): | |
if self.collide_point(*touch.pos): | |
self.touched = True | |
super(FocusArrowKeys, self).on_touch_down(touch) | |
def on_touch_up(self, touch): | |
super(FocusArrowKeys, self).on_touch_up(touch) | |
Clock.schedule_once(self._untouch, .25) | |
def keyboard_on_key_down(self, window, keycode, text, modifiers): | |
'''Trigger action when `space` or `enter` is pressed. | |
''' | |
# print(keycode, text, modifiers) | |
if keycode[1] in ('up', 'down', 'left', 'right'): | |
value = (self.step or 1) * (-1 if keycode[1] in ('down', 'left') else 1) | |
Clock.unschedule(self._untouch) | |
self.touched = True | |
self.value = max(self.min, min(self.max, (self.value+value))) | |
Clock.schedule_once(self._untouch, .25) | |
return True | |
return super(FocusArrowKeys, self).keyboard_on_key_down( | |
window, keycode, text, modifiers) | |
def _untouch(self, dt): | |
self.touched = False | |
class FocusArrowBehavior(FocusLook, FocusArrowKeys): | |
pass | |
class FocusSwitch(FocusBehavior, Factory.Switch): | |
touched = BooleanProperty(False) | |
def on_focused(self, instance, value): | |
anim = Animation(\ | |
focus_look_width=self.width if value else 0,\ | |
focus_opacity=.4 if value else 0, d=.25) | |
anim.start(self) | |
def on_touch_down(self, touch): | |
if self.collide_point(*touch.pos): | |
self.touched = True | |
return super().on_touch_down(touch) | |
def on_touch_up(self, touch): | |
if self.collide_point(*touch.pos): | |
Clock.schedule_once(self._untouch) | |
return super().on_touch_up(touch) | |
def keyboard_on_key_down(self, window, keycode, text, modifiers): | |
'''Trigger action when `space` or `enter` is pressed. | |
''' | |
# print(keycode, text, modifiers) | |
if keycode[1] in ('enter', 'spacebar'): | |
self.touched = True | |
self.active = not self.active | |
return True | |
return super(FocusSwitch, self).keyboard_on_key_down( | |
window, keycode, text, modifiers) | |
def _untouch(self, dt): | |
self.touched = False | |
def keyboard_on_key_up(self, *args): | |
Clock.schedule_once(self._untouch) | |
return super(FocusSwitch, self).keyboard_on_key_up( | |
*args) | |
class FocusSpinner(FocusLook, FocusBehavior, Factory.Spinner): | |
touched = BooleanProperty(False) | |
def on_text(self, instance, value): | |
if not self.disabled or not self.parent: | |
self.focus = is_desktop | |
def keyboard_on_key_down(self, window, keycode, text, modifiers): | |
'''Trigger action when `space` or `enter` is pressed. | |
''' | |
# print(keycode, text, modifiers) | |
if keycode[1] in ('enter', 'spacebar'): | |
self.trigger_action() | |
self._dropdown.children[0].children[-1].focus = is_desktop | |
return super(FocusSpinner, self).keyboard_on_key_down( | |
window, keycode, text, modifiers) | |
class FocusSpinnerOption(FocusLook, FocusBehavior): | |
def on_focused(self, instance, focus): | |
scroll = self.parent.parent | |
scroll.scroll_to(instance, padding=instance.height) | |
def on_touch_down(self, touch): | |
if self.collide_point(*touch.pos): | |
if self.parent and self.parent.parent and self.parent.parent.attach_to: | |
at = self.parent.parent.attach_to | |
if at.text == self.text: | |
at.text = '' | |
at.touched = True | |
Clock.unschedule(self._untouch) | |
super(FocusSpinnerOption, self).on_touch_down(touch) | |
def keyboard_on_key_down(self, window, keycode, text, modifiers): | |
'''Trigger action when `space` or `enter` is pressed. | |
''' | |
# print(keycode, text, modifiers) | |
if keycode[1] in ('enter', 'spacebar'): | |
if self.parent and self.parent.parent and self.parent.parent.attach_to: | |
self.parent.parent.attach_to.touched = True | |
Clock.unschedule(self._untouch) | |
self.trigger_action() | |
return True | |
if keycode[1] in ('up', 'down'): | |
children = self.parent.children | |
idx = children.index(self) | |
try: | |
lenc = len(children) | |
children[idx + (-1 if keycode[1] == 'down' else 1)].focus = is_desktop | |
except IndexError: | |
children[0].focus = is_desktop | |
return True | |
return super(FocusSpinnerOption, self).keyboard_on_key_down( | |
window, keycode, text, modifiers) | |
def _untouch(self, dt): | |
self.parent.parent.attach_to.touched = Fakse | |
class DownActiveBehavior(object): | |
def on_press(self): | |
path_list = self.source.split('/') | |
filename = path_list[-1] | |
path = '/'.join(path_list[:-1]) | |
file, ext = filename.split('.') | |
if '_active' not in file and '_down' not in file: | |
filename = file + '_down.' + ext | |
self.source = '/'.join((path, filename)) | |
Factory.register('DownActiveBehavior', DownActiveBehavior) | |
Factory.register('FocusLook', FocusLook) | |
Factory.register('FocusSpinnerOption', FocusSpinnerOption) | |
Factory.register('FocusArrowBehavior', FocusArrowBehavior) | |
Factory.register('FocusArrowKeys', FocusArrowKeys) | |
Factory.register('FocusLookBehavior', FocusLookBehavior) | |
Factory.register('HoverBehavior', HoverBehavior) | |
Factory.register('HoverLookBehavior', HoverLookBehavior) | |
Factory.register('TouchRippleBehavior', TouchRippleBehavior) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from kivy.clock import Clock | |
from kivy.uix.boxlayout import BoxLayout | |
from kivy.factory import Factory | |
from kivy.properties import ObjectProperty | |
from kivy.lang import Builder | |
class CalendarButton(Factory.ToggleButtonBehavior, Factory.FocusLookBehavior, Factory.Label): | |
datepicker = ObjectProperty(None) | |
Builder.load_string(''' | |
<CalendarButton> | |
size_hint: 1, 1 | |
disabled: True if self.text == '' else False | |
group: calendar | |
current_day_color: 1, 1, 1, 1 | |
canvas.before: | |
Color: | |
rgba: .5, .9, .5, .5 if self.state == 'down' else 0 | |
Rectangle: | |
size: self.size | |
pos: self.pos | |
canvas.after: | |
Color: | |
rgba: root.current_day_color or (1, 1, 1, 1) | |
Rectangle: | |
size: self.size | |
pos: self.pos | |
on_release: root.datepicker.day = self.text | |
on_touched: self.parent.parent.parent.touched = args[1] | |
on_parent: | |
root.current_day_color = 1, 1, 1, .2 if (args[1] and str(datetime.datetime.now().day) == self.text and args[1].month == datetime.datetime.now().month and str(args[1].year) == str(datetime.datetime.now().year)) else 0 | |
''') | |
Month = Builder.load_string(''' | |
<Month@Screen+Label> | |
text: self.name | |
''') | |
class DatePicker(Factory.FocusLookBehavior, BoxLayout): | |
Builder.load_string(''' | |
<DateNTimeSpinnerOption@FocusableSpinnerOption> | |
<DatePicker> | |
orientation: 'vertical' | |
month: spinner_month.text | |
day: '12' | |
year: 2022 | |
date: '{}-{}-{}'.format(self.day, self.month, self.year) | |
isodate: '{}-{:02d}-{:02d}'.format(int(self.year), (spinner_month.values.index(self.month)+1) if self.month else 0, int(self.day)) | |
BoxLayout | |
size_hint_y: None | |
padding: dp(9), 0 | |
spacing: dp(9) | |
height: dp(45 if not app.horiz_mode else 36) | |
FocusSpinner | |
id: spinner_month | |
option_cls: Factory.DateNTimeSpinnerOption | |
values: ('JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC') | |
text: self.values[datetime.datetime.now().month -1] | |
on_touched: root.touched = args[1] | |
on_text: | |
if args[1]:\ | |
grid.month = self.values.index(args[1])+1;\ | |
parent = grid.parent;\ | |
grid.parent = None;\ | |
grid.parent = parent | |
FocusSpinner | |
id: spinner_year | |
option_cls: Factory.DateNTimeSpinnerOption | |
text: str(datetime.datetime.now().year) | |
values: map(str, range(2006,datetime.datetime.now().year+1)) | |
# on_focus: scroll.scroll_to(self) | |
on_touched: root.touched = args[1] | |
on_text: | |
if args[1]:\ | |
root.year = self.text;\ | |
grid.year = self.text;\ | |
parent = grid.parent;\ | |
grid.parent = None;\ | |
grid.parent = parent | |
BoxLayout | |
orientation: 'vertical' | |
BoxLayout | |
canvas.before: | |
Color: | |
rgba: 1, 1, 1, 1 | |
Rectangle: | |
source: 'data/theme/' + app.theme + '/images/glow.png' | |
size: self.width, dp(4) | |
pos: self.pos | |
size_hint_y: None | |
height: dp(32) | |
Label | |
text: 'M' | |
Label | |
text: 'T' | |
Label | |
text: 'W' | |
Label | |
text: 'T' | |
Label | |
text: 'F' | |
Label | |
text: 'S' | |
Label | |
text: 'S' | |
GridLayout | |
id: grid | |
cols: 7 | |
rows: 6 | |
spacing: dp(1) | |
year: datetime.datetime.now().year | |
month: datetime.datetime.now().month | |
on_parent: | |
month = spinner_month.values.index(root.month) | |
# print(month +1, root.year) | |
if args[1] and root.year:\ | |
cal = calendar.Calendar();\ | |
self.clear_widgets();\ | |
[self.add_widget(Factory.CalendarButton(text=str(x), datepicker=root) if x!=0 else Factory.Widget())\ | |
for x in cal.itermonthdays(int(root.year), (month+1)) ] | |
''') | |
def on_touch_up(self, touch): | |
super().on_touch_up(touch) | |
self.touched = True | |
Clock.unschedule(self._untouch) | |
def set_date(self, response): | |
import datetime | |
date_time = datetime.datetime.fromisoformat(str(response)) | |
sm = self.ids.spinner_month | |
sm.text = sm.values[int(date_time.month) - 1] | |
self.ids.spinner_year.text = str(date_time.year) | |
day = str(date_time.day) | |
try: | |
for child in self.ids.grid.children: | |
if child.state == 'down': | |
child.state = 'normal' | |
if child.text == day: | |
child.state = 'down' | |
except AttributeError: | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment