You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

396 lines
8.9 KiB

from __future__ import annotations
from contextlib import nullcontext
from dataclasses import dataclass
from inspect import Parameter, signature
from typing import TYPE_CHECKING, Any, Awaitable, BinaryIO, Callable, Dict, List, Literal, Optional, Union
from . import background_tasks, globals # pylint: disable=redefined-builtin
from .dataclasses import KWONLY_SLOTS
from .slot import Slot
if TYPE_CHECKING:
from .client import Client
from .element import Element
from .observables import ObservableDict, ObservableList, ObservableSet
@dataclass(**KWONLY_SLOTS)
class EventArguments:
pass
@dataclass(**KWONLY_SLOTS)
class ObservableChangeEventArguments(EventArguments):
sender: Union[ObservableDict, ObservableList, ObservableSet]
@dataclass(**KWONLY_SLOTS)
class UiEventArguments(EventArguments):
sender: Element
client: Client
@dataclass(**KWONLY_SLOTS)
class GenericEventArguments(UiEventArguments):
args: Any
def __getitem__(self, key: str) -> Any:
if key == 'args':
globals.log.warning('msg["args"] is deprecated, use e.args instead '
'(see https://github.com/zauberzeug/nicegui/pull/1095)') # DEPRECATED
return self.args
raise KeyError(key)
@dataclass(**KWONLY_SLOTS)
class ClickEventArguments(UiEventArguments):
pass
@dataclass(**KWONLY_SLOTS)
class ChartEventArguments(UiEventArguments):
event_type: str
@dataclass(**KWONLY_SLOTS)
class ChartPointClickEventArguments(ChartEventArguments):
series_index: int
point_index: int
point_x: float
point_y: float
@dataclass(**KWONLY_SLOTS)
class ChartPointDragStartEventArguments(ChartEventArguments):
pass
@dataclass(**KWONLY_SLOTS)
class ChartPointDragEventArguments(ChartEventArguments):
series_index: int
point_index: int
point_x: float
point_y: float
@dataclass(**KWONLY_SLOTS)
class ChartPointDropEventArguments(ChartEventArguments):
series_index: int
point_index: int
point_x: float
point_y: float
@dataclass(**KWONLY_SLOTS)
class SceneClickHit:
object_id: str
object_name: str
x: float
y: float
z: float
@dataclass(**KWONLY_SLOTS)
class SceneClickEventArguments(ClickEventArguments):
click_type: str
button: int
alt: bool
ctrl: bool
meta: bool
shift: bool
hits: List[SceneClickHit]
@dataclass(**KWONLY_SLOTS)
class SceneDragEventArguments(ClickEventArguments):
type: Literal['dragstart', 'dragend']
object_id: str
object_name: str
x: float
y: float
z: float
@dataclass(**KWONLY_SLOTS)
class ColorPickEventArguments(UiEventArguments):
color: str
@dataclass(**KWONLY_SLOTS)
class MouseEventArguments(UiEventArguments):
type: str
image_x: float
image_y: float
button: int
buttons: int
alt: bool
ctrl: bool
meta: bool
shift: bool
@dataclass(**KWONLY_SLOTS)
class JoystickEventArguments(UiEventArguments):
action: str
x: Optional[float] = None
y: Optional[float] = None
@dataclass(**KWONLY_SLOTS)
class UploadEventArguments(UiEventArguments):
content: BinaryIO
name: str
type: str
@dataclass(**KWONLY_SLOTS)
class ValueChangeEventArguments(UiEventArguments):
value: Any
@dataclass(**KWONLY_SLOTS)
class TableSelectionEventArguments(UiEventArguments):
selection: List[Any]
@dataclass(**KWONLY_SLOTS)
class KeyboardAction:
keydown: bool
keyup: bool
repeat: bool
@dataclass(**KWONLY_SLOTS)
class KeyboardModifiers:
alt: bool
ctrl: bool
meta: bool
shift: bool
@dataclass(**KWONLY_SLOTS)
class KeyboardKey:
name: str
code: str
location: int
def __eq__(self, other: object) -> bool:
if isinstance(other, str):
return other in {self.name, self.code}
if isinstance(other, KeyboardKey):
return self == other
return False
def __repr__(self):
return str(self.name)
@property
def is_cursorkey(self):
return self.code.startswith('Arrow')
@property
def number(self) -> Optional[int]:
"""Integer value of a number key."""
return int(self.code.removeprefix('Digit')) if self.code.startswith('Digit') else None
@property
def backspace(self) -> bool:
return self.name == 'Backspace'
@property
def tab(self) -> bool:
return self.name == 'Tab'
@property
def enter(self) -> bool:
return self.name == 'enter'
@property
def shift(self) -> bool:
return self.name == 'Shift'
@property
def control(self) -> bool:
return self.name == 'Control'
@property
def alt(self) -> bool:
return self.name == 'Alt'
@property
def pause(self) -> bool:
return self.name == 'Pause'
@property
def caps_lock(self) -> bool:
return self.name == 'CapsLock'
@property
def escape(self) -> bool:
return self.name == 'Escape'
@property
def space(self) -> bool:
return self.name == 'Space'
@property
def page_up(self) -> bool:
return self.name == 'PageUp'
@property
def page_down(self) -> bool:
return self.name == 'PageDown'
@property
def end(self) -> bool:
return self.name == 'End'
@property
def home(self) -> bool:
return self.name == 'Home'
@property
def arrow_left(self) -> bool:
return self.name == 'ArrowLeft'
@property
def arrow_up(self) -> bool:
return self.name == 'ArrowUp'
@property
def arrow_right(self) -> bool:
return self.name == 'ArrowRight'
@property
def arrow_down(self) -> bool:
return self.name == 'ArrowDown'
@property
def print_screen(self) -> bool:
return self.name == 'PrintScreen'
@property
def insert(self) -> bool:
return self.name == 'Insert'
@property
def delete(self) -> bool:
return self.name == 'Delete'
@property
def meta(self) -> bool:
return self.name == 'Meta'
@property
def f1(self) -> bool:
return self.name == 'F1'
@property
def f2(self) -> bool:
return self.name == 'F2'
@property
def f3(self) -> bool:
return self.name == 'F3'
@property
def f4(self) -> bool:
return self.name == 'F4'
@property
def f5(self) -> bool:
return self.name == 'F5'
@property
def f6(self) -> bool:
return self.name == 'F6'
@property
def f7(self) -> bool:
return self.name == 'F7'
@property
def f8(self) -> bool:
return self.name == 'F8'
@property
def f9(self) -> bool:
return self.name == 'F9'
@property
def f10(self) -> bool:
return self.name == 'F10'
@property
def f11(self) -> bool:
return self.name == 'F11'
@property
def f12(self) -> bool:
return self.name == 'F12'
@dataclass(**KWONLY_SLOTS)
class KeyEventArguments(UiEventArguments):
action: KeyboardAction
key: KeyboardKey
modifiers: KeyboardModifiers
@dataclass(**KWONLY_SLOTS)
class ScrollEventArguments(UiEventArguments):
vertical_position: float
vertical_percentage: float
vertical_size: float
vertical_container_size: float
horizontal_position: float
horizontal_percentage: float
horizontal_size: float
horizontal_container_size: float
@dataclass(**KWONLY_SLOTS)
class JsonEditorSelectEventArguments(UiEventArguments):
selection: Dict
@dataclass(**KWONLY_SLOTS)
class JsonEditorChangeEventArguments(UiEventArguments):
content: Dict
errors: Dict
def handle_event(handler: Optional[Callable[..., Any]], arguments: EventArguments) -> None:
if handler is None:
return
try:
expects_arguments = any(p.default is Parameter.empty and
p.kind is not Parameter.VAR_POSITIONAL and
p.kind is not Parameter.VAR_KEYWORD
for p in signature(handler).parameters.values())
parent_slot: Union[Slot, nullcontext]
if isinstance(arguments, UiEventArguments):
if arguments.sender.is_ignoring_events:
return
assert arguments.sender.parent_slot is not None
parent_slot = arguments.sender.parent_slot
else:
parent_slot = nullcontext()
with parent_slot:
result = handler(arguments) if expects_arguments else handler()
if isinstance(result, Awaitable):
async def wait_for_result():
with parent_slot:
try:
await result
except Exception as e:
globals.handle_exception(e)
if globals.loop and globals.loop.is_running():
background_tasks.create(wait_for_result(), name=str(handler))
else:
globals.app.on_startup(wait_for_result())
except Exception as e:
globals.handle_exception(e)