electrum-bitcoinprivate/gui/kivy/uix/drawer.py

257 lines
8.3 KiB
Python

'''Drawer Widget to hold the main window and the menu/hidden section that
can be swiped in from the left. This Menu would be only hidden in phone mode
and visible in Tablet Mode.
This class is specifically in lined to save on start up speed(minimize i/o).
'''
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import OptionProperty, NumericProperty, ObjectProperty
from kivy.clock import Clock
from kivy.lang import Builder
import gc
# delayed imports
app = None
class Drawer(Factory.RelativeLayout):
'''Drawer Widget to hold the main window and the menu/hidden section that
can be swiped in from the left. This Menu would be only hidden in phone mode
and visible in Tablet Mode.
'''
state = OptionProperty('closed',
options=('closed', 'open', 'opening', 'closing'))
'''This indicates the current state the drawer is in.
:attr:`state` is a `OptionProperty` defaults to `closed`. Can be one of
`closed`, `open`, `opening`, `closing`.
'''
scroll_timeout = NumericProperty(200)
'''Timeout allowed to trigger the :data:`scroll_distance`,
in milliseconds. If the user has not moved :data:`scroll_distance`
within the timeout, the scrolling will be disabled and the touch event
will go to the children.
:data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`
and defaults to 200 (milliseconds)
'''
scroll_distance = NumericProperty('9dp')
'''Distance to move before scrolling the :class:`Drawer` in pixels.
As soon as the distance has been traveled, the :class:`Drawer` will
start to scroll, and no touch event will go to children.
It is advisable that you base this value on the dpi of your target
device's screen.
:data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`
and defaults to 20dp.
'''
drag_area = NumericProperty('9dp')
'''The percentage of area on the left edge that triggers the opening of
the drawer. from 0-1
:attr:`drag_area` is a `NumericProperty` defaults to 2
'''
hidden_widget = ObjectProperty(None)
''' This is the widget that is hidden in phone mode on the left side of
drawer or displayed on the left of the overlay widget in tablet mode.
:attr:`hidden_widget` is a `ObjectProperty` defaults to None.
'''
overlay_widget = ObjectProperty(None)
'''This a pointer to the default widget that is overlayed either on top or
to the right of the hidden widget.
'''
def __init__(self, **kwargs):
super(Drawer, self).__init__(**kwargs)
self._triigger_gc = Clock.create_trigger(self._re_enable_gc, .2)
def toggle_drawer(self):
if app.ui_mode[0] == 't':
return
Factory.Animation.cancel_all(self.overlay_widget)
anim = Factory.Animation(x=self.hidden_widget.width
if self.state in ('opening', 'closed') else 0,
d=.1, t='linear')
anim.bind(on_complete = self._complete_drawer_animation)
anim.start(self.overlay_widget)
def _re_enable_gc(self, dt):
global gc
gc.enable()
def on_touch_down(self, touch):
if self.disabled:
return
if not self.collide_point(*touch.pos):
return
touch.grab(self)
# disable gc for smooth interaction
# This is still not enough while wallet is synchronising
# look into pausing all background tasks while ui interaction like this
gc.disable()
global app
if not app:
app = App.get_running_app()
# skip on tablet mode
if app.ui_mode[0] == 't':
return super(Drawer, self).on_touch_down(touch)
state = self.state
touch.ud['send_touch_down'] = False
start = 0 #if state[0] == 'c' else self.hidden_widget.right
drag_area = self.drag_area\
if self.state[0] == 'c' else\
(self.overlay_widget.x)
if touch.x < start or touch.x > drag_area:
if self.state == 'open':
self.toggle_drawer()
return
return super(Drawer, self).on_touch_down(touch)
self._touch = touch
Clock.schedule_once(self._change_touch_mode,
self.scroll_timeout/1000.)
touch.ud['in_drag_area'] = True
touch.ud['send_touch_down'] = True
return
def on_touch_move(self, touch):
if not touch.grab_current is self:
return
self._touch = False
# skip on tablet mode
if app.ui_mode[0] == 't':
return super(Drawer, self).on_touch_move(touch)
if not touch.ud.get('in_drag_area', None):
return super(Drawer, self).on_touch_move(touch)
ov = self.overlay_widget
ov.x=min(self.hidden_widget.width,
max(ov.x + touch.dx*2, 0))
#_anim = Animation(x=x, duration=1/2, t='in_out_quart')
#_anim.cancel_all(ov)
#_anim.start(ov)
if abs(touch.x - touch.ox) < self.scroll_distance:
return
touch.ud['send_touch_down'] = False
Clock.unschedule(self._change_touch_mode)
self._touch = None
self.state = 'opening' if touch.dx > 0 else 'closing'
touch.ox = touch.x
return
def _change_touch_mode(self, *args):
if not self._touch:
return
touch = self._touch
touch.ungrab(self)
touch.ud['in_drag_area'] = False
touch.ud['send_touch_down'] = False
self._touch = None
super(Drawer, self).on_touch_down(touch)
return
def on_touch_up(self, touch):
if not touch.grab_current is self:
return
self._triigger_gc()
touch.ungrab(self)
touch.grab_current = None
# skip on tablet mode
get = touch.ud.get
if app.ui_mode[0] == 't':
return super(Drawer, self).on_touch_up(touch)
self.old_x = [1, ] * 10
self.speed = sum((
(self.old_x[x + 1] - self.old_x[x]) for x in range(9))) / 9.
if get('send_touch_down', None):
# touch up called before moving
Clock.unschedule(self._change_touch_mode)
self._touch = None
Clock.schedule_once(
lambda dt: super(Drawer, self).on_touch_down(touch))
if get('in_drag_area', None):
if abs(touch.x - touch.ox) < self.scroll_distance:
anim_to = (0 if self.state[0] == 'c'
else self.hidden_widget.width)
Factory.Animation(x=anim_to, d=.1).start(self.overlay_widget)
return
touch.ud['in_drag_area'] = False
if not get('send_touch_down', None):
self.toggle_drawer()
Clock.schedule_once(lambda dt: super(Drawer, self).on_touch_up(touch))
def _complete_drawer_animation(self, *args):
self.state = 'open' if self.state in ('opening', 'closed') else 'closed'
def add_widget(self, widget, index=1):
if not widget:
return
iget = self.ids.get
if not iget('hidden_widget') or not iget('overlay_widget'):
super(Drawer, self).add_widget(widget)
return
if not self.hidden_widget:
self.hidden_widget = self.ids.hidden_widget
if not self.overlay_widget:
self.overlay_widget = self.ids.overlay_widget
if self.overlay_widget.children and self.hidden_widget.children:
Logger.debug('Drawer: Accepts only two widgets. discarding rest')
return
if not self.hidden_widget.children:
self.hidden_widget.add_widget(widget)
else:
self.overlay_widget.add_widget(widget)
widget.x = 0
def remove_widget(self, widget):
if self.overlay_widget.children[0] == widget:
self.overlay_widget.clear_widgets()
return
if widget == self.hidden_widget.children:
self.hidden_widget.clear_widgets()
return
def clear_widgets(self):
self.overlay_widget.clear_widgets()
self.hidden_widget.clear_widgets()
if __name__ == '__main__':
from kivy.app import runTouchApp
from kivy.lang import Builder
runTouchApp(Builder.load_string('''
Drawer:
Button:
Button
'''))