electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

drawer.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 '''))