URI: 
       tdrawer.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tdrawer.py (8493B)
       ---
            1 '''Drawer Widget to hold the main window and the menu/hidden section that
            2 can be swiped in from the left. This Menu would be only hidden in phone mode
            3 and visible in Tablet Mode.
            4 
            5 This class is specifically in lined to save on start up speed(minimize i/o).
            6 '''
            7 
            8 from kivy.app import App
            9 from kivy.factory import Factory
           10 from kivy.properties import OptionProperty, NumericProperty, ObjectProperty
           11 from kivy.clock import Clock
           12 from kivy.lang import Builder
           13 from kivy.logger import Logger
           14 
           15 import gc
           16 
           17 # delayed imports
           18 app = None
           19 
           20 
           21 class Drawer(Factory.RelativeLayout):
           22     '''Drawer Widget to hold the main window and the menu/hidden section that
           23     can be swiped in from the left. This Menu would be only hidden in phone mode
           24     and visible in Tablet Mode.
           25 
           26     '''
           27 
           28     state = OptionProperty('closed',
           29                             options=('closed', 'open', 'opening', 'closing'))
           30     '''This indicates the current state the drawer is in.
           31 
           32     :attr:`state` is a `OptionProperty` defaults to `closed`. Can be one of
           33     `closed`, `open`, `opening`, `closing`.
           34     '''
           35 
           36     scroll_timeout = NumericProperty(200)
           37     '''Timeout allowed to trigger the :data:`scroll_distance`,
           38     in milliseconds. If the user has not moved :data:`scroll_distance`
           39     within the timeout, the scrolling will be disabled and the touch event
           40     will go to the children.
           41 
           42     :data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`
           43     and defaults to 200 (milliseconds)
           44     '''
           45 
           46     scroll_distance = NumericProperty('9dp')
           47     '''Distance to move before scrolling the :class:`Drawer` in pixels.
           48     As soon as the distance has been traveled, the :class:`Drawer` will
           49     start to scroll, and no touch event will go to children.
           50     It is advisable that you base this value on the dpi of your target
           51     device's screen.
           52 
           53     :data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`
           54     and defaults to 20dp.
           55     '''
           56 
           57     drag_area = NumericProperty('9dp')
           58     '''The percentage of area on the left edge that triggers the opening of
           59     the drawer. from 0-1
           60 
           61     :attr:`drag_area` is a `NumericProperty` defaults to 2
           62     '''
           63 
           64     hidden_widget = ObjectProperty(None)
           65     ''' This is the widget that is hidden in phone mode on the left side of
           66     drawer or displayed on the left of the overlay widget in tablet mode.
           67 
           68     :attr:`hidden_widget` is a `ObjectProperty` defaults to None.
           69     '''
           70 
           71     overlay_widget = ObjectProperty(None)
           72     '''This a pointer to the default widget that is overlayed either on top or
           73     to the right of the hidden widget.
           74     '''
           75 
           76     def __init__(self, **kwargs):
           77         super(Drawer, self).__init__(**kwargs)
           78 
           79         self._triigger_gc = Clock.create_trigger(self._re_enable_gc, .2)
           80 
           81     def toggle_drawer(self):
           82         if app.ui_mode[0] == 't':
           83             return
           84         Factory.Animation.cancel_all(self.overlay_widget)
           85         anim = Factory.Animation(x=self.hidden_widget.width
           86                             if self.state in ('opening', 'closed') else 0,
           87                             d=.1, t='linear')
           88         anim.bind(on_complete = self._complete_drawer_animation)
           89         anim.start(self.overlay_widget)
           90 
           91     def _re_enable_gc(self, dt):
           92         global gc
           93         gc.enable()
           94 
           95     def on_touch_down(self, touch):
           96         if self.disabled:
           97             return
           98 
           99         if not self.collide_point(*touch.pos):
          100             return
          101 
          102         touch.grab(self)
          103 
          104         # disable gc for smooth interaction
          105         # This is still not enough while wallet is synchronising
          106         # look into pausing all background tasks while ui interaction like this
          107         gc.disable()
          108 
          109         global app
          110         if not app:
          111             app = App.get_running_app()
          112 
          113         # skip on tablet mode
          114         if app.ui_mode[0] == 't':
          115             return super(Drawer, self).on_touch_down(touch)
          116 
          117         state = self.state
          118         touch.ud['send_touch_down'] = False
          119         start = 0 #if state[0] == 'c' else self.hidden_widget.right
          120         drag_area = self.drag_area\
          121            if self.state[0] == 'c' else\
          122            (self.overlay_widget.x)
          123 
          124         if touch.x < start or touch.x > drag_area:
          125             if self.state == 'open':
          126                 self.toggle_drawer()
          127                 return
          128             return super(Drawer, self).on_touch_down(touch)
          129 
          130         self._touch = touch
          131         Clock.schedule_once(self._change_touch_mode,
          132                             self.scroll_timeout/1000.)
          133         touch.ud['in_drag_area'] = True
          134         touch.ud['send_touch_down'] = True
          135         return
          136 
          137     def on_touch_move(self, touch):
          138         if not touch.grab_current is self:
          139             return
          140         self._touch = False
          141         # skip on tablet mode
          142         if app.ui_mode[0] == 't':
          143             return super(Drawer, self).on_touch_move(touch)
          144 
          145         if not touch.ud.get('in_drag_area', None):
          146             return super(Drawer, self).on_touch_move(touch)
          147 
          148         ov = self.overlay_widget
          149         ov.x=min(self.hidden_widget.width,
          150             max(ov.x + touch.dx*2, 0))
          151 
          152         #_anim = Animation(x=x, duration=1/2, t='in_out_quart')
          153         #_anim.cancel_all(ov)
          154         #_anim.start(ov)
          155 
          156         if abs(touch.x - touch.ox) < self.scroll_distance:
          157             return
          158 
          159         touch.ud['send_touch_down'] = False
          160         Clock.unschedule(self._change_touch_mode)
          161         self._touch = None
          162         self.state = 'opening' if touch.dx > 0 else 'closing'
          163         touch.ox = touch.x
          164         return
          165 
          166     def _change_touch_mode(self, *args):
          167         if not self._touch:
          168             return
          169         touch = self._touch
          170         touch.ungrab(self)
          171         touch.ud['in_drag_area'] = False
          172         touch.ud['send_touch_down'] = False
          173         self._touch = None
          174         super(Drawer, self).on_touch_down(touch)
          175         return
          176 
          177     def on_touch_up(self, touch):
          178         if not touch.grab_current is self:
          179             return
          180 
          181         self._triigger_gc()
          182 
          183         touch.ungrab(self)
          184         touch.grab_current = None
          185 
          186         # skip on tablet mode
          187         get  = touch.ud.get
          188         if app.ui_mode[0] == 't':
          189             return super(Drawer, self).on_touch_up(touch)
          190 
          191         self.old_x = [1, ] * 10
          192         self.speed = sum((
          193             (self.old_x[x + 1] - self.old_x[x]) for x in range(9))) / 9.
          194 
          195         if get('send_touch_down', None):
          196             # touch up called before moving
          197             Clock.unschedule(self._change_touch_mode)
          198             self._touch = None
          199             Clock.schedule_once(
          200                 lambda dt: super(Drawer, self).on_touch_down(touch))
          201         if get('in_drag_area', None):
          202             if abs(touch.x - touch.ox) < self.scroll_distance:
          203                 anim_to = (0 if self.state[0] == 'c'
          204                       else self.hidden_widget.width)
          205                 Factory.Animation(x=anim_to, d=.1).start(self.overlay_widget)
          206                 return
          207             touch.ud['in_drag_area'] = False
          208             if not get('send_touch_down', None):
          209                 self.toggle_drawer()
          210         Clock.schedule_once(lambda dt: super(Drawer, self).on_touch_up(touch))
          211 
          212     def _complete_drawer_animation(self, *args):
          213         self.state = 'open' if self.state in ('opening', 'closed') else 'closed'
          214 
          215     def add_widget(self, widget, index=1):
          216         if not widget:
          217             return
          218 
          219         iget = self.ids.get
          220         if not iget('hidden_widget') or not iget('overlay_widget'):
          221             super(Drawer, self).add_widget(widget)
          222             return
          223 
          224         if not self.hidden_widget:
          225             self.hidden_widget = self.ids.hidden_widget
          226         if not self.overlay_widget:
          227             self.overlay_widget = self.ids.overlay_widget
          228 
          229         if self.overlay_widget.children and self.hidden_widget.children:
          230             Logger.debug('Drawer: Accepts only two widgets. discarding rest')
          231             return
          232 
          233         if not self.hidden_widget.children:
          234             self.hidden_widget.add_widget(widget)
          235         else:
          236             self.overlay_widget.add_widget(widget)
          237             widget.x = 0
          238 
          239     def remove_widget(self, widget):
          240         if self.overlay_widget.children[0] == widget:
          241             self.overlay_widget.clear_widgets()
          242             return
          243         if widget == self.hidden_widget.children:
          244             self.hidden_widget.clear_widgets()
          245             return
          246 
          247     def clear_widgets(self):
          248         self.overlay_widget.clear_widgets()
          249         self.hidden_widget.clear_widgets()
          250 
          251 if __name__ == '__main__':
          252     from kivy.app import runTouchApp
          253     from kivy.lang import Builder
          254     runTouchApp(Builder.load_string('''
          255 Drawer:
          256     Button:
          257     Button
          258