[MouseFox logo]
The MouseFox Project
Join The Community Discord Server
[Discord logo]
Edit on GitHub

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.

font_name

Text font.

font_size

Text font size.

item_height

Item height size.

item_padding

Item padding.

items

Items in list.

selection

Currently selected item index.

paging_size

Number of items to move when paging (e.g. page up/down).

scroll_width

Scroll bar width.

scroll_color

Scroll bar color.

shorten

Like Label.shorten.

shorten_from

Like Label.shorten_from.

bg_color

Widget background.

text_color

Text color.

selection_color

Selection highlighting color.

enable_shifting

Enable changing the order of items.

invoke_double_tap_only

Only invoke items when double clicking.

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