rocket-welder-sdk 1.1.25__tar.gz → 1.1.26__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/PKG-INFO +2 -1
- rocket_welder_sdk-1.1.26/VERSION +1 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/pyproject.toml +5 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/external_controls/__init__.py +30 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/external_controls/contracts.py +100 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/external_controls/contracts_old.py +105 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/ui/__init__.py +48 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/ui/controls.py +362 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/ui/icons.py +21628 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/ui/ui_events_projection.py +226 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/ui/ui_service.py +358 -0
- rocket_welder_sdk-1.1.26/rocket_welder_sdk/ui/value_types.py +72 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk.egg-info/PKG-INFO +2 -1
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk.egg-info/SOURCES.txt +9 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk.egg-info/requires.txt +1 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/setup.py +2 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_external_controls_serialization.py +3 -24
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_external_controls_serialization_v2.py +4 -25
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_icons.py +3 -3
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_ui_controls.py +2 -2
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_ui_service_happy_path.py +79 -8
- rocket_welder_sdk-1.1.25/VERSION +0 -1
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/MANIFEST.in +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/README.md +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/logo.png +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk/__init__.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk/bytes_size.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk/connection_string.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk/controllers.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk/gst_metadata.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk/py.typed +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk/rocket_welder_client.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk.egg-info/dependency_links.txt +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/rocket_welder_sdk.egg-info/top_level.txt +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/setup.cfg +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_bytes_size.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_connection_string.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_controllers.py +0 -0
- {rocket_welder_sdk-1.1.25 → rocket_welder_sdk-1.1.26}/tests/test_gst_metadata.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rocket-welder-sdk
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.26
|
|
4
4
|
Summary: High-performance video streaming SDK for RocketWelder services using ZeroBuffer IPC
|
|
5
5
|
Home-page: https://github.com/modelingevolution/rocket-welder-sdk
|
|
6
6
|
Author: ModelingEvolution
|
|
@@ -29,6 +29,7 @@ Requires-Dist: numpy>=1.20.0
|
|
|
29
29
|
Requires-Dist: opencv-python>=4.5.0
|
|
30
30
|
Requires-Dist: zerobuffer-ipc>=1.1.17
|
|
31
31
|
Requires-Dist: pydantic>=2.5.0
|
|
32
|
+
Requires-Dist: py-micro-plumberd>=0.1.8
|
|
32
33
|
Provides-Extra: dev
|
|
33
34
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
34
35
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.1.26
|
|
@@ -36,6 +36,7 @@ dependencies = [
|
|
|
36
36
|
"opencv-python>=4.5.0",
|
|
37
37
|
"zerobuffer-ipc>=1.1.17",
|
|
38
38
|
"pydantic>=2.5.0",
|
|
39
|
+
"py-micro-plumberd>=0.1.8",
|
|
39
40
|
]
|
|
40
41
|
|
|
41
42
|
[project.optional-dependencies]
|
|
@@ -81,6 +82,10 @@ module = [
|
|
|
81
82
|
"cv2.*",
|
|
82
83
|
"zerobuffer",
|
|
83
84
|
"zerobuffer.*",
|
|
85
|
+
"py_micro_plumberd",
|
|
86
|
+
"py_micro_plumberd.*",
|
|
87
|
+
"esdbclient",
|
|
88
|
+
"esdbclient.*",
|
|
84
89
|
]
|
|
85
90
|
ignore_missing_imports = true
|
|
86
91
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""External Controls module for RocketWelder SDK."""
|
|
2
|
+
|
|
3
|
+
from rocket_welder_sdk.ui.value_types import ControlType
|
|
4
|
+
|
|
5
|
+
from .contracts import (
|
|
6
|
+
ArrowDirection,
|
|
7
|
+
# Events (UI → Container)
|
|
8
|
+
ButtonDown,
|
|
9
|
+
ButtonUp,
|
|
10
|
+
ChangeControls,
|
|
11
|
+
# Commands (Container → UI)
|
|
12
|
+
DefineControl,
|
|
13
|
+
DeleteControl, # Legacy alias - deprecated
|
|
14
|
+
DeleteControls,
|
|
15
|
+
KeyDown,
|
|
16
|
+
KeyUp,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"ArrowDirection",
|
|
21
|
+
"ButtonDown",
|
|
22
|
+
"ButtonUp",
|
|
23
|
+
"ChangeControls",
|
|
24
|
+
"ControlType", # Now using the single ControlType from ui.value_types
|
|
25
|
+
"DefineControl",
|
|
26
|
+
"DeleteControl", # Legacy - deprecated
|
|
27
|
+
"DeleteControls",
|
|
28
|
+
"KeyDown",
|
|
29
|
+
"KeyUp",
|
|
30
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""External Controls contracts using Pydantic v2 for modern serialization.
|
|
2
|
+
|
|
3
|
+
This module defines the contracts for external controls communication between
|
|
4
|
+
containers and the RocketWelder UI via EventStore, using Pydantic v2 for
|
|
5
|
+
proper serialization with PascalCase support.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from uuid import UUID, uuid4
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer
|
|
12
|
+
from pydantic.alias_generators import to_pascal
|
|
13
|
+
|
|
14
|
+
from rocket_welder_sdk.ui.value_types import ControlType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ArrowDirection(str, Enum):
|
|
18
|
+
"""Arrow directions for arrow grid control."""
|
|
19
|
+
|
|
20
|
+
UP = "Up"
|
|
21
|
+
DOWN = "Down"
|
|
22
|
+
LEFT = "Left"
|
|
23
|
+
RIGHT = "Right"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseContract(BaseModel):
|
|
27
|
+
"""Base class for all contracts with PascalCase serialization."""
|
|
28
|
+
|
|
29
|
+
model_config = ConfigDict(
|
|
30
|
+
# Convert snake_case fields to PascalCase for serialization
|
|
31
|
+
alias_generator=to_pascal,
|
|
32
|
+
# Allow both snake_case and PascalCase when deserializing
|
|
33
|
+
populate_by_name=True,
|
|
34
|
+
# Use Enum values for serialization
|
|
35
|
+
use_enum_values=True,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
id: UUID = Field(default_factory=uuid4)
|
|
39
|
+
|
|
40
|
+
@field_serializer("id")
|
|
41
|
+
def serialize_id(self, value: UUID) -> str:
|
|
42
|
+
"""Serialize UUID as string for JSON compatibility."""
|
|
43
|
+
return str(value)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Container → UI Commands (Stream: ExternalCommands-{SessionId})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DefineControl(BaseContract):
|
|
50
|
+
"""Command to define a new control in the UI."""
|
|
51
|
+
|
|
52
|
+
control_id: str
|
|
53
|
+
type: ControlType
|
|
54
|
+
properties: dict[str, str]
|
|
55
|
+
region_name: str
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DeleteControls(BaseContract):
|
|
59
|
+
"""Command to delete multiple controls from the UI."""
|
|
60
|
+
|
|
61
|
+
control_ids: list[str]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Legacy alias for backward compatibility (will be removed)
|
|
65
|
+
DeleteControl = DeleteControls
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ChangeControls(BaseContract):
|
|
69
|
+
"""Command to update properties of multiple controls."""
|
|
70
|
+
|
|
71
|
+
updates: dict[str, dict[str, str]] # ControlId -> {PropertyId -> Value}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# UI → Container Events (Stream: ExternalEvents-{SessionId})
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ButtonDown(BaseContract):
|
|
78
|
+
"""Event when a button is pressed down."""
|
|
79
|
+
|
|
80
|
+
control_id: str
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ButtonUp(BaseContract):
|
|
84
|
+
"""Event when a button is released."""
|
|
85
|
+
|
|
86
|
+
control_id: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class KeyDown(BaseContract):
|
|
90
|
+
"""Event when a key is pressed down."""
|
|
91
|
+
|
|
92
|
+
control_id: str
|
|
93
|
+
code: str # KeyCode value like "ArrowUp", "Enter", etc.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class KeyUp(BaseContract):
|
|
97
|
+
"""Event when a key is released."""
|
|
98
|
+
|
|
99
|
+
control_id: str
|
|
100
|
+
code: str # KeyCode value like "ArrowUp", "Enter", etc.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""External Controls event contracts for RocketWelder SDK (legacy - for backward compatibility)."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from uuid import UUID, uuid4
|
|
6
|
+
|
|
7
|
+
from rocket_welder_sdk.ui.value_types import ControlType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ArrowDirection(Enum):
|
|
11
|
+
"""Arrow directions for ArrowGrid control."""
|
|
12
|
+
|
|
13
|
+
UP = "Up"
|
|
14
|
+
DOWN = "Down"
|
|
15
|
+
LEFT = "Left"
|
|
16
|
+
RIGHT = "Right"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Container → UI Commands (Stream: ExternalCommands-{SessionId})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class DefineControl:
|
|
24
|
+
"""Command to define a new control in the UI."""
|
|
25
|
+
|
|
26
|
+
control_id: str
|
|
27
|
+
type: ControlType
|
|
28
|
+
properties: dict[str, str]
|
|
29
|
+
region_name: str
|
|
30
|
+
id: UUID = field(default_factory=uuid4)
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> dict[str, object]:
|
|
33
|
+
"""Convert to dictionary for EventStore."""
|
|
34
|
+
return {
|
|
35
|
+
"Id": str(self.id),
|
|
36
|
+
"ControlId": self.control_id,
|
|
37
|
+
"Type": self.type.value,
|
|
38
|
+
"Properties": self.properties,
|
|
39
|
+
"RegionName": self.region_name,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class DeleteControl:
|
|
45
|
+
"""Command to delete a control from the UI."""
|
|
46
|
+
|
|
47
|
+
control_id: str
|
|
48
|
+
id: UUID = field(default_factory=uuid4)
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> dict[str, str]:
|
|
51
|
+
"""Convert to dictionary for EventStore."""
|
|
52
|
+
return {"Id": str(self.id), "ControlId": self.control_id}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ChangeControls:
|
|
57
|
+
"""Command to update properties of multiple controls."""
|
|
58
|
+
|
|
59
|
+
updates: dict[str, dict[str, str]] # ControlId -> { PropertyId -> Value }
|
|
60
|
+
id: UUID = field(default_factory=uuid4)
|
|
61
|
+
|
|
62
|
+
def to_dict(self) -> dict[str, object]:
|
|
63
|
+
"""Convert to dictionary for EventStore."""
|
|
64
|
+
return {"Id": str(self.id), "Updates": self.updates}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# UI → Container Events (Stream: ExternalEvents-{SessionId})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class ButtonDown:
|
|
72
|
+
"""Event when button is pressed."""
|
|
73
|
+
|
|
74
|
+
control_id: str
|
|
75
|
+
id: UUID = field(default_factory=uuid4)
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> dict[str, str]:
|
|
78
|
+
"""Convert to dictionary for EventStore."""
|
|
79
|
+
return {"Id": str(self.id), "ControlId": self.control_id}
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_dict(cls, data: dict[str, object]) -> "ButtonDown":
|
|
83
|
+
"""Create from EventStore data."""
|
|
84
|
+
return cls(
|
|
85
|
+
control_id=str(data["ControlId"]), id=UUID(str(data["Id"])) if "Id" in data else uuid4()
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class ButtonUp:
|
|
91
|
+
"""Event when button is released."""
|
|
92
|
+
|
|
93
|
+
control_id: str
|
|
94
|
+
id: UUID = field(default_factory=uuid4)
|
|
95
|
+
|
|
96
|
+
def to_dict(self) -> dict[str, str]:
|
|
97
|
+
"""Convert to dictionary for EventStore."""
|
|
98
|
+
return {"Id": str(self.id), "ControlId": self.control_id}
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_dict(cls, data: dict[str, object]) -> "ButtonUp":
|
|
102
|
+
"""Create from EventStore data."""
|
|
103
|
+
return cls(
|
|
104
|
+
control_id=str(data["ControlId"]), id=UUID(str(data["Id"])) if "Id" in data else uuid4()
|
|
105
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""UI module for RocketWelder SDK."""
|
|
2
|
+
|
|
3
|
+
from rocket_welder_sdk.external_controls.contracts import ArrowDirection
|
|
4
|
+
|
|
5
|
+
from .controls import (
|
|
6
|
+
ArrowGridControl,
|
|
7
|
+
ControlBase,
|
|
8
|
+
IconButtonControl,
|
|
9
|
+
LabelControl,
|
|
10
|
+
)
|
|
11
|
+
from .icons import Custom, Icons, Material
|
|
12
|
+
from .ui_events_projection import UiEventsProjection
|
|
13
|
+
from .ui_service import (
|
|
14
|
+
ItemsControl,
|
|
15
|
+
UiControlFactory,
|
|
16
|
+
UiService,
|
|
17
|
+
)
|
|
18
|
+
from .value_types import (
|
|
19
|
+
Color,
|
|
20
|
+
ControlType,
|
|
21
|
+
RegionName,
|
|
22
|
+
Size,
|
|
23
|
+
Typography,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"ArrowDirection",
|
|
28
|
+
"ArrowGridControl",
|
|
29
|
+
"Color",
|
|
30
|
+
# Controls
|
|
31
|
+
"ControlBase",
|
|
32
|
+
# Enums
|
|
33
|
+
"ControlType",
|
|
34
|
+
"Custom",
|
|
35
|
+
"IconButtonControl",
|
|
36
|
+
# Icons
|
|
37
|
+
"Icons",
|
|
38
|
+
"ItemsControl",
|
|
39
|
+
"LabelControl",
|
|
40
|
+
"Material",
|
|
41
|
+
"RegionName",
|
|
42
|
+
"Size",
|
|
43
|
+
"Typography",
|
|
44
|
+
"UiControlFactory",
|
|
45
|
+
"UiEventsProjection",
|
|
46
|
+
# Services
|
|
47
|
+
"UiService",
|
|
48
|
+
]
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""UI Control base classes and implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, ClassVar
|
|
7
|
+
|
|
8
|
+
from rocket_welder_sdk.external_controls.contracts import (
|
|
9
|
+
ArrowDirection,
|
|
10
|
+
ButtonDown,
|
|
11
|
+
ButtonUp,
|
|
12
|
+
KeyDown,
|
|
13
|
+
KeyUp,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .value_types import Color, ControlType, Size, Typography
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .ui_service import UiService
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ControlBase(ABC):
|
|
23
|
+
"""Base class for all UI controls."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
control_id: str,
|
|
28
|
+
control_type: ControlType,
|
|
29
|
+
ui_service: UiService,
|
|
30
|
+
properties: dict[str, str] | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Initialize base control.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
control_id: Unique identifier for the control
|
|
37
|
+
control_type: Type of the control
|
|
38
|
+
ui_service: Reference to parent UiService
|
|
39
|
+
properties: Initial properties
|
|
40
|
+
"""
|
|
41
|
+
self.id: str = control_id
|
|
42
|
+
self.control_type: ControlType = control_type
|
|
43
|
+
self._ui_service: UiService = ui_service
|
|
44
|
+
self._properties: dict[str, str] = properties or {}
|
|
45
|
+
self._changed: dict[str, str] = {}
|
|
46
|
+
self._is_disposed: bool = False
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def is_dirty(self) -> bool:
|
|
50
|
+
"""Check if control has uncommitted changes."""
|
|
51
|
+
return bool(self._changed)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def changed(self) -> dict[str, str]:
|
|
55
|
+
"""Get pending changes."""
|
|
56
|
+
return self._changed.copy()
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def properties(self) -> dict[str, str]:
|
|
60
|
+
"""Get current properties including changes."""
|
|
61
|
+
props = self._properties.copy()
|
|
62
|
+
props.update(self._changed)
|
|
63
|
+
return props
|
|
64
|
+
|
|
65
|
+
def set_property(self, name: str, value: Any) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Set a property value.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
name: Property name
|
|
71
|
+
value: Property value (will be converted to string)
|
|
72
|
+
"""
|
|
73
|
+
str_value = str(value) if value is not None else ""
|
|
74
|
+
if self._properties.get(name) != str_value:
|
|
75
|
+
self._changed[name] = str_value
|
|
76
|
+
|
|
77
|
+
def commit_changes(self) -> None:
|
|
78
|
+
"""Commit pending changes to properties."""
|
|
79
|
+
self._properties.update(self._changed)
|
|
80
|
+
self._changed.clear()
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def handle_event(self, event: Any) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Handle an event for this control.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
event: Event to handle
|
|
89
|
+
"""
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
def dispose(self) -> None:
|
|
93
|
+
"""Dispose of the control."""
|
|
94
|
+
if not self._is_disposed:
|
|
95
|
+
self._is_disposed = True
|
|
96
|
+
self._ui_service.schedule_delete(self.id)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class IconButtonControl(ControlBase):
|
|
100
|
+
"""Icon button control with click events."""
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
control_id: str,
|
|
105
|
+
ui_service: UiService,
|
|
106
|
+
icon: str,
|
|
107
|
+
properties: dict[str, str] | None = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Initialize icon button control.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
control_id: Unique identifier
|
|
114
|
+
ui_service: Parent UiService
|
|
115
|
+
icon: SVG path for the icon
|
|
116
|
+
properties: Additional properties
|
|
117
|
+
"""
|
|
118
|
+
props = properties or {}
|
|
119
|
+
props["Icon"] = icon
|
|
120
|
+
super().__init__(control_id, ControlType.ICON_BUTTON, ui_service, props)
|
|
121
|
+
|
|
122
|
+
# Event handlers
|
|
123
|
+
self.on_button_down: Callable[[IconButtonControl], None] | None = None
|
|
124
|
+
self.on_button_up: Callable[[IconButtonControl], None] | None = None
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def icon(self) -> str:
|
|
128
|
+
"""Get icon SVG path."""
|
|
129
|
+
return self.properties.get("Icon", "")
|
|
130
|
+
|
|
131
|
+
@icon.setter
|
|
132
|
+
def icon(self, value: str) -> None:
|
|
133
|
+
"""Set icon SVG path."""
|
|
134
|
+
self.set_property("Icon", value)
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def text(self) -> str | None:
|
|
138
|
+
"""Get button text."""
|
|
139
|
+
return self.properties.get("Text")
|
|
140
|
+
|
|
141
|
+
@text.setter
|
|
142
|
+
def text(self, value: str | None) -> None:
|
|
143
|
+
"""Set button text."""
|
|
144
|
+
if value is not None:
|
|
145
|
+
self.set_property("Text", value)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def color(self) -> Color:
|
|
149
|
+
"""Get button color."""
|
|
150
|
+
color_str = self.properties.get("Color", Color.PRIMARY.value)
|
|
151
|
+
try:
|
|
152
|
+
return Color(color_str)
|
|
153
|
+
except ValueError:
|
|
154
|
+
return Color.PRIMARY
|
|
155
|
+
|
|
156
|
+
@color.setter
|
|
157
|
+
def color(self, value: Color | str) -> None:
|
|
158
|
+
"""Set button color."""
|
|
159
|
+
if isinstance(value, Color):
|
|
160
|
+
self.set_property("Color", value.value)
|
|
161
|
+
else:
|
|
162
|
+
# Try to find matching enum
|
|
163
|
+
for color in Color:
|
|
164
|
+
if color.value == value:
|
|
165
|
+
self.set_property("Color", value)
|
|
166
|
+
return
|
|
167
|
+
raise ValueError(f"Invalid color value: {value}")
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def size(self) -> Size:
|
|
171
|
+
"""Get button size."""
|
|
172
|
+
size_str = self.properties.get("Size", Size.MEDIUM.value)
|
|
173
|
+
try:
|
|
174
|
+
return Size(size_str)
|
|
175
|
+
except ValueError:
|
|
176
|
+
return Size.MEDIUM
|
|
177
|
+
|
|
178
|
+
@size.setter
|
|
179
|
+
def size(self, value: Size | str) -> None:
|
|
180
|
+
"""Set button size."""
|
|
181
|
+
if isinstance(value, Size):
|
|
182
|
+
self.set_property("Size", value.value)
|
|
183
|
+
else:
|
|
184
|
+
# Try to find matching enum
|
|
185
|
+
for size in Size:
|
|
186
|
+
if size.value == value:
|
|
187
|
+
self.set_property("Size", value)
|
|
188
|
+
return
|
|
189
|
+
raise ValueError(f"Invalid size value: {value}")
|
|
190
|
+
|
|
191
|
+
def handle_event(self, event: Any) -> None:
|
|
192
|
+
"""Handle button events."""
|
|
193
|
+
if isinstance(event, ButtonDown) and self.on_button_down:
|
|
194
|
+
self.on_button_down(self)
|
|
195
|
+
elif isinstance(event, ButtonUp) and self.on_button_up:
|
|
196
|
+
self.on_button_up(self)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ArrowGridControl(ControlBase):
|
|
200
|
+
"""Arrow grid control for directional input."""
|
|
201
|
+
|
|
202
|
+
# Mapping from key codes to arrow directions
|
|
203
|
+
KEY_TO_DIRECTION: ClassVar[dict[str, ArrowDirection]] = {
|
|
204
|
+
"ArrowUp": ArrowDirection.UP,
|
|
205
|
+
"ArrowDown": ArrowDirection.DOWN,
|
|
206
|
+
"ArrowLeft": ArrowDirection.LEFT,
|
|
207
|
+
"ArrowRight": ArrowDirection.RIGHT,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
def __init__(
|
|
211
|
+
self, control_id: str, ui_service: UiService, properties: dict[str, str] | None = None
|
|
212
|
+
) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Initialize arrow grid control.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
control_id: Unique identifier
|
|
218
|
+
ui_service: Parent UiService
|
|
219
|
+
properties: Additional properties
|
|
220
|
+
"""
|
|
221
|
+
super().__init__(control_id, ControlType.ARROW_GRID, ui_service, properties)
|
|
222
|
+
|
|
223
|
+
# Event handlers
|
|
224
|
+
self.on_arrow_down: Callable[[ArrowGridControl, ArrowDirection], None] | None = None
|
|
225
|
+
self.on_arrow_up: Callable[[ArrowGridControl, ArrowDirection], None] | None = None
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def size(self) -> Size:
|
|
229
|
+
"""Get grid size."""
|
|
230
|
+
size_str = self.properties.get("Size", Size.MEDIUM.value)
|
|
231
|
+
try:
|
|
232
|
+
return Size(size_str)
|
|
233
|
+
except ValueError:
|
|
234
|
+
return Size.MEDIUM
|
|
235
|
+
|
|
236
|
+
@size.setter
|
|
237
|
+
def size(self, value: Size | str) -> None:
|
|
238
|
+
"""Set grid size."""
|
|
239
|
+
if isinstance(value, Size):
|
|
240
|
+
self.set_property("Size", value.value)
|
|
241
|
+
else:
|
|
242
|
+
# Try to find matching enum
|
|
243
|
+
for size in Size:
|
|
244
|
+
if size.value == value:
|
|
245
|
+
self.set_property("Size", value)
|
|
246
|
+
return
|
|
247
|
+
raise ValueError(f"Invalid size value: {value}")
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def color(self) -> Color:
|
|
251
|
+
"""Get grid color."""
|
|
252
|
+
color_str = self.properties.get("Color", Color.PRIMARY.value)
|
|
253
|
+
try:
|
|
254
|
+
return Color(color_str)
|
|
255
|
+
except ValueError:
|
|
256
|
+
return Color.PRIMARY
|
|
257
|
+
|
|
258
|
+
@color.setter
|
|
259
|
+
def color(self, value: Color | str) -> None:
|
|
260
|
+
"""Set grid color."""
|
|
261
|
+
if isinstance(value, Color):
|
|
262
|
+
self.set_property("Color", value.value)
|
|
263
|
+
else:
|
|
264
|
+
# Try to find matching enum
|
|
265
|
+
for color in Color:
|
|
266
|
+
if color.value == value:
|
|
267
|
+
self.set_property("Color", value)
|
|
268
|
+
return
|
|
269
|
+
raise ValueError(f"Invalid color value: {value}")
|
|
270
|
+
|
|
271
|
+
def handle_event(self, event: Any) -> None:
|
|
272
|
+
"""Handle keyboard events and translate to arrow events."""
|
|
273
|
+
if isinstance(event, KeyDown):
|
|
274
|
+
direction = self.KEY_TO_DIRECTION.get(event.code)
|
|
275
|
+
if direction and self.on_arrow_down:
|
|
276
|
+
self.on_arrow_down(self, direction)
|
|
277
|
+
elif isinstance(event, KeyUp):
|
|
278
|
+
direction = self.KEY_TO_DIRECTION.get(event.code)
|
|
279
|
+
if direction and self.on_arrow_up:
|
|
280
|
+
self.on_arrow_up(self, direction)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class LabelControl(ControlBase):
|
|
284
|
+
"""Label control for displaying text."""
|
|
285
|
+
|
|
286
|
+
def __init__(
|
|
287
|
+
self,
|
|
288
|
+
control_id: str,
|
|
289
|
+
ui_service: UiService,
|
|
290
|
+
text: str,
|
|
291
|
+
properties: dict[str, str] | None = None,
|
|
292
|
+
) -> None:
|
|
293
|
+
"""
|
|
294
|
+
Initialize label control.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
control_id: Unique identifier
|
|
298
|
+
ui_service: Parent UiService
|
|
299
|
+
text: Label text
|
|
300
|
+
properties: Additional properties
|
|
301
|
+
"""
|
|
302
|
+
props = properties or {}
|
|
303
|
+
props["Text"] = text
|
|
304
|
+
super().__init__(control_id, ControlType.LABEL, ui_service, props)
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def text(self) -> str:
|
|
308
|
+
"""Get label text."""
|
|
309
|
+
return self.properties.get("Text", "")
|
|
310
|
+
|
|
311
|
+
@text.setter
|
|
312
|
+
def text(self, value: str) -> None:
|
|
313
|
+
"""Set label text."""
|
|
314
|
+
self.set_property("Text", value)
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def typography(self) -> Typography:
|
|
318
|
+
"""Get label typography."""
|
|
319
|
+
typo_str = self.properties.get("Typography", Typography.BODY1.value)
|
|
320
|
+
try:
|
|
321
|
+
return Typography(typo_str)
|
|
322
|
+
except ValueError:
|
|
323
|
+
return Typography.BODY1
|
|
324
|
+
|
|
325
|
+
@typography.setter
|
|
326
|
+
def typography(self, value: Typography | str) -> None:
|
|
327
|
+
"""Set label typography."""
|
|
328
|
+
if isinstance(value, Typography):
|
|
329
|
+
self.set_property("Typography", value.value)
|
|
330
|
+
else:
|
|
331
|
+
# Try to find matching enum
|
|
332
|
+
for typo in Typography:
|
|
333
|
+
if typo.value == value:
|
|
334
|
+
self.set_property("Typography", value)
|
|
335
|
+
return
|
|
336
|
+
raise ValueError(f"Invalid typography value: {value}")
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def color(self) -> Color:
|
|
340
|
+
"""Get label color."""
|
|
341
|
+
color_str = self.properties.get("Color", Color.TEXT_PRIMARY.value)
|
|
342
|
+
try:
|
|
343
|
+
return Color(color_str)
|
|
344
|
+
except ValueError:
|
|
345
|
+
return Color.TEXT_PRIMARY
|
|
346
|
+
|
|
347
|
+
@color.setter
|
|
348
|
+
def color(self, value: Color | str) -> None:
|
|
349
|
+
"""Set label color."""
|
|
350
|
+
if isinstance(value, Color):
|
|
351
|
+
self.set_property("Color", value.value)
|
|
352
|
+
else:
|
|
353
|
+
# Try to find matching enum
|
|
354
|
+
for color in Color:
|
|
355
|
+
if color.value == value:
|
|
356
|
+
self.set_property("Color", value)
|
|
357
|
+
return
|
|
358
|
+
raise ValueError(f"Invalid color value: {value}")
|
|
359
|
+
|
|
360
|
+
def handle_event(self, event: Any) -> None:
|
|
361
|
+
"""Labels typically don't handle events."""
|
|
362
|
+
pass
|