tkblend 0.4.0b1__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.
- tkblend-0.4.0b1/.agents/rules/platform-build-invariants.md +66 -0
- tkblend-0.4.0b1/.agents/rules/tooling.md +6 -0
- tkblend-0.4.0b1/.agents/skills/blend2d-vector-widgets/SKILL.md +134 -0
- tkblend-0.4.0b1/.github/workflows/ci.yml +263 -0
- tkblend-0.4.0b1/.github/workflows/release.yml +139 -0
- tkblend-0.4.0b1/.gitignore +49 -0
- tkblend-0.4.0b1/CMakeLists.txt +518 -0
- tkblend-0.4.0b1/CMakeLocalConfig.cmake.example +62 -0
- tkblend-0.4.0b1/LICENSE +21 -0
- tkblend-0.4.0b1/PKG-INFO +325 -0
- tkblend-0.4.0b1/README.md +286 -0
- tkblend-0.4.0b1/docs/README.md +37 -0
- tkblend-0.4.0b1/docs/architecture.md +150 -0
- tkblend-0.4.0b1/docs/assets/canvas_studio_custom.gif +0 -0
- tkblend-0.4.0b1/docs/assets/canvas_studio_overview.png +0 -0
- tkblend-0.4.0b1/docs/assets/custom_widgets_overview.png +0 -0
- tkblend-0.4.0b1/docs/assets/hero_analytics_cinematic.png +0 -0
- tkblend-0.4.0b1/docs/assets/hero_analytics_dark.png +0 -0
- tkblend-0.4.0b1/docs/assets/hero_analytics_light.png +0 -0
- tkblend-0.4.0b1/docs/assets/hero_showcase.gif +0 -0
- tkblend-0.4.0b1/docs/assets/realtime_particles.png +0 -0
- tkblend-0.4.0b1/docs/assets/realtime_spectrum.png +0 -0
- tkblend-0.4.0b1/docs/assets/realtime_visualizer.gif +0 -0
- tkblend-0.4.0b1/docs/assets/widget_catalog_overview.png +0 -0
- tkblend-0.4.0b1/docs/assets/widget_matrix_theming.gif +0 -0
- tkblend-0.4.0b1/docs/canvas_and_vector.md +240 -0
- tkblend-0.4.0b1/docs/caveats_and_edge_cases.md +330 -0
- tkblend-0.4.0b1/docs/examples.md +81 -0
- tkblend-0.4.0b1/docs/getting_started.md +163 -0
- tkblend-0.4.0b1/docs/theming.md +169 -0
- tkblend-0.4.0b1/docs/widgets.md +403 -0
- tkblend-0.4.0b1/examples/README.md +147 -0
- tkblend-0.4.0b1/examples/analytics_dashboard.py +473 -0
- tkblend-0.4.0b1/examples/api_inspector.py +1376 -0
- tkblend-0.4.0b1/examples/canvas_studio.py +682 -0
- tkblend-0.4.0b1/examples/cinematic_theme_demo.py +353 -0
- tkblend-0.4.0b1/examples/custom_widget_cookbook.py +508 -0
- tkblend-0.4.0b1/examples/file_explorer.py +373 -0
- tkblend-0.4.0b1/examples/gradient_widgets.py +1352 -0
- tkblend-0.4.0b1/examples/hub.py +453 -0
- tkblend-0.4.0b1/examples/icons_and_typography_demo.py +226 -0
- tkblend-0.4.0b1/examples/multimedia_dashboard.py +415 -0
- tkblend-0.4.0b1/examples/realtime_visualizer.py +569 -0
- tkblend-0.4.0b1/examples/showcase.py +561 -0
- tkblend-0.4.0b1/examples/table_demo.py +346 -0
- tkblend-0.4.0b1/merge.py +99 -0
- tkblend-0.4.0b1/pyproject.toml +132 -0
- tkblend-0.4.0b1/sanitizers.log +840 -0
- tkblend-0.4.0b1/scratch/test_all_examples.py +72 -0
- tkblend-0.4.0b1/scratch/test_playground_interaction.py +69 -0
- tkblend-0.4.0b1/scratch/test_screenshots.py +117 -0
- tkblend-0.4.0b1/scratch/test_showcase_all_pages.py +57 -0
- tkblend-0.4.0b1/scratch/test_showcase_steps.py +56 -0
- tkblend-0.4.0b1/scratch/verify_all_showcase_examples.py +58 -0
- tkblend-0.4.0b1/scripts/benchmark_render.py +98 -0
- tkblend-0.4.0b1/scripts/benchmark_vars.py +116 -0
- tkblend-0.4.0b1/scripts/benchmark_window_shape.py +185 -0
- tkblend-0.4.0b1/scripts/build.py +98 -0
- tkblend-0.4.0b1/scripts/build.sh +18 -0
- tkblend-0.4.0b1/scripts/build_dist.py +99 -0
- tkblend-0.4.0b1/scripts/build_dist.sh +18 -0
- tkblend-0.4.0b1/scripts/capture_showcase.py +147 -0
- tkblend-0.4.0b1/scripts/export_demo_screenshots.py +114 -0
- tkblend-0.4.0b1/scripts/generate_embedded_fonts.py +367 -0
- tkblend-0.4.0b1/scripts/generate_showcase_assets.py +555 -0
- tkblend-0.4.0b1/scripts/render_all_widgets_to_png.py +146 -0
- tkblend-0.4.0b1/scripts/run_coverage.sh +81 -0
- tkblend-0.4.0b1/scripts/run_sanitizers.sh +108 -0
- tkblend-0.4.0b1/scripts/run_tests.py +62 -0
- tkblend-0.4.0b1/scripts/standardize_table_themes.py +543 -0
- tkblend-0.4.0b1/scripts/test.sh +18 -0
- tkblend-0.4.0b1/scripts/validate_granular_appearance.py +686 -0
- tkblend-0.4.0b1/scripts/validate_theme_appearance.py +407 -0
- tkblend-0.4.0b1/scripts/verify_inputs_visuals.py +74 -0
- tkblend-0.4.0b1/scripts/verify_snapshots.py +167 -0
- tkblend-0.4.0b1/scripts/verify_table_visuals.py +112 -0
- tkblend-0.4.0b1/src/animation.cpp +98 -0
- tkblend-0.4.0b1/src/animation.hpp +22 -0
- tkblend-0.4.0b1/src/bindings.cpp +1477 -0
- tkblend-0.4.0b1/src/blit/blit_backend.h +43 -0
- tkblend-0.4.0b1/src/blit/blit_common.cpp +69 -0
- tkblend-0.4.0b1/src/blit/blit_macos.mm +112 -0
- tkblend-0.4.0b1/src/blit/blit_win32.cpp +185 -0
- tkblend-0.4.0b1/src/blit/blit_x11.cpp +106 -0
- tkblend-0.4.0b1/src/color.cpp +71 -0
- tkblend-0.4.0b1/src/color.hpp +74 -0
- tkblend-0.4.0b1/src/compiled_draw_batch.cpp +203 -0
- tkblend-0.4.0b1/src/compiled_draw_batch.hpp +114 -0
- tkblend-0.4.0b1/src/default_styles.hpp +781 -0
- tkblend-0.4.0b1/src/draw_batch.cpp +167 -0
- tkblend-0.4.0b1/src/draw_batch.hpp +113 -0
- tkblend-0.4.0b1/src/draw_engine.cpp +1350 -0
- tkblend-0.4.0b1/src/draw_engine.hpp +80 -0
- tkblend-0.4.0b1/src/emoji_engine.cpp +293 -0
- tkblend-0.4.0b1/src/emoji_engine.hpp +65 -0
- tkblend-0.4.0b1/src/fnv1a.hpp +62 -0
- tkblend-0.4.0b1/src/font/embedded_fonts.cpp +45898 -0
- tkblend-0.4.0b1/src/font/embedded_fonts.hpp +24 -0
- tkblend-0.4.0b1/src/font/font_resolver.hpp +28 -0
- tkblend-0.4.0b1/src/font/font_resolver_linux.cpp +403 -0
- tkblend-0.4.0b1/src/font/font_resolver_macos.mm +236 -0
- tkblend-0.4.0b1/src/font/font_resolver_win32.cpp +372 -0
- tkblend-0.4.0b1/src/font_manager.cpp +471 -0
- tkblend-0.4.0b1/src/font_manager.hpp +60 -0
- tkblend-0.4.0b1/src/gradient.cpp +44 -0
- tkblend-0.4.0b1/src/gradient.hpp +33 -0
- tkblend-0.4.0b1/src/native_controller.cpp +1476 -0
- tkblend-0.4.0b1/src/native_controller.hpp +284 -0
- tkblend-0.4.0b1/src/path.cpp +65 -0
- tkblend-0.4.0b1/src/path.hpp +28 -0
- tkblend-0.4.0b1/src/platform_compat.h +438 -0
- tkblend-0.4.0b1/src/shadow_engine.cpp +220 -0
- tkblend-0.4.0b1/src/shadow_engine.hpp +71 -0
- tkblend-0.4.0b1/src/string_utils.hpp +144 -0
- tkblend-0.4.0b1/src/style_engine.cpp +1015 -0
- tkblend-0.4.0b1/src/style_engine.hpp +222 -0
- tkblend-0.4.0b1/src/surface.cpp +1935 -0
- tkblend-0.4.0b1/src/surface.hpp +449 -0
- tkblend-0.4.0b1/src/surface_registry.hpp +609 -0
- tkblend-0.4.0b1/src/tkblend.hpp +13 -0
- tkblend-0.4.0b1/src/window_shape.cpp +233 -0
- tkblend-0.4.0b1/src/window_shape.hpp +32 -0
- tkblend-0.4.0b1/tests/test_batch_native_render.py +136 -0
- tkblend-0.4.0b1/tests/test_blitting.py +57 -0
- tkblend-0.4.0b1/tests/test_c_optimizations.py +220 -0
- tkblend-0.4.0b1/tests/test_class_auto_redraw.py +90 -0
- tkblend-0.4.0b1/tests/test_color.py +107 -0
- tkblend-0.4.0b1/tests/test_controller.py +254 -0
- tkblend-0.4.0b1/tests/test_css_variables.py +88 -0
- tkblend-0.4.0b1/tests/test_draw_engine.py +161 -0
- tkblend-0.4.0b1/tests/test_edge_cases_and_errors.py +73 -0
- tkblend-0.4.0b1/tests/test_embedded_fonts_and_icons.py +128 -0
- tkblend-0.4.0b1/tests/test_gradient.py +60 -0
- tkblend-0.4.0b1/tests/test_leaks_and_cleanup.py +376 -0
- tkblend-0.4.0b1/tests/test_logging_and_errors.py +98 -0
- tkblend-0.4.0b1/tests/test_nb_memory_leaks.py +1030 -0
- tkblend-0.4.0b1/tests/test_path.py +54 -0
- tkblend-0.4.0b1/tests/test_resilience_and_chaos.py +331 -0
- tkblend-0.4.0b1/tests/test_shadow_engine.py +49 -0
- tkblend-0.4.0b1/tests/test_surface.py +195 -0
- tkblend-0.4.0b1/tests/test_text_and_fonts.py +225 -0
- tkblend-0.4.0b1/tests/test_theme.py +553 -0
- tkblend-0.4.0b1/tests/test_theme_autodetect.py +192 -0
- tkblend-0.4.0b1/tests/test_theme_enforced_hierarchy.py +125 -0
- tkblend-0.4.0b1/tests/test_theme_files.py +180 -0
- tkblend-0.4.0b1/tests/test_theme_switching.py +93 -0
- tkblend-0.4.0b1/tests/test_thread_safety.py +865 -0
- tkblend-0.4.0b1/tests/test_tk_font_sync.py +148 -0
- tkblend-0.4.0b1/tests/test_tkblend.py +152 -0
- tkblend-0.4.0b1/tests/test_widgets.py +1026 -0
- tkblend-0.4.0b1/tkblend/__init__.py +340 -0
- tkblend-0.4.0b1/tkblend/controller.py +567 -0
- tkblend-0.4.0b1/tkblend/font.py +648 -0
- tkblend-0.4.0b1/tkblend/icons.py +4131 -0
- tkblend-0.4.0b1/tkblend/surface.py +964 -0
- tkblend-0.4.0b1/tkblend/theme.py +1011 -0
- tkblend-0.4.0b1/tkblend/themes/catppuccin_latte.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/catppuccin_mocha.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/cinematic.css +551 -0
- tkblend-0.4.0b1/tkblend/themes/cyberpunk.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/dark.css +411 -0
- tkblend-0.4.0b1/tkblend/themes/debug.css +465 -0
- tkblend-0.4.0b1/tkblend/themes/dracula.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/emerald.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/emerald_forest.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/light.css +410 -0
- tkblend-0.4.0b1/tkblend/themes/monokai.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/monokai_pro.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/nord.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/ocean.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/slate.css +281 -0
- tkblend-0.4.0b1/tkblend/themes/solarized_dark.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/solarized_light.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/sunset.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/sunset_amber.css +282 -0
- tkblend-0.4.0b1/tkblend/themes/tokyo_night.css +282 -0
- tkblend-0.4.0b1/tkblend/utils/__init__.py +13 -0
- tkblend-0.4.0b1/tkblend/utils/tcl_interp.py +66 -0
- tkblend-0.4.0b1/tkblend/utils/window_shape.py +89 -0
- tkblend-0.4.0b1/tkblend/widgets/__init__.py +59 -0
- tkblend-0.4.0b1/tkblend/widgets/badge.py +147 -0
- tkblend-0.4.0b1/tkblend/widgets/base.py +668 -0
- tkblend-0.4.0b1/tkblend/widgets/button.py +112 -0
- tkblend-0.4.0b1/tkblend/widgets/card.py +101 -0
- tkblend-0.4.0b1/tkblend/widgets/checkbox.py +76 -0
- tkblend-0.4.0b1/tkblend/widgets/circular_progress.py +143 -0
- tkblend-0.4.0b1/tkblend/widgets/combobox.py +540 -0
- tkblend-0.4.0b1/tkblend/widgets/constants.py +194 -0
- tkblend-0.4.0b1/tkblend/widgets/frame.py +79 -0
- tkblend-0.4.0b1/tkblend/widgets/label.py +39 -0
- tkblend-0.4.0b1/tkblend/widgets/progressbar.py +77 -0
- tkblend-0.4.0b1/tkblend/widgets/radio.py +108 -0
- tkblend-0.4.0b1/tkblend/widgets/range_slider.py +111 -0
- tkblend-0.4.0b1/tkblend/widgets/scrollable_frame.py +295 -0
- tkblend-0.4.0b1/tkblend/widgets/segmented_button.py +135 -0
- tkblend-0.4.0b1/tkblend/widgets/slider.py +106 -0
- tkblend-0.4.0b1/tkblend/widgets/sparkline.py +47 -0
- tkblend-0.4.0b1/tkblend/widgets/switch.py +81 -0
- tkblend-0.4.0b1/tkblend/widgets/table.py +1483 -0
- tkblend-0.4.0b1/tkblend/widgets/tabview.py +151 -0
- tkblend-0.4.0b1/tkblend/widgets/textbox.py +315 -0
- tkblend-0.4.0b1/tkblend/widgets/utils.py +280 -0
- tkblend-0.4.0b1/uv.lock +2230 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Multiplatform Build & Packaging Invariants
|
|
2
|
+
|
|
3
|
+
This rule documents critical invariants, hard-won fixes, and regression prevention guidelines across Linux, macOS, and Windows CI/CD, cibuildwheel, CMake, and native C++ extension workflows for `tkblend`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 🍎 macOS (`macos-14` ARM64 / `macos-15-intel` x86_64 / delocate)
|
|
8
|
+
|
|
9
|
+
1. **Static FreeType Compilation**:
|
|
10
|
+
- **Rule**: FreeType must always be built from source statically via `FetchContent` on `APPLE` with `BUILD_SHARED_LIBS=OFF`.
|
|
11
|
+
- **Why**: Homebrew's `freetype` dylibs are compiled with `MACOSX_DEPLOYMENT_TARGET >= 14.0`. Linking dynamic Homebrew FreeType causes `delocate-wheel` to reject the wheel for deployment targets `10.15` (Intel) and `11.0` (ARM64).
|
|
12
|
+
|
|
13
|
+
2. **Static Tcl/Tk Stubs**:
|
|
14
|
+
- **Rule**: Link only against static stub libraries (`libtkstub8.6.a`, `libtclstub8.6.a`) via `brew install tcl-tk` and `CMAKE_PREFIX_PATH=$(brew --prefix tcl-tk)`.
|
|
15
|
+
- **Why**: Never dynamically link `Tk.framework` or `Tcl.framework` at build time to ensure wheels load across different Python distributions and Tk versions.
|
|
16
|
+
|
|
17
|
+
3. **Dynamic Symbol Resolution for Tk Cocoa Internals**:
|
|
18
|
+
- **Rule**: Resolve Cocoa Tk symbols like `TkMacOSXGetCGContextForDrawable` dynamically at runtime via `dlsym(RTLD_DEFAULT, "TkMacOSXGetCGContextForDrawable")`.
|
|
19
|
+
- **Why**: Direct linker references or `__attribute__((weak_import))` still require build-time framework linking on older runner images, causing build failures.
|
|
20
|
+
|
|
21
|
+
4. **No Unsafe ObjC Window Shaping**:
|
|
22
|
+
- **Rule**: `is_window_shaping_supported()`, `apply_round_rect_shape()`, and `clear_window_shape()` must return `false` on macOS in `src/window_shape.cpp`.
|
|
23
|
+
- **Why**: In Aqua Cocoa Tk, `widget.winfo_id()` returns a pointer to Tk's internal C struct `MacDrawable`, NOT an Objective-C `NSView*`. Casting this pointer to `NSView*` and calling ObjC selectors (`[view isKindOfClass:...]`) causes an immediate segmentation fault (`EXC_BAD_ACCESS`). Child Tk widgets share the top-level `TKContentView` and rely on Blend2D canvas clipping.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. 🪟 Windows (`windows-latest` / MSVC / delvewheel)
|
|
28
|
+
|
|
29
|
+
1. **CMake Prefix Propagation to cibuildwheel**:
|
|
30
|
+
- **Rule**: Always pass `CMAKE_PREFIX_PATH` in both the GitHub Actions step `env:` and via `CIBW_ENVIRONMENT_WINDOWS: "CMAKE_PREFIX_PATH='${{ steps.tcltk.outputs.environment-path }}/Library'"`.
|
|
31
|
+
- **Why**: `cibuildwheel` spawns isolated build subshells that do not inherit `$GITHUB_ENV`.
|
|
32
|
+
|
|
33
|
+
2. **Excluding Tcl/Tk DLLs in delvewheel**:
|
|
34
|
+
- **Rule**: Maintain `--exclude tcl86t.dll --exclude tk86t.dll` in `tool.cibuildwheel.windows.repair-wheel-command`.
|
|
35
|
+
- **Why**: Prevents delvewheel from vendoring host Tcl/Tk DLLs into the wheel, allowing host Python's Tkinter to provide them at runtime.
|
|
36
|
+
|
|
37
|
+
3. **Headless & Standalone Test Virtualenv Isolation**:
|
|
38
|
+
- **Rule**: All Tkinter imports in `tkblend/__init__.py` must be wrapped in `try/except ImportError`. The C++ extension `_tkblend` is always imported directly.
|
|
39
|
+
- **Why**: cibuildwheel's clean Windows test virtualenvs do not bundle `_tkinter.pyd`. Guarding imports allows the smoke test `python -c "from tkblend import _tkblend; print('C++ extension loaded successfully')"` to verify binary integrity without crashing with `ModuleNotFoundError: No module named 'tkinter'`.
|
|
40
|
+
|
|
41
|
+
4. **Target Installation Destination**:
|
|
42
|
+
- **Rule**: Configure `install(TARGETS _tkblend LIBRARY DESTINATION tkblend RUNTIME DESTINATION tkblend ARCHIVE DESTINATION tkblend)` in `CMakeLists.txt`.
|
|
43
|
+
- **Why**: Ensures Windows `.pyd` files are placed inside the `tkblend/` package folder.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 3. 🐧 Linux (`manylinux_2_28` / x86_64 / aarch64 / auditwheel)
|
|
48
|
+
|
|
49
|
+
1. **Docker Container Dependencies**:
|
|
50
|
+
- **Rule**: Declare `yum install -y tcl-devel tk-devel xorg-x11-server-Xvfb xorg-x11-xinit` under `tool.cibuildwheel.linux.before-all`.
|
|
51
|
+
- **Why**: manylinux container environments run in clean isolation from the host runner.
|
|
52
|
+
|
|
53
|
+
2. **Virtual Display Server for Tests**:
|
|
54
|
+
- **Rule**: Run test suites using `xvfb-run -a pytest {project}/tests`.
|
|
55
|
+
- **Why**: Tkinter requires a running X11 display server to initialize widgets in headless CI runners.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 4. 🛠️ Local Development & Testing
|
|
60
|
+
|
|
61
|
+
- Always run Python commands and test suites using `uv`:
|
|
62
|
+
```bash
|
|
63
|
+
uv run pytest
|
|
64
|
+
uv run python <script>
|
|
65
|
+
```
|
|
66
|
+
- When native C++ sources in `src/` are modified, rebuild the native extension with `cmake --build build` and copy the compiled `_tkblend*.so` / `_tkblend*.pyd` into `tkblend/` and the active `.venv` environment.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Tooling & Environment Invariants
|
|
2
|
+
|
|
3
|
+
- Always run Python commands, scripts, and test suites using `uv`:
|
|
4
|
+
- `uv run pytest`
|
|
5
|
+
- `uv run python <script>`
|
|
6
|
+
- When native C++ sources in `src/` are modified, rebuild with `cmake --build build` and copy the resulting `_tkblend*.so` into `.venv/lib/python3.12/site-packages/tkblend/` and `tkblend/`.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: blend2d-vector-widgets
|
|
3
|
+
description: Architecture guide and reference patterns for developing pure Blend2D surface-based vector widgets in tkblend with zero TTK dependencies.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Blend2D Vector Widgets Architecture & Development Guide
|
|
7
|
+
|
|
8
|
+
This skill provides the architectural foundation, development workflow, and widget design patterns for `tkblend`'s pure surface-based vector UI components.
|
|
9
|
+
|
|
10
|
+
## Core Architectural Principles
|
|
11
|
+
|
|
12
|
+
1. **Zero TTK Dependencies**:
|
|
13
|
+
- `tkblend` does not use the Tcl/Tk TTK theme engine (`Ttk_RegisterElement`, `Ttk_RegisterTheme`, `ttk.Style`).
|
|
14
|
+
- All interactive components are self-contained vector widgets powered by Blend2D C++ rendering directly into Tk window drawables via zero-copy blitting without intermediate `PhotoImage` allocations.
|
|
15
|
+
|
|
16
|
+
2. **DPI-Aware Coordinate Scaling**:
|
|
17
|
+
- Always route dimensions and layout coordinates through `ScalingTracker`:
|
|
18
|
+
```python
|
|
19
|
+
scale = ScalingTracker.get_scaling_factor(master)
|
|
20
|
+
widget_w = int(logical_w * scale)
|
|
21
|
+
widget_h = int(logical_h * scale)
|
|
22
|
+
```
|
|
23
|
+
- Auto-activates process-level high-DPI awareness on Windows and detects scaling factors on macOS / Linux.
|
|
24
|
+
|
|
25
|
+
3. **BaseControl & NativeController Lifecycle (`tkblend.widgets.BaseControl`)**:
|
|
26
|
+
- Subclass `BaseControl`, which wraps `tk.Frame` with an integrated `NativeWidgetController` for zero-copy direct drawable blitting.
|
|
27
|
+
- **Transparent Frame Protocol**: Always initialize `super().__init__(..., background="")` so Tk's internal `DisplayFrame` has `framePtr->border == NULL` and does not erase Blend2D vector surfaces on Expose/Configure events.
|
|
28
|
+
- Implement `render(self, surf: Surface, pal: Palette, width: int, height: int, scale: float) -> None`:
|
|
29
|
+
1. Vector shapes, paths, text, or drop shadows are drawn into `surf`.
|
|
30
|
+
2. Background clearing is handled via `surf.clear(self._resolved_parent_bg)`.
|
|
31
|
+
- Request redraws via `self.request_redraw()`, which schedules non-blocking idle execution through `self.after_idle(self._execute_idle_redraw)`.
|
|
32
|
+
- **Direct C++ Hardware Blits on Expose / MapNotify**: `NativeWidgetController` handles X11 `Expose`, `MapNotify`, and `VisibilityNotify` events by immediately executing `blit_active_surface()` in C++ from the backing surface buffer.
|
|
33
|
+
|
|
34
|
+
4. **Dynamic Theming with `tkblend.theme`**:
|
|
35
|
+
- Query `get_theme()` for semantic colors: `bg`, `fg`, `primary`, `secondary`, `accent`, `card_bg`, `card_border`, `track_bg`, `thumb_color`.
|
|
36
|
+
- `BaseControl` automatically registers with `add_theme_listener` and re-renders on `set_theme("light" | "dark")`.
|
|
37
|
+
- **Avoid Static Color Locking**: Do not pass explicit theme colors in constructors unless a permanent user override is intended. Let child widgets dynamically inherit background via `_resolve_default_bg(master, palette)`.
|
|
38
|
+
- **Compound Container Protocol (`.bg_color`)**: Any compound container (`ScrollableFrame`, `Tabview`, `Table`, `TextBox`, `Frame`, `Card`) must expose a `.bg_color` property returning its active inner surface fill color so `cascade_bg_to_children()` can properly recurse.
|
|
39
|
+
- **Subcomponent Cascade in `_on_theme_changed`**:
|
|
40
|
+
Compound widgets must update their internal frames, canvas viewports, and vector scrollbars with `set_parent_bg()` and call `cascade_bg_to_children(self, inner_bg)`.
|
|
41
|
+
- **Startup UI Palette Injection**:
|
|
42
|
+
Call `self._on_theme_changed(pal)` or `tb.cascade_bg_to_children(self, pal.bg)` right after `_build_ui()` so standard helper `tk.Frame`s adapt immediately to theme card colors on first launch.
|
|
43
|
+
|
|
44
|
+
## Developing a Native Vector Widget
|
|
45
|
+
|
|
46
|
+
Follow this template for creating custom vector components with `BaseControl`:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import tkinter as tk
|
|
50
|
+
from typing import Optional
|
|
51
|
+
from tkblend.widgets import BaseControl
|
|
52
|
+
from tkblend.theme import Palette, get_theme
|
|
53
|
+
from tkblend.surface import Surface, ColorLike
|
|
54
|
+
|
|
55
|
+
class CustomGauge(BaseControl):
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
master: Optional[tk.Misc] = None,
|
|
59
|
+
size: int = 100,
|
|
60
|
+
value: float = 50.0,
|
|
61
|
+
color: Optional[ColorLike] = None,
|
|
62
|
+
parent_bg: Optional[str] = None,
|
|
63
|
+
**kwargs,
|
|
64
|
+
):
|
|
65
|
+
self._value = value
|
|
66
|
+
self._color = color
|
|
67
|
+
super().__init__(master=master, width=size, height=size, parent_bg=parent_bg, **kwargs)
|
|
68
|
+
|
|
69
|
+
def set_value(self, val: float) -> None:
|
|
70
|
+
self._value = max(0.0, min(100.0, float(val)))
|
|
71
|
+
self.request_redraw()
|
|
72
|
+
|
|
73
|
+
def render(self, surf: Surface, pal: Palette, width: int, height: int, scale: float) -> None:
|
|
74
|
+
s = scale
|
|
75
|
+
cx = width / 2.0
|
|
76
|
+
cy = height / 2.0
|
|
77
|
+
r = min(cx, cy) - 6.0 * s
|
|
78
|
+
|
|
79
|
+
# Draw track and progress
|
|
80
|
+
active_color = self._color or pal.primary
|
|
81
|
+
surf.stroke_circle(cx, cy, r, pal.track_bg, stroke_width=6.0 * s)
|
|
82
|
+
surf.fill_circle(cx, cy, r * (self._value / 100.0), active_color)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## C++ Native Extension Concurrency & Memory Safety
|
|
86
|
+
|
|
87
|
+
1. **GIL Management & Event Loop Thread Safety**:
|
|
88
|
+
- Never invoke raw `PyGILState_Ensure()` / `PyGILState_Release()` during `_tkinter` `Tcl_Eval` execution; use `nb::gil_scoped_acquire` in C++ event callbacks.
|
|
89
|
+
- Python-initiated redraws must be scheduled via `widget.after_idle()`.
|
|
90
|
+
- `Surface::resize()` runs under `nb::call_guard<nb::gil_scoped_release>()`.
|
|
91
|
+
- All buffer acquisitions (`SurfaceBufferObject`, `Surface::get_buffer()`) must register via `Surface::acquire_buffer_view()` / `Surface::release_buffer_view()` under `Surface::mutex_`.
|
|
92
|
+
- `active_buffers_` acts as a guard preventing resize and subsequent use-after-free while active memoryviews exist.
|
|
93
|
+
|
|
94
|
+
2. **Display List & Direct Drawing Parity**:
|
|
95
|
+
- Every operation in `DrawBatch` (`DrawOp`) must maintain 1:1 parity with direct `Surface` calls (including all shadow blur, spread, offset-x, offset-y, and color arguments).
|
|
96
|
+
- Any new high-level drawing primitive added to `Surface` must also be added with identical arguments to `DrawBatch`.
|
|
97
|
+
|
|
98
|
+
3. **Cross-Platform Native Hygiene**:
|
|
99
|
+
- Objective-C++ sources (macOS window shaping) must be pinned to `-fobjc-arc` and treat native view handles as borrowed references.
|
|
100
|
+
- Dynamic asset and font resolution must never use hardcoded user paths; dynamic `$HOME` / `$WINDIR` lookups must be used.
|
|
101
|
+
- Global native types (e.g. `SurfaceBufferType`) must be initialized via `std::call_once` or module init.
|
|
102
|
+
|
|
103
|
+
## Embedded Sub-widget State Bridging & Controller Parity
|
|
104
|
+
|
|
105
|
+
When developing compound vector widgets that embed native Tk elements (e.g., `Entry`, `TextBox` embedding `tk.Entry` or `tk.Text`):
|
|
106
|
+
1. **Property & Method Dual Parity**: `NativeController` and widget controllers must provide both property syntax (`controller.is_focused = True`) and method syntax (`controller.set_focused(True)`) for state mutators (`hovered`, `pressed`, `focused`, `disabled`, `checked`).
|
|
107
|
+
2. **Focus Event Delegation**: Child Tk widgets must delegate `<FocusIn>` and `<FocusOut>` events to update the controller state and immediately trigger `self.request_redraw()` so focus rings and border colors render responsively.
|
|
108
|
+
3. **Headless & CI Test Verification**: When testing focus state transitions in pytest fixtures, invoke the widget's focus callback hooks directly (`widget._on_entry_focus_in()`) to ensure reliable assertion across headless and unmapped X11 windows.
|
|
109
|
+
|
|
110
|
+
## Compound Vector Widget Event Isolation & GIL Safety Protocol
|
|
111
|
+
|
|
112
|
+
When developing compound, virtualized, or multi-element vector widgets subclassing `BaseControl` (such as `Table`, `CanvasContainer`, or composite charts) that define custom mouse event bindings (`self.bind("<Button-1>", ...)`, `self.bind("<ButtonRelease-1>", ...)`):
|
|
113
|
+
|
|
114
|
+
1. **Decouple `command` from `BaseControl`**:
|
|
115
|
+
- Always pass `command=None` to `super().__init__(..., command=None)` so `BaseControl` does not register a generic button-click callback.
|
|
116
|
+
- Store the user's callback in a widget-specific attribute (e.g., `self._command = command`) and expose it via `@property def command`.
|
|
117
|
+
|
|
118
|
+
2. **Neutralize C++ Controller Click Interception**:
|
|
119
|
+
- Explicitly disable button press state tracking and clear native click handlers during `__init__`:
|
|
120
|
+
```python
|
|
121
|
+
self.controller.auto_press = False
|
|
122
|
+
self.controller.clear_on_click()
|
|
123
|
+
```
|
|
124
|
+
- Override `_on_controller_click` as a no-op:
|
|
125
|
+
```python
|
|
126
|
+
def _on_controller_click(self, x: int, y: int, button: int) -> None:
|
|
127
|
+
"""No-op: Compound widgets manage clicks and interactions internally."""
|
|
128
|
+
pass
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
3. **Prevent `_tkinter` `tsdPtr->tstate` Desynchronization**:
|
|
132
|
+
- Never allow C++ `on_tk_event` callbacks to execute arbitrary user callbacks synchronously if the widget also binds Tk events for the same event type.
|
|
133
|
+
- A nested `Tcl_Eval` executed from a C++ event handler clears `_tkinter`'s saved thread state to `NULL`, triggering a fatal `PyEval_RestoreThread` crash when Tk dispatches downstream Python event bindings for that event.
|
|
134
|
+
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test (${{ matrix.os }} - Py ${{ matrix.python-version }})
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
os: [ubuntu-latest, macos-14, windows-latest]
|
|
17
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
18
|
+
exclude:
|
|
19
|
+
# macos-14 runner is ARM64 and only supports Python >= 3.10
|
|
20
|
+
- os: macos-14
|
|
21
|
+
python-version: "3.9"
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout code
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
with:
|
|
27
|
+
submodules: recursive
|
|
28
|
+
|
|
29
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
+
if: runner.os != 'Windows'
|
|
31
|
+
uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: ${{ matrix.python-version }}
|
|
34
|
+
cache: "pip"
|
|
35
|
+
|
|
36
|
+
- name: Install Linux build & display dependencies
|
|
37
|
+
if: runner.os == 'Linux'
|
|
38
|
+
run: |
|
|
39
|
+
sudo apt-get update
|
|
40
|
+
sudo apt-get install -y tcl8.6-dev tk8.6-dev xvfb libx11-dev
|
|
41
|
+
|
|
42
|
+
- name: Install macOS Tcl/Tk development files
|
|
43
|
+
if: runner.os == 'macOS'
|
|
44
|
+
run: |
|
|
45
|
+
brew install tcl-tk
|
|
46
|
+
echo "CMAKE_PREFIX_PATH=$(brew --prefix tcl-tk)" >> $GITHUB_ENV
|
|
47
|
+
|
|
48
|
+
- name: Install Windows Tcl/Tk development files
|
|
49
|
+
if: runner.os == 'Windows'
|
|
50
|
+
id: tcltk
|
|
51
|
+
uses: mamba-org/setup-micromamba@v3
|
|
52
|
+
with:
|
|
53
|
+
environment-name: tkblend-tcltk
|
|
54
|
+
create-args: >-
|
|
55
|
+
--channel conda-forge
|
|
56
|
+
python=${{ matrix.python-version }}
|
|
57
|
+
tk=8.6.13
|
|
58
|
+
init-shell: none
|
|
59
|
+
|
|
60
|
+
- name: Install build tools & dependencies
|
|
61
|
+
if: runner.os != 'Windows'
|
|
62
|
+
run: |
|
|
63
|
+
python -m pip install --upgrade pip setuptools wheel
|
|
64
|
+
python -m pip install cmake ninja nanobind scikit-build-core pytest Pillow darkdetect
|
|
65
|
+
|
|
66
|
+
- name: Install Windows build tools & dependencies
|
|
67
|
+
if: runner.os == 'Windows'
|
|
68
|
+
run: |
|
|
69
|
+
micromamba run -n tkblend-tcltk python -m pip install --upgrade pip setuptools wheel
|
|
70
|
+
micromamba run -n tkblend-tcltk python -m pip install cmake ninja nanobind scikit-build-core pytest Pillow darkdetect
|
|
71
|
+
|
|
72
|
+
- name: Verify Windows tkinter
|
|
73
|
+
if: runner.os == 'Windows'
|
|
74
|
+
run: micromamba run -n tkblend-tcltk python -c "import tkinter; print(tkinter.TkVersion)"
|
|
75
|
+
|
|
76
|
+
- name: Build and install tkblend (Windows)
|
|
77
|
+
if: runner.os == 'Windows'
|
|
78
|
+
run: micromamba run -n tkblend-tcltk python -m pip install -e . --no-build-isolation -v
|
|
79
|
+
env:
|
|
80
|
+
CMAKE_PREFIX_PATH: ${{ steps.tcltk.outputs.environment-path }}/Library
|
|
81
|
+
|
|
82
|
+
- name: Build and install tkblend (macOS / Linux)
|
|
83
|
+
if: runner.os != 'Windows'
|
|
84
|
+
run: |
|
|
85
|
+
python -m pip install -e . --no-build-isolation -v
|
|
86
|
+
|
|
87
|
+
- name: Run tests (Linux with Xvfb)
|
|
88
|
+
if: runner.os == 'Linux'
|
|
89
|
+
run: |
|
|
90
|
+
xvfb-run -a pytest -v
|
|
91
|
+
|
|
92
|
+
- name: Run tests (macOS / Windows)
|
|
93
|
+
if: runner.os == 'macOS'
|
|
94
|
+
run: pytest -v
|
|
95
|
+
|
|
96
|
+
- name: Run tests (Windows)
|
|
97
|
+
if: runner.os == 'Windows'
|
|
98
|
+
run: |
|
|
99
|
+
micromamba run -n tkblend-tcltk python -m pytest -v
|
|
100
|
+
|
|
101
|
+
sanitizers:
|
|
102
|
+
name: ASan & UBSan (Linux - Py 3.12)
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
steps:
|
|
105
|
+
- name: Checkout code
|
|
106
|
+
uses: actions/checkout@v4
|
|
107
|
+
with:
|
|
108
|
+
submodules: recursive
|
|
109
|
+
|
|
110
|
+
- name: Set up Python 3.12
|
|
111
|
+
uses: actions/setup-python@v5
|
|
112
|
+
with:
|
|
113
|
+
python-version: "3.12"
|
|
114
|
+
cache: "pip"
|
|
115
|
+
|
|
116
|
+
- name: Install Linux build & display dependencies
|
|
117
|
+
run: |
|
|
118
|
+
sudo apt-get update
|
|
119
|
+
sudo apt-get install -y tcl8.6-dev tk8.6-dev xvfb libx11-dev libasan8
|
|
120
|
+
|
|
121
|
+
- name: Install build tools & test dependencies
|
|
122
|
+
run: |
|
|
123
|
+
python -m pip install --upgrade pip setuptools wheel
|
|
124
|
+
python -m pip install cmake ninja nanobind scikit-build-core pytest Pillow darkdetect
|
|
125
|
+
|
|
126
|
+
- name: Run ASan & UBSan Sanitizer Test Suite
|
|
127
|
+
run: |
|
|
128
|
+
./scripts/run_sanitizers.sh
|
|
129
|
+
|
|
130
|
+
coverage:
|
|
131
|
+
name: Dual Code Coverage (Python + C++)
|
|
132
|
+
runs-on: ubuntu-latest
|
|
133
|
+
steps:
|
|
134
|
+
- name: Checkout code
|
|
135
|
+
uses: actions/checkout@v4
|
|
136
|
+
with:
|
|
137
|
+
submodules: recursive
|
|
138
|
+
|
|
139
|
+
- name: Set up Python 3.12
|
|
140
|
+
uses: actions/setup-python@v5
|
|
141
|
+
with:
|
|
142
|
+
python-version: "3.12"
|
|
143
|
+
cache: "pip"
|
|
144
|
+
|
|
145
|
+
- name: Install Linux build & display dependencies
|
|
146
|
+
run: |
|
|
147
|
+
sudo apt-get update
|
|
148
|
+
sudo apt-get install -y tcl8.6-dev tk8.6-dev xvfb libx11-dev gcovr
|
|
149
|
+
|
|
150
|
+
- name: Install build tools & coverage dependencies
|
|
151
|
+
run: |
|
|
152
|
+
python -m pip install --upgrade pip setuptools wheel
|
|
153
|
+
python -m pip install cmake ninja nanobind scikit-build-core pytest pytest-cov coverage gcovr Pillow darkdetect
|
|
154
|
+
|
|
155
|
+
- name: Run Dual Coverage Suite (Python + C++)
|
|
156
|
+
run: |
|
|
157
|
+
./scripts/run_coverage.sh
|
|
158
|
+
|
|
159
|
+
- name: Upload Python Coverage HTML Report
|
|
160
|
+
uses: actions/upload-artifact@v4
|
|
161
|
+
if: always()
|
|
162
|
+
with:
|
|
163
|
+
name: python-coverage-report
|
|
164
|
+
path: build/coverage/python_html/
|
|
165
|
+
|
|
166
|
+
- name: Upload C++ Coverage HTML Report
|
|
167
|
+
uses: actions/upload-artifact@v4
|
|
168
|
+
if: always()
|
|
169
|
+
with:
|
|
170
|
+
name: cpp-coverage-report
|
|
171
|
+
path: build/coverage/cpp_html/
|
|
172
|
+
|
|
173
|
+
windows_direct_link:
|
|
174
|
+
name: Windows Direct-Link Validation (Py 3.12)
|
|
175
|
+
runs-on: windows-latest
|
|
176
|
+
steps:
|
|
177
|
+
- name: Checkout code
|
|
178
|
+
uses: actions/checkout@v4
|
|
179
|
+
with:
|
|
180
|
+
submodules: recursive
|
|
181
|
+
|
|
182
|
+
- name: Install Windows Tcl/Tk development files
|
|
183
|
+
id: tcltk
|
|
184
|
+
uses: mamba-org/setup-micromamba@v3
|
|
185
|
+
with:
|
|
186
|
+
environment-name: tkblend-tcltk-direct
|
|
187
|
+
create-args: >-
|
|
188
|
+
--channel conda-forge
|
|
189
|
+
python=3.12
|
|
190
|
+
tk=8.6.13
|
|
191
|
+
init-shell: none
|
|
192
|
+
|
|
193
|
+
- name: Install Windows build tools & dependencies
|
|
194
|
+
run: |
|
|
195
|
+
micromamba run -n tkblend-tcltk-direct python -m pip install --upgrade pip setuptools wheel
|
|
196
|
+
micromamba run -n tkblend-tcltk-direct python -m pip install cmake ninja nanobind scikit-build-core pytest Pillow darkdetect
|
|
197
|
+
|
|
198
|
+
- name: Verify Windows tkinter
|
|
199
|
+
run: micromamba run -n tkblend-tcltk-direct python -c "import tkinter; print(tkinter.TkVersion)"
|
|
200
|
+
|
|
201
|
+
- name: Build and install tkblend (Direct-Link mode)
|
|
202
|
+
run: micromamba run -n tkblend-tcltk-direct python -m pip install -e . --no-build-isolation -v -Ccmake.define.TKBLEND_PURE_STUBS=OFF
|
|
203
|
+
env:
|
|
204
|
+
CMAKE_PREFIX_PATH: ${{ steps.tcltk.outputs.environment-path }}/Library
|
|
205
|
+
CMAKE_ARGS: "-DTKBLEND_PURE_STUBS=OFF"
|
|
206
|
+
|
|
207
|
+
- name: Verify PE imports for direct Tk linking
|
|
208
|
+
run: |
|
|
209
|
+
micromamba run -n tkblend-tcltk-direct python -c "
|
|
210
|
+
import glob, struct, sys
|
|
211
|
+
|
|
212
|
+
pyd_files = glob.glob('tkblend/_tkblend*.pyd')
|
|
213
|
+
if not pyd_files:
|
|
214
|
+
print('Error: Could not find _tkblend*.pyd in tkblend/', file=sys.stderr)
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
pyd_path = pyd_files[0]
|
|
218
|
+
print(f'Inspecting PE imports for: {pyd_path}')
|
|
219
|
+
|
|
220
|
+
with open(pyd_path, 'rb') as f:
|
|
221
|
+
data = f.read()
|
|
222
|
+
|
|
223
|
+
e_lfanew = struct.unpack_from('<I', data, 0x3C)[0]
|
|
224
|
+
magic = struct.unpack_from('<H', data, e_lfanew + 24)[0]
|
|
225
|
+
|
|
226
|
+
if magic == 0x20B:
|
|
227
|
+
import_rva, import_size = struct.unpack_from('<II', data, e_lfanew + 24 + 120)
|
|
228
|
+
else:
|
|
229
|
+
import_rva, import_size = struct.unpack_from('<II', data, e_lfanew + 24 + 104)
|
|
230
|
+
|
|
231
|
+
num_sections = struct.unpack_from('<H', data, e_lfanew + 6)[0]
|
|
232
|
+
opt_header_size = struct.unpack_from('<H', data, e_lfanew + 20)[0]
|
|
233
|
+
section_offset = e_lfanew + 24 + opt_header_size
|
|
234
|
+
|
|
235
|
+
def rva_to_offset(rva):
|
|
236
|
+
for i in range(num_sections):
|
|
237
|
+
sec = section_offset + i * 40
|
|
238
|
+
sec_vsize, sec_va, sec_raw_size, sec_raw_offset = struct.unpack_from('<IIII', data, sec + 8)
|
|
239
|
+
if sec_va <= rva < sec_va + max(sec_vsize, sec_raw_size):
|
|
240
|
+
return sec_raw_offset + (rva - sec_va)
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
imports = []
|
|
244
|
+
curr = rva_to_offset(import_rva)
|
|
245
|
+
while curr is not None and curr + 20 <= len(data):
|
|
246
|
+
orig_first_thunk, ts, fchain, name_rva, first_thunk = struct.unpack_from('<IIIII', data, curr)
|
|
247
|
+
if name_rva == 0:
|
|
248
|
+
break
|
|
249
|
+
name_offset = rva_to_offset(name_rva)
|
|
250
|
+
if name_offset:
|
|
251
|
+
name = data[name_offset:data.find(b'\x00', name_offset)].decode('ascii', errors='ignore')
|
|
252
|
+
imports.append(name.lower())
|
|
253
|
+
curr += 20
|
|
254
|
+
|
|
255
|
+
print(f'Imported DLLs: {imports}')
|
|
256
|
+
assert any('tk86' in imp or 'tk' in imp for imp in imports), f'Expected direct link to Tk DLL in PE imports, found: {imports}'
|
|
257
|
+
print('Verified: Direct link to Tk is active in PE imports.')
|
|
258
|
+
"
|
|
259
|
+
|
|
260
|
+
- name: Run tests (Windows Direct-Link)
|
|
261
|
+
run: |
|
|
262
|
+
micromamba run -n tkblend-tcltk-direct python -m pytest -v -m "not slow"
|
|
263
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build_sdist:
|
|
14
|
+
name: Build Source Distribution (sdist)
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout code
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
submodules: recursive
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
|
|
27
|
+
- name: Install build
|
|
28
|
+
run: python -m pip install --upgrade build
|
|
29
|
+
|
|
30
|
+
- name: Build sdist
|
|
31
|
+
run: python -m build --sdist
|
|
32
|
+
|
|
33
|
+
- name: Upload sdist artifact
|
|
34
|
+
uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist-sdist
|
|
37
|
+
path: dist/*.tar.gz
|
|
38
|
+
|
|
39
|
+
build_wheels:
|
|
40
|
+
name: Build Wheels (${{ matrix.os }} - ${{ matrix.cibw_archs }})
|
|
41
|
+
runs-on: ${{ matrix.os }}
|
|
42
|
+
strategy:
|
|
43
|
+
fail-fast: false
|
|
44
|
+
matrix:
|
|
45
|
+
include:
|
|
46
|
+
- os: ubuntu-latest
|
|
47
|
+
cibw_archs: "x86_64"
|
|
48
|
+
- os: ubuntu-latest
|
|
49
|
+
cibw_archs: "aarch64"
|
|
50
|
+
use_qemu: true
|
|
51
|
+
- os: macos-15-intel
|
|
52
|
+
cibw_archs: "x86_64"
|
|
53
|
+
- os: macos-14
|
|
54
|
+
cibw_archs: "arm64"
|
|
55
|
+
- os: windows-latest
|
|
56
|
+
cibw_archs: "AMD64"
|
|
57
|
+
|
|
58
|
+
steps:
|
|
59
|
+
- name: Checkout code
|
|
60
|
+
uses: actions/checkout@v4
|
|
61
|
+
with:
|
|
62
|
+
submodules: recursive
|
|
63
|
+
|
|
64
|
+
- name: Set up QEMU
|
|
65
|
+
if: matrix.use_qemu
|
|
66
|
+
uses: docker/setup-qemu-action@v3
|
|
67
|
+
with:
|
|
68
|
+
platforms: arm64
|
|
69
|
+
|
|
70
|
+
- name: Set up Python
|
|
71
|
+
uses: actions/setup-python@v5
|
|
72
|
+
with:
|
|
73
|
+
python-version: "3.12"
|
|
74
|
+
|
|
75
|
+
- name: Install macOS Tcl/Tk development files
|
|
76
|
+
if: runner.os == 'macOS'
|
|
77
|
+
run: |
|
|
78
|
+
brew install tcl-tk
|
|
79
|
+
echo "CMAKE_PREFIX_PATH=$(brew --prefix tcl-tk)" >> $GITHUB_ENV
|
|
80
|
+
|
|
81
|
+
- name: Install Windows Tcl/Tk development files
|
|
82
|
+
if: runner.os == 'Windows'
|
|
83
|
+
id: tcltk
|
|
84
|
+
uses: mamba-org/setup-micromamba@v3
|
|
85
|
+
with:
|
|
86
|
+
environment-name: tkblend-tcltk
|
|
87
|
+
create-args: >-
|
|
88
|
+
--channel conda-forge
|
|
89
|
+
tk=8.6.13
|
|
90
|
+
init-shell: none
|
|
91
|
+
|
|
92
|
+
- name: Set Windows CMake Prefix Path
|
|
93
|
+
if: runner.os == 'Windows'
|
|
94
|
+
run: |
|
|
95
|
+
echo "CMAKE_PREFIX_PATH=${{ steps.tcltk.outputs.environment-path }}/Library" >> $GITHUB_ENV
|
|
96
|
+
|
|
97
|
+
- name: Install cibuildwheel
|
|
98
|
+
run: python -m pip install cibuildwheel==2.22.0 delvewheel
|
|
99
|
+
|
|
100
|
+
- name: Build wheels via cibuildwheel
|
|
101
|
+
run: python -m cibuildwheel --output-dir wheelhouse
|
|
102
|
+
env:
|
|
103
|
+
CIBW_ARCHS: ${{ matrix.cibw_archs }}
|
|
104
|
+
CMAKE_PREFIX_PATH: ${{ steps.tcltk.outputs.environment-path && format('{0}/Library', steps.tcltk.outputs.environment-path) || '' }}
|
|
105
|
+
CIBW_ENVIRONMENT_WINDOWS: "CMAKE_PREFIX_PATH='${{ steps.tcltk.outputs.environment-path }}/Library'"
|
|
106
|
+
|
|
107
|
+
- name: Upload wheel artifacts
|
|
108
|
+
uses: actions/upload-artifact@v4
|
|
109
|
+
with:
|
|
110
|
+
name: dist-wheels-${{ matrix.os }}-${{ matrix.cibw_archs }}
|
|
111
|
+
path: wheelhouse/*.whl
|
|
112
|
+
|
|
113
|
+
github_release:
|
|
114
|
+
name: Create GitHub Release
|
|
115
|
+
needs: [build_sdist, build_wheels]
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
|
|
118
|
+
steps:
|
|
119
|
+
- name: Checkout code
|
|
120
|
+
uses: actions/checkout@v4
|
|
121
|
+
|
|
122
|
+
- name: Download all artifacts
|
|
123
|
+
uses: actions/download-artifact@v4
|
|
124
|
+
with:
|
|
125
|
+
path: dist
|
|
126
|
+
pattern: dist-*
|
|
127
|
+
merge-multiple: true
|
|
128
|
+
|
|
129
|
+
- name: List distribution files
|
|
130
|
+
run: |
|
|
131
|
+
ls -lh dist/
|
|
132
|
+
|
|
133
|
+
- name: Create GitHub Release
|
|
134
|
+
uses: softprops/action-gh-release@v2
|
|
135
|
+
with:
|
|
136
|
+
files: dist/*
|
|
137
|
+
generate_release_notes: true
|
|
138
|
+
draft: false
|
|
139
|
+
prerelease: ${{ contains(github.ref, 'b') || contains(github.ref, 'beta') || contains(github.ref, 'rc') || contains(github.ref, 'alpha') }}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Python bytecode and caches
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.pyd
|
|
7
|
+
*.dll
|
|
8
|
+
*.dylib
|
|
9
|
+
|
|
10
|
+
# Build directories and eggs
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
.eggs/
|
|
15
|
+
_skbuild/
|
|
16
|
+
CMakeLocalConfig.cmake
|
|
17
|
+
|
|
18
|
+
# Virtual environments
|
|
19
|
+
.venv/
|
|
20
|
+
env/
|
|
21
|
+
venv/
|
|
22
|
+
ENV/
|
|
23
|
+
|
|
24
|
+
# Testing and coverage
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
.coverage.*
|
|
28
|
+
htmlcov/
|
|
29
|
+
coverage.xml
|
|
30
|
+
*.gcda
|
|
31
|
+
*.gcno
|
|
32
|
+
*.gcov
|
|
33
|
+
|
|
34
|
+
# IDE and OS files
|
|
35
|
+
.vscode/
|
|
36
|
+
.idea/
|
|
37
|
+
.DS_Store
|
|
38
|
+
Thumbs.db
|
|
39
|
+
REFERENCE ONLY/
|
|
40
|
+
tests/visual_baselines/
|
|
41
|
+
CustomTkinter-6.0.0/
|
|
42
|
+
snapshots/
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
*.whl
|
|
46
|
+
*.tar.*
|
|
47
|
+
log/
|
|
48
|
+
artifacts/
|
|
49
|
+
screenshots/
|