from kivy.uix.gridlayout import GridLayout from kivy.uix.button import Button from kivy.uix.behaviors import CompoundSelectionBehavior from kivy.uix.behaviors import FocusBehavior from kivy.app import runTouchApp class SelectableGrid(FocusBehavior, CompoundSelectionBehavior, GridLayout): def __init__(self, **kwargs): super(SelectableGrid, self).__init__(**kwargs) def print_selection(*l): print('selected: ', [x.text for x in self.selected_nodes]) self.bind(selected_nodes=print_selection) def keyboard_on_key_down(self, window, keycode, text, modifiers): if super(SelectableGrid, self).keyboard_on_key_down( window, keycode, text, modifiers): return True if self.select_with_key_down(window, keycode, text, modifiers): return True return False def keyboard_on_key_up(self, window, keycode): if super(SelectableGrid, self).keyboard_on_key_up(window, keycode): return True if self.select_with_key_up(window, keycode): return True return False def goto_node(self, key, last_node, last_node_idx): ''' This function is used to go to the node by typing the number of the text of the button. ''' node, idx = super(SelectableGrid, self).goto_node(key, last_node, last_node_idx) if node != last_node: return node, idx items = list(enumerate(self.get_selectable_nodes())) '''If self.nodes_order_reversed (the default due to using self.children which is reversed), the index is counted from the starts of the selectable nodes, like normal but the nodes are traversed in the reverse order. ''' # start searching after the last selected node if not self.nodes_order_reversed: items = items[last_node_idx + 1:] + items[:last_node_idx + 1] else: items = items[:last_node_idx][::-1] + items[last_node_idx:][::-1] for i, child in items: if child.text.startswith(key): return child, i return node, idx def select_node(self, node): node.background_color = (1, 0, 0, 1) return super(SelectableGrid, self).select_node(node) def deselect_node(self, node): node.background_color = (1, 1, 1, 1) super(SelectableGrid, self).deselect_node(node) def do_touch(self, instance, touch): if ('button' in touch.profile and touch.button in ('scrollup', 'scrolldown', 'scrollleft', 'scrollright')) or\ instance.collide_point(*touch.pos): self.select_with_touch(instance, touch) else: return False return True root = SelectableGrid(cols=5, up_count=5, multiselect=True, scroll_count=1) for i in range(40): c = Button(text=str(i)) c.bind(on_touch_down=root.do_touch) root.add_widget(c) runTouchApp(root)