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.
- core_10x/__init__.py +42 -0
- core_10x/backbone/__init__.py +0 -0
- core_10x/backbone/backbone_store.py +59 -0
- core_10x/backbone/backbone_traitable.py +30 -0
- core_10x/backbone/backbone_user.py +66 -0
- core_10x/backbone/bound_data_domain.py +49 -0
- core_10x/backbone/namespace.py +101 -0
- core_10x/backbone/vault.py +38 -0
- core_10x/code_samples/__init__.py +0 -0
- core_10x/code_samples/_package_manifest.py +3 -0
- core_10x/code_samples/directories.py +181 -0
- core_10x/code_samples/person.py +76 -0
- core_10x/concrete_traits.py +356 -0
- core_10x/conftest.py +12 -0
- core_10x/curve.py +321 -0
- core_10x/data_domain.py +48 -0
- core_10x/data_domain_binder.py +45 -0
- core_10x/directory.py +250 -0
- core_10x/entity.py +8 -0
- core_10x/entity_filter.py +5 -0
- core_10x/environment_variables.py +147 -0
- core_10x/exec_control.py +84 -0
- core_10x/experimental/__init__.py +0 -0
- core_10x/experimental/data_protocol_ex.py +34 -0
- core_10x/global_cache.py +121 -0
- core_10x/manual_tests/__init__.py +0 -0
- core_10x/manual_tests/calendar_test.py +35 -0
- core_10x/manual_tests/ctor_update_bug.py +58 -0
- core_10x/manual_tests/debug_graph_on.py +17 -0
- core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
- core_10x/manual_tests/enum_bits_test.py +17 -0
- core_10x/manual_tests/env_vars_trivial_test.py +12 -0
- core_10x/manual_tests/existing_traitable.py +33 -0
- core_10x/manual_tests/k10x_test1.py +13 -0
- core_10x/manual_tests/named_constant_test.py +121 -0
- core_10x/manual_tests/nucleus_trivial_test.py +42 -0
- core_10x/manual_tests/polars_test.py +14 -0
- core_10x/manual_tests/py_class_test.py +4 -0
- core_10x/manual_tests/rc_test.py +42 -0
- core_10x/manual_tests/rdate_test.py +12 -0
- core_10x/manual_tests/reference_serialization_bug.py +19 -0
- core_10x/manual_tests/resource_trivial_test.py +10 -0
- core_10x/manual_tests/store_uri_test.py +6 -0
- core_10x/manual_tests/trait_definition_test.py +19 -0
- core_10x/manual_tests/trait_filter_test.py +15 -0
- core_10x/manual_tests/trait_flag_modification_test.py +42 -0
- core_10x/manual_tests/trait_modification_bug.py +26 -0
- core_10x/manual_tests/traitable_as_of_test.py +82 -0
- core_10x/manual_tests/traitable_heir_test.py +39 -0
- core_10x/manual_tests/traitable_history_test.py +41 -0
- core_10x/manual_tests/traitable_serialization_test.py +54 -0
- core_10x/manual_tests/traitable_trivial_test.py +71 -0
- core_10x/manual_tests/trivial_graph_test.py +16 -0
- core_10x/manual_tests/ts_class_association_test.py +64 -0
- core_10x/manual_tests/ts_trivial_test.py +35 -0
- core_10x/named_constant.py +425 -0
- core_10x/nucleus.py +81 -0
- core_10x/package_manifest.py +85 -0
- core_10x/package_refactoring.py +153 -0
- core_10x/py_class.py +431 -0
- core_10x/rc.py +155 -0
- core_10x/rdate.py +339 -0
- core_10x/resource.py +189 -0
- core_10x/roman_number.py +67 -0
- core_10x/testlib/__init__.py +0 -0
- core_10x/testlib/test_store.py +240 -0
- core_10x/testlib/traitable_history_tests.py +787 -0
- core_10x/testlib/ts_tests.py +280 -0
- core_10x/trait.py +377 -0
- core_10x/trait_definition.py +176 -0
- core_10x/trait_filter.py +205 -0
- core_10x/trait_method_error.py +36 -0
- core_10x/traitable.py +1082 -0
- core_10x/traitable_cli.py +153 -0
- core_10x/traitable_heir.py +33 -0
- core_10x/traitable_id.py +31 -0
- core_10x/ts_store.py +172 -0
- core_10x/ts_store_type.py +26 -0
- core_10x/ts_union.py +147 -0
- core_10x/ui_hint.py +153 -0
- core_10x/unit_tests/test_concrete_traits.py +156 -0
- core_10x/unit_tests/test_converters.py +51 -0
- core_10x/unit_tests/test_curve.py +157 -0
- core_10x/unit_tests/test_directory.py +54 -0
- core_10x/unit_tests/test_documentation.py +172 -0
- core_10x/unit_tests/test_environment_variables.py +15 -0
- core_10x/unit_tests/test_filters.py +239 -0
- core_10x/unit_tests/test_graph.py +348 -0
- core_10x/unit_tests/test_named_constant.py +98 -0
- core_10x/unit_tests/test_rc.py +11 -0
- core_10x/unit_tests/test_rdate.py +484 -0
- core_10x/unit_tests/test_trait_method_error.py +80 -0
- core_10x/unit_tests/test_trait_modification.py +19 -0
- core_10x/unit_tests/test_traitable.py +959 -0
- core_10x/unit_tests/test_traitable_history.py +1 -0
- core_10x/unit_tests/test_ts_store.py +1 -0
- core_10x/unit_tests/test_ts_union.py +369 -0
- core_10x/unit_tests/test_ui_nodes.py +81 -0
- core_10x/unit_tests/test_xxcalendar.py +471 -0
- core_10x/vault/__init__.py +0 -0
- core_10x/vault/sec_keys.py +133 -0
- core_10x/vault/security_keys_old.py +168 -0
- core_10x/vault/vault.py +56 -0
- core_10x/vault/vault_traitable.py +56 -0
- core_10x/vault/vault_user.py +70 -0
- core_10x/xdate_time.py +136 -0
- core_10x/xnone.py +71 -0
- core_10x/xxcalendar.py +228 -0
- infra_10x/__init__.py +0 -0
- infra_10x/manual_tests/__init__.py +0 -0
- infra_10x/manual_tests/test_misc.py +16 -0
- infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
- infra_10x/mongodb_admin.py +111 -0
- infra_10x/mongodb_store.py +346 -0
- infra_10x/mongodb_utils.py +129 -0
- infra_10x/unit_tests/conftest.py +13 -0
- infra_10x/unit_tests/test_mongo_db.py +36 -0
- infra_10x/unit_tests/test_mongo_history.py +1 -0
- py10x_universe-0.1.3.dist-info/METADATA +406 -0
- py10x_universe-0.1.3.dist-info/RECORD +214 -0
- py10x_universe-0.1.3.dist-info/WHEEL +4 -0
- py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
- ui_10x/__init__.py +0 -0
- ui_10x/apps/__init__.py +0 -0
- ui_10x/apps/collection_editor_app.py +100 -0
- ui_10x/choice.py +212 -0
- ui_10x/collection_editor.py +135 -0
- ui_10x/concrete_trait_widgets.py +220 -0
- ui_10x/conftest.py +8 -0
- ui_10x/entity_stocker.py +173 -0
- ui_10x/examples/__init__.py +0 -0
- ui_10x/examples/_guess_word_data.py +14076 -0
- ui_10x/examples/collection_editor.py +17 -0
- ui_10x/examples/date_selector.py +14 -0
- ui_10x/examples/entity_stocker.py +18 -0
- ui_10x/examples/guess_word.py +392 -0
- ui_10x/examples/message_box.py +20 -0
- ui_10x/examples/multi_choice.py +17 -0
- ui_10x/examples/py_data_browser.py +66 -0
- ui_10x/examples/radiobox.py +29 -0
- ui_10x/examples/single_choice.py +31 -0
- ui_10x/examples/style_sheet.py +47 -0
- ui_10x/examples/trivial_entity_editor.py +18 -0
- ui_10x/platform.py +20 -0
- ui_10x/platform_interface.py +517 -0
- ui_10x/py_data_browser.py +249 -0
- ui_10x/qt6/__init__.py +0 -0
- ui_10x/qt6/conftest.py +8 -0
- ui_10x/qt6/manual_tests/__init__.py +0 -0
- ui_10x/qt6/manual_tests/basic_test.py +35 -0
- ui_10x/qt6/platform_implementation.py +275 -0
- ui_10x/qt6/utils.py +665 -0
- ui_10x/rio/__init__.py +0 -0
- ui_10x/rio/apps/examples/examples/__init__.py +22 -0
- ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
- ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
- ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
- ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
- ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
- ui_10x/rio/apps/examples/rio.toml +14 -0
- ui_10x/rio/component_builder.py +497 -0
- ui_10x/rio/components/__init__.py +9 -0
- ui_10x/rio/components/group_box.py +31 -0
- ui_10x/rio/components/labeled_checkbox.py +18 -0
- ui_10x/rio/components/line_edit.py +37 -0
- ui_10x/rio/components/radio_button.py +32 -0
- ui_10x/rio/components/separator.py +24 -0
- ui_10x/rio/components/splitter.py +121 -0
- ui_10x/rio/components/tree_view.py +75 -0
- ui_10x/rio/conftest.py +35 -0
- ui_10x/rio/internals/__init__.py +0 -0
- ui_10x/rio/internals/app.py +192 -0
- ui_10x/rio/manual_tests/__init__.py +0 -0
- ui_10x/rio/manual_tests/basic_test.py +24 -0
- ui_10x/rio/manual_tests/splitter.py +27 -0
- ui_10x/rio/platform_implementation.py +91 -0
- ui_10x/rio/style_sheet.py +53 -0
- ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
- ui_10x/rio/unit_tests/test_internals.py +630 -0
- ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
- ui_10x/rio/widgets/__init__.py +46 -0
- ui_10x/rio/widgets/application.py +109 -0
- ui_10x/rio/widgets/button.py +48 -0
- ui_10x/rio/widgets/button_group.py +60 -0
- ui_10x/rio/widgets/calendar.py +23 -0
- ui_10x/rio/widgets/checkbox.py +24 -0
- ui_10x/rio/widgets/dialog.py +137 -0
- ui_10x/rio/widgets/group_box.py +27 -0
- ui_10x/rio/widgets/layout.py +34 -0
- ui_10x/rio/widgets/line_edit.py +37 -0
- ui_10x/rio/widgets/list.py +105 -0
- ui_10x/rio/widgets/message_box.py +70 -0
- ui_10x/rio/widgets/scroll_area.py +31 -0
- ui_10x/rio/widgets/spacer.py +6 -0
- ui_10x/rio/widgets/splitter.py +45 -0
- ui_10x/rio/widgets/text_edit.py +28 -0
- ui_10x/rio/widgets/tree.py +89 -0
- ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
- ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
- ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
- ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
- ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
- ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
- ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
- ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
- ui_10x/table_header_view.py +305 -0
- ui_10x/table_view.py +174 -0
- ui_10x/trait_editor.py +189 -0
- ui_10x/trait_widget.py +131 -0
- ui_10x/traitable_editor.py +200 -0
- ui_10x/traitable_view.py +131 -0
- ui_10x/unit_tests/conftest.py +8 -0
- ui_10x/unit_tests/test_platform.py +9 -0
- 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
|
+
)
|