# totally not stolen from my code for my chat app Portal ;) from __future__ import annotations from textual.screen import ModalScreen from textual.containers import Container from textual.widgets import Static from textual.geometry import Offset from textual.message import Message from textual.visual import VisualType from textual import on, events class NoSelectStatic(Static): """This class is used in window.py and windowbar.py to create buttons.""" @property def allow_select(self) -> bool: return False class ButtonStatic(NoSelectStatic): """This class is used in window.py, windowbar.py, and switcher.py to create buttons.""" class Pressed(Message): def __init__(self, button: ButtonStatic) -> None: super().__init__() self.button = button @property def control(self) -> ButtonStatic: return self.button def __init__( self, content: VisualType = "", *, expand: bool = False, shrink: bool = False, markup: bool = True, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, ) -> None: super().__init__( content=content, expand=expand, shrink=shrink, markup=markup, name=name, id=id, classes=classes, disabled=disabled, ) self.click_started_on: bool = False def on_mouse_down(self, event: events.MouseDown) -> None: self.add_class("pressed") self.click_started_on = True def on_mouse_up(self, event: events.MouseUp) -> None: self.remove_class("pressed") if self.click_started_on: self.post_message(self.Pressed(self)) self.click_started_on = False def on_leave(self, event: events.Leave) -> None: self.remove_class("pressed") self.click_started_on = False class ContextMenu(ModalScreen): DEFAULT_CSS = """ ContextMenu { background: $background 0%; align: left top; } #menu_container { padding: 0 1; background: $surface; width: 21; border: hkey $panel; & > ButtonStatic { content-align: left middle; &:hover { background: $panel-lighten-2; } &.pressed { background: $primary; } } } """ def __init__(self, options: list[str], offset: Offset): super().__init__() self.options = options self.mouse_offset = offset def on_mouse_up(self, event: events.MouseUp): if not self.query_one("#menu_container").region.contains(event.screen_x, event.screen_y): self.dismiss(None) @on(ButtonStatic.Pressed) async def thingy(self, event: ButtonStatic.Pressed): self.dismiss(event.button.content) def compose(self): with Container(id="menu_container"): for option in self.options: if isinstance(option, str): yield ButtonStatic(option) else: yield option def on_mount(self): menu_container = self.query_one("#menu_container") menu_container.styles.height = len(menu_container.children) + 2 menu_container.offset = self.mouse_offset