events.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. from dataclasses import dataclass
  2. from inspect import Parameter, signature
  3. from typing import TYPE_CHECKING, Any, Awaitable, BinaryIO, Callable, Dict, List, Optional
  4. from . import background_tasks, globals
  5. from .helpers import KWONLY_SLOTS
  6. if TYPE_CHECKING:
  7. from .client import Client
  8. from .element import Element
  9. @dataclass(**KWONLY_SLOTS)
  10. class EventArguments:
  11. sender: 'Element'
  12. client: 'Client'
  13. @dataclass(**KWONLY_SLOTS)
  14. class GenericEventArguments(EventArguments):
  15. args: Dict[str, Any]
  16. def __getitem__(self, key: str) -> Any:
  17. if key == 'args':
  18. globals.log.warning('msg["args"] is deprecated, use e.args instead '
  19. '(see https://github.com/zauberzeug/nicegui/pull/1095)') # DEPRECATED
  20. return self.args
  21. raise KeyError(key)
  22. @dataclass(**KWONLY_SLOTS)
  23. class ClickEventArguments(EventArguments):
  24. pass
  25. @dataclass(**KWONLY_SLOTS)
  26. class SceneClickHit:
  27. object_id: str
  28. object_name: str
  29. x: float
  30. y: float
  31. z: float
  32. @dataclass(**KWONLY_SLOTS)
  33. class SceneClickEventArguments(ClickEventArguments):
  34. click_type: str
  35. button: int
  36. alt: bool
  37. ctrl: bool
  38. meta: bool
  39. shift: bool
  40. hits: List[SceneClickHit]
  41. @dataclass(**KWONLY_SLOTS)
  42. class ColorPickEventArguments(EventArguments):
  43. color: str
  44. @dataclass(**KWONLY_SLOTS)
  45. class MouseEventArguments(EventArguments):
  46. type: str
  47. image_x: float
  48. image_y: float
  49. button: int
  50. buttons: int
  51. alt: bool
  52. ctrl: bool
  53. meta: bool
  54. shift: bool
  55. @dataclass(**KWONLY_SLOTS)
  56. class JoystickEventArguments(EventArguments):
  57. action: str
  58. x: Optional[float] = None
  59. y: Optional[float] = None
  60. @dataclass(**KWONLY_SLOTS)
  61. class UploadEventArguments(EventArguments):
  62. content: BinaryIO
  63. name: str
  64. type: str
  65. @dataclass(**KWONLY_SLOTS)
  66. class ValueChangeEventArguments(EventArguments):
  67. value: Any
  68. @dataclass(**KWONLY_SLOTS)
  69. class TableSelectionEventArguments(EventArguments):
  70. selection: List[Any]
  71. @dataclass(**KWONLY_SLOTS)
  72. class KeyboardAction:
  73. keydown: bool
  74. keyup: bool
  75. repeat: bool
  76. @dataclass(**KWONLY_SLOTS)
  77. class KeyboardModifiers:
  78. alt: bool
  79. ctrl: bool
  80. meta: bool
  81. shift: bool
  82. @dataclass(**KWONLY_SLOTS)
  83. class KeyboardKey:
  84. name: str
  85. code: str
  86. location: int
  87. def __eq__(self, other: object) -> bool:
  88. if isinstance(other, str):
  89. return self.name == other or self.code == other
  90. elif isinstance(other, KeyboardKey):
  91. return self == other
  92. else:
  93. return False
  94. def __repr__(self):
  95. return str(self.name)
  96. @property
  97. def is_cursorkey(self):
  98. return self.code.startswith('Arrow')
  99. @property
  100. def number(self) -> Optional[int]:
  101. """Integer value of a number key."""
  102. return int(self.code.removeprefix('Digit')) if self.code.startswith('Digit') else None
  103. @property
  104. def backspace(self) -> bool:
  105. return self.name == 'Backspace'
  106. @property
  107. def tab(self) -> bool:
  108. return self.name == 'Tab'
  109. @property
  110. def enter(self) -> bool:
  111. return self.name == 'enter'
  112. @property
  113. def shift(self) -> bool:
  114. return self.name == 'Shift'
  115. @property
  116. def control(self) -> bool:
  117. return self.name == 'Control'
  118. @property
  119. def alt(self) -> bool:
  120. return self.name == 'Alt'
  121. @property
  122. def pause(self) -> bool:
  123. return self.name == 'Pause'
  124. @property
  125. def caps_lock(self) -> bool:
  126. return self.name == 'CapsLock'
  127. @property
  128. def escape(self) -> bool:
  129. return self.name == 'Escape'
  130. @property
  131. def space(self) -> bool:
  132. return self.name == 'Space'
  133. @property
  134. def page_up(self) -> bool:
  135. return self.name == 'PageUp'
  136. @property
  137. def page_down(self) -> bool:
  138. return self.name == 'PageDown'
  139. @property
  140. def end(self) -> bool:
  141. return self.name == 'End'
  142. @property
  143. def home(self) -> bool:
  144. return self.name == 'Home'
  145. @property
  146. def arrow_left(self) -> bool:
  147. return self.name == 'ArrowLeft'
  148. @property
  149. def arrow_up(self) -> bool:
  150. return self.name == 'ArrowUp'
  151. @property
  152. def arrow_right(self) -> bool:
  153. return self.name == 'ArrowRight'
  154. @property
  155. def arrow_down(self) -> bool:
  156. return self.name == 'ArrowDown'
  157. @property
  158. def print_screen(self) -> bool:
  159. return self.name == 'PrintScreen'
  160. @property
  161. def insert(self) -> bool:
  162. return self.name == 'Insert'
  163. @property
  164. def delete(self) -> bool:
  165. return self.name == 'Delete'
  166. @property
  167. def meta(self) -> bool:
  168. return self.name == 'Meta'
  169. @property
  170. def f1(self) -> bool:
  171. return self.name == 'F1'
  172. @property
  173. def f2(self) -> bool:
  174. return self.name == 'F2'
  175. @property
  176. def f3(self) -> bool:
  177. return self.name == 'F3'
  178. @property
  179. def f4(self) -> bool:
  180. return self.name == 'F4'
  181. @property
  182. def f5(self) -> bool:
  183. return self.name == 'F5'
  184. @property
  185. def f6(self) -> bool:
  186. return self.name == 'F6'
  187. @property
  188. def f7(self) -> bool:
  189. return self.name == 'F7'
  190. @property
  191. def f8(self) -> bool:
  192. return self.name == 'F8'
  193. @property
  194. def f9(self) -> bool:
  195. return self.name == 'F9'
  196. @property
  197. def f10(self) -> bool:
  198. return self.name == 'F10'
  199. @property
  200. def f11(self) -> bool:
  201. return self.name == 'F11'
  202. @property
  203. def f12(self) -> bool:
  204. return self.name == 'F12'
  205. @dataclass(**KWONLY_SLOTS)
  206. class KeyEventArguments(EventArguments):
  207. action: KeyboardAction
  208. key: KeyboardKey
  209. modifiers: KeyboardModifiers
  210. @dataclass(**KWONLY_SLOTS)
  211. class ScrollEventArguments(EventArguments):
  212. vertical_position: float
  213. vertical_percentage: float
  214. vertical_size: float
  215. vertical_container_size: float
  216. horizontal_position: float
  217. horizontal_percentage: float
  218. horizontal_size: float
  219. horizontal_container_size: float
  220. def handle_event(handler: Optional[Callable[..., Any]], arguments: EventArguments) -> None:
  221. if handler is None:
  222. return
  223. try:
  224. expects_arguments = any(p.default is Parameter.empty and
  225. p.kind is not Parameter.VAR_POSITIONAL and
  226. p.kind is not Parameter.VAR_KEYWORD
  227. for p in signature(handler).parameters.values())
  228. if arguments.sender.is_ignoring_events:
  229. return
  230. parent_slot = arguments.sender.parent_slot
  231. assert parent_slot is not None
  232. with parent_slot:
  233. result = handler(arguments) if expects_arguments else handler()
  234. if isinstance(result, Awaitable):
  235. async def wait_for_result():
  236. with parent_slot:
  237. try:
  238. await result
  239. except Exception as e:
  240. globals.handle_exception(e)
  241. if globals.loop and globals.loop.is_running():
  242. background_tasks.create(wait_for_result(), name=str(handler))
  243. else:
  244. globals.app.on_startup(wait_for_result())
  245. except Exception as e:
  246. globals.handle_exception(e)