Mercurial > libervia-desktop-kivy
comparison libervia/desktop_kivy/core/widgets_handler.py @ 493:b3cedbee561d
refactoring: rename `cagou` to `libervia.desktop_kivy` + update imports and names following backend changes
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 18:26:16 +0200 |
parents | cagou/core/widgets_handler.py@203755bbe0fe |
children |
comparison
equal
deleted
inserted
replaced
492:5114bbb5daa3 | 493:b3cedbee561d |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 #Libervia Desktop-Kivy | |
4 # Copyright (C) 2016-2021 Jérôme Poisson (goffi@goffi.org) | |
5 | |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
19 | |
20 from libervia.backend.core import log as logging | |
21 from libervia.backend.core import exceptions | |
22 from libervia.frontends.quick_frontend import quick_widgets | |
23 from kivy.graphics import Color, Ellipse | |
24 from kivy.uix.layout import Layout | |
25 from kivy.uix.boxlayout import BoxLayout | |
26 from kivy.uix.stencilview import StencilView | |
27 from kivy.uix.carousel import Carousel | |
28 from kivy.uix.screenmanager import ScreenManager, Screen | |
29 from kivy.metrics import dp | |
30 from kivy import properties | |
31 from libervia.desktop_kivy import G | |
32 from .constants import Const as C | |
33 from . import cagou_widget | |
34 | |
35 log = logging.getLogger(__name__) | |
36 | |
37 | |
38 REMOVE_WID_LIMIT = dp(50) | |
39 MIN_WIDTH = MIN_HEIGHT = dp(70) | |
40 | |
41 | |
42 class BoxStencil(BoxLayout, StencilView): | |
43 pass | |
44 | |
45 | |
46 class WHWrapper(BoxLayout): | |
47 main_container = properties.ObjectProperty(None) | |
48 screen_manager = properties.ObjectProperty(None, allownone=True) | |
49 carousel = properties.ObjectProperty(None, allownone=True) | |
50 split_size = properties.NumericProperty(dp(1)) | |
51 split_margin = properties.NumericProperty(dp(2)) | |
52 split_color = properties.ListProperty([0.8, 0.8, 0.8, 1]) | |
53 split_color_move = C.COLOR_SEC_DARK | |
54 split_color_del = properties.ListProperty([0.8, 0.0, 0.0, 1]) | |
55 # sp stands for "split point" | |
56 sp_size = properties.NumericProperty(dp(1)) | |
57 sp_space = properties.NumericProperty(dp(4)) | |
58 sp_zone = properties.NumericProperty(dp(30)) | |
59 _split = properties.OptionProperty('None', options=['None', 'left', 'top']) | |
60 _split_del = properties.BooleanProperty(False) | |
61 | |
62 def __init__(self, **kwargs): | |
63 idx = kwargs.pop('_wid_idx') | |
64 self._wid_idx = idx | |
65 super(WHWrapper, self).__init__(**kwargs) | |
66 self._left_wids = set() | |
67 self._top_wids = set() | |
68 self._right_wids = set() | |
69 self._bottom_wids = set() | |
70 self._clear_attributes() | |
71 | |
72 def _clear_attributes(self): | |
73 self._former_slide = None | |
74 | |
75 def __repr__(self): | |
76 return "WHWrapper_{idx}".format(idx=self._wid_idx) | |
77 | |
78 def _main_wid(self, wid_list): | |
79 """return main widget of a side list | |
80 | |
81 main widget is either the widget currently splitted | |
82 or any widget if none is split | |
83 @return (WHWrapper, None): main widget or None | |
84 if there is not widget | |
85 """ | |
86 if not wid_list: | |
87 return None | |
88 for wid in wid_list: | |
89 if wid._split != 'None': | |
90 return wid | |
91 return next(iter(wid_list)) | |
92 | |
93 def on_parent(self, __, new_parent): | |
94 if new_parent is None: | |
95 # we detach all children so LiberviaDesktopKivyWidget.whwrapper won't link to this one | |
96 # anymore | |
97 self.clear_widgets() | |
98 | |
99 @property | |
100 def _left_wid(self): | |
101 return self._main_wid(self._left_wids) | |
102 | |
103 @property | |
104 def _top_wid(self): | |
105 return self._main_wid(self._top_wids) | |
106 | |
107 @property | |
108 def _right_wid(self): | |
109 return self._main_wid(self._right_wids) | |
110 | |
111 @property | |
112 def _bottom_wid(self): | |
113 return self._main_wid(self._bottom_wids) | |
114 | |
115 @property | |
116 def current_slide(self): | |
117 if (self.carousel is not None | |
118 and (self.screen_manager is None or self.screen_manager.current == '')): | |
119 return self.carousel.current_slide | |
120 elif self.screen_manager is not None: | |
121 # we should have exactly one children in current_screen, else there is a bug | |
122 return self.screen_manager.current_screen.children[0] | |
123 else: | |
124 try: | |
125 return self.main_container.children[0] | |
126 except IndexError: | |
127 log.error("No child found, this should not happen") | |
128 return None | |
129 | |
130 @property | |
131 def carousel_active(self): | |
132 """Return True if Carousel is used and active""" | |
133 if self.carousel is None: | |
134 return False | |
135 if self.screen_manager is not None and self.screen_manager.current != '': | |
136 return False | |
137 return True | |
138 | |
139 @property | |
140 def former_screen_wid(self): | |
141 """Return widget currently active for former screen""" | |
142 if self.screen_manager is None: | |
143 raise exceptions.InternalError( | |
144 "former_screen_wid can only be used if ScreenManager is used") | |
145 if self._former_screen_name is None: | |
146 return None | |
147 return self.get_screen_widget(self._former_screen_name) | |
148 | |
149 def get_screen_widget(self, screen_name): | |
150 """Return screen main widget, handling carousel if necessary""" | |
151 if self.carousel is not None and screen_name == '': | |
152 return self.carousel.current_slide | |
153 try: | |
154 return self.screen_manager.get_screen(screen_name).children[0] | |
155 except IndexError: | |
156 return None | |
157 | |
158 def _draw_ellipse(self): | |
159 """draw split ellipse""" | |
160 color = self.split_color_del if self._split_del else self.split_color_move | |
161 try: | |
162 self.canvas.after.remove(self.ellipse) | |
163 except AttributeError: | |
164 pass | |
165 if self._split == "top": | |
166 with self.canvas.after: | |
167 Color(*color) | |
168 self.ellipse = Ellipse(angle_start=90, angle_end=270, | |
169 pos=(self.x + self.width/2 - self.sp_zone/2, | |
170 self.y + self.height - self.sp_zone/2), | |
171 size=(self.sp_zone, self.sp_zone)) | |
172 elif self._split == "left": | |
173 with self.canvas.after: | |
174 Color(*color) | |
175 self.ellipse = Ellipse(angle_end=180, | |
176 pos=(self.x + -self.sp_zone/2, | |
177 self.y + self.height/2 - self.sp_zone/2), | |
178 size = (self.sp_zone, self.sp_zone)) | |
179 else: | |
180 raise exceptions.InternalError('unexpected split value') | |
181 | |
182 def on_touch_down(self, touch): | |
183 """activate split if touch is on a split zone""" | |
184 if not self.collide_point(*touch.pos): | |
185 return | |
186 log.debug("WIDGET IDX: {} (left: {}, top: {}, right: {}, bottom: {}), pos: {}, size: {}".format( | |
187 self._wid_idx, | |
188 'None' if not self._left_wids else [w._wid_idx for w in self._left_wids], | |
189 'None' if not self._top_wids else [w._wid_idx for w in self._top_wids], | |
190 'None' if not self._right_wids else [w._wid_idx for w in self._right_wids], | |
191 'None' if not self._bottom_wids else [w._wid_idx for w in self._bottom_wids], | |
192 self.pos, | |
193 self.size, | |
194 )) | |
195 touch_rx, touch_ry = self.to_widget(*touch.pos, relative=True) | |
196 if (touch_ry <= self.height and | |
197 touch_ry >= self.height - self.split_size - self.split_margin or | |
198 touch_ry <= self.height and | |
199 touch_ry >= self.height - self.sp_zone and | |
200 touch_rx >= self.width//2 - self.sp_zone//2 and | |
201 touch_rx <= self.width//2 + self.sp_zone//2): | |
202 # split area is touched, we activate top split mode | |
203 self._split = "top" | |
204 self._draw_ellipse() | |
205 elif (touch_rx >= 0 and | |
206 touch_rx <= self.split_size + self.split_margin or | |
207 touch_rx >= 0 and | |
208 touch_rx <= self.sp_zone and | |
209 touch_ry >= self.height//2 - self.sp_zone//2 and | |
210 touch_ry <= self.height//2 + self.sp_zone//2): | |
211 # split area is touched, we activate left split mode | |
212 self._split = "left" | |
213 touch.ud['ori_width'] = self.width | |
214 self._draw_ellipse() | |
215 else: | |
216 if self.carousel_active and len(self.carousel.slides) <= 1: | |
217 # we don't want swipe of carousel if there is only one slide | |
218 return StencilView.on_touch_down(self.carousel, touch) | |
219 else: | |
220 return super(WHWrapper, self).on_touch_down(touch) | |
221 | |
222 def on_touch_move(self, touch): | |
223 """handle size change and widget creation on split""" | |
224 if self._split == 'None': | |
225 return super(WHWrapper, self).on_touch_move(touch) | |
226 | |
227 elif self._split == 'top': | |
228 new_height = touch.y - self.y | |
229 | |
230 if new_height < MIN_HEIGHT: | |
231 return | |
232 | |
233 # we must not pass the top widget/border | |
234 if self._top_wids: | |
235 top = next(iter(self._top_wids)) | |
236 y_limit = top.y + top.height | |
237 | |
238 if top.height <= REMOVE_WID_LIMIT: | |
239 # we are in remove zone, we add visual hint for that | |
240 if not self._split_del and self._top_wids: | |
241 self._split_del = True | |
242 self._draw_ellipse() | |
243 else: | |
244 if self._split_del: | |
245 self._split_del = False | |
246 self._draw_ellipse() | |
247 else: | |
248 y_limit = self.y + self.height | |
249 | |
250 if touch.y >= y_limit: | |
251 return | |
252 | |
253 # all right, we can change size | |
254 self.height = new_height | |
255 self.ellipse.pos = (self.ellipse.pos[0], touch.y - self.sp_zone/2) | |
256 | |
257 if not self._top_wids: | |
258 # we are the last widget on the top | |
259 # so we create a new widget | |
260 new_wid = self.parent.add_widget() | |
261 self._top_wids.add(new_wid) | |
262 new_wid._bottom_wids.add(self) | |
263 for w in self._right_wids: | |
264 new_wid._right_wids.add(w) | |
265 w._left_wids.add(new_wid) | |
266 for w in self._left_wids: | |
267 new_wid._left_wids.add(w) | |
268 w._right_wids.add(new_wid) | |
269 | |
270 elif self._split == 'left': | |
271 ori_width = touch.ud['ori_width'] | |
272 new_x = touch.x | |
273 new_width = ori_width - (touch.x - touch.ox) | |
274 | |
275 if new_width < MIN_WIDTH: | |
276 return | |
277 | |
278 # we must not pass the left widget/border | |
279 if self._left_wids: | |
280 left = next(iter(self._left_wids)) | |
281 x_limit = left.x | |
282 | |
283 if left.width <= REMOVE_WID_LIMIT: | |
284 # we are in remove zone, we add visual hint for that | |
285 if not self._split_del and self._left_wids: | |
286 self._split_del = True | |
287 self._draw_ellipse() | |
288 else: | |
289 if self._split_del: | |
290 self._split_del = False | |
291 self._draw_ellipse() | |
292 else: | |
293 x_limit = self.x | |
294 | |
295 if new_x <= x_limit: | |
296 return | |
297 | |
298 # all right, we can change position/size | |
299 self.x = new_x | |
300 self.width = new_width | |
301 self.ellipse.pos = (touch.x - self.sp_zone/2, self.ellipse.pos[1]) | |
302 | |
303 if not self._left_wids: | |
304 # we are the last widget on the left | |
305 # so we create a new widget | |
306 new_wid = self.parent.add_widget() | |
307 self._left_wids.add(new_wid) | |
308 new_wid._right_wids.add(self) | |
309 for w in self._top_wids: | |
310 new_wid._top_wids.add(w) | |
311 w._bottom_wids.add(new_wid) | |
312 for w in self._bottom_wids: | |
313 new_wid._bottom_wids.add(w) | |
314 w._top_wids.add(new_wid) | |
315 | |
316 else: | |
317 raise Exception.InternalError('invalid _split value') | |
318 | |
319 def on_touch_up(self, touch): | |
320 if self._split == 'None': | |
321 return super(WHWrapper, self).on_touch_up(touch) | |
322 if self._split == 'top': | |
323 # we remove all top widgets in delete zone, | |
324 # and update there side widgets list | |
325 for top in self._top_wids.copy(): | |
326 if top.height <= REMOVE_WID_LIMIT: | |
327 G.host._remove_visible_widget(top.current_slide) | |
328 for w in top._top_wids: | |
329 w._bottom_wids.remove(top) | |
330 w._bottom_wids.update(top._bottom_wids) | |
331 for w in top._bottom_wids: | |
332 w._top_wids.remove(top) | |
333 w._top_wids.update(top._top_wids) | |
334 for w in top._left_wids: | |
335 w._right_wids.remove(top) | |
336 for w in top._right_wids: | |
337 w._left_wids.remove(top) | |
338 self.parent.remove_widget(top) | |
339 elif self._split == 'left': | |
340 # we remove all left widgets in delete zone, | |
341 # and update there side widgets list | |
342 for left in self._left_wids.copy(): | |
343 if left.width <= REMOVE_WID_LIMIT: | |
344 G.host._remove_visible_widget(left.current_slide) | |
345 for w in left._left_wids: | |
346 w._right_wids.remove(left) | |
347 w._right_wids.update(left._right_wids) | |
348 for w in left._right_wids: | |
349 w._left_wids.remove(left) | |
350 w._left_wids.update(left._left_wids) | |
351 for w in left._top_wids: | |
352 w._bottom_wids.remove(left) | |
353 for w in left._bottom_wids: | |
354 w._top_wids.remove(left) | |
355 self.parent.remove_widget(left) | |
356 self._split = 'None' | |
357 self.canvas.after.remove(self.ellipse) | |
358 del self.ellipse | |
359 | |
360 def clear_widgets(self): | |
361 current_slide = self.current_slide | |
362 if current_slide is not None: | |
363 G.host._remove_visible_widget(current_slide, ignore_missing=True) | |
364 | |
365 super().clear_widgets() | |
366 | |
367 self.screen_manager = None | |
368 self.carousel = None | |
369 self._clear_attributes() | |
370 | |
371 def set_widget(self, wid, index=0): | |
372 assert len(self.children) == 0 | |
373 | |
374 if wid.collection_carousel or wid.global_screen_manager: | |
375 self.main_container = self | |
376 else: | |
377 self.main_container = BoxStencil() | |
378 self.add_widget(self.main_container) | |
379 | |
380 if self.carousel is not None: | |
381 return self.carousel.add_widget(wid, index) | |
382 | |
383 if wid.global_screen_manager: | |
384 if self.screen_manager is None: | |
385 self.screen_manager = ScreenManager() | |
386 self.main_container.add_widget(self.screen_manager) | |
387 parent = Screen() | |
388 self.screen_manager.add_widget(parent) | |
389 self._former_screen_name = '' | |
390 self.screen_manager.bind(current=self.on_screen_change) | |
391 wid.screen_manager_init(self.screen_manager) | |
392 else: | |
393 parent = self.main_container | |
394 | |
395 if wid.collection_carousel: | |
396 # a Carousel is requested, and this is the first widget that we add | |
397 # so we need to create the carousel | |
398 self.carousel = Carousel( | |
399 direction = "right", | |
400 ignore_perpendicular_swipes = True, | |
401 loop = True, | |
402 ) | |
403 self._slides_update_lock = 0 | |
404 self.carousel.bind(current_slide=self.on_slide_change) | |
405 parent.add_widget(self.carousel) | |
406 self.carousel.add_widget(wid, index) | |
407 else: | |
408 # no Carousel requested, we add the widget as a direct child | |
409 parent.add_widget(wid) | |
410 G.host._add_visible_widget(wid) | |
411 | |
412 def change_widget(self, new_widget): | |
413 """Change currently displayed widget | |
414 | |
415 slides widgets will be updated | |
416 """ | |
417 if (self.carousel is not None | |
418 and self.carousel.current_slide.__class__ == new_widget.__class__): | |
419 # we have the same class, we reuse carousel and screen manager setting | |
420 | |
421 if self.carousel.current_slide != new_widget: | |
422 # slides update need to be blocked to avoid the update in on_slide_change | |
423 # which would mess the removal of current widgets | |
424 self._slides_update_lock += 1 | |
425 new_wid = None | |
426 for w in self.carousel.slides[:]: | |
427 if w.widget_hash == new_widget.widget_hash: | |
428 new_wid = w | |
429 continue | |
430 self.carousel.remove_widget(w) | |
431 if isinstance(w, quick_widgets.QuickWidget): | |
432 G.host.widgets.delete_widget(w) | |
433 if new_wid is None: | |
434 new_wid = G.host.get_or_clone(new_widget) | |
435 self.carousel.add_widget(new_wid) | |
436 self._update_hidden_slides() | |
437 self._slides_update_lock -= 1 | |
438 | |
439 if self.screen_manager is not None: | |
440 self.screen_manager.clear_widgets([ | |
441 s for s in self.screen_manager.screens if s.name != '']) | |
442 new_wid.screen_manager_init(self.screen_manager) | |
443 else: | |
444 # else, we restart fresh | |
445 self.clear_widgets() | |
446 self.set_widget(G.host.get_or_clone(new_widget)) | |
447 | |
448 def on_screen_change(self, screen_manager, new_screen): | |
449 try: | |
450 new_screen_wid = self.current_slide | |
451 except IndexError: | |
452 new_screen_wid = None | |
453 log.warning("Switching to a screen without children") | |
454 if new_screen == '' and self.carousel is not None: | |
455 # carousel may have been changed in the background, so we update slides | |
456 self._update_hidden_slides() | |
457 former_screen_wid = self.former_screen_wid | |
458 if isinstance(former_screen_wid, cagou_widget.LiberviaDesktopKivyWidget): | |
459 G.host._remove_visible_widget(former_screen_wid) | |
460 if isinstance(new_screen_wid, cagou_widget.LiberviaDesktopKivyWidget): | |
461 G.host._add_visible_widget(new_screen_wid) | |
462 self._former_screen_name = new_screen | |
463 G.host.selected_widget = new_screen_wid | |
464 | |
465 def on_slide_change(self, handler, new_slide): | |
466 if self._former_slide is new_slide: | |
467 # FIXME: workaround for Kivy a95d67f (and above?), Carousel.current_slide | |
468 # binding now calls on_slide_change twice with the same widget (here | |
469 # "new_slide"). To be checked with Kivy team. | |
470 return | |
471 log.debug(f"Slide change: new_slide = {new_slide}") | |
472 if self._former_slide is not None: | |
473 G.host._remove_visible_widget(self._former_slide, ignore_missing=True) | |
474 self._former_slide = new_slide | |
475 if self.carousel_active: | |
476 G.host.selected_widget = new_slide | |
477 if new_slide is not None: | |
478 G.host._add_visible_widget(new_slide) | |
479 self._update_hidden_slides() | |
480 | |
481 def hidden_list(self, visible_list, ignore=None): | |
482 """return widgets of same class as carousel current one, if they are hidden | |
483 | |
484 @param visible_list(list[QuickWidget]): widgets visible | |
485 @param ignore(QuickWidget, None): do no return this widget | |
486 @return (iter[QuickWidget]): widgets hidden | |
487 """ | |
488 # we want to avoid recreated widgets | |
489 added = [w.widget_hash for w in visible_list] | |
490 current_slide = self.carousel.current_slide | |
491 for w in G.host.widgets.get_widgets(current_slide.__class__, | |
492 profiles=current_slide.profiles): | |
493 wid_hash = w.widget_hash | |
494 if w in visible_list or wid_hash in added: | |
495 continue | |
496 if wid_hash == ignore.widget_hash: | |
497 continue | |
498 yield w | |
499 | |
500 | |
501 def _update_hidden_slides(self): | |
502 """adjust carousel slides according to visible widgets""" | |
503 if self._slides_update_lock or not self.carousel_active: | |
504 return | |
505 current_slide = self.carousel.current_slide | |
506 if not isinstance(current_slide, quick_widgets.QuickWidget): | |
507 return | |
508 # lock must be used here to avoid recursions | |
509 self._slides_update_lock += 1 | |
510 visible_list = G.host.get_visible_list(current_slide.__class__) | |
511 # we ignore current_slide as it may not be visible yet (e.g. if an other | |
512 # screen is shown | |
513 hidden = list(self.hidden_list(visible_list, ignore=current_slide)) | |
514 slides_sorted = sorted(set(hidden + [current_slide])) | |
515 to_remove = set(self.carousel.slides).difference({current_slide}) | |
516 for w in to_remove: | |
517 self.carousel.remove_widget(w) | |
518 if hidden: | |
519 # no need to add more than two widgets (next and previous), | |
520 # as the list will be updated on each new visible widget | |
521 current_idx = slides_sorted.index(current_slide) | |
522 try: | |
523 next_slide = slides_sorted[current_idx+1] | |
524 except IndexError: | |
525 next_slide = slides_sorted[0] | |
526 self.carousel.add_widget(G.host.get_or_clone(next_slide)) | |
527 if len(hidden)>1: | |
528 previous_slide = slides_sorted[current_idx-1] | |
529 self.carousel.add_widget(G.host.get_or_clone(previous_slide)) | |
530 | |
531 self._slides_update_lock -= 1 | |
532 | |
533 | |
534 class WidgetsHandlerLayout(Layout): | |
535 count = 0 | |
536 | |
537 def __init__(self, **kwargs): | |
538 super(WidgetsHandlerLayout, self).__init__(**kwargs) | |
539 self._layout_size = None # size used for the last layout | |
540 fbind = self.fbind | |
541 update = self._trigger_layout | |
542 fbind('children', update) | |
543 fbind('parent', update) | |
544 fbind('size', self.adjust_prop) | |
545 fbind('pos', update) | |
546 | |
547 @property | |
548 def default_widget(self): | |
549 return G.host.default_wid['factory'](G.host.default_wid, None, None) | |
550 | |
551 def adjust_prop(self, handler, new_size): | |
552 """Adjust children proportion | |
553 | |
554 useful when this widget is resized (e.g. when going to fullscreen) | |
555 """ | |
556 if len(self.children) > 1: | |
557 old_width, old_height = self._layout_size | |
558 if not old_width or not old_height: | |
559 # we don't want division by zero | |
560 return self._trigger_layout(handler, new_size) | |
561 width_factor = float(self.width) / old_width | |
562 height_factor = float(self.height) / old_height | |
563 for child in self.children: | |
564 child.width *= width_factor | |
565 child.height *= height_factor | |
566 child.x *= width_factor | |
567 child.y *= height_factor | |
568 self._trigger_layout(handler, new_size) | |
569 | |
570 def do_layout(self, *args): | |
571 self._layout_size = self.size[:] | |
572 for child in self.children: | |
573 # XXX: left must be calculated before right and bottom before top | |
574 # because they are the pos, and are used to caculate size (right and top) | |
575 # left | |
576 left = child._left_wid | |
577 left_end_x = self.x-1 if left is None else left.right | |
578 if child.x != left_end_x + 1 and child._split == "None": | |
579 child.x = left_end_x + 1 | |
580 # right | |
581 right = child._right_wid | |
582 right_x = self.right + 1 if right is None else right.x | |
583 if child.right != right_x - 1: | |
584 child.width = right_x - child.x - 1 | |
585 # bottom | |
586 bottom = child._bottom_wid | |
587 if bottom is None: | |
588 if child.y != self.y: | |
589 child.y = self.y | |
590 else: | |
591 if child.y != bottom.top + 1: | |
592 child.y = bottom.top + 1 | |
593 # top | |
594 top = child._top_wid | |
595 top_y = self.top+1 if top is None else top.y | |
596 if child.top != top_y - 1: | |
597 if child._split == "None": | |
598 child.height = top_y - child.y - 1 | |
599 | |
600 def remove_widget(self, wid): | |
601 super(WidgetsHandlerLayout, self).remove_widget(wid) | |
602 log.debug("widget deleted ({})".format(wid._wid_idx)) | |
603 | |
604 def add_widget(self, wid=None, index=0): | |
605 WidgetsHandlerLayout.count += 1 | |
606 if wid is None: | |
607 wid = self.default_widget | |
608 if G.host.selected_widget is None: | |
609 G.host.selected_widget = wid | |
610 wrapper = WHWrapper(_wid_idx=WidgetsHandlerLayout.count) | |
611 log.debug("WHWrapper created ({})".format(wrapper._wid_idx)) | |
612 wrapper.set_widget(wid) | |
613 super(WidgetsHandlerLayout, self).add_widget(wrapper, index) | |
614 return wrapper | |
615 | |
616 | |
617 class WidgetsHandler(WidgetsHandlerLayout): | |
618 | |
619 def __init__(self, **kw): | |
620 super(WidgetsHandler, self).__init__(**kw) | |
621 self.add_widget() |