rocket-welder-sdk 1.1.36.dev14__py3-none-any.whl

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.
Files changed (40) hide show
  1. rocket_welder_sdk/__init__.py +95 -0
  2. rocket_welder_sdk/bytes_size.py +234 -0
  3. rocket_welder_sdk/connection_string.py +291 -0
  4. rocket_welder_sdk/controllers.py +831 -0
  5. rocket_welder_sdk/external_controls/__init__.py +30 -0
  6. rocket_welder_sdk/external_controls/contracts.py +100 -0
  7. rocket_welder_sdk/external_controls/contracts_old.py +105 -0
  8. rocket_welder_sdk/frame_metadata.py +138 -0
  9. rocket_welder_sdk/gst_metadata.py +411 -0
  10. rocket_welder_sdk/high_level/__init__.py +54 -0
  11. rocket_welder_sdk/high_level/client.py +235 -0
  12. rocket_welder_sdk/high_level/connection_strings.py +331 -0
  13. rocket_welder_sdk/high_level/data_context.py +169 -0
  14. rocket_welder_sdk/high_level/frame_sink_factory.py +118 -0
  15. rocket_welder_sdk/high_level/schema.py +195 -0
  16. rocket_welder_sdk/high_level/transport_protocol.py +238 -0
  17. rocket_welder_sdk/keypoints_protocol.py +642 -0
  18. rocket_welder_sdk/opencv_controller.py +278 -0
  19. rocket_welder_sdk/periodic_timer.py +303 -0
  20. rocket_welder_sdk/py.typed +2 -0
  21. rocket_welder_sdk/rocket_welder_client.py +497 -0
  22. rocket_welder_sdk/segmentation_result.py +420 -0
  23. rocket_welder_sdk/session_id.py +238 -0
  24. rocket_welder_sdk/transport/__init__.py +31 -0
  25. rocket_welder_sdk/transport/frame_sink.py +122 -0
  26. rocket_welder_sdk/transport/frame_source.py +74 -0
  27. rocket_welder_sdk/transport/nng_transport.py +197 -0
  28. rocket_welder_sdk/transport/stream_transport.py +193 -0
  29. rocket_welder_sdk/transport/tcp_transport.py +154 -0
  30. rocket_welder_sdk/transport/unix_socket_transport.py +339 -0
  31. rocket_welder_sdk/ui/__init__.py +48 -0
  32. rocket_welder_sdk/ui/controls.py +362 -0
  33. rocket_welder_sdk/ui/icons.py +21628 -0
  34. rocket_welder_sdk/ui/ui_events_projection.py +226 -0
  35. rocket_welder_sdk/ui/ui_service.py +358 -0
  36. rocket_welder_sdk/ui/value_types.py +72 -0
  37. rocket_welder_sdk-1.1.36.dev14.dist-info/METADATA +845 -0
  38. rocket_welder_sdk-1.1.36.dev14.dist-info/RECORD +40 -0
  39. rocket_welder_sdk-1.1.36.dev14.dist-info/WHEEL +5 -0
  40. rocket_welder_sdk-1.1.36.dev14.dist-info/top_level.txt +1 -0
