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 | 
