pythonnative 0.22.1__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/appearance.py +124 -0
- pythonnative/components.py +708 -38
- pythonnative/hooks.py +41 -0
- pythonnative/images.py +196 -0
- pythonnative/mutations.py +3 -12
- pythonnative/native_views/__init__.py +1 -7
- pythonnative/native_views/android.py +573 -45
- pythonnative/native_views/desktop.py +78 -19
- pythonnative/native_views/ios.py +739 -51
- pythonnative/preview.py +31 -0
- pythonnative/project/config.py +1 -9
- pythonnative/screen.py +85 -0
- pythonnative/style.py +65 -6
- 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/virtual_rows.py +137 -0
- {pythonnative-0.22.1.dist-info → pythonnative-0.23.0.dist-info}/METADATA +1 -1
- {pythonnative-0.22.1.dist-info → pythonnative-0.23.0.dist-info}/RECORD +25 -19
- {pythonnative-0.22.1.dist-info → pythonnative-0.23.0.dist-info}/WHEEL +1 -1
- {pythonnative-0.22.1.dist-info → pythonnative-0.23.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.22.1.dist-info → pythonnative-0.23.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.22.1.dist-info → pythonnative-0.23.0.dist-info}/top_level.txt +0 -0
|
@@ -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)
|
|
@@ -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)
|