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,21 @@
1
+ from __future__ import annotations
2
+
3
+ from core_10x.code_samples.person import Person
4
+
5
+ import rio
6
+ from examples import UserSessionContext
7
+ from examples import components as comps
8
+
9
+
10
+ def guard(event: rio.GuardEvent) -> str | None:
11
+ return None if event.session[UserSessionContext].authenticated else '/'
12
+
13
+
14
+ @rio.page(
15
+ name='CollectionEditor',
16
+ url_segment='ce',
17
+ guard=guard,
18
+ )
19
+ class CollectionEditor(rio.Component):
20
+ def build(self) -> rio.Component:
21
+ return comps.CollectionEditorComponent(collection_class=Person)
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from infra_10x.mongodb_store import MongoStore # TODO: backbone
4
+ from ui_10x.rio.component_builder import UserSessionContext
5
+
6
+ import rio
7
+
8
+
9
+ @rio.page(
10
+ name='Login/Logout',
11
+ url_segment='',
12
+ )
13
+ class LoginPage(rio.Component):
14
+ username: str = ''
15
+ password: str = ''
16
+
17
+ error_message: str = ''
18
+
19
+ _currently_logging_in: bool = False
20
+
21
+ async def login(self, _: rio.TextInputConfirmEvent | None = None) -> None:
22
+ try:
23
+ self._currently_logging_in = True
24
+ self.force_refresh()
25
+
26
+ try:
27
+ runtime_context = self.session[UserSessionContext]
28
+ running, with_auth = MongoStore.is_running_with_auth(runtime_context.host)
29
+ if not running:
30
+ self.error_message = 'Authentication is not available'
31
+ return
32
+ if not self.username and with_auth:
33
+ self.error_message = 'Username is required'
34
+ return
35
+ runtime_context.traitable_store = MongoStore.instance(
36
+ hostname=runtime_context.host, dbname=runtime_context.dbname, username=self.username, password=self.password
37
+ )
38
+ runtime_context.authenticated = True
39
+
40
+ except Exception as e:
41
+ self.error_message = f'Login error - try again\n{e}'
42
+ return
43
+
44
+ # The login was successful
45
+ self.error_message = ''
46
+
47
+ # Done
48
+ finally:
49
+ self._currently_logging_in = False
50
+
51
+ async def logout(self, _: rio.TextInputConfirmEvent | None = None) -> None:
52
+ try:
53
+ runtime_context = self.session[UserSessionContext]
54
+ runtime_context.authenticated = False
55
+ runtime_context.mongo_store = None
56
+ except Exception as e:
57
+ self.error_message = f'Logout error\n {e}'
58
+ return
59
+
60
+ # The login was successful
61
+ self.error_message = ''
62
+
63
+ def build(self) -> rio.Component:
64
+ rows = (
65
+ [
66
+ rio.TextInput(text=self.bind().username, label='Username', on_confirm=self.login),
67
+ rio.TextInput(text=self.bind().password, label='Password', is_secret=True, on_confirm=self.login),
68
+ rio.Button('Sign In', on_press=self.login, is_loading=self._currently_logging_in),
69
+ ]
70
+ if not self.session[UserSessionContext].authenticated
71
+ else [
72
+ rio.Button('Sign Out', on_press=self.logout),
73
+ ]
74
+ )
75
+
76
+ return rio.Card(
77
+ rio.Column(
78
+ rio.Text('Sign In/Sign Out', style='heading1', justify='center'),
79
+ rio.Banner(text=self.error_message, style='danger', margin_top=1),
80
+ *rows,
81
+ spacing=1,
82
+ margin=2,
83
+ ),
84
+ margin_x=0.5,
85
+ align_y=0.5,
86
+ align_x=0.5,
87
+ min_width=24,
88
+ )
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from core_10x.ts_union import TsUnion
4
+ from ui_10x.examples.style_sheet import StyleSheet
5
+ from ui_10x.rio.component_builder import UserSessionContext
6
+ from ui_10x.traitable_editor import TraitableEditor
7
+
8
+ import rio
9
+
10
+
11
+ @rio.page(
12
+ name='StyleSheet',
13
+ url_segment='ss',
14
+ )
15
+ class StyleSheetPage(rio.Component):
16
+ def build(self) -> rio.Component:
17
+ session_context = self.session[UserSessionContext]
18
+ if not session_context.traitable_store:
19
+ session_context.traitable_store = TsUnion()
20
+ with session_context.traitable_store:
21
+ return TraitableEditor(StyleSheet(), _confirm=True).dialog().build(self.session)
@@ -0,0 +1,14 @@
1
+ # This is the configuration file for Rio, an easy to use app & web framework for
2
+ # Python.
3
+
4
+ [app]
5
+ # This is either "website" or "app"
6
+ app-type = "website"
7
+
8
+ # The name of your Python module
9
+ main-module = "examples"
10
+
11
+ # All files which are part of your project. Changes to these will trigger a
12
+ # reload and they will be packed up when deploying.
13
+ project-files = ["*.py", "/assets/", "/rio.toml"]
14
+
@@ -0,0 +1,497 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import inspect
5
+ import operator
6
+ import types
7
+ from collections import defaultdict
8
+ from contextlib import contextmanager
9
+ from dataclasses import dataclass
10
+ from functools import partial, reduce
11
+ from typing import TYPE_CHECKING
12
+
13
+ from core_10x.exec_control import BTP, INTERACTIVE
14
+ from core_10x.named_constant import Enum, NamedConstant
15
+ from core_10x.traitable import Traitable
16
+
17
+ import rio
18
+ import ui_10x.platform_interface as i
19
+ from ui_10x.rio.style_sheet import StyleSheet
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Callable
23
+
24
+ from core_10x.rc import RC
25
+ from core_10x.ts_store import TsStore
26
+ from py10x_core import BTraitableProcessor
27
+
28
+
29
+ @dataclass
30
+ class UserSessionContext:
31
+ # TODO: backbone
32
+ host: str
33
+ dbname: str
34
+ traitable_store: TsStore = None
35
+ interactive: BTraitableProcessor = None
36
+ authenticated: bool = False
37
+
38
+ def begin_using(self):
39
+ if self.traitable_store:
40
+ self.traitable_store.begin_using()
41
+
42
+ if not self.interactive:
43
+ self.interactive = INTERACTIVE()
44
+ print('interactive in', BTP.current(), self.interactive)
45
+
46
+ self.interactive.begin_using()
47
+
48
+ def end_using(self):
49
+ self.interactive.end_using()
50
+ if self.traitable_store:
51
+ self.traitable_store.end_using()
52
+
53
+ def __enter__(self):
54
+ return self.begin_using()
55
+
56
+ def __exit__(self, *args):
57
+ self.end_using()
58
+
59
+
60
+ CURRENT_SESSION: rio.Session | None = None
61
+
62
+
63
+ @contextmanager
64
+ def session_context(session: rio.Session):
65
+ global CURRENT_SESSION
66
+ assert CURRENT_SESSION is None, 'Must exit from session context first! Are you using async calls in session context?'
67
+ CURRENT_SESSION = session
68
+ try:
69
+ user_session = session[UserSessionContext]
70
+ except Exception:
71
+ user_session = None
72
+
73
+ if user_session:
74
+ user_session.begin_using()
75
+ try:
76
+ yield
77
+ finally:
78
+ if user_session:
79
+ user_session.end_using()
80
+ CURRENT_SESSION = None
81
+
82
+
83
+ class ConnectionType(NamedConstant):
84
+ DIRECT = lambda handler, *args: handler(args)
85
+ QUEUED = lambda handler, *args: asyncio.get_running_loop().call_soon(handler, *args)
86
+
87
+
88
+ class SignalDecl:
89
+ def __init__(self):
90
+ self.handlers: dict[rio.Session, set[tuple[Callable[[...], None], ConnectionType]]] = defaultdict(set)
91
+
92
+ def connect(self, handler: Callable[[...], None], type: ConnectionType = ConnectionType.QUEUED) -> bool:
93
+ self.handlers[CURRENT_SESSION].add((handler, type))
94
+ return True
95
+
96
+ @staticmethod
97
+ def _wrapper(ctx, handler: Callable[[...], None], *args):
98
+ with ctx:
99
+ handler(*args)
100
+
101
+ def emit(self, *args) -> None:
102
+ for handler, conn in self.handlers[CURRENT_SESSION]:
103
+ conn.value(partial(self._wrapper, BTP.current(), handler), *args)
104
+
105
+
106
+ class MouseEvent(i.MouseEvent):
107
+ __slots__ = ('event',)
108
+
109
+ def __init__(self, event: rio.PointerEvent):
110
+ self.event = event
111
+
112
+ def is_left_button(self) -> bool:
113
+ return self.event.button == 'left'
114
+
115
+ def is_right_button(self) -> bool:
116
+ return self.event.button == 'right'
117
+
118
+
119
+ class FontMetrics(i.FontMetrics):
120
+ __slots__ = ('_widget',)
121
+
122
+ def __init__(self, w: Widget):
123
+ self._widget = w
124
+
125
+ def average_char_width(self) -> int:
126
+ return 1 # best guess -- rio measures sizes in char heights
127
+
128
+
129
+ class SizePolicy(Enum):
130
+ MINIMUM_EXPANDING = ()
131
+ PREFERRED = ()
132
+
133
+
134
+ class TEXT_ALIGN(NamedConstant):
135
+ s_vertical = 0xF << 4
136
+
137
+ LEFT = 1
138
+ CENTER = 6
139
+ RIGHT = 11
140
+ TOP = LEFT << 4
141
+ V_CENTER = CENTER << 4
142
+ BOTTOM = RIGHT << 4
143
+
144
+ @classmethod
145
+ def from_str(cls, s: str) -> TEXT_ALIGN:
146
+ return super().from_str(s.upper()) # type: ignore[return-value]
147
+
148
+ def rio_attr(self) -> str:
149
+ return 'align_y' if self.value & self.s_vertical else 'align_x'
150
+
151
+ def rio_value(self) -> float:
152
+ return ((self.value >> 4 if self.value & self.s_vertical else self.value) - 1) / 10
153
+
154
+
155
+ class DynamicComponent(rio.Component):
156
+ builder: ComponentBuilder | None = None
157
+
158
+ def __post_init__(self):
159
+ self.key = f'dc_{id(self.builder)}'
160
+
161
+ def build(self) -> rio.Component:
162
+ assert self.builder, 'DynamicComponent has no builder'
163
+ subcomponent = self.builder.build(self.session)
164
+ if not self.builder.component:
165
+ self.builder.component = self
166
+ self.builder.subcomponent = subcomponent
167
+ else:
168
+ assert self.builder.component is self, 'DynamicComponent reused!'
169
+ return subcomponent
170
+
171
+
172
+ class ComponentBuilder:
173
+ __slots__ = ('_kwargs', 'component', 'subcomponent')
174
+
175
+ s_component_class: type[rio.Component] = None
176
+ s_forced_kwargs = {}
177
+ s_default_kwargs = {}
178
+ s_children_attr = 'children'
179
+ s_single_child = False
180
+ s_pass_children_in_kwargs = False
181
+ s_size_adjustments = ('min_width', 'min_height', 'margin_left', 'margin_top', 'margin_right', 'margin_bottom', 'margin_x', 'margin_y', 'margin')
182
+ s_layout_attrs = ('grow_x', 'grow_y', 'align_x', 'align_y')
183
+
184
+ @staticmethod
185
+ def current_session():
186
+ return CURRENT_SESSION
187
+
188
+ def _get_children(self) -> list:
189
+ children = self.get_children()
190
+ if self.s_single_child and children is None:
191
+ return []
192
+ return [children] if self.s_single_child else children
193
+
194
+ def get_children(self):
195
+ return self._kwargs[self.s_children_attr]
196
+
197
+ def set_children(self, children):
198
+ self._kwargs[self.s_children_attr] = children
199
+ self.force_update()
200
+
201
+ def child_count(self):
202
+ return len(self.get_children())
203
+
204
+ def _set_children(self, children):
205
+ if self.s_single_child and not children:
206
+ self.set_children(None)
207
+ else:
208
+ assert not self.s_single_child or len(children) == 1
209
+ self.set_children(children[0] if self.s_single_child else children)
210
+
211
+ def _make_kwargs(self, **kwargs):
212
+ defaults = {kw: value(self, kwargs) if callable(value) else value for kw, value in self.s_default_kwargs.items()}
213
+ return defaults | kwargs | self.s_forced_kwargs | dict(key=id(self))
214
+
215
+ def __init__(self, *children, **kwargs):
216
+ assert self.s_component_class, f'{self.__class__.__name__}: has no s_component_class'
217
+ self._kwargs = self._make_kwargs(**kwargs)
218
+ self.component = None
219
+ self.subcomponent = None
220
+ kw_kids = self._kwargs.get(self.s_children_attr)
221
+ if self.s_single_child:
222
+ self._set_children(children if children else [kw_kids])
223
+ else:
224
+ self._set_children([])
225
+ self.add_children(*children)
226
+ if kw_kids is not None:
227
+ self.add_children(*kw_kids)
228
+
229
+ def add_children(self, *children):
230
+ existing_children = set(self._get_children())
231
+ if self.s_single_child:
232
+ new_children = [child for child in children if child is not None and child not in existing_children]
233
+ if new_children:
234
+ assert not existing_children
235
+ self._set_children(new_children)
236
+ else:
237
+ self.get_children().extend(child for child in children if child is not None and child not in existing_children)
238
+ self.force_update()
239
+
240
+ def with_children(self, *args):
241
+ if not args:
242
+ return self
243
+ return self.__class__(*args, **self._kwargs)
244
+
245
+ def _build_children(self, session: rio.Session):
246
+ return [child() if isinstance(child, ComponentBuilder) else child for child in self._get_children() if child is not None]
247
+
248
+ @classmethod
249
+ def create_component(cls, *children, **kwargs) -> rio.Component | None:
250
+ if cls.s_component_class:
251
+ if cls.s_pass_children_in_kwargs:
252
+ # noinspection PyAugmentAssignment
253
+ kwargs = kwargs | {'children': list(children)}
254
+ children = ()
255
+ return cls.s_component_class(*children, **kwargs)
256
+ return None
257
+
258
+ def build(self, session: rio.Session) -> rio.Component | None:
259
+ kwargs = {k: v for k, v in self._kwargs.items() if k != self.s_children_attr}
260
+ for size_adjustment in self.s_size_adjustments:
261
+ if size_adjustment in kwargs:
262
+ kwargs[size_adjustment] /= session.pixels_per_font_height
263
+ children: list = self._build_children(session)
264
+ return self.create_component(*children, **kwargs)
265
+
266
+ def __call__(self) -> rio.Component:
267
+ kw = {k: self[k] for k in self.s_layout_attrs if k in self}
268
+ return DynamicComponent(builder=self, **kw) if not self.component else self.component
269
+
270
+ def __getitem__(self, item):
271
+ if hasattr(self.subcomponent, item):
272
+ return getattr(self.subcomponent, item)
273
+ return self._kwargs[item]
274
+
275
+ def __contains__(self, item):
276
+ return item in self._kwargs
277
+
278
+ @classmethod
279
+ def _not_supported(cls, message='not supported', item=None):
280
+ item = item or inspect.stack()[1].function
281
+ print(f'{cls.__name__}.{item}: - {message}')
282
+
283
+ def __setitem__(self, item, value):
284
+ if item != self.s_children_attr and not hasattr(self.s_component_class, item):
285
+ self._not_supported(item=item)
286
+ return
287
+
288
+ if hasattr(self.subcomponent, item):
289
+ setattr(self.subcomponent, item, value)
290
+ self._kwargs[item] = value
291
+
292
+ def force_update(self):
293
+ component: rio.Component = self.component
294
+ if component:
295
+ component.force_refresh()
296
+
297
+ def setdefault(self, item, default):
298
+ try:
299
+ value = self[item]
300
+ except KeyError:
301
+ value = self[item] = default
302
+ return value
303
+
304
+ def get(self, item, default=None):
305
+ try:
306
+ return self[item]
307
+ except KeyError:
308
+ return default
309
+
310
+ def callback(self, callback):
311
+ def cb(widget, *args, **kwargs):
312
+ with session_context(widget.subcomponent.session):
313
+ # note - callback must not yield the event loop!
314
+ return callback(*args, **kwargs)
315
+
316
+ return types.MethodType(cb, self)
317
+
318
+
319
+ class Widget(ComponentBuilder, i.Widget):
320
+ __slots__ = ('_layout',)
321
+
322
+ s_component_class = rio.Container
323
+ s_stretch_arg = 'grow_x'
324
+ s_default_layout_factory = lambda: FlowLayout()
325
+ s_default_kwargs = dict(grow_y=False, align_y=0)
326
+ s_unwrap_single_child = True
327
+
328
+ def __init_subclass__(cls, **kwargs):
329
+ cls.s_default_layout_factory = None
330
+ cls.s_default_kwargs = super().s_default_kwargs | cls.s_default_kwargs
331
+
332
+ def __init__(self, *children, **kwargs):
333
+ super().__init__(*children, **kwargs)
334
+ layout_factory = self.__class__.s_default_layout_factory
335
+ self._layout = layout_factory() if layout_factory else None
336
+
337
+ def __getstate__(self):
338
+ assert getattr(self, '__dict__', self) is self, f'widgets should not have __dict__: {self}'
339
+
340
+ def unbind(o):
341
+ if isinstance(o, types.MethodType) and o.__self__:
342
+ if isinstance((s := o.__self__), Traitable):
343
+ return '__rebind_traitable__', type(s), s.id(), o.__func__
344
+ if o.__self__ is self:
345
+ return '__rebind_self__', o.__func__
346
+ raise RuntimeError(f'cannot handle bound method {o}')
347
+ return o
348
+
349
+ def slot_set(c):
350
+ slots = getattr(c, '__slots__', ())
351
+ return {slots} if isinstance(slots, str) else set(slots)
352
+
353
+ all_slots = reduce(operator.or_, (slot_set(base) for base in self.__class__.__mro__), set())
354
+ return {k: unbind(getattr(self, k)) for k in all_slots}
355
+
356
+ def __setstate__(self, state):
357
+ for k, v in state.items():
358
+ if isinstance(v, tuple):
359
+ if v[0] == '__rebind_traitable__':
360
+ t_class, t_id, func = v[1:]
361
+ v = func.__get__(t_class(t_id))
362
+ elif v[0] == '__rebind_self__':
363
+ v = v[1].__get__(self)
364
+ setattr(self, k, v)
365
+
366
+ def set_layout(self, layout: i.Layout):
367
+ self._layout = layout
368
+
369
+ def _build_children(self, session: rio.Session):
370
+ return [self._layout.with_children(*self._get_children()).build(session)] if self._layout else super()._build_children(session)
371
+
372
+ def set_stretch(self, stretch):
373
+ assert stretch in (0, 1), 'Only stretch of 0 or 1 is currently supported'
374
+ self[self.s_stretch_arg] = bool(stretch)
375
+
376
+ def apply_style_sheet(self, style: dict) -> RC:
377
+ if text_align := TEXT_ALIGN.from_str(style.pop('text-align', '')):
378
+ self[text_align.rio_attr()] = text_align.rio_value()
379
+ if hasattr(self.s_component_class, 'text_style'):
380
+ ss = StyleSheet()
381
+ rc = ss.set_values(sheet=style)
382
+ self['text_style'] = ss.text_style or None
383
+ return rc
384
+ return StyleSheet.rc(style)
385
+
386
+ def set_style_sheet(self, sh: str):
387
+ from ui_10x.utils import UxStyleSheet # TODO: circular import
388
+
389
+ rc = self.apply_style_sheet(UxStyleSheet.loads(sh))
390
+ if not rc:
391
+ print(f'{self.__class__.__name__}.set_style_sheet: \n{rc.error()}')
392
+
393
+ def set_minimum_height(self, height: int):
394
+ self['min_height'] = height
395
+
396
+ def set_minimum_width(self, width: int):
397
+ self['min_width'] = width
398
+
399
+ def set_size_policy(self, x_policy: SizePolicy, y_policy: SizePolicy):
400
+ self['grow_x'] = x_policy == SizePolicy.MINIMUM_EXPANDING
401
+ self['grow_y'] = y_policy == SizePolicy.MINIMUM_EXPANDING
402
+
403
+ def set_tool_tip(self, tooltip: str):
404
+ self['tooltip'] = tooltip
405
+
406
+ def set_text(self, text: str):
407
+ if self.s_single_child:
408
+ self[self.s_children_attr] = text
409
+ return
410
+ self._not_supported()
411
+
412
+ def set_read_only(self, read_only: bool):
413
+ self['is_sensitive'] = not read_only
414
+
415
+ def style_sheet(self) -> str:
416
+ from ui_10x.utils import UxStyleSheet # TODO: circular import
417
+
418
+ if hasattr(self.s_component_class, 'text_style'):
419
+ ss = StyleSheet()
420
+ ss.text_style = self.get('text_style')
421
+ return UxStyleSheet.dumps(ss.sheet)
422
+
423
+ self._not_supported()
424
+ return ''
425
+
426
+ def set_enabled(self, enabled: bool):
427
+ self['is_sensitive'] = enabled
428
+
429
+ def font_metrics(self) -> FontMetrics:
430
+ return FontMetrics(self)
431
+
432
+ def set_geometry(self, *args):
433
+ ##TODO
434
+ self._not_supported()
435
+
436
+ def width(self) -> int:
437
+ ##TODO
438
+ self._not_supported()
439
+ return 0
440
+
441
+ def height(self) -> int:
442
+ ##TODO
443
+ self._not_supported()
444
+ return 0
445
+
446
+ def map_to_global(self, point: Point) -> Point:
447
+ ##TODO
448
+ self._not_supported()
449
+ return point
450
+
451
+ @classmethod
452
+ def create_component(cls, *children, **kwargs) -> rio.Component | None:
453
+ if cls.s_unwrap_single_child and len(children) == 1 and isinstance(first_child := children[0], rio.Component):
454
+ if kwargs:
455
+ cls._not_supported(f'ignored kwargs {kwargs}')
456
+ return first_child
457
+ return super().create_component(*children, **kwargs)
458
+
459
+
460
+ class Layout(Widget, i.Layout):
461
+ def add_widget(self, widget: Widget, stretch=None, **kwargs):
462
+ assert not kwargs, f'kwargs not supported: {kwargs}'
463
+ assert widget is not None
464
+ if stretch is not None:
465
+ widget.set_stretch(stretch)
466
+ self.get_children().append(widget)
467
+
468
+ def add_layout(self, layout: Layout, stretch=None, **kwargs):
469
+ self.add_widget(layout, stretch=stretch, **kwargs)
470
+
471
+ def set_spacing(self, spacing: int):
472
+ assert spacing == 0, 'Only zero is supported'
473
+ self['spacing'] = 0
474
+
475
+ def set_contents_margins(self, left, top, right, bottom):
476
+ self['margin_left'] = left
477
+ self['margin_top'] = top
478
+ self['margin_right'] = right
479
+ self['margin_bottom'] = bottom
480
+
481
+
482
+ class FlowLayout(Layout, i.Layout):
483
+ s_component_class = rio.FlowContainer
484
+
485
+
486
+ class Point(i.Point):
487
+ __slots__ = ('_x', '_y')
488
+
489
+ def __init__(self, x: int = 0, y: int = 0):
490
+ self._x = x
491
+ self._y = y
492
+
493
+ def x(self) -> int:
494
+ return self._x
495
+
496
+ def y(self) -> int:
497
+ return self._y
@@ -0,0 +1,9 @@
1
+ from .group_box import GroupBox
2
+ from .labeled_checkbox import LabeledCheckBox
3
+ from .line_edit import LineEditComponent
4
+ from .radio_button import RadioButton
5
+ from .separator import Separator
6
+ from .splitter import Splitter
7
+ from .tree_view import RioTreeItem, RioTreeView
8
+
9
+ __all__ = ['GroupBox', 'LabeledCheckBox', 'LineEditComponent', 'RadioButton', 'RioTreeItem', 'RioTreeView', 'Separator', 'Splitter']
@@ -0,0 +1,31 @@
1
+ import rio
2
+
3
+
4
+ class GroupBox(rio.Component):
5
+ """
6
+ A component that groups related controls with a labeled border.
7
+
8
+ Attributes:
9
+ title: The text displayed as the group's title.
10
+ children: A list of child components to be grouped.
11
+ """
12
+
13
+ children: list[rio.Component] = []
14
+ title: str = ''
15
+
16
+ def build(self) -> rio.Component:
17
+ """
18
+ Build the UI as a fieldset with a legend title and child components.
19
+ """
20
+ return rio.Stack(
21
+ rio.Rectangle(
22
+ fill=rio.Color.TRANSPARENT,
23
+ stroke_width=0.1,
24
+ stroke_color=rio.Color.GRAY,
25
+ corner_radius=0.5,
26
+ ),
27
+ rio.Column(
28
+ rio.Text(self.title, margin_bottom=-0.5), # Title sits on the border
29
+ rio.Column(*self.children, margin=1), # Content inside
30
+ ),
31
+ )
@@ -0,0 +1,18 @@
1
+ import rio
2
+
3
+
4
+ class LabeledCheckBox(rio.Component):
5
+ label: str = ''
6
+ is_on: bool = False
7
+ is_sensitive: bool = True
8
+ on_change: rio.EventHandler[rio.CheckboxChangeEvent] = None
9
+
10
+ def build(self):
11
+ return rio.Row(
12
+ rio.Text(self.label),
13
+ rio.Checkbox(
14
+ is_on=self.bind().is_on,
15
+ is_sensitive=self.is_sensitive,
16
+ on_change=self.on_change,
17
+ ),
18
+ )