comparison src/cagou/core/widgets_handler.py @ 38:9f45098289cc

widgets handler, core: hidden widgets can now be shown with swipes: - a couple of methods have been added to handle visible and hidden widgets - a new getOrClone method allow to recreate a widget if it already has a parent (can happen even if the widget is not shown, e.g. in a carousel) - handler now display hidden widgets of the same class as the displayed one when swiping. For instance, if a chat widget is displayed, and header input is used to show an other one, it's now possible to go back to the former by swiping. QuickWidget.onDelete method can be used to handle if a widget must be really deleted (return True) or just hidden (any other value). - handler use a subclass of Carousel for this new feature, with some adjustement so event can be passed to children without too much delay (and frustration). This may need to be adjusted again in the future. - handler.cagou_widget now give the main displayed widget in the handler - handler.changeWidget must be used when widget need to be changed (it's better to use host.switchWidget which will call it itself)
author Goffi <goffi@goffi.org>
date Sun, 28 Aug 2016 15:27:48 +0200
parents 02acbb297a61
children 1922506846be
comparison
equal deleted inserted replaced
37:6cf08d0ee460 38:9f45098289cc
21 from sat.core import log as logging 21 from sat.core import log as logging
22 log = logging.getLogger(__name__) 22 log = logging.getLogger(__name__)
23 from sat_frontends.quick_frontend import quick_widgets 23 from sat_frontends.quick_frontend import quick_widgets
24 from kivy.uix.boxlayout import BoxLayout 24 from kivy.uix.boxlayout import BoxLayout
25 from kivy.uix.button import Button 25 from kivy.uix.button import Button
26 from kivy.uix.carousel import Carousel
27 from kivy.metrics import dp
26 from kivy import properties 28 from kivy import properties
27 from cagou import G 29 from cagou import G
28 30
29 31
32 CAROUSEL_SCROLL_DISTANCE = dp(50)
33 CAROUSEL_SCROLL_TIMEOUT = 80
30 NEW_WIDGET_DIST = 10 34 NEW_WIDGET_DIST = 10
31 REMOVE_WIDGET_DIST = NEW_WIDGET_DIST 35 REMOVE_WIDGET_DIST = NEW_WIDGET_DIST
32 36
33 37
34 class WHSplitter(Button): 38 class WHSplitter(Button):
65 self.handler.removeWidget(self.horizontal) 69 self.handler.removeWidget(self.horizontal)
66 WHSplitter.split_move=None 70 WHSplitter.split_move=None
67 return super(WHSplitter, self).on_touch_up(touch) 71 return super(WHSplitter, self).on_touch_up(touch)
68 72
69 73
74 class HandlerCarousel(Carousel):
75
76 def __init__(self, *args, **kwargs):
77 super(HandlerCarousel, self).__init__(
78 *args,
79 direction='right',
80 loop=True,
81 **kwargs)
82 self._former_slide = None
83 self.bind(current_slide=self.onSlideChange)
84 self._slides_update_lock = False
85
86 def changeWidget(self, new_widget):
87 """Change currently displayed widget
88
89 slides widgets will be updated
90 """
91 # slides update need to be blocked to avoid the update in onSlideChange
92 # which would mess the removal of current widgets
93 self._slides_update_lock = True
94 current = self.current_slide
95 for w in self.slides:
96 if w == current or w == new_widget:
97 continue
98 if isinstance(w, quick_widgets.QuickWidget):
99 G.host.widgets.deleteWidget(w)
100 self.clear_widgets()
101 self.add_widget(new_widget)
102 self._slides_update_lock = False
103 self.updateHiddenSlides()
104
105 def onSlideChange(self, handler, new_slide):
106 if isinstance(self._former_slide, quick_widgets.QuickWidget):
107 G.host.removeVisibleWidget(self._former_slide)
108 self._former_slide = new_slide
109 if isinstance(new_slide, quick_widgets.QuickWidget):
110 G.host.addVisibleWidget(new_slide)
111 self.updateHiddenSlides()
112
113 def hiddenList(self, visible_list):
114 """return widgets of same class as holded one which are hidden
115
116 @param visible_list(list[QuickWidget]): widgets visible
117 @return (iter[QuickWidget]): widgets hidden
118 """
119 added = [(w.targets, w.profiles) for w in visible_list] # we want to avoid recreated widgets
120 for w in G.host.widgets.getWidgets(self.current_slide.__class__, profiles=self.current_slide.profiles):
121 if w in visible_list or (w.targets, w.profiles) in added:
122 continue
123 yield w
124
125 def widgets_sort(self, widget):
126 """method used as key to sort the widgets
127
128 order of the widgets when changing slide is affected
129 @param widget(QuickWidget): widget to sort
130 @return: a value which will be used for sorting
131 """
132 try:
133 return unicode(widget.target).lower()
134 except AttributeError:
135 return unicode(list(widget.targets)[0]).lower()
136
137 def updateHiddenSlides(self):
138 """adjust carousel slides according to visible widgets"""
139 if self._slides_update_lock:
140 return
141 if not isinstance(self.current_slide, quick_widgets.QuickWidget):
142 return
143 # lock must be used here to avoid recursions
144 self._slides_update_lock = True
145 visible_list = G.host.getVisibleList(self.current_slide.__class__)
146 hidden = list(self.hiddenList(visible_list))
147 slides_sorted = sorted(hidden + [self.current_slide], key=self.widgets_sort)
148 to_remove = set(self.slides).difference({self.current_slide})
149 for w in to_remove:
150 self.remove_widget(w)
151 if hidden:
152 # no need to add more than two widgets (next and previous),
153 # as the list will be updated on each new visible widget
154 current_idx = slides_sorted.index(self.current_slide)
155 try:
156 next_slide = slides_sorted[current_idx+1]
157 except IndexError:
158 next_slide = slides_sorted[0]
159 self.add_widget(G.host.getOrClone(next_slide))
160 if len(hidden)>1:
161 previous_slide = slides_sorted[current_idx-1]
162 self.add_widget(G.host.getOrClone(previous_slide))
163
164 if len(self.slides) == 1:
165 # we block carousel with high scroll_distance to avoid swiping
166 # when the is not other instance of the widget
167 self.scroll_distance=2**32
168 self.scroll_timeout=0
169 else:
170 self.scroll_distance = CAROUSEL_SCROLL_DISTANCE
171 self.scroll_timeout=CAROUSEL_SCROLL_TIMEOUT
172 self._slides_update_lock = False
173
174
70 class WidgetsHandler(BoxLayout): 175 class WidgetsHandler(BoxLayout):
71 176
72 def __init__(self, wid=None, **kw): 177 def __init__(self, wid=None, **kw):
73 if wid is None: 178 if wid is None:
74 wid=self.default_widget 179 wid=self.default_widget
75 self.vert_wid = self.hor_wid = None 180 self.vert_wid = self.hor_wid = None
76 BoxLayout.__init__(self, orientation="vertical", **kw) 181 BoxLayout.__init__(self, orientation="vertical", **kw)
77 self.blh = BoxLayout(orientation="horizontal") 182 self.blh = BoxLayout(orientation="horizontal")
78 self.blv = BoxLayout(orientation="vertical") 183 self.blv = BoxLayout(orientation="vertical")
79 self.blv.add_widget(WHSplitter(self)) 184 self.blv.add_widget(WHSplitter(self))
80 self.blv.add_widget(wid) 185 self.carousel = HandlerCarousel()
186 self.blv.add_widget(self.carousel)
81 self.blh.add_widget(WHSplitter(self, horizontal=False)) 187 self.blh.add_widget(WHSplitter(self, horizontal=False))
82 self.blh.add_widget(self.blv) 188 self.blh.add_widget(self.blv)
83 self.add_widget(self.blh) 189 self.add_widget(self.blh)
190 self.changeWidget(wid)
84 191
85 @property 192 @property
86 def default_widget(self): 193 def default_widget(self):
87 return G.host.default_wid['factory'](G.host.default_wid, None, None) 194 return G.host.default_wid['factory'](G.host.default_wid, None, None)
195
196 @property
197 def cagou_widget(self):
198 """get holded CagouWidget"""
199 return self.carousel.current_slide
200
201 def changeWidget(self, new_widget):
202 self.carousel.changeWidget(new_widget)
88 203
89 def removeWidget(self, vertical): 204 def removeWidget(self, vertical):
90 if vertical and self.vert_wid is not None: 205 if vertical and self.vert_wid is not None:
91 self.remove_widget(self.vert_wid) 206 self.remove_widget(self.vert_wid)
92 self.vert_wid.onDelete() 207 self.vert_wid.onDelete()
108 self.blh.add_widget(self.hor_wid, len(self.blh.children)) 223 self.blh.add_widget(self.hor_wid, len(self.blh.children))
109 self.hor_wid.width=size 224 self.hor_wid.width=size
110 225
111 def onDelete(self): 226 def onDelete(self):
112 # when this handler is deleted, we need to delete the holded CagouWidget 227 # when this handler is deleted, we need to delete the holded CagouWidget
113 cagou_widget = self.children[0].children[0].children[0] 228 cagou_widget = self.cagou_widget
114 if isinstance(cagou_widget, quick_widgets.QuickWidget): 229 if isinstance(cagou_widget, quick_widgets.QuickWidget):
115 G.host.widgets.deleteWidget(cagou_widget) 230 G.host.removeVisibleWidget(cagou_widget)