@@ -0,0 +1,226 @@
1
+ """UI Events Projection for handling incoming events from the UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from contextlib import suppress
8
+ from threading import Event as ThreadingEvent
9
+ from typing import TYPE_CHECKING, Any, Protocol
10
+
11
+ from rocket_welder_sdk.external_controls.contracts import (
12
+ ButtonDown,
13
+ ButtonUp,
14
+ KeyDown,
15
+ KeyUp,
16
+ )
17
+
18
+ if TYPE_CHECKING:
19
+ from esdbclient import EventStoreDBClient, RecordedEvent
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class IEventQueue(Protocol):
25
+ """Protocol for event queue."""
26
+
27
+ def enqueue_event(self, event: Any) -> None:
28
+ """Add event to the queue."""
29
+ ...
30
+
31
+
32
+ class UiEventsProjection:
33
+ """
34
+ Projection that subscribes to UI events stream and forwards them to the UiService.
35
+
36
+ Implements RAII pattern for resource management.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ session_id: str,
42
+ event_queue: IEventQueue,
43
+ eventstore_client: EventStoreDBClient,
44
+ ) -> None:
45
+ """
46
+ Initialize the UI events projection.
47
+
48
+ Args:
49
+ session_id: The session ID to subscribe to
50
+ event_queue: Queue to forward events to (typically UiService)
51
+ eventstore_client: EventStore client for subscription
52
+ """
53
+ self._session_id: str = session_id
54
+ self._event_queue: IEventQueue = event_queue
55
+ self._eventstore_client: EventStoreDBClient = eventstore_client
56
+ self._stream_name: str = f"Ui.Events-{session_id}"
57
+ self._subscription: Any | None = None
58
+ self._subscription_task: asyncio.Task[None] | None = None
59
+ self._cancellation_token: ThreadingEvent = ThreadingEvent()
60
+ self._is_running: bool = False
61
+
62
+ async def start(self) -> None:
63
+ """Start the subscription to UI events."""
64
+ if self._is_running:
65
+ raise RuntimeError("Projection is already running")
66
+
67
+ self._is_running = True
68
+ self._cancellation_token.clear()
69
+
70
+ # Start subscription task
71
+ self._subscription_task = asyncio.create_task(self._run_subscription())
72
+ logger.info(f"Started UI events projection for session {self._session_id}")
73
+
74
+ async def stop(self) -> None:
75
+ """Stop the subscription and clean up resources."""
76
+ if not self._is_running:
77
+ return
78
+
79
+ self._is_running = False
80
+ self._cancellation_token.set()
81
+
82
+ # Cancel subscription task
83
+ if self._subscription_task:
84
+ self._subscription_task.cancel()
85
+ with suppress(asyncio.CancelledError):
86
+ await self._subscription_task
87
+ self._subscription_task = None
88
+
89
+ # Close subscription
90
+ if self._subscription:
91
+ try:
92
+ # Context manager will handle cleanup
93
+ pass
94
+ except Exception as e:
95
+ logger.warning(f"Error closing subscription: {e}")
96
+ finally:
97
+ self._subscription = None
98
+
99
+ logger.info(f"Stopped UI events projection for session {self._session_id}")
100
+
101
+ async def _run_subscription(self) -> None:
102
+ """Run the subscription loop."""
103
+ while self._is_running and not self._cancellation_token.is_set():
104
+ try:
105
+ # Subscribe from the beginning of the stream
106
+ # Using catch-up subscription to get all events
107
+ with self._eventstore_client.subscribe_to_stream(
108
+ stream_name=self._stream_name,
109
+ stream_position=None, # Start from beginning
110
+ include_caught_up=True,
111
+ ) as subscription:
112
+ self._subscription = subscription
113
+ logger.debug(f"Subscribed to stream {self._stream_name}")
114
+
115
+ for recorded_event in subscription:
116
+ if self._cancellation_token.is_set():
117
+ break
118
+
119
+ # Check if this is a caught-up event
120
+ if hasattr(recorded_event, "is_caught_up") and recorded_event.is_caught_up:
121
+ logger.debug(f"Caught up with stream {self._stream_name}")
122
+ continue
123
+
124
+ # Process the event
125
+ await self._handle_event(recorded_event)
126
+
127
+ except asyncio.CancelledError:
128
+ # Task was cancelled, exit cleanly
129
+ break
130
+ except Exception as e:
131
+ logger.error(f"Error in subscription: {e}")
132
+ if self._is_running and not self._cancellation_token.is_set():
133
+ # Wait before retrying
134
+ await asyncio.sleep(5)
135
+ else:
136
+ break
137
+
138
+ async def _handle_event(self, recorded_event: RecordedEvent) -> None:
139
+ """
140
+ Handle a recorded event from the stream.
141
+
142
+ Args:
143
+ recorded_event: The recorded event from EventStore
144
+ """
145
+ try:
146
+ # Parse event type and data
147
+ event_type: str = recorded_event.type
148
+ # Handle both dict and bytes (esdbclient may return bytes)
149
+ event_data: dict[str, Any]
150
+ if isinstance(recorded_event.data, bytes):
151
+ import json
152
+
153
+ event_data = json.loads(recorded_event.data)
154
+ else:
155
+ event_data = recorded_event.data
156
+
157
+ # Map event type to control event class
158
+ event_class: type[Any] | None = None
159
+
160
+ if event_type == "ButtonDown":
161
+ event_class = ButtonDown
162
+ elif event_type == "ButtonUp":
163
+ event_class = ButtonUp
164
+ elif event_type == "KeyDown":
165
+ event_class = KeyDown
166
+ elif event_type == "KeyUp":
167
+ event_class = KeyUp
168
+ else:
169
+ logger.debug(f"Ignoring unknown event type: {event_type}")
170
+ return
171
+
172
+ # Create event instance
173
+ if event_class:
174
+ # Convert from PascalCase to snake_case if needed
175
+ normalized_data = self._normalize_event_data(event_data)
176
+ event = event_class.model_validate(normalized_data)
177
+
178
+ # Enqueue event for processing
179
+ self._event_queue.enqueue_event(event)
180
+ logger.debug(f"Enqueued {event_type} for control {event.control_id}")
181
+
182
+ except Exception as e:
183
+ logger.error(f"Error handling event {recorded_event.id}: {e}")
184
+
185
+ def _normalize_event_data(self, data: dict[str, Any]) -> dict[str, Any]:
186
+ """
187
+ Normalize event data from PascalCase to snake_case.
188
+
189
+ Args:
190
+ data: Event data dictionary
191
+
192
+ Returns:
193
+ Normalized data dictionary
194
+ """
195
+ # Map common field names
196
+ field_mapping = {
197
+ "ControlId": "control_id",
198
+ "Code": "code",
199
+ "Direction": "direction",
200
+ }
201
+
202
+ normalized: dict[str, Any] = {}
203
+ for key, value in data.items():
204
+ # Use mapping if available, otherwise convert to snake_case
205
+ if key in field_mapping:
206
+ normalized[field_mapping[key]] = value
207
+ else:
208
+ # Simple PascalCase to snake_case conversion
209
+ snake_key = key[0].lower() + key[1:]
210
+ normalized[snake_key] = value
211
+
212
+ return normalized
213
+
214
+ async def __aenter__(self) -> UiEventsProjection:
215
+ """Async context manager entry."""
216
+ await self.start()
217
+ return self
218
+
219
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
220
+ """Async context manager exit - ensures cleanup."""
221
+ await self.stop()
222
+
223
+ @property
224
+ def is_running(self) -> bool:
225
+ """Check if the projection is running."""
226
+ return self._is_running
@@ -0,0 +1,358 @@
1
+ """UI Service for managing controls and commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import UserList
6
+ from typing import Any
7
+
8
+ from py_micro_plumberd import CommandBus, EventStoreClient
9
+
10
+ from rocket_welder_sdk.external_controls.contracts import (
11
+ ChangeControls,
12
+ DefineControl,
13
+ DeleteControls,
14
+ )
15
+
16
+ from .controls import (
17
+ ArrowGridControl,
18
+ ControlBase,
19
+ IconButtonControl,
20
+ LabelControl,
21
+ )
22
+ from .ui_events_projection import UiEventsProjection
23
+ from .value_types import RegionName
24
+
25
+
26
+ class ItemsControl(UserList[ControlBase]):
27
+ """Collection of controls for a region with automatic command scheduling."""
28
+
29
+ data: list[ControlBase] # Type annotation for the data attribute
30
+
31
+ def __init__(self, ui_service: UiService, region_name: RegionName) -> None:
32
+ """
33
+ Initialize items control for a region.
34
+
35
+ Args:
36
+ ui_service: Parent UiService
37
+ region_name: Region where controls are placed
38
+ """
39
+ super().__init__()
40
+ self._ui_service: UiService = ui_service
41
+ self._region_name: RegionName = region_name
42
+
43
+ def append(self, item: ControlBase) -> None:
44
+ """Add control and schedule DefineControl command."""
45
+ if not isinstance(item, ControlBase):
46
+ raise TypeError("Only ControlBase instances can be added")
47
+
48
+ # Schedule DefineControl command
49
+ self._ui_service.schedule_define_control(item, self._region_name)
50
+ super().append(item)
51
+
52
+ def add(self, item: ControlBase) -> None:
53
+ """Add control (alias for append to match C# API)."""
54
+ self.append(item)
55
+
56
+ def remove(self, item: ControlBase) -> None:
57
+ """Remove control and schedule deletion."""
58
+ if item in self.data:
59
+ self._ui_service.schedule_delete(item.id)
60
+ super().remove(item)
61
+
62
+ def clear(self) -> None:
63
+ """Clear all controls and schedule deletions."""
64
+ for control in self.data:
65
+ self._ui_service.schedule_delete(control.id)
66
+ super().clear()
67
+
68
+
69
+ class UiControlFactory:
70
+ """Factory for creating UI controls."""
71
+
72
+ def __init__(self, ui_service: UiService) -> None:
73
+ """
74
+ Initialize factory with UiService reference.
75
+
76
+ Args:
77
+ ui_service: Parent UiService
78
+ """
79
+ self._ui_service: UiService = ui_service
80
+
81
+ def define_icon_button(
82
+ self, control_id: str, icon: str, properties: dict[str, str] | None = None
83
+ ) -> IconButtonControl:
84
+ """
85
+ Create an icon button control.
86
+
87
+ Args:
88
+ control_id: Unique identifier
89
+ icon: SVG path for the icon
90
+ properties: Additional properties
91
+
92
+ Returns:
93
+ Created IconButtonControl
94
+ """
95
+ control = IconButtonControl(control_id, self._ui_service, icon, properties)
96
+ self._ui_service.register_control(control)
97
+ return control
98
+
99
+ def define_arrow_grid(
100
+ self, control_id: str, properties: dict[str, str] | None = None
101
+ ) -> ArrowGridControl:
102
+ """
103
+ Create an arrow grid control.
104
+
105
+ Args:
106
+ control_id: Unique identifier
107
+ properties: Additional properties
108
+
109
+ Returns:
110
+ Created ArrowGridControl
111
+ """
112
+ control = ArrowGridControl(control_id, self._ui_service, properties)
113
+ self._ui_service.register_control(control)
114
+ return control
115
+
116
+ def define_label(
117
+ self, control_id: str, text: str, properties: dict[str, str] | None = None
118
+ ) -> LabelControl:
119
+ """
120
+ Create a label control.
121
+
122
+ Args:
123
+ control_id: Unique identifier
124
+ text: Label text
125
+ properties: Additional properties
126
+
127
+ Returns:
128
+ Created LabelControl
129
+ """
130
+ control = LabelControl(control_id, self._ui_service, text, properties)
131
+ self._ui_service.register_control(control)
132
+ return control
133
+
134
+
135
+ class UiService:
136
+ """Main service for managing UI controls and commands."""
137
+
138
+ @classmethod
139
+ def from_session_id(cls, session_id: str | Any) -> UiService:
140
+ """
141
+ Create UiService from session ID.
142
+
143
+ Args:
144
+ session_id: Session ID (string or UUID)
145
+
146
+ Returns:
147
+ New UiService instance
148
+ """
149
+ # Handle UUID or string
150
+ session_str = str(session_id) if not isinstance(session_id, str) else session_id
151
+ return cls(session_str)
152
+
153
+ def __init__(self, session_id: str) -> None:
154
+ """
155
+ Initialize UiService with session ID.
156
+
157
+ Args:
158
+ session_id: UI session ID for command routing
159
+ """
160
+ self.session_id: str = session_id
161
+ self.command_bus: CommandBus | None = None
162
+ self.factory: UiControlFactory = UiControlFactory(self)
163
+
164
+ # Control tracking
165
+ self._index: dict[str, ControlBase] = {}
166
+
167
+ # Scheduled operations
168
+ self._scheduled_definitions: list[tuple[ControlBase, RegionName]] = []
169
+ self._scheduled_deletions: list[str] = []
170
+
171
+ # Initialize regions - include all standard and preview regions
172
+ self._regions: dict[RegionName, ItemsControl] = {
173
+ RegionName.TOP: ItemsControl(self, RegionName.TOP),
174
+ RegionName.TOP_LEFT: ItemsControl(self, RegionName.TOP_LEFT),
175
+ RegionName.TOP_RIGHT: ItemsControl(self, RegionName.TOP_RIGHT),
176
+ RegionName.BOTTOM: ItemsControl(self, RegionName.BOTTOM),
177
+ RegionName.BOTTOM_LEFT: ItemsControl(self, RegionName.BOTTOM_LEFT),
178
+ RegionName.BOTTOM_RIGHT: ItemsControl(self, RegionName.BOTTOM_RIGHT),
179
+ # Preview regions for compatibility
180
+ RegionName.PREVIEW_TOP: ItemsControl(self, RegionName.PREVIEW_TOP),
181
+ RegionName.PREVIEW_TOP_LEFT: ItemsControl(self, RegionName.PREVIEW_TOP_LEFT),
182
+ RegionName.PREVIEW_TOP_RIGHT: ItemsControl(self, RegionName.PREVIEW_TOP_RIGHT),
183
+ RegionName.PREVIEW_BOTTOM: ItemsControl(self, RegionName.PREVIEW_BOTTOM),
184
+ RegionName.PREVIEW_BOTTOM_LEFT: ItemsControl(self, RegionName.PREVIEW_BOTTOM_LEFT),
185
+ RegionName.PREVIEW_BOTTOM_RIGHT: ItemsControl(self, RegionName.PREVIEW_BOTTOM_RIGHT),
186
+ RegionName.PREVIEW_BOTTOM_CENTER: ItemsControl(self, RegionName.PREVIEW_BOTTOM_CENTER),
187
+ }
188
+
189
+ # Event queue
190
+ self._event_queue: list[Any] = []
191
+
192
+ # Event projection
193
+ self._events_projection: UiEventsProjection | None = None
194
+
195
+ def __getitem__(self, region: RegionName) -> ItemsControl:
196
+ """Get controls for a region."""
197
+ return self._regions[region]
198
+
199
+ async def initialize(self, eventstore_client: EventStoreClient) -> None:
200
+ """
201
+ Initialize with EventStore client and start event projection.
202
+
203
+ Args:
204
+ eventstore_client: EventStore client for commands and events
205
+ """
206
+ self.command_bus = CommandBus(eventstore_client)
207
+
208
+ # Start the events projection to receive UI events
209
+ self._events_projection = UiEventsProjection(
210
+ session_id=self.session_id,
211
+ event_queue=self, # UiService implements the IEventQueue protocol
212
+ eventstore_client=eventstore_client._client, # Use the underlying esdbclient
213
+ )
214
+ await self._events_projection.start()
215
+
216
+ async def dispose(self) -> None:
217
+ """Dispose the service and clean up resources."""
218
+ # Stop the events projection
219
+ if self._events_projection:
220
+ await self._events_projection.stop()
221
+ self._events_projection = None
222
+
223
+ # Clear all controls
224
+ for control_id in list(self._index.keys()):
225
+ control = self._index[control_id]
226
+ control.dispose()
227
+
228
+ # Clear regions
229
+ for region in self._regions.values():
230
+ region.clear()
231
+
232
+ def register_control(self, control: ControlBase) -> None:
233
+ """
234
+ Register a control in the index.
235
+
236
+ Args:
237
+ control: Control to register
238
+ """
239
+ self._index[control.id] = control
240
+
241
+ def schedule_define_control(self, control: ControlBase, region: RegionName) -> None:
242
+ """
243
+ Schedule a DefineControl command.
244
+
245
+ Args:
246
+ control: Control to define
247
+ region: Region where control is placed
248
+ """
249
+ self._scheduled_definitions.append((control, region))
250
+
251
+ def schedule_delete(self, control_id: str) -> None:
252
+ """
253
+ Schedule a control deletion.
254
+
255
+ Args:
256
+ control_id: ID of control to delete
257
+ """
258
+ self._scheduled_deletions.append(control_id)
259
+
260
+ def enqueue_event(self, event: Any) -> None:
261
+ """
262
+ Enqueue an event for processing.
263
+
264
+ Args:
265
+ event: Event to enqueue
266
+ """
267
+ self._event_queue.append(event)
268
+
269
+ async def do(self) -> None:
270
+ """Process all scheduled operations and events."""
271
+ # Dispatch events
272
+ self._dispatch_events()
273
+
274
+ # Process scheduled definitions
275
+ await self._process_scheduled_definitions()
276
+
277
+ # Process scheduled deletions
278
+ await self._process_scheduled_deletions()
279
+
280
+ # Send property updates
281
+ await self._send_property_updates()
282
+
283
+ def _dispatch_events(self) -> None:
284
+ """Dispatch queued events to controls."""
285
+ for event in self._event_queue:
286
+ if hasattr(event, "control_id"):
287
+ control_id: str = event.control_id
288
+ control = self._index.get(control_id)
289
+ if control:
290
+ control.handle_event(event)
291
+ self._event_queue.clear()
292
+
293
+ async def _process_scheduled_definitions(self) -> None:
294
+ """Process scheduled DefineControl commands."""
295
+ for control, region in self._scheduled_definitions:
296
+ # Add to index when actually defining
297
+ self._index[control.id] = control
298
+
299
+ command = DefineControl(
300
+ control_id=control.id,
301
+ type=control.control_type,
302
+ properties=control.properties,
303
+ region_name=region.value,
304
+ )
305
+
306
+ if self.command_bus:
307
+ await self.command_bus.send_async(recipient_id=self.session_id, command=command)
308
+
309
+ control.commit_changes()
310
+
311
+ self._scheduled_definitions.clear()
312
+
313
+ async def _process_scheduled_deletions(self) -> None:
314
+ """Process scheduled DeleteControls commands."""
315
+ if not self._scheduled_deletions:
316
+ return
317
+
318
+ # Batch delete command
319
+ command = DeleteControls(control_ids=self._scheduled_deletions.copy())
320
+
321
+ if self.command_bus:
322
+ await self.command_bus.send_async(recipient_id=self.session_id, command=command)
323
+
324
+ # Remove from index and regions
325
+ for control_id in self._scheduled_deletions:
326
+ control = self._index.pop(control_id, None)
327
+ if control:
328
+ for region in self._regions.values():
329
+ if control in region:
330
+ region.data.remove(control)
331
+
332
+ self._scheduled_deletions.clear()
333
+
334
+ async def _send_property_updates(self) -> None:
335
+ """Send ChangeControls command for dirty controls."""
336
+ updates: dict[str, dict[str, str]] = {}
337
+
338
+ for region in self._regions.values():
339
+ for control in region:
340
+ if control.is_dirty:
341
+ updates[control.id] = control.changed
342
+
343
+ if updates and self.command_bus:
344
+ command = ChangeControls(updates=updates)
345
+
346
+ await self.command_bus.send_async(recipient_id=self.session_id, command=command)
347
+
348
+ # Commit changes
349
+ for control_id in updates:
350
+ self._index[control_id].commit_changes()
351
+
352
+ async def __aenter__(self) -> UiService:
353
+ """Async context manager entry."""
354
+ return self
355
+
356
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
357
+ """Async context manager exit - ensures cleanup."""
358
+ await self.dispose()
@@ -0,0 +1,72 @@
1
+ """Value types for UI controls matching C# value types."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class ControlType(str, Enum):
7
+ """Control types matching C# ControlType enum."""
8
+
9
+ ICON_BUTTON = "IconButton"
10
+ ARROW_GRID = "ArrowGrid"
11
+ LABEL = "Label"
12
+
13
+
14
+ class RegionName(str, Enum):
15
+ """Region names for control placement."""
16
+
17
+ TOP = "Top"
18
+ TOP_LEFT = "TopLeft"
19
+ TOP_RIGHT = "TopRight"
20
+ BOTTOM = "Bottom"
21
+ BOTTOM_LEFT = "BottomLeft"
22
+ BOTTOM_RIGHT = "BottomRight"
23
+
24
+ # Legacy names for compatibility
25
+ PREVIEW_TOP = "preview-top"
26
+ PREVIEW_TOP_LEFT = "preview-top-left"
27
+ PREVIEW_TOP_RIGHT = "preview-top-right"
28
+ PREVIEW_BOTTOM = "preview-bottom"
29
+ PREVIEW_BOTTOM_LEFT = "preview-bottom-left"
30
+ PREVIEW_BOTTOM_RIGHT = "preview-bottom-right"
31
+ PREVIEW_BOTTOM_CENTER = "preview-bottom-center"
32
+
33
+
34
+ class Color(str, Enum):
35
+ """Color values for controls."""
36
+
37
+ PRIMARY = "Primary"
38
+ SECONDARY = "Secondary"
39
+ SUCCESS = "Success"
40
+ INFO = "Info"
41
+ WARNING = "Warning"
42
+ ERROR = "Error"
43
+ TEXT_PRIMARY = "TextPrimary"
44
+ TEXT_SECONDARY = "TextSecondary"
45
+ DEFAULT = "Default"
46
+
47
+
48
+ class Size(str, Enum):
49
+ """Size values for controls."""
50
+
51
+ EXTRA_SMALL = "ExtraSmall"
52
+ SMALL = "Small"
53
+ MEDIUM = "Medium"
54
+ LARGE = "Large"
55
+ EXTRA_LARGE = "ExtraLarge"
56
+
57
+
58
+ class Typography(str, Enum):
59
+ """Typography values for text controls."""
60
+
61
+ H1 = "h1"
62
+ H2 = "h2"
63
+ H3 = "h3"
64
+ H4 = "h4"
65
+ H5 = "h5"
66
+ H6 = "h6"
67
+ SUBTITLE1 = "subtitle1"
68
+ SUBTITLE2 = "subtitle2"
69
+ BODY1 = "body1"
70
+ BODY2 = "body2"
71
+ CAPTION = "caption"
72
+ OVERLINE = "overline"