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