119 lines
2.8 KiB
Python
119 lines
2.8 KiB
Python
|
|
# 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
|