kvex.widgets.list
List widget.
1"""List widget.""" 2 3from typing import Optional 4from .. import kivy as kv 5from ..util import text_texture, from_atlas 6from ..colors import XColor 7from ..behaviors import XThemed, XFocusBehavior 8from .layouts import XRelative 9 10 11SELECTION_SOURCE = from_atlas("vkeyboard_background") 12SELECTION_ALPHA = 0.5, 1 # When focus is False, True respectively 13EMPTY_TEXTURE = text_texture(" ") 14 15# Cache setup 16kv.Cache.register("XList") 17cache_append = kv.Cache.append 18cache_get = kv.Cache.get 19cache_remove = kv.Cache.remove 20 21 22class XList(XThemed, XFocusBehavior, XRelative): 23 """Text list widget.""" 24 25 font_name = kv.StringProperty("Roboto") 26 """Text font.""" 27 font_size = kv.NumericProperty(16) 28 """Text font size.""" 29 item_height = kv.NumericProperty(35) 30 """Item height size.""" 31 item_padding = kv.ListProperty([10, 5]) 32 """Item padding.""" 33 items = kv.ListProperty() 34 """Items in list.""" 35 selection = kv.NumericProperty(0) 36 """Currently selected item index.""" 37 paging_size = kv.NumericProperty(None) 38 """Number of items to move when paging (e.g. page up/down).""" 39 scroll_width = kv.NumericProperty(5) 40 """Scroll bar width.""" 41 scroll_color = kv.ColorProperty([0.5, 0.5, 0.5, 0.5]) 42 """Scroll bar color.""" 43 shorten = kv.BooleanProperty(True) 44 """Like `Label.shorten`.""" 45 shorten_from = kv.StringProperty("center") 46 """Like `Label.shorten_from`.""" 47 bg_color = kv.ColorProperty([0, 0, 0, 0]) 48 """Widget background.""" 49 text_color = kv.ColorProperty() 50 """Text color.""" 51 selection_color = kv.ColorProperty([1, 1, 1, 0.5]) 52 """Selection highlighting color.""" 53 enable_shifting = kv.BooleanProperty(False) 54 """Enable changing the order of items.""" 55 invoke_double_tap_only = kv.BooleanProperty(True) 56 """Only invoke items when double clicking.""" 57 _label_kwargs = kv.DictProperty() 58 59 def __init__(self, **kwargs): 60 """See class documentation for details.""" 61 super().__init__(**kwargs) 62 self._rects = [] 63 self._scroll = 0 64 self.items = self.items or ["placeholder"] 65 self._refresh_label_kwargs() 66 self._create_other_graphics() 67 self._refresh_graphics() 68 self._refresh_selection_graphics() 69 self.register_event_type("on_invoke") 70 self.bind( 71 focus=self._refresh_colors, 72 items=self._on_items, 73 scroll=self._on_scroll, 74 size=self._on_geometry, 75 pos=self._on_geometry, 76 selection=self._on_selection, 77 bg_color=self._refresh_colors, 78 text_color=self._refresh_label_kwargs, 79 selection_color=self._refresh_colors, 80 scroll_color=self._refresh_colors, 81 item_height=self._refresh_label_kwargs, 82 item_padding=self._refresh_label_kwargs, 83 font_name=self._refresh_label_kwargs, 84 font_size=self._refresh_label_kwargs, 85 shorten=self._refresh_label_kwargs, 86 shorten_from=self._refresh_label_kwargs, 87 _label_kwargs=self._on_label_kwargs, 88 ) 89 90 def _on_items(self, w, items): 91 assert len(self.items) > 0 92 self.select() 93 self._refresh_items() 94 self._refresh_scroll_indicator() 95 96 def _on_scroll(self, *args): 97 self._refresh_items() 98 self._refresh_scroll_indicator() 99 100 def select(self, index: Optional[int] = None, /, *, delta: int = 0): 101 """Select an item at index and/or index delta.""" 102 if index is None: 103 index = self.selection 104 index += delta 105 index = max(0, min(index, len(self.items) - 1)) 106 self.selection = index 107 108 def set_scroll(self, index: Optional[int] = None, /, *, delta: int = 0): 109 """Scroll to item at index and/or index delta.""" 110 if index is None: 111 index = self.scroll 112 index += delta 113 self.scroll = index 114 115 def on_subtheme(self, subtheme): 116 """Apply colors.""" 117 self.bg_color = subtheme.bg.rgba 118 self.text_color = subtheme.fg.rgba 119 self.selection_color = subtheme.accent1.modified_alpha(0.5).rgba 120 self.scroll_color = subtheme.accent2.modified_alpha(0.5).rgba 121 122 def _on_selection(self, *args): 123 self.set_scroll() 124 self._refresh_selection_graphics() 125 126 def on_touch_down(self, touch): 127 """Handle invoking items.""" 128 r = super().on_touch_down(touch) 129 if not self.collide_point(*touch.pos): 130 return r 131 disable_invoke = False 132 rel_pos = self.to_widget(*touch.pos) 133 idx = int((self.height - rel_pos[1]) // self.item_height) 134 if idx >= len(self.items): 135 idx = len(self.items) - 1 136 disable_invoke = True 137 self.select(idx) 138 if idx == self.selection: 139 disable_by_dtap = self.invoke_double_tap_only and not touch.is_double_tap 140 if not disable_invoke and not disable_by_dtap: 141 self.invoke() 142 return True 143 144 def _refresh_selection_graphics(self, *args): 145 idx = self.selection 146 offset = idx - self.scroll 147 self._selection_rect.pos = self._get_rect_pos(offset) 148 self._selection_rect.size = self._rects[offset].size 149 150 def _refresh_scroll_indicator(self, *args): 151 scroll = self.scroll 152 rect_count = len(self._rects) 153 item_count = len(self.items) 154 widget_height = self.height 155 indicator_width = self.scroll_width 156 # Find relative scroll of indicator top and bottom edges 157 indicator_rel_top = 1 - scroll / item_count 158 indicator_rel_height = min(rect_count, item_count) / item_count 159 indicator_rel_bot = max(0, indicator_rel_top - indicator_rel_height) 160 # Adjust based on widget size 161 indicator_height = widget_height * indicator_rel_height 162 indicator_y = widget_height * indicator_rel_bot 163 indicator_x = self.width - indicator_width 164 # Sometimes we are asked to refresh when not all properties have finished 165 # updating, let's gracefully handle that by pretending we see the entire list 166 broken_geometry = ( 167 (not 0 <= indicator_rel_bot <= indicator_rel_top <= 1) 168 or (not 0 <= indicator_rel_height <= 1) 169 ) 170 if broken_geometry: 171 indicator_height = widget_height 172 indicator_y = 0 173 self._scroll_indicator.pos = indicator_x, indicator_y 174 self._scroll_indicator.size = indicator_width, indicator_height 175 176 def _on_geometry(self, *args): 177 self._bg.size = self.size 178 self._refresh_label_kwargs() 179 self._refresh_graphics() 180 181 def _create_other_graphics(self, *args): 182 with self.canvas.before: 183 self._bg_color = kv.Color(*self.bg_color) 184 self._bg = kv.Rectangle(size=(50, 50)) 185 kv.Color() 186 self._selection_rect_color = kv.Color(*self.selection_color) 187 self._selection_rect = kv.BorderImage( 188 source=SELECTION_SOURCE, 189 size=(50, 50), 190 ) 191 self._scroll_indicator_color = kv.Color(*self.scroll_color) 192 self._scroll_indicator = kv.Rectangle() 193 kv.Color() 194 self._refresh_colors() 195 196 def _refresh_colors(self, *args): 197 self._bg_color.rgba = XColor(*self.bg_color).rgba 198 self._scroll_indicator_color.rgba = XColor(*self.scroll_color).rgba 199 selection = XColor(*self.selection_color) 200 self._selection_rect_color.rgba = selection.rgba 201 self._selection_rect_color.a *= SELECTION_ALPHA[int(self.focus)] 202 203 def _refresh_graphics(self, *args): 204 self.canvas.clear() 205 height = self.height 206 item_height = self.item_height 207 rect_count = max(1, int(height / item_height)) 208 size = self.width, item_height 209 self._rects = [] 210 append = self._rects.append 211 with self.canvas: 212 for i in range(rect_count): 213 pos = self._get_rect_pos(i) 214 rect = kv.Rectangle(size=size, pos=pos, texture=EMPTY_TEXTURE) 215 append(rect) 216 self._refresh_items() 217 self._refresh_selection_graphics() 218 self._refresh_scroll_indicator() 219 220 def _get_rect_pos(self, idx: int): 221 return 0, self.height - (self.item_height * (idx + 1)) 222 223 def _refresh_items(self, *args): 224 items = self.items 225 scroll = self.scroll 226 item_count = len(items) 227 for i, rect in enumerate(self._rects): 228 text = None 229 texture = EMPTY_TEXTURE 230 idx = i + scroll 231 if idx < item_count: 232 text = items[idx] 233 texture = self._get_texture(text) 234 rect.texture = texture 235 236 def _on_label_kwargs(self, w, kwargs): 237 cache_remove("XList") 238 self._refresh_graphics() 239 240 def _refresh_label_kwargs(self, *args): 241 self._label_kwargs = dict( 242 font_name=self.font_name, 243 font_size=self.font_size, 244 text_size=(self.width, self.item_height), 245 padding=self.item_padding, 246 shorten=self.shorten, 247 shorten_from=self.shorten_from, 248 color=self.text_color, 249 valign="middle", 250 ) 251 252 def _get_texture(self, text: str): 253 texture_id = f"{text}{self._label_kwargs}" 254 texture = cache_get("XList", texture_id) 255 if texture is None: 256 label = kv.CoreMarkupLabel(text=text, **self._label_kwargs) 257 label.refresh() 258 texture = label.texture 259 cache_append("XList", texture_id, texture) 260 return texture 261 262 def _get_scroll(self): 263 return self._scroll 264 265 def _set_scroll(self, scroll): 266 selection = self.selection 267 line_count = len(self._rects) 268 min_scroll = max(0, selection - line_count + 1) 269 max_scroll = min(selection, len(self.items) - line_count) 270 new_scroll = max(min_scroll, min(scroll, max_scroll)) 271 is_new_scroll = new_scroll != self._scroll 272 self._scroll = new_scroll 273 return is_new_scroll 274 275 scroll = kv.AliasProperty(_get_scroll, _set_scroll) 276 277 def invoke(self, *args, index: Optional[int] = None): 278 """Invoke an item (as if it were selected and clicked).""" 279 if index is None: 280 index = self.selection 281 self.dispatch("on_invoke", index, self.items[index]) 282 283 def on_invoke(self, index: int, label: str): 284 """Triggered when an item was invoked.""" 285 pass 286 287 def keyboard_on_key_down(self, w, key_pair, text, mods): 288 """Handle key presses for navigation and invocation.""" 289 keycode, key = key_pair 290 if key in {"up", "down", "pageup", "pagedown"}: 291 self._handle_arrow_key(key, mods) 292 elif key.isnumeric(): 293 try: 294 index = int(key) 295 except ValueError: 296 index = None 297 if index is not None: 298 self.select(index) 299 self.invoke() 300 elif key in {"enter", "numpadenter"}: 301 self.invoke() 302 else: 303 return super().keyboard_on_key_down(w, key_pair, text, mods) 304 305 def _handle_arrow_key(self, key, mods): 306 mods = set(mods) - {"numpad"} 307 is_up = key.endswith("up") 308 is_down = key.endswith("down") 309 is_paging = key.startswith("page") 310 is_ctrl = "ctrl" in mods 311 is_shift = "shift" in mods 312 item_count = len(self.items) 313 paging_size = max(2, self.paging_size or int(len(self._rects) / 2)) 314 delta = item_count if is_ctrl else paging_size if is_paging else 1 315 select = 0 316 shift = 0 317 if is_up: 318 select = -delta 319 elif is_down: 320 select = delta 321 if self.enable_shifting and is_shift: 322 shift = select 323 self.shift(delta=shift) 324 self.select(delta=select) 325 326 def shift(self, delta: int, index: Optional[int] = None): 327 """Shift an item at index by delta positions (default to currently selected).""" 328 if delta == 0: 329 return 330 if index is None: 331 index = self.selection 332 items = list(self.items) 333 moving = items.pop(index) 334 new_index = index + delta 335 new_index = max(0, min(new_index, len(items))) 336 items.insert(new_index, moving) 337 self.items = items 338 339 340__all__ = ( 341 "XList", 342)
23class XList(XThemed, XFocusBehavior, XRelative): 24 """Text list widget.""" 25 26 font_name = kv.StringProperty("Roboto") 27 """Text font.""" 28 font_size = kv.NumericProperty(16) 29 """Text font size.""" 30 item_height = kv.NumericProperty(35) 31 """Item height size.""" 32 item_padding = kv.ListProperty([10, 5]) 33 """Item padding.""" 34 items = kv.ListProperty() 35 """Items in list.""" 36 selection = kv.NumericProperty(0) 37 """Currently selected item index.""" 38 paging_size = kv.NumericProperty(None) 39 """Number of items to move when paging (e.g. page up/down).""" 40 scroll_width = kv.NumericProperty(5) 41 """Scroll bar width.""" 42 scroll_color = kv.ColorProperty([0.5, 0.5, 0.5, 0.5]) 43 """Scroll bar color.""" 44 shorten = kv.BooleanProperty(True) 45 """Like `Label.shorten`.""" 46 shorten_from = kv.StringProperty("center") 47 """Like `Label.shorten_from`.""" 48 bg_color = kv.ColorProperty([0, 0, 0, 0]) 49 """Widget background.""" 50 text_color = kv.ColorProperty() 51 """Text color.""" 52 selection_color = kv.ColorProperty([1, 1, 1, 0.5]) 53 """Selection highlighting color.""" 54 enable_shifting = kv.BooleanProperty(False) 55 """Enable changing the order of items.""" 56 invoke_double_tap_only = kv.BooleanProperty(True) 57 """Only invoke items when double clicking.""" 58 _label_kwargs = kv.DictProperty() 59 60 def __init__(self, **kwargs): 61 """See class documentation for details.""" 62 super().__init__(**kwargs) 63 self._rects = [] 64 self._scroll = 0 65 self.items = self.items or ["placeholder"] 66 self._refresh_label_kwargs() 67 self._create_other_graphics() 68 self._refresh_graphics() 69 self._refresh_selection_graphics() 70 self.register_event_type("on_invoke") 71 self.bind( 72 focus=self._refresh_colors, 73 items=self._on_items, 74 scroll=self._on_scroll, 75 size=self._on_geometry, 76 pos=self._on_geometry, 77 selection=self._on_selection, 78 bg_color=self._refresh_colors, 79 text_color=self._refresh_label_kwargs, 80 selection_color=self._refresh_colors, 81 scroll_color=self._refresh_colors, 82 item_height=self._refresh_label_kwargs, 83 item_padding=self._refresh_label_kwargs, 84 font_name=self._refresh_label_kwargs, 85 font_size=self._refresh_label_kwargs, 86 shorten=self._refresh_label_kwargs, 87 shorten_from=self._refresh_label_kwargs, 88 _label_kwargs=self._on_label_kwargs, 89 ) 90 91 def _on_items(self, w, items): 92 assert len(self.items) > 0 93 self.select() 94 self._refresh_items() 95 self._refresh_scroll_indicator() 96 97 def _on_scroll(self, *args): 98 self._refresh_items() 99 self._refresh_scroll_indicator() 100 101 def select(self, index: Optional[int] = None, /, *, delta: int = 0): 102 """Select an item at index and/or index delta.""" 103 if index is None: 104 index = self.selection 105 index += delta 106 index = max(0, min(index, len(self.items) - 1)) 107 self.selection = index 108 109 def set_scroll(self, index: Optional[int] = None, /, *, delta: int = 0): 110 """Scroll to item at index and/or index delta.""" 111 if index is None: 112 index = self.scroll 113 index += delta 114 self.scroll = index 115 116 def on_subtheme(self, subtheme): 117 """Apply colors.""" 118 self.bg_color = subtheme.bg.rgba 119 self.text_color = subtheme.fg.rgba 120 self.selection_color = subtheme.accent1.modified_alpha(0.5).rgba 121 self.scroll_color = subtheme.accent2.modified_alpha(0.5).rgba 122 123 def _on_selection(self, *args): 124 self.set_scroll() 125 self._refresh_selection_graphics() 126 127 def on_touch_down(self, touch): 128 """Handle invoking items.""" 129 r = super().on_touch_down(touch) 130 if not self.collide_point(*touch.pos): 131 return r 132 disable_invoke = False 133 rel_pos = self.to_widget(*touch.pos) 134 idx = int((self.height - rel_pos[1]) // self.item_height) 135 if idx >= len(self.items): 136 idx = len(self.items) - 1 137 disable_invoke = True 138 self.select(idx) 139 if idx == self.selection: 140 disable_by_dtap = self.invoke_double_tap_only and not touch.is_double_tap 141 if not disable_invoke and not disable_by_dtap: 142 self.invoke() 143 return True 144 145 def _refresh_selection_graphics(self, *args): 146 idx = self.selection 147 offset = idx - self.scroll 148 self._selection_rect.pos = self._get_rect_pos(offset) 149 self._selection_rect.size = self._rects[offset].size 150 151 def _refresh_scroll_indicator(self, *args): 152 scroll = self.scroll 153 rect_count = len(self._rects) 154 item_count = len(self.items) 155 widget_height = self.height 156 indicator_width = self.scroll_width 157 # Find relative scroll of indicator top and bottom edges 158 indicator_rel_top = 1 - scroll / item_count 159 indicator_rel_height = min(rect_count, item_count) / item_count 160 indicator_rel_bot = max(0, indicator_rel_top - indicator_rel_height) 161 # Adjust based on widget size 162 indicator_height = widget_height * indicator_rel_height 163 indicator_y = widget_height * indicator_rel_bot 164 indicator_x = self.width - indicator_width 165 # Sometimes we are asked to refresh when not all properties have finished 166 # updating, let's gracefully handle that by pretending we see the entire list 167 broken_geometry = ( 168 (not 0 <= indicator_rel_bot <= indicator_rel_top <= 1) 169 or (not 0 <= indicator_rel_height <= 1) 170 ) 171 if broken_geometry: 172 indicator_height = widget_height 173 indicator_y = 0 174 self._scroll_indicator.pos = indicator_x, indicator_y 175 self._scroll_indicator.size = indicator_width, indicator_height 176 177 def _on_geometry(self, *args): 178 self._bg.size = self.size 179 self._refresh_label_kwargs() 180 self._refresh_graphics() 181 182 def _create_other_graphics(self, *args): 183 with self.canvas.before: 184 self._bg_color = kv.Color(*self.bg_color) 185 self._bg = kv.Rectangle(size=(50, 50)) 186 kv.Color() 187 self._selection_rect_color = kv.Color(*self.selection_color) 188 self._selection_rect = kv.BorderImage( 189 source=SELECTION_SOURCE, 190 size=(50, 50), 191 ) 192 self._scroll_indicator_color = kv.Color(*self.scroll_color) 193 self._scroll_indicator = kv.Rectangle() 194 kv.Color() 195 self._refresh_colors() 196 197 def _refresh_colors(self, *args): 198 self._bg_color.rgba = XColor(*self.bg_color).rgba 199 self._scroll_indicator_color.rgba = XColor(*self.scroll_color).rgba 200 selection = XColor(*self.selection_color) 201 self._selection_rect_color.rgba = selection.rgba 202 self._selection_rect_color.a *= SELECTION_ALPHA[int(self.focus)] 203 204 def _refresh_graphics(self, *args): 205 self.canvas.clear() 206 height = self.height 207 item_height = self.item_height 208 rect_count = max(1, int(height / item_height)) 209 size = self.width, item_height 210 self._rects = [] 211 append = self._rects.append 212 with self.canvas: 213 for i in range(rect_count): 214 pos = self._get_rect_pos(i) 215 rect = kv.Rectangle(size=size, pos=pos, texture=EMPTY_TEXTURE) 216 append(rect) 217 self._refresh_items() 218 self._refresh_selection_graphics() 219 self._refresh_scroll_indicator() 220 221 def _get_rect_pos(self, idx: int): 222 return 0, self.height - (self.item_height * (idx + 1)) 223 224 def _refresh_items(self, *args): 225 items = self.items 226 scroll = self.scroll 227 item_count = len(items) 228 for i, rect in enumerate(self._rects): 229 text = None 230 texture = EMPTY_TEXTURE 231 idx = i + scroll 232 if idx < item_count: 233 text = items[idx] 234 texture = self._get_texture(text) 235 rect.texture = texture 236 237 def _on_label_kwargs(self, w, kwargs): 238 cache_remove("XList") 239 self._refresh_graphics() 240 241 def _refresh_label_kwargs(self, *args): 242 self._label_kwargs = dict( 243 font_name=self.font_name, 244 font_size=self.font_size, 245 text_size=(self.width, self.item_height), 246 padding=self.item_padding, 247 shorten=self.shorten, 248 shorten_from=self.shorten_from, 249 color=self.text_color, 250 valign="middle", 251 ) 252 253 def _get_texture(self, text: str): 254 texture_id = f"{text}{self._label_kwargs}" 255 texture = cache_get("XList", texture_id) 256 if texture is None: 257 label = kv.CoreMarkupLabel(text=text, **self._label_kwargs) 258 label.refresh() 259 texture = label.texture 260 cache_append("XList", texture_id, texture) 261 return texture 262 263 def _get_scroll(self): 264 return self._scroll 265 266 def _set_scroll(self, scroll): 267 selection = self.selection 268 line_count = len(self._rects) 269 min_scroll = max(0, selection - line_count + 1) 270 max_scroll = min(selection, len(self.items) - line_count) 271 new_scroll = max(min_scroll, min(scroll, max_scroll)) 272 is_new_scroll = new_scroll != self._scroll 273 self._scroll = new_scroll 274 return is_new_scroll 275 276 scroll = kv.AliasProperty(_get_scroll, _set_scroll) 277 278 def invoke(self, *args, index: Optional[int] = None): 279 """Invoke an item (as if it were selected and clicked).""" 280 if index is None: 281 index = self.selection 282 self.dispatch("on_invoke", index, self.items[index]) 283 284 def on_invoke(self, index: int, label: str): 285 """Triggered when an item was invoked.""" 286 pass 287 288 def keyboard_on_key_down(self, w, key_pair, text, mods): 289 """Handle key presses for navigation and invocation.""" 290 keycode, key = key_pair 291 if key in {"up", "down", "pageup", "pagedown"}: 292 self._handle_arrow_key(key, mods) 293 elif key.isnumeric(): 294 try: 295 index = int(key) 296 except ValueError: 297 index = None 298 if index is not None: 299 self.select(index) 300 self.invoke() 301 elif key in {"enter", "numpadenter"}: 302 self.invoke() 303 else: 304 return super().keyboard_on_key_down(w, key_pair, text, mods) 305 306 def _handle_arrow_key(self, key, mods): 307 mods = set(mods) - {"numpad"} 308 is_up = key.endswith("up") 309 is_down = key.endswith("down") 310 is_paging = key.startswith("page") 311 is_ctrl = "ctrl" in mods 312 is_shift = "shift" in mods 313 item_count = len(self.items) 314 paging_size = max(2, self.paging_size or int(len(self._rects) / 2)) 315 delta = item_count if is_ctrl else paging_size if is_paging else 1 316 select = 0 317 shift = 0 318 if is_up: 319 select = -delta 320 elif is_down: 321 select = delta 322 if self.enable_shifting and is_shift: 323 shift = select 324 self.shift(delta=shift) 325 self.select(delta=select) 326 327 def shift(self, delta: int, index: Optional[int] = None): 328 """Shift an item at index by delta positions (default to currently selected).""" 329 if delta == 0: 330 return 331 if index is None: 332 index = self.selection 333 items = list(self.items) 334 moving = items.pop(index) 335 new_index = index + delta 336 new_index = max(0, min(new_index, len(items))) 337 items.insert(new_index, moving) 338 self.items = items
Text list widget.
XList(**kwargs)
60 def __init__(self, **kwargs): 61 """See class documentation for details.""" 62 super().__init__(**kwargs) 63 self._rects = [] 64 self._scroll = 0 65 self.items = self.items or ["placeholder"] 66 self._refresh_label_kwargs() 67 self._create_other_graphics() 68 self._refresh_graphics() 69 self._refresh_selection_graphics() 70 self.register_event_type("on_invoke") 71 self.bind( 72 focus=self._refresh_colors, 73 items=self._on_items, 74 scroll=self._on_scroll, 75 size=self._on_geometry, 76 pos=self._on_geometry, 77 selection=self._on_selection, 78 bg_color=self._refresh_colors, 79 text_color=self._refresh_label_kwargs, 80 selection_color=self._refresh_colors, 81 scroll_color=self._refresh_colors, 82 item_height=self._refresh_label_kwargs, 83 item_padding=self._refresh_label_kwargs, 84 font_name=self._refresh_label_kwargs, 85 font_size=self._refresh_label_kwargs, 86 shorten=self._refresh_label_kwargs, 87 shorten_from=self._refresh_label_kwargs, 88 _label_kwargs=self._on_label_kwargs, 89 )
See class documentation for details.
def
select(self, index: Optional[int] = None, /, *, delta: int = 0):
101 def select(self, index: Optional[int] = None, /, *, delta: int = 0): 102 """Select an item at index and/or index delta.""" 103 if index is None: 104 index = self.selection 105 index += delta 106 index = max(0, min(index, len(self.items) - 1)) 107 self.selection = index
Select an item at index and/or index delta.
def
set_scroll(self, index: Optional[int] = None, /, *, delta: int = 0):
109 def set_scroll(self, index: Optional[int] = None, /, *, delta: int = 0): 110 """Scroll to item at index and/or index delta.""" 111 if index is None: 112 index = self.scroll 113 index += delta 114 self.scroll = index
Scroll to item at index and/or index delta.
def
on_subtheme(self, subtheme):
116 def on_subtheme(self, subtheme): 117 """Apply colors.""" 118 self.bg_color = subtheme.bg.rgba 119 self.text_color = subtheme.fg.rgba 120 self.selection_color = subtheme.accent1.modified_alpha(0.5).rgba 121 self.scroll_color = subtheme.accent2.modified_alpha(0.5).rgba
Apply colors.
def
on_touch_down(self, touch):
127 def on_touch_down(self, touch): 128 """Handle invoking items.""" 129 r = super().on_touch_down(touch) 130 if not self.collide_point(*touch.pos): 131 return r 132 disable_invoke = False 133 rel_pos = self.to_widget(*touch.pos) 134 idx = int((self.height - rel_pos[1]) // self.item_height) 135 if idx >= len(self.items): 136 idx = len(self.items) - 1 137 disable_invoke = True 138 self.select(idx) 139 if idx == self.selection: 140 disable_by_dtap = self.invoke_double_tap_only and not touch.is_double_tap 141 if not disable_invoke and not disable_by_dtap: 142 self.invoke() 143 return True
Handle invoking items.
scroll
AliasProperty(getter, setter=None, rebind=False, watch_before_use=True, **kwargs) Create a property with a custom getter and setter.
If you don't find a Property class that fits to your needs, you can make
your own by creating custom Python getter and setter methods.
Example from kivy/uix/widget.py where `x` and `width` are instances of
`NumericProperty`::
def get_right(self):
return self.x + self.width
def set_right(self, value):
self.x = value - self.width
right = AliasProperty(get_right, set_right, bind=['x', 'width'])
If `x` were a non Kivy property then you have to return `True` from setter
to dispatch new value of `right`::
def set_right(self, value):
self.x = value - self.width
return True
Usually `bind` list should contain all Kivy properties used in getter
method. If you return `True` it will cause a dispatch which one should do
when the property value has changed, but keep in mind that the property
could already have dispatched the changed value if a kivy property the
alias property is bound was set in the setter, causing a second dispatch
if the setter returns `True`.
If you want to cache the value returned by getter then pass `cache=True`.
This way getter will only be called if new value is set or one of the
binded properties changes. In both cases new value of alias property will
be cached again.
To make property readonly pass `None` as setter. This way `AttributeError`
will be raised on every set attempt::
right = AliasProperty(get_right, None, bind=['x', 'width'], cache=True)
:Parameters:
`getter`: function
Function to use as a property getter.
`setter`: function
Function to use as a property setter. Callbacks bound to the
alias property won't be called when the property is set (e.g.
`right = 10`), unless the setter returns `True`.
`bind`: list/tuple
Properties to observe for changes as property name strings.
Changing values of this properties will dispatch value of the
alias property.
`cache`: boolean
If `True`, the value will be cached until one of the binded
elements changes or if setter returns `True`.
`rebind`: bool, defaults to `False`
See `ObjectProperty` for details.
`watch_before_use`: bool, defaults to ``True``
Whether the ``bind`` properties are tracked (bound) before this
property is used in any way.
By default, the getter is called if the ``bind`` properties update
or if the property value (unless cached) is read. As an
optimization to speed up widget creation, when ``watch_before_use``
is False, we only track the bound properties once this property is
used in any way (i.e. it is bound, it was set/read, etc).
The property value read/set/bound will be correct as expected in
both cases. The difference is only that when ``False``, any side
effects from the ``getter`` would not occur until this property is
interacted with in any way because the ``getter`` won't be called
early.
*Changed in version 1.9.0:*
`rebind` has been introduced.
*Changed in version 1.4.0:*
Parameter `cache` added.
def
invoke(self, *args, index: Optional[int] = None):
278 def invoke(self, *args, index: Optional[int] = None): 279 """Invoke an item (as if it were selected and clicked).""" 280 if index is None: 281 index = self.selection 282 self.dispatch("on_invoke", index, self.items[index])
Invoke an item (as if it were selected and clicked).
def
on_invoke(self, index: int, label: str):
284 def on_invoke(self, index: int, label: str): 285 """Triggered when an item was invoked.""" 286 pass
Triggered when an item was invoked.
def
keyboard_on_key_down(self, w, key_pair, text, mods):
288 def keyboard_on_key_down(self, w, key_pair, text, mods): 289 """Handle key presses for navigation and invocation.""" 290 keycode, key = key_pair 291 if key in {"up", "down", "pageup", "pagedown"}: 292 self._handle_arrow_key(key, mods) 293 elif key.isnumeric(): 294 try: 295 index = int(key) 296 except ValueError: 297 index = None 298 if index is not None: 299 self.select(index) 300 self.invoke() 301 elif key in {"enter", "numpadenter"}: 302 self.invoke() 303 else: 304 return super().keyboard_on_key_down(w, key_pair, text, mods)
Handle key presses for navigation and invocation.
def
shift(self, delta: int, index: Optional[int] = None):
327 def shift(self, delta: int, index: Optional[int] = None): 328 """Shift an item at index by delta positions (default to currently selected).""" 329 if delta == 0: 330 return 331 if index is None: 332 index = self.selection 333 items = list(self.items) 334 moving = items.pop(index) 335 new_index = index + delta 336 new_index = max(0, min(new_index, len(items))) 337 items.insert(new_index, moving) 338 self.items = items
Shift an item at index by delta positions (default to currently selected).
Inherited Members
- kivy.uix.behaviors.focus.FocusBehavior
- ignored_touch
- keyboard
- is_focusable
- focus
- focused
- keyboard_suggestions
- focus_next
- focus_previous
- keyboard_mode
- input_type
- unfocus_on_touch
- keyboard_on_textinput
- get_focus_next
- get_focus_previous
- show_keyboard
- hide_keyboard
- kivy.uix.relativelayout.RelativeLayout
- do_layout
- to_parent
- to_local
- on_motion
- on_touch_move
- on_touch_up
- kivy.uix.floatlayout.FloatLayout
- add_widget
- remove_widget
- kivy.uix.layout.Layout
- layout_hint_with_bounds
- kivy.uix.widget.Widget
- proxy_ref
- apply_class_lang_rules
- collide_point
- collide_widget
- on_kv_post
- clear_widgets
- register_for_motion_event
- unregister_for_motion_event
- export_to_png
- export_as_image
- get_root_window
- get_parent_window
- walk
- walk_reverse
- to_widget
- to_window
- get_window_matrix
- x
- y
- width
- height
- pos
- size
- get_right
- set_right
- right
- get_top
- set_top
- top
- get_center_x
- set_center_x
- center_x
- get_center_y
- set_center_y
- center_y
- center
- cls
- children
- parent
- size_hint_x
- size_hint_y
- size_hint
- pos_hint
- size_hint_min_x
- size_hint_min_y
- size_hint_min
- size_hint_max_x
- size_hint_max_y
- size_hint_max
- ids
- opacity
- on_opacity
- canvas
- get_disabled
- set_disabled
- inc_disabled
- dec_disabled
- disabled
- motion_filter
- kivy._event.EventDispatcher
- register_event_type
- unregister_event_types
- unregister_event_type
- is_event_type
- bind
- unbind
- fbind
- funbind
- unbind_uid
- get_property_observers
- events
- dispatch
- dispatch_generic
- dispatch_children
- setter
- getter
- property
- properties
- create_property
- apply_property