pythonnative 0.22.0__py3-none-any.whl → 0.23.0__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.
- pythonnative/__init__.py +16 -2
- pythonnative/animated.py +6 -6
- pythonnative/appearance.py +124 -0
- pythonnative/cli/pn.py +1 -1
- pythonnative/components.py +718 -48
- pythonnative/events.py +5 -5
- pythonnative/gestures.py +3 -3
- pythonnative/hooks.py +44 -3
- pythonnative/hot_reload.py +4 -4
- pythonnative/images.py +196 -0
- pythonnative/layout.py +3 -3
- pythonnative/mutations.py +4 -13
- pythonnative/native_modules/camera.py +1 -1
- pythonnative/native_modules/haptics.py +2 -2
- pythonnative/native_modules/location.py +1 -1
- pythonnative/native_modules/permissions.py +2 -2
- pythonnative/native_modules/secure_store.py +1 -1
- pythonnative/native_views/__init__.py +1 -7
- pythonnative/native_views/android.py +592 -64
- pythonnative/native_views/base.py +3 -3
- pythonnative/native_views/desktop.py +85 -26
- pythonnative/native_views/ios.py +762 -74
- pythonnative/navigation.py +4 -4
- pythonnative/net.py +3 -3
- pythonnative/platform.py +1 -1
- pythonnative/platform_metrics.py +5 -5
- pythonnative/preview.py +34 -3
- pythonnative/project/builder.py +1 -1
- pythonnative/project/config.py +4 -12
- pythonnative/project/doctor.py +2 -2
- pythonnative/project/icons.py +1 -1
- pythonnative/project/ios.py +1 -1
- pythonnative/project/permissions.py +2 -2
- pythonnative/project/runtime_assets.py +3 -3
- pythonnative/reconciler.py +9 -9
- pythonnative/runtime.py +8 -8
- pythonnative/screen.py +90 -5
- pythonnative/sdk/_components.py +1 -1
- pythonnative/storage.py +3 -3
- pythonnative/style.py +66 -7
- pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +2 -1
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNAccessibilityDelegate.kt +47 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNBorderDrawable.kt +67 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +172 -0
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +2 -2
- pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +1 -1
- pythonnative/virtual_rows.py +137 -0
- {pythonnative-0.22.0.dist-info → pythonnative-0.23.0.dist-info}/METADATA +13 -13
- {pythonnative-0.22.0.dist-info → pythonnative-0.23.0.dist-info}/RECORD +53 -47
- {pythonnative-0.22.0.dist-info → pythonnative-0.23.0.dist-info}/WHEEL +1 -1
- {pythonnative-0.22.0.dist-info → pythonnative-0.23.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.22.0.dist-info → pythonnative-0.23.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.22.0.dist-info → pythonnative-0.23.0.dist-info}/top_level.txt +0 -0
|
@@ -23,7 +23,7 @@ class ViewHandler:
|
|
|
23
23
|
|
|
24
24
|
A `ViewHandler` knows how to create, update, re-parent, and destroy
|
|
25
25
|
native views of one element type. The reconciler never calls a
|
|
26
|
-
handler directly
|
|
26
|
+
handler directly; it emits a batch of mutation ops
|
|
27
27
|
(`pythonnative.mutations`) that the
|
|
28
28
|
[`NativeViewRegistry`][pythonnative.native_views.NativeViewRegistry]
|
|
29
29
|
applies by dispatching to handlers. Handlers never need to know
|
|
@@ -132,7 +132,7 @@ class ViewHandler:
|
|
|
132
132
|
|
|
133
133
|
The default implementation returns ``(0, 0)``; override for
|
|
134
134
|
leaves whose size depends on their content. Container handlers
|
|
135
|
-
leave this alone
|
|
135
|
+
leave this alone; the engine sizes containers by laying out
|
|
136
136
|
their children.
|
|
137
137
|
|
|
138
138
|
Args:
|
|
@@ -149,7 +149,7 @@ class ViewHandler:
|
|
|
149
149
|
"""Execute an imperative command (e.g. ``"scroll_to_offset"``).
|
|
150
150
|
|
|
151
151
|
Commands are the escape hatch for one-shot imperative actions
|
|
152
|
-
that don't fit declarative props
|
|
152
|
+
that don't fit declarative props: scrolling, focusing,
|
|
153
153
|
flashing indicators. Unknown commands should be ignored.
|
|
154
154
|
|
|
155
155
|
Args:
|
|
@@ -74,7 +74,7 @@ from .base import ViewHandler
|
|
|
74
74
|
# ``pn preview`` installs it before mounting the app; the placement
|
|
75
75
|
# logic (``_place``) positions widgets *inside* their logical parent via
|
|
76
76
|
# Tk's ``-in`` option, which only works when both windows share a
|
|
77
|
-
# top-level
|
|
77
|
+
# top-level, guaranteed by the single-stage design.
|
|
78
78
|
|
|
79
79
|
_ROOT_CONTAINER: Any = None
|
|
80
80
|
_DEFAULT_FONT_SIZE = 15
|
|
@@ -125,7 +125,7 @@ def _tk_color(value: Any) -> Optional[str]:
|
|
|
125
125
|
"""Convert a PythonNative color into a Tk color string.
|
|
126
126
|
|
|
127
127
|
Accepts ``#rgb`` / ``#rrggbb`` / ``#aarrggbb`` hex (alpha is
|
|
128
|
-
dropped
|
|
128
|
+
dropped; Tk has no per-color alpha), ``rgb()`` / ``rgba()``
|
|
129
129
|
functional notation, ``(r, g, b)`` tuples, packed integers, and
|
|
130
130
|
named colors (passed through for Tk to resolve). Returns ``None``
|
|
131
131
|
for ``transparent`` / unparseable values so callers can leave the
|
|
@@ -376,10 +376,27 @@ def _apply_common(widget: Any, props: Dict[str, Any]) -> None:
|
|
|
376
376
|
widget.configure(background=color)
|
|
377
377
|
except Exception:
|
|
378
378
|
pass
|
|
379
|
-
|
|
379
|
+
side_width_keys = (
|
|
380
|
+
"border_left_width",
|
|
381
|
+
"border_top_width",
|
|
382
|
+
"border_right_width",
|
|
383
|
+
"border_bottom_width",
|
|
384
|
+
)
|
|
385
|
+
if any(k in props for k in ("border_width", "border_color", *side_width_keys)):
|
|
380
386
|
try:
|
|
381
387
|
width = props.get("border_width")
|
|
382
388
|
color = _tk_color(props.get("border_color")) or "#3c3c43"
|
|
389
|
+
# Tk highlight borders are uniform, so per-side borders are
|
|
390
|
+
# approximated with the widest side's width and its color.
|
|
391
|
+
side_widths = [props.get(k) for k in side_width_keys]
|
|
392
|
+
if any(w is not None for w in side_widths):
|
|
393
|
+
width = max(float(w) for w in side_widths if w is not None)
|
|
394
|
+
for side, w in zip(("left", "top", "right", "bottom"), side_widths):
|
|
395
|
+
if w is not None and float(w) > 0:
|
|
396
|
+
side_color = _tk_color(props.get(f"border_{side}_color"))
|
|
397
|
+
if side_color is not None:
|
|
398
|
+
color = side_color
|
|
399
|
+
break
|
|
383
400
|
if width:
|
|
384
401
|
widget.configure(
|
|
385
402
|
highlightthickness=int(round(_finite(width))),
|
|
@@ -390,6 +407,10 @@ def _apply_common(widget: Any, props: Dict[str, Any]) -> None:
|
|
|
390
407
|
widget.configure(highlightthickness=0)
|
|
391
408
|
except Exception:
|
|
392
409
|
pass
|
|
410
|
+
if "test_id" in props and props["test_id"] is not None:
|
|
411
|
+
# Stamped for introspection so preview-level tooling and tests
|
|
412
|
+
# can locate widgets the same way Maestro does on device.
|
|
413
|
+
widget._pn_test_id = str(props["test_id"])
|
|
393
414
|
if "transform" in props:
|
|
394
415
|
_set_translate_from_transform(widget, props["transform"])
|
|
395
416
|
_place(widget)
|
|
@@ -686,8 +707,8 @@ class ScrollViewHandler(FlexContainerHandler):
|
|
|
686
707
|
|
|
687
708
|
The layout engine lets the content grow past the viewport on the
|
|
688
709
|
scroll axis; this handler offsets its children's placement by the
|
|
689
|
-
current scroll offset (overflow outside the frame is *not* clipped
|
|
690
|
-
|
|
710
|
+
current scroll offset (overflow outside the frame is *not* clipped,
|
|
711
|
+
a documented preview limitation of the single-stage design).
|
|
691
712
|
|
|
692
713
|
Commands:
|
|
693
714
|
``scroll_to_offset(x=…, y=…)``: jump to an offset.
|
|
@@ -941,10 +962,13 @@ class TextInputHandler(DesktopViewHandler):
|
|
|
941
962
|
class ImageHandler(DesktopViewHandler):
|
|
942
963
|
"""Best-effort image preview.
|
|
943
964
|
|
|
944
|
-
Tk's ``PhotoImage`` loads PNG/GIF/PPM from local paths;
|
|
945
|
-
|
|
946
|
-
labeled placeholder.
|
|
947
|
-
|
|
965
|
+
Tk's ``PhotoImage`` loads PNG/GIF/PPM from local paths; JPEG isn't
|
|
966
|
+
supported without Pillow, so undecodable formats fall back to a
|
|
967
|
+
labeled placeholder. Network URLs are fetched through the shared
|
|
968
|
+
image pipeline (`pythonnative.images`), so caching and ``on_load``
|
|
969
|
+
/ ``on_error`` behave like the mobile backends. The handler keeps
|
|
970
|
+
a reference to the ``PhotoImage`` (Tk garbage-collects images that
|
|
971
|
+
aren't referenced).
|
|
948
972
|
"""
|
|
949
973
|
|
|
950
974
|
def build(self, props: Dict[str, Any]) -> Any:
|
|
@@ -952,24 +976,59 @@ class ImageHandler(DesktopViewHandler):
|
|
|
952
976
|
|
|
953
977
|
def apply(self, label: Any, props: Dict[str, Any]) -> None:
|
|
954
978
|
merged = getattr(label, "_pn_props", props)
|
|
955
|
-
if "
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if source and "://" not in str(source):
|
|
979
|
+
if "placeholder_color" in props:
|
|
980
|
+
color = _tk_color(props.get("placeholder_color"))
|
|
981
|
+
if color is not None:
|
|
959
982
|
try:
|
|
960
|
-
|
|
983
|
+
label.configure(background=color)
|
|
961
984
|
except Exception:
|
|
962
|
-
|
|
963
|
-
|
|
985
|
+
pass
|
|
986
|
+
if "source" in props:
|
|
987
|
+
source = props.get("source")
|
|
988
|
+
label._pn_pending_source = source
|
|
989
|
+
if source and "://" in str(source):
|
|
990
|
+
from ..images import fetch
|
|
991
|
+
|
|
992
|
+
def _on_ready(path: str, src: Any = source) -> None:
|
|
993
|
+
if getattr(label, "_pn_pending_source", None) == src:
|
|
994
|
+
self._show_file(label, path, src)
|
|
995
|
+
|
|
996
|
+
def _on_error(message: str, src: Any = source) -> None:
|
|
997
|
+
if getattr(label, "_pn_pending_source", None) == src:
|
|
998
|
+
self._show_fallback(label, src)
|
|
999
|
+
_fire(label, "on_error", message)
|
|
1000
|
+
|
|
1001
|
+
self._show_fallback(label, source)
|
|
1002
|
+
fetch(str(source), _on_ready, _on_error)
|
|
1003
|
+
else:
|
|
1004
|
+
self._show_file(label, str(source) if source else None, source)
|
|
1005
|
+
_apply_common(label, merged)
|
|
1006
|
+
|
|
1007
|
+
def _show_file(self, label: Any, path: Optional[str], source: Any) -> None:
|
|
1008
|
+
photo = None
|
|
1009
|
+
if path:
|
|
964
1010
|
try:
|
|
965
|
-
|
|
966
|
-
label.configure(image=photo, text="")
|
|
967
|
-
else:
|
|
968
|
-
name = str(source).rsplit("/", 1)[-1] if source else "image"
|
|
969
|
-
label.configure(image="", text=f"\U0001f5bc\n{name}", compound="center")
|
|
1011
|
+
photo = tk.PhotoImage(file=path)
|
|
970
1012
|
except Exception:
|
|
971
|
-
|
|
972
|
-
|
|
1013
|
+
photo = None
|
|
1014
|
+
label._pn_photo = photo # keep a reference alive
|
|
1015
|
+
try:
|
|
1016
|
+
if photo is not None:
|
|
1017
|
+
label.configure(image=photo, text="")
|
|
1018
|
+
_fire(label, "on_load")
|
|
1019
|
+
else:
|
|
1020
|
+
self._show_fallback(label, source)
|
|
1021
|
+
if path:
|
|
1022
|
+
_fire(label, "on_error", "decode failed")
|
|
1023
|
+
except Exception:
|
|
1024
|
+
pass
|
|
1025
|
+
|
|
1026
|
+
def _show_fallback(self, label: Any, source: Any) -> None:
|
|
1027
|
+
name = str(source).rsplit("/", 1)[-1] if source else "image"
|
|
1028
|
+
try:
|
|
1029
|
+
label.configure(image="", text=f"\U0001f5bc\n{name}", compound="center")
|
|
1030
|
+
except Exception:
|
|
1031
|
+
pass
|
|
973
1032
|
|
|
974
1033
|
def measure_intrinsic(self, native_view: Any, max_width: float, max_height: float) -> Tuple[float, float]:
|
|
975
1034
|
photo = getattr(native_view, "_pn_photo", None)
|
|
@@ -1232,7 +1291,7 @@ class PressableHandler(DesktopViewHandler):
|
|
|
1232
1291
|
|
|
1233
1292
|
|
|
1234
1293
|
class ModalHandler(DesktopViewHandler):
|
|
1235
|
-
"""Overlay modal
|
|
1294
|
+
"""Overlay modal, a frame that fills the stage when ``visible``.
|
|
1236
1295
|
|
|
1237
1296
|
The reconciler lays the modal's content out against the full
|
|
1238
1297
|
viewport (see ``Reconciler._layout_visible_modals``) and applies
|
|
@@ -1275,7 +1334,7 @@ class ModalHandler(DesktopViewHandler):
|
|
|
1275
1334
|
|
|
1276
1335
|
|
|
1277
1336
|
class TabBarHandler(DesktopViewHandler):
|
|
1278
|
-
"""Bottom tab bar
|
|
1337
|
+
"""Bottom tab bar, a row of buttons laid out across its width."""
|
|
1279
1338
|
|
|
1280
1339
|
def build(self, props: Dict[str, Any]) -> Any:
|
|
1281
1340
|
frame = tk.Frame(_master(), highlightthickness=1, bd=0, background="#f2f2f7")
|
|
@@ -1454,7 +1513,7 @@ class SegmentedControlHandler(DesktopViewHandler):
|
|
|
1454
1513
|
|
|
1455
1514
|
|
|
1456
1515
|
class DatePickerHandler(DesktopViewHandler):
|
|
1457
|
-
"""Preview DatePicker
|
|
1516
|
+
"""Preview DatePicker, a text entry for the ISO date/time string."""
|
|
1458
1517
|
|
|
1459
1518
|
def build(self, props: Dict[str, Any]) -> Any:
|
|
1460
1519
|
entry = tk.Entry(_master(), highlightthickness=1, bd=0)
|