You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
8.3 KiB
257 lines
8.3 KiB
'''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 |
|
''')) |