Mercurial > libervia-desktop-kivy
comparison libervia/desktop_kivy/core/behaviors.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/behaviors.py@203755bbe0fe |
children |
comparison
equal
deleted
inserted
replaced
492:5114bbb5daa3 | 493:b3cedbee561d |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 | |
4 #Libervia Desktop-Kivy | |
5 # Copyright (C) 2016-2021 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 | |
21 from kivy import properties | |
22 from kivy.animation import Animation | |
23 from kivy.clock import Clock | |
24 from kivy_garden import modernmenu | |
25 from functools import partial | |
26 | |
27 | |
28 class TouchMenu(modernmenu.ModernMenu): | |
29 pass | |
30 | |
31 | |
32 class TouchMenuItemBehavior: | |
33 """Class to use on every item where a menu may appear | |
34 | |
35 main_wid attribute must be set to the class inheriting from TouchMenuBehavior | |
36 do_item_action is the method called on simple click | |
37 get_menu_choices must return a list of menus for long press | |
38 menus there are dict as expected by ModernMenu | |
39 (translated text, index and callback) | |
40 """ | |
41 main_wid = properties.ObjectProperty() | |
42 click_timeout = properties.NumericProperty(0.4) | |
43 | |
44 def on_touch_down(self, touch): | |
45 if not self.collide_point(*touch.pos): | |
46 return | |
47 t = partial(self.open_menu, touch) | |
48 touch.ud['menu_timeout'] = t | |
49 Clock.schedule_once(t, self.click_timeout) | |
50 return super(TouchMenuItemBehavior, self).on_touch_down(touch) | |
51 | |
52 def do_item_action(self, touch): | |
53 pass | |
54 | |
55 def on_touch_up(self, touch): | |
56 if touch.ud.get('menu_timeout'): | |
57 Clock.unschedule(touch.ud['menu_timeout']) | |
58 if self.collide_point(*touch.pos) and self.main_wid.menu is None: | |
59 self.do_item_action(touch) | |
60 return super(TouchMenuItemBehavior, self).on_touch_up(touch) | |
61 | |
62 def open_menu(self, touch, dt): | |
63 self.main_wid.open_menu(self, touch) | |
64 del touch.ud['menu_timeout'] | |
65 | |
66 def get_menu_choices(self): | |
67 """return choice adapted to selected item | |
68 | |
69 @return (list[dict]): choices ad expected by ModernMenu | |
70 """ | |
71 return [] | |
72 | |
73 | |
74 class TouchMenuBehavior: | |
75 """Class to handle a menu appearing on long press on items | |
76 | |
77 classes using this behaviour need to have a float_layout property | |
78 pointing the main FloatLayout. | |
79 """ | |
80 float_layout = properties.ObjectProperty() | |
81 | |
82 def __init__(self, *args, **kwargs): | |
83 super(TouchMenuBehavior, self).__init__(*args, **kwargs) | |
84 self.menu = None | |
85 self.menu_item = None | |
86 | |
87 ## menu methods ## | |
88 | |
89 def clean_fl_children(self, layout, children): | |
90 """insure that self.menu and self.menu_item are None when menu is dimissed""" | |
91 if self.menu is not None and self.menu not in children: | |
92 self.menu = self.menu_item = None | |
93 | |
94 def clear_menu(self): | |
95 """remove menu if there is one""" | |
96 if self.menu is not None: | |
97 self.menu.dismiss() | |
98 self.menu = None | |
99 self.menu_item = None | |
100 | |
101 def open_menu(self, item, touch): | |
102 """open menu for item | |
103 | |
104 @param item(PathWidget): item when the menu has been requested | |
105 @param touch(kivy.input.MotionEvent): touch data | |
106 """ | |
107 if self.menu_item == item: | |
108 return | |
109 self.clear_menu() | |
110 pos = self.to_widget(*touch.pos) | |
111 choices = item.get_menu_choices() | |
112 if not choices: | |
113 return | |
114 self.menu = TouchMenu(choices=choices, | |
115 center=pos, | |
116 size_hint=(None, None)) | |
117 self.float_layout.add_widget(self.menu) | |
118 self.menu.start_display(touch) | |
119 self.menu_item = item | |
120 | |
121 def on_float_layout(self, wid, float_layout): | |
122 float_layout.bind(children=self.clean_fl_children) | |
123 | |
124 | |
125 class FilterBehavior(object): | |
126 """class to handle items filtering with animation""" | |
127 | |
128 def __init__(self, *args, **kwargs): | |
129 super(FilterBehavior, self).__init__(*args, **kwargs) | |
130 self._filter_last = {} | |
131 self._filter_anim = Animation(width = 0, | |
132 height = 0, | |
133 opacity = 0, | |
134 d = 0.5) | |
135 | |
136 def do_filter(self, parent, text, get_child_text, width_cb, height_cb, | |
137 continue_tests=None): | |
138 """filter the children | |
139 | |
140 filtered children will have a animation to set width, height and opacity to 0 | |
141 @param parent(kivy.uix.widget.Widget): parent layout of the widgets to filter | |
142 @param text(unicode): filter text (if this text is not present in a child, | |
143 the child is filtered out) | |
144 @param get_child_text(callable): must retrieve child text | |
145 child is used as sole argument | |
146 @param width_cb(callable, int, None): method to retrieve width when opened | |
147 child is used as sole argument, int can be used instead of callable | |
148 @param height_cb(callable, int, None): method to retrieve height when opened | |
149 child is used as sole argument, int can be used instead of callable | |
150 @param continue_tests(list[callable]): list of test to skip the item | |
151 all callables take child as sole argument. | |
152 if any of the callable return True, the child is skipped (i.e. not filtered) | |
153 """ | |
154 text = text.strip().lower() | |
155 filtering = len(text)>len(self._filter_last.get(parent, '')) | |
156 self._filter_last[parent] = text | |
157 for child in parent.children: | |
158 if continue_tests is not None and any((t(child) for t in continue_tests)): | |
159 continue | |
160 if text in get_child_text(child).lower(): | |
161 self._filter_anim.cancel(child) | |
162 for key, method in (('width', width_cb), | |
163 ('height', height_cb), | |
164 ('opacity', lambda c: 1)): | |
165 try: | |
166 setattr(child, key, method(child)) | |
167 except TypeError: | |
168 # method is not a callable, must be an int | |
169 setattr(child, key, method) | |
170 elif (filtering | |
171 and child.opacity > 0 | |
172 and not self._filter_anim.have_properties_to_animate(child)): | |
173 self._filter_anim.start(child) |