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
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)
|
|
|