diff --git a/Launcher.py b/Launcher.py index 29bd7176..713c0cd3 100644 --- a/Launcher.py +++ b/Launcher.py @@ -230,10 +230,11 @@ def run_gui(path: str, args: Any) -> None: from kivy.properties import ObjectProperty from kivy.core.window import Window from kivy.metrics import dp - from kivymd.uix.button import MDIconButton + from kivymd.uix.button import MDIconButton, MDButton from kivymd.uix.card import MDCard from kivymd.uix.menu import MDDropdownMenu from kivymd.uix.snackbar import MDSnackbar, MDSnackbarText + from kivymd.uix.textfield import MDTextField from kivy.lang.builder import Builder @@ -253,6 +254,7 @@ def run_gui(path: str, args: Any) -> None: navigation: MDGridLayout = ObjectProperty(None) grid: MDGridLayout = ObjectProperty(None) button_layout: ScrollBox = ObjectProperty(None) + search_box: MDTextField = ObjectProperty(None) cards: list[LauncherCard] current_filter: Sequence[str | Type] | None @@ -338,14 +340,29 @@ def run_gui(path: str, args: Any) -> None: scroll_percent = self.button_layout.convert_distance_to_scroll(0, top) self.button_layout.scroll_y = max(0, min(1, scroll_percent[1])) - def filter_clients(self, caller): + def filter_clients_by_type(self, caller: MDButton): self._refresh_components(caller.type) + self.search_box.text = "" + + def filter_clients_by_name(self, caller: MDTextField, name: str) -> None: + if len(name) == 0: + self._refresh_components(self.current_filter) + return + + sub_matches = [ + card for card in self.cards + if name.lower() in card.component.display_name.lower() and card.component.type != Type.HIDDEN + ] + self.button_layout.layout.clear_widgets() + for card in sub_matches: + self.button_layout.layout.add_widget(card) def build(self): self.top_screen = Builder.load_file(Utils.local_path("data/launcher.kv")) self.grid = self.top_screen.ids.grid self.navigation = self.top_screen.ids.navigation self.button_layout = self.top_screen.ids.button_layout + self.search_box = self.top_screen.ids.search_box self.set_colors() self.top_screen.md_bg_color = self.theme_cls.backgroundColor @@ -353,6 +370,7 @@ def run_gui(path: str, args: Any) -> None: refresh_components = self._refresh_components Window.bind(on_drop_file=self._on_drop_file) + Window.bind(on_keyboard=self._on_keyboard) for component in components: self.cards.append(self.build_card(component)) @@ -389,6 +407,15 @@ def run_gui(path: str, args: Any) -> None: else: logging.warning(f"unable to identify component for {file}") + def _on_keyboard(self, window: Window, key: int, scancode: int, codepoint: str, modifier: list[str]): + # Activate search as soon as we start typing, no matter if we are focused on the search box or not. + # Focus first, then capture the first character we type, otherwise it gets swallowed and lost. + # Limit text input to ASCII non-control characters (space bar to tilde). + if not self.search_box.focus: + self.search_box.focus = True + if key in range(32, 126): + self.search_box.text += codepoint + def _stop(self, *largs): # ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm. # Closing the window explicitly cleans it up. diff --git a/data/launcher.kv b/data/launcher.kv index 8c6a8288..1cb4e84a 100644 --- a/data/launcher.kv +++ b/data/launcher.kv @@ -80,7 +80,7 @@ MDFloatLayout: id: all style: "text" type: (Type.CLIENT, Type.TOOL, Type.ADJUSTER, Type.MISC) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "asterisk" @@ -90,7 +90,7 @@ MDFloatLayout: id: client style: "text" type: (Type.CLIENT, ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "controller" @@ -100,7 +100,7 @@ MDFloatLayout: id: Tool style: "text" type: (Type.TOOL, ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "desktop-classic" @@ -110,7 +110,7 @@ MDFloatLayout: id: adjuster style: "text" type: (Type.ADJUSTER, ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "wrench" @@ -120,7 +120,7 @@ MDFloatLayout: id: misc style: "text" type: (Type.MISC, ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "dots-horizontal-circle-outline" @@ -131,7 +131,7 @@ MDFloatLayout: id: favorites style: "text" type: ("favorites", ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "star" @@ -141,5 +141,21 @@ MDFloatLayout: MDNavigationDrawerDivider: - ScrollBox: - id: button_layout \ No newline at end of file + MDGridLayout: + id: main_layout + cols: 1 + spacing: "10dp" + + MDTextField: + id: search_box + mode: "outlined" + set_text: app.filter_clients_by_name + + MDTextFieldLeadingIcon: + icon: "magnify" + + MDTextFieldHintText: + text: "Search" + + ScrollBox: + id: button_layout