Mercurial > libervia-desktop-kivy
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) |