yoga-python 0.1.0__tar.gz
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.
- yoga_python-0.1.0/.github/workflows/create-release.yml +91 -0
- yoga_python-0.1.0/.github/workflows/quality.yml +54 -0
- yoga_python-0.1.0/.github/workflows/release.yml +159 -0
- yoga_python-0.1.0/.gitignore +30 -0
- yoga_python-0.1.0/.python-version +1 -0
- yoga_python-0.1.0/CMakeLists.txt +47 -0
- yoga_python-0.1.0/PKG-INFO +199 -0
- yoga_python-0.1.0/README.md +188 -0
- yoga_python-0.1.0/pyproject.toml +89 -0
- yoga_python-0.1.0/pyrightconfig.json +7 -0
- yoga_python-0.1.0/src/yoga/__init__.py +8 -0
- yoga_python-0.1.0/src/yoga/__init__.pyi +582 -0
- yoga_python-0.1.0/src/yoga/enums.py +132 -0
- yoga_python-0.1.0/src/yoga/value.py +54 -0
- yoga_python-0.1.0/src/yoga/yoga.cpp +1085 -0
- yoga_python-0.1.0/tests/conftest.py +11 -0
- yoga_python-0.1.0/tests/test_absolute_position.py +1550 -0
- yoga_python-0.1.0/tests/test_align_content.py +5075 -0
- yoga_python-0.1.0/tests/test_align_items.py +2175 -0
- yoga_python-0.1.0/tests/test_align_self.py +234 -0
- yoga_python-0.1.0/tests/test_android_news_feed.py +310 -0
- yoga_python-0.1.0/tests/test_aspect_ratio.py +978 -0
- yoga_python-0.1.0/tests/test_auto.py +283 -0
- yoga_python-0.1.0/tests/test_baseline.py +673 -0
- yoga_python-0.1.0/tests/test_baseline_func.py +55 -0
- yoga_python-0.1.0/tests/test_border.py +198 -0
- yoga_python-0.1.0/tests/test_box_sizing.py +2174 -0
- yoga_python-0.1.0/tests/test_clone_node.py +92 -0
- yoga_python-0.1.0/tests/test_computed_margin.py +105 -0
- yoga_python-0.1.0/tests/test_computed_padding.py +105 -0
- yoga_python-0.1.0/tests/test_config.py +33 -0
- yoga_python-0.1.0/tests/test_default_values.py +178 -0
- yoga_python-0.1.0/tests/test_dimension.py +87 -0
- yoga_python-0.1.0/tests/test_dirtied.py +109 -0
- yoga_python-0.1.0/tests/test_dirty_marking.py +309 -0
- yoga_python-0.1.0/tests/test_display.py +969 -0
- yoga_python-0.1.0/tests/test_display_contents.py +65 -0
- yoga_python-0.1.0/tests/test_edge.py +152 -0
- yoga_python-0.1.0/tests/test_events.py +266 -0
- yoga_python-0.1.0/tests/test_flex.py +574 -0
- yoga_python-0.1.0/tests/test_flex_direction.py +4060 -0
- yoga_python-0.1.0/tests/test_flex_gap.py +91 -0
- yoga_python-0.1.0/tests/test_flex_wrap.py +1845 -0
- yoga_python-0.1.0/tests/test_float_optional.py +299 -0
- yoga_python-0.1.0/tests/test_gap.py +2902 -0
- yoga_python-0.1.0/tests/test_had_overflow.py +139 -0
- yoga_python-0.1.0/tests/test_intrinsic_size.py +2776 -0
- yoga_python-0.1.0/tests/test_justify_content.py +1876 -0
- yoga_python-0.1.0/tests/test_layoutable_children.py +182 -0
- yoga_python-0.1.0/tests/test_margin.py +1647 -0
- yoga_python-0.1.0/tests/test_measure.py +859 -0
- yoga_python-0.1.0/tests/test_measure_cache.py +166 -0
- yoga_python-0.1.0/tests/test_measure_mode.py +258 -0
- yoga_python-0.1.0/tests/test_min_max_dimension.py +1202 -0
- yoga_python-0.1.0/tests/test_node_callback.py +77 -0
- yoga_python-0.1.0/tests/test_node_child.py +54 -0
- yoga_python-0.1.0/tests/test_ordinals.py +20 -0
- yoga_python-0.1.0/tests/test_padding.py +279 -0
- yoga_python-0.1.0/tests/test_percentage.py +1494 -0
- yoga_python-0.1.0/tests/test_persistence.py +249 -0
- yoga_python-0.1.0/tests/test_persistent_node_cloning.py +117 -0
- yoga_python-0.1.0/tests/test_relayout.py +257 -0
- yoga_python-0.1.0/tests/test_rounding.py +1031 -0
- yoga_python-0.1.0/tests/test_rounding_function.py +154 -0
- yoga_python-0.1.0/tests/test_rounding_measure.py +112 -0
- yoga_python-0.1.0/tests/test_scale_change.py +173 -0
- yoga_python-0.1.0/tests/test_size_overflow.py +164 -0
- yoga_python-0.1.0/tests/test_small_value_buffer.py +254 -0
- yoga_python-0.1.0/tests/test_static_position.py +5437 -0
- yoga_python-0.1.0/tests/test_style.py +43 -0
- yoga_python-0.1.0/tests/test_style_value_pool.py +92 -0
- yoga_python-0.1.0/tests/test_tree_mutation.py +93 -0
- yoga_python-0.1.0/tests/test_value.py +15 -0
- yoga_python-0.1.0/tests/test_zero_out_layout.py +36 -0
- yoga_python-0.1.0/uv.lock +246 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
name: Create Release Tag
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
release_type:
|
|
7
|
+
description: 'Release type'
|
|
8
|
+
required: true
|
|
9
|
+
type: choice
|
|
10
|
+
options:
|
|
11
|
+
- patch
|
|
12
|
+
- minor
|
|
13
|
+
- major
|
|
14
|
+
default: patch
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
create-tag:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
permissions:
|
|
20
|
+
contents: write
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
token: ${{ secrets.RELEASE_TOKEN }}
|
|
27
|
+
|
|
28
|
+
- name: Configure Git
|
|
29
|
+
run: |
|
|
30
|
+
git config user.name "${{ github.actor }}"
|
|
31
|
+
git config user.email "${{ github.actor }}@users.noreply.github.com"
|
|
32
|
+
|
|
33
|
+
- name: Get current version and calculate next version
|
|
34
|
+
id: version
|
|
35
|
+
run: |
|
|
36
|
+
CURRENT_VERSION=$(grep "^version = " pyproject.toml | cut -d'"' -f2)
|
|
37
|
+
echo "Current version: $CURRENT_VERSION"
|
|
38
|
+
|
|
39
|
+
if [ -z "$CURRENT_VERSION" ]; then
|
|
40
|
+
echo "Error: Could not extract version"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
|
|
45
|
+
|
|
46
|
+
RELEASE_TYPE="${{ inputs.release_type }}"
|
|
47
|
+
case $RELEASE_TYPE in
|
|
48
|
+
major)
|
|
49
|
+
NEW_MAJOR=$((MAJOR + 1)); NEW_MINOR=0; NEW_PATCH=0
|
|
50
|
+
;;
|
|
51
|
+
minor)
|
|
52
|
+
NEW_MAJOR=$MAJOR; NEW_MINOR=$((MINOR + 1)); NEW_PATCH=0
|
|
53
|
+
;;
|
|
54
|
+
patch)
|
|
55
|
+
NEW_MAJOR=$MAJOR; NEW_MINOR=$MINOR; NEW_PATCH=$((PATCH + 1))
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
|
|
59
|
+
NEW_VERSION="$NEW_MAJOR.$NEW_MINOR.$NEW_PATCH"
|
|
60
|
+
|
|
61
|
+
echo "Current: $CURRENT_VERSION -> New: $NEW_VERSION ($RELEASE_TYPE)"
|
|
62
|
+
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
|
63
|
+
echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
64
|
+
|
|
65
|
+
- name: Check if version already exists
|
|
66
|
+
run: |
|
|
67
|
+
VERSION="v${{ steps.version.outputs.new }}"
|
|
68
|
+
if git rev-parse "$VERSION" >/dev/null 2>&1; then
|
|
69
|
+
echo "Tag $VERSION already exists!"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
echo "Tag $VERSION does not exist"
|
|
73
|
+
|
|
74
|
+
- name: Update version in pyproject.toml
|
|
75
|
+
run: |
|
|
76
|
+
VERSION="${{ steps.version.outputs.new }}"
|
|
77
|
+
sed -i "s/^version = .*/version = \"$VERSION\"/" pyproject.toml
|
|
78
|
+
grep "^version = " pyproject.toml
|
|
79
|
+
|
|
80
|
+
- name: Commit and push
|
|
81
|
+
run: |
|
|
82
|
+
git add pyproject.toml
|
|
83
|
+
git commit -m "chore: bump version to ${{ steps.version.outputs.new }}"
|
|
84
|
+
git push origin main
|
|
85
|
+
|
|
86
|
+
- name: Create and push tag
|
|
87
|
+
run: |
|
|
88
|
+
VERSION="v${{ steps.version.outputs.new }}"
|
|
89
|
+
git tag -a "$VERSION" -m "Release $VERSION"
|
|
90
|
+
git push origin "$VERSION"
|
|
91
|
+
echo "Created tag $VERSION - this will trigger release workflow"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Code Quality
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
quality:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
+
run: uv python install ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies and build extension
|
|
26
|
+
run: uv sync --all-extras
|
|
27
|
+
|
|
28
|
+
- name: Lint with ruff
|
|
29
|
+
run: |
|
|
30
|
+
uv run ruff check
|
|
31
|
+
uv run ruff format --check
|
|
32
|
+
|
|
33
|
+
- name: Type check with pyright
|
|
34
|
+
run: uv run pyright
|
|
35
|
+
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: |
|
|
38
|
+
uv run pytest tests/ -v --tb=short --cov=src/yoga --cov-report=term-missing --cov-report=xml
|
|
39
|
+
|
|
40
|
+
- name: Upload coverage to Codecov
|
|
41
|
+
uses: codecov/codecov-action@v4
|
|
42
|
+
if: matrix.python-version == '3.12'
|
|
43
|
+
with:
|
|
44
|
+
file: ./coverage.xml
|
|
45
|
+
flags: unittests
|
|
46
|
+
name: codecov-umbrella
|
|
47
|
+
fail_ci_if_error: false
|
|
48
|
+
|
|
49
|
+
quality-check:
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
needs: quality
|
|
52
|
+
steps:
|
|
53
|
+
- name: All quality checks passed
|
|
54
|
+
run: echo "✅ All quality checks passed!"
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
name: 🚀 Release to PyPI and GitHub
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
version:
|
|
10
|
+
description: 'Version to release (e.g., 0.1.0)'
|
|
11
|
+
required: true
|
|
12
|
+
type: string
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build-and-validate:
|
|
19
|
+
name: 🏗️ Build and Validate Package
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout code
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
|
|
28
|
+
- name: Set up Python
|
|
29
|
+
uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: '3.12'
|
|
32
|
+
|
|
33
|
+
- name: Install uv
|
|
34
|
+
uses: astral-sh/setup-uv@v4
|
|
35
|
+
|
|
36
|
+
- name: Set up Python
|
|
37
|
+
run: uv python install 3.12
|
|
38
|
+
|
|
39
|
+
- name: Install Python build dependencies
|
|
40
|
+
run: |
|
|
41
|
+
python -m pip install --upgrade pip
|
|
42
|
+
pip install twine check-wheel-contents validate-pyproject auditwheel
|
|
43
|
+
|
|
44
|
+
- name: Validate pyproject.toml
|
|
45
|
+
run: validate-pyproject pyproject.toml
|
|
46
|
+
|
|
47
|
+
- name: Build Python package
|
|
48
|
+
run: uv build
|
|
49
|
+
|
|
50
|
+
- name: Repair Linux wheel for PyPI (manylinux tag)
|
|
51
|
+
run: |
|
|
52
|
+
for whl in dist/*linux_x86_64.whl dist/*linux_aarch64.whl; do
|
|
53
|
+
[ -f "$whl" ] || continue
|
|
54
|
+
auditwheel repair "$whl" -w dist
|
|
55
|
+
rm -f "$whl"
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
- name: Validate package contents
|
|
59
|
+
run: |
|
|
60
|
+
echo "📦 Validating package structure..."
|
|
61
|
+
check-wheel-contents dist/*.whl --ignore W004,W005,W009,W010
|
|
62
|
+
|
|
63
|
+
echo "📋 Package contents summary:"
|
|
64
|
+
python -m zipfile -l dist/*.whl | grep -E "\.py$|\.so$" | head -25
|
|
65
|
+
echo "..."
|
|
66
|
+
|
|
67
|
+
- name: Upload build artifacts
|
|
68
|
+
uses: actions/upload-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: python-package-distributions
|
|
71
|
+
path: dist/
|
|
72
|
+
retention-days: 7
|
|
73
|
+
|
|
74
|
+
publish-to-testpypi:
|
|
75
|
+
name: 🧪 Publish to TestPyPI
|
|
76
|
+
needs: [build-and-validate]
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
if: github.event_name == 'workflow_dispatch'
|
|
79
|
+
environment:
|
|
80
|
+
name: test-pypi
|
|
81
|
+
url: https://test.pypi.org/p/yoga-python
|
|
82
|
+
permissions:
|
|
83
|
+
id-token: write # OIDC trusted publishing
|
|
84
|
+
|
|
85
|
+
steps:
|
|
86
|
+
- name: Download package distributions
|
|
87
|
+
uses: actions/download-artifact@v4
|
|
88
|
+
with:
|
|
89
|
+
name: python-package-distributions
|
|
90
|
+
path: dist/
|
|
91
|
+
|
|
92
|
+
- name: Publish to TestPyPI
|
|
93
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
94
|
+
with:
|
|
95
|
+
repository-url: https://test.pypi.org/legacy/
|
|
96
|
+
print-hash: true
|
|
97
|
+
|
|
98
|
+
publish-to-pypi:
|
|
99
|
+
name: 🚀 Publish to PyPI
|
|
100
|
+
needs: [build-and-validate]
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
103
|
+
environment:
|
|
104
|
+
name: pypi
|
|
105
|
+
url: https://pypi.org/p/yoga-python
|
|
106
|
+
permissions:
|
|
107
|
+
id-token: write # OIDC trusted publishing
|
|
108
|
+
contents: write # For GitHub release creation
|
|
109
|
+
|
|
110
|
+
steps:
|
|
111
|
+
- name: Download package distributions
|
|
112
|
+
uses: actions/download-artifact@v4
|
|
113
|
+
with:
|
|
114
|
+
name: python-package-distributions
|
|
115
|
+
path: dist/
|
|
116
|
+
|
|
117
|
+
- name: Publish to PyPI
|
|
118
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
119
|
+
with:
|
|
120
|
+
print-hash: true
|
|
121
|
+
|
|
122
|
+
create-github-release:
|
|
123
|
+
name: 📋 Create GitHub Release
|
|
124
|
+
needs: [publish-to-pypi]
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
127
|
+
permissions:
|
|
128
|
+
contents: write
|
|
129
|
+
|
|
130
|
+
steps:
|
|
131
|
+
- name: Checkout code
|
|
132
|
+
uses: actions/checkout@v4
|
|
133
|
+
with:
|
|
134
|
+
fetch-depth: 0
|
|
135
|
+
|
|
136
|
+
- name: Download package distributions
|
|
137
|
+
uses: actions/download-artifact@v4
|
|
138
|
+
with:
|
|
139
|
+
name: python-package-distributions
|
|
140
|
+
path: dist/
|
|
141
|
+
|
|
142
|
+
- name: Determine release type
|
|
143
|
+
id: release-type
|
|
144
|
+
run: |
|
|
145
|
+
if [[ ${{ github.ref_name }} =~ -alpha|-beta|-rc ]]; then
|
|
146
|
+
echo "prerelease=true" >> $GITHUB_OUTPUT
|
|
147
|
+
else
|
|
148
|
+
echo "prerelease=false" >> $GITHUB_OUTPUT
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
- name: Create GitHub Release
|
|
152
|
+
run: |
|
|
153
|
+
gh release create ${{ github.ref_name }} \
|
|
154
|
+
--title "yoga-python ${{ github.ref_name }}" \
|
|
155
|
+
--generate-notes \
|
|
156
|
+
${{ steps.release-type.outputs.prerelease == 'true' && '--prerelease' || '--latest' }} \
|
|
157
|
+
dist/*
|
|
158
|
+
env:
|
|
159
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
|
|
12
|
+
# Cache directories
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
|
|
17
|
+
# Build artifacts
|
|
18
|
+
*.so
|
|
19
|
+
*.cmake/
|
|
20
|
+
CMakeFiles/
|
|
21
|
+
CMakeCache.txt
|
|
22
|
+
build_test/
|
|
23
|
+
src/yoga/*.so
|
|
24
|
+
src/yoga/CMakeInit.txt
|
|
25
|
+
src/yoga/install_manifest.txt
|
|
26
|
+
src/yoga/__pycache__/
|
|
27
|
+
|
|
28
|
+
# Reference C++ code (used for porting, not part of this repo)
|
|
29
|
+
reference/
|
|
30
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.15)
|
|
2
|
+
project(yoga_python)
|
|
3
|
+
|
|
4
|
+
# Allow FetchContent_Populate (we use it to add only inner yoga, not root install)
|
|
5
|
+
if(POLICY CMP0169)
|
|
6
|
+
cmake_policy(SET CMP0169 OLD)
|
|
7
|
+
endif()
|
|
8
|
+
|
|
9
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
10
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
11
|
+
set(PYBIND11_FINDPYTHON ON)
|
|
12
|
+
|
|
13
|
+
set(yoga_BUILD_TESTS OFF CACHE BOOL "Build Yoga tests" FORCE)
|
|
14
|
+
|
|
15
|
+
# Use local reference yoga if present, otherwise fetch from GitHub
|
|
16
|
+
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/reference/yoga/CMakeLists.txt)
|
|
17
|
+
message(STATUS "Using local reference yoga")
|
|
18
|
+
find_package(pybind11 REQUIRED)
|
|
19
|
+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/reference/yoga ${CMAKE_BINARY_DIR}/yoga-build EXCLUDE_FROM_ALL)
|
|
20
|
+
else()
|
|
21
|
+
message(STATUS "Fetching yoga from GitHub...")
|
|
22
|
+
include(FetchContent)
|
|
23
|
+
FetchContent_Declare(
|
|
24
|
+
yoga
|
|
25
|
+
GIT_REPOSITORY https://github.com/facebook/yoga.git
|
|
26
|
+
GIT_TAG cfdacac0e3c2e91ab15939027688756271a66025
|
|
27
|
+
GIT_PROGRESS TRUE
|
|
28
|
+
)
|
|
29
|
+
# Populate but do not add root project (avoids root install of include/lib).
|
|
30
|
+
# We only add the inner yoga library so we get yogacore without extra installs.
|
|
31
|
+
FetchContent_GetProperties(yoga)
|
|
32
|
+
if(NOT yoga_POPULATED)
|
|
33
|
+
FetchContent_Populate(yoga)
|
|
34
|
+
endif()
|
|
35
|
+
set(_yoga_root ${yoga_SOURCE_DIR})
|
|
36
|
+
set(yoga_SOURCE_DIR ${_yoga_root}/yoga)
|
|
37
|
+
set(yoga_BUILD_TESTS OFF CACHE BOOL "Build Yoga tests" FORCE)
|
|
38
|
+
add_subdirectory(${yoga_SOURCE_DIR} ${CMAKE_BINARY_DIR}/yoga-build EXCLUDE_FROM_ALL)
|
|
39
|
+
endif()
|
|
40
|
+
|
|
41
|
+
find_package(pybind11 REQUIRED)
|
|
42
|
+
pybind11_add_module(yoga src/yoga/yoga.cpp)
|
|
43
|
+
target_include_directories(yoga PRIVATE ${yoga_SOURCE_DIR})
|
|
44
|
+
target_link_libraries(yoga PRIVATE yogacore)
|
|
45
|
+
|
|
46
|
+
# Install the compiled extension to the yoga package directory
|
|
47
|
+
install(TARGETS yoga DESTINATION "yoga")
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: yoga-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python binding for Facebook Yoga layout engine
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
8
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
9
|
+
Requires-Dist: ruff>=0.3; extra == "dev"
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# yoga-python
|
|
13
|
+
|
|
14
|
+
Python bindings for [Facebook's Yoga](https://github.com/facebook/yoga) layout engine — a cross-platform implementation of [CSS Flexbox](https://www.w3.org/TR/css-flexbox-1/).
|
|
15
|
+
|
|
16
|
+
Yoga computes a layout tree of nodes with flexbox styles and outputs pixel-perfect positions. This package wraps the full C++ engine via [pybind11](https://github.com/pybind/pybind11), giving you the same layout behavior you get in React Native, Litho, and other Yoga-backed frameworks — directly from Python.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install yoga-python
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Building from source requires a C++20 compiler, CMake 3.15+, and pybind11:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install scikit-build-core pybind11
|
|
28
|
+
pip install .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from yoga import (
|
|
35
|
+
Node, Config, Direction, FlexDirection, Justify,
|
|
36
|
+
Align, Edge, YGValuePoint, YGValuePercent,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
root = Node()
|
|
40
|
+
root.flex_direction = FlexDirection.Row
|
|
41
|
+
root.width = YGValuePoint(300)
|
|
42
|
+
root.height = YGValuePoint(200)
|
|
43
|
+
root.set_padding(Edge.All, 10)
|
|
44
|
+
|
|
45
|
+
child_a = Node()
|
|
46
|
+
child_a.flex_grow = 1
|
|
47
|
+
child_a.set_margin(Edge.Right, 10)
|
|
48
|
+
root.insert_child(child_a, 0)
|
|
49
|
+
|
|
50
|
+
child_b = Node()
|
|
51
|
+
child_b.flex_grow = 2
|
|
52
|
+
root.insert_child(child_b, 1)
|
|
53
|
+
|
|
54
|
+
root.calculate_layout(300, 200, Direction.LTR)
|
|
55
|
+
|
|
56
|
+
print(f"child_a: {child_a.layout_left}, {child_a.layout_top}, "
|
|
57
|
+
f"{child_a.layout_width}x{child_a.layout_height}")
|
|
58
|
+
# child_a: 10.0, 10.0, 90.0x180.0
|
|
59
|
+
|
|
60
|
+
print(f"child_b: {child_b.layout_left}, {child_b.layout_top}, "
|
|
61
|
+
f"{child_b.layout_width}x{child_b.layout_height}")
|
|
62
|
+
# child_b: 110.0, 10.0, 180.0x180.0
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## API overview
|
|
66
|
+
|
|
67
|
+
### Node
|
|
68
|
+
|
|
69
|
+
The primary object. Create nodes, configure their styles, build a tree, then call `calculate_layout`.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
node = Node() # default config
|
|
73
|
+
node = Node(config) # with explicit config
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Style properties** (set directly as attributes):
|
|
77
|
+
|
|
78
|
+
| Property | Type | Example |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `flex_direction` | `FlexDirection` | `node.flex_direction = FlexDirection.Row` |
|
|
81
|
+
| `justify_content` | `Justify` | `node.justify_content = Justify.Center` |
|
|
82
|
+
| `align_items` | `Align` | `node.align_items = Align.Stretch` |
|
|
83
|
+
| `align_self` | `Align` | `node.align_self = Align.FlexEnd` |
|
|
84
|
+
| `align_content` | `Align` | `node.align_content = Align.SpaceBetween` |
|
|
85
|
+
| `flex_wrap` | `Wrap` | `node.flex_wrap = Wrap.Wrap` |
|
|
86
|
+
| `overflow` | `Overflow` | `node.overflow = Overflow.Hidden` |
|
|
87
|
+
| `display` | `Display` | `node.display = Display.Flex` |
|
|
88
|
+
| `position_type` | `PositionType` | `node.position_type = PositionType.Absolute` |
|
|
89
|
+
| `flex_grow` | `float` | `node.flex_grow = 1.0` |
|
|
90
|
+
| `flex_shrink` | `float` | `node.flex_shrink = 0.0` |
|
|
91
|
+
| `flex_basis` | `YGValue` | `node.flex_basis = YGValuePoint(100)` |
|
|
92
|
+
| `width` / `height` | `YGValue` | `node.width = YGValuePercent(50)` |
|
|
93
|
+
| `min_width` / `max_width` | `YGValue` | `node.min_width = YGValuePoint(80)` |
|
|
94
|
+
| `min_height` / `max_height` | `YGValue` | `node.max_height = YGValuePoint(400)` |
|
|
95
|
+
| `aspect_ratio` | `float` | `node.aspect_ratio = 16 / 9` |
|
|
96
|
+
| `box_sizing` | `BoxSizing` | `node.box_sizing = BoxSizing.ContentBox` |
|
|
97
|
+
| `direction` | `Direction` | `node.direction = Direction.LTR` |
|
|
98
|
+
|
|
99
|
+
**Edge-based properties** (margin, padding, border, position):
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
node.set_margin(Edge.Left, 10) # points
|
|
103
|
+
node.set_margin(Edge.Top, YGValuePercent(5)) # percent
|
|
104
|
+
node.set_padding(Edge.All, 20)
|
|
105
|
+
node.set_border(Edge.Bottom, 1)
|
|
106
|
+
node.set_position(Edge.Left, YGValuePoint(50))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Tree manipulation:**
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
node.insert_child(child, index)
|
|
113
|
+
node.remove_child(child)
|
|
114
|
+
node.remove_all_children()
|
|
115
|
+
node.set_children([child_a, child_b])
|
|
116
|
+
node.child_count # int
|
|
117
|
+
node[index] # Node (supports indexing)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Layout:**
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
node.calculate_layout(width, height, Direction.LTR)
|
|
124
|
+
|
|
125
|
+
node.layout_left # float
|
|
126
|
+
node.layout_top # float
|
|
127
|
+
node.layout_width # float
|
|
128
|
+
node.layout_height # float
|
|
129
|
+
node.layout_direction # Direction
|
|
130
|
+
node.layout_margin(Edge.Left) # float
|
|
131
|
+
node.layout_padding(Edge.Top) # float
|
|
132
|
+
node.layout_border(Edge.Right) # float
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Measure functions** for leaf nodes with custom content sizing:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
def measure(node, width, width_mode, height, height_mode):
|
|
139
|
+
return {"width": 100, "height": 50}
|
|
140
|
+
|
|
141
|
+
node.set_measure_func(measure)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Baseline functions** for custom baseline alignment:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
def baseline(node, width, height):
|
|
148
|
+
return height * 0.8
|
|
149
|
+
|
|
150
|
+
node.set_baseline_func(baseline)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Config
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
config = Config()
|
|
157
|
+
config.use_web_defaults = True
|
|
158
|
+
config.point_scale_factor = 2.0
|
|
159
|
+
config.errata = Errata.StretchFlexBasis
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Values
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
YGValuePoint(100) # 100px
|
|
166
|
+
YGValuePercent(50) # 50%
|
|
167
|
+
YGValueAuto # auto
|
|
168
|
+
YGValueUndefined # undefined
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Enums
|
|
172
|
+
|
|
173
|
+
All Yoga enums are available as Python enum types (pybind11 enums, int-convertible):
|
|
174
|
+
|
|
175
|
+
`Direction`, `FlexDirection`, `Justify`, `Align`, `PositionType`, `Wrap`, `Overflow`, `Display`, `Edge`, `Unit`, `MeasureMode`, `Dimension`, `BoxSizing`, `Gutter`, `Errata`, `NodeType`, `LogLevel`, `ExperimentalFeature`
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
git clone https://github.com/banditburai/yoga-python.git
|
|
181
|
+
cd yoga-python
|
|
182
|
+
uv sync --dev
|
|
183
|
+
|
|
184
|
+
# Run tests
|
|
185
|
+
uv run pytest tests/ -v
|
|
186
|
+
|
|
187
|
+
# Lint & type checking
|
|
188
|
+
uv run ruff check
|
|
189
|
+
uv run ruff format --check
|
|
190
|
+
uv run pyright
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Test coverage
|
|
194
|
+
|
|
195
|
+
The test suite is a 1:1 port of the [Yoga C++ test suite](https://github.com/facebook/yoga/tree/main/tests) — 822 tests covering all layout modes, edge cases, and behaviors. Tests that the C++ suite itself skips (`GTEST_SKIP`) are also skipped in Python.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT — same as [Yoga](https://github.com/facebook/yoga/blob/main/LICENSE).
|