events.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. from __future__ import annotations
  2. from contextlib import nullcontext
  3. from dataclasses import dataclass
  4. from inspect import Parameter, signature
  5. from typing import TYPE_CHECKING, Any, Awaitable, BinaryIO, Callable, Dict, Iterator, List, Literal, Optional, Union
  6. from . import background_tasks, core
  7. from .awaitable_response import AwaitableResponse
  8. from .dataclasses import KWONLY_SLOTS
  9. from .slot import Slot
  10. if TYPE_CHECKING:
  11. from .client import Client
  12. from .element import Element
  13. from .observables import ObservableCollection
  14. @dataclass(**KWONLY_SLOTS)
  15. class EventArguments:
  16. pass
  17. @dataclass(**KWONLY_SLOTS)
  18. class ObservableChangeEventArguments(EventArguments):
  19. sender: ObservableCollection
  20. @dataclass(**KWONLY_SLOTS)
  21. class UiEventArguments(EventArguments):
  22. sender: Element
  23. client: Client
  24. @dataclass(**KWONLY_SLOTS)
  25. class GenericEventArguments(UiEventArguments):
  26. args: Any
  27. @dataclass(**KWONLY_SLOTS)
  28. class ClickEventArguments(UiEventArguments):
  29. pass
  30. @dataclass(**KWONLY_SLOTS)
  31. class EChartPointClickEventArguments(UiEventArguments):
  32. component_type: str
  33. series_type: str
  34. series_index: int
  35. series_name: str
  36. name: str
  37. data_index: int
  38. data: Union[float, int, str]
  39. data_type: str
  40. value: Union[float, int, list]
  41. @dataclass(**KWONLY_SLOTS)
  42. class SceneClickHit:
  43. object_id: str
  44. object_name: str
  45. x: float
  46. y: float
  47. z: float
  48. @dataclass(**KWONLY_SLOTS)
  49. class SceneClickEventArguments(ClickEventArguments):
  50. click_type: str
  51. button: int
  52. alt: bool
  53. ctrl: bool
  54. meta: bool
  55. shift: bool
  56. hits: List[SceneClickHit]
  57. @dataclass(**KWONLY_SLOTS)
  58. class SceneDragEventArguments(ClickEventArguments):
  59. type: Literal['dragstart', 'dragend']
  60. object_id: str
  61. object_name: str
  62. x: float
  63. y: float
  64. z: float
  65. @dataclass(**KWONLY_SLOTS)
  66. class ColorPickEventArguments(UiEventArguments):
  67. color: str
  68. @dataclass(**KWONLY_SLOTS)
  69. class MouseEventArguments(UiEventArguments):
  70. type: str
  71. image_x: float
  72. image_y: float
  73. button: int
  74. buttons: int
  75. alt: bool
  76. ctrl: bool
  77. meta: bool
  78. shift: bool
  79. @dataclass(**KWONLY_SLOTS)
  80. class JoystickEventArguments(UiEventArguments):
  81. action: str
  82. x: Optional[float] = None
  83. y: Optional[float] = None
  84. @dataclass(**KWONLY_SLOTS)
  85. class UploadEventArguments(UiEventArguments):
  86. content: BinaryIO
  87. name: str
  88. type: str
  89. @dataclass(**KWONLY_SLOTS)
  90. class MultiUploadEventArguments(UiEventArguments):
  91. contents: List[BinaryIO]
  92. names: List[str]
  93. types: List[str]
  94. @dataclass(**KWONLY_SLOTS)
  95. class ValueChangeEventArguments(UiEventArguments):
  96. value: Any
  97. @dataclass(**KWONLY_SLOTS)
  98. class TableSelectionEventArguments(UiEventArguments):
  99. selection: List[Any]
  100. @dataclass(**KWONLY_SLOTS)
  101. class KeyboardAction:
  102. keydown: bool
  103. keyup: bool
  104. repeat: bool
  105. @dataclass(**KWONLY_SLOTS)
  106. class KeyboardModifiers:
  107. alt: bool
  108. ctrl: bool
  109. meta: bool
  110. shift: bool
  111. def __iter__(self) -> Iterator[bool]:
  112. return iter([self.alt, self.ctrl, self.meta, self.shift])
  113. def __len__(self) -> int:
  114. return sum(self)
  115. @dataclass(**KWONLY_SLOTS)
  116. class KeyboardKey:
  117. name: str
  118. code: str
  119. location: int
  120. def __eq__(self, other: object) -> bool:
  121. if isinstance(other, str):
  122. return other in {self.name, self.code}
  123. if isinstance(other, KeyboardKey):
  124. return self == other
  125. return False
  126. def __repr__(self):
  127. return str(self.name)
  128. @property
  129. def is_cursorkey(self) -> bool:
  130. """Whether the key is a cursor key (arrow keys)."""
  131. return self.code.startswith('Arrow')
  132. @property
  133. def number(self) -> Optional[int]:
  134. """Integer value of a number key."""
  135. return int(self.code[len('Digit'):]) if self.code.startswith('Digit') else None
  136. @property
  137. def backspace(self) -> bool:
  138. """Whether the key is the backspace key."""
  139. return self.name == 'Backspace'
  140. @property
  141. def tab(self) -> bool:
  142. """Whether the key is the tab key."""
  143. return self.name == 'Tab'
  144. @property
  145. def enter(self) -> bool:
  146. """Whether the key is the enter key."""
  147. return self.name == 'Enter'
  148. @property
  149. def shift(self) -> bool:
  150. """Whether the key is the shift key."""
  151. return self.name == 'Shift'
  152. @property
  153. def control(self) -> bool:
  154. """Whether the key is the control key."""
  155. return self.name == 'Control'
  156. @property
  157. def alt(self) -> bool:
  158. """Whether the key is the alt key."""
  159. return self.name == 'Alt'
  160. @property
  161. def pause(self) -> bool:
  162. """Whether the key is the pause key."""
  163. return self.name == 'Pause'
  164. @property
  165. def caps_lock(self) -> bool:
  166. """Whether the key is the caps lock key."""
  167. return self.name == 'CapsLock'
  168. @property
  169. def escape(self) -> bool:
  170. """Whether the key is the escape key."""
  171. return self.name == 'Escape'
  172. @property
  173. def space(self) -> bool:
  174. """Whether the key is the space key."""
  175. return self.name == 'Space'
  176. @property
  177. def page_up(self) -> bool:
  178. """Whether the key is the page up key."""
  179. return self.name == 'PageUp'
  180. @property
  181. def page_down(self) -> bool:
  182. """Whether the key is the page down key."""
  183. return self.name == 'PageDown'
  184. @property
  185. def end(self) -> bool:
  186. """Whether the key is the end key."""
  187. return self.name == 'End'
  188. @property
  189. def home(self) -> bool:
  190. """Whether the key is the home key."""
  191. return self.name == 'Home'
  192. @property
  193. def arrow_left(self) -> bool:
  194. """Whether the key is the arrow left key."""
  195. return self.name == 'ArrowLeft'
  196. @property
  197. def arrow_up(self) -> bool:
  198. """Whether the key is the arrow up key."""
  199. return self.name == 'ArrowUp'
  200. @property
  201. def arrow_right(self) -> bool:
  202. """Whether the key is the arrow right key."""
  203. return self.name == 'ArrowRight'
  204. @property
  205. def arrow_down(self) -> bool:
  206. """Whether the key is the arrow down key."""
  207. return self.name == 'ArrowDown'
  208. @property
  209. def print_screen(self) -> bool:
  210. """Whether the key is the print screen key."""
  211. return self.name == 'PrintScreen'
  212. @property
  213. def insert(self) -> bool:
  214. """Whether the key is the insert key."""
  215. return self.name == 'Insert'
  216. @property
  217. def delete(self) -> bool:
  218. """Whether the key is the delete key."""
  219. return self.name == 'Delete'
  220. @property
  221. def meta(self) -> bool:
  222. """Whether the key is the meta key."""
  223. return self.name == 'Meta'
  224. @property
  225. def f1(self) -> bool:
  226. """Whether the key is the F1 key."""
  227. return self.name == 'F1'
  228. @property
  229. def f2(self) -> bool:
  230. """Whether the key is the F2 key."""
  231. return self.name == 'F2'
  232. @property
  233. def f3(self) -> bool:
  234. """Whether the key is the F3 key."""
  235. return self.name == 'F3'
  236. @property
  237. def f4(self) -> bool:
  238. """Whether the key is the F4 key."""
  239. return self.name == 'F4'
  240. @property
  241. def f5(self) -> bool:
  242. """Whether the key is the F5 key."""
  243. return self.name == 'F5'
  244. @property
  245. def f6(self) -> bool:
  246. """Whether the key is the F6 key."""
  247. return self.name == 'F6'
  248. @property
  249. def f7(self) -> bool:
  250. """Whether the key is the F7 key."""
  251. return self.name == 'F7'
  252. @property
  253. def f8(self) -> bool:
  254. """Whether the key is the F8 key."""
  255. return self.name == 'F8'
  256. @property
  257. def f9(self) -> bool:
  258. """Whether the key is the F9 key."""
  259. return self.name == 'F9'
  260. @property
  261. def f10(self) -> bool:
  262. """Whether the key is the F10 key."""
  263. return self.name == 'F10'
  264. @property
  265. def f11(self) -> bool:
  266. """Whether the key is the F11 key."""
  267. return self.name == 'F11'
  268. @property
  269. def f12(self) -> bool:
  270. """Whether the key is the F12 key."""
  271. return self.name == 'F12'
  272. @dataclass(**KWONLY_SLOTS)
  273. class KeyEventArguments(UiEventArguments):
  274. action: KeyboardAction
  275. key: KeyboardKey
  276. modifiers: KeyboardModifiers
  277. @dataclass(**KWONLY_SLOTS)
  278. class ScrollEventArguments(UiEventArguments):
  279. vertical_position: float
  280. vertical_percentage: float
  281. vertical_size: float
  282. vertical_container_size: float
  283. horizontal_position: float
  284. horizontal_percentage: float
  285. horizontal_size: float
  286. horizontal_container_size: float
  287. @dataclass(**KWONLY_SLOTS)
  288. class JsonEditorSelectEventArguments(UiEventArguments):
  289. selection: Dict
  290. @dataclass(**KWONLY_SLOTS)
  291. class JsonEditorChangeEventArguments(UiEventArguments):
  292. content: Dict
  293. errors: Dict
  294. def handle_event(handler: Optional[Callable[..., Any]], arguments: EventArguments) -> None:
  295. """Call the given event handler.
  296. The handler is called within the context of the parent slot of the sender.
  297. If the handler is a coroutine, it is scheduled as a background task.
  298. If the handler expects arguments, the arguments are passed to the handler.
  299. Exceptions are caught and handled globally.
  300. :param handler: the event handler
  301. :param arguments: the event arguments
  302. """
  303. if handler is None:
  304. return
  305. try:
  306. expects_arguments = any(p.default is Parameter.empty and
  307. p.kind is not Parameter.VAR_POSITIONAL and
  308. p.kind is not Parameter.VAR_KEYWORD
  309. for p in signature(handler).parameters.values())
  310. parent_slot: Union[Slot, nullcontext]
  311. if isinstance(arguments, UiEventArguments):
  312. parent_slot = arguments.sender.parent_slot or arguments.sender.client.layout.default_slot
  313. else:
  314. parent_slot = nullcontext()
  315. with parent_slot:
  316. result = handler(arguments) if expects_arguments else handler()
  317. if isinstance(result, Awaitable) and not isinstance(result, AwaitableResponse):
  318. # NOTE: await an awaitable result even if the handler is not a coroutine (like a lambda statement)
  319. async def wait_for_result():
  320. with parent_slot:
  321. try:
  322. await result
  323. except Exception as e:
  324. core.app.handle_exception(e)
  325. if core.loop and core.loop.is_running():
  326. background_tasks.create(wait_for_result(), name=str(handler))
  327. else:
  328. core.app.on_startup(wait_for_result())
  329. except Exception as e:
  330. core.app.handle_exception(e)