FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

"""Yoga 3.2.1 layout for headless tests and the Python preview host. Mobile hosts run this same pinned core beside their native widgets. This module contains style translation and ownership, not a second flex algorithm. """ from __future__ import annotations import ctypes as C import math import weakref from typing import Any, Callable LAYOUT_STYLE_KEYS = frozenset( { "width", "height", "min_width", "max_width", "min_height", "max_height", "flex", "flex_grow", "flex_shrink", "flex_basis", "flex_wrap", "align_self", "align_content", "position", "top", "right", "bottom", "left", "start", "end", "margin", "margin_top", "margin_bottom", "margin_left", "margin_right", "margin_start", "margin_end", "margin_horizontal", "margin_vertical", "padding", "padding_top", "padding_bottom", "padding_left", "padding_right", "padding_start", "padding_end", "padding_horizontal", "padding_vertical", "border_width", "border_top_width", "border_right_width", "border_bottom_width", "border_left_width", "flex_direction", "justify_content", "align_items", "gap", "row_gap", "column_gap", "aspect_ratio", "direction", "display", } ) """Style keys that affect layout (and are consumed by the layout engine).""" _lib: Any = None _nodes: weakref.WeakValueDictionary[int, LayoutNode] = weakref.WeakValueDictionary() _Measure = C.CFUNCTYPE( None, C.c_void_p, C.c_float, C.c_int, C.c_float, C.c_int, C.POINTER(C.c_float), C.POINTER(C.c_float) ) @_Measure def _measure(ptr: int, width: float, wm: int, height: float, hm: int, outw: Any, outh: Any) -> None: node = _nodes.get(ptr) try: assert node is not None and node.measure is not None w, h = node.measure(width if wm else math.inf, height if hm else math.inf) outw[0], outh[0] = max(0, w), max(0, h) except BaseException as exc: if node is not None: node._error = exc outw[0] = outh[0] = 0 def _api() -> Any: global _lib if _lib is not None: return _lib from . import _yoga # type: ignore[attr-defined] lib = C.CDLL(_yoga.__file__) signatures: dict[str, Any] = { "YGNodeNew": (C.c_void_p, []), "YGNodeFree": (None, [C.c_void_p]), "YGNodeGetOwner": (C.c_void_p, [C.c_void_p]), "YGNodeRemoveChild": (None, [C.c_void_p, C.c_void_p]), "YGNodeRemoveAllChildren": (None, [C.c_void_p]), "YGNodeInsertChild": (None, [C.c_void_p, C.c_void_p, C.c_size_t]), "YGNodeCopyStyle": (None, [C.c_void_p, C.c_void_p]), "YGNodeMarkDirty": (None, [C.c_void_p]), "YGNodeCalculateLayout": (None, [C.c_void_p, C.c_float, C.c_float, C.c_int]), "pn_yoga_callback": (None, [_Measure]), "pn_yoga_measure": (None, [C.c_void_p, C.c_int]), } for axis in ("Left", "Top", "Width", "Height"): signatures["YGNodeLayoutGet" + axis] = (C.c_float, [C.c_void_p]) for name, (result, args) in signatures.items(): fn = getattr(lib, name) fn.restype, fn.argtypes = result, args lib.pn_yoga_callback(_measure) _lib = lib return lib # Enum order follows Yoga's public C ABI in the pinned vendored headers. ENUMS = { "direction": ("Direction", ["inherit", "ltr", "rtl"]), "flex_direction": ("FlexDirection", ["column", "column_reverse", "row", "row_reverse"]), "justify_content": ( "JustifyContent", ["flex_start", "center", "flex_end", "space_between", "space_around", "space_evenly"], ), "align_items": ( "AlignItems", [ "auto", "flex_start", "center", "flex_end", "stretch", "baseline", "space_between", "space_around", "space_evenly", ], ), "align_self": ( "AlignSelf", [ "auto", "flex_start", "center", "flex_end", "stretch", "baseline", "space_between", "space_around", "space_evenly", ], ), "align_content": ( "AlignContent", [ "auto", "flex_start", "center", "flex_end", "stretch", "baseline", "space_between", "space_around", "space_evenly", ], ), "position": ("PositionType", ["static", "relative", "absolute"]), "flex_wrap": ("FlexWrap", ["nowrap", "wrap", "wrap_reverse"]), "display": ("Display", ["flex", "none", "contents"]), } EDGES = { name: i for i, name in enumerate(["left", "top", "right", "bottom", "start", "end", "horizontal", "vertical", "all"]) } ALIASES = { "start": "flex_start", "leading": "flex_start", "top": "flex_start", "end": "flex_end", "trailing": "flex_end", "bottom": "flex_end", "fill": "stretch", } def _set(ptr: int, name: str, value: Any, edge: int | None = None, *, enum: bool = False) -> None: if value is None or isinstance(value, bool): return suffix = "" args: list[Any] = [ptr] types: list[Any] = [C.c_void_p] if edge is not None: args.append(edge) types.append(C.c_int) if value == "auto": suffix = "Auto" else: if isinstance(value, str) and value.endswith("%"): suffix, value = "Percent", float(value[:-1]) args.append(int(value) if enum else float(value)) types.append(C.c_int if enum else C.c_float) fn = getattr(_api(), "YGNodeStyleSet" + name + suffix) fn.restype, fn.argtypes = None, types fn(*args) def apply_style(ptr: int, style: dict[str, Any]) -> None: """Translate Pythonic style keys to Yoga's native style API.""" for key, value in style.items(): if value is None: continue if key in ENUMS: name, values = ENUMS[key] value = ALIASES.get(value, value) if key.startswith(("align_", "justify_")) else value if value not in values: raise ValueError(f"Invalid {key}: {value!r}") _set(ptr, name, values.index(value), enum=True) elif key in { "width", "height", "min_width", "min_height", "max_width", "max_height", "flex", "flex_grow", "flex_shrink", "flex_basis", "aspect_ratio", }: _set(ptr, "".join(part.title() for part in key.split("_")), value) elif key in {"gap", "row_gap", "column_gap"}: _set(ptr, "Gap", value, {"column_gap": 0, "row_gap": 1}.get(key, 2)) elif key in EDGES and key not in {"all", "horizontal", "vertical"}: _set(ptr, "Position", value, EDGES[key]) elif key.startswith(("padding", "margin")): group, _, edge = key.partition("_") if isinstance(value, dict): for side, amount in value.items(): _set(ptr, group.title(), amount, EDGES[side]) else: _set(ptr, group.title(), value, EDGES[edge or "all"]) elif key.startswith("border_") and key.endswith("width"): _set(ptr, "Border", value, EDGES[key[7:-6] or "all"]) class LayoutNode: """An owned Yoga node, with Python measurement for headless backends.""" def __init__( self, style: dict[str, Any] | None = None, children: list[LayoutNode] | None = None, measure: Callable[[float, float], tuple[float, float]] | None = None, user_data: Any = None, ) -> None: self.style = dict(style or {}) self.children = list(children or []) self.measure = measure self.user_data = user_data self.x = self.y = self.width = self.height = 0.0 self.dirty = True self._pn_scroll_axis: str | None = None self._ptr: int | None = None self._last_style: Any = None self._last_children: tuple[int, ...] = () self._error: BaseException | None = None def _sync(self) -> int: lib = _api() if self._ptr is None: self._ptr = lib.YGNodeNew() _nodes[self._ptr] = self weakref.finalize(self, lib.YGNodeFree, self._ptr) ptr = self._ptr style = dict(self.style) if self._pn_scroll_axis: style.setdefault("flex_shrink", 1) if style != self._last_style: fresh = lib.YGNodeNew() apply_style(fresh, style) if self._pn_scroll_axis: _set(fresh, "Overflow", 1, enum=True) lib.YGNodeCopyStyle(ptr, fresh) lib.YGNodeFree(fresh) self._last_style = style measured = self.measure is not None and not self.children lib.pn_yoga_measure(ptr, measured) if measured and self.dirty: lib.YGNodeMarkDirty(ptr) children = tuple(child._sync() for child in self.children) if children != self._last_children: lib.YGNodeRemoveAllChildren(ptr) for i, child in enumerate(children): owner = lib.YGNodeGetOwner(child) if owner: lib.YGNodeRemoveChild(owner, child) previous = _nodes.get(owner) if previous is not None: previous._last_children = () lib.YGNodeInsertChild(ptr, child, i) self._last_children = children return ptr def _read(self) -> None: lib = _api() self.x = lib.YGNodeLayoutGetLeft(self._ptr) self.y = lib.YGNodeLayoutGetTop(self._ptr) self.width = lib.YGNodeLayoutGetWidth(self._ptr) self.height = lib.YGNodeLayoutGetHeight(self._ptr) self.dirty = False if self._error is not None: error, self._error = self._error, None raise error for child in self.children: child._read() def calculate_layout(node: LayoutNode, width: float, height: float) -> None: """Calculate a tree's layout with the pinned Yoga core.""" ptr = node._sync() _api().YGNodeCalculateLayout( ptr, width if math.isfinite(width) else math.nan, height if math.isfinite(height) else math.nan, 1 ) node._read() def extract_layout_style(props: dict[str, Any]) -> dict[str, Any]: """Select properties owned by layout.""" return {key: value for key, value in props.items() if key in LAYOUT_STYLE_KEYS}

Back | FazBrowse Home | New Git URL