py10x-universe 0.1.3__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 (214) hide show
  1. core_10x/__init__.py +42 -0
  2. core_10x/backbone/__init__.py +0 -0
  3. core_10x/backbone/backbone_store.py +59 -0
  4. core_10x/backbone/backbone_traitable.py +30 -0
  5. core_10x/backbone/backbone_user.py +66 -0
  6. core_10x/backbone/bound_data_domain.py +49 -0
  7. core_10x/backbone/namespace.py +101 -0
  8. core_10x/backbone/vault.py +38 -0
  9. core_10x/code_samples/__init__.py +0 -0
  10. core_10x/code_samples/_package_manifest.py +3 -0
  11. core_10x/code_samples/directories.py +181 -0
  12. core_10x/code_samples/person.py +76 -0
  13. core_10x/concrete_traits.py +356 -0
  14. core_10x/conftest.py +12 -0
  15. core_10x/curve.py +321 -0
  16. core_10x/data_domain.py +48 -0
  17. core_10x/data_domain_binder.py +45 -0
  18. core_10x/directory.py +250 -0
  19. core_10x/entity.py +8 -0
  20. core_10x/entity_filter.py +5 -0
  21. core_10x/environment_variables.py +147 -0
  22. core_10x/exec_control.py +84 -0
  23. core_10x/experimental/__init__.py +0 -0
  24. core_10x/experimental/data_protocol_ex.py +34 -0
  25. core_10x/global_cache.py +121 -0
  26. core_10x/manual_tests/__init__.py +0 -0
  27. core_10x/manual_tests/calendar_test.py +35 -0
  28. core_10x/manual_tests/ctor_update_bug.py +58 -0
  29. core_10x/manual_tests/debug_graph_on.py +17 -0
  30. core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
  31. core_10x/manual_tests/enum_bits_test.py +17 -0
  32. core_10x/manual_tests/env_vars_trivial_test.py +12 -0
  33. core_10x/manual_tests/existing_traitable.py +33 -0
  34. core_10x/manual_tests/k10x_test1.py +13 -0
  35. core_10x/manual_tests/named_constant_test.py +121 -0
  36. core_10x/manual_tests/nucleus_trivial_test.py +42 -0
  37. core_10x/manual_tests/polars_test.py +14 -0
  38. core_10x/manual_tests/py_class_test.py +4 -0
  39. core_10x/manual_tests/rc_test.py +42 -0
  40. core_10x/manual_tests/rdate_test.py +12 -0
  41. core_10x/manual_tests/reference_serialization_bug.py +19 -0
  42. core_10x/manual_tests/resource_trivial_test.py +10 -0
  43. core_10x/manual_tests/store_uri_test.py +6 -0
  44. core_10x/manual_tests/trait_definition_test.py +19 -0
  45. core_10x/manual_tests/trait_filter_test.py +15 -0
  46. core_10x/manual_tests/trait_flag_modification_test.py +42 -0
  47. core_10x/manual_tests/trait_modification_bug.py +26 -0
  48. core_10x/manual_tests/traitable_as_of_test.py +82 -0
  49. core_10x/manual_tests/traitable_heir_test.py +39 -0
  50. core_10x/manual_tests/traitable_history_test.py +41 -0
  51. core_10x/manual_tests/traitable_serialization_test.py +54 -0
  52. core_10x/manual_tests/traitable_trivial_test.py +71 -0
  53. core_10x/manual_tests/trivial_graph_test.py +16 -0
  54. core_10x/manual_tests/ts_class_association_test.py +64 -0
  55. core_10x/manual_tests/ts_trivial_test.py +35 -0
  56. core_10x/named_constant.py +425 -0
  57. core_10x/nucleus.py +81 -0
  58. core_10x/package_manifest.py +85 -0
  59. core_10x/package_refactoring.py +153 -0
  60. core_10x/py_class.py +431 -0
  61. core_10x/rc.py +155 -0
  62. core_10x/rdate.py +339 -0
  63. core_10x/resource.py +189 -0
  64. core_10x/roman_number.py +67 -0
  65. core_10x/testlib/__init__.py +0 -0
  66. core_10x/testlib/test_store.py +240 -0
  67. core_10x/testlib/traitable_history_tests.py +787 -0
  68. core_10x/testlib/ts_tests.py +280 -0
  69. core_10x/trait.py +377 -0
  70. core_10x/trait_definition.py +176 -0
  71. core_10x/trait_filter.py +205 -0
  72. core_10x/trait_method_error.py +36 -0
  73. core_10x/traitable.py +1082 -0
  74. core_10x/traitable_cli.py +153 -0
  75. core_10x/traitable_heir.py +33 -0
  76. core_10x/traitable_id.py +31 -0
  77. core_10x/ts_store.py +172 -0
  78. core_10x/ts_store_type.py +26 -0
  79. core_10x/ts_union.py +147 -0
  80. core_10x/ui_hint.py +153 -0
  81. core_10x/unit_tests/test_concrete_traits.py +156 -0
  82. core_10x/unit_tests/test_converters.py +51 -0
  83. core_10x/unit_tests/test_curve.py +157 -0
  84. core_10x/unit_tests/test_directory.py +54 -0
  85. core_10x/unit_tests/test_documentation.py +172 -0
  86. core_10x/unit_tests/test_environment_variables.py +15 -0
  87. core_10x/unit_tests/test_filters.py +239 -0
  88. core_10x/unit_tests/test_graph.py +348 -0
  89. core_10x/unit_tests/test_named_constant.py +98 -0
  90. core_10x/unit_tests/test_rc.py +11 -0
  91. core_10x/unit_tests/test_rdate.py +484 -0
  92. core_10x/unit_tests/test_trait_method_error.py +80 -0
  93. core_10x/unit_tests/test_trait_modification.py +19 -0
  94. core_10x/unit_tests/test_traitable.py +959 -0
  95. core_10x/unit_tests/test_traitable_history.py +1 -0
  96. core_10x/unit_tests/test_ts_store.py +1 -0
  97. core_10x/unit_tests/test_ts_union.py +369 -0
  98. core_10x/unit_tests/test_ui_nodes.py +81 -0
  99. core_10x/unit_tests/test_xxcalendar.py +471 -0
  100. core_10x/vault/__init__.py +0 -0
  101. core_10x/vault/sec_keys.py +133 -0
  102. core_10x/vault/security_keys_old.py +168 -0
  103. core_10x/vault/vault.py +56 -0
  104. core_10x/vault/vault_traitable.py +56 -0
  105. core_10x/vault/vault_user.py +70 -0
  106. core_10x/xdate_time.py +136 -0
  107. core_10x/xnone.py +71 -0
  108. core_10x/xxcalendar.py +228 -0
  109. infra_10x/__init__.py +0 -0
  110. infra_10x/manual_tests/__init__.py +0 -0
  111. infra_10x/manual_tests/test_misc.py +16 -0
  112. infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
  113. infra_10x/mongodb_admin.py +111 -0
  114. infra_10x/mongodb_store.py +346 -0
  115. infra_10x/mongodb_utils.py +129 -0
  116. infra_10x/unit_tests/conftest.py +13 -0
  117. infra_10x/unit_tests/test_mongo_db.py +36 -0
  118. infra_10x/unit_tests/test_mongo_history.py +1 -0
  119. py10x_universe-0.1.3.dist-info/METADATA +406 -0
  120. py10x_universe-0.1.3.dist-info/RECORD +214 -0
  121. py10x_universe-0.1.3.dist-info/WHEEL +4 -0
  122. py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
  123. ui_10x/__init__.py +0 -0
  124. ui_10x/apps/__init__.py +0 -0
  125. ui_10x/apps/collection_editor_app.py +100 -0
  126. ui_10x/choice.py +212 -0
  127. ui_10x/collection_editor.py +135 -0
  128. ui_10x/concrete_trait_widgets.py +220 -0
  129. ui_10x/conftest.py +8 -0
  130. ui_10x/entity_stocker.py +173 -0
  131. ui_10x/examples/__init__.py +0 -0
  132. ui_10x/examples/_guess_word_data.py +14076 -0
  133. ui_10x/examples/collection_editor.py +17 -0
  134. ui_10x/examples/date_selector.py +14 -0
  135. ui_10x/examples/entity_stocker.py +18 -0
  136. ui_10x/examples/guess_word.py +392 -0
  137. ui_10x/examples/message_box.py +20 -0
  138. ui_10x/examples/multi_choice.py +17 -0
  139. ui_10x/examples/py_data_browser.py +66 -0
  140. ui_10x/examples/radiobox.py +29 -0
  141. ui_10x/examples/single_choice.py +31 -0
  142. ui_10x/examples/style_sheet.py +47 -0
  143. ui_10x/examples/trivial_entity_editor.py +18 -0
  144. ui_10x/platform.py +20 -0
  145. ui_10x/platform_interface.py +517 -0
  146. ui_10x/py_data_browser.py +249 -0
  147. ui_10x/qt6/__init__.py +0 -0
  148. ui_10x/qt6/conftest.py +8 -0
  149. ui_10x/qt6/manual_tests/__init__.py +0 -0
  150. ui_10x/qt6/manual_tests/basic_test.py +35 -0
  151. ui_10x/qt6/platform_implementation.py +275 -0
  152. ui_10x/qt6/utils.py +665 -0
  153. ui_10x/rio/__init__.py +0 -0
  154. ui_10x/rio/apps/examples/examples/__init__.py +22 -0
  155. ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
  156. ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
  157. ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
  158. ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
  159. ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
  160. ui_10x/rio/apps/examples/rio.toml +14 -0
  161. ui_10x/rio/component_builder.py +497 -0
  162. ui_10x/rio/components/__init__.py +9 -0
  163. ui_10x/rio/components/group_box.py +31 -0
  164. ui_10x/rio/components/labeled_checkbox.py +18 -0
  165. ui_10x/rio/components/line_edit.py +37 -0
  166. ui_10x/rio/components/radio_button.py +32 -0
  167. ui_10x/rio/components/separator.py +24 -0
  168. ui_10x/rio/components/splitter.py +121 -0
  169. ui_10x/rio/components/tree_view.py +75 -0
  170. ui_10x/rio/conftest.py +35 -0
  171. ui_10x/rio/internals/__init__.py +0 -0
  172. ui_10x/rio/internals/app.py +192 -0
  173. ui_10x/rio/manual_tests/__init__.py +0 -0
  174. ui_10x/rio/manual_tests/basic_test.py +24 -0
  175. ui_10x/rio/manual_tests/splitter.py +27 -0
  176. ui_10x/rio/platform_implementation.py +91 -0
  177. ui_10x/rio/style_sheet.py +53 -0
  178. ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
  179. ui_10x/rio/unit_tests/test_internals.py +630 -0
  180. ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
  181. ui_10x/rio/widgets/__init__.py +46 -0
  182. ui_10x/rio/widgets/application.py +109 -0
  183. ui_10x/rio/widgets/button.py +48 -0
  184. ui_10x/rio/widgets/button_group.py +60 -0
  185. ui_10x/rio/widgets/calendar.py +23 -0
  186. ui_10x/rio/widgets/checkbox.py +24 -0
  187. ui_10x/rio/widgets/dialog.py +137 -0
  188. ui_10x/rio/widgets/group_box.py +27 -0
  189. ui_10x/rio/widgets/layout.py +34 -0
  190. ui_10x/rio/widgets/line_edit.py +37 -0
  191. ui_10x/rio/widgets/list.py +105 -0
  192. ui_10x/rio/widgets/message_box.py +70 -0
  193. ui_10x/rio/widgets/scroll_area.py +31 -0
  194. ui_10x/rio/widgets/spacer.py +6 -0
  195. ui_10x/rio/widgets/splitter.py +45 -0
  196. ui_10x/rio/widgets/text_edit.py +28 -0
  197. ui_10x/rio/widgets/tree.py +89 -0
  198. ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
  199. ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
  200. ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
  201. ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
  202. ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
  203. ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
  204. ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
  205. ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
  206. ui_10x/table_header_view.py +305 -0
  207. ui_10x/table_view.py +174 -0
  208. ui_10x/trait_editor.py +189 -0
  209. ui_10x/trait_widget.py +131 -0
  210. ui_10x/traitable_editor.py +200 -0
  211. ui_10x/traitable_view.py +131 -0
  212. ui_10x/unit_tests/conftest.py +8 -0
  213. ui_10x/unit_tests/test_platform.py +9 -0
  214. ui_10x/utils.py +661 -0
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ import rio
4
+
5
+
6
+ class LineEditComponent(rio.Component):
7
+ text: str | None = None
8
+ tooltip: str | None = None
9
+ is_sensitive: bool = True
10
+ on_change: rio.EventHandler[[str]] = None
11
+ on_lose_focus: rio.EventHandler[[str]] = None
12
+ text_style: rio.TextStyle | None = None
13
+ is_secret: bool = False
14
+ on_pointer_up: rio.EventHandler[[rio.PointerEvent]] = None
15
+
16
+ def build(self):
17
+ component = rio.TextInput(
18
+ self.bind().text,
19
+ is_sensitive=self.is_sensitive,
20
+ on_change=self.on_change,
21
+ on_lose_focus=self.on_lose_focus,
22
+ text_style=self.text_style,
23
+ is_secret=self.is_secret,
24
+ )
25
+
26
+ if self.on_pointer_up is not None:
27
+ component = rio.PointerEventListener(
28
+ component,
29
+ on_pointer_up=self.on_pointer_up,
30
+ consume_events=False,
31
+ event_order='before-child',
32
+ )
33
+
34
+ if self.tooltip is not None:
35
+ component = rio.Tooltip(anchor=component, tip=self.tooltip)
36
+
37
+ return component
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import partial
4
+
5
+ import rio
6
+
7
+
8
+ class RadioButton(rio.Component):
9
+ label: str = ''
10
+ value: str = ''
11
+ checked: bool = False
12
+ on_select: rio.EventHandler[[]] = None
13
+
14
+ async def on_press(self, button) -> None:
15
+ if self.on_select:
16
+ self.on_select()
17
+ else:
18
+ self.checked = not self.checked
19
+ button.icon = self.icon_name()
20
+
21
+ def icon_name(self) -> str:
22
+ return f'radio_button_{"checked" if self.checked else "unchecked"}'
23
+
24
+ def build(self) -> rio.Component:
25
+ # Use an icon to visually represent the radio button state
26
+ icon_button = rio.IconButton(self.icon_name())
27
+ icon_button.on_press = partial(self.on_press, icon_button)
28
+ return rio.Row(
29
+ icon_button,
30
+ rio.Text(self.label),
31
+ spacing=0.5, # Space between icon and label
32
+ )
@@ -0,0 +1,24 @@
1
+ import typing
2
+
3
+ import rio
4
+
5
+
6
+ class Separator(rio.Component):
7
+ """
8
+ A component that draws a horizontal or vertical line to separate UI sections.
9
+
10
+ Attributes:
11
+ orientation: "horizontal" or "vertical" to define the separator's direction.
12
+ thickness: The width of the line in Rio units (default: 0.1).
13
+ color: The color of the line (default: gray).
14
+ """
15
+
16
+ orientation: typing.Literal['horizontal', 'vertical'] = 'horizontal'
17
+ thickness: float = 0.1
18
+ color: rio.Color = rio.Color.GRAY
19
+
20
+ def build(self) -> rio.Component:
21
+ if self.orientation == 'horizontal':
22
+ return rio.Rectangle(min_height=self.thickness, fill=self.color, margin_y=0.5, grow_y=False, align_y=0.5)
23
+ else:
24
+ return rio.Rectangle(min_width=self.thickness, fill=self.color, margin_x=0.5, grow_x=False, align_x=0.5)
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ import rio
6
+
7
+
8
+ class Splitter(rio.Component):
9
+ """
10
+ A custom Rio component that arranges children horizontally like a Row,
11
+ or vertically as a column with draggable splitters between them for resizing.
12
+ """
13
+
14
+ # Props
15
+ children: list[rio.Component] = []
16
+ direction: t.Literal['horizontal', 'vertical'] = 'vertical'
17
+ handle_size: float = 0.25 # Width of the splitter handle
18
+ min_size_percent: float = 10.0 # Minimum width for each child (%)
19
+ child_proportions: t.Literal['homogeneous'] | t.Sequence[float] = 'homogeneous'
20
+ _component_width: float = 0.0
21
+ _component_height: float = 0.0
22
+
23
+ def __post_init__(self):
24
+ if not isinstance(self.child_proportions, (list, tuple)):
25
+ num_children = len(self.children)
26
+ self.child_proportions = [1.0] * num_children if num_children else []
27
+ else:
28
+ assert len(self.child_proportions) == len(self.children)
29
+ assert all(p >= 0 for p in self.child_proportions), 'Proportions must be non-negative'
30
+ self.child_proportions = list(self.child_proportions)
31
+
32
+ def on_drag(self, index: int, event: rio.PointerMoveEvent) -> None:
33
+ """
34
+ Handle drag events on the splitter at the given index.
35
+ Adjusts the proportions of the two adjacent children.
36
+ """
37
+ horizontal = self.direction == 'horizontal'
38
+ total_size = self._component_width if horizontal else self._component_height
39
+
40
+ # Convert drag movement to a proportion change
41
+ total_proportion = sum(self.child_proportions)
42
+ relative_size = event.relative_x if horizontal else event.relative_y
43
+ delta_proportion = (relative_size / total_size) * total_proportion
44
+
45
+ # Adjust the proportions of the left and right children
46
+ prev_index = index
47
+ next_index = index + 1
48
+
49
+ # Calculate current sizes as percentages to check minimum constraints
50
+ current_sizes = [(p / total_proportion * 100.0) for p in self.child_proportions]
51
+ new_left_size = current_sizes[prev_index] + (delta_proportion / total_proportion * 100.0)
52
+ new_right_size = current_sizes[next_index] - (delta_proportion / total_proportion * 100.0)
53
+
54
+ # Check minimum size constraints
55
+ if new_left_size >= self.min_size_percent and new_right_size >= self.min_size_percent:
56
+ self.child_proportions[prev_index] += delta_proportion
57
+ self.child_proportions[next_index] -= delta_proportion
58
+ # Ensure proportions don't go negative
59
+ self.child_proportions[prev_index] = max(0.0, self.child_proportions[prev_index])
60
+ self.child_proportions[next_index] = max(0.0, self.child_proportions[next_index])
61
+
62
+ self.child_proportions = self.child_proportions # force refresh
63
+
64
+ def build(self) -> rio.Component:
65
+ # If no children, return an empty component
66
+ if not self.children:
67
+ return rio.Rectangle()
68
+
69
+ # Build the layout with children and splitters
70
+ components = []
71
+ horizontal = self.direction == 'horizontal'
72
+ for i, child in enumerate(self.children):
73
+ # Wrap child in ScrollArea
74
+ scrollable_content = rio.ScrollContainer(
75
+ content=child,
76
+ # scroll_x='never' if horizontal else 'auto',
77
+ # scroll_y='auto' if horizontal else 'never',
78
+ )
79
+ # Create the pane
80
+ pane = rio.Rectangle(
81
+ content=scrollable_content,
82
+ **{'grow_x' if horizontal else 'grow_y': True}, # Stretch to fill proportional space
83
+ margin=1, # Spacing around the child content
84
+ )
85
+ # Add a splitter handle to the right of all but the last pane
86
+ if i < len(self.children) - 1:
87
+ splitter = rio.PointerEventListener(
88
+ content=rio.Rectangle(
89
+ **{'grow_x' if horizontal else 'grow_y': False, 'min_width' if horizontal else 'min_height': self.handle_size},
90
+ fill=rio.Color.from_hex('#808080'),
91
+ cursor='move', # Valid CursorStyle for dragging
92
+ ),
93
+ on_drag_move=lambda event, idx=i: self.on_drag(idx, event),
94
+ **{
95
+ 'align_x' if horizontal else 'align_y': 1.0, # Position at the right edge
96
+ 'margin_right' if horizontal else 'margin_bottom': -self.handle_size / 2, # Extend slightly into the next pane
97
+ },
98
+ )
99
+ # Combine pane and splitter in a Stack
100
+ components.append(
101
+ rio.Stack(
102
+ pane,
103
+ splitter,
104
+ **{'grow_x' if horizontal else 'grow_y': True}, # Ensure the Stack follows the proportion
105
+ )
106
+ )
107
+ else:
108
+ # Last pane has no splitter
109
+ components.append(pane)
110
+
111
+ container = rio.Row if self.direction == 'horizontal' else rio.Column
112
+ return container(
113
+ *components,
114
+ spacing=0,
115
+ proportions=self.bind().child_proportions, # Dynamically control pane sizes
116
+ )
117
+
118
+ @rio.event.on_resize
119
+ def _on_resize(self, event: rio.event.ComponentResizeEvent) -> None:
120
+ self._component_width = event.width
121
+ self._component_height = event.height
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ import rio
6
+
7
+
8
+ class RioTreeItem(rio.Component):
9
+ """same as SimpleTreeItem, but includes tooltip and supports double-click"""
10
+
11
+ text: str = ''
12
+ on_double_press: rio.EventHandler[[]] = None
13
+ on_press: rio.EventHandler[[]] = None
14
+ on_change: rio.EventHandler[[]] = None
15
+ tooltip: str | None = None
16
+ editable: bool = False
17
+ editing: bool = False
18
+ children: list[RioTreeItem] = []
19
+ is_expanded: bool = False
20
+
21
+ def build_primary_text(self):
22
+ if not self.editing:
23
+ return rio.Text(self.text, justify='left', selectable=False)
24
+ return rio.TextInput(
25
+ self.text,
26
+ align_x=0, # justify left?
27
+ on_confirm=self.handle_edit_confirm,
28
+ )
29
+
30
+ def build_content(self):
31
+ content = self.build_primary_text()
32
+ if self.tooltip:
33
+ content = rio.Row(content, rio.Tooltip(anchor=content, tip=self.tooltip))
34
+ if self.on_double_press:
35
+ content = rio.PointerEventListener(content, on_double_press=self.handle_double_press)
36
+ return content
37
+
38
+ def handle_double_press(self, ev: rio.PointerEvent):
39
+ if self.editable:
40
+ self.editing = True
41
+ if self.on_double_press:
42
+ self.on_double_press()
43
+
44
+ def handle_edit_confirm(self, text):
45
+ assert self.editing
46
+ self.text = text
47
+ if self.on_change:
48
+ self.on_change()
49
+
50
+ def build(self):
51
+ return rio.SimpleTreeItem(
52
+ content=self.build_content(),
53
+ children=[child.build() for child in self.children],
54
+ is_expanded=self.is_expanded,
55
+ on_press=self.on_press,
56
+ )
57
+
58
+
59
+ class RioTreeView(rio.Component):
60
+ """makes item-level callbacks available on the tree level"""
61
+
62
+ children: list[rio.Component] = None
63
+ selection_mode: Literal['none', 'single', 'multiple'] = ('none',)
64
+ col_count: int = 1
65
+ header_labels: list[str] | None = None
66
+
67
+ def __post__init__(self):
68
+ if self.header_labels is None:
69
+ self.header_labels = ['header']
70
+
71
+ def build(self):
72
+ return rio.TreeView(
73
+ *[item.build() for item in self.children], # TODO: should not need to build each item
74
+ selection_mode=self.selection_mode,
75
+ )
ui_10x/rio/conftest.py ADDED
@@ -0,0 +1,35 @@
1
+ import os
2
+
3
+ import pytest
4
+ import rio.testing.browser_client
5
+
6
+
7
+ def running_with_coverage(config):
8
+ if not config.pluginmanager.getplugin('pytest_cov'):
9
+ return False
10
+
11
+ if not config.getoption('--cov', default='COV_CORE_SOURCE' in os.environ):
12
+ return False
13
+
14
+ return not config.getoption('--no-cov', default=False)
15
+
16
+
17
+ @pytest.fixture(scope='session', autouse=True)
18
+ async def manage_server(request):
19
+ if running_with_coverage(request.config):
20
+ # run headless client even if running with coverage
21
+ rio.testing.browser_client.DEBUGGER_ACTIVE = False
22
+ pytest.mark.timeout(180)
23
+ else:
24
+ if rio.testing.browser_client.DEBUGGER_ACTIVE:
25
+ pytest.mark.timeout(0 if rio.testing.browser_client.DEBUGGER_ACTIVE else 90)
26
+
27
+ async with rio.testing.browser_client.prepare_browser_client():
28
+ yield
29
+
30
+
31
+ @pytest.fixture(autouse=True)
32
+ def setup_ui_platform(monkeypatch):
33
+ monkeypatch.setenv('UI_PLATFORM', 'Rio')
34
+ yield
35
+ monkeypatch.undo()
File without changes
@@ -0,0 +1,192 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import pathlib
5
+ from dataclasses import dataclass
6
+ from functools import partial
7
+ from typing import TYPE_CHECKING
8
+
9
+ import ordered_set
10
+ from webview_proc import WebViewProcess
11
+
12
+ import rio
13
+ from rio import app_server, errors, utils
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Callable, Iterable
17
+
18
+ import uvicorn
19
+
20
+
21
+ @dataclass
22
+ class App10x:
23
+ """
24
+ # App10x - a custom App to use in dialog.
25
+ # rio.App is t.final, so App10x wraps rio.App and implements run_in_window
26
+ # --> run webview out-of-process
27
+ # --> run fastapi server in-process
28
+ # --> uses a subclass fo FastAPIServer to create a custom session class
29
+ # --> custom session class communicates with out-of-process webview (rather than using webview_shim)
30
+ """
31
+
32
+ app: rio.App
33
+ webview: WebViewProcess | None = None
34
+
35
+ @staticmethod
36
+ def _update_window_size(width: float | None, height: float | None) -> None:
37
+ import webview # imported here as called in separate process
38
+
39
+ if width is None and height is None:
40
+ return
41
+ window = webview.windows[0]
42
+ print(f'Current window size: {window.width}x{window.height} pixels')
43
+ pixels_per_rem = window.evaluate_js("""
44
+ let measure = document.createElement('div');
45
+ document.body.appendChild(measure);
46
+ measure.style.height = '1rem';
47
+ let pixels_per_rem = measure.getBoundingClientRect().height * window.devicePixelRatio;
48
+ measure.remove();
49
+ pixels_per_rem;
50
+ """)
51
+ width_in_pixels = window.width if width is None else round(width * pixels_per_rem)
52
+ height_in_pixels = window.height if height is None else round(height * pixels_per_rem)
53
+ print(f'Resizing window to {width_in_pixels}x{height_in_pixels} pixels ({width}x{height} rem at {pixels_per_rem} pixels/rem)')
54
+ window.resize(width_in_pixels, height_in_pixels)
55
+
56
+ def _run_in_window(
57
+ self,
58
+ *,
59
+ quiet: bool = True,
60
+ maximized: bool = False,
61
+ fullscreen: bool = False,
62
+ width: float | None = None,
63
+ height: float | None = None,
64
+ debug_mode: bool = False,
65
+ on_server_created: Callable[[uvicorn.Server], None] | None = None,
66
+ ) -> None:
67
+ host = 'localhost'
68
+ port = utils.ensure_valid_port(host, None)
69
+ url = f'http://{host}:{port}'
70
+
71
+ server: uvicorn.Server | None = None
72
+
73
+ icon_path = asyncio.run(self.app._fetch_icon_as_png_path())
74
+
75
+ self.webview = webview = WebViewProcess(
76
+ url=url,
77
+ title=self.app.name,
78
+ maximized=maximized,
79
+ fullscreen=fullscreen,
80
+ icon_path=icon_path,
81
+ func=partial(self._update_window_size, width, height),
82
+ on_close=lambda: server and setattr(server, 'should_exit', True),
83
+ )
84
+
85
+ def _on_server_created(_server: uvicorn.Server) -> None:
86
+ nonlocal server
87
+ server = _server
88
+
89
+ fastapi_server = server.config.app
90
+ fastapi_server.__class__ = FastapiServer
91
+ fastapi_server.app10x = self
92
+ if on_server_created:
93
+ on_server_created(server)
94
+
95
+ try:
96
+ self.app._run_as_web_server(
97
+ host=host,
98
+ port=port,
99
+ quiet=quiet,
100
+ running_in_window=True,
101
+ internal_on_app_start=webview.start,
102
+ internal_on_server_created=_on_server_created,
103
+ debug_mode=debug_mode,
104
+ )
105
+ except Exception as e:
106
+ print(f'Error running app: {e}')
107
+ finally:
108
+ if webview.is_alive():
109
+ webview.close()
110
+ webview.join()
111
+
112
+
113
+ class FastapiServer(app_server.FastapiServer):
114
+ app10x: App10x
115
+
116
+ async def create_session(self, *args, **kwargs) -> rio.Session:
117
+ session = await super().create_session(*args, **kwargs)
118
+ session.__class__ = Session
119
+ session.app10x = self.app10x
120
+ return session
121
+
122
+
123
+ class Session(rio.Session):
124
+ app10x: App10x
125
+
126
+ async def _close(self, close_remote_session: bool) -> None:
127
+ if not self.running_in_window:
128
+ await super()._close(close_remote_session=close_remote_session)
129
+
130
+ await super()._close(close_remote_session=False)
131
+ if close_remote_session:
132
+ self.app10x.webview.close()
133
+
134
+ async def _get_webview_window(self):
135
+ raise RuntimeError('Should not be called required in out-of-process webview')
136
+
137
+ async def set_title(self, title: str) -> None:
138
+ if not self.running_in_window:
139
+ await super().set_title(title)
140
+
141
+ self.app10x.webview.set_title(title)
142
+
143
+ async def pick_folder(self) -> pathlib.Path:
144
+ if not self.running_in_window:
145
+ return await super().pick_folder()
146
+
147
+ return pathlib.Path(self.app10x.webview.pick_folder())
148
+
149
+ async def pick_file(
150
+ self,
151
+ *,
152
+ file_types: Iterable[str] | None = None,
153
+ multiple: bool = False,
154
+ ) -> utils.FileInfo | list[utils.FileInfo]:
155
+ if not self.running_in_window:
156
+ return await super().pick_file(file_types=file_types, multiple=multiple)
157
+
158
+ # Normalize the file types
159
+ if file_types is not None:
160
+ # Normalize and deduplicate, but maintain the order
161
+ file_types = list(ordered_set.OrderedSet(utils.normalize_file_extension(file_type) for file_type in file_types))
162
+
163
+ selected = self.app10x.webview.pick_file(
164
+ file_types=[f'{extension} (*.{extension})' for extension in file_types],
165
+ multiple=multiple,
166
+ )
167
+
168
+ if not selected:
169
+ raise errors.NoFileSelectedError()
170
+
171
+ return [utils.FileInfo._from_path(path) for path in selected] if multiple else utils.FileInfo._from_path(selected)
172
+
173
+ async def save_file(
174
+ self,
175
+ file_contents: pathlib.Path | str | bytes,
176
+ file_name: str = 'Unnamed File',
177
+ *,
178
+ media_type: str | None = None,
179
+ directory: pathlib.Path | None = None,
180
+ ) -> None:
181
+ if not self.running_in_window:
182
+ return await super().save_file(file_contents, file_name, media_type=media_type, directory=directory)
183
+
184
+ self.app10x.webview.save_file(
185
+ file_contents=file_contents,
186
+ directory='' if directory is None else str(directory),
187
+ file_name=file_name,
188
+ )
189
+
190
+
191
+ Session._local_methods_ = rio.Session._local_methods_.copy()
192
+ Session._remote_methods_ = rio.Session._remote_methods_.copy()
File without changes
@@ -0,0 +1,24 @@
1
+ import os
2
+
3
+ if __name__ == '__main__':
4
+ assert os.environ.setdefault('UI_PLATFORM', 'Rio') == os.getenv('UI_PLATFORM')
5
+ from ui_10x.platform import ux
6
+
7
+ print(ux.Dialog(children=[ux.Label('Message'), ux.Label('Message2')], title='Title').exec())
8
+
9
+ # from functools import partial
10
+ #
11
+ # from rio import project_config, App, Text, TextStyle, ComponentPage, page
12
+
13
+ # app = App(
14
+ # pages=[
15
+ # ComponentPage( '', '',
16
+ # build=lambda: ux.VBoxLayout(
17
+ # ux.Label('a'),
18
+ # ux.PushButton('b',on_press=partial(print, 'b pressed')),
19
+ # )(),
20
+ # )
21
+ # ]
22
+ # )
23
+ # app.run_in_window()
24
+ #
@@ -0,0 +1,27 @@
1
+ from rio.debug.monkeypatches import apply_monkeypatches
2
+
3
+ import rio
4
+ from ui_10x.rio.components.splitter import Splitter
5
+
6
+
7
+ # Example app to demonstrate the Splitter component
8
+ class SplitterApp(rio.Component):
9
+ def build(self) -> rio.Component:
10
+ splitter = Splitter(
11
+ children=[
12
+ rio.Text('Pane 1', style='heading3'),
13
+ rio.Text('Pane 2', style='heading3'),
14
+ rio.Rectangle(content=rio.Container(rio.Container(rio.FlowContainer()))),
15
+ ],
16
+ handle_size=0.3, # Splitter handle width
17
+ direction='horizontal',
18
+ )
19
+ return rio.Container(rio.Column(rio.Column(rio.Row(rio.Button('A'), rio.Button('B')), rio.Container(splitter))))
20
+
21
+
22
+ # Run the app
23
+ if __name__ == '__main__':
24
+ app = rio.App(build=SplitterApp)
25
+ apply_monkeypatches()
26
+ # app._run_in_window(debug_mode=True)
27
+ app._run_as_web_server(debug_mode=True, port=8080, host='')
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from core_10x.global_cache import cache
4
+
5
+ from ui_10x.rio import component_builder, widgets
6
+
7
+
8
+ @cache
9
+ def init(): ...
10
+
11
+
12
+ class Object: ...
13
+
14
+
15
+ Application = widgets.Application
16
+
17
+
18
+ DirectConnection = component_builder.ConnectionType.DIRECT
19
+ QueuedConnection = component_builder.ConnectionType.QUEUED
20
+
21
+
22
+ SignalDecl = component_builder.SignalDecl
23
+
24
+
25
+ def signal_decl(arg=object):
26
+ assert arg is object, 'arg must be object'
27
+ return SignalDecl()
28
+
29
+
30
+ MouseEvent = component_builder.MouseEvent
31
+
32
+
33
+ SCROLL = widgets.SCROLL
34
+
35
+ Point = component_builder.Point
36
+ FontMetrics = component_builder.FontMetrics
37
+ SizePolicy = component_builder.FontMetrics
38
+ TEXT_ALIGN = component_builder.TEXT_ALIGN
39
+
40
+ Widget = component_builder.Widget
41
+ Layout = component_builder.Layout
42
+ FlowLayout = component_builder.FlowLayout
43
+
44
+ LineEdit = widgets.LineEdit
45
+ Label = widgets.Label
46
+ PushButton = widgets.PushButton
47
+
48
+ Spacer = widgets.Spacer
49
+
50
+ HBoxLayout = widgets.HBoxLayout
51
+ VBoxLayout = widgets.VBoxLayout
52
+ FormLayout = widgets.FormLayout
53
+
54
+ Dialog = widgets.Dialog
55
+
56
+ MessageBox = widgets.MessageBox
57
+
58
+ RadioButton = widgets.RadioButton
59
+ ButtonGroup = widgets.ButtonGroup
60
+
61
+ GroupBox = widgets.GroupBox
62
+
63
+ TextEdit = widgets.TextEdit
64
+ CheckBox = widgets.CheckBox
65
+ ScrollArea = widgets.ScrollArea
66
+ Separator = widgets.Separator
67
+
68
+
69
+ def separator(horizontal=True) -> Separator:
70
+ return Separator() if horizontal else Separator(orientation='vertical')
71
+
72
+
73
+ Direction = widgets.Direction
74
+
75
+ Vertical = Direction.VERTICAL
76
+ Horizontal = Direction.HORIZONTAL
77
+
78
+ Splitter = widgets.Splitter
79
+
80
+ Style = widgets.Style
81
+
82
+ FindFlags = widgets.FindFlags
83
+ MatchExactly = FindFlags.MATCH_EXACTLY
84
+
85
+ ListWidget = widgets.ListWidget
86
+ ListItem = widgets.ListItem
87
+
88
+ TreeWidget = widgets.TreeWidget
89
+ TreeItem = widgets.TreeItem
90
+
91
+ CalendarWidget = widgets.CalendarWidget