events.py 6.4 KB

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