kannadaLettersClassification
/
env
/lib
/python3.12
/site-packages
/prompt_toolkit
/widgets
/menus.py
| from __future__ import annotations | |
| from typing import Callable, Iterable, Sequence | |
| from prompt_toolkit.application.current import get_app | |
| from prompt_toolkit.filters import Condition | |
| from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple, StyleAndTextTuples | |
| from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase | |
| from prompt_toolkit.key_binding.key_processor import KeyPressEvent | |
| from prompt_toolkit.keys import Keys | |
| from prompt_toolkit.layout.containers import ( | |
| AnyContainer, | |
| ConditionalContainer, | |
| Container, | |
| Float, | |
| FloatContainer, | |
| HSplit, | |
| Window, | |
| ) | |
| from prompt_toolkit.layout.controls import FormattedTextControl | |
| from prompt_toolkit.mouse_events import MouseEvent, MouseEventType | |
| from prompt_toolkit.utils import get_cwidth | |
| from prompt_toolkit.widgets import Shadow | |
| from .base import Border | |
| __all__ = [ | |
| "MenuContainer", | |
| "MenuItem", | |
| ] | |
| E = KeyPressEvent | |
| class MenuContainer: | |
| """ | |
| :param floats: List of extra Float objects to display. | |
| :param menu_items: List of `MenuItem` objects. | |
| """ | |
| def __init__( | |
| self, | |
| body: AnyContainer, | |
| menu_items: list[MenuItem], | |
| floats: list[Float] | None = None, | |
| key_bindings: KeyBindingsBase | None = None, | |
| ) -> None: | |
| self.body = body | |
| self.menu_items = menu_items | |
| self.selected_menu = [0] | |
| # Key bindings. | |
| kb = KeyBindings() | |
| def in_main_menu() -> bool: | |
| return len(self.selected_menu) == 1 | |
| def in_sub_menu() -> bool: | |
| return len(self.selected_menu) > 1 | |
| # Navigation through the main menu. | |
| def _left(event: E) -> None: | |
| self.selected_menu[0] = max(0, self.selected_menu[0] - 1) | |
| def _right(event: E) -> None: | |
| self.selected_menu[0] = min( | |
| len(self.menu_items) - 1, self.selected_menu[0] + 1 | |
| ) | |
| def _down(event: E) -> None: | |
| self.selected_menu.append(0) | |
| def _cancel(event: E) -> None: | |
| "Leave menu." | |
| event.app.layout.focus_last() | |
| # Sub menu navigation. | |
| def _back(event: E) -> None: | |
| "Go back to parent menu." | |
| if len(self.selected_menu) > 1: | |
| self.selected_menu.pop() | |
| def _submenu(event: E) -> None: | |
| "go into sub menu." | |
| if self._get_menu(len(self.selected_menu) - 1).children: | |
| self.selected_menu.append(0) | |
| # If This item does not have a sub menu. Go up in the parent menu. | |
| elif ( | |
| len(self.selected_menu) == 2 | |
| and self.selected_menu[0] < len(self.menu_items) - 1 | |
| ): | |
| self.selected_menu = [ | |
| min(len(self.menu_items) - 1, self.selected_menu[0] + 1) | |
| ] | |
| if self.menu_items[self.selected_menu[0]].children: | |
| self.selected_menu.append(0) | |
| def _up_in_submenu(event: E) -> None: | |
| "Select previous (enabled) menu item or return to main menu." | |
| # Look for previous enabled items in this sub menu. | |
| menu = self._get_menu(len(self.selected_menu) - 2) | |
| index = self.selected_menu[-1] | |
| previous_indexes = [ | |
| i | |
| for i, item in enumerate(menu.children) | |
| if i < index and not item.disabled | |
| ] | |
| if previous_indexes: | |
| self.selected_menu[-1] = previous_indexes[-1] | |
| elif len(self.selected_menu) == 2: | |
| # Return to main menu. | |
| self.selected_menu.pop() | |
| def _down_in_submenu(event: E) -> None: | |
| "Select next (enabled) menu item." | |
| menu = self._get_menu(len(self.selected_menu) - 2) | |
| index = self.selected_menu[-1] | |
| next_indexes = [ | |
| i | |
| for i, item in enumerate(menu.children) | |
| if i > index and not item.disabled | |
| ] | |
| if next_indexes: | |
| self.selected_menu[-1] = next_indexes[0] | |
| def _click(event: E) -> None: | |
| "Click the selected menu item." | |
| item = self._get_menu(len(self.selected_menu) - 1) | |
| if item.handler: | |
| event.app.layout.focus_last() | |
| item.handler() | |
| # Controls. | |
| self.control = FormattedTextControl( | |
| self._get_menu_fragments, key_bindings=kb, focusable=True, show_cursor=False | |
| ) | |
| self.window = Window(height=1, content=self.control, style="class:menu-bar") | |
| submenu = self._submenu(0) | |
| submenu2 = self._submenu(1) | |
| submenu3 = self._submenu(2) | |
| def has_focus() -> bool: | |
| return get_app().layout.current_window == self.window | |
| self.container = FloatContainer( | |
| content=HSplit( | |
| [ | |
| # The titlebar. | |
| self.window, | |
| # The 'body', like defined above. | |
| body, | |
| ] | |
| ), | |
| floats=[ | |
| Float( | |
| xcursor=True, | |
| ycursor=True, | |
| content=ConditionalContainer( | |
| content=Shadow(body=submenu), filter=has_focus | |
| ), | |
| ), | |
| Float( | |
| attach_to_window=submenu, | |
| xcursor=True, | |
| ycursor=True, | |
| allow_cover_cursor=True, | |
| content=ConditionalContainer( | |
| content=Shadow(body=submenu2), | |
| filter=has_focus | |
| & Condition(lambda: len(self.selected_menu) >= 1), | |
| ), | |
| ), | |
| Float( | |
| attach_to_window=submenu2, | |
| xcursor=True, | |
| ycursor=True, | |
| allow_cover_cursor=True, | |
| content=ConditionalContainer( | |
| content=Shadow(body=submenu3), | |
| filter=has_focus | |
| & Condition(lambda: len(self.selected_menu) >= 2), | |
| ), | |
| ), | |
| # -- | |
| ] | |
| + (floats or []), | |
| key_bindings=key_bindings, | |
| ) | |
| def _get_menu(self, level: int) -> MenuItem: | |
| menu = self.menu_items[self.selected_menu[0]] | |
| for i, index in enumerate(self.selected_menu[1:]): | |
| if i < level: | |
| try: | |
| menu = menu.children[index] | |
| except IndexError: | |
| return MenuItem("debug") | |
| return menu | |
| def _get_menu_fragments(self) -> StyleAndTextTuples: | |
| focused = get_app().layout.has_focus(self.window) | |
| # This is called during the rendering. When we discover that this | |
| # widget doesn't have the focus anymore. Reset menu state. | |
| if not focused: | |
| self.selected_menu = [0] | |
| # Generate text fragments for the main menu. | |
| def one_item(i: int, item: MenuItem) -> Iterable[OneStyleAndTextTuple]: | |
| def mouse_handler(mouse_event: MouseEvent) -> None: | |
| hover = mouse_event.event_type == MouseEventType.MOUSE_MOVE | |
| if ( | |
| mouse_event.event_type == MouseEventType.MOUSE_DOWN | |
| or hover | |
| and focused | |
| ): | |
| # Toggle focus. | |
| app = get_app() | |
| if not hover: | |
| if app.layout.has_focus(self.window): | |
| if self.selected_menu == [i]: | |
| app.layout.focus_last() | |
| else: | |
| app.layout.focus(self.window) | |
| self.selected_menu = [i] | |
| yield ("class:menu-bar", " ", mouse_handler) | |
| if i == self.selected_menu[0] and focused: | |
| yield ("[SetMenuPosition]", "", mouse_handler) | |
| style = "class:menu-bar.selected-item" | |
| else: | |
| style = "class:menu-bar" | |
| yield style, item.text, mouse_handler | |
| result: StyleAndTextTuples = [] | |
| for i, item in enumerate(self.menu_items): | |
| result.extend(one_item(i, item)) | |
| return result | |
| def _submenu(self, level: int = 0) -> Window: | |
| def get_text_fragments() -> StyleAndTextTuples: | |
| result: StyleAndTextTuples = [] | |
| if level < len(self.selected_menu): | |
| menu = self._get_menu(level) | |
| if menu.children: | |
| result.append(("class:menu", Border.TOP_LEFT)) | |
| result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4))) | |
| result.append(("class:menu", Border.TOP_RIGHT)) | |
| result.append(("", "\n")) | |
| try: | |
| selected_item = self.selected_menu[level + 1] | |
| except IndexError: | |
| selected_item = -1 | |
| def one_item( | |
| i: int, item: MenuItem | |
| ) -> Iterable[OneStyleAndTextTuple]: | |
| def mouse_handler(mouse_event: MouseEvent) -> None: | |
| if item.disabled: | |
| # The arrow keys can't interact with menu items that are disabled. | |
| # The mouse shouldn't be able to either. | |
| return | |
| hover = mouse_event.event_type == MouseEventType.MOUSE_MOVE | |
| if ( | |
| mouse_event.event_type == MouseEventType.MOUSE_UP | |
| or hover | |
| ): | |
| app = get_app() | |
| if not hover and item.handler: | |
| app.layout.focus_last() | |
| item.handler() | |
| else: | |
| self.selected_menu = self.selected_menu[ | |
| : level + 1 | |
| ] + [i] | |
| if i == selected_item: | |
| yield ("[SetCursorPosition]", "") | |
| style = "class:menu-bar.selected-item" | |
| else: | |
| style = "" | |
| yield ("class:menu", Border.VERTICAL) | |
| if item.text == "-": | |
| yield ( | |
| style + "class:menu-border", | |
| f"{Border.HORIZONTAL * (menu.width + 3)}", | |
| mouse_handler, | |
| ) | |
| else: | |
| yield ( | |
| style, | |
| f" {item.text}".ljust(menu.width + 3), | |
| mouse_handler, | |
| ) | |
| if item.children: | |
| yield (style, ">", mouse_handler) | |
| else: | |
| yield (style, " ", mouse_handler) | |
| if i == selected_item: | |
| yield ("[SetMenuPosition]", "") | |
| yield ("class:menu", Border.VERTICAL) | |
| yield ("", "\n") | |
| for i, item in enumerate(menu.children): | |
| result.extend(one_item(i, item)) | |
| result.append(("class:menu", Border.BOTTOM_LEFT)) | |
| result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4))) | |
| result.append(("class:menu", Border.BOTTOM_RIGHT)) | |
| return result | |
| return Window(FormattedTextControl(get_text_fragments), style="class:menu") | |
| def floats(self) -> list[Float] | None: | |
| return self.container.floats | |
| def __pt_container__(self) -> Container: | |
| return self.container | |
| class MenuItem: | |
| def __init__( | |
| self, | |
| text: str = "", | |
| handler: Callable[[], None] | None = None, | |
| children: list[MenuItem] | None = None, | |
| shortcut: Sequence[Keys | str] | None = None, | |
| disabled: bool = False, | |
| ) -> None: | |
| self.text = text | |
| self.handler = handler | |
| self.children = children or [] | |
| self.shortcut = shortcut | |
| self.disabled = disabled | |
| self.selected_item = 0 | |
| def width(self) -> int: | |
| if self.children: | |
| return max(get_cwidth(c.text) for c in self.children) | |
| else: | |
| return 0 | |